From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999-2013 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Phosphor -- simulate a glass tty with long-sustain phosphor.
12  * Written by Jamie Zawinski <jwz@jwz.org>
13  * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
14  */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif /* HAVE_CONFIG_H */
19
20 #ifndef HAVE_COCOA
21 # include <X11/Intrinsic.h>
22 #endif
23
24 #include "screenhack.h"
25 #include "textclient.h"
26
27 #define FUZZY_BORDER
28
29 #define MAX(a,b) ((a)>(b)?(a):(b))
30 #define MIN(a,b) ((a)<(b)?(a):(b))
31
32 #define BLANK  0
33 #define FLARE  1
34 #define NORMAL 2
35 #define FADE   3
36 #define STATE_MAX FADE
37
38 #define CURSOR_INDEX 128
39
40 #define NPAR 16
41
42 #define BUILTIN_FONT
43
44 #ifdef BUILTIN_FONT
45 # include "images/6x10font.xbm"
46 #endif /* BUILTIN_FONT */
47
48 typedef struct {
49   unsigned char name;
50   int width, height;
51   Pixmap pixmap;
52 #ifdef FUZZY_BORDER
53   Pixmap pixmap2;
54 #endif /* FUZZY_BORDER */
55   Bool blank_p;
56 } p_char;
57
58 typedef struct {
59   p_char *p_char;
60   int state;
61   Bool changed;
62 } p_cell;
63
64 typedef struct {
65   Display *dpy;
66   Window window;
67   XWindowAttributes xgwa;
68   XFontStruct *font;
69   const char *program;
70   int grid_width, grid_height;
71   int char_width, char_height;
72   int saved_x, saved_y;
73   int scale;
74   int ticks;
75   int mode;
76   int escstate;
77   int csiparam[NPAR];
78   int curparam;
79   p_char **chars;
80   p_cell *cells;
81   XGCValues gcv;
82   GC gc0;
83   GC gc1;
84 #ifdef FUZZY_BORDER
85   GC gc2;
86 #endif /* FUZZY_BORDER */
87   GC *gcs;
88   XImage *font_bits;
89
90   int cursor_x, cursor_y;
91   XtIntervalId cursor_timer;
92   Time cursor_blink;
93   int delay;
94   Bool pty_p;
95
96   text_data *tc;
97
98   char last_c;
99   int bk;
100
101 } p_state;
102
103
104 static void capture_font_bits (p_state *state);
105 static p_char *make_character (p_state *state, int c);
106 static void char_to_pixmap (p_state *state, p_char *pc, int c);
107
108
109 /* About font metrics:
110
111    "lbearing" is the distance from the leftmost pixel of a character to
112    the logical origin of that character.  That is, it is the number of
113    pixels of the character which are to the left of its logical origin.
114
115    "rbearing" is the distance from the logical origin of a character to
116    the rightmost pixel of a character.  That is, it is the number of
117    pixels of the character to the right of its logical origin.
118
119    "descent" is the distance from the bottommost pixel of a character to
120    the logical baseline.  That is, it is the number of pixels of the
121    character which are below the baseline.
122
123    "ascent" is the distance from the logical baseline to the topmost pixel.
124    That is, it is the number of pixels of the character above the baseline.
125
126    Therefore, the bounding box of the "ink" of a character is
127    lbearing + rbearing by ascent + descent;
128
129    "width" is the distance from the logical origin of this character to
130    the position where the logical orgin of the next character should be
131    placed.
132
133    For our purposes, we're only interested in the part of the character
134    lying inside the "width" box.  If the characters have ink outside of
135    that box (the "charcell" box) then we're going to lose it.  Alas.
136  */
137
138
139 static void clear (p_state *);
140 static void set_cursor (p_state *, Bool on);
141
142 static void *
143 phosphor_init (Display *dpy, Window window)
144 {
145   int i;
146   unsigned long flags;
147   p_state *state = (p_state *) calloc (sizeof(*state), 1);
148   char *fontname = get_string_resource (dpy, "font", "Font");
149   XFontStruct *font;
150
151   state->dpy = dpy;
152   state->window = window;
153
154   XGetWindowAttributes (dpy, window, &state->xgwa);
155 /*  XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
156
157   state->delay = get_integer_resource (dpy, "delay", "Integer");
158   state->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
159
160   if (!strcasecmp (fontname, "builtin") ||
161       !strcasecmp (fontname, "(builtin)"))
162     {
163 #ifndef BUILTIN_FONT
164       fprintf (stderr, "%s: no builtin font\n", progname);
165       state->font = XLoadQueryFont (dpy, "fixed");
166 #endif /* !BUILTIN_FONT */
167     }
168   else
169     {
170       state->font = XLoadQueryFont (dpy, fontname);
171
172       if (!state->font)
173         {
174           fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
175           state->font = XLoadQueryFont (dpy, "fixed");
176         }
177       if (!state->font)
178         {
179           fprintf(stderr, "couldn't load font \"fixed\"");
180           exit(1);
181         }
182     }
183
184   font = state->font;
185   state->scale = get_integer_resource (dpy, "scale", "Integer");
186   state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
187   state->escstate = 0;
188
189
190   state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
191
192 # ifdef BUILTIN_FONT
193   if (! font)
194     {
195       state->char_width  = (font6x10_width / 256) - 1;
196       state->char_height = font6x10_height;
197     }
198   else
199 # endif /* BUILTIN_FONT */
200     {
201       state->char_width  = font->max_bounds.width;
202       state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
203     }
204
205   state->grid_width = state->xgwa.width / (state->char_width * state->scale);
206   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
207   state->cells = (p_cell *) calloc (sizeof(p_cell),
208                                     state->grid_width * state->grid_height);
209   state->chars = (p_char **) calloc (sizeof(p_char *), 256);
210
211   state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
212
213   {
214     int ncolors = MAX (0, state->ticks - 3);
215     XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
216     int h1, h2;
217     double s1, s2, v1, v2;
218
219     unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
220                                            "foreground", "Foreground");
221     unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
222                                            "background", "Background");
223     unsigned long flare = get_pixel_resource (state->dpy,state->xgwa.colormap,
224                                               "flareForeground", "Foreground");
225     unsigned long fade = get_pixel_resource (state->dpy,state->xgwa.colormap,
226                                              "fadeForeground", "Foreground");
227
228     XColor start, end;
229
230     start.pixel = fade;
231     XQueryColor (state->dpy, state->xgwa.colormap, &start);
232
233     end.pixel = bg;
234     XQueryColor (state->dpy, state->xgwa.colormap, &end);
235
236     /* Now allocate a ramp of colors from the main color to the background. */
237     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
238     rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
239     make_color_ramp (state->xgwa.screen, state->xgwa.visual,
240                      state->xgwa.colormap,
241                      h1, s1, v1,
242                      h2, s2, v2,
243                      colors, &ncolors,
244                      False, True, False);
245
246     /* Adjust to the number of colors we actually got. */
247     state->ticks = ncolors + STATE_MAX;
248
249     /* Now, GCs all around.
250      */
251     state->gcv.font = (font ? font->fid : 0);
252     state->gcv.cap_style = CapRound;
253 #ifdef FUZZY_BORDER
254     state->gcv.line_width = (int) (((long) state->scale) * 1.3);
255     if (state->gcv.line_width == state->scale)
256       state->gcv.line_width++;
257 #else /* !FUZZY_BORDER */
258     state->gcv.line_width = (int) (((long) state->scale) * 0.9);
259     if (state->gcv.line_width >= state->scale)
260       state->gcv.line_width = state->scale - 1;
261     if (state->gcv.line_width < 1)
262       state->gcv.line_width = 1;
263 #endif /* !FUZZY_BORDER */
264
265     flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
266
267     state->gcv.background = bg;
268     state->gcv.foreground = bg;
269     state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
270                                    &state->gcv);
271
272     state->gcv.foreground = flare;
273     state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
274                                    &state->gcv);
275
276     state->gcv.foreground = fg;
277     state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
278                                     &state->gcv);
279
280     for (i = 0; i < ncolors; i++)
281       {
282         state->gcv.foreground = colors[i].pixel;
283         state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
284                                                flags, &state->gcv);
285       }
286   }
287
288   capture_font_bits (state);
289
290   set_cursor (state, True);
291
292 /*  clear (state);*/
293
294   state->tc = textclient_open (dpy);
295   textclient_reshape (state->tc,
296                       state->xgwa.width,
297                       state->xgwa.height,
298                       state->grid_width  - 1,
299                       state->grid_height - 1);
300
301   return state;
302 }
303
304
305 /* Re-query the window size and update the internal character grid if changed.
306  */
307 static Bool
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 False;
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   return True;
345 }
346
347
348 static void
349 capture_font_bits (p_state *state)
350 {
351   XFontStruct *font = state->font;
352   int safe_width, height;
353   unsigned char string[257];
354   int i;
355   Pixmap p;
356
357 # ifdef BUILTIN_FONT
358   Pixmap p2 = 0;
359
360   if (!font)
361     {
362       safe_width = state->char_width + 1;
363       height = state->char_height;
364       p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
365                                         (char *) font6x10_bits,
366                                         font6x10_width,
367                                         font6x10_height,
368                                         1, 0, 1);
369     }
370   else
371 # endif /* BUILTIN_FONT */
372     {
373       safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
374       height = state->char_height;
375     }
376
377   p = XCreatePixmap (state->dpy, state->window,
378                      (safe_width * 256), height, 1);
379
380   for (i = 0; i < 256; i++)
381     string[i] = (unsigned char) i;
382   string[256] = 0;
383
384   state->gcv.foreground = 0;
385   state->gcv.background = 0;
386   state->gc0 = XCreateGC (state->dpy, p,
387                           (GCForeground | GCBackground),
388                           &state->gcv);
389
390   state->gcv.foreground = 1;
391   state->gc1 = XCreateGC (state->dpy, p,
392                           ((font ? GCFont : 0) |
393                            GCForeground | GCBackground |
394                            GCCapStyle | GCLineWidth),
395                           &state->gcv);
396
397 #ifdef HAVE_COCOA
398   jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
399   jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
400 #endif
401
402 #ifdef FUZZY_BORDER
403   {
404     state->gcv.line_width = (int) (((long) state->scale) * 0.8);
405     if (state->gcv.line_width >= state->scale)
406       state->gcv.line_width = state->scale - 1;
407     if (state->gcv.line_width < 1)
408       state->gcv.line_width = 1;
409     state->gc2 = XCreateGC (state->dpy, p,
410                             ((font ? GCFont : 0) |
411                              GCForeground | GCBackground |
412                              GCCapStyle | GCLineWidth),
413                             &state->gcv);
414   }
415 #endif /* FUZZY_BORDER */
416
417   XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
418
419 # ifdef BUILTIN_FONT
420   if (p2)
421     {
422       XCopyPlane (state->dpy, p2, p, state->gc1,
423                   0, 0, font6x10_width, font6x10_height, 
424                   0, 0, 1);
425       XFreePixmap (state->dpy, p2);
426     }
427   else
428 # endif /* BUILTIN_FONT */
429     {
430       for (i = 0; i < 256; i++)
431         {
432           if (string[i] < font->min_char_or_byte2 ||
433               string[i] > font->max_char_or_byte2)
434             continue;
435           XDrawString (state->dpy, p, state->gc1,
436                        i * safe_width, font->ascent,
437                        (char *) (string + i), 1);
438         }
439     }
440
441   /* Draw the cursor. */
442   XFillRectangle (state->dpy, p, state->gc1,
443                   (CURSOR_INDEX * safe_width), 1,
444                   (font
445                    ? (font->per_char
446                       ? font->per_char['n'-font->min_char_or_byte2].width
447                       : font->max_bounds.width)
448                    : state->char_width),
449                   (font
450                    ? font->ascent - 1
451                    : state->char_height));
452
453   state->font_bits = XGetImage (state->dpy, p, 0, 0,
454                                 (safe_width * 256), height, ~0L, XYPixmap);
455   XFreePixmap (state->dpy, p);
456
457   for (i = 0; i < 256; i++)
458     state->chars[i] = make_character (state, i);
459   state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
460 }
461
462
463 static p_char *
464 make_character (p_state *state, int c)
465 {
466   p_char *pc = (p_char *) malloc (sizeof (*pc));
467   pc->name = (unsigned char) c;
468   pc->width =  state->scale * state->char_width;
469   pc->height = state->scale * state->char_height;
470   char_to_pixmap (state, pc, c);
471   return pc;
472 }
473
474
475 static void
476 char_to_pixmap (p_state *state, p_char *pc, int c)
477 {
478   Pixmap p = 0;
479   GC gc;
480 #ifdef FUZZY_BORDER
481   Pixmap p2 = 0;
482   GC gc2;
483 #endif /* FUZZY_BORDER */
484   int from, to;
485   int x1, y;
486
487   XFontStruct *font = state->font;
488   int safe_width = (font 
489                     ? font->max_bounds.rbearing - font->min_bounds.lbearing
490                     : state->char_width + 1);
491
492   int width  = state->scale * state->char_width;
493   int height = state->scale * state->char_height;
494
495   if (font && (c < font->min_char_or_byte2 ||
496                c > font->max_char_or_byte2))
497     goto DONE;
498
499   gc = state->gc1;
500   p = XCreatePixmap (state->dpy, state->window, width, height, 1);
501   XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
502 #ifdef FUZZY_BORDER
503   gc2 = state->gc2;
504   p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
505   XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
506 #endif /* FUZZY_BORDER */
507
508   from = safe_width * c;
509   to =   safe_width * (c + 1);
510
511 #if 0
512   if (c > 75 && c < 150)
513     {
514       printf ("\n=========== %d (%c)\n", c, c);
515       for (y = 0; y < state->char_height; y++)
516         {
517           for (x1 = from; x1 < to; x1++)
518             printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
519           printf ("\n");
520         }
521     }
522 #endif
523
524   pc->blank_p = True;
525   for (y = 0; y < state->char_height; y++)
526     for (x1 = from; x1 < to; x1++)
527       if (XGetPixel (state->font_bits, x1, y))
528         {
529           int xoff = state->scale / 2;
530           int x2;
531           for (x2 = x1; x2 < to; x2++)
532             if (!XGetPixel (state->font_bits, x2, y))
533               break;
534           x2--;
535           XDrawLine (state->dpy, p, gc,
536                      (x1 - from) * state->scale + xoff, y * state->scale,
537                      (x2 - from) * state->scale + xoff, y * state->scale);
538 #ifdef FUZZY_BORDER
539           XDrawLine (state->dpy, p2, gc2,
540                      (x1 - from) * state->scale + xoff, y * state->scale,
541                      (x2 - from) * state->scale + xoff, y * state->scale);
542 #endif /* FUZZY_BORDER */
543           x1 = x2;
544           pc->blank_p = False;
545         }
546
547   /*  if (pc->blank_p && c == CURSOR_INDEX)
548     abort();*/
549
550  DONE:
551   pc->pixmap = p;
552 #ifdef FUZZY_BORDER
553   pc->pixmap2 = p2;
554 #endif /* FUZZY_BORDER */
555 }
556
557 \f
558 /* Managing the display. 
559  */
560
561 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
562 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
563
564 static Bool
565 set_cursor_1 (p_state *state, Bool on)
566 {
567   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
568                               + state->cursor_x];
569   p_char *cursor = state->chars[CURSOR_INDEX];
570   int new_state = (on ? NORMAL : FADE);
571
572   if (cell->p_char != cursor)
573     cell->changed = True;
574
575   if (cell->state != new_state)
576     cell->changed = True;
577
578   cell->p_char = cursor;
579   cell->state = new_state;
580   return cell->changed;
581 }
582
583 static void
584 set_cursor (p_state *state, Bool on)
585 {
586   if (set_cursor_1 (state, on))
587     {
588       if (state->cursor_timer)
589         XtRemoveTimeOut (state->cursor_timer);
590       state->cursor_timer = 0;
591       cursor_on_timer (state, 0);
592     }
593 }
594
595
596 static void
597 cursor_off_timer (XtPointer closure, XtIntervalId *id)
598 {
599   p_state *state = (p_state *) closure;
600   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
601   set_cursor_1 (state, False);
602   state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
603                                          cursor_on_timer, closure);
604 }
605
606 static void
607 cursor_on_timer (XtPointer closure, XtIntervalId *id)
608 {
609   p_state *state = (p_state *) closure;
610   XtAppContext app = XtDisplayToApplicationContext (state->dpy);
611   set_cursor_1 (state, True);
612   state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
613                                          cursor_off_timer, closure);
614 }
615
616
617 static void
618 clear (p_state *state)
619 {
620   int x, y;
621   state->cursor_x = 0;
622   state->cursor_y = 0;
623   for (y = 0; y < state->grid_height; y++)
624     for (x = 0; x < state->grid_width; x++)
625       {
626         p_cell *cell = &state->cells[state->grid_width * y + x];
627         if (cell->state == FLARE || cell->state == NORMAL)
628           {
629             cell->state = FADE;
630             cell->changed = True;
631           }
632       }
633   set_cursor (state, True);
634 }
635
636
637 static void
638 decay (p_state *state)
639 {
640   int x, y;
641   for (y = 0; y < state->grid_height; y++)
642     for (x = 0; x < state->grid_width; x++)
643       {
644         p_cell *cell = &state->cells[state->grid_width * y + x];
645         if (cell->state == FLARE)
646           {
647             cell->state = NORMAL;
648             cell->changed = True;
649           }
650         else if (cell->state >= FADE)
651           {
652             cell->state++;
653             if (cell->state >= state->ticks)
654               cell->state = BLANK;
655             cell->changed = True;
656           }
657       }
658 }
659
660
661 static void
662 scroll (p_state *state)
663 {
664   int x, y;
665
666   for (x = 0; x < state->grid_width; x++)
667     {
668       p_cell *from = 0, *to = 0;
669       for (y = 1; y < state->grid_height; y++)
670         {
671           from = &state->cells[state->grid_width * y     + x];
672           to   = &state->cells[state->grid_width * (y-1) + x];
673
674           if ((from->state == FLARE || from->state == NORMAL) &&
675               !from->p_char->blank_p)
676             {
677               *to = *from;
678               to->state = NORMAL;  /* should be FLARE?  Looks bad... */
679             }
680           else
681             {
682               if (to->state == FLARE || to->state == NORMAL)
683                 to->state = FADE;
684             }
685
686           to->changed = True;
687         }
688
689       to = from;
690       if (to && (to->state == FLARE || to->state == NORMAL))
691         {
692           to->state = FADE;
693           to->changed = True;
694         }
695     }
696   set_cursor (state, True);
697 }
698
699
700 static void
701 print_char (p_state *state, int c)
702 {
703   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
704                                + state->cursor_x];
705
706   /* Start the cursor fading (in case we don't end up overwriting it.) */
707   if (cell->state == FLARE || cell->state == NORMAL)
708     {
709       cell->state = FADE;
710       cell->changed = True;
711     }
712   
713 #ifdef HAVE_FORKPTY
714   if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
715                        It would be nice if we could just interpret them all
716                        the time, but that would require subprocesses to send
717                        CRLF line endings instead of bare LF, so that's no good.
718                      */
719     {
720       int i, start, end;
721       switch (state->escstate)
722         {
723         case 0:
724           switch (c)
725             {
726             case 7: /* BEL */
727               /* Dummy case - we don't want the screensaver to beep */
728               /* #### But maybe this should flash the screen? */
729               break;
730             case 8: /* BS */
731               if (state->cursor_x > 0)
732                 state->cursor_x--;
733               break;
734             case 9: /* HT */
735               if (state->cursor_x < state->grid_width - 8)
736                 {
737                   state->cursor_x = (state->cursor_x & ~7) + 8;
738                 }
739               else
740                 {
741                   state->cursor_x = 0;
742                   if (state->cursor_y < state->grid_height - 1)
743                     state->cursor_y++;
744                   else
745                     scroll (state);
746                 }
747               break;
748             case 10: /* LF */
749             case 11: /* VT */
750             case 12: /* FF */
751               if(state->last_c == 13)
752                 {
753                   cell->state = NORMAL;
754                   cell->p_char = state->chars[state->bk];
755                   cell->changed = True;
756                 }
757               if (state->cursor_y < state->grid_height - 1)
758                 state->cursor_y++;
759               else
760                 scroll (state);
761               break;
762             case 13: /* CR */
763               state->cursor_x = 0;
764               cell = &state->cells[state->grid_width * state->cursor_y];
765               if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
766                 state->bk = ' ';
767               else
768                 state->bk = cell->p_char->name;
769               break;
770             case 14: /* SO */
771             case 15: /* SI */
772               /* Dummy case - I don't want to load several fonts for
773                  the maybe two programs world-wide that use that */
774               break;
775             case 24: /* CAN */
776             case 26: /* SUB */
777               /* Dummy case - these interrupt escape sequences, so
778                  they don't do anything in this state */
779               break;
780             case 27: /* ESC */
781               state->escstate = 1;
782               break;
783             case 127: /* DEL */
784               /* Dummy case - this is supposed to be ignored */
785               break;
786             case 155: /* CSI */
787               state->escstate = 2;
788               for(i = 0; i < NPAR; i++)
789                 state->csiparam[i] = 0;
790               state->curparam = 0;
791               break;
792             default:
793               cell->state = FLARE;
794               cell->p_char = state->chars[c];
795               cell->changed = True;
796               state->cursor_x++;
797
798               if (c != ' ' && cell->p_char->blank_p)
799                 cell->p_char = state->chars[CURSOR_INDEX];
800
801               if (state->cursor_x >= state->grid_width - 1)
802                 {
803                   state->cursor_x = 0;
804                   if (state->cursor_y >= state->grid_height - 1)
805                     scroll (state);
806                   else
807                     state->cursor_y++;
808                 }
809               break;
810             }
811           break;
812         case 1:
813           switch (c)
814             {
815             case 24: /* CAN */
816             case 26: /* SUB */
817               state->escstate = 0;
818               break;
819             case 'c': /* Reset */
820               clear (state);
821               state->escstate = 0;
822               break;
823             case 'D': /* Linefeed */
824               if (state->cursor_y < state->grid_height - 1)
825                 state->cursor_y++;
826               else
827                 scroll (state);
828               state->escstate = 0;
829               break;
830             case 'E': /* Newline */
831               state->cursor_x = 0;
832               state->escstate = 0;
833               break;
834             case 'M': /* Reverse newline */
835               if (state->cursor_y > 0)
836                 state->cursor_y--;
837               state->escstate = 0;
838               break;
839             case '7': /* Save state */
840               state->saved_x = state->cursor_x;
841               state->saved_y = state->cursor_y;
842               state->escstate = 0;
843               break;
844             case '8': /* Restore state */
845               state->cursor_x = state->saved_x;
846               state->cursor_y = state->saved_y;
847               state->escstate = 0;
848               break;
849             case '[': /* CSI */
850               state->escstate = 2;
851               for(i = 0; i < NPAR; i++)
852                 state->csiparam[i] = 0;
853               state->curparam = 0;
854               break;
855             case '%': /* Select charset */
856               /* No, I don't support UTF-8, since the phosphor font
857                  isn't even Unicode anyway. We must still catch the
858                  last byte, though. */
859             case '(':
860             case ')':
861               /* I don't support different fonts either - see above
862                  for SO and SI */
863               state->escstate = 3;
864               break;
865             default:
866               /* Escape sequences not supported:
867                * 
868                * H - Set tab stop
869                * Z - Terminal identification
870                * > - Keypad change
871                * = - Other keypad change
872                * ] - OS command
873                */
874               state->escstate = 0;
875               break;
876             }
877           break;
878         case 2:
879           switch (c)
880             {
881             case 24: /* CAN */
882             case 26: /* SUB */
883               state->escstate = 0;
884               break;
885             case '0': case '1': case '2': case '3': case '4':
886             case '5': case '6': case '7': case '8': case '9':
887               if (state->curparam < NPAR)
888                 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
889               break;
890             case ';':
891               state->csiparam[++state->curparam] = 0;
892               break;
893             case '[':
894               state->escstate = 3;
895               break;
896             case '@':
897               for (i = 0; i < state->csiparam[0]; i++)
898                 {
899                   if(++state->cursor_x > state->grid_width)
900                     {
901                       state->cursor_x = 0;
902                       if (state->cursor_y < state->grid_height - 1)
903                         state->cursor_y++;
904                       else
905                         scroll (state);
906                     }
907                   cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
908                   if (cell->state == FLARE || cell->state == NORMAL)
909                     {
910                       cell->state = FADE;
911                       cell->changed = True;
912                     }
913                 }
914               state->escstate = 0;
915               break;
916             case 'F':
917               state->cursor_x = 0;
918             case 'A':
919               if (state->csiparam[0] == 0)
920                 state->csiparam[0] = 1;
921               if ((state->cursor_y -= state->csiparam[0]) < 0)
922                 state->cursor_y = 0;
923               state->escstate = 0;
924               break;
925             case 'E':
926               state->cursor_x = 0;
927             case 'e':
928             case 'B':
929               if (state->csiparam[0] == 0)
930                 state->csiparam[0] = 1;
931               if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
932                 state->cursor_y = state->grid_height - 1;
933               state->escstate = 0;
934               break;
935             case 'a':
936             case 'C':
937               if (state->csiparam[0] == 0)
938                 state->csiparam[0] = 1;
939               if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
940                 state->cursor_x = state->grid_width - 1;
941               state->escstate = 0;
942               break;
943             case 'D':
944               if (state->csiparam[0] == 0)
945                 state->csiparam[0] = 1;
946               if ((state->cursor_x -= state->csiparam[0]) < 0)
947                 state->cursor_x = 0;
948               state->escstate = 0;
949               break;
950             case 'd':
951               if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
952                 state->cursor_y = state->grid_height - 1;
953               state->escstate = 0;
954               break;
955             case '`':
956             case 'G':
957               if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
958                 state->cursor_x = state->grid_width - 1;
959               state->escstate = 0;
960               break;
961             case 'f':
962             case 'H':
963               if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
964                 state->cursor_y = state->grid_height - 1;
965               if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
966                 state->cursor_x = state->grid_width - 1;
967               if(state->cursor_y < 0)
968                 state->cursor_y = 0;
969               if(state->cursor_x < 0)
970                 state->cursor_x = 0;
971               state->escstate = 0;
972               break;
973             case 'J':
974               start = 0;
975               end = state->grid_height * state->grid_width;
976               if (state->csiparam[0] == 0)
977                 start = state->grid_width * state->cursor_y + state->cursor_x;
978               if (state->csiparam[0] == 1)
979                 end = state->grid_width * state->cursor_y + state->cursor_x;
980               for (i = start; i < end; i++)
981                 {
982                   cell = &state->cells[i];
983                   if (cell->state == FLARE || cell->state == NORMAL)
984                     {
985                       cell->state = FADE;
986                       cell->changed = True;
987                     }
988                 }
989               set_cursor (state, True);
990               state->escstate = 0;
991               break;
992             case 'K':
993               start = 0;
994               end = state->grid_width;
995               if (state->csiparam[0] == 0)
996                 start = state->cursor_x;
997               if (state->csiparam[1] == 1)
998                 end = state->cursor_x;
999               for (i = start; i < end; i++)
1000                 {
1001                   if (cell->state == FLARE || cell->state == NORMAL)
1002                     {
1003                       cell->state = FADE;
1004                       cell->changed = True;
1005                     }
1006                   cell++;
1007                 }
1008               state->escstate = 0;
1009               break;
1010             case 's': /* Save position */
1011               state->saved_x = state->cursor_x;
1012               state->saved_y = state->cursor_y;
1013               state->escstate = 0;
1014               break;
1015             case 'u': /* Restore position */
1016               state->cursor_x = state->saved_x;
1017               state->cursor_y = state->saved_y;
1018               state->escstate = 0;
1019               break;
1020             case '?': /* DEC Private modes */
1021               if ((state->curparam != 0) || (state->csiparam[0] != 0))
1022                 state->escstate = 0;
1023               break;
1024             default:
1025               /* Known unsupported CSIs:
1026                *
1027                * L - Insert blank lines
1028                * M - Delete lines (I don't know what this means...)
1029                * P - Delete characters
1030                * X - Erase characters (difference with P being...?)
1031                * c - Terminal identification
1032                * g - Clear tab stop(s)
1033                * h - Set mode (Mainly due to its complexity and lack of good
1034                      docs)
1035                * l - Clear mode
1036                * m - Set mode (Phosphor is, per defenition, green on black)
1037                * n - Status report
1038                * q - Set keyboard LEDs
1039                * r - Set scrolling region (too exhausting - noone uses this,
1040                      right?)
1041                */
1042               state->escstate = 0;
1043               break;
1044             }
1045           break;
1046         case 3:
1047           state->escstate = 0;
1048           break;
1049         }
1050       set_cursor (state, True);
1051     }
1052   else
1053 #endif /* HAVE_FORKPTY */
1054     {
1055       if (c == '\t') c = ' ';   /* blah. */
1056
1057       if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
1058         {
1059           if (c == '\n' && state->last_c == '\r')
1060             ;   /* CRLF -- do nothing */
1061           else
1062             {
1063               state->cursor_x = 0;
1064               if (state->cursor_y == state->grid_height - 1)
1065                 scroll (state);
1066               else
1067                 state->cursor_y++;
1068             }
1069         }
1070       else if (c == '\014')
1071         {
1072           clear (state);
1073         }
1074       else
1075         {
1076           cell->state = FLARE;
1077           cell->p_char = state->chars[c];
1078           cell->changed = True;
1079           state->cursor_x++;
1080
1081           if (c != ' ' && cell->p_char->blank_p)
1082             cell->p_char = state->chars[CURSOR_INDEX];
1083
1084           if (state->cursor_x >= state->grid_width - 1)
1085             {
1086               state->cursor_x = 0;
1087               if (state->cursor_y >= state->grid_height - 1)
1088                 scroll (state);
1089               else
1090                 state->cursor_y++;
1091             }
1092         }
1093       set_cursor (state, True);
1094     }
1095
1096   state->last_c = c;
1097 }
1098
1099
1100 static void
1101 update_display (p_state *state, Bool changed_only)
1102 {
1103   int x, y;
1104
1105   for (y = 0; y < state->grid_height; y++)
1106     for (x = 0; x < state->grid_width; x++)
1107       {
1108         p_cell *cell = &state->cells[state->grid_width * y + x];
1109         int width, height, tx, ty;
1110
1111         if (changed_only && !cell->changed)
1112           continue;
1113
1114         width = state->char_width * state->scale;
1115         height = state->char_height * state->scale;
1116         tx = x * width;
1117         ty = y * height;
1118
1119         if (cell->state == BLANK || cell->p_char->blank_p)
1120           {
1121             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1122                             tx, ty, width, height);
1123           }
1124         else
1125           {
1126 #ifdef FUZZY_BORDER
1127             GC gc1 = state->gcs[cell->state];
1128             GC gc2 = ((cell->state + 2) < state->ticks
1129                       ? state->gcs[cell->state + 2]
1130                       : 0);
1131             GC gc3 = (gc2 ? gc2 : gc1);
1132             if (gc3)
1133               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1134                           0, 0, width, height, tx, ty, 1L);
1135             if (gc2)
1136               {
1137                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1138                 XSetClipOrigin (state->dpy, gc1, tx, ty);
1139                 XFillRectangle (state->dpy, state->window, gc1,
1140                                 tx, ty, width, height);
1141                 XSetClipMask (state->dpy, gc1, None);
1142               }
1143 #else /* !FUZZY_BORDER */
1144
1145             XCopyPlane (state->dpy,
1146                         cell->p_char->pixmap, state->window,
1147                         state->gcs[cell->state],
1148                         0, 0, width, height, tx, ty, 1L);
1149
1150 #endif /* !FUZZY_BORDER */
1151           }
1152
1153         cell->changed = False;
1154       }
1155 }
1156
1157
1158 static unsigned long
1159 phosphor_draw (Display *dpy, Window window, void *closure)
1160 {
1161   p_state *state = (p_state *) closure;
1162   int c;
1163   update_display (state, True);
1164   decay (state);
1165
1166   c = textclient_getc (state->tc);
1167   if (c > 0) 
1168     print_char (state, c);
1169
1170   return state->delay;
1171 }
1172
1173
1174 static void
1175 phosphor_reshape (Display *dpy, Window window, void *closure, 
1176                  unsigned int w, unsigned int h)
1177 {
1178   p_state *state = (p_state *) closure;
1179   Bool changed_p = resize_grid (state);
1180
1181   if (! changed_p) return;
1182
1183   textclient_reshape (state->tc, w, h,
1184                       state->grid_width  - 1,
1185                       state->grid_height - 1);
1186 }
1187
1188
1189 static Bool
1190 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1191 {
1192   p_state *state = (p_state *) closure;
1193
1194   if (event->xany.type == Expose)
1195     update_display (state, False);
1196   else if (event->xany.type == KeyPress)
1197     return textclient_putc (state->tc, &event->xkey);
1198   return False;
1199 }
1200
1201 static void
1202 phosphor_free (Display *dpy, Window window, void *closure)
1203 {
1204   p_state *state = (p_state *) closure;
1205
1206   textclient_close (state->tc);
1207   if (state->cursor_timer)
1208     XtRemoveTimeOut (state->cursor_timer);
1209
1210   /* #### there's more to free here */
1211
1212   free (state);
1213 }
1214
1215
1216
1217 static const char *phosphor_defaults [] = {
1218   ".background:            Black",
1219   ".foreground:            #00FF00",
1220   "*fpsSolid:              true",
1221   "*fadeForeground:        #006400",
1222   "*flareForeground:       #FFFFFF",
1223 #if defined(BUILTIN_FONT)
1224   "*font:                  (builtin)",
1225 #elif defined(HAVE_COCOA)
1226   "*font:                  Monaco 15",
1227 #else
1228   "*font:                  fixed",
1229 #endif
1230   "*scale:                 6",
1231   "*ticks:                 20",
1232   "*delay:                 50000",
1233   "*cursor:                333",
1234   "*program:               xscreensaver-text",
1235   "*relaunch:              5",
1236   "*metaSendsESC:          True",
1237   "*swapBSDEL:             True",
1238 #ifdef HAVE_FORKPTY
1239   "*usePty:                True",
1240 #else  /* !HAVE_FORKPTY */
1241   "*usePty:                False",
1242 #endif /* !HAVE_FORKPTY */
1243   0
1244 };
1245
1246 static XrmOptionDescRec phosphor_options [] = {
1247   { "-font",            ".font",                XrmoptionSepArg, 0 },
1248   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
1249   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
1250   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1251   { "-program",         ".program",             XrmoptionSepArg, 0 },
1252   { "-pipe",            ".usePty",              XrmoptionNoArg, "False" },
1253   { "-pty",             ".usePty",              XrmoptionNoArg, "True"  },
1254   { "-meta",            ".metaSendsESC",        XrmoptionNoArg, "False" },
1255   { "-esc",             ".metaSendsESC",        XrmoptionNoArg, "True"  },
1256   { "-bs",              ".swapBSDEL",           XrmoptionNoArg, "False" },
1257   { "-del",             ".swapBSDEL",           XrmoptionNoArg, "True"  },
1258   { 0, 0, 0, 0 }
1259 };
1260
1261
1262 XSCREENSAVER_MODULE ("Phosphor", phosphor)