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