]> Dogcows Code - chaz/openbox/blob - openbox/stacking.c
add asserts to make sure we don't add things to stacking list that are not managed
[chaz/openbox] / openbox / stacking.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 stacking.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 "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"
30
31 GList *stacking_list = NULL;
32 /*! When true, stacking changes will not be reflected on the screen. This is
33 to freeze the on-screen stacking order while a window is being temporarily
34 raised during focus cycling */
35 static gboolean pause_changes = FALSE;
36
37 void stacking_set_list(void)
38 {
39 Window *windows = NULL;
40 GList *it;
41 guint i = 0;
42
43 /* on shutdown, don't update the properties, so that we can read it back
44 in on startup and re-stack the windows as they were before we shut down
45 */
46 if (ob_state() == OB_STATE_EXITING) return;
47
48 /* create an array of the window ids (from bottom to top,
49 reverse order!) */
50 if (stacking_list) {
51 windows = g_new(Window, g_list_length(stacking_list));
52 for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
53 if (WINDOW_IS_CLIENT(it->data))
54 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
55 }
56 }
57
58 OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST_STACKING, WINDOW,
59 (gulong*)windows, i);
60
61 g_free(windows);
62 }
63
64 static void do_restack(GList *wins, GList *before)
65 {
66 GList *it;
67 Window *win;
68 gint i;
69
70 #ifdef DEBUG
71 GList *next;
72
73 g_assert(wins);
74 /* pls only restack stuff in the same layer at a time */
75 for (it = wins; it; it = next) {
76 next = g_list_next(it);
77 if (!next) break;
78 g_assert (window_layer(it->data) == window_layer(next->data));
79 }
80 if (before)
81 g_assert(window_layer(it->data) >= window_layer(before->data));
82 #endif
83
84 win = g_new(Window, g_list_length(wins) + 1);
85
86 if (before == stacking_list)
87 win[0] = screen_support_win;
88 else if (!before)
89 win[0] = window_top(g_list_last(stacking_list)->data);
90 else
91 win[0] = window_top(g_list_previous(before)->data);
92
93 for (i = 1, it = wins; it; ++i, it = g_list_next(it)) {
94 win[i] = window_top(it->data);
95 g_assert(win[i] != None); /* better not call stacking shit before
96 setting your top level window value */
97 stacking_list = g_list_insert_before(stacking_list, before, it->data);
98 }
99
100 #ifdef DEBUG
101 /* some debug checking of the stacking list's order */
102 for (it = stacking_list; ; it = next) {
103 next = g_list_next(it);
104 if (!next) break;
105 g_assert(window_layer(it->data) >= window_layer(next->data));
106 }
107 #endif
108
109 if (!pause_changes)
110 XRestackWindows(obt_display, win, i);
111 g_free(win);
112
113 stacking_set_list();
114 }
115
116 void stacking_temp_raise(ObWindow *window)
117 {
118 Window win[2];
119 GList *it;
120 gulong start;
121
122 /* don't use this for internal windows..! it would lower them.. */
123 g_assert(window_layer(window) < OB_STACKING_LAYER_INTERNAL);
124
125 /* find the window to drop it underneath */
126 win[0] = screen_support_win;
127 for (it = stacking_list; it; it = g_list_next(it)) {
128 ObWindow *w = it->data;
129 if (window_layer(w) >= OB_STACKING_LAYER_INTERNAL)
130 win[0] = window_top(w);
131 else
132 break;
133 }
134
135 win[1] = window_top(window);
136 start = event_start_ignore_all_enters();
137 XRestackWindows(obt_display, win, 2);
138 event_end_ignore_all_enters(start);
139
140 pause_changes = TRUE;
141 }
142
143 void stacking_restore(void)
144 {
145 Window *win;
146 GList *it;
147 gint i;
148 gulong start;
149
150 win = g_new(Window, g_list_length(stacking_list) + 1);
151 win[0] = screen_support_win;
152 for (i = 1, it = stacking_list; it; ++i, it = g_list_next(it))
153 win[i] = window_top(it->data);
154 start = event_start_ignore_all_enters();
155 XRestackWindows(obt_display, win, i);
156 event_end_ignore_all_enters(start);
157 g_free(win);
158
159 pause_changes = FALSE;
160 }
161
162 static void do_raise(GList *wins)
163 {
164 GList *it;
165 GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
166 gint i;
167
168 for (it = wins; it; it = g_list_next(it)) {
169 ObStackingLayer l;
170
171 l = window_layer(it->data);
172 layer[l] = g_list_append(layer[l], it->data);
173 }
174
175 it = stacking_list;
176 for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
177 if (layer[i]) {
178 for (; it; it = g_list_next(it)) {
179 /* look for the top of the layer */
180 if (window_layer(it->data) <= (ObStackingLayer) i)
181 break;
182 }
183 do_restack(layer[i], it);
184 g_list_free(layer[i]);
185 }
186 }
187 }
188
189 static void do_lower(GList *wins)
190 {
191 GList *it;
192 GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
193 gint i;
194
195 for (it = wins; it; it = g_list_next(it)) {
196 ObStackingLayer l;
197
198 l = window_layer(it->data);
199 layer[l] = g_list_append(layer[l], it->data);
200 }
201
202 it = stacking_list;
203 for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
204 if (layer[i]) {
205 for (; it; it = g_list_next(it)) {
206 /* look for the top of the next layer down */
207 if (window_layer(it->data) < (ObStackingLayer) i)
208 break;
209 }
210 do_restack(layer[i], it);
211 g_list_free(layer[i]);
212 }
213 }
214 }
215
216 static void restack_windows(ObClient *selected, gboolean raise)
217 {
218 GList *it, *last, *below, *above, *next;
219 GList *wins = NULL;
220
221 GList *group_helpers = NULL;
222 GList *group_modals = NULL;
223 GList *group_trans = NULL;
224 GList *modals = NULL;
225 GList *trans = NULL;
226
227 if (raise) {
228 ObClient *p;
229
230 /* if a window is modal for another single window, then raise it to the
231 top too, the same is done with the focus order */
232 while (selected->modal && (p = client_direct_parent(selected)))
233 selected = p;
234 }
235
236 /* remove first so we can't run into ourself */
237 it = g_list_find(stacking_list, selected);
238 g_assert(it);
239 stacking_list = g_list_delete_link(stacking_list, it);
240
241 /* go from the bottom of the stacking list up. don't move any other windows
242 when lowering, we call this for each window independently */
243 if (raise) {
244 for (it = g_list_last(stacking_list); it; it = next) {
245 next = g_list_previous(it);
246
247 if (WINDOW_IS_CLIENT(it->data)) {
248 ObClient *ch = it->data;
249
250 /* only move windows in the same stacking layer */
251 if (ch->layer == selected->layer &&
252 /* looking for windows that are transients, and so would
253 remain above the selected window */
254 client_search_transient(selected, ch))
255 {
256 if (client_is_direct_child(selected, ch)) {
257 if (ch->modal)
258 modals = g_list_prepend(modals, ch);
259 else
260 trans = g_list_prepend(trans, ch);
261 }
262 else if (client_helper(ch)) {
263 if (selected->transient) {
264 /* helpers do not stay above transient windows */
265 continue;
266 }
267 group_helpers = g_list_prepend(group_helpers, ch);
268 }
269 else {
270 if (ch->modal)
271 group_modals = g_list_prepend(group_modals, ch);
272 else
273 group_trans = g_list_prepend(group_trans, ch);
274 }
275 stacking_list = g_list_delete_link(stacking_list, it);
276 }
277 }
278 }
279 }
280
281 /* put modals above other direct transients */
282 wins = g_list_concat(modals, trans);
283
284 /* put helpers below direct transients */
285 wins = g_list_concat(wins, group_helpers);
286
287 /* put the selected window right below these children */
288 wins = g_list_append(wins, selected);
289
290 /* if selected window is transient for group then raise it above others */
291 if (selected->transient_for_group) {
292 /* if it's modal, raise it above those also */
293 if (selected->modal) {
294 wins = g_list_concat(wins, group_modals);
295 group_modals = NULL;
296 }
297 wins = g_list_concat(wins, group_trans);
298 group_trans = NULL;
299 }
300
301 /* find where to put the selected window, start from bottom of list,
302 this is the window below everything we are re-adding to the list */
303 last = NULL;
304 for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
305 {
306 if (window_layer(it->data) < selected->layer) {
307 last = it;
308 continue;
309 }
310 /* if lowering, stop at the beginning of the layer */
311 if (!raise)
312 break;
313 /* if raising, stop at the end of the layer */
314 if (window_layer(it->data) > selected->layer)
315 break;
316
317 last = it;
318 }
319
320 /* save this position in the stacking list */
321 below = last;
322
323 /* find where to put the group transients, start from the top of list */
324 for (it = stacking_list; it; it = g_list_next(it)) {
325 /* skip past higher layers */
326 if (window_layer(it->data) > selected->layer)
327 continue;
328 /* if we reach the end of the layer (how?) then don't go further */
329 if (window_layer(it->data) < selected->layer)
330 break;
331 /* stop when we reach the first window in the group */
332 if (WINDOW_IS_CLIENT(it->data)) {
333 ObClient *c = it->data;
334 if (c->group == selected->group)
335 break;
336 }
337 /* if we don't hit any other group members, stop here because this
338 is where we are putting the selected window (and its children) */
339 if (it == below)
340 break;
341 }
342
343 /* save this position, this is the top of the group of windows between the
344 group transient ones we're restacking and the others up above that we're
345 restacking
346
347 we actually want to save 1 position _above_ that, for for loops to work
348 nicely, so move back one position in the list while saving it
349 */
350 above = it ? g_list_previous(it) : g_list_last(stacking_list);
351
352 /* put the windows inside the gap to the other windows we're stacking
353 into the restacking list, go from the bottom up so that we can use
354 g_list_prepend */
355 if (below) it = g_list_previous(below);
356 else it = g_list_last(stacking_list);
357 for (; it != above; it = next) {
358 next = g_list_previous(it);
359 wins = g_list_prepend(wins, it->data);
360 stacking_list = g_list_delete_link(stacking_list, it);
361 }
362
363 /* group transients go above the rest of the stuff acquired to now */
364 wins = g_list_concat(group_trans, wins);
365 /* group modals go on the very top */
366 wins = g_list_concat(group_modals, wins);
367
368 do_restack(wins, below);
369 g_list_free(wins);
370
371 /* lower our parents after us, so they go below us */
372 if (!raise && selected->parents) {
373 GSList *parents_copy, *sit;
374 GSList *reorder = NULL;
375
376 parents_copy = g_slist_copy(selected->parents);
377
378 /* go thru stacking list backwards so we can use g_slist_prepend */
379 for (it = g_list_last(stacking_list); it && parents_copy;
380 it = g_list_previous(it))
381 if ((sit = g_slist_find(parents_copy, it->data))) {
382 reorder = g_slist_prepend(reorder, sit->data);
383 parents_copy = g_slist_delete_link(parents_copy, sit);
384 }
385 g_assert(parents_copy == NULL);
386
387 /* call restack for each of these to lower them */
388 for (sit = reorder; sit; sit = g_slist_next(sit))
389 restack_windows(sit->data, raise);
390 }
391 }
392
393 void stacking_raise(ObWindow *window)
394 {
395 if (WINDOW_IS_CLIENT(window)) {
396 ObClient *selected;
397 selected = WINDOW_AS_CLIENT(window);
398 restack_windows(selected, TRUE);
399 } else {
400 GList *wins;
401 wins = g_list_append(NULL, window);
402 stacking_list = g_list_remove(stacking_list, window);
403 do_raise(wins);
404 g_list_free(wins);
405 }
406 }
407
408 void stacking_lower(ObWindow *window)
409 {
410 if (WINDOW_IS_CLIENT(window)) {
411 ObClient *selected;
412 selected = WINDOW_AS_CLIENT(window);
413 restack_windows(selected, FALSE);
414 } else {
415 GList *wins;
416 wins = g_list_append(NULL, window);
417 stacking_list = g_list_remove(stacking_list, window);
418 do_lower(wins);
419 g_list_free(wins);
420 }
421 }
422
423 void stacking_below(ObWindow *window, ObWindow *below)
424 {
425 GList *wins, *before;
426
427 if (window_layer(window) != window_layer(below))
428 return;
429
430 wins = g_list_append(NULL, window);
431 stacking_list = g_list_remove(stacking_list, window);
432 before = g_list_next(g_list_find(stacking_list, below));
433 do_restack(wins, before);
434 g_list_free(wins);
435 }
436
437 void stacking_add(ObWindow *win)
438 {
439 g_assert(screen_support_win != None); /* make sure I dont break this in the
440 future */
441 /* don't add windows that are being unmanaged ! */
442 if (WINDOW_IS_CLIENT(win)) g_assert(WINDOW_AS_CLIENT(win)->managed);
443
444 stacking_list = g_list_append(stacking_list, win);
445
446 stacking_raise(win);
447 }
448
449 static GList *find_highest_relative(ObClient *client)
450 {
451 GList *ret = NULL;
452
453 if (client->parents) {
454 GList *it;
455 GSList *top;
456
457 /* get all top level relatives of this client */
458 top = client_search_all_top_parents_layer(client);
459
460 /* go from the top of the stacking order down */
461 for (it = stacking_list; !ret && it; it = g_list_next(it)) {
462 if (WINDOW_IS_CLIENT(it->data)) {
463 ObClient *c = it->data;
464 /* only look at windows in the same layer and that are
465 visible */
466 if (c->layer == client->layer &&
467 !c->iconic &&
468 (c->desktop == client->desktop ||
469 c->desktop == DESKTOP_ALL ||
470 client->desktop == DESKTOP_ALL))
471 {
472 GSList *sit;
473
474 /* go through each top level parent and see it this window
475 is related to them */
476 for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
477 ObClient *topc = sit->data;
478
479 /* are they related ? */
480 if (topc == c || client_search_transient(topc, c))
481 ret = it;
482 }
483 }
484 }
485 }
486 }
487 return ret;
488 }
489
490 void stacking_add_nonintrusive(ObWindow *win)
491 {
492 ObClient *client;
493 GList *it_below = NULL; /* this client will be below us */
494 GList *it_above;
495 GList *wins;
496
497 if (!WINDOW_IS_CLIENT(win)) {
498 stacking_add(win); /* no special rules for others */
499 return;
500 }
501
502 client = WINDOW_AS_CLIENT(win);
503
504 /* don't add windows that are being unmanaged ! */
505 g_assert(client->managed);
506
507 /* insert above its highest parent (or its highest child !) */
508 it_below = find_highest_relative(client);
509
510 if (!it_below) {
511 /* nothing to put it directly above, so try find the focused client
512 to put it underneath it */
513 if (focus_client && client != focus_client &&
514 focus_client->layer == client->layer)
515 {
516 it_below = g_list_find(stacking_list, focus_client);
517 /* this can give NULL, but it means the focused window is on the
518 bottom of the stacking order, so go to the bottom in that case,
519 below it */
520 it_below = g_list_next(it_below);
521 }
522 else {
523 /* There is no window to put this directly above, so put it at the
524 top, so you know it is there.
525
526 It used to do this only if the window was focused and lower
527 it otherwise.
528
529 We also put it at the top not the bottom to fix a bug with
530 fullscreen windows. When focusLast is off and followsMouse is
531 on, when you switch desktops, the fullscreen window loses
532 focus and goes into its lower layer. If this puts it at the
533 bottom then when you come back to the desktop, the window is
534 at the bottom and won't get focus back.
535 */
536 it_below = stacking_list;
537 }
538 }
539
540 /* make sure it's not in the wrong layer though ! */
541 for (; it_below; it_below = g_list_next(it_below)) {
542 /* stop when the window is not in a higher layer than the window
543 it is going above (it_below) */
544 if (client->layer >= window_layer(it_below->data))
545 break;
546 }
547 for (; it_below != stacking_list; it_below = it_above) {
548 /* stop when the window is not in a lower layer than the
549 window it is going under (it_above) */
550 it_above = it_below ?
551 g_list_previous(it_below) : g_list_last(stacking_list);
552 if (client->layer <= window_layer(it_above->data))
553 break;
554 }
555
556 wins = g_list_append(NULL, win);
557 do_restack(wins, it_below);
558 g_list_free(wins);
559 }
560
561 /*! Returns TRUE if client is occluded by the sibling. If sibling is NULL it
562 tries against all other clients.
563 */
564 static gboolean stacking_occluded(ObClient *client, ObClient *sibling)
565 {
566 GList *it;
567 gboolean occluded = FALSE;
568 gboolean found = FALSE;
569
570 /* no need for any looping in this case */
571 if (sibling && client->layer != sibling->layer)
572 return occluded;
573
574 for (it = stacking_list; it;
575 it = (found ? g_list_previous(it) :g_list_next(it)))
576 if (WINDOW_IS_CLIENT(it->data)) {
577 ObClient *c = it->data;
578 if (found && !c->iconic &&
579 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
580 c->desktop == client->desktop) &&
581 !client_search_transient(client, c))
582 {
583 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
584 {
585 if (sibling != NULL) {
586 if (c == sibling) {
587 occluded = TRUE;
588 break;
589 }
590 }
591 else if (c->layer == client->layer) {
592 occluded = TRUE;
593 break;
594 }
595 else if (c->layer > client->layer)
596 break; /* we past its layer */
597 }
598 }
599 else if (c == client)
600 found = TRUE;
601 }
602 return occluded;
603 }
604
605 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
606 against all other clients.
607 */
608 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
609 {
610 GList *it;
611 gboolean occludes = FALSE;
612 gboolean found = FALSE;
613
614 /* no need for any looping in this case */
615 if (sibling && client->layer != sibling->layer)
616 return occludes;
617
618 for (it = stacking_list; it; it = g_list_next(it))
619 if (WINDOW_IS_CLIENT(it->data)) {
620 ObClient *c = it->data;
621 if (found && !c->iconic &&
622 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
623 c->desktop == client->desktop) &&
624 !client_search_transient(c, client))
625 {
626 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
627 {
628 if (sibling != NULL) {
629 if (c == sibling) {
630 occludes = TRUE;
631 break;
632 }
633 }
634 else if (c->layer == client->layer) {
635 occludes = TRUE;
636 break;
637 }
638 else if (c->layer < client->layer)
639 break; /* we past its layer */
640 }
641 }
642 else if (c == client)
643 found = TRUE;
644 }
645 return occludes;
646 }
647
648 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
649 gint detail)
650 {
651 gboolean ret = FALSE;
652
653 if (sibling && ((client->desktop != sibling->desktop &&
654 client->desktop != DESKTOP_ALL &&
655 sibling->desktop != DESKTOP_ALL) ||
656 sibling->iconic))
657 {
658 ob_debug("Setting restack sibling to NULL, they are not on the same "
659 "desktop or it is iconified");
660 sibling = NULL;
661 }
662
663 switch (detail) {
664 case Below:
665 ob_debug("Restack request Below for client %s sibling %s",
666 client->title, sibling ? sibling->title : "(all)");
667 /* just lower it */
668 stacking_lower(CLIENT_AS_WINDOW(client));
669 ret = TRUE;
670 break;
671 case BottomIf:
672 ob_debug("Restack request BottomIf for client %s sibling %s",
673 client->title, sibling ? sibling->title : "(all)");
674 /* if this client occludes sibling (or anything if NULL), then
675 lower it to the bottom */
676 if (stacking_occludes(client, sibling)) {
677 stacking_lower(CLIENT_AS_WINDOW(client));
678 ret = TRUE;
679 }
680 break;
681 case Above:
682 ob_debug("Restack request Above for client %s sibling %s",
683 client->title, sibling ? sibling->title : "(all)");
684 stacking_raise(CLIENT_AS_WINDOW(client));
685 ret = TRUE;
686 break;
687 case TopIf:
688 ob_debug("Restack request TopIf for client %s sibling %s",
689 client->title, sibling ? sibling->title : "(all)");
690 if (stacking_occluded(client, sibling)) {
691 stacking_raise(CLIENT_AS_WINDOW(client));
692 ret = TRUE;
693 }
694 break;
695 case Opposite:
696 ob_debug("Restack request Opposite for client %s sibling %s",
697 client->title, sibling ? sibling->title : "(all)");
698 if (stacking_occluded(client, sibling)) {
699 stacking_raise(CLIENT_AS_WINDOW(client));
700 ret = TRUE;
701 }
702 else if (stacking_occludes(client, sibling)) {
703 stacking_lower(CLIENT_AS_WINDOW(client));
704 ret = TRUE;
705 }
706 break;
707 }
708 return ret;
709 }
This page took 0.071962 seconds and 5 git commands to generate.