fixes for transients
[mikachu/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        Ben 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
29 GList  *stacking_list = NULL;
30
31 void stacking_set_list()
32 {
33     Window *windows = NULL;
34     GList *it;
35     guint i = 0;
36
37     /* on shutdown, don't update the properties, so that we can read it back
38        in on startup and re-stack the windows as they were before we shut down
39     */
40     if (ob_state() == OB_STATE_EXITING) return;
41
42     /* create an array of the window ids (from bottom to top,
43        reverse order!) */
44     if (stacking_list) {
45         windows = g_new(Window, g_list_length(stacking_list));
46         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
47             if (WINDOW_IS_CLIENT(it->data))
48                 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
49         }
50     }
51
52     PROP_SETA32(RootWindow(ob_display, ob_screen),
53                 net_client_list_stacking, window, (gulong*)windows, i);
54
55     g_free(windows);
56 }
57
58 static void do_restack(GList *wins, GList *before)
59 {
60     GList *it;
61     Window *win;
62     gint i;
63
64 #ifdef DEBUG
65     GList *next;
66     /* pls only restack stuff in the same layer at a time */
67     for (it = wins; it; it = next) {
68         next = g_list_next(it);
69         if (!next) break;
70         g_assert (window_layer(it->data) == window_layer(next->data));
71     }
72     if (before)
73         g_assert(window_layer(it->data) >= window_layer(before->data));
74 #endif
75
76     win = g_new(Window, g_list_length(wins) + 1);
77
78     if (before == stacking_list)
79         win[0] = screen_support_win;
80     else if (!before)
81         win[0] = window_top(g_list_last(stacking_list)->data);
82     else
83         win[0] = window_top(g_list_previous(before)->data);
84
85     for (i = 1, it = wins; it; ++i, it = g_list_next(it)) {
86         win[i] = window_top(it->data);
87         g_assert(win[i] != None); /* better not call stacking shit before
88                                      setting your top level window value */
89         stacking_list = g_list_insert_before(stacking_list, before, it->data);
90     }
91
92 #ifdef DEBUG
93     /* some debug checking of the stacking list's order */
94     for (it = stacking_list; ; it = next) {
95         next = g_list_next(it);
96         if (!next) break;
97         g_assert(window_layer(it->data) >= window_layer(next->data));
98     }
99 #endif
100
101     XRestackWindows(ob_display, win, i);
102     g_free(win);
103
104     stacking_set_list();
105 }
106
107 static void do_raise(GList *wins)
108 {
109     GList *it;
110     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
111     gint i;
112
113     for (it = wins; it; it = g_list_next(it)) {
114         ObStackingLayer l;
115
116         l = window_layer(it->data);
117         layer[l] = g_list_append(layer[l], it->data);
118     }
119
120     it = stacking_list;
121     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
122         if (layer[i]) {
123             for (; it; it = g_list_next(it)) {
124                 /* look for the top of the layer */
125                 if (window_layer(it->data) <= (ObStackingLayer) i)
126                     break;
127             }
128             do_restack(layer[i], it);
129             g_list_free(layer[i]);
130         }
131     }
132 }
133
134 static void do_lower(GList *wins)
135 {
136     GList *it;
137     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
138     gint i;
139
140     for (it = wins; it; it = g_list_next(it)) {
141         ObStackingLayer l;
142
143         l = window_layer(it->data);
144         layer[l] = g_list_append(layer[l], it->data);
145     }
146
147     it = stacking_list;
148     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
149         if (layer[i]) {
150             for (; it; it = g_list_next(it)) {
151                 /* look for the top of the next layer down */
152                 if (window_layer(it->data) < (ObStackingLayer) i)
153                     break;
154             }
155             do_restack(layer[i], it);
156             g_list_free(layer[i]);
157         }
158     }
159 }
160
161 static void restack_windows(ObClient *selected, gboolean raise)
162 {
163     GList *it, *last, *below, *above, *next;
164     GList *wins = NULL;
165
166     GList *group_modals = NULL;
167     GList *group_trans = NULL;
168     GList *modals = NULL;
169     GList *trans = NULL;
170
171     if (!raise && selected->transient_for) {
172         GSList *top, *top_it;
173         GSList *top_reorder = NULL;
174         
175         /* if it's a transient lowering, lower its parents so that we can lower
176            this window, or it won't move */
177         top = client_search_all_top_parents(selected);
178
179         /* that is, if it has any parents */
180         if (!(top->data == selected && top->next == NULL)) {
181             /* go thru stacking list backwards so we can use g_slist_prepend */
182             for (it = g_list_last(stacking_list); it && top;
183                  it = g_list_previous(it))
184                 if ((top_it = g_slist_find(top, it->data))) {
185                     top_reorder = g_slist_prepend(top_reorder, top_it->data);
186                     top = g_slist_delete_link(top, top_it);
187                 }
188             g_assert(top == NULL);
189
190             /* call restack for each of these to lower them */
191             for (top_it = top_reorder; top_it; top_it = g_slist_next(top_it))
192                 restack_windows(top_it->data, raise);
193             return;
194         }
195     }
196
197     /* remove first so we can't run into ourself */
198     it = g_list_find(stacking_list, selected);
199     g_assert(it);
200     stacking_list = g_list_delete_link(stacking_list, it);
201
202     /* go from the bottom of the stacking list up */
203     for (it = g_list_last(stacking_list); it; it = next) {
204         next = g_list_previous(it);
205
206         if (WINDOW_IS_CLIENT(it->data)) {
207             ObClient *ch = it->data;
208
209             /* only move windows in the same stacking layer */
210             if (ch->layer == selected->layer &&
211                 client_search_transient(selected, ch))
212             {
213                 if (client_is_direct_child(selected, ch)) {
214                     if (ch->modal)
215                         modals = g_list_prepend(modals, ch);
216                     else
217                         trans = g_list_prepend(trans, ch);
218                 }
219                 else {
220                     if (ch->modal)
221                         group_modals = g_list_prepend(group_modals, ch);
222                     else
223                         group_trans = g_list_prepend(group_trans, ch);
224                 }
225                 stacking_list = g_list_delete_link(stacking_list, it);
226             }
227         }
228     }
229
230     /* put transients of the selected window right above it */
231     wins = g_list_concat(modals, trans);
232     wins = g_list_append(wins, selected);
233
234     /* if selected window is transient for group then raise it above others */
235     if (selected->transient_for == OB_TRAN_GROUP) {
236         /* if it's modal, raise it above those also */
237         if (selected->modal) {
238             wins = g_list_concat(wins, group_modals);
239             group_modals = NULL;
240         }
241         wins = g_list_concat(wins, group_trans);
242         group_trans = NULL;
243     }
244
245     /* find where to put the selected window, start from bottom of list,
246        this is the window below everything we are re-adding to the list */
247     last = NULL;
248     for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
249     {
250         if (window_layer(it->data) < selected->layer) {
251             last = it;
252             continue;
253         }
254         /* if lowering, stop at the beginning of the layer */
255         if (!raise)
256             break;
257         /* if raising, stop at the end of the layer */
258         if (window_layer(it->data) > selected->layer)
259             break;
260
261         last = it;
262     }
263
264     /* save this position in the stacking list */
265     below = last;
266
267     /* find where to put the group transients, start from the top of list */
268     for (it = stacking_list; it; it = g_list_next(it)) {
269         /* skip past higher layers */
270         if (window_layer(it->data) > selected->layer)
271             continue;
272         /* if we reach the end of the layer (how?) then don't go further */
273         if (window_layer(it->data) < selected->layer)
274             break;
275         /* stop when we reach the first window in the group */
276         if (WINDOW_IS_CLIENT(it->data)) {
277             ObClient *c = it->data;
278             if (c->group == selected->group)
279                 break;
280         }
281         /* if we don't hit any other group members, stop here because this
282            is where we are putting the selected window (and its children) */
283         if (it == below)
284             break;
285     }
286
287     /* save this position, this is the top of the group of windows between the
288        group transient ones we're restacking and the others up above that we're
289        restacking
290
291        we actually want to save 1 position _above_ that, for for loops to work
292        nicely, so move back one position in the list while saving it
293     */
294     above = it ? g_list_previous(it) : g_list_last(stacking_list);
295
296     /* put the windows inside the gap to the other windows we're stacking
297        into the restacking list, go from the bottom up so that we can use
298        g_list_prepend */
299     if (below) it = g_list_previous(below);
300     else       it = g_list_last(stacking_list);
301     for (; it != above; it = next) {
302         next = g_list_previous(it);
303         wins = g_list_prepend(wins, it->data);
304         stacking_list = g_list_delete_link(stacking_list, it);
305     }
306
307     /* group transients go above the rest of the stuff acquired to now */
308     wins = g_list_concat(group_trans, wins);
309     /* group modals go on the very top */
310     wins = g_list_concat(group_modals, wins);
311
312     do_restack(wins, below);
313     g_list_free(wins);
314 }
315
316 void stacking_raise(ObWindow *window)
317 {
318     if (WINDOW_IS_CLIENT(window)) {
319         ObClient *selected;
320         selected = WINDOW_AS_CLIENT(window);
321         restack_windows(selected, TRUE);
322     } else {
323         GList *wins;
324         wins = g_list_append(NULL, window);
325         stacking_list = g_list_remove(stacking_list, window);
326         do_raise(wins);
327         g_list_free(wins);
328     }
329 }
330
331 void stacking_lower(ObWindow *window)
332 {
333     if (WINDOW_IS_CLIENT(window)) {
334         ObClient *selected;
335         selected = WINDOW_AS_CLIENT(window);
336         restack_windows(selected, FALSE);
337     } else {
338         GList *wins;
339         wins = g_list_append(NULL, window);
340         stacking_list = g_list_remove(stacking_list, window);
341         do_lower(wins);
342         g_list_free(wins);
343     }
344 }
345
346 void stacking_below(ObWindow *window, ObWindow *below)
347 {
348     GList *wins, *before;
349
350     if (window_layer(window) != window_layer(below))
351         return;
352
353     wins = g_list_append(NULL, window);
354     stacking_list = g_list_remove(stacking_list, window);
355     before = g_list_next(g_list_find(stacking_list, below));
356     do_restack(wins, before);
357     g_list_free(wins);
358 }
359
360 void stacking_add(ObWindow *win)
361 {
362     g_assert(screen_support_win != None); /* make sure I dont break this in the
363                                              future */
364
365     stacking_list = g_list_append(stacking_list, win);
366     stacking_raise(win);
367 }
368
369 void stacking_add_nonintrusive(ObWindow *win)
370 {
371     ObClient *client;
372     ObClient *parent = NULL;
373     GList *it_below = NULL;
374
375     if (!WINDOW_IS_CLIENT(win)) {
376         stacking_add(win); /* no special rules for others */
377         return;
378     }
379
380     client = WINDOW_AS_CLIENT(win);
381
382     /* insert above its highest parent */
383     if (client->transient_for) {
384         if (client->transient_for != OB_TRAN_GROUP) {
385             parent = client->transient_for;
386         } else {
387             GSList *sit;
388             GList *it;
389
390             if (client->group)
391                 for (it = stacking_list; !parent && it; it = g_list_next(it)) {
392                     if ((sit = g_slist_find(client->group->members, it->data)))
393                         for (sit = client->group->members; !parent && sit;
394                              sit = g_slist_next(sit))
395                         {
396                             ObClient *c = sit->data;
397                             /* checking transient_for prevents infinate loops!
398                              */
399                             if (sit->data == it->data && !c->transient_for)
400                                 parent = it->data;
401                         }
402                 }
403         }
404     }
405
406     if (!(it_below = g_list_find(stacking_list, parent))) {
407         /* no parent to put above, try find the focused client to go
408            under */
409         if (focus_client && focus_client->layer == client->layer) {
410             if ((it_below = g_list_find(stacking_list, focus_client)))
411                 it_below = it_below->next;
412         }
413     }
414     if (!it_below) {
415         /* there is no window to put this directly above, so put it at the
416            bottom */
417         stacking_list = g_list_prepend(stacking_list, win);
418         stacking_lower(win);
419     } else {
420         /* make sure it's not in the wrong layer though ! */
421         for (; it_below; it_below = g_list_next(it_below))
422         {
423             /* stop when the window is not in a higher layer than the window
424                it is going above (it_below) */
425             if (client->layer >= window_layer(it_below->data))
426                 break;
427         }
428         for (; it_below != stacking_list;
429              it_below = g_list_previous(it_below))
430         {
431             /* stop when the window is not in a lower layer than the
432                window it is going under (it_above) */
433             GList *it_above = g_list_previous(it_below);
434             if (client->layer <= window_layer(it_above->data))
435                 break;
436         }
437
438         GList *wins = g_list_append(NULL, win);
439         do_restack(wins, it_below);
440         g_list_free(wins);
441     }
442 }