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