glib can't handle -Wcast-qual
[dana/openbox.git] / openbox / stacking.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    stacking.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "openbox.h"
21 #include "screen.h"
22 #include "focus.h"
23 #include "client.h"
24 #include "group.h"
25 #include "frame.h"
26 #include "window.h"
27 #include "event.h"
28 #include "debug.h"
29 #include "obt/prop.h"
30
31 GList  *stacking_list = NULL;
32 GList  *stacking_list_tail = NULL;
33 /*! When true, stacking changes will not be reflected on the screen.  This is
34   to freeze the on-screen stacking order while a window is being temporarily
35   raised during focus cycling */
36 static gboolean pause_changes = FALSE;
37
38 void stacking_set_list(void)
39 {
40     Window *windows = NULL;
41     GList *it;
42     guint i = 0;
43
44     /* on shutdown, don't update the properties, so that we can read it back
45        in on startup and re-stack the windows as they were before we shut down
46     */
47     if (ob_state() == OB_STATE_EXITING) return;
48
49     /* create an array of the window ids (from bottom to top,
50        reverse order!) */
51     if (stacking_list) {
52         windows = g_new(Window, g_list_length(stacking_list));
53         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
54             if (WINDOW_IS_CLIENT(it->data))
55                 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
56         }
57     }
58
59     OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST_STACKING, WINDOW,
60                     (gulong*)windows, i);
61
62     g_free(windows);
63 }
64
65 static void do_restack(GList *wins, GList *before)
66 {
67     GList *it;
68     Window *win;
69     gint i;
70
71 #ifdef DEBUG
72     GList *next;
73
74     g_assert(wins);
75     /* pls only restack stuff in the same layer at a time */
76     for (it = wins; it; it = next) {
77         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_below(ObWindow *window, ObWindow *below)
427 {
428     GList *wins, *before;
429
430     if (window_layer(window) != window_layer(below))
431         return;
432
433     wins = g_list_append(NULL, window);
434     stacking_list = g_list_remove(stacking_list, window);
435     before = g_list_next(g_list_find(stacking_list, below));
436     do_restack(wins, before);
437     g_list_free(wins);
438     stacking_list_tail = g_list_last(stacking_list);
439 }
440
441 void stacking_add(ObWindow *win)
442 {
443     g_assert(screen_support_win != None); /* make sure I dont break this in the
444                                              future */
445     /* don't add windows that are being unmanaged ! */
446     if (WINDOW_IS_CLIENT(win)) g_assert(WINDOW_AS_CLIENT(win)->managed);
447
448     stacking_list = g_list_append(stacking_list, win);
449
450     stacking_raise(win);
451     /* stacking_list_tail set by stacking_raise() */
452 }
453
454 static GList *find_highest_relative(ObClient *client)
455 {
456     GList *ret = NULL;
457
458     if (client->parents) {
459         GList *it;
460         GSList *top;
461
462         /* get all top level relatives of this client */
463         top = client_search_all_top_parents_layer(client);
464
465         /* go from the top of the stacking order down */
466         for (it = stacking_list; !ret && it; it = g_list_next(it)) {
467             if (WINDOW_IS_CLIENT(it->data)) {
468                 ObClient *c = it->data;
469                 /* only look at windows in the same layer and that are
470                    visible */
471                 if (c->layer == client->layer &&
472                     !c->iconic &&
473                     (c->desktop == client->desktop ||
474                      c->desktop == DESKTOP_ALL ||
475                      client->desktop == DESKTOP_ALL))
476                 {
477                     GSList *sit;
478
479                     /* go through each top level parent and see it this window
480                        is related to them */
481                     for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
482                         ObClient *topc = sit->data;
483
484                         /* are they related ? */
485                         if (topc == c || client_search_transient(topc, c))
486                             ret = it;
487                     }
488                 }
489             }
490         }
491     }
492     return ret;
493 }
494
495 void stacking_add_nonintrusive(ObWindow *win)
496 {
497     ObClient *client;
498     GList *it_below = NULL; /* this client will be below us */
499     GList *it_above;
500     GList *wins;
501
502     if (!WINDOW_IS_CLIENT(win)) {
503         stacking_add(win); /* no special rules for others */
504         return;
505     }
506
507     client = WINDOW_AS_CLIENT(win);
508
509     /* don't add windows that are being unmanaged ! */
510     g_assert(client->managed);
511
512     /* insert above its highest parent (or its highest child !) */
513     it_below = find_highest_relative(client);
514
515     if (!it_below) {
516         /* nothing to put it directly above, so try find the focused client
517            to put it underneath it */
518         if (focus_client && client != focus_client &&
519             focus_client->layer == client->layer)
520         {
521             it_below = g_list_find(stacking_list, focus_client);
522             /* this can give NULL, but it means the focused window is on the
523                bottom of the stacking order, so go to the bottom in that case,
524                below it */
525             it_below = g_list_next(it_below);
526         }
527         else {
528             /* There is no window to put this directly above, so put it at the
529                top, so you know it is there.
530
531                It used to do this only if the window was focused and lower
532                it otherwise.
533
534                We also put it at the top not the bottom to fix a bug with
535                fullscreen windows. When focusLast is off and followsMouse is
536                on, when you switch desktops, the fullscreen window loses
537                focus and goes into its lower layer. If this puts it at the
538                bottom then when you come back to the desktop, the window is
539                at the bottom and won't get focus back.
540             */
541             it_below = stacking_list;
542         }
543     }
544
545     /* make sure it's not in the wrong layer though ! */
546     for (; it_below; it_below = g_list_next(it_below)) {
547         /* stop when the window is not in a higher layer than the window
548            it is going above (it_below) */
549         if (client->layer >= window_layer(it_below->data))
550             break;
551     }
552     for (; it_below != stacking_list; it_below = it_above) {
553         /* stop when the window is not in a lower layer than the
554            window it is going under (it_above) */
555         it_above = it_below ?
556             g_list_previous(it_below) : g_list_last(stacking_list);
557         if (client->layer <= window_layer(it_above->data))
558             break;
559     }
560
561     wins = g_list_append(NULL, win);
562     do_restack(wins, it_below);
563     g_list_free(wins);
564     stacking_list_tail = g_list_last(stacking_list);
565 }
566
567 /*! Returns TRUE if client is occluded by the sibling. If sibling is NULL it
568   tries against all other clients.
569 */
570 static gboolean stacking_occluded(ObClient *client, ObClient *sibling)
571 {
572     GList *it;
573     gboolean occluded = FALSE;
574     gboolean found = FALSE;
575
576     /* no need for any looping in this case */
577     if (sibling && client->layer != sibling->layer)
578         return occluded;
579
580     for (it = stacking_list; it;
581          it = (found ? g_list_previous(it) :g_list_next(it)))
582         if (WINDOW_IS_CLIENT(it->data)) {
583             ObClient *c = it->data;
584             if (found && !c->iconic &&
585                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
586                  c->desktop == client->desktop) &&
587                 !client_search_transient(client, c))
588             {
589                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
590                 {
591                     if (sibling != NULL) {
592                         if (c == sibling) {
593                             occluded = TRUE;
594                             break;
595                         }
596                     }
597                     else if (c->layer == client->layer) {
598                         occluded = TRUE;
599                         break;
600                     }
601                     else if (c->layer > client->layer)
602                         break; /* we past its layer */
603                 }
604             }
605             else if (c == client)
606                 found = TRUE;
607         }
608     return occluded;
609 }
610
611 /*! Returns TRUE if client occludes the sibling. If sibling is NULL it tries
612   against all other clients.
613 */
614 static gboolean stacking_occludes(ObClient *client, ObClient *sibling)
615 {
616     GList *it;
617     gboolean occludes = FALSE;
618     gboolean found = FALSE;
619
620     /* no need for any looping in this case */
621     if (sibling && client->layer != sibling->layer)
622         return occludes;
623
624     for (it = stacking_list; it; it = g_list_next(it))
625         if (WINDOW_IS_CLIENT(it->data)) {
626             ObClient *c = it->data;
627             if (found && !c->iconic &&
628                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
629                  c->desktop == client->desktop) &&
630                 !client_search_transient(c, client))
631             {
632                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
633                 {
634                     if (sibling != NULL) {
635                         if (c == sibling) {
636                             occludes = TRUE;
637                             break;
638                         }
639                     }
640                     else if (c->layer == client->layer) {
641                         occludes = TRUE;
642                         break;
643                     }
644                     else if (c->layer < client->layer)
645                         break; /* we past its layer */
646                 }
647             }
648             else if (c == client)
649                 found = TRUE;
650         }
651     return occludes;
652 }
653
654 gboolean stacking_restack_request(ObClient *client, ObClient *sibling,
655                                   gint detail)
656 {
657     gboolean ret = FALSE;
658
659     if (sibling && ((client->desktop != sibling->desktop &&
660                      client->desktop != DESKTOP_ALL &&
661                      sibling->desktop != DESKTOP_ALL) ||
662                     sibling->iconic))
663     {
664         ob_debug("Setting restack sibling to NULL, they are not on the same "
665                  "desktop or it is iconified");
666         sibling = NULL;
667     }
668
669     switch (detail) {
670     case Below:
671         ob_debug("Restack request Below for client %s sibling %s",
672                  client->title, sibling ? sibling->title : "(all)");
673         /* just lower it */
674         stacking_lower(CLIENT_AS_WINDOW(client));
675         ret = TRUE;
676         break;
677     case BottomIf:
678         ob_debug("Restack request BottomIf for client %s sibling %s",
679                  client->title, sibling ? sibling->title : "(all)");
680         /* if this client occludes sibling (or anything if NULL), then
681            lower it to the bottom */
682         if (stacking_occludes(client, sibling)) {
683             stacking_lower(CLIENT_AS_WINDOW(client));
684             ret = TRUE;
685         }
686         break;
687     case Above:
688         ob_debug("Restack request Above for client %s sibling %s",
689                  client->title, sibling ? sibling->title : "(all)");
690         stacking_raise(CLIENT_AS_WINDOW(client));
691         ret = TRUE;
692         break;
693     case TopIf:
694         ob_debug("Restack request TopIf for client %s sibling %s",
695                  client->title, sibling ? sibling->title : "(all)");
696         if (stacking_occluded(client, sibling)) {
697             stacking_raise(CLIENT_AS_WINDOW(client));
698             ret = TRUE;
699         }
700         break;
701     case Opposite:
702         ob_debug("Restack request Opposite for client %s sibling %s",
703                  client->title, sibling ? sibling->title : "(all)");
704         if (stacking_occluded(client, sibling)) {
705             stacking_raise(CLIENT_AS_WINDOW(client));
706             ret = TRUE;
707         }
708         else if (stacking_occludes(client, sibling)) {
709             stacking_lower(CLIENT_AS_WINDOW(client));
710             ret = TRUE;
711         }
712         break;
713     }
714     return ret;
715 }