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