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