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