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