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