Allow moving fullscreen windows between monitors
[openbox.git] / openbox / dock.c
blobea9b7f498ffb953bf85efe34a1489f9c922d9ad3
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 dock.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
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 "obrender/theme.h"
27 #include "obt/prop.h"
29 #define DOCK_EVENT_MASK (ButtonPressMask | ButtonReleaseMask | \
30 EnterWindowMask | LeaveWindowMask)
31 #define DOCKAPP_EVENT_MASK (StructureNotifyMask)
32 #define DOCK_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
33 ButtonMotionMask)
35 static ObDock *dock;
36 static guint show_timeout_id;
37 static guint hide_timeout_id;
39 StrutPartial dock_strut;
41 static void dock_app_grab_button(ObDockApp *app, gboolean grab)
43 if (grab) {
44 grab_button_full(config_dock_app_move_button,
45 config_dock_app_move_modifiers, app->icon_win,
46 ButtonPressMask | ButtonReleaseMask |
47 ButtonMotionMask,
48 GrabModeAsync, OB_CURSOR_MOVE);
49 } else {
50 ungrab_button(config_dock_app_move_button,
51 config_dock_app_move_modifiers, app->icon_win);
55 static guint window_hash(Window *w) { return *w; }
56 static gboolean window_comp(Window *w1, Window *w2) { return *w1 == *w2; }
58 void dock_startup(gboolean reconfig)
60 XSetWindowAttributes attrib;
62 if (reconfig) {
63 GList *it;
65 XSetWindowBorder(obt_display, dock->frame,
66 RrColorPixel(ob_rr_theme->osd_border_color));
67 XSetWindowBorderWidth(obt_display, dock->frame, ob_rr_theme->obwidth);
69 RrAppearanceFree(dock->a_frame);
70 dock->a_frame = RrAppearanceCopy(ob_rr_theme->osd_bg);
72 stacking_add(DOCK_AS_WINDOW(dock));
74 dock_configure();
75 dock_hide(TRUE);
77 for (it = dock->dock_apps; it; it = g_list_next(it))
78 dock_app_grab_button(it->data, TRUE);
79 return;
82 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
83 0, 0, 0, 0, 0, 0, 0, 0);
85 dock = g_slice_new0(ObDock);
86 dock->obwin.type = OB_WINDOW_CLASS_DOCK;
88 dock->hidden = TRUE;
90 dock->dock_map = g_hash_table_new((GHashFunc)window_hash,
91 (GEqualFunc)window_comp);
93 attrib.event_mask = DOCK_EVENT_MASK;
94 attrib.override_redirect = True;
95 attrib.do_not_propagate_mask = DOCK_NOPROPAGATEMASK;
96 dock->frame = XCreateWindow(obt_display, obt_root(ob_screen),
97 0, 0, 1, 1, 0,
98 RrDepth(ob_rr_inst), InputOutput,
99 RrVisual(ob_rr_inst),
100 CWOverrideRedirect | CWEventMask |
101 CWDontPropagate,
102 &attrib);
103 dock->a_frame = RrAppearanceCopy(ob_rr_theme->osd_bg);
104 XSetWindowBorder(obt_display, dock->frame,
105 RrColorPixel(ob_rr_theme->osd_border_color));
106 XSetWindowBorderWidth(obt_display, dock->frame, ob_rr_theme->obwidth);
108 /* Setting the window type so xcompmgr can tell what it is */
109 OBT_PROP_SET32(dock->frame, NET_WM_WINDOW_TYPE, ATOM,
110 OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DOCK));
112 window_add(&dock->frame, DOCK_AS_WINDOW(dock));
113 stacking_add(DOCK_AS_WINDOW(dock));
116 void dock_shutdown(gboolean reconfig)
118 if (reconfig) {
119 GList *it;
121 stacking_remove(DOCK_AS_WINDOW(dock));
123 for (it = dock->dock_apps; it; it = g_list_next(it))
124 dock_app_grab_button(it->data, FALSE);
125 return;
128 g_hash_table_destroy(dock->dock_map);
130 XDestroyWindow(obt_display, dock->frame);
131 RrAppearanceFree(dock->a_frame);
132 window_remove(dock->frame);
133 stacking_remove(dock);
134 g_slice_free(ObDock, dock);
135 dock = NULL;
138 void dock_manage(Window icon_win, Window name_win)
140 ObDockApp *app;
141 XWindowAttributes attrib;
142 gchar **data;
144 app = g_slice_new0(ObDockApp);
145 app->name_win = name_win;
146 app->icon_win = icon_win;
148 if (OBT_PROP_GETSS_TYPE(app->name_win, WM_CLASS, STRING_NO_CC, &data)) {
149 if (data[0]) {
150 app->name = g_strdup(data[0]);
151 if (data[1])
152 app->class = g_strdup(data[1]);
154 g_strfreev(data);
157 if (app->name == NULL) app->name = g_strdup("");
158 if (app->class == NULL) app->class = g_strdup("");
160 if (XGetWindowAttributes(obt_display, app->icon_win, &attrib)) {
161 app->w = attrib.width;
162 app->h = attrib.height;
163 } else {
164 app->w = app->h = 64;
167 dock->dock_apps = g_list_append(dock->dock_apps, app);
168 g_hash_table_insert(dock->dock_map, &app->icon_win, app);
169 dock_configure();
171 XReparentWindow(obt_display, app->icon_win, dock->frame, app->x, app->y);
173 This is the same case as in frame.c for client windows. When Openbox is
174 starting, the window is already mapped so we see unmap events occur for
175 it. There are 2 unmap events generated that we see, one with the 'event'
176 member set the root window, and one set to the client, but both get
177 handled and need to be ignored.
179 if (ob_state() == OB_STATE_STARTING)
180 app->ignore_unmaps += 2;
181 XChangeSaveSet(obt_display, app->icon_win, SetModeInsert);
182 XMapWindow(obt_display, app->icon_win);
184 if (app->name_win != app->icon_win) {
185 XReparentWindow(obt_display, app->name_win, dock->frame, -1000, -1000);
186 XChangeSaveSet(obt_display, app->name_win, SetModeInsert);
187 XMapWindow(obt_display, app->name_win);
190 XSync(obt_display, False);
192 XSelectInput(obt_display, app->icon_win, DOCKAPP_EVENT_MASK);
194 dock_app_grab_button(app, TRUE);
196 ob_debug("Managed Dock App: 0x%lx 0x%lx (%s)",
197 app->icon_win, app->name_win, app->class);
199 grab_server(FALSE);
202 void dock_unmanage_all(void)
204 while (dock->dock_apps)
205 dock_unmanage(dock->dock_apps->data, TRUE);
208 void dock_unmanage(ObDockApp *app, gboolean reparent)
210 dock_app_grab_button(app, FALSE);
211 XSelectInput(obt_display, app->icon_win, NoEventMask);
212 /* remove the window from our save set */
213 XChangeSaveSet(obt_display, app->icon_win, SetModeDelete);
214 XSync(obt_display, False);
216 if (reparent) {
217 XReparentWindow(obt_display, app->icon_win, obt_root(ob_screen), 0, 0);
218 if (app->name_win != app->icon_win)
219 XReparentWindow(obt_display, app->name_win,
220 obt_root(ob_screen), 0, 0);
223 dock->dock_apps = g_list_remove(dock->dock_apps, app);
224 g_hash_table_remove(dock->dock_map, &app->icon_win);
225 dock_configure();
227 ob_debug("Unmanaged Dock App: 0x%lx (%s)", app->icon_win, app->class);
229 g_free(app->name);
230 g_free(app->class);
231 g_slice_free(ObDockApp, app);
234 void dock_configure(void)
236 GList *it;
237 gint hspot, vspot;
238 gint gravity;
239 gint l, r, t, b;
240 gint strw, strh;
241 const Rect *a;
242 gint hidesize;
244 RrMargins(dock->a_frame, &l, &t, &r, &b);
245 hidesize = MAX(1, ob_rr_theme->obwidth);
247 dock->area.width = dock->area.height = 0;
249 /* get the size */
250 for (it = dock->dock_apps; it; it = g_list_next(it)) {
251 ObDockApp *app = it->data;
252 switch (config_dock_orient) {
253 case OB_ORIENTATION_HORZ:
254 dock->area.width += app->w;
255 dock->area.height = MAX(dock->area.height, app->h);
256 break;
257 case OB_ORIENTATION_VERT:
258 dock->area.width = MAX(dock->area.width, app->w);
259 dock->area.height += app->h;
260 break;
264 if (dock->dock_apps) {
265 dock->area.width += l + r;
266 dock->area.height += t + b;
269 hspot = l;
270 vspot = t;
272 /* position the apps */
273 for (it = dock->dock_apps; it; it = g_list_next(it)) {
274 ObDockApp *app = it->data;
275 switch (config_dock_orient) {
276 case OB_ORIENTATION_HORZ:
277 app->x = hspot;
278 app->y = (dock->area.height - app->h) / 2;
279 hspot += app->w;
280 break;
281 case OB_ORIENTATION_VERT:
282 app->x = (dock->area.width - app->w) / 2;
283 app->y = vspot;
284 vspot += app->h;
285 break;
288 XMoveWindow(obt_display, app->icon_win, app->x, app->y);
291 /* used for calculating offsets */
292 dock->area.width += ob_rr_theme->obwidth * 2;
293 dock->area.height += ob_rr_theme->obwidth * 2;
295 a = screen_physical_area_all_monitors();
297 /* calculate position */
298 if (config_dock_floating) {
299 dock->area.x = config_dock_x;
300 dock->area.y = config_dock_y;
301 gravity = NorthWestGravity;
302 } else {
303 switch (config_dock_pos) {
304 case OB_DIRECTION_NORTHWEST:
305 dock->area.x = 0;
306 dock->area.y = 0;
307 gravity = NorthWestGravity;
308 break;
309 case OB_DIRECTION_NORTH:
310 dock->area.x = a->width / 2;
311 dock->area.y = 0;
312 gravity = NorthGravity;
313 break;
314 case OB_DIRECTION_NORTHEAST:
315 dock->area.x = a->width;
316 dock->area.y = 0;
317 gravity = NorthEastGravity;
318 break;
319 case OB_DIRECTION_WEST:
320 dock->area.x = 0;
321 dock->area.y = a->height / 2;
322 gravity = WestGravity;
323 break;
324 case OB_DIRECTION_EAST:
325 dock->area.x = a->width;
326 dock->area.y = a->height / 2;
327 gravity = EastGravity;
328 break;
329 case OB_DIRECTION_SOUTHWEST:
330 dock->area.x = 0;
331 dock->area.y = a->height;
332 gravity = SouthWestGravity;
333 break;
334 case OB_DIRECTION_SOUTH:
335 dock->area.x = a->width / 2;
336 dock->area.y = a->height;
337 gravity = SouthGravity;
338 break;
339 case OB_DIRECTION_SOUTHEAST:
340 dock->area.x = a->width;
341 dock->area.y = a->height;
342 gravity = SouthEastGravity;
343 break;
344 default:
345 g_assert_not_reached();
349 switch(gravity) {
350 case NorthGravity:
351 case CenterGravity:
352 case SouthGravity:
353 dock->area.x -= dock->area.width / 2;
354 break;
355 case NorthEastGravity:
356 case EastGravity:
357 case SouthEastGravity:
358 dock->area.x -= dock->area.width;
359 break;
361 switch(gravity) {
362 case WestGravity:
363 case CenterGravity:
364 case EastGravity:
365 dock->area.y -= dock->area.height / 2;
366 break;
367 case SouthWestGravity:
368 case SouthGravity:
369 case SouthEastGravity:
370 dock->area.y -= dock->area.height;
371 break;
374 if (config_dock_hide && dock->hidden) {
375 if (!config_dock_floating) {
376 switch (config_dock_pos) {
377 case OB_DIRECTION_NORTHWEST:
378 switch (config_dock_orient) {
379 case OB_ORIENTATION_HORZ:
380 dock->area.y -= dock->area.height - hidesize;
381 break;
382 case OB_ORIENTATION_VERT:
383 dock->area.x -= dock->area.width - hidesize;
384 break;
386 break;
387 case OB_DIRECTION_NORTH:
388 dock->area.y -= dock->area.height - hidesize;
389 break;
390 case OB_DIRECTION_NORTHEAST:
391 switch (config_dock_orient) {
392 case OB_ORIENTATION_HORZ:
393 dock->area.y -= dock->area.height - hidesize;
394 break;
395 case OB_ORIENTATION_VERT:
396 dock->area.x += dock->area.width - hidesize;
397 break;
399 break;
400 case OB_DIRECTION_WEST:
401 dock->area.x -= dock->area.width - hidesize;
402 break;
403 case OB_DIRECTION_EAST:
404 dock->area.x += dock->area.width - hidesize;
405 break;
406 case OB_DIRECTION_SOUTHWEST:
407 switch (config_dock_orient) {
408 case OB_ORIENTATION_HORZ:
409 dock->area.y += dock->area.height - hidesize;
410 break;
411 case OB_ORIENTATION_VERT:
412 dock->area.x -= dock->area.width - hidesize;
413 break;
414 } break;
415 case OB_DIRECTION_SOUTH:
416 dock->area.y += dock->area.height - hidesize;
417 break;
418 case OB_DIRECTION_SOUTHEAST:
419 switch (config_dock_orient) {
420 case OB_ORIENTATION_HORZ:
421 dock->area.y += dock->area.height - hidesize;
422 break;
423 case OB_ORIENTATION_VERT:
424 dock->area.x += dock->area.width - hidesize;
425 break;
427 break;
432 if (!config_dock_floating && config_dock_hide) {
433 strw = hidesize;
434 strh = hidesize;
435 } else {
436 strw = dock->area.width;
437 strh = dock->area.height;
440 /* set the strut */
441 if (!dock->dock_apps) {
442 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
443 0, 0, 0, 0, 0, 0, 0, 0);
445 else if (config_dock_floating || config_dock_nostrut) {
446 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, 0,
447 0, 0, 0, 0, 0, 0, 0, 0);
449 else {
450 switch (config_dock_pos) {
451 case OB_DIRECTION_NORTHWEST:
452 switch (config_dock_orient) {
453 case OB_ORIENTATION_HORZ:
454 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
455 0, 0, dock->area.x, dock->area.x
456 + dock->area.width - 1, 0, 0, 0, 0);
457 break;
458 case OB_ORIENTATION_VERT:
459 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
460 dock->area.y, dock->area.y
461 + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
462 break;
464 break;
465 case OB_DIRECTION_NORTH:
466 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
467 0, 0, dock->area.x, dock->area.x
468 + dock->area.width - 1, 0, 0, 0, 0);
469 break;
470 case OB_DIRECTION_NORTHEAST:
471 switch (config_dock_orient) {
472 case OB_ORIENTATION_HORZ:
473 STRUT_PARTIAL_SET(dock_strut, 0, strh, 0, 0,
474 0, 0, dock->area.x, dock->area.x
475 + dock->area.width -1, 0, 0, 0, 0);
476 break;
477 case OB_ORIENTATION_VERT:
478 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
479 0, 0, 0, 0, dock->area.y, dock->area.y
480 + dock->area.height - 1, 0, 0);
481 break;
483 break;
484 case OB_DIRECTION_WEST:
485 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
486 dock->area.y, dock->area.y
487 + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
488 break;
489 case OB_DIRECTION_EAST:
490 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
491 0, 0, 0, 0, dock->area.y, dock->area.y
492 + dock->area.height - 1, 0, 0);
493 break;
494 case OB_DIRECTION_SOUTHWEST:
495 switch (config_dock_orient) {
496 case OB_ORIENTATION_HORZ:
497 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
498 0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
499 + dock->area.width - 1);
500 break;
501 case OB_ORIENTATION_VERT:
502 STRUT_PARTIAL_SET(dock_strut, strw, 0, 0, 0,
503 dock->area.y, dock->area.y
504 + dock->area.height - 1, 0, 0, 0, 0, 0, 0);
505 break;
507 break;
508 case OB_DIRECTION_SOUTH:
509 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
510 0, 0, 0, 0, 0, 0, dock->area.x, dock->area.x
511 + dock->area.width - 1);
512 break;
513 case OB_DIRECTION_SOUTHEAST:
514 switch (config_dock_orient) {
515 case OB_ORIENTATION_HORZ:
516 STRUT_PARTIAL_SET(dock_strut, 0, 0, 0, strh,
517 0, 0, 0, 0, 0, 0, dock->area.x,
518 dock->area.x + dock->area.width - 1);
519 break;
520 case OB_ORIENTATION_VERT:
521 STRUT_PARTIAL_SET(dock_strut, 0, 0, strw, 0,
522 0, 0, 0, 0, dock->area.y, dock->area.y
523 + dock->area.height - 1, 0, 0);
524 break;
526 break;
530 /* not used for actually sizing shit */
531 dock->area.width -= ob_rr_theme->obwidth * 2;
532 dock->area.height -= ob_rr_theme->obwidth * 2;
534 if (dock->dock_apps) {
535 g_assert(dock->area.width > 0);
536 g_assert(dock->area.height > 0);
538 XMoveResizeWindow(obt_display, dock->frame, dock->area.x, dock->area.y,
539 dock->area.width, dock->area.height);
541 RrPaint(dock->a_frame, dock->frame, dock->area.width,
542 dock->area.height);
543 XMapWindow(obt_display, dock->frame);
544 } else
545 XUnmapWindow(obt_display, dock->frame);
547 /* but they are useful outside of this function! but don't add it if the
548 dock is actually not visible */
549 if (dock->dock_apps) {
550 dock->area.width += ob_rr_theme->obwidth * 2;
551 dock->area.height += ob_rr_theme->obwidth * 2;
554 /* screen_resize() depends on this function to call screen_update_areas(),
555 so if this changes, also update screen_resize(). */
556 screen_update_areas();
559 void dock_app_configure(ObDockApp *app, gint w, gint h)
561 app->w = w;
562 app->h = h;
563 dock_configure();
566 void dock_app_drag(ObDockApp *app, XMotionEvent *e)
568 ObDockApp *over = NULL;
569 GList *it;
570 gint x, y;
571 gboolean after;
572 gboolean stop;
574 x = e->x_root;
575 y = e->y_root;
577 /* are we on top of the dock? */
578 if (!(x >= dock->area.x &&
579 y >= dock->area.y &&
580 x < dock->area.x + dock->area.width &&
581 y < dock->area.y + dock->area.height))
582 return;
584 x -= dock->area.x;
585 y -= dock->area.y;
587 /* which dock app are we on top of? */
588 stop = FALSE;
589 for (it = dock->dock_apps; it; it = g_list_next(it)) {
590 over = it->data;
591 switch (config_dock_orient) {
592 case OB_ORIENTATION_HORZ:
593 if (x >= over->x && x < over->x + over->w)
594 stop = TRUE;
595 break;
596 case OB_ORIENTATION_VERT:
597 if (y >= over->y && y < over->y + over->h)
598 stop = TRUE;
599 break;
601 /* dont go to it->next! */
602 if (stop) break;
604 if (!it || app == over) return;
606 x -= over->x;
607 y -= over->y;
609 switch (config_dock_orient) {
610 case OB_ORIENTATION_HORZ:
611 after = (x > over->w / 2);
612 break;
613 case OB_ORIENTATION_VERT:
614 after = (y > over->h / 2);
615 break;
616 default:
617 g_assert_not_reached();
620 /* remove before doing the it->next! */
621 dock->dock_apps = g_list_remove(dock->dock_apps, app);
623 if (after) it = it->next;
625 dock->dock_apps = g_list_insert_before(dock->dock_apps, it, app);
626 dock_configure();
629 static gboolean hide_timeout(gpointer data)
631 /* hide */
632 dock->hidden = TRUE;
633 dock_configure();
635 return FALSE; /* don't repeat */
638 static gboolean show_timeout(gpointer data)
640 /* show */
641 dock->hidden = FALSE;
642 dock_configure();
644 return FALSE; /* don't repeat */
647 static void destroy_timeout(gpointer data)
649 gint *id = data;
650 *id = 0;
653 void dock_hide(gboolean hide)
655 if (!hide) {
656 if (dock->hidden && config_dock_hide) {
657 show_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
658 config_dock_show_delay,
659 show_timeout, &show_timeout_id, destroy_timeout);
660 } else if (!dock->hidden && config_dock_hide && hide_timeout_id) {
661 if (hide_timeout_id) g_source_remove(hide_timeout_id);
663 } else {
664 if (!dock->hidden && config_dock_hide) {
665 hide_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
666 config_dock_hide_delay,
667 hide_timeout, &hide_timeout_id, destroy_timeout);
668 } else if (dock->hidden && config_dock_hide && show_timeout_id) {
669 if (show_timeout_id) g_source_remove(show_timeout_id);
674 void dock_get_area(Rect *a)
676 RECT_SET(*a, dock->area.x, dock->area.y,
677 dock->area.width, dock->area.height);
680 void dock_raise_dock(void)
682 stacking_raise(DOCK_AS_WINDOW(dock));
685 void dock_lower_dock(void)
687 stacking_lower(DOCK_AS_WINDOW(dock));
690 ObDockApp* dock_find_dockapp(Window xwin)
692 return g_hash_table_lookup(dock->dock_map, &xwin);