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