1 /* wmDrawer (c) 2002-2004 Valery Febvre <vfebvre@easter-eggs.com>
2  *
3  * wmDrawer is a dock application (dockapp) which provides a
4  * drawer (button bar) to launch applications from.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/select.h>
24 #include <sys/wait.h>
25 #include <time.h>
26 #include <signal.h>
27 
28 #if defined SOLARIS
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.h>
31 #endif
32 
33 #include "types_defs.h"
34 
35 #include <X11/extensions/shape.h>
36 #include <X11/cursorfont.h>
37 
38 #include "config.h"
39 #include "graphics.h"
40 #include "images.h"
41 
42 #include "pixmaps/defaultDockIcon.xpm"
43 #include "pixmaps/defaultIconsBg.xpm"
44 #include "pixmaps/defaultIcon.xpm"
45 #include "pixmaps/defaultHighlightImg.xpm"
46 
47 static void signalHandler (int signum);
48 static void quit (int status);
49 static void xfreeAllMemory (void);
50 static void buildDock (int argc, char *argv[]);
51 static void setDockIcon (void);
52 static void getDockPosition (void);
53 static void getDrawerPosition (int *x, int *y);
54 static void showDrawer (void);
55 static void hideDrawer (void);
56 static void buildDrawer (void);
57 static void buildTooltip (void);
58 static void highlightBtn (int col, int row);
59 static void highlightBtnShading (int col, int row);
60 static void highlightBtnTarget (int col, int row);
61 static void unhighlightBtn (Window btnWin);
62 static void rebuildApp (void);
63 static void execCmd (const char *cmd);
64 static void mySleep (unsigned long msec);
65 static int  xEnterEventOrTimeout (XEvent *e, unsigned long msec);
66 static void convertMCoordIntoBCoord (int mcol, int mrow, int *bcol, int *brow);
67 static void eventLoop (void);
68 
69 Display *display;
70 static Window dockWin, dockIconWin;
71 static drawingArea drawer, subDrawer, tooltip;
72 static XFontStruct *font;
73 drawerButton **btns;
74 static Cursor cursor;
75 static Pixmap highlightImgPix, highlightImgMask;
76 
77 static unsigned int drawerOpened = 0;
78 static unsigned int dpyWidth, dpyHeight;
79 static unsigned int dockX, dockY;
80 static unsigned int drawerW, drawerH;
81 static unsigned int btnDim = 0;
82 static int highlightCol, highlightRow;
83 
84 extern drawerConfig config;
85 extern unsigned int nbRows, nbCols, drawerOK;
86 extern unsigned int useDefaultIconsBg, useDefaultDockIcon, useDefaultHighlightImg;
87 
main(int argc,char ** argv)88 int main (int argc, char **argv) {
89   struct sigaction action;
90 
91   parseOptions (argc, argv);
92   parseConfig ();
93   buildDock (argc, argv);
94   setDockIcon ();
95   buildDrawer ();
96   buildTooltip ();
97   action.sa_handler = signalHandler;
98   sigemptyset (&action.sa_mask);
99   action.sa_flags = SA_NOCLDSTOP | SA_NODEFER;
100   sigaction (SIGTERM, &action, NULL);
101   sigaction (SIGINT,  &action, NULL);
102   sigaction (SIGCHLD, &action, NULL);
103 
104   eventLoop ();
105   return 0;
106 }
107 
signalHandler(int signum)108 static void signalHandler (int signum) {
109   switch (signum) {
110   case SIGINT:
111   case SIGTERM:
112     quit (EXIT_SUCCESS);
113     break;
114   case SIGCHLD:
115     while (waitpid (-1, NULL, WNOHANG | WUNTRACED) > 0);
116     break;
117   default:
118     quit (EXIT_FAILURE);
119   }
120 }
121 
quit(int status)122 static void quit (int status) {
123   printf ("Bye bye, thx to use %s\n", PACKAGE);
124   xfreeAllMemory ();
125   freeAllMemory ();
126   XCloseDisplay (display);
127   exit (status);
128 }
129 
xfreeAllMemory(void)130 void xfreeAllMemory (void) {
131   unsigned int col, row;
132 
133   /* free buttons' pixmap and GC */
134   for (col=0; col<nbCols; col++) {
135     for (row=0; row<nbRows; row++) {
136       XFreeGC (display, btns[col][row].da.gc);
137       if (btns[col][row].da.pixmap != None) {
138 	XFreePixmap (display, btns[col][row].da.pixmap);
139       }
140     }
141   }
142   /* free tooltip GC and font */
143   XFreeGC (display, tooltip.gc);
144   XFreeFont (display, font);
145   /* destroy all windows and sub-windows */
146   if (drawerOK == 1) {
147     XDestroyWindow (display, drawer.win);
148     XDestroyWindow (display, tooltip.win);
149   }
150 }
151 
buildDock(int argc,char * argv[])152 static void buildDock (int argc, char *argv[]) {
153   XClassHint *classhint = NULL;
154   XWMHints *wmhints = NULL;
155   XSizeHints *sizehints = NULL;
156   int tmp;
157 
158   display = XOpenDisplay ("");
159   if (!display) {
160     printf ("%s error: couldn't open display\n", PACKAGE);
161     exit (EXIT_FAILURE);
162   }
163 
164   /* query SHAPE extension */
165   if (!XShapeQueryExtension (display, &tmp, &tmp) && config.transparency > 0) {
166     printf ("%s warning: SHAPE extension not supported\n", PACKAGE);
167     config.transparency = 0;
168   }
169 
170   // create dock wins
171   dockWin = XCreateSimpleWindow (display, DefaultRootWindow (display),
172 				 0, 0, config.dockW, config.dockH, 0, 0, 0);
173   dockIconWin = XCreateSimpleWindow (display, DefaultRootWindow (display),
174 				     0, 0, config.dockW, config.dockH,
175 				     0, 0, 0);
176 
177   /* set ClassHint */
178   classhint = XAllocClassHint ();
179   if (!classhint) {
180     printf ("%s error: can't allocate memory for class hints!\n", PACKAGE);
181     exit (EXIT_FAILURE);
182   }
183   classhint->res_class = PACKAGE;
184   /* Ed Goforth <e.goforth@computer.org> */
185   /* Get the instanceName from the config file or command-line */
186   /* to set the client instance name (application name) */
187   /* so the window manager can differentiate between multiple instances. */
188   classhint->res_name = config.instanceName;
189   XSetClassHint (display, dockWin, classhint);
190   XSetClassHint (display, dockIconWin, classhint);
191   XFree (classhint);
192 
193   // set name and icon name for dock wins
194   XStoreName (display, dockWin, PACKAGE);
195   XStoreName (display, dockIconWin, PACKAGE);
196   XSetIconName (display, dockWin, PACKAGE);
197   XSetIconName (display, dockIconWin, PACKAGE);
198 
199   /* set WMHints */
200   wmhints = XAllocWMHints ();
201   if (!wmhints) {
202     printf ("%s error: can't allocate memory for wm hints!\n", PACKAGE);
203     exit (EXIT_FAILURE);
204   }
205   wmhints->flags = IconWindowHint | WindowGroupHint;
206   if (config.windowedMode == 0) {
207     wmhints->flags |= StateHint;
208     wmhints->initial_state = WithdrawnState;
209   }
210   wmhints->window_group = dockWin;
211   wmhints->icon_window = dockIconWin;
212   XSetWMHints (display, dockWin, wmhints);
213   XFree (wmhints);
214 
215   /* set SizeHints */
216   sizehints = XAllocSizeHints ();
217   sizehints->flags = USSize;
218   if (config.windowedMode == 0) {
219     sizehints->flags |= USPosition;
220     sizehints->x = sizehints->y = 0;
221   }
222   else {
223     sizehints->flags |= PMinSize | PMaxSize;
224     sizehints->min_width  = sizehints->max_width  = config.dockW;
225     sizehints->min_height = sizehints->max_height = config.dockH;
226   }
227   sizehints->width = config.dockW;
228   sizehints->height = config.dockH;
229   XSetWMNormalHints (display, dockWin, sizehints);
230   XFree (sizehints);
231 
232   /* Ed Goforth <e.goforth@computer.org> */
233   /* This allows window managers (such as WindowMaker) to pick up the */
234   /* command-line arguments that were used when the app was launched. */
235   /* This is particularly useful when docking. */
236   XSetCommand (display, dockWin, argv, argc);
237   XSetCommand (display, dockIconWin, argv, argc);
238 
239 #define INPUT_MASK ButtonPressMask | ButtonReleaseMask | ExposureMask | EnterWindowMask | LeaveWindowMask | StructureNotifyMask | SubstructureNotifyMask
240   XSelectInput (display, dockWin, INPUT_MASK);
241   XSelectInput (display, dockIconWin, INPUT_MASK);
242 #undef INPUT_MASK
243 }
244 
setDockIcon(void)245 static void setDockIcon (void) {
246   Pixmap dockIconPix, dockIconMask, icon;
247   int dockIconW, dockIconH;
248   unsigned int dx, dy, res = 0;
249   GC gc;
250   XGCValues xgcv;
251 
252   if (useDefaultDockIcon == 0)
253     res = createPixmapFromFile (config.dockIcon, &dockIconPix, &dockIconMask,
254 				&dockIconW, &dockIconH, config.dockW, config.dockH,
255 				RESIZE_SMALLER);
256   if (useDefaultDockIcon == 1 || res == 0)
257     createPixmapFromData ((const char **)defaultDockIcon_xpm, &dockIconPix,
258 			  &dockIconMask, &dockIconW, &dockIconH,
259 			  config.dockW, config.dockH, RESIZE_SMALLER);
260 
261   dx = (config.dockW - dockIconW) / 2;
262   dy = (config.dockH - dockIconH) / 2;
263 
264   XShapeCombineMask (display, dockIconWin,
265 		     ShapeBounding, dx, dy, dockIconMask, ShapeSet);
266   XShapeCombineMask (display, dockWin,
267 		     ShapeBounding, dx, dy, dockIconMask, ShapeSet);
268 
269   icon = XCreatePixmap (display, dockWin, config.dockW, config.dockH,
270 			(unsigned int)DefaultDepth (display, 0));
271   xgcv.clip_x_origin = dx;
272   xgcv.clip_y_origin = dy;
273   xgcv.clip_mask = dockIconMask; /* could be omitted */
274   xgcv.graphics_exposures = False;
275   gc = XCreateGC (display, dockWin,
276 		  GCClipMask|GCClipXOrigin|GCClipYOrigin|GCGraphicsExposures,
277 		  &xgcv);
278   XCopyArea (display, dockIconPix, icon, gc, 0, 0,
279 	     dockIconW, dockIconH, dx, dy);
280 
281   XSetWindowBackgroundPixmap (display, dockWin, icon);
282   XSetWindowBackgroundPixmap (display, dockIconWin, icon);
283   if (icon != None) XFreePixmap (display, icon);
284   XFreeGC (display, gc);
285 
286   if (dockIconPix != None) XFreePixmap (display, dockIconPix);
287   if (dockIconMask != None) XFreePixmap (display, dockIconMask);
288 
289   XMapWindow (display, dockWin);
290   XClearWindow (display, dockWin);
291   XClearWindow (display, dockIconWin);
292 
293   XFlush (display);
294 }
295 
getDockPosition(void)296 static void getDockPosition (void) {
297   Window win, frame, parent, root, *children;
298   unsigned int nchildren;
299   XWindowAttributes waP, waC;
300 
301   dpyWidth =  DisplayWidth  (display, XDefaultScreen (display));
302   dpyHeight = DisplayHeight (display, XDefaultScreen (display));
303 
304   if (config.windowedMode == 1) {
305     win = dockWin;
306   }
307   else {
308     win = dockIconWin;
309   }
310   /* get frame win */
311   root = 0;
312   frame = parent = win;
313   while (parent != root) {
314     frame = parent;
315     XQueryTree (display, frame, &root, &parent, &children, &nchildren);
316     dbg_msg (1, "Parent win: 0x%x\n", parent);
317     if (children != None) XFree (children);
318   }
319 
320   XGetWindowAttributes (display, frame, &waP);
321   XGetWindowAttributes (display, win, &waC);
322   dockX = waP.x + waC.x;
323   dockY = waP.y + waC.y;
324   dbg_msg (1, "Dock position: %d, %d\n", dockX, dockY);
325 }
326 
getDrawerPosition(int * x,int * y)327 static void getDrawerPosition (int *x, int *y) {
328   getDockPosition ();
329   switch (config.direction) {
330   case topToBottom:
331     *x = dockX - (int) (drawerW + config.borderSize*2 - config.dockW) / 2;
332     *y = dockY + config.dockH;
333     break;
334   case rightToLeft:
335     *x = dockX - (drawerW + config.borderSize*2);
336     *y = dockY - (int) (drawerH + config.borderSize*2 - config.dockH) / 2;
337     break;
338   case bottomToTop:
339     *x = dockX - (int) (drawerW + config.borderSize*2 - config.dockW) / 2;
340     *y = dockY - (drawerH + config.borderSize*2);
341     break;
342   case leftToRight:
343     *x = dockX + config.dockW;
344     *y = dockY - (int) (drawerH + config.borderSize*2 - config.dockH) / 2;
345     break;
346   }
347   if (*x < 0) *x = 0;
348   if (*x + drawerW + config.borderSize*2 > dpyWidth - 1) {
349     *x = dpyWidth - (drawerW + config.borderSize*2);
350   }
351   if (*y < 0) *y = 0;
352   if (*y + drawerH + config.borderSize*2 > dpyHeight - 1) {
353     *y = dpyHeight - (drawerH + config.borderSize*2);
354   }
355 }
356 
showDrawer(void)357 static void showDrawer (void) {
358   int x, y;
359   unsigned int dw, dh, inc;
360 
361   if (!drawerOK)
362     return;
363   drawerOpened = 1;
364   highlightCol = highlightRow = -1;
365   getDrawerPosition(&x, &y);
366 
367   // if no animation for drawer
368   if (config.animationSpeed == 0) {
369     XMoveResizeWindow (display, drawer.win, x, y, drawerW, drawerH);
370     XMapRaised (display, drawer.win);
371     return;
372   }
373 
374   inc = (nbRows * btnDim) / ((5 - config.animationSpeed) * 4);
375   switch (config.direction) {
376   case topToBottom:
377     for (dh = inc; dh < drawerH+inc; dh = dh+inc) {
378       if (dh >= drawerH) {
379 	XMoveResizeWindow (display, drawer.win, x, y, drawerW, drawerH);
380       }
381       else {
382 	XMoveResizeWindow (display, drawer.win, x, y, drawerW, dh);
383       }
384       XMapRaised (display, drawer.win);
385       XClearWindow (display, subDrawer.win);
386       XSync (display, 0);
387       mySleep (15);
388     }
389     break;
390   case rightToLeft:
391     for (dw = inc; dw < drawerW+inc; dw = dw+inc) {
392       if (dw >= drawerW) {
393 	XMoveResizeWindow (display, drawer.win, x, y, drawerW, drawerH);
394       }
395       else {
396 	XMoveResizeWindow (display, drawer.win,
397 			   dockX - dw - config.borderSize*2, y, dw, drawerH);
398       }
399       XMapRaised (display, drawer.win);
400       XClearWindow (display, subDrawer.win);
401       XSync (display, 0);
402       mySleep (15);
403     }
404     break;
405   case bottomToTop:
406     for (dh = inc; dh < drawerH+inc; dh = dh+inc) {
407       if (dh >= drawerH) {
408 	XMoveResizeWindow (display, drawer.win, x, y, drawerW, drawerH);
409       }
410       else {
411 	XMoveResizeWindow (display, drawer.win,
412 			   x, dockY - dh - config.borderSize*2, drawerW, dh);
413       }
414       XMapRaised (display, drawer.win);
415       XClearWindow (display, subDrawer.win);
416       XSync (display, 0);
417       mySleep (15);
418     }
419     break;
420   case leftToRight:
421     for (dw = inc; dw < drawerW+inc; dw = dw+inc) {
422       if (dw >= drawerW) {
423 	XMoveResizeWindow (display, drawer.win, x, y, drawerW, drawerH);
424       }
425       else {
426 	XMoveResizeWindow (display, drawer.win, x, y, dw, drawerH);
427       }
428       XMapRaised (display, drawer.win);
429       XClearWindow (display, subDrawer.win);
430       XSync (display, 0);
431       mySleep (15);
432     }
433     break;
434   default:
435     XMapWindow (display, drawer.win);
436   }
437 }
438 
hideDrawer(void)439 static void hideDrawer (void) {
440   int x, y;
441   unsigned int dw, dh, inc;
442 
443   if (!drawerOK)
444     return;
445   drawerOpened = 0;
446   XUnmapWindow (display, tooltip.win);
447 
448   // if no animation for drawer
449   if (config.animationSpeed == 0) {
450     XUnmapWindow (display, drawer.win);
451     return;
452   }
453 
454   inc = (nbRows * btnDim) / ((5 - config.animationSpeed) * 4);
455   switch (config.direction) {
456   case topToBottom:
457     x = dockX - (int) (drawerW + config.borderSize*2 - config.dockW) / 2;
458     if (x + drawerW + config.borderSize*2 > dpyWidth - 1) {
459       x = dpyWidth - (drawerW + config.borderSize*2);
460     }
461     if (x < 0) x = 0;
462     for (dh = drawerH - inc; dh >= inc; dh=dh-inc) {
463       XMoveResizeWindow (display, drawer.win,
464 			 x, dockY + config.dockH, drawerW, dh);
465       XMapRaised (display, drawer.win);
466       XSync (display, 0);
467       mySleep (15);
468    }
469     break;
470   case rightToLeft:
471     y = dockY - (int) (drawerH + config.borderSize*2 - config.dockH) / 2;
472     if (y + drawerH + config.borderSize*2 > dpyHeight - 1) {
473       y = dpyHeight - (drawerH + config.borderSize*2);
474     }
475     if (y < 0) y = 0;
476     for (dw = drawerW - inc; dw >= inc; dw=dw-inc) {
477       XMoveResizeWindow (display, drawer.win,
478 			 dockX - dw - config.borderSize*2, y, dw, drawerH);
479       XMapRaised (display, drawer.win);
480       XSync (display, 0);
481       mySleep (15);
482     }
483     break;
484   case bottomToTop:
485     x = dockX - (int) (drawerW + config.borderSize*2 - config.dockW) / 2;
486     if (x + drawerW + config.borderSize*2 > dpyWidth - 1) {
487       x = dpyWidth - (drawerW + config.borderSize*2);
488     }
489     if (x < 0) x = 0;
490     for (dh = drawerH - inc; dh >= inc; dh = dh-inc) {
491       XMoveResizeWindow (display, drawer.win,
492 			 x, dockY - dh - config.borderSize*2, drawerW, dh);
493       XMapRaised (display, drawer.win);
494       XSync (display, 0);
495       mySleep (15);
496     }
497     break;
498   case leftToRight:
499     y = dockY - (int) (drawerH + config.borderSize*2 - config.dockH) / 2;
500     if (y + drawerH + config.borderSize*2 > dpyHeight - 1) {
501       y = dpyHeight - (drawerH + config.borderSize*2);
502     }
503     if (y < 0) y = 0;
504     for (dw = drawerW - inc; dw >= inc; dw=dw-inc) {
505       XMoveResizeWindow (display, drawer.win,
506 			 dockX + config.dockW, y, dw, drawerH);
507       XMapRaised (display, drawer.win);
508       XSync (display, 0);
509       mySleep (15);
510     }
511     break;
512   }
513   XUnmapWindow (display, drawer.win);
514 }
515 
buildDrawer(void)516 static void buildDrawer (void) {
517   /* XColor cursor_color; */
518   unsigned int res = 0, x, y, xr, yr, row, col;
519   int pixmapW, pixmapH;
520   unsigned long resizeMask;
521 
522   Pixmap bgImgPix, bgImgMask;
523   Pixmap iconPix, iconMask;
524 
525   if (!drawerOK)
526     return;
527   btns = config.entries;
528 
529   resizeMask = RESIZE_SMALLER;
530   if (config.iconsExpand == 1)
531     resizeMask |= RESIZE_GREATER;
532 
533   if (config.btnsSize == 0) {
534     btnDim = config.dockW / nbCols;
535     if (btnDim < MIN_ICON_SIZE) btnDim = MIN_ICON_SIZE;
536   }
537   else {
538     btnDim = config.btnsSize;
539   }
540 
541   if (config.direction == leftToRight || config.direction == rightToLeft) {
542     drawerW = nbRows * btnDim;
543     drawerH = nbCols * btnDim;
544   }
545   else {
546     drawerW = nbCols * btnDim;
547     drawerH = nbRows * btnDim;
548   }
549   dbg_msg(1, "drawerW = %d, drawerH = %d, btn dim = %d\n",
550 	  drawerW, drawerH, btnDim);
551 
552   /* create drawer's root window */
553   drawer.win = XCreateSimpleWindow (display, DefaultRootWindow (display),
554 				   0, 0, drawerW, drawerH,
555 				   config.borderSize, 0, 0);
556   drawer.xswa.event_mask  = EnterWindowMask | LeaveWindowMask;
557   drawer.xswa.override_redirect = True;
558   XChangeWindowAttributes (display, drawer.win,
559 			   CWEventMask | CWOverrideRedirect, &(drawer.xswa));
560   if (config.transparency == 1) {
561     XSetWindowBackgroundPixmap (display, drawer.win, ParentRelative);
562   }
563 
564   /* create drawer's subwindow */
565   subDrawer.win = XCreateSimpleWindow (display, drawer.win,
566 				       0, 0, drawerW, drawerH,
567 				       0, 0, 0);
568   subDrawer.xswa.event_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask;
569   subDrawer.xswa.override_redirect = False;
570   XChangeWindowAttributes (display, subDrawer.win,
571 			   CWEventMask | CWOverrideRedirect, &(subDrawer.xswa));
572   if (config.transparency == 1) {
573     XSetWindowBackgroundPixmap (display, subDrawer.win, ParentRelative);
574   }
575   XMapWindow (display, subDrawer.win);
576 
577   /* highlight mask pixmap */
578   if (useDefaultHighlightImg == 0) {
579     res = createPixmapFromFile (config.highlightImg,
580 				&highlightImgPix, &highlightImgMask,
581 				&pixmapW, &pixmapH, btnDim, btnDim, resizeMask);
582   }
583   if (useDefaultHighlightImg == 1 || res == 0) {
584     createPixmapFromData ((const char **)defaultHighlightImg_xpm,
585 			  &highlightImgPix, &highlightImgMask,
586 			  &pixmapW, &pixmapH, btnDim, btnDim, resizeMask);
587   }
588 
589   /* btn's background pixmap */
590   if (useDefaultIconsBg == 0) {
591     res = createPixmapFromFile (config.iconsBg, &bgImgPix, &bgImgMask,
592 				&pixmapW, &pixmapH, btnDim, btnDim, resizeMask);
593   }
594   if (useDefaultIconsBg == 1 || res == 0) {
595     createPixmapFromData ((const char **)defaultIconsBg_xpm,
596 			  &bgImgPix, &bgImgMask,
597 			  &pixmapW, &pixmapH, btnDim, btnDim, resizeMask);
598   }
599 
600   /* Create boutons windows and set bg */
601   for (col=0; col<nbCols; col++) {
602     for (row=0; row<nbRows; row++) {
603       if (config.direction == leftToRight || config.direction == rightToLeft) {
604 	x = row * btnDim;
605 	y = col * btnDim;
606       }
607       else {
608 	x = col * btnDim;
609 	y = row * btnDim;
610       }
611       btns[col][row].da.win = XCreateSimpleWindow (display, subDrawer.win,
612 						   x, y, btnDim, btnDim,
613 						   0, 0, 0);
614       btns[col][row].da.pixmap = XCreatePixmap (display, btns[col][row].da.win,
615 						btnDim, btnDim,
616 						DefaultDepth (display, 0));
617       btns[col][row].da.gc = XCreateGC (display, btns[col][row].da.pixmap, 0,
618 					&(btns[col][row].da.xgcv));
619       if (config.transparency == 0) {
620 	XCopyArea (display, bgImgPix, btns[col][row].da.pixmap,
621 		   btns[col][row].da.gc,
622 		   0, 0, btnDim, btnDim, 0, 0);
623       }
624     }
625   }
626   if (bgImgPix != None) XFreePixmap (display, bgImgPix);
627   if (bgImgMask != None) XFreePixmap (display, bgImgMask);
628 
629   /* Draw icons */
630   for (col=0; col<nbCols; col++) {
631     for (row=0; row<nbRows; row++) {
632       if (btns[col][row].isEmpty == 1) continue;
633       res = createPixmapFromFile (btns[col][row].image, &iconPix, &iconMask,
634 				  &pixmapW, &pixmapH, btnDim, btnDim,
635 				  resizeMask);
636       if (res == 0)
637 	createPixmapFromData ((const char **)defaultIcon_xpm,
638 			      &iconPix, &iconMask,
639 			      &pixmapW, &pixmapH, btnDim, btnDim, resizeMask);
640       xr = (btnDim - pixmapW) / 2;
641       yr = (btnDim - pixmapH) / 2;
642       if (config.transparency == 1) {
643 	XShapeCombineMask (display, btns[col][row].da.win, ShapeBounding, xr, yr,
644 			   iconMask, ShapeSet);
645       }
646       btns[col][row].da.xgcv.clip_x_origin = xr;
647       btns[col][row].da.xgcv.clip_y_origin = yr;
648       btns[col][row].da.xgcv.clip_mask = iconMask;
649       XChangeGC (display, btns[col][row].da.gc,
650 		 GCClipXOrigin | GCClipYOrigin | GCClipMask,
651 		 &(btns[col][row].da.xgcv));
652       XCopyArea (display, iconPix, btns[col][row].da.pixmap,
653 		 btns[col][row].da.gc, 0, 0, btnDim, btnDim, xr, yr);
654     }
655   }
656   if (iconPix != None) XFreePixmap (display, iconPix);
657   if (iconMask != None) XFreePixmap (display, iconMask);
658 
659   for (col=0; col<nbCols; col++) {
660     for (row=0; row<nbRows; row++) {
661       if (btns[col][row].isEmpty == 1 && config.transparency == 1) continue;
662       btns[col][row].da.xswa.event_mask = 0;
663       btns[col][row].da.xswa.background_pixmap = btns[col][row].da.pixmap;
664       btns[col][row].da.xswa.override_redirect = False;
665       XChangeWindowAttributes (display, btns[col][row].da.win,
666 			       CWEventMask | CWBackPixmap | CWOverrideRedirect,
667 			       &(btns[col][row].da.xswa));
668       XMapWindow (display, btns[col][row].da.win);
669     }
670   }
671 
672   /* Cursor */
673   cursor = XCreateFontCursor (display, config.cursor * 2);
674   /* Red cursor, just for fun ? */
675   /*   cursor_color.red = 65535; */
676   /*   cursor_color.green = 0; */
677   /*   cursor_color.blue = 0; */
678   /*   XRecolorCursor(display, cursor, &cursor_color, */
679   /*     &WhitePixel (display, DefaultScreen (display))); */
680   XDefineCursor (display, drawer.win, cursor);
681   XFreeCursor (display, cursor);
682 }
683 
buildTooltip(void)684 static void buildTooltip (void) {
685   /* load font */
686   if ((font = XLoadQueryFont (display, config.tooltipsFont)) == NULL) {
687     printf("%s warning: cannot open font \"%s\" for tooltips\n",
688 	   PACKAGE, config.tooltipsFont);
689     return;
690   }
691   /* build window */
692   tooltip.win = XCreateSimpleWindow (display, RootWindow(display, 0),
693 				     0, 0, 1, 1, 1, 0,
694 				     WhitePixel (display,
695 						 DefaultScreen (display)));
696   tooltip.gc = XCreateGC (display, tooltip.win, 0, &(tooltip.xgcv));
697   XSetFont (display, tooltip.gc, font->fid);
698   XSetForeground (display, tooltip.gc,
699 		  BlackPixel (display, DefaultScreen (display)));
700   XSetBackground (display, tooltip.gc,
701 		  WhitePixel (display, DefaultScreen (display)));
702   tooltip.xswa.event_mask = 0;
703   tooltip.xswa.override_redirect = True;
704   XChangeWindowAttributes (display, tooltip.win,
705 			   CWEventMask | CWOverrideRedirect, &(tooltip.xswa));
706 }
707 
highlightBtn(int col,int row)708 static void highlightBtn (int col, int row) {
709   /* if transparency = 1 (pseudo transparency), only highlight shading works */
710   if (config.transparency == 1 && config.highlight == 1) {
711     config.highlight = 2;
712   }
713   switch (config.highlight) {
714   case 1:
715     highlightBtnTarget (col, row);
716     break;
717   case 2:
718     highlightBtnShading (col, row);
719     break;
720   }
721 }
722 
highlightBtnShading(int col,int row)723 static void highlightBtnShading (int col, int row) {
724   XImage *img;
725   ShadingInfo shade;
726 
727   img = XGetImage (display, btns[col][row].da.win,
728 		   0, 0, btnDim, btnDim, AllPlanes, ZPixmap);
729   shade.shading = config.highlightShading.shading;
730   shade.tintColor.red   = config.highlightShading.tintColor.red * 256;
731   shade.tintColor.green = config.highlightShading.tintColor.green * 256;
732   shade.tintColor.blue  = config.highlightShading.tintColor.blue * 256;
733   ShadeXImage (display, img, &shade);
734   /* gc is OK, nothing to do */
735   XPutImage (display, btns[col][row].da.win,
736 	     btns[col][row].da.gc, img, 0, 0, 0, 0, btnDim, btnDim);
737   XDestroyImage (img);
738   if( !XCopyArea (display, btns[col][row].da.win, btns[col][row].da.win,
739 		  btns[col][row].da.gc, 0, 0, btnDim, btnDim, 0, 0)) {
740     XFillRectangle ( display, btns[col][row].da.win, btns[col][row].da.gc,
741 		     0, 0, btnDim, btnDim);
742   }
743 }
744 
highlightBtnTarget(int col,int row)745 static void highlightBtnTarget (int col, int row) {
746   XGCValues xgcv;
747   GC highlightGC;
748 
749   xgcv.clip_mask = highlightImgMask;
750   highlightGC = XCreateGC (display, btns[col][row].da.win, GCClipMask, &xgcv);
751   XCopyArea (display, highlightImgPix, btns[col][row].da.win,
752 	     highlightGC, 0, 0, btnDim, btnDim, 0, 0);
753   XFreeGC (display, highlightGC);
754 }
755 
unhighlightBtn(Window btnWin)756 static void unhighlightBtn (Window btnWin) {
757   //XClearArea (display, btnWin, 0, 0, btnDim, btnDim, False);
758   XClearWindow (display, btnWin);
759 }
760 
rebuildApp(void)761 static void rebuildApp (void) {
762   if (configChanged ()) {
763     if (drawerOpened == 1) hideDrawer ();
764     /* restore mouse cursor */
765     XUndefineCursor (display, drawer.win);
766     xfreeAllMemory ();
767     freeAllMemory ();
768 
769     parseConfig ();
770     XResizeWindow(display, dockWin, config.dockW, config.dockH);
771     XResizeWindow(display, dockIconWin, config.dockW, config.dockH);
772     setDockIcon ();
773     buildDrawer ();
774     buildTooltip ();
775   }
776 }
777 
execCmd(const char * cmd)778 void execCmd (const char *cmd) {
779   int cpid;
780 
781   cpid = fork ();
782   if (cpid == -1) {
783     printf ("%s error: can't fork\n", PACKAGE);
784   }
785   else if (cpid == 0) {
786     setsid ();
787     execl ("/bin/sh", "/bin/sh", "-c", cmd, NULL);
788     exit (EXIT_SUCCESS);
789   }
790 }
791 
792 /* msec is miliseconds, msec <= 999 */
mySleep(unsigned long msec)793 static void mySleep (unsigned long msec) {
794   struct timespec req, rem;
795 
796   req.tv_sec = 0;
797   req.tv_nsec = msec * 1000000;
798 
799   while (nanosleep (&req, &rem) != 0) {
800     req = rem;
801   }
802 }
803 
xEnterEventOrTimeout(XEvent * e,unsigned long msec)804 static int xEnterEventOrTimeout (XEvent *e, unsigned long msec) {
805   struct timeval timeout;
806   fd_set rset;
807 
808   dbg_msg (1, "Enter xNextEventOrTimeout function\n");
809 
810   XSync (display, False);
811   if (XPending (display)) {
812     XNextEvent (display, e);
813     dbg_msg (1, "Event type = %d\n", e->type);
814     if (e->type == EnterNotify) return 1;
815   }
816 
817   timeout.tv_sec  = msec / 1000;
818   timeout.tv_usec = (msec % 1000) * 1000;
819   FD_ZERO (&rset);
820   FD_SET (ConnectionNumber (display), &rset);
821 
822   if (select (ConnectionNumber (display)+1, &rset, NULL, NULL, &timeout) > 0) {
823     XNextEvent (display, e);
824     return 1;
825   }
826   return 0;
827 }
828 
829 /* Convert mouse coordinate into button coordinate */
convertMCoordIntoBCoord(int mcol,int mrow,int * bcol,int * brow)830 static void convertMCoordIntoBCoord (int mcol, int mrow, int *bcol, int *brow) {
831   switch (config.direction) {
832   case topToBottom:
833   case bottomToTop:
834     *bcol = (int)((float)mcol / btnDim);
835     *brow = (int)((float)mrow / btnDim);
836     break;
837   case rightToLeft:
838   case leftToRight:
839     *bcol = (int)((float)mrow / btnDim);
840     *brow = (int)((float)mcol / btnDim);
841     break;
842   }
843 }
844 
eventLoop(void)845 static void eventLoop (void) {
846   XEvent e;
847   int x, y, w ,h;
848   /* coord of btn pressed */
849   int prow = 0, pcol = 0;
850   /* btn's coord during motion */
851   int mrow = 0, mcol = 0;
852   /* coord when btn released */
853   int rrow = 0, rcol = 0;
854   /* X btn's coord */
855   int xbtn = 0, ybtn = 0;
856   unsigned int delta, btnIsPressed = 0;
857 
858   while (True) {
859     XNextEvent (display, &e);
860     rebuildApp ();
861     /* look at X.h & Xlib.h in /usr/X11R6/include/X11/ */
862     switch (e.type) {
863     case Expose:
864       dbg_msg (2, "Expose event: count = %d, win = 0x%x\n",
865 	       e.xexpose.count, e.xexpose.window);
866       break;
867     case ReparentNotify :
868     case ConfigureNotify:
869       dbg_msg (1, "ConfigureNotify event\n");
870       if (drawerOpened == 1) {
871 	getDrawerPosition (&x, &y);
872 	XMoveWindow (display, drawer.win, x, y);
873       }
874       break;
875     case MotionNotify:
876       if (config.highlight == 0 || btnIsPressed == 1 || drawerOpened == 0) break;
877       convertMCoordIntoBCoord(e.xmotion.x, e.xmotion.y, &mcol, &mrow);
878       if (highlightCol == mcol && highlightRow == mrow) break;
879       dbg_msg (1, "Motion event: win=0x%x, subwin=0x%x, (%d, %d) (%d, %d)\n",
880 	       e.xmotion.window, e.xmotion.subwindow,
881 	       highlightCol, highlightRow, mcol, mrow);
882       if (mcol < 0 || mcol >= nbCols || mrow < 0 || mrow >= nbRows) break;
883       /* Ok, first raise drawer */
884       XRaiseWindow (display, drawer.win);
885       /* tooltips */
886       if (config.tooltips == 1) {
887 	if (!btns[mcol][mrow].isEmpty && strlen (btns[mcol][mrow].tooltip) > 0) {
888 	  getDrawerPosition (&x, &y);
889 	  w = XTextWidth (font, btns[mcol][mrow].tooltip,
890 			  strlen(btns[mcol][mrow].tooltip)) + 8;
891 	  h = font->max_bounds.ascent + font->max_bounds.descent + 4;
892 	  switch (config.direction) {
893 	  case rightToLeft:
894 	  case leftToRight:
895 	    x = x + mrow * btnDim + btnDim / 2;
896 	    y = y + mcol * btnDim - h - 4;
897 	    break;
898 	  case bottomToTop:
899 	  case topToBottom:
900 	    x = x + mcol * btnDim + btnDim / 2;
901 	    y = y + mrow * btnDim - h - 4;
902 	    break;
903 	  }
904 	  if (x < 0) x = 0;
905 	  if (x + w + 2 > dpyWidth - 1) x = dpyWidth - (w + 2);
906 	  XMoveResizeWindow (display, tooltip.win, x, y, w, h);
907 	  XMapRaised (display, tooltip.win);
908 	  XClearWindow (display, tooltip.win);
909 	  XDrawString (display, tooltip.win, tooltip.gc,
910 		       0 + 4, font->max_bounds.ascent + font->max_bounds.descent,
911 		       btns[mcol][mrow].tooltip,
912 		       strlen(btns[mcol][mrow].tooltip));
913 	}
914 	else {
915 	  XUnmapWindow (display, tooltip.win);
916 	}
917       }
918       /* highlight */
919       if (highlightCol >= 0 && highlightRow >= 0) {
920 	if (btns[highlightCol][highlightRow].isEmpty == 0) {
921 	  unhighlightBtn (btns[highlightCol][highlightRow].da.win);
922 	}
923       }
924       if (btns[mcol][mrow].isEmpty == 0) {
925 	  highlightBtn (mcol, mrow);
926 	  highlightCol = mcol;
927 	  highlightRow = mrow;
928       }
929       else highlightCol = highlightRow = -1;
930       break;
931     case ButtonPress:
932       dbg_msg (1, "Button press event: win = 0x%x, cursor = (%d,%d)\n",
933 	       e.xbutton.window, e.xbutton.y, e.xbutton.y);
934       if (drawerOpened == 0) {
935 	showDrawer ();
936       }
937       else if (drawerOpened == 1 && e.xbutton.window != subDrawer.win) {
938 	hideDrawer ();
939       }
940       else if (drawerOpened == 1 && e.xbutton.window == subDrawer.win) {
941         xbtn = e.xbutton.x / btnDim;
942         ybtn = e.xbutton.y / btnDim;
943 	convertMCoordIntoBCoord (e.xbutton.x, e.xbutton.y, &pcol, &prow);
944 	delta = 1;
945 	if (btns[pcol][prow].isEmpty == 0) {
946 	  btnIsPressed = 1;
947 	  XMoveWindow (display, btns[pcol][prow].da.win,
948 		       xbtn * btnDim + delta, ybtn * btnDim + delta);
949 	  XMapRaised (display, btns[pcol][prow].da.win);
950 	  XSync (display, 0);
951 	}
952       }
953       break;
954     case ButtonRelease:
955       dbg_msg (1, "Button release event: win = 0x%x, cursor = (%d,%d)\n",
956 	       e.xbutton.window, e.xbutton.y, e.xbutton.y);
957       btnIsPressed = 0;
958       if (drawerOpened == 1 && e.xbutton.window == subDrawer.win) {
959 	convertMCoordIntoBCoord(e.xbutton.x, e.xbutton.y, &rcol, &rrow);
960 	if (btns[pcol][prow].isEmpty == 0) {
961 	  XMoveWindow (display, btns[pcol][prow].da.win,
962 		       xbtn * btnDim, ybtn * btnDim);
963 	  XMapRaised (display, btns[pcol][prow].da.win);
964 	  XSync (display, False);
965 	}
966 	if (prow == rrow && pcol == rcol && btns[pcol][prow].isEmpty == 0) {
967 	  /* if release coord = press coord => OK exec cmd */
968 	  dbg_msg (1, "Command executed : %s (%d, %d)\n",
969 		   btns[pcol][prow].command, pcol, prow);
970 	  execCmd (btns[pcol][prow].command);
971 	  if ((e.xbutton.state & ShiftMask) != 0 || e.xbutton.button != 1) {
972 	    /* If Shift is down whilst selecting app keep the drawer open */
973 	    /* or mouse button != 1 => nothing */
974 	  }
975 	  else {
976 	    XUnmapWindow (display, tooltip.win);
977 	    mySleep (500);
978 	    hideDrawer ();
979 	  }
980 	}
981       }
982       break;
983     case EnterNotify:
984       dbg_msg (1, "Enter window: 0x%x 0x%x, mode=%d, detail=%d\n",
985 	       (unsigned int)e.xcrossing.window,
986 	       drawer.win, e.xcrossing.mode, e.xcrossing.detail);
987       /* mode != 2 to avoid EnterNotify event emit after ButtonRelease
988 	 in windowed mode */
989       if (drawerOpened == 0 && config.showOnHover == 1 && e.xcrossing.mode != 2) {
990 	showDrawer ();
991       }
992       break;
993     case LeaveNotify:
994       dbg_msg (1, "Leave window: win=0x%x, mode=%d, detail=%d\n",
995 	       (unsigned int)e.xcrossing.window, e.xcrossing.mode,
996 	       e.xcrossing.detail);
997       /* if we leave drawer => unhighlight the last button highlighted and
998 	 unmap tooltip window */
999       if (config.highlight > 0 && btnIsPressed == 0 && drawerOpened == 1 &&
1000 	  e.xcrossing.window == drawer.win &&
1001 	  highlightCol >= 0 && highlightRow >= 0) {
1002 	XUnmapWindow (display, tooltip.win);
1003 	unhighlightBtn (btns[highlightCol][highlightRow].da.win);
1004 	highlightCol = highlightRow = -1;
1005       }
1006       /* mode = NotifyNormal:0, NotifyGrab:1, NotifyUngrab:2, NotifyWhileGrabbed:3
1007 	 mode != 1 to avoid LeaveNotify event emit before ButtonPress
1008 	 in windowed mode */
1009       if (drawerOpened == 1 && config.hideOnOut == 1 && e.xcrossing.mode != 1) {
1010 	if (! xEnterEventOrTimeout (&e, config.hideTimeout)) {
1011 	  hideDrawer ();
1012 	}
1013       }
1014       break;
1015     case DestroyNotify:
1016       quit (EXIT_SUCCESS);
1017     }
1018   }
1019 }
1020