Redraw the focus cycle popup when the list of focusable windows changes, rather than...
[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 "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     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         PROP_SET32(RootWindow(ob_display, ob_screen),
99                    net_active_window, window, active);
100     }
101
102     /* make sure the focus cycle popup shows things in the right order */
103     focus_cycle_reorder();
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\n");
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\n");
122             return c;
123         }
124
125     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
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, 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\n");
142             return c;
143         }
144     }
145
146     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window\n");
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, FALSE) &&
157             (allow_refocus || client_focus_target(c) != old) &&
158             client_focus(c))
159         {
160             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window\n");
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     /* nothing is focused, update the colormap and _the root property_ */
191     focus_set_client(NULL);
192
193     /* when nothing will be focused, send focus to the backup target */
194     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
195                    event_curtime);
196 }
197
198 void focus_order_add_new(ObClient *c)
199 {
200     if (c->iconic)
201         focus_order_to_top(c);
202     else {
203         g_assert(!g_list_find(focus_order, c));
204         /* if there are any iconic windows, put this above them in the order,
205            but if there are not, then put it under the currently focused one */
206         if (focus_order && ((ObClient*)focus_order->data)->iconic)
207             focus_order = g_list_insert(focus_order, c, 0);
208         else
209             focus_order = g_list_insert(focus_order, c, 1);
210     }
211
212     focus_cycle_add(c);
213 }
214
215 void focus_order_remove(ObClient *c)
216 {
217     focus_order = g_list_remove(focus_order, c);
218
219     focus_cycle_remove(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, TRUE, iconic_windows, all_desktops,
279                                FALSE, FALSE, FALSE))
280         {
281             return TRUE;
282         }
283     }
284     return FALSE;
285 }
286
287 gboolean focus_valid_target(ObClient *ft,
288                             gboolean helper_windows,
289                             gboolean iconic_windows,
290                             gboolean all_desktops,
291                             gboolean dock_windows,
292                             gboolean desktop_windows,
293                             gboolean user_request)
294 {
295     gboolean ok = FALSE;
296
297     /* see if the window is still managed or is going away */
298     if (!ft->managed) return FALSE;
299
300     /* it's on this desktop unless you want all desktops.
301
302        do this check first because it will usually filter out the most
303        windows */
304     ok = (all_desktops || ft->desktop == screen_desktop ||
305           ft->desktop == DESKTOP_ALL);
306
307     /* the window can receive focus somehow */
308     ok = ok && (ft->can_focus || ft->focus_notify);
309
310     /* the window is not iconic, or we're allowed to go to iconic ones */
311     ok = ok && (iconic_windows || !ft->iconic);
312
313     /* it's the right type of window */
314     if (dock_windows || desktop_windows)
315         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
316                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
317     /* modal windows are important and can always get focus if they are
318        visible and stuff, so don't change 'ok' based on their type */
319     else if (!ft->modal)
320         /* normal non-helper windows are valid targets */
321         ok = ok &&
322             ((client_normal(ft) && !client_helper(ft))
323              ||
324              /* helper windows are valid targets if... */
325              (client_helper(ft) &&
326               /* ...a window in its group already has focus and we want to
327                  include helper windows ... */
328               ((focus_client && ft->group == focus_client->group &&
329                 helper_windows) ||
330                /* ... or if there are no other windows in its group
331                   that can be focused instead */
332                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
333
334     /* it's not set to skip the taskbar (but this only applies to normal typed
335        windows, and is overridden if the window is modal or if the user asked
336        for this window to be focused) */
337     ok = ok && (ft->type != OB_CLIENT_TYPE_NORMAL ||
338                 ft->modal ||
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                                                      TRUE,
349                                                      iconic_windows,
350                                                      all_desktops,
351                                                      dock_windows,
352                                                      desktop_windows,
353                                                      FALSE));
354     }
355
356     return ok;
357 }