]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
changes to the timer api, pass the timer to the callback function.
[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 "dispatch.h"
13 #include "focus.h"
14 #include "stacking.h"
15 #include "popup.h"
16
17 #include <X11/Xlib.h>
18 #include <glib.h>
19 #include <assert.h>
20
21 ObClient *focus_client;
22 GList **focus_order; /* these lists are created when screen_startup
23                         sets the number of desktops */
24
25 static ObClient *focus_cycle_target;
26 static Popup *focus_cycle_popup;
27
28 void focus_startup()
29 {
30
31     focus_cycle_popup = popup_new(TRUE);
32
33     /* start with nothing focused */
34     focus_set_client(NULL);
35 }
36
37 void focus_shutdown()
38 {
39     guint i;
40
41     for (i = 0; i < screen_num_desktops; ++i)
42         g_list_free(focus_order[i]);
43     g_free(focus_order);
44
45     popup_free(focus_cycle_popup);
46
47     /* reset focus to root */
48     XSetInputFocus(ob_display, PointerRoot, RevertToPointerRoot,
49                    event_lasttime);
50 }
51
52 static void push_to_top(ObClient *client)
53 {
54     guint desktop;
55
56     desktop = client->desktop;
57     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
58     focus_order[desktop] = g_list_remove(focus_order[desktop], client);
59     focus_order[desktop] = g_list_prepend(focus_order[desktop], client);
60 }
61
62 void focus_set_client(ObClient *client)
63 {
64     Window active;
65     ObClient *old;
66
67 #ifdef DEBUG_FOCUS
68     ob_debug("focus_set_client 0x%lx\n", client ? client->window : 0);
69 #endif
70
71     /* uninstall the old colormap, and install the new one */
72     screen_install_colormap(focus_client, FALSE);
73     screen_install_colormap(client, TRUE);
74
75     if (client == NULL) {
76         /* when nothing will be focused, send focus to the backup target */
77         XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
78                        event_lasttime);
79         XSync(ob_display, FALSE);
80     }
81
82     /* in the middle of cycling..? kill it. */
83     if (focus_cycle_target)
84         focus_cycle(TRUE, TRUE, TRUE, TRUE);
85
86     old = focus_client;
87     focus_client = client;
88
89     /* move to the top of the list */
90     if (client != NULL)
91         push_to_top(client);
92
93     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
94     if (ob_state() != OB_STATE_EXITING) {
95         active = client ? client->window : None;
96         PROP_SET32(RootWindow(ob_display, ob_screen),
97                    net_active_window, window, active);
98     }
99
100     if (focus_client != NULL)
101         dispatch_client(Event_Client_Focus, focus_client, 0, 0);
102     if (old != NULL)
103         dispatch_client(Event_Client_Unfocus, old, 0, 0);
104 }
105
106 static gboolean focus_under_pointer()
107 {
108     int x, y;
109     GList *it;
110
111     if (screen_pointer_pos(&x, &y)) {
112         for (it = stacking_list; it != NULL; it = it->next) {
113             if (WINDOW_IS_CLIENT(it->data)) {
114                 ObClient *c = WINDOW_AS_CLIENT(it->data);
115                 if (c->desktop == screen_desktop &&
116                     RECT_CONTAINS(c->frame->area, x, y))
117                     break;
118             }
119         }
120         if (it != NULL) {
121             g_assert(WINDOW_IS_CLIENT(it->data));
122             return client_normal(it->data) && client_focus(it->data);
123         }
124     }
125     return FALSE;
126 }
127
128 /* finds the first transient that isn't 'skip' and ensure's that client_normal
129  is true for it */
130 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
131 {
132     GSList *it;
133     ObClient *ret;
134
135     for (it = c->transients; it; it = it->next) {
136         if (it->data == top) return NULL;
137         ret = find_transient_recursive(it->data, top, skip);
138         if (ret && ret != skip && client_normal(ret)) return ret;
139         if (it->data != skip && client_normal(it->data)) return it->data;
140     }
141     return NULL;
142 }
143
144 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
145 {
146     ObClient *target = find_transient_recursive(top, top, old);
147     if (!target) {
148         /* make sure client_normal is true always */
149         if (!client_normal(top))
150             return FALSE;
151         target = top; /* no transient, keep the top */
152     }
153     return client_focus(target);
154 }
155
156 void focus_fallback(ObFocusFallbackType type)
157 {
158     GList *it;
159     ObClient *old = NULL;
160
161     old = focus_client;
162
163     /* unfocus any focused clients.. they can be focused by Pointer events
164        and such, and then when I try focus them, I won't get a FocusIn event
165        at all for them.
166     */
167     focus_set_client(NULL);
168
169     if (!(type == OB_FOCUS_FALLBACK_DESKTOP ?
170           config_focus_last_on_desktop : config_focus_last)) {
171         if (config_focus_follow) focus_under_pointer();
172         return;
173     }
174
175     if (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
176         /* try for transient relations */
177         if (old->transient_for) {
178             if (old->transient_for == OB_TRAN_GROUP) {
179                 for (it = focus_order[screen_desktop]; it; it = it->next) {
180                     GSList *sit;
181
182                     for (sit = old->group->members; sit; sit = sit->next)
183                         if (sit->data == it->data)
184                             if (focus_fallback_transient(sit->data, old))
185                                 return;
186                 }
187             } else {
188                 if (focus_fallback_transient(old->transient_for, old))
189                     return;
190             }
191         }
192
193         /* try for group relations */
194         if (old->group) {
195             GSList *sit;
196
197             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
198                 for (sit = old->group->members; sit; sit = sit->next)
199                     if (sit->data == it->data)
200                         if (sit->data != old && client_normal(sit->data))
201                             if (client_can_focus(sit->data)) {
202                                 gboolean r = client_focus(sit->data);
203                                 assert(r);
204                                 return;
205                             }
206         }
207     }
208
209     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
210         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
211             if (client_normal(it->data) &&
212                 /* dont fall back to 'anonymous' fullscreen windows. theres no
213                    checks for this is in transient/group fallbacks, so they can
214                    be fallback targets there. */
215                 !((ObClient*)it->data)->fullscreen &&
216                 client_can_focus(it->data)) {
217                 gboolean r = client_focus(it->data);
218                 assert(r);
219                 return;
220             }
221
222     /* nothing to focus, and already set it to none above */
223 }
224
225 static void popup_cycle(ObClient *c, gboolean show)
226 {
227     if (!show) {
228         popup_hide(focus_cycle_popup);
229     } else {
230         Rect *a;
231         ObClient *p = c;
232         char *title;
233
234         a = screen_physical_area_monitor(0);
235         popup_position(focus_cycle_popup, CenterGravity,
236                        a->x + a->width / 2, a->y + a->height / 2);
237 /*        popup_size(focus_cycle_popup, a->height/2, a->height/16);
238         popup_show(focus_cycle_popup, c->title,
239                    client_icon(c, a->height/16, a->height/16));
240 */
241         /* XXX the size and the font extents need to be related on some level
242          */
243         popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
244
245         /* use the transient's parent's title/icon */
246         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
247             p = p->transient_for;
248
249         if (p == c)
250             title = NULL;
251         else
252             title = g_strconcat((c->iconic ? c->icon_title : c->title),
253                                 " - ",
254                                 (p->iconic ? p->icon_title : p->title),
255                                 NULL);
256
257         popup_show(focus_cycle_popup,
258                    (title ? title : (c->iconic ? c->icon_title : c->title)),
259                    client_icon(p, 48, 48));
260         g_free(title);
261     }
262 }
263
264 ObClient *focus_cycle(gboolean forward, gboolean linear, gboolean done,
265                     gboolean cancel)
266 {
267     static ObClient *first = NULL;
268     static ObClient *t = NULL;
269     static GList *order = NULL;
270     GList *it, *start, *list;
271     ObClient *ft;
272
273     if (cancel) {
274         if (focus_cycle_target)
275             frame_adjust_focus(focus_cycle_target->frame, FALSE);
276         if (focus_client)
277             frame_adjust_focus(focus_client->frame, TRUE);
278         goto done_cycle;
279     } else if (done) {
280         if (focus_cycle_target)
281             client_activate(focus_cycle_target);
282         goto done_cycle;
283     }
284     if (!first)
285         grab_pointer(TRUE, None);
286
287     if (!first) first = focus_client;
288     if (!focus_cycle_target) focus_cycle_target = focus_client;
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->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
315             if (ft != focus_cycle_target) { /* prevents flicker */
316                 if (focus_cycle_target)
317                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
318                 focus_cycle_target = ft;
319                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
320             }
321             popup_cycle(ft, config_focus_popup);
322             return ft;
323         }
324     } while (it != start);
325
326 done_cycle:
327     t = NULL;
328     first = NULL;
329     focus_cycle_target = NULL;
330     g_list_free(order);
331     order = NULL;
332
333     popup_cycle(ft, FALSE);
334     grab_pointer(FALSE, None);
335
336     return NULL;
337 }
338
339 void focus_order_add_new(ObClient *c)
340 {
341     guint d, i;
342
343     if (c->iconic)
344         focus_order_to_top(c);
345     else {
346         d = c->desktop;
347         if (d == DESKTOP_ALL) {
348             for (i = 0; i < screen_num_desktops; ++i) {
349                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
350                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
351                 else
352                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
353             }
354         } else
355              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
356                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
357             else
358                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
359     }
360 }
361
362 void focus_order_remove(ObClient *c)
363 {
364     guint d, i;
365
366     d = c->desktop;
367     if (d == DESKTOP_ALL) {
368         for (i = 0; i < screen_num_desktops; ++i)
369             focus_order[i] = g_list_remove(focus_order[i], c);
370     } else
371         focus_order[d] = g_list_remove(focus_order[d], c);
372 }
373
374 static void to_top(ObClient *c, guint d)
375 {
376     focus_order[d] = g_list_remove(focus_order[d], c);
377     if (!c->iconic) {
378         focus_order[d] = g_list_prepend(focus_order[d], c);
379     } else {
380         GList *it;
381
382         /* insert before first iconic window */
383         for (it = focus_order[d];
384              it && !((ObClient*)it->data)->iconic; it = it->next);
385         g_list_insert_before(focus_order[d], it, c);
386     }
387 }
388
389 void focus_order_to_top(ObClient *c)
390 {
391     guint d, i;
392
393     d = c->desktop;
394     if (d == DESKTOP_ALL) {
395         for (i = 0; i < screen_num_desktops; ++i)
396             to_top(c, i);
397     } else
398         to_top(c, d);
399 }
400
401 static void to_bottom(ObClient *c, guint d)
402 {
403     focus_order[d] = g_list_remove(focus_order[d], c);
404     if (c->iconic) {
405         focus_order[d] = g_list_append(focus_order[d], c);
406     } else {
407         GList *it;
408
409         /* insert before first iconic window */
410         for (it = focus_order[d];
411              it && !((ObClient*)it->data)->iconic; it = it->next);
412         g_list_insert_before(focus_order[d], it, c);
413     }
414 }
415
416 void focus_order_to_bottom(ObClient *c)
417 {
418     guint d, i;
419
420     d = c->desktop;
421     if (d == DESKTOP_ALL) {
422         for (i = 0; i < screen_num_desktops; ++i)
423             to_bottom(c, i);
424     } else
425         to_bottom(c, d);
426 }