1 /* Xteddy - a cuddly bear to place on your desktop. */
2 /* Author: Stefan Gustavson, ISY-LiTH, 1994         */
3 /* Internet email address: stegu@itn.liu.se         */
4 /* This software is distributed under the GNU       */
5 /* Public Licence (GPL).                            */
6 /* Also, if you modify this program or include it   */
7 /* in some kind of official distribution, I would   */
8 /* like to know about it.                           */
9 
10 /* Xpm pixmap manipulation routines for color       */
11 /* and grayscale teddies are from the Xpm library   */
12 /* by Arnaud Le Hors, lehors@sophia.inria.fr,       */
13 /* Copyright 1990-93 GROUPE BULL                    */
14 
15 /* This is Xteddy version 2.2 as of 2009-02-25.     */
16 /* from  Peter De Wachter <pdewacht@gmail.com>      */
17 /* Changes: see ChangeLog                           */
18 
19 #define DEFAULT_IMAGE_DIR "/usr/local/share/xteddy"
20 #define DEFAULT_LOCAL_IMAGE_DIR "/usr/local/share/xteddy"
21 #define XTEDDY_VERSION "2.02"
22 
23 #include <X11/Xlib.h>
24 #include <X11/Xutil.h>
25 #include <X11/Xos.h>
26 #include <X11/Xatom.h>
27 #include <X11/keysym.h>
28 #include <X11/extensions/shape.h>
29 #include <X11/cursorfont.h>
30 
31 #include <Imlib2.h>
32 
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <sys/param.h>
39 
40 #ifndef TRUE
41 #define TRUE 1
42 #endif
43 
44 #ifndef FALSE
45 #define FALSE 0
46 #endif
47 
48 static char *progname;
49 Display *display;
50 int screen_num;
51 
52 ino_t xteddy_inodenum;	/*  the inode of the executable. */
53 struct stat stat_buf_xteddy;
54 
55 char *Extensions[] = {
56   "xpm",
57   "png",
58   "tif",
59   "jpg",
60   "jpeg",
61   "gif",
62   "pnm",
63   "XPM",
64   "PNG",
65   "TIF",
66   "JPG",
67   "JPEG",
68   "GIF",
69   "PNG",
70   NULL
71 };
72 
InitTeddy(char * teddy)73 char *InitTeddy(char *teddy)
74 /* Initializing filename */
75 {
76   char     fbuf[MAXPATHLEN], sbuf[MAXPATHLEN], *name, *ext, *sext;
77   struct   stat stat_buf;
78   char   **ptr;
79   ino_t   teddy_inodenum;
80 
81   if ( !teddy || !*(teddy) ) return NULL;
82   if ( !stat(teddy, &stat_buf) ){
83     /* If the executable is in the current directory, xteddy will try to load itself (the binary!) and fail. Don't do this. */
84     teddy_inodenum=stat_buf.st_ino;
85     if (teddy_inodenum==xteddy_inodenum){
86       fprintf(stderr, "Warning: the file named '%s' in the current directory is the executable program itself! Not trying to display it as an image.\n", teddy);
87       /* Note, if the current dir contains a subdir named xteddy and xteddy is invoked as "xteddy" without the path, this error will also be triggered. The error message above
88       will be (misleadingly) printed, but the correct action (not trying to load a directory as an image) will occur. */
89     }else{
90       return strdup(teddy);
91     }
92   }
93 
94   /* Test, some image formats PIXMAP_PATH */
95   strcat(strcpy(fbuf, PIXMAP_PATH), "/");
96   strcat(strcat(strcpy(sbuf, DEFAULT_IMAGE_DIR), "/"), teddy);
97   name = fbuf + strlen(fbuf);
98   strcpy(name, teddy);
99   if ( !stat(fbuf, &stat_buf) ) return strdup(fbuf);
100   if ( !stat(sbuf, &stat_buf) ) return strdup(sbuf);
101   strcat(name, ".");
102   ext  = name + strlen(name);
103   sext = sbuf + strlen(sbuf);
104   for ( ptr = Extensions; *ptr; ptr++ ) {
105     strcat(name, *ptr);
106     if ( !stat(name, &stat_buf) ) return strdup(name);
107     if ( !stat(fbuf, &stat_buf) ) return strdup(fbuf);
108     strcat(sbuf, ".");   /* missing the dot before the extension! */
109     strcat(sbuf, *ptr);
110     if ( !stat(sbuf, &stat_buf) ) return strdup(sbuf);
111     *ext = *sext = 0;
112   }
113 
114   /* Repeat with a different search path. */
115   strcat(strcat(strcpy(sbuf, DEFAULT_LOCAL_IMAGE_DIR), "/"), teddy);
116   if ( !stat(sbuf, &stat_buf) ) return strdup(sbuf);
117   sext = sbuf + strlen(sbuf);
118   for ( ptr = Extensions; *ptr; ptr++ ) {
119     strcat(sbuf, ".");   /* missing the dot before the extension! */
120     strcat(sbuf, *ptr);
121     if ( !stat(sbuf, &stat_buf) ) return strdup(sbuf);
122     *sext = 0;
123   }
124 
125   /* failed to find anything. */
126   return NULL;
127 }
128 
main(int argc,char ** argv)129 int main(int argc, char **argv)
130 {
131   /* Display, window and gc manipulation variables */
132   Window               win;
133   Pixmap               pm_image, pm_mask;
134   XSetWindowAttributes setwinattr;
135   unsigned long        valuemask, inputmask;
136   int                  x, y, geomflags;
137   unsigned int         xw, xh;
138   unsigned int         border_width = 0;
139   unsigned int         display_width, display_height;
140   XSizeHints           size_hints;
141   XWMHints             wm_hints;
142   XClassHint           class_hints;
143 #ifdef FORTIFY_SOURCE
144   XTextProperty        windowName, iconName;
145 #else
146   XTextProperty        windowName;
147 #endif
148   int                  argnum;
149   int                  use_wm, float_up, allow_quit, cursor_blank;
150   XEvent               report;
151   char                *display_name = NULL;
152   char                 buffer[20];
153   int                  bufsize = 20;
154   KeySym               keysym;
155   XComposeStatus       compose;
156   int                  charcount;
157   Cursor               cursor;
158   char                *teddy;
159   char                *file;
160 
161   /* Window movement variables */
162   XWindowChanges       winchanges;
163   Window               root, child, basewin;
164   int                  offs_x, offs_y, new_x, new_y, tmp_x, tmp_y;
165   unsigned int         tmp_mask;
166 
167   Imlib_Image         *im;
168   int                  im_width, im_height;
169 
170   /* Determine program name */
171   if ((progname = strrchr(argv[0],'/')) == NULL)
172     progname = argv[0];
173   else
174     progname++;
175   teddy = progname;
176 
177 
178   /* Option handling: "-wm", "-float", "-noquit", "-geometry", */
179   /* "-display" and "-nocursor" are recognized. See manual page for details. */
180   /* -F<name> ... Other pixmap name */
181   /* -F or -f are equivalent. A space may be included if desired after the option (eg "-f xduck") */
182   /* -v prints version. -h or --help prints help */
183   use_wm = FALSE;
184   float_up = FALSE;
185   allow_quit = TRUE;
186   cursor_blank = FALSE;
187   x = y = 0;
188   geomflags = 0;
189   for ( argnum = 1; argnum < argc; argnum++ ) {
190     if (!strcmp(argv[argnum],"-wm"))
191       use_wm = TRUE;
192     if (!strcmp(argv[argnum],"-float"))
193       float_up = TRUE;
194     if (!strcmp(argv[argnum],"-noquit"))
195       allow_quit = FALSE;
196     if (!strcmp(argv[argnum],"-geometry"))
197       geomflags = XParseGeometry(argv[++argnum], &x, &y, &xw, &xh);
198     if (!strcmp(argv[argnum],"-display"))
199       display_name = argv[++argnum];
200     if (!strcmp(argv[argnum],"-nocursor"))
201       cursor_blank = TRUE;
202     /* Use -f or -F; -f seems more natural. But -float does not mean "-f loat" ! */
203     if ( (!strncmp(argv[argnum],"-F", 2)) || ( (!strncmp(argv[argnum],"-f", 2)) && (strcmp(argv[argnum],"-float")) ) )
204       teddy = argv[argnum] + 2;
205       if (strlen(teddy) == 0){	/* accept "-f <name>" as well as "-f<name>" */
206         teddy = argv[argnum] + 3;
207         argnum ++;
208       }
209     if (!strcmp(argv[argnum],"-v")){
210      fprintf(stderr,"xteddy version: %s\n",XTEDDY_VERSION);
211      exit (0);
212     }
213     if ((!strncmp(argv[argnum],"-h", 2)) || (!strncmp(argv[argnum],"--help", 2))){  /* show help */
214       fprintf (stderr,"xteddy is a cute teddy bear that sits on your X desktop.\n");
215       fprintf (stderr,"Usage: xteddy [ OPTIONS ] [ -f <filename> ]\n");
216       fprintf (stderr,"        -wm                     use the window manager\n");
217       fprintf (stderr,"        -float                  always float on top of other windows\n");
218       fprintf (stderr,"        -noquit                 disable the 'q' key to quit.\n");
219       fprintf (stderr,"        -f,-F <filename>        use <filename> as the image.\n");
220       fprintf (stderr,"        -geometry,-display      have their usual meaning in X.\n");
221       fprintf (stderr,"        -h, --help              display this help and exit\n");
222       fprintf (stderr,"        -v                      print version information and exit\n\n");
223       fprintf (stderr,"The default image is a teddy bear, but others may be selected with -f (or -F).\n");
224       fprintf (stderr,"To tuck him back up in bed, move the mouse over him and press the 'q' or 'Esc' key.\n");
225       fprintf (stderr,"This is xteddy version %s. See `man 1 xteddy` for more information.\n",XTEDDY_VERSION);
226       exit (0);
227     }
228   }
229 
230   stat(argv[0], &stat_buf_xteddy);  /* find the inode number of the executable file itself */
231   xteddy_inodenum=stat_buf_xteddy.st_ino; /* [This can be fooled if it is invoked as "xteddy" without the path, AND eg the working directory contains a subdirectory called xteddy] */
232 
233   /* Connect to X server */
234   if ( (display = XOpenDisplay(display_name)) == NULL )
235     {
236       (void) fprintf(stderr, "%s: Cannot connect to X server %s\n",
237 		     progname, XDisplayName(display_name));
238       exit(-1);
239     }
240   /* Get screen size and depth */
241   display_width  = DisplayWidth(display, screen_num);
242   display_height = DisplayHeight(display, screen_num);
243 
244   /* set the maximum number of colors to allocate for 8bpp and less to 128 */
245   imlib_set_color_usage(128);
246   /* dither for depths < 24bpp */
247   imlib_context_set_dither(1);
248   /* set the display, visual and colormap we are using */
249   imlib_context_set_display(display);
250   imlib_context_set_visual(DefaultVisual(display, DefaultScreen(display)));
251   imlib_context_set_colormap(DefaultColormap(display, DefaultScreen(display)));
252 
253   if ( !(file = InitTeddy(teddy)) ) {
254     fprintf(stderr, "Can not find any image with name '%s'.\n", teddy);
255     return -1;
256   }
257   if ( !(im=imlib_load_image_immediately(file)) ) {
258     fprintf(stderr, "Most probably, the file '%s' is not a valid image.\n", teddy);
259     return -1;
260   }
261   imlib_context_set_image(im);
262   im_width = imlib_image_get_width();
263   im_height = imlib_image_get_height();
264   /* Set the window position according to user preferences */
265   if (geomflags & XNegative)
266     x = display_width - im_width + x;
267   if (geomflags & YNegative)
268     y = display_height - im_height + y;
269   /* Clip against bounds to stay on the screen */
270   if (x<0) x=0;
271   if (x > display_width - im_width) x = display_width - im_width;
272   if (y<0) y=0;
273   if (y > display_height - im_height) y = display_height - im_height;
274 
275   /* Create the main window */
276   win = XCreateSimpleWindow(display, DefaultRootWindow(display),
277 			    x,y,im_width,im_height,border_width,
278 			    BlackPixel(display,screen_num),
279 			    WhitePixel(display,screen_num));
280   XSelectInput(display,win,StructureNotifyMask);
281 
282   imlib_context_set_drawable(win);
283   imlib_render_pixmaps_for_whole_image(&pm_image, &pm_mask);
284   XSetWindowBackgroundPixmap(display, win, pm_image);
285   XShapeCombineMask(display, win, ShapeBounding, 0, 0, pm_mask, ShapeSet);
286   XFreePixmap(display, pm_image);
287   XFreePixmap(display, pm_mask);
288   imlib_free_image_and_decache();
289 
290   basewin = win;
291 
292   if (use_wm)
293     setwinattr.override_redirect = FALSE;
294   else
295     setwinattr.override_redirect = TRUE;
296 
297   if (cursor_blank)
298     {
299       Pixmap blank;
300       XColor dummy;
301       const char data[1] = { 0 };
302       blank = XCreateBitmapFromData (display, win, data, 1, 1);
303       if (blank == None)
304         {
305           fprintf (stderr, "error: out of memory.\n");
306           exit (1);
307         }
308       cursor = XCreatePixmapCursor (display, blank, blank, &dummy, &dummy, 0, 0);
309       XFreePixmap (display , blank);
310     }
311   else
312     cursor = XCreateFontCursor(display, XC_heart);
313 
314   setwinattr.cursor = cursor;
315   valuemask = CWOverrideRedirect | CWCursor;
316   XChangeWindowAttributes(display, win, valuemask, &setwinattr);
317 
318   /* Report size hints and other stuff to the window manager */
319   size_hints.min_width  = im_width;    /* Don't allow any resizing */
320   size_hints.min_height = im_height;
321   size_hints.max_width  = im_width;
322   size_hints.max_height = im_height;
323   size_hints.flags = PPosition | PSize | PMinSize | PMaxSize;
324   if (XStringListToTextProperty(&(teddy), 1, &windowName) == 0)
325     {
326       (void) fprintf(stderr, "%s: structure allocation for windowName failed.\n", progname);
327       return -1;
328     }
329   wm_hints.initial_state = NormalState;
330   wm_hints.input = TRUE;
331   wm_hints.flags = StateHint | IconPixmapHint | InputHint;
332 
333   class_hints.res_name = progname;
334   class_hints.res_class = "Xteddy";
335 
336 #ifdef FORTIFY_SOURCE
337   XSetWMProperties(display, win, &windowName, &iconName,
338 		   argv, argc, &size_hints, &wm_hints, &class_hints);
339 #else
340   XSetWMProperties(display, win, &windowName, NULL,
341 		   argv, argc, &size_hints, &wm_hints, &class_hints);
342 #endif
343 
344   /* Select event types wanted */
345   inputmask = ExposureMask | KeyPressMask | ButtonPressMask |
346     ButtonReleaseMask | StructureNotifyMask | ButtonMotionMask |
347       PointerMotionHintMask | EnterWindowMask | LeaveWindowMask;
348   if (float_up) inputmask |= VisibilityChangeMask;
349   XSelectInput(display, win, inputmask);
350 
351   /* Display window */
352   XMapWindow(display,win);
353 
354   /* Get and process the events */
355   while (1)
356     {
357       XNextEvent(display, &report);
358       switch(report.type)
359 	{
360 	case ReparentNotify:
361 	  /* Window was reparented by the window manager */
362 	  if (!use_wm)
363 	    (void) fprintf(stderr,
364 			   "%s: Window manager wouldn't leave the window alone!\n",
365 			   progname);
366 	  basewin = report.xreparent.parent;
367 	  break;
368 	case EnterNotify:
369 	  /* Grab the keyboard while the pointer is in the window */
370 	  XGrabKeyboard(display, win, FALSE, GrabModeAsync, GrabModeAsync,
371 			CurrentTime);
372 	  break;
373 	case LeaveNotify:
374 	  /* Release the keyboard when the pointer leaves the window */
375 	  XUngrabKeyboard(display, CurrentTime);
376 	  break;
377 	case ButtonPress:
378 	  /* Raise xteddy above sibling windows  */
379 	  XRaiseWindow(display, win);
380 	  /* Remember where the mouse went down */
381 	  XQueryPointer(display, basewin, &root, &child, &tmp_x, &tmp_y,
382 			&offs_x, &offs_y, &tmp_mask);
383 	  break;
384 	case ButtonRelease:
385 	  /* Place xteddy at the new position */
386 	  XQueryPointer(display, basewin, &root, &child, &new_x, &new_y,
387 		        &tmp_x, &tmp_y, &tmp_mask);
388 	  winchanges.x = new_x - offs_x;
389 	  winchanges.y = new_y - offs_y;
390 	  XReconfigureWMWindow(display, basewin, screen_num,
391 			       CWX | CWY, &winchanges);
392 	  break;
393 	case MotionNotify:
394 	  /* Move xteddy around with the mouse */
395 	  while (XCheckMaskEvent(display, ButtonMotionMask, &report));
396 	  if (!XQueryPointer(display, report.xmotion.window, &root, &child,
397 			    &new_x, &new_y, &tmp_x, &tmp_y, &tmp_mask))
398 	    break;
399 	  winchanges.x = new_x - offs_x;
400 	  winchanges.y = new_y - offs_y;
401 	  XReconfigureWMWindow(display, win, screen_num,
402 			       CWX | CWY, &winchanges);
403 	  break;
404 	case VisibilityNotify:
405 	  /* Put xteddy on top of overlapping windows */
406 	  if (float_up)
407 	    if ((report.xvisibility.state == VisibilityFullyObscured)
408 		|| (report.xvisibility.state == VisibilityPartiallyObscured))
409 	      XRaiseWindow(display,win);
410 	  break;
411 	case KeyPress:
412 	  /* Exit on "q" or "Q" or "Esc" */
413 	  charcount = XLookupString(&report.xkey, buffer, bufsize,
414 				    &keysym, &compose);
415 	  if((keysym == XK_Q) || (keysym == XK_q)  || (keysym == XK_Escape))
416 	    {
417               if (allow_quit)
418 		{
419 		  XCloseDisplay(display);
420 		  return 0;
421 		}
422 	    }
423 	  break;
424 	default:
425 	  /* Throw away all other events */
426 	  break;
427 	} /* end switch */
428     } /* end while */
429 }
430 
431