1 /* xscreensaver, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
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
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.)
17 #include "screenhack.h"
18 #include <X11/Intrinsic.h>
20 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
22 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
24 extern XtAppContext app;
29 int x, y, width, height;
30 int ascent, lbearing, rbearing;
34 int target_x, target_y;
55 enum { IN, PAUSE, OUT } anim_state;
56 enum { LEFT, CENTER, RIGHT } alignment;
65 XWindowAttributes xgwa;
67 Pixmap b, ba; /* double-buffer to reduce flicker */
70 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
72 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
74 Bool dbuf; /* Whether we're using double buffering. */
75 Bool dbeclear_p; /* ? */
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 */
83 enum { PAGE, SCROLL } mode;
85 char *font_override; /* if -font was specified on the cmd line */
89 Time subproc_relaunch_delay;
90 Bool input_available_p;
92 char buf [40]; /* this only needs to be as big as one "word". */
97 Bool spawn_p; /* whether it is time to create a new sentence */
103 static void launch_text_generator (state *);
104 static void drain_input (state *s);
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.
112 pick_font_1 (state *s, sentence *se)
117 XFontStruct *info = 0;
118 int count = 0, count2 = 0;
124 XFreeFont (s->dpy, se->font);
125 free (se->font_name);
130 if (s->font_override)
131 sprintf (pattern, "%.200s", s->font_override);
133 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
140 "0", /* pixel size */
141 "0", /* point size */
142 "0", /* resolution x */
143 "0", /* resolution y */
146 s->charset); /* registry + encoding */
148 names = XListFonts (s->dpy, pattern, 1000, &count);
152 if (s->font_override)
153 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
155 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
160 i = random() % count;
162 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
165 fprintf (stderr, "%s: pattern %s\n"
166 " gave unusable %s\n\n",
167 progname, pattern, names[i]);
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;
177 unsigned long avg_width=0;
178 char *registry=0, *encoding=0;
180 char *bogus = "\"?\"";
182 # define STR(ATOM,VAR) \
184 a = XInternAtom (s->dpy, (ATOM), False); \
185 if (XGetFontProperty (font, a, &value)) \
186 VAR = XGetAtomName (s->dpy, value); \
190 # define INT(ATOM,VAR) \
192 a = XInternAtom (s->dpy, (ATOM), False); \
193 if (!XGetFontProperty (font, a, &VAR) || \
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);
216 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
222 if (min < 10) min = 10;
223 if (max < 30) max = 30;
227 pixel = min + ((random() % r) + (random() % r) + (random() % r));
229 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
234 /* Occasionally change the aspect ratio of the font, by increasing
235 either the X or Y resolution (while leaving the other alone.)
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.)
241 if (! (random() % 8))
244 double scale = 1 + (frand(n) + frand(n) + frand(n));
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,
263 fprintf (stderr, "%s: font has bogus %s property: %s\n",
264 progname, bogus, names[i]);
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);
279 XFreeFontInfo (names2, info, count2);
280 XFreeFontNames (names);
282 if (! ok) return False;
284 se->font = XLoadQueryFont (s->dpy, pattern);
287 fprintf (stderr, "%s: unable to load font %s\n",
292 if (se->font->min_bounds.width == se->font->max_bounds.width &&
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.
304 "%s: skipping bogus monospace non-charcell font: %s\n",
310 fprintf(stderr, "%s: %s\n", progname, pattern);
312 se->font_name = strdup (pattern);
313 XSetFont (s->dpy, se->fg_gc, se->font->fid);
318 /* Finds the set of scalable fonts on the system; picks one;
319 and loads that font in a random pixel size.
322 pick_font (state *s, sentence *se)
325 for (i = 0; i < 20; i++)
326 if (pick_font_1 (s, se))
328 fprintf (stderr, "%s: too many failures: giving up!\n", progname);
333 static char *unread_word_text = 0;
335 /* Returns a newly-allocated string with one word in it, or NULL if there
336 is no complete word available.
339 get_word_text (state *s)
341 char *start = s->buf;
346 if (unread_word_text)
348 char *s = unread_word_text;
349 unread_word_text = 0;
353 /* Skip over whitespace at the beginning of the buffer,
354 and count up how many linebreaks we see while doing so.
362 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
369 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
370 to trigger a sentence break here.) */
374 /* Skip forward to the end of this word (find next whitespace.) */
382 /* If we have a word, allocate a string for it */
385 result = malloc ((end - start) + 1);
386 strncpy (result, start, (end-start));
387 result [end-start] = 0;
392 /* Make room in the buffer by compressing out any bytes we've processed.
396 int n = end - s->buf;
397 memmove (s->buf, end, sizeof(s->buf) - n);
401 /* See if there is more to be read, now that there's room in the buffer. */
408 /* Gets some random text, and creates a "word" object from it.
411 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
415 int dir, ascent, descent;
416 int bw = s->border_width;
421 w = (word *) calloc (1, sizeof(*w));
422 XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
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;
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.
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
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.)
447 if (w->rbearing > w->width)
448 w->rbearing = w->width;
451 if (s->mode == SCROLL && !alloc_p) abort();
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);
462 gcv.font = se->font->fid;
465 gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
469 gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
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);
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);
484 /* Draw foreground text */
485 XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
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,
500 XSetFunction (s->dpy, gc1, GXset);
501 if (w->ascent != w->height)
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);
510 if (w->lbearing != 0)
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);
519 if (w->rbearing != w->width)
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);
529 XFreeGC (s->dpy, gc0);
530 XFreeGC (s->dpy, gc1);
539 free_word (state *s, word *w)
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);
548 new_sentence (state *s)
552 sentence *se = (sentence *) calloc (1, sizeof (*se));
553 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
561 free_sentence (state *s, sentence *se)
564 for (i = 0; i < se->nwords; i++)
565 free_word (s, se->words[i]);
566 if (se->words) free (se->words);
569 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
571 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
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);
581 /* free the word, and put its text back at the front of the input queue,
582 to be read next time. */
584 unread_word (state *s, word *w)
586 if (unread_word_text)
588 unread_word_text = w->text;
594 /* Divide each of the words in the sentence into one character words,
595 without changing the positions of those characters.
598 split_words (state *s, sentence *se)
603 for (i = 0; i < se->nwords; i++)
604 nwords2 += strlen (se->words[i]->text);
606 words2 = (word **) calloc (nwords2, sizeof(*words2));
608 for (i = 0, j = 0; i < se->nwords; i++)
610 word *ow = se->words[i];
611 int L = strlen (ow->text);
616 int sx = ow->start_x;
617 int sy = ow->start_y;
618 int tx = ow->target_x;
619 int ty = ow->target_y;
621 for (k = 0; k < L; k++)
623 char *t2 = malloc (2);
629 w2 = new_word (s, se, t2, True);
632 xoff = (w2->lbearing - ow->lbearing);
633 yoff = (ow->ascent - w2->ascent);
637 w2->start_x = sx + xoff;
638 w2->start_y = sy + yoff;
639 w2->target_x = tx + xoff;
640 w2->target_y = ty + yoff;
653 se->nwords = nwords2;
657 /* Set the source or destination position of the words to be somewhere
661 scatter_sentence (state *s, sentence *se)
666 int flock_p = ((random() % 4) == 0);
667 int mode = (flock_p ? (random() % 12) : 0);
669 for (i = 0; i < se->nwords; i++)
671 word *w = se->words[i];
673 int r = (flock_p ? mode : (random() % 4));
676 /* random positions on the edges */
680 y = random() % s->xgwa.height;
683 x = off + s->xgwa.width;
684 y = random() % s->xgwa.height;
687 x = random() % s->xgwa.width;
688 y = -off - w->height;
691 x = random() % s->xgwa.width;
692 y = off + s->xgwa.height;
695 /* straight towards the edges */
702 x = off + s->xgwa.width;
707 y = -off - w->height;
711 y = off + s->xgwa.height;
718 y = -off - w->height;
722 y = off + s->xgwa.height;
725 x = off + s->xgwa.width;
726 y = off + s->xgwa.height;
729 x = off + s->xgwa.width;
730 y = -off - w->height;
738 if (se->anim_state == IN)
751 w->nticks = ((100 + ((random() % 140) +
762 /* Set the source position of the words to be off the right side,
763 and the destination to be off the left side.
766 aim_sentence (state *s, sentence *se)
772 if (se->nwords <= 0) abort();
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.
778 for (i = 0; i < 10; i++)
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 */
786 for (i = 0; i < se->nwords; i++)
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;
795 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
797 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
802 for (i = 0; i < se->nwords; i++)
804 word *w = se->words[i];
811 /* Randomize the order of the words in the list (since that changes
812 which ones are "on top".)
815 shuffle_words (state *s, sentence *se)
818 for (i = 0; i < se->nwords-1; i++)
820 int j = i + (random() % (se->nwords - i));
821 word *swap = se->words[i];
822 se->words[i] = se->words[j];
828 /* qsort comparitor */
830 cmp_sentences (const void *aa, const void *bb)
832 const sentence *a = *(sentence **) aa;
833 const sentence *b = *(sentence **) bb;
834 return ((a ? a->id : 999999) - (b ? b->id : 999999));
838 /* Sort the sentences by id, so that sentences added later are on top.
841 sort_sentences (state *s)
843 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
847 /* Re-pick the colors of the text and border
850 recolor (state *s, sentence *se)
853 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
855 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
857 se->fg.flags = DoRed|DoGreen|DoBlue;
858 se->bg.flags = DoRed|DoGreen|DoBlue;
860 switch (random() % 2)
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);
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;
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);
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);
897 align_line (state *s, sentence *se, int line_start, int x, int right)
900 switch (se->alignment)
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;
909 for (j = line_start; j < se->nwords; j++)
910 se->words[j]->target_x += off;
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
920 populate_sentence (state *s, sentence *se)
923 int left, right, top, x, y;
928 int array_size = 100;
930 se->move_chars_p = (s->mode == SCROLL ? False :
931 (random() % 3) ? False : True);
932 se->alignment = (random() % 3);
938 for (i = 0; i < se->nwords; i++)
939 free_word (s, se->words[i]);
943 se->words = (word **) calloc (array_size, sizeof(*se->words));
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);
955 right = s->xgwa.width;
956 top = random() % s->xgwa.height;
968 char *txt = get_word_text (s);
973 return; /* If the stream is empty, bail. */
975 break; /* If EOF after some words, end of sentence. */
978 if (! se->font) /* Got a word: need a font now */
981 if (y < se->font->ascent)
982 y += se->font->ascent;
983 space = XTextWidth (se->font, " ", 1);
986 w = new_word (s, se, txt, !se->move_chars_p);
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. */
992 char c = w->text[strlen(w->text)-1];
993 if (c == '.' || c == '?' || c == '!')
997 /* If the sentence is kind of long already, terminate at commas, etc. */
998 if (se->nwords >= 12)
1000 char c = w->text[strlen(w->text)-1];
1001 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1002 c == ')' || c == ']' || c == '}')
1006 if (se->nwords >= 25) /* ok that's just about enough out of you */
1009 if (s->mode == PAGE &&
1010 x + w->rbearing > right) /* wrap line */
1012 align_line (s, se, line_start, x, right);
1013 line_start = se->nwords;
1016 y += se->font->ascent;
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.)
1022 if (se->nwords > 0 &&
1023 y + se->font->ascent > s->xgwa.height)
1031 w->target_x = x + w->lbearing;
1032 w->target_y = y - w->ascent;
1034 x += w->rbearing + space;
1037 if (se->nwords >= (array_size - 1))
1040 se->words = (word **) realloc (se->words,
1041 array_size * sizeof(*se->words));
1044 fprintf (stderr, "%s: out of memory (%d words)\n",
1045 progname, array_size);
1050 se->words[se->nwords++] = w;
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);
1065 aim_sentence (s, se);
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");
1084 /* Render a single word object to the screen.
1087 draw_word (state *s, sentence *se, word *w)
1089 if (! w->pixmap) return;
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)
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,
1106 /* If there is room for more sentences, add one.
1109 more_sentences (state *s)
1113 for (i = 0; i < s->nsentences; i++)
1115 sentence *se = s->sentences[i];
1118 se = new_sentence (s);
1119 populate_sentence (s, se);
1121 s->spawn_p = False, any = True;
1124 free_sentence (s, se);
1127 s->sentences[i] = se;
1129 s->latest_sentence = se->id;
1134 if (any) sort_sentences (s);
1138 /* Render all the words to the screen, and run the animation one step.
1141 draw_sentence (state *s, sentence *se)
1148 for (i = 0; i < se->nwords; i++)
1150 word *w = se->words[i];
1155 if (se->anim_state != PAUSE &&
1156 w->tick <= w->nticks)
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);
1165 if (se->anim_state == OUT && s->mode == PAGE)
1166 w->tick++; /* go out faster */
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);
1178 moved = (w->tick <= w->nticks);
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.
1185 if (se->anim_state != OUT &&
1187 se->id == s->latest_sentence)
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));
1193 if (new_p || rand_p)
1195 se->anim_state = OUT;
1199 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1201 se->words[0]->x + se->width,
1202 rand_p ? " randomly" : "");
1213 draw_word (s, se, w);
1216 if (moved && se->anim_state == PAUSE)
1221 switch (se->anim_state)
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);
1232 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1236 if (--se->pause_tick <= 0)
1238 se->anim_state = OUT;
1242 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1249 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1253 for (j = 0; j < s->nsentences; j++)
1254 if (s->sentences[j] == se)
1255 s->sentences[j] = 0;
1256 free_sentence (s, se);
1267 /* Render all the words to the screen, and run the animation one step.
1268 Clear screen first, swap buffers after.
1271 draw_fontglide (state *s)
1279 XFillRectangle (s->dpy, s->b, s->bg_gc,
1280 0, 0, s->xgwa.width, s->xgwa.height);
1282 for (i = 0; i < s->nsentences; i++)
1283 draw_sentence (s, s->sentences[i]);
1285 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
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);
1294 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1297 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1298 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1304 handle_events (state *s)
1306 while (XPending (s->dpy))
1309 XNextEvent (s->dpy, &event);
1311 if (event.xany.type == ConfigureNotify)
1313 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1315 if (s->dbuf && (s->ba))
1317 XFreePixmap (s->dpy, s->ba);
1318 s->ba = XCreatePixmap (s->dpy, s->window,
1319 s->xgwa.width, s->xgwa.height,
1321 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
1322 s->xgwa.width, s->xgwa.height);
1327 screenhack_handle_event (s->dpy, &event);
1334 (This bit mostly cribbed from phosphor.c)
1338 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1340 state *s = (state *) closure;
1341 s->input_available_p = True;
1346 launch_text_generator (state *s)
1348 char *oprogram = get_string_resource ("program", "Program");
1351 program = (char *) malloc (strlen (oprogram) + 10);
1352 strcpy (program, "( ");
1353 strcat (program, oprogram);
1354 strcat (program, " ) 2>&1");
1357 fprintf (stderr, "%s: forking: %s\n", progname, program);
1359 if ((s->pipe = popen (program, "r")))
1362 XtAppAddInput (app, fileno (s->pipe),
1363 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1364 subproc_cb, (XtPointer) s);
1374 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1376 state *s = (state *) closure;
1377 launch_text_generator (s);
1381 /* When the subprocess has generated some output, this reads as much as it
1382 can into s->buf at s->buf_tail.
1385 drain_input (state *s)
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
1390 if (! s->input_available_p)
1391 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1392 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1394 if (! s->pipe) return;
1395 if (! s->input_available_p) return;
1396 s->input_available_p = False;
1398 if (s->buf_tail < sizeof(s->buf) - 2)
1400 int target = sizeof(s->buf) - s->buf_tail - 2;
1402 n = read (fileno (s->pipe),
1403 (void *) (s->buf + s->buf_tail),
1408 s->buf[s->buf_tail] = 0;
1412 XtRemoveInput (s->pipe_id);
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')
1421 s->buf[s->buf_tail++] = '\n';
1422 s->buf[s->buf_tail] = 0;
1425 /* Then add one more, to make sure there's a sentence break at EOF.
1427 s->buf[s->buf_tail++] = '\n';
1428 s->buf[s->buf_tail] = 0;
1430 /* Set up a timer to re-launch the subproc in a bit. */
1431 XtAppAddTimeOut (app, s->subproc_relaunch_delay,
1432 relaunch_generator_timer,
1439 /* Window setup and resource loading */
1442 init_fontglide (Display *dpy, Window window)
1445 state *s = (state *) calloc (1, sizeof(*s));
1449 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
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;
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;
1460 s->speed = get_float_resource ("speed", "Float");
1461 if (s->speed <= 0 || s->speed > 200)
1464 s->linger = get_float_resource ("linger", "Float");
1465 if (s->linger <= 0 || s->linger > 200)
1468 s->debug_p = get_boolean_resource ("debug", "Debug");
1469 s->trails_p = get_boolean_resource ("trails", "Trails");
1471 s->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
1472 s->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
1474 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
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"))
1482 else if (!strcasecmp (ss, "page"))
1487 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1494 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1496 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1498 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1500 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1504 s->ba = XCreatePixmap (s->dpy, s->window,
1505 s->xgwa.width, s->xgwa.height,
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);
1519 s->subproc_relaunch_delay = 2 * 1000;
1521 launch_text_generator (s);
1523 s->nsentences = 5; /* #### */
1524 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1532 char *progclass = "FontGlide";
1534 char *defaults [] = {
1535 ".background: #000000",
1536 ".foreground: #DDDDDD",
1537 ".borderColor: #555555",
1539 "*program: " FORTUNE_PROGRAM,
1542 "*fontCharset: iso8859-1",
1543 "*fontBorderWidth: 2",
1548 "*doubleBuffer: True",
1549 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1551 "*useDBEClear: True",
1552 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1556 XrmOptionDescRec options [] = {
1557 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
1558 { "-page", ".mode", XrmoptionNoArg, "page" },
1559 { "-random", ".mode", XrmoptionNoArg, "random" },
1560 { "-delay", ".delay", XrmoptionSepArg, 0 },
1561 { "-speed", ".speed", XrmoptionSepArg, 0 },
1562 { "-linger", ".linger", XrmoptionSepArg, 0 },
1563 { "-program", ".program", XrmoptionSepArg, 0 },
1564 { "-font", ".font", XrmoptionSepArg, 0 },
1565 { "-fn", ".font", XrmoptionSepArg, 0 },
1566 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
1567 { "-trails", ".trails", XrmoptionNoArg, "True" },
1568 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
1569 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
1570 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
1571 { "-debug", ".debug", XrmoptionNoArg, "True" },
1577 screenhack (Display *dpy, Window window)
1579 state *s = init_fontglide (dpy, window);
1580 int delay = get_integer_resource ("delay", "Integer");
1587 if (delay) usleep (delay);