new method for loading menu files etc
[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 #if 0
194         /* try for group relations */
195         if (old->group) {
196             GSList *sit;
197
198             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
199                 for (sit = old->group->members; sit; sit = sit->next)
200                     if (sit->data == it->data)
201                         if (sit->data != old && client_normal(sit->data))
202                             if (client_can_focus(sit->data)) {
203                                 gboolean r = client_focus(sit->data);
204                                 assert(r);
205                                 return;
206                             }
207         }
208 #endif
209     }
210
211     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
212         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
213             if (client_normal(it->data) &&
214                 /* dont fall back to 'anonymous' fullscreen windows. theres no
215                    checks for this is in transient/group fallbacks, so they can
216                    be fallback targets there. */
217                 !((ObClient*)it->data)->fullscreen &&
218                 client_can_focus(it->data)) {
219                 gboolean r = client_focus(it->data);
220                 assert(r);
221                 return;
222             }
223
224     /* nothing to focus, and already set it to none above */
225 }
226
227 static void popup_cycle(ObClient *c, gboolean show)
228 {
229     if (!show) {
230         popup_hide(focus_cycle_popup);
231     } else {
232         Rect *a;
233         ObClient *p = c;
234         char *title;
235
236         a = screen_physical_area_monitor(0);
237         popup_position(focus_cycle_popup, CenterGravity,
238                        a->x + a->width / 2, a->y + a->height / 2);
239 /*        popup_size(focus_cycle_popup, a->height/2, a->height/16);
240         popup_show(focus_cycle_popup, c->title,
241                    client_icon(c, a->height/16, a->height/16));
242 */
243         /* XXX the size and the font extents need to be related on some level
244          */
245         popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
246
247         /* use the transient's parent's title/icon */
248         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
249             p = p->transient_for;
250
251         if (p == c)
252             title = NULL;
253         else
254             title = g_strconcat((c->iconic ? c->icon_title : c->title),
255                                 " - ",
256                                 (p->iconic ? p->icon_title : p->title),
257                                 NULL);
258
259         popup_show(focus_cycle_popup,
260                    (title ? title : (c->iconic ? c->icon_title : c->title)),
261                    client_icon(p, 48, 48));
262         g_free(title);
263     }
264 }
265
266 ObClient *focus_cycle(gboolean forward, gboolean linear, gboolean done,
267                       gboolean cancel)
268 {
269     static ObClient *first = NULL;
270     static ObClient *t = NULL;
271     static GList *order = NULL;
272     GList *it, *start, *list;
273     ObClient *ft;
274
275     if (cancel) {
276         if (focus_cycle_target)
277             frame_adjust_focus(focus_cycle_target->frame, FALSE);
278         if (focus_client)
279             frame_adjust_focus(focus_client->frame, TRUE);
280         goto done_cycle;
281     } else if (done) {
282         if (focus_cycle_target)
283             client_activate(focus_cycle_target, FALSE);
284         goto done_cycle;
285     }
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->skip_taskbar &&
315              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
316             if (ft != focus_cycle_target) { /* prevents flicker */
317                 if (focus_cycle_target)
318                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
319                 focus_cycle_target = ft;
320                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
321             }
322             popup_cycle(ft, config_focus_popup);
323             return ft;
324         }
325     } while (it != start);
326
327 done_cycle:
328     t = NULL;
329     first = NULL;
330     focus_cycle_target = NULL;
331     g_list_free(order);
332     order = NULL;
333
334     popup_cycle(ft, FALSE);
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 }