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