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