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 /* oprogram contains a "%d" where the current number of columns goes
1149 strcpy (program, "( ");
1150 sprintf (program + strlen(program), oprogram, state->grid_width);
1151 strcat (program, " ) 2>&1");
1154 if(state->mode == 1)
1159 ws.ws_row = state->grid_height - 1;
1160 ws.ws_col = state->grid_width - 2;
1161 ws.ws_xpixel = state->xgwa.width;
1162 ws.ws_ypixel = state->xgwa.height;
1165 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1167 /* Unable to fork */
1168 sprintf (buf, "%.100s: forkpty", progname);
1171 else if(!state->pid)
1173 /* This is the child fork. */
1176 if (putenv("TERM=vt100"))
1178 av[i++] = "/bin/sh";
1183 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1189 /* This is the parent fork. */
1190 state->pipe = fdopen(fd, "r+");
1192 XtAppAddInput (app, fileno (state->pipe),
1193 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1194 subproc_cb, (XtPointer) state);
1198 #endif /* HAVE_FORKPTY */
1200 /* don't mess up controlling terminal if someone dumbly does
1201 "-pipe -program tcsh". */
1204 if ((state->pipe = popen (program, "r")))
1207 XtAppAddInput (app, fileno (state->pipe),
1208 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1209 subproc_cb, (XtPointer) state);
1213 sprintf (buf, "%.100s: %.100s", progname, program);
1223 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1225 p_state *state = (p_state *) closure;
1226 launch_text_generator (state);
1231 drain_input (p_state *state)
1233 if (state->input_available_p)
1236 int n = read (fileno (state->pipe), (void *) s, 1);
1239 print_char (state, s[0]);
1243 XtRemoveInput (state->pipe_id);
1247 waitpid(state->pid, NULL, 0);
1248 fclose (state->pipe);
1252 pclose (state->pipe);
1256 if (state->cursor_x != 0) /* break line if unbroken */
1257 print_char (state, '\n'); /* blank line */
1258 print_char (state, '\n');
1260 /* Set up a timer to re-launch the subproc in a bit. */
1261 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1262 relaunch_generator_timer,
1266 state->input_available_p = False;
1271 /* The interpretation of the ModN modifiers is dependent on what keys
1272 are bound to them: Mod1 does not necessarily mean "meta". It only
1273 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1274 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1275 aren't necessarily the same thing. Icepicks in my forehead!
1278 do_icccm_meta_key_stupidity (Display *dpy)
1280 unsigned int modbits = 0;
1282 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1283 for (i = 3; i < 8; i++)
1284 for (j = 0; j < modmap->max_keypermod; j++)
1286 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1289 if (code == 0) continue;
1290 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1291 for (k = 0; k < nsyms; k++)
1292 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1293 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1294 modbits |= (1 << i);
1297 XFreeModifiermap (modmap);
1301 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1304 meta_modifier (Display *dpy)
1306 static Bool done_once = False;
1307 static unsigned int mask = 0;
1310 /* Really, we are supposed to recompute this if a KeymapNotify
1311 event comes in, but fuck it. */
1313 mask = do_icccm_meta_key_stupidity (dpy);
1320 handle_events (p_state *state)
1322 XSync (state->dpy, False);
1323 while (XPending (state->dpy))
1326 XNextEvent (state->dpy, &event);
1328 if (event.xany.type == ConfigureNotify)
1330 resize_grid (state);
1332 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1335 /* Tell the sub-process that the screen size has changed. */
1337 ws.ws_row = state->grid_height - 1;
1338 ws.ws_col = state->grid_width - 2;
1339 ws.ws_xpixel = state->xgwa.width;
1340 ws.ws_ypixel = state->xgwa.height;
1341 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1342 kill (state->pid, SIGWINCH);
1344 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1346 else if (event.xany.type == Expose)
1348 update_display (state, False);
1350 else if (event.xany.type == KeyPress)
1353 unsigned char c = 0;
1354 XLookupString (&event.xkey, (char *) &c, 1, &keysym,
1356 if (c != 0 && state->pipe)
1358 if (!state->swap_bs_del_p) ;
1359 else if (c == 127) c = 8;
1360 else if (c == 8) c = 127;
1362 /* If meta was held down, send ESC, or turn on the high bit. */
1363 if (event.xkey.state & meta_modifier (state->dpy))
1365 if (state->meta_sends_esc_p)
1366 fputc ('\033', state->pipe);
1371 fputc (c, state->pipe);
1372 fflush (state->pipe);
1373 event.xany.type = 0; /* don't interpret this event defaultly. */
1377 screenhack_handle_event (state->dpy, &event);
1380 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1381 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1386 char *progclass = "Phosphor";
1388 char *defaults [] = {
1389 ".background: Black",
1390 ".foreground: Green",
1391 "*fadeForeground: DarkGreen",
1392 "*flareForeground: White",
1398 "*program: xscreensaver-text --cols %d",
1400 "*metaSendsESC: True",
1404 #else /* !HAVE_FORKPTY */
1406 #endif /* !HAVE_FORKPTY */
1410 XrmOptionDescRec options [] = {
1411 { "-font", ".font", XrmoptionSepArg, 0 },
1412 { "-scale", ".scale", XrmoptionSepArg, 0 },
1413 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1414 { "-delay", ".delay", XrmoptionSepArg, 0 },
1415 { "-program", ".program", XrmoptionSepArg, 0 },
1416 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1417 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1418 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1419 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1420 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1421 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1427 screenhack (Display *dpy, Window window)
1429 int delay = get_integer_resource ("delay", "Integer");
1430 p_state *state = init_phosphor (dpy, window);
1436 run_phosphor (state);
1438 handle_events (state);
1439 if (delay) usleep (delay);