Merge branch 'backport' into work
[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 "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     /* 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(obt_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(obt_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(obt_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_modals = NULL;
220     GList *group_trans = NULL;
221     GList *modals = NULL;
222     GList *trans = NULL;
223
224     if (raise) {
225         ObClient *p;
226
227         /* if a window is modal for another single window, then raise it to the
228            top too, the same is done with the focus order */
229         while (selected->modal && (p = client_direct_parent(selected)))
230             selected = p;
231     }
232
233     /* remove first so we can't run into ourself */
234     it = g_list_find(stacking_list, selected);
235     g_assert(it);
236     stacking_list = g_list_delete_link(stacking_list, it);
237
238     /* go from the bottom of the stacking list up. don't move any other windows
239        when lowering, we call this for each window independently */
240     if (raise) {
241         for (it = g_list_last(stacking_list); it; it = next) {
242             next = g_list_previous(it);
243
244             if (WINDOW_IS_CLIENT(it->data)) {
245                 ObClient *ch = it->data;
246
247                 /* only move windows in the same stacking layer */
248                 if (ch->layer == selected->layer &&
249                     client_search_transient(selected, ch))
250                 {
251                     if (client_is_direct_child(selected, ch)) {
252                         if (ch->modal)
253                             modals = g_list_prepend(modals, ch);
254                         else
255                             trans = g_list_prepend(trans, ch);
256                     }
257                     else {
258                         if (ch->modal)
259                             group_modals = g_list_prepend(group_modals, ch);
260                         else
261                             group_trans = g_list_prepend(group_trans, ch);
262                     }
263                     stacking_list = g_list_delete_link(stacking_list, it);
264                 }
265             }
266         }
267     }
268
269     /* put transients of the selected window right above it */
270     wins = g_list_concat(modals, trans);
271     wins = g_list_append(wins, selected);
272
273     /* if selected window is transient for group then raise it above others */
274     if (selected->transient_for_group) {
275         /* if it's modal, raise it above those also */
276         if (selected->modal) {
277             wins = g_list_concat(wins, group_modals);
278             group_modals = NULL;
279         }
280         wins = g_list_concat(wins, group_trans);
281         group_trans = NULL;
282     }
283
284     /* find where to put the selected window, start from bottom of list,
285        this is the window below everything we are re-adding to the list */
286     last = NULL;
287     for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
288     {
289         if (window_layer(it->data) < selected->layer) {
290             last = it;
291             continue;
292         }
293         /* if lowering, stop at the beginning of the layer */
294         if (!raise)
295             break;
296         /* if raising, stop at the end of the layer */
297         if (window_layer(it->data) > selected->layer)
298             break;
299
300         last = it;
301     }
302
303     /* save this position in the stacking list */
304     below = last;
305
306     /* find where to put the group transients, start from the top of list */
307     for (it = stacking_list; it; it = g_list_next(it)) {
308         /* skip past higher layers */
309         if (window_layer(it->data) > selected->layer)
310             continue;
311         /* if we reach the end of the layer (how?) then don't go further */
312         if (window_layer(it->data) < selected->layer)
313             break;
314         /* stop when we reach the first window in the group */
315         if (WINDOW_IS_CLIENT(it->data)) {
316             ObClient *c = it->data;
317             if (c->group == selected->group)
318                 break;
319         }
320         /* if we don't hit any other group members, stop here because this
321            is where we are putting the selected window (and its children) */
322         if (it == below)
323             break;
324     }
325
326     /* save this position, this is the top of the group of windows between the
327        group transient ones we're restacking and the others up above that we're
328        restacking
329
330        we actually want to save 1 position _above_ that, for for loops to work
331        nicely, so move back one position in the list while saving it
332     */
333     above = it ? g_list_previous(it) : g_list_last(stacking_list);
334
335     /* put the windows inside the gap to the other windows we're stacking
336        into the restacking list, go from the bottom up so that we can use
337        g_list_prepend */
338     if (below) it = g_list_previous(below);
339     else       it = g_list_last(stacking_list);
340     for (; it != above; it = next) {
341         next = g_list_previous(it);
342         wins = g_list_prepend(wins, it->data);
343         stacking_list = g_list_delete_link(stacking_list, it);
344     }
345
346     /* group transients go above the rest of the stuff acquired to now */
347     wins = g_list_concat(group_trans, wins);
348     /* group modals go on the very top */
349     wins = g_list_concat(group_modals, wins);
350
351     do_restack(wins, below);
352     g_list_free(wins);
353
354     /* lower our parents after us, so they go below us */
355     if (!raise && selected->parents) {
356         GSList *parents_copy, *sit;
357         GSList *reorder = NULL;
358
359         parents_copy = g_slist_copy(selected->parents);
360
361         /* go thru stacking list backwards so we can use g_slist_prepend */
362         for (it = g_list_last(stacking_list); it && parents_copy;
363              it = g_list_previous(it))
364             if ((sit = g_slist_find(parents_copy, it->data))) {
365                 reorder = g_slist_prepend(reorder, sit->data);
366                 parents_copy = g_slist_delete_link(parents_copy, sit);
367             }
368         g_assert(parents_copy == NULL);
369
370         /* call restack for each of these to lower them */
371         for (sit = reorder; sit; sit = g_slist_next(sit))
372             restack_windows(sit->data, raise);
373     }
374 }
375
376 void stacking_raise(ObWindow *window)
377 {
378     if (WINDOW_IS_CLIENT(window)) {
379         ObClient *selected;
380         selected = WINDOW_AS_CLIENT(window);
381         restack_windows(selected, TRUE);
382     } else {
383         GList *wins;
384         wins = g_list_append(NULL, window);
385         stacking_list = g_list_remove(stacking_list, window);
386         do_raise(wins);
387         g_list_free(wins);
388     }
389 }
390
391 void stacking_lower(ObWindow *window)
392 {
393     if (WINDOW_IS_CLIENT(window)) {
394         ObClient *selected;
395         selected = WINDOW_AS_CLIENT(window);
396         restack_windows(selected, FALSE);
397     } else {
398         GList *wins;
399         wins = g_list_append(NULL, window);
400         stacking_list = g_list_remove(stacking_list, window);
401         do_lower(wins);
402         g_list_free(wins);
403     }
404 }
405
406 void stacking_below(ObWindow *window, ObWindow *below)
407 {
408     GList *wins, *before;
409
410     if (window_layer(window) != window_layer(below))
411         return;
412
413     wins = g_list_append(NULL, window);
414     stacking_list = g_list_remove(stacking_list, window);
415     before = g_list_next(g_list_find(stacking_list, below));
416     do_restack(wins, before);
417     g_list_free(wins);
418 }
419
420 void stacking_add(ObWindow *win)
421 {
422     g_assert(screen_support_win != None); /* make sure I dont break this in the
423                                              future */
424
425     stacking_list = g_list_append(stacking_list, win);
426     stacking_raise(win);
427 }
428
429 static GList *find_highest_relative(ObClient *client)
430 {
431     GList *ret = NULL;
432
433     if (client->parents) {
434         GList *it;
435         GSList *top;
436
437         /* get all top level relatives of this client */
438         top = client_search_all_top_parents_layer(client);
439
440         /* go from the top of the stacking order down */
441         for (it = stacking_list; !ret && it; it = g_list_next(it)) {
442             if (WINDOW_IS_CLIENT(it->data)) {
443                 ObClient *c = it->data;
444                 /* only look at windows in the same layer and that are
445                    visible */
446                 if (c->layer == client->layer &&
447                     !c->iconic &&
448                     (c->desktop == client->desktop ||
449                      c->desktop == DESKTOP_ALL ||
450                      client->desktop == DESKTOP_ALL))
451                 {
452                     GSList *sit;
453
454                     /* go through each top level parent and see it this window
455                        is related to them */
456                     for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
457                         ObClient *topc = sit->data;
458
459                         /* are they related ? */
460                         if (topc == c || client_search_transient(topc, c))
461                             ret = it;
462                     }
463                 }
464             }
465         }
466     }
467     return ret;
468 }
469
470 void stacking_add_nonintrusive(ObWindow *win)
471 {
472     ObClient *client;
473     GList *it_below = NULL; /* this client will be below us */
474     GList *it_above;
475     GList *wins;
476
477     if (!WINDOW_IS_CLIENT(win)) {
478         stacking_add(win); /* no special rules for others */
479         return;
480     }
481
482     client = WINDOW_AS_CLIENT(win);
483
484     /* insert above its highest parent (or its highest child !) */
485     it_below = find_highest_relative(client);
486
487     if (!it_below) {
488         /* nothing to put it directly above, so try find the focused client
489            to put it underneath it */
490         if (focus_client && client != focus_client &&
491             focus_client->layer == client->layer)
492         {
493             it_below = g_list_find(stacking_list, focus_client);
494             /* this can give NULL, but it means the focused window is on the
495                bottom of the stacking order, so go to the bottom in that case,
496                below it */
497             it_below = g_list_next(it_below);
498         }
499         else {
500             /* There is no window to put this directly above, so put it at the
501                top, so you know it is there.
502
503                It used to do this only if the window was focused and lower
504                it otherwise.
505
506                We also put it at the top not the bottom to fix a bug with
507                fullscreen windows. When focusLast is off and followsMouse is
508                on, when you switch desktops, the fullscreen window loses
509                focus and goes into its lower layer. If this puts it at the
510                bottom then when you come back to the desktop, the window is
511                at the bottom and won't get focus back.
512             */
513             it_below = stacking_list;
514         }
515     }
516
517     /* make sure it's not in the wrong layer though ! */
518     for (; it_below; it_below = g_list_next(it_below)) {
519         /* stop when the window is not in a higher layer than the window
520            it is going above (it_below) */
521         if (client->layer >= window_layer(it_below->data))
522             break;
523     }
524     for (; it_below != stacking_list; it_below = it_above) {
525         /* stop when the window is not in a lower layer than the
526            window it is going under (it_above) */
527         it_above = it_below ?
528             g_list_previous(it_below) : g_list_last(stacking_list);
529         if (client->layer <= window_layer(it_above->data))
530             break;
531     }
532
533     wins = g_list_append(NULL, win);
534     do_restack(wins, it_below);
535     g_list_free(wins);
536 }
537
538 /*! Returns TRUE if client is occluded by the sibling. If sibling is NULL it
539   tries against all other clients.
540 */
541 static gboolean stacking_occluded(ObClient *client, ObClient *sibling)
542 {
543     GList *it;
544     gboolean occluded = FALSE;
545     gboolean found = FALSE;
546
547     /* no need for any looping in this case */
548     if (sibling && client->layer != sibling->layer)
549         return occluded;
550
551     for (it = stacking_list; it;
552          it = (found ? g_list_previous(it) :g_list_next(it)))
553         if (WINDOW_IS_CLIENT(it->data)) {
554             ObClient *c = it->data;
555             if (found && !c->iconic &&
556                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
557                  c->desktop == client->desktop) &&
558                 !client_search_transient(client, c))
559             {
560                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
561                 {
562                     if (sibling != NULL) {
563                         if (c == sibling) {
564                             occluded = TRUE;
565                             break;
566                         }
567                     }
568                     else if (c->layer == client->layer) {
569                         occluded = TRUE;
570                         break;
571                     }
572                     else if (c->layer > client->layer)
573                         break; /* we past its layer */
574                 }
575             }
576             else if (c == client)
577                 found = TRUE;
578         }
579     return occluded;
580 }
581
582 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
583   against all other clients.
584 */
585 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
586 {
587     GList *it;
588     gboolean occludes = FALSE;
589     gboolean found = FALSE;
590
591     /* no need for any looping in this case */
592     if (sibling && client->layer != sibling->layer)
593         return occludes;
594
595     for (it = stacking_list; it; it = g_list_next(it))
596         if (WINDOW_IS_CLIENT(it->data)) {
597             ObClient *c = it->data;
598             if (found && !c->iconic &&
599                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
600                  c->desktop == client->desktop) &&
601                 !client_search_transient(c, client))
602             {
603                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
604                 {
605                     if (sibling != NULL) {
606                         if (c == sibling) {
607                             occludes = TRUE;
608                             break;
609                         }
610                     }
611                     else if (c->layer == client->layer) {
612                         occludes = TRUE;
613                         break;
614                     }
615                     else if (c->layer < client->layer)
616                         break; /* we past its layer */
617                 }
618             }
619             else if (c == client)
620                 found = TRUE;
621         }
622     return occludes;
623 }
624
625 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
626                                   gint detail)
627 {
628     gboolean ret = FALSE;
629
630     if (sibling && ((client->desktop != sibling->desktop &&
631                      client->desktop != DESKTOP_ALL &&
632                      sibling->desktop != DESKTOP_ALL) ||
633                     sibling->iconic))
634     {
635         ob_debug("Setting restack sibling to NULL, they are not on the same "
636                  "desktop or it is iconified");
637         sibling = NULL;
638     }
639
640     switch (detail) {
641     case Below:
642         ob_debug("Restack request Below for client %s sibling %s",
643                  client->title, sibling ? sibling->title : "(all)");
644         /* just lower it */
645         stacking_lower(CLIENT_AS_WINDOW(client));
646         ret = TRUE;
647         break;
648     case BottomIf:
649         ob_debug("Restack request BottomIf for client %s sibling %s",
650                  client->title, sibling ? sibling->title : "(all)");
651         /* if this client occludes sibling (or anything if NULL), then
652            lower it to the bottom */
653         if (stacking_occludes(client, sibling)) {
654             stacking_lower(CLIENT_AS_WINDOW(client));
655             ret = TRUE;
656         }
657         break;
658     case Above:
659         ob_debug("Restack request Above for client %s sibling %s",
660                  client->title, sibling ? sibling->title : "(all)");
661         stacking_raise(CLIENT_AS_WINDOW(client));
662         ret = TRUE;
663         break;
664     case TopIf:
665         ob_debug("Restack request TopIf for client %s sibling %s",
666                  client->title, sibling ? sibling->title : "(all)");
667         if (stacking_occluded(client, sibling)) {
668             stacking_raise(CLIENT_AS_WINDOW(client));
669             ret = TRUE;
670         }
671         break;
672     case Opposite:
673         ob_debug("Restack request Opposite for client %s sibling %s",
674                  client->title, sibling ? sibling->title : "(all)");
675         if (stacking_occluded(client, sibling)) {
676             stacking_raise(CLIENT_AS_WINDOW(client));
677             ret = TRUE;
678         }
679         else if (stacking_occludes(client, sibling)) {
680             stacking_lower(CLIENT_AS_WINDOW(client));
681             ret = TRUE;
682         }
683         break;
684     }
685     return ret;
686 }