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