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