1 /* xscreensaver, Copyright (c) 2003, 2005 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);
288 fprintf (stderr, "%s: unable to load font %s\n",
293 if (se->font->min_bounds.width == se->font->max_bounds.width &&
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.
305 "%s: skipping bogus monospace non-charcell font: %s\n",
311 fprintf(stderr, "%s: %s\n", progname, pattern);
313 se->font_name = strdup (pattern);
314 XSetFont (s->dpy, se->fg_gc, se->font->fid);
319 /* Finds the set of scalable fonts on the system; picks one;
320 and loads that font in a random pixel size.
323 pick_font (state *s, sentence *se)
326 for (i = 0; i < 20; i++)
327 if (pick_font_1 (s, se))
329 fprintf (stderr, "%s: too many font-loading failures: giving up!\n", progname);
334 static char *unread_word_text = 0;
336 /* Returns a newly-allocated string with one word in it, or NULL if there
337 is no complete word available.
340 get_word_text (state *s)
342 char *start = s->buf;
347 if (unread_word_text)
349 char *s = unread_word_text;
350 unread_word_text = 0;
354 /* Skip over whitespace at the beginning of the buffer,
355 and count up how many linebreaks we see while doing so.
363 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
370 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
371 to trigger a sentence break here.) */
375 /* Skip forward to the end of this word (find next whitespace.) */
383 /* If we have a word, allocate a string for it */
386 result = malloc ((end - start) + 1);
387 strncpy (result, start, (end-start));
388 result [end-start] = 0;
393 /* Make room in the buffer by compressing out any bytes we've processed.
397 int n = end - s->buf;
398 memmove (s->buf, end, sizeof(s->buf) - n);
402 /* See if there is more to be read, now that there's room in the buffer. */
409 /* Gets some random text, and creates a "word" object from it.
412 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
416 int dir, ascent, descent;
417 int bw = s->border_width;
422 w = (word *) calloc (1, sizeof(*w));
423 XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
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;
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.
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
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.)
448 if (w->rbearing > w->width)
449 w->rbearing = w->width;
452 if (s->mode == SCROLL && !alloc_p) abort();
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);
463 gcv.font = se->font->fid;
466 gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
470 gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
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);
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);
485 /* Draw foreground text */
486 XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
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,
501 XSetFunction (s->dpy, gc1, GXset);
502 if (w->ascent != w->height)
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);
511 if (w->lbearing != 0)
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);
520 if (w->rbearing != w->width)
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);
530 XFreeGC (s->dpy, gc0);
531 XFreeGC (s->dpy, gc1);
540 free_word (state *s, word *w)
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);
549 new_sentence (state *s)
553 sentence *se = (sentence *) calloc (1, sizeof (*se));
554 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
562 free_sentence (state *s, sentence *se)
565 for (i = 0; i < se->nwords; i++)
566 free_word (s, se->words[i]);
567 if (se->words) free (se->words);
570 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
572 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
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);
582 /* free the word, and put its text back at the front of the input queue,
583 to be read next time. */
585 unread_word (state *s, word *w)
587 if (unread_word_text)
589 unread_word_text = w->text;
595 /* Divide each of the words in the sentence into one character words,
596 without changing the positions of those characters.
599 split_words (state *s, sentence *se)
604 for (i = 0; i < se->nwords; i++)
605 nwords2 += strlen (se->words[i]->text);
607 words2 = (word **) calloc (nwords2, sizeof(*words2));
609 for (i = 0, j = 0; i < se->nwords; i++)
611 word *ow = se->words[i];
612 int L = strlen (ow->text);
617 int sx = ow->start_x;
618 int sy = ow->start_y;
619 int tx = ow->target_x;
620 int ty = ow->target_y;
622 for (k = 0; k < L; k++)
624 char *t2 = malloc (2);
630 w2 = new_word (s, se, t2, True);
633 xoff = (w2->lbearing - ow->lbearing);
634 yoff = (ow->ascent - w2->ascent);
638 w2->start_x = sx + xoff;
639 w2->start_y = sy + yoff;
640 w2->target_x = tx + xoff;
641 w2->target_y = ty + yoff;
654 se->nwords = nwords2;
658 /* Set the source or destination position of the words to be somewhere
662 scatter_sentence (state *s, sentence *se)
667 int flock_p = ((random() % 4) == 0);
668 int mode = (flock_p ? (random() % 12) : 0);
670 for (i = 0; i < se->nwords; i++)
672 word *w = se->words[i];
674 int r = (flock_p ? mode : (random() % 4));
677 /* random positions on the edges */
681 y = random() % s->xgwa.height;
684 x = off + s->xgwa.width;
685 y = random() % s->xgwa.height;
688 x = random() % s->xgwa.width;
689 y = -off - w->height;
692 x = random() % s->xgwa.width;
693 y = off + s->xgwa.height;
696 /* straight towards the edges */
703 x = off + s->xgwa.width;
708 y = -off - w->height;
712 y = off + s->xgwa.height;
719 y = -off - w->height;
723 y = off + s->xgwa.height;
726 x = off + s->xgwa.width;
727 y = off + s->xgwa.height;
730 x = off + s->xgwa.width;
731 y = -off - w->height;
739 if (se->anim_state == IN)
752 w->nticks = ((100 + ((random() % 140) +
763 /* Set the source position of the words to be off the right side,
764 and the destination to be off the left side.
767 aim_sentence (state *s, sentence *se)
773 if (se->nwords <= 0) abort();
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.
779 for (i = 0; i < 10; i++)
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 */
787 for (i = 0; i < se->nwords; i++)
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;
796 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
798 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
803 for (i = 0; i < se->nwords; i++)
805 word *w = se->words[i];
812 /* Randomize the order of the words in the list (since that changes
813 which ones are "on top".)
816 shuffle_words (state *s, sentence *se)
819 for (i = 0; i < se->nwords-1; i++)
821 int j = i + (random() % (se->nwords - i));
822 word *swap = se->words[i];
823 se->words[i] = se->words[j];
829 /* qsort comparitor */
831 cmp_sentences (const void *aa, const void *bb)
833 const sentence *a = *(sentence **) aa;
834 const sentence *b = *(sentence **) bb;
835 return ((a ? a->id : 999999) - (b ? b->id : 999999));
839 /* Sort the sentences by id, so that sentences added later are on top.
842 sort_sentences (state *s)
844 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
848 /* Re-pick the colors of the text and border
851 recolor (state *s, sentence *se)
854 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
856 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
858 se->fg.flags = DoRed|DoGreen|DoBlue;
859 se->bg.flags = DoRed|DoGreen|DoBlue;
861 switch (random() % 2)
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);
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;
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);
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);
898 align_line (state *s, sentence *se, int line_start, int x, int right)
901 switch (se->alignment)
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;
910 for (j = line_start; j < se->nwords; j++)
911 se->words[j]->target_x += off;
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
921 populate_sentence (state *s, sentence *se)
924 int left, right, top, x, y;
929 int array_size = 100;
931 se->move_chars_p = (s->mode == SCROLL ? False :
932 (random() % 3) ? False : True);
933 se->alignment = (random() % 3);
939 for (i = 0; i < se->nwords; i++)
940 free_word (s, se->words[i]);
944 se->words = (word **) calloc (array_size, sizeof(*se->words));
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);
956 right = s->xgwa.width;
957 top = random() % s->xgwa.height;
969 char *txt = get_word_text (s);
974 return; /* If the stream is empty, bail. */
976 break; /* If EOF after some words, end of sentence. */
979 if (! se->font) /* Got a word: need a font now */
982 if (y < se->font->ascent)
983 y += se->font->ascent;
984 space = XTextWidth (se->font, " ", 1);
987 w = new_word (s, se, txt, !se->move_chars_p);
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. */
993 char c = w->text[strlen(w->text)-1];
994 if (c == '.' || c == '?' || c == '!')
998 /* If the sentence is kind of long already, terminate at commas, etc. */
999 if (se->nwords >= 12)
1001 char c = w->text[strlen(w->text)-1];
1002 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1003 c == ')' || c == ']' || c == '}')
1007 if (se->nwords >= 25) /* ok that's just about enough out of you */
1010 if (s->mode == PAGE &&
1011 x + w->rbearing > right) /* wrap line */
1013 align_line (s, se, line_start, x, right);
1014 line_start = se->nwords;
1017 y += se->font->ascent;
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.)
1023 if (se->nwords > 0 &&
1024 y + se->font->ascent > s->xgwa.height)
1032 w->target_x = x + w->lbearing;
1033 w->target_y = y - w->ascent;
1035 x += w->rbearing + space;
1038 if (se->nwords >= (array_size - 1))
1041 se->words = (word **) realloc (se->words,
1042 array_size * sizeof(*se->words));
1045 fprintf (stderr, "%s: out of memory (%d words)\n",
1046 progname, array_size);
1051 se->words[se->nwords++] = w;
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);
1066 aim_sentence (s, se);
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");
1085 /* Render a single word object to the screen.
1088 draw_word (state *s, sentence *se, word *w)
1090 if (! w->pixmap) return;
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)
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,
1107 /* If there is room for more sentences, add one.
1110 more_sentences (state *s)
1114 for (i = 0; i < s->nsentences; i++)
1116 sentence *se = s->sentences[i];
1119 se = new_sentence (s);
1120 populate_sentence (s, se);
1122 s->spawn_p = False, any = True;
1125 free_sentence (s, se);
1128 s->sentences[i] = se;
1130 s->latest_sentence = se->id;
1135 if (any) sort_sentences (s);
1139 /* Render all the words to the screen, and run the animation one step.
1142 draw_sentence (state *s, sentence *se)
1149 for (i = 0; i < se->nwords; i++)
1151 word *w = se->words[i];
1156 if (se->anim_state != PAUSE &&
1157 w->tick <= w->nticks)
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);
1166 if (se->anim_state == OUT && s->mode == PAGE)
1167 w->tick++; /* go out faster */
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);
1179 moved = (w->tick <= w->nticks);
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.
1186 if (se->anim_state != OUT &&
1188 se->id == s->latest_sentence)
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));
1194 if (new_p || rand_p)
1196 se->anim_state = OUT;
1200 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1202 se->words[0]->x + se->width,
1203 rand_p ? " randomly" : "");
1214 draw_word (s, se, w);
1217 if (moved && se->anim_state == PAUSE)
1222 switch (se->anim_state)
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);
1233 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1237 if (--se->pause_tick <= 0)
1239 se->anim_state = OUT;
1243 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1250 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1254 for (j = 0; j < s->nsentences; j++)
1255 if (s->sentences[j] == se)
1256 s->sentences[j] = 0;
1257 free_sentence (s, se);
1268 /* Render all the words to the screen, and run the animation one step.
1269 Clear screen first, swap buffers after.
1272 draw_fontglide (state *s)
1280 XFillRectangle (s->dpy, s->b, s->bg_gc,
1281 0, 0, s->xgwa.width, s->xgwa.height);
1283 for (i = 0; i < s->nsentences; i++)
1284 draw_sentence (s, s->sentences[i]);
1286 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
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);
1295 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1298 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1299 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1305 handle_events (state *s)
1307 while (XPending (s->dpy))
1310 XNextEvent (s->dpy, &event);
1312 if (event.xany.type == ConfigureNotify)
1314 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1316 if (s->dbuf && (s->ba))
1318 XFreePixmap (s->dpy, s->ba);
1319 s->ba = XCreatePixmap (s->dpy, s->window,
1320 s->xgwa.width, s->xgwa.height,
1322 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
1323 s->xgwa.width, s->xgwa.height);
1328 screenhack_handle_event (s->dpy, &event);
1335 (This bit mostly cribbed from phosphor.c)
1339 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1341 state *s = (state *) closure;
1342 s->input_available_p = True;
1347 launch_text_generator (state *s)
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");
1356 fprintf (stderr, "%s: forking: %s\n", progname, program);
1358 if ((s->pipe = popen (program, "r")))
1361 XtAppAddInput (app, fileno (s->pipe),
1362 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1363 subproc_cb, (XtPointer) s);
1373 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1375 state *s = (state *) closure;
1376 launch_text_generator (s);
1380 /* When the subprocess has generated some output, this reads as much as it
1381 can into s->buf at s->buf_tail.
1384 drain_input (state *s)
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
1389 if (! s->input_available_p)
1390 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1391 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1393 if (! s->pipe) return;
1394 if (! s->input_available_p) return;
1395 s->input_available_p = False;
1397 if (s->buf_tail < sizeof(s->buf) - 2)
1399 int target = sizeof(s->buf) - s->buf_tail - 2;
1401 n = read (fileno (s->pipe),
1402 (void *) (s->buf + s->buf_tail),
1407 s->buf[s->buf_tail] = 0;
1411 XtRemoveInput (s->pipe_id);
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')
1420 s->buf[s->buf_tail++] = '\n';
1421 s->buf[s->buf_tail] = 0;
1424 /* Then add one more, to make sure there's a sentence break at EOF.
1426 s->buf[s->buf_tail++] = '\n';
1427 s->buf[s->buf_tail] = 0;
1429 /* Set up a timer to re-launch the subproc in a bit. */
1430 XtAppAddTimeOut (app, s->subproc_relaunch_delay,
1431 relaunch_generator_timer,
1438 /* Window setup and resource loading */
1441 init_fontglide (Display *dpy, Window window)
1444 state *s = (state *) calloc (1, sizeof(*s));
1448 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
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;
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;
1459 s->speed = get_float_resource ("speed", "Float");
1460 if (s->speed <= 0 || s->speed > 200)
1463 s->linger = get_float_resource ("linger", "Float");
1464 if (s->linger <= 0 || s->linger > 200)
1467 s->debug_p = get_boolean_resource ("debug", "Debug");
1468 s->trails_p = get_boolean_resource ("trails", "Trails");
1470 s->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
1471 s->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
1473 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
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"))
1481 else if (!strcasecmp (ss, "page"))
1486 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1493 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1495 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1497 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1499 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1503 s->ba = XCreatePixmap (s->dpy, s->window,
1504 s->xgwa.width, s->xgwa.height,
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);
1518 s->subproc_relaunch_delay = 2 * 1000;
1520 launch_text_generator (s);
1522 s->nsentences = 5; /* #### */
1523 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1531 char *progclass = "FontGlide";
1533 char *defaults [] = {
1534 ".background: #000000",
1535 ".foreground: #DDDDDD",
1536 ".borderColor: #555555",
1538 "*program: xscreensaver-text",
1541 "*fontCharset: iso8859-1",
1542 "*fontBorderWidth: 2",
1547 "*doubleBuffer: True",
1548 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1550 "*useDBEClear: True",
1551 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
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" },
1576 screenhack (Display *dpy, Window window)
1578 state *s = init_fontglide (dpy, window);
1579 int delay = get_integer_resource ("delay", "Integer");
1586 if (delay) usleep (delay);