1 /* wmsystemtray
2  * Copyright © 2009-2010  Brad Jorsch <anomie@users.sourceforge.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <signal.h>
25 #include <stdarg.h>
26 #include <sys/select.h>
27 
28 #include <X11/Xlib.h>
29 #include <X11/Xatom.h>
30 #include <X11/xpm.h>
31 #include <X11/extensions/Xfixes.h>
32 #include <X11/extensions/shape.h>
33 #include <X11/Xmu/SysUtil.h>
34 
35 #include "wmsystemtray.h"
36 #include "wmsystemtray.xpm"
37 #include "fdtray.h"
38 
39 /* Global variables */
40 volatile sig_atomic_t exitapp=False;
41 volatile sig_atomic_t sigusr=0;
42 
43 const char *PROGNAME=NULL;
44 Bool nonwmaker=False;
45 int iconsize=24;
46 Bool need_update=False;
47 
48 static char *display_name=NULL;
49 static char *geometry="";
50 static int debug_level=DEBUG_WARN;
51 static int icons_per_row=2;
52 static int icons_per_col=2;
53 static int num_windows=1;
54 static Bool id_windows=False;
55 static Bool point_messages=False;
56 static int fill_style=0;
57 static int arrow_style=0;
58 //static Bool no_messages=False;
59 
60 Display *display=NULL;
61 int screen=0;
62 Window root=None;
63 Window selwindow=None;
64 static Atom _NET_WM_PING;
65 static Atom WM_DELETE_WINDOW;
66 static Atom WM_PROTOCOLS;
67 
68 static Pixmap pixmap;
69 static Window *mainwin=NULL, *iconwin=NULL;
70 static struct trayicon *icons=NULL;
71 static int num_mapped_icons=0;
72 static int current_page=0;
73 static Window down_window=None;
74 static int down_button=0;
75 static long selwindow_mask = PropertyChangeMask;
76 static GC gc10x20, gc5x8;
77 static char *fgcolor = NULL, *bgcolor = NULL;
78 
79 #define NUM_TYPES 1
80 static struct trayfuncs *types[NUM_TYPES];
81 
get_type(int id)82 static struct trayfuncs *get_type(int id){
83     if(id < 0 || id >= NUM_TYPES){
84         warn(DEBUG_WARN, "Invalid tray icon type %d", id);
85         return NULL;
86     } else {
87         return types[id];
88     }
89 }
90 
91 /* X functions */
92 typedef int (*x_error_handler)(Display *, XErrorEvent *);
93 x_error_handler orig_x_error_handler = NULL;
94 static Bool x_error_occurred = False;
ignore_BadWindow(Display * d,XErrorEvent * e)95 static int ignore_BadWindow(Display *d, XErrorEvent *e){
96     if(e->error_code != BadWindow) orig_x_error_handler(d,e);
97     warn(DEBUG_INFO, "Ignoring BadWindow error");
98     x_error_occurred = True;
99     return 0;
100 }
101 
catch_BadWindow_errors()102 void *catch_BadWindow_errors(){
103     x_error_occurred = False;
104     if(!orig_x_error_handler){
105         orig_x_error_handler = XSetErrorHandler(&ignore_BadWindow);
106         return (void *)orig_x_error_handler;
107     } else {
108         return (void *)XSetErrorHandler(&ignore_BadWindow);
109     }
110 }
111 
uncatch_BadWindow_errors(void * v)112 Bool uncatch_BadWindow_errors(void *v){
113     XSync(display, False);
114     XSetErrorHandler((x_error_handler)v);
115     return x_error_occurred;
116 }
117 
get_X_time()118 Time get_X_time(){
119     XEvent ev;
120     XChangeProperty(display, selwindow, XA_WM_CLASS, XA_STRING, 8, PropModeAppend, NULL, 0);
121     XWindowEvent(display, selwindow, PropertyChangeMask, &ev);
122     warn(DEBUG_DEBUG, "X time is %ld", ev.xproperty.time);
123     return ev.xproperty.time;
124 }
125 
126 /* Icon handling */
is_icon_parent(Window w)127 Bool is_icon_parent(Window w){
128     for(int i=0; i<num_windows; i++){
129         if(iconwin[i]==w) return True;
130     }
131     return False;
132 }
133 
icon_add(int type,Window w,void * data)134 struct trayicon *icon_add(int type, Window w, void *data){
135     if(exitapp) return NULL;
136     if(!get_type(type)) return NULL;
137 
138     struct trayicon *icon = malloc(sizeof(struct trayicon));
139     if(!icon){
140         warn(DEBUG_ERROR, "Memory allocation failed");
141         return NULL;
142     }
143 
144     void *v=catch_BadWindow_errors();
145     XFixesChangeSaveSet(display, w, SetModeInsert, SaveSetRoot, SaveSetUnmap);
146     if(uncatch_BadWindow_errors(v)){
147         warn(DEBUG_INFO, "Tray icon %lx is invalid, not adding", w);
148         return NULL;
149     }
150 
151     warn(DEBUG_DEBUG, "Adding tray icon %lx of type %d", w, type);
152     icon->type = type;
153     icon->w = w;
154     icon->data = data;
155     icon->parent = None;
156     icon->x = 0; icon->y = 0;
157     icon->mapped = False;
158     icon->visible = False;
159     icon->next = NULL;
160     struct trayicon **p;
161     for(p = &icons; *p; p=&(*p)->next);
162     *p = icon;
163     need_update = True;
164     return icon;
165 }
166 
icon_remove(Window w)167 void icon_remove(Window w){
168     struct trayicon *i, **n;
169     n = &icons;
170     while(*n){
171         i = *n;
172         if(i->w == w){
173             *n = i->next;
174             warn(DEBUG_DEBUG, "Removing tray icon %lx of type %d", i->w, i->type);
175             struct trayfuncs *funcs = get_type(i->type);
176             if(funcs && funcs->remove_icon) funcs->remove_icon(i);
177             if(i->mapped){
178                 num_mapped_icons--;
179                 need_update = True;
180             }
181             free(i);
182         } else {
183             n = &i->next;
184         }
185     }
186 }
187 
icon_find(Window w)188 struct trayicon *icon_find(Window w){
189     for(struct trayicon *i=icons; i; i=i->next){
190         if(i->w == w) return i;
191     }
192     return NULL;
193 }
194 
icon_set_mapping(struct trayicon * icon,Bool map)195 Bool icon_set_mapping(struct trayicon *icon, Bool map){
196     if(icon->mapped == map) return True;
197 
198     warn(DEBUG_DEBUG, "%sapping tray icon %lx", map?"M":"Unm", icon->w);
199     icon->mapped = map;
200     num_mapped_icons += map?1:-1;
201     need_update = True;
202     return True;
203 }
204 
205 
icon_begin_message(Window w,int id,int length,int timeout)206 Bool icon_begin_message(Window w, int id, int length, int timeout){
207     warn(DEBUG_INFO, "begin_icon_message called for window %lx id %d (length=%d timeout=%d)", w, id, length, timeout);
208     // XXX
209     return False;
210 }
211 
icon_message_data(Window w,int id,char * data,int datalen)212 Bool icon_message_data(Window w, int id, char *data, int datalen){
213     warn(DEBUG_INFO, "icon_message_data called for window %lx id %d: %.*s", w, id, datalen, data);
214     // XXX
215     return False;
216 }
217 
218 //static void icon_message_show(struct iconmessage *msg){
219 //    // XXX
220 //    // If point_messages is true, look up the icon and if it has x and y then set the corresponding hints
221 //}
222 
icon_cancel_message(Window w,int id)223 void icon_cancel_message(Window w, int id){
224     warn(DEBUG_INFO, "cancel_icon_message called for window %lx id %d", w, id);
225     // XXX
226 }
227 
228 
229 /* Signal handler */
sighandler(int i)230 static void sighandler(int i){
231     switch(i){
232       case SIGINT:
233       case SIGQUIT:
234       case SIGKILL: // Useless, but...
235       case SIGPIPE:
236       case SIGTERM:
237       case SIGABRT:
238         exitapp=True;
239         break;
240       case SIGHUP:
241         // Reload something?
242         break;
243       case SIGUSR1:
244         sigusr--;
245         break;
246       case SIGUSR2:
247         sigusr++;
248         break;
249     }
250     signal(i,sighandler);
251 }
252 
253 /* Display functions */
update()254 static void update(){
255     int i, j, x, y, w;
256     int icons_per_page=icons_per_row*icons_per_col*num_windows;
257     char buf[42];
258     Window dummy;
259     int pages;
260 
261     need_update = False;
262 
263 redo:
264     pages = (num_mapped_icons-1)/icons_per_page+1;
265     if(current_page >= pages){
266         warn(DEBUG_DEBUG, "Page %d is empty!", current_page+1);
267         current_page=pages-1;
268     }
269     warn(DEBUG_DEBUG, "Updating display: page %d of %d", current_page+1, pages);
270 
271     for(i = 0; i<num_windows; i++){
272         warn(DEBUG_DEBUG, "Drawing window %d (%lx)", i, iconwin[i]);
273         if(arrow_style==0 ||
274            (arrow_style==1 && i==0) ||
275            (arrow_style==2 && i==num_windows-1)){
276             y=(current_page==0)?0:(iconwin[i]==down_window && down_button==-1)?20:10;
277             XCopyArea(display, pixmap, iconwin[i], gc5x8, 0,y, 15,10, 4,52);
278         }
279         if(arrow_style==0 ||
280            (arrow_style==1 && i==num_windows-1) ||
281            (arrow_style==2 && i==num_windows-1)){
282             sprintf(buf, "%d/%d", current_page+1, pages);
283             x = strlen(buf);
284             XClearArea(display, iconwin[i], 19,52, 25,8, False);
285             XDrawString(display, iconwin[i], gc5x8, (current_page>8)?19:24,60, buf, x);
286             y=(current_page+1>=pages)?0:(iconwin[i]==down_window && down_button==1)?20:10;
287             XCopyArea(display, pixmap, iconwin[i], gc5x8, 15,y, 15,10, 45,52);
288         }
289         if(id_windows){
290             sprintf(buf, "%d", i);
291             XDrawString(display, iconwin[i], gc10x20, 8,50, buf, strlen(buf));
292         }
293     }
294 
295     struct trayicon *icon = icons;
296     i=-current_page*icons_per_page;
297     while(icon){
298         void *v=catch_BadWindow_errors();
299         if(!icon->mapped || i<0 || i>=icons_per_page){
300             warn(DEBUG_DEBUG, "Tray icon %lx is not visible", icon->w);
301             icon->visible = False;
302             if(icon->parent == None){
303                 // Parent it somewhere
304                 warn(DEBUG_DEBUG, "Reparenting %lx to %lx", icon->w, iconwin[0]);
305                 XReparentWindow(display, icon->w, iconwin[0], 0, 0);
306                 icon->parent = iconwin[0];
307             }
308             XUnmapWindow(display, icon->w);
309         } else {
310             icon->visible = True;
311             j = i;
312             switch(fill_style){
313               case 0:
314                 w = j / (icons_per_row * icons_per_col);
315                 j = j % (icons_per_row * icons_per_col);
316                 y = j / icons_per_row;
317                 x = j % icons_per_row;
318                 break;
319               case 1:
320                 y = j / (icons_per_row * num_windows);
321                 j = j % (icons_per_row * num_windows);
322                 w = j / icons_per_row;
323                 x = j % icons_per_row;
324                 break;
325               default:
326                 x=0; y=0; w=0;
327                 break;
328             }
329             x=8+x*iconsize;
330             y=4+y*iconsize;
331             warn(DEBUG_DEBUG, "[%d] Tray icon %lx at %d %d,%d", i, icon->w, w, x, y);
332             if(icon->parent != iconwin[w]){
333                 warn(DEBUG_DEBUG, "Reparenting %lx to %lx", icon->w, iconwin[w]);
334                 XReparentWindow(display, icon->w, iconwin[w], x, y);
335                 icon->parent = iconwin[w];
336             }
337             XMoveResizeWindow(display, icon->w, x, y, iconsize, iconsize);
338             XClearArea(display, icon->w, 0, 0, 0, 0, True);
339             XMapRaised(display, icon->w);
340             XTranslateCoordinates(display, icon->w, root, iconsize/2, iconsize/2, &icon->x, &icon->y, &dummy);
341         }
342         if(uncatch_BadWindow_errors(v)){
343             warn(DEBUG_INFO, "Tray icon %lx is invalid, removing and restarting layout", icon->w);
344             icon_remove(icon->w);
345             goto redo;
346         }
347         if(icon->mapped) i++;
348         icon = icon->next;
349     }
350 
351     // XXX: if point_messages is true, can we re-point any notifications?
352 }
353 
selwindow_add_mask(long mask)354 void selwindow_add_mask(long mask){
355     selwindow_mask |= mask;
356     warn(DEBUG_DEBUG, "selwindow mask is now %lx", selwindow_mask);
357     XSelectInput(display, selwindow, selwindow_mask);
358 }
359 
create_dock_windows(int argc,char ** argv)360 static void create_dock_windows(int argc, char **argv){
361     XClassHint     *classHint;
362     XWMHints       *wmHints;
363     XSizeHints     *sizeHints;
364     Atom           wmProtocols[2];
365     char           hostname[256];
366     XTextProperty  machineName;
367     XRectangle     rects[2] = {
368         { .x = 8, .y = 4, .width = 48, .height = 48 },
369         { .x = 4, .y = 52, .width = 56, .height = 10 }
370     };
371     Atom           _NET_WM_PID;
372     XGCValues      gcv;
373     unsigned long  gcm;
374     char           buf[1024];
375     int            err, dummy=0, pid;
376     Pixel          fgpix, bgpix;
377 
378     pid = getpid();
379     warn(DEBUG_DEBUG, "My pid is %d", pid);
380 
381     warn(DEBUG_INFO, "Opening display '%s'", XDisplayName(display_name));
382     if(!(display = XOpenDisplay(display_name)))
383         die("Can't open display %s", XDisplayName(display_name));
384     screen = DefaultScreen(display);
385     root = RootWindow(display, screen);
386 
387     fgpix = BlackPixel(display, screen);
388     bgpix = WhitePixel(display, screen);
389     if(fgcolor != NULL){
390         warn(DEBUG_DEBUG, "Allocating colormap entry for fgcolor '%s'", fgcolor);
391         XColor color;
392         XWindowAttributes a;
393         XGetWindowAttributes(display, root, &a);
394         color.pixel = 0;
395         if(!XParseColor(display, a.colormap, fgcolor, &color)){
396             warn(DEBUG_ERROR, "Specified foreground color '%s' could not be parsed, using Black", fgcolor);
397             fgcolor = NULL;
398         } else if(!XAllocColor(display, a.colormap, &color)) {
399             warn(DEBUG_ERROR, "Cannot allocate colormap entry for foreground color '%s', using Black", fgcolor);
400             fgcolor = NULL;
401         } else {
402             fgpix = color.pixel;
403         }
404     }
405     if(bgcolor != NULL){
406         warn(DEBUG_DEBUG, "Allocating colormap entry for bgcolor '%s'", bgcolor);
407         XColor color;
408         XWindowAttributes a;
409         XGetWindowAttributes(display, root, &a);
410         color.pixel = 0;
411         if(!XParseColor(display, a.colormap, bgcolor, &color)){
412             warn(DEBUG_ERROR, "Specified background color '%s' could not be parsed, using default", bgcolor);
413             bgcolor = NULL;
414         } else if(!XAllocColor(display, a.colormap, &color)) {
415             warn(DEBUG_ERROR, "Cannot allocate colormap entry for background color '%s', using default", bgcolor);
416             bgcolor = NULL;
417         } else {
418             bgpix = color.pixel;
419         }
420     }
421 
422     warn(DEBUG_DEBUG, "Interning atoms");
423     _NET_WM_PING = XInternAtom(display, "_NET_WM_PING", False);
424     _NET_WM_PID = XInternAtom(display, "_NET_WM_PID", False);
425     WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", False);
426     WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False);
427 
428     /* Load images */
429     warn(DEBUG_DEBUG, "Loading XPM");
430     err = XpmCreatePixmapFromData(display, root, wmsystemtray_xpm, &pixmap, NULL, NULL);
431     if(err != XpmSuccess) die("Could not load xpm (%d)", err);
432 
433     /* Create hints */
434     warn(DEBUG_DEBUG, "Allocating window hints");
435     sizeHints = XAllocSizeHints();
436     classHint = XAllocClassHint();
437     wmHints = XAllocWMHints();
438     if(!sizeHints || !classHint || !wmHints) die("Memory allocation failed");
439 
440     sizeHints->flags = USSize | USPosition | PSize | PBaseSize | PMinSize | PMaxSize;
441     sizeHints->x = 0;
442     sizeHints->y = 0;
443     sizeHints->width = 64;
444     sizeHints->height = 64;
445     sizeHints->base_width = sizeHints->width;
446     sizeHints->base_height = sizeHints->height;
447     sizeHints->min_width = 64;
448     sizeHints->min_height = 64;
449     sizeHints->max_width = 64;
450     sizeHints->max_height = 64;
451     warn(DEBUG_DEBUG, "Parsing geometry string '%s'", geometry);
452     XWMGeometry(display, screen, geometry, NULL, 1, sizeHints,
453                 &sizeHints->x, &sizeHints->y, &sizeHints->width, &sizeHints->height, &dummy);
454     sizeHints->base_width = sizeHints->width;
455     sizeHints->base_height = sizeHints->height;
456     warn(DEBUG_DEBUG,"%d %d %d %d", sizeHints->x, sizeHints->y, sizeHints->base_width, sizeHints->base_height);
457 
458     classHint->res_class = "wmsystemtray";
459     classHint->res_name = buf;
460 
461     if(nonwmaker){
462         wmHints->flags = StateHint | WindowGroupHint;
463         wmHints->initial_state = NormalState;
464     } else {
465         wmHints->flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
466         wmHints->initial_state = WithdrawnState;
467         wmHints->icon_x = sizeHints->x;
468         wmHints->icon_y = sizeHints->y;
469     }
470 
471     wmProtocols[0] = _NET_WM_PING;
472     wmProtocols[1] = WM_DELETE_WINDOW;
473 
474     machineName.encoding = XA_STRING;
475     machineName.format = 8;
476     machineName.nitems = XmuGetHostname(hostname, sizeof(hostname));
477     machineName.value = (unsigned char *) hostname;
478 
479     /* Create windows */
480     warn(DEBUG_DEBUG, "Allocating space for %d windows", num_windows);
481     mainwin = malloc(num_windows * sizeof(*mainwin));
482     iconwin = nonwmaker?mainwin:malloc(num_windows * sizeof(*iconwin));
483     if(!mainwin || !iconwin) die("Memory allocation failed");
484 
485     warn(DEBUG_DEBUG, "Creating selection window");
486     selwindow = XCreateSimpleWindow(display, root, -1,-1,1,1,0,0,0);
487     XSelectInput(display, selwindow, selwindow_mask);
488     strncpy(buf, "selwindow", sizeof(buf));
489     XSetClassHint(display, selwindow, classHint);
490     XStoreName(display, selwindow, PROGNAME);
491     XSetCommand(display, selwindow, argv, argc);
492     XSetWMProtocols(display, selwindow, wmProtocols, 2);
493     XSetWMClientMachine(display, selwindow, &machineName);
494     XChangeProperty(display, selwindow, _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
495     XShapeCombineRectangles(display, selwindow, ShapeBounding, 0, 0, NULL, 0, ShapeSet, YXBanded);
496 
497     warn(DEBUG_DEBUG, "Creating GCs");
498     gcm = GCForeground | GCBackground | GCGraphicsExposures | GCFont;
499     gcv.foreground = fgpix;
500     gcv.background = bgpix;
501     gcv.graphics_exposures = True;
502     gcv.font = XLoadFont(display, "10x20");
503     gc10x20 = XCreateGC(display, root, gcm, &gcv);
504     gcv.font = XLoadFont(display, "5x8");
505     gc5x8 = XCreateGC(display, root, gcm, &gcv);
506 
507     for(int i=0; i<num_windows; i++){
508         if(nonwmaker){
509             iconwin[i] = XCreateSimpleWindow(display, root, sizeHints->x, sizeHints->y, sizeHints->width, sizeHints->height, 1, fgpix, bgpix);
510         } else {
511             mainwin[i] = XCreateSimpleWindow(display, root, -1,-1,1,1,0,0,0);
512             iconwin[i] = XCreateSimpleWindow(display, mainwin[i], sizeHints->x, sizeHints->y, sizeHints->width, sizeHints->height, 1, fgpix, bgpix);
513         }
514         warn(DEBUG_DEBUG, "Dock window #%d is %lx", i, iconwin[i]);
515 
516         XSetWMNormalHints(display, mainwin[i], sizeHints);
517         XSetWMNormalHints(display, iconwin[i], sizeHints);
518 
519         snprintf(buf, sizeof(buf), "%s%d", PROGNAME, i);
520         XSetClassHint(display, mainwin[i], classHint);
521         XSetClassHint(display, iconwin[i], classHint);
522 
523         XSelectInput(display, iconwin[i], ButtonPressMask | ExposureMask | ButtonReleaseMask | StructureNotifyMask | VisibilityChangeMask);
524 
525         XStoreName(display, mainwin[i], PROGNAME);
526         XStoreName(display, iconwin[i], PROGNAME);
527         XSetCommand(display, mainwin[i], argv, argc);
528         XSetCommand(display, iconwin[i], argv, argc);
529 
530         if(!nonwmaker) wmHints->icon_window = iconwin[i];
531         wmHints->window_group = mainwin[i];
532         XSetWMHints(display, mainwin[i], wmHints);
533 
534         XSetWMProtocols(display, mainwin[i], wmProtocols, 2);
535         XSetWMProtocols(display, iconwin[i], wmProtocols, 2);
536 
537         XSetWMClientMachine(display, mainwin[i], &machineName);
538         XChangeProperty(display, mainwin[i], _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
539         XSetWMClientMachine(display, iconwin[i], &machineName);
540         XChangeProperty(display, iconwin[i], _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
541 
542         if(!nonwmaker || bgcolor == NULL){
543             XSetWindowBackgroundPixmap(display, iconwin[i], ParentRelative);
544             XShapeCombineRectangles(display, iconwin[i], ShapeBounding, 0, 0, rects, 2, ShapeSet, YXBanded);
545         }
546 
547         XMapWindow(display, mainwin[i]);
548     }
549 
550     warn(DEBUG_DEBUG, "Freeing X hints");
551     XFree(wmHints);
552     XFree(sizeHints);
553     XFree(classHint);
554 
555     update();
556 }
557 
cleanup()558 static void cleanup(){
559     warn(DEBUG_INFO, "Cleaning up for exit");
560     for(int i=0; i<NUM_TYPES; i++){
561         if(types[i] && types[i]->closing) types[i]->closing();
562     }
563     warn(DEBUG_DEBUG, "Removing all icons");
564     while(icons) icon_remove(icons->w);
565     warn(DEBUG_DEBUG, "Deinitializing protocols");
566     for(int i=0; i<NUM_TYPES; i++){
567         if(types[i] && types[i]->deinit) types[i]->deinit();
568     }
569     warn(DEBUG_DEBUG, "Closing display");
570     if(mainwin) free(mainwin);
571     if(iconwin && !nonwmaker) free(iconwin);
572     if(display) XCloseDisplay(display);
573 }
574 
575 /* Error and warning functions */
warn(int level,char * fmt,...)576 void warn(int level, char *fmt, ...){
577     va_list ap;
578 
579     if(debug_level<level) return;
580     va_start(ap, fmt);
581     fprintf(stderr, "%s: ", PROGNAME);
582     vfprintf(stderr, fmt, ap);
583     fprintf(stderr, "\n");
584     va_end(ap);
585 }
586 
die(char * fmt,...)587 void die(char *fmt, ...){
588     va_list ap;
589 
590     cleanup();
591 
592     va_start(ap, fmt);
593     fprintf(stderr, "%s: ", PROGNAME);
594     vfprintf(stderr, fmt, ap);
595     fprintf(stderr, "\n");
596     va_end(ap);
597     exit(1);
598 }
599 
600 static void usage(int exitcode) __attribute__ ((noreturn));
usage(int exitcode)601 static void usage(int exitcode){
602     fprintf(stderr, "USAGE: %s [<options>]\n", PROGNAME);
603     fprintf(stderr, "  -display <display>    X display to connect to\n");
604     fprintf(stderr, "  -geometry <geom>      Initial window geometry\n");
605     fprintf(stderr, "      --help            This message\n");
606     fprintf(stderr, "  -V, --version         Print the version number and exit\n");
607     fprintf(stderr, "  -v, --verbose         Print more messages (can be repeated)\n");
608     fprintf(stderr, "  -q, --quiet           Print fewer messages (can be repeated)\n");
609     fprintf(stderr, "  -s, --small           Use small (16x16) icons\n");
610     fprintf(stderr, "  -w, --windows <n>     Number of dock windows to create\n");
611     fprintf(stderr, "      --id-windows      Identify the individual windows\n");
612 //    fprintf(stderr, "  -P, --point-messages  If an icon sends a popup message through the systray\n                        protocol, have the notification point to the icon");
613     fprintf(stderr, "      --fill-rows       Fill the top row of all windows first\n");
614     fprintf(stderr, "      --arrows <place>  How to place the arrows: all, horizontal, vertical\n");
615     fprintf(stderr, "  -c, --fgcolor <color> Text color used, default is black\n");
616     fprintf(stderr, "      --bgcolor <color> The window background color in non-Window Maker mode\n");
617     fprintf(stderr, "      --non-wmaker      Use in a non-Window Maker window manager\n");
618     fprintf(stderr, "\n");
619     exit(exitcode);
620 }
621 
parse_args(int argc,char ** argv)622 static int parse_args(int argc, char **argv){
623     char *c;
624 
625     if((PROGNAME=strrchr(argv[0], '/'))==NULL || !PROGNAME[1])
626         PROGNAME=argv[0];
627     else PROGNAME++;
628 
629     /* Parse command line args */
630     int j=1;
631     for(int i=1; i<argc; i++){
632         argv[j++]=argv[i];
633         if(!strcmp(argv[i],"-display") || !strcmp(argv[i],"--display")){
634             if(i+1 >= argc) die("%s requires an argument", argv[i]);
635             display_name=argv[++i];
636             argv[j++]=argv[i];
637         } else if(!strcmp(argv[i],"-geometry") || !strcmp(argv[i],"--geometry")){
638             if(i+1 >= argc) die("%s requires an argument", argv[i]);
639             geometry=argv[++i];
640             argv[j++]=argv[i];
641         } else if(!strcmp(argv[i],"--help")){
642             usage(0);
643         } else if(!strcmp(argv[i],"-V") || !strcmp(argv[i],"--version")){
644             printf("%s\n", VERSION);
645             exit(0);
646         } else if(!strcmp(argv[i],"-v") || !strcmp(argv[i],"--verbose")){
647             debug_level++;
648         } else if(!strcmp(argv[i],"-q") || !strcmp(argv[i],"--quiet")){
649             debug_level--;
650         } else if(!strcmp(argv[i],"--non-wmaker")){
651             nonwmaker=True;
652         } else if(!strcmp(argv[i],"-s") || !strcmp(argv[i],"--small")){
653             iconsize=16;
654             icons_per_row=3;
655             icons_per_col=3;
656         } else if(!strcmp(argv[i],"-w") || !strcmp(argv[i],"--windows")){
657             if(i+1 >= argc) die("%s requires an argument", argv[i]);
658             num_windows=strtol(argv[i+1],&c,0);
659             if(num_windows<1 || *c) die("%s requires a positive integer", argv[i]);
660             i++;
661             argv[j++]=argv[i];
662         } else if(!strcmp(argv[i],"--id-windows")){
663             id_windows=True;
664             j--;
665         } else if(!strcmp(argv[i],"-P") || !strcmp(argv[i],"--point-messages")){
666             point_messages=True;
667         } else if(!strcmp(argv[i],"--fill-rows")){
668             fill_style=1;
669         } else if(!strcmp(argv[i],"--arrows")){
670             if(i+1 >= argc) die("%s requires an argument", argv[i]);
671             if(!strcmp(argv[i+1],"all")){
672                 arrow_style=0;
673             } else if(!strcmp(argv[i+1],"horizontal")){
674                 arrow_style=1;
675             } else if(!strcmp(argv[i+1],"vertical")){
676                 arrow_style=2;
677             } else {
678                 die("Invalid value for %s", argv[i]);
679             }
680             i++;
681             argv[j++]=argv[i];
682         } else if(!strcmp(argv[i],"-c") || !strcmp(argv[i],"--fgcolor")){
683             fgcolor = argv[++i];
684             argv[j++]=argv[i];
685         } else if(!strcmp(argv[i],"--bgcolor")){
686             bgcolor = argv[++i];
687             argv[j++]=argv[i];
688         } else {
689             debug_level=DEBUG_ERROR;
690             warn(DEBUG_ERROR, "Unrecognized command line argument %s", argv[i]);
691             usage(1);
692         }
693     }
694     return j;
695 }
696 
main(int argc,char * argv[])697 int main(int argc, char *argv[]){
698     warn(DEBUG_DEBUG, "Parsing args");
699     argc = parse_args(argc, argv);
700 
701     warn(DEBUG_DEBUG, "Creating windows");
702     create_dock_windows(argc, argv);
703 
704     warn(DEBUG_DEBUG, "Initializing protocols");
705     for(int i=0; i<NUM_TYPES; i++) types[i]=NULL;
706     if(!(types[0] = fdtray_init(0, argc, argv)))
707         die("Could not initialize the freedesktop.org tray protocol");
708 
709     warn(DEBUG_DEBUG, "Setting signal handlers");
710     signal(SIGINT, sighandler);
711     signal(SIGQUIT, sighandler);
712     signal(SIGKILL, sighandler); // Useless, but...
713     signal(SIGPIPE, sighandler);
714     signal(SIGTERM, sighandler);
715     signal(SIGABRT, sighandler);
716     signal(SIGHUP, sighandler);
717     signal(SIGUSR1, sighandler);
718     signal(SIGUSR2, sighandler);
719 
720     XEvent ev;
721     int fd;
722     fd_set rfds;
723     warn(DEBUG_DEBUG, "Entering main loop");
724     while(!exitapp){
725         if (sigusr) {
726             warn(DEBUG_INFO, "Handling SIGUSR1/SIGUSR2, delta = %d", sigusr);
727             int pages = (num_mapped_icons-1)/icons_per_row/icons_per_col/num_windows + 1;
728             current_page += sigusr;
729             sigusr = 0;
730             while(current_page < 0) current_page += pages;
731             while(current_page >= pages) current_page -= pages;
732             need_update=True;
733         }
734 
735         while(XPending(display)){
736             struct trayicon *icon = NULL;
737             XNextEvent(display, &ev);
738             warn(DEBUG_DEBUG, "Got X event %d", ev.type);
739             switch(ev.type){
740               case GraphicsExpose:
741               case Expose:
742               case MapRequest:
743                 need_update=True;
744                 break;
745 
746               case MapNotify:
747                 icon = icon_find(ev.xmap.window);
748                 if(icon && !icon->visible){
749                     warn(DEBUG_WARN, "A poorly-behaved application tried to map window %lx!", ev.xmap.window);
750                     need_update=True;
751                 }
752                 break;
753 
754               case UnmapNotify:
755                 icon = icon_find(ev.xunmap.window);
756                 if(icon && icon->visible){
757                     warn(DEBUG_WARN, "A poorly-behaved application tried to unmap window %lx!", ev.xmap.window);
758                     need_update=True;
759                 }
760                 break;
761 
762               case DestroyNotify:
763                 if(exitapp) break;
764                 if(selwindow==ev.xdestroywindow.window){
765                     warn(DEBUG_WARN, "Selection window %lx destroyed!", ev.xdestroywindow.window);
766                     exitapp=1;
767                 }
768                 for(int i=0; !exitapp && i<num_windows; i++){
769                     if(mainwin[i]==ev.xdestroywindow.window || iconwin[i]==ev.xdestroywindow.window){
770                         warn(DEBUG_WARN, "Window %lx destroyed!", ev.xdestroywindow.window);
771                         exitapp=1;
772                     }
773                 }
774                 break;
775 
776               case ButtonPress:
777                 switch (ev.xbutton.button) {
778                   case 1:
779                     down_window = ev.xbutton.window;
780                     down_button = 0;
781                     if(ev.xbutton.y >= 53 && ev.xbutton.y < 61){
782                         if(ev.xbutton.x >= 5 && ev.xbutton.x < 18){
783                             warn(DEBUG_INFO, "Left button mouse down");
784                             down_button = -1;
785                         } else if(ev.xbutton.x >= 46 && ev.xbutton.x < 59){
786                             warn(DEBUG_INFO, "Right button mouse down");
787                             down_button = 1;
788                         }
789                     }
790                     need_update = True;
791                     break;
792                   case 4:
793                   case 6:
794                     warn(DEBUG_INFO, "Left/Up scroll wheel");
795                     if(current_page > 0){
796                         current_page--;
797                         need_update=True;
798                     }
799                     break;
800                   case 5:
801                   case 7:
802                     warn(DEBUG_INFO, "Right/Down scroll wheel");
803                     if(current_page < (num_mapped_icons-1)/icons_per_row/icons_per_col/num_windows){
804                         current_page++;
805                         need_update=True;
806                     }
807                     break;
808                 }
809                 break;
810 
811               case ButtonRelease:
812                 switch (ev.xbutton.button) {
813                   case 1:
814                     if(ev.xbutton.y >= 53 && ev.xbutton.y < 61){
815                         if(ev.xbutton.x >= 5 && ev.xbutton.x < 18 && down_button == -1){
816                             warn(DEBUG_INFO, "Left button mouse up");
817                             if(current_page > 0){
818                                 current_page--;
819                                 need_update=True;
820                             }
821                         } else if(ev.xbutton.x >= 46 && ev.xbutton.x < 59 && down_button == 1){
822                             warn(DEBUG_INFO, "Right button mouse up");
823                             if(current_page < (num_mapped_icons-1)/icons_per_row/icons_per_col/num_windows){
824                                 current_page++;
825                                 need_update=True;
826                             }
827                         }
828                     }
829                     down_window = None;
830                     down_button = 0;
831                     need_update = True;
832                     break;
833                 }
834                 break;
835 
836               case ClientMessage:
837                 if(ev.xclient.message_type == WM_PROTOCOLS && ev.xclient.format == 32){
838                     if(ev.xclient.data.l[0] == _NET_WM_PING){
839                         warn(DEBUG_DEBUG, "_NET_WM_PING!");
840                         ev.xclient.window = root;
841                         XSendEvent(display, root, False, SubstructureNotifyMask|SubstructureRedirectMask, &ev);
842                     } else if(ev.xclient.data.l[0] == WM_DELETE_WINDOW){
843                         warn(DEBUG_DEBUG, "WM_DELETE_WINDOW called for %lx!", ev.xclient.window);
844                         exitapp=1;
845                     }
846                 }
847                 break;
848             }
849             for(int i=0; i<NUM_TYPES; i++){
850                 if(types[i]->handle_event) types[i]->handle_event(&ev);
851             }
852         }
853         if(exitapp) break;
854         warn(DEBUG_DEBUG, "Need update? %s", need_update?"Yes":"No");
855         if(need_update) update();
856 
857         if(XPending(display)) continue;
858         fd=ConnectionNumber(display);
859         FD_ZERO(&rfds);
860         FD_SET(fd, &rfds);
861         select(fd+1, &rfds, NULL, NULL, NULL);
862     }
863     warn(DEBUG_DEBUG, "Main loop ended");
864 
865     cleanup();
866 
867     warn(DEBUG_DEBUG, "Exiting app");
868     return 0;
869 }
870