source: trunk/third/xscreensaver/hacks/fontglide.c @ 20148

Revision 20148, 40.4 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r20147, which included commits to RCS files with non-trunk default branches.
Line 
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
24extern XtAppContext app;
25
26
27typedef 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
39typedef 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
62typedef 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
103static void launch_text_generator (state *);
104static 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 */
111static Bool
112pick_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 */
321static void
322pick_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
333static 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 */
338static char *
339get_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 */
410static word *
411new_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
538static void
539free_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
547static sentence *
548new_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
560static void
561free_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. */
583static void
584unread_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 */
597static void
598split_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 */
660static void
661scatter_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 */
765static void
766aim_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 */
814static void
815shuffle_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 */
829static int
830cmp_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 */
840static void
841sort_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 */
849static void
850recolor (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
896static void
897align_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 */
919static void
920populate_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 */
1086static void
1087draw_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 */
1108static void
1109more_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 */
1140static void
1141draw_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 */
1270static void
1271draw_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
1303static void
1304handle_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
1333/* Subprocess.
1334   (This bit mostly cribbed from phosphor.c)
1335 */
1336
1337static void
1338subproc_cb (XtPointer closure, int *source, XtInputId *id)
1339{
1340  state *s = (state *) closure;
1341  s->input_available_p = True;
1342}
1343
1344
1345static void
1346launch_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
1373static void
1374relaunch_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 */
1384static void
1385drain_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
1439/* Window setup and resource loading */
1440
1441static state *
1442init_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
1532char *progclass = "FontGlide";
1533
1534char *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
1556XrmOptionDescRec 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
1576void
1577screenhack (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}
Note: See TracBrowser for help on using the repository browser.