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