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