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