]> Dogcows Code - chaz/openbox/blob - openbox/focus.c
some smarter focus fallback for sloppy focus
[chaz/openbox] / 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 }
This page took 0.060403 seconds and 5 git commands to generate.