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