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