add back focusLast
[mikachu/openbox.git] / openbox / focus.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus.c for the Openbox window manager
4    Copyright (c) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "debug.h"
20 #include "event.h"
21 #include "openbox.h"
22 #include "grab.h"
23 #include "framerender.h"
24 #include "client.h"
25 #include "config.h"
26 #include "frame.h"
27 #include "screen.h"
28 #include "group.h"
29 #include "prop.h"
30 #include "focus.h"
31 #include "stacking.h"
32 #include "popup.h"
33
34 #include <X11/Xlib.h>
35 #include <glib.h>
36 #include <assert.h>
37
38 ObClient *focus_client;
39 GList **focus_order; /* these lists are created when screen_startup
40                         sets the number of desktops */
41 ObClient *focus_cycle_target;
42
43 static ObIconPopup *focus_cycle_popup;
44
45 void focus_startup(gboolean reconfig)
46 {
47     focus_cycle_popup = icon_popup_new(TRUE);
48
49     if (!reconfig)
50         /* start with nothing focused */
51         focus_set_client(NULL);
52 }
53
54 void focus_shutdown(gboolean reconfig)
55 {
56     guint i;
57
58     icon_popup_free(focus_cycle_popup);
59
60     if (!reconfig) {
61         for (i = 0; i < screen_num_desktops; ++i)
62             g_list_free(focus_order[i]);
63         g_free(focus_order);
64
65         /* reset focus to root */
66         XSetInputFocus(ob_display, PointerRoot, RevertToPointerRoot,
67                        event_lasttime);
68     }
69 }
70
71 static void push_to_top(ObClient *client)
72 {
73     guint desktop;
74
75     desktop = client->desktop;
76     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
77     focus_order[desktop] = g_list_remove(focus_order[desktop], client);
78     focus_order[desktop] = g_list_prepend(focus_order[desktop], client);
79 }
80
81 void focus_set_client(ObClient *client)
82 {
83     Window active;
84     ObClient *old;
85
86 #ifdef DEBUG_FOCUS
87     ob_debug("focus_set_client 0x%lx\n", client ? client->window : 0);
88 #endif
89
90     /* uninstall the old colormap, and install the new one */
91     screen_install_colormap(focus_client, FALSE);
92     screen_install_colormap(client, TRUE);
93
94     if (client == NULL) {
95         /* when nothing will be focused, send focus to the backup target */
96         XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
97                        event_lasttime);
98         XSync(ob_display, FALSE);
99     }
100
101     /* in the middle of cycling..? kill it. */
102     if (focus_cycle_target)
103         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE);
104
105     old = focus_client;
106     focus_client = client;
107
108     /* move to the top of the list */
109     if (client != NULL)
110         push_to_top(client);
111
112     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
113     if (ob_state() != OB_STATE_EXITING) {
114         active = client ? client->window : None;
115         PROP_SET32(RootWindow(ob_display, ob_screen),
116                    net_active_window, window, active);
117     }
118 }
119
120 static gboolean focus_under_pointer()
121 {
122     ObClient *c;
123
124     if ((c = client_under_pointer()))
125         return client_normal(c) && client_focus(c);
126     return FALSE;
127 }
128
129 /* finds the first transient that isn't 'skip' and ensure's that client_normal
130  is true for it */
131 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
132 {
133     GSList *it;
134     ObClient *ret;
135
136     for (it = c->transients; it; it = it->next) {
137         if (it->data == top) return NULL;
138         ret = find_transient_recursive(it->data, top, skip);
139         if (ret && ret != skip && client_normal(ret)) return ret;
140         if (it->data != skip && client_normal(it->data)) return it->data;
141     }
142     return NULL;
143 }
144
145 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
146 {
147     ObClient *target = find_transient_recursive(top, top, old);
148     if (!target) {
149         /* make sure client_normal is true always */
150         if (!client_normal(top))
151             return FALSE;
152         target = top; /* no transient, keep the top */
153     }
154     return client_focus(target);
155 }
156
157 void focus_fallback(ObFocusFallbackType type)
158 {
159     GList *it;
160     ObClient *old = NULL;
161
162     old = focus_client;
163
164     /* unfocus any focused clients.. they can be focused by Pointer events
165        and such, and then when I try focus them, I won't get a FocusIn event
166        at all for them.
167     */
168     focus_set_client(NULL);
169
170     if (!config_focus_last && config_focus_follow)
171         if (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     }
390     if (focus_cycle_target) {
391         popup_cycle(focus_cycle_target, dialog);
392         if (dialog)
393             return;
394     }
395
396
397 done_cycle:
398     if (done && focus_cycle_target)
399         client_activate(focus_cycle_target, FALSE);
400
401     first = NULL;
402     focus_cycle_target = NULL;
403
404     popup_cycle(ft, FALSE);
405
406     return;
407 }
408
409 void focus_order_add_new(ObClient *c)
410 {
411     guint d, i;
412
413     if (c->iconic)
414         focus_order_to_top(c);
415     else {
416         d = c->desktop;
417         if (d == DESKTOP_ALL) {
418             for (i = 0; i < screen_num_desktops; ++i) {
419                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
420                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
421                 else
422                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
423             }
424         } else
425              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
426                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
427             else
428                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
429     }
430 }
431
432 void focus_order_remove(ObClient *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             focus_order[i] = g_list_remove(focus_order[i], c);
440     } else
441         focus_order[d] = g_list_remove(focus_order[d], c);
442 }
443
444 static void to_top(ObClient *c, guint d)
445 {
446     focus_order[d] = g_list_remove(focus_order[d], c);
447     if (!c->iconic) {
448         focus_order[d] = g_list_prepend(focus_order[d], c);
449     } else {
450         GList *it;
451
452         /* insert before first iconic window */
453         for (it = focus_order[d];
454              it && !((ObClient*)it->data)->iconic; it = it->next);
455         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
456     }
457 }
458
459 void focus_order_to_top(ObClient *c)
460 {
461     guint d, i;
462
463     d = c->desktop;
464     if (d == DESKTOP_ALL) {
465         for (i = 0; i < screen_num_desktops; ++i)
466             to_top(c, i);
467     } else
468         to_top(c, d);
469 }
470
471 static void to_bottom(ObClient *c, guint d)
472 {
473     focus_order[d] = g_list_remove(focus_order[d], c);
474     if (c->iconic) {
475         focus_order[d] = g_list_append(focus_order[d], c);
476     } else {
477         GList *it;
478
479         /* insert before first iconic window */
480         for (it = focus_order[d];
481              it && !((ObClient*)it->data)->iconic; it = it->next);
482         g_list_insert_before(focus_order[d], it, c);
483     }
484 }
485
486 void focus_order_to_bottom(ObClient *c)
487 {
488     guint d, i;
489
490     d = c->desktop;
491     if (d == DESKTOP_ALL) {
492         for (i = 0; i < screen_num_desktops; ++i)
493             to_bottom(c, i);
494     } else
495         to_bottom(c, d);
496 }