]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
skip windows in the alt-tabbing which have the SKIP_TASKBAR hint set on them
[mikachu/openbox.git] / openbox / focus.c
1 #include "debug.h"
2 #include "event.h"
3 #include "openbox.h"
4 #include "grab.h"
5 #include "framerender.h"
6 #include "client.h"
7 #include "config.h"
8 #include "frame.h"
9 #include "screen.h"
10 #include "group.h"
11 #include "prop.h"
12 #include "dispatch.h"
13 #include "focus.h"
14 #include "stacking.h"
15 #include "popup.h"
16
17 #include <X11/Xlib.h>
18 #include <glib.h>
19 #include <assert.h>
20
21 ObClient *focus_client;
22 GList **focus_order; /* these lists are created when screen_startup
23                         sets the number of desktops */
24
25 static ObClient *focus_cycle_target;
26 static Popup *focus_cycle_popup;
27
28 void focus_startup()
29 {
30
31     focus_cycle_popup = popup_new(TRUE);
32
33     /* start with nothing focused */
34     focus_set_client(NULL);
35 }
36
37 void focus_shutdown()
38 {
39     guint i;
40
41     for (i = 0; i < screen_num_desktops; ++i)
42         g_list_free(focus_order[i]);
43     g_free(focus_order);
44
45     popup_free(focus_cycle_popup);
46
47     /* reset focus to root */
48     XSetInputFocus(ob_display, PointerRoot, RevertToPointerRoot,
49                    event_lasttime);
50 }
51
52 static void push_to_top(ObClient *client)
53 {
54     guint desktop;
55
56     desktop = client->desktop;
57     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
58     focus_order[desktop] = g_list_remove(focus_order[desktop], client);
59     focus_order[desktop] = g_list_prepend(focus_order[desktop], client);
60 }
61
62 void focus_set_client(ObClient *client)
63 {
64     Window active;
65     ObClient *old;
66
67 #ifdef DEBUG_FOCUS
68     ob_debug("focus_set_client 0x%lx\n", client ? client->window : 0);
69 #endif
70
71     /* uninstall the old colormap, and install the new one */
72     screen_install_colormap(focus_client, FALSE);
73     screen_install_colormap(client, TRUE);
74
75     if (client == NULL) {
76         /* when nothing will be focused, send focus to the backup target */
77         XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
78                        event_lasttime);
79         XSync(ob_display, FALSE);
80     }
81
82     /* in the middle of cycling..? kill it. */
83     if (focus_cycle_target)
84         focus_cycle(TRUE, TRUE, TRUE, TRUE);
85
86     old = focus_client;
87     focus_client = client;
88
89     /* move to the top of the list */
90     if (client != NULL)
91         push_to_top(client);
92
93     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
94     if (ob_state() != OB_STATE_EXITING) {
95         active = client ? client->window : None;
96         PROP_SET32(RootWindow(ob_display, ob_screen),
97                    net_active_window, window, active);
98     }
99
100     if (focus_client != NULL)
101         dispatch_client(Event_Client_Focus, focus_client, 0, 0);
102     if (old != NULL)
103         dispatch_client(Event_Client_Unfocus, old, 0, 0);
104 }
105
106 static gboolean focus_under_pointer()
107 {
108     int x, y;
109     GList *it;
110
111     if (screen_pointer_pos(&x, &y)) {
112         for (it = stacking_list; it != NULL; it = it->next) {
113             if (WINDOW_IS_CLIENT(it->data)) {
114                 ObClient *c = WINDOW_AS_CLIENT(it->data);
115                 if (c->desktop == screen_desktop &&
116                     RECT_CONTAINS(c->frame->area, x, y))
117                     break;
118             }
119         }
120         if (it != NULL) {
121             g_assert(WINDOW_IS_CLIENT(it->data));
122             return client_normal(it->data) && client_focus(it->data);
123         }
124     }
125     return FALSE;
126 }
127
128 /* finds the first transient that isn't 'skip' and ensure's that client_normal
129  is true for it */
130 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
131 {
132     GSList *it;
133     ObClient *ret;
134
135     for (it = c->transients; it; it = it->next) {
136         if (it->data == top) return NULL;
137         ret = find_transient_recursive(it->data, top, skip);
138         if (ret && ret != skip && client_normal(ret)) return ret;
139         if (it->data != skip && client_normal(it->data)) return it->data;
140     }
141     return NULL;
142 }
143
144 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
145 {
146     ObClient *target = find_transient_recursive(top, top, old);
147     if (!target) {
148         /* make sure client_normal is true always */
149         if (!client_normal(top))
150             return FALSE;
151         target = top; /* no transient, keep the top */
152     }
153     return client_focus(target);
154 }
155
156 void focus_fallback(ObFocusFallbackType type)
157 {
158     GList *it;
159     ObClient *old = NULL;
160
161     old = focus_client;
162
163     /* unfocus any focused clients.. they can be focused by Pointer events
164        and such, and then when I try focus them, I won't get a FocusIn event
165        at all for them.
166     */
167     focus_set_client(NULL);
168
169     if (!(type == OB_FOCUS_FALLBACK_DESKTOP ?
170           config_focus_last_on_desktop : config_focus_last)) {
171         if (config_focus_follow) focus_under_pointer();
172         return;
173     }
174
175     if (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
176         /* try for transient relations */
177         if (old->transient_for) {
178             if (old->transient_for == OB_TRAN_GROUP) {
179                 for (it = focus_order[screen_desktop]; it; it = it->next) {
180                     GSList *sit;
181
182                     for (sit = old->group->members; sit; sit = sit->next)
183                         if (sit->data == it->data)
184                             if (focus_fallback_transient(sit->data, old))
185                                 return;
186                 }
187             } else {
188                 if (focus_fallback_transient(old->transient_for, old))
189                     return;
190             }
191         }
192
193         /* try for group relations */
194         if (old->group) {
195             GSList *sit;
196
197             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
198                 for (sit = old->group->members; sit; sit = sit->next)
199                     if (sit->data == it->data)
200                         if (sit->data != old && client_normal(sit->data))
201                             if (client_can_focus(sit->data)) {
202                                 gboolean r = client_focus(sit->data);
203                                 assert(r);
204                                 return;
205                             }
206         }
207     }
208
209     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
210         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
211             if (client_normal(it->data) &&
212                 /* dont fall back to 'anonymous' fullscreen windows. theres no
213                    checks for this is in transient/group fallbacks, so they can
214                    be fallback targets there. */
215                 !((ObClient*)it->data)->fullscreen &&
216                 client_can_focus(it->data)) {
217                 gboolean r = client_focus(it->data);
218                 assert(r);
219                 return;
220             }
221
222     /* nothing to focus, and already set it to none above */
223 }
224
225 static void popup_cycle(ObClient *c, gboolean show)
226 {
227     if (!show) {
228         popup_hide(focus_cycle_popup);
229     } else {
230         Rect *a;
231         ObClient *p = c;
232         char *title;
233
234         a = screen_physical_area_monitor(0);
235         popup_position(focus_cycle_popup, CenterGravity,
236                        a->x + a->width / 2, a->y + a->height / 2);
237 /*        popup_size(focus_cycle_popup, a->height/2, a->height/16);
238         popup_show(focus_cycle_popup, c->title,
239                    client_icon(c, a->height/16, a->height/16));
240 */
241         /* XXX the size and the font extents need to be related on some level
242          */
243         popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
244
245         /* use the transient's parent's title/icon */
246         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
247             p = p->transient_for;
248
249         if (p == c)
250             title = NULL;
251         else
252             title = g_strconcat((c->iconic ? c->icon_title : c->title),
253                                 " - ",
254                                 (p->iconic ? p->icon_title : p->title),
255                                 NULL);
256
257         popup_show(focus_cycle_popup,
258                    (title ? title : (c->iconic ? c->icon_title : c->title)),
259                    client_icon(p, 48, 48));
260         g_free(title);
261     }
262 }
263
264 ObClient *focus_cycle(gboolean forward, gboolean linear, gboolean done,
265                       gboolean cancel)
266 {
267     static ObClient *first = NULL;
268     static ObClient *t = NULL;
269     static GList *order = NULL;
270     GList *it, *start, *list;
271     ObClient *ft;
272
273     if (cancel) {
274         if (focus_cycle_target)
275             frame_adjust_focus(focus_cycle_target->frame, FALSE);
276         if (focus_client)
277             frame_adjust_focus(focus_client->frame, TRUE);
278         goto done_cycle;
279     } else if (done) {
280         if (focus_cycle_target)
281             client_activate(focus_cycle_target, FALSE);
282         goto done_cycle;
283     }
284
285     if (!first) first = focus_client;
286     if (!focus_cycle_target) focus_cycle_target = focus_client;
287
288     if (linear) list = client_list;
289     else        list = focus_order[screen_desktop];
290
291     start = it = g_list_find(list, focus_cycle_target);
292     if (!start) /* switched desktops or something? */
293         start = it = forward ? g_list_last(list) : g_list_first(list);
294     if (!start) goto done_cycle;
295
296     do {
297         if (forward) {
298             it = it->next;
299             if (it == NULL) it = g_list_first(list);
300         } else {
301             it = it->prev;
302             if (it == NULL) it = g_list_last(list);
303         }
304         /*ft = client_focus_target(it->data);*/
305         ft = it->data;
306         /* we don't use client_can_focus here, because that doesn't let you
307            focus an iconic window, but we want to be able to, so we just check
308            if the focus flags on the window allow it, and its on the current
309            desktop */
310         if (ft->transients == NULL && client_normal(ft) &&
311             ((ft->can_focus || ft->focus_notify) &&
312              !ft->skip_taskbar &&
313              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
314             if (ft != focus_cycle_target) { /* prevents flicker */
315                 if (focus_cycle_target)
316                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
317                 focus_cycle_target = ft;
318                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
319             }
320             popup_cycle(ft, config_focus_popup);
321             return ft;
322         }
323     } while (it != start);
324
325 done_cycle:
326     t = NULL;
327     first = NULL;
328     focus_cycle_target = NULL;
329     g_list_free(order);
330     order = NULL;
331
332     popup_cycle(ft, FALSE);
333
334     return NULL;
335 }
336
337 void focus_order_add_new(ObClient *c)
338 {
339     guint d, i;
340
341     if (c->iconic)
342         focus_order_to_top(c);
343     else {
344         d = c->desktop;
345         if (d == DESKTOP_ALL) {
346             for (i = 0; i < screen_num_desktops; ++i) {
347                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
348                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
349                 else
350                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
351             }
352         } else
353              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
354                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
355             else
356                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
357     }
358 }
359
360 void focus_order_remove(ObClient *c)
361 {
362     guint d, i;
363
364     d = c->desktop;
365     if (d == DESKTOP_ALL) {
366         for (i = 0; i < screen_num_desktops; ++i)
367             focus_order[i] = g_list_remove(focus_order[i], c);
368     } else
369         focus_order[d] = g_list_remove(focus_order[d], c);
370 }
371
372 static void to_top(ObClient *c, guint d)
373 {
374     focus_order[d] = g_list_remove(focus_order[d], c);
375     if (!c->iconic) {
376         focus_order[d] = g_list_prepend(focus_order[d], c);
377     } else {
378         GList *it;
379
380         /* insert before first iconic window */
381         for (it = focus_order[d];
382              it && !((ObClient*)it->data)->iconic; it = it->next);
383         g_list_insert_before(focus_order[d], it, c);
384     }
385 }
386
387 void focus_order_to_top(ObClient *c)
388 {
389     guint d, i;
390
391     d = c->desktop;
392     if (d == DESKTOP_ALL) {
393         for (i = 0; i < screen_num_desktops; ++i)
394             to_top(c, i);
395     } else
396         to_top(c, d);
397 }
398
399 static void to_bottom(ObClient *c, guint d)
400 {
401     focus_order[d] = g_list_remove(focus_order[d], c);
402     if (c->iconic) {
403         focus_order[d] = g_list_append(focus_order[d], c);
404     } else {
405         GList *it;
406
407         /* insert before first iconic window */
408         for (it = focus_order[d];
409              it && !((ObClient*)it->data)->iconic; it = it->next);
410         g_list_insert_before(focus_order[d], it, c);
411     }
412 }
413
414 void focus_order_to_bottom(ObClient *c)
415 {
416     guint d, i;
417
418     d = c->desktop;
419     if (d == DESKTOP_ALL) {
420         for (i = 0; i < screen_num_desktops; ++i)
421             to_bottom(c, i);
422     } else
423         to_bottom(c, d);
424 }