move the keyboard and mouse plugins into the kernel for mucho sexiness.
[dana/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
285     if (!first) first = focus_client;
286     if (!focus_cycle_target) focus_cycle_target = focus_client;
287
288     if (linear) list = client_list;
289     else        list = focus_order[screen_desktop];
290
291     start = it = g_list_find(list, focus_cycle_target);
292     if (!start) /* switched desktops or something? */
293         start = it = forward ? g_list_last(list) : g_list_first(list);
294     if (!start) goto done_cycle;
295
296     do {
297         if (forward) {
298             it = it->next;
299             if (it == NULL) it = g_list_first(list);
300         } else {
301             it = it->prev;
302             if (it == NULL) it = g_list_last(list);
303         }
304         /*ft = client_focus_target(it->data);*/
305         ft = it->data;
306         /* we don't use client_can_focus here, because that doesn't let you
307            focus an iconic window, but we want to be able to, so we just check
308            if the focus flags on the window allow it, and its on the current
309            desktop */
310         if (ft->transients == NULL && client_normal(ft) &&
311             ((ft->can_focus || ft->focus_notify) &&
312              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
313             if (ft != focus_cycle_target) { /* prevents flicker */
314                 if (focus_cycle_target)
315                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
316                 focus_cycle_target = ft;
317                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
318             }
319             popup_cycle(ft, config_focus_popup);
320             return ft;
321         }
322     } while (it != start);
323
324 done_cycle:
325     t = NULL;
326     first = NULL;
327     focus_cycle_target = NULL;
328     g_list_free(order);
329     order = NULL;
330
331     popup_cycle(ft, FALSE);
332
333     return NULL;
334 }
335
336 void focus_order_add_new(ObClient *c)
337 {
338     guint d, i;
339
340     if (c->iconic)
341         focus_order_to_top(c);
342     else {
343         d = c->desktop;
344         if (d == DESKTOP_ALL) {
345             for (i = 0; i < screen_num_desktops; ++i) {
346                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
347                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
348                 else
349                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
350             }
351         } else
352              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
353                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
354             else
355                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
356     }
357 }
358
359 void focus_order_remove(ObClient *c)
360 {
361     guint d, i;
362
363     d = c->desktop;
364     if (d == DESKTOP_ALL) {
365         for (i = 0; i < screen_num_desktops; ++i)
366             focus_order[i] = g_list_remove(focus_order[i], c);
367     } else
368         focus_order[d] = g_list_remove(focus_order[d], c);
369 }
370
371 static void to_top(ObClient *c, guint d)
372 {
373     focus_order[d] = g_list_remove(focus_order[d], c);
374     if (!c->iconic) {
375         focus_order[d] = g_list_prepend(focus_order[d], c);
376     } else {
377         GList *it;
378
379         /* insert before first iconic window */
380         for (it = focus_order[d];
381              it && !((ObClient*)it->data)->iconic; it = it->next);
382         g_list_insert_before(focus_order[d], it, c);
383     }
384 }
385
386 void focus_order_to_top(ObClient *c)
387 {
388     guint d, i;
389
390     d = c->desktop;
391     if (d == DESKTOP_ALL) {
392         for (i = 0; i < screen_num_desktops; ++i)
393             to_top(c, i);
394     } else
395         to_top(c, d);
396 }
397
398 static void to_bottom(ObClient *c, guint d)
399 {
400     focus_order[d] = g_list_remove(focus_order[d], c);
401     if (c->iconic) {
402         focus_order[d] = g_list_append(focus_order[d], c);
403     } else {
404         GList *it;
405
406         /* insert before first iconic window */
407         for (it = focus_order[d];
408              it && !((ObClient*)it->data)->iconic; it = it->next);
409         g_list_insert_before(focus_order[d], it, c);
410     }
411 }
412
413 void focus_order_to_bottom(ObClient *c)
414 {
415     guint d, i;
416
417     d = c->desktop;
418     if (d == DESKTOP_ALL) {
419         for (i = 0; i < screen_num_desktops; ++i)
420             to_bottom(c, i);
421     } else
422         to_bottom(c, d);
423 }