completely netwm using now! yay. next stop: configure mouse button actions or maybe...
[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 #ifdef MIKACHU //this only works in mikabox
755 void ob_action(Window win, char *action)
756 {
757     XClientMessageEvent xev;
758     char act_name[14+strlen(action)];
759     sprintf(act_name, "_OB_WM_ACTION_%s", action);
760
761     xev.type = ClientMessage;
762     xev.window = win;
763     xev.message_type = atoms[_OB_WM_ACTION];
764     xev.format = 32;
765     /*strncpy(xev.data.b, action, 20);*/
766     xev.data.l[0] = XInternAtom(dd, act_name, False);
767     XSendEvent(dd, root_win, False, SubstructureRedirectMask, (XEvent *)&xev);
768 }
769 #endif
770 void netwm_action(Window win, atom_t atom, Time time, long l)
771 {
772     XClientMessageEvent xev = {
773     .type = ClientMessage,
774     .window = win,
775     .format = 32,
776     .data.l = {0},
777     .message_type = atoms[atom]
778     };
779     
780     if (atom == _NET_ACTIVE_WINDOW) {
781         xev.data.l[0] = 2;
782         xev.data.l[1] = time;
783         xev.data.l[2] = 0;
784     } else if (atom == _NET_CURRENT_DESKTOP) {
785         xev.data.l[0] = l;
786         xev.data.l[1] = time;
787     } else if (atom == _NET_RESTACK_WINDOW) {
788         xev.data.l[0] = 2;
789         xev.data.l[2] = l;
790     } else {
791         xev.message_type = atoms[_NET_WM_STATE];
792         xev.data.l[0] = l;
793         xev.data.l[1] = atoms[atom];
794         xev.data.l[3] = 2;
795     }
796
797     XSendEvent(dd, root_win, False, SubstructureNotifyMask|SubstructureRedirectMask, (XEvent *)&xev);
798 }
799
800 #ifdef PAGER
801
802 void switch_desk(int new_desk, Time time)
803 {
804     if (get_number_of_desktops() <= new_desk)
805         return;
806
807     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
808 }
809
810 void pager_draw_button(int x, int num)
811 {
812     char label;
813 #ifdef XFT
814     XftColor col;
815 #endif
816
817     if (num == tb.my_desktop) {
818         /* current desktop */
819         draw_box(x, PAGER_BUTTON_WIDTH);
820     } else {
821         set_foreground(0);
822         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
823     }
824
825     label = '1' + num;
826
827 #ifdef XFT
828     col.color.alpha = 0xffff;
829     col.color.red = cols[5].red;
830     col.color.green = cols[5].green;
831     col.color.blue = cols[5].blue;
832     XftDrawString8(xftdraw, &col, xfs,
833                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
834                    &label, 1);
835 #else
836     set_foreground(5);
837     XDrawString(dd, tb.win, fore_gc,
838                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
839                 text_y, &label, 1);
840 #endif
841 }
842
843 void pager_draw(void)
844 {
845     int desks, i, x = GRILL_WIDTH;
846
847     desks = get_number_of_desktops();
848
849     for (i = 0; i < desks; i++) {
850         pager_draw_button(x, i);
851         if (i > 8)
852             break;
853         x += PAGER_BUTTON_WIDTH;
854     }
855
856     pager_size = x;
857 }
858
859 #endif
860
861 void gui_draw_taskbar(void)
862 {
863     task *tk;
864     int x, width, taskw;
865
866 #ifdef PAGER
867     pager_draw();
868 #else
869     pager_size = TEXTPAD;
870 #endif
871
872     width = WINWIDTH - (pager_size + GRILL_WIDTH + GRILL_WIDTH);
873     x = pager_size + 2;
874
875     if (tb.num_tasks == 0)
876         goto clear;
877
878     taskw = width / tb.num_tasks;
879     if (taskw > MAX_TASK_WIDTH)
880         taskw = MAX_TASK_WIDTH;
881
882     tk = tb.task_list;
883     while (tk) {
884         tk->pos_x = x;
885         tk->width = taskw - 1;
886         gui_draw_task(tk);
887         x += taskw;
888         tk = tk->next;
889     }
890
891     if (x < (width + pager_size + 2)) {
892 clear:
893         gui_draw_vline(x);
894         set_foreground(0);
895         fill_rect(x + 2, 0, WINWIDTH, WINHEIGHT);
896     }
897
898     gui_draw_vline(8);
899     gui_draw_vline(WINWIDTH - 8);
900
901     draw_grill(2);
902     draw_grill(WINWIDTH - 6);
903 }
904
905 task *find_task(Window win)
906 {
907     task *list = tb.task_list;
908     while (list) {
909         if (list->win == win)
910             return list;
911         list = list->next;
912     }
913     return 0;
914 }
915
916 void del_task(Window win)
917 {
918     task *next, *prev = 0, *list = tb.task_list;
919
920     while (list) {
921         next = list->next;
922         if (list->win == win) {
923             /* unlink and free this task */
924             tb.num_tasks--;
925             if (list->icon_copied) {
926                 XFreePixmap(dd, list->icon);
927                 if (list->mask != None)
928                     XFreePixmap(dd, list->mask);
929             }
930             if (list->name)
931                 XFree(list->name);
932             free(list);
933             if (prev == 0)
934                 tb.task_list = next;
935             else
936                 prev->next = next;
937             return;
938         }
939         prev = list;
940         list = next;
941     }
942 }
943
944 void move_taskbar(void)
945 {
946     int x, y;
947
948     x = y = 0;
949
950     if (tb.hidden)
951         x = TEXTPAD - WINWIDTH;
952
953     if (!tb.at_top)
954         y = scr_height - WINHEIGHT;
955
956     XMoveWindow(dd, tb.win, x, y);
957 }
958
959 void taskbar_read_clientlist(void)
960 {
961     Window *win, focus_win = 0;
962     int num, i, desk, new_desk = 0;
963     task *list, *next;
964     desk = get_current_desktop();
965 #ifdef MIKACHU
966     if (desk == 0)
967         WINWIDTH = 827;
968     else
969         WINWIDTH = 1280;
970
971     XResizeWindow(dd, rspanelwin, WINWIDTH, WINHEIGHT);
972     move_taskbar();
973 #endif
974     if (desk != tb.my_desktop) {
975         new_desk = 1;
976         tb.my_desktop = desk;
977     }
978
979     win = get_prop_data(root_win, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, &num);
980     if (win && num > 0)
981         focus_win = win[0];
982
983     win = get_prop_data(root_win, atoms[_NET_CLIENT_LIST], XA_WINDOW, &num);
984     if (!win)
985         return;
986
987     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
988     list = tb.task_list;
989     while (list) {
990         list->focused = (focus_win == list->win);
991         next = list->next;
992
993         if (!new_desk)
994             for (i = num - 1; i >= 0; i--)
995                 if (list->win == win[i] && !is_hidden(win[i]))
996                     goto dontdel;
997         del_task(list->win);
998 dontdel:
999
1000         list = next;
1001     }
1002
1003     /* add any new windows */
1004     for (i = 0; i < num; i++) {
1005         if (!find_task(win[i]))
1006             add_task(win[i], (win[i] == focus_win));
1007     }
1008
1009     XFree(win);
1010 }
1011
1012 void handle_press(int x, int y, int button, Time time)
1013 {
1014     task *tk;
1015
1016 #ifdef PAGER
1017     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
1018         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
1019     else
1020 #endif
1021     /* clicked left grill */
1022     if (x < 6) {
1023         if (button == 1) {
1024             tb.at_top = 1 - tb.at_top;
1025             move_taskbar();
1026         } else if (button == 4) {
1027             tb.hidden = 1;
1028             move_taskbar();
1029         }
1030     }
1031
1032     /* clicked right grill */
1033     else if (x + TEXTPAD > WINWIDTH) {
1034         if (tb.hidden && (button == 1 || button == 5)) {
1035             tb.hidden = 0;
1036             move_taskbar();
1037         } else if (!tb.hidden && (button == 1 || button == 4)) {
1038             tb.hidden = 1;
1039             move_taskbar();
1040         }
1041     } else {
1042         tk = tb.task_list;
1043         while (tk) {
1044             /* clicked on a task button */
1045             if (x > tk->pos_x && x < tk->pos_x + tk->width) {
1046                 switch (button) {
1047                     case 1:
1048                         if (tk->iconified && tk->shaded)
1049 //                            ob_action(tk->win, "unshade");
1050 // probably not needed        netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
1051                         //ob_action(tk->win, "activate");
1052                         netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
1053                         break;
1054                     case 2:
1055                         if (tk->iconified)
1056                             //ob_action(tk->win, "deiconify");
1057                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
1058                         else
1059                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, ADD);
1060                             //ob_action(tk->win, "iconify");
1061                         break;
1062                     case 3:
1063                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
1064                         //ob_action(tk->win, "raiselower");
1065                         break;
1066                     case 4:
1067                         if (is_shaded(tk->win))
1068                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
1069                         else
1070                                 netwm_action(tk->win, _NET_WM_STATE_SHADED, time, ADD);
1071                         break;
1072                     case 5:
1073                         if (is_shaded(tk->win))
1074                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, REMOVE);
1075                         else
1076                                 netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
1077                         break;
1078                     case 9:
1079                         if (tk->iconified)
1080                             netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, REMOVE);
1081                             //ob_action(tk->win, "deiconify");
1082 #ifdef MIKACHU //this doesn't work for anyone else anyway
1083                         else
1084                             ob_action(tk->win, "focus");
1085 #endif
1086                         break;
1087                     case 6:
1088 //                        ob_action(tk->win, "lower");
1089                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
1090                         break;
1091                     case 7:
1092 //                        ob_action(tk->win, "raise");
1093                         netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
1094                         break;
1095                 }
1096                 return;
1097             }
1098             tk = tk->next;
1099         } /* clicked on the background */
1100         switch (button) {
1101             case 1:
1102                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
1103                 //ob_action(tb.win, "raise");
1104                 break;
1105             case 2:
1106                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
1107 //                ob_action(tb.win, "lower");
1108                 break;
1109             case 3:
1110                 netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
1111 //                ob_action(tb.win, "raiselower");
1112                 break;
1113             case 4:
1114                 tb.hidden = 1;
1115                 move_taskbar();
1116                 break;
1117             case 5:
1118                 tb.hidden = 0;
1119                 move_taskbar();
1120                 break;
1121         }
1122     }
1123 }
1124
1125 void handle_focusin(Window win)
1126 {
1127     task *tk;
1128
1129     tk = tb.task_list;
1130     while (tk) {
1131         if (tk->focused) {
1132             if (tk->win != win) {
1133                 tk->focused = 0;
1134                 gui_draw_task(tk);
1135             }
1136         } else {
1137             if (tk->win == win) {
1138                 tk->focused = 1;
1139                 gui_draw_task(tk);
1140             }
1141         }
1142         tk = tk->next;
1143     }
1144 }
1145
1146 void handle_propertynotify(Window win, Atom at)
1147 {
1148     task *tk;
1149
1150     if (win == root_win) {
1151         if (at == atoms[_NET_CLIENT_LIST] || at == atoms[_NET_CURRENT_DESKTOP] || at == atoms[_NET_CLIENT_LIST_STACKING]) {
1152             taskbar_read_clientlist();
1153             gui_draw_taskbar();
1154         }
1155         return;
1156     }
1157
1158     tk = find_task(win);
1159     if (!tk)
1160         return;
1161
1162     if (at == XA_WM_NAME || at == atoms[_NET_WM_NAME]) {
1163         /* window's title changed */
1164         char *newname;
1165         newname = get_prop_data(tk->win, atoms[_NET_WM_NAME], atoms[STRING_UTF8], 0) ?:
1166                   get_prop_data(tk->win, XA_WM_NAME, XA_STRING, 0);
1167         if (newname) {
1168             if (tk->name && !strcmp(newname, tk->name)) {
1169                 XFree(newname);
1170                 return;
1171             }
1172         }
1173         if (tk->name)
1174             XFree(tk->name);
1175         tk->name = newname;
1176         gui_draw_task(tk);
1177     } else if (at == atoms[_NET_WM_STATE]) {
1178         /* iconified state changed? */
1179         if (is_iconified(tk->win) != tk->iconified) {
1180             tk->iconified = !tk->iconified;
1181             gui_draw_task(tk);
1182         }
1183         /* shaded state changed? */
1184         if (is_shaded(tk->win) != tk->shaded) {
1185             tk->shaded = !tk->shaded;
1186             gui_draw_task(tk);
1187         }
1188     } else if (at == XA_WM_HINTS) {
1189         /* some windows set their WM_HINTS icon after mapping */
1190         //if (tk->icon == generic_icon) {
1191             get_task_hinticon(tk);
1192             gui_draw_task(tk);
1193         //}
1194     } else if (at == atoms[_NET_WM_DESKTOP]) {
1195         if (find_desktop(win) != get_current_desktop())
1196             del_task(tk->win);
1197     }
1198 }
1199
1200 void handle_error(Display * d, XErrorEvent * ev)
1201 {
1202 }
1203
1204 int
1205 #ifdef NOSTDLIB
1206 _start(void)
1207 #else
1208 main(int argc, char *argv[])
1209 #endif
1210 {
1211     XEvent ev;
1212     fd_set fd;
1213     int xfd;
1214
1215     dd = XOpenDisplay(NULL);
1216     if (!dd)
1217         return 0;
1218     scr_screen = DefaultScreen(dd);
1219     scr_depth = DefaultDepth(dd, scr_screen);
1220     scr_height = DisplayHeight(dd, scr_screen);
1221     scr_width = DisplayWidth(dd, scr_screen);
1222     root_win = RootWindow(dd, scr_screen);
1223
1224     /* helps us catch windows closing/opening */
1225     XSelectInput(dd, root_win, PropertyChangeMask);
1226
1227     XSetErrorHandler((XErrorHandler) handle_error);
1228
1229     XInternAtoms(dd, atom_names, ATOM_COUNT, False, atoms);
1230
1231     gui_init();
1232     bzero(&tb, sizeof(struct taskbar));
1233     tb.win = gui_create_taskbar();
1234     xfd = ConnectionNumber(dd);
1235     gui_sync();
1236
1237     while (1) {
1238         FD_ZERO(&fd);
1239         FD_SET(xfd, &fd);
1240         select(xfd + 1, &fd, 0, 0, 0);
1241
1242         while (XPending(dd)) {
1243             XNextEvent(dd, &ev);
1244             switch (ev.type) {
1245             case ButtonPress:
1246                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
1247                 break;
1248             case DestroyNotify:
1249                 del_task(ev.xdestroywindow.window);
1250                 /* fall through */
1251             case Expose:
1252                 gui_draw_taskbar();
1253                 break;
1254             case PropertyNotify:
1255                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
1256                 break;
1257             case FocusIn:
1258                 if (ev.xfocus.mode != NotifyGrab)
1259                     handle_focusin(ev.xfocus.window);
1260                 break;
1261             /*default:
1262                    printf ("unknown evt type: %d\n", ev.type); */
1263             }
1264         }
1265     }
1266
1267     /*XCloseDisplay (dd);
1268
1269        return 0; */
1270 }
1271