6f9fe9170a261d88bf174ce61a31bed7e897ee8e
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999-2006 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   int grid_width, grid_height;
91   int char_width, char_height;
92   int saved_x, saved_y;
93   int scale;
94   int ticks;
95   int mode;
96   pid_t pid;
97   int escstate;
98   int csiparam[NPAR];
99   int curparam;
100   p_char **chars;
101   p_cell *cells;
102   XGCValues gcv;
103   GC gc0;
104   GC gc1;
105 #ifdef FUZZY_BORDER
106   GC gc2;
107 #endif /* FUZZY_BORDER */
108   GC *gcs;
109   XImage *font_bits;
110
111   int cursor_x, cursor_y;
112   XtIntervalId cursor_timer;
113   XtIntervalId pipe_timer;
114   Time cursor_blink;
115
116   FILE *pipe;
117   XtInputId pipe_id;
118   Bool input_available_p;
119   Time subproc_relaunch_delay;
120   XComposeStatus compose;
121   Bool meta_sends_esc_p;
122   Bool swap_bs_del_p;
123   int delay;
124
125   char last_c;
126   int bk;
127
128   Bool meta_done_once;
129   unsigned int meta_mask;
130
131 } p_state;
132
133
134 static void capture_font_bits (p_state *state);
135 static p_char *make_character (p_state *state, int c);
136 static void drain_input (p_state *state);
137 static void char_to_pixmap (p_state *state, p_char *pc, int c);
138 static void launch_text_generator (p_state *state);
139
140
141 /* About font metrics:
142
143    "lbearing" is the distance from the leftmost pixel of a character to
144    the logical origin of that character.  That is, it is the number of
145    pixels of the character which are to the left of its logical origin.
146
147    "rbearing" is the distance from the logical origin of a character to
148    the rightmost pixel of a character.  That is, it is the number of
149    pixels of the character to the right of its logical origin.
150
151    "descent" is the distance from the bottommost pixel of a character to
152    the logical baseline.  That is, it is the number of pixels of the
153    character which are below the baseline.
154
155    "ascent" is the distance from the logical baseline to the topmost pixel.
156    That is, it is the number of pixels of the character above the baseline.
157
158    Therefore, the bounding box of the "ink" of a character is
159    lbearing + rbearing by ascent + descent;
160
161    "width" is the distance from the logical origin of this character to
162    the position where the logical orgin of the next character should be
163    placed.
164
165    For our purposes, we're only interested in the part of the character
166    lying inside the "width" box.  If the characters have ink outside of
167    that box (the "charcell" box) then we're going to lose it.  Alas.
168  */
169
170
171 static void clear (p_state *);
172 static void set_cursor (p_state *, Bool on);
173
174 static void *
175 phosphor_init (Display *dpy, Window window)
176 {
177   int i;
178   unsigned long flags;
179   p_state *state = (p_state *) calloc (sizeof(*state), 1);
180   char *fontname = get_string_resource (dpy, "font", "Font");
181   XFontStruct *font;
182
183   state->dpy = dpy;
184   state->window = window;
185
186   XGetWindowAttributes (dpy, window, &state->xgwa);
187 /*  XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
188
189   state->delay = get_integer_resource (dpy, "delay", "Integer");
190   state->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
191   state->swap_bs_del_p    = get_boolean_resource (dpy, "swapBSDEL",    "Boolean");
192
193   if (!strcasecmp (fontname, "builtin") ||
194       !strcasecmp (fontname, "(builtin)"))
195     {
196 #ifndef BUILTIN_FONT
197       fprintf (stderr, "%s: no builtin font\n", progname);
198       state->font = XLoadQueryFont (dpy, "fixed");
199 #endif /* !BUILTIN_FONT */
200     }
201   else
202     {
203       state->font = XLoadQueryFont (dpy, fontname);
204
205       if (!state->font)
206         {
207           fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
208           state->font = XLoadQueryFont (dpy, "fixed");
209         }
210       if (!state->font)
211         {
212           fprintf(stderr, "couldn't load font \"fixed\"");
213           exit(1);
214         }
215     }
216
217   font = state->font;
218   state->scale = get_integer_resource (dpy, "scale", "Integer");
219   state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
220   state->escstate = 0;
221
222   {
223     char *s = get_string_resource (dpy, "mode", "Integer");
224     state->mode = 0;
225     if (!s || !*s || !strcasecmp (s, "pipe"))
226       state->mode = 0;
227     else if (!strcasecmp (s, "pty"))
228       state->mode = 1;
229     else
230       fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
231                progname, s);
232
233 #ifndef HAVE_FORKPTY
234     fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
235              progname);
236     state->mode = 0;
237 #endif /* HAVE_FORKPTY */
238   }
239
240 #if 0
241   for (i = 0; i < font->n_properties; i++)
242     if (font->properties[i].name == XA_FONT)
243       printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
244 #endif /* 0 */
245
246   state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
247   state->subproc_relaunch_delay =
248     (1000 * get_integer_resource (dpy, "relaunch", "Time"));
249
250 # ifdef BUILTIN_FONT
251   if (! font)
252     {
253       state->char_width  = (font6x10_width / 256) - 1;
254       state->char_height = font6x10_height;
255     }
256   else
257 # endif /* BUILTIN_FONT */
258     {
259       state->char_width  = font->max_bounds.width;
260       state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
261     }
262
263   state->grid_width = state->xgwa.width / (state->char_width * state->scale);
264   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
265   state->cells = (p_cell *) calloc (sizeof(p_cell),
266                                     state->grid_width * state->grid_height);
267   state->chars = (p_char **) calloc (sizeof(p_char *), 256);
268
269   state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
270
271   {
272     int ncolors = MAX (0, state->ticks - 3);
273     XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
274     int h1, h2;
275     double s1, s2, v1, v2;
276
277     unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
278                                            "foreground", "Foreground");
279     unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
280                                            "background", "Background");
281     unsigned long flare = get_pixel_resource (state->dpy,state->xgwa.colormap,
282                                               "flareForeground", "Foreground");
283     unsigned long fade = get_pixel_resource (state->dpy,state->xgwa.colormap,
284                                              "fadeForeground", "Foreground");
285
286     XColor start, end;
287
288     start.pixel = fade;
289     XQueryColor (state->dpy, state->xgwa.colormap, &start);
290
291     end.pixel = bg;
292     XQueryColor (state->dpy, state->xgwa.colormap, &end);
293
294     /* Now allocate a ramp of colors from the main color to the background. */
295     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
296     rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
297     make_color_ramp (state->dpy, state->xgwa.colormap,
298                      h1, s1, v1,
299                      h2, s2, v2,
300                      colors, &ncolors,
301                      False, True, False);
302
303     /* Adjust to the number of colors we actually got. */
304     state->ticks = ncolors + STATE_MAX;
305
306     /* Now, GCs all around.
307      */
308     state->gcv.font = (font ? font->fid : 0);
309     state->gcv.cap_style = CapRound;
310 #ifdef FUZZY_BORDER
311     state->gcv.line_width = (int) (((long) state->scale) * 1.3);
312     if (state->gcv.line_width == state->scale)
313       state->gcv.line_width++;
314 #else /* !FUZZY_BORDER */
315     state->gcv.line_width = (int) (((long) state->scale) * 0.9);
316     if (state->gcv.line_width >= state->scale)
317       state->gcv.line_width = state->scale - 1;
318     if (state->gcv.line_width < 1)
319       state->gcv.line_width = 1;
320 #endif /* !FUZZY_BORDER */
321
322     flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
323
324     state->gcv.background = bg;
325     state->gcv.foreground = bg;
326     state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
327                                    &state->gcv);
328
329     state->gcv.foreground = flare;
330     state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
331                                    &state->gcv);
332
333     state->gcv.foreground = fg;
334     state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
335                                     &state->gcv);
336
337     for (i = 0; i < ncolors; i++)
338       {
339         state->gcv.foreground = colors[i].pixel;
340         state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
341                                                flags, &state->gcv);
342       }
343   }
344
345   capture_font_bits (state);
346
347   set_cursor (state, True);
348
349   launch_text_generator (state);
350 /*  clear (state);*/
351
352   return state;
353 }
354
355
356 /* Re-query the window size and update the internal character grid if changed.
357  */
358 static void
359 resize_grid (p_state *state)
360 {
361   int ow = state->grid_width;
362   int oh = state->grid_height;
363   p_cell *ocells = state->cells;
364   int x, y;
365
366   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
367
368   state->grid_width = state->xgwa.width   /(state->char_width  * state->scale);
369   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
370
371   if (ow == state->grid_width &&
372       oh == state->grid_height)
373     return;
374
375   state->cells = (p_cell *) calloc (sizeof(p_cell),
376                                     state->grid_width * state->grid_height);
377
378   for (y = 0; y < state->grid_height; y++)
379     {
380       for (x = 0; x < state->grid_width; x++)
381         {
382           p_cell *ncell = &state->cells [state->grid_width * y + x];
383           if (x < ow && y < oh)
384             *ncell = ocells [ow * y + x];
385           ncell->changed = True;
386         }
387     }
388
389   if (state->cursor_x >= state->grid_width)
390     state->cursor_x = state->grid_width-1;
391   if (state->cursor_y >= state->grid_height)
392     state->cursor_y = state->grid_height-1;
393
394   free (ocells);
395 }
396
397
398 static void
399 capture_font_bits (p_state *state)
400 {
401   XFontStruct *font = state->font;
402   int safe_width, height;
403   unsigned char string[257];
404   int i;
405   Pixmap p;
406
407 # ifdef BUILTIN_FONT
408   Pixmap p2 = 0;
409
410   if (!font)
411     {
412       safe_width = state->char_width + 1;
413       height = state->char_height;
414       p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
415                                         (char *) font6x10_bits,
416                                         font6x10_width,
417                                         font6x10_height,
418                                         1, 0, 1);
419     }
420   else
421 # endif /* BUILTIN_FONT */
422     {
423       safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
424       height = state->char_height;
425     }
426
427   p = XCreatePixmap (state->dpy, state->window,
428                      (safe_width * 256), height, 1);
429
430   for (i = 0; i < 256; i++)
431     string[i] = (unsigned char) i;
432   string[256] = 0;
433
434   state->gcv.foreground = 0;
435   state->gcv.background = 0;
436   state->gc0 = XCreateGC (state->dpy, p,
437                           (GCForeground | GCBackground),
438                           &state->gcv);
439
440   state->gcv.foreground = 1;
441   state->gc1 = XCreateGC (state->dpy, p,
442                           ((font ? GCFont : 0) |
443                            GCForeground | GCBackground |
444                            GCCapStyle | GCLineWidth),
445                           &state->gcv);
446
447 #ifdef HAVE_COCOA
448   jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
449   jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
450 #endif
451
452 #ifdef FUZZY_BORDER
453   {
454     state->gcv.line_width = (int) (((long) state->scale) * 0.8);
455     if (state->gcv.line_width >= state->scale)
456       state->gcv.line_width = state->scale - 1;
457     if (state->gcv.line_width < 1)
458       state->gcv.line_width = 1;
459     state->gc2 = XCreateGC (state->dpy, p,
460                             ((font ? GCFont : 0) |
461                              GCForeground | GCBackground |
462                              GCCapStyle | GCLineWidth),
463                             &state->gcv);
464   }
465 #endif /* FUZZY_BORDER */
466
467   XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
468
469 # ifdef BUILTIN_FONT
470   if (p2)
471     {
472       XCopyPlane (state->dpy, p2, p, state->gc1,
473                   0, 0, font6x10_width, font6x10_height, 
474                   0, 0, 1);
475       XFreePixmap (state->dpy, p2);
476     }
477   else
478 # endif /* BUILTIN_FONT */
479     {
480       for (i = 0; i < 256; i++)
481         {
482           if (string[i] < font->min_char_or_byte2 ||
483               string[i] > font->max_char_or_byte2)
484             continue;
485           XDrawString (state->dpy, p, state->gc1,
486                        i * safe_width, font->ascent,
487                        (char *) (string + i), 1);
488         }
489     }
490
491   /* Draw the cursor. */
492   XFillRectangle (state->dpy, p, state->gc1,
493                   (CURSOR_INDEX * safe_width), 1,
494                   (font
495                    ? (font->per_char
496                       ? font->per_char['n'-font->min_char_or_byte2].width
497                       : font->max_bounds.width)
498                    : state->char_width),
499                   (font
500                    ? font->ascent - 1
501                    : state->char_height));
502
503   state->font_bits = XGetImage (state->dpy, p, 0, 0,
504                                 (safe_width * 256), height, ~0L, XYPixmap);
505   XFreePixmap (state->dpy, p);
506
507   for (i = 0; i < 256; i++)
508     state->chars[i] = make_character (state, i);
509   state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
510 }
511
512
513 static p_char *
514 make_character (p_state *state, int c)
515 {
516   p_char *pc = (p_char *) malloc (sizeof (*pc));
517   pc->name = (unsigned char) c;
518   pc->width =  state->scale * state->char_width;
519   pc->height = state->scale * state->char_height;
520   char_to_pixmap (state, pc, c);
521   return pc;
522 }
523
524
525 static void
526 char_to_pixmap (p_state *state, p_char *pc, int c)
527 {
528   Pixmap p = 0;
529   GC gc;
530 #ifdef FUZZY_BORDER
531   Pixmap p2 = 0;
532   GC gc2;
533 #endif /* FUZZY_BORDER */
534   int from, to;
535   int x1, y;
536
537   XFontStruct *font = state->font;
538   int safe_width = (font 
539                     ? font->max_bounds.rbearing - font->min_bounds.lbearing
540                     : state->char_width + 1);
541
542   int width  = state->scale * state->char_width;
543   int height = state->scale * state->char_height;
544
545   if (font && (c < font->min_char_or_byte2 ||
546                c > font->max_char_or_byte2))
547     goto DONE;
548
549   gc = state->gc1;
550   p = XCreatePixmap (state->dpy, state->window, width, height, 1);
551   XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
552 #ifdef FUZZY_BORDER
553   gc2 = state->gc2;
554   p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
555   XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
556 #endif /* FUZZY_BORDER */
557
558   from = safe_width * c;
559   to =   safe_width * (c + 1);
560
561 #if 0
562   if (c > 75 && c < 150)
563     {
564       printf ("\n=========== %d (%c)\n", c, c);
565       for (y = 0; y < state->char_height; y++)
566         {
567           for (x1 = from; x1 < to; x1++)
568             printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
569           printf ("\n");
570         }
571     }
572 #endif
573
574   pc->blank_p = True;
575   for (y = 0; y < state->char_height; y++)
576     for (x1 = from; x1 < to; x1++)
577       if (XGetPixel (state->font_bits, x1, y))
578         {
579           int xoff = state->scale / 2;
580           int x2;
581           for (x2 = x1; x2 < to; x2++)
582             if (!XGetPixel (state->font_bits, x2, y))
583               break;
584           x2--;
585           XDrawLine (state->dpy, p, gc,
586                      (x1 - from) * state->scale + xoff, y * state->scale,
587                      (x2 - from) * state->scale + xoff, y * state->scale);
588 #ifdef FUZZY_BORDER
589           XDrawLine (state->dpy, p2, gc2,
590                      (x1 - from) * state->scale + xoff, y * state->scale,
591                      (x2 - from) * state->scale + xoff, y * state->scale);
592 #endif /* FUZZY_BORDER */
593           x1 = x2;
594           pc->blank_p = False;
595         }
596
597   /*  if (pc->blank_p && c == CURSOR_INDEX)
598     abort();*/
599
600  DONE:
601   pc->pixmap = p;
602 #ifdef FUZZY_BORDER
603   pc->pixmap2 = p2;
604 #endif /* FUZZY_BORDER */
605 }
606
607 \f
608 /* Managing the display. 
609  */
610
611 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
612 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
613
614 static Bool
615 set_cursor_1 (p_state *state, Bool on)
616 {
617   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
618                               + state->cursor_x];
619   p_char *cursor = state->chars[CURSOR_INDEX];
620   int new_state = (on ? NORMAL : FADE);
621
622   if (cell->p_char != cursor)
623     cell->changed = True;
624
625   if (cell->state != new_state)
626     cell->changed = True;
627
628   cell->p_char = cursor;
629   cell->state = new_state;
630   return cell->changed;
631 }
632
633 static void
634 set_cursor (p_state *state, Bool on)
635 {
636   if (set_cursor_1 (state, on))
637     {
638       if (state->cursor_timer)
639         XtRemoveTimeOut (state->cursor_timer);
640       state->cursor_timer = 0;
641       cursor_on_timer (state, 0);
642     }
643 }
644
645
646
647
648 static void
649 cursor_off_timer (XtPointer closure, XtIntervalId *id)
650 {
651   p_state *state = (p_state *) closure;
652   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
653   set_cursor_1 (state, False);
654   state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
655                                          cursor_on_timer, closure);
656 }
657
658 static void
659 cursor_on_timer (XtPointer closure, XtIntervalId *id)
660 {
661   p_state *state = (p_state *) closure;
662   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
663   set_cursor_1 (state, True);
664   state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
665                                          cursor_off_timer, closure);
666 }
667
668
669 static void
670 clear (p_state *state)
671 {
672   int x, y;
673   state->cursor_x = 0;
674   state->cursor_y = 0;
675   for (y = 0; y < state->grid_height; y++)
676     for (x = 0; x < state->grid_width; x++)
677       {
678         p_cell *cell = &state->cells[state->grid_width * y + x];
679         if (cell->state == FLARE || cell->state == NORMAL)
680           {
681             cell->state = FADE;
682             cell->changed = True;
683           }
684       }
685   set_cursor (state, True);
686 }
687
688
689 static void
690 decay (p_state *state)
691 {
692   int x, y;
693   for (y = 0; y < state->grid_height; y++)
694     for (x = 0; x < state->grid_width; x++)
695       {
696         p_cell *cell = &state->cells[state->grid_width * y + x];
697         if (cell->state == FLARE)
698           {
699             cell->state = NORMAL;
700             cell->changed = True;
701           }
702         else if (cell->state >= FADE)
703           {
704             cell->state++;
705             if (cell->state >= state->ticks)
706               cell->state = BLANK;
707             cell->changed = True;
708           }
709       }
710 }
711
712
713 static void
714 scroll (p_state *state)
715 {
716   int x, y;
717
718   for (x = 0; x < state->grid_width; x++)
719     {
720       p_cell *from = 0, *to = 0;
721       for (y = 1; y < state->grid_height; y++)
722         {
723           from = &state->cells[state->grid_width * y     + x];
724           to   = &state->cells[state->grid_width * (y-1) + x];
725
726           if ((from->state == FLARE || from->state == NORMAL) &&
727               !from->p_char->blank_p)
728             {
729               *to = *from;
730               to->state = NORMAL;  /* should be FLARE?  Looks bad... */
731             }
732           else
733             {
734               if (to->state == FLARE || to->state == NORMAL)
735                 to->state = FADE;
736             }
737
738           to->changed = True;
739         }
740
741       to = from;
742       if (to && (to->state == FLARE || to->state == NORMAL))
743         {
744           to->state = FADE;
745           to->changed = True;
746         }
747     }
748   set_cursor (state, True);
749 }
750
751
752 static void
753 print_char (p_state *state, int c)
754 {
755   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
756                                + state->cursor_x];
757   int i, start, end;
758
759   /* Start the cursor fading (in case we don't end up overwriting it.) */
760   if (cell->state == FLARE || cell->state == NORMAL)
761     {
762       cell->state = FADE;
763       cell->changed = True;
764     }
765   
766   if (state->pid)  /* Only interpret VT100 sequences if running in pty-mode.
767                       It would be nice if we could just interpret them all
768                       the time, but that would require subprocesses to send
769                       CRLF line endings instead of bare LF, so that's no good.
770                     */
771     {
772       switch (state->escstate)
773         {
774         case 0:
775           switch (c)
776             {
777             case 7: /* BEL */
778               /* Dummy case - we don't want the screensaver to beep */
779               /* #### But maybe this should flash the screen? */
780               break;
781             case 8: /* BS */
782               if (state->cursor_x > 0)
783                 state->cursor_x--;
784               break;
785             case 9: /* HT */
786               if (state->cursor_x < state->grid_width - 8)
787                 {
788                   state->cursor_x = (state->cursor_x & ~7) + 8;
789                 }
790               else
791                 {
792                   state->cursor_x = 0;
793                   if (state->cursor_y < state->grid_height - 1)
794                     state->cursor_y++;
795                   else
796                     scroll (state);
797                 }
798               break;
799             case 10: /* LF */
800             case 11: /* VT */
801             case 12: /* FF */
802               if(state->last_c == 13)
803                 {
804                   cell->state = NORMAL;
805                   cell->p_char = state->chars[state->bk];
806                   cell->changed = True;
807                 }
808               if (state->cursor_y < state->grid_height - 1)
809                 state->cursor_y++;
810               else
811                 scroll (state);
812               break;
813             case 13: /* CR */
814               state->cursor_x = 0;
815               cell = &state->cells[state->grid_width * state->cursor_y];
816               if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
817                 state->bk = ' ';
818               else
819                 state->bk = cell->p_char->name;
820               break;
821             case 14: /* SO */
822             case 15: /* SI */
823               /* Dummy case - I don't want to load several fonts for
824                  the maybe two programs world-wide that use that */
825               break;
826             case 24: /* CAN */
827             case 26: /* SUB */
828               /* Dummy case - these interrupt escape sequences, so
829                  they don't do anything in this state */
830               break;
831             case 27: /* ESC */
832               state->escstate = 1;
833               break;
834             case 127: /* DEL */
835               /* Dummy case - this is supposed to be ignored */
836               break;
837             case 155: /* CSI */
838               state->escstate = 2;
839               for(i = 0; i < NPAR; i++)
840                 state->csiparam[i] = 0;
841               state->curparam = 0;
842               break;
843             default:
844               cell->state = FLARE;
845               cell->p_char = state->chars[c];
846               cell->changed = True;
847               state->cursor_x++;
848
849               if (c != ' ' && cell->p_char->blank_p)
850                 cell->p_char = state->chars[CURSOR_INDEX];
851
852               if (state->cursor_x >= state->grid_width - 1)
853                 {
854                   state->cursor_x = 0;
855                   if (state->cursor_y >= state->grid_height - 1)
856                     scroll (state);
857                   else
858                     state->cursor_y++;
859                 }
860               break;
861             }
862           break;
863         case 1:
864           switch (c)
865             {
866             case 24: /* CAN */
867             case 26: /* SUB */
868               state->escstate = 0;
869               break;
870             case 'c': /* Reset */
871               clear (state);
872               state->escstate = 0;
873               break;
874             case 'D': /* Linefeed */
875               if (state->cursor_y < state->grid_height - 1)
876                 state->cursor_y++;
877               else
878                 scroll (state);
879               state->escstate = 0;
880               break;
881             case 'E': /* Newline */
882               state->cursor_x = 0;
883               state->escstate = 0;
884               break;
885             case 'M': /* Reverse newline */
886               if (state->cursor_y > 0)
887                 state->cursor_y--;
888               state->escstate = 0;
889               break;
890             case '7': /* Save state */
891               state->saved_x = state->cursor_x;
892               state->saved_y = state->cursor_y;
893               state->escstate = 0;
894               break;
895             case '8': /* Restore state */
896               state->cursor_x = state->saved_x;
897               state->cursor_y = state->saved_y;
898               state->escstate = 0;
899               break;
900             case '[': /* CSI */
901               state->escstate = 2;
902               for(i = 0; i < NPAR; i++)
903                 state->csiparam[i] = 0;
904               state->curparam = 0;
905               break;
906             case '%': /* Select charset */
907               /* No, I don't support UTF-8, since the phosphor font
908                  isn't even Unicode anyway. We must still catch the
909                  last byte, though. */
910             case '(':
911             case ')':
912               /* I don't support different fonts either - see above
913                  for SO and SI */
914               state->escstate = 3;
915               break;
916             default:
917               /* Escape sequences not supported:
918                * 
919                * H - Set tab stop
920                * Z - Terminal identification
921                * > - Keypad change
922                * = - Other keypad change
923                * ] - OS command
924                */
925               state->escstate = 0;
926               break;
927             }
928           break;
929         case 2:
930           switch (c)
931             {
932             case 24: /* CAN */
933             case 26: /* SUB */
934               state->escstate = 0;
935               break;
936             case '0': case '1': case '2': case '3': case '4':
937             case '5': case '6': case '7': case '8': case '9':
938               if (state->curparam < NPAR)
939                 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
940               break;
941             case ';':
942               state->csiparam[++state->curparam] = 0;
943               break;
944             case '[':
945               state->escstate = 3;
946               break;
947             case '@':
948               for (i = 0; i < state->csiparam[0]; i++)
949                 {
950                   if(++state->cursor_x > state->grid_width)
951                     {
952                       state->cursor_x = 0;
953                       if (state->cursor_y < state->grid_height - 1)
954                         state->cursor_y++;
955                       else
956                         scroll (state);
957                     }
958                   cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
959                   if (cell->state == FLARE || cell->state == NORMAL)
960                     {
961                       cell->state = FADE;
962                       cell->changed = True;
963                     }
964                 }
965               state->escstate = 0;
966               break;
967             case 'F':
968               state->cursor_x = 0;
969             case 'A':
970               if (state->csiparam[0] == 0)
971                 state->csiparam[0] = 1;
972               if ((state->cursor_y -= state->csiparam[0]) < 0)
973                 state->cursor_y = 0;
974               state->escstate = 0;
975               break;
976             case 'E':
977               state->cursor_x = 0;
978             case 'e':
979             case 'B':
980               if (state->csiparam[0] == 0)
981                 state->csiparam[0] = 1;
982               if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
983                 state->cursor_y = state->grid_height - 1;
984               state->escstate = 0;
985               break;
986             case 'a':
987             case 'C':
988               if (state->csiparam[0] == 0)
989                 state->csiparam[0] = 1;
990               if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
991                 state->cursor_x = state->grid_width - 1;
992               state->escstate = 0;
993               break;
994             case 'D':
995               if (state->csiparam[0] == 0)
996                 state->csiparam[0] = 1;
997               if ((state->cursor_x -= state->csiparam[0]) < 0)
998                 state->cursor_x = 0;
999               state->escstate = 0;
1000               break;
1001             case 'd':
1002               if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1003                 state->cursor_y = state->grid_height - 1;
1004               state->escstate = 0;
1005               break;
1006             case '`':
1007             case 'G':
1008               if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
1009                 state->cursor_x = state->grid_width - 1;
1010               state->escstate = 0;
1011               break;
1012             case 'f':
1013             case 'H':
1014               if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1015                 state->cursor_y = state->grid_height - 1;
1016               if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
1017                 state->cursor_x = state->grid_width - 1;
1018               if(state->cursor_y < 0)
1019                 state->cursor_y = 0;
1020               if(state->cursor_x < 0)
1021                 state->cursor_x = 0;
1022               state->escstate = 0;
1023               break;
1024             case 'J':
1025               start = 0;
1026               end = state->grid_height * state->grid_width;
1027               if (state->csiparam[0] == 0)
1028                 start = state->grid_width * state->cursor_y + state->cursor_x;
1029               if (state->csiparam[0] == 1)
1030                 end = state->grid_width * state->cursor_y + state->cursor_x;
1031               for (i = start; i < end; i++)
1032                 {
1033                   cell = &state->cells[i];
1034                   if (cell->state == FLARE || cell->state == NORMAL)
1035                     {
1036                       cell->state = FADE;
1037                       cell->changed = True;
1038                     }
1039                 }
1040               set_cursor (state, True);
1041               state->escstate = 0;
1042               break;
1043             case 'K':
1044               start = 0;
1045               end = state->grid_width;
1046               if (state->csiparam[0] == 0)
1047                 start = state->cursor_x;
1048               if (state->csiparam[1] == 1)
1049                 end = state->cursor_x;
1050               for (i = start; i < end; i++)
1051                 {
1052                   if (cell->state == FLARE || cell->state == NORMAL)
1053                     {
1054                       cell->state = FADE;
1055                       cell->changed = True;
1056                     }
1057                   cell++;
1058                 }
1059               state->escstate = 0;
1060               break;
1061             case 's': /* Save position */
1062               state->saved_x = state->cursor_x;
1063               state->saved_y = state->cursor_y;
1064               state->escstate = 0;
1065               break;
1066             case 'u': /* Restore position */
1067               state->cursor_x = state->saved_x;
1068               state->cursor_y = state->saved_y;
1069               state->escstate = 0;
1070               break;
1071             case '?': /* DEC Private modes */
1072               if ((state->curparam != 0) || (state->csiparam[0] != 0))
1073                 state->escstate = 0;
1074               break;
1075             default:
1076               /* Known unsupported CSIs:
1077                *
1078                * L - Insert blank lines
1079                * M - Delete lines (I don't know what this means...)
1080                * P - Delete characters
1081                * X - Erase characters (difference with P being...?)
1082                * c - Terminal identification
1083                * g - Clear tab stop(s)
1084                * h - Set mode (Mainly due to its complexity and lack of good
1085                      docs)
1086                * l - Clear mode
1087                * m - Set mode (Phosphor is, per defenition, green on black)
1088                * n - Status report
1089                * q - Set keyboard LEDs
1090                * r - Set scrolling region (too exhausting - noone uses this,
1091                      right?)
1092                */
1093               state->escstate = 0;
1094               break;
1095             }
1096           break;
1097         case 3:
1098           state->escstate = 0;
1099           break;
1100         }
1101       set_cursor (state, True);
1102     }
1103   else
1104     {
1105       if (c == '\t') c = ' ';   /* blah. */
1106
1107       if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
1108         {
1109           if (c == '\n' && state->last_c == '\r')
1110             ;   /* CRLF -- do nothing */
1111           else
1112             {
1113               state->cursor_x = 0;
1114               if (state->cursor_y == state->grid_height - 1)
1115                 scroll (state);
1116               else
1117                 state->cursor_y++;
1118             }
1119         }
1120       else if (c == '\014')
1121         {
1122           clear (state);
1123         }
1124       else
1125         {
1126           cell->state = FLARE;
1127           cell->p_char = state->chars[c];
1128           cell->changed = True;
1129           state->cursor_x++;
1130
1131           if (c != ' ' && cell->p_char->blank_p)
1132             cell->p_char = state->chars[CURSOR_INDEX];
1133
1134           if (state->cursor_x >= state->grid_width - 1)
1135             {
1136               state->cursor_x = 0;
1137               if (state->cursor_y >= state->grid_height - 1)
1138                 scroll (state);
1139               else
1140                 state->cursor_y++;
1141             }
1142         }
1143       set_cursor (state, True);
1144     }
1145
1146   state->last_c = c;
1147 }
1148
1149
1150 static void
1151 update_display (p_state *state, Bool changed_only)
1152 {
1153   int x, y;
1154
1155   for (y = 0; y < state->grid_height; y++)
1156     for (x = 0; x < state->grid_width; x++)
1157       {
1158         p_cell *cell = &state->cells[state->grid_width * y + x];
1159         int width, height, tx, ty;
1160
1161         if (changed_only && !cell->changed)
1162           continue;
1163
1164         width = state->char_width * state->scale;
1165         height = state->char_height * state->scale;
1166         tx = x * width;
1167         ty = y * height;
1168
1169         if (cell->state == BLANK || cell->p_char->blank_p)
1170           {
1171             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1172                             tx, ty, width, height);
1173           }
1174         else
1175           {
1176 #ifdef FUZZY_BORDER
1177             GC gc1 = state->gcs[cell->state];
1178             GC gc2 = ((cell->state + 2) < state->ticks
1179                       ? state->gcs[cell->state + 2]
1180                       : 0);
1181             GC gc3 = (gc2 ? gc2 : gc1);
1182             if (gc3)
1183               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1184                           0, 0, width, height, tx, ty, 1L);
1185             if (gc2)
1186               {
1187                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1188                 XSetClipOrigin (state->dpy, gc1, tx, ty);
1189                 XFillRectangle (state->dpy, state->window, gc1,
1190                                 tx, ty, width, height);
1191                 XSetClipMask (state->dpy, gc1, None);
1192               }
1193 #else /* !FUZZY_BORDER */
1194
1195             XCopyPlane (state->dpy,
1196                         cell->p_char->pixmap, state->window,
1197                         state->gcs[cell->state],
1198                         0, 0, width, height, tx, ty, 1L);
1199
1200 #endif /* !FUZZY_BORDER */
1201           }
1202
1203         cell->changed = False;
1204       }
1205 }
1206
1207
1208 static unsigned long
1209 phosphor_draw (Display *dpy, Window window, void *closure)
1210 {
1211   p_state *state = (p_state *) closure;
1212   update_display (state, True);
1213   decay (state);
1214   drain_input (state);
1215   return state->delay;
1216 }
1217
1218 \f
1219 /* Subprocess.
1220  */
1221
1222 static void
1223 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1224 {
1225   p_state *state = (p_state *) closure;
1226   state->input_available_p = True;
1227 }
1228
1229
1230 static void
1231 launch_text_generator (p_state *state)
1232 {
1233   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1234   Display *dpy = state->dpy;
1235   char buf[255];
1236   char *oprogram = get_string_resource (dpy, "program", "Program");
1237   char *program = (char *) malloc (strlen (oprogram) + 50);
1238
1239   strcpy (program, "( ");
1240   strcat (program, oprogram);
1241
1242   /* Kludge!  Special-case "xscreensaver-text" to tell it how wide
1243      the screen is.  We used to do this by just always feeding
1244      `program' through sprintf() and setting the default value to
1245      "xscreensaver-text --cols %d", but that makes things blow up
1246      if someone ever uses a --program that includes a % anywhere.
1247    */
1248   if (!strcmp (oprogram, "xscreensaver-text"))
1249     sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
1250
1251   strcat (program, " ) 2>&1");
1252
1253 #ifdef HAVE_FORKPTY
1254   if(state->mode == 1)
1255     {
1256       int fd;
1257       struct winsize ws;
1258       
1259       ws.ws_row = state->grid_height - 1;
1260       ws.ws_col = state->grid_width  - 2;
1261       ws.ws_xpixel = state->xgwa.width;
1262       ws.ws_ypixel = state->xgwa.height;
1263       
1264       state->pipe = NULL;
1265       if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1266         {
1267           /* Unable to fork */
1268           sprintf (buf, "%.100s: forkpty", progname);
1269           perror(buf);
1270         }
1271       else if(!state->pid)
1272         {
1273           /* This is the child fork. */
1274           char *av[10];
1275           int i = 0;
1276           if (putenv("TERM=vt100"))
1277             abort();
1278           av[i++] = "/bin/sh";
1279           av[i++] = "-c";
1280           av[i++] = program;
1281           av[i] = 0;
1282           execvp (av[0], av);
1283           sprintf (buf, "%.100s: %.100s", progname, oprogram);
1284           perror(buf);
1285           exit(1);
1286         }
1287       else
1288         {
1289           /* This is the parent fork. */
1290           state->pipe = fdopen(fd, "r+");
1291           state->pipe_id =
1292             XtAppAddInput (app, fileno (state->pipe),
1293                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
1294                            subproc_cb, (XtPointer) state);
1295         }
1296     }
1297   else
1298 #endif /* HAVE_FORKPTY */
1299     {
1300       /* don't mess up controlling terminal if someone dumbly does
1301          "-pipe -program tcsh". */
1302       static int protected_stdin_p = 0;
1303       if (! protected_stdin_p) {
1304         fclose (stdin);
1305         open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
1306         protected_stdin_p = 1;
1307       }
1308
1309       if ((state->pipe = popen (program, "r")))
1310         {
1311           state->pipe_id =
1312             XtAppAddInput (app, fileno (state->pipe),
1313                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
1314                            subproc_cb, (XtPointer) state);
1315         }
1316       else
1317         {
1318           sprintf (buf, "%.100s: %.100s", progname, program);
1319           perror (buf);
1320         }
1321     }
1322
1323   free (program);
1324 }
1325
1326
1327 static void
1328 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1329 {
1330   p_state *state = (p_state *) closure;
1331   if (!state->pipe_timer) abort();
1332   state->pipe_timer = 0;
1333   launch_text_generator (state);
1334 }
1335
1336
1337 static void
1338 drain_input (p_state *state)
1339 {
1340   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1341   if (state->input_available_p)
1342     {
1343       unsigned char s[2];
1344       int n = read (fileno (state->pipe), (void *) s, 1);
1345       if (n == 1)
1346         {
1347           print_char (state, s[0]);
1348         }
1349       else
1350         {
1351           XtRemoveInput (state->pipe_id);
1352           state->pipe_id = 0;
1353           if (state->pid)
1354             {
1355               waitpid(state->pid, NULL, 0);
1356               fclose (state->pipe);
1357             }
1358           else
1359             {
1360               pclose (state->pipe);
1361             }
1362           state->pipe = 0;
1363
1364           if (state->cursor_x != 0) {   /* break line if unbroken */
1365             print_char (state, '\r');
1366             print_char (state, '\n');
1367           }
1368           print_char (state, '\r');     /* blank line */
1369           print_char (state, '\n');
1370
1371           /* Set up a timer to re-launch the subproc in a bit. */
1372           state->pipe_timer =
1373             XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1374                              relaunch_generator_timer,
1375                              (XtPointer) state);
1376         }
1377         
1378       state->input_available_p = False;
1379     }
1380 }
1381
1382
1383 /* The interpretation of the ModN modifiers is dependent on what keys
1384    are bound to them: Mod1 does not necessarily mean "meta".  It only
1385    means "meta" if Meta_L or Meta_R are bound to it.  If Meta_L is on
1386    Mod5, then Mod5 is the one that means Meta.  Oh, and Meta and Alt
1387    aren't necessarily the same thing.  Icepicks in my forehead!
1388  */
1389 static unsigned int
1390 do_icccm_meta_key_stupidity (Display *dpy)
1391 {
1392   unsigned int modbits = 0;
1393 # ifndef HAVE_COCOA
1394   int i, j, k;
1395   XModifierKeymap *modmap = XGetModifierMapping (dpy);
1396   for (i = 3; i < 8; i++)
1397     for (j = 0; j < modmap->max_keypermod; j++)
1398       {
1399         int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1400         KeySym *syms;
1401         int nsyms = 0;
1402         if (code == 0) continue;
1403         syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1404         for (k = 0; k < nsyms; k++)
1405           if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1406               syms[k] == XK_Alt_L  || syms[k] == XK_Alt_R)
1407             modbits |= (1 << i);
1408         XFree (syms);
1409       }
1410   XFreeModifiermap (modmap);
1411 # endif /* HAVE_COCOA */
1412   return modbits;
1413 }
1414
1415 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
1416  */
1417 static unsigned int
1418 meta_modifier (p_state *state)
1419 {
1420   if (!state->meta_done_once)
1421     {
1422       /* Really, we are supposed to recompute this if a KeymapNotify
1423          event comes in, but fuck it. */
1424       state->meta_done_once = True;
1425       state->meta_mask = do_icccm_meta_key_stupidity (state->dpy);
1426     }
1427   return state->meta_mask;
1428 }
1429
1430
1431 static void
1432 phosphor_reshape (Display *dpy, Window window, void *closure, 
1433                  unsigned int w, unsigned int h)
1434 {
1435   p_state *state = (p_state *) closure;
1436   resize_grid (state);
1437
1438 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1439   if (state->pid)
1440     {
1441       /* Tell the sub-process that the screen size has changed. */
1442       struct winsize ws;
1443       ws.ws_row = state->grid_height - 1;
1444       ws.ws_col = state->grid_width  - 2;
1445       ws.ws_xpixel = state->xgwa.width;
1446       ws.ws_ypixel = state->xgwa.height;
1447       ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1448       kill (state->pid, SIGWINCH);
1449     }
1450 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1451 }
1452
1453 static Bool
1454 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1455 {
1456   p_state *state = (p_state *) closure;
1457
1458   if (event->xany.type == Expose)
1459     update_display (state, False);
1460   else if (event->xany.type == KeyPress)
1461     {
1462       KeySym keysym;
1463       unsigned char c = 0;
1464       XLookupString (&event->xkey, (char *) &c, 1, &keysym,
1465                      &state->compose);
1466       if (c != 0 && state->pipe)
1467         {
1468           if (!state->swap_bs_del_p) ;
1469           else if (c == 127) c = 8;
1470           else if (c == 8)   c = 127;
1471
1472           /* If meta was held down, send ESC, or turn on the high bit. */
1473           if (event->xkey.state & meta_modifier (state))
1474             {
1475               if (state->meta_sends_esc_p)
1476                 fputc ('\033', state->pipe);
1477               else
1478                 c |= 0x80;
1479             }
1480
1481           fputc (c, state->pipe);
1482           fflush (state->pipe);
1483           event->xany.type = 0;  /* don't interpret this event defaultly. */
1484         }
1485       return True;
1486     }
1487
1488   return False;
1489 }
1490
1491 static void
1492 phosphor_free (Display *dpy, Window window, void *closure)
1493 {
1494   p_state *state = (p_state *) closure;
1495
1496   if (state->pipe_id)
1497     XtRemoveInput (state->pipe_id);
1498   if (state->pipe)
1499     pclose (state->pipe);
1500   if (state->cursor_timer)
1501     XtRemoveTimeOut (state->cursor_timer);
1502   if (state->pipe_timer)
1503     XtRemoveTimeOut (state->pipe_timer);
1504
1505   /* #### there's more to free here */
1506
1507   free (state);
1508 }
1509
1510
1511
1512 static const char *phosphor_defaults [] = {
1513   ".background:            Black",
1514   ".foreground:            #00FF00",
1515   "*fadeForeground:        #006400",
1516   "*flareForeground:       #FFFFFF",
1517 #if defined(BUILTIN_FONT)
1518   "*font:                  (builtin)",
1519 #elif defined(HAVE_COCOA)
1520   "*font:                  Monaco 15",
1521 #else
1522   "*font:                  fixed",
1523 #endif
1524   "*scale:                 6",
1525   "*ticks:                 20",
1526   "*delay:                 50000",
1527   "*cursor:                333",
1528   "*program:               xscreensaver-text",
1529   "*relaunch:              5",
1530   "*metaSendsESC:          True",
1531   "*swapBSDEL:             True",
1532 #ifdef HAVE_FORKPTY
1533   "*mode:                  pty",
1534 #else  /* !HAVE_FORKPTY */
1535   "*mode:                  pipe",
1536 #endif /* !HAVE_FORKPTY */
1537   0
1538 };
1539
1540 static XrmOptionDescRec phosphor_options [] = {
1541   { "-font",            ".font",                XrmoptionSepArg, 0 },
1542   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
1543   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
1544   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1545   { "-program",         ".program",             XrmoptionSepArg, 0 },
1546   { "-pty",             ".mode",                XrmoptionNoArg, "pty"   },
1547   { "-pipe",            ".mode",                XrmoptionNoArg, "pipe"  },
1548   { "-meta",            ".metaSendsESC",        XrmoptionNoArg, "False" },
1549   { "-esc",             ".metaSendsESC",        XrmoptionNoArg, "True"  },
1550   { "-bs",              ".swapBSDEL",           XrmoptionNoArg, "False" },
1551   { "-del",             ".swapBSDEL",           XrmoptionNoArg, "True"  },
1552   { 0, 0, 0, 0 }
1553 };
1554
1555
1556 XSCREENSAVER_MODULE ("Phosphor", phosphor)