wmail: updated change-log and bumped to 2.2.
[dockapps.git] / wmswallow / wmswallow.c
blobb542e94a69e42e9fb672400987d0f9887e94af99
1 /* wmswallow.c */
3 /* #define DEBUG 1 */
4 /* Sometimes i want to get quick access to this flag :-)*/
6 /* Time-stamp: <00/05/15 23:13:43 friedel> */
8 /* Copyright 2000 Friedrich Delgado Friedrichs */
10 /* Swallow applications in the Windowmaker dock */
11 /* Originally i started with asbeats 0.2 (by iznogood@bohemians.org) (simply
12 the smallest WindowMaker dockapp i could find, since there was no proper
13 Documentation to find on how to make an app dockable), which i stripped
14 down to have a basic dockable app, and then stole code from fvwm2 to
15 swallow an Application */
16 /* Man, this was easy! Why did nobody implement this before? (That's me,
17 looking astonished at the first working version at Mon Apr 17 02:16:31
18 CEST 2000 after only 4 hours of mostly learning how to program the X-Environment and
19 hacking and guessing a little :-) */
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <X11/Xlib.h>
25 #include <X11/Xutil.h>
26 #include <X11/extensions/shape.h>
27 #include <time.h>
28 #include <X11/Xatom.h>
29 #include <string.h>
31 #include "version.h"
33 /* That's all we need from xpm.h */
34 #if ! defined(_XtIntrinsic_h) && ! defined(PIXEL_ALREADY_TYPEDEFED)
35 typedef unsigned long Pixel; /* Index into colormap */
36 # define PIXEL_ALREADY_TYPEDEFED
37 #endif
38 /* Now we got rid of that stupid libXpm dependency :-) */
40 #define WIDTH 55
41 #define HEIGHT 57
42 /* 55x57 seems to be the default size for a WindowMaker dockapp */
43 /* settable by "-geometry" switch */
45 Display *dpy;
46 Window Root;
47 Window iconwin,win;
48 Window swallowed;
49 XSizeHints mysizehints;
51 #define MW_EVENTS (ExposureMask | ButtonPressMask |\
52 StructureNotifyMask |\
53 ButtonReleaseMask |\
54 EnterWindowMask|LeaveWindowMask)
55 #define SW_EVENTS (PropertyChangeMask | StructureNotifyMask |\
56 ResizeRedirectMask | SubstructureNotifyMask)
57 #define FIND_EVENTS (SubstructureNotifyMask | StructureNotifyMask)
58 #define READY_EVENTS FIND_EVENTS
59 #define FALSE 0
60 #define TRUE (!FALSE)
62 Pixel GetColor(char *name);
63 void FlushWindow();
64 void usage (char *progname);
65 int parseargs(int argc, char *argv[]);
66 Window findnamedwindow (char *class);
67 Window findnamedwindowacc (char *class, Window window);
68 int checkwindow (Window window, char *class);
69 int execstuff(int argc, char *oldargv[]);
70 Window startandfind(int argc, char *oldargv[], char* class);
71 int printlist(FILE * stream, char * string, char **stringlist);
72 int flush_expose (Window w);
73 void stealshape (Window w);
74 int sendexpose (Window w);
75 void waitformap (Window win);
76 /* int softenwindow (Window w); */ /* won't work, kept for historical reasons */
78 /* Parameters that can be customized via commandline switches */
79 char *execstring=NULL;
80 char *geometry=NULL;
81 int getclick=FALSE;
82 int shape=TRUE;
83 int focus=FALSE;
84 int unmanaged=FALSE;
85 int winid=0;
86 char *display_name=NULL;
88 int main(int argc,char *argv[])
90 int screen;
91 int d_depth;
92 XWMHints mywmhints;
93 Pixel back_pix,fore_pix;
95 int i;
96 unsigned int borderwidth;
97 char *wname="wmswallow";
99 int remainarg, remainargc;
101 XEvent Event;
102 XTextProperty name;
103 XClassHint classHint;
105 remainarg=parseargs(argc, argv); /* remainarg > 0 afterwards */
106 remainargc=argc-remainarg;
107 #ifdef DEBUG
108 fprintf(stderr, "remainarg: %d, remainargc: %d, argc: %d\n", remainarg,
109 remainargc,argc);
110 fflush(stderr);
111 #endif
113 if (!(dpy = XOpenDisplay(display_name))) {
114 fprintf(stderr,"wmswallow: can't open display %s\n",
115 XDisplayName(display_name));
116 exit (1);
118 screen=DefaultScreen(dpy);
119 Root=RootWindow(dpy, screen);
121 /* So, now we've got everything we need to get Events from the XServer */
122 if (remainargc>1) {
123 winid=startandfind(remainargc-1, argv+remainarg+1, argv[remainarg]);
124 if (winid==0) {
125 perror("wmswallow: startandfind failed");
126 /* Real error handling in execstuff()*/
127 exit (1);
131 d_depth=DefaultDepth(dpy, screen);
132 /* XConnectionNumber(dpy); */ /* useless */
133 mysizehints.flags=USSize|USPosition;
134 mysizehints.x=0;
135 mysizehints.y=0;
136 back_pix=GetColor("white");
137 fore_pix=GetColor("black");
138 XWMGeometry(dpy, screen, geometry, NULL, (borderwidth =1),
139 &mysizehints, &mysizehints.x, &mysizehints.y,
140 &mysizehints.width, &mysizehints.height, &i);
141 mysizehints.width=WIDTH;
142 mysizehints.height=HEIGHT;
143 if (geometry!=NULL) {
144 #ifdef DEBUG
145 fprintf(stderr,"Setting geometry to: %s\n",geometry);
146 fflush(stderr);
147 #endif
148 XParseGeometry(geometry, &mysizehints.x, &mysizehints.y,
149 &mysizehints.width, &mysizehints.height);
152 win=XCreateSimpleWindow(dpy, Root, mysizehints.x, mysizehints.y,
153 mysizehints.width, mysizehints.height, borderwidth,
154 fore_pix, back_pix);
155 iconwin=XCreateSimpleWindow(dpy, win, mysizehints.x, mysizehints.y,
156 mysizehints.width, mysizehints.height, borderwidth,
157 fore_pix, back_pix);
158 XSetWMNormalHints(dpy, win, &mysizehints);
159 classHint.res_name="wmswallow";
160 classHint.res_class="WMswallow";
161 XSetClassHint(dpy, win, &classHint);
162 XSelectInput(dpy, win, MW_EVENTS);
163 XSelectInput(dpy, iconwin, MW_EVENTS);
164 if(XStringListToTextProperty(&wname, 1, &name)==0)
166 fprintf(stderr, "wmswallow: can't allocate window name\n");
167 exit(-1);
169 XSetWMName(dpy, win, &name);
170 mywmhints.initial_state = WithdrawnState;
171 mywmhints.icon_window = iconwin;
172 mywmhints.icon_x = mysizehints.x;
173 mywmhints.icon_y = mysizehints.y;
174 mywmhints.window_group = win;
175 mywmhints.flags = StateHint | IconWindowHint |
176 IconPositionHint | WindowGroupHint;
177 XSetWMHints(dpy, win, &mywmhints);
178 XSetCommand(dpy, win, argv, argc);
180 if (winid==0) {
181 swallowed=findnamedwindow(argv[remainarg]); /* Find which window to
182 swallow*/
183 #ifdef DEBUG
184 fprintf(stderr,"%s has Window-id 0x%lx\n", argv[remainarg], swallowed);
185 fflush(stderr);
186 #endif
188 else
189 swallowed=winid;
192 /* "Swallow" it */
193 XReparentWindow(dpy, swallowed, iconwin, 0, 0);
194 if (getclick) {
195 /* softenwindow (swallowed); */ /* Change some attributes */
196 XSelectInput(dpy, swallowed, SW_EVENTS|ButtonPressMask);
198 else {
199 XSelectInput(dpy, swallowed, SW_EVENTS); /* Workaround for apps like
200 perfmeter that don't let us
201 get their mouseclicks :-( */
203 XSetWindowBorderWidth(dpy, swallowed,0);
204 XMoveResizeWindow(dpy, swallowed, 0, 0,
205 mysizehints.width, mysizehints.height);
207 /* Now we do some special juju for shaped windows: */
209 /* ...tell the window to repaint itself, please! */
210 if (shape) {
211 sendexpose(swallowed);
213 /* ... ok, window should be repainted and a shaped window should have updated
214 its mask accordingly! (-: End of shape-juju :-) */
216 /* Now steal the shape of the Window we just swallowed! */
217 stealshape(swallowed); }
218 XMapWindow(dpy,win);
219 XMapSubwindows(dpy,win);
220 FlushWindow();
222 while(1)
224 while (XPending(dpy))
226 XNextEvent(dpy,&Event);
227 switch(Event.type)
229 case ButtonPress:
230 #ifdef DEBUG
231 fprintf (stderr, "wmswallow: Got ButtonPress Event\n");
232 fflush(stderr);
233 #endif
234 if (getclick)
235 system(execstring);
236 break;
237 case Expose:
238 if(Event.xexpose.count == 0 ) {
239 #ifdef DEBUG
240 fprintf (stderr, "wmswallow: Got Expose Event, count==0\n");
241 fflush(stderr);
242 #endif
243 if (shape)
244 stealshape(swallowed); /* Oclock changes its shape! That's why
245 we have to steal it *again* */
246 FlushWindow();
247 XMapRaised(dpy,swallowed);
248 /* the following Produces "focus-flicker" */
249 /* XMapSubwindows(dpy,win); */
250 /* XMapWindow(dpy,win); */
252 break;
253 case EnterNotify:
254 if (focus)
255 XSetInputFocus(dpy, swallowed, RevertToPointerRoot,
256 CurrentTime);
257 break;
258 case LeaveNotify:
259 if (focus)
260 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot,
261 CurrentTime);
262 break;
264 case DestroyNotify:
265 XCloseDisplay(dpy);
266 exit(0);
267 default:
268 #ifdef DEBUG
269 /* fprintf (stderr, "wmswallow: Got Some Other Event\n");
270 fflush(stderr); */
271 #endif
272 break;
275 XFlush(dpy);
276 usleep(50000L);
278 return 1;
281 /* int softenwindow (Window w) { */
282 /* XSetWindowAttributes attributes; */
284 /* attributes.override_redirect=FALSE; */
285 /* attributes.event_mask=SW_EVENTS|MW_EVENTS; */
286 /* attributes.do_not_propagate_mask=0; */
288 /* XChangeWindowAttributes(dpy, w, */
289 /* CWOverrideRedirect|CWEventMask|CWDontPropagate, */
290 /* &attributes); */
291 /* return TRUE; */
292 /* } */
294 int sendexpose (Window w) {
295 XExposeEvent xexp;
296 XEvent Event;
297 int retval;
299 xexp.type=Expose;
300 xexp.serial=0;
301 xexp.send_event=TRUE;
302 xexp.display=dpy;
303 xexp.window=w;
304 xexp.x=0;
305 xexp.y=0;
306 xexp.width=mysizehints.width;
307 xexp.height=mysizehints.height;
308 xexp.count=0;
309 Event.xexpose=xexp;
310 retval=XSendEvent(dpy, w, FALSE, ExposureMask, &Event);
311 /* XFlush(dpy); */ /* ... send all queued Events */
312 /* usleep(5000L); */ /* ... take a tiny doze */
313 XSync(dpy, FALSE); /* I doubt if this is really better than Flushing and
314 pausing */
315 return retval;
318 void stealshape(Window w) {
319 XShapeCombineShape (dpy, iconwin, ShapeBounding, 0, 0, w,
320 ShapeBounding, ShapeSet);
321 /* XShapeCombineShape (dpy, win, ShapeBounding, 0, 0, w, */
322 /* ShapeBounding, ShapeSet); */
323 /*Re-read specs! */
324 /* XShapeCombineShape (dpy, win, ShapeClip, 0, 0, w, */
325 /* ShapeClip, ShapeSet); */
326 /* XShapeCombineShape (dpy, iconwin, ShapeClip, 0, 0, w, */
327 /* ShapeClip, ShapeSet); */
330 void nocolor(char *a, char *b)
332 fprintf(stderr,"wmswallow: can't %s %s\n", a,b);
336 int flush_expose (Window w)
338 XEvent dummy;
339 int i=0;
340 while(XCheckTypedWindowEvent(dpy,w,Expose,&dummy))
341 i++;
342 return i;
345 void FlushWindow()
347 flush_expose(swallowed);
348 flush_expose (iconwin);
349 flush_expose(win);
352 Pixel GetColor(char *name)
354 XColor color;
355 XWindowAttributes attributes;
356 XGetWindowAttributes(dpy,Root,&attributes);
357 color.pixel=0;
358 if (!XParseColor(dpy,attributes.colormap,name,&color))
359 nocolor("parse",name);
360 else if(!XAllocColor (dpy,attributes.colormap,&color))
361 nocolor("alloc",name);
362 return color.pixel;
365 void usage(char *progname){
366 printf(
367 "wmswallow Version %s\n"
368 " by Friedrich Delgado Friedrichs (c) 2000\n"
369 "\n"
370 "Usage:\n"
371 " %s [<flags>] [windowname [command [args ...]]]\n"
372 " Will swallow the first X-Window it finds with a WM_NAME or\n"
373 " WM_CLASS matching <windowname>\n"
374 "\n"
375 " Flags:\n"
376 " -h: prints this message and exits\n"
377 " -geometry <string>: use specified geometry for swallowed\n"
378 " window\n"
379 " -display <string>: connect to specified display\n"
380 " -shape: use the shape extension (default)\n"
381 " -noshape: don't use the shape extension\n"
382 " -focus: Window should take focus\n"
383 " -nofocus: Window shouldn't take focus(default)\n"
384 " -managed: Assume window is managed by the\n"
385 " windowmanager (default)\n"
386 " -unmanaged: Assume window is not managed by the\n"
387 " windowmanager\n"
388 " -getclick <string>: on mouseclick, execute <string>\n"
389 " instead of passing the Event to the\n"
390 " swallowed window.\n"
391 " -id [0x]<hexnumber>: swallow window with id <hexnumber>\n"
392 " The command with args will be executed, before swallowing.\n",
393 VERSION, progname);
396 /* Parse commandline args, returns first non-switch argnumber */
397 int parseargs(int argc, char *argv[]){
398 int argnum;
399 int lastarg=1;
400 if (argc<2) {
401 usage(argv[0]);
402 exit(0);
405 for(argnum=1; argnum<argc && *argv[argnum]=='-'; lastarg=++argnum) {
406 if (!strncmp(argv[argnum],"-h",2) ||
407 !strncmp(argv[argnum],"--",2)) {
408 usage(argv[0]);
409 exit(0);
410 } else if (!strcmp(argv[argnum],"-geometry")||
411 !strcmp(argv[argnum],"-geom"))
412 geometry=argv[++argnum];
413 else if (!strcmp(argv[argnum],"-display"))
414 display_name = argv[++argnum];
415 else if (!strcmp(argv[argnum],"-noshape"))
416 shape=FALSE;
417 else if (!strcmp(argv[argnum],"-shape"))
418 shape=TRUE;
419 else if (!strcmp(argv[argnum],"-unmanaged"))
420 unmanaged=TRUE;
421 else if (!strcmp(argv[argnum],"-managed"))
422 unmanaged=FALSE;
423 else if (!strcmp(argv[argnum],"-nofocus"))
424 focus=FALSE;
425 else if (!strcmp(argv[argnum],"-focus"))
426 focus=TRUE;
427 else if (!strcmp(argv[argnum],"-getclick")) {
428 execstring=(char *)malloc(strlen(argv[++argnum])+1+2);
429 strcpy(execstring, argv[argnum]);
430 strcat(execstring, " &");
431 getclick=TRUE;
432 } else if (!strcmp(argv[argnum],"-id"))
433 winid=strtol(argv[++argnum], NULL, 16);
434 else {
435 usage(argv[0]);
436 exit(0);
439 return lastarg; /*Return number of first argument, that is neither a switch nor
440 an argument to a switch */
444 /* Print a NULL-terminated list of char* onto file stream */
445 int printlist(FILE * stream, char * string, char **stringlist) {
446 int i=0;
448 fprintf(stream, string);
449 if (stringlist!=NULL) {
450 while (stringlist[i]!=NULL) {
451 fprintf(stream, " §");
452 fprintf(stream, stringlist[i]);
453 fprintf(stream, "§ ");
454 ++i;
456 } else {
457 return(TRUE);
459 return(FALSE);
462 /* Select SubstructureNotify on the root-window, start the command, then look
463 if we get Create Events for a matching window, set winid */
464 Window startandfind (int argc, char *oldargv[], char* class) {
465 int found=0;
466 XEvent Event;
467 Window winreturn=(Window)0;
468 Window wintmp=(Window)0;
470 #ifdef DEBUG
471 fprintf(stderr, "Checking for name: %s\n", class);
472 fflush(stderr);
473 #endif
475 XSelectInput(dpy, Root, FIND_EVENTS);
476 if (!execstuff(argc, oldargv))
477 return FALSE; /* execstuff failed, should not return, but
478 nevertheless...*/
479 while (!found) {
480 while (!found && XPending(dpy)) {
481 /* FIXME: We hang, when the application starts, but we
482 cannot find the window! */
483 XNextEvent(dpy, &Event);
484 switch (Event.type) { /* Switch, in case we check for more than one
485 Event-Type one day */
486 /* We're waiting for the wm to reparent the window! */
487 case ReparentNotify:
488 #ifdef DEBUG
489 fprintf (stderr, "wmswallow: Got ReparentNotify Event\n");
490 fflush(stderr);
491 #endif
492 wintmp=Event.xreparent.window;
493 if (checkwindow(wintmp, class)) {
494 winreturn=wintmp;
495 found=TRUE;
496 } else if ((winreturn=findnamedwindowacc(class, wintmp))) {
497 found=TRUE;
499 break;
500 case CreateNotify: case MapNotify:
501 wintmp=Event.xcreatewindow.window;
502 #ifdef DEBUG
503 fprintf (stderr, "wmswallow: Got CreateNotify Event for window "
504 "0x%lx\n", wintmp);
505 fflush(stderr);
506 #endif
507 if (unmanaged) {
508 if (checkwindow(wintmp, class)) {
509 winreturn=wintmp;
510 found=TRUE;
511 } else if ((winreturn=findnamedwindowacc(class, wintmp))) {
512 found=TRUE;
515 break;
516 default:
517 break;
521 XSelectInput(dpy, Root, None);
522 #ifdef DEBUG
523 fprintf (stderr, "wmswallow: found it"
524 "0x%lx\n", wintmp);
525 fflush(stderr);
526 #endif
528 waitformap(winreturn);
529 /* Ok, the window has been created, Reparented by WindowMaker and mapped */
530 /* What else can we do to make sure the window was created? */
532 sleep(1); /* doze just a sec, should be more than enough in any case */
534 return winreturn;
537 /* Execute a command */
538 int execstuff (int argc, char *oldargv[]) {
539 char **argv;
540 int i, success, forked;
542 argv=(char **)malloc((argc+1)*sizeof(char *));
544 for (i=0; i<argc; i++) {
545 argv[i]=oldargv[i];
547 argv[i]=NULL;
549 forked=fork();
550 if (forked==-1) {
551 perror("Could not fork");
552 exit(1);
554 if (forked) {
555 #ifdef DEBUG
556 printlist(stderr, "Trying to execute:", argv);
557 fprintf(stderr, "\n");
558 #endif
559 success=execvp(argv[0],argv);
560 if (success!=0) {
561 printlist(stderr, "Could not execute:", argv);
562 fprintf(stderr, "\n");
563 exit(1);
565 } /* Removed the sleep, since it keeps us from getting the Create Event */
566 free(argv);
567 return(TRUE);
570 void waitformap (Window win) {
571 int found=0;
572 XEvent Event;
574 XSelectInput(dpy, win, READY_EVENTS);
575 found=0;
576 while (!found && XPending(dpy)) {
577 if (XCheckMaskEvent(dpy, READY_EVENTS, &Event))
578 if (Event.type==MapNotify)
579 if (Event.xmap.window==win)
580 found=TRUE;
582 #ifdef DEBUG
583 fprintf (stderr, "wmswallow: Got MapNotify Event for window 0x%lx\n", win);
584 fflush(stderr);
585 #endif
586 while (XCheckTypedWindowEvent(dpy, win, MapNotify, &Event))
587 ; /* Flush the map Events */
588 XSelectInput(dpy, win, None);
591 /* Find window which matches WM_NAME or WM_CLASS */
592 Window findnamedwindow (char *class) {
593 /* Get All Children of the Root-Window */
594 Window result;
595 if ((result=(findnamedwindowacc (class, Root)))!=0)
596 return result;
597 else {
598 fprintf(stderr, "Could not find %s\n", class);
599 exit (1);
603 /* Recursively walk through all the windows and their children to find the
604 first one that matches WM_NAME or WM_CLASS */
605 /* Only called by findnamedwindow() */
606 Window findnamedwindowacc (char *class, Window window) {
607 Window root_return;
608 Window parent_return;
609 Window *children_return;
610 unsigned int nchildren_return;
611 Window runner;
612 Window result;
613 int i;
615 children_return=(Window *)NULL;
616 result=(Window)0;
618 if (checkwindow(window, class))
619 return window;
621 if (XQueryTree (dpy, window, &root_return, &parent_return,
622 &children_return, &nchildren_return)&&nchildren_return>0) {
624 (i=0,runner=*children_return;i<nchildren_return;
625 runner=children_return[++i]) {
626 if ((result=findnamedwindowacc(class, runner))!=0)
627 break; /* Leave this loop */ /* It's one of the children */
629 /* end of for loop*/ /* checked all windows to no avail */
630 } /* If the if (XQueryTree...)-part wasn't executed, wmswallow could not get
631 Windows (probably no children) */
632 if (children_return)
633 XFree(children_return);
634 return result;
637 /* Checks if the window has WM_NAME or WM_CLASS properties fitting *class */
638 int checkwindow (Window window, char *class) {
639 XClassHint class_hints;
640 XTextProperty prop;
642 int found=0;
644 class_hints.res_name = class_hints.res_class = prop.value =(char *) NULL;
646 /* Check WM_CLASS properties name and class */
647 if (XGetClassHint(dpy, window, &class_hints)) {
648 if (!strcmp(class_hints.res_name, class) ||
649 !strcmp(class_hints.res_class, class)) {
650 found = 1; /* It's this window! */
652 #ifdef DEBUG
653 fprintf (stderr, "wmswallow: checkwindow: 0x%lx, class: %s, name: %s\n",
654 win, class_hints.res_class, class_hints.res_name);
655 fflush(stderr);
656 #endif
658 /* Check WM_NAME property */
659 if (!found && XGetWMName(dpy, window, &prop))
660 if (prop.nitems && !strcmp(prop.value, class)) {
661 found = 1; /* (-: It's really this window, and we're lucky we guessed its
662 name correctly :-) */
664 #ifdef DEBUG
665 fprintf (stderr, "wmswallow: WM_NAME: %s\n",
666 prop.value);
667 fflush(stderr);
668 #endif
670 /* Clean up */
671 if (prop.value)
672 XFree(prop.value);
673 if (class_hints.res_class)
674 XFree(class_hints.res_class);
675 if (class_hints.res_name)
676 XFree(class_hints.res_name);
677 return found;