ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[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             /* There maybe the case that we won't be able to exit from
200                this routine (especially when the geometry is too small)!!
201
202                Ensure that we can exit from this routine.
203              */
204 #if 1
205             if (!tries && (st->move_length <= 1)) {
206               st->move_length = 1;
207               break;
208             }
209 #endif
210             switch (random() % 8)
211             {
212             case 0:
213                 if (st->x - X_INCR * st->move_length >= 5)
214                     st->move_dir = LEFT;
215                 break;
216             case 1:
217                 if (st->x + X_INCR * st->move_length <= st->Width - 70)
218                     st->move_dir = RIGHT;
219                 break;
220             case 2:
221                 if (st->y - (Y_INCR * st->move_length) >= 5)
222                     st->move_dir = UP, st->interval = 40;
223                 break;
224             case 3:
225                 if (st->y + Y_INCR * st->move_length <= st->Height - 70)
226                     st->move_dir = DOWN, st->interval = 20;
227                 break;
228             case 4:
229                 if (st->x - X_INCR * st->move_length >= 5 && st->y - (Y_INCR * st->move_length) >= 5)
230                     st->move_dir = (LEFT | UP);
231                 break;
232             case 5:
233                 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
234                     st->y - Y_INCR * st->move_length >= 5)
235                     st->move_dir = (RIGHT | UP);
236                 break;
237             case 6:
238                 if (st->x - X_INCR * st->move_length >= 5 &&
239                     st->y + Y_INCR * st->move_length <= st->Height - 70)
240                     st->move_dir = (LEFT | DOWN);
241                 break;
242             case 7:
243                 if (st->x + X_INCR * st->move_length <= st->Width - 70 &&
244                     st->y + Y_INCR * st->move_length <= st->Height - 70)
245                     st->move_dir = (RIGHT | DOWN);
246                 break;
247             default:
248                 /* No Defaults */
249                 break;
250             }
251         } while (!st->move_dir);
252     }
253     if (st->move_dir)
254       walk(st, st->move_dir);
255     --st->move_length;
256     st->next_fn = move;
257 }
258
259 #ifdef HAVE_XPM
260 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
261   XCopyArea (dpy,frame,window,gc,x,y,w,h,x2,y2)
262 #else
263 # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \
264   XCopyPlane(dpy,frame,window,gc,x,y,w,h,x2,y2,1L)
265 #endif
266
267 static void
268 walk (struct state *st, int dir)
269 {
270     register int    incr = 0;
271
272     if (dir & (LEFT | RIGHT))
273     {                           /* left/right movement (mabye up/st->down too) */
274         st->walk_up = -st->walk_up;             /* bouncing effect (even if hit a wall) */
275         if (dir & LEFT)
276         {
277             incr = X_INCR;
278             st->walk_frame = (st->walk_up < 0) ? st->left1 : st->left2;
279         }
280         else
281         {
282             incr = -X_INCR;
283             st->walk_frame = (st->walk_up < 0) ? st->right1 : st->right2;
284         }
285         if ((st->walk_lastdir == FRONT || st->walk_lastdir == DOWN) && dir & UP)
286         {
287
288             /*
289              * workaround silly bug that leaves screen dust when guy is
290              * facing forward or st->down and moves up-left/right.
291              */
292             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
293         }
294         /* note that maybe neither UP nor DOWN is set! */
295         if (dir & UP && st->y > Y_INCR)
296             st->y -= Y_INCR;
297         else if (dir & DOWN && st->y < st->Height - 64)
298             st->y += Y_INCR;
299     }
300     /* Explicit up/st->down movement only (no left/right) */
301     else if (dir == UP)
302         COPY(st->dpy, st->front, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y -= Y_INCR);
303     else if (dir == DOWN)
304         COPY(st->dpy, st->down, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y += Y_INCR);
305     else if (dir == FRONT && st->walk_frame != st->front)
306     {
307         if (st->walk_up > 0)
308             st->walk_up = -st->walk_up;
309         if (st->walk_lastdir & LEFT)
310             st->walk_frame = st->left_front;
311         else if (st->walk_lastdir & RIGHT)
312             st->walk_frame = st->right_front;
313         else
314             st->walk_frame = st->front;
315         COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
316     }
317     if (dir & LEFT)
318         while (--incr >= 0)
319         {
320             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, --st->x, st->y + st->walk_up);
321         }
322     else if (dir & RIGHT)
323         while (++incr <= 0)
324         {
325             COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, 64, 64, ++st->x, st->y + st->walk_up);
326         }
327     st->walk_lastdir = dir;
328 }
329
330 static int
331 think (struct state *st)
332 {
333     if (random() & 1)
334         walk(st, FRONT);
335     if (random() & 1)
336     {
337       st->words = get_words(st);
338       return 1;
339     }
340     return 0;
341 }
342
343 #define MAXLINES 25
344
345 #undef BUFSIZ
346 #define BUFSIZ ((MAXLINES + 1) * 100)
347
348
349 static void
350 talk (struct state *st, int force_erase)
351 {
352     int             width = 0,
353                     height,
354                     Z,
355                     total = 0;
356     register char  *p,
357                    *p2;
358     char            buf[BUFSIZ],
359                     args[MAXLINES][256];
360
361     /* clear what we've written */
362     if (st->talking || force_erase)
363     {
364         if (!st->talking)
365             return;
366         XFillRectangle(st->dpy, st->window, st->bg_gc, st->s_rect.x - 5, st->s_rect.y - 5,
367                        st->s_rect.width + 10, st->s_rect.height + 10);
368         st->talking = 0;
369         if (!force_erase)
370           st->next_fn = move;
371         st->interval = 0;
372         {
373           /* might as well check the st->window for size changes now... */
374           XWindowAttributes xgwa;
375           XGetWindowAttributes (st->dpy, st->window, &xgwa);
376           st->Width = xgwa.width + 2;
377           st->Height = xgwa.height + 2;
378         }
379         return;
380     }
381     st->talking = 1;
382     walk(st, FRONT);
383     p = strcpy(buf, st->words);
384
385     for (p2 = p; *p2; p2++)
386       if (*p2 == '\t') *p2 = ' ';
387
388     if (!(p2 = strchr(p, '\n')) || !p2[1])
389       {
390         total = strlen (st->words);
391         strcpy (args[0], st->words);
392         width = XTextWidth(st->font, st->words, total);
393         height = 0;
394       }
395     else
396       /* p2 now points to the first '\n' */
397       for (height = 0; p; height++)
398         {
399           int             w;
400           *p2 = 0;
401           if ((w = XTextWidth(st->font, p, p2 - p)) > width)
402             width = w;
403           total += p2 - p;      /* total chars; count to determine reading
404                                  * time */
405           (void) strcpy(args[height], p);
406           if (height == MAXLINES - 1)
407             {
408               /* puts("Message too long!"); */
409               break;
410             }
411           p = p2 + 1;
412           if (!(p2 = strchr(p, '\n')))
413             break;
414         }
415     height++;
416
417     /*
418      * Figure out the height and width in pixels (height, width) extend the
419      * new box by 15 pixels on the sides (30 total) top and bottom.
420      */
421     st->s_rect.width = width + 30;
422     st->s_rect.height = height * font_height(st->font) + 30;
423     if (st->x - st->s_rect.width - 10 < 5)
424         st->s_rect.x = 5;
425     else if ((st->s_rect.x = st->x + 32 - (st->s_rect.width + 15) / 2)
426              + st->s_rect.width + 15 > st->Width - 5)
427         st->s_rect.x = st->Width - 15 - st->s_rect.width;
428     if (st->y - st->s_rect.height - 10 < 5)
429         st->s_rect.y = st->y + 64 + 5;
430     else
431         st->s_rect.y = st->y - 5 - st->s_rect.height;
432
433     XFillRectangle(st->dpy, st->window, st->text_bg_gc,
434          st->s_rect.x, st->s_rect.y, st->s_rect.width, st->s_rect.height);
435
436     /* make a box that's 5 pixels thick. Then add a thin box inside it */
437     XSetLineAttributes(st->dpy, st->text_fg_gc, 5, 0, 0, 0);
438     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
439                    st->s_rect.x, st->s_rect.y, st->s_rect.width - 1, st->s_rect.height - 1);
440     XSetLineAttributes(st->dpy, st->text_fg_gc, 0, 0, 0, 0);
441     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
442          st->s_rect.x + 7, st->s_rect.y + 7, st->s_rect.width - 15, st->s_rect.height - 15);
443
444     st->X = 15;
445     st->Y = 15 + font_height(st->font);
446
447     /* now print each string in reverse order (start at bottom of box) */
448     for (Z = 0; Z < height; Z++)
449     {
450         int L = strlen(args[Z]);
451         if (args[Z][L-1] == '\r' || args[Z][L-1] == '\n')
452           args[Z][--L] = 0;
453         XDrawString(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + st->X, st->s_rect.y + st->Y,
454                     args[Z], L);
455         st->Y += font_height(st->font);
456     }
457     st->interval = (total / 15) * 1000;
458     if (st->interval < 2000) st->interval = 2000;
459     st->next_fn = talk_1;
460 }
461
462 static void
463 talk_1 (struct state *st) 
464 {
465   talk(st, 0);
466 }
467
468
469 static unsigned long
470 look (struct state *st)
471 {
472     if (random() % 3)
473     {
474         COPY(st->dpy, (random() & 1) ? st->down : st->front, st->window, st->fg_gc,
475              0, 0, 64, 64, st->x, st->y);
476         return 1000L;
477     }
478     if (!(random() % 5))
479         return 0;
480     if (random() % 3)
481     {
482         COPY(st->dpy, (random() & 1) ? st->left_front : st->right_front,
483              st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
484         return 1000L;
485     }
486     if (!(random() % 5))
487         return 0;
488     COPY(st->dpy, (random() & 1) ? st->left1 : st->right1, st->window, st->fg_gc,
489          0, 0, 64, 64, st->x, st->y);
490     return 1000L;
491 }
492
493
494 static void
495 init_words (struct state *st)
496 {
497   st->program = get_string_resource (st->dpy, "program", "Program");
498
499   if (st->program)      /* get stderr on stdout, so it shows up on the window */
500     {
501       st->orig_program = st->program;
502       st->program = (char *) malloc (strlen (st->program) + 10);
503       strcpy (st->program, "( ");
504       strcat (st->program, st->orig_program);
505       strcat (st->program, " ) 2>&1");
506     }
507
508   st->words = get_words(st);    
509 }
510
511 static char *
512 get_words (struct state *st)
513 {
514     FILE           *pp;
515     register char  *p = st->word_buf;
516
517     st->word_buf[0] = '\0';
518
519         if ((pp = popen(st->program, "r")))
520         {
521             while (fgets(p, sizeof(st->word_buf) - strlen(st->word_buf), pp))
522             {
523                 if (strlen(st->word_buf) + 1 < sizeof(st->word_buf))
524                     p = st->word_buf + strlen(st->word_buf);
525                 else
526                     break;
527             }
528             (void) pclose(pp);
529             if (! st->word_buf[0])
530               sprintf (st->word_buf, "\"%s\" produced no output!", st->orig_program);
531             else if (!st->first_time &&
532                      (strstr (st->word_buf, ": not found") ||
533                       strstr (st->word_buf, ": Not found") ||
534                       strstr (st->word_buf, ": command not found") ||
535                       strstr (st->word_buf, ": Command not found")))
536               switch (random () % 20)
537                 {
538                 case 1: strcat (st->word_buf, "( Get with the st->program, bub. )\n");
539                   break;
540                 case 2: strcat (st->word_buf,
541                   "( I blow my nose at you, you silly person! ) \n"); break;
542                 case 3: strcat (st->word_buf,
543                   "\nThe resource you want to\nset is `noseguy.program'\n");
544                   break;
545                 case 4:
546                   strcat(st->word_buf,"\nHelp!!  Help!!\nAAAAAAGGGGHHH!!  \n\n"); break;
547                 case 5: strcpy (st->word_buf, "You have new mail.\n"); break;
548                 case 6:
549                   strcat(st->word_buf,"( Hello?  Are you paying attention? )\n");break;
550                 case 7:
551                   strcat (st->word_buf, "sh: what kind of fool do you take me for? \n");
552                   break;
553                 }
554             st->first_time = 0;
555             p = st->word_buf;
556         }
557         else
558         {
559             perror(st->program);
560         }
561
562     return p;
563 }
564
565
566 \f
567 static const char *noseguy_defaults [] = {
568   ".background:     black",
569   ".foreground:     #CCCCCC",
570   "*textForeground: black",
571   "*textBackground: #CCCCCC",
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)