1 /* xscreensaver, Copyright (c) 2003, 2005, 2006 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.)
18 #endif /* HAVE_CONFIG_H */
23 # include <X11/Intrinsic.h>
30 #include "screenhack.h"
32 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
34 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
38 int x, y, width, height;
39 int ascent, lbearing, rbearing;
43 int target_x, target_y;
64 enum { IN, PAUSE, OUT } anim_state;
65 enum { LEFT, CENTER, RIGHT } alignment;
74 XWindowAttributes xgwa;
76 Pixmap b, ba; /* double-buffer to reduce flicker */
79 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
82 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
84 Bool dbuf; /* Whether we're using double buffering. */
86 int border_width; /* size of the font outline */
87 char *charset; /* registry and encoding for font lookups */
88 double speed; /* frame rate multiplier */
89 double linger; /* multiplier for how long to leave words on screen */
93 enum { PAGE, SCROLL } mode;
95 char *font_override; /* if -font was specified on the cmd line */
98 XtIntervalId timer_id;
100 Time subproc_relaunch_delay;
101 /* Bool input_available_p; */
103 char buf [40]; /* this only needs to be as big as one "word". */
107 sentence **sentences;
108 Bool spawn_p; /* whether it is time to create a new sentence */
111 unsigned long frame_delay;
118 static void launch_text_generator (state *);
119 static void drain_input (state *s);
123 pick_font_size (state *s)
125 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
126 int min, max, r, pixel;
131 if (min < 10) min = 10;
132 if (max < 30) max = 30;
136 pixel = min + ((random() % r) + (random() % r) + (random() % r));
138 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
145 /* Finds the set of scalable fonts on the system; picks one;
146 and loads that font in a random pixel size.
147 Returns False if something went wrong.
150 pick_font_1 (state *s, sentence *se)
155 # ifndef HAVE_COCOA /* real Xlib */
158 XFontStruct *info = 0;
159 int count = 0, count2 = 0;
164 XFreeFont (s->dpy, se->font);
165 free (se->font_name);
170 if (s->font_override)
171 sprintf (pattern, "%.200s", s->font_override);
173 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
180 "0", /* pixel size */
181 "0", /* point size */
182 "0", /* resolution x */
183 "0", /* resolution y */
186 s->charset); /* registry + encoding */
188 names = XListFonts (s->dpy, pattern, 1000, &count);
192 if (s->font_override)
193 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
195 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
200 i = random() % count;
202 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
205 fprintf (stderr, "%s: pattern %s\n"
206 " gave unusable %s\n\n",
207 progname, pattern, names[i]);
212 XFontStruct *font = &info[0];
213 unsigned long value = 0;
214 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
215 unsigned long pixel=0, point=0, res_x=0, res_y=0;
217 unsigned long avg_width=0;
218 char *registry=0, *encoding=0;
220 char *bogus = "\"?\"";
222 # define STR(ATOM,VAR) \
224 a = XInternAtom (s->dpy, (ATOM), False); \
225 if (XGetFontProperty (font, a, &value)) \
226 VAR = XGetAtomName (s->dpy, value); \
230 # define INT(ATOM,VAR) \
232 a = XInternAtom (s->dpy, (ATOM), False); \
233 if (!XGetFontProperty (font, a, &VAR) || \
237 STR ("FOUNDRY", foundry);
238 STR ("FAMILY_NAME", family);
239 STR ("WEIGHT_NAME", weight);
240 STR ("SLANT", slant);
241 STR ("SETWIDTH_NAME", setwidth);
242 STR ("ADD_STYLE_NAME", add_style);
243 INT ("PIXEL_SIZE", pixel);
244 INT ("POINT_SIZE", point);
245 INT ("RESOLUTION_X", res_x);
246 INT ("RESOLUTION_Y", res_y);
247 STR ("SPACING", spacing);
248 INT ("AVERAGE_WIDTH", avg_width);
249 STR ("CHARSET_REGISTRY", registry);
250 STR ("CHARSET_ENCODING", encoding);
255 pixel = pick_font_size (s);
258 /* Occasionally change the aspect ratio of the font, by increasing
259 either the X or Y resolution (while leaving the other alone.)
261 #### Looks like this trick doesn't really work that well: the
262 metrics of the individual characters are ok, but the
263 overall font ascent comes out wrong (unscaled.)
265 if (! (random() % 8))
268 double scale = 1 + (frand(n) + frand(n) + frand(n));
277 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
278 foundry, family, weight, slant, setwidth, add_style,
279 pixel, "*", /* point, */
280 res_x, res_y, spacing,
287 fprintf (stderr, "%s: font has bogus %s property: %s\n",
288 progname, bogus, names[i]);
290 if (foundry) XFree (foundry);
291 if (family) XFree (family);
292 if (weight) XFree (weight);
293 if (slant) XFree (slant);
294 if (setwidth) XFree (setwidth);
295 if (add_style) XFree (add_style);
296 if (spacing) XFree (spacing);
297 if (registry) XFree (registry);
298 if (encoding) XFree (encoding);
303 XFreeFontInfo (names2, info, count2);
304 XFreeFontNames (names);
306 # else /* HAVE_COCOA */
308 if (s->font_override)
309 sprintf (pattern, "%.200s", s->font_override);
312 const char *family = "random";
313 const char *weight = ((random() % 2) ? "normal" : "bold");
314 const char *slant = ((random() % 2) ? "o" : "r");
315 int size = 10 * pick_font_size (s);
316 sprintf (pattern, "*-%s-%s-%s-*-%d-*", family, weight, slant, size);
319 # endif /* HAVE_COCOA */
321 if (! ok) return False;
323 se->font = XLoadQueryFont (s->dpy, pattern);
327 fprintf (stderr, "%s: unable to load font %s\n",
332 if (se->font->min_bounds.width == se->font->max_bounds.width &&
335 /* This is to weed out
336 "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
337 "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1".
338 We asked for only proportional fonts, but this fixed-width font
339 shows up anyway -- but it has goofy metrics (see below) so it
340 looks terrible anyway.
344 "%s: skipping bogus monospace non-charcell font: %s\n",
350 fprintf(stderr, "%s: %s\n", progname, pattern);
352 se->font_name = strdup (pattern);
353 XSetFont (s->dpy, se->fg_gc, se->font->fid);
358 /* Finds the set of scalable fonts on the system; picks one;
359 and loads that font in a random pixel size.
362 pick_font (state *s, sentence *se)
365 for (i = 0; i < 20; i++)
366 if (pick_font_1 (s, se))
368 fprintf (stderr, "%s: too many font-loading failures: giving up!\n", progname);
373 static char *unread_word_text = 0;
375 /* Returns a newly-allocated string with one word in it, or NULL if there
376 is no complete word available.
379 get_word_text (state *s)
381 char *start = s->buf;
388 if (unread_word_text)
390 start = unread_word_text;
391 unread_word_text = 0;
395 /* Skip over whitespace at the beginning of the buffer,
396 and count up how many linebreaks we see while doing so.
404 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
411 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
412 to trigger a sentence break here.) */
416 /* Skip forward to the end of this word (find next whitespace.) */
424 /* If we have a word, allocate a string for it */
427 result = malloc ((end - start) + 1);
428 strncpy (result, start, (end-start));
429 result [end-start] = 0;
434 /* Make room in the buffer by compressing out any bytes we've processed.
438 int n = end - s->buf;
439 memmove (s->buf, end, sizeof(s->buf) - n);
447 /* Gets some random text, and creates a "word" object from it.
450 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
454 int dir, ascent, descent;
455 int bw = s->border_width;
460 w = (word *) calloc (1, sizeof(*w));
461 XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
463 /* Leave a little more slack */
464 overall.lbearing -= (bw * 2);
465 overall.rbearing += (bw * 2);
466 overall.ascent += (bw * 2);
467 overall.descent += (bw * 2);
469 w->width = overall.rbearing - overall.lbearing + bw + bw;
470 w->height = overall.ascent + overall.descent + bw + bw;
471 w->ascent = overall.ascent + bw;
472 w->lbearing = overall.lbearing - bw;
473 w->rbearing = overall.width + bw;
476 /* The metrics on some fonts are strange -- e.g.,
477 "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
478 "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
479 an rbearing so wide that it looks like there are two spaces after
480 each letter. If this character says it has an rbearing that is to
481 the right of its ink, ignore that.
483 #### Of course, this hack only helps when we're in `move_chars_p' mode
484 and drawing a char at a time -- when we draw the whole word at once,
485 XDrawString believes the bogus metrics and spaces the font out
488 Sigh, this causes some text to mis-render in, e.g.,
489 "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
490 (in "ux", we need the rbearing on "r" or we get too much overlap.)
492 if (w->rbearing > w->width)
493 w->rbearing = w->width;
496 if (s->mode == SCROLL && !alloc_p) abort();
504 w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
505 w->mask = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
507 gcv.font = se->font->fid;
510 gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
514 gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
517 XFillRectangle (s->dpy, w->mask, gc0, 0, 0, w->width, w->height);
518 XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
522 /* bounding box (behind the characters) */
523 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
524 0, 0, w->width-1, w->height-1);
525 XDrawRectangle (s->dpy, w->mask, gc1,
526 0, 0, w->width-1, w->height-1);
532 /* bounding box (behind *each* character) */
535 for (ss = txt; *ss; ss++)
537 XTextExtents (se->font, ss, 1, &dir, &ascent, &descent, &overall);
538 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
539 x, w->ascent - overall.ascent,
541 overall.ascent + overall.descent);
542 XDrawRectangle (s->dpy, w->mask, gc1,
543 x, w->ascent - overall.ascent,
545 overall.ascent + overall.descent);
547 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
548 x - overall.lbearing, w->ascent - overall.ascent,
550 overall.ascent + overall.descent);
551 XDrawRectangle (s->dpy, w->mask, gc1,
552 x - overall.lbearing, w->ascent - overall.ascent,
554 overall.ascent + overall.descent);
562 /* Draw foreground text */
563 XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
566 /* Cheesy hack to draw a border */
567 /* (I should be able to do this in i*2 time instead of i*i time,
568 but I can't get it right, so fuck it.) */
569 XSetFunction (s->dpy, gc1, GXor);
570 for (i = -bw; i <= bw; i++)
571 for (j = -bw; j <= bw; j++)
572 XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
573 0, 0, w->width, w->height,
578 XSetFunction (s->dpy, gc1, GXcopy);
579 if (w->ascent != w->height)
581 /* baseline (on top of the characters) */
582 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
583 0, w->ascent, w->width-1, w->ascent);
584 XDrawLine (s->dpy, w->mask, gc1,
585 0, w->ascent, w->width-1, w->ascent);
588 if (w->lbearing != 0)
590 /* left edge of charcell */
591 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
592 w->lbearing, 0, w->lbearing, w->height-1);
593 XDrawLine (s->dpy, w->mask, gc1,
594 w->lbearing, 0, w->lbearing, w->height-1);
597 if (w->rbearing != w->width)
599 /* right edge of charcell */
600 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
601 w->rbearing, 0, w->rbearing, w->height-1);
602 XDrawLine (s->dpy, w->mask, gc1,
603 w->rbearing, 0, w->rbearing, w->height-1);
607 XFreeGC (s->dpy, gc0);
608 XFreeGC (s->dpy, gc1);
617 free_word (state *s, word *w)
619 if (w->text) free (w->text);
620 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
621 if (w->mask) XFreePixmap (s->dpy, w->mask);
626 new_sentence (state *st, state *s)
629 sentence *se = (sentence *) calloc (1, sizeof (*se));
630 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
632 se->id = ++st->id_tick;
638 free_sentence (state *s, sentence *se)
641 for (i = 0; i < se->nwords; i++)
642 free_word (s, se->words[i]);
643 if (se->words) free (se->words);
646 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
648 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
650 if (se->font_name) free (se->font_name);
651 if (se->font) XFreeFont (s->dpy, se->font);
652 if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
658 /* free the word, and put its text back at the front of the input queue,
659 to be read next time. */
661 unread_word (state *s, word *w)
663 if (unread_word_text)
665 unread_word_text = w->text;
671 /* Divide each of the words in the sentence into one character words,
672 without changing the positions of those characters.
675 split_words (state *s, sentence *se)
680 for (i = 0; i < se->nwords; i++)
681 nwords2 += strlen (se->words[i]->text);
683 words2 = (word **) calloc (nwords2, sizeof(*words2));
685 for (i = 0, j = 0; i < se->nwords; i++)
687 word *ow = se->words[i];
688 int L = strlen (ow->text);
693 int sx = ow->start_x;
694 int sy = ow->start_y;
695 int tx = ow->target_x;
696 int ty = ow->target_y;
698 for (k = 0; k < L; k++)
700 char *t2 = malloc (2);
706 w2 = new_word (s, se, t2, True);
709 xoff = (w2->lbearing - ow->lbearing);
710 yoff = (ow->ascent - w2->ascent);
714 w2->start_x = sx + xoff;
715 w2->start_y = sy + yoff;
716 w2->target_x = tx + xoff;
717 w2->target_y = ty + yoff;
730 se->nwords = nwords2;
734 /* Set the source or destination position of the words to be somewhere
738 scatter_sentence (state *s, sentence *se)
743 int flock_p = ((random() % 4) == 0);
744 int mode = (flock_p ? (random() % 12) : 0);
746 for (i = 0; i < se->nwords; i++)
748 word *w = se->words[i];
750 int r = (flock_p ? mode : (random() % 4));
753 /* random positions on the edges */
757 y = random() % s->xgwa.height;
760 x = off + s->xgwa.width;
761 y = random() % s->xgwa.height;
764 x = random() % s->xgwa.width;
765 y = -off - w->height;
768 x = random() % s->xgwa.width;
769 y = off + s->xgwa.height;
772 /* straight towards the edges */
779 x = off + s->xgwa.width;
784 y = -off - w->height;
788 y = off + s->xgwa.height;
795 y = -off - w->height;
799 y = off + s->xgwa.height;
802 x = off + s->xgwa.width;
803 y = off + s->xgwa.height;
806 x = off + s->xgwa.width;
807 y = -off - w->height;
815 if (se->anim_state == IN)
828 w->nticks = ((100 + ((random() % 140) +
839 /* Set the source position of the words to be off the right side,
840 and the destination to be off the left side.
843 aim_sentence (state *s, sentence *se)
849 if (se->nwords <= 0) abort();
851 /* Have the sentence shift up or down a little bit; not too far, and
852 never let it fall off the top or bottom of the screen before its
853 last character has reached the left edge.
855 for (i = 0; i < 10; i++)
857 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
858 yoff = ty - se->words[0]->target_y;
859 if (yoff < s->xgwa.height/3) /* this one is ok */
863 for (i = 0; i < se->nwords; i++)
865 word *w = se->words[i];
866 w->start_x = w->target_x + s->xgwa.width;
867 w->target_x -= se->width;
868 w->start_y = w->target_y;
872 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
874 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
879 for (i = 0; i < se->nwords; i++)
881 word *w = se->words[i];
888 /* Randomize the order of the words in the list (since that changes
889 which ones are "on top".)
892 shuffle_words (state *s, sentence *se)
895 for (i = 0; i < se->nwords-1; i++)
897 int j = i + (random() % (se->nwords - i));
898 word *swap = se->words[i];
899 se->words[i] = se->words[j];
905 /* qsort comparitor */
907 cmp_sentences (const void *aa, const void *bb)
909 const sentence *a = *(sentence **) aa;
910 const sentence *b = *(sentence **) bb;
911 return ((a ? a->id : 999999) - (b ? b->id : 999999));
915 /* Sort the sentences by id, so that sentences added later are on top.
918 sort_sentences (state *s)
920 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
924 /* Re-pick the colors of the text and border
927 recolor (state *s, sentence *se)
930 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
932 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
934 se->fg.flags = DoRed|DoGreen|DoBlue;
935 se->bg.flags = DoRed|DoGreen|DoBlue;
937 switch (random() % 2)
939 case 0: /* bright fg, dim bg */
940 se->fg.red = (random() % 0x8888) + 0x8888;
941 se->fg.green = (random() % 0x8888) + 0x8888;
942 se->fg.blue = (random() % 0x8888) + 0x8888;
943 se->bg.red = (random() % 0x5555);
944 se->bg.green = (random() % 0x5555);
945 se->bg.blue = (random() % 0x5555);
948 case 1: /* bright bg, dim fg */
949 se->fg.red = (random() % 0x4444);
950 se->fg.green = (random() % 0x4444);
951 se->fg.blue = (random() % 0x4444);
952 se->bg.red = (random() % 0x4444) + 0xCCCC;
953 se->bg.green = (random() % 0x4444) + 0xCCCC;
954 se->bg.blue = (random() % 0x4444) + 0xCCCC;
963 se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
964 se->bg.red*2 + se->bg.green*3 + se->bg.blue);
966 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
967 XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
968 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
969 XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
974 align_line (state *s, sentence *se, int line_start, int x, int right)
977 switch (se->alignment)
979 case LEFT: off = 0; break;
980 case CENTER: off = (right - x) / 2; break;
981 case RIGHT: off = (right - x); break;
982 default: abort(); break;
986 for (j = line_start; j < se->nwords; j++)
987 se->words[j]->target_x += off;
991 /* Fill the sentence with new words: in "page" mode, fills the page
992 with text; in "scroll" mode, just makes one long horizontal sentence.
993 The sentence might have *no* words in it, if no text is currently
997 populate_sentence (state *s, sentence *se)
1000 int left, right, top, x, y;
1005 int array_size = 100;
1007 se->move_chars_p = (s->mode == SCROLL ? False :
1008 (random() % 3) ? False : True);
1009 se->alignment = (random() % 3);
1015 for (i = 0; i < se->nwords; i++)
1016 free_word (s, se->words[i]);
1020 se->words = (word **) calloc (array_size, sizeof(*se->words));
1026 left = random() % (s->xgwa.width / 3);
1027 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1028 top = random() % (s->xgwa.height * 2 / 3);
1032 right = s->xgwa.width;
1033 top = random() % s->xgwa.height;
1045 char *txt = get_word_text (s);
1049 if (se->nwords == 0)
1050 return; /* If the stream is empty, bail. */
1052 break; /* If EOF after some words, end of sentence. */
1055 if (! se->font) /* Got a word: need a font now */
1058 if (y < se->font->ascent)
1059 y += se->font->ascent;
1060 space = XTextWidth (se->font, " ", 1);
1063 w = new_word (s, se, txt, !se->move_chars_p);
1065 /* If we have a few words, let punctuation terminate the sentence:
1066 stop gathering more words if the last word ends in a period, etc. */
1067 if (se->nwords >= 4)
1069 char c = w->text[strlen(w->text)-1];
1070 if (c == '.' || c == '?' || c == '!')
1074 /* If the sentence is kind of long already, terminate at commas, etc. */
1075 if (se->nwords >= 12)
1077 char c = w->text[strlen(w->text)-1];
1078 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1079 c == ')' || c == ']' || c == '}')
1083 if (se->nwords >= 25) /* ok that's just about enough out of you */
1086 if (s->mode == PAGE &&
1087 x + w->rbearing > right) /* wrap line */
1089 align_line (s, se, line_start, x, right);
1090 line_start = se->nwords;
1093 y += se->font->ascent;
1095 /* If we're close to the bottom of the screen, stop, and
1096 unread the current word. (But not if this is the first
1097 word, otherwise we might just get stuck on it.)
1099 if (se->nwords > 0 &&
1100 y + se->font->ascent > s->xgwa.height)
1108 w->target_x = x + w->lbearing;
1109 w->target_y = y - w->ascent;
1111 x += w->rbearing + space;
1114 if (se->nwords >= (array_size - 1))
1117 se->words = (word **) realloc (se->words,
1118 array_size * sizeof(*se->words));
1121 fprintf (stderr, "%s: out of memory (%d words)\n",
1122 progname, array_size);
1127 se->words[se->nwords++] = w;
1135 align_line (s, se, line_start, x, right);
1136 if (se->move_chars_p)
1137 split_words (s, se);
1138 scatter_sentence (s, se);
1139 shuffle_words (s, se);
1142 aim_sentence (s, se);
1152 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1153 for (i = 0; i < se->nwords; i++)
1154 fprintf (stderr, " %s", se->words[i]->text);
1155 fprintf (stderr, "\n");
1161 /* Render a single word object to the screen.
1164 draw_word (state *s, sentence *se, word *w)
1166 if (! w->pixmap) return;
1168 if (w->x + w->width < 0 ||
1169 w->y + w->height < 0 ||
1170 w->x > s->xgwa.width ||
1171 w->y > s->xgwa.height)
1174 XSetClipMask (s->dpy, se->fg_gc, w->mask);
1175 XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1176 XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1177 0, 0, w->width, w->height,
1183 /* If there is room for more sentences, add one.
1186 more_sentences (state *s)
1190 for (i = 0; i < s->nsentences; i++)
1192 sentence *se = s->sentences[i];
1195 se = new_sentence (s, s);
1196 populate_sentence (s, se);
1198 s->spawn_p = False, any = True;
1201 free_sentence (s, se);
1204 s->sentences[i] = se;
1206 s->latest_sentence = se->id;
1211 if (any) sort_sentences (s);
1215 /* Render all the words to the screen, and run the animation one step.
1218 draw_sentence (state *s, sentence *se)
1225 for (i = 0; i < se->nwords; i++)
1227 word *w = se->words[i];
1232 if (se->anim_state != PAUSE &&
1233 w->tick <= w->nticks)
1235 int dx = w->target_x - w->start_x;
1236 int dy = w->target_y - w->start_y;
1237 double r = sin (w->tick * M_PI / (2 * w->nticks));
1238 w->x = w->start_x + (dx * r);
1239 w->y = w->start_y + (dy * r);
1242 if (se->anim_state == OUT && s->mode == PAGE)
1243 w->tick++; /* go out faster */
1249 int dx = w->target_x - w->start_x;
1250 int dy = w->target_y - w->start_y;
1251 double r = (double) w->tick / w->nticks;
1252 w->x = w->start_x + (dx * r);
1253 w->y = w->start_y + (dy * r);
1255 moved = (w->tick <= w->nticks);
1257 /* Launch a new sentence when:
1258 - the front of this sentence is almost off the left edge;
1259 - the end of this sentence is almost on screen.
1262 if (se->anim_state != OUT &&
1264 se->id == s->latest_sentence)
1266 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1267 w->x + se->width < (s->xgwa.width * 2.1));
1268 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1270 if (new_p || rand_p)
1272 se->anim_state = OUT;
1276 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1278 se->words[0]->x + se->width,
1279 rand_p ? " randomly" : "");
1290 draw_word (s, se, w);
1293 if (moved && se->anim_state == PAUSE)
1298 switch (se->anim_state)
1301 se->anim_state = PAUSE;
1302 se->pause_tick = (se->nwords * 7 * s->linger);
1303 if (se->move_chars_p)
1304 se->pause_tick /= 5;
1305 scatter_sentence (s, se);
1306 shuffle_words (s, se);
1309 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1313 if (--se->pause_tick <= 0)
1315 se->anim_state = OUT;
1319 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1326 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1330 for (j = 0; j < s->nsentences; j++)
1331 if (s->sentences[j] == se)
1332 s->sentences[j] = 0;
1333 free_sentence (s, se);
1344 static unsigned long
1345 fontglide_draw_metrics (state *s)
1348 char *fn = (s->font_override ? s->font_override : "fixed");
1349 XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1350 XCharStruct c, overall;
1351 int dir, ascent, descent;
1354 unsigned long red = 0xFFFF0000; /* so shoot me */
1355 unsigned long green = 0xFF00FF00;
1356 unsigned long blue = 0xFF6666FF;
1359 txt[0] = s->debug_metrics_p;
1362 gc = XCreateGC (s->dpy, s->window, 0, 0);
1363 XSetFont (s->dpy, gc, font->fid);
1366 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1369 XTextExtents (font, txt, strlen(txt),
1370 &dir, &ascent, &descent, &overall);
1371 c = font->per_char[((unsigned char *) txt)[0] - font->min_char_or_byte2];
1373 XClearWindow (s->dpy, s->window);
1375 x = (s->xgwa.width - overall.width) / 2;
1376 y = (s->xgwa.height - (2 * (ascent + descent))) / 2;
1378 for (i = 0; i < 2; i++)
1380 XCharStruct cc = (i == 0 ? c : overall);
1382 int x2 = s->xgwa.width - 40;
1383 int x3 = s->xgwa.width;
1385 XSetForeground (s->dpy, gc, red);
1386 XDrawLine (s->dpy, s->window, gc, 0, y - ascent, x3, y - ascent);
1387 XDrawLine (s->dpy, s->window, gc, 0, y + descent, x3, y + descent);
1389 XSetForeground (s->dpy, gc, green);
1390 /* ascent, baseline, descent */
1391 XDrawLine (s->dpy, s->window, gc, x1, y - cc.ascent, x2, y - cc.ascent);
1392 XDrawLine (s->dpy, s->window, gc, x1, y, x2, y);
1393 XDrawLine (s->dpy, s->window, gc, x1, y + cc.descent, x2, y + cc.descent);
1396 XSetForeground (s->dpy, gc, blue);
1397 XDrawLine (s->dpy, s->window, gc,
1399 x, y + descent + 10);
1400 XDrawLine (s->dpy, s->window, gc,
1401 x + cc.width, y - ascent - 10,
1402 x + cc.width, y + descent + 10);
1404 /* lbearing, rbearing */
1405 XSetForeground (s->dpy, gc, green);
1406 XDrawLine (s->dpy, s->window, gc,
1407 x + cc.lbearing, y - ascent,
1408 x + cc.lbearing, y + descent);
1409 XDrawLine (s->dpy, s->window, gc,
1410 x + cc.rbearing, y - ascent,
1411 x + cc.rbearing, y + descent);
1413 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1414 XDrawString (s->dpy, s->window, gc, x, y, txt, strlen(txt));
1416 y += (ascent + descent) * 2;
1419 XFreeGC (s->dpy, gc);
1420 XFreeFont (s->dpy, font);
1421 return s->frame_delay;
1425 /* Render all the words to the screen, and run the animation one step.
1426 Clear screen first, swap buffers after.
1428 static unsigned long
1429 fontglide_draw (Display *dpy, Window window, void *closure)
1431 state *s = (state *) closure;
1434 if (s->debug_metrics_p)
1435 return fontglide_draw_metrics (closure);
1441 XFillRectangle (s->dpy, s->b, s->bg_gc,
1442 0, 0, s->xgwa.width, s->xgwa.height);
1444 for (i = 0; i < s->nsentences; i++)
1445 draw_sentence (s, s->sentences[i]);
1447 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1450 XdbeSwapInfo info[1];
1451 info[0].swap_window = s->window;
1452 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1453 XdbeSwapBuffers (s->dpy, info, 1);
1456 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1459 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1460 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1463 return s->frame_delay;
1470 (This bit mostly cribbed from phosphor.c)
1474 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1476 /* state *s = (state *) closure; */
1477 /* s->input_available_p = True; */
1482 launch_text_generator (state *s)
1484 char *oprogram = get_string_resource (s->dpy, "program", "Program");
1485 char *program = (char *) malloc (strlen (oprogram) + 10);
1486 strcpy (program, "( ");
1487 strcat (program, oprogram);
1488 strcat (program, " ) 2>&1");
1491 fprintf (stderr, "%s: forking: %s\n", progname, program);
1493 if ((s->pipe = popen (program, "r")))
1496 XtAppAddInput (XtDisplayToApplicationContext (s->dpy),
1498 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1499 subproc_cb, (XtPointer) s);
1509 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1511 state *s = (state *) closure;
1512 if (!s->timer_id) abort();
1514 launch_text_generator (s);
1518 /* whether there is data available to be read on the file descriptor
1521 input_available_p (int fd)
1523 struct timeval tv = { 0, };
1526 /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1529 memset (&fds, 0, sizeof(fds));
1532 return select (fd+1, &fds, NULL, NULL, &tv);
1536 /* When the subprocess has generated some output, this reads as much as it
1537 can into s->buf at s->buf_tail.
1540 drain_input (state *s)
1542 XtAppContext app = XtDisplayToApplicationContext (s->dpy);
1544 if (! s->pipe) return;
1546 /* if (! s->input_available_p) return; */
1547 /* s->input_available_p = False; */
1549 if (! input_available_p (fileno (s->pipe)))
1553 if (s->buf_tail < sizeof(s->buf) - 2)
1555 int target = sizeof(s->buf) - s->buf_tail - 2;
1557 n = read (fileno (s->pipe),
1558 (void *) (s->buf + s->buf_tail),
1563 s->buf[s->buf_tail] = 0;
1567 XtRemoveInput (s->pipe_id);
1572 /* If the process didn't print a terminating newline, add one. */
1573 if (s->buf_tail > 1 &&
1574 s->buf[s->buf_tail-1] != '\n')
1576 s->buf[s->buf_tail++] = '\n';
1577 s->buf[s->buf_tail] = 0;
1580 /* Then add one more, to make sure there's a sentence break at EOF.
1582 s->buf[s->buf_tail++] = '\n';
1583 s->buf[s->buf_tail] = 0;
1585 /* Set up a timer to re-launch the subproc in a bit. */
1586 if (s->timer_id) abort();
1587 s->timer_id = XtAppAddTimeOut (app, s->subproc_relaunch_delay,
1588 relaunch_generator_timer,
1595 /* Window setup and resource loading */
1598 fontglide_init (Display *dpy, Window window)
1601 state *s = (state *) calloc (1, sizeof(*s));
1604 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
1606 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1608 s->font_override = get_string_resource (dpy, "font", "Font");
1609 if (s->font_override && (!*s->font_override || *s->font_override == '('))
1610 s->font_override = 0;
1612 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
1613 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
1614 if (s->border_width < 0 || s->border_width > 20)
1615 s->border_width = 1;
1617 s->speed = get_float_resource (dpy, "speed", "Float");
1618 if (s->speed <= 0 || s->speed > 200)
1621 s->linger = get_float_resource (dpy, "linger", "Float");
1622 if (s->linger <= 0 || s->linger > 200)
1625 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
1626 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
1627 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
1630 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
1632 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
1636 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
1639 char *ss = get_string_resource (dpy, "mode", "Mode");
1640 if (!ss || !*ss || !strcasecmp (ss, "random"))
1641 s->mode = ((random() % 2) ? SCROLL : PAGE);
1642 else if (!strcasecmp (ss, "scroll"))
1644 else if (!strcasecmp (ss, "page"))
1649 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1656 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1657 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
1659 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1661 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1663 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1667 s->ba = XCreatePixmap (s->dpy, s->window,
1668 s->xgwa.width, s->xgwa.height,
1678 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
1679 "background", "Background");
1680 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1682 s->subproc_relaunch_delay = 2 * 1000;
1684 if (! s->debug_metrics_p)
1685 launch_text_generator (s);
1687 s->nsentences = 5; /* #### */
1688 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1696 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
1698 state *s = (state *) closure;
1700 if (! s->debug_metrics_p)
1702 else if (event->xany.type == ButtonPress)
1704 s->debug_metrics_p++;
1705 if (s->debug_metrics_p > 255)
1706 s->debug_metrics_p = ' ';
1707 else if (s->debug_metrics_p > 127 &&
1708 s->debug_metrics_p < 159)
1709 s->debug_metrics_p = 160;
1712 else if (event->xany.type == KeyPress)
1716 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1718 s->debug_metrics_p = (unsigned char) c;
1727 fontglide_reshape (Display *dpy, Window window, void *closure,
1728 unsigned int w, unsigned int h)
1730 state *s = (state *) closure;
1731 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1733 if (s->dbuf && (s->ba))
1735 XFreePixmap (s->dpy, s->ba);
1736 s->ba = XCreatePixmap (s->dpy, s->window,
1737 s->xgwa.width, s->xgwa.height,
1739 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
1740 s->xgwa.width, s->xgwa.height);
1746 fontglide_free (Display *dpy, Window window, void *closure)
1748 state *s = (state *) closure;
1751 XtRemoveInput (s->pipe_id);
1755 XtRemoveTimeOut (s->timer_id);
1757 /* #### there's more to free here */
1763 static const char *fontglide_defaults [] = {
1764 ".background: #000000",
1765 ".foreground: #DDDDDD",
1766 ".borderColor: #555555",
1768 "*program: xscreensaver-text",
1771 "*fontCharset: iso8859-1",
1772 "*fontBorderWidth: 2",
1777 "*debugMetrics: False",
1778 "*doubleBuffer: True",
1779 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1781 "*useDBEClear: True",
1782 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1786 static XrmOptionDescRec fontglide_options [] = {
1787 { "-mode", ".mode", XrmoptionSepArg, 0 },
1788 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
1789 { "-page", ".mode", XrmoptionNoArg, "page" },
1790 { "-random", ".mode", XrmoptionNoArg, "random" },
1791 { "-delay", ".delay", XrmoptionSepArg, 0 },
1792 { "-speed", ".speed", XrmoptionSepArg, 0 },
1793 { "-linger", ".linger", XrmoptionSepArg, 0 },
1794 { "-program", ".program", XrmoptionSepArg, 0 },
1795 { "-font", ".font", XrmoptionSepArg, 0 },
1796 { "-fn", ".font", XrmoptionSepArg, 0 },
1797 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
1798 { "-trails", ".trails", XrmoptionNoArg, "True" },
1799 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
1800 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
1801 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
1802 { "-debug", ".debug", XrmoptionNoArg, "True" },
1803 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
1808 XSCREENSAVER_MODULE ("FontGlide", fontglide)