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