http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[xscreensaver] / hacks / fontglide.c
1 /* xscreensaver, Copyright (c) 2003 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  * fontglide -- reads text from a subprocess and puts it on the screen using
12  * large characters that glide in from the edges, assemble, then disperse.
13  * Requires a system with scalable fonts.  (X's font handing sucks.  A lot.)
14  */
15
16 #include <math.h>
17 #include "screenhack.h"
18 #include <X11/Intrinsic.h>
19
20 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
21 #include "xdbe.h"
22 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
23
24 extern XtAppContext app;
25
26
27 typedef struct {
28   char *text;
29   int x, y, width, height;
30   int ascent, lbearing, rbearing;
31
32   int nticks, tick;
33   int start_x,  start_y;
34   int target_x, target_y;
35   Pixmap pixmap, mask;
36 } word;
37
38
39 typedef struct {
40   int id;
41   XColor fg;
42   XColor bg;
43   Bool dark_p;
44   Bool move_chars_p;
45   int width;
46
47   char *font_name;
48   XFontStruct *font;
49
50   GC fg_gc;
51
52   int nwords;
53   word **words;
54
55   enum { IN, PAUSE, OUT } anim_state;
56   enum { LEFT, CENTER, RIGHT } alignment;
57   int pause_tick;
58
59 } sentence;
60
61
62 typedef struct {
63   Display *dpy;
64   Window window;
65   XWindowAttributes xgwa;
66
67   Pixmap b, ba; /* double-buffer to reduce flicker */
68   GC bg_gc;
69
70 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
71   XdbeBackBuffer backb;
72 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
73
74   Bool dbuf;            /* Whether we're using double buffering. */
75   Bool dbeclear_p;      /* ? */
76
77   int border_width;     /* size of the font outline */
78   char *charset;        /* registry and encoding for font lookups */
79   double speed;         /* frame rate multiplier */
80   double linger;        /* multiplier for how long to leave words on screen */
81   Bool trails_p;
82   Bool debug_p;
83   enum { PAGE, SCROLL } mode;
84
85   char *font_override;  /* if -font was specified on the cmd line */
86
87   FILE *pipe;
88   XtInputId pipe_id;
89   Time subproc_relaunch_delay;
90   Bool input_available_p;
91
92   char buf [40];        /* this only needs to be as big as one "word". */
93   int buf_tail;
94
95   int nsentences;
96   sentence **sentences;
97   Bool spawn_p;         /* whether it is time to create a new sentence */
98   int latest_sentence;
99
100 } state;
101
102
103 static void launch_text_generator (state *);
104 static void drain_input (state *s);
105
106
107 /* Finds the set of scalable fonts on the system; picks one;
108    and loads that font in a random pixel size.
109    Returns False if something went wrong.
110  */
111 static Bool
112 pick_font_1 (state *s, sentence *se)
113 {
114   char pattern[1024];
115   char **names = 0;
116   char **names2 = 0;
117   XFontStruct *info = 0;
118   int count = 0, count2 = 0;
119   int i;
120   Bool ok = False;
121
122   if (se->font)
123     {
124       XFreeFont (s->dpy, se->font);
125       free (se->font_name);
126       se->font = 0;
127       se->font_name = 0;
128     }
129
130   if (s->font_override)
131     sprintf (pattern, "%.200s", s->font_override);
132   else
133     sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
134              "*",         /* foundry */
135              "*",         /* family */
136              "*",         /* weight */
137              "*",         /* slant */
138              "*",         /* swidth */
139              "*",         /* adstyle */
140              "0",         /* pixel size */
141              "0",         /* point size */
142              "0",         /* resolution x */
143              "0",         /* resolution y */
144              "p",         /* spacing */
145              "0",         /* avg width */
146              s->charset); /* registry + encoding */
147
148   names = XListFonts (s->dpy, pattern, 1000, &count);
149
150   if (count <= 0)
151     {
152       if (s->font_override)
153         fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
154       else
155         fprintf (stderr, "%s: no scalable fonts found!  (pattern: %s)\n",
156                  progname, pattern);
157       exit (1);
158     }
159
160   i = random() % count;
161
162   names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
163   if (count2 <= 0)
164     {
165       fprintf (stderr, "%s: pattern %s\n"
166                 "     gave unusable %s\n\n",
167                progname, pattern, names[i]);
168       goto FAIL;
169     }
170
171   {
172     XFontStruct *font = &info[0];
173     unsigned long value = 0;
174     char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
175     unsigned long pixel=0, point=0, res_x=0, res_y=0;
176     char *spacing=0;
177     unsigned long avg_width=0;
178     char *registry=0, *encoding=0;
179     Atom a;
180     char *bogus = "\"?\"";
181
182 # define STR(ATOM,VAR)                                  \
183   bogus = (ATOM);                                       \
184   a = XInternAtom (s->dpy, (ATOM), False);              \
185   if (XGetFontProperty (font, a, &value))               \
186     VAR = XGetAtomName (s->dpy, value);                 \
187   else                                                  \
188     goto FAIL2
189
190 # define INT(ATOM,VAR)                                  \
191   bogus = (ATOM);                                       \
192   a = XInternAtom (s->dpy, (ATOM), False);              \
193   if (!XGetFontProperty (font, a, &VAR) ||              \
194       VAR > 9999)                                       \
195     goto FAIL2
196
197     STR ("FOUNDRY",          foundry);
198     STR ("FAMILY_NAME",      family);
199     STR ("WEIGHT_NAME",      weight);
200     STR ("SLANT",            slant);
201     STR ("SETWIDTH_NAME",    setwidth);
202     STR ("ADD_STYLE_NAME",   add_style);
203     INT ("PIXEL_SIZE",       pixel);
204     INT ("POINT_SIZE",       point);
205     INT ("RESOLUTION_X",     res_x);
206     INT ("RESOLUTION_Y",     res_y);
207     STR ("SPACING",          spacing);
208     INT ("AVERAGE_WIDTH",    avg_width);
209     STR ("CHARSET_REGISTRY", registry);
210     STR ("CHARSET_ENCODING", encoding);
211
212 #undef INT
213 #undef STR
214
215     {
216       double scale = s->xgwa.height / 1024.0;  /* shrink for small windows */
217       int min, max, r;
218
219       min = scale * 24;
220       max = scale * 260;
221
222       if (min < 10) min = 10;
223       if (max < 30) max = 30;
224
225       r = ((max-min)/3)+1;
226
227       pixel = min + ((random() % r) + (random() % r) + (random() % r));
228
229       if (s->mode == SCROLL)  /* scroll mode likes bigger fonts */
230         pixel *= 1.5;
231     }
232
233 #if 0
234     /* Occasionally change the aspect ratio of the font, by increasing
235        either the X or Y resolution (while leaving the other alone.)
236
237        #### Looks like this trick doesn't really work that well: the
238             metrics of the individual characters are ok, but the
239             overall font ascent comes out wrong (unscaled.)
240      */
241     if (! (random() % 8))
242       {
243         double n = 2.5 / 3;
244         double scale = 1 + (frand(n) + frand(n) + frand(n));
245         if (random() % 2)
246           res_x *= scale;
247         else
248           res_y *= scale;
249       }
250 # endif
251
252     sprintf (pattern,
253              "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
254              foundry, family, weight, slant, setwidth, add_style,
255              pixel, "*", /* point, */
256              res_x, res_y, spacing,
257              "*", /* avg_width */
258              registry, encoding);
259     ok = True;
260
261   FAIL2:
262     if (!ok)
263       fprintf (stderr, "%s: font has bogus %s property: %s\n",
264                progname, bogus, names[i]);
265
266     if (foundry)   XFree (foundry);
267     if (family)    XFree (family);
268     if (weight)    XFree (weight);
269     if (slant)     XFree (slant);
270     if (setwidth)  XFree (setwidth);
271     if (add_style) XFree (add_style);
272     if (spacing)   XFree (spacing);
273     if (registry)  XFree (registry);
274     if (encoding)  XFree (encoding);
275   }
276
277  FAIL: 
278
279   XFreeFontInfo (names2, info, count2);
280   XFreeFontNames (names);
281
282   if (! ok) return False;
283
284   se->font = XLoadQueryFont (s->dpy, pattern);
285   if (! se->font)
286     {
287       fprintf (stderr, "%s: unable to load font %s\n",
288                progname, pattern);
289       return False;
290     }
291
292   if (se->font->min_bounds.width == se->font->max_bounds.width &&
293       !s->font_override)
294     {
295       /* This is to weed out
296          "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
297          "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1".
298          We asked for only proportional fonts, but this fixed-width font
299          shows up anyway -- but it has goofy metrics (see below) so it
300          looks terrible anyway.
301        */
302       if (s->debug_p)
303         fprintf (stderr,
304                  "%s: skipping bogus monospace non-charcell font: %s\n",
305                  progname, pattern);
306       return False;
307     }
308
309   if (s->debug_p) 
310     fprintf(stderr, "%s: %s\n", progname, pattern);
311
312   se->font_name = strdup (pattern);
313   XSetFont (s->dpy, se->fg_gc, se->font->fid);
314   return True;
315 }
316
317
318 /* Finds the set of scalable fonts on the system; picks one;
319    and loads that font in a random pixel size.
320  */
321 static void
322 pick_font (state *s, sentence *se)
323 {
324   int i;
325   for (i = 0; i < 20; i++)
326     if (pick_font_1 (s, se))
327       return;
328   fprintf (stderr, "%s: too many failures: giving up!\n", progname);
329   exit (1);
330 }
331
332
333 static char *unread_word_text = 0;
334
335 /* Returns a newly-allocated string with one word in it, or NULL if there
336    is no complete word available.
337  */
338 static char *
339 get_word_text (state *s)
340 {
341   char *start = s->buf;
342   char *end;
343   char *result = 0;
344   int lfs = 0;
345
346   if (unread_word_text)
347     {
348       char *s = unread_word_text;
349       unread_word_text = 0;
350       return s;
351     }
352
353   /* Skip over whitespace at the beginning of the buffer,
354      and count up how many linebreaks we see while doing so.
355    */
356   while (*start &&
357          (*start == ' ' ||
358           *start == '\t' ||
359           *start == '\r' ||
360           *start == '\n'))
361     {
362       if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
363         lfs++;
364       start++;
365     }
366
367   end = start;
368
369   /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
370      to trigger a sentence break here.) */
371   if (lfs >= 2)
372     goto DONE;
373
374   /* Skip forward to the end of this word (find next whitespace.) */
375   while (*end &&
376          (! (*end == ' ' ||
377              *end == '\t' ||
378              *end == '\r' ||
379              *end == '\n')))
380     end++;
381
382   /* If we have a word, allocate a string for it */
383   if (end > start)
384     {
385       result = malloc ((end - start) + 1);
386       strncpy (result, start, (end-start));
387       result [end-start] = 0;
388     }
389
390  DONE:
391
392   /* Make room in the buffer by compressing out any bytes we've processed.
393    */
394   if (end > s->buf)
395     {
396       int n = end - s->buf;
397       memmove (s->buf, end, sizeof(s->buf) - n);
398       s->buf_tail -= n;
399     }
400
401   /* See if there is more to be read, now that there's room in the buffer. */
402   drain_input (s);
403
404   return result;
405 }
406
407
408 /* Gets some random text, and creates a "word" object from it.
409  */
410 static word *
411 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
412 {
413   word *w;
414   XCharStruct overall;
415   int dir, ascent, descent;
416   int bw = s->border_width;
417
418   if (!txt)
419     return 0;
420
421   w = (word *) calloc (1, sizeof(*w));
422   XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
423
424   w->width    = overall.rbearing - overall.lbearing + bw + bw;
425   w->height   = overall.ascent   + overall.descent  + bw + bw;
426   w->ascent   = overall.ascent   + bw;
427   w->lbearing = overall.lbearing - bw;
428   w->rbearing = overall.width    + bw;
429
430 # if 0
431   /* The metrics on some fonts are strange -- e.g.,
432      "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
433      "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
434      an rbearing so wide that it looks like there are two spaces after
435      each letter.  If this character says it has an rbearing that is to
436      the right of its ink, ignore that.
437
438      #### Of course, this hack only helps when we're in `move_chars_p' mode
439           and drawing a char at a time -- when we draw the whole word at once,
440           XDrawString believes the bogus metrics and spaces the font out
441           crazily anyway.
442
443      Sigh, this causes some text to mis-render in, e.g.,
444      "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
445      (in "ux", we need the rbearing on "r" or we get too much overlap.)
446    */
447   if (w->rbearing > w->width)
448     w->rbearing = w->width;
449 # endif /* 0 */
450
451   if (s->mode == SCROLL && !alloc_p) abort();
452
453   if (alloc_p)
454     {
455       int i, j;
456       XGCValues gcv;
457       GC gc0, gc1;
458
459       w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
460       w->mask   = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
461
462       gcv.font = se->font->fid;
463       gcv.foreground = 0L;
464       gcv.background = 1L;
465       gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
466                        &gcv);
467       gcv.foreground = 1L;
468       gcv.background = 0L;
469       gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
470                        &gcv);
471
472       XFillRectangle (s->dpy, w->mask,   gc0, 0, 0, w->width, w->height);
473       XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
474
475       if (s->debug_p)
476         {
477           /* bounding box (behind the characters) */
478           XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
479                           0, 0, w->width-1, w->height-1);
480           XDrawRectangle (s->dpy, w->mask,   gc1,
481                           0, 0, w->width-1, w->height-1);
482         }
483
484       /* Draw foreground text */
485       XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
486                    txt, strlen(txt));
487
488       /* Cheesy hack to draw a border */
489       /* (I should be able to do this in i*2 time instead of i*i time,
490          but I can't get it right, so fuck it.) */
491       XSetFunction (s->dpy, gc1, GXor);
492       for (i = -bw; i <= bw; i++)
493         for (j = -bw; j <= bw; j++)
494           XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
495                      0, 0, w->width, w->height,
496                      i, j);
497
498       if (s->debug_p)
499         {
500           XSetFunction (s->dpy, gc1, GXset);
501           if (w->ascent != w->height)
502             {
503               /* baseline (on top of the characters) */
504               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
505                          0, w->ascent, w->width-1, w->ascent);
506               XDrawLine (s->dpy, w->mask,   gc1,
507                          0, w->ascent, w->width-1, w->ascent);
508             }
509
510           if (w->lbearing != 0)
511             {
512               /* left edge of charcell */
513               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
514                          w->lbearing, 0, w->lbearing, w->height-1);
515               XDrawLine (s->dpy, w->mask,   gc1,
516                          w->lbearing, 0, w->lbearing, w->height-1);
517             }
518
519           if (w->rbearing != w->width)
520             {
521               /* right edge of charcell */
522               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
523                          w->rbearing, 0, w->rbearing, w->height-1);
524               XDrawLine (s->dpy, w->mask,   gc1,
525                          w->rbearing, 0, w->rbearing, w->height-1);
526             }
527         }
528
529       XFreeGC (s->dpy, gc0);
530       XFreeGC (s->dpy, gc1);
531     }
532
533   w->text = txt;
534   return w;
535 }
536
537
538 static void
539 free_word (state *s, word *w)
540 {
541   if (w->text)   free (w->text);
542   if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
543   if (w->mask)   XFreePixmap (s->dpy, w->mask);
544 }
545
546
547 static sentence *
548 new_sentence (state *s)
549 {
550   static int id = 0;
551   XGCValues gcv;
552   sentence *se = (sentence *) calloc (1, sizeof (*se));
553   se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
554   se->anim_state = IN;
555   se->id = ++id;
556   return se;
557 }
558
559
560 static void
561 free_sentence (state *s, sentence *se)
562 {
563   int i;
564   for (i = 0; i < se->nwords; i++)
565     free_word (s, se->words[i]);
566   if (se->words) free (se->words);
567
568   if (se->fg.flags)
569     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
570   if (se->bg.flags)
571     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
572
573   if (se->font_name) free (se->font_name);
574   if (se->font) XFreeFont (s->dpy, se->font);
575   if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
576
577   free (se);
578 }
579
580
581 /* free the word, and put its text back at the front of the input queue,
582    to be read next time. */
583 static void
584 unread_word (state *s, word *w)
585 {
586   if (unread_word_text)
587     abort();
588   unread_word_text = w->text;
589   w->text = 0;
590   free_word (s, w);
591 }
592
593
594 /* Divide each of the words in the sentence into one character words,
595    without changing the positions of those characters.
596  */
597 static void
598 split_words (state *s, sentence *se)
599 {
600   word **words2;
601   int nwords2 = 0;
602   int i, j;
603   for (i = 0; i < se->nwords; i++)
604     nwords2 += strlen (se->words[i]->text);
605
606   words2 = (word **) calloc (nwords2, sizeof(*words2));
607
608   for (i = 0, j = 0; i < se->nwords; i++)
609     {
610       word *ow = se->words[i];
611       int L = strlen (ow->text);
612       int k;
613
614       int x  = ow->x;
615       int y  = ow->y;
616       int sx = ow->start_x;
617       int sy = ow->start_y;
618       int tx = ow->target_x;
619       int ty = ow->target_y;
620
621       for (k = 0; k < L; k++)
622         {
623           char *t2 = malloc (2);
624           word *w2;
625           int xoff, yoff;
626
627           t2[0] = ow->text[k];
628           t2[1] = 0;
629           w2 = new_word (s, se, t2, True);
630           words2[j++] = w2;
631
632           xoff = (w2->lbearing - ow->lbearing);
633           yoff = (ow->ascent - w2->ascent);
634
635           w2->x        = x  + xoff;
636           w2->y        = y  + yoff;
637           w2->start_x  = sx + xoff;
638           w2->start_y  = sy + yoff;
639           w2->target_x = tx + xoff;
640           w2->target_y = ty + yoff;
641
642           x  += w2->rbearing;
643           sx += w2->rbearing;
644           tx += w2->rbearing;
645         }
646
647       free_word (s, ow);
648       se->words[i] = 0;
649     }
650   free (se->words);
651
652   se->words = words2;
653   se->nwords = nwords2;
654 }
655
656
657 /* Set the source or destination position of the words to be somewhere
658    off screen.
659  */
660 static void
661 scatter_sentence (state *s, sentence *se)
662 {
663   int i = 0;
664   int off = 100;
665
666   int flock_p = ((random() % 4) == 0);
667   int mode = (flock_p ? (random() % 12) : 0);
668
669   for (i = 0; i < se->nwords; i++)
670     {
671       word *w = se->words[i];
672       int x, y;
673       int r = (flock_p ? mode : (random() % 4));
674       switch (r)
675         {
676           /* random positions on the edges */
677
678         case 0:
679           x = -off - w->width;
680           y = random() % s->xgwa.height;
681           break;
682         case 1:
683           x = off + s->xgwa.width;
684           y = random() % s->xgwa.height;
685           break;
686         case 2:
687           x = random() % s->xgwa.width;
688           y = -off - w->height;
689           break;
690         case 3:
691           x = random() % s->xgwa.width;
692           y = off + s->xgwa.height;
693           break;
694
695           /* straight towards the edges */
696
697         case 4:
698           x = -off - w->width;
699           y = w->target_y;
700           break;
701         case 5:
702           x = off + s->xgwa.width;
703           y = w->target_y;
704           break;
705         case 6:
706           x = w->target_x;
707           y = -off - w->height;
708           break;
709         case 7:
710           x = w->target_x;
711           y = off + s->xgwa.height;
712           break;
713
714           /* corners */
715
716         case 8:
717           x = -off - w->width;
718           y = -off - w->height;
719           break;
720         case 9:
721           x = -off - w->width;
722           y =  off + s->xgwa.height;
723           break;
724         case 10:
725           x =  off + s->xgwa.width;
726           y =  off + s->xgwa.height;
727           break;
728         case 11:
729           x =  off + s->xgwa.width;
730           y = -off - w->height;
731           break;
732
733         default:
734           abort();
735           break;
736         }
737
738       if (se->anim_state == IN)
739         {
740           w->start_x = x;
741           w->start_y = y;
742         }
743       else
744         {
745           w->start_x = w->x;
746           w->start_y = w->y;
747           w->target_x = x;
748           w->target_y = y;
749         }
750
751       w->nticks = ((100 + ((random() % 140) +
752                            (random() % 140) +
753                            (random() % 140)))
754                    / s->speed);
755       if (w->nticks < 2)
756         w->nticks = 2;
757       w->tick = 0;
758     }
759 }
760
761
762 /* Set the source position of the words to be off the right side,
763    and the destination to be off the left side.
764  */
765 static void
766 aim_sentence (state *s, sentence *se)
767 {
768   int i = 0;
769   int nticks;
770   int yoff = 0;
771
772   if (se->nwords <= 0) abort();
773
774   /* Have the sentence shift up or down a little bit; not too far, and
775      never let it fall off the top or bottom of the screen before its
776      last character has reached the left edge.
777    */
778   for (i = 0; i < 10; i++)
779     {
780       int ty = random() % (s->xgwa.height - se->words[0]->ascent);
781       yoff = ty - se->words[0]->target_y;
782       if (yoff < s->xgwa.height/3)  /* this one is ok */
783         break;
784     }
785
786   for (i = 0; i < se->nwords; i++)
787     {
788       word *w = se->words[i];
789       w->start_x   = w->target_x + s->xgwa.width;
790       w->target_x -= se->width;
791       w->start_y   = w->target_y;
792       w->target_y += yoff;
793     }
794
795   nticks = ((se->words[0]->start_x - se->words[0]->target_x)
796             / (s->speed * 10));
797   nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
798
799   if (nticks < 2)
800     nticks = 2;
801
802   for (i = 0; i < se->nwords; i++)
803     {
804       word *w = se->words[i];
805       w->nticks = nticks;
806       w->tick = 0;
807     }
808 }
809
810
811 /* Randomize the order of the words in the list (since that changes
812    which ones are "on top".)
813  */
814 static void
815 shuffle_words (state *s, sentence *se)
816 {
817   int i;
818   for (i = 0; i < se->nwords-1; i++)
819     {
820       int j = i + (random() % (se->nwords - i));
821       word *swap = se->words[i];
822       se->words[i] = se->words[j];
823       se->words[j] = swap;
824     }
825 }
826
827
828 /* qsort comparitor */
829 static int
830 cmp_sentences (const void *aa, const void *bb)
831 {
832   const sentence *a = *(sentence **) aa;
833   const sentence *b = *(sentence **) bb;
834   return ((a ? a->id : 999999) - (b ? b->id : 999999));
835 }
836
837
838 /* Sort the sentences by id, so that sentences added later are on top.
839  */
840 static void
841 sort_sentences (state *s)
842 {
843   qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
844 }
845
846
847 /* Re-pick the colors of the text and border
848  */
849 static void
850 recolor (state *s, sentence *se)
851 {
852   if (se->fg.flags)
853     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
854   if (se->bg.flags)
855     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
856
857   se->fg.flags  = DoRed|DoGreen|DoBlue;
858   se->bg.flags  = DoRed|DoGreen|DoBlue;
859
860   switch (random() % 2)
861     {
862     case 0:   /* bright fg, dim bg */
863       se->fg.red    = (random() % 0x8888) + 0x8888;
864       se->fg.green  = (random() % 0x8888) + 0x8888;
865       se->fg.blue   = (random() % 0x8888) + 0x8888;
866       se->bg.red    = (random() % 0x5555);
867       se->bg.green  = (random() % 0x5555);
868       se->bg.blue   = (random() % 0x5555);
869       break;
870
871     case 1:   /* bright bg, dim fg */
872       se->fg.red    = (random() % 0x4444);
873       se->fg.green  = (random() % 0x4444);
874       se->fg.blue   = (random() % 0x4444);
875       se->bg.red    = (random() % 0x4444) + 0xCCCC;
876       se->bg.green  = (random() % 0x4444) + 0xCCCC;
877       se->bg.blue   = (random() % 0x4444) + 0xCCCC;
878       break;
879
880     default:
881       abort();
882       break;
883     }
884
885   if (s->debug_p)
886     se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
887                   se->bg.red*2 + se->bg.green*3 + se->bg.blue);
888
889   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
890     XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
891   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
892     XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
893 }
894
895
896 static void
897 align_line (state *s, sentence *se, int line_start, int x, int right)
898 {
899   int off, j;
900   switch (se->alignment)
901     {
902     case LEFT:   off = 0;               break;
903     case CENTER: off = (right - x) / 2; break;
904     case RIGHT:  off = (right - x);     break;
905     default:     abort();               break;
906     }
907
908   if (off != 0)
909     for (j = line_start; j < se->nwords; j++)
910       se->words[j]->target_x += off;
911 }
912
913
914 /* Fill the sentence with new words: in "page" mode, fills the page
915    with text; in "scroll" mode, just makes one long horizontal sentence.
916    The sentence might have *no* words in it, if no text is currently
917    available.
918  */
919 static void
920 populate_sentence (state *s, sentence *se)
921 {
922   int i = 0;
923   int left, right, top, x, y;
924   int space = 0;
925   int line_start = 0;
926   Bool done = False;
927
928   int array_size = 100;
929
930   se->move_chars_p = (s->mode == SCROLL ? False :
931                       (random() % 3) ? False : True);
932   se->alignment = (random() % 3);
933
934   recolor (s, se);
935
936   if (se->words)
937     {
938       for (i = 0; i < se->nwords; i++)
939         free_word (s, se->words[i]);
940       free (se->words);
941     }
942
943   se->words = (word **) calloc (array_size, sizeof(*se->words));
944   se->nwords = 0;
945
946   switch (s->mode)
947     {
948     case PAGE:
949       left  = random() % (s->xgwa.width / 3);
950       right = s->xgwa.width - (random() % (s->xgwa.width / 3));
951       top = random() % (s->xgwa.height * 2 / 3);
952       break;
953     case SCROLL:
954       left = 0;
955       right = s->xgwa.width;
956       top = random() % s->xgwa.height;
957       break;
958     default:
959       abort();
960       break;
961     }
962
963   x = left;
964   y = top;
965
966   while (!done)
967     {
968       char *txt = get_word_text (s);
969       word *w;
970       if (!txt)
971         {
972           if (se->nwords == 0)
973             return;             /* If the stream is empty, bail. */
974           else
975             break;              /* If EOF after some words, end of sentence. */
976         }
977
978       if (! se->font)           /* Got a word: need a font now */
979         {
980           pick_font (s, se);
981           if (y < se->font->ascent)
982             y += se->font->ascent;
983           space = XTextWidth (se->font, " ", 1);
984         }
985
986       w = new_word (s, se, txt, !se->move_chars_p);
987
988       /* If we have a few words, let punctuation terminate the sentence:
989          stop gathering more words if the last word ends in a period, etc. */
990       if (se->nwords >= 4)
991         {
992           char c = w->text[strlen(w->text)-1];
993           if (c == '.' || c == '?' || c == '!')
994             done = True;
995         }
996
997       /* If the sentence is kind of long already, terminate at commas, etc. */
998       if (se->nwords >= 12)
999         {
1000           char c = w->text[strlen(w->text)-1];
1001           if (c == ',' || c == ';' || c == ':' || c == '-' ||
1002               c == ')' || c == ']' || c == '}')
1003             done = True;
1004         }
1005
1006       if (se->nwords >= 25)  /* ok that's just about enough out of you */
1007         done = True;
1008
1009       if (s->mode == PAGE &&
1010           x + w->rbearing > right)                      /* wrap line */
1011         {
1012           align_line (s, se, line_start, x, right);
1013           line_start = se->nwords;
1014
1015           x = left;
1016           y += se->font->ascent;
1017
1018           /* If we're close to the bottom of the screen, stop, and 
1019              unread the current word.  (But not if this is the first
1020              word, otherwise we might just get stuck on it.)
1021            */
1022           if (se->nwords > 0 &&
1023               y + se->font->ascent > s->xgwa.height)
1024             {
1025               unread_word (s, w);
1026               done = True;
1027               break;
1028             }
1029         }
1030
1031       w->target_x = x + w->lbearing;
1032       w->target_y = y - w->ascent;
1033
1034       x += w->rbearing + space;
1035       se->width = x;
1036
1037       if (se->nwords >= (array_size - 1))
1038         {
1039           array_size += 100;
1040           se->words = (word **) realloc (se->words,
1041                                          array_size * sizeof(*se->words));
1042           if (!se->words)
1043             {
1044               fprintf (stderr, "%s: out of memory (%d words)\n",
1045                        progname, array_size);
1046               exit (1);
1047             }
1048         }
1049
1050       se->words[se->nwords++] = w;
1051     }
1052
1053   se->width -= space;
1054
1055   switch (s->mode)
1056     {
1057     case PAGE:
1058       align_line (s, se, line_start, x, right);
1059       if (se->move_chars_p)
1060         split_words (s, se);
1061       scatter_sentence (s, se);
1062       shuffle_words (s, se);
1063       break;
1064     case SCROLL:
1065       aim_sentence (s, se);
1066       break;
1067     default:
1068       abort();
1069       break;
1070     }
1071
1072 # ifdef DEBUG
1073   if (s->debug_p)
1074     {
1075       fprintf (stderr, "%s: sentence %d:", progname, se->id);
1076       for (i = 0; i < se->nwords; i++)
1077         fprintf (stderr, " %s", se->words[i]->text);
1078       fprintf (stderr, "\n");
1079     }
1080 # endif
1081 }
1082
1083
1084 /* Render a single word object to the screen.
1085  */
1086 static void
1087 draw_word (state *s, sentence *se, word *w)
1088 {
1089   if (! w->pixmap) return;
1090
1091   if (w->x + w->width < 0 ||
1092       w->y + w->height < 0 ||
1093       w->x > s->xgwa.width ||
1094       w->y > s->xgwa.height)
1095     return;
1096
1097   XSetClipMask (s->dpy, se->fg_gc, w->mask);
1098   XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1099   XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1100               0, 0, w->width, w->height,
1101               w->x, w->y,
1102               1L);
1103 }
1104
1105
1106 /* If there is room for more sentences, add one.
1107  */
1108 static void
1109 more_sentences (state *s)
1110 {
1111   int i;
1112   Bool any = False;
1113   for (i = 0; i < s->nsentences; i++)
1114     {
1115       sentence *se = s->sentences[i];
1116       if (! se)
1117         {
1118           se = new_sentence (s);
1119           populate_sentence (s, se);
1120           if (se->nwords > 0)
1121             s->spawn_p = False, any = True;
1122           else
1123             {
1124               free_sentence (s, se);
1125               se = 0;
1126             }
1127           s->sentences[i] = se;
1128           if (se)
1129             s->latest_sentence = se->id;
1130           break;
1131         }
1132     }
1133
1134   if (any) sort_sentences (s);
1135 }
1136
1137
1138 /* Render all the words to the screen, and run the animation one step.
1139  */
1140 static void
1141 draw_sentence (state *s, sentence *se)
1142 {
1143   int i;
1144   Bool moved = False;
1145
1146   if (! se) return;
1147
1148   for (i = 0; i < se->nwords; i++)
1149     {
1150       word *w = se->words[i];
1151
1152       switch (s->mode)
1153         {
1154         case PAGE:
1155           if (se->anim_state != PAUSE &&
1156               w->tick <= w->nticks)
1157             {
1158               int dx = w->target_x - w->start_x;
1159               int dy = w->target_y - w->start_y;
1160               double r = sin (w->tick * M_PI / (2 * w->nticks));
1161               w->x = w->start_x + (dx * r);
1162               w->y = w->start_y + (dy * r);
1163
1164               w->tick++;
1165               if (se->anim_state == OUT && s->mode == PAGE)
1166                 w->tick++;  /* go out faster */
1167               moved = True;
1168             }
1169           break;
1170         case SCROLL:
1171           {
1172             int dx = w->target_x - w->start_x;
1173             int dy = w->target_y - w->start_y;
1174             double r = (double) w->tick / w->nticks;
1175             w->x = w->start_x + (dx * r);
1176             w->y = w->start_y + (dy * r);
1177             w->tick++;
1178             moved = (w->tick <= w->nticks);
1179
1180             /* Launch a new sentence when:
1181                - the front of this sentence is almost off the left edge;
1182                - the end of this sentence is almost on screen.
1183                - or, randomly
1184              */
1185             if (se->anim_state != OUT &&
1186                 i == 0 &&
1187                 se->id == s->latest_sentence)
1188               {
1189                 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1190                               w->x + se->width < (s->xgwa.width * 2.1));
1191                 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1192
1193                 if (new_p || rand_p)
1194                   {
1195                     se->anim_state = OUT;
1196                     s->spawn_p = True;
1197 # ifdef DEBUG
1198                     if (s->debug_p)
1199                       fprintf (stderr, "%s: OUT   %d (x2 = %d%s)\n",
1200                                progname, se->id,
1201                                se->words[0]->x + se->width,
1202                                rand_p ? " randomly" : "");
1203 # endif
1204                   }
1205               }
1206           }
1207           break;
1208         default:
1209           abort();
1210           break;
1211         }
1212
1213       draw_word (s, se, w);
1214     }
1215
1216   if (moved && se->anim_state == PAUSE)
1217     abort();
1218
1219   if (! moved)
1220     {
1221       switch (se->anim_state)
1222         {
1223         case IN:
1224           se->anim_state = PAUSE;
1225           se->pause_tick = (se->nwords * 7 * s->linger);
1226           if (se->move_chars_p)
1227             se->pause_tick /= 5;
1228           scatter_sentence (s, se);
1229           shuffle_words (s, se);
1230 # ifdef DEBUG
1231           if (s->debug_p)
1232             fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1233 # endif
1234           break;
1235         case PAUSE:
1236           if (--se->pause_tick <= 0)
1237             {
1238               se->anim_state = OUT;
1239               s->spawn_p = True;
1240 # ifdef DEBUG
1241               if (s->debug_p)
1242                 fprintf (stderr, "%s: OUT   %d\n", progname, se->id);
1243 # endif
1244             }
1245           break;
1246         case OUT:
1247 # ifdef DEBUG
1248           if (s->debug_p)
1249             fprintf (stderr, "%s: DEAD  %d\n", progname, se->id);
1250 # endif
1251           {
1252             int j;
1253             for (j = 0; j < s->nsentences; j++)
1254               if (s->sentences[j] == se)
1255                 s->sentences[j] = 0;
1256             free_sentence (s, se);
1257           }
1258           break;
1259         default:
1260           abort();
1261           break;
1262         }
1263     }
1264 }
1265
1266
1267 /* Render all the words to the screen, and run the animation one step.
1268    Clear screen first, swap buffers after.
1269  */
1270 static void
1271 draw_fontglide (state *s)
1272 {
1273   int i;
1274
1275   if (s->spawn_p)
1276     more_sentences (s);
1277
1278   if (!s->trails_p)
1279     XFillRectangle (s->dpy, s->b, s->bg_gc,
1280                     0, 0, s->xgwa.width, s->xgwa.height);
1281
1282   for (i = 0; i < s->nsentences; i++)
1283     draw_sentence (s, s->sentences[i]);
1284
1285 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1286   if (s->backb)
1287     {
1288       XdbeSwapInfo info[1];
1289       info[0].swap_window = s->window;
1290       info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1291       XdbeSwapBuffers (s->dpy, info, 1);
1292     }
1293   else
1294 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1295   if (s->dbuf)
1296     {
1297       XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1298                  0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1299     }
1300 }
1301
1302
1303 static void
1304 handle_events (state *s)
1305 {
1306   while (XPending (s->dpy))
1307     {
1308       XEvent event;
1309       XNextEvent (s->dpy, &event);
1310
1311       if (event.xany.type == ConfigureNotify)
1312         {
1313           XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1314
1315           if (s->dbuf && (s->ba))
1316             {
1317               XFreePixmap (s->dpy, s->ba);
1318               s->ba = XCreatePixmap (s->dpy, s->window, 
1319                                      s->xgwa.width, s->xgwa.height,
1320                                      s->xgwa.depth);
1321               XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0, 
1322                               s->xgwa.width, s->xgwa.height);
1323               s->b = s->ba;
1324             }
1325         }
1326
1327       screenhack_handle_event (s->dpy, &event);
1328     }
1329 }
1330
1331
1332 \f
1333 /* Subprocess.
1334    (This bit mostly cribbed from phosphor.c)
1335  */
1336
1337 static void
1338 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1339 {
1340   state *s = (state *) closure;
1341   s->input_available_p = True;
1342 }
1343
1344
1345 static void
1346 launch_text_generator (state *s)
1347 {
1348   char *oprogram = get_string_resource ("program", "Program");
1349   char *program;
1350
1351   program = (char *) malloc (strlen (oprogram) + 10);
1352   strcpy (program, "( ");
1353   strcat (program, oprogram);
1354   strcat (program, " ) 2>&1");
1355
1356   if (s->debug_p)
1357     fprintf (stderr, "%s: forking: %s\n", progname, program);
1358
1359   if ((s->pipe = popen (program, "r")))
1360     {
1361       s->pipe_id =
1362         XtAppAddInput (app, fileno (s->pipe),
1363                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
1364                        subproc_cb, (XtPointer) s);
1365     }
1366   else
1367     {
1368       perror (program);
1369     }
1370 }
1371
1372
1373 static void
1374 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1375 {
1376   state *s = (state *) closure;
1377   launch_text_generator (s);
1378 }
1379
1380
1381 /* When the subprocess has generated some output, this reads as much as it
1382    can into s->buf at s->buf_tail.
1383  */
1384 static void
1385 drain_input (state *s)
1386 {
1387   /* allow subproc_cb() to run, if the select() down in Xt says that
1388      input is available.  This tells us whether we can read() without
1389      blocking. */
1390   if (! s->input_available_p)
1391     if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1392       XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1393
1394   if (! s->pipe) return;
1395   if (! s->input_available_p) return;
1396   s->input_available_p = False;
1397
1398   if (s->buf_tail < sizeof(s->buf) - 2)
1399     {
1400       int target = sizeof(s->buf) - s->buf_tail - 2;
1401       int n;
1402       n = read (fileno (s->pipe),
1403                 (void *) (s->buf + s->buf_tail),
1404                 target);
1405       if (n > 0)
1406         {
1407           s->buf_tail += n;
1408           s->buf[s->buf_tail] = 0;
1409         }
1410       else
1411         {
1412           XtRemoveInput (s->pipe_id);
1413           s->pipe_id = 0;
1414           pclose (s->pipe);
1415           s->pipe = 0;
1416
1417           /* If the process didn't print a terminating newline, add one. */
1418           if (s->buf_tail > 1 &&
1419               s->buf[s->buf_tail-1] != '\n')
1420             {
1421               s->buf[s->buf_tail++] = '\n';
1422               s->buf[s->buf_tail] = 0;
1423             }
1424
1425           /* Then add one more, to make sure there's a sentence break at EOF.
1426            */
1427           s->buf[s->buf_tail++] = '\n';
1428           s->buf[s->buf_tail] = 0;
1429
1430           /* Set up a timer to re-launch the subproc in a bit. */
1431           XtAppAddTimeOut (app, s->subproc_relaunch_delay,
1432                            relaunch_generator_timer,
1433                            (XtPointer) s);
1434         }
1435     }
1436 }
1437
1438 \f
1439 /* Window setup and resource loading */
1440
1441 static state *
1442 init_fontglide (Display *dpy, Window window)
1443 {
1444   XGCValues gcv;
1445   state *s = (state *) calloc (1, sizeof(*s));
1446   s->dpy = dpy;
1447   s->window = window;
1448
1449   XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1450
1451   s->font_override = get_string_resource ("font", "Font");
1452   if (s->font_override && (!*s->font_override || *s->font_override == '('))
1453     s->font_override = 0;
1454
1455   s->charset = get_string_resource ("fontCharset", "FontCharset");
1456   s->border_width = get_integer_resource ("fontBorderWidth", "Integer");
1457   if (s->border_width < 0 || s->border_width > 20)
1458     s->border_width = 1;
1459
1460   s->speed = get_float_resource ("speed", "Float");
1461   if (s->speed <= 0 || s->speed > 200)
1462     s->speed = 1;
1463
1464   s->linger = get_float_resource ("linger", "Float");
1465   if (s->linger <= 0 || s->linger > 200)
1466     s->linger = 1;
1467
1468   s->debug_p = get_boolean_resource ("debug", "Debug");
1469   s->trails_p = get_boolean_resource ("trails", "Trails");
1470
1471   s->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
1472   s->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
1473
1474   if (s->trails_p) s->dbuf = False;  /* don't need it in this case */
1475
1476   {
1477     char *ss = get_string_resource ("mode", "Mode");
1478     if (!ss || !*ss || !strcasecmp (ss, "random"))
1479       s->mode = ((random() % 2) ? SCROLL : PAGE);
1480     else if (!strcasecmp (ss, "scroll"))
1481       s->mode = SCROLL;
1482     else if (!strcasecmp (ss, "page"))
1483       s->mode = PAGE;
1484     else
1485       {
1486         fprintf (stderr,
1487                 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1488                  progname, ss);
1489       }
1490   }
1491
1492   if (s->dbuf)
1493     {
1494 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1495       if (s->dbeclear_p)
1496         s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1497       else
1498         s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1499       s->backb = s->b;
1500 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1501
1502       if (!s->b)
1503         {
1504           s->ba = XCreatePixmap (s->dpy, s->window, 
1505                                  s->xgwa.width, s->xgwa.height,
1506                                  s->xgwa.depth);
1507           s->b = s->ba;
1508         }
1509     }
1510   else
1511     {
1512       s->b = s->window;
1513     }
1514
1515   gcv.foreground = get_pixel_resource ("background", "Background",
1516                                        s->dpy, s->xgwa.colormap);
1517   s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1518
1519   s->subproc_relaunch_delay = 2 * 1000;
1520
1521   launch_text_generator (s);
1522
1523   s->nsentences = 5; /* #### */
1524   s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1525   s->spawn_p = True;
1526
1527   return s;
1528 }
1529
1530
1531 \f
1532 char *progclass = "FontGlide";
1533
1534 char *defaults [] = {
1535   ".background:         #000000",
1536   ".foreground:         #DDDDDD",
1537   ".borderColor:        #555555",
1538   "*delay:              10000",
1539   "*program:          " FORTUNE_PROGRAM,
1540   "*mode:               random",
1541   ".font:               (default)",
1542   "*fontCharset:        iso8859-1",
1543   "*fontBorderWidth:    2",
1544   "*speed:              1.0",
1545   "*linger:             1.0",
1546   "*trails:             False",
1547   "*debug:              False",
1548   "*doubleBuffer:       True",
1549 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1550   "*useDBE:             True",
1551   "*useDBEClear:        True",
1552 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1553   0
1554 };
1555
1556 XrmOptionDescRec options [] = {
1557   { "-scroll",          ".mode",            XrmoptionNoArg, "scroll" },
1558   { "-page",            ".mode",            XrmoptionNoArg, "page"   },
1559   { "-random",          ".mode",            XrmoptionNoArg, "random" },
1560   { "-delay",           ".delay",           XrmoptionSepArg, 0 },
1561   { "-speed",           ".speed",           XrmoptionSepArg, 0 },
1562   { "-linger",          ".linger",          XrmoptionSepArg, 0 },
1563   { "-program",         ".program",         XrmoptionSepArg, 0 },
1564   { "-font",            ".font",            XrmoptionSepArg, 0 },
1565   { "-fn",              ".font",            XrmoptionSepArg, 0 },
1566   { "-bw",              ".fontBorderWidth", XrmoptionSepArg, 0 },
1567   { "-trails",          ".trails",          XrmoptionNoArg,  "True"  },
1568   { "-no-trails",       ".trails",          XrmoptionNoArg,  "False" },
1569   { "-db",              ".doubleBuffer",    XrmoptionNoArg,  "True"  },
1570   { "-no-db",           ".doubleBuffer",    XrmoptionNoArg,  "False" },
1571   { "-debug",           ".debug",           XrmoptionNoArg,  "True"  },
1572   { 0, 0, 0, 0 }
1573 };
1574
1575
1576 void
1577 screenhack (Display *dpy, Window window)
1578 {
1579   state *s = init_fontglide (dpy, window);
1580   int delay = get_integer_resource ("delay", "Integer");
1581
1582   while (1)
1583     {
1584       handle_events (s);
1585       draw_fontglide (s);
1586       XSync (dpy, False);
1587       if (delay) usleep (delay);
1588     }
1589 }