]> icculus.org git repositories - dana/openbox.git/blob - openbox/focus.c
make the prompt buttons respond to button presses. keyboard input code is there too...
[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     /* if there is a grab going on, then we need to cancel it. if we move
200        focus during the grab, applications will get NotifyWhileGrabbed events
201        and ignore them !
202
203        actions should not rely on being able to move focus during an
204        interactive grab.
205     */
206     event_cancel_all_key_grabs();
207
208     /* when nothing will be focused, send focus to the backup target */
209     XSetInputFocus(obt_display, screen_support_win, RevertToPointerRoot,
210                    event_curtime);
211 }
212
213 void focus_order_add_new(ObClient *c)
214 {
215     if (c->iconic)
216         focus_order_to_top(c);
217     else {
218         g_assert(!g_list_find(focus_order, c));
219         /* if there are any iconic windows, put this above them in the order,
220            but if there are not, then put it under the currently focused one */
221         if (focus_order && ((ObClient*)focus_order->data)->iconic)
222             focus_order = g_list_insert(focus_order, c, 0);
223         else
224             focus_order = g_list_insert(focus_order, c, 1);
225     }
226
227     /* in the middle of cycling..? kill it. */
228     focus_cycle_stop(c);
229 }
230
231 void focus_order_remove(ObClient *c)
232 {
233     focus_order = g_list_remove(focus_order, c);
234
235     /* in the middle of cycling..? kill it. */
236     focus_cycle_stop(c);
237 }
238
239 void focus_order_to_top(ObClient *c)
240 {
241     focus_order = g_list_remove(focus_order, c);
242     if (!c->iconic) {
243         focus_order = g_list_prepend(focus_order, c);
244     } else {
245         GList *it;
246
247         /* insert before first iconic window */
248         for (it = focus_order;
249              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
250         focus_order = g_list_insert_before(focus_order, it, c);
251     }
252 }
253
254 void focus_order_to_bottom(ObClient *c)
255 {
256     focus_order = g_list_remove(focus_order, c);
257     if (c->iconic) {
258         focus_order = g_list_append(focus_order, c);
259     } else {
260         GList *it;
261
262         /* insert before first iconic window */
263         for (it = focus_order;
264              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
265         focus_order = g_list_insert_before(focus_order, it, c);
266     }
267 }
268
269 ObClient *focus_order_find_first(guint desktop)
270 {
271     GList *it;
272     for (it = focus_order; it; it = g_list_next(it)) {
273         ObClient *c = it->data;
274         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
275             return c;
276     }
277     return NULL;
278 }
279
280 /*! Returns if a focus target has valid group siblings that can be cycled
281   to in its place */
282 static gboolean focus_target_has_siblings(ObClient *ft,
283                                           gboolean iconic_windows,
284                                           gboolean all_desktops)
285
286 {
287     GSList *it;
288
289     if (!ft->group) return FALSE;
290
291     for (it = ft->group->members; it; it = g_slist_next(it)) {
292         ObClient *c = it->data;
293         /* check that it's not a helper window to avoid infinite recursion */
294         if (c != ft && c->type == OB_CLIENT_TYPE_NORMAL &&
295             focus_valid_target(c, TRUE, iconic_windows, all_desktops,
296                                FALSE, FALSE))
297         {
298             return TRUE;
299         }
300     }
301     return FALSE;
302 }
303
304 gboolean focus_valid_target(ObClient *ft,
305                             gboolean helper_windows,
306                             gboolean iconic_windows,
307                             gboolean all_desktops,
308                             gboolean dock_windows,
309                             gboolean desktop_windows)
310 {
311     gboolean ok = FALSE;
312
313     /* it's on this desktop unless you want all desktops.
314
315        do this check first because it will usually filter out the most
316        windows */
317     ok = (all_desktops || ft->desktop == screen_desktop ||
318           ft->desktop == DESKTOP_ALL);
319
320     /* the window can receive focus somehow */
321     ok = ok && (ft->can_focus || ft->focus_notify);
322
323     /* the window is not iconic, or we're allowed to go to iconic ones */
324     ok = ok && (iconic_windows || !ft->iconic);
325
326     /* it's the right type of window */
327     if (dock_windows || desktop_windows)
328         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
329                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
330     /* modal windows are important and can always get focus if they are
331        visible and stuff, so don't change 'ok' based on their type */
332     else if (!ft->modal)
333         /* normal non-helper windows are valid targets */
334         ok = ok &&
335             ((client_normal(ft) && !client_helper(ft))
336              ||
337              /* helper windows are valid targets if... */
338              (client_helper(ft) &&
339               /* ...a window in its group already has focus and we want to
340                  include helper windows ... */
341               ((focus_client && ft->group == focus_client->group &&
342                 helper_windows) ||
343                /* ... or if there are no other windows in its group
344                   that can be focused instead */
345                !focus_target_has_siblings(ft, iconic_windows, all_desktops))));
346
347     /* it's not set to skip the taskbar (but this only applies to normal typed
348        windows, and is overridden if the window is modal) */
349     ok = ok && (ft->type != OB_CLIENT_TYPE_NORMAL ||
350                 ft->modal ||
351                 !ft->skip_taskbar);
352
353     /* it's not going to just send focus off somewhere else (modal window),
354        unless that modal window is not one of our valid targets, then let
355        you choose this window and bring the modal one here */
356     {
357         ObClient *cft = client_focus_target(ft);
358         ok = ok && (ft == cft || !focus_valid_target(cft,
359                                                      TRUE,
360                                                      iconic_windows,
361                                                      all_desktops,
362                                                      dock_windows,
363                                                      desktop_windows));
364     }
365
366     return ok;
367 }
368