From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[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 #define LINELEN 256
333
334 static void
335 talk (struct state *st, int force_erase)
336 {
337     int             width = 0,
338                     height,
339                     Z,
340                     total = 0;
341     register char  *p,
342                    *p2;
343     char            args[MAXLINES][LINELEN];
344
345     /* clear what we've written */
346     if (st->talking || force_erase)
347     {
348         if (!st->talking)
349             return;
350         XFillRectangle(st->dpy, st->window, st->bg_gc, st->s_rect.x - 5, st->s_rect.y - 5,
351                        st->s_rect.width + 10, st->s_rect.height + 10);
352         st->talking = 0;
353         if (!force_erase)
354           st->next_fn = move;
355         st->interval = 0;
356         {
357           /* might as well check the st->window for size changes now... */
358           XWindowAttributes xgwa;
359           XGetWindowAttributes (st->dpy, st->window, &xgwa);
360           st->Width = xgwa.width + 2;
361           st->Height = xgwa.height + 2;
362         }
363         return;
364     }
365     p = st->words;
366     /* If there is actually no words, just return */
367     if (!*p)
368     {
369       st->talking = 0;
370       return;
371     }
372     st->talking = 1;
373     walk(st, FRONT);
374
375     for (p2 = p; *p2; p2++)
376       if (*p2 == '\t') *p2 = ' ';
377
378     if (!(p2 = strchr(p, '\n')) || !p2[1])
379       {
380         total = strlen (st->words);
381         strncpy (args[0], st->words, LINELEN);
382         args[0][LINELEN - 1] = 0;
383         width = XTextWidth(st->font, st->words, total);
384         height = 0;
385       }
386     else
387       /* p2 now points to the first '\n' */
388       for (height = 0; p; height++)
389         {
390           int             w;
391           *p2 = 0;
392           if ((w = XTextWidth(st->font, p, p2 - p)) > width)
393             width = w;
394           total += p2 - p;      /* total chars; count to determine reading
395                                  * time */
396           (void) strncpy(args[height], p, LINELEN);
397           args[height][LINELEN - 1] = 0;
398           if (height == MAXLINES - 1)
399             {
400               /* puts("Message too long!"); */
401               break;
402             }
403           p = p2 + 1;
404           if (!(p2 = strchr(p, '\n')))
405             break;
406         }
407     height++;
408
409     /*
410      * Figure out the height and width in pixels (height, width) extend the
411      * new box by 15 pixels on the sides (30 total) top and bottom.
412      */
413     st->s_rect.width = width + 30;
414     st->s_rect.height = height * font_height(st->font) + 30;
415     if (st->x - st->s_rect.width - 10 < 5)
416         st->s_rect.x = 5;
417     else if ((st->s_rect.x = st->x + 32 - (st->s_rect.width + 15) / 2)
418              + st->s_rect.width + 15 > st->Width - 5)
419         st->s_rect.x = st->Width - 15 - st->s_rect.width;
420     if (st->y - st->s_rect.height - 10 < 5)
421         st->s_rect.y = st->y + 64 + 5;
422     else
423         st->s_rect.y = st->y - 5 - st->s_rect.height;
424
425     XFillRectangle(st->dpy, st->window, st->text_bg_gc,
426          st->s_rect.x, st->s_rect.y, st->s_rect.width, st->s_rect.height);
427
428     /* make a box that's 5 pixels thick. Then add a thin box inside it */
429     XSetLineAttributes(st->dpy, st->text_fg_gc, 5, 0, 0, 0);
430     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
431                    st->s_rect.x, st->s_rect.y, st->s_rect.width - 1, st->s_rect.height - 1);
432     XSetLineAttributes(st->dpy, st->text_fg_gc, 0, 0, 0, 0);
433     XDrawRectangle(st->dpy, st->window, st->text_fg_gc,
434          st->s_rect.x + 7, st->s_rect.y + 7, st->s_rect.width - 15, st->s_rect.height - 15);
435
436     st->X = 15;
437     st->Y = 15 + font_height(st->font);
438
439     /* now print each string in reverse order (start at bottom of box) */
440     for (Z = 0; Z < height; Z++)
441     {
442         int L = strlen(args[Z]);
443         /* If there are continuous new lines, L can be 0 */
444         if (L && (args[Z][L-1] == '\r' || args[Z][L-1] == '\n'))
445           args[Z][--L] = 0;
446         XDrawString(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + st->X, st->s_rect.y + st->Y,
447                     args[Z], L);
448         st->Y += font_height(st->font);
449     }
450     st->interval = (total / 15) * 1000;
451     if (st->interval < 2000) st->interval = 2000;
452     st->next_fn = talk_1;
453     *st->words = 0;
454     st->lines = 0;
455 }
456
457 static void
458 talk_1 (struct state *st) 
459 {
460   talk(st, 0);
461 }
462
463
464 static unsigned long
465 look (struct state *st)
466 {
467     if (random() % 3)
468     {
469         COPY(st->dpy, (random() & 1) ? st->down : st->front, st->window, st->fg_gc,
470              0, 0, 64, 64, st->x, st->y);
471         return 1000L;
472     }
473     if (!(random() % 5))
474         return 0;
475     if (random() % 3)
476     {
477         COPY(st->dpy, (random() & 1) ? st->left_front : st->right_front,
478              st->window, st->fg_gc, 0, 0, 64, 64, st->x, st->y);
479         return 1000L;
480     }
481     if (!(random() % 5))
482         return 0;
483     COPY(st->dpy, (random() & 1) ? st->left1 : st->right1, st->window, st->fg_gc,
484          0, 0, 64, 64, st->x, st->y);
485     return 1000L;
486 }
487
488
489 static void
490 fill_words (struct state *st)
491 {
492   char *p = st->words + strlen(st->words);
493   while (p < st->words + sizeof(st->words) - 1 &&
494          st->lines < MAXLINES)
495     {
496       int c = textclient_getc (st->tc);
497       if (c == '\n')
498         st->lines++;
499       if (c > 0)
500         *p++ = (char) c;
501       else
502         break;
503     }
504   *p = 0;
505 }
506
507
508 \f
509 static const char *noseguy_defaults [] = {
510   ".background:     black",
511   ".foreground:     #CCCCCC",
512   "*textForeground: black",
513   "*textBackground: #CCCCCC",
514   "*fpsSolid:    true",
515   "*program:     xscreensaver-text --cols 40 | head -n15",
516   "*usePty:      False",
517   ".font:        -*-new century schoolbook-*-r-*-*-*-180-*-*-*-*-*-*",
518   0
519 };
520
521 static XrmOptionDescRec noseguy_options [] = {
522   { "-program",         ".program",             XrmoptionSepArg, 0 },
523   { "-font",            ".font",                XrmoptionSepArg, 0 },
524   { "-text-foreground", ".textForeground",      XrmoptionSepArg, 0 },
525   { "-text-background", ".textBackground",      XrmoptionSepArg, 0 },
526   { 0, 0, 0, 0 }
527 };
528
529
530 static void *
531 noseguy_init (Display *d, Window w)
532 {
533   struct state *st = (struct state *) calloc (1, sizeof(*st));
534   unsigned long fg, bg, text_fg, text_bg;
535   XWindowAttributes xgwa;
536   Colormap cmap;
537   char *fontname;
538   XGCValues gcvalues;
539   st->dpy = d;
540   st->window = w;
541   st->first_time = 1;
542
543   fontname = get_string_resource (st->dpy, "font", "Font");
544   XGetWindowAttributes (st->dpy, st->window, &xgwa);
545   st->Width = xgwa.width + 2;
546   st->Height = xgwa.height + 2;
547   cmap = xgwa.colormap;
548
549   st->program = get_string_resource (st->dpy, "program", "Program");
550   st->tc = textclient_open (st->dpy);
551   init_images(st);
552
553   if (!fontname || !*fontname)
554     fprintf (stderr, "%s: no font specified.\n", progname);
555   st->font = XLoadQueryFont(st->dpy, fontname);
556   if (!st->font) {
557     fprintf (stderr, "%s: could not load font %s.\n", progname, fontname);
558     exit(1);
559   }
560
561   fg = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground");
562   bg = get_pixel_resource (st->dpy, cmap, "background", "Background");
563   text_fg = get_pixel_resource (st->dpy, cmap, "textForeground", "Foreground");
564   text_bg = get_pixel_resource (st->dpy, cmap, "textBackground", "Background");
565   /* notice when unspecified */
566   if (! get_string_resource (st->dpy, "textForeground", "Foreground"))
567     text_fg = bg;
568   if (! get_string_resource (st->dpy, "textBackground", "Background"))
569     text_bg = fg;
570
571   gcvalues.font = st->font->fid;
572   gcvalues.foreground = fg;
573   gcvalues.background = bg;
574   st->fg_gc = XCreateGC (st->dpy, st->window,
575                          GCForeground|GCBackground|GCFont,
576                      &gcvalues);
577   gcvalues.foreground = bg;
578   gcvalues.background = fg;
579   st->bg_gc = XCreateGC (st->dpy, st->window,
580                          GCForeground|GCBackground|GCFont,
581                      &gcvalues);
582   gcvalues.foreground = text_fg;
583   gcvalues.background = text_bg;
584   st->text_fg_gc = XCreateGC (st->dpy, st->window,
585                               GCForeground|GCBackground|GCFont,
586                           &gcvalues);
587   gcvalues.foreground = text_bg;
588   gcvalues.background = text_fg;
589   st->text_bg_gc = XCreateGC (st->dpy, st->window, 
590                               GCForeground|GCBackground|GCFont,
591                           &gcvalues);
592   st->x = st->Width / 2;
593   st->y = st->Height / 2;
594   st->state = IS_MOVING;
595   st->next_fn = move;
596   st->walk_up = 1;
597   return st;
598 }
599      
600 static unsigned long
601 noseguy_draw (Display *dpy, Window window, void *closure)
602 {
603   struct state *st = (struct state *) closure;
604   fill_words(st);
605   st->next_fn(st);
606   return (st->interval * 1000);
607 }
608
609 static void
610 noseguy_reshape (Display *dpy, Window window, void *closure, 
611                  unsigned int w, unsigned int h)
612 {
613   struct state *st = (struct state *) closure;
614   st->Width = w + 2;
615   st->Height = h + 2;
616 }
617
618 static Bool
619 noseguy_event (Display *dpy, Window window, void *closure, XEvent *event)
620 {
621   return False;
622 }
623
624 static void
625 noseguy_free (Display *dpy, Window window, void *closure)
626 {
627   struct state *st = (struct state *) closure;
628   textclient_close (st->tc);
629   free (st);
630 }
631
632 XSCREENSAVER_MODULE ("NoseGuy", noseguy)