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