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