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