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
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
442     stacking_list = g_list_append(stacking_list, win);
443     stacking_raise(win);
444 }
445
446 static GList *find_highest_relative(ObClient *client)
447 {
448     GList *ret = NULL;
449
450     if (client->parents) {
451         GList *it;
452         GSList *top;
453
454         /* get all top level relatives of this client */
455         top = client_search_all_top_parents_layer(client);
456
457         /* go from the top of the stacking order down */
458         for (it = stacking_list; !ret && it; it = g_list_next(it)) {
459             if (WINDOW_IS_CLIENT(it->data)) {
460                 ObClient *c = it->data;
461                 /* only look at windows in the same layer and that are
462                    visible */
463                 if (c->layer == client->layer &&
464                     !c->iconic &&
465                     (c->desktop == client->desktop ||
466                      c->desktop == DESKTOP_ALL ||
467                      client->desktop == DESKTOP_ALL))
468                 {
469                     GSList *sit;
470
471                     /* go through each top level parent and see it this window
472                        is related to them */
473                     for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
474                         ObClient *topc = sit->data;
475
476                         /* are they related ? */
477                         if (topc == c || client_search_transient(topc, c))
478                             ret = it;
479                     }
480                 }
481             }
482         }
483     }
484     return ret;
485 }
486
487 void stacking_add_nonintrusive(ObWindow *win)
488 {
489     ObClient *client;
490     GList *it_below = NULL; /* this client will be below us */
491     GList *it_above;
492     GList *wins;
493
494     if (!WINDOW_IS_CLIENT(win)) {
495         stacking_add(win); /* no special rules for others */
496         return;
497     }
498
499     client = WINDOW_AS_CLIENT(win);
500
501     /* insert above its highest parent (or its highest child !) */
502     it_below = find_highest_relative(client);
503
504     if (!it_below) {
505         /* nothing to put it directly above, so try find the focused client
506            to put it underneath it */
507         if (focus_client && client != focus_client &&
508             focus_client->layer == client->layer)
509         {
510             it_below = g_list_find(stacking_list, focus_client);
511             /* this can give NULL, but it means the focused window is on the
512                bottom of the stacking order, so go to the bottom in that case,
513                below it */
514             it_below = g_list_next(it_below);
515         }
516         else {
517             /* There is no window to put this directly above, so put it at the
518                top, so you know it is there.
519
520                It used to do this only if the window was focused and lower
521                it otherwise.
522
523                We also put it at the top not the bottom to fix a bug with
524                fullscreen windows. When focusLast is off and followsMouse is
525                on, when you switch desktops, the fullscreen window loses
526                focus and goes into its lower layer. If this puts it at the
527                bottom then when you come back to the desktop, the window is
528                at the bottom and won't get focus back.
529             */
530             it_below = stacking_list;
531         }
532     }
533
534     /* make sure it's not in the wrong layer though ! */
535     for (; it_below; it_below = g_list_next(it_below)) {
536         /* stop when the window is not in a higher layer than the window
537            it is going above (it_below) */
538         if (client->layer >= window_layer(it_below->data))
539             break;
540     }
541     for (; it_below != stacking_list; it_below = it_above) {
542         /* stop when the window is not in a lower layer than the
543            window it is going under (it_above) */
544         it_above = it_below ?
545             g_list_previous(it_below) : g_list_last(stacking_list);
546         if (client->layer <= window_layer(it_above->data))
547             break;
548     }
549
550     wins = g_list_append(NULL, win);
551     do_restack(wins, it_below);
552     g_list_free(wins);
553 }
554
555 /*! Returns TRUE if client is occluded by the sibling. If sibling is NULL it
556   tries against all other clients.
557 */
558 static gboolean stacking_occluded(ObClient *client, ObClient *sibling)
559 {
560     GList *it;
561     gboolean occluded = FALSE;
562     gboolean found = FALSE;
563
564     /* no need for any looping in this case */
565     if (sibling && client->layer != sibling->layer)
566         return occluded;
567
568     for (it = stacking_list; it;
569          it = (found ? g_list_previous(it) :g_list_next(it)))
570         if (WINDOW_IS_CLIENT(it->data)) {
571             ObClient *c = it->data;
572             if (found && !c->iconic &&
573                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
574                  c->desktop == client->desktop) &&
575                 !client_search_transient(client, c))
576             {
577                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
578                 {
579                     if (sibling != NULL) {
580                         if (c == sibling) {
581                             occluded = TRUE;
582                             break;
583                         }
584                     }
585                     else if (c->layer == client->layer) {
586                         occluded = TRUE;
587                         break;
588                     }
589                     else if (c->layer > client->layer)
590                         break; /* we past its layer */
591                 }
592             }
593             else if (c == client)
594                 found = TRUE;
595         }
596     return occluded;
597 }
598
599 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
600   against all other clients.
601 */
602 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
603 {
604     GList *it;
605     gboolean occludes = FALSE;
606     gboolean found = FALSE;
607
608     /* no need for any looping in this case */
609     if (sibling && client->layer != sibling->layer)
610         return occludes;
611
612     for (it = stacking_list; it; it = g_list_next(it))
613         if (WINDOW_IS_CLIENT(it->data)) {
614             ObClient *c = it->data;
615             if (found && !c->iconic &&
616                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
617                  c->desktop == client->desktop) &&
618                 !client_search_transient(c, client))
619             {
620                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
621                 {
622                     if (sibling != NULL) {
623                         if (c == sibling) {
624                             occludes = TRUE;
625                             break;
626                         }
627                     }
628                     else if (c->layer == client->layer) {
629                         occludes = TRUE;
630                         break;
631                     }
632                     else if (c->layer < client->layer)
633                         break; /* we past its layer */
634                 }
635             }
636             else if (c == client)
637                 found = TRUE;
638         }
639     return occludes;
640 }
641
642 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
643                                   gint detail)
644 {
645     gboolean ret = FALSE;
646
647     if (sibling && ((client->desktop != sibling->desktop &&
648                      client->desktop != DESKTOP_ALL &&
649                      sibling->desktop != DESKTOP_ALL) ||
650                     sibling->iconic))
651     {
652         ob_debug("Setting restack sibling to NULL, they are not on the same "
653                  "desktop or it is iconified");
654         sibling = NULL;
655     }
656
657     switch (detail) {
658     case Below:
659         ob_debug("Restack request Below for client %s sibling %s",
660                  client->title, sibling ? sibling->title : "(all)");
661         /* just lower it */
662         stacking_lower(CLIENT_AS_WINDOW(client));
663         ret = TRUE;
664         break;
665     case BottomIf:
666         ob_debug("Restack request BottomIf for client %s sibling %s",
667                  client->title, sibling ? sibling->title : "(all)");
668         /* if this client occludes sibling (or anything if NULL), then
669            lower it to the bottom */
670         if (stacking_occludes(client, sibling)) {
671             stacking_lower(CLIENT_AS_WINDOW(client));
672             ret = TRUE;
673         }
674         break;
675     case Above:
676         ob_debug("Restack request Above for client %s sibling %s",
677                  client->title, sibling ? sibling->title : "(all)");
678         stacking_raise(CLIENT_AS_WINDOW(client));
679         ret = TRUE;
680         break;
681     case TopIf:
682         ob_debug("Restack request TopIf for client %s sibling %s",
683                  client->title, sibling ? sibling->title : "(all)");
684         if (stacking_occluded(client, sibling)) {
685             stacking_raise(CLIENT_AS_WINDOW(client));
686             ret = TRUE;
687         }
688         break;
689     case Opposite:
690         ob_debug("Restack request Opposite for client %s sibling %s",
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 }