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