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