]> icculus.org git repositories - mikachu/rspanel.git/blob - rspanel.c
57105103c8c6a16449ee8a07fb5a715a4ecf006b
[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     desk = find_desktop(win);
116     if ((desk != 0xffffffff && tb.my_desktop != desk))
117         return;
118
119     XSelectInput(sc.dd, win, PropertyChangeMask);
120
121     tk = calloc(1, sizeof(task));
122     tk->win = win;
123     tk->focused = focus;
124     tk->name = xprop_get_utf8(&sc, tk->win, _NET_WM_NAME) ?:
125                xprop_get_string(&sc, tk->win, WM_NAME);
126     /* XXX use this? */
127     //tk->locale = get_prop_data(win, XA_WM_LOCALE_NAME, XA_STRING, 0);
128     tk->iconified = is_iconified(win);
129     tk->shaded = is_shaded(win);
130     tk->hidden = is_hidden(win);
131     task_update_icons(tk);
132
133     /* now append it to our linked list */
134
135     list = tb.task_list;
136     if (!list) {
137         tb.task_list = tk;
138         return;
139     }
140     while (1) {
141         if (!list->next) {
142             list->next = tk;
143             return;
144         }
145         list = list->next;
146     }
147 }
148
149 static void netwm_action(Window win, xprop_t prop, Time time, long l)
150 {
151     XClientMessageEvent xev = {
152     .type = ClientMessage,
153     .window = win,
154     .format = 32,
155     .data.l = {0},
156     .message_type = sc.atoms[prop]
157     };
158     
159     if (prop == _NET_ACTIVE_WINDOW) {
160         xev.data.l[0] = 2;
161         xev.data.l[1] = time;
162         xev.data.l[2] = 0;
163     } else if (prop == _NET_CURRENT_DESKTOP) {
164         xev.data.l[0] = l;
165         xev.data.l[1] = time;
166     } else if (prop == _NET_RESTACK_WINDOW) {
167         xev.data.l[0] = 2;
168         xev.data.l[2] = l;
169     } else if (prop == _OB_FOCUS) {
170     } else {
171         xev.message_type = sc.atoms[_NET_WM_STATE];
172         xev.data.l[0] = l;
173         xev.data.l[1] = sc.atoms[prop];
174         xev.data.l[3] = 2;
175     }
176
177     XSendEvent(sc.dd, sc.root, False,
178                SubstructureNotifyMask|SubstructureRedirectMask,
179                (XEvent *)&xev);
180 }
181
182 #ifdef PAGER
183
184 static void switch_desk(int new_desk, Time time)
185 {
186     if (get_number_of_desktops() <= new_desk)
187         return;
188
189     netwm_action(None, _NET_CURRENT_DESKTOP, time, new_desk);
190 }
191
192 /* This doesn't work with obrender yet */
193 static void pager_draw_button(int x, int num)
194 {
195     char label;
196 #ifdef XFT
197     XftColor col;
198 #endif
199
200     if (num == tb.my_desktop) {
201         /* current desktop */
202         draw_box(x, PAGER_BUTTON_WIDTH);
203     } else {
204         set_foreground(0);
205         fill_rect(x, 1, PAGER_BUTTON_WIDTH + 1, WINHEIGHT - 2);
206     }
207
208     label = '1' + num;
209
210 #ifdef XFT
211     col.color.alpha = 0xffff;
212     col.color.red = cols[5].red;
213     col.color.green = cols[5].green;
214     col.color.blue = cols[5].blue;
215     XftDrawString8(xftdraw, &col, xfs,
216                    x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2), text_y,
217                    &label, 1);
218 #else
219     set_foreground(5);
220     XDrawString(sc.dd, tb.win, fore_gc,
221                 x + ((PAGER_BUTTON_WIDTH - PAGER_DIGIT_WIDTH) / 2) - 1,
222                 text_y, &label, 1);
223 #endif
224 }
225
226 static void pager_draw(void)
227 {
228     int desks, i, x = GRILL_WIDTH;
229
230     desks = get_number_of_desktops();
231
232     for (i = 0; i < desks; i++) {
233         pager_draw_button(x, i);
234         if (i > 8)
235             break;
236         x += PAGER_BUTTON_WIDTH;
237     }
238
239     pager_size = x;
240 }
241
242 #endif
243
244 static task *find_task(Window win)
245 {
246     task *list = tb.task_list;
247     while (list) {
248         if (list->win == win)
249             return list;
250         list = list->next;
251     }
252     return 0;
253 }
254
255 static void del_task(Window win)
256 {
257     task *next, *prev = 0, *list = tb.task_list;
258
259     while (list) {
260         next = list->next;
261         if (list->win == win) {
262             /* unlink and free this task */
263             free_icons(list);
264             if (list->name)
265                 XFree(list->name);
266             free(list);
267             if (prev == 0)
268                 tb.task_list = next;
269             else
270                 prev->next = next;
271             return;
272         }
273         prev = list;
274         list = next;
275     }
276 }
277
278 static void taskbar_read_clientlist(void)
279 {
280     Window *win, focus_win = 0;
281     int num, i, desk, new_desk = 0;
282     task *list, *next;
283     desk = get_current_desktop();
284
285 #ifdef MIKACHU
286     if (desk == 0) {
287         if (tb.w == 827)
288             goto skip;
289         tb.w = 827;
290     } else {
291         if (tb.w == 1280)
292             goto skip;
293         tb.w = 1280;
294     }
295     XResizeWindow(sc.dd, tb.win, tb.w, tb.h);
296 skip:
297 #endif
298
299     if (desk != tb.my_desktop) {
300         new_desk = 1;
301         tb.my_desktop = desk;
302     }
303
304     win = xprop_get_data(&sc, sc.root, _NET_ACTIVE_WINDOW, XA_WINDOW, &num);
305     if (win && num > 0) {
306         focus_win = win[0];
307         XFree(win);
308     }
309
310     win = xprop_get_data(&sc, sc.root, _NET_CLIENT_LIST, XA_WINDOW, &num);
311     if (!win)
312         return;
313
314     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
315     list = tb.task_list;
316     while (list) {
317         list->focused = (focus_win == list->win);
318         next = list->next;
319
320         if (!new_desk)
321             for (i = num - 1; i >= 0; i--)
322                 if (list->win == win[i])
323                     goto dontdel;
324         del_task(list->win);
325 dontdel:
326
327         list = next;
328     }
329
330     /* add any new windows */
331     for (i = 0; i < num; i++) {
332         if (!find_task(win[i]))
333             add_task(win[i], (win[i] == focus_win));
334     }
335
336     XFree(win);
337 }
338
339 static void handle_press(int x, int y, int button, Time time)
340 {
341     task *tk;
342
343 #ifdef PAGER
344     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
345         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
346     else
347 #endif
348     /* clicked left grill */
349     if (x < 6) {
350         if (button == 1) {
351             tb.at_top = 1 - tb.at_top;
352             gui_move_taskbar(&sc, &tb);
353         } else if (button == 4) {
354             tb.hidden = 1;
355             gui_move_taskbar(&sc, &tb);
356         }
357     }
358
359     /* clicked right grill */
360     else if (x + TEXTPAD > tb.w) {
361         if (tb.hidden && (button == 1 || button == 5)) {
362             tb.hidden = 0;
363             gui_move_taskbar(&sc, &tb);
364         } else if (!tb.hidden && (button == 1 || button == 4)) {
365             tb.hidden = 1;
366             gui_move_taskbar(&sc, &tb);
367         }
368     } else {
369         tk = tb.task_list;
370         while (tk) {
371             /* clicked on a task button */
372             /* XXX Make this configureable */
373             if (task_shown(tk) && (x > tk->pos_x && x < tk->pos_x + tk->width))
374             {
375                 switch (button) {
376                 case 1:
377                     netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
378                     break;
379                 case 2:
380                     if (tk->iconified)
381                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
382                                      time, XPROP_REMOVE);
383                     else
384                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
385                                      time, XPROP_ADD);
386                     break;
387                 case 3:
388                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
389                     break;
390                 case 4:
391                     if (is_shaded(tk->win))
392                             netwm_action(tk->win, _NET_RESTACK_WINDOW,
393                                          time, Below);
394                     else
395                             netwm_action(tk->win, _NET_WM_STATE_SHADED,
396                                          time, XPROP_ADD);
397                     break;
398                 case 5:
399                     if (is_shaded(tk->win))
400                         netwm_action(tk->win, _NET_WM_STATE_SHADED,
401                                      time, XPROP_REMOVE);
402                     else
403                             netwm_action(tk->win, _NET_RESTACK_WINDOW,
404                                          time, Above);
405                     break;
406                 case 9:
407                     if (tk->iconified)
408                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN,
409                                      time, XPROP_REMOVE);
410                     else
411                         netwm_action(tk->win, _OB_FOCUS, 0, 0);
412                     break;
413                 case 6:
414                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
415                     break;
416                 case 7:
417                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
418                     break;
419                 }
420                 return;
421             }
422             tk = tk->next;
423         } /* clicked on the background */
424         switch (button) {
425         case 1:
426             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
427             break;
428         case 2:
429             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
430             break;
431         case 3:
432             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
433             break;
434         case 4:
435             tb.hidden = 1;
436             gui_move_taskbar(&sc, &tb);
437             break;
438         case 5:
439             tb.hidden = 0;
440             gui_move_taskbar(&sc, &tb);
441             break;
442         }
443     }
444 }
445
446 #if 0
447 static void handle_focusin(Window win)
448 {
449     task *tk;
450
451     tk = tb.task_list;
452     while (tk) {
453         if (tk->focused) {
454             if (tk->win != win) {
455                 tk->focused = 0;
456                 gui_draw_task(tk);
457             }
458         } else {
459             if (tk->win == win) {
460                 tk->focused = 1;
461                 gui_draw_task(tk);
462             }
463         }
464         tk = tk->next;
465     }
466 }
467 #endif
468
469 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
470 {
471     Atom at = *(Atom*)arg;
472     return (e->type == PropertyNotify && e->xproperty.atom == at);
473 }
474
475 static void handle_propertynotify(Window win, Atom at)
476 {
477     task *tk;
478
479     if (win == sc.root) {
480         /* XXX only redraw the task that got focused/unfocused
481          * when _NET_ACTIVE_WINDOW triggers this,
482          * redraw everything if that task was hidden before though */
483         if (at == sc.atoms[_NET_CLIENT_LIST]
484             || at == sc.atoms[_NET_CURRENT_DESKTOP]
485             || at == sc.atoms[_NET_ACTIVE_WINDOW])
486         {
487             XEvent ce;
488             Atom check;
489
490             check = sc.atoms[_NET_CLIENT_LIST];
491             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
492                                  (XPointer)&check));
493             check = sc.atoms[_NET_CURRENT_DESKTOP];
494             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
495                                  (XPointer)&check));
496             check = sc.atoms[_NET_ACTIVE_WINDOW];
497             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property,
498                                  (XPointer)&check));
499
500             taskbar_read_clientlist();
501             gui_draw_taskbar(&sc, &tb);
502         }
503         return;
504     }
505
506     /* XXX make this work when SKIP_TASKBAR is toggled too */
507     /* show skip_taskbar tasks if they're focused */
508     tk = find_task(win);
509     if (!tk)
510         return;
511
512     if (at == sc.atoms[WM_NAME] || at == sc.atoms[_NET_WM_NAME]) {
513         /* window's title changed */
514         /* XXX make a function for this and use from here and add_task */
515         char *newname;
516         newname = xprop_get_utf8(&sc, tk->win, _NET_WM_NAME) ?:
517                   xprop_get_string(&sc, tk->win, WM_NAME);
518         if (newname) {
519             /* It didn't change */
520             if (tk->name && !strcmp(newname, tk->name)) {
521                 XFree(newname);
522                 return;
523             }
524         }
525         if (tk->name)
526             XFree(tk->name);
527         tk->name = newname;
528         gui_draw_task(&sc, &tb, tk, TRUE);
529     } else if (at == sc.atoms[_NET_WM_ICON]) {
530         task_update_icons(tk);
531         gui_draw_task(&sc, &tb, tk, TRUE);
532     } else if (at == sc.atoms[_NET_WM_STATE]) {
533         /* iconified state changed? */
534         if (is_iconified(tk->win) != tk->iconified) {
535             tk->iconified = !tk->iconified;
536             if (!tk->hidden)
537                 gui_draw_task(&sc, &tb, tk, TRUE);
538         }
539         /* shaded state changed? */
540         if (is_shaded(tk->win) != tk->shaded) {
541             tk->shaded = !tk->shaded;
542             gui_draw_task(&sc, &tb, tk, TRUE);
543         }
544         if (is_hidden(tk->win) != tk->hidden) {
545             tk->hidden = !tk->hidden;
546             gui_draw_taskbar(&sc, &tb);
547         }
548     } else if (at == sc.atoms[_NET_WM_DESKTOP]) {
549         if (find_desktop(win) != get_current_desktop())
550             del_task(tk->win);
551     }
552 }
553
554 static void handle_error(Display * d, XErrorEvent * ev)
555 {
556 }
557
558 int
559 #ifdef NOSTDLIB
560 _start(void)
561 #else
562 main(int argc, char *argv[])
563 #endif
564 {
565     XEvent ev;
566     int xfd;
567
568     sc.dd = XOpenDisplay(NULL);
569     if (!sc.dd)
570         return 0;
571     xfd = ConnectionNumber(sc.dd);
572
573     sc.num = DefaultScreen(sc.dd);
574     sc.height = DisplayHeight(sc.dd, sc.num);
575     sc.width = DisplayWidth(sc.dd, sc.num);
576     sc.root = RootWindow(sc.dd, sc.num);
577     sc.rr = RrInstanceNew(sc.dd, sc.num);
578     sc.theme = NULL;
579     sc.fore_gc = None;
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