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