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