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