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