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