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