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