Merge branch 'backport'
[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     focus_order = g_list_remove(focus_order, client);
61     focus_order = g_list_prepend(focus_order, client);
62 }
63
64 void focus_set_client(ObClient *client)
65 {
66     Window active;
67
68     ob_debug_type(OB_DEBUG_FOCUS,
69                   "focus_set_client 0x%lx\n", client ? client->window : 0);
70
71     if (focus_client == client)
72         return;
73
74     /* uninstall the old colormap, and install the new one */
75     screen_install_colormap(focus_client, FALSE);
76     screen_install_colormap(client, TRUE);
77
78     /* in the middle of cycling..? kill it. */
79     focus_cycle_stop(focus_client);
80     focus_cycle_stop(client);
81
82     focus_client = client;
83
84     if (client != NULL) {
85         /* move to the top of the list */
86         push_to_top(client);
87         /* remove hiliting from the window when it gets focused */
88         client_hilite(client, FALSE);
89     }
90
91     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
92     if (ob_state() != OB_STATE_EXITING) {
93         active = client ? client->window : None;
94         PROP_SET32(RootWindow(ob_display, ob_screen),
95                    net_active_window, window, active);
96     }
97 }
98
99 static ObClient* focus_fallback_target(gboolean allow_refocus,
100                                        gboolean allow_pointer,
101                                        gboolean allow_omnipresent,
102                                        ObClient *old)
103 {
104     GList *it;
105     ObClient *c;
106
107     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
108     if (allow_pointer && config_focus_follow)
109         if ((c = client_under_pointer()) &&
110             (allow_refocus || client_focus_target(c) != old) &&
111             (client_normal(c) &&
112              client_focus(c)))
113         {
114             ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
115             return c;
116         }
117
118     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
119     for (it = focus_order; it; it = g_list_next(it)) {
120         c = it->data;
121         /* fallback focus to a window if:
122            1. it is on the current desktop. this ignores omnipresent
123            windows, which are problematic in their own rite, unless they are
124            specifically allowed
125            2. it is a valid auto-focus target
126            3. it is not shaded
127         */
128         if ((allow_omnipresent || c->desktop == screen_desktop) &&
129             focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, FALSE) &&
130             !c->shaded &&
131             (allow_refocus || client_focus_target(c) != old) &&
132             client_focus(c))
133         {
134             ob_debug_type(OB_DEBUG_FOCUS, "found in focus order\n");
135             return c;
136         }
137     }
138
139     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window\n");
140     for (it = focus_order; it; it = g_list_next(it)) {
141         c = it->data;
142         /* fallback focus to a window if:
143            1. it is on the current desktop. this ignores omnipresent
144            windows, which are problematic in their own rite.
145            2. it is a normal type window, don't fall back onto a dock or
146            a splashscreen or a desktop window (save the desktop as a
147            backup fallback though)
148         */
149         if (focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, TRUE) &&
150             (allow_refocus || client_focus_target(c) != old) &&
151             client_focus(c))
152         {
153             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window\n");
154             return c;
155         }
156     }
157
158     return NULL;
159 }
160
161 ObClient* focus_fallback(gboolean allow_refocus, gboolean allow_pointer,
162                          gboolean allow_omnipresent, gboolean focus_lost)
163 {
164     ObClient *new;
165     ObClient *old = focus_client;
166
167     /* unfocus any focused clients.. they can be focused by Pointer events
168        and such, and then when we try focus them, we won't get a FocusIn
169        event at all for them. */
170     if (focus_lost)
171         focus_nothing();
172
173     new = focus_fallback_target(allow_refocus, allow_pointer,
174                                 allow_omnipresent, old);
175     /* get what was really focused */
176     if (new) new = client_focus_target(new);
177
178     return new;
179 }
180
181 void focus_nothing()
182 {
183     /* Install our own colormap */
184     if (focus_client != NULL) {
185         screen_install_colormap(focus_client, FALSE);
186         screen_install_colormap(NULL, TRUE);
187     }
188
189     /* nothing is focused, update the colormap and _the root property_ */
190     focus_set_client(NULL);
191
192     /* if there is a grab going on, then we need to cancel it. if we move
193        focus during the grab, applications will get NotifyWhileGrabbed events
194        and ignore them !
195
196        actions should not rely on being able to move focus during an
197        interactive grab.
198     */
199     event_cancel_all_key_grabs();
200
201     /* when nothing will be focused, send focus to the backup target */
202     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
203                    event_curtime);
204 }
205
206 void focus_order_add_new(ObClient *c)
207 {
208     if (c->iconic)
209         focus_order_to_top(c);
210     else {
211         g_assert(!g_list_find(focus_order, c));
212         /* if there are any iconic windows, put this above them in the order,
213            but if there are not, then put it under the currently focused one */
214         if (focus_order && ((ObClient*)focus_order->data)->iconic)
215             focus_order = g_list_insert(focus_order, c, 0);
216         else
217             focus_order = g_list_insert(focus_order, c, 1);
218     }
219
220     /* in the middle of cycling..? kill it. */
221     focus_cycle_stop(c);
222 }
223
224 void focus_order_remove(ObClient *c)
225 {
226     focus_order = g_list_remove(focus_order, c);
227
228     /* in the middle of cycling..? kill it. */
229     focus_cycle_stop(c);
230 }
231
232 void focus_order_to_top(ObClient *c)
233 {
234     focus_order = g_list_remove(focus_order, c);
235     if (!c->iconic) {
236         focus_order = g_list_prepend(focus_order, c);
237     } else {
238         GList *it;
239
240         /* insert before first iconic window */
241         for (it = focus_order;
242              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
243         focus_order = g_list_insert_before(focus_order, it, c);
244     }
245 }
246
247 void focus_order_to_bottom(ObClient *c)
248 {
249     focus_order = g_list_remove(focus_order, c);
250     if (c->iconic) {
251         focus_order = g_list_append(focus_order, c);
252     } else {
253         GList *it;
254
255         /* insert before first iconic window */
256         for (it = focus_order;
257              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
258         focus_order = g_list_insert_before(focus_order, it, c);
259     }
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))
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 {
304     gboolean ok = FALSE;
305
306     /* it's on this desktop unless you want all desktops.
307
308        do this check first because it will usually filter out the most
309        windows */
310     ok = (all_desktops || ft->desktop == screen_desktop ||
311           ft->desktop == DESKTOP_ALL);
312
313     /* the window can receive focus somehow */
314     ok = ok && (ft->can_focus || ft->focus_notify);
315
316     /* the window is not iconic, or we're allowed to go to iconic ones */
317     ok = ok && (iconic_windows || !ft->iconic);
318
319     /* it's the right type of window */
320     if (dock_windows || desktop_windows)
321         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
322                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
323     /* modal windows are important and can always get focus if they are
324        visible and stuff, so don't change 'ok' based on their type */
325     else if (!ft->modal)
326         /* normal non-helper windows are valid targets */
327         ok = ok &&
328             ((client_normal(ft) && !client_helper(ft))
329              ||
330              /* helper windows are valid targets if... */
331              (client_helper(ft) &&
332               /* ...a window in its group already has focus and we want to
333                  include helper windows ... */
334               ((focus_client && ft->group == focus_client->group &&
335                 helper_windows) ||
336                /* ... or if there are no other windows in its group
337                   that can be focused instead */
338                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
339
340     /* it's not set to skip the taskbar (unless it is a type that would be
341        expected to set this hint, or modal) */
342     ok = ok && ((ft->type == OB_CLIENT_TYPE_DOCK ||
343                  ft->type == OB_CLIENT_TYPE_DESKTOP ||
344                  ft->type == OB_CLIENT_TYPE_TOOLBAR ||
345                  ft->type == OB_CLIENT_TYPE_MENU ||
346                  ft->type == OB_CLIENT_TYPE_UTILITY) ||
347                 ft->modal ||
348                 !ft->skip_taskbar);
349
350     /* it's not going to just send focus off somewhere else (modal window),
351        unless that modal window is not one of our valid targets, then let
352        you choose this window and bring the modal one here */
353     {
354         ObClient *cft = client_focus_target(ft);
355         ok = ok && (ft == cft || !focus_valid_target(cft,
356                                                      TRUE,
357                                                      iconic_windows,
358                                                      all_desktops,
359                                                      dock_windows,
360                                                      desktop_windows));
361     }
362
363     return ok;
364 }
365