Merge branch 'backport' into work
[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         /* make sure the focus cycle popup shows things in the right order */
95         focus_cycle_reorder();
96     }
97
98     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
99     if (ob_state() != OB_STATE_EXITING) {
100         active = client ? client->window : None;
101         OBT_PROP_SET32(obt_root(ob_screen), NET_ACTIVE_WINDOW, WINDOW, active);
102     }
103 }
104
105 static ObClient* focus_fallback_target(gboolean allow_refocus,
106                                        gboolean allow_pointer,
107                                        gboolean allow_omnipresent,
108                                        ObClient *old)
109 {
110     GList *it;
111     ObClient *c;
112
113     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff");
114     if (allow_pointer && config_focus_follow)
115         if ((c = client_under_pointer()) &&
116             (allow_refocus || client_focus_target(c) != old) &&
117             (client_normal(c) &&
118              client_focus(c)))
119         {
120             ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff");
121             return c;
122         }
123
124     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order");
125     for (it = focus_order; it; it = g_list_next(it)) {
126         c = it->data;
127         /* fallback focus to a window if:
128            1. it is on the current desktop. this ignores omnipresent
129            windows, which are problematic in their own rite, unless they are
130            specifically allowed
131            2. it is a valid auto-focus target
132            3. it is not shaded
133         */
134         if ((allow_omnipresent || c->desktop == screen_desktop) &&
135             focus_valid_target(c, screen_desktop,
136                                TRUE, FALSE, 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, screen_desktop,
157                                TRUE, FALSE, FALSE, FALSE, TRUE, FALSE) &&
158             (allow_refocus || client_focus_target(c) != old) &&
159             client_focus(c))
160         {
161             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window");
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     /* nothing is focused, update the colormap and _the root property_ */
192     focus_set_client(NULL);
193
194     /* when nothing will be focused, send focus to the backup target */
195     XSetInputFocus(obt_display, screen_support_win, RevertToPointerRoot,
196                    event_curtime);
197 }
198
199 void focus_order_add_new(ObClient *c)
200 {
201     if (c->iconic)
202         focus_order_to_top(c);
203     else {
204         g_assert(!g_list_find(focus_order, c));
205         /* if there are only iconic windows, put this above them in the order,
206            but if there are not, then put it under the currently focused one */
207         if (focus_order && ((ObClient*)focus_order->data)->iconic)
208             focus_order = g_list_insert(focus_order, c, 0);
209         else
210             focus_order = g_list_insert(focus_order, c, 1);
211     }
212
213     focus_cycle_addremove(c, TRUE);
214 }
215
216 void focus_order_remove(ObClient *c)
217 {
218     focus_order = g_list_remove(focus_order, c);
219
220     focus_cycle_addremove(c, TRUE);
221 }
222
223 void focus_order_like_new(struct _ObClient *c)
224 {
225     focus_order = g_list_remove(focus_order, c);
226     focus_order_add_new(c);
227 }
228
229 void focus_order_to_top(ObClient *c)
230 {
231     focus_order = g_list_remove(focus_order, c);
232     if (!c->iconic) {
233         focus_order = g_list_prepend(focus_order, c);
234     } else {
235         GList *it;
236
237         /* insert before first iconic window */
238         for (it = focus_order;
239              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
240         focus_order = g_list_insert_before(focus_order, it, c);
241     }
242
243     focus_cycle_reorder();
244 }
245
246 void focus_order_to_bottom(ObClient *c)
247 {
248     focus_order = g_list_remove(focus_order, c);
249     if (c->iconic) {
250         focus_order = g_list_append(focus_order, c);
251     } else {
252         GList *it;
253
254         /* insert before first iconic window */
255         for (it = focus_order;
256              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
257         focus_order = g_list_insert_before(focus_order, it, c);
258     }
259
260     focus_cycle_reorder();
261 }
262
263 ObClient *focus_order_find_first(guint desktop)
264 {
265     GList *it;
266     for (it = focus_order; it; it = g_list_next(it)) {
267         ObClient *c = it->data;
268         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
269             return c;
270     }
271     return NULL;
272 }
273
274 /*! Returns if a focus target has valid group siblings that can be cycled
275   to in its place */
276 static gboolean focus_target_has_siblings(ObClient *ft,
277                                           gboolean iconic_windows,
278                                           gboolean all_desktops)
279
280 {
281     GSList *it;
282
283     if (!ft->group) return FALSE;
284
285     for (it = ft->group->members; it; it = g_slist_next(it)) {
286         ObClient *c = it->data;
287         /* check that it's not a helper window to avoid infinite recursion */
288         if (c != ft && c->type == OB_CLIENT_TYPE_NORMAL &&
289             focus_valid_target(c, screen_desktop,
290                                TRUE, iconic_windows, all_desktops,
291                                FALSE, FALSE, FALSE))
292         {
293             return TRUE;
294         }
295     }
296     return FALSE;
297 }
298
299 gboolean focus_valid_target(ObClient *ft,
300                             guint    desktop,
301                             gboolean helper_windows,
302                             gboolean iconic_windows,
303                             gboolean all_desktops,
304                             gboolean dock_windows,
305                             gboolean desktop_windows,
306                             gboolean user_request)
307 {
308     /* NOTE: if any of these things change on a client, then they should call
309        focus_cycle_addremove() to make sure the client is not shown/hidden
310        when it should not be */
311
312     gboolean ok = FALSE;
313
314     /* see if the window is still managed or is going away */
315     if (!ft->managed) return FALSE;
316
317     /* it's on this desktop unless you want all desktops.
318
319        do this check first because it will usually filter out the most
320        windows */
321     ok = (all_desktops || ft->desktop == desktop ||
322           ft->desktop == DESKTOP_ALL);
323
324     /* the window can receive focus somehow */
325     ok = ok && (ft->can_focus || ft->focus_notify);
326
327     /* the window is not iconic, or we're allowed to go to iconic ones */
328     ok = ok && (iconic_windows || !ft->iconic);
329
330     /* it's the right type of window */
331     if (dock_windows || desktop_windows)
332         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
333                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
334     /* modal windows are important and can always get focus if they are
335        visible and stuff, so don't change 'ok' based on their type */
336     else if (!ft->modal)
337         /* normal non-helper windows are valid targets */
338         ok = ok &&
339             ((client_normal(ft) && !client_helper(ft))
340              ||
341              /* helper windows are valid targets if... */
342              (client_helper(ft) &&
343               /* ...a window in its group already has focus and we want to
344                  include helper windows ... */
345               ((focus_client && ft->group == focus_client->group &&
346                 helper_windows) ||
347                /* ... or if there are no other windows in its group
348                   that can be focused instead */
349                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
350
351     /* it's not set to skip the taskbar (but this only applies to normal typed
352        windows, and is overridden if the window is modal or if the user asked
353        for this window to be focused, or if the window is iconified) */
354     ok = ok && (ft->type != OB_CLIENT_TYPE_NORMAL ||
355                 ft->modal ||
356                 ft->iconic ||
357                 user_request ||
358                 !ft->skip_taskbar);
359
360     /* it's not going to just send focus off somewhere else (modal window),
361        unless that modal window is not one of our valid targets, then let
362        you choose this window and bring the modal one here */
363     {
364         ObClient *cft = client_focus_target(ft);
365         ok = ok && (ft == cft || !focus_valid_target(cft,
366                                                      screen_desktop,
367                                                      TRUE,
368                                                      iconic_windows,
369                                                      all_desktops,
370                                                      dock_windows,
371                                                      desktop_windows,
372                                                      FALSE));
373     }
374
375     return ok;
376 }