]> icculus.org git repositories - dana/openbox.git/blob - openbox/stacking.c
Rename ObFramePlugin to ObFrameEngine
[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 "engine_interface.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)->w_client;
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     Rect client_area = render_plugin->frame_get_window_area(client->frame);
548     /* no need for any looping in this case */
549     if (sibling && client->layer != sibling->layer)
550         return occluded;
551
552     for (it = stacking_list; it;
553          it = (found ? g_list_previous(it) :g_list_next(it)))
554         if (WINDOW_IS_CLIENT(it->data)) {
555             ObClient *c = it->data;
556             if (found && !c->iconic &&
557                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
558                  c->desktop == client->desktop) &&
559                 !client_search_transient(client, c))
560             {
561                 Rect c_area = render_plugin->frame_get_window_area(c->frame);
562                 if (RECT_INTERSECTS_RECT(c_area, client_area))
563                 {
564                     if (sibling != NULL) {
565                         if (c == sibling) {
566                             occluded = TRUE;
567                             break;
568                         }
569                     }
570                     else if (c->layer == client->layer) {
571                         occluded = TRUE;
572                         break;
573                     }
574                     else if (c->layer > client->layer)
575                         break; /* we past its layer */
576                 }
577             }
578             else if (c == client)
579                 found = TRUE;
580         }
581     return occluded;
582 }
583
584 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
585   against all other clients.
586 */
587 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
588 {
589     GList *it;
590     gboolean occludes = FALSE;
591     gboolean found = FALSE;
592
593     Rect client_area = render_plugin->frame_get_window_area(client->frame);
594
595     /* no need for any looping in this case */
596     if (sibling && client->layer != sibling->layer)
597         return occludes;
598
599     for (it = stacking_list; it; it = g_list_next(it))
600         if (WINDOW_IS_CLIENT(it->data)) {
601             ObClient *c = it->data;
602             if (found && !c->iconic &&
603                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
604                  c->desktop == client->desktop) &&
605                 !client_search_transient(c, client))
606             {
607                 Rect c_area = render_plugin->frame_get_window_area(c->frame);
608                 if (RECT_INTERSECTS_RECT(c_area, client_area))
609                 {
610                     if (sibling != NULL) {
611                         if (c == sibling) {
612                             occludes = TRUE;
613                             break;
614                         }
615                     }
616                     else if (c->layer == client->layer) {
617                         occludes = TRUE;
618                         break;
619                     }
620                     else if (c->layer < client->layer)
621                         break; /* we past its layer */
622                 }
623             }
624             else if (c == client)
625                 found = TRUE;
626         }
627     return occludes;
628 }
629
630 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
631                                   gint detail)
632 {
633     gboolean ret = FALSE;
634
635     if (sibling && ((client->desktop != sibling->desktop &&
636                      client->desktop != DESKTOP_ALL &&
637                      sibling->desktop != DESKTOP_ALL) ||
638                     sibling->iconic))
639     {
640         ob_debug("Setting restack sibling to NULL, they are not on the same "
641                  "desktop or it is iconified");
642         sibling = NULL;
643     }
644
645     switch (detail) {
646     case Below:
647         ob_debug("Restack request Below for client %s sibling %s",
648                  client->title, sibling ? sibling->title : "(all)");
649         /* just lower it */
650         stacking_lower(CLIENT_AS_WINDOW(client));
651         ret = TRUE;
652         break;
653     case BottomIf:
654         ob_debug("Restack request BottomIf for client %s sibling %s",
655                  client->title, sibling ? sibling->title : "(all)");
656         /* if this client occludes sibling (or anything if NULL), then
657            lower it to the bottom */
658         if (stacking_occludes(client, sibling)) {
659             stacking_lower(CLIENT_AS_WINDOW(client));
660             ret = TRUE;
661         }
662         break;
663     case Above:
664         ob_debug("Restack request Above for client %s sibling %s",
665                  client->title, sibling ? sibling->title : "(all)");
666         stacking_raise(CLIENT_AS_WINDOW(client));
667         ret = TRUE;
668         break;
669     case TopIf:
670         ob_debug("Restack request TopIf for client %s sibling %s",
671                  client->title, sibling ? sibling->title : "(all)");
672         if (stacking_occluded(client, sibling)) {
673             stacking_raise(CLIENT_AS_WINDOW(client));
674             ret = TRUE;
675         }
676         break;
677     case Opposite:
678         ob_debug("Restack request Opposite for client %s sibling %s",
679                  client->title, sibling ? sibling->title : "(all)");
680         if (stacking_occluded(client, sibling)) {
681             stacking_raise(CLIENT_AS_WINDOW(client));
682             ret = TRUE;
683         }
684         else if (stacking_occludes(client, sibling)) {
685             stacking_lower(CLIENT_AS_WINDOW(client));
686             ret = TRUE;
687         }
688         break;
689     }
690     return ret;
691 }