1 /* xscreensaver, Copyright (c) 1999, 2000, 2004 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>
36 #endif /* HAVE_FORKPTY */
38 extern XtAppContext app;
42 #define MAX(a,b) ((a)>(b)?(a):(b))
43 #define MIN(a,b) ((a)<(b)?(a):(b))
49 #define STATE_MAX FADE
51 #define CURSOR_INDEX 128
61 #endif /* FUZZY_BORDER */
74 XWindowAttributes xgwa;
76 int grid_width, grid_height;
77 int char_width, char_height;
93 #endif /* FUZZY_BORDER */
97 int cursor_x, cursor_y;
98 XtIntervalId cursor_timer;
103 Bool input_available_p;
104 Time subproc_relaunch_delay;
105 XComposeStatus compose;
106 Bool meta_sends_esc_p;
112 static void capture_font_bits (p_state *state);
113 static p_char *make_character (p_state *state, int c);
114 static void drain_input (p_state *state);
115 static void char_to_pixmap (p_state *state, p_char *pc, int c);
116 static void launch_text_generator (p_state *state);
119 /* About font metrics:
121 "lbearing" is the distance from the leftmost pixel of a character to
122 the logical origin of that character. That is, it is the number of
123 pixels of the character which are to the left of its logical origin.
125 "rbearing" is the distance from the logical origin of a character to
126 the rightmost pixel of a character. That is, it is the number of
127 pixels of the character to the right of its logical origin.
129 "descent" is the distance from the bottommost pixel of a character to
130 the logical baseline. That is, it is the number of pixels of the
131 character which are below the baseline.
133 "ascent" is the distance from the logical baseline to the topmost pixel.
134 That is, it is the number of pixels of the character above the baseline.
136 Therefore, the bounding box of the "ink" of a character is
137 lbearing + rbearing by ascent + descent;
139 "width" is the distance from the logical origin of this character to
140 the position where the logical orgin of the next character should be
143 For our purposes, we're only interested in the part of the character
144 lying inside the "width" box. If the characters have ink outside of
145 that box (the "charcell" box) then we're going to lose it. Alas.
149 init_phosphor (Display *dpy, Window window)
153 p_state *state = (p_state *) calloc (sizeof(*state), 1);
154 char *fontname = get_string_resource ("font", "Font");
158 state->window = window;
160 XGetWindowAttributes (dpy, window, &state->xgwa);
161 XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);
163 state->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
164 state->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
166 state->font = XLoadQueryFont (dpy, fontname);
170 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
171 state->font = XLoadQueryFont (dpy, "fixed");
175 fprintf(stderr, "couldn't load font \"fixed\"");
180 state->scale = get_integer_resource ("scale", "Integer");
181 state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
185 char *s = get_string_resource ("mode", "Integer");
187 if (!s || !*s || !strcasecmp (s, "pipe"))
189 else if (!strcasecmp (s, "pty"))
192 fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
196 fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
199 #endif /* HAVE_FORKPTY */
203 for (i = 0; i < font->n_properties; i++)
204 if (font->properties[i].name == XA_FONT)
205 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
208 state->cursor_blink = get_integer_resource ("cursor", "Time");
209 state->subproc_relaunch_delay =
210 (1000 * get_integer_resource ("relaunch", "Time"));
212 state->char_width = font->max_bounds.width;
213 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
215 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
216 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
217 state->cells = (p_cell *) calloc (sizeof(p_cell),
218 state->grid_width * state->grid_height);
219 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
221 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
224 int ncolors = MAX (0, state->ticks - 3);
225 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
227 double s1, s2, v1, v2;
229 unsigned long fg = get_pixel_resource ("foreground", "Foreground",
230 state->dpy, state->xgwa.colormap);
231 unsigned long bg = get_pixel_resource ("background", "Background",
232 state->dpy, state->xgwa.colormap);
233 unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
234 state->dpy,state->xgwa.colormap);
235 unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
236 state->dpy,state->xgwa.colormap);
241 XQueryColor (state->dpy, state->xgwa.colormap, &start);
244 XQueryColor (state->dpy, state->xgwa.colormap, &end);
246 /* Now allocate a ramp of colors from the main color to the background. */
247 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
248 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
249 make_color_ramp (state->dpy, state->xgwa.colormap,
255 /* Adjust to the number of colors we actually got. */
256 state->ticks = ncolors + STATE_MAX;
258 /* Now, GCs all around.
260 state->gcv.font = font->fid;
261 state->gcv.cap_style = CapRound;
263 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
264 if (state->gcv.line_width == state->scale)
265 state->gcv.line_width++;
266 #else /* !FUZZY_BORDER */
267 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
268 if (state->gcv.line_width >= state->scale)
269 state->gcv.line_width = state->scale - 1;
270 if (state->gcv.line_width < 1)
271 state->gcv.line_width = 1;
272 #endif /* !FUZZY_BORDER */
274 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
276 state->gcv.background = bg;
277 state->gcv.foreground = bg;
278 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
281 state->gcv.foreground = flare;
282 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
285 state->gcv.foreground = fg;
286 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
289 for (i = 0; i < ncolors; i++)
291 state->gcv.foreground = colors[i].pixel;
292 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
297 capture_font_bits (state);
299 launch_text_generator (state);
305 /* Re-query the window size and update the internal character grid if changed.
308 resize_grid (p_state *state)
310 int ow = state->grid_width;
311 int oh = state->grid_height;
312 p_cell *ocells = state->cells;
315 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
317 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
318 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
320 if (ow == state->grid_width &&
321 oh == state->grid_height)
324 state->cells = (p_cell *) calloc (sizeof(p_cell),
325 state->grid_width * state->grid_height);
327 for (y = 0; y < state->grid_height; y++)
329 for (x = 0; x < state->grid_width; x++)
331 p_cell *ncell = &state->cells [state->grid_width * y + x];
332 if (x < ow && y < oh)
333 *ncell = ocells [ow * y + x];
334 ncell->changed = True;
338 if (state->cursor_x >= state->grid_width)
339 state->cursor_x = state->grid_width-1;
340 if (state->cursor_y >= state->grid_height)
341 state->cursor_y = state->grid_height-1;
348 capture_font_bits (p_state *state)
350 XFontStruct *font = state->font;
351 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
352 int height = state->char_height;
353 unsigned char string[257];
355 Pixmap p = XCreatePixmap (state->dpy, state->window,
356 (safe_width * 256), height, 1);
358 for (i = 0; i < 256; i++)
359 string[i] = (unsigned char) i;
362 state->gcv.foreground = 0;
363 state->gcv.background = 0;
364 state->gc0 = XCreateGC (state->dpy, p,
365 (GCForeground | GCBackground),
368 state->gcv.foreground = 1;
369 state->gc1 = XCreateGC (state->dpy, p,
370 (GCFont | GCForeground | GCBackground |
371 GCCapStyle | GCLineWidth),
376 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
377 if (state->gcv.line_width >= state->scale)
378 state->gcv.line_width = state->scale - 1;
379 if (state->gcv.line_width < 1)
380 state->gcv.line_width = 1;
381 state->gc2 = XCreateGC (state->dpy, p,
382 (GCFont | GCForeground | GCBackground |
383 GCCapStyle | GCLineWidth),
386 #endif /* FUZZY_BORDER */
388 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
390 for (i = 0; i < 256; i++)
392 if (string[i] < font->min_char_or_byte2 ||
393 string[i] > font->max_char_or_byte2)
395 XDrawString (state->dpy, p, state->gc1,
396 i * safe_width, font->ascent,
397 (char *) (string + i), 1);
400 /* Draw the cursor. */
401 XFillRectangle (state->dpy, p, state->gc1,
402 (CURSOR_INDEX * safe_width), 1,
404 ? font->per_char['n'-font->min_char_or_byte2].width
405 : font->max_bounds.width),
409 XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
410 0, 0, (safe_width * 256), height, 0, 0, 1L);
411 XSync(state->dpy, False);
414 XSync (state->dpy, False);
415 state->font_bits = XGetImage (state->dpy, p, 0, 0,
416 (safe_width * 256), height, ~0L, XYPixmap);
417 XFreePixmap (state->dpy, p);
419 for (i = 0; i < 256; i++)
420 state->chars[i] = make_character (state, i);
421 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
426 make_character (p_state *state, int c)
428 p_char *pc = (p_char *) malloc (sizeof (*pc));
429 pc->name = (unsigned char) c;
430 pc->width = state->scale * state->char_width;
431 pc->height = state->scale * state->char_height;
432 char_to_pixmap (state, pc, c);
438 char_to_pixmap (p_state *state, p_char *pc, int c)
445 #endif /* FUZZY_BORDER */
449 XFontStruct *font = state->font;
450 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
452 int width = state->scale * state->char_width;
453 int height = state->scale * state->char_height;
455 if (c < font->min_char_or_byte2 ||
456 c > font->max_char_or_byte2)
460 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
461 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
464 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
465 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
466 #endif /* FUZZY_BORDER */
468 from = safe_width * c;
469 to = safe_width * (c + 1);
472 if (c > 75 && c < 150)
474 printf ("\n=========== %d (%c)\n", c, c);
475 for (y = 0; y < state->char_height; y++)
477 for (x1 = from; x1 < to; x1++)
478 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
485 for (y = 0; y < state->char_height; y++)
486 for (x1 = from; x1 < to; x1++)
487 if (XGetPixel (state->font_bits, x1, y))
489 int xoff = state->scale / 2;
491 for (x2 = x1; x2 < to; x2++)
492 if (!XGetPixel (state->font_bits, x2, y))
495 XDrawLine (state->dpy, p, gc,
496 (x1 - from) * state->scale + xoff, y * state->scale,
497 (x2 - from) * state->scale + xoff, y * state->scale);
499 XDrawLine (state->dpy, p2, gc2,
500 (x1 - from) * state->scale + xoff, y * state->scale,
501 (x2 - from) * state->scale + xoff, y * state->scale);
502 #endif /* FUZZY_BORDER */
507 /* if (pc->blank_p && c == CURSOR_INDEX)
514 #endif /* FUZZY_BORDER */
518 /* Managing the display.
521 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
522 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
525 set_cursor_1 (p_state *state, Bool on)
527 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
529 p_char *cursor = state->chars[CURSOR_INDEX];
530 int new_state = (on ? NORMAL : FADE);
532 if (cell->p_char != cursor)
533 cell->changed = True;
535 if (cell->state != new_state)
536 cell->changed = True;
538 cell->p_char = cursor;
539 cell->state = new_state;
540 return cell->changed;
544 set_cursor (p_state *state, Bool on)
546 if (set_cursor_1 (state, on))
548 if (state->cursor_timer)
549 XtRemoveTimeOut (state->cursor_timer);
550 state->cursor_timer = 0;
551 cursor_on_timer (state, 0);
559 cursor_off_timer (XtPointer closure, XtIntervalId *id)
561 p_state *state = (p_state *) closure;
562 set_cursor_1 (state, False);
563 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
564 cursor_on_timer, closure);
568 cursor_on_timer (XtPointer closure, XtIntervalId *id)
570 p_state *state = (p_state *) closure;
571 set_cursor_1 (state, True);
572 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
573 cursor_off_timer, closure);
578 clear (p_state *state)
583 for (y = 0; y < state->grid_height; y++)
584 for (x = 0; x < state->grid_width; x++)
586 p_cell *cell = &state->cells[state->grid_width * y + x];
587 if (cell->state == FLARE || cell->state == NORMAL)
590 cell->changed = True;
593 set_cursor (state, True);
598 decay (p_state *state)
601 for (y = 0; y < state->grid_height; y++)
602 for (x = 0; x < state->grid_width; x++)
604 p_cell *cell = &state->cells[state->grid_width * y + x];
605 if (cell->state == FLARE)
607 cell->state = NORMAL;
608 cell->changed = True;
610 else if (cell->state >= FADE)
613 if (cell->state >= state->ticks)
615 cell->changed = True;
622 scroll (p_state *state)
626 for (x = 0; x < state->grid_width; x++)
628 p_cell *from = 0, *to = 0;
629 for (y = 1; y < state->grid_height; y++)
631 from = &state->cells[state->grid_width * y + x];
632 to = &state->cells[state->grid_width * (y-1) + x];
634 if ((from->state == FLARE || from->state == NORMAL) &&
635 !from->p_char->blank_p)
638 to->state = NORMAL; /* should be FLARE? Looks bad... */
642 if (to->state == FLARE || to->state == NORMAL)
650 if (to && (to->state == FLARE || to->state == NORMAL))
656 set_cursor (state, True);
661 print_char (p_state *state, int c)
663 static char last_c = 0;
666 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
670 /* Start the cursor fading (in case we don't end up overwriting it.) */
671 if (cell->state == FLARE || cell->state == NORMAL)
674 cell->changed = True;
677 if (state->pid) /* Only interpret VT100 sequences if running in pty-mode.
678 It would be nice if we could just interpret them all
679 the time, but that would require subprocesses to send
680 CRLF line endings instead of bare LF, so that's no good.
683 switch (state->escstate)
689 /* Dummy case - we don't want the screensaver to beep */
690 /* #### But maybe this should flash the screen? */
693 if (state->cursor_x > 0)
697 if (state->cursor_x < state->grid_width - 8)
699 state->cursor_x = (state->cursor_x & ~7) + 8;
704 if (state->cursor_y < state->grid_height - 1)
715 cell->state = NORMAL;
716 cell->p_char = state->chars[bk];
717 cell->changed = True;
719 if (state->cursor_y < state->grid_height - 1)
726 cell = &state->cells[state->grid_width * state->cursor_y];
727 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
730 bk = cell->p_char->name;
734 /* Dummy case - I don't want to load several fonts for
735 the maybe two programs world-wide that use that */
739 /* Dummy case - these interrupt escape sequences, so
740 they don't do anything in this state */
746 /* Dummy case - this is supposed to be ignored */
750 for(i = 0; i < NPAR; i++)
751 state->csiparam[i] = 0;
756 cell->p_char = state->chars[c];
757 cell->changed = True;
760 if (c != ' ' && cell->p_char->blank_p)
761 cell->p_char = state->chars[CURSOR_INDEX];
763 if (state->cursor_x >= state->grid_width - 1)
766 if (state->cursor_y >= state->grid_height - 1)
781 case 'c': /* Reset */
785 case 'D': /* Linefeed */
786 if (state->cursor_y < state->grid_height - 1)
792 case 'E': /* Newline */
796 case 'M': /* Reverse newline */
797 if (state->cursor_y > 0)
801 case '7': /* Save state */
802 state->saved_x = state->cursor_x;
803 state->saved_y = state->cursor_y;
806 case '8': /* Restore state */
807 state->cursor_x = state->saved_x;
808 state->cursor_y = state->saved_y;
813 for(i = 0; i < NPAR; i++)
814 state->csiparam[i] = 0;
817 case '%': /* Select charset */
818 /* No, I don't support UTF-8, since the phosphor font
819 isn't even Unicode anyway. We must still catch the
820 last byte, though. */
823 /* I don't support different fonts either - see above
828 /* Escape sequences not supported:
831 * Z - Terminal identification
833 * = - Other keypad change
847 case '0': case '1': case '2': case '3': case '4':
848 case '5': case '6': case '7': case '8': case '9':
849 if (state->curparam < NPAR)
850 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
853 state->csiparam[++state->curparam] = 0;
859 for (i = 0; i < state->csiparam[0]; i++)
861 if(++state->cursor_x > state->grid_width)
864 if (state->cursor_y < state->grid_height - 1)
869 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
870 if (cell->state == FLARE || cell->state == NORMAL)
873 cell->changed = True;
881 if (state->csiparam[0] == 0)
882 state->csiparam[0] = 1;
883 if ((state->cursor_y -= state->csiparam[0]) < 0)
891 if (state->csiparam[0] == 0)
892 state->csiparam[0] = 1;
893 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
894 state->cursor_y = state->grid_height - 1;
899 if (state->csiparam[0] == 0)
900 state->csiparam[0] = 1;
901 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
902 state->cursor_x = state->grid_width - 1;
906 if (state->csiparam[0] == 0)
907 state->csiparam[0] = 1;
908 if ((state->cursor_x -= state->csiparam[0]) < 0)
913 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
914 state->cursor_y = state->grid_height - 1;
919 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
920 state->cursor_x = state->grid_width - 1;
925 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
926 state->cursor_y = state->grid_height - 1;
927 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
928 state->cursor_x = state->grid_width - 1;
929 if(state->cursor_y < 0)
931 if(state->cursor_x < 0)
937 end = state->grid_height * state->grid_width;
938 if (state->csiparam[0] == 0)
939 start = state->grid_width * state->cursor_y + state->cursor_x;
940 if (state->csiparam[0] == 1)
941 end = state->grid_width * state->cursor_y + state->cursor_x;
942 for (i = start; i < end; i++)
944 cell = &state->cells[i];
945 if (cell->state == FLARE || cell->state == NORMAL)
948 cell->changed = True;
951 set_cursor (state, True);
956 end = state->grid_width;
957 if (state->csiparam[0] == 0)
958 start = state->cursor_x;
959 if (state->csiparam[1] == 1)
960 end = state->cursor_x;
961 for (i = start; i < end; i++)
963 if (cell->state == FLARE || cell->state == NORMAL)
966 cell->changed = True;
972 case 's': /* Save position */
973 state->saved_x = state->cursor_x;
974 state->saved_y = state->cursor_y;
977 case 'u': /* Restore position */
978 state->cursor_x = state->saved_x;
979 state->cursor_y = state->saved_y;
982 case '?': /* DEC Private modes */
983 if ((state->curparam != 0) || (state->csiparam[0] != 0))
987 /* Known unsupported CSIs:
989 * L - Insert blank lines
990 * M - Delete lines (I don't know what this means...)
991 * P - Delete characters
992 * X - Erase characters (difference with P being...?)
993 * c - Terminal identification
994 * g - Clear tab stop(s)
995 * h - Set mode (Mainly due to its complexity and lack of good
998 * m - Set mode (Phosphor is, per defenition, green on black)
1000 * q - Set keyboard LEDs
1001 * r - Set scrolling region (too exhausting - noone uses this,
1004 state->escstate = 0;
1009 state->escstate = 0;
1012 set_cursor (state, True);
1016 if (c == '\t') c = ' '; /* blah. */
1018 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1020 if (c == '\n' && last_c == '\r')
1021 ; /* CRLF -- do nothing */
1024 state->cursor_x = 0;
1025 if (state->cursor_y == state->grid_height - 1)
1031 else if (c == '\014')
1037 cell->state = FLARE;
1038 cell->p_char = state->chars[c];
1039 cell->changed = True;
1042 if (c != ' ' && cell->p_char->blank_p)
1043 cell->p_char = state->chars[CURSOR_INDEX];
1045 if (state->cursor_x >= state->grid_width - 1)
1047 state->cursor_x = 0;
1048 if (state->cursor_y >= state->grid_height - 1)
1054 set_cursor (state, True);
1062 update_display (p_state *state, Bool changed_only)
1066 for (y = 0; y < state->grid_height; y++)
1067 for (x = 0; x < state->grid_width; x++)
1069 p_cell *cell = &state->cells[state->grid_width * y + x];
1070 int width, height, tx, ty;
1072 if (changed_only && !cell->changed)
1075 width = state->char_width * state->scale;
1076 height = state->char_height * state->scale;
1080 if (cell->state == BLANK || cell->p_char->blank_p)
1082 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1083 tx, ty, width, height);
1088 GC gc1 = state->gcs[cell->state];
1089 GC gc2 = ((cell->state + 2) < state->ticks
1090 ? state->gcs[cell->state + 2]
1092 GC gc3 = (gc2 ? gc2 : gc1);
1094 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1095 0, 0, width, height, tx, ty, 1L);
1098 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1099 XSetClipOrigin (state->dpy, gc1, tx, ty);
1100 XFillRectangle (state->dpy, state->window, gc1,
1101 tx, ty, width, height);
1102 XSetClipMask (state->dpy, gc1, None);
1104 #else /* !FUZZY_BORDER */
1106 XCopyPlane (state->dpy,
1107 cell->p_char->pixmap, state->window,
1108 state->gcs[cell->state],
1109 0, 0, width, height, tx, ty, 1L);
1111 #endif /* !FUZZY_BORDER */
1114 cell->changed = False;
1120 run_phosphor (p_state *state)
1122 update_display (state, True);
1124 drain_input (state);
1132 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1134 p_state *state = (p_state *) closure;
1135 state->input_available_p = True;
1140 launch_text_generator (p_state *state)
1143 char *oprogram = get_string_resource ("program", "Program");
1146 if(state->mode == 1)
1151 ws.ws_row = state->grid_height - 1;
1152 ws.ws_col = state->grid_width - 2;
1153 ws.ws_xpixel = state->xgwa.width;
1154 ws.ws_ypixel = state->xgwa.height;
1157 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1159 /* Unable to fork */
1160 sprintf (buf, "%.100s: forkpty", progname);
1163 else if(!state->pid)
1165 /* This is the child fork. */
1166 if (putenv("TERM=vt100"))
1168 execl("/bin/sh", "/bin/sh", "-c", oprogram, NULL);
1169 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1175 /* This is the parent fork. */
1176 state->pipe = fdopen(fd, "r+");
1178 XtAppAddInput (app, fileno (state->pipe),
1179 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1180 subproc_cb, (XtPointer) state);
1184 #endif /* HAVE_FORKPTY */
1186 char *program = (char *) malloc (strlen (oprogram) + 10);
1188 strcpy (program, "( ");
1189 strcat (program, oprogram);
1190 strcat (program, " ) 2>&1");
1192 /* don't mess up controlling terminal if someone dumbly does
1193 "-pipe -program tcsh". */
1196 if ((state->pipe = popen (program, "r")))
1199 XtAppAddInput (app, fileno (state->pipe),
1200 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1201 subproc_cb, (XtPointer) state);
1205 sprintf (buf, "%.100s: %.100s", progname, program);
1213 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1215 p_state *state = (p_state *) closure;
1216 launch_text_generator (state);
1221 drain_input (p_state *state)
1223 if (state->input_available_p)
1226 int n = read (fileno (state->pipe), (void *) s, 1);
1229 print_char (state, s[0]);
1233 XtRemoveInput (state->pipe_id);
1237 waitpid(state->pid, NULL, 0);
1238 fclose (state->pipe);
1242 pclose (state->pipe);
1246 if (state->cursor_x != 0) /* break line if unbroken */
1247 print_char (state, '\n'); /* blank line */
1248 print_char (state, '\n');
1250 /* Set up a timer to re-launch the subproc in a bit. */
1251 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1252 relaunch_generator_timer,
1256 state->input_available_p = False;
1261 /* The interpretation of the ModN modifiers is dependent on what keys
1262 are bound to them: Mod1 does not necessarily mean "meta". It only
1263 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1264 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1265 aren't necessarily the same thing. Icepicks in my forehead!
1268 do_icccm_meta_key_stupidity (Display *dpy)
1270 unsigned int modbits = 0;
1272 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1273 for (i = 3; i < 8; i++)
1274 for (j = 0; j < modmap->max_keypermod; j++)
1276 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1279 if (code == 0) continue;
1280 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1281 for (k = 0; k < nsyms; k++)
1282 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1283 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1284 modbits |= (1 << i);
1287 XFreeModifiermap (modmap);
1291 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1294 meta_modifier (Display *dpy)
1296 static Bool done_once = False;
1297 static unsigned int mask = 0;
1300 /* Really, we are supposed to recompute this if a KeymapNotify
1301 event comes in, but fuck it. */
1303 mask = do_icccm_meta_key_stupidity (dpy);
1310 handle_events (p_state *state)
1312 XSync (state->dpy, False);
1313 while (XPending (state->dpy))
1316 XNextEvent (state->dpy, &event);
1318 if (event.xany.type == ConfigureNotify)
1320 resize_grid (state);
1322 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1325 /* Tell the sub-process that the screen size has changed. */
1327 ws.ws_row = state->grid_height - 1;
1328 ws.ws_col = state->grid_width - 2;
1329 ws.ws_xpixel = state->xgwa.width;
1330 ws.ws_ypixel = state->xgwa.height;
1331 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1332 kill (state->pid, SIGWINCH);
1334 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1336 else if (event.xany.type == Expose)
1338 update_display (state, False);
1340 else if (event.xany.type == KeyPress)
1343 unsigned char c = 0;
1344 XLookupString (&event.xkey, (char *) &c, 1, &keysym,
1346 if (c != 0 && state->pipe)
1348 if (!state->swap_bs_del_p) ;
1349 else if (c == 127) c = 8;
1350 else if (c == 8) c = 127;
1352 /* If meta was held down, send ESC, or turn on the high bit. */
1353 if (event.xkey.state & meta_modifier (state->dpy))
1355 if (state->meta_sends_esc_p)
1356 fputc ('\033', state->pipe);
1361 fputc (c, state->pipe);
1362 fflush (state->pipe);
1363 event.xany.type = 0; /* don't interpret this event defaultly. */
1367 screenhack_handle_event (state->dpy, &event);
1370 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1371 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1376 char *progclass = "Phosphor";
1378 char *defaults [] = {
1379 ".background: Black",
1380 ".foreground: Green",
1381 "*fadeForeground: DarkGreen",
1382 "*flareForeground: White",
1388 "*program: " FORTUNE_PROGRAM,
1390 "*metaSendsESC: True",
1394 #else /* !HAVE_FORKPTY */
1396 #endif /* !HAVE_FORKPTY */
1400 XrmOptionDescRec options [] = {
1401 { "-font", ".font", XrmoptionSepArg, 0 },
1402 { "-scale", ".scale", XrmoptionSepArg, 0 },
1403 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1404 { "-delay", ".delay", XrmoptionSepArg, 0 },
1405 { "-program", ".program", XrmoptionSepArg, 0 },
1406 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1407 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1408 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1409 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1410 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1411 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1417 screenhack (Display *dpy, Window window)
1419 int delay = get_integer_resource ("delay", "Integer");
1420 p_state *state = init_phosphor (dpy, window);
1426 run_phosphor (state);
1428 handle_events (state);
1429 if (delay) usleep (delay);