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