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