http://www.jwz.org/xscreensaver/xscreensaver-5.10.tar.gz
[xscreensaver] / hacks / noseguy.c
1 /* xscreensaver, Copyright (c) 1992-2008 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
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>.
15  */
16
17 #include "screenhack.h"
18 #include "xpm-pixmap.h"
19 #include <stdio.h>
20
21 #ifdef HAVE_COCOA
22 # define HAVE_XPM
23 #endif
24
25 extern FILE *popen (const char *, const char *);
26 extern int pclose (FILE *);
27
28 #define font_height(font)               (font->ascent + font->descent)
29
30
31 struct state {
32   Display *dpy;
33   Window window;
34   int Width, Height;
35   GC fg_gc, bg_gc, text_fg_gc, text_bg_gc;
36   char *words;
37   int x, y;
38   XFontStruct *font;
39
40   unsigned long interval;
41   Pixmap left1, left2, right1, right2;
42   Pixmap left_front, right_front, front, down;
43
44   char *program, *orig_program;
45
46   int state;    /* indicates states: walking or getting passwd */
47   int first_time;
48
49   void (*next_fn) (struct state *);
50
51   int move_length, move_dir;
52
53   int      walk_lastdir;
54   int      walk_up;
55   Pixmap   walk_frame;
56
57   int X, Y, talking;
58
59   struct {
60     int x, y, width, height;
61   } s_rect;
62
63   char word_buf[BUFSIZ];
64 };
65
66 static char *get_words (struct state *);
67 static void walk (struct state *, int dir);
68 static void talk (struct state *, int erase);
69 static void talk_1 (struct state *);
70 static int think (struct state *);
71 static unsigned long look (struct state *); 
72
73 #define IS_MOVING  1
74
75 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
76 # include "images/noseguy/nose-f1.xpm"
77 # include "images/noseguy/nose-f2.xpm"
78 # include "images/noseguy/nose-f3.xpm"
79 # include "images/noseguy/nose-f4.xpm"
80 # include "images/noseguy/nose-l1.xpm"
81 # include "images/noseguy/nose-l2.xpm"
82 # include "images/noseguy/nose-r1.xpm"
83 # include "images/noseguy/nose-r2.xpm"
84 #else
85 # include "images/noseguy/nose-f1.xbm"
86 # include "images/noseguy/nose-f2.xbm"
87 # include "images/noseguy/nose-f3.xbm"
88 # include "images/noseguy/nose-f4.xbm"
89 # include "images/noseguy/nose-l1.xbm"
90 # include "images/noseguy/nose-l2.xbm"
91 # include "images/noseguy/nose-r1.xbm"
92 # include "images/noseguy/nose-r2.xbm"
93 #endif
94
95 static void
96 init_images (struct state *st)
97 {
98   Pixmap *images[8];
99 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
100   char **bits[8];
101 #else
102   unsigned char *bits[8];
103 #endif
104
105   int i = 0;
106   images[i++] = &st->left1;
107   images[i++] = &st->left2;
108   images[i++] = &st->right1;
109   images[i++] = &st->right2;
110   images[i++] = &st->left_front;
111   images[i++] = &st->right_front;
112   images[i++] = &st->front;
113   images[i++] = &st->down;
114
115 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
116
117   i = 0;
118   bits[i++] = nose_l1_xpm;
119   bits[i++] = nose_l2_xpm;
120   bits[i++] = nose_r1_xpm;
121   bits[i++] = nose_r2_xpm;
122   bits[i++] = nose_f2_xpm;
123   bits[i++] = nose_f3_xpm;
124   bits[i++] = nose_f1_xpm;
125   bits[i++] = nose_f4_xpm;
126
127   for (i = 0; i < sizeof (images) / sizeof(*images); i++)
128     {
129       Pixmap pixmap = xpm_data_to_pixmap (st->dpy, st->window, bits[i],
130                                           0, 0, 0);
131       if (!pixmap)
132         {
133           fprintf (stderr, "%s: Can't load nose images\n", progname);
134           exit (1);
135         }
136       *images[i] = pixmap;
137     }
138 #else
139   i = 0;
140   bits[i++] = nose_l1_bits;
141   bits[i++] = nose_l2_bits;
142   bits[i++] = nose_r1_bits;
143   bits[i++] = nose_r2_bits;
144   bits[i++] = nose_f2_bits;
145   bits[i++] = nose_f3_bits;
146   bits[i++] = nose_f1_bits;
147   bits[i++] = nose_f4_bits;
148
149   for (i = 0; i < sizeof (images) / sizeof(*images); i++)
150     if (!(*images[i] =
151           XCreatePixmapFromBitmapData(st->dpy, st->window,
152                                       (char *) bits[i], 64, 64, 1, 0, 1)))
153       {
154         fprintf (stderr, "%s: Can't load nose images\n", progname);
155         exit (1);
156       }
157 #endif
158 }
159
160 #define LEFT    001
161 #define RIGHT   002
162 #define DOWN    004
163 #define UP      010
164 #define FRONT   020
165 #define X_INCR 3
166 #define Y_INCR 2
167
168 static void
169 move (struct state *st)
170 {
171     if (!st->move_length)
172     {
173         register int    tries = 0;
174         st->move_dir = 0;
175         if ((random() & 1) && think(st))
176         {
177             talk(st, 0);                /* sets timeout to itself */
178             return;
179         }
180         if (!(random() % 3) && (st->interval = look(st)))
181         {
182             st->next_fn = move;
183             return;
184         }
185         st->interval = 20 + random() % 100;
186         do
187         {
188             if (!tries)
189                 st->move_length = st->Width / 100 + random() % 90, tries = 8;
190             else
191                 tries--;
192             /* There maybe the case that we won't be able to exit from
193                this routine (especially when the geometry is too small)!!
194
195                Ensure that we can exit from this routine.
196              */
197 #if 1
198             if (!tries && (st->move_length <= 1)) {
199               st->move_length = 1;
200               break;
201             }
202 #endif
203             switch (random() % 8)
204             {
205             case 0:
206                 if (st->x - X_INCR * st->move_length >= 5)
207                     st->move_dir = LEFT;
208                 break;
209             case 1:
210                 if (st->x + X_INCR * st->move_length <= st->Width - 70)
211                     st->move_dir = RIGHT;
212                 break;
213             case 2:
214                 if (st->y - (Y_INCR * st->move_length) >= 5)
215                     st->move_dir = UP, st->interval = 40;
216                 break;
217             case 3:
218                 if (st->y + Y_INCR * st->move_length <= st->Height - 70)
219                     st->move_dir = DOWN, st->interval = 20;
220                 break;
221             case 4:
222                 if (st->x - X_INCR * st->move_length >= 5 && st->y - (Y_INCR * st->move_length) >= 5)
223                     st->move_dir = (LEFT | UP);
224                 break;
225             case 5:
226                 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
227                     st->y - Y_INCR * st->move_length >= 5)
228                     st->move_dir = (RIGHT | UP);
229                 break;
230             case 6:
231                 if (st->x - X_INCR * st->move_length >= 5 &&
232                     st->y + Y_INCR * st->move_length <= st->Height - 70)
233                     st->move_dir = (LEFT | DOWN);
234                 break;
235             case 7:
236                 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
237                     st->y + Y_INCR * st->move_length <= st->Height - 70)
238                     st->move_dir = (RIGHT | DOWN);
239                 break;
240             default:
241                 /* No Defaults */
242                 break;
243             }
244         } while (!st->move_dir);
245     }
246     if (st->move_dir)
247       walk(st, st->move_dir);
248     --st->move_length;
249     st->next_fn = move;
250 }
251
252 #ifdef HAVE_XPM
253 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
254   XCopyArea (dpy,frame,window,gc,x,y,w,h,x2,y2)
255 #else
256 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
257   XCopyPlane(dpy,frame,window,gc,x,y,w,h,x2,y2,1L)
258 #endif
259
260 static void
261 walk (struct state *st, int dir)
262 {
263     register int    incr = 0;
264
265     if (dir & (LEFT | RIGHT))
266     {                           /* left/right movement (mabye up/st->down too) */
267         st->walk_up = -st->walk_up;             /* bouncing effect (even if hit a wall) */
268         if (dir & LEFT)
269         {
270             incr = X_INCR;
271             st->walk_frame = (st->walk_up < 0) ? st->left1 : st->left2;
272         }
273         else
274         {
275             incr = -X_INCR;
276             st->walk_frame = (st->walk_up < 0) ? st->right1 : st->right2;
277         }
278         if ((st->walk_lastdir == FRONT || st->walk_lastdir == DOWN) && dir & UP)
279         {
280
281             /*
282              * workaround silly bug that leaves screen dust when guy is
283              * facing forward or st->down and moves up-left/right.
284              */
285             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
286         }
287         /* note that maybe neither UP nor DOWN is set! */
288         if (dir & UP && st->y > Y_INCR)
289             st->y -= Y_INCR;
290         else if (dir & DOWN && st->y < st->Height - 64)
291             st->y += Y_INCR;
292     }
293     /* Explicit up/st->down movement only (no left/right) */
294     else if (dir == UP)
295         COPY(st->dpy, st->front, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y -= Y_INCR);
296     else if (dir == DOWN)
297         COPY(st->dpy, st->down, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y += Y_INCR);
298     else if (dir == FRONT && st->walk_frame != st->front)
299     {
300         if (st->walk_up > 0)
301             st->walk_up = -st->walk_up;
302         if (st->walk_lastdir & LEFT)
303             st->walk_frame = st->left_front;
304         else if (st->walk_lastdir & RIGHT)
305             st->walk_frame = st->right_front;
306         else
307             st->walk_frame = st->front;
308         COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
309     }
310     if (dir & LEFT)
311         while (--incr >= 0)
312         {
313             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, --st->x, st->y + st->walk_up);
314         }
315     else if (dir & RIGHT)
316         while (++incr <= 0)
317         {
318             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, ++st->x, st->y + st->walk_up);
319         }
320     st->walk_lastdir = dir;
321 }
322
323 static int
324 think (struct state *st)
325 {
326     if (random() & 1)
327         walk(st, FRONT);
328     if (random() & 1)
329     {
330       st->words = get_words(st);
331       return 1;
332     }
333     return 0;
334 }
335
336 #define MAXLINES 25
337
338 #undef BUFSIZ
339 #define BUFSIZ ((MAXLINES + 1) * 100)
340
341
342 static void
343 talk (struct state *st, int force_erase)
344 {
345     int             width = 0,
346                     height,
347                     Z,
348                     total = 0;
349     register char  *p,
350                    *p2;
351     char            buf[BUFSIZ],
352                     args[MAXLINES][256];
353
354     /* clear what we've written */
355     if (st->talking || force_erase)
356     {
357         if (!st->talking)
358             return;
359         XFillRectangle(st->dpy, st->window, st->bg_gc, st->s_rect.x - 5, st->s_rect.y - 5,
360                        st->s_rect.width + 10, st->s_rect.height + 10);
361         st->talking = 0;
362         if (!force_erase)
363           st->next_fn = move;
364         st->interval = 0;
365         {
366           /* might as well check the st->window for size changes now... */
367           XWindowAttributes xgwa;
368           XGetWindowAttributes (st->dpy, st->window, &xgwa);
369           st->Width = xgwa.width + 2;
370           st->Height = xgwa.height + 2;
371         }
372         return;
373     }
374     st->talking = 1;
375     walk(st, FRONT);
376     p = strcpy(buf, st->words);
377
378     for (p2 = p; *p2; p2++)
379       if (*p2 == '\t') *p2 = ' ';
380
381     if (!(p2 = strchr(p, '\n')) || !p2[1])
382       {
383         total = strlen (st->words);
384         strcpy (args[0], st->words);
385         width = XTextWidth(st->font, st->words, total);
386         height = 0;
387       }
388     else
389       /* p2 now points to the first '\n' */
390       for (height = 0; p; height++)
391         {
392           int             w;
393           *p2 = 0;
394           if ((w = XTextWidth(st->font, p, p2 - p)) > width)
395             width = w;
396           total += p2 - p;      /* total chars; count to determine reading
397                                  * time */
398           (void) strcpy(args[height], p);
399           if (height == MAXLINES - 1)
400             {
401               /* puts("Message too long!"); */
402               break;
403             }
404           p = p2 + 1;
405           if (!(p2 = strchr(p, '\n')))
406             break;
407         }
408     height++;
409
410     /*
411      * Figure out the height and width in pixels (height, width) extend the
412      * new box by 15 pixels on the sides (30 total) top and bottom.
413      */
414     st->s_rect.width = width + 30;
415     st->s_rect.height = height * font_height(st->font) + 30;
416     if (st->x - st->s_rect.width - 10 < 5)
417         st->s_rect.x = 5;
418     else if ((st->s_rect.x = st->x + 32 - (st->s_rect.width + 15) / 2)
419              + st->s_rect.width + 15 > st->Width - 5)
420         st->s_rect.x = st->Width - 15 - st->s_rect.width;
421     if (st->y - st->s_rect.height - 10 < 5)
422         st->s_rect.y = st->y + 64 + 5;
423     else
424         st->s_rect.y = st->y - 5 - st->s_rect.height;
425
426     XFillRectangle(st->dpy, st->window, st->text_bg_gc,
427          st->s_rect.x, st->s_rect.y, st->s_rect.width, st->s_rect.height);
428
429     /* make a box that's 5 pixels thick. Then add a thin box inside it */
430     XSetLineAttributes(st->dpy, st->text_fg_gc, 5, 0, 0, 0);
431     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
432                    st->s_rect.x, st->s_rect.y, st->s_rect.width - 1, st->s_rect.height - 1);
433     XSetLineAttributes(st->dpy, st->text_fg_gc, 0, 0, 0, 0);
434     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
435          st->s_rect.x + 7, st->s_rect.y + 7, st->s_rect.width - 15, st->s_rect.height - 15);
436
437     st->X = 15;
438     st->Y = 15 + font_height(st->font);
439
440     /* now print each string in reverse order (start at bottom of box) */
441     for (Z = 0; Z < height; Z++)
442     {
443         int L = strlen(args[Z]);
444         if (args[Z][L-1] == '\r' || args[Z][L-1] == '\n')
445           args[Z][--L] = 0;
446         XDrawString(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + st->X, st->s_rect.y + st->Y,
447                     args[Z], L);
448         st->Y += font_height(st->font);
449     }
450     st->interval = (total / 15) * 1000;
451     if (st->interval < 2000) st->interval = 2000;
452     st->next_fn = talk_1;
453 }
454
455 static void
456 talk_1 (struct state *st) 
457 {
458   talk(st, 0);
459 }
460
461
462 static unsigned long
463 look (struct state *st)
464 {
465     if (random() % 3)
466     {
467         COPY(st->dpy, (random() & 1) ? st->down : st->front, st->window, st->fg_gc,
468              0, 0, 64, 64, st->x, st->y);
469         return 1000L;
470     }
471     if (!(random() % 5))
472         return 0;
473     if (random() % 3)
474     {
475         COPY(st->dpy, (random() & 1) ? st->left_front : st->right_front,
476              st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
477         return 1000L;
478     }
479     if (!(random() % 5))
480         return 0;
481     COPY(st->dpy, (random() & 1) ? st->left1 : st->right1, st->window, st->fg_gc,
482          0, 0, 64, 64, st->x, st->y);
483     return 1000L;
484 }
485
486
487 static void
488 init_words (struct state *st)
489 {
490   st->program = get_string_resource (st->dpy, "program", "Program");
491
492   if (st->program)      /* get stderr on stdout, so it shows up on the window */
493     {
494       st->orig_program = st->program;
495       st->program = (char *) malloc (strlen (st->program) + 10);
496       strcpy (st->program, "( ");
497       strcat (st->program, st->orig_program);
498       strcat (st->program, " ) 2>&1");
499     }
500
501   st->words = get_words(st);    
502 }
503
504 static char *
505 get_words (struct state *st)
506 {
507     FILE           *pp;
508     register char  *p = st->word_buf;
509
510     st->word_buf[0] = '\0';
511
512         if ((pp = popen(st->program, "r")))
513         {
514             while (fgets(p, sizeof(st->word_buf) - strlen(st->word_buf), pp))
515             {
516                 if (strlen(st->word_buf) + 1 < sizeof(st->word_buf))
517                     p = st->word_buf + strlen(st->word_buf);
518                 else
519                     break;
520             }
521             (void) pclose(pp);
522             if (! st->word_buf[0])
523               sprintf (st->word_buf, "\"%s\" produced no output!", st->orig_program);
524             else if (!st->first_time &&
525                      (strstr (st->word_buf, ": not found") ||
526                       strstr (st->word_buf, ": Not found") ||
527                       strstr (st->word_buf, ": command not found") ||
528                       strstr (st->word_buf, ": Command not found")))
529               switch (random () % 20)
530                 {
531                 case 1: strcat (st->word_buf, "( Get with the st->program, bub. )\n");
532                   break;
533                 case 2: strcat (st->word_buf,
534                   "( I blow my nose at you, you silly person! ) \n"); break;
535                 case 3: strcat (st->word_buf,
536                   "\nThe resource you want to\nset is `noseguy.program'\n");
537                   break;
538                 case 4:
539                   strcat(st->word_buf,"\nHelp!!  Help!!\nAAAAAAGGGGHHH!!  \n\n"); break;
540                 case 5: strcpy (st->word_buf, "You have new mail.\n"); break;
541                 case 6:
542                   strcat(st->word_buf,"( Hello?  Are you paying attention? )\n");break;
543                 case 7:
544                   strcat (st->word_buf, "sh: what kind of fool do you take me for? \n");
545                   break;
546                 }
547             st->first_time = 0;
548             p = st->word_buf;
549         }
550         else
551         {
552             perror(st->program);
553         }
554
555     return p;
556 }
557
558
559 \f
560 static const char *noseguy_defaults [] = {
561   ".background:     black",
562   ".foreground:     #CCCCCC",
563   "*textForeground: black",
564   "*textBackground: #CCCCCC",
565   "*fpsSolid:   true",
566   "*program:     xscreensaver-text --cols 40 | head -n15",
567   ".font:        -*-new century schoolbook-*-r-*-*-*-180-*-*-*-*-*-*",
568   0
569 };
570
571 static XrmOptionDescRec noseguy_options [] = {
572   { "-program",         ".program",             XrmoptionSepArg, 0 },
573   { "-font",            ".font",                XrmoptionSepArg, 0 },
574   { "-text-foreground", ".textForeground",      XrmoptionSepArg, 0 },
575   { "-text-background", ".textBackground",      XrmoptionSepArg, 0 },
576   { 0, 0, 0, 0 }
577 };
578
579
580 static void *
581 noseguy_init (Display *d, Window w)
582 {
583   struct state *st = (struct state *) calloc (1, sizeof(*st));
584   unsigned long fg, bg, text_fg, text_bg;
585   XWindowAttributes xgwa;
586   Colormap cmap;
587   char *fontname;
588   XGCValues gcvalues;
589   st->dpy = d;
590   st->window = w;
591   st->first_time = 1;
592
593   fontname = get_string_resource (st->dpy, "font", "Font");
594   XGetWindowAttributes (st->dpy, st->window, &xgwa);
595   st->Width = xgwa.width + 2;
596   st->Height = xgwa.height + 2;
597   cmap = xgwa.colormap;
598
599   init_words(st);
600   init_images(st);
601
602   if (!fontname || !*fontname)
603     fprintf (stderr, "%s: no font specified.\n", progname);
604   st->font = XLoadQueryFont(st->dpy, fontname);
605   if (!st->font)
606     fprintf (stderr, "%s: could not load font %s.\n", progname, fontname);
607
608   fg = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground");
609   bg = get_pixel_resource (st->dpy, cmap, "background", "Background");
610   text_fg = get_pixel_resource (st->dpy, cmap, "textForeground", "Foreground");
611   text_bg = get_pixel_resource (st->dpy, cmap, "textBackground", "Background");
612   /* notice when unspecified */
613   if (! get_string_resource (st->dpy, "textForeground", "Foreground"))
614     text_fg = bg;
615   if (! get_string_resource (st->dpy, "textBackground", "Background"))
616     text_bg = fg;
617
618   gcvalues.font = st->font->fid;
619   gcvalues.foreground = fg;
620   gcvalues.background = bg;
621   st->fg_gc = XCreateGC (st->dpy, st->window,
622                          GCForeground|GCBackground|GCFont,
623                      &gcvalues);
624   gcvalues.foreground = bg;
625   gcvalues.background = fg;
626   st->bg_gc = XCreateGC (st->dpy, st->window,
627                          GCForeground|GCBackground|GCFont,
628                      &gcvalues);
629   gcvalues.foreground = text_fg;
630   gcvalues.background = text_bg;
631   st->text_fg_gc = XCreateGC (st->dpy, st->window,
632                               GCForeground|GCBackground|GCFont,
633                           &gcvalues);
634   gcvalues.foreground = text_bg;
635   gcvalues.background = text_fg;
636   st->text_bg_gc = XCreateGC (st->dpy, st->window, 
637                               GCForeground|GCBackground|GCFont,
638                           &gcvalues);
639   st->x = st->Width / 2;
640   st->y = st->Height / 2;
641   st->state = IS_MOVING;
642   st->next_fn = move;
643   st->walk_up = 1;
644   return st;
645 }
646      
647 static unsigned long
648 noseguy_draw (Display *dpy, Window window, void *closure)
649 {
650   struct state *st = (struct state *) closure;
651   st->next_fn(st);
652   return (st->interval * 1000);
653 }
654
655 static void
656 noseguy_reshape (Display *dpy, Window window, void *closure, 
657                  unsigned int w, unsigned int h)
658 {
659 }
660
661 static Bool
662 noseguy_event (Display *dpy, Window window, void *closure, XEvent *event)
663 {
664   return False;
665 }
666
667 static void
668 noseguy_free (Display *dpy, Window window, void *closure)
669 {
670 }
671
672 XSCREENSAVER_MODULE ("NoseGuy", noseguy)