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