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