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