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