1 /* xscreensaver, Copyright (c) 1999-2011 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>
18 #endif /* HAVE_CONFIG_H */
25 # define XK_MISCELLANY
26 # include <X11/keysymdef.h>
27 # include <X11/Xatom.h>
28 # include <X11/Intrinsic.h>
33 # include <fcntl.h> /* for O_RDWR */
37 # include <sys/ioctl.h>
44 #endif /* HAVE_FORKPTY */
46 #include "screenhack.h"
50 #define MAX(a,b) ((a)>(b)?(a):(b))
51 #define MIN(a,b) ((a)<(b)?(a):(b))
57 #define STATE_MAX FADE
59 #define CURSOR_INDEX 128
66 # include "images/6x10font.xbm"
67 #endif /* BUILTIN_FONT */
75 #endif /* FUZZY_BORDER */
88 XWindowAttributes xgwa;
91 int grid_width, grid_height;
92 int char_width, char_height;
108 #endif /* FUZZY_BORDER */
112 int cursor_x, cursor_y;
113 XtIntervalId cursor_timer;
114 XtIntervalId pipe_timer;
119 Bool input_available_p;
120 Time subproc_relaunch_delay;
121 XComposeStatus compose;
122 Bool meta_sends_esc_p;
130 unsigned int meta_mask;
135 static void capture_font_bits (p_state *state);
136 static p_char *make_character (p_state *state, int c);
137 static void drain_input (p_state *state);
138 static void char_to_pixmap (p_state *state, p_char *pc, int c);
139 static void launch_text_generator (p_state *state);
142 /* About font metrics:
144 "lbearing" is the distance from the leftmost pixel of a character to
145 the logical origin of that character. That is, it is the number of
146 pixels of the character which are to the left of its logical origin.
148 "rbearing" is the distance from the logical origin of a character to
149 the rightmost pixel of a character. That is, it is the number of
150 pixels of the character to the right of its logical origin.
152 "descent" is the distance from the bottommost pixel of a character to
153 the logical baseline. That is, it is the number of pixels of the
154 character which are below the baseline.
156 "ascent" is the distance from the logical baseline to the topmost pixel.
157 That is, it is the number of pixels of the character above the baseline.
159 Therefore, the bounding box of the "ink" of a character is
160 lbearing + rbearing by ascent + descent;
162 "width" is the distance from the logical origin of this character to
163 the position where the logical orgin of the next character should be
166 For our purposes, we're only interested in the part of the character
167 lying inside the "width" box. If the characters have ink outside of
168 that box (the "charcell" box) then we're going to lose it. Alas.
172 static void clear (p_state *);
173 static void set_cursor (p_state *, Bool on);
176 phosphor_init (Display *dpy, Window window)
180 p_state *state = (p_state *) calloc (sizeof(*state), 1);
181 char *fontname = get_string_resource (dpy, "font", "Font");
185 state->window = window;
187 XGetWindowAttributes (dpy, window, &state->xgwa);
188 /* XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
190 state->delay = get_integer_resource (dpy, "delay", "Integer");
191 state->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
192 state->swap_bs_del_p = get_boolean_resource (dpy, "swapBSDEL", "Boolean");
194 if (!strcasecmp (fontname, "builtin") ||
195 !strcasecmp (fontname, "(builtin)"))
198 fprintf (stderr, "%s: no builtin font\n", progname);
199 state->font = XLoadQueryFont (dpy, "fixed");
200 #endif /* !BUILTIN_FONT */
204 state->font = XLoadQueryFont (dpy, fontname);
208 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
209 state->font = XLoadQueryFont (dpy, "fixed");
213 fprintf(stderr, "couldn't load font \"fixed\"");
219 state->scale = get_integer_resource (dpy, "scale", "Integer");
220 state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
224 char *s = get_string_resource (dpy, "mode", "Integer");
226 if (!s || !*s || !strcasecmp (s, "pipe"))
228 else if (!strcasecmp (s, "pty"))
231 fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
235 fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
238 #endif /* HAVE_FORKPTY */
242 for (i = 0; i < font->n_properties; i++)
243 if (font->properties[i].name == XA_FONT)
244 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
247 state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
248 state->subproc_relaunch_delay =
249 (1000 * get_integer_resource (dpy, "relaunch", "Time"));
254 state->char_width = (font6x10_width / 256) - 1;
255 state->char_height = font6x10_height;
258 # endif /* BUILTIN_FONT */
260 state->char_width = font->max_bounds.width;
261 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
264 state->program = get_string_resource (dpy, "program", "Program");
267 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
269 const char *s = getenv ("XSCREENSAVER_STANDALONE");
270 if (s && *s && strcmp(s, "0"))
273 state->program = getenv ("SHELL");
278 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
279 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
280 state->cells = (p_cell *) calloc (sizeof(p_cell),
281 state->grid_width * state->grid_height);
282 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
284 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
287 int ncolors = MAX (0, state->ticks - 3);
288 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
290 double s1, s2, v1, v2;
292 unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
293 "foreground", "Foreground");
294 unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
295 "background", "Background");
296 unsigned long flare = get_pixel_resource (state->dpy,state->xgwa.colormap,
297 "flareForeground", "Foreground");
298 unsigned long fade = get_pixel_resource (state->dpy,state->xgwa.colormap,
299 "fadeForeground", "Foreground");
304 XQueryColor (state->dpy, state->xgwa.colormap, &start);
307 XQueryColor (state->dpy, state->xgwa.colormap, &end);
309 /* Now allocate a ramp of colors from the main color to the background. */
310 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
311 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
312 make_color_ramp (state->dpy, state->xgwa.colormap,
318 /* Adjust to the number of colors we actually got. */
319 state->ticks = ncolors + STATE_MAX;
321 /* Now, GCs all around.
323 state->gcv.font = (font ? font->fid : 0);
324 state->gcv.cap_style = CapRound;
326 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
327 if (state->gcv.line_width == state->scale)
328 state->gcv.line_width++;
329 #else /* !FUZZY_BORDER */
330 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
331 if (state->gcv.line_width >= state->scale)
332 state->gcv.line_width = state->scale - 1;
333 if (state->gcv.line_width < 1)
334 state->gcv.line_width = 1;
335 #endif /* !FUZZY_BORDER */
337 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
339 state->gcv.background = bg;
340 state->gcv.foreground = bg;
341 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
344 state->gcv.foreground = flare;
345 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
348 state->gcv.foreground = fg;
349 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
352 for (i = 0; i < ncolors; i++)
354 state->gcv.foreground = colors[i].pixel;
355 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
360 capture_font_bits (state);
362 set_cursor (state, True);
364 launch_text_generator (state);
371 /* Re-query the window size and update the internal character grid if changed.
374 resize_grid (p_state *state)
376 int ow = state->grid_width;
377 int oh = state->grid_height;
378 p_cell *ocells = state->cells;
381 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
383 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
384 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
386 if (ow == state->grid_width &&
387 oh == state->grid_height)
390 state->cells = (p_cell *) calloc (sizeof(p_cell),
391 state->grid_width * state->grid_height);
393 for (y = 0; y < state->grid_height; y++)
395 for (x = 0; x < state->grid_width; x++)
397 p_cell *ncell = &state->cells [state->grid_width * y + x];
398 if (x < ow && y < oh)
399 *ncell = ocells [ow * y + x];
400 ncell->changed = True;
404 if (state->cursor_x >= state->grid_width)
405 state->cursor_x = state->grid_width-1;
406 if (state->cursor_y >= state->grid_height)
407 state->cursor_y = state->grid_height-1;
415 capture_font_bits (p_state *state)
417 XFontStruct *font = state->font;
418 int safe_width, height;
419 unsigned char string[257];
428 safe_width = state->char_width + 1;
429 height = state->char_height;
430 p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
431 (char *) font6x10_bits,
437 # endif /* BUILTIN_FONT */
439 safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
440 height = state->char_height;
443 p = XCreatePixmap (state->dpy, state->window,
444 (safe_width * 256), height, 1);
446 for (i = 0; i < 256; i++)
447 string[i] = (unsigned char) i;
450 state->gcv.foreground = 0;
451 state->gcv.background = 0;
452 state->gc0 = XCreateGC (state->dpy, p,
453 (GCForeground | GCBackground),
456 state->gcv.foreground = 1;
457 state->gc1 = XCreateGC (state->dpy, p,
458 ((font ? GCFont : 0) |
459 GCForeground | GCBackground |
460 GCCapStyle | GCLineWidth),
464 jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
465 jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
470 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
471 if (state->gcv.line_width >= state->scale)
472 state->gcv.line_width = state->scale - 1;
473 if (state->gcv.line_width < 1)
474 state->gcv.line_width = 1;
475 state->gc2 = XCreateGC (state->dpy, p,
476 ((font ? GCFont : 0) |
477 GCForeground | GCBackground |
478 GCCapStyle | GCLineWidth),
481 #endif /* FUZZY_BORDER */
483 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
488 XCopyPlane (state->dpy, p2, p, state->gc1,
489 0, 0, font6x10_width, font6x10_height,
491 XFreePixmap (state->dpy, p2);
494 # endif /* BUILTIN_FONT */
496 for (i = 0; i < 256; i++)
498 if (string[i] < font->min_char_or_byte2 ||
499 string[i] > font->max_char_or_byte2)
501 XDrawString (state->dpy, p, state->gc1,
502 i * safe_width, font->ascent,
503 (char *) (string + i), 1);
507 /* Draw the cursor. */
508 XFillRectangle (state->dpy, p, state->gc1,
509 (CURSOR_INDEX * safe_width), 1,
512 ? font->per_char['n'-font->min_char_or_byte2].width
513 : font->max_bounds.width)
514 : state->char_width),
517 : state->char_height));
519 state->font_bits = XGetImage (state->dpy, p, 0, 0,
520 (safe_width * 256), height, ~0L, XYPixmap);
521 XFreePixmap (state->dpy, p);
523 for (i = 0; i < 256; i++)
524 state->chars[i] = make_character (state, i);
525 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
530 make_character (p_state *state, int c)
532 p_char *pc = (p_char *) malloc (sizeof (*pc));
533 pc->name = (unsigned char) c;
534 pc->width = state->scale * state->char_width;
535 pc->height = state->scale * state->char_height;
536 char_to_pixmap (state, pc, c);
542 char_to_pixmap (p_state *state, p_char *pc, int c)
549 #endif /* FUZZY_BORDER */
553 XFontStruct *font = state->font;
554 int safe_width = (font
555 ? font->max_bounds.rbearing - font->min_bounds.lbearing
556 : state->char_width + 1);
558 int width = state->scale * state->char_width;
559 int height = state->scale * state->char_height;
561 if (font && (c < font->min_char_or_byte2 ||
562 c > font->max_char_or_byte2))
566 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
567 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
570 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
571 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
572 #endif /* FUZZY_BORDER */
574 from = safe_width * c;
575 to = safe_width * (c + 1);
578 if (c > 75 && c < 150)
580 printf ("\n=========== %d (%c)\n", c, c);
581 for (y = 0; y < state->char_height; y++)
583 for (x1 = from; x1 < to; x1++)
584 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
591 for (y = 0; y < state->char_height; y++)
592 for (x1 = from; x1 < to; x1++)
593 if (XGetPixel (state->font_bits, x1, y))
595 int xoff = state->scale / 2;
597 for (x2 = x1; x2 < to; x2++)
598 if (!XGetPixel (state->font_bits, x2, y))
601 XDrawLine (state->dpy, p, gc,
602 (x1 - from) * state->scale + xoff, y * state->scale,
603 (x2 - from) * state->scale + xoff, y * state->scale);
605 XDrawLine (state->dpy, p2, gc2,
606 (x1 - from) * state->scale + xoff, y * state->scale,
607 (x2 - from) * state->scale + xoff, y * state->scale);
608 #endif /* FUZZY_BORDER */
613 /* if (pc->blank_p && c == CURSOR_INDEX)
620 #endif /* FUZZY_BORDER */
624 /* Managing the display.
627 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
628 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
631 set_cursor_1 (p_state *state, Bool on)
633 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
635 p_char *cursor = state->chars[CURSOR_INDEX];
636 int new_state = (on ? NORMAL : FADE);
638 if (cell->p_char != cursor)
639 cell->changed = True;
641 if (cell->state != new_state)
642 cell->changed = True;
644 cell->p_char = cursor;
645 cell->state = new_state;
646 return cell->changed;
650 set_cursor (p_state *state, Bool on)
652 if (set_cursor_1 (state, on))
654 if (state->cursor_timer)
655 XtRemoveTimeOut (state->cursor_timer);
656 state->cursor_timer = 0;
657 cursor_on_timer (state, 0);
665 cursor_off_timer (XtPointer closure, XtIntervalId *id)
667 p_state *state = (p_state *) closure;
668 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
669 set_cursor_1 (state, False);
670 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
671 cursor_on_timer, closure);
675 cursor_on_timer (XtPointer closure, XtIntervalId *id)
677 p_state *state = (p_state *) closure;
678 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
679 set_cursor_1 (state, True);
680 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
681 cursor_off_timer, closure);
686 clear (p_state *state)
691 for (y = 0; y < state->grid_height; y++)
692 for (x = 0; x < state->grid_width; x++)
694 p_cell *cell = &state->cells[state->grid_width * y + x];
695 if (cell->state == FLARE || cell->state == NORMAL)
698 cell->changed = True;
701 set_cursor (state, True);
706 decay (p_state *state)
709 for (y = 0; y < state->grid_height; y++)
710 for (x = 0; x < state->grid_width; x++)
712 p_cell *cell = &state->cells[state->grid_width * y + x];
713 if (cell->state == FLARE)
715 cell->state = NORMAL;
716 cell->changed = True;
718 else if (cell->state >= FADE)
721 if (cell->state >= state->ticks)
723 cell->changed = True;
730 scroll (p_state *state)
734 for (x = 0; x < state->grid_width; x++)
736 p_cell *from = 0, *to = 0;
737 for (y = 1; y < state->grid_height; y++)
739 from = &state->cells[state->grid_width * y + x];
740 to = &state->cells[state->grid_width * (y-1) + x];
742 if ((from->state == FLARE || from->state == NORMAL) &&
743 !from->p_char->blank_p)
746 to->state = NORMAL; /* should be FLARE? Looks bad... */
750 if (to->state == FLARE || to->state == NORMAL)
758 if (to && (to->state == FLARE || to->state == NORMAL))
764 set_cursor (state, True);
769 print_char (p_state *state, int c)
771 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
775 /* Start the cursor fading (in case we don't end up overwriting it.) */
776 if (cell->state == FLARE || cell->state == NORMAL)
779 cell->changed = True;
782 if (state->pid) /* Only interpret VT100 sequences if running in pty-mode.
783 It would be nice if we could just interpret them all
784 the time, but that would require subprocesses to send
785 CRLF line endings instead of bare LF, so that's no good.
788 switch (state->escstate)
794 /* Dummy case - we don't want the screensaver to beep */
795 /* #### But maybe this should flash the screen? */
798 if (state->cursor_x > 0)
802 if (state->cursor_x < state->grid_width - 8)
804 state->cursor_x = (state->cursor_x & ~7) + 8;
809 if (state->cursor_y < state->grid_height - 1)
818 if(state->last_c == 13)
820 cell->state = NORMAL;
821 cell->p_char = state->chars[state->bk];
822 cell->changed = True;
824 if (state->cursor_y < state->grid_height - 1)
831 cell = &state->cells[state->grid_width * state->cursor_y];
832 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
835 state->bk = cell->p_char->name;
839 /* Dummy case - I don't want to load several fonts for
840 the maybe two programs world-wide that use that */
844 /* Dummy case - these interrupt escape sequences, so
845 they don't do anything in this state */
851 /* Dummy case - this is supposed to be ignored */
855 for(i = 0; i < NPAR; i++)
856 state->csiparam[i] = 0;
861 cell->p_char = state->chars[c];
862 cell->changed = True;
865 if (c != ' ' && cell->p_char->blank_p)
866 cell->p_char = state->chars[CURSOR_INDEX];
868 if (state->cursor_x >= state->grid_width - 1)
871 if (state->cursor_y >= state->grid_height - 1)
886 case 'c': /* Reset */
890 case 'D': /* Linefeed */
891 if (state->cursor_y < state->grid_height - 1)
897 case 'E': /* Newline */
901 case 'M': /* Reverse newline */
902 if (state->cursor_y > 0)
906 case '7': /* Save state */
907 state->saved_x = state->cursor_x;
908 state->saved_y = state->cursor_y;
911 case '8': /* Restore state */
912 state->cursor_x = state->saved_x;
913 state->cursor_y = state->saved_y;
918 for(i = 0; i < NPAR; i++)
919 state->csiparam[i] = 0;
922 case '%': /* Select charset */
923 /* No, I don't support UTF-8, since the phosphor font
924 isn't even Unicode anyway. We must still catch the
925 last byte, though. */
928 /* I don't support different fonts either - see above
933 /* Escape sequences not supported:
936 * Z - Terminal identification
938 * = - Other keypad change
952 case '0': case '1': case '2': case '3': case '4':
953 case '5': case '6': case '7': case '8': case '9':
954 if (state->curparam < NPAR)
955 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
958 state->csiparam[++state->curparam] = 0;
964 for (i = 0; i < state->csiparam[0]; i++)
966 if(++state->cursor_x > state->grid_width)
969 if (state->cursor_y < state->grid_height - 1)
974 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
975 if (cell->state == FLARE || cell->state == NORMAL)
978 cell->changed = True;
986 if (state->csiparam[0] == 0)
987 state->csiparam[0] = 1;
988 if ((state->cursor_y -= state->csiparam[0]) < 0)
996 if (state->csiparam[0] == 0)
997 state->csiparam[0] = 1;
998 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
999 state->cursor_y = state->grid_height - 1;
1000 state->escstate = 0;
1004 if (state->csiparam[0] == 0)
1005 state->csiparam[0] = 1;
1006 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
1007 state->cursor_x = state->grid_width - 1;
1008 state->escstate = 0;
1011 if (state->csiparam[0] == 0)
1012 state->csiparam[0] = 1;
1013 if ((state->cursor_x -= state->csiparam[0]) < 0)
1014 state->cursor_x = 0;
1015 state->escstate = 0;
1018 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1019 state->cursor_y = state->grid_height - 1;
1020 state->escstate = 0;
1024 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
1025 state->cursor_x = state->grid_width - 1;
1026 state->escstate = 0;
1030 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1031 state->cursor_y = state->grid_height - 1;
1032 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
1033 state->cursor_x = state->grid_width - 1;
1034 if(state->cursor_y < 0)
1035 state->cursor_y = 0;
1036 if(state->cursor_x < 0)
1037 state->cursor_x = 0;
1038 state->escstate = 0;
1042 end = state->grid_height * state->grid_width;
1043 if (state->csiparam[0] == 0)
1044 start = state->grid_width * state->cursor_y + state->cursor_x;
1045 if (state->csiparam[0] == 1)
1046 end = state->grid_width * state->cursor_y + state->cursor_x;
1047 for (i = start; i < end; i++)
1049 cell = &state->cells[i];
1050 if (cell->state == FLARE || cell->state == NORMAL)
1053 cell->changed = True;
1056 set_cursor (state, True);
1057 state->escstate = 0;
1061 end = state->grid_width;
1062 if (state->csiparam[0] == 0)
1063 start = state->cursor_x;
1064 if (state->csiparam[1] == 1)
1065 end = state->cursor_x;
1066 for (i = start; i < end; i++)
1068 if (cell->state == FLARE || cell->state == NORMAL)
1071 cell->changed = True;
1075 state->escstate = 0;
1077 case 's': /* Save position */
1078 state->saved_x = state->cursor_x;
1079 state->saved_y = state->cursor_y;
1080 state->escstate = 0;
1082 case 'u': /* Restore position */
1083 state->cursor_x = state->saved_x;
1084 state->cursor_y = state->saved_y;
1085 state->escstate = 0;
1087 case '?': /* DEC Private modes */
1088 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1089 state->escstate = 0;
1092 /* Known unsupported CSIs:
1094 * L - Insert blank lines
1095 * M - Delete lines (I don't know what this means...)
1096 * P - Delete characters
1097 * X - Erase characters (difference with P being...?)
1098 * c - Terminal identification
1099 * g - Clear tab stop(s)
1100 * h - Set mode (Mainly due to its complexity and lack of good
1103 * m - Set mode (Phosphor is, per defenition, green on black)
1105 * q - Set keyboard LEDs
1106 * r - Set scrolling region (too exhausting - noone uses this,
1109 state->escstate = 0;
1114 state->escstate = 0;
1117 set_cursor (state, True);
1121 if (c == '\t') c = ' '; /* blah. */
1123 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1125 if (c == '\n' && state->last_c == '\r')
1126 ; /* CRLF -- do nothing */
1129 state->cursor_x = 0;
1130 if (state->cursor_y == state->grid_height - 1)
1136 else if (c == '\014')
1142 cell->state = FLARE;
1143 cell->p_char = state->chars[c];
1144 cell->changed = True;
1147 if (c != ' ' && cell->p_char->blank_p)
1148 cell->p_char = state->chars[CURSOR_INDEX];
1150 if (state->cursor_x >= state->grid_width - 1)
1152 state->cursor_x = 0;
1153 if (state->cursor_y >= state->grid_height - 1)
1159 set_cursor (state, True);
1167 update_display (p_state *state, Bool changed_only)
1171 for (y = 0; y < state->grid_height; y++)
1172 for (x = 0; x < state->grid_width; x++)
1174 p_cell *cell = &state->cells[state->grid_width * y + x];
1175 int width, height, tx, ty;
1177 if (changed_only && !cell->changed)
1180 width = state->char_width * state->scale;
1181 height = state->char_height * state->scale;
1185 if (cell->state == BLANK || cell->p_char->blank_p)
1187 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1188 tx, ty, width, height);
1193 GC gc1 = state->gcs[cell->state];
1194 GC gc2 = ((cell->state + 2) < state->ticks
1195 ? state->gcs[cell->state + 2]
1197 GC gc3 = (gc2 ? gc2 : gc1);
1199 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1200 0, 0, width, height, tx, ty, 1L);
1203 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1204 XSetClipOrigin (state->dpy, gc1, tx, ty);
1205 XFillRectangle (state->dpy, state->window, gc1,
1206 tx, ty, width, height);
1207 XSetClipMask (state->dpy, gc1, None);
1209 #else /* !FUZZY_BORDER */
1211 XCopyPlane (state->dpy,
1212 cell->p_char->pixmap, state->window,
1213 state->gcs[cell->state],
1214 0, 0, width, height, tx, ty, 1L);
1216 #endif /* !FUZZY_BORDER */
1219 cell->changed = False;
1224 static unsigned long
1225 phosphor_draw (Display *dpy, Window window, void *closure)
1227 p_state *state = (p_state *) closure;
1228 update_display (state, True);
1230 drain_input (state);
1231 return state->delay;
1239 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1241 p_state *state = (p_state *) closure;
1242 state->input_available_p = True;
1247 launch_text_generator (p_state *state)
1249 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1251 const char *oprogram = state->program;
1252 char *program = (char *) malloc (strlen (oprogram) + 50);
1254 strcpy (program, "( ");
1255 strcat (program, oprogram);
1257 /* Kludge! Special-case "xscreensaver-text" to tell it how wide
1258 the screen is. We used to do this by just always feeding
1259 `program' through sprintf() and setting the default value to
1260 "xscreensaver-text --cols %d", but that makes things blow up
1261 if someone ever uses a --program that includes a % anywhere.
1263 if (!strcmp (oprogram, "xscreensaver-text"))
1264 sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
1266 strcat (program, " ) 2>&1");
1269 if(state->mode == 1)
1274 ws.ws_row = state->grid_height - 1;
1275 ws.ws_col = state->grid_width - 2;
1276 ws.ws_xpixel = state->xgwa.width;
1277 ws.ws_ypixel = state->xgwa.height;
1280 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1282 /* Unable to fork */
1283 sprintf (buf, "%.100s: forkpty", progname);
1286 else if(!state->pid)
1288 /* This is the child fork. */
1291 if (putenv("TERM=vt100"))
1293 av[i++] = "/bin/sh";
1298 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1304 /* This is the parent fork. */
1305 state->pipe = fdopen(fd, "r+");
1307 XtAppAddInput (app, fileno (state->pipe),
1308 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1309 subproc_cb, (XtPointer) state);
1313 #endif /* HAVE_FORKPTY */
1315 /* don't mess up controlling terminal if someone dumbly does
1316 "-pipe -program tcsh". */
1317 static int protected_stdin_p = 0;
1318 if (! protected_stdin_p) {
1320 open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
1321 protected_stdin_p = 1;
1324 if ((state->pipe = popen (program, "r")))
1327 XtAppAddInput (app, fileno (state->pipe),
1328 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1329 subproc_cb, (XtPointer) state);
1333 sprintf (buf, "%.100s: %.100s", progname, program);
1343 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1345 p_state *state = (p_state *) closure;
1346 /* if (!state->pipe_timer) abort(); */
1347 state->pipe_timer = 0;
1348 launch_text_generator (state);
1353 drain_input (p_state *state)
1355 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1356 if (state->input_available_p && state->pipe)
1359 int n = read (fileno (state->pipe), (void *) s, 1);
1363 print_char (state, s[0]);
1367 XtRemoveInput (state->pipe_id);
1371 waitpid(state->pid, NULL, 0);
1372 fclose (state->pipe);
1377 pclose (state->pipe);
1381 if (state->cursor_x != 0) { /* break line if unbroken */
1382 print_char (state, '\r');
1383 print_char (state, '\n');
1385 print_char (state, '\r'); /* blank line */
1386 print_char (state, '\n');
1388 /* Set up a timer to re-launch the subproc in a bit. */
1390 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1391 relaunch_generator_timer,
1395 state->input_available_p = False;
1400 /* The interpretation of the ModN modifiers is dependent on what keys
1401 are bound to them: Mod1 does not necessarily mean "meta". It only
1402 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1403 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1404 aren't necessarily the same thing. Icepicks in my forehead!
1407 do_icccm_meta_key_stupidity (Display *dpy)
1409 unsigned int modbits = 0;
1412 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1413 for (i = 3; i < 8; i++)
1414 for (j = 0; j < modmap->max_keypermod; j++)
1416 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1419 if (code == 0) continue;
1420 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1421 for (k = 0; k < nsyms; k++)
1422 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1423 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1424 modbits |= (1 << i);
1427 XFreeModifiermap (modmap);
1428 # endif /* HAVE_COCOA */
1432 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1435 meta_modifier (p_state *state)
1437 if (!state->meta_done_once)
1439 /* Really, we are supposed to recompute this if a KeymapNotify
1440 event comes in, but fuck it. */
1441 state->meta_done_once = True;
1442 state->meta_mask = do_icccm_meta_key_stupidity (state->dpy);
1444 return state->meta_mask;
1449 phosphor_reshape (Display *dpy, Window window, void *closure,
1450 unsigned int w, unsigned int h)
1452 p_state *state = (p_state *) closure;
1453 Bool changed_p = resize_grid (state);
1455 if (! changed_p) return;
1457 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1458 if (state->pid && state->pipe)
1460 /* Tell the sub-process that the screen size has changed. */
1462 ws.ws_row = state->grid_height - 1;
1463 ws.ws_col = state->grid_width - 2;
1464 ws.ws_xpixel = state->xgwa.width;
1465 ws.ws_ypixel = state->xgwa.height;
1466 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1467 kill (state->pid, SIGWINCH);
1469 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1472 /* If we're running xscreensaver-text, then kill and restart it any
1473 time the window is resized so that it gets an updated --cols arg
1474 right away. But if we're running something else, leave it alone.
1476 if (!strcmp (state->program, "xscreensaver-text"))
1479 kill (state->pid, SIGTERM);
1481 pclose (state->pipe);
1482 state->input_available_p = False;
1483 relaunch_generator_timer (state, 0);
1489 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1491 p_state *state = (p_state *) closure;
1493 if (event->xany.type == Expose)
1494 update_display (state, False);
1495 else if (event->xany.type == KeyPress)
1498 unsigned char c = 0;
1499 XLookupString (&event->xkey, (char *) &c, 1, &keysym,
1501 if (c != 0 && state->pipe)
1503 if (!state->swap_bs_del_p) ;
1504 else if (c == 127) c = 8;
1505 else if (c == 8) c = 127;
1507 /* If meta was held down, send ESC, or turn on the high bit. */
1508 if (event->xkey.state & meta_modifier (state))
1510 if (state->meta_sends_esc_p)
1511 fputc ('\033', state->pipe);
1516 fputc (c, state->pipe);
1517 fflush (state->pipe);
1518 event->xany.type = 0; /* don't interpret this event defaultly. */
1527 phosphor_free (Display *dpy, Window window, void *closure)
1529 p_state *state = (p_state *) closure;
1532 XtRemoveInput (state->pipe_id);
1534 pclose (state->pipe);
1535 if (state->cursor_timer)
1536 XtRemoveTimeOut (state->cursor_timer);
1537 if (state->pipe_timer)
1538 XtRemoveTimeOut (state->pipe_timer);
1540 /* #### there's more to free here */
1547 static const char *phosphor_defaults [] = {
1548 ".background: Black",
1549 ".foreground: #00FF00",
1551 "*fadeForeground: #006400",
1552 "*flareForeground: #FFFFFF",
1553 #if defined(BUILTIN_FONT)
1555 #elif defined(HAVE_COCOA)
1564 "*program: xscreensaver-text",
1566 "*metaSendsESC: True",
1570 #else /* !HAVE_FORKPTY */
1572 #endif /* !HAVE_FORKPTY */
1576 static XrmOptionDescRec phosphor_options [] = {
1577 { "-font", ".font", XrmoptionSepArg, 0 },
1578 { "-scale", ".scale", XrmoptionSepArg, 0 },
1579 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1580 { "-delay", ".delay", XrmoptionSepArg, 0 },
1581 { "-program", ".program", XrmoptionSepArg, 0 },
1582 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1583 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1584 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1585 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1586 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1587 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1592 XSCREENSAVER_MODULE ("Phosphor", phosphor)