replace the focus_backup with the screen_support_win, we dont need 2 offscreen windows.
[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(ob_root, net_active_window, window, active);
100     }
101
102     if (focus_client != NULL)
103         dispatch_client(Event_Client_Focus, focus_client, 0, 0);
104     if (old != NULL)
105         dispatch_client(Event_Client_Unfocus, old, 0, 0);
106 }
107
108 static gboolean focus_under_pointer()
109 {
110     int x, y;
111     GList *it;
112
113     if (ob_pointer_pos(&x, &y)) {
114         for (it = stacking_list; it != NULL; it = it->next) {
115             if (WINDOW_IS_CLIENT(it->data)) {
116                 ObClient *c = WINDOW_AS_CLIENT(it->data);
117                 if (c->desktop == screen_desktop &&
118                     RECT_CONTAINS(c->frame->area, x, y))
119                     break;
120             }
121         }
122         if (it != NULL) {
123             g_assert(WINDOW_IS_CLIENT(it->data));
124             return client_normal(it->data) && client_focus(it->data);
125         }
126     }
127     return FALSE;
128 }
129
130 /* finds the first transient that isn't 'skip' and ensure's that client_normal
131  is true for it */
132 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
133 {
134     GSList *it;
135     ObClient *ret;
136
137     for (it = c->transients; it; it = it->next) {
138         if (it->data == top) return NULL;
139         ret = find_transient_recursive(it->data, top, skip);
140         if (ret && ret != skip && client_normal(ret)) return ret;
141         if (it->data != skip && client_normal(it->data)) return it->data;
142     }
143     return NULL;
144 }
145
146 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
147 {
148     ObClient *target = find_transient_recursive(top, top, old);
149     if (!target) {
150         /* make sure client_normal is true always */
151         if (!client_normal(top))
152             return FALSE;
153         target = top; /* no transient, keep the top */
154     }
155     return client_focus(target);
156 }
157
158 void focus_fallback(ObFocusFallbackType type)
159 {
160     GList *it;
161     ObClient *old = NULL;
162
163     old = focus_client;
164
165     /* unfocus any focused clients.. they can be focused by Pointer events
166        and such, and then when I try focus them, I won't get a FocusIn event
167        at all for them.
168     */
169     focus_set_client(NULL);
170
171     if (!(type == OB_FOCUS_FALLBACK_DESKTOP ?
172           config_focus_last_on_desktop : config_focus_last)) {
173         if (config_focus_follow) focus_under_pointer();
174         return;
175     }
176
177     if (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
178         /* try for transient relations */
179         if (old->transient_for) {
180             if (old->transient_for == OB_TRAN_GROUP) {
181                 for (it = focus_order[screen_desktop]; it; it = it->next) {
182                     GSList *sit;
183
184                     for (sit = old->group->members; sit; sit = sit->next)
185                         if (sit->data == it->data)
186                             if (focus_fallback_transient(sit->data, old))
187                                 return;
188                 }
189             } else {
190                 if (focus_fallback_transient(old->transient_for, old))
191                     return;
192             }
193         }
194
195         /* try for group relations */
196         if (old->group) {
197             GSList *sit;
198
199             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
200                 for (sit = old->group->members; sit; sit = sit->next)
201                     if (sit->data == it->data)
202                         if (sit->data != old && client_normal(sit->data))
203                             if (client_can_focus(sit->data)) {
204                                 gboolean r = client_focus(sit->data);
205                                 assert(r);
206                                 return;
207                             }
208         }
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, 320, 48);
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);
284         goto done_cycle;
285     }
286     if (!first)
287         grab_pointer(TRUE, None);
288
289     if (!first) first = focus_client;
290     if (!focus_cycle_target) focus_cycle_target = focus_client;
291
292     if (linear) list = client_list;
293     else        list = focus_order[screen_desktop];
294
295     start = it = g_list_find(list, focus_cycle_target);
296     if (!start) /* switched desktops or something? */
297         start = it = forward ? g_list_last(list) : g_list_first(list);
298     if (!start) goto done_cycle;
299
300     do {
301         if (forward) {
302             it = it->next;
303             if (it == NULL) it = g_list_first(list);
304         } else {
305             it = it->prev;
306             if (it == NULL) it = g_list_last(list);
307         }
308         /*ft = client_focus_target(it->data);*/
309         ft = it->data;
310         /* we don't use client_can_focus here, because that doesn't let you
311            focus an iconic window, but we want to be able to, so we just check
312            if the focus flags on the window allow it, and its on the current
313            desktop */
314         if (ft->transients == NULL && client_normal(ft) &&
315             ((ft->can_focus || ft->focus_notify) &&
316              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
317             if (ft != focus_cycle_target) { /* prevents flicker */
318                 if (focus_cycle_target)
319                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
320                 focus_cycle_target = ft;
321                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
322             }
323             popup_cycle(ft, config_focus_popup);
324             return ft;
325         }
326     } while (it != start);
327
328 done_cycle:
329     t = NULL;
330     first = NULL;
331     focus_cycle_target = NULL;
332     g_list_free(order);
333     order = NULL;
334
335     popup_cycle(ft, FALSE);
336     grab_pointer(FALSE, None);
337
338     return NULL;
339 }
340
341 void focus_order_add_new(ObClient *c)
342 {
343     guint d, i;
344
345     if (c->iconic)
346         focus_order_to_top(c);
347     else {
348         d = c->desktop;
349         if (d == DESKTOP_ALL) {
350             for (i = 0; i < screen_num_desktops; ++i) {
351                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
352                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
353                 else
354                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
355             }
356         } else
357              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
358                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
359             else
360                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
361     }
362 }
363
364 void focus_order_remove(ObClient *c)
365 {
366     guint d, i;
367
368     d = c->desktop;
369     if (d == DESKTOP_ALL) {
370         for (i = 0; i < screen_num_desktops; ++i)
371             focus_order[i] = g_list_remove(focus_order[i], c);
372     } else
373         focus_order[d] = g_list_remove(focus_order[d], c);
374 }
375
376 static void to_top(ObClient *c, guint d)
377 {
378     focus_order[d] = g_list_remove(focus_order[d], c);
379     if (!c->iconic) {
380         focus_order[d] = g_list_prepend(focus_order[d], c);
381     } else {
382         GList *it;
383
384         /* insert before first iconic window */
385         for (it = focus_order[d];
386              it && !((ObClient*)it->data)->iconic; it = it->next);
387         g_list_insert_before(focus_order[d], it, c);
388     }
389 }
390
391 void focus_order_to_top(ObClient *c)
392 {
393     guint d, i;
394
395     d = c->desktop;
396     if (d == DESKTOP_ALL) {
397         for (i = 0; i < screen_num_desktops; ++i)
398             to_top(c, i);
399     } else
400         to_top(c, d);
401 }
402
403 static void to_bottom(ObClient *c, guint d)
404 {
405     focus_order[d] = g_list_remove(focus_order[d], c);
406     if (c->iconic) {
407         focus_order[d] = g_list_append(focus_order[d], c);
408     } else {
409         GList *it;
410
411         /* insert before first iconic window */
412         for (it = focus_order[d];
413              it && !((ObClient*)it->data)->iconic; it = it->next);
414         g_list_insert_before(focus_order[d], it, c);
415     }
416 }
417
418 void focus_order_to_bottom(ObClient *c)
419 {
420     guint d, i;
421
422     d = c->desktop;
423     if (d == DESKTOP_ALL) {
424         for (i = 0; i < screen_num_desktops; ++i)
425             to_bottom(c, i);
426     } else
427         to_bottom(c, d);
428 }