revert my commit of d3th, it fucked up focus badly
[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 == NULL) {
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     /* move to the top of the list */
91     if (client != NULL)
92         push_to_top(client);
93
94     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
95     if (ob_state() != OB_STATE_EXITING) {
96         active = client ? client->window : None;
97         PROP_SET32(RootWindow(ob_display, ob_screen),
98                    net_active_window, window, active);
99     }
100 }
101
102 static gboolean focus_under_pointer()
103 {
104     int x, y;
105     GList *it;
106
107     if (screen_pointer_pos(&x, &y)) {
108         for (it = stacking_list; it != NULL; it = it->next) {
109             if (WINDOW_IS_CLIENT(it->data)) {
110                 ObClient *c = WINDOW_AS_CLIENT(it->data);
111                 if (c->desktop == screen_desktop &&
112                     RECT_CONTAINS(c->frame->area, x, y))
113                     break;
114             }
115         }
116         if (it != NULL) {
117             g_assert(WINDOW_IS_CLIENT(it->data));
118             return client_normal(it->data) && client_focus(it->data);
119         }
120     }
121     return FALSE;
122 }
123
124 /* finds the first transient that isn't 'skip' and ensure's that client_normal
125  is true for it */
126 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
127 {
128     GSList *it;
129     ObClient *ret;
130
131     for (it = c->transients; it; it = it->next) {
132         if (it->data == top) return NULL;
133         ret = find_transient_recursive(it->data, top, skip);
134         if (ret && ret != skip && client_normal(ret)) return ret;
135         if (it->data != skip && client_normal(it->data)) return it->data;
136     }
137     return NULL;
138 }
139
140 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
141 {
142     ObClient *target = find_transient_recursive(top, top, old);
143     if (!target) {
144         /* make sure client_normal is true always */
145         if (!client_normal(top))
146             return FALSE;
147         target = top; /* no transient, keep the top */
148     }
149     return client_focus(target);
150 }
151
152 void focus_fallback(ObFocusFallbackType type)
153 {
154     GList *it;
155     ObClient *old = NULL;
156
157     old = focus_client;
158
159     /* unfocus any focused clients.. they can be focused by Pointer events
160        and such, and then when I try focus them, I won't get a FocusIn event
161        at all for them.
162     */
163     focus_set_client(NULL);
164
165     if (config_focus_follow && focus_under_pointer())
166         return;
167
168     if (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
169         /* try for transient relations */
170         if (old->transient_for) {
171             if (old->transient_for == OB_TRAN_GROUP) {
172                 for (it = focus_order[screen_desktop]; it; it = it->next) {
173                     GSList *sit;
174
175                     for (sit = old->group->members; sit; sit = sit->next)
176                         if (sit->data == it->data)
177                             if (focus_fallback_transient(sit->data, old))
178                                 return;
179                 }
180             } else {
181                 if (focus_fallback_transient(old->transient_for, old))
182                     return;
183             }
184         }
185
186 #if 0
187         /* try for group relations */
188         if (old->group) {
189             GSList *sit;
190
191             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
192                 for (sit = old->group->members; sit; sit = sit->next)
193                     if (sit->data == it->data)
194                         if (sit->data != old && client_normal(sit->data))
195                             if (client_can_focus(sit->data)) {
196                                 gboolean r = client_focus(sit->data);
197                                 assert(r);
198                                 return;
199                             }
200         }
201 #endif
202     }
203
204     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
205         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
206             if (client_normal(it->data) &&
207                 /* dont fall back to 'anonymous' fullscreen windows. theres no
208                    checks for this is in transient/group fallbacks, so they can
209                    be fallback targets there. */
210                 !((ObClient*)it->data)->fullscreen &&
211                 client_can_focus(it->data)) {
212                 gboolean r = client_focus(it->data);
213                 assert(r);
214                 return;
215             }
216
217     /* nothing to focus, and already set it to none above */
218 }
219
220 static void popup_cycle(ObClient *c, gboolean show)
221 {
222     if (!show) {
223         icon_popup_hide(focus_cycle_popup);
224     } else {
225         Rect *a;
226         ObClient *p = c;
227         char *title;
228
229         a = screen_physical_area_monitor(0);
230         icon_popup_position(focus_cycle_popup, CenterGravity,
231                             a->x + a->width / 2, a->y + a->height / 2);
232 /*        icon_popup_size(focus_cycle_popup, a->height/2, a->height/16);
233         icon_popup_show(focus_cycle_popup, c->title,
234                         client_icon(c, a->height/16, a->height/16));
235 */
236         /* XXX the size and the font extents need to be related on some level
237          */
238         icon_popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
239
240         /* use the transient's parent's title/icon */
241         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
242             p = p->transient_for;
243
244         if (p == c)
245             title = NULL;
246         else
247             title = g_strconcat((c->iconic ? c->icon_title : c->title),
248                                 " - ",
249                                 (p->iconic ? p->icon_title : p->title),
250                                 NULL);
251
252         icon_popup_show(focus_cycle_popup,
253                         (title ? title :
254                          (c->iconic ? c->icon_title : c->title)),
255                         client_icon(p, 48, 48));
256         g_free(title);
257     }
258 }
259
260 static gboolean valid_focus_target(ObClient *ft)
261 {
262     /* we don't use client_can_focus here, because that doesn't let you
263        focus an iconic window, but we want to be able to, so we just check
264        if the focus flags on the window allow it, and its on the current
265        desktop */
266     return (ft->transients == NULL && client_normal(ft) &&
267             ((ft->can_focus || ft->focus_notify) &&
268              !ft->skip_taskbar &&
269              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL)));
270 }
271
272 void focus_cycle(gboolean forward, gboolean linear,
273                  gboolean dialog, gboolean done, gboolean cancel)
274 {
275     static ObClient *first = NULL;
276     static ObClient *t = NULL;
277     static GList *order = NULL;
278     GList *it, *start, *list;
279     ObClient *ft;
280
281     if (cancel) {
282         if (focus_cycle_target)
283             frame_adjust_focus(focus_cycle_target->frame, FALSE);
284         if (focus_client)
285             frame_adjust_focus(focus_client->frame, TRUE);
286         focus_cycle_target = NULL;
287         goto done_cycle;
288     } else if (done && dialog) {
289         goto done_cycle;
290     }
291
292     if (!focus_order[screen_desktop])
293         goto done_cycle;
294
295     if (!first) first = focus_client;
296     if (!focus_cycle_target) focus_cycle_target = focus_client;
297
298     if (linear) list = client_list;
299     else        list = focus_order[screen_desktop];
300
301     start = it = g_list_find(list, focus_cycle_target);
302     if (!start) /* switched desktops or something? */
303         start = it = forward ? g_list_last(list) : g_list_first(list);
304     if (!start) goto done_cycle;
305
306     do {
307         if (forward) {
308             it = it->next;
309             if (it == NULL) it = g_list_first(list);
310         } else {
311             it = it->prev;
312             if (it == NULL) it = g_list_last(list);
313         }
314         /*ft = client_focus_target(it->data);*/
315         ft = it->data;
316         if (valid_focus_target(ft)) {
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, dialog);
324             return;
325         }
326     } while (it != start);
327
328 done_cycle:
329     if (done && focus_cycle_target)
330         client_activate(focus_cycle_target, FALSE);
331
332     t = NULL;
333     first = NULL;
334     focus_cycle_target = NULL;
335     g_list_free(order);
336     order = NULL;
337
338     popup_cycle(ft, FALSE);
339
340     return;
341 }
342
343 void focus_directional_cycle(ObDirection dir,
344                              gboolean dialog, gboolean done, gboolean cancel)
345 {
346     static ObClient *first = NULL;
347     ObClient *ft = NULL;
348
349     if (cancel) {
350         if (focus_cycle_target)
351             frame_adjust_focus(focus_cycle_target->frame, FALSE);
352         if (focus_client)
353             frame_adjust_focus(focus_client->frame, TRUE);
354         focus_cycle_target = NULL;
355         goto done_cycle;
356     } else if (done && dialog) {
357         goto done_cycle;
358     }
359
360     if (!focus_order[screen_desktop])
361         goto done_cycle;
362
363     if (!first) first = focus_client;
364     if (!focus_cycle_target) focus_cycle_target = focus_client;
365
366     if (focus_cycle_target)
367         ft = client_find_directional(focus_cycle_target, dir);
368     else {
369         GList *it;
370
371         for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
372             if (valid_focus_target(it->data))
373                 ft = it->data;
374     }
375         
376     if (ft) {
377         if (ft != focus_cycle_target) {/* prevents flicker */
378             if (focus_cycle_target)
379                 frame_adjust_focus(focus_cycle_target->frame, FALSE);
380             focus_cycle_target = ft;
381             frame_adjust_focus(focus_cycle_target->frame, TRUE);
382         }
383         popup_cycle(ft, dialog);
384         if (dialog)
385             return;
386     }
387
388 done_cycle:
389     if (done && focus_cycle_target)
390         client_activate(focus_cycle_target, FALSE);
391
392     first = NULL;
393     focus_cycle_target = NULL;
394
395     popup_cycle(ft, FALSE);
396
397     return;
398 }
399
400 void focus_order_add_new(ObClient *c)
401 {
402     guint d, i;
403
404     if (c->iconic)
405         focus_order_to_top(c);
406     else {
407         d = c->desktop;
408         if (d == DESKTOP_ALL) {
409             for (i = 0; i < screen_num_desktops; ++i) {
410                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
411                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
412                 else
413                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
414             }
415         } else
416              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
417                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
418             else
419                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
420     }
421 }
422
423 void focus_order_remove(ObClient *c)
424 {
425     guint d, i;
426
427     d = c->desktop;
428     if (d == DESKTOP_ALL) {
429         for (i = 0; i < screen_num_desktops; ++i)
430             focus_order[i] = g_list_remove(focus_order[i], c);
431     } else
432         focus_order[d] = g_list_remove(focus_order[d], c);
433 }
434
435 static void to_top(ObClient *c, guint d)
436 {
437     focus_order[d] = g_list_remove(focus_order[d], c);
438     if (!c->iconic) {
439         focus_order[d] = g_list_prepend(focus_order[d], c);
440     } else {
441         GList *it;
442
443         /* insert before first iconic window */
444         for (it = focus_order[d];
445              it && !((ObClient*)it->data)->iconic; it = it->next);
446         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
447     }
448 }
449
450 void focus_order_to_top(ObClient *c)
451 {
452     guint d, i;
453
454     d = c->desktop;
455     if (d == DESKTOP_ALL) {
456         for (i = 0; i < screen_num_desktops; ++i)
457             to_top(c, i);
458     } else
459         to_top(c, d);
460 }
461
462 static void to_bottom(ObClient *c, guint d)
463 {
464     focus_order[d] = g_list_remove(focus_order[d], c);
465     if (c->iconic) {
466         focus_order[d] = g_list_append(focus_order[d], c);
467     } else {
468         GList *it;
469
470         /* insert before first iconic window */
471         for (it = focus_order[d];
472              it && !((ObClient*)it->data)->iconic; it = it->next);
473         g_list_insert_before(focus_order[d], it, c);
474     }
475 }
476
477 void focus_order_to_bottom(ObClient *c)
478 {
479     guint d, i;
480
481     d = c->desktop;
482     if (d == DESKTOP_ALL) {
483         for (i = 0; i < screen_num_desktops; ++i)
484             to_bottom(c, i);
485     } else
486         to_bottom(c, d);
487 }