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