ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.tar.gz
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999 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  */
13
14 #include "screenhack.h"
15 #include <stdio.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18 #include <X11/Intrinsic.h>
19
20 extern XtAppContext app;
21
22 #define FUZZY_BORDER
23
24 #define MAX(a,b) ((a)>(b)?(a):(b))
25 #define MIN(a,b) ((a)<(b)?(a):(b))
26
27 #define BLANK  0
28 #define FLARE  1
29 #define NORMAL 2
30 #define FADE   3
31 #define STATE_MAX FADE
32
33 #define CURSOR_INDEX 128
34
35 typedef struct {
36   unsigned char name;
37   int width, height;
38   Pixmap pixmap;
39 #ifdef FUZZY_BORDER
40   Pixmap pixmap2;
41 #endif /* FUZZY_BORDER */
42   Bool blank_p;
43 } p_char;
44
45 typedef struct {
46   p_char *p_char;
47   int state;
48   Bool changed;
49 } p_cell;
50
51 typedef struct {
52   Display *dpy;
53   Window window;
54   XWindowAttributes xgwa;
55   XFontStruct *font;
56   int grid_width, grid_height;
57   int char_width, char_height;
58   int scale;
59   int ticks;
60   p_char **chars;
61   p_cell *cells;
62   XGCValues gcv;
63   GC gc0;
64   GC gc1;
65 #ifdef FUZZY_BORDER
66   GC gc2;
67 #endif /* FUZZY_BORDER */
68   GC *gcs;
69   XImage *font_bits;
70
71   int cursor_x, cursor_y;
72   XtIntervalId cursor_timer;
73   Time cursor_blink;
74
75   FILE *pipe;
76   XtInputId pipe_id;
77   Bool input_available_p;
78   Time subproc_relaunch_delay;
79
80 } p_state;
81
82
83 static void capture_font_bits (p_state *state);
84 static p_char *make_character (p_state *state, int c);
85 static void drain_input (p_state *state);
86 static void char_to_pixmap (p_state *state, p_char *pc, int c);
87 static void launch_text_generator (p_state *state);
88
89
90 /* About font metrics:
91
92    "lbearing" is the distance from the leftmost pixel of a character to
93    the logical origin of that character.  That is, it is the number of
94    pixels of the character which are to the left of its logical origin.
95
96    "rbearing" is the distance from the logical origin of a character to
97    the rightmost pixel of a character.  That is, it is the number of
98    pixels of the character to the right of its logical origin.
99
100    "descent" is the distance from the bottommost pixel of a character to
101    the logical baseline.  That is, it is the number of pixels of the
102    character which are below the baseline.
103
104    "ascent" is the distance from the logical baseline to the topmost pixel.
105    That is, it is the number of pixels of the character above the baseline.
106
107    Therefore, the bounding box of the "ink" of a character is
108    lbearing + rbearing by ascent + descent;
109
110    "width" is the distance from the logical origin of this character to
111    the position where the logical orgin of the next character should be
112    placed.
113
114    For our purposes, we're only interested in the part of the character
115    lying inside the "width" box.  If the characters have ink outside of
116    that box (the "charcell" box) then we're going to lose it.  Alas.
117  */
118
119 static p_state *
120 init_phosphor (Display *dpy, Window window)
121 {
122   int i;
123   unsigned long flags;
124   p_state *state = (p_state *) calloc (sizeof(*state), 1);
125   char *fontname = get_string_resource ("font", "Font");
126   XFontStruct *font;
127
128   state->dpy = dpy;
129   state->window = window;
130
131   XGetWindowAttributes (dpy, window, &state->xgwa);
132
133   state->font = XLoadQueryFont (dpy, fontname);
134
135   if (!state->font)
136     {
137       fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
138       state->font = XLoadQueryFont (dpy, "fixed");
139     }
140   if (!state->font)
141     {
142       fprintf(stderr, "couldn't load font \"fixed\"");
143       exit(1);
144     }
145
146   font = state->font;
147   state->scale = get_integer_resource ("scale", "Integer");
148   state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
149
150 #if 0
151   for (i = 0; i < font->n_properties; i++)
152     if (font->properties[i].name == XA_FONT)
153       printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
154 #endif /* 0 */
155
156   state->cursor_blink = get_integer_resource ("cursor", "Time");
157   state->subproc_relaunch_delay =
158     (1000 * get_integer_resource ("relaunch", "Time"));
159
160   state->char_width  = font->max_bounds.width;
161   state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
162
163   state->grid_width = state->xgwa.width / (state->char_width * state->scale);
164   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
165   state->cells = (p_cell *) calloc (sizeof(p_cell),
166                                     state->grid_width * state->grid_height);
167   state->chars = (p_char **) calloc (sizeof(p_char *), 256);
168
169   state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
170
171   {
172     int ncolors = MAX (0, state->ticks - 3);
173     XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
174     int h1, h2;
175     double s1, s2, v1, v2;
176
177     unsigned long fg = get_pixel_resource ("foreground", "Foreground",
178                                            state->dpy, state->xgwa.colormap);
179     unsigned long bg = get_pixel_resource ("background", "Background",
180                                            state->dpy, state->xgwa.colormap);
181     unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
182                                               state->dpy,state->xgwa.colormap);
183     unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
184                                              state->dpy,state->xgwa.colormap);
185
186     XColor start, end;
187
188     start.pixel = fade;
189     XQueryColor (state->dpy, state->xgwa.colormap, &start);
190
191     end.pixel = bg;
192     XQueryColor (state->dpy, state->xgwa.colormap, &end);
193
194     /* Now allocate a ramp of colors from the main color to the background. */
195     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
196     rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
197     make_color_ramp (state->dpy, state->xgwa.colormap,
198                      h1, s1, v1,
199                      h2, s2, v2,
200                      colors, &ncolors,
201                      False, True, False);
202
203     /* Adjust to the number of colors we actually got. */
204     state->ticks = ncolors + STATE_MAX;
205
206     /* Now, GCs all around.
207      */
208     state->gcv.font = font->fid;
209     state->gcv.cap_style = CapRound;
210 #ifdef FUZZY_BORDER
211     state->gcv.line_width = (int) (((long) state->scale) * 1.3);
212     if (state->gcv.line_width == state->scale)
213       state->gcv.line_width++;
214 #else /* !FUZZY_BORDER */
215     state->gcv.line_width = (int) (((long) state->scale) * 0.9);
216     if (state->gcv.line_width >= state->scale)
217       state->gcv.line_width = state->scale - 1;
218     if (state->gcv.line_width < 1)
219       state->gcv.line_width = 1;
220 #endif /* !FUZZY_BORDER */
221
222     flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
223
224     state->gcv.background = bg;
225     state->gcv.foreground = bg;
226     state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
227                                    &state->gcv);
228
229     state->gcv.foreground = flare;
230     state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
231                                    &state->gcv);
232
233     state->gcv.foreground = fg;
234     state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
235                                     &state->gcv);
236
237     for (i = 0; i < ncolors; i++)
238       {
239         state->gcv.foreground = colors[i].pixel;
240         state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
241                                                flags, &state->gcv);
242       }
243   }
244
245   capture_font_bits (state);
246
247   launch_text_generator (state);
248
249   return state;
250 }
251
252
253 static void
254 capture_font_bits (p_state *state)
255 {
256   XFontStruct *font = state->font;
257   int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
258   int height = state->char_height;
259   unsigned char string[257];
260   int i;
261   Pixmap p = XCreatePixmap (state->dpy, state->window,
262                             (safe_width * 256), height, 1);
263
264   for (i = 0; i < 256; i++)
265     string[i] = (unsigned char) i;
266   string[256] = 0;
267
268   state->gcv.foreground = 0;
269   state->gcv.background = 0;
270   state->gc0 = XCreateGC (state->dpy, p,
271                           (GCForeground | GCBackground),
272                           &state->gcv);
273
274   state->gcv.foreground = 1;
275   state->gc1 = XCreateGC (state->dpy, p,
276                           (GCFont | GCForeground | GCBackground |
277                            GCCapStyle | GCLineWidth),
278                           &state->gcv);
279
280 #ifdef FUZZY_BORDER
281   {
282     state->gcv.line_width = (int) (((long) state->scale) * 0.8);
283     if (state->gcv.line_width >= state->scale)
284       state->gcv.line_width = state->scale - 1;
285     if (state->gcv.line_width < 1)
286       state->gcv.line_width = 1;
287     state->gc2 = XCreateGC (state->dpy, p,
288                             (GCFont | GCForeground | GCBackground |
289                              GCCapStyle | GCLineWidth),
290                             &state->gcv);
291   }
292 #endif /* FUZZY_BORDER */
293
294   XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
295
296   for (i = 0; i < 256; i++)
297     {
298       if (string[i] < font->min_char_or_byte2 ||
299           string[i] > font->max_char_or_byte2)
300         continue;
301       XDrawString (state->dpy, p, state->gc1,
302                    i * safe_width, font->ascent,
303                    string + i, 1);
304     }
305
306   /* Draw the cursor. */
307   XFillRectangle (state->dpy, p, state->gc1,
308                   (CURSOR_INDEX * safe_width), 1,
309                   (font->per_char
310                    ? font->per_char['n'-font->min_char_or_byte2].width
311                    : font->max_bounds.width),
312                   font->ascent - 1);
313
314 #if 0
315   XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
316               0, 0, (safe_width * 256), height, 0, 0, 1L);
317   XSync(state->dpy, False);
318 #endif
319
320   XSync (state->dpy, False);
321   state->font_bits = XGetImage (state->dpy, p, 0, 0,
322                                 (safe_width * 256), height, ~0L, XYPixmap);
323   XFreePixmap (state->dpy, p);
324
325   for (i = 0; i < 256; i++)
326     state->chars[i] = make_character (state, i);
327   state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
328 }
329
330
331 static p_char *
332 make_character (p_state *state, int c)
333 {
334   p_char *pc = (p_char *) malloc (sizeof (*pc));
335   pc->name = (unsigned char) c;
336   pc->width =  state->scale * state->char_width;
337   pc->height = state->scale * state->char_height;
338   char_to_pixmap (state, pc, c);
339   return pc;
340 }
341
342
343 static void
344 char_to_pixmap (p_state *state, p_char *pc, int c)
345 {
346   Pixmap p = 0;
347   GC gc;
348 #ifdef FUZZY_BORDER
349   Pixmap p2 = 0;
350   GC gc2;
351 #endif /* FUZZY_BORDER */
352   int from, to;
353   int x1, y;
354
355   XFontStruct *font = state->font;
356   int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
357
358   int width  = state->scale * state->char_width;
359   int height = state->scale * state->char_height;
360
361   if (c < font->min_char_or_byte2 ||
362       c > font->max_char_or_byte2)
363     goto DONE;
364
365   gc = state->gc1;
366   p = XCreatePixmap (state->dpy, state->window, width, height, 1);
367   XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
368 #ifdef FUZZY_BORDER
369   gc2 = state->gc2;
370   p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
371   XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
372 #endif /* FUZZY_BORDER */
373
374   from = safe_width * c;
375   to =   safe_width * (c + 1);
376
377 #if 0
378   if (c > 75 && c < 150)
379     {
380       printf ("\n=========== %d (%c)\n", c, c);
381       for (y = 0; y < state->char_height; y++)
382         {
383           for (x1 = from; x1 < to; x1++)
384             printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
385           printf ("\n");
386         }
387     }
388 #endif
389
390   pc->blank_p = True;
391   for (y = 0; y < state->char_height; y++)
392     for (x1 = from; x1 < to; x1++)
393       if (XGetPixel (state->font_bits, x1, y))
394         {
395           int xoff = state->scale / 2;
396           int x2;
397           for (x2 = x1; x2 < to; x2++)
398             if (!XGetPixel (state->font_bits, x2, y))
399               break;
400           x2--;
401           XDrawLine (state->dpy, p, gc,
402                      (x1 - from) * state->scale + xoff, y * state->scale,
403                      (x2 - from) * state->scale + xoff, y * state->scale);
404 #ifdef FUZZY_BORDER
405           XDrawLine (state->dpy, p2, gc2,
406                      (x1 - from) * state->scale + xoff, y * state->scale,
407                      (x2 - from) * state->scale + xoff, y * state->scale);
408 #endif /* FUZZY_BORDER */
409           x1 = x2;
410           pc->blank_p = False;
411         }
412
413   /*  if (pc->blank_p && c == CURSOR_INDEX)
414     abort();*/
415
416  DONE:
417   pc->pixmap = p;
418 #ifdef FUZZY_BORDER
419   pc->pixmap2 = p2;
420 #endif /* FUZZY_BORDER */
421 }
422
423 \f
424 /* Managing the display. 
425  */
426
427 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
428 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
429
430 static Bool
431 set_cursor_1 (p_state *state, Bool on)
432 {
433   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
434                               + state->cursor_x];
435   p_char *cursor = state->chars[CURSOR_INDEX];
436   int new_state = (on ? NORMAL : FADE);
437
438   if (cell->p_char != cursor)
439     cell->changed = True;
440
441   if (cell->state != new_state)
442     cell->changed = True;
443
444   cell->p_char = cursor;
445   cell->state = new_state;
446   return cell->changed;
447 }
448
449 static void
450 set_cursor (p_state *state, Bool on)
451 {
452   if (set_cursor_1 (state, on))
453 ;
454     {
455       if (state->cursor_timer)
456         XtRemoveTimeOut (state->cursor_timer);
457       state->cursor_timer = 0;
458       cursor_on_timer (state, 0);
459     }
460 }
461
462
463
464
465 static void
466 cursor_off_timer (XtPointer closure, XtIntervalId *id)
467 {
468   p_state *state = (p_state *) closure;
469   set_cursor_1 (state, False);
470   state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
471                                          cursor_on_timer, closure);
472 }
473
474 static void
475 cursor_on_timer (XtPointer closure, XtIntervalId *id)
476 {
477   p_state *state = (p_state *) closure;
478   set_cursor_1 (state, True);
479   state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
480                                          cursor_off_timer, closure);
481 }
482
483
484 static void
485 clear (p_state *state)
486 {
487   int x, y;
488   state->cursor_x = 0;
489   state->cursor_y = 0;
490   for (y = 0; y < state->grid_height; y++)
491     for (x = 0; x < state->grid_width; x++)
492       {
493         p_cell *cell = &state->cells[state->grid_width * y + x];
494         if (cell->state == FLARE || cell->state == NORMAL)
495           {
496             cell->state = FADE;
497             cell->changed = True;
498           }
499       }
500   set_cursor (state, True);
501 }
502
503
504 static void
505 decay (p_state *state)
506 {
507   int x, y;
508   for (y = 0; y < state->grid_height; y++)
509     for (x = 0; x < state->grid_width; x++)
510       {
511         p_cell *cell = &state->cells[state->grid_width * y + x];
512         if (cell->state == FLARE)
513           {
514             cell->state = NORMAL;
515             cell->changed = True;
516           }
517         else if (cell->state >= FADE)
518           {
519             cell->state++;
520             if (cell->state >= state->ticks)
521               cell->state = BLANK;
522             cell->changed = True;
523           }
524       }
525 }
526
527
528 static void
529 scroll (p_state *state)
530 {
531   int x, y;
532   for (x = 0; x < state->grid_width; x++)
533     {
534       p_cell *from = 0, *to = 0;
535       for (y = 1; y < state->grid_height; y++)
536         {
537           from = &state->cells[state->grid_width * y     + x];
538           to   = &state->cells[state->grid_width * (y-1) + x];
539
540           if ((from->state == FLARE || from->state == NORMAL) &&
541               !from->p_char->blank_p)
542             {
543               *to = *from;
544               to->state = NORMAL;  /* should be FLARE?  Looks bad... */
545             }
546           else
547             {
548               if (to->state == FLARE || to->state == NORMAL)
549                 to->state = FADE;
550             }
551
552           to->changed = True;
553         }
554
555       to = from;
556       if (to->state == FLARE || to->state == NORMAL)
557         {
558           to->state = FADE;
559           to->changed = True;
560         }
561     }
562   set_cursor (state, True);
563 }
564
565
566 static void
567 print_char (p_state *state, int c)
568 {
569   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
570                               + state->cursor_x];
571
572   /* Start the cursor fading (in case we don't end up overwriting it.) */
573   if (cell->state == FLARE || cell->state == NORMAL)
574     {
575       cell->state = FADE;
576       cell->changed = True;
577     }
578
579   if (c == '\t') c = ' ';   /* blah. */
580
581   if (c == '\r' || c == '\n')
582     {
583       state->cursor_x = 0;
584       if (state->cursor_y == state->grid_height - 1)
585         scroll (state);
586       else
587         state->cursor_y++;
588     }
589   else if (c == '\014')
590     {
591       clear (state);
592     }
593   else
594     {
595       cell->state = FLARE;
596       cell->p_char = state->chars[c];
597       cell->changed = True;
598       state->cursor_x++;
599
600       if (c != ' ' && cell->p_char->blank_p)
601         cell->p_char = state->chars[CURSOR_INDEX];
602
603       if (state->cursor_x >= state->grid_width - 1)
604         {
605           state->cursor_x = 0;
606           if (state->cursor_y >= state->grid_height - 1)
607             scroll (state);
608           else
609             state->cursor_y++;
610         }
611     }
612   set_cursor (state, True);
613 }
614
615
616 static void
617 update_display (p_state *state, Bool changed_only)
618 {
619   int x, y;
620
621   for (y = 0; y < state->grid_height; y++)
622     for (x = 0; x < state->grid_width; x++)
623       {
624         p_cell *cell = &state->cells[state->grid_width * y + x];
625         int width, height, tx, ty;
626
627         if (changed_only && !cell->changed)
628           continue;
629
630         width = state->char_width * state->scale;
631         height = state->char_height * state->scale;
632         tx = x * width;
633         ty = y * height;
634
635         if (cell->state == BLANK || cell->p_char->blank_p)
636           {
637             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
638                             tx, ty, width, height);
639           }
640         else
641           {
642 #ifdef FUZZY_BORDER
643             GC gc1 = state->gcs[cell->state];
644             GC gc2 = ((cell->state + 2) < state->ticks
645                       ? state->gcs[cell->state + 2]
646                       : 0);
647             GC gc3 = (gc2 ? gc2 : gc1);
648             if (gc3)
649               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
650                           0, 0, width, height, tx, ty, 1L);
651             if (gc2)
652               {
653                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
654                 XSetClipOrigin (state->dpy, gc1, tx, ty);
655                 XFillRectangle (state->dpy, state->window, gc1,
656                                 tx, ty, width, height);
657                 XSetClipMask (state->dpy, gc1, None);
658               }
659 #else /* !FUZZY_BORDER */
660
661             XCopyPlane (state->dpy,
662                         cell->p_char->pixmap, state->window,
663                         state->gcs[cell->state],
664                         0, 0, width, height, tx, ty, 1L);
665
666 #endif /* !FUZZY_BORDER */
667           }
668
669         cell->changed = False;
670       }
671 }
672
673
674 static void
675 run_phosphor (p_state *state)
676 {
677   update_display (state, True);
678   decay (state);
679   drain_input (state);
680 }
681
682 \f
683 /* Subprocess.
684  */
685
686 static void
687 subproc_cb (XtPointer closure, int *source, XtInputId *id)
688 {
689   p_state *state = (p_state *) closure;
690   state->input_available_p = True;
691 }
692
693
694 static void
695 launch_text_generator (p_state *state)
696 {
697   char *oprogram = get_string_resource ("program", "Program");
698   char *program = (char *) malloc (strlen (oprogram) + 10);
699
700   strcpy (program, "( ");
701   strcat (program, oprogram);
702   strcat (program, " ) 2>&1");
703
704   if ((state->pipe = popen (program, "r")))
705     {
706       state->pipe_id =
707         XtAppAddInput (app, fileno (state->pipe),
708                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
709                        subproc_cb, (XtPointer) state);
710     }
711   else
712     {
713       perror (program);
714     }
715 }
716
717
718 static void
719 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
720 {
721   p_state *state = (p_state *) closure;
722   launch_text_generator (state);
723 }
724
725
726 static void
727 drain_input (p_state *state)
728 {
729   if (state->input_available_p)
730     {
731       unsigned char s[2];
732       int n = read (fileno (state->pipe), (void *) s, 1);
733       if (n == 1)
734         {
735           print_char (state, s[0]);
736         }
737       else
738         {
739           XtRemoveInput (state->pipe_id);
740           state->pipe_id = 0;
741           pclose (state->pipe);
742           state->pipe = 0;
743
744           if (state->cursor_x != 0)     /* break line if unbroken */
745             print_char (state, '\n');   /* blank line */
746           print_char (state, '\n');
747
748           /* Set up a timer to re-launch the subproc in a bit. */
749           XtAppAddTimeOut (app, state->subproc_relaunch_delay,
750                            relaunch_generator_timer,
751                            (XtPointer) state);
752         }
753         
754       state->input_available_p = False;
755     }
756 }
757
758
759 \f
760 char *progclass = "Phosphor";
761
762 char *defaults [] = {
763   ".background:            Black",
764   ".foreground:            Green",
765   "*fadeForeground:        DarkGreen",
766   "*flareForeground:       White",
767   "*font:                  fixed",
768   "*scale:                 6",
769   "*ticks:                 20",
770   "*delay:                 50000",
771   "*cursor:                333",
772   "*program:             " ZIPPY_PROGRAM,
773   "*relaunch:              5",
774   0
775 };
776
777 XrmOptionDescRec options [] = {
778   { "-font",            ".font",                XrmoptionSepArg, 0 },
779   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
780   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
781   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
782   { "-program",         ".program",             XrmoptionSepArg, 0 },
783   { 0, 0, 0, 0 }
784 };
785
786
787 void
788 screenhack (Display *dpy, Window window)
789 {
790   int delay = get_integer_resource ("delay", "Integer");
791   p_state *state = init_phosphor (dpy, window);
792
793   clear (state);
794
795   while (1)
796     {
797       run_phosphor (state);
798       XSync (dpy, False);
799       screenhack_handle_events (dpy);
800
801       if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
802         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
803
804       if (delay) usleep (delay);
805     }
806 }