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