http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.34.tar.gz
[xscreensaver] / hacks / phosphor.c
1 /* xscreensaver, Copyright (c) 1999, 2000 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                    (char *) (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
533   for (x = 0; x < state->grid_width; x++)
534     {
535       p_cell *from = 0, *to = 0;
536       for (y = 1; y < state->grid_height; y++)
537         {
538           from = &state->cells[state->grid_width * y     + x];
539           to   = &state->cells[state->grid_width * (y-1) + x];
540
541           if ((from->state == FLARE || from->state == NORMAL) &&
542               !from->p_char->blank_p)
543             {
544               *to = *from;
545               to->state = NORMAL;  /* should be FLARE?  Looks bad... */
546             }
547           else
548             {
549               if (to->state == FLARE || to->state == NORMAL)
550                 to->state = FADE;
551             }
552
553           to->changed = True;
554         }
555
556       to = from;
557       if (to && (to->state == FLARE || to->state == NORMAL))
558         {
559           to->state = FADE;
560           to->changed = True;
561         }
562     }
563   set_cursor (state, True);
564 }
565
566
567 static void
568 print_char (p_state *state, int c)
569 {
570   static char last_c = 0;
571
572   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
573                               + state->cursor_x];
574
575   /* Start the cursor fading (in case we don't end up overwriting it.) */
576   if (cell->state == FLARE || cell->state == NORMAL)
577     {
578       cell->state = FADE;
579       cell->changed = True;
580     }
581
582   if (c == '\t') c = ' ';   /* blah. */
583
584   if (c == '\r' || c == '\n')
585     {
586       if (c == '\n' && last_c == '\r')
587         ;   /* CRLF -- do nothing */
588       else
589         {
590           state->cursor_x = 0;
591           if (state->cursor_y == state->grid_height - 1)
592             scroll (state);
593           else
594             state->cursor_y++;
595         }
596     }
597   else if (c == '\014')
598     {
599       clear (state);
600     }
601   else
602     {
603       cell->state = FLARE;
604       cell->p_char = state->chars[c];
605       cell->changed = True;
606       state->cursor_x++;
607
608       if (c != ' ' && cell->p_char->blank_p)
609         cell->p_char = state->chars[CURSOR_INDEX];
610
611       if (state->cursor_x >= state->grid_width - 1)
612         {
613           state->cursor_x = 0;
614           if (state->cursor_y >= state->grid_height - 1)
615             scroll (state);
616           else
617             state->cursor_y++;
618         }
619     }
620   set_cursor (state, True);
621
622   last_c = c;
623 }
624
625
626 static void
627 update_display (p_state *state, Bool changed_only)
628 {
629   int x, y;
630
631   for (y = 0; y < state->grid_height; y++)
632     for (x = 0; x < state->grid_width; x++)
633       {
634         p_cell *cell = &state->cells[state->grid_width * y + x];
635         int width, height, tx, ty;
636
637         if (changed_only && !cell->changed)
638           continue;
639
640         width = state->char_width * state->scale;
641         height = state->char_height * state->scale;
642         tx = x * width;
643         ty = y * height;
644
645         if (cell->state == BLANK || cell->p_char->blank_p)
646           {
647             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
648                             tx, ty, width, height);
649           }
650         else
651           {
652 #ifdef FUZZY_BORDER
653             GC gc1 = state->gcs[cell->state];
654             GC gc2 = ((cell->state + 2) < state->ticks
655                       ? state->gcs[cell->state + 2]
656                       : 0);
657             GC gc3 = (gc2 ? gc2 : gc1);
658             if (gc3)
659               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
660                           0, 0, width, height, tx, ty, 1L);
661             if (gc2)
662               {
663                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
664                 XSetClipOrigin (state->dpy, gc1, tx, ty);
665                 XFillRectangle (state->dpy, state->window, gc1,
666                                 tx, ty, width, height);
667                 XSetClipMask (state->dpy, gc1, None);
668               }
669 #else /* !FUZZY_BORDER */
670
671             XCopyPlane (state->dpy,
672                         cell->p_char->pixmap, state->window,
673                         state->gcs[cell->state],
674                         0, 0, width, height, tx, ty, 1L);
675
676 #endif /* !FUZZY_BORDER */
677           }
678
679         cell->changed = False;
680       }
681 }
682
683
684 static void
685 run_phosphor (p_state *state)
686 {
687   update_display (state, True);
688   decay (state);
689   drain_input (state);
690 }
691
692 \f
693 /* Subprocess.
694  */
695
696 static void
697 subproc_cb (XtPointer closure, int *source, XtInputId *id)
698 {
699   p_state *state = (p_state *) closure;
700   state->input_available_p = True;
701 }
702
703
704 static void
705 launch_text_generator (p_state *state)
706 {
707   char *oprogram = get_string_resource ("program", "Program");
708   char *program = (char *) malloc (strlen (oprogram) + 10);
709
710   strcpy (program, "( ");
711   strcat (program, oprogram);
712   strcat (program, " ) 2>&1");
713
714   if ((state->pipe = popen (program, "r")))
715     {
716       state->pipe_id =
717         XtAppAddInput (app, fileno (state->pipe),
718                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
719                        subproc_cb, (XtPointer) state);
720     }
721   else
722     {
723       perror (program);
724     }
725 }
726
727
728 static void
729 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
730 {
731   p_state *state = (p_state *) closure;
732   launch_text_generator (state);
733 }
734
735
736 static void
737 drain_input (p_state *state)
738 {
739   if (state->input_available_p)
740     {
741       unsigned char s[2];
742       int n = read (fileno (state->pipe), (void *) s, 1);
743       if (n == 1)
744         {
745           print_char (state, s[0]);
746         }
747       else
748         {
749           XtRemoveInput (state->pipe_id);
750           state->pipe_id = 0;
751           pclose (state->pipe);
752           state->pipe = 0;
753
754           if (state->cursor_x != 0)     /* break line if unbroken */
755             print_char (state, '\n');   /* blank line */
756           print_char (state, '\n');
757
758           /* Set up a timer to re-launch the subproc in a bit. */
759           XtAppAddTimeOut (app, state->subproc_relaunch_delay,
760                            relaunch_generator_timer,
761                            (XtPointer) state);
762         }
763         
764       state->input_available_p = False;
765     }
766 }
767
768
769 \f
770 char *progclass = "Phosphor";
771
772 char *defaults [] = {
773   ".background:            Black",
774   ".foreground:            Green",
775   "*fadeForeground:        DarkGreen",
776   "*flareForeground:       White",
777   "*font:                  fixed",
778   "*scale:                 6",
779   "*ticks:                 20",
780   "*delay:                 50000",
781   "*cursor:                333",
782   "*program:             " ZIPPY_PROGRAM,
783   "*relaunch:              5",
784   0
785 };
786
787 XrmOptionDescRec options [] = {
788   { "-font",            ".font",                XrmoptionSepArg, 0 },
789   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
790   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
791   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
792   { "-program",         ".program",             XrmoptionSepArg, 0 },
793   { 0, 0, 0, 0 }
794 };
795
796
797 void
798 screenhack (Display *dpy, Window window)
799 {
800   int delay = get_integer_resource ("delay", "Integer");
801   p_state *state = init_phosphor (dpy, window);
802
803   clear (state);
804
805   while (1)
806     {
807       run_phosphor (state);
808       XSync (dpy, False);
809       screenhack_handle_events (dpy);
810
811       if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
812         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
813
814       if (delay) usleep (delay);
815     }
816 }