c30ad676e34a54643d97ba602e59535054815fad
[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 #endif
593     if (desk != tb.my_desktop) {
594         new_desk = 1;
595         tb.my_desktop = desk;
596     }
597
598     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
599     if (win && num > 0) {
600         focus_win = win[0];
601         XFree(win);
602     }
603
604     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
605     if (!win)
606         return;
607
608     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
609     list = tb.task_list;
610     while (list) {
611         list->focused = (focus_win == list->win);
612         next = list->next;
613
614         if (!new_desk)
615             for (i = num - 1; i >= 0; i--)
616                 if (list->win == win[i] && !is_hidden(win[i]))
617                     goto dontdel;
618         del_task(list->win);
619 dontdel:
620
621         list = next;
622     }
623
624     /* add any new windows */
625     for (i = 0; i < num; i++) {
626         if (!find_task(win[i]))
627             add_task(win[i], (win[i] == focus_win));
628     }
629
630     XFree(win);
631 }
632
633 static void handle_press(int x, int y, int button, Time time)
634 {
635     task *tk;
636
637 #ifdef PAGER
638     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
639         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
640     else
641 #endif
642     /* clicked left grill */
643     if (x < 6) {
644         if (button == 1) {
645             tb.at_top = 1 - tb.at_top;
646             move_taskbar();
647         } else if (button == 4) {
648             tb.hidden = 1;
649             move_taskbar();
650         }
651     }
652
653     /* clicked right grill */
654     else if (x + TEXTPAD > WINWIDTH) {
655         if (tb.hidden && (button == 1 || button == 5)) {
656             tb.hidden = 0;
657             move_taskbar();
658         } else if (!tb.hidden && (button == 1 || button == 4)) {
659             tb.hidden = 1;
660             move_taskbar();
661         }
662     } else {
663         tk = tb.task_list;
664         while (tk) {
665             /* clicked on a task button */
666             /* XXX Make this configureable */
667             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
668                 switch (button) {
669                     case 1:
670                         netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
671                         break;
672                     case 2:
673                         if (tk->iconified)
674                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
675                         else
676                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
677                         break;
678                     case 3:
679                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
680                         break;
681                     case 4:
682                         if (is_shaded(tk->win))
683                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
684                         else
685                                 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
686                         break;
687                     case 5:
688                         if (is_shaded(tk->win))
689                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
690                         else
691                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
692                         break;
693                     case 9:
694                         if (tk->iconified)
695                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
696                         else
697                             netwm_action(tk->win, _OB_FOCUS, 0, 0);
698                         break;
699                     case 6:
700                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
701                         break;
702                     case 7:
703                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
704                         break;
705                 }
706                 return;
707             }
708             tk = tk->next;
709         } /* clicked on the background */
710         switch (button) {
711             case 1:
712                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
713                 break;
714             case 2:
715                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
716                 break;
717             case 3:
718                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
719                 break;
720             case 4:
721                 tb.hidden = 1;
722                 move_taskbar();
723                 break;
724             case 5:
725                 tb.hidden = 0;
726                 move_taskbar();
727                 break;
728         }
729     }
730 }
731
732 #if 0
733 static void handle_focusin(Window win)
734 {
735     task *tk;
736
737     tk = tb.task_list;
738     while (tk) {
739         if (tk->focused) {
740             if (tk->win != win) {
741                 tk->focused = 0;
742                 gui_draw_task(tk);
743             }
744         } else {
745             if (tk->win == win) {
746                 tk->focused = 1;
747                 gui_draw_task(tk);
748             }
749         }
750         tk = tk->next;
751     }
752 }
753 #endif
754
755 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
756 {
757     Atom at = *(Atom*)arg;
758     return (e->type == PropertyNotify && e->xproperty.atom == at);
759 }
760
761 static void handle_propertynotify(Window win, Atom at)
762 {
763     task *tk;
764
765     if (win == root_win) {
766         /* XXX only redraw the task that got focused/unfocused
767          * when _NET_ACTIVE_WINDOW triggers this */
768         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
769             || at == atoms[_NET_ACTIVE_WINDOW])
770         {
771             XEvent ce;
772             Atom check;
773
774             check = atoms[_NET_CLIENT_LIST];
775             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
776             check = atoms[_NET_CURRENT_DESKTOP];
777             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
778             check = atoms[_NET_ACTIVE_WINDOW];
779             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
780
781             taskbar_read_clientlist();
782             gui_draw_taskbar();
783         }
784         return;
785     }
786
787     /* XXX make this work when SKIP_TASKBAR is toggled too */
788     tk = find_task(win);
789     if (!tk)
790         return;
791
792     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
793         /* window's title changed */
794         char *newname;
795         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
796                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
797         if (newname) {
798             /* It didn't change */
799             if (tk->name && !strcmp(newname, tk->name)) {
800                 XFree(newname);
801                 return;
802             }
803         }
804         if (tk->name)
805             XFree(tk->name);
806         tk->name = newname;
807         gui_draw_task(tk, TRUE);
808     } else if (at == atoms[_NET_WM_STATE]) {
809         /* iconified state changed? */
810         if (is_iconified(tk->win) != tk->iconified) {
811             tk->iconified = !tk->iconified;
812             gui_draw_task(tk, TRUE);
813         }
814         /* shaded state changed? */
815         if (is_shaded(tk->win) != tk->shaded) {
816             tk->shaded = !tk->shaded;
817             gui_draw_task(tk, TRUE);
818         }
819         /* XXX use _NET_WM_ICON */
820 //    } else if (at == XA_WM_HINTS) {
821         /* some windows set their WM_HINTS icon after mapping */
822         //if (tk->icon == generic_icon) {
823 //            get_task_hinticon(tk);
824 //            gui_draw_task(tk, TRUE);
825         //}
826     } else if (at == atoms[_NET_WM_DESKTOP]) {
827         if (find_desktop(win) != get_current_desktop())
828             del_task(tk->win);
829     }
830 }
831
832 static void handle_error(Display * d, XErrorEvent * ev)
833 {
834 }
835
836 int
837 #ifdef NOSTDLIB
838 _start(void)
839 #else
840 main(int argc, char *argv[])
841 #endif
842 {
843     XEvent ev;
844     fd_set fd;
845     int xfd;
846
847     dd = XOpenDisplay(NULL);
848     if (!dd)
849         return 0;
850     scr_screen = DefaultScreen(dd);
851     scr_depth = DefaultDepth(dd, scr_screen);
852     scr_height = DisplayHeight(dd, scr_screen);
853     scr_width = DisplayWidth(dd, scr_screen);
854     root_win = RootWindow(dd, scr_screen);
855
856     /* helps us catch windows closing/opening */
857     XSelectInput(dd, root_win, PropertyChangeMask);
858
859     XSetErrorHandler((XErrorHandler) handle_error);
860
861     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
862
863     gui_init();
864     memset(&tb, 0, sizeof(tb));
865     tb.win = gui_create_taskbar();
866     xfd = ConnectionNumber(dd);
867     XSync(dd, False);
868
869     while (1) {
870         FD_ZERO(&fd);
871         FD_SET(xfd, &fd);
872         select(xfd + 1, &fd, 0, 0, 0);
873
874         while (XPending(dd)) {
875             XNextEvent(dd, &ev);
876             switch (ev.type) {
877             case ButtonPress:
878                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
879                 break;
880             case PropertyNotify:
881                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
882                 break;
883             /*default:
884                    printf ("unknown evt type: %d\n", ev.type); */
885             }
886         }
887     }
888     /* RrInstanceFree(inst);
889      * XCloseDisplay (dd);
890
891        return 0; */
892 }
893