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