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