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