1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* marquee --- types a text-file or a text ribbon */
3
4 #if 0
5 static const char sccsid[] = "@(#)marquee.c 5.00 2000/11/01 xlockmore";
6
7 #endif
8
9 /*-
10 * Copyright (c) 1995 by Tobias Gloth and David Bagley
11 *
12 * Permission to use, copy, modify, and distribute this software and its
13 * documentation for any purpose and without fee is hereby granted,
14 * provided that the above copyright notice appear in all copies and that
15 * both that copyright notice and this permission notice appear in
16 * supporting documentation.
17 *
18 * This file is provided AS IS with no warranties of any kind. The author
19 * shall have no liability with respect to the infringement of copyrights,
20 * trade secrets or any patents by this file or any part thereof. In no
21 * event will the author be liable for any lost revenue or profits or
22 * other special, indirect and consequential damages.
23 *
24 * Revision History:
25 * 01-Nov-2000: Allocation checks
26 * 10-May-1997: Compatible with xscreensaver
27 * 03-Nov-1995: Many changes (hopefully good ones) by David Bagley
28 * 01-Oct-1995: Written by Tobias Gloth
29 */
30
31 #ifdef STANDALONE
32 #define MODE_marquee
33 #define DEFAULTS "*delay: 100000 \n" \
34 "*ncolors: 64 \n" \
35 "*font: fixed\n" \
36 "*text:\n" \
37 "*filename: \n" \
38 "*fortunefile: \n" \
39 "*program: \n" \
40
41 # define free_marquee 0
42 # define reshape_marquee 0
43 # define marquee_handle_event 0
44 #define SMOOTH_COLORS
45 #include "xlockmore.h" /* in xscreensaver distribution */
46 #else /* STANDALONE */
47 #include "xlock.h" /* in xlockmore distribution */
48 #include "iostuff.h"
49 #endif /* STANDALONE */
50
51 #ifdef MODE_marquee
52
53 ENTRYPOINT ModeSpecOpt marquee_opts =
54 {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
55
56 #ifdef USE_MODULES
57 ModStruct marquee_description =
58 {"marquee", "init_marquee", "draw_marquee", "release_marquee",
59 "init_marquee", "init_marquee", (char *) NULL, &marquee_opts,
60 100000, 1, 1, 1, 64, 1.0, "",
61 "Shows messages", 0, NULL};
62
63 #endif
64
65 #ifdef USE_MB
font_height(XFontStruct * f)66 static int font_height(XFontStruct *f)
67 {
68 XRectangle mbRect;
69
70 XmbTextExtents(fontset, "Aj", 1, NULL, &mbRect);
71 return mbRect.height;
72 }
73 #else
74 #define font_height(f) ((f==None)?8:f->ascent + f->descent)
75 #endif
76
77 extern XFontStruct *getFont(Display * display);
78 extern char *getWords(int screen, int screens);
79 extern int isRibbon(void);
80
81 typedef struct {
82 int ascent;
83 int height;
84 int win_height;
85 int win_width;
86 int x;
87 int y;
88 int t;
89 int startx;
90 int nonblanks;
91 int color;
92 int time;
93 GC gc;
94 char *words;
95 char modwords[256];
96 } marqueestruct;
97
98 static marqueestruct *marquees = (marqueestruct *) NULL;
99
100 static XFontStruct *mode_font = None;
101 static int char_width[256];
102
103 static int
font_width(XFontStruct * font,char ch)104 font_width(XFontStruct * font, char ch)
105 {
106 int dummy;
107 XCharStruct xcs;
108
109 (void) XTextExtents(font, &ch, 1, &dummy, &dummy, &dummy, &xcs);
110 return xcs.width;
111 }
112
113 /* returns 1 if c is a printable char, the test should work for most 8 * bit
114 charsets (e.g. latin1), if we would use isprint, we would * depend of
115 locale settings that are probably incorrect. */
116
117 static int
is_valid_char(char c)118 is_valid_char(char c)
119 {
120 return (unsigned char) c >= ' ';
121 }
122
123 static int
is_char_back_char(char * s)124 is_char_back_char(char *s)
125 {
126 return is_valid_char(*s) && *(s + 1) == '\b' &&
127 *(s + 2) && is_valid_char(*(s + 2));
128 }
129
130 static int
char_back_char_width(char * s)131 char_back_char_width(char *s)
132 {
133 int w1 = char_width[(int) (unsigned char) *s];
134 int w2 = char_width[(int) (unsigned char) *(s + 2)];
135
136 return w2 < w1 ? w1 : w2;
137 }
138
139
140 /*-
141 * fix strings of the form abc^H^H^H123 to a^H1b^H2c^H3, since we only
142 * handle backspace for char, back, char correctly. We do this without
143 * duplicating the string, but I'm not sure if there are conditions
144 * when the string is actually const. (when it is def_message, no ^Hs
145 * are present, I'm not sure about resource strings)
146 */
147
148 static char *
fixup_back(char * s)149 fixup_back(char *s)
150 {
151 char *p, *p1, *p2;
152 char tmp[1000];
153 char *t;
154 char *w;
155
156 /* first of all, check if we have to do anything */
157
158 if (!*s)
159 return s;
160
161 for (p = s + 1; *p; p++)
162 if (*p == '\b' && *(p + 1) == '\b')
163 break;
164 if (!*p)
165 return s;
166
167 /* now search for runs of the form char*n, back*n, char. */
168
169 for (p = s; *p; p++) {
170 for (p1 = p; *p1 && is_valid_char(*p1); p1++);
171 if (*p1 == '\b') {
172 for (p2 = p1; *p2 && *p2 == '\b'; p2++);
173
174 /* do we have `enough' chars for the backspaces? */
175
176 if (p2 - p1 > 1 && p1 - p >= p2 - p1) {
177 if (p1 - p > p2 - p1) {
178 p = p1 - (p2 - p1);
179 }
180 /* the situation is as follows:
181 p points to the first char,
182 p1 to the first backspace (end first char run),
183 p2 to the first char in the 2nd run
184
185 Question: how to do that without tmp storage?
186 */
187 (void) strncpy(tmp, p, p1 - p);
188 t = tmp;
189 w = p;
190 while (t - tmp < p1 - p && *p2) {
191 *w++ = *t++;
192 *w++ = '\b';
193 *w++ = *p2++;
194 }
195 p = p2;
196 } else {
197 p = p2;
198 }
199 } else {
200 /* we hit some other control char, just continue at this
201 position */
202 p = p1;
203 }
204 }
205 return s;
206 }
207
208 static int
charBytes(unsigned char cb)209 charBytes(unsigned char cb)
210 {
211 #ifdef USE_MB
212 int mb = 1;
213
214 /*if ((cb & 0x80) && !(cb & 0x40))
215 an extended byte*/
216 if ((cb & 0x80) && (cb & 0x40))
217 mb = 2;
218 else
219 return mb;
220 if (cb & 0x20)
221 mb = 3;
222 else
223 return mb;
224 if (cb & 0x10)
225 mb = 4;
226 else
227 return mb;
228 if (cb & 0x08)
229 mb = 5;
230 else
231 return mb;
232 if (cb & 0x04)
233 return 6;
234 else
235 return mb;
236 #else
237 return 1;
238 #endif
239 }
240
241 static int
charWidth(char * string)242 charWidth(char * string)
243 {
244 #ifdef USE_MB
245 XRectangle rect;
246 int mb = charBytes(*string);
247
248 XmbTextExtents(fontset, string, mb, NULL, &rect);
249 return rect.width;
250 #else
251 return char_width[(int) (unsigned char) *string];
252 #endif
253 }
254
255 static int
text_font_width(char * string)256 text_font_width(char *string)
257 {
258 int n = 0, x = 0, t = 0;
259
260 /* The following does not handle a tab or other weird junk */
261 while (*string != '\0') {
262 if (x > n)
263 n = x;
264 switch (*string) {
265 case '\v':
266 case '\f':
267 case '\n':
268 x = 0;
269 t = 0;
270 break;
271 case '\b':
272 /* we handle only char, ^H, char smartly, if
273 * we have something different, we use the
274 * (probably wrong) assumption that we have
275 * a monospaced font. */
276 if (t) {
277 t--;
278 x -= char_width[(int) (' ')];
279 }
280 break;
281 case '\t':
282 x += char_width[(int) (' ')] * (8 - (t % 8));
283 t = ((t + 8) / 8) * 8;
284 break;
285 case '\r':
286 break;
287 default:
288 t++;
289 /* handle char, ^H, char */
290 if (is_char_back_char(string)) {
291 x += char_back_char_width(string);
292 string += 2;
293 } else {
294 x += charWidth(string);
295 }
296 }
297 string++;
298 }
299 return n;
300 }
301
302 static int
text_height(char * string)303 text_height(char *string)
304 {
305 int n = 0;
306
307 while (*string != '\0') {
308 if ((*string == '\n') || (*string == '\f') || (*string == '\v'))
309 n++;
310 string++;
311 }
312 return n;
313 }
314
315 static int
add_blanks(marqueestruct * mp)316 add_blanks(marqueestruct * mp)
317 {
318 int width;
319
320 if (mp->t < 251) {
321 mp->modwords[mp->t] = ' ';
322 mp->t++;
323 mp->modwords[mp->t] = ' ';
324 mp->t++;
325 mp->modwords[mp->t] = '\0';
326 (void) strcat(mp->modwords, " ");
327 }
328 mp->x -= 2 * char_width[(int) (' ')];
329 width = charWidth(mp->modwords);
330 if (mp->x <= -width) {
331 mp->x += width;
332 (void) memcpy(mp->modwords, &(mp->modwords[1]), mp->nonblanks);
333 mp->nonblanks--;
334 }
335 return (mp->nonblanks < 0);
336 }
337
338 static void
add_letter(marqueestruct * mp,char * letter)339 add_letter(marqueestruct * mp, char * letter)
340 {
341 int width;
342 int b = charBytes(letter[0]);
343 int i;
344
345 if (mp->t < 253 - b) {
346 for (i = 0; i < b; i++) {
347 mp->modwords[mp->t] = letter[i];
348 mp->t++;
349 }
350 mp->modwords[mp->t] = '\0';
351 (void) strcat(mp->modwords, " ");
352 }
353 mp->x -= charWidth(letter);
354 width = charWidth(mp->modwords);
355 if (mp->x <= -width) {
356 mp->x += width;
357 (void) memcpy(mp->modwords, &(mp->modwords[1]), mp->t);
358 for (i = 0; i < b; i++) {
359 mp->modwords[mp->t] = ' ';
360 mp->t--;
361 }
362 } else
363 mp->nonblanks = mp->t;
364 }
365
366 static void
free_marquee_screen(Display * display,marqueestruct * mp)367 free_marquee_screen(Display * display, marqueestruct * mp)
368 {
369 if (mp == NULL) {
370 return;
371 }
372 if (mp->gc != None)
373 XFreeGC(display, mp->gc);
374 mp = NULL;
375 }
376
377 ENTRYPOINT void
init_marquee(ModeInfo * mi)378 init_marquee(ModeInfo * mi)
379 {
380 Display *display = MI_DISPLAY(mi);
381 marqueestruct *mp;
382 XGCValues gcv;
383 int i;
384
385 MI_INIT(mi, marquees);
386 mp = &marquees[MI_SCREEN(mi)];
387
388 mp->win_width = MI_WIDTH(mi);
389 mp->win_height = MI_HEIGHT(mi);
390 if (MI_NPIXELS(mi) > 2)
391 mp->color = NRAND(MI_NPIXELS(mi));
392 mp->time = 0;
393 mp->t = 0;
394 mp->nonblanks = 0;
395 mp->x = 0;
396
397 MI_CLEARWINDOW(mi);
398
399 if (mode_font == None)
400 mode_font = getFont(display);
401 if (mp->gc == NULL && mode_font != None) {
402 gcv.font = mode_font->fid;
403 gcv.graphics_exposures = False;
404 gcv.foreground = MI_WHITE_PIXEL(mi);
405 gcv.background = MI_BLACK_PIXEL(mi);
406 if ((mp->gc = XCreateGC(display, MI_WINDOW(mi),
407 GCForeground | GCBackground | GCGraphicsExposures | GCFont,
408 &gcv)) == None) {
409 return;
410 }
411 mp->ascent = mode_font->ascent;
412 mp->height = font_height(mode_font);
413 for (i = 0; i < 256; i++)
414 if ((i >= (int) mode_font->min_char_or_byte2) &&
415 (i <= (int) mode_font->max_char_or_byte2))
416 char_width[i] = font_width(mode_font, (char) i);
417 else
418 char_width[i] = font_width(mode_font, (char) mode_font->default_char);
419 } else if (mode_font == None) {
420 for (i = 0; i < 256; i++)
421 char_width[i] = 8;
422 }
423 mp->words = fixup_back(getWords(MI_SCREEN(mi), MI_NUM_SCREENS(mi)));
424 mp->y = 0;
425
426 if (isRibbon()) {
427 mp->x = mp->win_width;
428 if (mp->win_height > font_height(mode_font))
429 mp->y += NRAND(mp->win_height - font_height(mode_font));
430 else if (mp->win_height < font_height(mode_font))
431 mp->y -= NRAND(font_height(mode_font) - mp->win_height);
432 } else {
433 int text_ht = text_height(mp->words);
434 int text_font_wid = text_font_width(mp->words);
435
436 if (mp->win_height > text_ht * font_height(mode_font))
437 mp->y = NRAND(mp->win_height - text_ht * font_height(mode_font));
438 if (mp->y < 0)
439 mp->y = 0;
440 mp->x = 0;
441 if (mp->win_width > text_font_wid)
442 mp->x += NRAND(mp->win_width - text_font_wid);
443 /* else if (mp->win_width < text_font_wid)
444 mp->x -= NRAND(text_font_wid - mp->win_width); */
445 mp->startx = mp->x;
446 }
447 }
448
449 ENTRYPOINT void
draw_marquee(ModeInfo * mi)450 draw_marquee(ModeInfo * mi)
451 {
452 Display *display = MI_DISPLAY(mi);
453 Window window = MI_WINDOW(mi);
454 char *space = (char *) " ";
455 unsigned char *ch;
456 marqueestruct *mp = &marquees[MI_SCREEN(mi)];
457
458 if (marquees == NULL)
459 return;
460 mp = &marquees[MI_SCREEN(mi)];
461 if (mp->gc == None && mode_font != None)
462 return;
463
464 MI_IS_DRAWN(mi) = True;
465 ch = (unsigned char*) mp->words;
466 if (isRibbon()) {
467 ch = (unsigned char*) mp->words;
468 switch (*ch) {
469 case '\0':
470 if (add_blanks(mp)) {
471 init_marquee(mi);
472 return;
473 }
474 break;
475 case '\b':
476 case '\r':
477 case '\n':
478 case '\t':
479 case '\v':
480 case '\f':
481 add_letter(mp, (char *) " ");
482 mp->words++;
483 break;
484 default:
485 add_letter(mp, (char *) ch);
486 mp->words++;
487 }
488 if (MI_NPIXELS(mi) > 2) {
489 XSetForeground(display, mp->gc, MI_PIXEL(mi, mp->color));
490 if (++mp->color == MI_NPIXELS(mi))
491 mp->color = 0;
492 } else
493 XSetForeground(display, mp->gc, MI_WHITE_PIXEL(mi));
494 (void) XDrawImageString(display, MI_WINDOW(mi), mp->gc,
495 mp->x, mp->y + mp->ascent, mp->modwords, mp->t + 2);
496 } else {
497 switch (*ch) {
498 case '\0':
499 if (++mp->time > 16)
500 init_marquee(mi);
501 return;
502 case '\b':
503 if (mp->t) {
504 /* see note in text_font_width */
505 mp->t--;
506 mp->x -= char_width[(int) (' ')];
507 }
508 break;
509 case '\v':
510 case '\f':
511 case '\n':
512 mp->x = mp->startx;
513 mp->t = 0;
514 mp->y += mp->height;
515 if (mp->y + mp->height > mp->win_height) {
516 XCopyArea(display, window, window, mp->gc,
517 0, mp->height, mp->win_width, mp->y - mp->height, 0, 0);
518 XSetForeground(display, mp->gc, MI_BLACK_PIXEL(mi));
519 mp->y -= mp->height;
520 XFillRectangle(display, window, mp->gc,
521 0, mp->y, mp->win_width, mp->height);
522 }
523 break;
524 case '\t':
525 (void) XDrawString(display, window, mp->gc, mp->x, mp->y + mp->ascent,
526 space, 8 - (mp->t % 8));
527 mp->x += char_width[(int) (' ')] * (8 - (mp->t % 8));
528 mp->t = ((mp->t + 8) / 8) * 8;
529 break;
530 case '\r':
531 break;
532 default:
533 if (MI_NPIXELS(mi) > 2) {
534 XSetForeground(display, mp->gc, MI_PIXEL(mi, mp->color));
535 if (++mp->color == MI_NPIXELS(mi))
536 mp->color = 0;
537 } else
538 XSetForeground(display, mp->gc, MI_WHITE_PIXEL(mi));
539 if (is_char_back_char((char *) ch)) {
540 int xmid = mp->x + (char_back_char_width((char *) ch) + 1) / 2;
541
542 (void) XDrawString(display, window, mp->gc,
543 xmid - char_width[(int) (const char) *ch] / 2,
544 mp->y + mp->ascent, (char *) ch, 1);
545 (void) XDrawString(display, window, mp->gc,
546 xmid - char_width[(int) (const char) *(ch + 2)] / 2,
547 mp->y + mp->ascent, (char *) ch + 2, 1);
548 mp->x += char_back_char_width((char *) ch);
549 mp->words += 2;
550 } else {
551 int mb = charBytes(*ch);
552
553 (void) XDrawString(display, window, mp->gc,
554 mp->x, mp->y + mp->ascent, (char *) ch, mb);
555 mp->x += charWidth((char *) ch);
556 }
557 mp->t++;
558 }
559 mp->words += charBytes(*ch);
560 }
561 }
562
563 ENTRYPOINT void
release_marquee(ModeInfo * mi)564 release_marquee(ModeInfo * mi)
565 {
566 if (marquees != NULL) {
567 int screen;
568
569 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
570 marqueestruct *mp = &marquees[screen];
571 free_marquee_screen(MI_DISPLAY(mi), mp);
572 }
573 free(marquees);
574 marquees = (marqueestruct *) NULL;
575 }
576 if (mode_font != None) {
577 XFreeFont(MI_DISPLAY(mi), mode_font);
578 mode_font = None;
579 }
580 }
581
582 XSCREENSAVER_MODULE ("Marquee", marquee)
583
584 #endif /* MODE_marquee */
585