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>
31 #endif /* HAVE_FORKPTY */
33 extern XtAppContext app;
37 #define MAX(a,b) ((a)>(b)?(a):(b))
38 #define MIN(a,b) ((a)<(b)?(a):(b))
44 #define STATE_MAX FADE
46 #define CURSOR_INDEX 128
56 #endif /* FUZZY_BORDER */
69 XWindowAttributes xgwa;
71 int grid_width, grid_height;
72 int char_width, char_height;
88 #endif /* FUZZY_BORDER */
92 int cursor_x, cursor_y;
93 XtIntervalId cursor_timer;
98 Bool input_available_p;
99 Time subproc_relaunch_delay;
100 XComposeStatus compose;
101 Bool meta_sends_esc_p;
107 static void capture_font_bits (p_state *state);
108 static p_char *make_character (p_state *state, int c);
109 static void drain_input (p_state *state);
110 static void char_to_pixmap (p_state *state, p_char *pc, int c);
111 static void launch_text_generator (p_state *state);
114 /* About font metrics:
116 "lbearing" is the distance from the leftmost pixel of a character to
117 the logical origin of that character. That is, it is the number of
118 pixels of the character which are to the left of its logical origin.
120 "rbearing" is the distance from the logical origin of a character to
121 the rightmost pixel of a character. That is, it is the number of
122 pixels of the character to the right of its logical origin.
124 "descent" is the distance from the bottommost pixel of a character to
125 the logical baseline. That is, it is the number of pixels of the
126 character which are below the baseline.
128 "ascent" is the distance from the logical baseline to the topmost pixel.
129 That is, it is the number of pixels of the character above the baseline.
131 Therefore, the bounding box of the "ink" of a character is
132 lbearing + rbearing by ascent + descent;
134 "width" is the distance from the logical origin of this character to
135 the position where the logical orgin of the next character should be
138 For our purposes, we're only interested in the part of the character
139 lying inside the "width" box. If the characters have ink outside of
140 that box (the "charcell" box) then we're going to lose it. Alas.
144 init_phosphor (Display *dpy, Window window)
148 p_state *state = (p_state *) calloc (sizeof(*state), 1);
149 char *fontname = get_string_resource ("font", "Font");
153 state->window = window;
155 XGetWindowAttributes (dpy, window, &state->xgwa);
156 XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);
158 state->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
159 state->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
161 state->font = XLoadQueryFont (dpy, fontname);
165 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
166 state->font = XLoadQueryFont (dpy, "fixed");
170 fprintf(stderr, "couldn't load font \"fixed\"");
175 state->scale = get_integer_resource ("scale", "Integer");
176 state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
180 char *s = get_string_resource ("mode", "Integer");
182 if (!s || !*s || !strcasecmp (s, "pipe"))
184 else if (!strcasecmp (s, "pty"))
187 fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
191 fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
194 #endif /* HAVE_FORKPTY */
198 for (i = 0; i < font->n_properties; i++)
199 if (font->properties[i].name == XA_FONT)
200 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
203 state->cursor_blink = get_integer_resource ("cursor", "Time");
204 state->subproc_relaunch_delay =
205 (1000 * get_integer_resource ("relaunch", "Time"));
207 state->char_width = font->max_bounds.width;
208 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
210 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
211 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
212 state->cells = (p_cell *) calloc (sizeof(p_cell),
213 state->grid_width * state->grid_height);
214 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
216 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
219 int ncolors = MAX (0, state->ticks - 3);
220 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
222 double s1, s2, v1, v2;
224 unsigned long fg = get_pixel_resource ("foreground", "Foreground",
225 state->dpy, state->xgwa.colormap);
226 unsigned long bg = get_pixel_resource ("background", "Background",
227 state->dpy, state->xgwa.colormap);
228 unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
229 state->dpy,state->xgwa.colormap);
230 unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
231 state->dpy,state->xgwa.colormap);
236 XQueryColor (state->dpy, state->xgwa.colormap, &start);
239 XQueryColor (state->dpy, state->xgwa.colormap, &end);
241 /* Now allocate a ramp of colors from the main color to the background. */
242 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
243 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
244 make_color_ramp (state->dpy, state->xgwa.colormap,
250 /* Adjust to the number of colors we actually got. */
251 state->ticks = ncolors + STATE_MAX;
253 /* Now, GCs all around.
255 state->gcv.font = font->fid;
256 state->gcv.cap_style = CapRound;
258 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
259 if (state->gcv.line_width == state->scale)
260 state->gcv.line_width++;
261 #else /* !FUZZY_BORDER */
262 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
263 if (state->gcv.line_width >= state->scale)
264 state->gcv.line_width = state->scale - 1;
265 if (state->gcv.line_width < 1)
266 state->gcv.line_width = 1;
267 #endif /* !FUZZY_BORDER */
269 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
271 state->gcv.background = bg;
272 state->gcv.foreground = bg;
273 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
276 state->gcv.foreground = flare;
277 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
280 state->gcv.foreground = fg;
281 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
284 for (i = 0; i < ncolors; i++)
286 state->gcv.foreground = colors[i].pixel;
287 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
292 capture_font_bits (state);
294 launch_text_generator (state);
300 /* Re-query the window size and update the internal character grid if changed.
303 resize_grid (p_state *state)
305 int ow = state->grid_width;
306 int oh = state->grid_height;
307 p_cell *ocells = state->cells;
310 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
312 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
313 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
315 if (ow == state->grid_width &&
316 oh == state->grid_height)
319 state->cells = (p_cell *) calloc (sizeof(p_cell),
320 state->grid_width * state->grid_height);
322 for (y = 0; y < state->grid_height; y++)
324 for (x = 0; x < state->grid_width; x++)
326 p_cell *ncell = &state->cells [state->grid_width * y + x];
327 if (x < ow && y < oh)
328 *ncell = ocells [ow * y + x];
329 ncell->changed = True;
333 if (state->cursor_x >= state->grid_width)
334 state->cursor_x = state->grid_width-1;
335 if (state->cursor_y >= state->grid_height)
336 state->cursor_y = state->grid_height-1;
343 capture_font_bits (p_state *state)
345 XFontStruct *font = state->font;
346 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
347 int height = state->char_height;
348 unsigned char string[257];
350 Pixmap p = XCreatePixmap (state->dpy, state->window,
351 (safe_width * 256), height, 1);
353 for (i = 0; i < 256; i++)
354 string[i] = (unsigned char) i;
357 state->gcv.foreground = 0;
358 state->gcv.background = 0;
359 state->gc0 = XCreateGC (state->dpy, p,
360 (GCForeground | GCBackground),
363 state->gcv.foreground = 1;
364 state->gc1 = XCreateGC (state->dpy, p,
365 (GCFont | GCForeground | GCBackground |
366 GCCapStyle | GCLineWidth),
371 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
372 if (state->gcv.line_width >= state->scale)
373 state->gcv.line_width = state->scale - 1;
374 if (state->gcv.line_width < 1)
375 state->gcv.line_width = 1;
376 state->gc2 = XCreateGC (state->dpy, p,
377 (GCFont | GCForeground | GCBackground |
378 GCCapStyle | GCLineWidth),
381 #endif /* FUZZY_BORDER */
383 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
385 for (i = 0; i < 256; i++)
387 if (string[i] < font->min_char_or_byte2 ||
388 string[i] > font->max_char_or_byte2)
390 XDrawString (state->dpy, p, state->gc1,
391 i * safe_width, font->ascent,
392 (char *) (string + i), 1);
395 /* Draw the cursor. */
396 XFillRectangle (state->dpy, p, state->gc1,
397 (CURSOR_INDEX * safe_width), 1,
399 ? font->per_char['n'-font->min_char_or_byte2].width
400 : font->max_bounds.width),
404 XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
405 0, 0, (safe_width * 256), height, 0, 0, 1L);
406 XSync(state->dpy, False);
409 XSync (state->dpy, False);
410 state->font_bits = XGetImage (state->dpy, p, 0, 0,
411 (safe_width * 256), height, ~0L, XYPixmap);
412 XFreePixmap (state->dpy, p);
414 for (i = 0; i < 256; i++)
415 state->chars[i] = make_character (state, i);
416 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
421 make_character (p_state *state, int c)
423 p_char *pc = (p_char *) malloc (sizeof (*pc));
424 pc->name = (unsigned char) c;
425 pc->width = state->scale * state->char_width;
426 pc->height = state->scale * state->char_height;
427 char_to_pixmap (state, pc, c);
433 char_to_pixmap (p_state *state, p_char *pc, int c)
440 #endif /* FUZZY_BORDER */
444 XFontStruct *font = state->font;
445 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
447 int width = state->scale * state->char_width;
448 int height = state->scale * state->char_height;
450 if (c < font->min_char_or_byte2 ||
451 c > font->max_char_or_byte2)
455 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
456 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
459 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
460 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
461 #endif /* FUZZY_BORDER */
463 from = safe_width * c;
464 to = safe_width * (c + 1);
467 if (c > 75 && c < 150)
469 printf ("\n=========== %d (%c)\n", c, c);
470 for (y = 0; y < state->char_height; y++)
472 for (x1 = from; x1 < to; x1++)
473 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
480 for (y = 0; y < state->char_height; y++)
481 for (x1 = from; x1 < to; x1++)
482 if (XGetPixel (state->font_bits, x1, y))
484 int xoff = state->scale / 2;
486 for (x2 = x1; x2 < to; x2++)
487 if (!XGetPixel (state->font_bits, x2, y))
490 XDrawLine (state->dpy, p, gc,
491 (x1 - from) * state->scale + xoff, y * state->scale,
492 (x2 - from) * state->scale + xoff, y * state->scale);
494 XDrawLine (state->dpy, p2, gc2,
495 (x1 - from) * state->scale + xoff, y * state->scale,
496 (x2 - from) * state->scale + xoff, y * state->scale);
497 #endif /* FUZZY_BORDER */
502 /* if (pc->blank_p && c == CURSOR_INDEX)
509 #endif /* FUZZY_BORDER */
513 /* Managing the display.
516 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
517 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
520 set_cursor_1 (p_state *state, Bool on)
522 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
524 p_char *cursor = state->chars[CURSOR_INDEX];
525 int new_state = (on ? NORMAL : FADE);
527 if (cell->p_char != cursor)
528 cell->changed = True;
530 if (cell->state != new_state)
531 cell->changed = True;
533 cell->p_char = cursor;
534 cell->state = new_state;
535 return cell->changed;
539 set_cursor (p_state *state, Bool on)
541 if (set_cursor_1 (state, on))
543 if (state->cursor_timer)
544 XtRemoveTimeOut (state->cursor_timer);
545 state->cursor_timer = 0;
546 cursor_on_timer (state, 0);
554 cursor_off_timer (XtPointer closure, XtIntervalId *id)
556 p_state *state = (p_state *) closure;
557 set_cursor_1 (state, False);
558 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
559 cursor_on_timer, closure);
563 cursor_on_timer (XtPointer closure, XtIntervalId *id)
565 p_state *state = (p_state *) closure;
566 set_cursor_1 (state, True);
567 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
568 cursor_off_timer, closure);
573 clear (p_state *state)
578 for (y = 0; y < state->grid_height; y++)
579 for (x = 0; x < state->grid_width; x++)
581 p_cell *cell = &state->cells[state->grid_width * y + x];
582 if (cell->state == FLARE || cell->state == NORMAL)
585 cell->changed = True;
588 set_cursor (state, True);
593 decay (p_state *state)
596 for (y = 0; y < state->grid_height; y++)
597 for (x = 0; x < state->grid_width; x++)
599 p_cell *cell = &state->cells[state->grid_width * y + x];
600 if (cell->state == FLARE)
602 cell->state = NORMAL;
603 cell->changed = True;
605 else if (cell->state >= FADE)
608 if (cell->state >= state->ticks)
610 cell->changed = True;
617 scroll (p_state *state)
621 for (x = 0; x < state->grid_width; x++)
623 p_cell *from = 0, *to = 0;
624 for (y = 1; y < state->grid_height; y++)
626 from = &state->cells[state->grid_width * y + x];
627 to = &state->cells[state->grid_width * (y-1) + x];
629 if ((from->state == FLARE || from->state == NORMAL) &&
630 !from->p_char->blank_p)
633 to->state = NORMAL; /* should be FLARE? Looks bad... */
637 if (to->state == FLARE || to->state == NORMAL)
645 if (to && (to->state == FLARE || to->state == NORMAL))
651 set_cursor (state, True);
656 print_char (p_state *state, int c)
658 static char last_c = 0;
661 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
665 /* Start the cursor fading (in case we don't end up overwriting it.) */
666 if (cell->state == FLARE || cell->state == NORMAL)
669 cell->changed = True;
672 if (state->pid) /* Only interpret VT100 sequences if running in pty-mode.
673 It would be nice if we could just interpret them all
674 the time, but that would require subprocesses to send
675 CRLF line endings instead of bare LF, so that's no good.
678 switch (state->escstate)
684 /* Dummy case - we don't want the screensaver to beep */
685 /* #### But maybe this should flash the screen? */
688 if (state->cursor_x > 0)
692 if (state->cursor_x < state->grid_width - 8)
694 state->cursor_x = (state->cursor_x & ~7) + 8;
699 if (state->cursor_y < state->grid_height - 1)
710 cell->state = NORMAL;
711 cell->p_char = state->chars[bk];
712 cell->changed = True;
714 if (state->cursor_y < state->grid_height - 1)
721 cell = &state->cells[state->grid_width * state->cursor_y];
722 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
725 bk = cell->p_char->name;
729 /* Dummy case - I don't want to load several fonts for
730 the maybe two programs world-wide that use that */
734 /* Dummy case - these interrupt escape sequences, so
735 they don't do anything in this state */
741 /* Dummy case - this is supposed to be ignored */
745 for(i = 0; i < NPAR; i++)
746 state->csiparam[i] = 0;
751 cell->p_char = state->chars[c];
752 cell->changed = True;
755 if (c != ' ' && cell->p_char->blank_p)
756 cell->p_char = state->chars[CURSOR_INDEX];
758 if (state->cursor_x >= state->grid_width - 1)
761 if (state->cursor_y >= state->grid_height - 1)
776 case 'c': /* Reset */
780 case 'D': /* Linefeed */
781 if (state->cursor_y < state->grid_height - 1)
787 case 'E': /* Newline */
791 case 'M': /* Reverse newline */
792 if (state->cursor_y > 0)
796 case '7': /* Save state */
797 state->saved_x = state->cursor_x;
798 state->saved_y = state->cursor_y;
801 case '8': /* Restore state */
802 state->cursor_x = state->saved_x;
803 state->cursor_y = state->saved_y;
808 for(i = 0; i < NPAR; i++)
809 state->csiparam[i] = 0;
812 case '%': /* Select charset */
813 /* No, I don't support UTF-8, since the phosphor font
814 isn't even Unicode anyway. We must still catch the
815 last byte, though. */
818 /* I don't support different fonts either - see above
823 /* Escape sequences not supported:
826 * Z - Terminal identification
828 * = - Other keypad change
842 case '0': case '1': case '2': case '3': case '4':
843 case '5': case '6': case '7': case '8': case '9':
844 if (state->curparam < NPAR)
845 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
848 state->csiparam[++state->curparam] = 0;
854 for (i = 0; i < state->csiparam[0]; i++)
856 if(++state->cursor_x > state->grid_width)
859 if (state->cursor_y < state->grid_height - 1)
864 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
865 if (cell->state == FLARE || cell->state == NORMAL)
868 cell->changed = True;
876 if (state->csiparam[0] == 0)
877 state->csiparam[0] = 1;
878 if ((state->cursor_y -= state->csiparam[0]) < 0)
886 if (state->csiparam[0] == 0)
887 state->csiparam[0] = 1;
888 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
889 state->cursor_y = state->grid_height - 1;
894 if (state->csiparam[0] == 0)
895 state->csiparam[0] = 1;
896 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
897 state->cursor_x = state->grid_width - 1;
901 if (state->csiparam[0] == 0)
902 state->csiparam[0] = 1;
903 if ((state->cursor_x -= state->csiparam[0]) < 0)
908 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
909 state->cursor_y = state->grid_height - 1;
914 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
915 state->cursor_x = state->grid_width - 1;
920 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
921 state->cursor_y = state->grid_height - 1;
922 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
923 state->cursor_x = state->grid_width - 1;
924 if(state->cursor_y < 0)
926 if(state->cursor_x < 0)
932 end = state->grid_height * state->grid_width;
933 if (state->csiparam[0] == 0)
934 start = state->grid_width * state->cursor_y + state->cursor_x;
935 if (state->csiparam[0] == 1)
936 end = state->grid_width * state->cursor_y + state->cursor_x;
937 for (i = start; i < end; i++)
939 cell = &state->cells[i];
940 if (cell->state == FLARE || cell->state == NORMAL)
943 cell->changed = True;
946 set_cursor (state, True);
951 end = state->grid_width;
952 if (state->csiparam[0] == 0)
953 start = state->cursor_x;
954 if (state->csiparam[1] == 1)
955 end = state->cursor_x;
956 for (i = start; i < end; i++)
958 if (cell->state == FLARE || cell->state == NORMAL)
961 cell->changed = True;
967 case 's': /* Save position */
968 state->saved_x = state->cursor_x;
969 state->saved_y = state->cursor_y;
972 case 'u': /* Restore position */
973 state->cursor_x = state->saved_x;
974 state->cursor_y = state->saved_y;
977 case '?': /* DEC Private modes */
978 if ((state->curparam != 0) || (state->csiparam[0] != 0))
982 /* Known unsupported CSIs:
984 * L - Insert blank lines
985 * M - Delete lines (I don't know what this means...)
986 * P - Delete characters
987 * X - Erase characters (difference with P being...?)
988 * c - Terminal identification
989 * g - Clear tab stop(s)
990 * h - Set mode (Mainly due to its complexity and lack of good
993 * m - Set mode (Phosphor is, per defenition, green on black)
995 * q - Set keyboard LEDs
996 * r - Set scrolling region (too exhausting - noone uses this,
1004 state->escstate = 0;
1007 set_cursor (state, True);
1011 if (c == '\t') c = ' '; /* blah. */
1013 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1015 if (c == '\n' && last_c == '\r')
1016 ; /* CRLF -- do nothing */
1019 state->cursor_x = 0;
1020 if (state->cursor_y == state->grid_height - 1)
1026 else if (c == '\014')
1032 cell->state = FLARE;
1033 cell->p_char = state->chars[c];
1034 cell->changed = True;
1037 if (c != ' ' && cell->p_char->blank_p)
1038 cell->p_char = state->chars[CURSOR_INDEX];
1040 if (state->cursor_x >= state->grid_width - 1)
1042 state->cursor_x = 0;
1043 if (state->cursor_y >= state->grid_height - 1)
1049 set_cursor (state, True);
1057 update_display (p_state *state, Bool changed_only)
1061 for (y = 0; y < state->grid_height; y++)
1062 for (x = 0; x < state->grid_width; x++)
1064 p_cell *cell = &state->cells[state->grid_width * y + x];
1065 int width, height, tx, ty;
1067 if (changed_only && !cell->changed)
1070 width = state->char_width * state->scale;
1071 height = state->char_height * state->scale;
1075 if (cell->state == BLANK || cell->p_char->blank_p)
1077 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1078 tx, ty, width, height);
1083 GC gc1 = state->gcs[cell->state];
1084 GC gc2 = ((cell->state + 2) < state->ticks
1085 ? state->gcs[cell->state + 2]
1087 GC gc3 = (gc2 ? gc2 : gc1);
1089 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1090 0, 0, width, height, tx, ty, 1L);
1093 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1094 XSetClipOrigin (state->dpy, gc1, tx, ty);
1095 XFillRectangle (state->dpy, state->window, gc1,
1096 tx, ty, width, height);
1097 XSetClipMask (state->dpy, gc1, None);
1099 #else /* !FUZZY_BORDER */
1101 XCopyPlane (state->dpy,
1102 cell->p_char->pixmap, state->window,
1103 state->gcs[cell->state],
1104 0, 0, width, height, tx, ty, 1L);
1106 #endif /* !FUZZY_BORDER */
1109 cell->changed = False;
1115 run_phosphor (p_state *state)
1117 update_display (state, True);
1119 drain_input (state);
1127 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1129 p_state *state = (p_state *) closure;
1130 state->input_available_p = True;
1135 launch_text_generator (p_state *state)
1138 char *oprogram = get_string_resource ("program", "Program");
1141 if(state->mode == 1)
1146 ws.ws_row = state->grid_height - 1;
1147 ws.ws_col = state->grid_width - 2;
1148 ws.ws_xpixel = state->xgwa.width;
1149 ws.ws_ypixel = state->xgwa.height;
1152 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1154 /* Unable to fork */
1155 sprintf (buf, "%.100s: forkpty", progname);
1158 else if(!state->pid)
1160 /* This is the child fork. */
1161 if (putenv("TERM=vt100"))
1163 execl("/bin/sh", "/bin/sh", "-c", oprogram, NULL);
1164 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1170 /* This is the parent fork. */
1171 state->pipe = fdopen(fd, "r+");
1173 XtAppAddInput (app, fileno (state->pipe),
1174 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1175 subproc_cb, (XtPointer) state);
1179 #endif /* HAVE_FORKPTY */
1181 char *program = (char *) malloc (strlen (oprogram) + 10);
1183 strcpy (program, "( ");
1184 strcat (program, oprogram);
1185 strcat (program, " ) 2>&1");
1187 /* don't mess up controlling terminal if someone dumbly does
1188 "-pipe -program tcsh". */
1191 if ((state->pipe = popen (program, "r")))
1194 XtAppAddInput (app, fileno (state->pipe),
1195 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1196 subproc_cb, (XtPointer) state);
1200 sprintf (buf, "%.100s: %.100s", progname, program);
1208 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1210 p_state *state = (p_state *) closure;
1211 launch_text_generator (state);
1216 drain_input (p_state *state)
1218 if (state->input_available_p)
1221 int n = read (fileno (state->pipe), (void *) s, 1);
1224 print_char (state, s[0]);
1228 XtRemoveInput (state->pipe_id);
1232 waitpid(state->pid, NULL, 0);
1233 fclose (state->pipe);
1237 pclose (state->pipe);
1241 if (state->cursor_x != 0) /* break line if unbroken */
1242 print_char (state, '\n'); /* blank line */
1243 print_char (state, '\n');
1245 /* Set up a timer to re-launch the subproc in a bit. */
1246 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1247 relaunch_generator_timer,
1251 state->input_available_p = False;
1256 /* The interpretation of the ModN modifiers is dependent on what keys
1257 are bound to them: Mod1 does not necessarily mean "meta". It only
1258 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1259 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1260 aren't necessarily the same thing. Icepicks in my forehead!
1263 do_icccm_meta_key_stupidity (Display *dpy)
1265 unsigned int modbits = 0;
1267 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1268 for (i = 3; i < 8; i++)
1269 for (j = 0; j < modmap->max_keypermod; j++)
1271 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1274 if (code == 0) continue;
1275 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1276 for (k = 0; k < nsyms; k++)
1277 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1278 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1279 modbits |= (1 << i);
1282 XFreeModifiermap (modmap);
1286 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1289 meta_modifier (Display *dpy)
1291 static Bool done_once = False;
1292 static unsigned int mask = 0;
1295 /* Really, we are supposed to recompute this if a KeymapNotify
1296 event comes in, but fuck it. */
1298 mask = do_icccm_meta_key_stupidity (dpy);
1305 handle_events (p_state *state)
1307 XSync (state->dpy, False);
1308 while (XPending (state->dpy))
1311 XNextEvent (state->dpy, &event);
1313 if (event.xany.type == ConfigureNotify)
1315 resize_grid (state);
1317 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1320 /* Tell the sub-process that the screen size has changed. */
1322 ws.ws_row = state->grid_height - 1;
1323 ws.ws_col = state->grid_width - 2;
1324 ws.ws_xpixel = state->xgwa.width;
1325 ws.ws_ypixel = state->xgwa.height;
1326 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1327 kill (state->pid, SIGWINCH);
1329 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1331 else if (event.xany.type == Expose)
1333 update_display (state, False);
1335 else if (event.xany.type == KeyPress)
1338 unsigned char c = 0;
1339 XLookupString (&event.xkey, (char *) &c, 1, &keysym,
1341 if (c != 0 && state->pipe)
1343 if (!state->swap_bs_del_p) ;
1344 else if (c == 127) c = 8;
1345 else if (c == 8) c = 127;
1347 /* If meta was held down, send ESC, or turn on the high bit. */
1348 if (event.xkey.state & meta_modifier (state->dpy))
1350 if (state->meta_sends_esc_p)
1351 fputc ('\033', state->pipe);
1356 fputc (c, state->pipe);
1357 fflush (state->pipe);
1358 event.xany.type = 0; /* don't interpret this event defaultly. */
1362 screenhack_handle_event (state->dpy, &event);
1365 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1366 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1371 char *progclass = "Phosphor";
1373 char *defaults [] = {
1374 ".background: Black",
1375 ".foreground: Green",
1376 "*fadeForeground: DarkGreen",
1377 "*flareForeground: White",
1383 "*program: " FORTUNE_PROGRAM,
1385 "*metaSendsESC: True",
1389 #else /* !HAVE_FORKPTY */
1391 #endif /* !HAVE_FORKPTY */
1395 XrmOptionDescRec options [] = {
1396 { "-font", ".font", XrmoptionSepArg, 0 },
1397 { "-scale", ".scale", XrmoptionSepArg, 0 },
1398 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1399 { "-delay", ".delay", XrmoptionSepArg, 0 },
1400 { "-program", ".program", XrmoptionSepArg, 0 },
1401 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1402 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1403 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1404 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1405 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1406 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1412 screenhack (Display *dpy, Window window)
1414 int delay = get_integer_resource ("delay", "Integer");
1415 p_state *state = init_phosphor (dpy, window);
1421 run_phosphor (state);
1423 handle_events (state);
1424 if (delay) usleep (delay);