1 /* xscreensaver, Copyright (c) 1992-2013 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
12 /* Make a little guy with a big nose and a hat wanter around the screen,
13 spewing out messages. Derived from xnlock by
14 Dan Heller <argv@danheller.com>.
17 #include "screenhack.h"
18 #include "xpm-pixmap.h"
19 #include "textclient.h"
25 #define font_height(font) (font->ascent + font->descent)
32 GC fg_gc, bg_gc, text_fg_gc, text_bg_gc;
36 unsigned long interval;
37 Pixmap left1, left2, right1, right2;
38 Pixmap left_front, right_front, front, down;
43 int state; /* indicates states: walking or getting passwd */
46 void (*next_fn) (struct state *);
48 int move_length, move_dir;
57 int x, y, width, height;
64 static void fill_words (struct state *);
65 static void walk (struct state *, int dir);
66 static void talk (struct state *, int erase);
67 static void talk_1 (struct state *);
68 static int think (struct state *);
69 static unsigned long look (struct state *);
73 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
74 # include "images/noseguy/nose-f1.xpm"
75 # include "images/noseguy/nose-f2.xpm"
76 # include "images/noseguy/nose-f3.xpm"
77 # include "images/noseguy/nose-f4.xpm"
78 # include "images/noseguy/nose-l1.xpm"
79 # include "images/noseguy/nose-l2.xpm"
80 # include "images/noseguy/nose-r1.xpm"
81 # include "images/noseguy/nose-r2.xpm"
83 # include "images/noseguy/nose-f1.xbm"
84 # include "images/noseguy/nose-f2.xbm"
85 # include "images/noseguy/nose-f3.xbm"
86 # include "images/noseguy/nose-f4.xbm"
87 # include "images/noseguy/nose-l1.xbm"
88 # include "images/noseguy/nose-l2.xbm"
89 # include "images/noseguy/nose-r1.xbm"
90 # include "images/noseguy/nose-r2.xbm"
94 init_images (struct state *st)
97 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
100 unsigned char *bits[8];
104 images[i++] = &st->left1;
105 images[i++] = &st->left2;
106 images[i++] = &st->right1;
107 images[i++] = &st->right2;
108 images[i++] = &st->left_front;
109 images[i++] = &st->right_front;
110 images[i++] = &st->front;
111 images[i] = &st->down;
113 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
116 bits[i++] = nose_l1_xpm;
117 bits[i++] = nose_l2_xpm;
118 bits[i++] = nose_r1_xpm;
119 bits[i++] = nose_r2_xpm;
120 bits[i++] = nose_f2_xpm;
121 bits[i++] = nose_f3_xpm;
122 bits[i++] = nose_f1_xpm;
123 bits[i] = nose_f4_xpm;
125 for (i = 0; i < sizeof (images) / sizeof(*images); i++)
127 Pixmap pixmap = xpm_data_to_pixmap (st->dpy, st->window, bits[i],
131 fprintf (stderr, "%s: Can't load nose images\n", progname);
138 bits[i++] = nose_l1_bits;
139 bits[i++] = nose_l2_bits;
140 bits[i++] = nose_r1_bits;
141 bits[i++] = nose_r2_bits;
142 bits[i++] = nose_f2_bits;
143 bits[i++] = nose_f3_bits;
144 bits[i++] = nose_f1_bits;
145 bits[i++] = nose_f4_bits;
147 for (i = 0; i < sizeof (images) / sizeof(*images); i++)
149 XCreatePixmapFromBitmapData(st->dpy, st->window,
150 (char *) bits[i], 64, 64, 1, 0, 1)))
152 fprintf (stderr, "%s: Can't load nose images\n", progname);
167 move (struct state *st)
169 if (!st->move_length)
171 register int tries = 0;
173 if ((random() & 1) && think(st))
175 talk(st, 0); /* sets timeout to itself */
178 if (!(random() % 3) && (st->interval = look(st)))
183 st->interval = 20 + random() % 100;
187 st->move_length = st->Width / 100 + random() % 90, tries = 8;
190 /* There maybe the case that we won't be able to exit from
191 this routine (especially when the geometry is too small)!!
193 Ensure that we can exit from this routine.
196 if (!tries && (st->move_length <= 1)) {
201 switch (random() % 8)
204 if (st->x - X_INCR * st->move_length >= 5)
208 if (st->x + X_INCR * st->move_length <= st->Width - 70)
209 st->move_dir = RIGHT;
212 if (st->y - (Y_INCR * st->move_length) >= 5)
213 st->move_dir = UP, st->interval = 40;
216 if (st->y + Y_INCR * st->move_length <= st->Height - 70)
217 st->move_dir = DOWN, st->interval = 20;
220 if (st->x - X_INCR * st->move_length >= 5 && st->y - (Y_INCR * st->move_length) >= 5)
221 st->move_dir = (LEFT | UP);
224 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
225 st->y - Y_INCR * st->move_length >= 5)
226 st->move_dir = (RIGHT | UP);
229 if (st->x - X_INCR * st->move_length >= 5 &&
230 st->y + Y_INCR * st->move_length <= st->Height - 70)
231 st->move_dir = (LEFT | DOWN);
234 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
235 st->y + Y_INCR * st->move_length <= st->Height - 70)
236 st->move_dir = (RIGHT | DOWN);
242 } while (!st->move_dir);
245 walk(st, st->move_dir);
251 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
252 XCopyArea (dpy,frame,window,gc,x,y,w,h,x2,y2)
254 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
255 XCopyPlane(dpy,frame,window,gc,x,y,w,h,x2,y2,1L)
259 walk (struct state *st, int dir)
261 register int incr = 0;
263 if (dir & (LEFT | RIGHT))
264 { /* left/right movement (mabye up/st->down too) */
265 st->walk_up = -st->walk_up; /* bouncing effect (even if hit a wall) */
269 st->walk_frame = (st->walk_up < 0) ? st->left1 : st->left2;
274 st->walk_frame = (st->walk_up < 0) ? st->right1 : st->right2;
276 if ((st->walk_lastdir == FRONT || st->walk_lastdir == DOWN) && dir & UP)
280 * workaround silly bug that leaves screen dust when guy is
281 * facing forward or st->down and moves up-left/right.
283 COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
285 /* note that maybe neither UP nor DOWN is set! */
286 if (dir & UP && st->y > Y_INCR)
288 else if (dir & DOWN && st->y < st->Height - 64)
291 /* Explicit up/st->down movement only (no left/right) */
293 COPY(st->dpy, st->front, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y -= Y_INCR);
294 else if (dir == DOWN)
295 COPY(st->dpy, st->down, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y += Y_INCR);
296 else if (dir == FRONT && st->walk_frame != st->front)
299 st->walk_up = -st->walk_up;
300 if (st->walk_lastdir & LEFT)
301 st->walk_frame = st->left_front;
302 else if (st->walk_lastdir & RIGHT)
303 st->walk_frame = st->right_front;
305 st->walk_frame = st->front;
306 COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
311 COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, --st->x, st->y + st->walk_up);
313 else if (dir & RIGHT)
316 COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, ++st->x, st->y + st->walk_up);
318 st->walk_lastdir = dir;
322 think (struct state *st)
335 talk (struct state *st, int force_erase)
343 char args[MAXLINES][LINELEN];
345 /* clear what we've written */
346 if (st->talking || force_erase)
350 XFillRectangle(st->dpy, st->window, st->bg_gc, st->s_rect.x - 5, st->s_rect.y - 5,
351 st->s_rect.width + 10, st->s_rect.height + 10);
357 /* might as well check the st->window for size changes now... */
358 XWindowAttributes xgwa;
359 XGetWindowAttributes (st->dpy, st->window, &xgwa);
360 st->Width = xgwa.width + 2;
361 st->Height = xgwa.height + 2;
366 /* If there is actually no words, just return */
375 for (p2 = p; *p2; p2++)
376 if (*p2 == '\t') *p2 = ' ';
378 if (!(p2 = strchr(p, '\n')) || !p2[1])
380 total = strlen (st->words);
381 strncpy (args[0], st->words, LINELEN);
382 args[0][LINELEN - 1] = 0;
383 width = XTextWidth(st->font, st->words, total);
387 /* p2 now points to the first '\n' */
388 for (height = 0; p; height++)
392 if ((w = XTextWidth(st->font, p, p2 - p)) > width)
394 total += p2 - p; /* total chars; count to determine reading
396 (void) strncpy(args[height], p, LINELEN);
397 args[height][LINELEN - 1] = 0;
398 if (height == MAXLINES - 1)
400 /* puts("Message too long!"); */
404 if (!(p2 = strchr(p, '\n')))
410 * Figure out the height and width in pixels (height, width) extend the
411 * new box by 15 pixels on the sides (30 total) top and bottom.
413 st->s_rect.width = width + 30;
414 st->s_rect.height = height * font_height(st->font) + 30;
415 if (st->x - st->s_rect.width - 10 < 5)
417 else if ((st->s_rect.x = st->x + 32 - (st->s_rect.width + 15) / 2)
418 + st->s_rect.width + 15 > st->Width - 5)
419 st->s_rect.x = st->Width - 15 - st->s_rect.width;
420 if (st->y - st->s_rect.height - 10 < 5)
421 st->s_rect.y = st->y + 64 + 5;
423 st->s_rect.y = st->y - 5 - st->s_rect.height;
425 XFillRectangle(st->dpy, st->window, st->text_bg_gc,
426 st->s_rect.x, st->s_rect.y, st->s_rect.width, st->s_rect.height);
428 /* make a box that's 5 pixels thick. Then add a thin box inside it */
429 XSetLineAttributes(st->dpy, st->text_fg_gc, 5, 0, 0, 0);
430 XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
431 st->s_rect.x, st->s_rect.y, st->s_rect.width - 1, st->s_rect.height - 1);
432 XSetLineAttributes(st->dpy, st->text_fg_gc, 0, 0, 0, 0);
433 XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
434 st->s_rect.x + 7, st->s_rect.y + 7, st->s_rect.width - 15, st->s_rect.height - 15);
437 st->Y = 15 + font_height(st->font);
439 /* now print each string in reverse order (start at bottom of box) */
440 for (Z = 0; Z < height; Z++)
442 int L = strlen(args[Z]);
443 /* If there are continuous new lines, L can be 0 */
444 if (L && (args[Z][L-1] == '\r' || args[Z][L-1] == '\n'))
446 XDrawString(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + st->X, st->s_rect.y + st->Y,
448 st->Y += font_height(st->font);
450 st->interval = (total / 15) * 1000;
451 if (st->interval < 2000) st->interval = 2000;
452 st->next_fn = talk_1;
458 talk_1 (struct state *st)
465 look (struct state *st)
469 COPY(st->dpy, (random() & 1) ? st->down : st->front, st->window, st->fg_gc,
470 0, 0, 64, 64, st->x, st->y);
477 COPY(st->dpy, (random() & 1) ? st->left_front : st->right_front,
478 st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
483 COPY(st->dpy, (random() & 1) ? st->left1 : st->right1, st->window, st->fg_gc,
484 0, 0, 64, 64, st->x, st->y);
490 fill_words (struct state *st)
492 char *p = st->words + strlen(st->words);
493 while (p < st->words + sizeof(st->words) - 1 &&
494 st->lines < MAXLINES)
496 int c = textclient_getc (st->tc);
509 static const char *noseguy_defaults [] = {
510 ".background: black",
511 ".foreground: #CCCCCC",
512 "*textForeground: black",
513 "*textBackground: #CCCCCC",
515 "*program: xscreensaver-text --cols 40 | head -n15",
517 ".font: -*-new century schoolbook-*-r-*-*-*-180-*-*-*-*-*-*",
521 static XrmOptionDescRec noseguy_options [] = {
522 { "-program", ".program", XrmoptionSepArg, 0 },
523 { "-font", ".font", XrmoptionSepArg, 0 },
524 { "-text-foreground", ".textForeground", XrmoptionSepArg, 0 },
525 { "-text-background", ".textBackground", XrmoptionSepArg, 0 },
531 noseguy_init (Display *d, Window w)
533 struct state *st = (struct state *) calloc (1, sizeof(*st));
534 unsigned long fg, bg, text_fg, text_bg;
535 XWindowAttributes xgwa;
543 fontname = get_string_resource (st->dpy, "font", "Font");
544 XGetWindowAttributes (st->dpy, st->window, &xgwa);
545 st->Width = xgwa.width + 2;
546 st->Height = xgwa.height + 2;
547 cmap = xgwa.colormap;
549 st->program = get_string_resource (st->dpy, "program", "Program");
550 st->tc = textclient_open (st->dpy);
553 if (!fontname || !*fontname)
554 fprintf (stderr, "%s: no font specified.\n", progname);
555 st->font = XLoadQueryFont(st->dpy, fontname);
557 fprintf (stderr, "%s: could not load font %s.\n", progname, fontname);
561 fg = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground");
562 bg = get_pixel_resource (st->dpy, cmap, "background", "Background");
563 text_fg = get_pixel_resource (st->dpy, cmap, "textForeground", "Foreground");
564 text_bg = get_pixel_resource (st->dpy, cmap, "textBackground", "Background");
565 /* notice when unspecified */
566 if (! get_string_resource (st->dpy, "textForeground", "Foreground"))
568 if (! get_string_resource (st->dpy, "textBackground", "Background"))
571 gcvalues.font = st->font->fid;
572 gcvalues.foreground = fg;
573 gcvalues.background = bg;
574 st->fg_gc = XCreateGC (st->dpy, st->window,
575 GCForeground|GCBackground|GCFont,
577 gcvalues.foreground = bg;
578 gcvalues.background = fg;
579 st->bg_gc = XCreateGC (st->dpy, st->window,
580 GCForeground|GCBackground|GCFont,
582 gcvalues.foreground = text_fg;
583 gcvalues.background = text_bg;
584 st->text_fg_gc = XCreateGC (st->dpy, st->window,
585 GCForeground|GCBackground|GCFont,
587 gcvalues.foreground = text_bg;
588 gcvalues.background = text_fg;
589 st->text_bg_gc = XCreateGC (st->dpy, st->window,
590 GCForeground|GCBackground|GCFont,
592 st->x = st->Width / 2;
593 st->y = st->Height / 2;
594 st->state = IS_MOVING;
601 noseguy_draw (Display *dpy, Window window, void *closure)
603 struct state *st = (struct state *) closure;
606 return (st->interval * 1000);
610 noseguy_reshape (Display *dpy, Window window, void *closure,
611 unsigned int w, unsigned int h)
613 struct state *st = (struct state *) closure;
619 noseguy_event (Display *dpy, Window window, void *closure, XEvent *event)
625 noseguy_free (Display *dpy, Window window, void *closure)
627 struct state *st = (struct state *) closure;
628 textclient_close (st->tc);
632 XSCREENSAVER_MODULE ("NoseGuy", noseguy)