http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.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       if (state->cursor_timer)
455         XtRemoveTimeOut (state->cursor_timer);
456       state->cursor_timer = 0;
457       cursor_on_timer (state, 0);
458     }
459 }
460
461
462
463
464 static void
465 cursor_off_timer (XtPointer closure, XtIntervalId *id)
466 {
467   p_state *state = (p_state *) closure;
468   set_cursor_1 (state, False);
469   state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
470                                          cursor_on_timer, closure);
471 }
472
473 static void
474 cursor_on_timer (XtPointer closure, XtIntervalId *id)
475 {
476   p_state *state = (p_state *) closure;
477   set_cursor_1 (state, True);
478   state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
479                                          cursor_off_timer, closure);
480 }
481
482
483 static void
484 clear (p_state *state)
485 {
486   int x, y;
487   state->cursor_x = 0;
488   state->cursor_y = 0;
489   for (y = 0; y < state->grid_height; y++)
490     for (x = 0; x < state->grid_width; x++)
491       {
492         p_cell *cell = &state->cells[state->grid_width * y + x];
493         if (cell->state == FLARE || cell->state == NORMAL)
494           {
495             cell->state = FADE;
496             cell->changed = True;
497           }
498       }
499   set_cursor (state, True);
500 }
501
502
503 static void
504 decay (p_state *state)
505 {
506   int x, y;
507   for (y = 0; y < state->grid_height; y++)
508     for (x = 0; x < state->grid_width; x++)
509       {
510         p_cell *cell = &state->cells[state->grid_width * y + x];
511         if (cell->state == FLARE)
512           {
513             cell->state = NORMAL;
514             cell->changed = True;
515           }
516         else if (cell->state >= FADE)
517           {
518             cell->state++;
519             if (cell->state >= state->ticks)
520               cell->state = BLANK;
521             cell->changed = True;
522           }
523       }
524 }
525
526
527 static void
528 scroll (p_state *state)
529 {
530   int x, y;
531
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 && (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   static char last_c = 0;
570
571   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
572                               + state->cursor_x];
573
574   /* Start the cursor fading (in case we don't end up overwriting it.) */
575   if (cell->state == FLARE || cell->state == NORMAL)
576     {
577       cell->state = FADE;
578       cell->changed = True;
579     }
580
581   if (c == '\t') c = ' ';   /* blah. */
582
583   if (c == '\r' || c == '\n')
584     {
585       if (c == '\n' && last_c == '\r')
586         ;   /* CRLF -- do nothing */
587       else
588         {
589           state->cursor_x = 0;
590           if (state->cursor_y == state->grid_height - 1)
591             scroll (state);
592           else
593             state->cursor_y++;
594         }
595     }
596   else if (c == '\014')
597     {
598       clear (state);
599     }
600   else
601     {
602       cell->state = FLARE;
603       cell->p_char = state->chars[c];
604       cell->changed = True;
605       state->cursor_x++;
606
607       if (c != ' ' && cell->p_char->blank_p)
608         cell->p_char = state->chars[CURSOR_INDEX];
609
610       if (state->cursor_x >= state->grid_width - 1)
611         {
612           state->cursor_x = 0;
613           if (state->cursor_y >= state->grid_height - 1)
614             scroll (state);
615           else
616             state->cursor_y++;
617         }
618     }
619   set_cursor (state, True);
620
621   last_c = c;
622 }
623
624
625 static void
626 update_display (p_state *state, Bool changed_only)
627 {
628   int x, y;
629
630   for (y = 0; y < state->grid_height; y++)
631     for (x = 0; x < state->grid_width; x++)
632       {
633         p_cell *cell = &state->cells[state->grid_width * y + x];
634         int width, height, tx, ty;
635
636         if (changed_only && !cell->changed)
637           continue;
638
639         width = state->char_width * state->scale;
640         height = state->char_height * state->scale;
641         tx = x * width;
642         ty = y * height;
643
644         if (cell->state == BLANK || cell->p_char->blank_p)
645           {
646             XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
647                             tx, ty, width, height);
648           }
649         else
650           {
651 #ifdef FUZZY_BORDER
652             GC gc1 = state->gcs[cell->state];
653             GC gc2 = ((cell->state + 2) < state->ticks
654                       ? state->gcs[cell->state + 2]
655                       : 0);
656             GC gc3 = (gc2 ? gc2 : gc1);
657             if (gc3)
658               XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
659                           0, 0, width, height, tx, ty, 1L);
660             if (gc2)
661               {
662                 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
663                 XSetClipOrigin (state->dpy, gc1, tx, ty);
664                 XFillRectangle (state->dpy, state->window, gc1,
665                                 tx, ty, width, height);
666                 XSetClipMask (state->dpy, gc1, None);
667               }
668 #else /* !FUZZY_BORDER */
669
670             XCopyPlane (state->dpy,
671                         cell->p_char->pixmap, state->window,
672                         state->gcs[cell->state],
673                         0, 0, width, height, tx, ty, 1L);
674
675 #endif /* !FUZZY_BORDER */
676           }
677
678         cell->changed = False;
679       }
680 }
681
682
683 static void
684 run_phosphor (p_state *state)
685 {
686   update_display (state, True);
687   decay (state);
688   drain_input (state);
689 }
690
691 \f
692 /* Subprocess.
693  */
694
695 static void
696 subproc_cb (XtPointer closure, int *source, XtInputId *id)
697 {
698   p_state *state = (p_state *) closure;
699   state->input_available_p = True;
700 }
701
702
703 static void
704 launch_text_generator (p_state *state)
705 {
706   char *oprogram = get_string_resource ("program", "Program");
707   char *program = (char *) malloc (strlen (oprogram) + 10);
708
709   strcpy (program, "( ");
710   strcat (program, oprogram);
711   strcat (program, " ) 2>&1");
712
713   if ((state->pipe = popen (program, "r")))
714     {
715       state->pipe_id =
716         XtAppAddInput (app, fileno (state->pipe),
717                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
718                        subproc_cb, (XtPointer) state);
719     }
720   else
721     {
722       perror (program);
723     }
724 }
725
726
727 static void
728 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
729 {
730   p_state *state = (p_state *) closure;
731   launch_text_generator (state);
732 }
733
734
735 static void
736 drain_input (p_state *state)
737 {
738   if (state->input_available_p)
739     {
740       unsigned char s[2];
741       int n = read (fileno (state->pipe), (void *) s, 1);
742       if (n == 1)
743         {
744           print_char (state, s[0]);
745         }
746       else
747         {
748           XtRemoveInput (state->pipe_id);
749           state->pipe_id = 0;
750           pclose (state->pipe);
751           state->pipe = 0;
752
753           if (state->cursor_x != 0)     /* break line if unbroken */
754             print_char (state, '\n');   /* blank line */
755           print_char (state, '\n');
756
757           /* Set up a timer to re-launch the subproc in a bit. */
758           XtAppAddTimeOut (app, state->subproc_relaunch_delay,
759                            relaunch_generator_timer,
760                            (XtPointer) state);
761         }
762         
763       state->input_available_p = False;
764     }
765 }
766
767
768 \f
769 char *progclass = "Phosphor";
770
771 char *defaults [] = {
772   ".background:            Black",
773   ".foreground:            Green",
774   "*fadeForeground:        DarkGreen",
775   "*flareForeground:       White",
776   "*font:                  fixed",
777   "*scale:                 6",
778   "*ticks:                 20",
779   "*delay:                 50000",
780   "*cursor:                333",
781   "*program:             " FORTUNE_PROGRAM,
782   "*relaunch:              5",
783   0
784 };
785
786 XrmOptionDescRec options [] = {
787   { "-font",            ".font",                XrmoptionSepArg, 0 },
788   { "-scale",           ".scale",               XrmoptionSepArg, 0 },
789   { "-ticks",           ".ticks",               XrmoptionSepArg, 0 },
790   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
791   { "-program",         ".program",             XrmoptionSepArg, 0 },
792   { 0, 0, 0, 0 }
793 };
794
795
796 void
797 screenhack (Display *dpy, Window window)
798 {
799   int delay = get_integer_resource ("delay", "Integer");
800   p_state *state = init_phosphor (dpy, window);
801
802   clear (state);
803
804   while (1)
805     {
806       run_phosphor (state);
807       XSync (dpy, False);
808       screenhack_handle_events (dpy);
809
810       if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
811         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
812
813       if (delay) usleep (delay);
814     }
815 }