]> icculus.org git repositories - mikachu/rspanel.git/blob - rspanel.c
improve support for skip_taskbar windows, now the state is updated instantly, and...
[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))
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     tk->hidden = is_hidden(win);
328     task_update_icon(tk);
329
330     /* now append it to our linked list */
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     unfocused_task->surface.border = RR_SURFACE_PARENTREL;
426     unfocused_task->surface.border_color = RrColorNew(inst, 0, 0, 0);
427
428     iconified_task = RrAppearanceCopy(unfocused_task);
429     iconified_task->surface.relief = RR_RELIEF_SUNKEN;
430     iconified_task->surface.parent = background;
431
432     focused_task->surface.parent = background;
433     focused_task->surface.grad = RR_SURFACE_CROSS_DIAGONAL;
434     focused_task->surface.secondary = RrColorNew(inst, 70, 80, 110);
435     focused_task->surface.primary = RrColorNew(inst, 130, 160, 250);
436     focused_task->surface.relief = RR_RELIEF_RAISED;
437
438     normal_text->surface.grad = RR_SURFACE_PARENTREL;
439     normal_text->texture[0].type = RR_TEXTURE_TEXT;
440     normal_text->texture[0].data.text.font
441         = RrFontOpen(inst, "Candara, sans", 10, RR_FONTWEIGHT_NORMAL, RR_FONTSLANT_NORMAL);
442     normal_text->texture[0].data.text.justify = RR_JUSTIFY_LEFT;
443     normal_text->texture[0].data.text.ellipsize = RR_ELLIPSIZE_END;
444
445     iconified_text = RrAppearanceCopy(normal_text);
446     normal_text->texture[0].data.text.color = RrColorNew(inst, 20, 20, 40);
447     focused_text = RrAppearanceCopy(normal_text);
448     iconified_text->texture[0].data.text.shadow_offset_x = 2;
449     iconified_text->texture[0].data.text.shadow_offset_y = 2;
450     iconified_text->texture[0].data.text.shadow_alpha = 100;
451     iconified_text->texture[0].data.text.shadow_color = RrColorNew(inst, 0, 0, 0);
452     shaded_text = RrAppearanceCopy(normal_text);
453     shaded_text->texture[0].data.text.color = RrColorNew(inst, 50, 60, 90);
454     iconified_text->texture[0].data.text.color = RrColorNew(inst, 200, 200, 200);
455     focused_text->texture[0].data.text.color = RrColorNew(inst, 230, 230, 255);
456 }
457
458 const icon* best_task_icon(task *tk, gint w, gint h)
459 {
460     static icon deficon;
461     guint i;
462     gulong min_diff, min_i;
463
464     min_diff = ABS(tk->icons[0].width - w) + ABS(tk->icons[0].height - h);
465     min_i = 0;
466
467     for (i = 1; i < tk->nicons; ++i) {
468         gulong diff;
469
470         diff = ABS(tk->icons[i].width - w) + ABS(tk->icons[i].height - h);
471         if (diff < min_diff) {
472             min_diff = diff;
473             min_i = i;
474         }
475     }
476     return &tk->icons[min_i];
477 }
478
479 #define PADDING 2
480 #define ICON_SIZE WINHEIGHT-2*PADDING
481 static void gui_draw_task(task *tk, int redraw)
482 {
483     RrAppearance *a;
484     RrAppearance *b;
485     const icon *i;
486
487     if (tk->iconified)
488         b = iconified_task;
489     else if (tk->focused)
490         b = focused_task;
491     else if (tk->shaded)
492         b = iconified_task;
493     else
494         b = unfocused_task;
495
496     if (tk->iconified)
497         a = iconified_text;
498     else if (tk->shaded)
499         a = shaded_text;
500     else if (tk->focused)
501         a = focused_text;
502     else
503         a = normal_text;
504
505     i = best_task_icon(tk, ICON_SIZE, ICON_SIZE);
506
507     if (i) {
508         RrTexture *c = &a_icon->texture[0];
509         RrTextureRGBA *d = &c->data.rgba;
510         c->type = RR_TEXTURE_RGBA;
511         a_icon->surface.parent = b;
512         a_icon->surface.parentx = PADDING;
513         a_icon->surface.parenty = PADDING;
514         d->width = i->width;
515         d->height = i->height;
516         d->alpha = tk->iconified ? 0x80 : tk->shaded ? 0xB0 : 0xff;
517         d->data = i->data;
518     }
519
520     a->surface.parent = b;
521     a->surface.parentx = ICON_SIZE+2*PADDING;
522     a->texture[0].data.text.string = tk->name;
523     b->surface.parentx = tk->pos_x;
524
525     RrPaintPixmap(b, tk->width, WINHEIGHT);
526     RrPaintPixmap(a, tk->width-3*PADDING-ICON_SIZE, WINHEIGHT);
527     RrPaintPixmap(a_icon, ICON_SIZE, ICON_SIZE);
528
529     XCopyArea(dd, a_icon->pixmap, b->pixmap, fore_gc, 0, 0,
530               ICON_SIZE, ICON_SIZE, PADDING, PADDING);
531     XCopyArea(dd, a->pixmap, b->pixmap, fore_gc, 0, 0,
532               tk->width-3*PADDING-ICON_SIZE, WINHEIGHT, ICON_SIZE+2*PADDING, 0);
533     XCopyArea(dd, b->pixmap, bgpixmap, fore_gc, 0, 0,
534               tk->width, WINHEIGHT, tk->pos_x, 0);
535
536     a_icon->texture[1].type = RR_TEXTURE_NONE;
537     XFreePixmap(dd, a->pixmap);
538     XFreePixmap(dd, b->pixmap);
539     XFreePixmap(dd, a_icon->pixmap);
540     if (redraw) {
541         XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
542         XClearWindow(dd, tb.win);
543     }
544 }
545
546 static void netwm_action(Window win, atom_t atom, Time time, long l)
547 {
548     XClientMessageEvent xev = {
549     .type = ClientMessage,
550     .window = win,
551     .format = 32,
552     .data.l = {0},
553     .message_type = atoms[atom]
554     };
555     
556     if (atom == _NET_ACTIVE_WINDOW) {
557         xev.data.l[0] = 2;
558         xev.data.l[1] = time;
559         xev.data.l[2] = 0;
560     } else if (atom == _NET_CURRENT_DESKTOP) {
561         xev.data.l[0] = l;
562         xev.data.l[1] = time;
563     } else if (atom == _NET_RESTACK_WINDOW) {
564         xev.data.l[0] = 2;
565         xev.data.l[2] = l;
566     } else if (atom == _OB_FOCUS) {
567     } else {
568         xev.message_type = atoms[_NET_WM_STATE];
569         xev.data.l[0] = l;
570         xev.data.l[1] = atoms[atom];
571         xev.data.l[3] = 2;
572     }
573
574     XSendEvent(dd, root_win, False, SubstructureNotifyMask
575                |SubstructureRedirectMask, (XEvent *)&xev);
576 }
577
578 static void set_icon_geometry(task *tk)
579 {
580     long coords[4];
581
582     coords[0] = tb.x + tk->pos_x;
583     coords[1] = tb.y;
584     coords[2] = MAX(tk->width, 1);
585     coords[3] = WINHEIGHT;
586
587     XChangeProperty(dd, tk->win,
588                     atoms[_NET_WM_ICON_GEOMETRY], XA_CARDINAL,
589                     32, PropModeReplace, (unsigned char*) &coords, 4);
590 }
591
592
593 #ifdef PAGER
594
595 static void switch_desk(int new_desk, Time time)
596 {
597     if (get_number_of_desktops() <= new_desk)
598         return;
599
600     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
601 }
602
603 /* This doesn't work with obrender yet */
604 static void pager_draw_button(int x, int num)
605 {
606     char label;
607 #ifdef XFT
608     XftColor col;
609 #endif
610
611     if (num == tb.my_desktop) {
612         /* current desktop */
613         draw_box(x, PAGER_BUTTON_WIDTH);
614     } else {
615         set_foreground(0);
616         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
617     }
618
619     label = '1' + num;
620
621 #ifdef XFT
622     col.color.alpha = 0xffff;
623     col.color.red = cols[5].red;
624     col.color.green = cols[5].green;
625     col.color.blue = cols[5].blue;
626     XftDrawString8(xftdraw, &col, xfs,
627                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
628                    &label, 1);
629 #else
630     set_foreground(5);
631     XDrawString(dd, tb.win, fore_gc,
632                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
633                 text_y, &label, 1);
634 #endif
635 }
636
637 static void pager_draw(void)
638 {
639     int desks, i, x = GRILL_WIDTH;
640
641     desks = get_number_of_desktops();
642
643     for (i = 0; i < desks; i++) {
644         pager_draw_button(x, i);
645         if (i > 8)
646             break;
647         x += PAGER_BUTTON_WIDTH;
648     }
649
650     pager_size = x;
651 }
652
653 #endif
654
655 static void gui_draw_taskbar(void)
656 {
657     task *tk;
658     int x, width, taskw;
659     int num_tasks = 0;
660
661 #ifdef PAGER
662     pager_draw();
663 #else
664     pager_size = TEXTPAD;
665 #endif
666
667     x = pager_size + 2;
668     width = WINWIDTH - (pager_size + GRILL_WIDTH);
669 #warning only rerender if width changed!
670     if (bgpixmap) XFreePixmap(dd, bgpixmap);
671     bgpixmap = XCreatePixmap(dd, root_win, WINWIDTH, WINHEIGHT, RrDepth(inst));
672
673     XFreePixmap(dd, RrPaintPixmap(background, WINWIDTH, WINHEIGHT));
674     XCopyArea(dd, background->pixmap, bgpixmap, fore_gc, 0, 0, WINWIDTH, WINHEIGHT, 0, 0);
675
676     /* find the number of visible tasks */
677     for (tk = tb.task_list; tk; tk = tk->next) {
678         if (!tk->focused && tk->hidden)
679             continue;
680         num_tasks++;
681     }
682
683     if (num_tasks == 0)
684         goto clear;
685
686     taskw = width / num_tasks;
687     if (taskw > MAX_TASK_WIDTH)
688         taskw = MAX_TASK_WIDTH;
689
690     for (tk = tb.task_list; tk; tk = tk->next) {
691         if (!tk->focused && tk->hidden)
692             continue;
693         tk->pos_x = x;
694         tk->width = taskw - 1;
695         gui_draw_task(tk, FALSE);
696         set_icon_geometry(tk);
697         x += taskw;
698     }
699
700 clear:
701     XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
702     XClearWindow(dd, tb.win);
703 }
704
705 static task *find_task(Window win)
706 {
707     task *list = tb.task_list;
708     while (list) {
709         if (list->win == win)
710             return list;
711         list = list->next;
712     }
713     return 0;
714 }
715
716 static void del_task(Window win)
717 {
718     task *next, *prev = 0, *list = tb.task_list;
719
720     while (list) {
721         next = list->next;
722         if (list->win == win) {
723             /* unlink and free this task */
724             free_icons(list);
725             if (list->name)
726                 XFree(list->name);
727             free(list);
728             if (prev == 0)
729                 tb.task_list = next;
730             else
731                 prev->next = next;
732             return;
733         }
734         prev = list;
735         list = next;
736     }
737 }
738
739 static void move_taskbar(void)
740 {
741     tb.x = tb.y = 0;
742
743     if (tb.hidden)
744         tb.x = TEXTPAD - WINWIDTH;
745
746     if (!tb.at_top)
747         tb.y = scr_height - WINHEIGHT;
748
749     XMoveWindow(dd, tb.win, tb.x, tb.y);
750 }
751
752 static void taskbar_read_clientlist(void)
753 {
754     Window *win, focus_win = 0;
755     int num, i, desk, new_desk = 0;
756     task *list, *next;
757     desk = get_current_desktop();
758
759 #ifdef MIKACHU
760     if (desk == 0)
761         WINWIDTH = 827;
762     else
763         WINWIDTH = 1280;
764
765     XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
766 #endif
767
768     if (desk != tb.my_desktop) {
769         new_desk = 1;
770         tb.my_desktop = desk;
771     }
772
773     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
774     if (win && num > 0) {
775         focus_win = win[0];
776         XFree(win);
777     }
778
779     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
780     if (!win)
781         return;
782
783     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
784     list = tb.task_list;
785     while (list) {
786         list->focused = (focus_win == list->win);
787         next = list->next;
788
789         if (!new_desk)
790             for (i = num - 1; i >= 0; i--)
791                 if (list->win == win[i])
792                     goto dontdel;
793         del_task(list->win);
794 dontdel:
795
796         list = next;
797     }
798
799     /* add any new windows */
800     for (i = 0; i < num; i++) {
801         if (!find_task(win[i]))
802             add_task(win[i], (win[i] == focus_win));
803     }
804
805     XFree(win);
806 }
807
808 static void handle_press(int x, int y, int button, Time time)
809 {
810     task *tk;
811
812 #ifdef PAGER
813     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
814         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
815     else
816 #endif
817     /* clicked left grill */
818     if (x < 6) {
819         if (button == 1) {
820             tb.at_top = 1 - tb.at_top;
821             move_taskbar();
822         } else if (button == 4) {
823             tb.hidden = 1;
824             move_taskbar();
825         }
826     }
827
828     /* clicked right grill */
829     else if (x + TEXTPAD > WINWIDTH) {
830         if (tb.hidden && (button == 1 || button == 5)) {
831             tb.hidden = 0;
832             move_taskbar();
833         } else if (!tb.hidden && (button == 1 || button == 4)) {
834             tb.hidden = 1;
835             move_taskbar();
836         }
837     } else {
838         tk = tb.task_list;
839         while (tk) {
840             /* clicked on a task button */
841             /* XXX Make this configureable */
842             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
843                 switch (button) {
844                     case 1:
845                         netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
846                         break;
847                     case 2:
848                         if (tk->iconified)
849                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
850                         else
851                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
852                         break;
853                     case 3:
854                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
855                         break;
856                     case 4:
857                         if (is_shaded(tk->win))
858                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
859                         else
860                                 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
861                         break;
862                     case 5:
863                         if (is_shaded(tk->win))
864                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
865                         else
866                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
867                         break;
868                     case 9:
869                         if (tk->iconified)
870                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
871                         else
872                             netwm_action(tk->win, _OB_FOCUS, 0, 0);
873                         break;
874                     case 6:
875                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
876                         break;
877                     case 7:
878                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
879                         break;
880                 }
881                 return;
882             }
883             tk = tk->next;
884         } /* clicked on the background */
885         switch (button) {
886             case 1:
887                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
888                 break;
889             case 2:
890                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
891                 break;
892             case 3:
893                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
894                 break;
895             case 4:
896                 tb.hidden = 1;
897                 move_taskbar();
898                 break;
899             case 5:
900                 tb.hidden = 0;
901                 move_taskbar();
902                 break;
903         }
904     }
905 }
906
907 #if 0
908 static void handle_focusin(Window win)
909 {
910     task *tk;
911
912     tk = tb.task_list;
913     while (tk) {
914         if (tk->focused) {
915             if (tk->win != win) {
916                 tk->focused = 0;
917                 gui_draw_task(tk);
918             }
919         } else {
920             if (tk->win == win) {
921                 tk->focused = 1;
922                 gui_draw_task(tk);
923             }
924         }
925         tk = tk->next;
926     }
927 }
928 #endif
929
930 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
931 {
932     Atom at = *(Atom*)arg;
933     return (e->type == PropertyNotify && e->xproperty.atom == at);
934 }
935
936 static void handle_propertynotify(Window win, Atom at)
937 {
938     task *tk;
939
940     if (win == root_win) {
941         /* XXX only redraw the task that got focused/unfocused
942          * when _NET_ACTIVE_WINDOW triggers this,
943          * redraw everything if that task was hidden before though */
944         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
945             || at == atoms[_NET_ACTIVE_WINDOW])
946         {
947             XEvent ce;
948             Atom check;
949
950             check = atoms[_NET_CLIENT_LIST];
951             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
952             check = atoms[_NET_CURRENT_DESKTOP];
953             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
954             check = atoms[_NET_ACTIVE_WINDOW];
955             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
956
957             taskbar_read_clientlist();
958             gui_draw_taskbar();
959         }
960         return;
961     }
962
963     /* XXX make this work when SKIP_TASKBAR is toggled too */
964     /* show skip_taskbar tasks if they're focused */
965     tk = find_task(win);
966     if (!tk)
967         return;
968
969     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
970         /* window's title changed */
971         /* XXX make a function for this and use from here and add_task */
972         char *newname;
973         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
974                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
975         if (newname) {
976             /* It didn't change */
977             if (tk->name && !strcmp(newname, tk->name)) {
978                 XFree(newname);
979                 return;
980             }
981         }
982         if (tk->name)
983             XFree(tk->name);
984         tk->name = newname;
985         gui_draw_task(tk, TRUE);
986     } else if (at == atoms[_NET_WM_ICON]) {
987         task_update_icon(tk);
988         gui_draw_task(tk, TRUE);
989     } else if (at == atoms[_NET_WM_STATE]) {
990         /* iconified state changed? */
991         if (is_iconified(tk->win) != tk->iconified) {
992             tk->iconified = !tk->iconified;
993             gui_draw_task(tk, TRUE);
994         }
995         /* shaded state changed? */
996         if (is_shaded(tk->win) != tk->shaded) {
997             tk->shaded = !tk->shaded;
998             gui_draw_task(tk, TRUE);
999         }
1000         if (is_hidden(tk->win) != tk->hidden) {
1001             tk->hidden = !tk->hidden;
1002             gui_draw_taskbar();
1003         }
1004     } else if (at == atoms[_NET_WM_DESKTOP]) {
1005         if (find_desktop(win) != get_current_desktop())
1006             del_task(tk->win);
1007     }
1008 }
1009
1010 static void handle_error(Display * d, XErrorEvent * ev)
1011 {
1012 }
1013
1014 int
1015 #ifdef NOSTDLIB
1016 _start(void)
1017 #else
1018 main(int argc, char *argv[])
1019 #endif
1020 {
1021     XEvent ev;
1022     fd_set fd;
1023     int xfd;
1024
1025     dd = XOpenDisplay(NULL);
1026     if (!dd)
1027         return 0;
1028     scr_screen = DefaultScreen(dd);
1029     scr_depth = DefaultDepth(dd, scr_screen);
1030     scr_height = DisplayHeight(dd, scr_screen);
1031     scr_width = DisplayWidth(dd, scr_screen);
1032     root_win = RootWindow(dd, scr_screen);
1033
1034     /* helps us catch windows closing/opening */
1035     XSelectInput(dd, root_win, PropertyChangeMask);
1036
1037     XSetErrorHandler((XErrorHandler) handle_error);
1038
1039     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1040
1041     gui_init();
1042     memset(&tb, 0, sizeof(tb));
1043     tb.win = gui_create_taskbar();
1044     xfd = ConnectionNumber(dd);
1045     XSync(dd, False);
1046     taskbar_read_clientlist();
1047     gui_draw_taskbar();
1048
1049     while (1) {
1050         FD_ZERO(&fd);
1051         FD_SET(xfd, &fd);
1052         select(xfd + 1, &fd, 0, 0, 0);
1053
1054         while (XPending(dd)) {
1055             XNextEvent(dd, &ev);
1056             switch (ev.type) {
1057             case ButtonPress:
1058                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
1059                 break;
1060             case PropertyNotify:
1061                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1062                 break;
1063             /*default:
1064                    printf ("unknown evt type: %d\n", ev.type); */
1065             }
1066         }
1067     }
1068     /* RrInstanceFree(inst);
1069      * XCloseDisplay (dd);
1070
1071        return 0; */
1072 }
1073