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