add copyright headers, adjust --version output to include copyright, and --help outpu...
[dana/openbox.git] / openbox / focus.c
1 /* -*- indent-tabs-mode: t; 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     int x, y;
123     GList *it;
124
125     if (screen_pointer_pos(&x, &y)) {
126         for (it = stacking_list; it != NULL; it = it->next) {
127             if (WINDOW_IS_CLIENT(it->data)) {
128                 ObClient *c = WINDOW_AS_CLIENT(it->data);
129                 if (c->desktop == screen_desktop &&
130                     RECT_CONTAINS(c->frame->area, x, y))
131                     break;
132             }
133         }
134         if (it != NULL) {
135             g_assert(WINDOW_IS_CLIENT(it->data));
136             return client_normal(it->data) && client_focus(it->data);
137         }
138     }
139     return FALSE;
140 }
141
142 /* finds the first transient that isn't 'skip' and ensure's that client_normal
143  is true for it */
144 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
145 {
146     GSList *it;
147     ObClient *ret;
148
149     for (it = c->transients; it; it = it->next) {
150         if (it->data == top) return NULL;
151         ret = find_transient_recursive(it->data, top, skip);
152         if (ret && ret != skip && client_normal(ret)) return ret;
153         if (it->data != skip && client_normal(it->data)) return it->data;
154     }
155     return NULL;
156 }
157
158 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
159 {
160     ObClient *target = find_transient_recursive(top, top, old);
161     if (!target) {
162         /* make sure client_normal is true always */
163         if (!client_normal(top))
164             return FALSE;
165         target = top; /* no transient, keep the top */
166     }
167     return client_focus(target);
168 }
169
170 void focus_fallback(ObFocusFallbackType type)
171 {
172     GList *it;
173     ObClient *old = NULL;
174
175     old = focus_client;
176
177     /* unfocus any focused clients.. they can be focused by Pointer events
178        and such, and then when I try focus them, I won't get a FocusIn event
179        at all for them.
180     */
181     focus_set_client(NULL);
182
183     if (config_focus_follow && 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         popup_cycle(ft, dialog);
402         if (dialog)
403             return;
404     }
405
406 done_cycle:
407     if (done && focus_cycle_target)
408         client_activate(focus_cycle_target, FALSE);
409
410     first = NULL;
411     focus_cycle_target = NULL;
412
413     popup_cycle(ft, FALSE);
414
415     return;
416 }
417
418 void focus_order_add_new(ObClient *c)
419 {
420     guint d, i;
421
422     if (c->iconic)
423         focus_order_to_top(c);
424     else {
425         d = c->desktop;
426         if (d == DESKTOP_ALL) {
427             for (i = 0; i < screen_num_desktops; ++i) {
428                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
429                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
430                 else
431                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
432             }
433         } else
434              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
435                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
436             else
437                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
438     }
439 }
440
441 void focus_order_remove(ObClient *c)
442 {
443     guint d, i;
444
445     d = c->desktop;
446     if (d == DESKTOP_ALL) {
447         for (i = 0; i < screen_num_desktops; ++i)
448             focus_order[i] = g_list_remove(focus_order[i], c);
449     } else
450         focus_order[d] = g_list_remove(focus_order[d], c);
451 }
452
453 static void to_top(ObClient *c, guint d)
454 {
455     focus_order[d] = g_list_remove(focus_order[d], c);
456     if (!c->iconic) {
457         focus_order[d] = g_list_prepend(focus_order[d], c);
458     } else {
459         GList *it;
460
461         /* insert before first iconic window */
462         for (it = focus_order[d];
463              it && !((ObClient*)it->data)->iconic; it = it->next);
464         focus_order[d] = g_list_insert_before(focus_order[d], it, c);
465     }
466 }
467
468 void focus_order_to_top(ObClient *c)
469 {
470     guint d, i;
471
472     d = c->desktop;
473     if (d == DESKTOP_ALL) {
474         for (i = 0; i < screen_num_desktops; ++i)
475             to_top(c, i);
476     } else
477         to_top(c, d);
478 }
479
480 static void to_bottom(ObClient *c, guint d)
481 {
482     focus_order[d] = g_list_remove(focus_order[d], c);
483     if (c->iconic) {
484         focus_order[d] = g_list_append(focus_order[d], c);
485     } else {
486         GList *it;
487
488         /* insert before first iconic window */
489         for (it = focus_order[d];
490              it && !((ObClient*)it->data)->iconic; it = it->next);
491         g_list_insert_before(focus_order[d], it, c);
492     }
493 }
494
495 void focus_order_to_bottom(ObClient *c)
496 {
497     guint d, i;
498
499     d = c->desktop;
500     if (d == DESKTOP_ALL) {
501         for (i = 0; i < screen_num_desktops; ++i)
502             to_bottom(c, i);
503     } else
504         to_bottom(c, d);
505 }