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