break focus. or maybe make it better.
[mikachu/openbox.git] / openbox / focus.c
1 #include "debug.h"
2 #include "event.h"
3 #include "openbox.h"
4 #include "grab.h"
5 #include "framerender.h"
6 #include "client.h"
7 #include "config.h"
8 #include "frame.h"
9 #include "screen.h"
10 #include "group.h"
11 #include "prop.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; /* these lists are created when screen_startup
22                         sets the number of desktops */
23 ObClient *focus_cycle_target;
24
25 static ObIconPopup *focus_cycle_popup;
26
27 void focus_startup(gboolean reconfig)
28 {
29     focus_cycle_popup = icon_popup_new(TRUE);
30
31     if (!reconfig)
32         /* start with nothing focused */
33         focus_set_client(NULL);
34 }
35
36 void focus_shutdown(gboolean reconfig)
37 {
38     guint i;
39
40     icon_popup_free(focus_cycle_popup);
41
42     if (!reconfig) {
43         for (i = 0; i < screen_num_desktops; ++i)
44             g_list_free(focus_order[i]);
45         g_free(focus_order);
46
47         /* reset focus to root */
48         XSetInputFocus(ob_display, PointerRoot, RevertToPointerRoot,
49                        event_lasttime);
50     }
51 }
52
53 static void push_to_top(ObClient *client)
54 {
55     guint desktop;
56
57     desktop = client->desktop;
58     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
59     focus_order[desktop] = g_list_remove(focus_order[desktop], client);
60     focus_order[desktop] = g_list_prepend(focus_order[desktop], client);
61 }
62
63 void focus_set_client(ObClient *client)
64 {
65     Window active;
66     ObClient *old;
67
68 #ifdef DEBUG_FOCUS
69     ob_debug("focus_set_client 0x%lx\n", client ? client->window : 0);
70 #endif
71
72     /* uninstall the old colormap, and install the new one */
73     screen_install_colormap(focus_client, FALSE);
74     screen_install_colormap(client, TRUE);
75
76     if (!client) {
77         /* when nothing will be focused, send focus to the backup target */
78         XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
79                        event_lasttime);
80         XSync(ob_display, FALSE);
81     }
82
83     /* in the middle of cycling..? kill it. */
84     if (focus_cycle_target)
85         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE);
86
87     old = focus_client;
88     focus_client = client;
89
90     if (old) {
91         /* focus state can affect the stacking layer */
92         client_calc_layer(old);
93     }
94     if (client) {
95         /* focus state can affect the stacking layer */
96         client_calc_layer(client);
97
98         /* move to the top of the list */
99         push_to_top(client);
100     }
101
102     /* set the NET_ACTIVE_WINDOW hint */
103     active = client ? client->window : None;
104     PROP_SET32(RootWindow(ob_display, ob_screen),
105                net_active_window, window, active);
106 }
107
108 static gboolean focus_under_pointer()
109 {
110     int x, y;
111     GList *it;
112
113     if (screen_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 (config_focus_follow && focus_under_pointer())
172         return;
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 #if 0
193         /* try for group relations */
194         if (old->group) {
195             GSList *sit;
196
197             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
198                 for (sit = old->group->members; sit; sit = sit->next)
199                     if (sit->data == it->data)
200                         if (sit->data != old && client_normal(sit->data))
201                             if (client_can_focus(sit->data)) {
202                                 gboolean r = client_focus(sit->data);
203                                 assert(r);
204                                 return;
205                             }
206         }
207 #endif
208     }
209
210     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
211         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
212             if (client_normal(it->data) &&
213                 /* dont fall back to 'anonymous' fullscreen windows. theres no
214                    checks for this is in transient/group fallbacks, so they can
215                    be fallback targets there. */
216                 !((ObClient*)it->data)->fullscreen &&
217                 client_can_focus(it->data)) {
218                 gboolean r = client_focus(it->data);
219                 assert(r);
220                 return;
221             }
222
223     /* nothing to focus, and already set it to none above */
224 }
225
226 static void popup_cycle(ObClient *c, gboolean show)
227 {
228     if (!show) {
229         icon_popup_hide(focus_cycle_popup);
230     } else {
231         Rect *a;
232         ObClient *p = c;
233         char *title;
234
235         a = screen_physical_area_monitor(0);
236         icon_popup_position(focus_cycle_popup, CenterGravity,
237                             a->x + a->width / 2, a->y + a->height / 2);
238 /*        icon_popup_size(focus_cycle_popup, a->height/2, a->height/16);
239         icon_popup_show(focus_cycle_popup, c->title,
240                         client_icon(c, a->height/16, a->height/16));
241 */
242         /* XXX the size and the font extents need to be related on some level
243          */
244         icon_popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
245
246         /* use the transient's parent's title/icon */
247         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
248             p = p->transient_for;
249
250         if (p == c)
251             title = NULL;
252         else
253             title = g_strconcat((c->iconic ? c->icon_title : c->title),
254                                 " - ",
255                                 (p->iconic ? p->icon_title : p->title),
256                                 NULL);
257
258         icon_popup_show(focus_cycle_popup,
259                         (title ? title :
260                          (c->iconic ? c->icon_title : c->title)),
261                         client_icon(p, 48, 48));
262         g_free(title);
263     }
264 }
265
266 static gboolean valid_focus_target(ObClient *ft)
267 {
268     /* we don't use client_can_focus here, because that doesn't let you
269        focus an iconic window, but we want to be able to, so we just check
270        if the focus flags on the window allow it, and its on the current
271        desktop */
272     return (ft->transients == NULL && client_normal(ft) &&
273             ((ft->can_focus || ft->focus_notify) &&
274              !ft->skip_taskbar &&
275              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL)));
276 }
277
278 void focus_cycle(gboolean forward, gboolean linear,
279                  gboolean dialog, gboolean done, gboolean cancel)
280 {
281     static ObClient *first = NULL;
282     static ObClient *t = NULL;
283     static GList *order = NULL;
284     GList *it, *start, *list;
285     ObClient *ft;
286
287     if (cancel) {
288         if (focus_cycle_target)
289             frame_adjust_focus(focus_cycle_target->frame, FALSE);
290         if (focus_client)
291             frame_adjust_focus(focus_client->frame, TRUE);
292         focus_cycle_target = NULL;
293         goto done_cycle;
294     } else if (done && dialog) {
295         goto done_cycle;
296     }
297
298     if (!focus_order[screen_desktop])
299         goto done_cycle;
300
301     if (!first) first = focus_client;
302     if (!focus_cycle_target) focus_cycle_target = focus_client;
303
304     if (linear) list = client_list;
305     else        list = focus_order[screen_desktop];
306
307     start = it = g_list_find(list, focus_cycle_target);
308     if (!start) /* switched desktops or something? */
309         start = it = forward ? g_list_last(list) : g_list_first(list);
310     if (!start) goto done_cycle;
311
312     do {
313         if (forward) {
314             it = it->next;
315             if (it == NULL) it = g_list_first(list);
316         } else {
317             it = it->prev;
318             if (it == NULL) it = g_list_last(list);
319         }
320         /*ft = client_focus_target(it->data);*/
321         ft = it->data;
322         if (valid_focus_target(ft)) {
323             if (ft != focus_cycle_target) { /* prevents flicker */
324                 if (focus_cycle_target)
325                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
326                 focus_cycle_target = ft;
327                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
328             }
329             popup_cycle(ft, dialog);
330             return;
331         }
332     } while (it != start);
333
334 done_cycle:
335     if (done && focus_cycle_target)
336         client_activate(focus_cycle_target, FALSE);
337
338     t = NULL;
339     first = NULL;
340     focus_cycle_target = NULL;
341     g_list_free(order);
342     order = NULL;
343
344     popup_cycle(ft, FALSE);
345
346     return;
347 }
348
349 void focus_directional_cycle(ObDirection dir,
350                              gboolean dialog, gboolean done, gboolean cancel)
351 {
352     static ObClient *first = NULL;
353     ObClient *ft = NULL;
354
355     if (cancel) {
356         if (focus_cycle_target)
357             frame_adjust_focus(focus_cycle_target->frame, FALSE);
358         if (focus_client)
359             frame_adjust_focus(focus_client->frame, TRUE);
360         focus_cycle_target = NULL;
361         goto done_cycle;
362     } else if (done && dialog) {
363         goto done_cycle;
364     }
365
366     if (!focus_order[screen_desktop])
367         goto done_cycle;
368
369     if (!first) first = focus_client;
370     if (!focus_cycle_target) focus_cycle_target = focus_client;
371
372     if (focus_cycle_target)
373         ft = client_find_directional(focus_cycle_target, dir);
374     else {
375         GList *it;
376
377         for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
378             if (valid_focus_target(it->data))
379                 ft = it->data;
380     }
381         
382     if (ft) {
383         if (ft != focus_cycle_target) {/* prevents flicker */
384             if (focus_cycle_target)
385                 frame_adjust_focus(focus_cycle_target->frame, FALSE);
386             focus_cycle_target = ft;
387             frame_adjust_focus(focus_cycle_target->frame, TRUE);
388         }
389         popup_cycle(ft, dialog);
390         if (dialog)
391             return;
392     }
393
394 done_cycle:
395     if (done && focus_cycle_target)
396         client_activate(focus_cycle_target, FALSE);
397
398     first = NULL;
399     focus_cycle_target = NULL;
400
401     popup_cycle(ft, FALSE);
402
403     return;
404 }
405
406 void focus_order_add_new(ObClient *c)
407 {
408     guint d, i;
409
410     if (c->iconic)
411         focus_order_to_top(c);
412     else {
413         d = c->desktop;
414         if (d == DESKTOP_ALL) {
415             for (i = 0; i < screen_num_desktops; ++i) {
416                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
417                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
418                 else
419                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
420             }
421         } else
422              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
423                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
424             else
425                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
426     }
427 }
428
429 void focus_order_remove(ObClient *c)
430 {
431     guint d, i;
432
433     d = c->desktop;
434     if (d == DESKTOP_ALL) {
435         for (i = 0; i < screen_num_desktops; ++i)
436             focus_order[i] = g_list_remove(focus_order[i], c);
437     } else
438         focus_order[d] = g_list_remove(focus_order[d], c);
439 }
440
441 static void to_top(ObClient *c, guint d)
442 {
443     focus_order[d] = g_list_remove(focus_order[d], c);
444     if (!c->iconic) {
445         focus_order[d] = g_list_prepend(focus_order[d], c);
446     } else {
447         GList *it;
448
449         /* insert before first iconic window */
450         for (it = focus_order[d];
451              it && !((ObClient*)it->data)->iconic; it = it->next);
452         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
453     }
454 }
455
456 void focus_order_to_top(ObClient *c)
457 {
458     guint d, i;
459
460     d = c->desktop;
461     if (d == DESKTOP_ALL) {
462         for (i = 0; i < screen_num_desktops; ++i)
463             to_top(c, i);
464     } else
465         to_top(c, d);
466 }
467
468 static void to_bottom(ObClient *c, guint d)
469 {
470     focus_order[d] = g_list_remove(focus_order[d], c);
471     if (c->iconic) {
472         focus_order[d] = g_list_append(focus_order[d], c);
473     } else {
474         GList *it;
475
476         /* insert before first iconic window */
477         for (it = focus_order[d];
478              it && !((ObClient*)it->data)->iconic; it = it->next);
479         g_list_insert_before(focus_order[d], it, c);
480     }
481 }
482
483 void focus_order_to_bottom(ObClient *c)
484 {
485     guint d, i;
486
487     d = c->desktop;
488     if (d == DESKTOP_ALL) {
489         for (i = 0; i < screen_num_desktops; ++i)
490             to_bottom(c, i);
491     } else
492         to_bottom(c, d);
493 }