From http://www.jwz.org/xscreensaver/xscreensaver-5.15.tar.gz
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999-2011 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  *
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>
14  */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif /* HAVE_CONFIG_H */
19
20 #include <stdio.h>
21 #include <signal.h>
22 #include <sys/wait.h>
23
24 #ifndef HAVE_COCOA
25 # define XK_MISCELLANY
26 # include <X11/keysymdef.h>
27 # include <X11/Xatom.h>
28 # include <X11/Intrinsic.h>
29 #endif
30
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 # include <fcntl.h>  /* for O_RDWR */
34 #endif
35
36 #ifdef HAVE_FORKPTY
37 # include <sys/ioctl.h>
38 # ifdef HAVE_PTY_H
39 #  include <pty.h>
40 # endif
41 # ifdef HAVE_UTIL_H
42 #  include <util.h>
43 # endif
44 #endif /* HAVE_FORKPTY */
45
46 #include "screenhack.h"
47
48 #define FUZZY_BORDER
49
50 #define MAX(a,b) ((a)>(b)?(a):(b))
51 #define MIN(a,b) ((a)<(b)?(a):(b))
52
53 #define BLANK  0
54 #define FLARE  1
55 #define NORMAL 2
56 #define FADE   3
57 #define STATE_MAX FADE
58
59 #define CURSOR_INDEX 128
60
61 #define NPAR 16
62
63 #define BUILTIN_FONT
64
65 #ifdef BUILTIN_FONT
66 # include "images/6x10font.xbm"
67 #endif /* BUILTIN_FONT */
68
69 typedef struct {
70   unsigned char name;
71   int width, height;
72   Pixmap pixmap;
73 #ifdef FUZZY_BORDER
74   Pixmap pixmap2;
75 #endif /* FUZZY_BORDER */
76   Bool blank_p;
77 } p_char;
78
79 typedef struct {
80   p_char *p_char;
81   int state;
82   Bool changed;
83 } p_cell;
84
85 typedef struct {
86   Display *dpy;
87   Window window;
88   XWindowAttributes xgwa;
89   XFontStruct *font;
90   const char *program;
91   int grid_width, grid_height;
92   int char_width, char_height;
93   int saved_x, saved_y;
94   int scale;
95   int ticks;
96   int mode;
97   pid_t pid;
98   int escstate;
99   int csiparam[NPAR];
100   int curparam;
101   p_char **chars;
102   p_cell *cells;
103   XGCValues gcv;
104   GC gc0;
105   GC gc1;
106 #ifdef FUZZY_BORDER
107   GC gc2;
108 #endif /* FUZZY_BORDER */
109   GC *gcs;
110   XImage *font_bits;
111
112   int cursor_x, cursor_y;
113   XtIntervalId cursor_timer;
114   XtIntervalId pipe_timer;
115   Time cursor_blink;
116
117   FILE *pipe;
118   XtInputId pipe_id;
119   Bool input_available_p;
120   Time subproc_relaunch_delay;
121   XComposeStatus compose;
122   Bool meta_sends_esc_p;
123   Bool swap_bs_del_p;
124   int delay;
125
126   char last_c;
127   int bk;
128
129   Bool meta_done_once;
130   unsigned int meta_mask;
131
132 } p_state;
133
134
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);
140
141
142 /* About font metrics:
143
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.
147
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.
151
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.
155
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.
158
159    Therefore, the bounding box of the "ink" of a character is
160    lbearing + rbearing by ascent + descent;
161
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
164    placed.
165
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.
169  */
170
171
172 static void clear (p_state *);
173 static void set_cursor (p_state *, Bool on);
174
175 static void *
176 phosphor_init (Display *dpy, Window window)
177 {
178   int i;
179   unsigned long flags;
180   p_state *state = (p_state *) calloc (sizeof(*state), 1);
181   char *fontname = get_string_resource (dpy, "font", "Font");
182   XFontStruct *font;
183
184   state->dpy = dpy;
185   state->window = window;
186
187   XGetWindowAttributes (dpy, window, &state->xgwa);
188 /*  XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
189
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");
193
194   if (!strcasecmp (fontname, "builtin") ||
195       !strcasecmp (fontname, "(builtin)"))
196     {
197 #ifndef BUILTIN_FONT
198       fprintf (stderr, "%s: no builtin font\n", progname);
199       state->font = XLoadQueryFont (dpy, "fixed");
200 #endif /* !BUILTIN_FONT */
201     }
202   else
203     {
204       state->font = XLoadQueryFont (dpy, fontname);
205
206       if (!state->font)
207         {
208           fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
209           state->font = XLoadQueryFont (dpy, "fixed");
210         }
211       if (!state->font)
212         {
213           fprintf(stderr, "couldn't load font \"fixed\"");
214           exit(1);
215         }
216     }
217
218   font = state->font;
219   state->scale = get_integer_resource (dpy, "scale", "Integer");
220   state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
221   state->escstate = 0;
222
223   {
224     char *s = get_string_resource (dpy, "mode", "Integer");
225     state->mode = 0;
226     if (!s || !*s || !strcasecmp (s, "pipe"))
227       state->mode = 0;
228     else if (!strcasecmp (s, "pty"))
229       state->mode = 1;
230     else
231       fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
232                progname, s);
233
234 #ifndef HAVE_FORKPTY
235     fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
236              progname);
237     state->mode = 0;
238 #endif /* HAVE_FORKPTY */
239   }
240
241 #if 0
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));
245 #endif /* 0 */
246
247   state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
248   state->subproc_relaunch_delay =
249     (1000 * get_integer_resource (dpy, "relaunch", "Time"));
250
251 # ifdef BUILTIN_FONT
252   if (! font)
253     {
254       state->char_width  = (font6x10_width / 256) - 1;
255       state->char_height = font6x10_height;
256     }
257   else
258 # endif /* BUILTIN_FONT */
259     {
260       state->char_width  = font->max_bounds.width;
261       state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
262     }
263
264   state->program = get_string_resource (dpy, "program", "Program");
265
266
267   /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
268   {
269     const char *s = getenv ("XSCREENSAVER_STANDALONE");
270     if (s && *s && strcmp(s, "0"))
271       {
272         state->mode = 1;
273         state->program = getenv ("SHELL");
274       }
275   }
276
277
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);
283
284   state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
285
286   {
287     int ncolors = MAX (0, state->ticks - 3);
288     XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
289     int h1, h2;
290     double s1, s2, v1, v2;
291
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");
300
301     XColor start, end;
302
303     start.pixel = fade;
304     XQueryColor (state->dpy, state->xgwa.colormap, &start);
305
306     end.pixel = bg;
307     XQueryColor (state->dpy, state->xgwa.colormap, &end);
308
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,
313                      h1, s1, v1,
314                      h2, s2, v2,
315                      colors, &ncolors,
316                      False, True, False);
317
318     /* Adjust to the number of colors we actually got. */
319     state->ticks = ncolors + STATE_MAX;
320
321     /* Now, GCs all around.
322      */
323     state->gcv.font = (font ? font->fid : 0);
324     state->gcv.cap_style = CapRound;
325 #ifdef FUZZY_BORDER
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 */
336
337     flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
338
339     state->gcv.background = bg;
340     state->gcv.foreground = bg;
341     state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
342                                    &state->gcv);
343
344     state->gcv.foreground = flare;
345     state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
346                                    &state->gcv);
347
348     state->gcv.foreground = fg;
349     state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
350                                     &state->gcv);
351
352     for (i = 0; i < ncolors; i++)
353       {
354         state->gcv.foreground = colors[i].pixel;
355         state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
356                                                flags, &state->gcv);
357       }
358   }
359
360   capture_font_bits (state);
361
362   set_cursor (state, True);
363
364   launch_text_generator (state);
365 /*  clear (state);*/
366
367   return state;
368 }
369
370
371 /* Re-query the window size and update the internal character grid if changed.
372  */
373 static Bool
374 resize_grid (p_state *state)
375 {
376   int ow = state->grid_width;
377   int oh = state->grid_height;
378   p_cell *ocells = state->cells;
379   int x, y;
380
381   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
382
383   state->grid_width = state->xgwa.width   /(state->char_width  * state->scale);
384   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
385
386   if (ow == state->grid_width &&
387       oh == state->grid_height)
388     return False;
389
390   state->cells = (p_cell *) calloc (sizeof(p_cell),
391                                     state->grid_width * state->grid_height);
392
393   for (y = 0; y < state->grid_height; y++)
394     {
395       for (x = 0; x < state->grid_width; x++)
396         {
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;
401         }
402     }
403
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;
408
409   free (ocells);
410   return True;
411 }
412
413
414 static void
415 capture_font_bits (p_state *state)
416 {
417   XFontStruct *font = state->font;
418   int safe_width, height;
419   unsigned char string[257];
420   int i;
421   Pixmap p;
422
423 # ifdef BUILTIN_FONT
424   Pixmap p2 = 0;
425
426   if (!font)
427     {
428       safe_width = state->char_width + 1;
429       height = state->char_height;
430       p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
431                                         (char *) font6x10_bits,
432                                         font6x10_width,
433                                         font6x10_height,
434                                         1, 0, 1);
435     }
436   else
437 # endif /* BUILTIN_FONT */
438     {
439       safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
440       height = state->char_height;
441     }
442
443   p = XCreatePixmap (state->dpy, state->window,
444                      (safe_width * 256), height, 1);
445
446   for (i = 0; i < 256; i++)
447     string[i] = (unsigned char) i;
448   string[256] = 0;
449
450   state->gcv.foreground = 0;
451   state->gcv.background = 0;
452   state->gc0 = XCreateGC (state->dpy, p,
453                           (GCForeground | GCBackground),
454                           &state->gcv);
455
456   state->gcv.foreground = 1;
457   state->gc1 = XCreateGC (state->dpy, p,
458                           ((font ? GCFont : 0) |
459                            GCForeground | GCBackground |
460                            GCCapStyle | GCLineWidth),
461                           &state->gcv);
462
463 #ifdef HAVE_COCOA
464   jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
465   jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
466 #endif
467
468 #ifdef FUZZY_BORDER
469   {
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),
479                             &state->gcv);
480   }
481 #endif /* FUZZY_BORDER */
482
483   XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
484
485 # ifdef BUILTIN_FONT
486   if (p2)
487     {
488       XCopyPlane (state->dpy, p2, p, state->gc1,
489                   0, 0, font6x10_width, font6x10_height, 
490                   0, 0, 1);
491       XFreePixmap (state->dpy, p2);
492     }
493   else
494 # endif /* BUILTIN_FONT */
495     {
496       for (i = 0; i < 256; i++)
497         {
498           if (string[i] < font->min_char_or_byte2 ||
499               string[i] > font->max_char_or_byte2)
500             continue;
501           XDrawString (state->dpy, p, state->gc1,
502                        i * safe_width, font->ascent,
503                        (char *) (string + i), 1);
504         }
505     }
506
507   /* Draw the cursor. */
508   XFillRectangle (state->dpy, p, state->gc1,
509                   (CURSOR_INDEX * safe_width), 1,
510                   (font
511                    ? (font->per_char
512                       ? font->per_char['n'-font->min_char_or_byte2].width
513                       : font->max_bounds.width)
514                    : state->char_width),
515                   (font
516                    ? font->ascent - 1
517                    : state->char_height));
518
519   state->font_bits = XGetImage (state->dpy, p, 0, 0,
520                                 (safe_width * 256), height, ~0L, XYPixmap);
521   XFreePixmap (state->dpy, p);
522
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);
526 }
527
528
529 static p_char *
530 make_character (p_state *state, int c)
531 {
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);
537   return pc;
538 }
539
540
541 static void
542 char_to_pixmap (p_state *state, p_char *pc, int c)
543 {
544   Pixmap p = 0;
545   GC gc;
546 #ifdef FUZZY_BORDER
547   Pixmap p2 = 0;
548   GC gc2;
549 #endif /* FUZZY_BORDER */
550   int from, to;
551   int x1, y;
552
553   XFontStruct *font = state->font;
554   int safe_width = (font 
555                     ? font->max_bounds.rbearing - font->min_bounds.lbearing
556                     : state->char_width + 1);
557
558   int width  = state->scale * state->char_width;
559   int height = state->scale * state->char_height;
560
561   if (font && (c < font->min_char_or_byte2 ||
562                c > font->max_char_or_byte2))
563     goto DONE;
564
565   gc = state->gc1;
566   p = XCreatePixmap (state->dpy, state->window, width, height, 1);
567   XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
568 #ifdef FUZZY_BORDER
569   gc2 = state->gc2;
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 */
573
574   from = safe_width * c;
575   to =   safe_width * (c + 1);
576
577 #if 0
578   if (c > 75 && c < 150)
579     {
580       printf ("\n=========== %d (%c)\n", c, c);
581       for (y = 0; y < state->char_height; y++)
582         {
583           for (x1 = from; x1 < to; x1++)
584             printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
585           printf ("\n");
586         }
587     }
588 #endif
589
590   pc->blank_p = True;
591   for (y = 0; y < state->char_height; y++)
592     for (x1 = from; x1 < to; x1++)
593       if (XGetPixel (state->font_bits, x1, y))
594         {
595           int xoff = state->scale / 2;
596           int x2;
597           for (x2 = x1; x2 < to; x2++)
598             if (!XGetPixel (state->font_bits, x2, y))
599               break;
600           x2--;
601           XDrawLine (state->dpy, p, gc,
602                      (x1 - from) * state->scale + xoff, y * state->scale,
603                      (x2 - from) * state->scale + xoff, y * state->scale);
604 #ifdef FUZZY_BORDER
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 */
609           x1 = x2;
610           pc->blank_p = False;
611         }
612
613   /*  if (pc->blank_p && c == CURSOR_INDEX)
614     abort();*/
615
616  DONE:
617   pc->pixmap = p;
618 #ifdef FUZZY_BORDER
619   pc->pixmap2 = p2;
620 #endif /* FUZZY_BORDER */
621 }
622
623 \f
624 /* Managing the display. 
625  */
626
627 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
628 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
629
630 static Bool
631 set_cursor_1 (p_state *state, Bool on)
632 {
633   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
634                               + state->cursor_x];
635   p_char *cursor = state->chars[CURSOR_INDEX];
636   int new_state = (on ? NORMAL : FADE);
637
638   if (cell->p_char != cursor)
639     cell->changed = True;
640
641   if (cell->state != new_state)
642     cell->changed = True;
643
644   cell->p_char = cursor;
645   cell->state = new_state;
646   return cell->changed;
647 }
648
649 static void
650 set_cursor (p_state *state, Bool on)
651 {
652   if (set_cursor_1 (state, on))
653     {
654       if (state->cursor_timer)
655         XtRemoveTimeOut (state->cursor_timer);
656       state->cursor_timer = 0;
657       cursor_on_timer (state, 0);
658     }
659 }
660
661
662
663
664 static void
665 cursor_off_timer (XtPointer closure, XtIntervalId *id)
666 {
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);
672 }
673
674 static void
675 cursor_on_timer (XtPointer closure, XtIntervalId *id)
676 {
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);
682 }
683
684
685 static void
686 clear (p_state *state)
687 {
688   int x, y;
689   state->cursor_x = 0;
690   state->cursor_y = 0;
691   for (y = 0; y < state->grid_height; y++)
692     for (x = 0; x < state->grid_width; x++)
693       {
694         p_cell *cell = &state->cells[state->grid_width * y + x];
695         if (cell->state == FLARE || cell->state == NORMAL)
696           {
697             cell->state = FADE;
698             cell->changed = True;
699           }
700       }
701   set_cursor (state, True);
702 }
703
704
705 static void
706 decay (p_state *state)
707 {
708   int x, y;
709   for (y = 0; y < state->grid_height; y++)
710     for (x = 0; x < state->grid_width; x++)
711       {
712         p_cell *cell = &state->cells[state->grid_width * y + x];
713         if (cell->state == FLARE)
714           {
715             cell->state = NORMAL;
716             cell->changed = True;
717           }
718         else if (cell->state >= FADE)
719           {
720             cell->state++;
721             if (cell->state >= state->ticks)
722               cell->state = BLANK;
723             cell->changed = True;
724           }
725       }
726 }
727
728
729 static void
730 scroll (p_state *state)
731 {
732   int x, y;
733
734   for (x = 0; x < state->grid_width; x++)
735     {
736       p_cell *from = 0, *to = 0;
737       for (y = 1; y < state->grid_height; y++)
738         {
739           from = &state->cells[state->grid_width * y     + x];
740           to   = &state->cells[state->grid_width * (y-1) + x];
741
742           if ((from->state == FLARE || from->state == NORMAL) &&
743               !from->p_char->blank_p)
744             {
745               *to = *from;
746               to->state = NORMAL;  /* should be FLARE?  Looks bad... */
747             }
748           else
749             {
750               if (to->state == FLARE || to->state == NORMAL)
751                 to->state = FADE;
752             }
753
754           to->changed = True;
755         }
756
757       to = from;
758       if (to && (to->state == FLARE || to->state == NORMAL))
759         {
760           to->state = FADE;
761           to->changed = True;
762         }
763     }
764   set_cursor (state, True);
765 }
766
767
768 static void
769 print_char (p_state *state, int c)
770 {
771   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
772                                + state->cursor_x];
773   int i, start, end;
774
775   /* Start the cursor fading (in case we don't end up overwriting it.) */
776   if (cell->state == FLARE || cell->state == NORMAL)
777     {
778       cell->state = FADE;
779       cell->changed = True;
780     }
781   
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.
786                     */
787     {
788       switch (state->escstate)
789         {
790         case 0:
791           switch (c)
792             {
793             case 7: /* BEL */
794               /* Dummy case - we don't want the screensaver to beep */
795               /* #### But maybe this should flash the screen? */
796               break;
797             case 8: /* BS */
798               if (state->cursor_x > 0)
799                 state->cursor_x--;
800               break;
801             case 9: /* HT */
802               if (state->cursor_x < state->grid_width - 8)
803                 {
804                   state->cursor_x = (state->cursor_x & ~7) + 8;
805                 }
806               else
807                 {
808                   state->cursor_x = 0;
809                   if (state->cursor_y < state->grid_height - 1)
810                     state->cursor_y++;
811                   else
812                     scroll (state);
813                 }
814               break;
815             case 10: /* LF */
816             case 11: /* VT */
817             case 12: /* FF */
818               if(state->last_c == 13)
819                 {
820                   cell->state = NORMAL;
821                   cell->p_char = state->chars[state->bk];
822                   cell->changed = True;
823                 }
824               if (state->cursor_y < state->grid_height - 1)
825                 state->cursor_y++;
826               else
827                 scroll (state);
828               break;
829             case 13: /* CR */
830               state->cursor_x = 0;
831               cell = &state->cells[state->grid_width * state->cursor_y];
832               if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
833                 state->bk = ' ';
834               else
835                 state->bk = cell->p_char->name;
836               break;
837             case 14: /* SO */
838             case 15: /* SI */
839               /* Dummy case - I don't want to load several fonts for
840                  the maybe two programs world-wide that use that */
841               break;
842             case 24: /* CAN */
843             case 26: /* SUB */
844               /* Dummy case - these interrupt escape sequences, so
845                  they don't do anything in this state */
846               break;
847             case 27: /* ESC */
848               state->escstate = 1;
849               break;
850             case 127: /* DEL */
851               /* Dummy case - this is supposed to be ignored */
852               break;
853             case 155: /* CSI */
854               state->escstate = 2;
855               for(i = 0; i < NPAR; i++)
856                 state->csiparam[i] = 0;
857               state->curparam = 0;
858               break;
859             default:
860               cell->state = FLARE;
861               cell->p_char = state->chars[c];
862               cell->changed = True;
863               state->cursor_x++;
864
865               if (c != ' ' && cell->p_char->blank_p)
866                 cell->p_char = state->chars[CURSOR_INDEX];
867
868               if (state->cursor_x >= state->grid_width - 1)
869                 {
870                   state->cursor_x = 0;
871                   if (state->cursor_y >= state->grid_height - 1)
872                     scroll (state);
873                   else
874                     state->cursor_y++;
875                 }
876               break;
877             }
878           break;
879         case 1:
880           switch (c)
881             {
882             case 24: /* CAN */
883             case 26: /* SUB */
884               state->escstate = 0;
885               break;
886             case 'c': /* Reset */
887               clear (state);
888               state->escstate = 0;
889               break;
890             case 'D': /* Linefeed */
891               if (state->cursor_y < state->grid_height - 1)
892                 state->cursor_y++;
893               else
894                 scroll (state);
895               state->escstate = 0;
896               break;
897             case 'E': /* Newline */
898               state->cursor_x = 0;
899               state->escstate = 0;
900               break;
901             case 'M': /* Reverse newline */
902               if (state->cursor_y > 0)
903                 state->cursor_y--;
904               state->escstate = 0;
905               break;
906             case '7': /* Save state */
907               state->saved_x = state->cursor_x;
908               state->saved_y = state->cursor_y;
909               state->escstate = 0;
910               break;
911             case '8': /* Restore state */
912               state->cursor_x = state->saved_x;
913               state->cursor_y = state->saved_y;
914               state->escstate = 0;
915               break;
916             case '[': /* CSI */
917               state->escstate = 2;
918               for(i = 0; i < NPAR; i++)
919                 state->csiparam[i] = 0;
920               state->curparam = 0;
921               break;
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. */
926             case '(':
927             case ')':
928               /* I don't support different fonts either - see above
929                  for SO and SI */
930               state->escstate = 3;
931               break;
932             default:
933               /* Escape sequences not supported:
934                * 
935                * H - Set tab stop
936                * Z - Terminal identification
937                * > - Keypad change
938                * = - Other keypad change
939                * ] - OS command
940                */
941               state->escstate = 0;
942               break;
943             }
944           break;
945         case 2:
946           switch (c)
947             {
948             case 24: /* CAN */
949             case 26: /* SUB */
950               state->escstate = 0;
951               break;
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');
956               break;
957             case ';':
958               state->csiparam[++state->curparam] = 0;
959               break;
960             case '[':
961               state->escstate = 3;
962               break;
963             case '@':
964               for (i = 0; i < state->csiparam[0]; i++)
965                 {
966                   if(++state->cursor_x > state->grid_width)
967                     {
968                       state->cursor_x = 0;
969                       if (state->cursor_y < state->grid_height - 1)
970                         state->cursor_y++;
971                       else
972                         scroll (state);
973                     }
974                   cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
975                   if (cell->state == FLARE || cell->state == NORMAL)
976                     {
977                       cell->state = FADE;
978                       cell->changed = True;
979                     }
980                 }
981               state->escstate = 0;
982               break;
983             case 'F':
984               state->cursor_x = 0;
985             case 'A':
986               if (state->csiparam[0] == 0)
987                 state->csiparam[0] = 1;
988               if ((state->cursor_y -= state->csiparam[0]) < 0)
989                 state->cursor_y = 0;
990               state->escstate = 0;
991               break;
992             case 'E':
993               state->cursor_x = 0;
994             case 'e':
995             case 'B':
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;
1001               break;
1002             case 'a':
1003             case 'C':
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;
1009               break;
1010             case 'D':
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;
1016               break;
1017             case 'd':
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;
1021               break;
1022             case '`':
1023             case 'G':
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;
1027               break;
1028             case 'f':
1029             case 'H':
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;
1039               break;
1040             case 'J':
1041               start = 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++)
1048                 {
1049                   cell = &state->cells[i];
1050                   if (cell->state == FLARE || cell->state == NORMAL)
1051                     {
1052                       cell->state = FADE;
1053                       cell->changed = True;
1054                     }
1055                 }
1056               set_cursor (state, True);
1057               state->escstate = 0;
1058               break;
1059             case 'K':
1060               start = 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++)
1067                 {
1068                   if (cell->state == FLARE || cell->state == NORMAL)
1069                     {
1070                       cell->state = FADE;
1071                       cell->changed = True;
1072                     }
1073                   cell++;
1074                 }
1075               state->escstate = 0;
1076               break;
1077             case 's': /* Save position */
1078               state->saved_x = state->cursor_x;
1079               state->saved_y = state->cursor_y;
1080               state->escstate = 0;
1081               break;
1082             case 'u': /* Restore position */
1083               state->cursor_x = state->saved_x;
1084               state->cursor_y = state->saved_y;
1085               state->escstate = 0;
1086               break;
1087             case '?': /* DEC Private modes */
1088               if ((state->curparam != 0) || (state->csiparam[0] != 0))
1089                 state->escstate = 0;
1090               break;
1091             default:
1092               /* Known unsupported CSIs:
1093                *
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
1101                      docs)
1102                * l - Clear mode
1103                * m - Set mode (Phosphor is, per defenition, green on black)
1104                * n - Status report
1105                * q - Set keyboard LEDs
1106                * r - Set scrolling region (too exhausting - noone uses this,
1107                      right?)
1108                */
1109               state->escstate = 0;
1110               break;
1111             }
1112           break;
1113         case 3:
1114           state->escstate = 0;
1115           break;
1116         }
1117       set_cursor (state, True);
1118     }
1119   else
1120     {
1121       if (c == '\t') c = ' ';   /* blah. */
1122
1123       if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
1124         {
1125           if (c == '\n' && state->last_c == '\r')
1126             ;   /* CRLF -- do nothing */
1127           else
1128             {
1129               state->cursor_x = 0;
1130               if (state->cursor_y == state->grid_height - 1)
1131                 scroll (state);
1132               else
1133                 state->cursor_y++;
1134             }
1135         }
1136       else if (c == '\014')
1137         {
1138           clear (state);
1139         }
1140       else
1141         {
1142           cell->state = FLARE;
1143           cell->p_char = state->chars[c];
1144           cell->changed = True;
1145           state->cursor_x++;
1146
1147           if (c != ' ' && cell->p_char->blank_p)
1148             cell->p_char = state->chars[CURSOR_INDEX];
1149
1150           if (state->cursor_x >= state->grid_width - 1)
1151             {
1152               state->cursor_x = 0;
1153               if (state->cursor_y >= state->grid_height - 1)
1154                 scroll (state);
1155               else
1156                 state->cursor_y++;
1157             }
1158         }
1159       set_cursor (state, True);
1160     }
1161
1162   state->last_c = c;
1163 }
1164
1165
1166 static void
1167 update_display (p_state *state, Bool changed_only)
1168 {
1169   int x, y;
1170
1171   for (y = 0; y < state->grid_height; y++)
1172     for (x = 0; x < state->grid_width; x++)
1173       {
1174         p_cell *cell = &state->cells[state->grid_width * y + x];
1175         int width, height, tx, ty;
1176
1177         if (changed_only && !cell->changed)
1178           continue;
1179
1180         width = state->char_width * state->scale;
1181         height = state->char_height * state->scale;
1182         tx = x * width;
1183         ty = y * height;
1184
1185         if (cell->state == BLANK || cell->p_char->blank_p)
1186           {
1187             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1188                             tx, ty, width, height);
1189           }
1190         else
1191           {
1192 #ifdef FUZZY_BORDER
1193             GC gc1 = state->gcs[cell->state];
1194             GC gc2 = ((cell->state + 2) < state->ticks
1195                       ? state->gcs[cell->state + 2]
1196                       : 0);
1197             GC gc3 = (gc2 ? gc2 : gc1);
1198             if (gc3)
1199               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1200                           0, 0, width, height, tx, ty, 1L);
1201             if (gc2)
1202               {
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);
1208               }
1209 #else /* !FUZZY_BORDER */
1210
1211             XCopyPlane (state->dpy,
1212                         cell->p_char->pixmap, state->window,
1213                         state->gcs[cell->state],
1214                         0, 0, width, height, tx, ty, 1L);
1215
1216 #endif /* !FUZZY_BORDER */
1217           }
1218
1219         cell->changed = False;
1220       }
1221 }
1222
1223
1224 static unsigned long
1225 phosphor_draw (Display *dpy, Window window, void *closure)
1226 {
1227   p_state *state = (p_state *) closure;
1228   update_display (state, True);
1229   decay (state);
1230   drain_input (state);
1231   return state->delay;
1232 }
1233
1234 \f
1235 /* Subprocess.
1236  */
1237
1238 static void
1239 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1240 {
1241   p_state *state = (p_state *) closure;
1242   state->input_available_p = True;
1243 }
1244
1245
1246 static void
1247 launch_text_generator (p_state *state)
1248 {
1249   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1250   char buf[255];
1251   const char *oprogram = state->program;
1252   char *program = (char *) malloc (strlen (oprogram) + 50);
1253
1254   strcpy (program, "( ");
1255   strcat (program, oprogram);
1256
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.
1262    */
1263   if (!strcmp (oprogram, "xscreensaver-text"))
1264     sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
1265
1266   strcat (program, " ) 2>&1");
1267
1268 #ifdef HAVE_FORKPTY
1269   if(state->mode == 1)
1270     {
1271       int fd;
1272       struct winsize ws;
1273       
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;
1278       
1279       state->pipe = NULL;
1280       if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1281         {
1282           /* Unable to fork */
1283           sprintf (buf, "%.100s: forkpty", progname);
1284           perror(buf);
1285         }
1286       else if(!state->pid)
1287         {
1288           /* This is the child fork. */
1289           char *av[10];
1290           int i = 0;
1291           if (putenv("TERM=vt100"))
1292             abort();
1293           av[i++] = "/bin/sh";
1294           av[i++] = "-c";
1295           av[i++] = program;
1296           av[i] = 0;
1297           execvp (av[0], av);
1298           sprintf (buf, "%.100s: %.100s", progname, oprogram);
1299           perror(buf);
1300           exit(1);
1301         }
1302       else
1303         {
1304           /* This is the parent fork. */
1305           state->pipe = fdopen(fd, "r+");
1306           state->pipe_id =
1307             XtAppAddInput (app, fileno (state->pipe),
1308                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
1309                            subproc_cb, (XtPointer) state);
1310         }
1311     }
1312   else
1313 #endif /* HAVE_FORKPTY */
1314     {
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) {
1319         fclose (stdin);
1320         open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
1321         protected_stdin_p = 1;
1322       }
1323
1324       if ((state->pipe = popen (program, "r")))
1325         {
1326           state->pipe_id =
1327             XtAppAddInput (app, fileno (state->pipe),
1328                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
1329                            subproc_cb, (XtPointer) state);
1330         }
1331       else
1332         {
1333           sprintf (buf, "%.100s: %.100s", progname, program);
1334           perror (buf);
1335         }
1336     }
1337
1338   free (program);
1339 }
1340
1341
1342 static void
1343 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1344 {
1345   p_state *state = (p_state *) closure;
1346   /* if (!state->pipe_timer) abort(); */
1347   state->pipe_timer = 0;
1348   launch_text_generator (state);
1349 }
1350
1351
1352 static void
1353 drain_input (p_state *state)
1354 {
1355   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1356   if (state->input_available_p && state->pipe)
1357     {
1358       unsigned char s[2];
1359       int n = read (fileno (state->pipe), (void *) s, 1);
1360
1361       if (n == 1)
1362         {
1363           print_char (state, s[0]);
1364         }
1365       else
1366         {
1367           XtRemoveInput (state->pipe_id);
1368           state->pipe_id = 0;
1369           if (state->pid)
1370             {
1371               waitpid(state->pid, NULL, 0);
1372               fclose (state->pipe);
1373               state->pid = 0;
1374             }
1375           else
1376             {
1377               pclose (state->pipe);
1378             }
1379           state->pipe = 0;
1380
1381           if (state->cursor_x != 0) {   /* break line if unbroken */
1382             print_char (state, '\r');
1383             print_char (state, '\n');
1384           }
1385           print_char (state, '\r');     /* blank line */
1386           print_char (state, '\n');
1387
1388           /* Set up a timer to re-launch the subproc in a bit. */
1389           state->pipe_timer =
1390             XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1391                              relaunch_generator_timer,
1392                              (XtPointer) state);
1393         }
1394         
1395       state->input_available_p = False;
1396     }
1397 }
1398
1399
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!
1405  */
1406 static unsigned int
1407 do_icccm_meta_key_stupidity (Display *dpy)
1408 {
1409   unsigned int modbits = 0;
1410 # ifndef HAVE_COCOA
1411   int i, j, k;
1412   XModifierKeymap *modmap = XGetModifierMapping (dpy);
1413   for (i = 3; i < 8; i++)
1414     for (j = 0; j < modmap->max_keypermod; j++)
1415       {
1416         int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1417         KeySym *syms;
1418         int nsyms = 0;
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);
1425         XFree (syms);
1426       }
1427   XFreeModifiermap (modmap);
1428 # endif /* HAVE_COCOA */
1429   return modbits;
1430 }
1431
1432 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
1433  */
1434 static unsigned int
1435 meta_modifier (p_state *state)
1436 {
1437   if (!state->meta_done_once)
1438     {
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);
1443     }
1444   return state->meta_mask;
1445 }
1446
1447
1448 static void
1449 phosphor_reshape (Display *dpy, Window window, void *closure, 
1450                  unsigned int w, unsigned int h)
1451 {
1452   p_state *state = (p_state *) closure;
1453   Bool changed_p = resize_grid (state);
1454
1455   if (! changed_p) return;
1456
1457 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1458   if (state->pid && state->pipe)
1459     {
1460       /* Tell the sub-process that the screen size has changed. */
1461       struct winsize ws;
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);
1468     }
1469 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1470
1471
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.
1475    */
1476   if (!strcmp (state->program, "xscreensaver-text"))
1477     {
1478       if (state->pid)
1479         kill (state->pid, SIGTERM);
1480       if (state->pipe)
1481         pclose (state->pipe);
1482       state->input_available_p = False;
1483       relaunch_generator_timer (state, 0);
1484     }
1485 }
1486
1487
1488 static Bool
1489 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1490 {
1491   p_state *state = (p_state *) closure;
1492
1493   if (event->xany.type == Expose)
1494     update_display (state, False);
1495   else if (event->xany.type == KeyPress)
1496     {
1497       KeySym keysym;
1498       unsigned char c = 0;
1499       XLookupString (&event->xkey, (char *) &c, 1, &keysym,
1500                      &state->compose);
1501       if (c != 0 && state->pipe)
1502         {
1503           if (!state->swap_bs_del_p) ;
1504           else if (c == 127) c = 8;
1505           else if (c == 8)   c = 127;
1506
1507           /* If meta was held down, send ESC, or turn on the high bit. */
1508           if (event->xkey.state & meta_modifier (state))
1509             {
1510               if (state->meta_sends_esc_p)
1511                 fputc ('\033', state->pipe);
1512               else
1513                 c |= 0x80;
1514             }
1515
1516           fputc (c, state->pipe);
1517           fflush (state->pipe);
1518           event->xany.type = 0;  /* don't interpret this event defaultly. */
1519         }
1520       return True;
1521     }
1522
1523   return False;
1524 }
1525
1526 static void
1527 phosphor_free (Display *dpy, Window window, void *closure)
1528 {
1529   p_state *state = (p_state *) closure;
1530
1531   if (state->pipe_id)
1532     XtRemoveInput (state->pipe_id);
1533   if (state->pipe)
1534     pclose (state->pipe);
1535   if (state->cursor_timer)
1536     XtRemoveTimeOut (state->cursor_timer);
1537   if (state->pipe_timer)
1538     XtRemoveTimeOut (state->pipe_timer);
1539
1540   /* #### there's more to free here */
1541
1542   free (state);
1543 }
1544
1545
1546
1547 static const char *phosphor_defaults [] = {
1548   ".background:            Black",
1549   ".foreground:            #00FF00",
1550   "*fpsSolid:              true",
1551   "*fadeForeground:        #006400",
1552   "*flareForeground:       #FFFFFF",
1553 #if defined(BUILTIN_FONT)
1554   "*font:                  (builtin)",
1555 #elif defined(HAVE_COCOA)
1556   "*font:                  Monaco 15",
1557 #else
1558   "*font:                  fixed",
1559 #endif
1560   "*scale:                 6",
1561   "*ticks:                 20",
1562   "*delay:                 50000",
1563   "*cursor:                333",
1564   "*program:               xscreensaver-text",
1565   "*relaunch:              5",
1566   "*metaSendsESC:          True",
1567   "*swapBSDEL:             True",
1568 #ifdef HAVE_FORKPTY
1569   "*mode:                  pty",
1570 #else  /* !HAVE_FORKPTY */
1571   "*mode:                  pipe",
1572 #endif /* !HAVE_FORKPTY */
1573   0
1574 };
1575
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"  },
1588   { 0, 0, 0, 0 }
1589 };
1590
1591
1592 XSCREENSAVER_MODULE ("Phosphor", phosphor)