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