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