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