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