/* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ /* Make a little guy with a big nose and a hat wanter around the screen, spewing out messages. Derived from xnlock by Dan Heller . */ #include "screenhack.h" #include "ximage-loader.h" #include "textclient.h" #include "xft.h" #define font_height(font) (font->ascent + font->descent) typedef struct { Pixmap p, m; } PM; struct state { Display *dpy; Window window; int Width, Height; GC fg_gc, bg_gc, text_fg_gc, text_bg_gc; int x, y; XftFont *xftfont; XftColor xftcolor; XftDraw *xftdraw; unsigned long interval; PM left1, left2, right1, right2, left_front, right_front, front, down; int pix_w, pix_h; text_data *tc; int state; /* indicates states: walking or getting passwd */ int first_time; void (*next_fn) (struct state *); int move_length, move_dir; int walk_lastdir; int walk_up; PM *walk_frame; int X, Y, talking; struct { int x, y, width, height; } s_rect; char words[10240]; int lines; }; static void fill_words (struct state *); static void walk (struct state *, int dir); static void talk (struct state *, int erase); static void talk_1 (struct state *); static int think (struct state *); static unsigned long look (struct state *); #define IS_MOVING 1 #include "images/gen/nose-f1_png.h" #include "images/gen/nose-f2_png.h" #include "images/gen/nose-f3_png.h" #include "images/gen/nose-f4_png.h" #include "images/gen/nose-l1_png.h" #include "images/gen/nose-l2_png.h" #include "images/gen/nose-r1_png.h" #include "images/gen/nose-r2_png.h" static Pixmap double_pixmap (Display *dpy, Visual *visual, int depth, Pixmap pixmap, int pix_w, int pix_h) { int x, y; Pixmap p2 = XCreatePixmap(dpy, pixmap, pix_w*2, pix_h*2, depth); XImage *i1 = XGetImage (dpy, pixmap, 0, 0, pix_w, pix_h, ~0L, (depth == 1 ? XYPixmap : ZPixmap)); XImage *i2 = XCreateImage (dpy, visual, depth, (depth == 1 ? XYPixmap : ZPixmap), 0, 0, pix_w*2, pix_h*2, 8, 0); XGCValues gcv; GC gc = XCreateGC (dpy, p2, 0, &gcv); i2->data = (char *) calloc(i2->height, i2->bytes_per_line); for (y = 0; y < pix_h; y++) for (x = 0; x < pix_w; x++) { unsigned long p = XGetPixel(i1, x, y); XPutPixel(i2, x*2, y*2, p); XPutPixel(i2, x*2+1, y*2, p); XPutPixel(i2, x*2, y*2+1, p); XPutPixel(i2, x*2+1, y*2+1, p); } free(i1->data); i1->data = 0; XDestroyImage(i1); XPutImage(dpy, p2, gc, i2, 0, 0, 0, 0, i2->width, i2->height); XFreeGC (dpy, gc); free(i2->data); i2->data = 0; XDestroyImage(i2); XFreePixmap(dpy, pixmap); return p2; } static void init_images (struct state *st) { PM *images[8]; struct { const unsigned char *png; unsigned long size; } bits[8]; XWindowAttributes xgwa; XGetWindowAttributes (st->dpy, st->window, &xgwa); int i = 0; images[i++] = &st->left1; images[i++] = &st->left2; images[i++] = &st->right1; images[i++] = &st->right2; images[i++] = &st->left_front; images[i++] = &st->right_front; images[i++] = &st->front; images[i] = &st->down; #define DEF(N,S) bits[i].png = N; bits[i].size = S; i++ i = 0; DEF(nose_l1_png, sizeof(nose_l1_png)); DEF(nose_l2_png, sizeof(nose_l2_png)); DEF(nose_r1_png, sizeof(nose_r1_png)); DEF(nose_r2_png, sizeof(nose_r2_png)); DEF(nose_f2_png, sizeof(nose_f2_png)); DEF(nose_f3_png, sizeof(nose_f3_png)); DEF(nose_f1_png, sizeof(nose_f1_png)); DEF(nose_f4_png, sizeof(nose_f4_png)); for (i = 0; i < sizeof (images) / sizeof(*images); i++) { Pixmap mask = 0; Pixmap pixmap = image_data_to_pixmap (st->dpy, st->window, bits[i].png, bits[i].size, &st->pix_w, &st->pix_h, &mask); if (!pixmap) { fprintf (stderr, "%s: Can't load nose images\n", progname); exit (1); } images[i]->p = pixmap; images[i]->m = mask; } if (xgwa.width > 2560) /* Retina display */ { for (i = 0; i < sizeof (images) / sizeof(*images); i++) { images[i]->p = double_pixmap (st->dpy, xgwa.visual, xgwa.depth, images[i]->p, st->pix_w, st->pix_h); images[i]->m = double_pixmap (st->dpy, xgwa.visual, 1, images[i]->m, st->pix_w, st->pix_h); } st->pix_w *= 2; st->pix_h *= 2; } } #define LEFT 001 #define RIGHT 002 #define DOWN 004 #define UP 010 #define FRONT 020 #define X_INCR 3 #define Y_INCR 2 static void move (struct state *st) { if (!st->move_length) { register int tries = 0; st->move_dir = 0; if ((random() & 1) && think(st)) { talk(st, 0); /* sets timeout to itself */ return; } if (!(random() % 3) && (st->interval = look(st))) { st->next_fn = move; return; } st->interval = 20 + random() % 100; do { if (!tries) st->move_length = st->Width / 100 + random() % 90, tries = 8; else tries--; /* There maybe the case that we won't be able to exit from this routine (especially when the geometry is too small)!! Ensure that we can exit from this routine. */ #if 1 if (!tries && (st->move_length <= 1)) { st->move_length = 1; break; } #endif switch (random() % 8) { case 0: if (st->x - X_INCR * st->move_length >= 5) st->move_dir = LEFT; break; case 1: if (st->x + X_INCR * st->move_length <= st->Width - 70) st->move_dir = RIGHT; break; case 2: if (st->y - (Y_INCR * st->move_length) >= 5) st->move_dir = UP, st->interval = 40; break; case 3: if (st->y + Y_INCR * st->move_length <= st->Height - 70) st->move_dir = DOWN, st->interval = 20; break; case 4: if (st->x - X_INCR * st->move_length >= 5 && st->y - (Y_INCR * st->move_length) >= 5) st->move_dir = (LEFT | UP); break; case 5: if (st->x + X_INCR * st->move_length <= st->Width - 70 && st->y - Y_INCR * st->move_length >= 5) st->move_dir = (RIGHT | UP); break; case 6: if (st->x - X_INCR * st->move_length >= 5 && st->y + Y_INCR * st->move_length <= st->Height - 70) st->move_dir = (LEFT | DOWN); break; case 7: if (st->x + X_INCR * st->move_length <= st->Width - 70 && st->y + Y_INCR * st->move_length <= st->Height - 70) st->move_dir = (RIGHT | DOWN); break; default: /* No Defaults */ break; } } while (!st->move_dir); } if (st->move_dir) walk(st, st->move_dir); --st->move_length; st->next_fn = move; } # define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) do {\ int X2 = (x2), Y2 = (y2); \ PM *FRAME = (frame); \ XFillRectangle(dpy,window,st->bg_gc,X2,Y2,w,h); \ XSetClipMask (dpy,gc,FRAME->m); \ XSetClipOrigin (dpy,gc,X2,Y2); \ XCopyArea (dpy,FRAME->p,window,gc,x,y,w,h,X2,Y2); \ XSetClipMask (dpy,gc,None); \ } while(0) static void walk (struct state *st, int dir) { register int incr = 0; if (dir & (LEFT | RIGHT)) { /* left/right movement (mabye up/st->down too) */ st->walk_up = -st->walk_up; /* bouncing effect (even if hit a wall) */ if (dir & LEFT) { incr = X_INCR; st->walk_frame = (st->walk_up < 0) ? &st->left1 : &st->left2; } else { incr = -X_INCR; st->walk_frame = (st->walk_up < 0) ? &st->right1 : &st->right2; } if ((st->walk_lastdir == FRONT || st->walk_lastdir == DOWN) && dir & UP) { /* * workaround silly bug that leaves screen dust when guy is * facing forward or st->down and moves up-left/right. */ COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y); } /* note that maybe neither UP nor DOWN is set! */ if (dir & UP && st->y > Y_INCR) st->y -= Y_INCR; else if (dir & DOWN && st->y < st->Height - st->pix_h) st->y += Y_INCR; } /* Explicit up/st->down movement only (no left/right) */ else if (dir == UP) COPY(st->dpy, &st->front, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y -= Y_INCR); else if (dir == DOWN) COPY(st->dpy, &st->down, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y += Y_INCR); else if (dir == FRONT && st->walk_frame != &st->front) { if (st->walk_up > 0) st->walk_up = -st->walk_up; if (st->walk_lastdir & LEFT) st->walk_frame = &st->left_front; else if (st->walk_lastdir & RIGHT) st->walk_frame = &st->right_front; else st->walk_frame = &st->front; COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y); } if (dir & LEFT) while (--incr >= 0) { COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, --st->x, st->y + st->walk_up); } else if (dir & RIGHT) while (++incr <= 0) { COPY(st->dpy, st->walk_frame, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, ++st->x, st->y + st->walk_up); } st->walk_lastdir = dir; } static int think (struct state *st) { if (random() & 1) walk(st, FRONT); if (random() & 1) return 1; return 0; } #define MAXLINES 10 #define LINELEN 256 static void talk (struct state *st, int force_erase) { int width = 0, height, Z, total = 0; register char *p, *p2; char args[MAXLINES][LINELEN]; /* clear what we've written */ if (st->talking || force_erase) { if (!st->talking) return; XFillRectangle(st->dpy, st->window, st->bg_gc, st->s_rect.x - 5, st->s_rect.y - 5, st->s_rect.width + 10, st->s_rect.height + 10); st->talking = 0; if (!force_erase) st->next_fn = move; st->interval = 0; { /* might as well check the st->window for size changes now... */ XWindowAttributes xgwa; XGetWindowAttributes (st->dpy, st->window, &xgwa); st->Width = xgwa.width + 2; st->Height = xgwa.height + 2; } return; } p = st->words; /* If there is actually no words, just return */ if (!*p) { st->talking = 0; return; } st->talking = 1; walk(st, FRONT); for (p2 = p; *p2; p2++) if (*p2 == '\t') *p2 = ' '; if (!(p2 = strchr(p, '\n')) || !p2[1]) { XGlyphInfo extents; total = strlen (st->words); strncpy (args[0], st->words, LINELEN); args[0][LINELEN - 1] = 0; XftTextExtentsUtf8 (st->dpy, st->xftfont, (FcChar8 *) st->words, total, &extents); width = extents.xOff; height = 0; } else /* p2 now points to the first '\n' */ for (height = 0; p; height++) { int w; XGlyphInfo extents; *p2 = 0; XftTextExtentsUtf8 (st->dpy, st->xftfont, (FcChar8 *) p, p2 - p, &extents); w = extents.xOff; if (w > width) width = w; total += p2 - p; /* total chars; count to determine reading * time */ (void) strncpy(args[height], p, LINELEN); args[height][LINELEN - 1] = 0; if (height == MAXLINES - 1) { /* puts("Message too long!"); */ break; } p = p2 + 1; if (!(p2 = strchr(p, '\n'))) break; } height++; /* * Figure out the height and width in pixels (height, width) extend the * new box by 15 pixels on the sides (30 total) top and bottom. */ st->s_rect.width = width + 30; st->s_rect.height = height * font_height(st->xftfont) + 30; if (st->x - st->s_rect.width - 10 < 5) st->s_rect.x = 5; else if ((st->s_rect.x = st->x + 32 - (st->s_rect.width + 15) / 2) + st->s_rect.width + 15 > st->Width - 5) st->s_rect.x = st->Width - 15 - st->s_rect.width; if (st->y - st->s_rect.height - 10 < 5) st->s_rect.y = st->y + st->pix_h + 5; else st->s_rect.y = st->y - 5 - st->s_rect.height; XFillRectangle(st->dpy, st->window, st->text_bg_gc, st->s_rect.x, st->s_rect.y, st->s_rect.width, st->s_rect.height); /* make a box that's 5 pixels thick. Then add a thin box inside it */ XSetLineAttributes(st->dpy, st->text_fg_gc, 5, 0, 0, 0); XDrawRectangle(st->dpy, st->window, st->text_fg_gc, st->s_rect.x, st->s_rect.y, st->s_rect.width - 1, st->s_rect.height - 1); XSetLineAttributes(st->dpy, st->text_fg_gc, 0, 0, 0, 0); XDrawRectangle(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + 7, st->s_rect.y + 7, st->s_rect.width - 15, st->s_rect.height - 15); st->X = 15; st->Y = 15 + font_height(st->xftfont); /* now print each string in reverse order (start at bottom of box) */ for (Z = 0; Z < height; Z++) { int L = strlen(args[Z]); /* If there are continuous new lines, L can be 0 */ if (L && (args[Z][L-1] == '\r' || args[Z][L-1] == '\n')) args[Z][--L] = 0; XftDrawStringUtf8 (st->xftdraw, &st->xftcolor, st->xftfont, st->s_rect.x + st->X, st->s_rect.y + st->Y, (FcChar8 *) args[Z], L); st->Y += font_height(st->xftfont); } st->interval = (total / 15) * 1000; if (st->interval < 2000) st->interval = 2000; st->next_fn = talk_1; *st->words = 0; st->lines = 0; } static void talk_1 (struct state *st) { talk(st, 0); } static unsigned long look (struct state *st) { if (random() % 3) { COPY(st->dpy, (random() & 1) ? &st->down : &st->front, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y); return 1000L; } if (!(random() % 5)) return 0; if (random() % 3) { COPY(st->dpy, (random() & 1) ? &st->left_front : &st->right_front, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y); return 1000L; } if (!(random() % 5)) return 0; COPY(st->dpy, (random() & 1) ? &st->left1 : &st->right1, st->window, st->fg_gc, 0, 0, st->pix_w, st->pix_h, st->x, st->y); return 1000L; } static void fill_words (struct state *st) { char *p = st->words + strlen(st->words); char *c; int lines = 0; int max = MAXLINES; for (c = st->words; c < p; c++) if (*c == '\n') lines++; while (p < st->words + sizeof(st->words) - 1 && lines < max) { int c = textclient_getc (st->tc); if (c == '\n') lines++; if (c > 0) *p++ = (char) c; else break; } *p = 0; st->lines = lines; } static const char *noseguy_defaults [] = { ".background: black", ".foreground: #CCCCCC", "*textForeground: black", "*textBackground: #CCCCCC", "*fpsSolid: true", "*program: xscreensaver-text", "*usePty: False", ".font: -*-helvetica-medium-r-*-*-*-140-*-*-*-*-*-*", 0 }; static XrmOptionDescRec noseguy_options [] = { { "-program", ".program", XrmoptionSepArg, 0 }, { "-font", ".font", XrmoptionSepArg, 0 }, { "-text-foreground", ".textForeground", XrmoptionSepArg, 0 }, { "-text-background", ".textBackground", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; static void * noseguy_init (Display *d, Window w) { struct state *st = (struct state *) calloc (1, sizeof(*st)); unsigned long fg, bg, text_fg, text_bg; XWindowAttributes xgwa; Colormap cmap; char *fontname, *cname, *s; XGCValues gcvalues; st->dpy = d; st->window = w; st->first_time = 1; fontname = get_string_resource (st->dpy, "font", "Font"); XGetWindowAttributes (st->dpy, st->window, &xgwa); st->Width = xgwa.width + 2; st->Height = xgwa.height + 2; cmap = xgwa.colormap; st->tc = textclient_open (st->dpy); { int w = 40; int h = 15; textclient_reshape (st->tc, w, h, w, h, /* Passing MAXLINES isn't actually necessary */ 0); } init_images(st); st->xftfont = XftFontOpenXlfd (st->dpy, screen_number (xgwa.screen), fontname); free (fontname); cname = get_string_resource (st->dpy, "textForeground", "Foreground"); XftColorAllocName (st->dpy, xgwa.visual, xgwa.colormap, cname, &st->xftcolor); free (cname); st->xftdraw = XftDrawCreate (st->dpy, st->window, xgwa.visual, xgwa.colormap); fg = get_pixel_resource (st->dpy, cmap, "foreground", "Foreground"); bg = get_pixel_resource (st->dpy, cmap, "background", "Background"); text_fg = get_pixel_resource (st->dpy, cmap, "textForeground", "Foreground"); text_bg = get_pixel_resource (st->dpy, cmap, "textBackground", "Background"); /* notice when unspecified */ s = get_string_resource (st->dpy, "textForeground", "Foreground"); if (s) free (s); else text_fg = bg; s = get_string_resource (st->dpy, "textBackground", "Background"); if (s) free (s); else text_bg = fg; gcvalues.foreground = fg; gcvalues.background = bg; st->fg_gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcvalues); gcvalues.foreground = bg; gcvalues.background = fg; st->bg_gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcvalues); gcvalues.foreground = text_fg; gcvalues.background = text_bg; st->text_fg_gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcvalues); gcvalues.foreground = text_bg; gcvalues.background = text_fg; st->text_bg_gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcvalues); st->x = st->Width / 2; st->y = st->Height / 2; st->state = IS_MOVING; st->next_fn = move; st->walk_up = 1; return st; } static unsigned long noseguy_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; fill_words(st); st->next_fn(st); return (st->interval * 1000); } static void noseguy_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *) closure; st->Width = w + 2; st->Height = h + 2; } static Bool noseguy_event (Display *dpy, Window window, void *closure, XEvent *event) { return False; } static void noseguy_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; textclient_close (st->tc); XftFontClose (dpy, st->xftfont); /* XftColorFree (dpy, st->xgwa.visual, st->xgwa.colormap, st->xftcolor); */ XftDrawDestroy (st->xftdraw); XFreeGC (dpy, st->fg_gc); XFreeGC (dpy, st->bg_gc); XFreeGC (dpy, st->text_fg_gc); XFreeGC (dpy, st->text_bg_gc); # define FP(F) \ if (st->F.p) XFreePixmap (dpy, st->F.p); \ if (st->F.m) XFreePixmap (dpy, st->F.m) FP (left1); FP (left2); FP (right1); FP (right2); FP (left_front); FP (right_front); FP (front); FP (down); free (st); } XSCREENSAVER_MODULE ("NoseGuy", noseguy)