b90ac3b87f629173c6b26e7c714ebcf3bc7e6557
[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 #undef HAVE_XPM
21 #ifdef HAVE_XPM
22 #include <X11/xpm.h>
23 #include "icon.xpm"
24 #endif
25
26 /* you can edit these */
27 #define MAX_TASK_WIDTH 500
28 #define ICONWIDTH 16
29 #define ICONHEIGHT 16
30 #define WINHEIGHT 24
31 #ifndef MIKACHU
32 #        define WINWIDTH 1280
33 #else
34 int WINWIDTH = 827;
35 #endif
36 #define FONT_NAME "-*-Technical*-m*-r-*-*-14-*-*"
37 #define XFT_FONT "Arial Unicode MS-12"
38 #define PANGO_FONT_PREF "Utopia"
39 //#define PANGO_FONT_PREF "Technical"
40 #define PANGO_FONT_SIZE 13
41 //#define PAGER         /* use a desktop pager? */
42 #define PAGER_DIGIT_WIDTH 6
43 #define PAGER_BUTTON_WIDTH 20
44 #ifdef PAGER
45 #undef PANGO
46 #endif
47
48 /* don't edit these */
49 #define TEXTPAD 6
50 #define GRILL_WIDTH 10
51
52 #ifdef PANGO
53 #undef XFT
54 #define XFT
55 #include <pango/pango.h>
56 #include <pango/pangoxft.h>
57 #endif
58 #ifdef XFT
59 #include <X11/Xft/Xft.h>
60 #endif
61
62 #include "rspanel.h"
63
64 Window rspanelwin;
65 Display *dd;
66 Window root_win;
67 Pixmap generic_icon;
68 Pixmap generic_mask;
69 GC fore_gc;
70 taskbar tb;
71 int scr_screen;
72 int scr_depth;
73 int scr_width;
74 int scr_height;
75 int text_y;
76 int pager_size;
77
78 #ifdef XFT
79 XftDraw *xftdraw;
80 #ifdef PANGO
81 PangoLayout *pl;
82 #else
83 XftFont *xfs;
84 #endif
85 #else
86 XFontStruct *xfs;
87 #endif
88
89 struct colors {
90     unsigned short red, green, blue;
91 } cols[] = {
92     {0xd75c, 0xd75c, 0xe75c},        /* 0. light gray */
93     {0xbefb, 0xbaea, 0xcefb},        /* 1. mid gray */
94     {0xaefb, 0xaaea, 0xfefb},        /* 2. dark gray */
95     {0xefbe, 0xefbe, 0xffbe},        /* 3. white */
96     {0x8617, 0x8207, 0x9617},        /* 4. darkest gray */
97     {0x0000, 0x0000, 0x1000},        /* 5. black */
98 };
99
100 #define PALETTE_COUNT (sizeof (cols) / sizeof (cols[0].red) / 3)
101
102 unsigned long palette[PALETTE_COUNT];
103
104 char *atom_names[] = {
105     /* clients */
106     "KWM_WIN_ICON",
107     "WM_STATE",
108     "_MOTIF_WM_HINTS",
109     "_NET_WM_STATE",
110     "_NET_WM_STATE_SKIP_TASKBAR",
111     "_NET_WM_STATE_SHADED",
112     "_NET_WM_STATE_BELOW",
113     "_NET_WM_STATE_HIDDEN",
114     "_NET_WM_DESKTOP",
115     "_NET_WM_WINDOW_TYPE",
116     "_NET_WM_WINDOW_TYPE_DOCK",
117     "_NET_WM_STRUT",
118     "_WIN_HINTS",
119     /* root */
120     "_NET_CLIENT_LIST",
121     "_NET_CLIENT_LIST_STACKING",
122     "_NET_NUMBER_OF_DESKTOPS",
123     "_NET_CURRENT_DESKTOP",
124     "_OB_WM_ACTION",
125     "_NET_WM_NAME",
126     "UTF8_STRING",
127     "_NET_ACTIVE_WINDOW",
128     "_NET_RESTACK_WINDOW",
129 };
130
131 #define ATOM_COUNT (sizeof (atom_names) / sizeof (atom_names[0]))
132
133 Atom atoms[ATOM_COUNT];
134
135 typedef enum {
136 KWM_WIN_ICON,
137 WM_STATE,
138 _MOTIF_WM_HINTS,
139 _NET_WM_STATE,
140 _NET_WM_STATE_SKIP_TASKBAR,
141 _NET_WM_STATE_SHADED,
142 _NET_WM_STATE_BELOW,
143 _NET_WM_STATE_HIDDEN,
144 _NET_WM_DESKTOP,
145 _NET_WM_WINDOW_TYPE,
146 _NET_WM_WINDOW_TYPE_DOCK,
147 _NET_WM_STRUT,
148 _WIN_HINTS,
149 _NET_CLIENT_LIST,
150 _NET_CLIENT_LIST_STACKING,
151 _NET_NUMBER_OF_DESKTOPS,
152 _NET_CURRENT_DESKTOP,
153 _OB_WM_ACTION,
154 _NET_WM_NAME,
155 STRING_UTF8,
156 _NET_ACTIVE_WINDOW,
157 _NET_RESTACK_WINDOW,
158 } atom_t;
159
160 enum {
161     REMOVE = 0,
162     ADD,
163     TOGGLE
164 };
165
166 void *get_prop_data(Window win, Atom prop, Atom type, int *items)
167 {
168     Atom type_ret;
169     int format_ret;
170     unsigned long items_ret;
171     unsigned long after_ret;
172     unsigned char *prop_data;
173
174     prop_data = 0;
175
176     XGetWindowProperty(dd, win, prop, 0, 0x7fffffff, False, type, &type_ret,
177                        &format_ret, &items_ret, &after_ret, &prop_data);
178     if (items)
179         *items = items_ret;
180
181     return prop_data;
182 }
183
184 void set_foreground(int index)
185 {
186     XSetForeground(dd, fore_gc, palette[index]);
187 }
188
189 void draw_line(int x, int y, int a, int b)
190 {
191     XDrawLine(dd, tb.win, fore_gc, x, y, a, b);
192 }
193
194 void fill_rect(int x, int y, int a, int b)
195 {
196     XFillRectangle(dd, tb.win, fore_gc, x, y, a, b);
197 }
198
199 void scale_icon(task * tk)
200 {
201     int xx, yy, x, y;
202     unsigned int w, h, d, bw;
203     Pixmap pix, mk = None;
204     XGCValues gcv;
205     GC mgc = mgc;
206
207     XGetGeometry(dd, tk->icon, &pix, &x, &y, &w, &h, &bw, &d);
208     pix = XCreatePixmap(dd, tk->win, ICONWIDTH, ICONHEIGHT, scr_depth);
209
210     if (tk->mask != None) {
211         mk = XCreatePixmap(dd, tk->win, ICONWIDTH, ICONHEIGHT, 1);
212         gcv.subwindow_mode = IncludeInferiors;
213         gcv.graphics_exposures = False;
214         mgc = XCreateGC(dd, mk, GCGraphicsExposures | GCSubwindowMode, &gcv);
215     }
216
217     set_foreground(3);
218
219     /* this is my simple & dirty scaling routine */
220     for (y = ICONHEIGHT - 1; y >= 0; y--) {
221         yy = (y * h) / ICONHEIGHT;
222         for (x = ICONWIDTH - 1; x >= 0; x--) {
223             xx = (x * w) / ICONWIDTH;
224             if (d != scr_depth)
225                 XCopyPlane(dd, tk->icon, pix, fore_gc, xx, yy, 1, 1, x, y, 1);
226             else
227                 XCopyArea(dd, tk->icon, pix, fore_gc, xx, yy, 1, 1, x, y);
228             if (mk != None)
229                 XCopyArea(dd, tk->mask, mk, mgc, xx, yy, 1, 1, x, y);
230         }
231     }
232
233     if (mk != None) {
234         XFreeGC(dd, mgc);
235         tk->mask = mk;
236     }
237
238     tk->icon = pix;
239 }
240
241 void get_task_hinticon(task * tk)
242 {
243     XWMHints *hin;
244
245     if (tk->icon != None && tk->icon != generic_icon)
246         if (tk->icon_copied) {
247             XFreePixmap(dd, tk->icon);
248             if (tk->mask != None && tk->mask != generic_mask)
249                 XFreePixmap(dd, tk->mask);
250         }
251
252     tk->icon = None;
253     tk->mask = None;
254
255     hin = (XWMHints *)get_prop_data(tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
256     if (hin) {
257         if ((hin->flags & IconPixmapHint)) {
258             if ((hin->flags & IconMaskHint)) {
259                 tk->mask = hin->icon_mask;
260             }
261
262             tk->icon = hin->icon_pixmap;
263             tk->icon_copied = 1;
264             scale_icon(tk);
265         }
266         XFree(hin);
267     }
268
269     if (tk->icon == None) {
270         tk->icon = generic_icon;
271         if (tk->mask != None)
272             XFreePixmap(dd, tk->mask);
273         tk->mask = generic_mask;
274     }
275 }
276
277 void get_task_kdeicon(task * tk)
278 {
279     unsigned long *data;
280
281     data = get_prop_data(tk->win, atoms[KWM_WIN_ICON], atoms[KWM_WIN_ICON], 0);
282     if (data) {
283         tk->icon = data[0];
284         tk->mask = data[1];
285         XFree(data);
286     }
287 }
288
289 int generic_get_int(Window win, Atom at)
290 {
291     int num = 0;
292     unsigned long *data;
293
294     data = get_prop_data(win, at, XA_CARDINAL, 0);
295     if (data) {
296         num = *data;
297         XFree(data);
298     }
299     return num;
300 }
301
302 int find_desktop(Window win)
303 {
304     return generic_get_int(win, atoms[_NET_WM_DESKTOP]);
305 }
306
307 int is_hidden(Window win)
308 {
309     unsigned long *data;
310     int ret = 0;
311     int num;
312
313     data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
314     if (data) {
315         while (num) {
316             num--;
317             if ((data[num]) == atoms[_NET_WM_STATE_SKIP_TASKBAR])
318                 ret = 1;
319         }
320         XFree(data);
321     }
322     return ret;
323 }
324
325 int is_iconified(Window win)
326 {
327     unsigned long *data;
328     int ret = 0;
329     int num;
330
331     data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
332     if (data) {
333         while (num) {
334             num--;
335             if ((data[num]) == atoms[_NET_WM_STATE_HIDDEN])
336                 ret = 1;
337         }
338         XFree(data);
339     }
340     return ret;
341 }
342
343 int is_shaded(Window win)
344 {
345     unsigned long *data;
346     int ret = 0;
347     int num;
348
349     data = get_prop_data(win, atoms[_NET_WM_STATE], XA_ATOM, &num);
350     if (data) {
351         while (num) {
352             num--;
353             if ((data[num]) == atoms[_NET_WM_STATE_SHADED])
354                 ret = 1;
355         }
356         XFree(data);
357     }
358     return ret;
359 }
360
361 int get_current_desktop(void)
362 {
363     return generic_get_int(root_win, atoms[_NET_CURRENT_DESKTOP]);
364 }
365
366 int get_number_of_desktops(void)
367 {
368     return generic_get_int(root_win, atoms[_NET_NUMBER_OF_DESKTOPS]);
369 }
370
371 void add_task(Window win, int focus)
372 {
373     task *tk, *list;
374     int desk;
375
376     if (win == tb.win)
377         return;
378
379     /* is this window on a different desktop? */
380     desk = find_desktop(win);
381     if ((desk != 0xffffffff && tb.my_desktop != desk) || is_hidden(win))
382         return;
383
384     tk = calloc(1, sizeof(task));
385     tk->win = win;
386     tk->focused = focus;
387     tk->name = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
388                get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
389 //    tk->name = get_prop_data(win, XA_WM_NAME, XA_STRING, 0);
390     //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
391     tk->iconified = is_iconified(win);
392     tk->shaded = is_shaded(win);
393
394     get_task_kdeicon(tk);
395     if (tk->icon == None)
396         get_task_hinticon(tk);
397
398     XSelectInput(dd, win, PropertyChangeMask | FocusChangeMask
399                                              | StructureNotifyMask);
400
401     /* now append it to our linked list */
402     tb.num_tasks++;
403
404     list = tb.task_list;
405     if (!list) {
406         tb.task_list = tk;
407         return;
408     }
409     while (1) {
410         if (!list->next) {
411             list->next = tk;
412             return;
413         }
414         list = list->next;
415     }
416 }
417
418 void gui_sync(void)
419 {
420     XSync(dd, False);
421 }
422
423 void set_prop(Window win, Atom at, Atom type, long val)
424 {
425     XChangeProperty(dd, win, at, type, 32,
426                     PropModeReplace, (unsigned char *)&val, 1);
427 }
428
429 Window gui_create_taskbar(void)
430 {
431     Window win;
432     MWMHints mwm;
433     XSizeHints size_hints;
434     XWMHints wmhints;
435     XSetWindowAttributes att;
436     XClassHint xclhints;
437
438     att.background_pixel = palette[0];
439     att.event_mask = ButtonPressMask | ExposureMask;
440
441     win = rspanelwin = XCreateWindow(/* display  */ dd,
442                                      /* parent   */ root_win,
443                                      /* x        */ 0,
444                                      /* y        */ scr_height - WINHEIGHT,
445                                      /* width    */ WINWIDTH,
446                                      /* height   */ WINHEIGHT,
447                                      /* border   */ 0,
448                                      /* depth    */ CopyFromParent,
449                                      /* class    */ InputOutput,
450                                      /* visual   */ CopyFromParent,
451                                      /*value mask*/ CWBackPixel | CWEventMask,
452                                      /* attribs  */ &att);
453
454     /* reside on ALL desktops */
455     set_prop(win, atoms[_NET_WM_DESKTOP], XA_CARDINAL, 0xFFFFFFFF);
456     set_prop(win, atoms[_NET_WM_WINDOW_TYPE], XA_ATOM,
457              atoms[_NET_WM_WINDOW_TYPE_DOCK]);
458     set_prop(win, atoms[_NET_WM_STATE], XA_ATOM, atoms[_NET_WM_STATE_BELOW]);
459     /* use old gnome hint since sawfish doesn't support _NET_WM_STRUT */
460     set_prop(win, atoms[_WIN_HINTS], XA_CARDINAL,
461              WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST |
462              WIN_HINTS_SKIP_TASKBAR | WIN_HINTS_DO_NOT_COVER);
463     XChangeProperty(dd, win, XA_WM_NAME, XA_STRING, 8, PropModeReplace,
464                     (unsigned char *)"rspanel", 7);
465
466     /* borderless motif hint */
467     bzero(&mwm, sizeof(mwm));
468     mwm.flags = MWM_HINTS_DECORATIONS;
469     XChangeProperty(dd, win, atoms[_MOTIF_WM_HINTS], atoms[_MOTIF_WM_HINTS], 32,
470                     PropModeReplace, (unsigned char *)&mwm,
471                     sizeof(MWMHints) / 4);
472
473     /* make sure the WM obays our window position */
474     size_hints.flags = PPosition;
475     /*XSetWMNormalHints (dd, win, &size_hints); */
476     XChangeProperty(dd, win, XA_WM_NORMAL_HINTS, XA_WM_SIZE_HINTS, 32,
477                     PropModeReplace, (unsigned char *)&size_hints,
478                     sizeof(XSizeHints) / 4);
479
480     /* make our window unfocusable */
481     wmhints.flags = InputHint;
482     wmhints.input = False;
483     /*XSetWMHints (dd, win, &wmhints); */
484     XChangeProperty(dd, win, XA_WM_HINTS, XA_WM_HINTS, 32, PropModeReplace,
485                     (unsigned char *)&wmhints, sizeof(XWMHints) / 4);
486
487     xclhints.res_name = "rspanel";
488     xclhints.res_class = "RSPanel";
489     XSetClassHint(dd, win, &xclhints);
490
491     XMapWindow(dd, win);
492
493 #ifdef XFT
494     xftdraw = XftDrawCreate(dd, win, DefaultVisual(dd, scr_screen),
495                             DefaultColormap(dd, scr_screen));
496 #endif
497 #ifdef PANGO
498     g_type_init();
499 #endif
500
501     return win;
502 }
503
504 void gui_init(void)
505 {
506     XGCValues gcv;
507     XColor xcl;
508     unsigned int i;
509 #ifndef XFT
510     char *fontname;
511 #endif
512 #ifdef PANGO
513     PangoContext *context;
514     PangoFontDescription *pfd;
515 #endif
516
517     i = 0;
518     do {
519         xcl.red = cols[i].red;
520         xcl.green = cols[i].green;
521         xcl.blue = cols[i].blue;
522         XAllocColor(dd, DefaultColormap(dd, scr_screen), &xcl);
523         palette[i] = xcl.pixel;
524         i++;
525     } while (i < PALETTE_COUNT);
526
527 #ifdef PANGO
528 #elif XFT
529     xfs = XftFontOpenName(dd, scr_screen, XFT_FONT);
530 #else
531     fontname = FONT_NAME;
532     do {
533         xfs = XLoadQueryFont(dd, fontname);
534         fontname = "fixed";
535     } while (!xfs);
536 #endif
537
538     gcv.graphics_exposures = False;
539 #ifndef PANGO
540 # ifdef XFT
541     text_y = xfs->ascent + ((WINHEIGHT - (xfs->ascent + xfs->descent)) / 2);
542     fore_gc = XCreateGC(dd, root_win, GCGraphicsExposures, &gcv);
543 # else
544     text_y = xfs->ascent + ((WINHEIGHT - xfs->ascent) / 2);
545     gcv.font = xfs->fid;
546     fore_gc = XCreateGC(dd, root_win, GCFont | GCGraphicsExposures, &gcv);
547 # endif
548 #else
549     pfd = pango_font_description_new();
550     pango_font_description_set_absolute_size(pfd, PANGO_FONT_SIZE*PANGO_SCALE);
551     pango_font_description_set_family(pfd, PANGO_FONT_PREF);
552     context = pango_xft_get_context(dd, root_win);
553     pango_context_set_font_description(context, pfd);
554     pl = pango_layout_new(context);
555     
556     pango_layout_set_single_paragraph_mode(pl, TRUE);
557     pango_layout_set_ellipsize(pl, PANGO_ELLIPSIZE_END);
558
559     text_y = (WINHEIGHT*PANGO_SCALE-7*PANGO_SCALE);
560     pango_font_description_free(pfd);
561     g_object_unref(context);
562
563     fore_gc = XCreateGC(dd, root_win, GCGraphicsExposures, &gcv);
564 #endif
565
566 #ifdef HAVE_XPM
567     XpmCreatePixmapFromData(dd, root_win, icon_xpm, &generic_icon,
568                             &generic_mask, NULL);
569 #else
570     generic_icon = 0;
571 #endif
572 }
573
574 void gui_draw_vline(int x)
575 {
576     set_foreground(4);
577     draw_line(x, 0, x, WINHEIGHT);
578     set_foreground(3);
579     draw_line(x + 1, 0, x + 1, WINHEIGHT);
580 }
581
582 void draw_box(int x, int width)
583 {
584     set_foreground(1);                /* mid gray */
585     fill_rect(x + 3, 2, width - 2, WINHEIGHT - 4);
586
587     set_foreground(3);                /* white */
588     draw_line(x + 3, WINHEIGHT - 2, x + width - 1, WINHEIGHT - 2);
589     draw_line(x + width - 1, 1, x + width - 1, WINHEIGHT - 2);
590
591     set_foreground(4);                /* darkest gray */
592     draw_line(x + 3, 1, x + width - 1, 1);
593     draw_line(x + 3, 2, x + 3, WINHEIGHT - 3);
594 }
595
596 void gui_draw_task(task * tk)
597 {
598     int x = tk->pos_x;
599     int taskw = tk->width;
600 #ifdef XFT
601 #ifndef PANGO
602     int len;
603     XGlyphInfo ext;
604 #endif
605     XftColor col;
606 #endif
607
608     gui_draw_vline(x);
609
610     if (tk->focused) {
611         draw_box(x, taskw);
612     } else {
613         set_foreground(0);        /* mid gray */
614         fill_rect(x + 2, 0, taskw - 1, WINHEIGHT);
615     }
616
617     if (tk->name) {
618         int text_x = x + TEXTPAD + TEXTPAD + (tk->icon ? ICONWIDTH : -1*ICONWIDTH/4);
619 #define SETCOL(x) col.color.red = cols[x].red;\
620                   col.color.green = cols[x].green;\
621                   col.color.blue = cols[x].blue;
622 #ifdef PANGO
623
624     pango_layout_set_width(pl, /*-1);*/(taskw - text_x + x) * PANGO_SCALE);
625     pango_layout_set_text(pl, tk->name, -1);
626     col.color.alpha = 0xffff;
627
628     if (tk->iconified) {
629         SETCOL(3)
630         pango_xft_render_layout_line(xftdraw, &col, pango_layout_get_line(pl, 0), (text_x+2)*PANGO_SCALE, text_y + 2);
631         SETCOL(4)
632     } else if (tk->shaded) {
633         SETCOL(3)
634         pango_xft_render_layout_line(xftdraw, &col, pango_layout_get_line(pl, 0), (text_x-2)*PANGO_SCALE, text_y - 2);
635         SETCOL(4)
636     } else {
637         SETCOL(5)
638     }
639     pango_xft_render_layout_line(xftdraw, &col, pango_layout_get_line(pl, 0), text_x*PANGO_SCALE, text_y);
640     
641 #elif XFT
642
643         /* check how many chars can fit */
644         len = strlen(tk->name);
645         while (1) {
646             XftTextExtentsUtf8(dd, xfs, tk->name, len, &ext);
647             if (ext.width < taskw - (text_x - x) - 2 || len <= 0)
648                 break;
649             len--;
650         }
651
652         col.color.alpha = 0xffff;
653
654         if (tk->iconified) {
655             /* draw task's name dark (iconified) */
656             SETCOL(3)
657             XftDrawStringUtf8(xftdraw, &col, xfs, text_x, text_y + 1, tk->name,
658                            len);
659             SETCOL(4)
660         } else if (tk->shaded) {
661             /* draw task's name dark (shaded) */
662             SETCOL(3)
663             XftDrawStringUtf8(xftdraw, &col, xfs, text_x, text_y - 1, tk->name,
664                            len);
665             SETCOL(4)
666         } else {
667             SETCOL(5)
668 #undef SETCOL
669         }
670
671         /* draw task's name here */
672         XftDrawStringUtf8(xftdraw, &col, xfs, text_x, text_y, tk->name, len);
673 #else
674
675         /* check how many chars can fit */
676         len = strlen(tk->name);
677
678         while (XTextWidth(xfs, tk->name, len) >= taskw - (text_x - x) - 2
679                && len > 0)
680             len--;
681
682         if (tk->iconified) {
683             /* draw task's name dark (iconified) */
684             set_foreground(3);
685             XDrawString(dd, tb.win, fore_gc, text_x, text_y + 1, tk->name,
686                         len);
687             set_foreground(4);
688         } else if (tk->shaded) {
689             /* draw task's name dark (shaded) */
690             set_foreground(3);
691             XDrawString(dd, tb.win, fore_gc, text_x, text_y - 1, tk->name,
692                         len);
693             set_foreground(4);
694         } else {
695             set_foreground(5);
696         }
697
698         /* draw task's name here */
699         XDrawString(dd, tb.win, fore_gc, text_x, text_y, tk->name, len);
700 #endif
701     }
702
703 #ifndef HAVE_XPM
704     if (!tk->icon)
705         return;
706 #endif
707
708     /* draw the task's icon */
709     XSetClipMask(dd, fore_gc, tk->mask);
710     XSetClipOrigin(dd, fore_gc, x + TEXTPAD, (WINHEIGHT - ICONHEIGHT) / 2);
711     XCopyArea(dd, tk->icon, tb.win, fore_gc, 0, 0, ICONWIDTH, ICONHEIGHT,
712               x + TEXTPAD, (WINHEIGHT - ICONHEIGHT) / 2);
713     XSetClipMask(dd, fore_gc, None);
714 }
715
716 void draw_dot(int x, int y)
717 {
718     set_foreground(4);
719     XDrawPoint(dd, tb.win, fore_gc, x, y);
720     set_foreground(3);
721     XDrawPoint(dd, tb.win, fore_gc, x + 1, y + 1);
722 }
723
724 void draw_grill(int x)
725 {
726     int y = 0;
727     while (y < WINHEIGHT - 4) {
728         y += 3;
729         draw_dot(x + 3, y);
730         draw_dot(x, y);
731     }
732 }
733
734 #ifdef MIKACHU //this only works in mikabox
735 void ob_action(Window win, char *action)
736 {
737     XClientMessageEvent xev;
738     char act_name[14+strlen(action)];
739     sprintf(act_name, "_OB_WM_ACTION_%s", action);
740
741     xev.type = ClientMessage;
742     xev.window = win;
743     xev.message_type = atoms[_OB_WM_ACTION];
744     xev.format = 32;
745     /*strncpy(xev.data.b, action, 20);*/
746     xev.data.l[0] = XInternAtom(dd, act_name, False);
747     XSendEvent(dd, root_win, False, SubstructureRedirectMask, (XEvent *)&xev);
748 }
749 #endif
750 void netwm_action(Window win, atom_t atom, Time time, long l)
751 {
752     XClientMessageEvent xev = {
753     .type = ClientMessage,
754     .window = win,
755     .format = 32,
756     .data.l = {0},
757     .message_type = atoms[atom]
758     };
759     
760     if (atom == _NET_ACTIVE_WINDOW) {
761         xev.data.l[0] = 2;
762         xev.data.l[1] = time;
763         xev.data.l[2] = 0;
764     } else if (atom == _NET_CURRENT_DESKTOP) {
765         xev.data.l[0] = l;
766         xev.data.l[1] = time;
767     } else if (atom == _NET_RESTACK_WINDOW) {
768         xev.data.l[0] = 2;
769         xev.data.l[2] = l;
770     } else {
771         xev.message_type = atoms[_NET_WM_STATE];
772         xev.data.l[0] = l;
773         xev.data.l[1] = atoms[atom];
774         xev.data.l[3] = 2;
775     }
776
777     XSendEvent(dd, root_win, False, SubstructureNotifyMask|SubstructureRedirectMask, (XEvent *)&xev);
778 }
779
780 #ifdef PAGER
781
782 void switch_desk(int new_desk, Time time)
783 {
784     if (get_number_of_desktops() <= new_desk)
785         return;
786
787     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
788 }
789
790 void pager_draw_button(int x, int num)
791 {
792     char label;
793 #ifdef XFT
794     XftColor col;
795 #endif
796
797     if (num == tb.my_desktop) {
798         /* current desktop */
799         draw_box(x, PAGER_BUTTON_WIDTH);
800     } else {
801         set_foreground(0);
802         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
803     }
804
805     label = '1' + num;
806
807 #ifdef XFT
808     col.color.alpha = 0xffff;
809     col.color.red = cols[5].red;
810     col.color.green = cols[5].green;
811     col.color.blue = cols[5].blue;
812     XftDrawString8(xftdraw, &col, xfs,
813                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
814                    &label, 1);
815 #else
816     set_foreground(5);
817     XDrawString(dd, tb.win, fore_gc,
818                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
819                 text_y, &label, 1);
820 #endif
821 }
822
823 void pager_draw(void)
824 {
825     int desks, i, x = GRILL_WIDTH;
826
827     desks = get_number_of_desktops();
828
829     for (i = 0; i < desks; i++) {
830         pager_draw_button(x, i);
831         if (i > 8)
832             break;
833         x += PAGER_BUTTON_WIDTH;
834     }
835
836     pager_size = x;
837 }
838
839 #endif
840
841 void gui_draw_taskbar(void)
842 {
843     task *tk;
844     int x, width, taskw;
845
846 #ifdef PAGER
847     pager_draw();
848 #else
849     pager_size = TEXTPAD;
850 #endif
851
852     width = WINWIDTH - (pager_size + GRILL_WIDTH + GRILL_WIDTH);
853     x = pager_size + 2;
854
855     if (tb.num_tasks == 0)
856         goto clear;
857
858     taskw = width / tb.num_tasks;
859     if (taskw > MAX_TASK_WIDTH)
860         taskw = MAX_TASK_WIDTH;
861
862     tk = tb.task_list;
863     while (tk) {
864         tk->pos_x = x;
865         tk->width = taskw - 1;
866         gui_draw_task(tk);
867         x += taskw;
868         tk = tk->next;
869     }
870
871     if (x < (width + pager_size + 2)) {
872 clear:
873         gui_draw_vline(x);
874         set_foreground(0);
875         fill_rect(x + 2, 0, WINWIDTH, WINHEIGHT);
876     }
877
878     gui_draw_vline(8);
879     gui_draw_vline(WINWIDTH - 8);
880
881     draw_grill(2);
882     draw_grill(WINWIDTH - 6);
883 }
884
885 task *find_task(Window win)
886 {
887     task *list = tb.task_list;
888     while (list) {
889         if (list->win == win)
890             return list;
891         list = list->next;
892     }
893     return 0;
894 }
895
896 void del_task(Window win)
897 {
898     task *next, *prev = 0, *list = tb.task_list;
899
900     while (list) {
901         next = list->next;
902         if (list->win == win) {
903             /* unlink and free this task */
904             tb.num_tasks--;
905             if (list->icon_copied) {
906                 XFreePixmap(dd, list->icon);
907                 if (list->mask != None)
908                     XFreePixmap(dd, list->mask);
909             }
910             if (list->name)
911                 XFree(list->name);
912             free(list);
913             if (prev == 0)
914                 tb.task_list = next;
915             else
916                 prev->next = next;
917             return;
918         }
919         prev = list;
920         list = next;
921     }
922 }
923
924 void move_taskbar(void)
925 {
926     int x, y;
927
928     x = y = 0;
929
930     if (tb.hidden)
931         x = TEXTPAD - WINWIDTH;
932
933     if (!tb.at_top)
934         y = scr_height - WINHEIGHT;
935
936     XMoveWindow(dd, tb.win, x, y);
937 }
938
939 void taskbar_read_clientlist(void)
940 {
941     Window *win, focus_win = 0;
942     int num, i, desk, new_desk = 0;
943     task *list, *next;
944     desk = get_current_desktop();
945 #ifdef MIKACHU
946     if (desk == 0)
947         WINWIDTH = 827;
948     else
949         WINWIDTH = 1280;
950
951     XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
952     move_taskbar();
953 #endif
954     if (desk != tb.my_desktop) {
955         new_desk = 1;
956         tb.my_desktop = desk;
957     }
958
959     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
960     if (win && num > 0)
961         focus_win = win[0];
962
963     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
964     if (!win)
965         return;
966
967     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
968     list = tb.task_list;
969     while (list) {
970         list->focused = (focus_win == list->win);
971         next = list->next;
972
973         if (!new_desk)
974             for (i = num - 1; i >= 0; i--)
975                 if (list->win == win[i] && !is_hidden(win[i]))
976                     goto dontdel;
977         del_task(list->win);
978 dontdel:
979
980         list = next;
981     }
982
983     /* add any new windows */
984     for (i = 0; i < num; i++) {
985         if (!find_task(win[i]))
986             add_task(win[i], (win[i] == focus_win));
987     }
988
989     XFree(win);
990 }
991
992 void handle_press(int x, int y, int button, Time time)
993 {
994     task *tk;
995
996 #ifdef PAGER
997     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
998         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
999     else
1000 #endif
1001     /* clicked left grill */
1002     if (x < 6) {
1003         if (button == 1) {
1004             tb.at_top = 1 - tb.at_top;
1005             move_taskbar();
1006         } else if (button == 4) {
1007             tb.hidden = 1;
1008             move_taskbar();
1009         }
1010     }
1011
1012     /* clicked right grill */
1013     else if (x + TEXTPAD > WINWIDTH) {
1014         if (tb.hidden && (button == 1 || button == 5)) {
1015             tb.hidden = 0;
1016             move_taskbar();
1017         } else if (!tb.hidden && (button == 1 || button == 4)) {
1018             tb.hidden = 1;
1019             move_taskbar();
1020         }
1021     } else {
1022         tk = tb.task_list;
1023         while (tk) {
1024             /* clicked on a task button */
1025             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
1026                 switch (button) {
1027                     case 1:
1028 //                        if (tk->iconified && tk->shaded)
1029 //                            ob_action(tk->win, "unshade");
1030 // probably not needed        netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
1031                         //ob_action(tk->win, "activate");
1032                         netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
1033                         break;
1034                     case 2:
1035                         if (tk->iconified)
1036                             //ob_action(tk->win, "deiconify");
1037                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
1038                         else
1039                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
1040                             //ob_action(tk->win, "iconify");
1041                         break;
1042                     case 3:
1043                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
1044                         //ob_action(tk->win, "raiselower");
1045                         break;
1046                     case 4:
1047                         if (is_shaded(tk->win))
1048                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
1049                         else
1050                                 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
1051                         break;
1052                     case 5:
1053                         if (is_shaded(tk->win))
1054                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
1055                         else
1056                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
1057                         break;
1058                     case 9:
1059                         if (tk->iconified)
1060                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
1061                             //ob_action(tk->win, "deiconify");
1062 #ifdef MIKACHU //this doesn't work for anyone else anyway
1063                         else
1064                             ob_action(tk->win, "focus");
1065 #endif
1066                         break;
1067                     case 6:
1068 //                        ob_action(tk->win, "lower");
1069                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
1070                         break;
1071                     case 7:
1072 //                        ob_action(tk->win, "raise");
1073                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
1074                         break;
1075                 }
1076                 return;
1077             }
1078             tk = tk->next;
1079         } /* clicked on the background */
1080         switch (button) {
1081             case 1:
1082                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
1083                 //ob_action(tb.win, "raise");
1084                 break;
1085             case 2:
1086                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
1087 //                ob_action(tb.win, "lower");
1088                 break;
1089             case 3:
1090                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
1091 //                ob_action(tb.win, "raiselower");
1092                 break;
1093             case 4:
1094                 tb.hidden = 1;
1095                 move_taskbar();
1096                 break;
1097             case 5:
1098                 tb.hidden = 0;
1099                 move_taskbar();
1100                 break;
1101         }
1102     }
1103 }
1104
1105 void handle_focusin(Window win)
1106 {
1107     task *tk;
1108
1109     tk = tb.task_list;
1110     while (tk) {
1111         if (tk->focused) {
1112             if (tk->win != win) {
1113                 tk->focused = 0;
1114                 gui_draw_task(tk);
1115             }
1116         } else {
1117             if (tk->win == win) {
1118                 tk->focused = 1;
1119                 gui_draw_task(tk);
1120             }
1121         }
1122         tk = tk->next;
1123     }
1124 }
1125
1126 void handle_propertynotify(Window win, Atom at)
1127 {
1128     task *tk;
1129
1130     if (win == root_win) {
1131         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP] || at == atoms[_NET_CLIENT_LIST_STACKING]) {
1132             taskbar_read_clientlist();
1133             gui_draw_taskbar();
1134         }
1135         return;
1136     }
1137
1138     tk = find_task(win);
1139     if (!tk)
1140         return;
1141
1142     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
1143         /* window's title changed */
1144         char *newname;
1145         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
1146                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
1147         if (newname) {
1148             if (tk->name && !strcmp(newname, tk->name)) {
1149                 XFree(newname);
1150                 return;
1151             }
1152         }
1153         if (tk->name)
1154             XFree(tk->name);
1155         tk->name = newname;
1156         gui_draw_task(tk);
1157     } else if (at == atoms[_NET_WM_STATE]) {
1158         /* iconified state changed? */
1159         if (is_iconified(tk->win) != tk->iconified) {
1160             tk->iconified = !tk->iconified;
1161             gui_draw_task(tk);
1162         }
1163         /* shaded state changed? */
1164         if (is_shaded(tk->win) != tk->shaded) {
1165             tk->shaded = !tk->shaded;
1166             gui_draw_task(tk);
1167         }
1168     } else if (at == XA_WM_HINTS) {
1169         /* some windows set their WM_HINTS icon after mapping */
1170         //if (tk->icon == generic_icon) {
1171             get_task_hinticon(tk);
1172             gui_draw_task(tk);
1173         //}
1174     } else if (at == atoms[_NET_WM_DESKTOP]) {
1175         if (find_desktop(win) != get_current_desktop())
1176             del_task(tk->win);
1177     }
1178 }
1179
1180 void handle_error(Display * d, XErrorEvent * ev)
1181 {
1182 }
1183
1184 int
1185 #ifdef NOSTDLIB
1186 _start(void)
1187 #else
1188 main(int argc, char *argv[])
1189 #endif
1190 {
1191     XEvent ev;
1192     fd_set fd;
1193     int xfd;
1194
1195     dd = XOpenDisplay(NULL);
1196     if (!dd)
1197         return 0;
1198     scr_screen = DefaultScreen(dd);
1199     scr_depth = DefaultDepth(dd, scr_screen);
1200     scr_height = DisplayHeight(dd, scr_screen);
1201     scr_width = DisplayWidth(dd, scr_screen);
1202     root_win = RootWindow(dd, scr_screen);
1203
1204     /* helps us catch windows closing/opening */
1205     XSelectInput(dd, root_win, PropertyChangeMask);
1206
1207     XSetErrorHandler((XErrorHandler) handle_error);
1208
1209     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1210
1211     gui_init();
1212     bzero(&tb, sizeof(struct taskbar));
1213     tb.win = gui_create_taskbar();
1214     xfd = ConnectionNumber(dd);
1215     gui_sync();
1216
1217     while (1) {
1218         FD_ZERO(&fd);
1219         FD_SET(xfd, &fd);
1220         select(xfd + 1, &fd, 0, 0, 0);
1221
1222         while (XPending(dd)) {
1223             XNextEvent(dd, &ev);
1224             switch (ev.type) {
1225             case ButtonPress:
1226                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
1227                 break;
1228             case DestroyNotify:
1229                 del_task(ev.xdestroywindow.window);
1230                 /* fall through */
1231             case Expose:
1232                 gui_draw_taskbar();
1233                 break;
1234             case PropertyNotify:
1235                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1236                 break;
1237             case FocusIn:
1238                 if (ev.xfocus.mode != NotifyGrab)
1239                     handle_focusin(ev.xfocus.window);
1240                 break;
1241             /*default:
1242                    printf ("unknown evt type: %d\n", ev.type); */
1243             }
1244         }
1245     }
1246
1247     /*XCloseDisplay (dd);
1248
1249        return 0; */
1250 }
1251