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