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