setenv and unsetenv dont exist in Solaris 9. (Fixes bug #4663)
[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     /* when focus is moved to a new window, the last_user_time timestamp would
105        no longer be valid, as it applies for the focused window */
106     event_reset_user_time();
107 }
108
109 static ObClient* focus_fallback_target(gboolean allow_refocus,
110                                        gboolean allow_pointer,
111                                        gboolean allow_omnipresent,
112                                        ObClient *old)
113 {
114     GList *it;
115     ObClient *c;
116
117     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff");
118     if (allow_pointer && config_focus_follow)
119         if ((c = client_under_pointer()) &&
120             (allow_refocus || client_focus_target(c) != old) &&
121             (client_normal(c) &&
122              client_focus(c)))
123         {
124             ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff");
125             return c;
126         }
127
128     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order");
129     for (it = focus_order; it; it = g_list_next(it)) {
130         c = it->data;
131         /* fallback focus to a window if:
132            1. it is on the current desktop. this ignores omnipresent
133            windows, which are problematic in their own rite, unless they are
134            specifically allowed
135            2. it is a valid auto-focus target
136            3. it is not shaded
137         */
138         if ((allow_omnipresent || c->desktop == screen_desktop) &&
139             focus_valid_target(c, screen_desktop,
140                                TRUE, FALSE, FALSE, TRUE, FALSE, FALSE,
141                                FALSE) &&
142             !c->shaded &&
143             (allow_refocus || client_focus_target(c) != old) &&
144             client_focus(c))
145         {
146             ob_debug_type(OB_DEBUG_FOCUS, "found in focus order");
147             return c;
148         }
149     }
150
151     ob_debug_type(OB_DEBUG_FOCUS, "trying a desktop window");
152     for (it = focus_order; it; it = g_list_next(it)) {
153         c = it->data;
154         /* fallback focus to a window if:
155            1. it is on the current desktop. this ignores omnipresent
156            windows, which are problematic in their own rite.
157            2. it is a normal type window, don't fall back onto a dock or
158            a splashscreen or a desktop window (save the desktop as a
159            backup fallback though)
160         */
161         if (focus_valid_target(c, screen_desktop,
162                                TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE) &&
163             (allow_refocus || client_focus_target(c) != old) &&
164             client_focus(c))
165         {
166             ob_debug_type(OB_DEBUG_FOCUS, "found a desktop window");
167             return c;
168         }
169     }
170
171     return NULL;
172 }
173
174 ObClient* focus_fallback(gboolean allow_refocus, gboolean allow_pointer,
175                          gboolean allow_omnipresent, gboolean focus_lost)
176 {
177     ObClient *new;
178     ObClient *old = focus_client;
179
180     /* unfocus any focused clients.. they can be focused by Pointer events
181        and such, and then when we try focus them, we won't get a FocusIn
182        event at all for them. */
183     if (focus_lost)
184         focus_nothing();
185
186     new = focus_fallback_target(allow_refocus, allow_pointer,
187                                 allow_omnipresent, old);
188     /* get what was really focused */
189     if (new) new = client_focus_target(new);
190
191     return new;
192 }
193
194 void focus_nothing(void)
195 {
196     /* nothing is focused, update the colormap and _the root property_ */
197     focus_set_client(NULL);
198
199     /* when nothing will be focused, send focus to the backup target */
200     XSetInputFocus(obt_display, screen_support_win, RevertToPointerRoot,
201                    event_time());
202 }
203
204 void focus_order_add_new(ObClient *c)
205 {
206     if (c->iconic)
207         focus_order_to_top(c);
208     else {
209         g_assert(!g_list_find(focus_order, c));
210         /* if there are only iconic windows, put this above them in the order,
211            but if there are not, then put it under the currently focused one */
212         if (focus_order && ((ObClient*)focus_order->data)->iconic)
213             focus_order = g_list_insert(focus_order, c, 0);
214         else
215             focus_order = g_list_insert(focus_order, c, 1);
216     }
217
218     focus_cycle_addremove(c, TRUE);
219 }
220
221 void focus_order_remove(ObClient *c)
222 {
223     focus_order = g_list_remove(focus_order, c);
224
225     focus_cycle_addremove(c, TRUE);
226 }
227
228 void focus_order_like_new(struct _ObClient *c)
229 {
230     focus_order = g_list_remove(focus_order, c);
231     focus_order_add_new(c);
232 }
233
234 void focus_order_to_top(ObClient *c)
235 {
236     focus_order = g_list_remove(focus_order, c);
237     if (!c->iconic) {
238         focus_order = g_list_prepend(focus_order, c);
239     } else {
240         GList *it;
241
242         /* insert before first iconic window */
243         for (it = focus_order;
244              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
245         focus_order = g_list_insert_before(focus_order, it, c);
246     }
247
248     focus_cycle_reorder();
249 }
250
251 void focus_order_to_bottom(ObClient *c)
252 {
253     focus_order = g_list_remove(focus_order, c);
254     if (c->iconic) {
255         focus_order = g_list_append(focus_order, c);
256     } else {
257         GList *it;
258
259         /* insert before first iconic window */
260         for (it = focus_order;
261              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
262         focus_order = g_list_insert_before(focus_order, it, c);
263     }
264
265     focus_cycle_reorder();
266 }
267
268 ObClient *focus_order_find_first(guint desktop)
269 {
270     GList *it;
271     for (it = focus_order; it; it = g_list_next(it)) {
272         ObClient *c = it->data;
273         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
274             return c;
275     }
276     return NULL;
277 }
278
279 /*! Returns if a focus target has valid group siblings that can be cycled
280   to in its place */
281 static gboolean focus_target_has_siblings(ObClient *ft,
282                                           gboolean iconic_windows,
283                                           gboolean all_desktops)
284
285 {
286     GSList *it;
287
288     if (!ft->group) return FALSE;
289
290     for (it = ft->group->members; it; it = g_slist_next(it)) {
291         ObClient *c = it->data;
292         /* check that it's not a helper window to avoid infinite recursion */
293         if (c != ft && c->type == OB_CLIENT_TYPE_NORMAL &&
294             focus_valid_target(c, screen_desktop,
295                                TRUE, iconic_windows, all_desktops,
296                                TRUE, FALSE, FALSE, FALSE))
297         {
298             return TRUE;
299         }
300     }
301     return FALSE;
302 }
303
304 gboolean focus_valid_target(ObClient *ft,
305                             guint    desktop,
306                             gboolean helper_windows,
307                             gboolean iconic_windows,
308                             gboolean all_desktops,
309                             gboolean nonhilite_windows,
310                             gboolean dock_windows,
311                             gboolean desktop_windows,
312                             gboolean user_request)
313 {
314     /* NOTE: if any of these things change on a client, then they should call
315        focus_cycle_addremove() to make sure the client is not shown/hidden
316        when it should not be */
317
318     gboolean ok = FALSE;
319
320     /* see if the window is still managed or is going away */
321     if (!ft->managed) return FALSE;
322
323     /* it's on this desktop unless you want all desktops.
324
325        do this check first because it will usually filter out the most
326        windows */
327     ok = (all_desktops || ft->desktop == desktop ||
328           ft->desktop == DESKTOP_ALL);
329
330     /* if we only include hilited windows, check if the window is */
331     ok = ok && (nonhilite_windows || ft->demands_attention);
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 is overridden if the
361        window is modal or if the user asked for this window to be focused,
362        or if the window is iconified (and does not have any parents with
363        which to uniconify it), and it is not used for windows which are
364        hilited, or dialog windows as these need user interaction and should
365        not be long-lasting windows */
366     ok = ok && (!ft->skip_taskbar ||
367                 (ft->modal || user_request ||
368                  (ft->iconic && !ft->parents) ||
369                  ft->demands_attention ||
370                  ft->type == OB_CLIENT_TYPE_DIALOG));
371
372     /* it's not going to just send focus off somewhere else (modal window),
373        unless that modal window is not one of our valid targets, then let
374        you choose this window and bring the modal one here */
375     {
376         ObClient *cft = client_focus_target(ft);
377         ok = ok && (ft == cft || !focus_valid_target(cft,
378                                                      screen_desktop,
379                                                      TRUE,
380                                                      iconic_windows,
381                                                      all_desktops,
382                                                      nonhilite_windows,
383                                                      dock_windows,
384                                                      desktop_windows,
385                                                      FALSE));
386     }
387
388     return ok;
389 }