d4caec35fe1a62a5b8208ff00e7ff0c6e26902ee
[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 "rspanel.h"
9 #include "gui.h"
10 #include "icon.h"
11 #include "dims.h"
12 #include "xprop.h"
13
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <unistd.h>
20
21 #include <X11/Xutil.h>
22 #include <iconv.h>
23
24
25 #include "rspanel.h"
26
27 screen sc;
28 taskbar tb;
29
30 static int find_desktop(Window win)
31 {
32     return xprop_get_num(&sc, win, _NET_WM_DESKTOP);
33 }
34
35 static int find_state(Window win, xprop_t prop)
36 {
37     unsigned long *data;
38     int ret = 0;
39     int num;
40
41     data = xprop_get_data(&sc, win, _NET_WM_STATE, XA_ATOM, &num);
42     if (data) {
43         while (num--) {
44             if ((data[num]) == sc.atoms[prop])
45                 ret = 1;
46         }
47         XFree(data);
48     }
49     return ret;
50 }
51
52 /* this naming is very confusing :) */
53 static int is_hidden(Window win)
54 {
55     return find_state(win, _NET_WM_STATE_SKIP_TASKBAR);
56 }
57
58 static int is_iconified(Window win)
59 {
60     return find_state(win, _NET_WM_STATE_HIDDEN);
61 }
62
63 static int is_shaded(Window win)
64 {
65     return find_state(win, _NET_WM_STATE_SHADED);
66 }
67
68 static char* get_openbox_theme()
69 {
70 }
71
72 static int get_current_desktop(void)
73 {
74     return xprop_get_num(&sc, sc.root, _NET_CURRENT_DESKTOP);
75 }
76
77 #ifdef PAGER
78 static int get_number_of_desktops(void)
79 {
80     return xprop_get_int(sc.root, _NET_NUMBER_OF_DESKTOPS);
81 }
82 #endif
83
84 static void free_icons(task *tk)
85 {
86     unsigned int i;
87
88     for (i = 0; i < tk->nicons; ++i)
89         free(tk->icons[i].data);
90     if (tk->nicons > 0)
91         free(tk->icons);
92     tk->nicons = 0;
93 }
94
95 static void task_update_icons(task *tk)
96 {
97     free_icons(tk);
98     tk->icons = icon_update(&sc, tk->win, &tk->nicons);
99 }
100
101 int task_shown(task *tk)
102 {
103     return tk->focused || !tk->hidden;
104 }
105
106 static void add_task(Window win, int focus)
107 {
108     task *tk, *list;
109     int desk;
110
111     if (win == tb.win)
112         return;
113
114     /* is this window on a different desktop? */
115     /* XXX add even if is_hidden, but don't show it later, so we can
116      * unhide it when the prop is removed */
117     desk = find_desktop(win);
118     if ((desk != 0xffffffff && tb.my_desktop != desk))
119         return;
120
121     XSelectInput(sc.dd, win, PropertyChangeMask);
122
123     tk = calloc(1, sizeof(task));
124     tk->win = win;
125     tk->focused = focus;
126     tk->name = xprop_get_utf8(&sc, tk->win, _NET_WM_NAME) ?:
127                xprop_get_string(&sc, tk->win, WM_NAME);
128     /* XXX use this? */
129     //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
130     tk->iconified = is_iconified(win);
131     tk->shaded = is_shaded(win);
132     tk->hidden = is_hidden(win);
133     task_update_icons(tk);
134
135     /* now append it to our linked list */
136
137     list = tb.task_list;
138     if (!list) {
139         tb.task_list = tk;
140         return;
141     }
142     while (1) {
143         if (!list->next) {
144             list->next = tk;
145             return;
146         }
147         list = list->next;
148     }
149 }
150
151 static void netwm_action(Window win, xprop_t prop, Time time, long l)
152 {
153     XClientMessageEvent xev = {
154     .type = ClientMessage,
155     .window = win,
156     .format = 32,
157     .data.l = {0},
158     .message_type = sc.atoms[prop]
159     };
160     
161     if (prop == _NET_ACTIVE_WINDOW) {
162         xev.data.l[0] = 2;
163         xev.data.l[1] = time;
164         xev.data.l[2] = 0;
165     } else if (prop == _NET_CURRENT_DESKTOP) {
166         xev.data.l[0] = l;
167         xev.data.l[1] = time;
168     } else if (prop == _NET_RESTACK_WINDOW) {
169         xev.data.l[0] = 2;
170         xev.data.l[2] = l;
171     } else if (prop == _OB_FOCUS) {
172     } else {
173         xev.message_type = sc.atoms[_NET_WM_STATE];
174         xev.data.l[0] = l;
175         xev.data.l[1] = sc.atoms[prop];
176         xev.data.l[3] = 2;
177     }
178
179     XSendEvent(sc.dd, sc.root, False,
180                SubstructureNotifyMask|SubstructureRedirectMask,
181                (XEvent *)&xev);
182 }
183
184 #ifdef PAGER
185
186 static void switch_desk(int new_desk, Time time)
187 {
188     if (get_number_of_desktops() <= new_desk)
189         return;
190
191     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
192 }
193
194 /* This doesn't work with obrender yet */
195 static void pager_draw_button(int x, int num)
196 {
197     char label;
198 #ifdef XFT
199     XftColor col;
200 #endif
201
202     if (num == tb.my_desktop) {
203         /* current desktop */
204         draw_box(x, PAGER_BUTTON_WIDTH);
205     } else {
206         set_foreground(0);
207         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
208     }
209
210     label = '1' + num;
211
212 #ifdef XFT
213     col.color.alpha = 0xffff;
214     col.color.red = cols[5].red;
215     col.color.green = cols[5].green;
216     col.color.blue = cols[5].blue;
217     XftDrawString8(xftdraw, &col, xfs,
218                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
219                    &label, 1);
220 #else
221     set_foreground(5);
222     XDrawString(sc.dd, tb.win, fore_gc,
223                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
224                 text_y, &label, 1);
225 #endif
226 }
227
228 static void pager_draw(void)
229 {
230     int desks, i, x = GRILL_WIDTH;
231
232     desks = get_number_of_desktops();
233
234     for (i = 0; i < desks; i++) {
235         pager_draw_button(x, i);
236         if (i > 8)
237             break;
238         x += PAGER_BUTTON_WIDTH;
239     }
240
241     pager_size = x;
242 }
243
244 #endif
245
246 static task *find_task(Window win)
247 {
248     task *list = tb.task_list;
249     while (list) {
250         if (list->win == win)
251             return list;
252         list = list->next;
253     }
254     return 0;
255 }
256
257 static void del_task(Window win)
258 {
259     task *next, *prev = 0, *list = tb.task_list;
260
261     while (list) {
262         next = list->next;
263         if (list->win == win) {
264             /* unlink and free this task */
265             free_icons(list);
266             if (list->name)
267                 XFree(list->name);
268             free(list);
269             if (prev == 0)
270                 tb.task_list = next;
271             else
272                 prev->next = next;
273             return;
274         }
275         prev = list;
276         list = next;
277     }
278 }
279
280 static void taskbar_read_clientlist(void)
281 {
282     Window *win, focus_win = 0;
283     int num, i, desk, new_desk = 0;
284     task *list, *next;
285     desk = get_current_desktop();
286
287 #ifdef MIKACHU
288     if (desk == 0) {
289         if (tb.w == 827)
290             goto skip;
291         tb.w = 827;
292     } else {
293         if (tb.w == 1280)
294             goto skip;
295         tb.w = 1280;
296     }
297     XResizeWindow(sc.dd, tb.win, tb.w, tb.h);
298 skip:
299 #endif
300
301     if (desk != tb.my_desktop) {
302         new_desk = 1;
303         tb.my_desktop = desk;
304     }
305
306     win = xprop_get_data(&sc, sc.root, _NET_ACTIVE_WINDOW, XA_WINDOW, &num);
307     if (win && num > 0) {
308         focus_win = win[0];
309         XFree(win);
310     }
311
312     win = xprop_get_data(&sc, sc.root, _NET_CLIENT_LIST, XA_WINDOW, &num);
313     if (!win)
314         return;
315
316     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
317     list = tb.task_list;
318     while (list) {
319         list->focused = (focus_win == list->win);
320         next = list->next;
321
322         if (!new_desk)
323             for (i = num - 1; i >= 0; i--)
324                 if (list->win == win[i])
325                     goto dontdel;
326         del_task(list->win);
327 dontdel:
328
329         list = next;
330     }
331
332     /* add any new windows */
333     for (i = 0; i < num; i++) {
334         if (!find_task(win[i]))
335             add_task(win[i], (win[i] == focus_win));
336     }
337
338     XFree(win);
339 }
340
341 static void handle_press(int x, int y, int button, Time time)
342 {
343     task *tk;
344
345 #ifdef PAGER
346     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
347         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
348     else
349 #endif
350     /* clicked left grill */
351     if (x < 6) {
352         if (button == 1) {
353             tb.at_top = 1 - tb.at_top;
354             gui_move_taskbar(&sc, &tb);
355         } else if (button == 4) {
356             tb.hidden = 1;
357             gui_move_taskbar(&sc, &tb);
358         }
359     }
360
361     /* clicked right grill */
362     else if (x + TEXTPAD > tb.w) {
363         if (tb.hidden && (button == 1 || button == 5)) {
364             tb.hidden = 0;
365             gui_move_taskbar(&sc, &tb);
366         } else if (!tb.hidden && (button == 1 || button == 4)) {
367             tb.hidden = 1;
368             gui_move_taskbar(&sc, &tb);
369         }
370     } else {
371         tk = tb.task_list;
372         while (tk) {
373             /* clicked on a task button */
374             /* XXX Make this configureable */
375             if (task_shown(tk) && (x > tk->pos_x && x < tk->pos_x + tk->width))
376             {
377                 switch (button) {
378                 case 1:
379                     netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
380                     break;
381                 case 2:
382                     if (tk->iconified)
383                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
384                                      time, XPROP_REMOVE);
385                     else
386                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
387                                      time, XPROP_ADD);
388                     break;
389                 case 3:
390                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
391                     break;
392                 case 4:
393                     if (is_shaded(tk->win))
394                             netwm_action(tk->win, _NET_RESTACK_WINDOW,
395                                          time, Below);
396                     else
397                             netwm_action(tk->win, _NET_WM_STATE_SHADED,
398                                          time, XPROP_ADD);
399                     break;
400                 case 5:
401                     if (is_shaded(tk->win))
402                         netwm_action(tk->win, _NET_WM_STATE_SHADED,
403                                      time, XPROP_REMOVE);
404                     else
405                             netwm_action(tk->win, _NET_RESTACK_WINDOW,
406                                          time, Above);
407                     break;
408                 case 9:
409                     if (tk->iconified)
410                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
411                                      time, XPROP_REMOVE);
412                     else
413                         netwm_action(tk->win, _OB_FOCUS, 0, 0);
414                     break;
415                 case 6:
416                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
417                     break;
418                 case 7:
419                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
420                     break;
421                 }
422                 return;
423             }
424             tk = tk->next;
425         } /* clicked on the background */
426         switch (button) {
427         case 1:
428             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
429             break;
430         case 2:
431             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
432             break;
433         case 3:
434             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
435             break;
436         case 4:
437             tb.hidden = 1;
438             gui_move_taskbar(&sc, &tb);
439             break;
440         case 5:
441             tb.hidden = 0;
442             gui_move_taskbar(&sc, &tb);
443             break;
444         }
445     }
446 }
447
448 #if 0
449 static void handle_focusin(Window win)
450 {
451     task *tk;
452
453     tk = tb.task_list;
454     while (tk) {
455         if (tk->focused) {
456             if (tk->win != win) {
457                 tk->focused = 0;
458                 gui_draw_task(tk);
459             }
460         } else {
461             if (tk->win == win) {
462                 tk->focused = 1;
463                 gui_draw_task(tk);
464             }
465         }
466         tk = tk->next;
467     }
468 }
469 #endif
470
471 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
472 {
473     Atom at = *(Atom*)arg;
474     return (e->type == PropertyNotify && e->xproperty.atom == at);
475 }
476
477 static void handle_propertynotify(Window win, Atom at)
478 {
479     task *tk;
480
481     if (win == sc.root) {
482         /* XXX only redraw the task that got focused/unfocused
483          * when _NET_ACTIVE_WINDOW triggers this,
484          * redraw everything if that task was hidden before though */
485         if (at == sc.atoms[_NET_CLIENT_LIST]
486             || at == sc.atoms[_NET_CURRENT_DESKTOP]
487             || at == sc.atoms[_NET_ACTIVE_WINDOW])
488         {
489             XEvent ce;
490             Atom check;
491
492             check = sc.atoms[_NET_CLIENT_LIST];
493             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
494                                  (XPointer)&check));
495             check = sc.atoms[_NET_CURRENT_DESKTOP];
496             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
497                                  (XPointer)&check));
498             check = sc.atoms[_NET_ACTIVE_WINDOW];
499             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
500                                  (XPointer)&check));
501
502             taskbar_read_clientlist();
503             gui_draw_taskbar(&sc, &tb);
504         }
505         return;
506     }
507
508     /* XXX make this work when SKIP_TASKBAR is toggled too */
509     /* show skip_taskbar tasks if they're focused */
510     tk = find_task(win);
511     if (!tk)
512         return;
513
514     if (at == sc.atoms[WM_NAME] || at == sc.atoms[_NET_WM_NAME]) {
515         /* window's title changed */
516         /* XXX make a function for this and use from here and add_task */
517         char *newname;
518         newname = xprop_get_utf8(&sc, tk->win, _NET_WM_NAME) ?:
519                   xprop_get_string(&sc, tk->win, WM_NAME);
520         if (newname) {
521             /* It didn't change */
522             if (tk->name && !strcmp(newname, tk->name)) {
523                 XFree(newname);
524                 return;
525             }
526         }
527         if (tk->name)
528             XFree(tk->name);
529         tk->name = newname;
530         gui_draw_task(&sc, &tb, tk, TRUE);
531     } else if (at == sc.atoms[_NET_WM_ICON]) {
532         task_update_icons(tk);
533         gui_draw_task(&sc, &tb, tk, TRUE);
534     } else if (at == sc.atoms[_NET_WM_STATE]) {
535         /* iconified state changed? */
536         if (is_iconified(tk->win) != tk->iconified) {
537             tk->iconified = !tk->iconified;
538             if (!tk->hidden)
539                 gui_draw_task(&sc, &tb, tk, TRUE);
540         }
541         /* shaded state changed? */
542         if (is_shaded(tk->win) != tk->shaded) {
543             tk->shaded = !tk->shaded;
544             gui_draw_task(&sc, &tb, tk, TRUE);
545         }
546         if (is_hidden(tk->win) != tk->hidden) {
547             tk->hidden = !tk->hidden;
548             gui_draw_taskbar(&sc, &tb);
549         }
550     } else if (at == sc.atoms[_NET_WM_DESKTOP]) {
551         if (find_desktop(win) != get_current_desktop())
552             del_task(tk->win);
553     }
554 }
555
556 static void handle_error(Display * d, XErrorEvent * ev)
557 {
558 }
559
560 int
561 #ifdef NOSTDLIB
562 _start(void)
563 #else
564 main(int argc, char *argv[])
565 #endif
566 {
567     XEvent ev;
568     int xfd;
569
570     sc.dd = XOpenDisplay(NULL);
571     if (!sc.dd)
572         return 0;
573     xfd = ConnectionNumber(sc.dd);
574
575     sc.num = DefaultScreen(sc.dd);
576     sc.height = DisplayHeight(sc.dd, sc.num);
577     sc.width = DisplayWidth(sc.dd, sc.num);
578     sc.root = RootWindow(sc.dd, sc.num);
579     sc.rr = RrInstanceNew(sc.dd, sc.num);
580
581     /* helps us catch windows closing/opening */
582     XSelectInput(sc.dd, sc.root, PropertyChangeMask | SubstructureNotifyMask);
583
584     XSetErrorHandler((XErrorHandler) handle_error);
585
586     xprop_init(&sc);
587
588     gui_init(&sc);
589     memset(&tb, 0, sizeof(tb));
590     gui_create_taskbar(&sc, &tb);
591     XSync(sc.dd, False);
592     taskbar_read_clientlist();
593     gui_draw_taskbar(&sc, &tb);
594     XFlush(sc.dd);
595
596     while (1) {
597         fd_set fd;
598
599         FD_ZERO(&fd);
600         FD_SET(xfd, &fd);
601         select(xfd + 1, &fd, 0, 0, 0);
602
603         while (XPending(sc.dd)) {
604             XNextEvent(sc.dd, &ev);
605             switch (ev.type) {
606             case ButtonPress:
607                 handle_press(ev.xbutton.x, ev.xbutton.y,
608                              ev.xbutton.button, ev.xbutton.time);
609                 break;
610             case PropertyNotify:
611                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
612                 break;
613             /*default:
614                    printf ("unknown evt type: %d\n", ev.type); */
615             }
616         }
617     }
618     /* RrInstanceFree(inst);
619      * XCloseDisplay (dd);
620
621        return 0; */
622 }
623