]> icculus.org git repositories - mikachu/rspanel.git/blob - rspanel.c
compile, then commit. compile, then commit. compile, then commit
[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 = 0;
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
918     win = get_prop_data(root_win, atom__NET_CLIENT_LIST, XA_WINDOW, &num);
919     if (!win)
920         return;
921
922     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
923     list = tb.task_list;
924     while (list) {
925         list->focused = (focus_win == list->win);
926         next = list->next;
927
928         if (!new_desk)
929             for (i = num - 1; i >= 0; i--)
930                 if (list->win == win[i] && !is_hidden(win[i]))
931                     goto dontdel;
932         del_task(list->win);
933 dontdel:
934
935         list = next;
936     }
937
938     /* add any new windows */
939     for (i = 0; i < num; i++) {
940         if (!find_task(win[i]))
941             add_task(win[i], (win[i] == focus_win));
942     }
943
944     XFree(win);
945 }
946
947 void handle_press(int x, int y, int button)
948 {
949     task *tk;
950
951 #ifdef PAGER
952     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
953         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH);
954     else
955 #endif
956     /* clicked left grill */
957     if (x < 6) {
958         if (button == 1) {
959             tb.at_top = 1 - tb.at_top;
960             move_taskbar();
961         } else if (button == 4) {
962             tb.hidden = 1;
963             move_taskbar();
964         }
965     }
966
967     /* clicked right grill */
968     else if (x + TEXTPAD > WINWIDTH) {
969         if (tb.hidden && (button == 1 || button == 5)) {
970             tb.hidden = 0;
971             move_taskbar();
972         } else if (!tb.hidden && (button == 1 || button == 4)) {
973             tb.hidden = 1;
974             move_taskbar();
975         }
976     } else {
977         tk = tb.task_list;
978         while (tk) {
979             /* clicked on a task button */
980             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
981                 switch (button) {
982                     case 1:
983                         if (tk->iconified && tk->shaded)
984                             ob_action(tk->win, "unshade");
985                         ob_action(tk->win, "activate");
986                         break;
987                     case 2:
988                         if (tk->iconified)
989                             ob_action(tk->win, "deiconify");
990                         else
991                             ob_action(tk->win, "iconify");
992                         break;
993                     case 3:
994                         ob_action(tk->win, "raiselower");
995                         break;
996                     case 4:
997                         ob_action(tk->win, "shadelower");
998                         break;
999                     case 5:
1000                         ob_action(tk->win, "unshaderaise");
1001                         break;
1002                     case 9:
1003                         if (tk->iconified)
1004                             ob_action(tk->win, "deiconify");
1005                         ob_action(tk->win, "focus");
1006                         break;
1007                     case 6:
1008                         ob_action(tk->win, "lower");
1009                         break;
1010                     case 7:
1011                         ob_action(tk->win, "raise");
1012                         break;
1013                 }
1014                 return;
1015             }
1016             tk = tk->next;
1017         } /* clicked on the background */
1018         switch (button) {
1019             case 1:
1020                 ob_action(tb.win, "raise");
1021                 break;
1022             case 2:
1023                 ob_action(tb.win, "lower");
1024                 break;
1025             case 3:
1026                 ob_action(tb.win, "raiselower");
1027                 break;
1028             case 4:
1029                 tb.hidden = 1;
1030                 move_taskbar();
1031                 break;
1032             case 5:
1033                 tb.hidden = 0;
1034                 move_taskbar();
1035                 break;
1036         }
1037     }
1038 }
1039
1040 void handle_focusin(Window win)
1041 {
1042     task *tk;
1043
1044     tk = tb.task_list;
1045     while (tk) {
1046         if (tk->focused) {
1047             if (tk->win != win) {
1048                 tk->focused = 0;
1049                 gui_draw_task(tk);
1050             }
1051         } else {
1052             if (tk->win == win) {
1053                 tk->focused = 1;
1054                 gui_draw_task(tk);
1055             }
1056         }
1057         tk = tk->next;
1058     }
1059 }
1060
1061 void handle_propertynotify(Window win, Atom at)
1062 {
1063     task *tk;
1064
1065     if (win == root_win) {
1066         if (at == atom__NET_CLIENT_LIST || at == atom__NET_CURRENT_DESKTOP || at == atom__NET_CLIENT_LIST_STACKING) {
1067             taskbar_read_clientlist();
1068             gui_draw_taskbar();
1069         }
1070         return;
1071     }
1072
1073     tk = find_task(win);
1074     if (!tk)
1075         return;
1076
1077     if (at == XA_WM_NAME || at == atom__NET_WM_NAME) {
1078         /* window's title changed */
1079         char *newname;
1080         newname = get_prop_data(tk->win, atom__NET_WM_NAME, atom_STRING_UTF8, 0) ?:
1081                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
1082         if (newname) {
1083             if (tk->name && !strcmp(newname, tk->name)) {
1084                 XFree(newname);
1085                 return;
1086             }
1087         }
1088         if (tk->name)
1089             XFree(tk->name);
1090         tk->name = newname;
1091         gui_draw_task(tk);
1092     } else if (at == atom__NET_WM_STATE) {
1093         /* iconified state changed? */
1094         if (is_iconified(tk->win) != tk->iconified) {
1095             tk->iconified = !tk->iconified;
1096             gui_draw_task(tk);
1097         }
1098         /* shaded state changed? */
1099         if (is_shaded(tk->win) != tk->shaded) {
1100             tk->shaded = !tk->shaded;
1101             gui_draw_task(tk);
1102         }
1103     } else if (at == XA_WM_HINTS) {
1104         /* some windows set their WM_HINTS icon after mapping */
1105         //if (tk->icon == generic_icon) {
1106             get_task_hinticon(tk);
1107             gui_draw_task(tk);
1108         //}
1109     } else if (at == atom__NET_WM_DESKTOP) {
1110         if (find_desktop(win) != get_current_desktop())
1111             del_task(tk->win);
1112     }
1113 }
1114
1115 void handle_error(Display * d, XErrorEvent * ev)
1116 {
1117 }
1118
1119 int
1120 #ifdef NOSTDLIB
1121 _start(void)
1122 #else
1123 main(int argc, char *argv[])
1124 #endif
1125 {
1126     XEvent ev;
1127     fd_set fd;
1128     int xfd;
1129
1130     dd = XOpenDisplay(NULL);
1131     if (!dd)
1132         return 0;
1133     scr_screen = DefaultScreen(dd);
1134     scr_depth = DefaultDepth(dd, scr_screen);
1135     scr_height = DisplayHeight(dd, scr_screen);
1136     scr_width = DisplayWidth(dd, scr_screen);
1137     root_win = RootWindow(dd, scr_screen);
1138
1139     /* helps us catch windows closing/opening */
1140     XSelectInput(dd, root_win, PropertyChangeMask);
1141
1142     XSetErrorHandler((XErrorHandler) handle_error);
1143
1144     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1145
1146     gui_init();
1147     bzero(&tb, sizeof(struct taskbar));
1148     tb.win = gui_create_taskbar();
1149     xfd = ConnectionNumber(dd);
1150     gui_sync();
1151
1152     while (1) {
1153         FD_ZERO(&fd);
1154         FD_SET(xfd, &fd);
1155         select(xfd + 1, &fd, 0, 0, 0);
1156
1157         while (XPending(dd)) {
1158             XNextEvent(dd, &ev);
1159             switch (ev.type) {
1160             case ButtonPress:
1161                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button);
1162                 break;
1163             case DestroyNotify:
1164                 del_task(ev.xdestroywindow.window);
1165                 /* fall through */
1166             case Expose:
1167                 gui_draw_taskbar();
1168                 break;
1169             case PropertyNotify:
1170                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1171                 break;
1172             case FocusIn:
1173                 handle_focusin(ev.xfocus.window);
1174                 break;
1175             /*default:
1176                    printf ("unknown evt type: %d\n", ev.type); */
1177             }
1178         }
1179     }
1180
1181     /*XCloseDisplay (dd);
1182
1183        return 0; */
1184 }
1185