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