1 /* xscreensaver, Copyright (c) 1999-2005 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Phosphor -- simulate a glass tty with long-sustain phosphor.
12 * Written by Jamie Zawinski <jwz@jwz.org>
13 * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
16 #include "screenhack.h"
22 #include <X11/Xutil.h>
23 #include <X11/Xatom.h>
24 #include <X11/Intrinsic.h>
27 #include <X11/keysymdef.h>
30 # include <sys/ioctl.h>
37 #endif /* HAVE_FORKPTY */
39 extern XtAppContext app;
43 #define MAX(a,b) ((a)>(b)?(a):(b))
44 #define MIN(a,b) ((a)<(b)?(a):(b))
50 #define STATE_MAX FADE
52 #define CURSOR_INDEX 128
62 #endif /* FUZZY_BORDER */
75 XWindowAttributes xgwa;
77 int grid_width, grid_height;
78 int char_width, char_height;
94 #endif /* FUZZY_BORDER */
98 int cursor_x, cursor_y;
99 XtIntervalId cursor_timer;
104 Bool input_available_p;
105 Time subproc_relaunch_delay;
106 XComposeStatus compose;
107 Bool meta_sends_esc_p;
113 static void capture_font_bits (p_state *state);
114 static p_char *make_character (p_state *state, int c);
115 static void drain_input (p_state *state);
116 static void char_to_pixmap (p_state *state, p_char *pc, int c);
117 static void launch_text_generator (p_state *state);
120 /* About font metrics:
122 "lbearing" is the distance from the leftmost pixel of a character to
123 the logical origin of that character. That is, it is the number of
124 pixels of the character which are to the left of its logical origin.
126 "rbearing" is the distance from the logical origin of a character to
127 the rightmost pixel of a character. That is, it is the number of
128 pixels of the character to the right of its logical origin.
130 "descent" is the distance from the bottommost pixel of a character to
131 the logical baseline. That is, it is the number of pixels of the
132 character which are below the baseline.
134 "ascent" is the distance from the logical baseline to the topmost pixel.
135 That is, it is the number of pixels of the character above the baseline.
137 Therefore, the bounding box of the "ink" of a character is
138 lbearing + rbearing by ascent + descent;
140 "width" is the distance from the logical origin of this character to
141 the position where the logical orgin of the next character should be
144 For our purposes, we're only interested in the part of the character
145 lying inside the "width" box. If the characters have ink outside of
146 that box (the "charcell" box) then we're going to lose it. Alas.
150 init_phosphor (Display *dpy, Window window)
154 p_state *state = (p_state *) calloc (sizeof(*state), 1);
155 char *fontname = get_string_resource ("font", "Font");
159 state->window = window;
161 XGetWindowAttributes (dpy, window, &state->xgwa);
162 XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);
164 state->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
165 state->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
167 state->font = XLoadQueryFont (dpy, fontname);
171 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
172 state->font = XLoadQueryFont (dpy, "fixed");
176 fprintf(stderr, "couldn't load font \"fixed\"");
181 state->scale = get_integer_resource ("scale", "Integer");
182 state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
186 char *s = get_string_resource ("mode", "Integer");
188 if (!s || !*s || !strcasecmp (s, "pipe"))
190 else if (!strcasecmp (s, "pty"))
193 fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
197 fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
200 #endif /* HAVE_FORKPTY */
204 for (i = 0; i < font->n_properties; i++)
205 if (font->properties[i].name == XA_FONT)
206 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
209 state->cursor_blink = get_integer_resource ("cursor", "Time");
210 state->subproc_relaunch_delay =
211 (1000 * get_integer_resource ("relaunch", "Time"));
213 state->char_width = font->max_bounds.width;
214 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
216 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
217 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
218 state->cells = (p_cell *) calloc (sizeof(p_cell),
219 state->grid_width * state->grid_height);
220 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
222 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
225 int ncolors = MAX (0, state->ticks - 3);
226 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
228 double s1, s2, v1, v2;
230 unsigned long fg = get_pixel_resource ("foreground", "Foreground",
231 state->dpy, state->xgwa.colormap);
232 unsigned long bg = get_pixel_resource ("background", "Background",
233 state->dpy, state->xgwa.colormap);
234 unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
235 state->dpy,state->xgwa.colormap);
236 unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
237 state->dpy,state->xgwa.colormap);
242 XQueryColor (state->dpy, state->xgwa.colormap, &start);
245 XQueryColor (state->dpy, state->xgwa.colormap, &end);
247 /* Now allocate a ramp of colors from the main color to the background. */
248 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
249 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
250 make_color_ramp (state->dpy, state->xgwa.colormap,
256 /* Adjust to the number of colors we actually got. */
257 state->ticks = ncolors + STATE_MAX;
259 /* Now, GCs all around.
261 state->gcv.font = font->fid;
262 state->gcv.cap_style = CapRound;
264 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
265 if (state->gcv.line_width == state->scale)
266 state->gcv.line_width++;
267 #else /* !FUZZY_BORDER */
268 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
269 if (state->gcv.line_width >= state->scale)
270 state->gcv.line_width = state->scale - 1;
271 if (state->gcv.line_width < 1)
272 state->gcv.line_width = 1;
273 #endif /* !FUZZY_BORDER */
275 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
277 state->gcv.background = bg;
278 state->gcv.foreground = bg;
279 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
282 state->gcv.foreground = flare;
283 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
286 state->gcv.foreground = fg;
287 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
290 for (i = 0; i < ncolors; i++)
292 state->gcv.foreground = colors[i].pixel;
293 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
298 capture_font_bits (state);
300 launch_text_generator (state);
306 /* Re-query the window size and update the internal character grid if changed.
309 resize_grid (p_state *state)
311 int ow = state->grid_width;
312 int oh = state->grid_height;
313 p_cell *ocells = state->cells;
316 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
318 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
319 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
321 if (ow == state->grid_width &&
322 oh == state->grid_height)
325 state->cells = (p_cell *) calloc (sizeof(p_cell),
326 state->grid_width * state->grid_height);
328 for (y = 0; y < state->grid_height; y++)
330 for (x = 0; x < state->grid_width; x++)
332 p_cell *ncell = &state->cells [state->grid_width * y + x];
333 if (x < ow && y < oh)
334 *ncell = ocells [ow * y + x];
335 ncell->changed = True;
339 if (state->cursor_x >= state->grid_width)
340 state->cursor_x = state->grid_width-1;
341 if (state->cursor_y >= state->grid_height)
342 state->cursor_y = state->grid_height-1;
349 capture_font_bits (p_state *state)
351 XFontStruct *font = state->font;
352 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
353 int height = state->char_height;
354 unsigned char string[257];
356 Pixmap p = XCreatePixmap (state->dpy, state->window,
357 (safe_width * 256), height, 1);
359 for (i = 0; i < 256; i++)
360 string[i] = (unsigned char) i;
363 state->gcv.foreground = 0;
364 state->gcv.background = 0;
365 state->gc0 = XCreateGC (state->dpy, p,
366 (GCForeground | GCBackground),
369 state->gcv.foreground = 1;
370 state->gc1 = XCreateGC (state->dpy, p,
371 (GCFont | GCForeground | GCBackground |
372 GCCapStyle | GCLineWidth),
377 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
378 if (state->gcv.line_width >= state->scale)
379 state->gcv.line_width = state->scale - 1;
380 if (state->gcv.line_width < 1)
381 state->gcv.line_width = 1;
382 state->gc2 = XCreateGC (state->dpy, p,
383 (GCFont | GCForeground | GCBackground |
384 GCCapStyle | GCLineWidth),
387 #endif /* FUZZY_BORDER */
389 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
391 for (i = 0; i < 256; i++)
393 if (string[i] < font->min_char_or_byte2 ||
394 string[i] > font->max_char_or_byte2)
396 XDrawString (state->dpy, p, state->gc1,
397 i * safe_width, font->ascent,
398 (char *) (string + i), 1);
401 /* Draw the cursor. */
402 XFillRectangle (state->dpy, p, state->gc1,
403 (CURSOR_INDEX * safe_width), 1,
405 ? font->per_char['n'-font->min_char_or_byte2].width
406 : font->max_bounds.width),
410 XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
411 0, 0, (safe_width * 256), height, 0, 0, 1L);
412 XSync(state->dpy, False);
415 XSync (state->dpy, False);
416 state->font_bits = XGetImage (state->dpy, p, 0, 0,
417 (safe_width * 256), height, ~0L, XYPixmap);
418 XFreePixmap (state->dpy, p);
420 for (i = 0; i < 256; i++)
421 state->chars[i] = make_character (state, i);
422 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
427 make_character (p_state *state, int c)
429 p_char *pc = (p_char *) malloc (sizeof (*pc));
430 pc->name = (unsigned char) c;
431 pc->width = state->scale * state->char_width;
432 pc->height = state->scale * state->char_height;
433 char_to_pixmap (state, pc, c);
439 char_to_pixmap (p_state *state, p_char *pc, int c)
446 #endif /* FUZZY_BORDER */
450 XFontStruct *font = state->font;
451 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
453 int width = state->scale * state->char_width;
454 int height = state->scale * state->char_height;
456 if (c < font->min_char_or_byte2 ||
457 c > font->max_char_or_byte2)
461 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
462 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
465 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
466 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
467 #endif /* FUZZY_BORDER */
469 from = safe_width * c;
470 to = safe_width * (c + 1);
473 if (c > 75 && c < 150)
475 printf ("\n=========== %d (%c)\n", c, c);
476 for (y = 0; y < state->char_height; y++)
478 for (x1 = from; x1 < to; x1++)
479 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
486 for (y = 0; y < state->char_height; y++)
487 for (x1 = from; x1 < to; x1++)
488 if (XGetPixel (state->font_bits, x1, y))
490 int xoff = state->scale / 2;
492 for (x2 = x1; x2 < to; x2++)
493 if (!XGetPixel (state->font_bits, x2, y))
496 XDrawLine (state->dpy, p, gc,
497 (x1 - from) * state->scale + xoff, y * state->scale,
498 (x2 - from) * state->scale + xoff, y * state->scale);
500 XDrawLine (state->dpy, p2, gc2,
501 (x1 - from) * state->scale + xoff, y * state->scale,
502 (x2 - from) * state->scale + xoff, y * state->scale);
503 #endif /* FUZZY_BORDER */
508 /* if (pc->blank_p && c == CURSOR_INDEX)
515 #endif /* FUZZY_BORDER */
519 /* Managing the display.
522 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
523 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
526 set_cursor_1 (p_state *state, Bool on)
528 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
530 p_char *cursor = state->chars[CURSOR_INDEX];
531 int new_state = (on ? NORMAL : FADE);
533 if (cell->p_char != cursor)
534 cell->changed = True;
536 if (cell->state != new_state)
537 cell->changed = True;
539 cell->p_char = cursor;
540 cell->state = new_state;
541 return cell->changed;
545 set_cursor (p_state *state, Bool on)
547 if (set_cursor_1 (state, on))
549 if (state->cursor_timer)
550 XtRemoveTimeOut (state->cursor_timer);
551 state->cursor_timer = 0;
552 cursor_on_timer (state, 0);
560 cursor_off_timer (XtPointer closure, XtIntervalId *id)
562 p_state *state = (p_state *) closure;
563 set_cursor_1 (state, False);
564 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
565 cursor_on_timer, closure);
569 cursor_on_timer (XtPointer closure, XtIntervalId *id)
571 p_state *state = (p_state *) closure;
572 set_cursor_1 (state, True);
573 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
574 cursor_off_timer, closure);
579 clear (p_state *state)
584 for (y = 0; y < state->grid_height; y++)
585 for (x = 0; x < state->grid_width; x++)
587 p_cell *cell = &state->cells[state->grid_width * y + x];
588 if (cell->state == FLARE || cell->state == NORMAL)
591 cell->changed = True;
594 set_cursor (state, True);
599 decay (p_state *state)
602 for (y = 0; y < state->grid_height; y++)
603 for (x = 0; x < state->grid_width; x++)
605 p_cell *cell = &state->cells[state->grid_width * y + x];
606 if (cell->state == FLARE)
608 cell->state = NORMAL;
609 cell->changed = True;
611 else if (cell->state >= FADE)
614 if (cell->state >= state->ticks)
616 cell->changed = True;
623 scroll (p_state *state)
627 for (x = 0; x < state->grid_width; x++)
629 p_cell *from = 0, *to = 0;
630 for (y = 1; y < state->grid_height; y++)
632 from = &state->cells[state->grid_width * y + x];
633 to = &state->cells[state->grid_width * (y-1) + x];
635 if ((from->state == FLARE || from->state == NORMAL) &&
636 !from->p_char->blank_p)
639 to->state = NORMAL; /* should be FLARE? Looks bad... */
643 if (to->state == FLARE || to->state == NORMAL)
651 if (to && (to->state == FLARE || to->state == NORMAL))
657 set_cursor (state, True);
662 print_char (p_state *state, int c)
664 static char last_c = 0;
667 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
671 /* Start the cursor fading (in case we don't end up overwriting it.) */
672 if (cell->state == FLARE || cell->state == NORMAL)
675 cell->changed = True;
678 if (state->pid) /* Only interpret VT100 sequences if running in pty-mode.
679 It would be nice if we could just interpret them all
680 the time, but that would require subprocesses to send
681 CRLF line endings instead of bare LF, so that's no good.
684 switch (state->escstate)
690 /* Dummy case - we don't want the screensaver to beep */
691 /* #### But maybe this should flash the screen? */
694 if (state->cursor_x > 0)
698 if (state->cursor_x < state->grid_width - 8)
700 state->cursor_x = (state->cursor_x & ~7) + 8;
705 if (state->cursor_y < state->grid_height - 1)
716 cell->state = NORMAL;
717 cell->p_char = state->chars[bk];
718 cell->changed = True;
720 if (state->cursor_y < state->grid_height - 1)
727 cell = &state->cells[state->grid_width * state->cursor_y];
728 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
731 bk = cell->p_char->name;
735 /* Dummy case - I don't want to load several fonts for
736 the maybe two programs world-wide that use that */
740 /* Dummy case - these interrupt escape sequences, so
741 they don't do anything in this state */
747 /* Dummy case - this is supposed to be ignored */
751 for(i = 0; i < NPAR; i++)
752 state->csiparam[i] = 0;
757 cell->p_char = state->chars[c];
758 cell->changed = True;
761 if (c != ' ' && cell->p_char->blank_p)
762 cell->p_char = state->chars[CURSOR_INDEX];
764 if (state->cursor_x >= state->grid_width - 1)
767 if (state->cursor_y >= state->grid_height - 1)
782 case 'c': /* Reset */
786 case 'D': /* Linefeed */
787 if (state->cursor_y < state->grid_height - 1)
793 case 'E': /* Newline */
797 case 'M': /* Reverse newline */
798 if (state->cursor_y > 0)
802 case '7': /* Save state */
803 state->saved_x = state->cursor_x;
804 state->saved_y = state->cursor_y;
807 case '8': /* Restore state */
808 state->cursor_x = state->saved_x;
809 state->cursor_y = state->saved_y;
814 for(i = 0; i < NPAR; i++)
815 state->csiparam[i] = 0;
818 case '%': /* Select charset */
819 /* No, I don't support UTF-8, since the phosphor font
820 isn't even Unicode anyway. We must still catch the
821 last byte, though. */
824 /* I don't support different fonts either - see above
829 /* Escape sequences not supported:
832 * Z - Terminal identification
834 * = - Other keypad change
848 case '0': case '1': case '2': case '3': case '4':
849 case '5': case '6': case '7': case '8': case '9':
850 if (state->curparam < NPAR)
851 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
854 state->csiparam[++state->curparam] = 0;
860 for (i = 0; i < state->csiparam[0]; i++)
862 if(++state->cursor_x > state->grid_width)
865 if (state->cursor_y < state->grid_height - 1)
870 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
871 if (cell->state == FLARE || cell->state == NORMAL)
874 cell->changed = True;
882 if (state->csiparam[0] == 0)
883 state->csiparam[0] = 1;
884 if ((state->cursor_y -= state->csiparam[0]) < 0)
892 if (state->csiparam[0] == 0)
893 state->csiparam[0] = 1;
894 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
895 state->cursor_y = state->grid_height - 1;
900 if (state->csiparam[0] == 0)
901 state->csiparam[0] = 1;
902 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
903 state->cursor_x = state->grid_width - 1;
907 if (state->csiparam[0] == 0)
908 state->csiparam[0] = 1;
909 if ((state->cursor_x -= state->csiparam[0]) < 0)
914 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
915 state->cursor_y = state->grid_height - 1;
920 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
921 state->cursor_x = state->grid_width - 1;
926 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
927 state->cursor_y = state->grid_height - 1;
928 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
929 state->cursor_x = state->grid_width - 1;
930 if(state->cursor_y < 0)
932 if(state->cursor_x < 0)
938 end = state->grid_height * state->grid_width;
939 if (state->csiparam[0] == 0)
940 start = state->grid_width * state->cursor_y + state->cursor_x;
941 if (state->csiparam[0] == 1)
942 end = state->grid_width * state->cursor_y + state->cursor_x;
943 for (i = start; i < end; i++)
945 cell = &state->cells[i];
946 if (cell->state == FLARE || cell->state == NORMAL)
949 cell->changed = True;
952 set_cursor (state, True);
957 end = state->grid_width;
958 if (state->csiparam[0] == 0)
959 start = state->cursor_x;
960 if (state->csiparam[1] == 1)
961 end = state->cursor_x;
962 for (i = start; i < end; i++)
964 if (cell->state == FLARE || cell->state == NORMAL)
967 cell->changed = True;
973 case 's': /* Save position */
974 state->saved_x = state->cursor_x;
975 state->saved_y = state->cursor_y;
978 case 'u': /* Restore position */
979 state->cursor_x = state->saved_x;
980 state->cursor_y = state->saved_y;
983 case '?': /* DEC Private modes */
984 if ((state->curparam != 0) || (state->csiparam[0] != 0))
988 /* Known unsupported CSIs:
990 * L - Insert blank lines
991 * M - Delete lines (I don't know what this means...)
992 * P - Delete characters
993 * X - Erase characters (difference with P being...?)
994 * c - Terminal identification
995 * g - Clear tab stop(s)
996 * h - Set mode (Mainly due to its complexity and lack of good
999 * m - Set mode (Phosphor is, per defenition, green on black)
1001 * q - Set keyboard LEDs
1002 * r - Set scrolling region (too exhausting - noone uses this,
1005 state->escstate = 0;
1010 state->escstate = 0;
1013 set_cursor (state, True);
1017 if (c == '\t') c = ' '; /* blah. */
1019 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1021 if (c == '\n' && last_c == '\r')
1022 ; /* CRLF -- do nothing */
1025 state->cursor_x = 0;
1026 if (state->cursor_y == state->grid_height - 1)
1032 else if (c == '\014')
1038 cell->state = FLARE;
1039 cell->p_char = state->chars[c];
1040 cell->changed = True;
1043 if (c != ' ' && cell->p_char->blank_p)
1044 cell->p_char = state->chars[CURSOR_INDEX];
1046 if (state->cursor_x >= state->grid_width - 1)
1048 state->cursor_x = 0;
1049 if (state->cursor_y >= state->grid_height - 1)
1055 set_cursor (state, True);
1063 update_display (p_state *state, Bool changed_only)
1067 for (y = 0; y < state->grid_height; y++)
1068 for (x = 0; x < state->grid_width; x++)
1070 p_cell *cell = &state->cells[state->grid_width * y + x];
1071 int width, height, tx, ty;
1073 if (changed_only && !cell->changed)
1076 width = state->char_width * state->scale;
1077 height = state->char_height * state->scale;
1081 if (cell->state == BLANK || cell->p_char->blank_p)
1083 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1084 tx, ty, width, height);
1089 GC gc1 = state->gcs[cell->state];
1090 GC gc2 = ((cell->state + 2) < state->ticks
1091 ? state->gcs[cell->state + 2]
1093 GC gc3 = (gc2 ? gc2 : gc1);
1095 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1096 0, 0, width, height, tx, ty, 1L);
1099 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1100 XSetClipOrigin (state->dpy, gc1, tx, ty);
1101 XFillRectangle (state->dpy, state->window, gc1,
1102 tx, ty, width, height);
1103 XSetClipMask (state->dpy, gc1, None);
1105 #else /* !FUZZY_BORDER */
1107 XCopyPlane (state->dpy,
1108 cell->p_char->pixmap, state->window,
1109 state->gcs[cell->state],
1110 0, 0, width, height, tx, ty, 1L);
1112 #endif /* !FUZZY_BORDER */
1115 cell->changed = False;
1121 run_phosphor (p_state *state)
1123 update_display (state, True);
1125 drain_input (state);
1133 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1135 p_state *state = (p_state *) closure;
1136 state->input_available_p = True;
1141 launch_text_generator (p_state *state)
1144 char *oprogram = get_string_resource ("program", "Program");
1145 char *program = (char *) malloc (strlen (oprogram) + 50);
1147 strcpy (program, "( ");
1148 strcat (program, oprogram);
1150 /* Kludge! Special-case "xscreensaver-text" to tell it how wide
1151 the screen is. We used to do this by just always feeding
1152 `program' through sprintf() and setting the default value to
1153 "xscreensaver-text --cols %d", but that makes things blow up
1154 if someone ever uses a --program that includes a % anywhere.
1156 if (!strcmp (oprogram, "xscreensaver-text"))
1157 sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
1159 strcat (program, " ) 2>&1");
1162 if(state->mode == 1)
1167 ws.ws_row = state->grid_height - 1;
1168 ws.ws_col = state->grid_width - 2;
1169 ws.ws_xpixel = state->xgwa.width;
1170 ws.ws_ypixel = state->xgwa.height;
1173 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1175 /* Unable to fork */
1176 sprintf (buf, "%.100s: forkpty", progname);
1179 else if(!state->pid)
1181 /* This is the child fork. */
1184 if (putenv("TERM=vt100"))
1186 av[i++] = "/bin/sh";
1191 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1197 /* This is the parent fork. */
1198 state->pipe = fdopen(fd, "r+");
1200 XtAppAddInput (app, fileno (state->pipe),
1201 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1202 subproc_cb, (XtPointer) state);
1206 #endif /* HAVE_FORKPTY */
1208 /* don't mess up controlling terminal if someone dumbly does
1209 "-pipe -program tcsh". */
1212 if ((state->pipe = popen (program, "r")))
1215 XtAppAddInput (app, fileno (state->pipe),
1216 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1217 subproc_cb, (XtPointer) state);
1221 sprintf (buf, "%.100s: %.100s", progname, program);
1231 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1233 p_state *state = (p_state *) closure;
1234 launch_text_generator (state);
1239 drain_input (p_state *state)
1241 if (state->input_available_p)
1244 int n = read (fileno (state->pipe), (void *) s, 1);
1247 print_char (state, s[0]);
1251 XtRemoveInput (state->pipe_id);
1255 waitpid(state->pid, NULL, 0);
1256 fclose (state->pipe);
1260 pclose (state->pipe);
1264 if (state->cursor_x != 0) /* break line if unbroken */
1265 print_char (state, '\n'); /* blank line */
1266 print_char (state, '\n');
1268 /* Set up a timer to re-launch the subproc in a bit. */
1269 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1270 relaunch_generator_timer,
1274 state->input_available_p = False;
1279 /* The interpretation of the ModN modifiers is dependent on what keys
1280 are bound to them: Mod1 does not necessarily mean "meta". It only
1281 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1282 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1283 aren't necessarily the same thing. Icepicks in my forehead!
1286 do_icccm_meta_key_stupidity (Display *dpy)
1288 unsigned int modbits = 0;
1290 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1291 for (i = 3; i < 8; i++)
1292 for (j = 0; j < modmap->max_keypermod; j++)
1294 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1297 if (code == 0) continue;
1298 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1299 for (k = 0; k < nsyms; k++)
1300 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1301 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1302 modbits |= (1 << i);
1305 XFreeModifiermap (modmap);
1309 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1312 meta_modifier (Display *dpy)
1314 static Bool done_once = False;
1315 static unsigned int mask = 0;
1318 /* Really, we are supposed to recompute this if a KeymapNotify
1319 event comes in, but fuck it. */
1321 mask = do_icccm_meta_key_stupidity (dpy);
1328 handle_events (p_state *state)
1330 XSync (state->dpy, False);
1331 while (XPending (state->dpy))
1334 XNextEvent (state->dpy, &event);
1336 if (event.xany.type == ConfigureNotify)
1338 resize_grid (state);
1340 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1343 /* Tell the sub-process that the screen size has changed. */
1345 ws.ws_row = state->grid_height - 1;
1346 ws.ws_col = state->grid_width - 2;
1347 ws.ws_xpixel = state->xgwa.width;
1348 ws.ws_ypixel = state->xgwa.height;
1349 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1350 kill (state->pid, SIGWINCH);
1352 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1354 else if (event.xany.type == Expose)
1356 update_display (state, False);
1358 else if (event.xany.type == KeyPress)
1361 unsigned char c = 0;
1362 XLookupString (&event.xkey, (char *) &c, 1, &keysym,
1364 if (c != 0 && state->pipe)
1366 if (!state->swap_bs_del_p) ;
1367 else if (c == 127) c = 8;
1368 else if (c == 8) c = 127;
1370 /* If meta was held down, send ESC, or turn on the high bit. */
1371 if (event.xkey.state & meta_modifier (state->dpy))
1373 if (state->meta_sends_esc_p)
1374 fputc ('\033', state->pipe);
1379 fputc (c, state->pipe);
1380 fflush (state->pipe);
1381 event.xany.type = 0; /* don't interpret this event defaultly. */
1385 screenhack_handle_event (state->dpy, &event);
1388 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1389 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1394 char *progclass = "Phosphor";
1396 char *defaults [] = {
1397 ".background: Black",
1398 ".foreground: Green",
1399 "*fadeForeground: DarkGreen",
1400 "*flareForeground: White",
1406 "*program: xscreensaver-text",
1408 "*metaSendsESC: True",
1412 #else /* !HAVE_FORKPTY */
1414 #endif /* !HAVE_FORKPTY */
1418 XrmOptionDescRec options [] = {
1419 { "-font", ".font", XrmoptionSepArg, 0 },
1420 { "-scale", ".scale", XrmoptionSepArg, 0 },
1421 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1422 { "-delay", ".delay", XrmoptionSepArg, 0 },
1423 { "-program", ".program", XrmoptionSepArg, 0 },
1424 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1425 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1426 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1427 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1428 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1429 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1435 screenhack (Display *dpy, Window window)
1437 int delay = get_integer_resource ("delay", "Integer");
1438 p_state *state = init_phosphor (dpy, window);
1444 run_phosphor (state);
1446 handle_events (state);
1447 if (delay) usleep (delay);