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