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