set _NET_WM_ICON_GEOMETRY
[mikachu/rspanel.git] / rspanel.c
1
2 /**********************************************************
3  ** Rather Small Panel 0.8beta1 Copyright (c) 2006       **
4  ** By Mikael Magnusson                                  **
5  ** See file COPYING for license details.                **
6  **********************************************************/
7
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14
15 #include <X11/Xlib.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18 #include <iconv.h>
19
20 #undef HAVE_XPM
21 #ifdef HAVE_XPM
22 #include <X11/xpm.h>
23 #include "icon.xpm"
24 #endif
25
26 /* you can edit these */
27 #define MAX_TASK_WIDTH 500
28 #define ICONWIDTH 16
29 #define ICONHEIGHT 16
30 #define WINHEIGHT 24
31 #ifndef MIKACHU
32 #        define WINWIDTH 1280
33 #else
34 int WINWIDTH = 827;
35 #endif
36
37 /* don't edit these */
38 #define TEXTPAD 6
39 #define GRILL_WIDTH 10
40
41 #include <openbox/render.h>
42 #include <openbox/theme.h>
43
44 #include "rspanel.h"
45
46 Window rspanelwin;
47 Display *dd;
48 Window root_win;
49 GC fore_gc;
50 taskbar tb;
51 int scr_screen;
52 int scr_depth;
53 int scr_width;
54 int scr_height;
55 int text_y;
56 int pager_size;
57
58 RrInstance *inst;
59 RrAppearance *background;
60 RrAppearance *focused_task;
61 RrAppearance *unfocused_task;
62 RrAppearance *normal_text;
63 RrAppearance *iconified_text;
64 RrAppearance *shaded_text;
65 RrAppearance *focused_text;
66
67 /* we draw stuff to this guy then set him as a window
68  * background pixmap, yay no flickering! */
69 Pixmap bgpixmap;
70
71 char *atom_names[] = {
72     /* clients */
73     "KWM_WIN_ICON",
74     "WM_STATE",
75     "_MOTIF_WM_HINTS",
76     "_NET_WM_STATE",
77     "_NET_WM_STATE_SKIP_TASKBAR",
78     "_NET_WM_STATE_SHADED",
79     "_NET_WM_STATE_BELOW",
80     "_NET_WM_STATE_HIDDEN",
81     "_NET_WM_DESKTOP",
82     "_NET_WM_WINDOW_TYPE",
83     "_NET_WM_WINDOW_TYPE_DOCK",
84     "_NET_WM_STRUT",
85     "_WIN_HINTS",
86     /* root */
87     "_NET_CLIENT_LIST",
88     "_NET_CLIENT_LIST_STACKING",
89     "_NET_NUMBER_OF_DESKTOPS",
90     "_NET_CURRENT_DESKTOP",
91     "_OB_WM_ACTION",
92     "_NET_WM_NAME",
93     "UTF8_STRING",
94     "_NET_ACTIVE_WINDOW",
95     "_NET_RESTACK_WINDOW",
96     "_OB_FOCUS",
97     "_NET_WM_ICON_GEOMETRY"
98 };
99
100 typedef enum {
101 KWM_WIN_ICON,
102 WM_STATE,
103 _MOTIF_WM_HINTS,
104 _NET_WM_STATE,
105 _NET_WM_STATE_SKIP_TASKBAR,
106 _NET_WM_STATE_SHADED,
107 _NET_WM_STATE_BELOW,
108 _NET_WM_STATE_HIDDEN,
109 _NET_WM_DESKTOP,
110 _NET_WM_WINDOW_TYPE,
111 _NET_WM_WINDOW_TYPE_DOCK,
112 _NET_WM_STRUT,
113 _WIN_HINTS,
114 _NET_CLIENT_LIST,
115 _NET_CLIENT_LIST_STACKING,
116 _NET_NUMBER_OF_DESKTOPS,
117 _NET_CURRENT_DESKTOP,
118 _OB_WM_ACTION,
119 _NET_WM_NAME,
120 STRING_UTF8,
121 _NET_ACTIVE_WINDOW,
122 _NET_RESTACK_WINDOW,
123 _OB_FOCUS,
124 _NET_WM_ICON_GEOMETRY,
125 ATOM_COUNT,
126 } atom_t;
127
128 Atom atoms[ATOM_COUNT];
129
130 enum {
131     REMOVE = 0,
132     ADD,
133     TOGGLE
134 };
135
136 static void *get_prop_data(Window win, Atom prop, Atom type, int *items)
137 {
138     Atom type_ret;
139     int format_ret;
140     unsigned long items_ret;
141     unsigned long after_ret;
142     unsigned char *prop_data;
143
144     prop_data = 0;
145
146     XGetWindowProperty(dd, win, prop, 0, 0x7fffffff, False, type, &type_ret,
147                        &format_ret, &items_ret, &after_ret, &prop_data);
148     if (items)
149         *items = items_ret;
150
151     return prop_data;
152 }
153
154 static int generic_get_int(Window win, Atom at)
155 {
156     int num = 0;
157     unsigned long *data;
158
159     data = get_prop_data(win, at, XA_CARDINAL, 0);
160     if (data) {
161         num = *data;
162         XFree(data);
163     }
164     return num;
165 }
166
167 static int find_desktop(Window win)
168 {
169     return generic_get_int(win, atoms[_NET_WM_DESKTOP]);
170 }
171
172 static int find_state(Window win, atom_t atom)
173 {
174     unsigned long *data;
175     int ret = 0;
176     int num;
177
178     data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
179     if (data) {
180         while (num--) {
181             if ((data[num]) == atoms[atom])
182                 ret = 1;
183         }
184         XFree(data);
185     }
186     return ret;
187 }
188
189 /* this naming is very confusing :) */
190 static int is_hidden(Window win)
191 {
192     return find_state(win, _NET_WM_STATE_SKIP_TASKBAR);
193 }
194
195 static int is_iconified(Window win)
196 {
197     return find_state(win, _NET_WM_STATE_HIDDEN);
198 }
199
200 static int is_shaded(Window win)
201 {
202     return find_state(win, _NET_WM_STATE_SHADED);
203 }
204
205 static int get_current_desktop(void)
206 {
207     return generic_get_int(root_win, atoms[_NET_CURRENT_DESKTOP]);
208 }
209
210 #ifdef PAGER
211 static int get_number_of_desktops(void)
212 {
213     return generic_get_int(root_win, atoms[_NET_NUMBER_OF_DESKTOPS]);
214 }
215 #endif
216
217 static void add_task(Window win, int focus)
218 {
219     task *tk, *list;
220     int desk;
221
222     if (win == tb.win)
223         return;
224
225     /* is this window on a different desktop? */
226     desk = find_desktop(win);
227     if ((desk != 0xffffffff && tb.my_desktop != desk) || is_hidden(win))
228         return;
229
230     tk = calloc(1, sizeof(task));
231     tk->win = win;
232     tk->focused = focus;
233     tk->name = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
234                get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
235 //    tk->name = get_prop_data(win, XA_WM_NAME, XA_STRING, 0);
236     //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
237     tk->iconified = is_iconified(win);
238     tk->shaded = is_shaded(win);
239
240     XSelectInput(dd, win, PropertyChangeMask | StructureNotifyMask);
241
242     /* now append it to our linked list */
243     tb.num_tasks++;
244
245     list = tb.task_list;
246     if (!list) {
247         tb.task_list = tk;
248         return;
249     }
250     while (1) {
251         if (!list->next) {
252             list->next = tk;
253             return;
254         }
255         list = list->next;
256     }
257 }
258
259 static void set_prop(Window win, Atom at, Atom type, long val)
260 {
261     XChangeProperty(dd, win, at, type, 32,
262                     PropModeReplace, (unsigned char *)&val, 1);
263 }
264
265 static Window gui_create_taskbar(void)
266 {
267     Window win;
268     XSizeHints size_hints;
269     XSetWindowAttributes att;
270     XClassHint xclhints;
271
272     att.event_mask = ButtonPressMask;
273
274     tb.x = 0;
275     tb.y = scr_height - WINHEIGHT;
276
277     win = rspanelwin = XCreateWindow(/* display  */ dd,
278                                      /* parent   */ root_win,
279                                      /* x        */ tb.x,
280                                      /* y        */ tb.y,
281 /* XXX Maybe just use scr_width here? */
282                                      /* width    */ WINWIDTH,
283                                      /* height   */ WINHEIGHT,
284                                      /* border   */ 0,
285                                      /* depth    */ CopyFromParent,
286                                      /* class    */ InputOutput,
287                                      /* visual   */ CopyFromParent,
288                                      /*value mask*/ CWEventMask,
289                                      /* attribs  */ &att);
290
291     /* reside on ALL desktops */
292     set_prop(win, atoms[_NET_WM_DESKTOP], XA_CARDINAL, 0xFFFFFFFF);
293     set_prop(win, atoms[_NET_WM_WINDOW_TYPE], XA_ATOM,
294              atoms[_NET_WM_WINDOW_TYPE_DOCK]);
295     set_prop(win, atoms[_NET_WM_STATE], XA_ATOM, atoms[_NET_WM_STATE_BELOW]);
296     XChangeProperty(dd, win, XA_WM_NAME, XA_STRING, 8, PropModeReplace,
297                     (unsigned char *)"rspanel", 7);
298
299     /* make sure the WM obays our window position */
300     size_hints.flags = PPosition;
301     /*XSetWMNormalHints (dd, win, &size_hints); */
302     XChangeProperty(dd, win, XA_WM_NORMAL_HINTS, XA_WM_SIZE_HINTS, 32,
303                     PropModeReplace, (unsigned char *)&size_hints,
304                     sizeof(XSizeHints) / 4);
305
306     xclhints.res_name = "rspanel";
307     xclhints.res_class = "RSPanel";
308     XSetClassHint(dd, win, &xclhints);
309
310     XMapWindow(dd, win);
311
312     return win;
313 }
314
315 static void gui_init(void)
316 {
317     XGCValues gcv;
318
319     gcv.graphics_exposures = False;
320
321     fore_gc = XCreateGC(dd, root_win, GCGraphicsExposures, &gcv);
322
323     inst = RrInstanceNew(dd, scr_screen);
324     background = RrAppearanceNew(inst, 0);
325     focused_task = RrAppearanceNew(inst, 0);
326     unfocused_task = RrAppearanceNew(inst, 0);
327     normal_text = RrAppearanceNew(inst, 1);
328
329     background->surface.grad = RR_SURFACE_DIAGONAL;
330     background->surface.primary = RrColorNew(inst, 170, 170, 190);
331     background->surface.secondary = RrColorNew(inst, 100, 100, 160);
332
333     unfocused_task->surface.parent = background;
334     unfocused_task->surface.grad = RR_SURFACE_PARENTREL;
335     unfocused_task->surface.relief = RR_RELIEF_SUNKEN;
336
337     focused_task->surface.parent = background;
338     focused_task->surface.grad = RR_SURFACE_CROSS_DIAGONAL;
339     focused_task->surface.secondary = RrColorNew(inst, 70, 80, 110);
340     focused_task->surface.primary = RrColorNew(inst, 130, 160, 250);
341     focused_task->surface.relief = RR_RELIEF_RAISED;
342
343     normal_text->surface.grad = RR_SURFACE_PARENTREL;
344     normal_text->texture[0].type = RR_TEXTURE_TEXT;
345     normal_text->texture[0].data.text.font = RrFontOpenDefault(inst);
346     normal_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
347
348     iconified_text = RrAppearanceCopy(normal_text);
349     normal_text->texture[0].data.text.color = RrColorNew(inst, 20, 20, 40);
350     focused_text = RrAppearanceCopy(normal_text);
351     iconified_text->texture[0].data.text.shadow_offset_x = 2;
352     iconified_text->texture[0].data.text.shadow_offset_y = 2;
353     iconified_text->texture[0].data.text.shadow_alpha = 100;
354     iconified_text->texture[0].data.text.shadow_color = RrColorNew(inst, 0, 0, 0);
355     shaded_text = RrAppearanceCopy(normal_text);
356     shaded_text->texture[0].data.text.color = RrColorNew(inst, 50, 60, 90);
357     iconified_text->texture[0].data.text.color = RrColorNew(inst, 200, 200, 200);
358     focused_text->texture[0].data.text.color = RrColorNew(inst, 230, 230, 255);
359 }
360
361 #define PADDING 4
362 static void gui_draw_task(task *tk, int redraw)
363 {
364     RrAppearance *a;
365     RrAppearance *b;
366     if (tk->iconified)
367         a = iconified_text;
368     else if (tk->shaded)
369         a = shaded_text;
370     else if (tk->focused)
371         a = focused_text;
372     else
373         a = normal_text;
374     b = tk->focused ? focused_task : unfocused_task;
375     a->surface.parent = b;
376     a->surface.parentx = PADDING;
377     a->texture[0].data.text.string = tk->name;
378     b->surface.parentx = tk->pos_x;
379     RrPaintPixmap(b, tk->width, WINHEIGHT);
380     RrPaintPixmap(a, tk->width-2*PADDING, WINHEIGHT);
381 #if PADDING
382     XCopyArea(dd, a->pixmap, b->pixmap, fore_gc, 0, 0, tk->width-2*PADDING, WINHEIGHT, PADDING, 0);
383     XCopyArea(dd, b->pixmap, bgpixmap, fore_gc, 0, 0, tk->width, WINHEIGHT, tk->pos_x, 0);
384 #else
385     XCopyArea(dd, a->pixmap, bgpixmap, fore_gc, 0, 0, tk->width, WINHEIGHT, tk->pos_x, 0);
386 #endif
387
388     XFreePixmap(dd, a->pixmap);
389     XFreePixmap(dd, b->pixmap);
390     if (redraw) {
391         XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
392         XClearWindow(dd, tb.win);
393     }
394 }
395
396 static void netwm_action(Window win, atom_t atom, Time time, long l)
397 {
398     XClientMessageEvent xev = {
399     .type = ClientMessage,
400     .window = win,
401     .format = 32,
402     .data.l = {0},
403     .message_type = atoms[atom]
404     };
405     
406     if (atom == _NET_ACTIVE_WINDOW) {
407         xev.data.l[0] = 2;
408         xev.data.l[1] = time;
409         xev.data.l[2] = 0;
410     } else if (atom == _NET_CURRENT_DESKTOP) {
411         xev.data.l[0] = l;
412         xev.data.l[1] = time;
413     } else if (atom == _NET_RESTACK_WINDOW) {
414         xev.data.l[0] = 2;
415         xev.data.l[2] = l;
416     } else if (atom == _OB_FOCUS) {
417     } else {
418         xev.message_type = atoms[_NET_WM_STATE];
419         xev.data.l[0] = l;
420         xev.data.l[1] = atoms[atom];
421         xev.data.l[3] = 2;
422     }
423
424     XSendEvent(dd, root_win, False, SubstructureNotifyMask
425                |SubstructureRedirectMask, (XEvent *)&xev);
426 }
427
428 static void set_icon_geometry(task *tk)
429 {
430     long coords[4];
431
432     coords[0] = tb.x + tk->pos_x;
433     coords[1] = tb.y;
434     coords[2] = MAX(tk->width, 1);
435     coords[3] = WINHEIGHT;
436
437     XChangeProperty(dd, tk->win,
438                     atoms[_NET_WM_ICON_GEOMETRY], XA_CARDINAL,
439                     32, PropModeReplace, (unsigned char*) &coords, 4);
440 }
441
442
443 #ifdef PAGER
444
445 static void switch_desk(int new_desk, Time time)
446 {
447     if (get_number_of_desktops() <= new_desk)
448         return;
449
450     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
451 }
452
453 /* This doesn't work with obrender yet */
454 static void pager_draw_button(int x, int num)
455 {
456     char label;
457 #ifdef XFT
458     XftColor col;
459 #endif
460
461     if (num == tb.my_desktop) {
462         /* current desktop */
463         draw_box(x, PAGER_BUTTON_WIDTH);
464     } else {
465         set_foreground(0);
466         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
467     }
468
469     label = '1' + num;
470
471 #ifdef XFT
472     col.color.alpha = 0xffff;
473     col.color.red = cols[5].red;
474     col.color.green = cols[5].green;
475     col.color.blue = cols[5].blue;
476     XftDrawString8(xftdraw, &col, xfs,
477                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
478                    &label, 1);
479 #else
480     set_foreground(5);
481     XDrawString(dd, tb.win, fore_gc,
482                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
483                 text_y, &label, 1);
484 #endif
485 }
486
487 static void pager_draw(void)
488 {
489     int desks, i, x = GRILL_WIDTH;
490
491     desks = get_number_of_desktops();
492
493     for (i = 0; i < desks; i++) {
494         pager_draw_button(x, i);
495         if (i > 8)
496             break;
497         x += PAGER_BUTTON_WIDTH;
498     }
499
500     pager_size = x;
501 }
502
503 #endif
504
505 static void gui_draw_taskbar(void)
506 {
507     task *tk;
508     int x, width, taskw;
509
510 #ifdef PAGER
511     pager_draw();
512 #else
513     pager_size = TEXTPAD;
514 #endif
515
516     x = pager_size + 2;
517     width = WINWIDTH - (pager_size + GRILL_WIDTH);
518 #warning only rerender if width changed!
519     if (bgpixmap) XFreePixmap(dd, bgpixmap);
520     bgpixmap = XCreatePixmap(dd, root_win, WINWIDTH, WINHEIGHT, RrDepth(inst));
521
522     XFreePixmap(dd, RrPaintPixmap(background, WINWIDTH, WINHEIGHT));
523     XCopyArea(dd, background->pixmap, bgpixmap, fore_gc, 0, 0, WINWIDTH, WINHEIGHT, 0, 0);
524     if (tb.num_tasks == 0)
525         goto clear;
526
527     taskw = width / tb.num_tasks;
528     if (taskw > MAX_TASK_WIDTH)
529         taskw = MAX_TASK_WIDTH;
530
531     tk = tb.task_list;
532     while (tk) {
533         tk->pos_x = x;
534         tk->width = taskw - 1;
535         gui_draw_task(tk, FALSE);
536         set_icon_geometry(tk);
537         x += taskw;
538         tk = tk->next;
539     }
540
541 clear:
542     XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
543     XClearWindow(dd, tb.win);
544 }
545
546 static task *find_task(Window win)
547 {
548     task *list = tb.task_list;
549     while (list) {
550         if (list->win == win)
551             return list;
552         list = list->next;
553     }
554     return 0;
555 }
556
557 static void del_task(Window win)
558 {
559     task *next, *prev = 0, *list = tb.task_list;
560
561     while (list) {
562         next = list->next;
563         if (list->win == win) {
564             /* unlink and free this task */
565             tb.num_tasks--;
566             if (list->icon_copied) {
567                 XFreePixmap(dd, list->icon);
568                 if (list->mask != None)
569                     XFreePixmap(dd, list->mask);
570             }
571             if (list->name)
572                 XFree(list->name);
573             free(list);
574             if (prev == 0)
575                 tb.task_list = next;
576             else
577                 prev->next = next;
578             return;
579         }
580         prev = list;
581         list = next;
582     }
583 }
584
585 static void move_taskbar(void)
586 {
587     tb.x = tb.y = 0;
588
589     if (tb.hidden)
590         tb.x = TEXTPAD - WINWIDTH;
591
592     if (!tb.at_top)
593         tb.y = scr_height - WINHEIGHT;
594
595     XMoveWindow(dd, tb.win, tb.x, tb.y);
596 }
597
598 static void taskbar_read_clientlist(void)
599 {
600     Window *win, focus_win = 0;
601     int num, i, desk, new_desk = 0;
602     task *list, *next;
603     desk = get_current_desktop();
604
605 #ifdef MIKACHU
606     if (desk == 0)
607         WINWIDTH = 827;
608     else
609         WINWIDTH = 1280;
610
611     XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
612 #endif
613
614     if (desk != tb.my_desktop) {
615         new_desk = 1;
616         tb.my_desktop = desk;
617     }
618
619     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
620     if (win && num > 0) {
621         focus_win = win[0];
622         XFree(win);
623     }
624
625     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
626     if (!win)
627         return;
628
629     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
630     list = tb.task_list;
631     while (list) {
632         list->focused = (focus_win == list->win);
633         next = list->next;
634
635         if (!new_desk)
636             for (i = num - 1; i >= 0; i--)
637                 if (list->win == win[i] && !is_hidden(win[i]))
638                     goto dontdel;
639         del_task(list->win);
640 dontdel:
641
642         list = next;
643     }
644
645     /* add any new windows */
646     for (i = 0; i < num; i++) {
647         if (!find_task(win[i]))
648             add_task(win[i], (win[i] == focus_win));
649     }
650
651     XFree(win);
652 }
653
654 static void handle_press(int x, int y, int button, Time time)
655 {
656     task *tk;
657
658 #ifdef PAGER
659     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
660         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
661     else
662 #endif
663     /* clicked left grill */
664     if (x < 6) {
665         if (button == 1) {
666             tb.at_top = 1 - tb.at_top;
667             move_taskbar();
668         } else if (button == 4) {
669             tb.hidden = 1;
670             move_taskbar();
671         }
672     }
673
674     /* clicked right grill */
675     else if (x + TEXTPAD > WINWIDTH) {
676         if (tb.hidden && (button == 1 || button == 5)) {
677             tb.hidden = 0;
678             move_taskbar();
679         } else if (!tb.hidden && (button == 1 || button == 4)) {
680             tb.hidden = 1;
681             move_taskbar();
682         }
683     } else {
684         tk = tb.task_list;
685         while (tk) {
686             /* clicked on a task button */
687             /* XXX Make this configureable */
688             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
689                 switch (button) {
690                     case 1:
691                         netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
692                         break;
693                     case 2:
694                         if (tk->iconified)
695                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
696                         else
697                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
698                         break;
699                     case 3:
700                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
701                         break;
702                     case 4:
703                         if (is_shaded(tk->win))
704                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
705                         else
706                                 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
707                         break;
708                     case 5:
709                         if (is_shaded(tk->win))
710                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
711                         else
712                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
713                         break;
714                     case 9:
715                         if (tk->iconified)
716                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
717                         else
718                             netwm_action(tk->win, _OB_FOCUS, 0, 0);
719                         break;
720                     case 6:
721                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
722                         break;
723                     case 7:
724                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
725                         break;
726                 }
727                 return;
728             }
729             tk = tk->next;
730         } /* clicked on the background */
731         switch (button) {
732             case 1:
733                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
734                 break;
735             case 2:
736                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
737                 break;
738             case 3:
739                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
740                 break;
741             case 4:
742                 tb.hidden = 1;
743                 move_taskbar();
744                 break;
745             case 5:
746                 tb.hidden = 0;
747                 move_taskbar();
748                 break;
749         }
750     }
751 }
752
753 #if 0
754 static void handle_focusin(Window win)
755 {
756     task *tk;
757
758     tk = tb.task_list;
759     while (tk) {
760         if (tk->focused) {
761             if (tk->win != win) {
762                 tk->focused = 0;
763                 gui_draw_task(tk);
764             }
765         } else {
766             if (tk->win == win) {
767                 tk->focused = 1;
768                 gui_draw_task(tk);
769             }
770         }
771         tk = tk->next;
772     }
773 }
774 #endif
775
776 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
777 {
778     Atom at = *(Atom*)arg;
779     return (e->type == PropertyNotify && e->xproperty.atom == at);
780 }
781
782 static void handle_propertynotify(Window win, Atom at)
783 {
784     task *tk;
785
786     if (win == root_win) {
787         /* XXX only redraw the task that got focused/unfocused
788          * when _NET_ACTIVE_WINDOW triggers this */
789         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
790             || at == atoms[_NET_ACTIVE_WINDOW])
791         {
792             XEvent ce;
793             Atom check;
794
795             check = atoms[_NET_CLIENT_LIST];
796             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
797             check = atoms[_NET_CURRENT_DESKTOP];
798             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
799             check = atoms[_NET_ACTIVE_WINDOW];
800             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
801
802             taskbar_read_clientlist();
803             gui_draw_taskbar();
804         }
805         return;
806     }
807
808     /* XXX make this work when SKIP_TASKBAR is toggled too */
809     tk = find_task(win);
810     if (!tk)
811         return;
812
813     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
814         /* window's title changed */
815         char *newname;
816         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
817                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
818         if (newname) {
819             /* It didn't change */
820             if (tk->name && !strcmp(newname, tk->name)) {
821                 XFree(newname);
822                 return;
823             }
824         }
825         if (tk->name)
826             XFree(tk->name);
827         tk->name = newname;
828         gui_draw_task(tk, TRUE);
829     } else if (at == atoms[_NET_WM_STATE]) {
830         /* iconified state changed? */
831         if (is_iconified(tk->win) != tk->iconified) {
832             tk->iconified = !tk->iconified;
833             gui_draw_task(tk, TRUE);
834         }
835         /* shaded state changed? */
836         if (is_shaded(tk->win) != tk->shaded) {
837             tk->shaded = !tk->shaded;
838             gui_draw_task(tk, TRUE);
839         }
840         /* XXX use _NET_WM_ICON */
841 //    } else if (at == XA_WM_HINTS) {
842         /* some windows set their WM_HINTS icon after mapping */
843         //if (tk->icon == generic_icon) {
844 //            get_task_hinticon(tk);
845 //            gui_draw_task(tk, TRUE);
846         //}
847     } else if (at == atoms[_NET_WM_DESKTOP]) {
848         if (find_desktop(win) != get_current_desktop())
849             del_task(tk->win);
850     }
851 }
852
853 static void handle_error(Display * d, XErrorEvent * ev)
854 {
855 }
856
857 int
858 #ifdef NOSTDLIB
859 _start(void)
860 #else
861 main(int argc, char *argv[])
862 #endif
863 {
864     XEvent ev;
865     fd_set fd;
866     int xfd;
867
868     dd = XOpenDisplay(NULL);
869     if (!dd)
870         return 0;
871     scr_screen = DefaultScreen(dd);
872     scr_depth = DefaultDepth(dd, scr_screen);
873     scr_height = DisplayHeight(dd, scr_screen);
874     scr_width = DisplayWidth(dd, scr_screen);
875     root_win = RootWindow(dd, scr_screen);
876
877     /* helps us catch windows closing/opening */
878     XSelectInput(dd, root_win, PropertyChangeMask);
879
880     XSetErrorHandler((XErrorHandler) handle_error);
881
882     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
883
884     gui_init();
885     memset(&tb, 0, sizeof(tb));
886     tb.win = gui_create_taskbar();
887     xfd = ConnectionNumber(dd);
888     XSync(dd, False);
889
890     while (1) {
891         FD_ZERO(&fd);
892         FD_SET(xfd, &fd);
893         select(xfd + 1, &fd, 0, 0, 0);
894
895         while (XPending(dd)) {
896             XNextEvent(dd, &ev);
897             switch (ev.type) {
898             case ButtonPress:
899                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
900                 break;
901             case PropertyNotify:
902                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
903                 break;
904             /*default:
905                    printf ("unknown evt type: %d\n", ev.type); */
906             }
907         }
908     }
909     /* RrInstanceFree(inst);
910      * XCloseDisplay (dd);
911
912        return 0; */
913 }
914