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