]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
allow cycling to iconic 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 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 */
239     focus_set_client(NULL);
240 }
241
242 static void popup_cycle(Client *c, gboolean show)
243 {
244     if (!show) {
245         popup_hide(focus_cycle_popup);
246     } else {
247         Rect *a;
248         Client *p = c;
249         char *title;
250
251         a = screen_area(c->desktop);
252         popup_position(focus_cycle_popup, CenterGravity,
253                        a->x + a->width / 2, a->y + a->height / 2);
254 /*        popup_size(focus_cycle_popup, a->height/2, a->height/16);
255         popup_show(focus_cycle_popup, c->title,
256                    client_icon(c, a->height/16, a->height/16));
257 */
258         /* XXX the size and the font extents need to be related on some level
259          */
260         popup_size(focus_cycle_popup, 320, 48);
261
262         /* use the transient's parent's title/icon */
263         while (p->transient_for && p->transient_for != TRAN_GROUP)
264             p = p->transient_for;
265
266         if (p == c)
267             title = NULL;
268         else
269             title = g_strconcat((p->iconic ? p->icon_title : p->title),
270                                 " - ",
271                                 (c->iconic ? c->icon_title : c->title),
272                                 NULL);
273
274         popup_show(focus_cycle_popup,
275                    (title ? title : (c->iconic ? c->icon_title : c->title)),
276                    client_icon(p, 48, 48));
277         g_free(title);
278     }
279 }
280
281 Client *focus_cycle(gboolean forward, gboolean linear, gboolean done,
282                     gboolean cancel)
283 {
284     static Client *first = NULL;
285     static Client *t = NULL;
286     static GList *order = NULL;
287     GList *it, *start, *list;
288     Client *ft;
289
290     if (cancel) {
291         if (focus_cycle_target)
292             frame_adjust_focus(focus_cycle_target->frame, FALSE);
293         if (focus_client)
294             frame_adjust_focus(focus_client->frame, TRUE);
295         goto done_cycle;
296     } else if (done) {
297         if (focus_cycle_target)
298             client_activate(focus_cycle_target);
299         goto done_cycle;
300     }
301     if (!first)
302         grab_pointer(TRUE, None);
303
304     if (!first) first = focus_client;
305     if (!focus_cycle_target) focus_cycle_target = focus_client;
306
307     if (linear) list = client_list;
308     else        list = focus_order[screen_desktop];
309
310     start = it = g_list_find(list, focus_cycle_target);
311     if (!start) /* switched desktops or something? */
312         start = it = forward ? g_list_last(list) : g_list_first(list);
313     if (!start) goto done_cycle;
314
315     do {
316         if (forward) {
317             it = it->next;
318             if (it == NULL) it = g_list_first(list);
319         } else {
320             it = it->prev;
321             if (it == NULL) it = g_list_last(list);
322         }
323         /*ft = client_focus_target(it->data);*/
324         ft = it->data;
325         /* we don't use client_can_focus here, because that doesn't let you
326            focus an iconic window, but we want to be able to, so we just check
327            if the focus flags on the window allow it, and its on the current
328            desktop */
329         if (ft->transients == NULL && client_normal(ft) &&
330             ((ft->can_focus || ft->focus_notify) &&
331              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
332             if (ft != focus_cycle_target) { /* prevents flicker */
333                 if (focus_cycle_target)
334                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
335                 focus_cycle_target = ft;
336                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
337             }
338             popup_cycle(ft, config_focus_popup);
339             return ft;
340         }
341     } while (it != start);
342
343 done_cycle:
344     t = NULL;
345     first = NULL;
346     focus_cycle_target = NULL;
347     g_list_free(order);
348     order = NULL;
349
350     popup_cycle(ft, FALSE);
351     grab_pointer(FALSE, None);
352
353     return NULL;
354 }
355
356 void focus_order_add_new(Client *c)
357 {
358     guint d, i;
359
360     if (c->iconic)
361         focus_order_to_top(c);
362     else {
363         d = c->desktop;
364         if (d == DESKTOP_ALL) {
365             for (i = 0; i < screen_num_desktops; ++i) {
366                 if (focus_order[i] && ((Client*)focus_order[i]->data)->iconic)
367                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
368                 else
369                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
370             }
371         } else
372              if (focus_order[d] && ((Client*)focus_order[d]->data)->iconic)
373                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
374             else
375                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
376     }
377 }
378
379 void focus_order_remove(Client *c)
380 {
381     guint d, i;
382
383     d = c->desktop;
384     if (d == DESKTOP_ALL) {
385         for (i = 0; i < screen_num_desktops; ++i)
386             focus_order[i] = g_list_remove(focus_order[i], c);
387     } else
388         focus_order[d] = g_list_remove(focus_order[d], c);
389 }
390
391 static void to_top(Client *c, guint d)
392 {
393     focus_order[d] = g_list_remove(focus_order[d], c);
394     if (!c->iconic) {
395         focus_order[d] = g_list_prepend(focus_order[d], c);
396     } else {
397         GList *it;
398
399         /* insert before first iconic window */
400         for (it = focus_order[d];
401              it && !((Client*)it->data)->iconic; it = it->next);
402         g_list_insert_before(focus_order[d], it, c);
403     }
404 }
405
406 void focus_order_to_top(Client *c)
407 {
408     guint d, i;
409
410     d = c->desktop;
411     if (d == DESKTOP_ALL) {
412         for (i = 0; i < screen_num_desktops; ++i)
413             to_top(c, i);
414     } else
415         to_top(c, d);
416 }
417
418 static void to_bottom(Client *c, guint d)
419 {
420     focus_order[d] = g_list_remove(focus_order[d], c);
421     if (c->iconic) {
422         focus_order[d] = g_list_append(focus_order[d], c);
423     } else {
424         GList *it;
425
426         /* insert before first iconic window */
427         for (it = focus_order[d];
428              it && !((Client*)it->data)->iconic; it = it->next);
429         g_list_insert_before(focus_order[d], it, c);
430     }
431 }
432
433 void focus_order_to_bottom(Client *c)
434 {
435     guint d, i;
436
437     d = c->desktop;
438     if (d == DESKTOP_ALL) {
439         for (i = 0; i < screen_num_desktops; ++i)
440             to_bottom(c, i);
441     } else
442         to_bottom(c, d);
443 }