X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Fnoseguy.c;h=726c2a7439ec20162758716aa85bfb44f7225152;hb=78add6e627ee5f10e1fa6f3852602ea5066eee5a;hp=cae0397f740cfeac227d1233d42e9d8bae75a702;hpb=5f1f12f2a37da634000f96d18d59cc73a8814ef7;p=xscreensaver diff --git a/hacks/noseguy.c b/hacks/noseguy.c index cae0397f..726c2a74 100644 --- a/hacks/noseguy.c +++ b/hacks/noseguy.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski +/* 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 @@ -15,33 +15,31 @@ */ #include "screenhack.h" -#include "xpm-pixmap.h" -#include +#include "ximage-loader.h" +#include "textclient.h" +#include "xft.h" -#ifdef HAVE_COCOA -# define HAVE_XPM -#endif - -extern FILE *popen (const char *, const char *); -extern int pclose (FILE *); +#define font_height(font) (font->ascent + font->descent) -#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; - char *words; int x, y; - XFontStruct *font; + + XftFont *xftfont; + XftColor xftcolor; + XftDraw *xftdraw; unsigned long interval; - Pixmap left1, left2, right1, right2; - Pixmap left_front, right_front, front, down; + PM left1, left2, right1, right2, left_front, right_front, front, down; + int pix_w, pix_h; - char *program, *orig_program; + text_data *tc; int state; /* indicates states: walking or getting passwd */ int first_time; @@ -52,7 +50,7 @@ struct state { int walk_lastdir; int walk_up; - Pixmap walk_frame; + PM *walk_frame; int X, Y, talking; @@ -60,10 +58,11 @@ struct state { int x, y, width, height; } s_rect; - char word_buf[BUFSIZ]; + char words[10240]; + int lines; }; -static char *get_words (struct state *); +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 *); @@ -72,35 +71,56 @@ static unsigned long look (struct state *); #define IS_MOVING 1 -#if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM) -# include "images/noseguy/nose-f1.xpm" -# include "images/noseguy/nose-f2.xpm" -# include "images/noseguy/nose-f3.xpm" -# include "images/noseguy/nose-f4.xpm" -# include "images/noseguy/nose-l1.xpm" -# include "images/noseguy/nose-l2.xpm" -# include "images/noseguy/nose-r1.xpm" -# include "images/noseguy/nose-r2.xpm" -#else -# include "images/noseguy/nose-f1.xbm" -# include "images/noseguy/nose-f2.xbm" -# include "images/noseguy/nose-f3.xbm" -# include "images/noseguy/nose-f4.xbm" -# include "images/noseguy/nose-l1.xbm" -# include "images/noseguy/nose-l2.xbm" -# include "images/noseguy/nose-r1.xbm" -# include "images/noseguy/nose-r2.xbm" -#endif +#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) { - Pixmap *images[8]; -#if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM) - char **bits[8]; -#else - unsigned char *bits[8]; -#endif + 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; @@ -110,51 +130,46 @@ init_images (struct state *st) images[i++] = &st->left_front; images[i++] = &st->right_front; images[i++] = &st->front; - images[i++] = &st->down; - -#if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM) + images[i] = &st->down; +#define DEF(N,S) bits[i].png = N; bits[i].size = S; i++ i = 0; - bits[i++] = nose_l1_xpm; - bits[i++] = nose_l2_xpm; - bits[i++] = nose_r1_xpm; - bits[i++] = nose_r2_xpm; - bits[i++] = nose_f2_xpm; - bits[i++] = nose_f3_xpm; - bits[i++] = nose_f1_xpm; - bits[i++] = nose_f4_xpm; + 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 pixmap = xpm_data_to_pixmap (st->dpy, st->window, bits[i], - 0, 0, 0); + 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] = pixmap; + images[i]->p = pixmap; + images[i]->m = mask; } -#else - i = 0; - bits[i++] = nose_l1_bits; - bits[i++] = nose_l2_bits; - bits[i++] = nose_r1_bits; - bits[i++] = nose_r2_bits; - bits[i++] = nose_f2_bits; - bits[i++] = nose_f3_bits; - bits[i++] = nose_f1_bits; - bits[i++] = nose_f4_bits; - for (i = 0; i < sizeof (images) / sizeof(*images); i++) - if (!(*images[i] = - XCreatePixmapFromBitmapData(st->dpy, st->window, - (char *) bits[i], 64, 64, 1, 0, 1))) - { - fprintf (stderr, "%s: Can't load nose images\n", progname); - exit (1); - } -#endif + 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 @@ -249,13 +264,15 @@ move (struct state *st) st->next_fn = move; } -#ifdef HAVE_XPM -# define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \ - XCopyArea (dpy,frame,window,gc,x,y,w,h,x2,y2) -#else -# define COPY(dpy,frame,window,gc,x,y,w,h,x2,y2) \ - XCopyPlane(dpy,frame,window,gc,x,y,w,h,x2,y2,1L) -#endif +# 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) @@ -268,12 +285,12 @@ walk (struct state *st, int dir) if (dir & LEFT) { incr = X_INCR; - st->walk_frame = (st->walk_up < 0) ? st->left1 : st->left2; + 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; + st->walk_frame = (st->walk_up < 0) ? &st->right1 : &st->right2; } if ((st->walk_lastdir == FRONT || st->walk_lastdir == DOWN) && dir & UP) { @@ -282,40 +299,46 @@ walk (struct state *st, int dir) * 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, 64, 64, st->x, st->y); + 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 - 64) + 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, 64, 64, st->x, st->y -= Y_INCR); + 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, 64, 64, st->x, st->y += Y_INCR); - else if (dir == FRONT && st->walk_frame != st->front) + 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; + st->walk_frame = &st->left_front; else if (st->walk_lastdir & RIGHT) - st->walk_frame = st->right_front; + 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, 64, 64, st->x, st->y); + 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, 64, 64, --st->x, st->y + st->walk_up); + 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, 64, 64, ++st->x, st->y + st->walk_up); + 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; } @@ -326,18 +349,12 @@ think (struct state *st) if (random() & 1) walk(st, FRONT); if (random() & 1) - { - st->words = get_words(st); return 1; - } return 0; } -#define MAXLINES 25 - -#undef BUFSIZ -#define BUFSIZ ((MAXLINES + 1) * 100) - +#define MAXLINES 10 +#define LINELEN 256 static void talk (struct state *st, int force_erase) @@ -348,8 +365,7 @@ talk (struct state *st, int force_erase) total = 0; register char *p, *p2; - char buf[BUFSIZ], - args[MAXLINES][256]; + char args[MAXLINES][LINELEN]; /* clear what we've written */ if (st->talking || force_erase) @@ -371,18 +387,30 @@ talk (struct state *st, int force_erase) } return; } + p = st->words; + /* If there is actually no words, just return */ + if (!*p) + { + st->talking = 0; + return; + } st->talking = 1; walk(st, FRONT); - p = strcpy(buf, st->words); for (p2 = p; *p2; p2++) if (*p2 == '\t') *p2 = ' '; if (!(p2 = strchr(p, '\n')) || !p2[1]) { + XGlyphInfo extents; + total = strlen (st->words); - strcpy (args[0], st->words); - width = XTextWidth(st->font, st->words, total); + 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 @@ -390,12 +418,20 @@ talk (struct state *st, int force_erase) for (height = 0; p; height++) { int w; + XGlyphInfo extents; *p2 = 0; - if ((w = XTextWidth(st->font, p, p2 - p)) > width) - width = w; + + 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) strcpy(args[height], p); + (void) strncpy(args[height], p, LINELEN); + args[height][LINELEN - 1] = 0; if (height == MAXLINES - 1) { /* puts("Message too long!"); */ @@ -412,14 +448,14 @@ talk (struct state *st, int force_erase) * 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->font) + 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 + 64 + 5; + st->s_rect.y = st->y + st->pix_h + 5; else st->s_rect.y = st->y - 5 - st->s_rect.height; @@ -435,21 +471,26 @@ talk (struct state *st, int force_erase) 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->font); + 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 (args[Z][L-1] == '\r' || args[Z][L-1] == '\n') + /* 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; - XDrawString(st->dpy, st->window, st->text_fg_gc, st->s_rect.x + st->X, st->s_rect.y + st->Y, - args[Z], L); - st->Y += font_height(st->font); + 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 @@ -464,95 +505,52 @@ look (struct state *st) { if (random() % 3) { - COPY(st->dpy, (random() & 1) ? st->down : st->front, st->window, st->fg_gc, - 0, 0, 64, 64, st->x, st->y); + 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, 64, 64, st->x, st->y); + 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, 64, 64, st->x, st->y); + 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 -init_words (struct state *st) +fill_words (struct state *st) { - st->program = get_string_resource (st->dpy, "program", "Program"); + 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++; - if (st->program) /* get stderr on stdout, so it shows up on the window */ + while (p < st->words + sizeof(st->words) - 1 && + lines < max) { - st->orig_program = st->program; - st->program = (char *) malloc (strlen (st->program) + 10); - strcpy (st->program, "( "); - strcat (st->program, st->orig_program); - strcat (st->program, " ) 2>&1"); + int c = textclient_getc (st->tc); + if (c == '\n') + lines++; + if (c > 0) + *p++ = (char) c; + else + break; } + *p = 0; - st->words = get_words(st); -} - -static char * -get_words (struct state *st) -{ - FILE *pp; - register char *p = st->word_buf; - - st->word_buf[0] = '\0'; - - if ((pp = popen(st->program, "r"))) - { - while (fgets(p, sizeof(st->word_buf) - strlen(st->word_buf), pp)) - { - if (strlen(st->word_buf) + 1 < sizeof(st->word_buf)) - p = st->word_buf + strlen(st->word_buf); - else - break; - } - (void) pclose(pp); - if (! st->word_buf[0]) - sprintf (st->word_buf, "\"%s\" produced no output!", st->orig_program); - else if (!st->first_time && - (strstr (st->word_buf, ": not found") || - strstr (st->word_buf, ": Not found") || - strstr (st->word_buf, ": command not found") || - strstr (st->word_buf, ": Command not found"))) - switch (random () % 20) - { - case 1: strcat (st->word_buf, "( Get with the st->program, bub. )\n"); - break; - case 2: strcat (st->word_buf, - "( I blow my nose at you, you silly person! ) \n"); break; - case 3: strcat (st->word_buf, - "\nThe resource you want to\nset is `noseguy.program'\n"); - break; - case 4: - strcat(st->word_buf,"\nHelp!! Help!!\nAAAAAAGGGGHHH!! \n\n"); break; - case 5: strcpy (st->word_buf, "You have new mail.\n"); break; - case 6: - strcat(st->word_buf,"( Hello? Are you paying attention? )\n");break; - case 7: - strcat (st->word_buf, "sh: what kind of fool do you take me for? \n"); - break; - } - st->first_time = 0; - p = st->word_buf; - } - else - { - perror(st->program); - } - - return p; + st->lines = lines; } @@ -562,9 +560,10 @@ static const char *noseguy_defaults [] = { ".foreground: #CCCCCC", "*textForeground: black", "*textBackground: #CCCCCC", - "*fpsSolid: true", - "*program: xscreensaver-text --cols 40 | head -n15", - ".font: -*-new century schoolbook-*-r-*-*-*-180-*-*-*-*-*-*", + "*fpsSolid: true", + "*program: xscreensaver-text", + "*usePty: False", + ".font: -*-helvetica-medium-r-*-*-*-140-*-*-*-*-*-*", 0 }; @@ -596,16 +595,26 @@ noseguy_init (Display *d, Window w) st->Height = xgwa.height + 2; cmap = xgwa.colormap; - init_words(st); + 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); - if (!fontname || !*fontname) - fprintf (stderr, "%s: no font specified.\n", progname); - st->font = XLoadQueryFont(st->dpy, fontname); - if (!st->font) { - fprintf (stderr, "%s: could not load font %s.\n", progname, fontname); - exit(1); - } + st->xftfont = XftFontOpenXlfd (st->dpy, screen_number (xgwa.screen), + fontname); + XftColorAllocName (st->dpy, xgwa.visual, xgwa.colormap, + get_string_resource (st->dpy, + "textForeground", "Foreground"), + &st->xftcolor); + 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"); @@ -617,26 +626,25 @@ noseguy_init (Display *d, Window w) if (! get_string_resource (st->dpy, "textBackground", "Background")) text_bg = fg; - gcvalues.font = st->font->fid; gcvalues.foreground = fg; gcvalues.background = bg; st->fg_gc = XCreateGC (st->dpy, st->window, - GCForeground|GCBackground|GCFont, + GCForeground|GCBackground, &gcvalues); gcvalues.foreground = bg; gcvalues.background = fg; st->bg_gc = XCreateGC (st->dpy, st->window, - GCForeground|GCBackground|GCFont, + GCForeground|GCBackground, &gcvalues); gcvalues.foreground = text_fg; gcvalues.background = text_bg; st->text_fg_gc = XCreateGC (st->dpy, st->window, - GCForeground|GCBackground|GCFont, + GCForeground|GCBackground, &gcvalues); gcvalues.foreground = text_bg; gcvalues.background = text_fg; st->text_bg_gc = XCreateGC (st->dpy, st->window, - GCForeground|GCBackground|GCFont, + GCForeground|GCBackground, &gcvalues); st->x = st->Width / 2; st->y = st->Height / 2; @@ -650,6 +658,7 @@ 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); } @@ -658,6 +667,9 @@ 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 @@ -669,6 +681,9 @@ noseguy_event (Display *dpy, Window window, void *closure, XEvent *event) static void noseguy_free (Display *dpy, Window window, void *closure) { + struct state *st = (struct state *) closure; + textclient_close (st->tc); + free (st); } XSCREENSAVER_MODULE ("NoseGuy", noseguy)