strip leading/trailing whitespace off stuff when reading it from the configs
[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 "focus.h"
31 #include "stacking.h"
32 #include "obt/prop.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(obt_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", 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         OBT_PROP_SET32(obt_root(ob_screen), 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");
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");
122             return c;
123         }
124
125     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order");
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) &&
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");
142             return c;
143         }
144     }
145
146     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window");
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) &&
157             (allow_refocus || client_focus_target(c) != old) &&
158             client_focus(c))
159         {
160             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window");
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     /* Install our own colormap */
191     if (focus_client != NULL) {
192         screen_install_colormap(focus_client, FALSE);
193         screen_install_colormap(NULL, TRUE);
194     }
195
196     /* nothing is focused, update the colormap and _the root property_ */
197     focus_set_client(NULL);
198
199     event_cancel_all_key_grabs();
200
201     /* when nothing will be focused, send focus to the backup target */
202     XSetInputFocus(obt_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 (but this only applies to normal typed
341        windows, and is overridden if the window is modal) */
342     ok = ok && (ft->type != OB_CLIENT_TYPE_NORMAL ||
343                 ft->modal ||
344                 !ft->skip_taskbar);
345
346     /* it's not going to just send focus off somewhere else (modal window),
347        unless that modal window is not one of our valid targets, then let
348        you choose this window and bring the modal one here */
349     {
350         ObClient *cft = client_focus_target(ft);
351         ok = ok && (ft == cft || !focus_valid_target(cft,
352                                                      TRUE,
353                                                      iconic_windows,
354                                                      all_desktops,
355                                                      dock_windows,
356                                                      desktop_windows));
357     }
358
359     return ok;
360 }