80 cols
[openbox.git] / openbox / stacking.c
blob58a85ecade840762317ba5cadf64c3f8ec8f32d3
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 stacking.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 "openbox.h"
21 #include "screen.h"
22 #include "focus.h"
23 #include "client.h"
24 #include "group.h"
25 #include "frame.h"
26 #include "window.h"
27 #include "event.h"
28 #include "debug.h"
29 #include "obt/prop.h"
31 GList *stacking_list = NULL;
32 GList *stacking_list_tail = NULL;
33 /*! When true, stacking changes will not be reflected on the screen. This is
34 to freeze the on-screen stacking order while a window is being temporarily
35 raised during focus cycling */
36 static gboolean pause_changes = FALSE;
38 void stacking_set_list(void)
40 Window *windows = NULL;
41 GList *it;
42 guint i = 0;
44 /* on shutdown, don't update the properties, so that we can read it back
45 in on startup and re-stack the windows as they were before we shut down
47 if (ob_state() == OB_STATE_EXITING) return;
49 /* create an array of the window ids (from bottom to top,
50 reverse order!) */
51 if (stacking_list) {
52 windows = g_new(Window, g_list_length(stacking_list));
53 for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
54 if (WINDOW_IS_CLIENT(it->data))
55 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
59 OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST_STACKING, WINDOW,
60 (gulong*)windows, i);
62 g_free(windows);
65 static void do_restack(GList *wins, GList *before)
67 GList *it;
68 Window *win;
69 gint i;
71 #ifdef DEBUG
72 GList *next;
74 g_assert(wins);
75 /* pls only restack stuff in the same layer at a time */
76 for (it = wins; it; it = next) {
77 next = g_list_next(it);
78 if (!next) break;
79 g_assert (window_layer(it->data) == window_layer(next->data));
81 if (before)
82 g_assert(window_layer(it->data) >= window_layer(before->data));
83 #endif
85 win = g_new(Window, g_list_length(wins) + 1);
87 if (before == stacking_list)
88 win[0] = screen_support_win;
89 else if (!before)
90 win[0] = window_top(g_list_last(stacking_list)->data);
91 else
92 win[0] = window_top(g_list_previous(before)->data);
94 for (i = 1, it = wins; it; ++i, it = g_list_next(it)) {
95 win[i] = window_top(it->data);
96 g_assert(win[i] != None); /* better not call stacking shit before
97 setting your top level window value */
98 stacking_list = g_list_insert_before(stacking_list, before, it->data);
101 #ifdef DEBUG
102 /* some debug checking of the stacking list's order */
103 for (it = stacking_list; ; it = next) {
104 next = g_list_next(it);
105 if (!next) break;
106 g_assert(window_layer(it->data) >= window_layer(next->data));
108 #endif
110 if (!pause_changes)
111 XRestackWindows(obt_display, win, i);
112 g_free(win);
114 stacking_set_list();
117 void stacking_temp_raise(ObWindow *window)
119 Window win[2];
120 GList *it;
121 gulong start;
123 /* don't use this for internal windows..! it would lower them.. */
124 g_assert(window_layer(window) < OB_STACKING_LAYER_INTERNAL);
126 /* find the window to drop it underneath */
127 win[0] = screen_support_win;
128 for (it = stacking_list; it; it = g_list_next(it)) {
129 ObWindow *w = it->data;
130 if (window_layer(w) >= OB_STACKING_LAYER_INTERNAL)
131 win[0] = window_top(w);
132 else
133 break;
136 win[1] = window_top(window);
137 start = event_start_ignore_all_enters();
138 XRestackWindows(obt_display, win, 2);
139 event_end_ignore_all_enters(start);
141 pause_changes = TRUE;
144 void stacking_restore(void)
146 Window *win;
147 GList *it;
148 gint i;
149 gulong start;
151 win = g_new(Window, g_list_length(stacking_list) + 1);
152 win[0] = screen_support_win;
153 for (i = 1, it = stacking_list; it; ++i, it = g_list_next(it))
154 win[i] = window_top(it->data);
155 start = event_start_ignore_all_enters();
156 XRestackWindows(obt_display, win, i);
157 event_end_ignore_all_enters(start);
158 g_free(win);
160 pause_changes = FALSE;
163 static void do_raise(GList *wins)
165 GList *it;
166 GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
167 gint i;
169 for (it = wins; it; it = g_list_next(it)) {
170 ObStackingLayer l;
172 l = window_layer(it->data);
173 layer[l] = g_list_append(layer[l], it->data);
176 it = stacking_list;
177 for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
178 if (layer[i]) {
179 for (; it; it = g_list_next(it)) {
180 /* look for the top of the layer */
181 if (window_layer(it->data) <= (ObStackingLayer) i)
182 break;
184 do_restack(layer[i], it);
185 g_list_free(layer[i]);
190 static void do_lower(GList *wins)
192 GList *it;
193 GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
194 gint i;
196 for (it = wins; it; it = g_list_next(it)) {
197 ObStackingLayer l;
199 l = window_layer(it->data);
200 layer[l] = g_list_append(layer[l], it->data);
203 it = stacking_list;
204 for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
205 if (layer[i]) {
206 for (; it; it = g_list_next(it)) {
207 /* look for the top of the next layer down */
208 if (window_layer(it->data) < (ObStackingLayer) i)
209 break;
211 do_restack(layer[i], it);
212 g_list_free(layer[i]);
217 static void restack_windows(ObClient *selected, gboolean raise)
219 GList *it, *last, *below, *above, *next;
220 GList *wins = NULL;
222 GList *group_helpers = NULL;
223 GList *group_modals = NULL;
224 GList *group_trans = NULL;
225 GList *modals = NULL;
226 GList *trans = NULL;
228 if (raise) {
229 ObClient *p;
231 /* if a window is modal for another single window, then raise it to the
232 top too, the same is done with the focus order */
233 while (selected->modal && (p = client_direct_parent(selected)))
234 selected = p;
237 /* remove first so we can't run into ourself */
238 it = g_list_find(stacking_list, selected);
239 g_assert(it);
240 stacking_list = g_list_delete_link(stacking_list, it);
242 /* go from the bottom of the stacking list up. don't move any other windows
243 when lowering, we call this for each window independently */
244 if (raise) {
245 for (it = g_list_last(stacking_list); it; it = next) {
246 next = g_list_previous(it);
248 if (WINDOW_IS_CLIENT(it->data)) {
249 ObClient *ch = it->data;
251 /* only move windows in the same stacking layer */
252 if (ch->layer == selected->layer &&
253 /* looking for windows that are transients, and so would
254 remain above the selected window */
255 client_search_transient(selected, ch))
257 if (client_is_direct_child(selected, ch)) {
258 if (ch->modal)
259 modals = g_list_prepend(modals, ch);
260 else
261 trans = g_list_prepend(trans, ch);
263 else if (client_helper(ch)) {
264 if (selected->transient) {
265 /* helpers do not stay above transient windows */
266 continue;
268 group_helpers = g_list_prepend(group_helpers, ch);
270 else {
271 if (ch->modal)
272 group_modals = g_list_prepend(group_modals, ch);
273 else
274 group_trans = g_list_prepend(group_trans, ch);
276 stacking_list = g_list_delete_link(stacking_list, it);
282 /* put modals above other direct transients */
283 wins = g_list_concat(modals, trans);
285 /* put helpers below direct transients */
286 wins = g_list_concat(wins, group_helpers);
288 /* put the selected window right below these children */
289 wins = g_list_append(wins, selected);
291 /* if selected window is transient for group then raise it above others */
292 if (selected->transient_for_group) {
293 /* if it's modal, raise it above those also */
294 if (selected->modal) {
295 wins = g_list_concat(wins, group_modals);
296 group_modals = NULL;
298 wins = g_list_concat(wins, group_trans);
299 group_trans = NULL;
302 /* find where to put the selected window, start from bottom of list,
303 this is the window below everything we are re-adding to the list */
304 last = NULL;
305 for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
307 if (window_layer(it->data) < selected->layer) {
308 last = it;
309 continue;
311 /* if lowering, stop at the beginning of the layer */
312 if (!raise)
313 break;
314 /* if raising, stop at the end of the layer */
315 if (window_layer(it->data) > selected->layer)
316 break;
318 last = it;
321 /* save this position in the stacking list */
322 below = last;
324 /* find where to put the group transients, start from the top of list */
325 for (it = stacking_list; it; it = g_list_next(it)) {
326 /* skip past higher layers */
327 if (window_layer(it->data) > selected->layer)
328 continue;
329 /* if we reach the end of the layer (how?) then don't go further */
330 if (window_layer(it->data) < selected->layer)
331 break;
332 /* stop when we reach the first window in the group */
333 if (WINDOW_IS_CLIENT(it->data)) {
334 ObClient *c = it->data;
335 if (c->group == selected->group)
336 break;
338 /* if we don't hit any other group members, stop here because this
339 is where we are putting the selected window (and its children) */
340 if (it == below)
341 break;
344 /* save this position, this is the top of the group of windows between the
345 group transient ones we're restacking and the others up above that we're
346 restacking
348 we actually want to save 1 position _above_ that, for for loops to work
349 nicely, so move back one position in the list while saving it
351 above = it ? g_list_previous(it) : g_list_last(stacking_list);
353 /* put the windows inside the gap to the other windows we're stacking
354 into the restacking list, go from the bottom up so that we can use
355 g_list_prepend */
356 if (below) it = g_list_previous(below);
357 else it = g_list_last(stacking_list);
358 for (; it != above; it = next) {
359 next = g_list_previous(it);
360 wins = g_list_prepend(wins, it->data);
361 stacking_list = g_list_delete_link(stacking_list, it);
364 /* group transients go above the rest of the stuff acquired to now */
365 wins = g_list_concat(group_trans, wins);
366 /* group modals go on the very top */
367 wins = g_list_concat(group_modals, wins);
369 do_restack(wins, below);
370 g_list_free(wins);
372 /* lower our parents after us, so they go below us */
373 if (!raise && selected->parents) {
374 GSList *parents_copy, *sit;
375 GSList *reorder = NULL;
377 parents_copy = g_slist_copy(selected->parents);
379 /* go thru stacking list backwards so we can use g_slist_prepend */
380 for (it = g_list_last(stacking_list); it && parents_copy;
381 it = g_list_previous(it))
382 if ((sit = g_slist_find(parents_copy, it->data))) {
383 reorder = g_slist_prepend(reorder, sit->data);
384 parents_copy = g_slist_delete_link(parents_copy, sit);
386 g_assert(parents_copy == NULL);
388 /* call restack for each of these to lower them */
389 for (sit = reorder; sit; sit = g_slist_next(sit))
390 restack_windows(sit->data, raise);
394 void stacking_raise(ObWindow *window)
396 if (WINDOW_IS_CLIENT(window)) {
397 ObClient *selected;
398 selected = WINDOW_AS_CLIENT(window);
399 restack_windows(selected, TRUE);
400 } else {
401 GList *wins;
402 wins = g_list_append(NULL, window);
403 stacking_list = g_list_remove(stacking_list, window);
404 do_raise(wins);
405 g_list_free(wins);
407 stacking_list_tail = g_list_last(stacking_list);
410 void stacking_lower(ObWindow *window)
412 if (WINDOW_IS_CLIENT(window)) {
413 ObClient *selected;
414 selected = WINDOW_AS_CLIENT(window);
415 restack_windows(selected, FALSE);
416 } else {
417 GList *wins;
418 wins = g_list_append(NULL, window);
419 stacking_list = g_list_remove(stacking_list, window);
420 do_lower(wins);
421 g_list_free(wins);
423 stacking_list_tail = g_list_last(stacking_list);
426 void stacking_below(ObWindow *window, ObWindow *below)
428 GList *wins, *before;
430 if (window_layer(window) != window_layer(below))
431 return;
433 wins = g_list_append(NULL, window);
434 stacking_list = g_list_remove(stacking_list, window);
435 before = g_list_next(g_list_find(stacking_list, below));
436 do_restack(wins, before);
437 g_list_free(wins);
438 stacking_list_tail = g_list_last(stacking_list);
441 void stacking_add(ObWindow *win)
443 g_assert(screen_support_win != None); /* make sure I dont break this in the
444 future */
445 /* don't add windows that are being unmanaged ! */
446 if (WINDOW_IS_CLIENT(win)) g_assert(WINDOW_AS_CLIENT(win)->managed);
448 stacking_list = g_list_append(stacking_list, win);
450 stacking_raise(win);
451 /* stacking_list_tail set by stacking_raise() */
454 static GList *find_highest_relative(ObClient *client)
456 GList *ret = NULL;
458 if (client->parents) {
459 GList *it;
460 GSList *top;
462 /* get all top level relatives of this client */
463 top = client_search_all_top_parents_layer(client);
465 /* go from the top of the stacking order down */
466 for (it = stacking_list; !ret && it; it = g_list_next(it)) {
467 if (WINDOW_IS_CLIENT(it->data)) {
468 ObClient *c = it->data;
469 /* only look at windows in the same layer and that are
470 visible */
471 if (c->layer == client->layer &&
472 !c->iconic &&
473 (c->desktop == client->desktop ||
474 c->desktop == DESKTOP_ALL ||
475 client->desktop == DESKTOP_ALL))
477 GSList *sit;
479 /* go through each top level parent and see it this window
480 is related to them */
481 for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
482 ObClient *topc = sit->data;
484 /* are they related ? */
485 if (topc == c || client_search_transient(topc, c))
486 ret = it;
492 return ret;
495 void stacking_add_nonintrusive(ObWindow *win)
497 ObClient *client;
498 GList *it_below = NULL; /* this client will be below us */
499 GList *it_above;
500 GList *wins;
502 if (!WINDOW_IS_CLIENT(win)) {
503 stacking_add(win); /* no special rules for others */
504 return;
507 client = WINDOW_AS_CLIENT(win);
509 /* don't add windows that are being unmanaged ! */
510 g_assert(client->managed);
512 /* insert above its highest parent (or its highest child !) */
513 it_below = find_highest_relative(client);
515 if (!it_below) {
516 /* nothing to put it directly above, so try find the focused client
517 to put it underneath it */
518 if (focus_client && client != focus_client &&
519 focus_client->layer == client->layer)
521 it_below = g_list_find(stacking_list, focus_client);
522 /* this can give NULL, but it means the focused window is on the
523 bottom of the stacking order, so go to the bottom in that case,
524 below it */
525 it_below = g_list_next(it_below);
527 else {
528 /* There is no window to put this directly above, so put it at the
529 top, so you know it is there.
531 It used to do this only if the window was focused and lower
532 it otherwise.
534 We also put it at the top not the bottom to fix a bug with
535 fullscreen windows. When focusLast is off and followsMouse is
536 on, when you switch desktops, the fullscreen window loses
537 focus and goes into its lower layer. If this puts it at the
538 bottom then when you come back to the desktop, the window is
539 at the bottom and won't get focus back.
541 it_below = stacking_list;
545 /* make sure it's not in the wrong layer though ! */
546 for (; it_below; it_below = g_list_next(it_below)) {
547 /* stop when the window is not in a higher layer than the window
548 it is going above (it_below) */
549 if (client->layer >= window_layer(it_below->data))
550 break;
552 for (; it_below != stacking_list; it_below = it_above) {
553 /* stop when the window is not in a lower layer than the
554 window it is going under (it_above) */
555 it_above = it_below ?
556 g_list_previous(it_below) : g_list_last(stacking_list);
557 if (client->layer <= window_layer(it_above->data))
558 break;
561 wins = g_list_append(NULL, win);
562 do_restack(wins, it_below);
563 g_list_free(wins);
564 stacking_list_tail = g_list_last(stacking_list);
567 /*! Returns TRUE if client is occluded by the sibling. If sibling is NULL it
568 tries against all other clients.
570 static gboolean stacking_occluded(ObClient *client, ObClient *sibling)
572 GList *it;
573 gboolean occluded = FALSE;
574 gboolean found = FALSE;
576 /* no need for any looping in this case */
577 if (sibling && client->layer != sibling->layer)
578 return occluded;
580 for (it = stacking_list; it;
581 it = (found ? g_list_previous(it) :g_list_next(it)))
582 if (WINDOW_IS_CLIENT(it->data)) {
583 ObClient *c = it->data;
584 if (found && !c->iconic &&
585 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
586 c->desktop == client->desktop) &&
587 !client_search_transient(client, c))
589 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
591 if (sibling != NULL) {
592 if (c == sibling) {
593 occluded = TRUE;
594 break;
597 else if (c->layer == client->layer) {
598 occluded = TRUE;
599 break;
601 else if (c->layer > client->layer)
602 break; /* we past its layer */
605 else if (c == client)
606 found = TRUE;
608 return occluded;
611 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
612 against all other clients.
614 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
616 GList *it;
617 gboolean occludes = FALSE;
618 gboolean found = FALSE;
620 /* no need for any looping in this case */
621 if (sibling && client->layer != sibling->layer)
622 return occludes;
624 for (it = stacking_list; it; it = g_list_next(it))
625 if (WINDOW_IS_CLIENT(it->data)) {
626 ObClient *c = it->data;
627 if (found && !c->iconic &&
628 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
629 c->desktop == client->desktop) &&
630 !client_search_transient(c, client))
632 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
634 if (sibling != NULL) {
635 if (c == sibling) {
636 occludes = TRUE;
637 break;
640 else if (c->layer == client->layer) {
641 occludes = TRUE;
642 break;
644 else if (c->layer < client->layer)
645 break; /* we past its layer */
648 else if (c == client)
649 found = TRUE;
651 return occludes;
654 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
655 gint detail)
657 gboolean ret = FALSE;
659 if (sibling && ((client->desktop != sibling->desktop &&
660 client->desktop != DESKTOP_ALL &&
661 sibling->desktop != DESKTOP_ALL) ||
662 sibling->iconic))
664 ob_debug("Setting restack sibling to NULL, they are not on the same "
665 "desktop or it is iconified");
666 sibling = NULL;
669 switch (detail) {
670 case Below:
671 ob_debug("Restack request Below for client %s sibling %s",
672 client->title, sibling ? sibling->title : "(all)");
673 /* just lower it */
674 stacking_lower(CLIENT_AS_WINDOW(client));
675 ret = TRUE;
676 break;
677 case BottomIf:
678 ob_debug("Restack request BottomIf for client %s sibling %s",
679 client->title, sibling ? sibling->title : "(all)");
680 /* if this client occludes sibling (or anything if NULL), then
681 lower it to the bottom */
682 if (stacking_occludes(client, sibling)) {
683 stacking_lower(CLIENT_AS_WINDOW(client));
684 ret = TRUE;
686 break;
687 case Above:
688 ob_debug("Restack request Above for client %s sibling %s",
689 client->title, sibling ? sibling->title : "(all)");
690 stacking_raise(CLIENT_AS_WINDOW(client));
691 ret = TRUE;
692 break;
693 case TopIf:
694 ob_debug("Restack request TopIf for client %s sibling %s",
695 client->title, sibling ? sibling->title : "(all)");
696 if (stacking_occluded(client, sibling)) {
697 stacking_raise(CLIENT_AS_WINDOW(client));
698 ret = TRUE;
700 break;
701 case Opposite:
702 ob_debug("Restack request Opposite for client %s sibling %s",
703 client->title, sibling ? sibling->title : "(all)");
704 if (stacking_occluded(client, sibling)) {
705 stacking_raise(CLIENT_AS_WINDOW(client));
706 ret = TRUE;
708 else if (stacking_occludes(client, sibling)) {
709 stacking_lower(CLIENT_AS_WINDOW(client));
710 ret = TRUE;
712 break;
714 return ret;