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