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