]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
some smarter focus fallback for sloppy focus
[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 (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
183         if (old->transient_for) {
184             gboolean trans = FALSE;
185
186             if (config_focus_last || !config_focus_follow)
187                 trans = TRUE;
188             else {
189                 ObClient *c;
190
191                 if ((c = client_under_pointer()) &&
192                     client_search_transient(client_search_top_transient(c),
193                                             old))
194                     trans = TRUE;
195             }
196
197             /* try for transient relations */
198             if (trans) {
199                 if (old->transient_for == OB_TRAN_GROUP) {
200                     for (it = focus_order[screen_desktop]; it; it = it->next) {
201                         GSList *sit;
202
203                         for (sit = old->group->members; sit; sit = sit->next)
204                             if (sit->data == it->data)
205                                 if (focus_fallback_transient(sit->data, old))
206                                     return;
207                     }
208                 } else {
209                     if (focus_fallback_transient(old->transient_for, old))
210                         return;
211                 }
212             }
213         }
214     }
215
216     if (!config_focus_last && config_focus_follow)
217         if (focus_under_pointer())
218             return;
219
220 #if 0
221         /* try for group relations */
222         if (old->group) {
223             GSList *sit;
224
225             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
226                 for (sit = old->group->members; sit; sit = sit->next)
227                     if (sit->data == it->data)
228                         if (sit->data != old && client_normal(sit->data))
229                             if (client_can_focus(sit->data)) {
230                                 gboolean r = client_focus(sit->data);
231                                 assert(r);
232                                 return;
233                             }
234         }
235 #endif
236
237     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
238         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
239             if (client_normal(it->data) && client_can_focus(it->data)) {
240                 gboolean r = client_focus(it->data);
241                 assert(r);
242                 return;
243             }
244
245     /* nothing to focus, and already set it to none above */
246 }
247
248 static void popup_cycle(ObClient *c, gboolean show)
249 {
250     if (!show) {
251         icon_popup_hide(focus_cycle_popup);
252     } else {
253         Rect *a;
254         ObClient *p = c;
255         char *title;
256
257         a = screen_physical_area_monitor(0);
258         icon_popup_position(focus_cycle_popup, CenterGravity,
259                             a->x + a->width / 2, a->y + a->height / 2);
260 /*        icon_popup_size(focus_cycle_popup, a->height/2, a->height/16);
261         icon_popup_show(focus_cycle_popup, c->title,
262                         client_icon(c, a->height/16, a->height/16));
263 */
264         /* XXX the size and the font extents need to be related on some level
265          */
266         icon_popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
267
268         /* use the transient's parent's title/icon */
269         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
270             p = p->transient_for;
271
272         if (p == c)
273             title = NULL;
274         else
275             title = g_strconcat((c->iconic ? c->icon_title : c->title),
276                                 " - ",
277                                 (p->iconic ? p->icon_title : p->title),
278                                 NULL);
279
280         icon_popup_show(focus_cycle_popup,
281                         (title ? title :
282                          (c->iconic ? c->icon_title : c->title)),
283                         client_icon(p, 48, 48));
284         g_free(title);
285     }
286 }
287
288 static gboolean valid_focus_target(ObClient *ft)
289 {
290     /* we don't use client_can_focus here, because that doesn't let you
291        focus an iconic window, but we want to be able to, so we just check
292        if the focus flags on the window allow it, and its on the current
293        desktop */
294     return (ft == client_focus_target(ft) && client_normal(ft) &&
295             ((ft->can_focus || ft->focus_notify) &&
296              !ft->skip_taskbar &&
297              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL)));
298 }
299
300 void focus_cycle(gboolean forward, gboolean linear,
301                  gboolean dialog, gboolean done, gboolean cancel)
302 {
303     static ObClient *first = NULL;
304     static ObClient *t = NULL;
305     static GList *order = NULL;
306     GList *it, *start, *list;
307     ObClient *ft = NULL;
308
309     if (cancel) {
310         if (focus_cycle_target)
311             frame_adjust_focus(focus_cycle_target->frame, FALSE);
312         if (focus_client)
313             frame_adjust_focus(focus_client->frame, TRUE);
314         focus_cycle_target = NULL;
315         goto done_cycle;
316     } else if (done && dialog) {
317         goto done_cycle;
318     }
319
320     if (!focus_order[screen_desktop])
321         goto done_cycle;
322
323     if (!first) first = focus_client;
324     if (!focus_cycle_target) focus_cycle_target = focus_client;
325
326     if (linear) list = client_list;
327     else        list = focus_order[screen_desktop];
328
329     start = it = g_list_find(list, focus_cycle_target);
330     if (!start) /* switched desktops or something? */
331         start = it = forward ? g_list_last(list) : g_list_first(list);
332     if (!start) goto done_cycle;
333
334     do {
335         if (forward) {
336             it = it->next;
337             if (it == NULL) it = g_list_first(list);
338         } else {
339             it = it->prev;
340             if (it == NULL) it = g_list_last(list);
341         }
342         ft = it->data;
343         if (valid_focus_target(ft)) {
344             if (ft != focus_cycle_target) { /* prevents flicker */
345                 if (focus_cycle_target)
346                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
347                 focus_cycle_target = ft;
348                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
349             }
350             popup_cycle(ft, dialog);
351             return;
352         }
353     } while (it != start);
354
355 done_cycle:
356     if (done && focus_cycle_target)
357         client_activate(focus_cycle_target, FALSE);
358
359     t = NULL;
360     first = NULL;
361     focus_cycle_target = NULL;
362     g_list_free(order);
363     order = NULL;
364
365     popup_cycle(ft, FALSE);
366
367     return;
368 }
369
370 void focus_directional_cycle(ObDirection dir,
371                              gboolean dialog, gboolean done, gboolean cancel)
372 {
373     static ObClient *first = NULL;
374     ObClient *ft = NULL;
375
376     if (cancel) {
377         if (focus_cycle_target)
378             frame_adjust_focus(focus_cycle_target->frame, FALSE);
379         if (focus_client)
380             frame_adjust_focus(focus_client->frame, TRUE);
381         focus_cycle_target = NULL;
382         goto done_cycle;
383     } else if (done && dialog) {
384         goto done_cycle;
385     }
386
387     if (!focus_order[screen_desktop])
388         goto done_cycle;
389
390     if (!first) first = focus_client;
391     if (!focus_cycle_target) focus_cycle_target = focus_client;
392
393     if (focus_cycle_target)
394         ft = client_find_directional(focus_cycle_target, dir);
395     else {
396         GList *it;
397
398         for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
399             if (valid_focus_target(it->data))
400                 ft = it->data;
401     }
402         
403     if (ft) {
404         if (ft != focus_cycle_target) {/* prevents flicker */
405             if (focus_cycle_target)
406                 frame_adjust_focus(focus_cycle_target->frame, FALSE);
407             focus_cycle_target = ft;
408             frame_adjust_focus(focus_cycle_target->frame, TRUE);
409         }
410     }
411     if (focus_cycle_target) {
412         popup_cycle(focus_cycle_target, dialog);
413         if (dialog)
414             return;
415     }
416
417
418 done_cycle:
419     if (done && focus_cycle_target)
420         client_activate(focus_cycle_target, FALSE);
421
422     first = NULL;
423     focus_cycle_target = NULL;
424
425     popup_cycle(ft, FALSE);
426
427     return;
428 }
429
430 void focus_order_add_new(ObClient *c)
431 {
432     guint d, i;
433
434     if (c->iconic)
435         focus_order_to_top(c);
436     else {
437         d = c->desktop;
438         if (d == DESKTOP_ALL) {
439             for (i = 0; i < screen_num_desktops; ++i) {
440                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
441                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
442                 else
443                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
444             }
445         } else
446              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
447                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
448             else
449                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
450     }
451 }
452
453 void focus_order_remove(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             focus_order[i] = g_list_remove(focus_order[i], c);
461     } else
462         focus_order[d] = g_list_remove(focus_order[d], c);
463 }
464
465 static void to_top(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_prepend(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         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
477     }
478 }
479
480 void focus_order_to_top(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_top(c, i);
488     } else
489         to_top(c, d);
490 }
491
492 static void to_bottom(ObClient *c, guint d)
493 {
494     focus_order[d] = g_list_remove(focus_order[d], c);
495     if (c->iconic) {
496         focus_order[d] = g_list_append(focus_order[d], c);
497     } else {
498         GList *it;
499
500         /* insert before first iconic window */
501         for (it = focus_order[d];
502              it && !((ObClient*)it->data)->iconic; it = it->next);
503         g_list_insert_before(focus_order[d], it, c);
504     }
505 }
506
507 void focus_order_to_bottom(ObClient *c)
508 {
509     guint d, i;
510
511     d = c->desktop;
512     if (d == DESKTOP_ALL) {
513         for (i = 0; i < screen_num_desktops; ++i)
514             to_bottom(c, i);
515     } else
516         to_bottom(c, d);
517 }