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