From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999-2016 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; unsigned 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 int
733 process_unicrud (p_state *state, int c)
734 {
735   if ((c & 0xE0) == 0xC0) {        /* 110xxxxx: 11 bits, 2 bytes */
736     state->unicruds = 1;
737     state->unicrud[0] = c;
738     state->escstate = 102;
739   } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx: 16 bits, 3 bytes */
740     state->unicruds = 1;
741     state->unicrud[0] = c;
742     state->escstate = 103;
743   } else if ((c & 0xF8) == 0xF0) { /* 11110xxx: 21 bits, 4 bytes */
744     state->unicruds = 1;
745     state->unicrud[0] = c;
746     state->escstate = 104;
747   } else if ((c & 0xFC) == 0xF8) { /* 111110xx: 26 bits, 5 bytes */
748     state->unicruds = 1;
749     state->unicrud[0] = c;
750     state->escstate = 105;
751   } else if ((c & 0xFE) == 0xFC) { /* 1111110x: 31 bits, 6 bytes */
752     state->unicruds = 1;
753     state->unicrud[0] = c;
754     state->escstate = 106;
755   } else if (state->unicruds == 0) {
756     return c;
757   } else {
758     int total = state->escstate - 100;  /* see what I did there */
759     if (state->unicruds < total) {
760       /* Buffer more bytes of the UTF-8 sequence */
761       state->unicrud[state->unicruds++] = c;
762     }
763
764     if (state->unicruds >= total) {
765       /* Done! Convert it to Latin1 and print that. */
766       char *s;
767       state->unicrud[state->unicruds] = 0;
768       s = utf8_to_latin1 ((const char *) state->unicrud, False);
769       state->unicruds = 0;
770       state->escstate = 0;
771       if (s) {
772         c = (unsigned char) s[0];
773         free (s);
774         return c;
775       }
776     }
777   }
778   return 0;
779 }
780
781
782 static void
783 print_char (p_state *state, int c)
784 {
785   int cols = state->grid_width;
786   int rows = state->grid_height;
787   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
788                                + state->cursor_x];
789
790   /* Start the cursor fading (in case we don't end up overwriting it.) */
791   if (cell->state == FLARE || cell->state == NORMAL)
792     {
793       cell->state = FADE;
794       cell->changed = True;
795     }
796   
797 #ifdef HAVE_FORKPTY
798   if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
799                        It would be nice if we could just interpret them all
800                        the time, but that would require subprocesses to send
801                        CRLF line endings instead of bare LF, so that's no good.
802                      */
803     {
804       int i;
805       int start, end;
806
807       /* Mostly duplicated in apple2-main.c */
808
809       switch (state->escstate)
810         {
811         case 0:
812           switch (c)
813             {
814             case 7: /* BEL */
815               /* Dummy case - we don't want the screensaver to beep */
816               /* #### But maybe this should flash the screen? */
817               break;
818             case 8: /* BS */
819               if (state->cursor_x > 0)
820                 state->cursor_x--;
821               break;
822             case 9: /* HT */
823               if (state->cursor_x < cols - 8)
824                 {
825                   state->cursor_x = (state->cursor_x & ~7) + 8;
826                 }
827               else
828                 {
829                   state->cursor_x = 0;
830                   if (state->cursor_y < rows - 1)
831                     state->cursor_y++;
832                   else
833                     scroll (state);
834                 }
835               break;
836             case 10: /* LF */
837 # ifndef HAVE_FORKPTY
838               state->cursor_x = 0;      /* No ptys on iPhone; assume CRLF. */
839 # endif
840             case 11: /* VT */
841             case 12: /* FF */
842               if(state->last_c == 13)
843                 {
844                   cell->state = NORMAL;
845                   cell->p_char = state->chars[state->bk];
846                   cell->changed = True;
847                 }
848               if (state->cursor_y < rows - 1)
849                 state->cursor_y++;
850               else
851                 scroll (state);
852               break;
853             case 13: /* CR */
854               state->cursor_x = 0;
855               cell = &state->cells[cols * state->cursor_y];
856               if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
857                 state->bk = ' ';
858               else
859                 state->bk = cell->p_char->name;
860               break;
861             case 14: /* SO */
862             case 15: /* SI */
863               /* Dummy case - there is one and only one font. */
864               break;
865             case 24: /* CAN */
866             case 26: /* SUB */
867               /* Dummy case - these interrupt escape sequences, so
868                  they don't do anything in this state */
869               break;
870             case 27: /* ESC */
871               state->escstate = 1;
872               break;
873             case 127: /* DEL */
874               /* Dummy case - this is supposed to be ignored */
875               break;
876             case 155: /* CSI */
877               state->escstate = 2;
878               for(i = 0; i < NPAR; i++)
879                 state->csiparam[i] = 0;
880               state->curparam = 0;
881               break;
882             default:
883
884             PRINT: /* Come from states 102-106 */
885               c = process_unicrud (state, c);
886               if (! c)
887                 break;
888
889               /* If the cursor is in column 39 and we print a character, then
890                  that character shows up in column 39, and the cursor is no
891                  longer visible on the screen (it's in "column 40".)  If
892                  another character is printed, then that character shows up in
893                  column 0, and the cursor moves to column 1.
894
895                  This is empirically what xterm and gnome-terminal do, so that
896                  must be the right thing.  (In xterm, the cursor vanishes,
897                  whereas; in gnome-terminal, the cursor overprints the
898                  character in col 39.)
899                */
900               cell->state = FLARE;
901               cell->p_char = state->chars[c];
902               cell->changed = True;
903               state->cursor_x++;
904
905               if (c != ' ' && cell->p_char->blank_p)
906                 cell->p_char = state->chars[CURSOR_INDEX];
907
908               if (state->cursor_x >= cols - 1 /*####*/)
909                 {
910                   state->cursor_x = 0;
911                   if (state->cursor_y >= rows - 1)
912                     scroll (state);
913                   else
914                     state->cursor_y++;
915                 }
916               break;
917             }
918           break;
919         case 1:
920           switch (c)
921             {
922             case 24: /* CAN */
923             case 26: /* SUB */
924               state->escstate = 0;
925               break;
926             case 'c': /* Reset */
927               clear (state);
928               state->escstate = 0;
929               break;
930             case 'D': /* Linefeed */
931               if (state->cursor_y < rows - 1)
932                 state->cursor_y++;
933               else
934                 scroll (state);
935               state->escstate = 0;
936               break;
937             case 'E': /* Newline */
938               state->cursor_x = 0;
939               state->escstate = 0;
940               break;
941             case 'M': /* Reverse newline */
942               if (state->cursor_y > 0)
943                 state->cursor_y--;
944               state->escstate = 0;
945               break;
946             case '7': /* Save state */
947               state->saved_x = state->cursor_x;
948               state->saved_y = state->cursor_y;
949               state->escstate = 0;
950               break;
951             case '8': /* Restore state */
952               state->cursor_x = state->saved_x;
953               state->cursor_y = state->saved_y;
954               state->escstate = 0;
955               break;
956             case '[': /* CSI */
957               state->escstate = 2;
958               for(i = 0; i < NPAR; i++)
959                 state->csiparam[i] = 0;
960               state->curparam = 0;
961               break;
962             case '%': /* Select charset */
963               /* @: Select default (ISO 646 / ISO 8859-1)
964                  G: Select UTF-8
965                  8: Select UTF-8 (obsolete)
966
967                  We can just ignore this and always process UTF-8, I think?
968                  We must still catch the last byte, though.
969                */
970             case '(':
971             case ')':
972               /* I don't support different fonts either - see above
973                  for SO and SI */
974               state->escstate = 3;
975               break;
976             default:
977               /* Escape sequences not supported:
978                * 
979                * H - Set tab stop
980                * Z - Terminal identification
981                * > - Keypad change
982                * = - Other keypad change
983                * ] - OS command
984                */
985               state->escstate = 0;
986               break;
987             }
988           break;
989         case 2:
990           switch (c)
991             {
992             case 24: /* CAN */
993             case 26: /* SUB */
994               state->escstate = 0;
995               break;
996             case '0': case '1': case '2': case '3': case '4':
997             case '5': case '6': case '7': case '8': case '9':
998               if (state->curparam < NPAR)
999                 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
1000               break;
1001             case ';':
1002               state->csiparam[++state->curparam] = 0;
1003               break;
1004             case '[':
1005               state->escstate = 3;
1006               break;
1007             case '@':
1008               for (i = 0; i < state->csiparam[0]; i++)
1009                 {
1010                   if(++state->cursor_x > cols)
1011                     {
1012                       state->cursor_x = 0;
1013                       if (state->cursor_y < rows - 1)
1014                         state->cursor_y++;
1015                       else
1016                         scroll (state);
1017                     }
1018                   cell = &state->cells[cols * state->cursor_y + state->cursor_x];
1019                   if (cell->state == FLARE || cell->state == NORMAL)
1020                     {
1021                       cell->state = FADE;
1022                       cell->changed = True;
1023                     }
1024                 }
1025               state->escstate = 0;
1026               break;
1027             case 'F':
1028               state->cursor_x = 0;
1029             case 'A':
1030               if (state->csiparam[0] == 0)
1031                 state->csiparam[0] = 1;
1032               if ((state->cursor_y -= state->csiparam[0]) < 0)
1033                 state->cursor_y = 0;
1034               state->escstate = 0;
1035               break;
1036             case 'E':
1037               state->cursor_x = 0;
1038             case 'e':
1039             case 'B':
1040               if (state->csiparam[0] == 0)
1041                 state->csiparam[0] = 1;
1042               if ((state->cursor_y += state->csiparam[0]) >= rows - 1 /*####*/)
1043                 state->cursor_y = rows - 1;
1044               state->escstate = 0;
1045               break;
1046             case 'a':
1047             case 'C':
1048               if (state->csiparam[0] == 0)
1049                 state->csiparam[0] = 1;
1050               if ((state->cursor_x += state->csiparam[0]) >= cols - 1 /*####*/)
1051                 state->cursor_x = cols - 1;
1052               state->escstate = 0;
1053               break;
1054             case 'D':
1055               if (state->csiparam[0] == 0)
1056                 state->csiparam[0] = 1;
1057               if ((state->cursor_x -= state->csiparam[0]) < 0)
1058                 state->cursor_x = 0;
1059               state->escstate = 0;
1060               break;
1061             case 'd':
1062               if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
1063                 state->cursor_y = rows - 1;
1064               state->escstate = 0;
1065               break;
1066             case '`':
1067             case 'G':
1068               if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols - 1 /*####*/)
1069                 state->cursor_x = cols - 1;
1070               state->escstate = 0;
1071               break;
1072             case 'f':
1073             case 'H':
1074               if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
1075                 state->cursor_y = rows - 1;
1076               if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols - 1 /*####*/)
1077                 state->cursor_x = cols - 1;
1078               if(state->cursor_y < 0)
1079                 state->cursor_y = 0;
1080               if(state->cursor_x < 0)
1081                 state->cursor_x = 0;
1082               state->escstate = 0;
1083               break;
1084             case 'J':
1085               start = 0;
1086               end = rows * cols;
1087               if (state->csiparam[0] == 0)
1088                 start = cols * state->cursor_y + state->cursor_x;
1089               if (state->csiparam[0] == 1)
1090                 end = cols * state->cursor_y + state->cursor_x;
1091               for (i = start; i < end; i++)
1092                 {
1093                   cell = &state->cells[i];
1094                   if (cell->state == FLARE || cell->state == NORMAL)
1095                     {
1096                       cell->state = FADE;
1097                       cell->changed = True;
1098                     }
1099                 }
1100               set_cursor (state, True);
1101               state->escstate = 0;
1102               break;
1103             case 'K':
1104               start = 0;
1105               end = cols;
1106               if (state->csiparam[0] == 0)
1107                 start = state->cursor_x;
1108               if (state->csiparam[1] == 1)
1109                 end = state->cursor_x;
1110               for (i = start; i < end; i++)
1111                 {
1112                   if (cell->state == FLARE || cell->state == NORMAL)
1113                     {
1114                       cell->state = FADE;
1115                       cell->changed = True;
1116                     }
1117                   cell++;
1118                 }
1119               state->escstate = 0;
1120               break;
1121             case 'm': /* Set attributes unimplemented (bold, blink, rev) */
1122               state->escstate = 0;
1123               break;
1124             case 's': /* Save position */
1125               state->saved_x = state->cursor_x;
1126               state->saved_y = state->cursor_y;
1127               state->escstate = 0;
1128               break;
1129             case 'u': /* Restore position */
1130               state->cursor_x = state->saved_x;
1131               state->cursor_y = state->saved_y;
1132               state->escstate = 0;
1133               break;
1134             case '?': /* DEC Private modes */
1135               if ((state->curparam != 0) || (state->csiparam[0] != 0))
1136                 state->escstate = 0;
1137               break;
1138             default:
1139               /* Known unsupported CSIs:
1140                *
1141                * L - Insert blank lines
1142                * M - Delete lines (I don't know what this means...)
1143                * P - Delete characters
1144                * X - Erase characters (difference with P being...?)
1145                * c - Terminal identification
1146                * g - Clear tab stop(s)
1147                * h - Set mode (Mainly due to its complexity and lack of good
1148                      docs)
1149                * l - Clear mode
1150                * m - Set mode (Phosphor is, per defenition, green on black)
1151                * n - Status report
1152                * q - Set keyboard LEDs
1153                * r - Set scrolling region (too exhausting - noone uses this,
1154                      right?)
1155                */
1156               state->escstate = 0;
1157               break;
1158             }
1159           break;
1160         case 3:
1161           state->escstate = 0;
1162           break;
1163
1164         case 102:       /* states 102-106 are for UTF-8 decoding */
1165         case 103:
1166         case 104:
1167         case 105:
1168         case 106:
1169           goto PRINT;
1170
1171         default:
1172           abort();
1173         }
1174       set_cursor (state, True);
1175     }
1176   else
1177 #endif /* HAVE_FORKPTY */
1178     {
1179       if (c == '\t') c = ' ';   /* blah. */
1180
1181       if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
1182         {
1183           if (c == '\n' && state->last_c == '\r')
1184             ;   /* CRLF -- do nothing */
1185           else
1186             {
1187               state->cursor_x = 0;
1188               if (state->cursor_y == rows - 1)
1189                 scroll (state);
1190               else
1191                 state->cursor_y++;
1192             }
1193         }
1194       else if (c == '\014')
1195         {
1196           clear (state);
1197         }
1198       else
1199         {
1200           c = process_unicrud (state, c);
1201           if (!c) return;
1202
1203           cell->state = FLARE;
1204           cell->p_char = state->chars[c];
1205           cell->changed = True;
1206           state->cursor_x++;
1207
1208           if (c != ' ' && cell->p_char->blank_p)
1209             cell->p_char = state->chars[CURSOR_INDEX];
1210
1211           if (state->cursor_x >= cols - 1)
1212             {
1213               state->cursor_x = 0;
1214               if (state->cursor_y >= rows - 1)
1215                 scroll (state);
1216               else
1217                 state->cursor_y++;
1218             }
1219         }
1220       set_cursor (state, True);
1221     }
1222
1223   state->last_c = c;
1224 }
1225
1226
1227 static void
1228 update_display (p_state *state, Bool changed_only)
1229 {
1230   int x, y;
1231
1232   for (y = 0; y < state->grid_height; y++)
1233     for (x = 0; x < state->grid_width; x++)
1234       {
1235         p_cell *cell = &state->cells[state->grid_width * y + x];
1236         int width, height, tx, ty;
1237
1238         if (changed_only && !cell->changed)
1239           continue;
1240
1241         width = state->char_width * state->scale;
1242         height = state->char_height * state->scale;
1243         tx = x * width;
1244         ty = y * height;
1245
1246         if (cell->state == BLANK || cell->p_char->blank_p)
1247           {
1248             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1249                             tx, ty, width, height);
1250           }
1251         else
1252           {
1253 #ifdef FUZZY_BORDER
1254             GC gc1 = state->gcs[cell->state];
1255             GC gc2 = ((cell->state + 2) < state->ticks
1256                       ? state->gcs[cell->state + 2]
1257                       : 0);
1258             GC gc3 = (gc2 ? gc2 : gc1);
1259             if (gc3)
1260               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1261                           0, 0, width, height, tx, ty, 1L);
1262             if (gc2)
1263               {
1264                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1265                 XSetClipOrigin (state->dpy, gc1, tx, ty);
1266                 XFillRectangle (state->dpy, state->window, gc1,
1267                                 tx, ty, width, height);
1268                 XSetClipMask (state->dpy, gc1, None);
1269               }
1270 #else /* !FUZZY_BORDER */
1271
1272             XCopyPlane (state->dpy,
1273                         cell->p_char->pixmap, state->window,
1274                         state->gcs[cell->state],
1275                         0, 0, width, height, tx, ty, 1L);
1276
1277 #endif /* !FUZZY_BORDER */
1278           }
1279
1280         cell->changed = False;
1281       }
1282 }
1283
1284
1285 static unsigned long
1286 phosphor_draw (Display *dpy, Window window, void *closure)
1287 {
1288   p_state *state = (p_state *) closure;
1289   int c;
1290   update_display (state, True);
1291   decay (state);
1292
1293   c = textclient_getc (state->tc);
1294   if (c > 0) 
1295     print_char (state, c);
1296
1297   return state->delay;
1298 }
1299
1300
1301 static void
1302 phosphor_reshape (Display *dpy, Window window, void *closure, 
1303                  unsigned int w, unsigned int h)
1304 {
1305   p_state *state = (p_state *) closure;
1306   Bool changed_p = resize_grid (state);
1307
1308   if (! changed_p) return;
1309
1310   textclient_reshape (state->tc, w, h,
1311                       state->grid_width  - 1,
1312                       state->grid_height - 1,
1313                       0);
1314 }
1315
1316
1317 static Bool
1318 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1319 {
1320   p_state *state = (p_state *) closure;
1321
1322   if (event->xany.type == Expose)
1323     update_display (state, False);
1324   else if (event->xany.type == KeyPress)
1325     return textclient_putc (state->tc, &event->xkey);
1326   return False;
1327 }
1328
1329 static void
1330 phosphor_free (Display *dpy, Window window, void *closure)
1331 {
1332   p_state *state = (p_state *) closure;
1333
1334   textclient_close (state->tc);
1335   if (state->cursor_timer)
1336     XtRemoveTimeOut (state->cursor_timer);
1337
1338   /* #### there's more to free here */
1339
1340   free (state);
1341 }
1342
1343
1344
1345 static const char *phosphor_defaults [] = {
1346   ".background:            Black",
1347   ".foreground:            #00FF00",
1348   "*fpsSolid:              true",
1349 #if defined(BUILTIN_FONT)
1350   "*font:                  (builtin)",
1351 #elif defined(HAVE_COCOA)
1352   "*font:                  Monaco 15",
1353 #else
1354   "*font:                  fixed",
1355 #endif
1356   "*scale:                 6",
1357   "*ticks:                 20",
1358   "*delay:                 50000",
1359   "*cursor:                333",
1360   "*program:               xscreensaver-text",
1361   "*relaunch:              5",
1362   "*metaSendsESC:          True",
1363   "*swapBSDEL:             True",
1364 #ifdef HAVE_FORKPTY
1365   "*usePty:                True",
1366 #else  /* !HAVE_FORKPTY */
1367   "*usePty:                False",
1368 #endif /* !HAVE_FORKPTY */
1369   0
1370 };
1371
1372 static XrmOptionDescRec phosphor_options [] = {
1373   { "-font",            ".font",                XrmoptionSepArg, 0 },
1374   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
1375   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
1376   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1377   { "-program",         ".program",             XrmoptionSepArg, 0 },
1378   { "-pipe",            ".usePty",              XrmoptionNoArg, "False" },
1379   { "-pty",             ".usePty",              XrmoptionNoArg, "True"  },
1380   { "-meta",            ".metaSendsESC",        XrmoptionNoArg, "False" },
1381   { "-esc",             ".metaSendsESC",        XrmoptionNoArg, "True"  },
1382   { "-bs",              ".swapBSDEL",           XrmoptionNoArg, "False" },
1383   { "-del",             ".swapBSDEL",           XrmoptionNoArg, "True"  },
1384   { 0, 0, 0, 0 }
1385 };
1386
1387
1388 XSCREENSAVER_MODULE ("Phosphor", phosphor)