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