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