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