c2d7e11e5dd879d8a776fd7bfb65ce0524d1d3db
[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     /* in the middle of cycling..? kill it. */
87     focus_cycle_stop(focus_client);
88     focus_cycle_stop(client);
89
90     focus_client = client;
91
92     if (client != NULL) {
93         /* move to the top of the list */
94         push_to_top(client);
95         /* remove hiliting from the window when it gets focused */
96         client_hilite(client, FALSE);
97     }
98
99     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
100     if (ob_state() != OB_STATE_EXITING) {
101         active = client ? client->window : None;
102         PROP_SET32(RootWindow(ob_display, ob_screen),
103                    net_active_window, window, active);
104     }
105 }
106
107 static ObClient* focus_fallback_target(gboolean allow_refocus,
108                                        gboolean allow_pointer,
109                                        gboolean allow_omnipresent,
110                                        ObClient *old)
111 {
112     GList *it;
113     ObClient *c;
114
115     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
116     if (allow_pointer && config_focus_follow)
117         if ((c = client_under_pointer()) &&
118             (allow_refocus || client_focus_target(c) != old) &&
119             (client_normal(c) &&
120              client_focus(c)))
121         {
122             ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
123             return c;
124         }
125
126     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
127     for (it = focus_order; it; it = g_list_next(it)) {
128         c = it->data;
129         /* fallback focus to a window if:
130            1. it is on the current desktop. this ignores omnipresent
131            windows, which are problematic in their own rite, unless they are
132            specifically allowed
133            2. it is a valid auto-focus target
134            3. it is not shaded
135         */
136         if ((allow_omnipresent || c->desktop == screen_desktop) &&
137             focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, FALSE) &&
138             !c->shaded &&
139             (allow_refocus || client_focus_target(c) != old) &&
140             client_focus(c))
141         {
142             ob_debug_type(OB_DEBUG_FOCUS, "found in focus order\n");
143             return c;
144         }
145     }
146
147     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window\n");
148     for (it = focus_order; it; it = g_list_next(it)) {
149         c = it->data;
150         /* fallback focus to a window if:
151            1. it is on the current desktop. this ignores omnipresent
152            windows, which are problematic in their own rite.
153            2. it is a normal type window, don't fall back onto a dock or
154            a splashscreen or a desktop window (save the desktop as a
155            backup fallback though)
156         */
157         if (focus_valid_target(c, TRUE, FALSE, FALSE, FALSE, TRUE) &&
158             (allow_refocus || client_focus_target(c) != old) &&
159             client_focus(c))
160         {
161             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window\n");
162             return c;
163         }
164     }
165
166     return NULL;
167 }
168
169 ObClient* focus_fallback(gboolean allow_refocus, gboolean allow_pointer,
170                          gboolean allow_omnipresent, gboolean focus_lost)
171 {
172     ObClient *new;
173     ObClient *old = focus_client;
174
175     /* unfocus any focused clients.. they can be focused by Pointer events
176        and such, and then when we try focus them, we won't get a FocusIn
177        event at all for them. */
178     if (focus_lost)
179         focus_nothing();
180
181     new = focus_fallback_target(allow_refocus, allow_pointer,
182                                 allow_omnipresent, old);
183     /* get what was really focused */
184     if (new) new = client_focus_target(new);
185
186     return new;
187 }
188
189 void focus_nothing(void)
190 {
191     /* Install our own colormap */
192     if (focus_client != NULL) {
193         screen_install_colormap(focus_client, FALSE);
194         screen_install_colormap(NULL, TRUE);
195     }
196
197     /* nothing is focused, update the colormap and _the root property_ */
198     focus_set_client(NULL);
199
200     /* if there is a grab going on, then we need to cancel it. if we move
201        focus during the grab, applications will get NotifyWhileGrabbed events
202        and ignore them !
203
204        actions should not rely on being able to move focus during an
205        interactive grab.
206     */
207     event_cancel_all_key_grabs();
208
209     /* when nothing will be focused, send focus to the backup target */
210     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
211                    event_curtime);
212 }
213
214 void focus_order_add_new(ObClient *c)
215 {
216     if (c->iconic)
217         focus_order_to_top(c);
218     else {
219         g_assert(!g_list_find(focus_order, c));
220         /* if there are any iconic windows, put this above them in the order,
221            but if there are not, then put it under the currently focused one */
222         if (focus_order && ((ObClient*)focus_order->data)->iconic)
223             focus_order = g_list_insert(focus_order, c, 0);
224         else
225             focus_order = g_list_insert(focus_order, c, 1);
226     }
227
228     /* in the middle of cycling..? kill it. */
229     focus_cycle_stop(c);
230 }
231
232 void focus_order_remove(ObClient *c)
233 {
234     focus_order = g_list_remove(focus_order, c);
235
236     /* in the middle of cycling..? kill it. */
237     focus_cycle_stop(c);
238 }
239
240 void focus_order_to_top(ObClient *c)
241 {
242     focus_order = g_list_remove(focus_order, c);
243     if (!c->iconic) {
244         focus_order = g_list_prepend(focus_order, c);
245     } else {
246         GList *it;
247
248         /* insert before first iconic window */
249         for (it = focus_order;
250              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
251         focus_order = g_list_insert_before(focus_order, it, c);
252     }
253 }
254
255 void focus_order_to_bottom(ObClient *c)
256 {
257     focus_order = g_list_remove(focus_order, c);
258     if (c->iconic) {
259         focus_order = g_list_append(focus_order, c);
260     } else {
261         GList *it;
262
263         /* insert before first iconic window */
264         for (it = focus_order;
265              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
266         focus_order = g_list_insert_before(focus_order, it, c);
267     }
268 }
269
270 ObClient *focus_order_find_first(guint desktop)
271 {
272     GList *it;
273     for (it = focus_order; it; it = g_list_next(it)) {
274         ObClient *c = it->data;
275         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
276             return c;
277     }
278     return NULL;
279 }
280
281 /*! Returns if a focus target has valid group siblings that can be cycled
282   to in its place */
283 static gboolean focus_target_has_siblings(ObClient *ft,
284                                           gboolean iconic_windows,
285                                           gboolean all_desktops)
286
287 {
288     GSList *it;
289
290     if (!ft->group) return FALSE;
291
292     for (it = ft->group->members; it; it = g_slist_next(it)) {
293         ObClient *c = it->data;
294         /* check that it's not a helper window to avoid infinite recursion */
295         if (c != ft && c->type == OB_CLIENT_TYPE_NORMAL &&
296             focus_valid_target(c, TRUE, iconic_windows, all_desktops,
297                                FALSE, FALSE))
298         {
299             return TRUE;
300         }
301     }
302     return FALSE;
303 }
304
305 gboolean focus_valid_target(ObClient *ft,
306                             gboolean helper_windows,
307                             gboolean iconic_windows,
308                             gboolean all_desktops,
309                             gboolean dock_windows,
310                             gboolean desktop_windows)
311 {
312     gboolean ok = 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 only applies to normal typed
349        windows, and is overridden if the window is modal) */
350     ok = ok && (ft->type != OB_CLIENT_TYPE_NORMAL ||
351                 ft->modal ||
352                 !ft->skip_taskbar);
353
354     /* it's not going to just send focus off somewhere else (modal window),
355        unless that modal window is not one of our valid targets, then let
356        you choose this window and bring the modal one here */
357     {
358         ObClient *cft = client_focus_target(ft);
359         ok = ok && (ft == cft || !focus_valid_target(cft,
360                                                      TRUE,
361                                                      iconic_windows,
362                                                      all_desktops,
363                                                      dock_windows,
364                                                      desktop_windows));
365     }
366
367     return ok;
368 }
369