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