]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
fix crashing when doing a directional focus with no windows on the desktop
[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 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 void focus_cycle(gboolean forward, gboolean linear,
264                  gboolean dialog, gboolean done, gboolean cancel)
265 {
266     static ObClient *first = NULL;
267     static ObClient *t = NULL;
268     static GList *order = NULL;
269     GList *it, *start, *list;
270     ObClient *ft;
271
272     if (cancel) {
273         if (focus_cycle_target)
274             frame_adjust_focus(focus_cycle_target->frame, FALSE);
275         if (focus_client)
276             frame_adjust_focus(focus_client->frame, TRUE);
277         focus_cycle_target = NULL;
278         goto done_cycle;
279     } else if (done && dialog) {
280         goto done_cycle;
281     }
282
283     if (!focus_order[screen_desktop])
284         goto done_cycle;
285
286     if (!first) first = focus_order[screen_desktop]->data;
287     if (!focus_cycle_target) focus_cycle_target =
288                                  focus_order[screen_desktop]->data;
289
290     if (linear) list = client_list;
291     else        list = focus_order[screen_desktop];
292
293     start = it = g_list_find(list, focus_cycle_target);
294     if (!start) /* switched desktops or something? */
295         start = it = forward ? g_list_last(list) : g_list_first(list);
296     if (!start) goto done_cycle;
297
298     do {
299         if (forward) {
300             it = it->next;
301             if (it == NULL) it = g_list_first(list);
302         } else {
303             it = it->prev;
304             if (it == NULL) it = g_list_last(list);
305         }
306         /*ft = client_focus_target(it->data);*/
307         ft = it->data;
308         /* we don't use client_can_focus here, because that doesn't let you
309            focus an iconic window, but we want to be able to, so we just check
310            if the focus flags on the window allow it, and its on the current
311            desktop */
312         if (ft->transients == NULL && client_normal(ft) &&
313             ((ft->can_focus || ft->focus_notify) &&
314              !ft->skip_taskbar &&
315              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
316             if (ft != focus_cycle_target) { /* prevents flicker */
317                 if (focus_cycle_target)
318                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
319                 focus_cycle_target = ft;
320                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
321             }
322             popup_cycle(ft, dialog);
323             return;
324         }
325     } while (it != start);
326
327 done_cycle:
328     if (done && focus_cycle_target)
329         client_activate(focus_cycle_target, FALSE);
330
331     t = NULL;
332     first = NULL;
333     focus_cycle_target = NULL;
334     g_list_free(order);
335     order = NULL;
336
337     popup_cycle(ft, FALSE);
338
339     return;
340 }
341
342 void focus_directional_cycle(ObDirection dir,
343                              gboolean dialog, gboolean done, gboolean cancel)
344 {
345     static ObClient *first = NULL;
346     ObClient *ft;
347
348     if (cancel) {
349         if (focus_cycle_target)
350             frame_adjust_focus(focus_cycle_target->frame, FALSE);
351         if (focus_client)
352             frame_adjust_focus(focus_client->frame, TRUE);
353         focus_cycle_target = NULL;
354         goto done_cycle;
355     } else if (done && dialog) {
356         goto done_cycle;
357     }
358
359     if (!focus_order[screen_desktop])
360         goto done_cycle;
361
362     if (!first) first = focus_order[screen_desktop]->data;
363     if (!focus_cycle_target) focus_cycle_target =
364                                  focus_order[screen_desktop]->data;
365
366     if ((ft = client_find_directional(focus_cycle_target, dir))) {
367         if (ft != focus_cycle_target) {/* prevents flicker */
368             if (focus_cycle_target)
369                 frame_adjust_focus(focus_cycle_target->frame, FALSE);
370             focus_cycle_target = ft;
371             frame_adjust_focus(focus_cycle_target->frame, TRUE);
372         }
373         popup_cycle(ft, dialog);
374     }
375     if (dialog)
376         return;
377
378 done_cycle:
379     if (done && focus_cycle_target)
380         client_activate(focus_cycle_target, FALSE);
381
382     first = NULL;
383     focus_cycle_target = NULL;
384
385     popup_cycle(ft, FALSE);
386
387     return;
388 }
389
390 void focus_order_add_new(ObClient *c)
391 {
392     guint d, i;
393
394     if (c->iconic)
395         focus_order_to_top(c);
396     else {
397         d = c->desktop;
398         if (d == DESKTOP_ALL) {
399             for (i = 0; i < screen_num_desktops; ++i) {
400                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
401                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
402                 else
403                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
404             }
405         } else
406              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
407                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
408             else
409                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
410     }
411 }
412
413 void focus_order_remove(ObClient *c)
414 {
415     guint d, i;
416
417     d = c->desktop;
418     if (d == DESKTOP_ALL) {
419         for (i = 0; i < screen_num_desktops; ++i)
420             focus_order[i] = g_list_remove(focus_order[i], c);
421     } else
422         focus_order[d] = g_list_remove(focus_order[d], c);
423 }
424
425 static void to_top(ObClient *c, guint d)
426 {
427     focus_order[d] = g_list_remove(focus_order[d], c);
428     if (!c->iconic) {
429         focus_order[d] = g_list_prepend(focus_order[d], c);
430     } else {
431         GList *it;
432
433         /* insert before first iconic window */
434         for (it = focus_order[d];
435              it && !((ObClient*)it->data)->iconic; it = it->next);
436         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
437     }
438 }
439
440 void focus_order_to_top(ObClient *c)
441 {
442     guint d, i;
443
444     d = c->desktop;
445     if (d == DESKTOP_ALL) {
446         for (i = 0; i < screen_num_desktops; ++i)
447             to_top(c, i);
448     } else
449         to_top(c, d);
450 }
451
452 static void to_bottom(ObClient *c, guint d)
453 {
454     focus_order[d] = g_list_remove(focus_order[d], c);
455     if (c->iconic) {
456         focus_order[d] = g_list_append(focus_order[d], c);
457     } else {
458         GList *it;
459
460         /* insert before first iconic window */
461         for (it = focus_order[d];
462              it && !((ObClient*)it->data)->iconic; it = it->next);
463         g_list_insert_before(focus_order[d], it, c);
464     }
465 }
466
467 void focus_order_to_bottom(ObClient *c)
468 {
469     guint d, i;
470
471     d = c->desktop;
472     if (d == DESKTOP_ALL) {
473         for (i = 0; i < screen_num_desktops; ++i)
474             to_bottom(c, i);
475     } else
476         to_bottom(c, d);
477 }