82b085a7ccba66f657e49a1a9550035f4750844c
[dana/openbox.git] / openbox / dock.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    dock.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "dock.h"
22 #include "screen.h"
23 #include "config.h"
24 #include "grab.h"
25 #include "openbox.h"
26 #include "render/theme.h"
27 #include "obt/prop.h"
28
29 #define DOCK_EVENT_MASK (ButtonPressMask | ButtonReleaseMask | \
30                          EnterWindowMask | LeaveWindowMask)
31 #define DOCKAPP_EVENT_MASK (StructureNotifyMask)
32 #define DOCK_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
33                               ButtonMotionMask)
34
35 static ObDock *dock;
36
37 StrutPartial dock_strut;
38
39 static void dock_app_grab_button(ObDockApp *app, gboolean grab)
40 {
41     if (grab) {
42         grab_button_full(config_dock_app_move_button,
43                          config_dock_app_move_modifiers, app->icon_win,
44                          ButtonPressMask | ButtonReleaseMask |
45                          ButtonMotionMask,
46                          GrabModeAsync, OB_CURSOR_MOVE);
47     } else {
48         ungrab_button(config_dock_app_move_button,
49                       config_dock_app_move_modifiers, app->icon_win);
50     }
51 }
52
53 void dock_startup(gboolean reconfig)
54 {
55     XSetWindowAttributes attrib;
56
57     if (reconfig) {
58         GList *it;
59
60         XSetWindowBorder(obt_display, dock->frame,
61                          RrColorPixel(ob_rr_theme->osd_border_color));
62         XSetWindowBorderWidth(obt_display, dock->frame, ob_rr_theme->obwidth);
63
64         RrAppearanceFree(dock->a_frame);
65         dock->a_frame = RrAppearanceCopy(ob_rr_theme->osd_hilite_bg);
66
67         stacking_add(DOCK_AS_WINDOW(dock));
68
69         dock_configure();
70         dock_hide(TRUE);
71
72         for (it = dock->dock_apps; it; it = g_list_next(it))
73             dock_app_grab_button(it->data, TRUE);
74         return;
75     }
76
77     STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
78                       0, 0, 0, 0, 0, 0, 0, 0);
79
80     dock = g_new0(ObDock, 1);
81     dock->obwin.type = OB_WINDOW_CLASS_DOCK;
82
83     dock->hidden = TRUE;
84
85     attrib.event_mask = DOCK_EVENT_MASK;
86     attrib.override_redirect = True;
87     attrib.do_not_propagate_mask = DOCK_NOPROPAGATEMASK;
88     dock->frame = XCreateWindow(obt_display, obt_root(ob_screen),
89                                 0, 0, 1, 1, 0,
90                                 RrDepth(ob_rr_inst), InputOutput,
91                                 RrVisual(ob_rr_inst),
92                                 CWOverrideRedirect | CWEventMask |
93                                 CWDontPropagate,
94                                 &attrib);
95     dock->a_frame = RrAppearanceCopy(ob_rr_theme->osd_hilite_bg);
96     XSetWindowBorder(obt_display, dock->frame,
97                      RrColorPixel(ob_rr_theme->osd_border_color));
98     XSetWindowBorderWidth(obt_display, dock->frame, ob_rr_theme->obwidth);
99
100     /* Setting the window type so xcompmgr can tell what it is */
101     OBT_PROP_SET32(dock->frame, NET_WM_WINDOW_TYPE, ATOM,
102                    OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DOCK));
103
104     window_add(&dock->frame, DOCK_AS_WINDOW(dock));
105     stacking_add(DOCK_AS_WINDOW(dock));
106 }
107
108 void dock_shutdown(gboolean reconfig)
109 {
110     if (reconfig) {
111         GList *it;
112
113         stacking_remove(DOCK_AS_WINDOW(dock));
114
115         for (it = dock->dock_apps; it; it = g_list_next(it))
116             dock_app_grab_button(it->data, FALSE);
117         return;
118     }
119
120     XDestroyWindow(obt_display, dock->frame);
121     RrAppearanceFree(dock->a_frame);
122     window_remove(dock->frame);
123     stacking_remove(dock);
124 }
125
126 void dock_add(Window win, XWMHints *wmhints)
127 {
128     ObDockApp *app;
129     XWindowAttributes attrib;
130     gchar **data;
131
132     app = g_new0(ObDockApp, 1);
133     app->win = win;
134     app->icon_win = (wmhints->flags & IconWindowHint) ?
135         wmhints->icon_window : win;
136
137     if (OBT_PROP_GETSS(app->win, WM_CLASS, locale, &data)) {
138         if (data[0]) {
139             app->name = g_strdup(data[0]);
140             if (data[1])
141                 app->class = g_strdup(data[1]);
142         }
143         g_strfreev(data);
144     }
145
146     if (app->name == NULL) app->name = g_strdup("");
147     if (app->class == NULL) app->class = g_strdup("");
148
149     if (XGetWindowAttributes(obt_display, app->icon_win, &attrib)) {
150         app->w = attrib.width;
151         app->h = attrib.height;
152     } else {
153         app->w = app->h = 64;
154     }
155
156     dock->dock_apps = g_list_append(dock->dock_apps, app);
157     dock_configure();
158
159     XReparentWindow(obt_display, app->icon_win, dock->frame, app->x, app->y);
160     /*
161       This is the same case as in frame.c for client windows. When Openbox is
162       starting, the window is already mapped so we see unmap events occur for
163       it. There are 2 unmap events generated that we see, one with the 'event'
164       member set the root window, and one set to the client, but both get
165       handled and need to be ignored.
166     */
167     if (ob_state() == OB_STATE_STARTING)
168         app->ignore_unmaps += 2;
169
170     if (app->win != app->icon_win) {
171         /* have to map it so that it can be re-managed on a restart */
172         XMoveWindow(obt_display, app->win, -1000, -1000);
173         XMapWindow(obt_display, app->win);
174     }
175     XMapWindow(obt_display, app->icon_win);
176     XSync(obt_display, False);
177
178     /* specify that if we exit, the window should not be destroyed and should
179        be reparented back to root automatically */
180     XChangeSaveSet(obt_display, app->icon_win, SetModeInsert);
181     XSelectInput(obt_display, app->icon_win, DOCKAPP_EVENT_MASK);
182
183     dock_app_grab_button(app, TRUE);
184
185     ob_debug("Managed Dock App: 0x%lx (%s)", app->icon_win, app->class);
186 }
187
188 void dock_remove_all(void)
189 {
190     while (dock->dock_apps)
191         dock_remove(dock->dock_apps->data, TRUE);
192 }
193
194 void dock_remove(ObDockApp *app, gboolean reparent)
195 {
196     dock_app_grab_button(app, FALSE);
197     XSelectInput(obt_display, app->icon_win, NoEventMask);
198     /* remove the window from our save set */
199     XChangeSaveSet(obt_display, app->icon_win, SetModeDelete);
200     XSync(obt_display, False);
201
202     if (reparent)
203         XReparentWindow(obt_display, app->icon_win,
204                         obt_root(ob_screen), app->x, app->y);
205
206     dock->dock_apps = g_list_remove(dock->dock_apps, app);
207     dock_configure();
208
209     ob_debug("Unmanaged Dock App: 0x%lx (%s)", app->icon_win, app->class);
210
211     g_free(app->name);
212     g_free(app->class);
213     g_free(app);
214 }
215
216 void dock_configure(void)
217 {
218     GList *it;
219     gint hspot, vspot;
220     gint gravity;
221     gint l, r, t, b;
222     gint strw, strh;
223     Rect *a;
224     gint hidesize;
225
226     RrMargins(dock->a_frame, &l, &t, &r, &b);
227     hidesize = MAX(1, ob_rr_theme->obwidth);
228
229     dock->area.width = dock->area.height = 0;
230
231     /* get the size */
232     for (it = dock->dock_apps; it; it = g_list_next(it)) {
233         ObDockApp *app = it->data;
234         switch (config_dock_orient) {
235         case OB_ORIENTATION_HORZ:
236             dock->area.width += app->w;
237             dock->area.height = MAX(dock->area.height, app->h);
238             break;
239         case OB_ORIENTATION_VERT:
240             dock->area.width = MAX(dock->area.width, app->w);
241             dock->area.height += app->h;
242             break;
243         }
244     }
245
246     if (dock->dock_apps) {
247         dock->area.width += l + r;
248         dock->area.height += t + b;
249     }
250
251     hspot = l;
252     vspot = t;
253
254     /* position the apps */
255     for (it = dock->dock_apps; it; it = g_list_next(it)) {
256         ObDockApp *app = it->data;
257         switch (config_dock_orient) {
258         case OB_ORIENTATION_HORZ:
259             app->x = hspot;
260             app->y = (dock->area.height - app->h) / 2;
261             hspot += app->w;
262             break;
263         case OB_ORIENTATION_VERT:
264             app->x = (dock->area.width - app->w) / 2;
265             app->y = vspot;
266             vspot += app->h;
267             break;
268         }
269
270         XMoveWindow(obt_display, app->icon_win, app->x, app->y);
271     }
272
273     /* used for calculating offsets */
274     dock->area.width += ob_rr_theme->obwidth * 2;
275     dock->area.height += ob_rr_theme->obwidth * 2;
276
277     a = screen_physical_area_all_monitors();
278
279     /* calculate position */
280     if (config_dock_floating) {
281         dock->area.x = config_dock_x;
282         dock->area.y = config_dock_y;
283         gravity = NorthWestGravity;
284     } else {
285         switch (config_dock_pos) {
286         case OB_DIRECTION_NORTHWEST:
287             dock->area.x = 0;
288             dock->area.y = 0;
289             gravity = NorthWestGravity;
290             break;
291         case OB_DIRECTION_NORTH:
292             dock->area.x = a->width / 2;
293             dock->area.y = 0;
294             gravity = NorthGravity;
295             break;
296         case OB_DIRECTION_NORTHEAST:
297             dock->area.x = a->width;
298             dock->area.y = 0;
299             gravity = NorthEastGravity;
300             break;
301         case OB_DIRECTION_WEST:
302             dock->area.x = 0;
303             dock->area.y = a->height / 2;
304             gravity = WestGravity;
305             break;
306         case OB_DIRECTION_EAST:
307             dock->area.x = a->width;
308             dock->area.y = a->height / 2;
309             gravity = EastGravity;
310             break;
311         case OB_DIRECTION_SOUTHWEST:
312             dock->area.x = 0;
313             dock->area.y = a->height;
314             gravity = SouthWestGravity;
315             break;
316         case OB_DIRECTION_SOUTH:
317             dock->area.x = a->width / 2;
318             dock->area.y = a->height;
319             gravity = SouthGravity;
320             break;
321         case OB_DIRECTION_SOUTHEAST:
322             dock->area.x = a->width;
323             dock->area.y = a->height;
324             gravity = SouthEastGravity;
325             break;
326         default:
327             g_assert_not_reached();
328         }
329     }
330
331     switch(gravity) {
332     case NorthGravity:
333     case CenterGravity:
334     case SouthGravity:
335         dock->area.x -= dock->area.width / 2;
336         break;
337     case NorthEastGravity:
338     case EastGravity:
339     case SouthEastGravity:
340         dock->area.x -= dock->area.width;
341         break;
342     }
343     switch(gravity) {
344     case WestGravity:
345     case CenterGravity:
346     case EastGravity:
347         dock->area.y -= dock->area.height / 2;
348         break;
349     case SouthWestGravity:
350     case SouthGravity:
351     case SouthEastGravity:
352         dock->area.y -= dock->area.height;
353         break;
354     }
355
356     if (config_dock_hide && dock->hidden) {
357         if (!config_dock_floating) {
358             switch (config_dock_pos) {
359             case OB_DIRECTION_NORTHWEST:
360                 switch (config_dock_orient) {
361                 case OB_ORIENTATION_HORZ:
362                     dock->area.y -= dock->area.height - hidesize;
363                     break;
364                 case OB_ORIENTATION_VERT:
365                     dock->area.x -= dock->area.width - hidesize;
366                     break;
367                 }
368                 break;
369             case OB_DIRECTION_NORTH:
370                 dock->area.y -= dock->area.height - hidesize;
371                 break;
372             case OB_DIRECTION_NORTHEAST:
373                 switch (config_dock_orient) {
374                 case OB_ORIENTATION_HORZ:
375                     dock->area.y -= dock->area.height - hidesize;
376                     break;
377                 case OB_ORIENTATION_VERT:
378                     dock->area.x += dock->area.width - hidesize;
379                     break;
380                 }
381                 break;
382             case OB_DIRECTION_WEST:
383                 dock->area.x -= dock->area.width - hidesize;
384                 break;
385             case OB_DIRECTION_EAST:
386                 dock->area.x += dock->area.width - hidesize;
387                 break;
388             case OB_DIRECTION_SOUTHWEST:
389                 switch (config_dock_orient) {
390                 case OB_ORIENTATION_HORZ:
391                     dock->area.y += dock->area.height - hidesize;
392                     break;
393                 case OB_ORIENTATION_VERT:
394                     dock->area.x -= dock->area.width - hidesize;
395                     break;
396                 } break;
397             case OB_DIRECTION_SOUTH:
398                 dock->area.y += dock->area.height - hidesize;
399                 break;
400             case OB_DIRECTION_SOUTHEAST:
401                 switch (config_dock_orient) {
402                 case OB_ORIENTATION_HORZ:
403                     dock->area.y += dock->area.height - hidesize;
404                     break;
405                 case OB_ORIENTATION_VERT:
406                     dock->area.x += dock->area.width - hidesize;
407                     break;
408                 }
409                 break;
410             }
411         }
412     }
413
414     if (!config_dock_floating && config_dock_hide) {
415         strw = hidesize;
416         strh = hidesize;
417     } else {
418         strw = dock->area.width;
419         strh = dock->area.height;
420     }
421
422     /* set the strut */
423     if (!dock->dock_apps) {
424         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
425                           0, 0, 0, 0, 0, 0, 0, 0);
426     }
427     else if (config_dock_floating || config_dock_nostrut) {
428         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
429                           0, 0, 0, 0, 0, 0, 0, 0);
430     }
431     else {
432         switch (config_dock_pos) {
433         case OB_DIRECTION_NORTHWEST:
434             switch (config_dock_orient) {
435             case OB_ORIENTATION_HORZ:
436                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
437                                   0, 0, dock->area.x, dock->area.x
438                                   + dock->area.width - 1, 0, 0, 0, 0);
439                 break;
440             case OB_ORIENTATION_VERT:
441                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
442                                   dock->area.y, dock->area.y
443                                   + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
444                 break;
445             }
446             break;
447         case OB_DIRECTION_NORTH:
448             STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
449                               0, 0, dock->area.x, dock->area.x
450                               + dock->area.width - 1, 0, 0, 0, 0);
451             break;
452         case OB_DIRECTION_NORTHEAST:
453             switch (config_dock_orient) {
454             case OB_ORIENTATION_HORZ:
455                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
456                                   0, 0, dock->area.x, dock->area.x
457                                   + dock->area.width -1, 0, 0, 0, 0);
458                 break;
459             case OB_ORIENTATION_VERT:
460                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
461                                   0, 0, 0, 0, dock->area.y, dock->area.y
462                                   + dock->area.height - 1, 0, 0);
463                 break;
464             }
465             break;
466         case OB_DIRECTION_WEST:
467             STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
468                               dock->area.y, dock->area.y
469                               + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
470             break;
471         case OB_DIRECTION_EAST:
472             STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
473                               0, 0, 0, 0, dock->area.y, dock->area.y
474                               + dock->area.height - 1, 0, 0);
475             break;
476         case OB_DIRECTION_SOUTHWEST:
477             switch (config_dock_orient) {
478             case OB_ORIENTATION_HORZ:
479                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
480                                   0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
481                                   + dock->area.width - 1);
482                 break;
483             case OB_ORIENTATION_VERT:
484                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
485                                   dock->area.y, dock->area.y
486                                   + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
487                 break;
488             }
489             break;
490         case OB_DIRECTION_SOUTH:
491             STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
492                               0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
493                               + dock->area.width - 1);
494             break;
495         case OB_DIRECTION_SOUTHEAST:
496             switch (config_dock_orient) {
497             case OB_ORIENTATION_HORZ:
498                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
499                                   0, 0, 0, 0, 0, 0, dock->area.x,
500                                   dock->area.x + dock->area.width - 1);
501                 break;
502             case OB_ORIENTATION_VERT:
503                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
504                                   0, 0, 0, 0, dock->area.y, dock->area.y
505                                   + dock->area.height - 1, 0, 0);
506                 break;
507             }
508             break;
509         }
510     }
511
512     /* not used for actually sizing shit */
513     dock->area.width -= ob_rr_theme->obwidth * 2;
514     dock->area.height -= ob_rr_theme->obwidth * 2;
515
516     if (dock->dock_apps) {
517         g_assert(dock->area.width > 0);
518         g_assert(dock->area.height > 0);
519
520         XMoveResizeWindow(obt_display, dock->frame, dock->area.x, dock->area.y,
521                           dock->area.width, dock->area.height);
522
523         RrPaint(dock->a_frame, dock->frame, dock->area.width,
524                 dock->area.height);
525         XMapWindow(obt_display, dock->frame);
526     } else
527         XUnmapWindow(obt_display, dock->frame);
528
529     /* but they are useful outside of this function! but don't add it if the
530        dock is actually not visible */
531     if (dock->dock_apps) {
532         dock->area.width += ob_rr_theme->obwidth * 2;
533         dock->area.height += ob_rr_theme->obwidth * 2;
534     }
535
536     screen_update_areas();
537
538     g_free(a);
539 }
540
541 void dock_app_configure(ObDockApp *app, gint w, gint h)
542 {
543     app->w = w;
544     app->h = h;
545     dock_configure();
546 }
547
548 void dock_app_drag(ObDockApp *app, XMotionEvent *e)
549 {
550     ObDockApp *over = NULL;
551     GList *it;
552     gint x, y;
553     gboolean after;
554     gboolean stop;
555
556     x = e->x_root;
557     y = e->y_root;
558
559     /* are we on top of the dock? */
560     if (!(x >= dock->area.x &&
561           y >= dock->area.y &&
562           x < dock->area.x + dock->area.width &&
563           y < dock->area.y + dock->area.height))
564         return;
565
566     x -= dock->area.x;
567     y -= dock->area.y;
568
569     /* which dock app are we on top of? */
570     stop = FALSE;
571     for (it = dock->dock_apps; it; it = g_list_next(it)) {
572         over = it->data;
573         switch (config_dock_orient) {
574         case OB_ORIENTATION_HORZ:
575             if (x >= over->x && x < over->x + over->w)
576                 stop = TRUE;
577             break;
578         case OB_ORIENTATION_VERT:
579             if (y >= over->y && y < over->y + over->h)
580                 stop = TRUE;
581             break;
582         }
583         /* dont go to it->next! */
584         if (stop) break;
585     }
586     if (!it || app == over) return;
587
588     x -= over->x;
589     y -= over->y;
590
591     switch (config_dock_orient) {
592     case OB_ORIENTATION_HORZ:
593         after = (x > over->w / 2);
594         break;
595     case OB_ORIENTATION_VERT:
596         after = (y > over->h / 2);
597         break;
598     default:
599         g_assert_not_reached();
600     }
601
602     /* remove before doing the it->next! */
603     dock->dock_apps = g_list_remove(dock->dock_apps, app);
604
605     if (after) it = it->next;
606
607     dock->dock_apps = g_list_insert_before(dock->dock_apps, it, app);
608     dock_configure();
609 }
610
611 static gboolean hide_timeout(gpointer data)
612 {
613     /* hide */
614     dock->hidden = TRUE;
615     dock_configure();
616
617     return FALSE; /* don't repeat */
618 }
619
620 static gboolean show_timeout(gpointer data)
621 {
622     /* hide */
623     dock->hidden = FALSE;
624     dock_configure();
625
626     return FALSE; /* don't repeat */
627 }
628
629 void dock_hide(gboolean hide)
630 {
631     if (!hide) {
632         if (dock->hidden && config_dock_hide) {
633             obt_main_loop_timeout_add(ob_main_loop,
634                                       config_dock_show_delay * 1000,
635                                       show_timeout, NULL,
636                                       g_direct_equal, NULL);
637         } else if (!dock->hidden && config_dock_hide) {
638             obt_main_loop_timeout_remove(ob_main_loop, hide_timeout);
639         }
640     } else {
641         if (!dock->hidden && config_dock_hide) {
642             obt_main_loop_timeout_add(ob_main_loop,
643                                       config_dock_hide_delay * 1000,
644                                       hide_timeout, NULL,
645                                       g_direct_equal, NULL);
646         } else if (dock->hidden && config_dock_hide) {
647             obt_main_loop_timeout_remove(ob_main_loop, show_timeout);
648         }
649     }
650 }
651
652 void dock_get_area(Rect *a)
653 {
654     RECT_SET(*a, dock->area.x, dock->area.y,
655              dock->area.width, dock->area.height);
656 }
657
658 ObDockApp* dock_find_dockapp(Window xwin)
659 {
660     GList *it;
661     /* there are never that many dock apps, so we can use a list here instead
662        of a hash table */
663     for (it = dock->dock_apps; it; it = g_list_next(it)) {
664         ObDockApp *app = it->data;
665         if (app->icon_win == xwin)
666             return app;
667     }
668     return NULL;
669 }