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