Bump to 3.4.11.2 and update changelog
[mikachu/openbox.git] / openbox / focus.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus.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 "debug.h"
21 #include "event.h"
22 #include "openbox.h"
23 #include "grab.h"
24 #include "client.h"
25 #include "config.h"
26 #include "group.h"
27 #include "focus_cycle.h"
28 #include "screen.h"
29 #include "prop.h"
30 #include "keyboard.h"
31 #include "focus.h"
32 #include "stacking.h"
33
34 #include <X11/Xlib.h>
35 #include <glib.h>
36
37 #define FOCUS_INDICATOR_WIDTH 6
38
39 ObClient *focus_client = NULL;
40 GList *focus_order = NULL;
41
42 void focus_startup(gboolean reconfig)
43 {
44     if (reconfig) return;
45
46     /* start with nothing focused */
47     focus_nothing();
48 }
49
50 void focus_shutdown(gboolean reconfig)
51 {
52     if (reconfig) return;
53
54     /* reset focus to root */
55     XSetInputFocus(ob_display, PointerRoot, RevertToNone, CurrentTime);
56 }
57
58 static void push_to_top(ObClient *client)
59 {
60     ObClient *p;
61
62     /* if it is modal for a single window, then put that window at the top
63        of the focus order first, so it will be right after ours. the same is
64        done with stacking */
65     if (client->modal && (p = client_direct_parent(client)))
66         push_to_top(p);
67
68     focus_order = g_list_remove(focus_order, client);
69     focus_order = g_list_prepend(focus_order, client);
70 }
71
72 void focus_set_client(ObClient *client)
73 {
74     Window active;
75
76     ob_debug_type(OB_DEBUG_FOCUS,
77                   "focus_set_client 0x%lx\n", client ? client->window : 0);
78
79     if (focus_client == client)
80         return;
81
82     /* uninstall the old colormap, and install the new one */
83     screen_install_colormap(focus_client, FALSE);
84     screen_install_colormap(client, TRUE);
85
86     focus_client = client;
87
88     if (client != NULL) {
89         /* move to the top of the list */
90         push_to_top(client);
91         /* remove hiliting from the window when it gets focused */
92         client_hilite(client, FALSE);
93
94         /* make sure the focus cycle popup shows things in the right order */
95         focus_cycle_reorder();
96     }
97
98     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
99     if (ob_state() != OB_STATE_EXITING) {
100         active = client ? client->window : None;
101         PROP_SET32(RootWindow(ob_display, ob_screen),
102                    net_active_window, window, active);
103     }
104 }
105
106 static ObClient* focus_fallback_target(gboolean allow_refocus,
107                                        gboolean allow_pointer,
108                                        gboolean allow_omnipresent,
109                                        ObClient *old)
110 {
111     GList *it;
112     ObClient *c;
113
114     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
115     if (allow_pointer && config_focus_follow)
116         if ((c = client_under_pointer()) &&
117             (allow_refocus || client_focus_target(c) != old) &&
118             (client_normal(c) &&
119              client_focus(c)))
120         {
121             ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
122             return c;
123         }
124
125     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
126     for (it = focus_order; it; it = g_list_next(it)) {
127         c = it->data;
128         /* fallback focus to a window if:
129            1. it is on the current desktop. this ignores omnipresent
130            windows, which are problematic in their own rite, unless they are
131            specifically allowed
132            2. it is a valid auto-focus target
133            3. it is not shaded
134         */
135         if ((allow_omnipresent || c->desktop == screen_desktop) &&
136             focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE) &&
137             !c->shaded &&
138             (allow_refocus || client_focus_target(c) != old) &&
139             client_focus(c))
140         {
141             ob_debug_type(OB_DEBUG_FOCUS, "found in focus order\n");
142             return c;
143         }
144     }
145
146     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window\n");
147     for (it = focus_order; it; it = g_list_next(it)) {
148         c = it->data;
149         /* fallback focus to a window if:
150            1. it is on the current desktop. this ignores omnipresent
151            windows, which are problematic in their own rite.
152            2. it is a normal type window, don't fall back onto a dock or
153            a splashscreen or a desktop window (save the desktop as a
154            backup fallback though)
155         */
156         if (focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE) &&
157             (allow_refocus || client_focus_target(c) != old) &&
158             client_focus(c))
159         {
160             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window\n");
161             return c;
162         }
163     }
164
165     return NULL;
166 }
167
168 ObClient* focus_fallback(gboolean allow_refocus, gboolean allow_pointer,
169                          gboolean allow_omnipresent, gboolean focus_lost)
170 {
171     ObClient *new;
172     ObClient *old = focus_client;
173
174     /* unfocus any focused clients.. they can be focused by Pointer events
175        and such, and then when we try focus them, we won't get a FocusIn
176        event at all for them. */
177     if (focus_lost)
178         focus_nothing();
179
180     new = focus_fallback_target(allow_refocus, allow_pointer,
181                                 allow_omnipresent, old);
182     /* get what was really focused */
183     if (new) new = client_focus_target(new);
184
185     return new;
186 }
187
188 void focus_nothing(void)
189 {
190     /* nothing is focused, update the colormap and _the root property_ */
191     focus_set_client(NULL);
192
193     /* when nothing will be focused, send focus to the backup target */
194     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
195                    event_curtime);
196 }
197
198 void focus_order_add_new(ObClient *c)
199 {
200     if (c->iconic)
201         focus_order_to_top(c);
202     else {
203         g_assert(!g_list_find(focus_order, c));
204         /* if there are only iconic windows, put this above them in the order,
205            but if there are not, then put it under the currently focused one */
206         if (focus_order && ((ObClient*)focus_order->data)->iconic)
207             focus_order = g_list_insert(focus_order, c, 0);
208         else
209             focus_order = g_list_insert(focus_order, c, 1);
210     }
211
212     focus_cycle_addremove(c, TRUE);
213 }
214
215 void focus_order_remove(ObClient *c)
216 {
217     focus_order = g_list_remove(focus_order, c);
218
219     focus_cycle_addremove(c, TRUE);
220 }
221
222 void focus_order_like_new(struct _ObClient *c)
223 {
224     focus_order = g_list_remove(focus_order, c);
225     focus_order_add_new(c);
226 }
227
228 void focus_order_to_top(ObClient *c)
229 {
230     focus_order = g_list_remove(focus_order, c);
231     if (!c->iconic) {
232         focus_order = g_list_prepend(focus_order, c);
233     } else {
234         GList *it;
235
236         /* insert before first iconic window */
237         for (it = focus_order;
238              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
239         focus_order = g_list_insert_before(focus_order, it, c);
240     }
241
242     focus_cycle_reorder();
243 }
244
245 void focus_order_to_bottom(ObClient *c)
246 {
247     focus_order = g_list_remove(focus_order, c);
248     if (c->iconic) {
249         focus_order = g_list_append(focus_order, c);
250     } else {
251         GList *it;
252
253         /* insert before first iconic window */
254         for (it = focus_order;
255              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
256         focus_order = g_list_insert_before(focus_order, it, c);
257     }
258
259     focus_cycle_reorder();
260 }
261
262 ObClient *focus_order_find_first(guint desktop)
263 {
264     GList *it;
265     for (it = focus_order; it; it = g_list_next(it)) {
266         ObClient *c = it->data;
267         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
268             return c;
269     }
270     return NULL;
271 }
272
273 /*! Returns if a focus target has valid group siblings that can be cycled
274   to in its place */
275 static gboolean focus_target_has_siblings(ObClient *ft,
276                                           gboolean iconic_windows,
277                                           gboolean all_desktops)
278
279 {
280     GSList *it;
281
282     if (!ft->group) return FALSE;
283
284     for (it = ft->group->members; it; it = g_slist_next(it)) {
285         ObClient *c = it->data;
286         /* check that it's not a helper window to avoid infinite recursion */
287         if (c != ft && c->type == OB_CLIENT_TYPE_NORMAL &&
288             focus_valid_target(c, TRUE, iconic_windows, all_desktops,
289                                FALSE, FALSE, FALSE))
290         {
291             return TRUE;
292         }
293     }
294     return FALSE;
295 }
296
297 gboolean focus_valid_target(ObClient *ft,
298                             gboolean helper_windows,
299                             gboolean iconic_windows,
300                             gboolean all_desktops,
301                             gboolean dock_windows,
302                             gboolean desktop_windows,
303                             gboolean user_request)
304 {
305     /* NOTE: if any of these things change on a client, then they should call
306        focus_cycle_addremove() to make sure the client is not shown/hidden
307        when it should not be */
308
309     gboolean ok = FALSE;
310
311     /* see if the window is still managed or is going away */
312     if (!ft->managed) return FALSE;
313
314     /* it's on this desktop unless you want all desktops.
315
316        do this check first because it will usually filter out the most
317        windows */
318     ok = (all_desktops || ft->desktop == screen_desktop ||
319           ft->desktop == DESKTOP_ALL);
320
321     /* the window can receive focus somehow */
322     ok = ok && (ft->can_focus || ft->focus_notify);
323
324     /* the window is not iconic, or we're allowed to go to iconic ones */
325     ok = ok && (iconic_windows || !ft->iconic);
326
327     /* it's the right type of window */
328     if (dock_windows || desktop_windows)
329         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
330                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
331     /* modal windows are important and can always get focus if they are
332        visible and stuff, so don't change 'ok' based on their type */
333     else if (!ft->modal)
334         /* normal non-helper windows are valid targets */
335         ok = ok &&
336             ((client_normal(ft) && !client_helper(ft))
337              ||
338              /* helper windows are valid targets if... */
339              (client_helper(ft) &&
340               /* ...a window in its group already has focus and we want to
341                  include helper windows ... */
342               ((focus_client && ft->group == focus_client->group &&
343                 helper_windows) ||
344                /* ... or if there are no other windows in its group
345                   that can be focused instead */
346                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
347
348     /* it's not set to skip the taskbar (but this is overridden if the
349        window is modal or if the user asked for this window to be focused,
350        or if the window is iconified (and does not have any parents with
351        which to uniconify it), and it is not used for windows which are
352        hilited, or dialog windows as these need user interaction and should
353        not be long-lasting windows */
354     ok = ok && (!ft->skip_taskbar ||
355                 (ft->modal || user_request ||
356                  (ft->iconic && !ft->parents) ||
357                  ft->demands_attention ||
358                  ft->type == OB_CLIENT_TYPE_DIALOG));
359
360     /* it's not going to just send focus off somewhere else (modal window),
361        unless that modal window is not one of our valid targets, then let
362        you choose this window and bring the modal one here */
363     {
364         ObClient *cft = client_focus_target(ft);
365         ok = ok && (ft == cft || !focus_valid_target(cft,
366                                                      TRUE,
367                                                      iconic_windows,
368                                                      all_desktops,
369                                                      dock_windows,
370                                                      desktop_windows,
371                                                      FALSE));
372     }
373
374     return ok;
375 }