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