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