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