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