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