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