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