Move the main loop out into the libobt
[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 "prop.h"
24 #include "config.h"
25 #include "grab.h"
26 #include "openbox.h"
27 #include "render/theme.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(ob_display, dock->frame,
61                          RrColorPixel(ob_rr_theme->osd_border_color));
62         XSetWindowBorderWidth(ob_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 = Window_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(ob_display, RootWindow(ob_display, 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(ob_display, dock->frame,
97                      RrColorPixel(ob_rr_theme->osd_border_color));
98     XSetWindowBorderWidth(ob_display, dock->frame, ob_rr_theme->obwidth);
99
100     /* Setting the window type so xcompmgr can tell what it is */
101     PROP_SET32(dock->frame, net_wm_window_type, atom,
102                prop_atoms.net_wm_window_type_dock);
103
104     g_hash_table_insert(window_map, &dock->frame, 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(ob_display, dock->frame);
121     RrAppearanceFree(dock->a_frame);
122     g_hash_table_remove(window_map, &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->obwin.type = Window_DockApp;
134     app->win = win;
135     app->icon_win = (wmhints->flags & IconWindowHint) ?
136         wmhints->icon_window : win;
137
138     if (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(ob_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(ob_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(ob_display, app->win, -1000, -1000);
174         XMapWindow(ob_display, app->win);
175     }
176     XMapWindow(ob_display, app->icon_win);
177     XSync(ob_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(ob_display, app->icon_win, SetModeInsert);
182     XSelectInput(ob_display, app->icon_win, DOCKAPP_EVENT_MASK);
183
184     dock_app_grab_button(app, TRUE);
185
186     g_hash_table_insert(window_map, &app->icon_win, app);
187
188     ob_debug("Managed Dock App: 0x%lx (%s)\n", app->icon_win, app->class);
189 }
190
191 void dock_remove_all(void)
192 {
193     while (dock->dock_apps)
194         dock_remove(dock->dock_apps->data, TRUE);
195 }
196
197 void dock_remove(ObDockApp *app, gboolean reparent)
198 {
199     dock_app_grab_button(app, FALSE);
200     XSelectInput(ob_display, app->icon_win, NoEventMask);
201     /* remove the window from our save set */
202     XChangeSaveSet(ob_display, app->icon_win, SetModeDelete);
203     XSync(ob_display, False);
204
205     g_hash_table_remove(window_map, &app->icon_win);
206
207     if (reparent)
208         XReparentWindow(ob_display, app->icon_win,
209                         RootWindow(ob_display, ob_screen), app->x, app->y);
210
211     dock->dock_apps = g_list_remove(dock->dock_apps, app);
212     dock_configure();
213
214     ob_debug("Unmanaged Dock App: 0x%lx (%s)\n", app->icon_win, app->class);
215
216     g_free(app->name);
217     g_free(app->class);
218     g_free(app);
219 }
220
221 void dock_configure(void)
222 {
223     GList *it;
224     gint hspot, vspot;
225     gint gravity;
226     gint l, r, t, b;
227     gint strw, strh;
228     Rect *a;
229     gint hidesize;
230
231     RrMargins(dock->a_frame, &l, &t, &r, &b);
232     hidesize = MAX(1, ob_rr_theme->obwidth);
233
234     dock->area.width = dock->area.height = 0;
235
236     /* get the size */
237     for (it = dock->dock_apps; it; it = g_list_next(it)) {
238         ObDockApp *app = it->data;
239         switch (config_dock_orient) {
240         case OB_ORIENTATION_HORZ:
241             dock->area.width += app->w;
242             dock->area.height = MAX(dock->area.height, app->h);
243             break;
244         case OB_ORIENTATION_VERT:
245             dock->area.width = MAX(dock->area.width, app->w);
246             dock->area.height += app->h;
247             break;
248         }
249     }
250
251     dock->area.width += l + r;
252     dock->area.height += t + b;
253
254     hspot = l;
255     vspot = t;
256
257     /* position the apps */
258     for (it = dock->dock_apps; it; it = g_list_next(it)) {
259         ObDockApp *app = it->data;
260         switch (config_dock_orient) {
261         case OB_ORIENTATION_HORZ:
262             app->x = hspot;
263             app->y = (dock->area.height - app->h) / 2;
264             hspot += app->w;
265             break;
266         case OB_ORIENTATION_VERT:
267             app->x = (dock->area.width - app->w) / 2;
268             app->y = vspot;
269             vspot += app->h;
270             break;
271         }
272
273         XMoveWindow(ob_display, app->icon_win, app->x, app->y);
274     }
275
276     /* used for calculating offsets */
277     dock->area.width += ob_rr_theme->obwidth * 2;
278     dock->area.height += ob_rr_theme->obwidth * 2;
279
280     a = screen_physical_area_all_monitors();
281
282     /* calculate position */
283     if (config_dock_floating) {
284         dock->area.x = config_dock_x;
285         dock->area.y = config_dock_y;
286         gravity = NorthWestGravity;
287     } else {
288         switch (config_dock_pos) {
289         case OB_DIRECTION_NORTHWEST:
290             dock->area.x = 0;
291             dock->area.y = 0;
292             gravity = NorthWestGravity;
293             break;
294         case OB_DIRECTION_NORTH:
295             dock->area.x = a->width / 2;
296             dock->area.y = 0;
297             gravity = NorthGravity;
298             break;
299         case OB_DIRECTION_NORTHEAST:
300             dock->area.x = a->width;
301             dock->area.y = 0;
302             gravity = NorthEastGravity;
303             break;
304         case OB_DIRECTION_WEST:
305             dock->area.x = 0;
306             dock->area.y = a->height / 2;
307             gravity = WestGravity;
308             break;
309         case OB_DIRECTION_EAST:
310             dock->area.x = a->width;
311             dock->area.y = a->height / 2;
312             gravity = EastGravity;
313             break;
314         case OB_DIRECTION_SOUTHWEST:
315             dock->area.x = 0;
316             dock->area.y = a->height;
317             gravity = SouthWestGravity;
318             break;
319         case OB_DIRECTION_SOUTH:
320             dock->area.x = a->width / 2;
321             dock->area.y = a->height;
322             gravity = SouthGravity;
323             break;
324         case OB_DIRECTION_SOUTHEAST:
325             dock->area.x = a->width;
326             dock->area.y = a->height;
327             gravity = SouthEastGravity;
328             break;
329         default:
330             g_assert_not_reached();
331         }
332     }
333
334     switch(gravity) {
335     case NorthGravity:
336     case CenterGravity:
337     case SouthGravity:
338         dock->area.x -= dock->area.width / 2;
339         break;
340     case NorthEastGravity:
341     case EastGravity:
342     case SouthEastGravity:
343         dock->area.x -= dock->area.width;
344         break;
345     }
346     switch(gravity) {
347     case WestGravity:
348     case CenterGravity:
349     case EastGravity:
350         dock->area.y -= dock->area.height / 2;
351         break;
352     case SouthWestGravity:
353     case SouthGravity:
354     case SouthEastGravity:
355         dock->area.y -= dock->area.height;
356         break;
357     }
358
359     if (config_dock_hide && dock->hidden) {
360         if (!config_dock_floating) {
361             switch (config_dock_pos) {
362             case OB_DIRECTION_NORTHWEST:
363                 switch (config_dock_orient) {
364                 case OB_ORIENTATION_HORZ:
365                     dock->area.y -= dock->area.height - hidesize;
366                     break;
367                 case OB_ORIENTATION_VERT:
368                     dock->area.x -= dock->area.width - hidesize;
369                     break;
370                 }
371                 break;
372             case OB_DIRECTION_NORTH:
373                 dock->area.y -= dock->area.height - hidesize;
374                 break;
375             case OB_DIRECTION_NORTHEAST:
376                 switch (config_dock_orient) {
377                 case OB_ORIENTATION_HORZ:
378                     dock->area.y -= dock->area.height - hidesize;
379                     break;
380                 case OB_ORIENTATION_VERT:
381                     dock->area.x += dock->area.width - hidesize;
382                     break;
383                 }
384                 break;
385             case OB_DIRECTION_WEST:
386                 dock->area.x -= dock->area.width - hidesize;
387                 break;
388             case OB_DIRECTION_EAST:
389                 dock->area.x += dock->area.width - hidesize;
390                 break;
391             case OB_DIRECTION_SOUTHWEST:
392                 switch (config_dock_orient) {
393                 case OB_ORIENTATION_HORZ:
394                     dock->area.y += dock->area.height - hidesize;
395                     break;
396                 case OB_ORIENTATION_VERT:
397                     dock->area.x -= dock->area.width - hidesize;
398                     break;
399                 } break;
400             case OB_DIRECTION_SOUTH:
401                 dock->area.y += dock->area.height - hidesize;
402                 break;
403             case OB_DIRECTION_SOUTHEAST:
404                 switch (config_dock_orient) {
405                 case OB_ORIENTATION_HORZ:
406                     dock->area.y += dock->area.height - hidesize;
407                     break;
408                 case OB_ORIENTATION_VERT:
409                     dock->area.x += dock->area.width - hidesize;
410                     break;
411                 }
412                 break;
413             }
414         }
415     }
416
417     if (!config_dock_floating && config_dock_hide) {
418         strw = hidesize;
419         strh = hidesize;
420     } else {
421         strw = dock->area.width;
422         strh = dock->area.height;
423     }
424
425     /* set the strut */
426     if (!dock->dock_apps) {
427         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
428                           0, 0, 0, 0, 0, 0, 0, 0);
429     } else if (config_dock_floating || config_dock_nostrut)
430     {
431         STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
432                           0, 0, 0, 0, 0, 0, 0, 0);
433     } else {
434         switch (config_dock_pos) {
435         case OB_DIRECTION_NORTHWEST:
436             switch (config_dock_orient) {
437             case OB_ORIENTATION_HORZ:
438                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
439                                   0, 0, dock->area.x, dock->area.x
440                                   + dock->area.width - 1, 0, 0, 0, 0);
441                 break;
442             case OB_ORIENTATION_VERT:
443                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
444                                   dock->area.y, dock->area.y
445                                   + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
446                 break;
447             }
448             break;
449         case OB_DIRECTION_NORTH:
450             STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
451                               0, 0, dock->area.x, dock->area.x
452                               + dock->area.width - 1, 0, 0, 0, 0);
453             break;
454         case OB_DIRECTION_NORTHEAST:
455             switch (config_dock_orient) {
456             case OB_ORIENTATION_HORZ:
457                 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
458                                   0, 0, dock->area.x, dock->area.x
459                                   + dock->area.width -1, 0, 0, 0, 0);
460                 break;
461             case OB_ORIENTATION_VERT:
462                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
463                                   0, 0, 0, 0, dock->area.y, dock->area.y
464                                   + dock->area.height - 1, 0, 0);
465                 break;
466             }
467             break;
468         case OB_DIRECTION_WEST:
469             STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
470                               dock->area.y, dock->area.y
471                               + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
472             break;
473         case OB_DIRECTION_EAST:
474             STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
475                               0, 0, 0, 0, dock->area.y, dock->area.y
476                               + dock->area.height - 1, 0, 0);
477             break;
478         case OB_DIRECTION_SOUTHWEST:
479             switch (config_dock_orient) {
480             case OB_ORIENTATION_HORZ:
481                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
482                                   0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
483                                   + dock->area.width - 1);
484                 break;
485             case OB_ORIENTATION_VERT:
486                 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
487                                   dock->area.y, dock->area.y
488                                   + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
489                 break;
490             }
491             break;
492         case OB_DIRECTION_SOUTH:
493             STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
494                               0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
495                               + dock->area.width - 1);
496             break;
497         case OB_DIRECTION_SOUTHEAST:
498             switch (config_dock_orient) {
499             case OB_ORIENTATION_HORZ:
500                 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
501                                   0, 0, 0, 0, 0, 0, dock->area.x,
502                                   dock->area.x + dock->area.width - 1);
503                 break;
504             case OB_ORIENTATION_VERT:
505                 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
506                                   0, 0, 0, 0, dock->area.y, dock->area.y
507                                   + dock->area.height - 1, 0, 0);
508                 break;
509             }
510             break;
511         }
512     }
513
514     /* not used for actually sizing shit */
515     dock->area.width -= ob_rr_theme->obwidth * 2;
516     dock->area.height -= ob_rr_theme->obwidth * 2;
517
518     if (dock->dock_apps) {
519         g_assert(dock->area.width > 0);
520         g_assert(dock->area.height > 0);
521
522         XMoveResizeWindow(ob_display, dock->frame, dock->area.x, dock->area.y,
523                           dock->area.width, dock->area.height);
524
525         RrPaint(dock->a_frame, dock->frame, dock->area.width,
526                 dock->area.height);
527         XMapWindow(ob_display, dock->frame);
528     } else
529         XUnmapWindow(ob_display, dock->frame);
530
531     /* but they are useful outside of this function! */
532     dock->area.width += ob_rr_theme->obwidth * 2;
533     dock->area.height += ob_rr_theme->obwidth * 2;
534
535     screen_update_areas();
536
537     g_free(a);
538 }
539
540 void dock_app_configure(ObDockApp *app, gint w, gint h)
541 {
542     app->w = w;
543     app->h = h;
544     dock_configure();
545 }
546
547 void dock_app_drag(ObDockApp *app, XMotionEvent *e)
548 {
549     ObDockApp *over = NULL;
550     GList *it;
551     gint x, y;
552     gboolean after;
553     gboolean stop;
554
555     x = e->x_root;
556     y = e->y_root;
557
558     /* are we on top of the dock? */
559     if (!(x >= dock->area.x &&
560           y >= dock->area.y &&
561           x < dock->area.x + dock->area.width &&
562           y < dock->area.y + dock->area.height))
563         return;
564
565     x -= dock->area.x;
566     y -= dock->area.y;
567
568     /* which dock app are we on top of? */
569     stop = FALSE;
570     for (it = dock->dock_apps; it; it = g_list_next(it)) {
571         over = it->data;
572         switch (config_dock_orient) {
573         case OB_ORIENTATION_HORZ:
574             if (x >= over->x && x < over->x + over->w)
575                 stop = TRUE;
576             break;
577         case OB_ORIENTATION_VERT:
578             if (y >= over->y && y < over->y + over->h)
579                 stop = TRUE;
580             break;
581         }
582         /* dont go to it->next! */
583         if (stop) break;
584     }
585     if (!it || app == over) return;
586
587     x -= over->x;
588     y -= over->y;
589
590     switch (config_dock_orient) {
591     case OB_ORIENTATION_HORZ:
592         after = (x > over->w / 2);
593         break;
594     case OB_ORIENTATION_VERT:
595         after = (y > over->h / 2);
596         break;
597     default:
598         g_assert_not_reached();
599     }
600
601     /* remove before doing the it->next! */
602     dock->dock_apps = g_list_remove(dock->dock_apps, app);
603
604     if (after) it = it->next;
605
606     dock->dock_apps = g_list_insert_before(dock->dock_apps, it, app);
607     dock_configure();
608 }
609
610 static gboolean hide_timeout(gpointer data)
611 {
612     /* hide */
613     dock->hidden = TRUE;
614     dock_configure();
615
616     return FALSE; /* don't repeat */
617 }
618
619 static gboolean show_timeout(gpointer data)
620 {
621     /* hide */
622     dock->hidden = FALSE;
623     dock_configure();
624
625     return FALSE; /* don't repeat */
626 }
627
628 void dock_hide(gboolean hide)
629 {
630     if (!hide) {
631         if (dock->hidden && config_dock_hide) {
632             obt_main_loop_timeout_add(ob_main_loop,
633                                       config_dock_show_delay * 1000,
634                                       show_timeout, NULL,
635                                       g_direct_equal, NULL);
636         } else if (!dock->hidden && config_dock_hide) {
637             obt_main_loop_timeout_remove(ob_main_loop, hide_timeout);
638         }
639     } else {
640         if (!dock->hidden && config_dock_hide) {
641             obt_main_loop_timeout_add(ob_main_loop,
642                                       config_dock_hide_delay * 1000,
643                                       hide_timeout, NULL,
644                                       g_direct_equal, NULL);
645         } else if (dock->hidden && config_dock_hide) {
646             obt_main_loop_timeout_remove(ob_main_loop, show_timeout);
647         }
648     }
649 }
650
651 void dock_get_area(Rect *a)
652 {
653     RECT_SET(*a, dock->area.x, dock->area.y,
654              dock->area.width, dock->area.height);
655 }