move the code to find the window under the pointer out of focus.c to client.c
[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_follow && focus_under_pointer())
171         return;
172
173     if (type == OB_FOCUS_FALLBACK_UNFOCUSING && old) {
174         /* try for transient relations */
175         if (old->transient_for) {
176             if (old->transient_for == OB_TRAN_GROUP) {
177                 for (it = focus_order[screen_desktop]; it; it = it->next) {
178                     GSList *sit;
179
180                     for (sit = old->group->members; sit; sit = sit->next)
181                         if (sit->data == it->data)
182                             if (focus_fallback_transient(sit->data, old))
183                                 return;
184                 }
185             } else {
186                 if (focus_fallback_transient(old->transient_for, old))
187                     return;
188             }
189         }
190
191 #if 0
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 #endif
207     }
208
209     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
210         if (type != OB_FOCUS_FALLBACK_UNFOCUSING || it->data != old)
211             if (client_normal(it->data) &&
212                 /* dont fall back to 'anonymous' fullscreen windows. theres no
213                    checks for this is in transient/group fallbacks, so they can
214                    be fallback targets there. */
215                 !((ObClient*)it->data)->fullscreen &&
216                 client_can_focus(it->data)) {
217                 gboolean r = client_focus(it->data);
218                 assert(r);
219                 return;
220             }
221
222     /* nothing to focus, and already set it to none above */
223 }
224
225 static void popup_cycle(ObClient *c, gboolean show)
226 {
227     if (!show) {
228         icon_popup_hide(focus_cycle_popup);
229     } else {
230         Rect *a;
231         ObClient *p = c;
232         char *title;
233
234         a = screen_physical_area_monitor(0);
235         icon_popup_position(focus_cycle_popup, CenterGravity,
236                             a->x + a->width / 2, a->y + a->height / 2);
237 /*        icon_popup_size(focus_cycle_popup, a->height/2, a->height/16);
238         icon_popup_show(focus_cycle_popup, c->title,
239                         client_icon(c, a->height/16, a->height/16));
240 */
241         /* XXX the size and the font extents need to be related on some level
242          */
243         icon_popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
244
245         /* use the transient's parent's title/icon */
246         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
247             p = p->transient_for;
248
249         if (p == c)
250             title = NULL;
251         else
252             title = g_strconcat((c->iconic ? c->icon_title : c->title),
253                                 " - ",
254                                 (p->iconic ? p->icon_title : p->title),
255                                 NULL);
256
257         icon_popup_show(focus_cycle_popup,
258                         (title ? title :
259                          (c->iconic ? c->icon_title : c->title)),
260                         client_icon(p, 48, 48));
261         g_free(title);
262     }
263 }
264
265 static gboolean valid_focus_target(ObClient *ft)
266 {
267     /* we don't use client_can_focus here, because that doesn't let you
268        focus an iconic window, but we want to be able to, so we just check
269        if the focus flags on the window allow it, and its on the current
270        desktop */
271     return (ft->transients == NULL && client_normal(ft) &&
272             ((ft->can_focus || ft->focus_notify) &&
273              !ft->skip_taskbar &&
274              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL)));
275 }
276
277 void focus_cycle(gboolean forward, gboolean linear,
278                  gboolean dialog, gboolean done, gboolean cancel)
279 {
280     static ObClient *first = NULL;
281     static ObClient *t = NULL;
282     static GList *order = NULL;
283     GList *it, *start, *list;
284     ObClient *ft;
285
286     if (cancel) {
287         if (focus_cycle_target)
288             frame_adjust_focus(focus_cycle_target->frame, FALSE);
289         if (focus_client)
290             frame_adjust_focus(focus_client->frame, TRUE);
291         focus_cycle_target = NULL;
292         goto done_cycle;
293     } else if (done && dialog) {
294         goto done_cycle;
295     }
296
297     if (!focus_order[screen_desktop])
298         goto done_cycle;
299
300     if (!first) first = focus_client;
301     if (!focus_cycle_target) focus_cycle_target = focus_client;
302
303     if (linear) list = client_list;
304     else        list = focus_order[screen_desktop];
305
306     start = it = g_list_find(list, focus_cycle_target);
307     if (!start) /* switched desktops or something? */
308         start = it = forward ? g_list_last(list) : g_list_first(list);
309     if (!start) goto done_cycle;
310
311     do {
312         if (forward) {
313             it = it->next;
314             if (it == NULL) it = g_list_first(list);
315         } else {
316             it = it->prev;
317             if (it == NULL) it = g_list_last(list);
318         }
319         /*ft = client_focus_target(it->data);*/
320         ft = it->data;
321         if (valid_focus_target(ft)) {
322             if (ft != focus_cycle_target) { /* prevents flicker */
323                 if (focus_cycle_target)
324                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
325                 focus_cycle_target = ft;
326                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
327             }
328             popup_cycle(ft, dialog);
329             return;
330         }
331     } while (it != start);
332
333 done_cycle:
334     if (done && focus_cycle_target)
335         client_activate(focus_cycle_target, FALSE);
336
337     t = NULL;
338     first = NULL;
339     focus_cycle_target = NULL;
340     g_list_free(order);
341     order = NULL;
342
343     popup_cycle(ft, FALSE);
344
345     return;
346 }
347
348 void focus_directional_cycle(ObDirection dir,
349                              gboolean dialog, gboolean done, gboolean cancel)
350 {
351     static ObClient *first = NULL;
352     ObClient *ft = NULL;
353
354     if (cancel) {
355         if (focus_cycle_target)
356             frame_adjust_focus(focus_cycle_target->frame, FALSE);
357         if (focus_client)
358             frame_adjust_focus(focus_client->frame, TRUE);
359         focus_cycle_target = NULL;
360         goto done_cycle;
361     } else if (done && dialog) {
362         goto done_cycle;
363     }
364
365     if (!focus_order[screen_desktop])
366         goto done_cycle;
367
368     if (!first) first = focus_client;
369     if (!focus_cycle_target) focus_cycle_target = focus_client;
370
371     if (focus_cycle_target)
372         ft = client_find_directional(focus_cycle_target, dir);
373     else {
374         GList *it;
375
376         for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
377             if (valid_focus_target(it->data))
378                 ft = it->data;
379     }
380         
381     if (ft) {
382         if (ft != focus_cycle_target) {/* prevents flicker */
383             if (focus_cycle_target)
384                 frame_adjust_focus(focus_cycle_target->frame, FALSE);
385             focus_cycle_target = ft;
386             frame_adjust_focus(focus_cycle_target->frame, TRUE);
387         }
388     }
389     if (focus_cycle_target) {
390         popup_cycle(focus_cycle_target, dialog);
391         if (dialog)
392             return;
393     }
394
395
396 done_cycle:
397     if (done && focus_cycle_target)
398         client_activate(focus_cycle_target, FALSE);
399
400     first = NULL;
401     focus_cycle_target = NULL;
402
403     popup_cycle(ft, FALSE);
404
405     return;
406 }
407
408 void focus_order_add_new(ObClient *c)
409 {
410     guint d, i;
411
412     if (c->iconic)
413         focus_order_to_top(c);
414     else {
415         d = c->desktop;
416         if (d == DESKTOP_ALL) {
417             for (i = 0; i < screen_num_desktops; ++i) {
418                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
419                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
420                 else
421                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
422             }
423         } else
424              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
425                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
426             else
427                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
428     }
429 }
430
431 void focus_order_remove(ObClient *c)
432 {
433     guint d, i;
434
435     d = c->desktop;
436     if (d == DESKTOP_ALL) {
437         for (i = 0; i < screen_num_desktops; ++i)
438             focus_order[i] = g_list_remove(focus_order[i], c);
439     } else
440         focus_order[d] = g_list_remove(focus_order[d], c);
441 }
442
443 static void to_top(ObClient *c, guint d)
444 {
445     focus_order[d] = g_list_remove(focus_order[d], c);
446     if (!c->iconic) {
447         focus_order[d] = g_list_prepend(focus_order[d], c);
448     } else {
449         GList *it;
450
451         /* insert before first iconic window */
452         for (it = focus_order[d];
453              it && !((ObClient*)it->data)->iconic; it = it->next);
454         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
455     }
456 }
457
458 void focus_order_to_top(ObClient *c)
459 {
460     guint d, i;
461
462     d = c->desktop;
463     if (d == DESKTOP_ALL) {
464         for (i = 0; i < screen_num_desktops; ++i)
465             to_top(c, i);
466     } else
467         to_top(c, d);
468 }
469
470 static void to_bottom(ObClient *c, guint d)
471 {
472     focus_order[d] = g_list_remove(focus_order[d], c);
473     if (c->iconic) {
474         focus_order[d] = g_list_append(focus_order[d], c);
475     } else {
476         GList *it;
477
478         /* insert before first iconic window */
479         for (it = focus_order[d];
480              it && !((ObClient*)it->data)->iconic; it = it->next);
481         g_list_insert_before(focus_order[d], it, c);
482     }
483 }
484
485 void focus_order_to_bottom(ObClient *c)
486 {
487     guint d, i;
488
489     d = c->desktop;
490     if (d == DESKTOP_ALL) {
491         for (i = 0; i < screen_num_desktops; ++i)
492             to_bottom(c, i);
493     } else
494         to_bottom(c, d);
495 }