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