]> icculus.org git repositories - mikachu/rspanel.git/blob - rspanel.c
some fixes to skip_taskbar stuff, and reindent case labels to same label as switch
[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 inline int dont_show_task(task *tk)
656 {
657     return !tk->focused && tk->hidden;
658 }
659
660 static void gui_draw_taskbar(void)
661 {
662     task *tk;
663     int x, width, taskw;
664     int num_tasks = 0;
665
666 #ifdef PAGER
667     pager_draw();
668 #else
669     pager_size = TEXTPAD;
670 #endif
671
672     x = pager_size + 2;
673     width = WINWIDTH - (pager_size + GRILL_WIDTH);
674 #warning only rerender if width changed!
675     if (bgpixmap) XFreePixmap(dd, bgpixmap);
676     bgpixmap = XCreatePixmap(dd, root_win, WINWIDTH, WINHEIGHT, RrDepth(inst));
677
678     XFreePixmap(dd, RrPaintPixmap(background, WINWIDTH, WINHEIGHT));
679     XCopyArea(dd, background->pixmap, bgpixmap, fore_gc, 0, 0, WINWIDTH, WINHEIGHT, 0, 0);
680
681     /* find the number of visible tasks */
682     for (tk = tb.task_list; tk; tk = tk->next) {
683         if (dont_show_task(tk))
684             continue;
685         num_tasks++;
686     }
687
688     if (num_tasks == 0)
689         goto clear;
690
691     taskw = width / num_tasks;
692     if (taskw > MAX_TASK_WIDTH)
693         taskw = MAX_TASK_WIDTH;
694
695     for (tk = tb.task_list; tk; tk = tk->next) {
696         if (dont_show_task(tk))
697             continue;
698         tk->pos_x = x;
699         tk->width = taskw - 1;
700         gui_draw_task(tk, FALSE);
701         set_icon_geometry(tk);
702         x += taskw;
703     }
704
705 clear:
706     XSetWindowBackgroundPixmap(dd, tb.win, bgpixmap);
707     XClearWindow(dd, tb.win);
708 }
709
710 static task *find_task(Window win)
711 {
712     task *list = tb.task_list;
713     while (list) {
714         if (list->win == win)
715             return list;
716         list = list->next;
717     }
718     return 0;
719 }
720
721 static void del_task(Window win)
722 {
723     task *next, *prev = 0, *list = tb.task_list;
724
725     while (list) {
726         next = list->next;
727         if (list->win == win) {
728             /* unlink and free this task */
729             free_icons(list);
730             if (list->name)
731                 XFree(list->name);
732             free(list);
733             if (prev == 0)
734                 tb.task_list = next;
735             else
736                 prev->next = next;
737             return;
738         }
739         prev = list;
740         list = next;
741     }
742 }
743
744 static void move_taskbar(void)
745 {
746     tb.x = tb.y = 0;
747
748     if (tb.hidden)
749         tb.x = TEXTPAD - WINWIDTH;
750
751     if (!tb.at_top)
752         tb.y = scr_height - WINHEIGHT;
753
754     XMoveWindow(dd, tb.win, tb.x, tb.y);
755 }
756
757 static void taskbar_read_clientlist(void)
758 {
759     Window *win, focus_win = 0;
760     int num, i, desk, new_desk = 0;
761     task *list, *next;
762     desk = get_current_desktop();
763
764 #ifdef MIKACHU
765     if (desk == 0)
766         WINWIDTH = 827;
767     else
768         WINWIDTH = 1280;
769
770     XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
771 #endif
772
773     if (desk != tb.my_desktop) {
774         new_desk = 1;
775         tb.my_desktop = desk;
776     }
777
778     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
779     if (win && num > 0) {
780         focus_win = win[0];
781         XFree(win);
782     }
783
784     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
785     if (!win)
786         return;
787
788     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
789     list = tb.task_list;
790     while (list) {
791         list->focused = (focus_win == list->win);
792         next = list->next;
793
794         if (!new_desk)
795             for (i = num - 1; i >= 0; i--)
796                 if (list->win == win[i])
797                     goto dontdel;
798         del_task(list->win);
799 dontdel:
800
801         list = next;
802     }
803
804     /* add any new windows */
805     for (i = 0; i < num; i++) {
806         if (!find_task(win[i]))
807             add_task(win[i], (win[i] == focus_win));
808     }
809
810     XFree(win);
811 }
812
813 static void handle_press(int x, int y, int button, Time time)
814 {
815     task *tk;
816
817 #ifdef PAGER
818     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
819         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
820     else
821 #endif
822     /* clicked left grill */
823     if (x < 6) {
824         if (button == 1) {
825             tb.at_top = 1 - tb.at_top;
826             move_taskbar();
827         } else if (button == 4) {
828             tb.hidden = 1;
829             move_taskbar();
830         }
831     }
832
833     /* clicked right grill */
834     else if (x + TEXTPAD > WINWIDTH) {
835         if (tb.hidden && (button == 1 || button == 5)) {
836             tb.hidden = 0;
837             move_taskbar();
838         } else if (!tb.hidden && (button == 1 || button == 4)) {
839             tb.hidden = 1;
840             move_taskbar();
841         }
842     } else {
843         tk = tb.task_list;
844         while (tk) {
845             /* clicked on a task button */
846             /* XXX Make this configureable */
847             if (!dont_show_task(tk)
848                 && (x > tk->pos_x && x < tk->pos_x + tk->width))
849             {
850                 switch (button) {
851                 case 1:
852                     netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
853                     break;
854                 case 2:
855                     if (tk->iconified)
856                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
857                     else
858                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
859                     break;
860                 case 3:
861                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
862                     break;
863                 case 4:
864                     if (is_shaded(tk->win))
865                             netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
866                     else
867                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
868                     break;
869                 case 5:
870                     if (is_shaded(tk->win))
871                         netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
872                     else
873                             netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
874                     break;
875                 case 9:
876                     if (tk->iconified)
877                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
878                     else
879                         netwm_action(tk->win, _OB_FOCUS, 0, 0);
880                     break;
881                 case 6:
882                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
883                     break;
884                 case 7:
885                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
886                     break;
887                 }
888                 return;
889             }
890             tk = tk->next;
891         } /* clicked on the background */
892         switch (button) {
893         case 1:
894             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
895             break;
896         case 2:
897             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
898             break;
899         case 3:
900             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
901             break;
902         case 4:
903             tb.hidden = 1;
904             move_taskbar();
905             break;
906         case 5:
907             tb.hidden = 0;
908             move_taskbar();
909             break;
910         }
911     }
912 }
913
914 #if 0
915 static void handle_focusin(Window win)
916 {
917     task *tk;
918
919     tk = tb.task_list;
920     while (tk) {
921         if (tk->focused) {
922             if (tk->win != win) {
923                 tk->focused = 0;
924                 gui_draw_task(tk);
925             }
926         } else {
927             if (tk->win == win) {
928                 tk->focused = 1;
929                 gui_draw_task(tk);
930             }
931         }
932         tk = tk->next;
933     }
934 }
935 #endif
936
937 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
938 {
939     Atom at = *(Atom*)arg;
940     return (e->type == PropertyNotify && e->xproperty.atom == at);
941 }
942
943 static void handle_propertynotify(Window win, Atom at)
944 {
945     task *tk;
946
947     if (win == root_win) {
948         /* XXX only redraw the task that got focused/unfocused
949          * when _NET_ACTIVE_WINDOW triggers this,
950          * redraw everything if that task was hidden before though */
951         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP]
952             || at == atoms[_NET_ACTIVE_WINDOW])
953         {
954             XEvent ce;
955             Atom check;
956
957             check = atoms[_NET_CLIENT_LIST];
958             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
959             check = atoms[_NET_CURRENT_DESKTOP];
960             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
961             check = atoms[_NET_ACTIVE_WINDOW];
962             while (XCheckIfEvent(dd, &ce, look_for_duplicate_property, (XPointer)&check));
963
964             taskbar_read_clientlist();
965             gui_draw_taskbar();
966         }
967         return;
968     }
969
970     /* XXX make this work when SKIP_TASKBAR is toggled too */
971     /* show skip_taskbar tasks if they're focused */
972     tk = find_task(win);
973     if (!tk)
974         return;
975
976     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
977         /* window's title changed */
978         /* XXX make a function for this and use from here and add_task */
979         char *newname;
980         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
981                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
982         if (newname) {
983             /* It didn't change */
984             if (tk->name && !strcmp(newname, tk->name)) {
985                 XFree(newname);
986                 return;
987             }
988         }
989         if (tk->name)
990             XFree(tk->name);
991         tk->name = newname;
992         gui_draw_task(tk, TRUE);
993     } else if (at == atoms[_NET_WM_ICON]) {
994         task_update_icon(tk);
995         gui_draw_task(tk, TRUE);
996     } else if (at == atoms[_NET_WM_STATE]) {
997         /* iconified state changed? */
998         if (is_iconified(tk->win) != tk->iconified) {
999             tk->iconified = !tk->iconified;
1000             if (!tk->hidden)
1001                 gui_draw_task(tk, TRUE);
1002         }
1003         /* shaded state changed? */
1004         if (is_shaded(tk->win) != tk->shaded) {
1005             tk->shaded = !tk->shaded;
1006             gui_draw_task(tk, TRUE);
1007         }
1008         if (is_hidden(tk->win) != tk->hidden) {
1009             tk->hidden = !tk->hidden;
1010             gui_draw_taskbar();
1011         }
1012     } else if (at == atoms[_NET_WM_DESKTOP]) {
1013         if (find_desktop(win) != get_current_desktop())
1014             del_task(tk->win);
1015     }
1016 }
1017
1018 static void handle_error(Display * d, XErrorEvent * ev)
1019 {
1020 }
1021
1022 int
1023 #ifdef NOSTDLIB
1024 _start(void)
1025 #else
1026 main(int argc, char *argv[])
1027 #endif
1028 {
1029     XEvent ev;
1030     fd_set fd;
1031     int xfd;
1032
1033     dd = XOpenDisplay(NULL);
1034     if (!dd)
1035         return 0;
1036     scr_screen = DefaultScreen(dd);
1037     scr_depth = DefaultDepth(dd, scr_screen);
1038     scr_height = DisplayHeight(dd, scr_screen);
1039     scr_width = DisplayWidth(dd, scr_screen);
1040     root_win = RootWindow(dd, scr_screen);
1041
1042     /* helps us catch windows closing/opening */
1043     XSelectInput(dd, root_win, PropertyChangeMask);
1044
1045     XSetErrorHandler((XErrorHandler) handle_error);
1046
1047     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1048
1049     gui_init();
1050     memset(&tb, 0, sizeof(tb));
1051     tb.win = gui_create_taskbar();
1052     xfd = ConnectionNumber(dd);
1053     XSync(dd, False);
1054     taskbar_read_clientlist();
1055     gui_draw_taskbar();
1056
1057     while (1) {
1058         FD_ZERO(&fd);
1059         FD_SET(xfd, &fd);
1060         select(xfd + 1, &fd, 0, 0, 0);
1061
1062         while (XPending(dd)) {
1063             XNextEvent(dd, &ev);
1064             switch (ev.type) {
1065             case ButtonPress:
1066                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
1067                 break;
1068             case PropertyNotify:
1069                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1070                 break;
1071             /*default:
1072                    printf ("unknown evt type: %d\n", ev.type); */
1073             }
1074         }
1075     }
1076     /* RrInstanceFree(inst);
1077      * XCloseDisplay (dd);
1078
1079        return 0; */
1080 }
1081