Fix stacking for transients vs helper windows. Fixes bug #3851
[dana/openbox.git] / 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 }