]> icculus.org git repositories - mikachu/rspanel.git/blob - rspanel.c
move around code like crazy, into icon.c gui.c xprop.c. it still needs work but...
[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         tb->w = 827;
290     else
291         tb->w = 1280;
292
293     XResizeWindow(sc.dd, tb.win, tb->w, tb->h);
294 #endif
295
296     if (desk != tb.my_desktop) {
297         new_desk = 1;
298         tb.my_desktop = desk;
299     }
300
301     win = xprop_get_data(&sc, sc.root, _NET_ACTIVE_WINDOW, XA_WINDOW, &num);
302     if (win && num > 0) {
303         focus_win = win[0];
304         XFree(win);
305     }
306
307     win = xprop_get_data(&sc, sc.root, _NET_CLIENT_LIST, XA_WINDOW, &num);
308     if (!win)
309         return;
310
311     /* remove windows that arn't in the _NET_CLIENT_LIST anymore */
312     list = tb.task_list;
313     while (list) {
314         list->focused = (focus_win == list->win);
315         next = list->next;
316
317         if (!new_desk)
318             for (i = num - 1; i >= 0; i--)
319                 if (list->win == win[i])
320                     goto dontdel;
321         del_task(list->win);
322 dontdel:
323
324         list = next;
325     }
326
327     /* add any new windows */
328     for (i = 0; i < num; i++) {
329         if (!find_task(win[i]))
330             add_task(win[i], (win[i] == focus_win));
331     }
332
333     XFree(win);
334 }
335
336 static void handle_press(int x, int y, int button, Time time)
337 {
338     task *tk;
339
340 #ifdef PAGER
341     if ((y > 2 && y < WINHEIGHT - 2 && x > GRILL_WIDTH) && button == 1)
342         switch_desk((x - GRILL_WIDTH) / PAGER_BUTTON_WIDTH, time);
343     else
344 #endif
345     /* clicked left grill */
346     if (x < 6) {
347         if (button == 1) {
348             tb.at_top = 1 - tb.at_top;
349             gui_move_taskbar(&sc, &tb);
350         } else if (button == 4) {
351             tb.hidden = 1;
352             gui_move_taskbar(&sc, &tb);
353         }
354     }
355
356     /* clicked right grill */
357     else if (x + TEXTPAD > tb.w) {
358         if (tb.hidden && (button == 1 || button == 5)) {
359             tb.hidden = 0;
360             gui_move_taskbar(&sc, &tb);
361         } else if (!tb.hidden && (button == 1 || button == 4)) {
362             tb.hidden = 1;
363             gui_move_taskbar(&sc, &tb);
364         }
365     } else {
366         tk = tb.task_list;
367         while (tk) {
368             /* clicked on a task button */
369             /* XXX Make this configureable */
370             if (task_shown(tk) && (x > tk->pos_x && x < tk->pos_x + tk->width))
371             {
372                 switch (button) {
373                 case 1:
374                     netwm_action(tk->win, _NET_ACTIVE_WINDOW, time, 0);
375                     break;
376                 case 2:
377                     if (tk->iconified)
378                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, XPROP_REMOVE);
379                     else
380                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, XPROP_ADD);
381                     break;
382                 case 3:
383                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Opposite);
384                     break;
385                 case 4:
386                     if (is_shaded(tk->win))
387                             netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
388                     else
389                             netwm_action(tk->win, _NET_WM_STATE_SHADED, time, XPROP_ADD);
390                     break;
391                 case 5:
392                     if (is_shaded(tk->win))
393                         netwm_action(tk->win, _NET_WM_STATE_SHADED, time, XPROP_REMOVE);
394                     else
395                             netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
396                     break;
397                 case 9:
398                     if (tk->iconified)
399                         netwm_action(tk->win, _NET_WM_STATE_HIDDEN, time, XPROP_REMOVE);
400                     else
401                         netwm_action(tk->win, _OB_FOCUS, 0, 0);
402                     break;
403                 case 6:
404                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Below);
405                     break;
406                 case 7:
407                     netwm_action(tk->win, _NET_RESTACK_WINDOW, time, Above);
408                     break;
409                 }
410                 return;
411             }
412             tk = tk->next;
413         } /* clicked on the background */
414         switch (button) {
415         case 1:
416             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Above);
417             break;
418         case 2:
419             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Below);
420             break;
421         case 3:
422             netwm_action(tb.win, _NET_RESTACK_WINDOW, time, Opposite);
423             break;
424         case 4:
425             tb.hidden = 1;
426             gui_move_taskbar(&sc, &tb);
427             break;
428         case 5:
429             tb.hidden = 0;
430             gui_move_taskbar(&sc, &tb);
431             break;
432         }
433     }
434 }
435
436 #if 0
437 static void handle_focusin(Window win)
438 {
439     task *tk;
440
441     tk = tb.task_list;
442     while (tk) {
443         if (tk->focused) {
444             if (tk->win != win) {
445                 tk->focused = 0;
446                 gui_draw_task(tk);
447             }
448         } else {
449             if (tk->win == win) {
450                 tk->focused = 1;
451                 gui_draw_task(tk);
452             }
453         }
454         tk = tk->next;
455     }
456 }
457 #endif
458
459 static Bool look_for_duplicate_property(Display *d, XEvent *e, XPointer arg)
460 {
461     Atom at = *(Atom*)arg;
462     return (e->type == PropertyNotify && e->xproperty.atom == at);
463 }
464
465 static void handle_propertynotify(Window win, Atom at)
466 {
467     task *tk;
468
469     if (win == sc.root) {
470         /* XXX only redraw the task that got focused/unfocused
471          * when _NET_ACTIVE_WINDOW triggers this,
472          * redraw everything if that task was hidden before though */
473         if (at == sc.atoms[_NET_CLIENT_LIST] || at == sc.atoms[_NET_CURRENT_DESKTOP] ||
474             at == sc.atoms[_NET_ACTIVE_WINDOW])
475         {
476             XEvent ce;
477             Atom check;
478
479             check = sc.atoms[_NET_CLIENT_LIST];
480             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property, (XPointer)&check));
481             check = sc.atoms[_NET_CURRENT_DESKTOP];
482             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property, (XPointer)&check));
483             check = sc.atoms[_NET_ACTIVE_WINDOW];
484             while (XCheckIfEvent(sc.dd, &ce, look_for_duplicate_property, (XPointer)&check));
485
486             taskbar_read_clientlist();
487             gui_draw_taskbar(&sc, &tb);
488         }
489         return;
490     }
491
492     /* XXX make this work when SKIP_TASKBAR is toggled too */
493     /* show skip_taskbar tasks if they're focused */
494     tk = find_task(win);
495     if (!tk)
496         return;
497
498     if (at == sc.atoms[WM_NAME] || at == sc.atoms[_NET_WM_NAME]) {
499         /* window's title changed */
500         /* XXX make a function for this and use from here and add_task */
501         char *newname;
502         newname = xprop_get_utf8(&sc, tk->win, _NET_WM_NAME) ?:
503                   xprop_get_string(&sc, tk->win, WM_NAME);
504         if (newname) {
505             /* It didn't change */
506             if (tk->name && !strcmp(newname, tk->name)) {
507                 XFree(newname);
508                 return;
509             }
510         }
511         if (tk->name)
512             XFree(tk->name);
513         tk->name = newname;
514         gui_draw_task(&sc, &tb, tk, TRUE);
515     } else if (at == sc.atoms[_NET_WM_ICON]) {
516         task_update_icons(tk);
517         gui_draw_task(&sc, &tb, tk, TRUE);
518     } else if (at == sc.atoms[_NET_WM_STATE]) {
519         /* iconified state changed? */
520         if (is_iconified(tk->win) != tk->iconified) {
521             tk->iconified = !tk->iconified;
522             if (!tk->hidden)
523                 gui_draw_task(&sc, &tb, tk, TRUE);
524         }
525         /* shaded state changed? */
526         if (is_shaded(tk->win) != tk->shaded) {
527             tk->shaded = !tk->shaded;
528             gui_draw_task(&sc, &tb, tk, TRUE);
529         }
530         if (is_hidden(tk->win) != tk->hidden) {
531             tk->hidden = !tk->hidden;
532             gui_draw_taskbar(&sc, &tb);
533         }
534     } else if (at == sc.atoms[_NET_WM_DESKTOP]) {
535         if (find_desktop(win) != get_current_desktop())
536             del_task(tk->win);
537     }
538 }
539
540 static void handle_error(Display * d, XErrorEvent * ev)
541 {
542 }
543
544 int
545 #ifdef NOSTDLIB
546 _start(void)
547 #else
548 main(int argc, char *argv[])
549 #endif
550 {
551     XEvent ev;
552     int xfd;
553
554     sc.dd = XOpenDisplay(NULL);
555     if (!sc.dd)
556         return 0;
557     xfd = ConnectionNumber(sc.dd);
558
559     sc.num = DefaultScreen(sc.dd);
560     sc.height = DisplayHeight(sc.dd, sc.num);
561     sc.width = DisplayWidth(sc.dd, sc.num);
562     sc.root = RootWindow(sc.dd, sc.num);
563     sc.rr = RrInstanceNew(sc.dd, sc.num);
564
565     /* helps us catch windows closing/opening */
566     XSelectInput(sc.dd, sc.root, PropertyChangeMask | SubstructureNotifyMask);
567
568     XSetErrorHandler((XErrorHandler) handle_error);
569
570     xprop_init(&sc);
571
572     gui_init(&sc);
573     memset(&tb, 0, sizeof(tb));
574     gui_create_taskbar(&sc, &tb);
575     XSync(sc.dd, False);
576     taskbar_read_clientlist();
577     gui_draw_taskbar(&sc, &tb);
578     XFlush(sc.dd);
579
580     while (1) {
581         fd_set fd;
582
583         FD_ZERO(&fd);
584         FD_SET(xfd, &fd);
585         select(xfd + 1, &fd, 0, 0, 0);
586
587         while (XPending(sc.dd)) {
588             XNextEvent(sc.dd, &ev);
589             switch (ev.type) {
590             case ButtonPress:
591                 handle_press(ev.xbutton.x, ev.xbutton.y, ev.xbutton.button, ev.xbutton.time);
592                 break;
593             case PropertyNotify:
594                 handle_propertynotify(ev.xproperty.window, ev.xproperty.atom);
595                 break;
596             /*default:
597                    printf ("unknown evt type: %d\n", ev.type); */
598             }
599         }
600     }
601     /* RrInstanceFree(inst);
602      * XCloseDisplay (dd);
603
604        return 0; */
605 }
606