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