rename the Client struct to ObClient
[dana/openbox.git] / openbox / focus.c
1 #include "event.h"
2 #include "openbox.h"
3 #include "grab.h"
4 #include "framerender.h"
5 #include "client.h"
6 #include "config.h"
7 #include "frame.h"
8 #include "screen.h"
9 #include "group.h"
10 #include "prop.h"
11 #include "dispatch.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 = NULL;
21 GList **focus_order = NULL; /* these lists are created when screen_startup
22                                sets the number of desktops */
23
24 Window focus_backup = None;
25
26 static ObClient *focus_cycle_target = NULL;
27 static Popup *focus_cycle_popup = NULL;
28
29 void focus_startup()
30 {
31     /* create the window which gets focus when no clients get it. Have to
32        make it override-redirect so we don't try manage it, since it is
33        mapped. */
34     XSetWindowAttributes attrib;
35
36     focus_client = NULL;
37
38     attrib.override_redirect = TRUE;
39     focus_backup = XCreateWindow(ob_display, ob_root,
40                                  -100, -100, 1, 1, 0,
41                                  CopyFromParent, InputOutput, CopyFromParent,
42                                  CWOverrideRedirect, &attrib);
43     XMapRaised(ob_display, focus_backup);
44
45     /* do this *after* focus_backup is created, since it is used for
46        stacking */
47     focus_cycle_popup = popup_new(TRUE);
48
49     /* start with nothing focused */
50     focus_set_client(NULL);
51 }
52
53 void focus_shutdown()
54 {
55     guint i;
56
57     for (i = 0; i < screen_num_desktops; ++i)
58         g_list_free(focus_order[i]);
59     g_free(focus_order);
60     focus_order = NULL;
61
62     popup_free(focus_cycle_popup);
63     focus_cycle_popup = NULL;
64
65     XDestroyWindow(ob_display, focus_backup);
66
67     /* reset focus to root */
68     XSetInputFocus(ob_display, PointerRoot, RevertToPointerRoot,
69                    event_lasttime);
70 }
71
72 static void push_to_top(ObClient *client)
73 {
74     guint desktop;
75
76     desktop = client->desktop;
77     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
78     focus_order[desktop] = g_list_remove(focus_order[desktop], client);
79     focus_order[desktop] = g_list_prepend(focus_order[desktop], client);
80 }
81
82 void focus_set_client(ObClient *client)
83 {
84     Window active;
85     ObClient *old;
86
87 #ifdef DEBUG_FOCUS
88     g_message("focus_set_client 0x%lx", client ? client->window : 0);
89 #endif
90
91     /* uninstall the old colormap, and install the new one */
92     screen_install_colormap(focus_client, FALSE);
93     screen_install_colormap(client, TRUE);
94
95     if (client == NULL) {
96         /* when nothing will be focused, send focus to the backup target */
97         XSetInputFocus(ob_display, focus_backup, RevertToPointerRoot,
98                        event_lasttime);
99         XSync(ob_display, FALSE);
100     }
101
102     /* in the middle of cycling..? kill it. */
103     if (focus_cycle_target)
104         focus_cycle(TRUE, TRUE, TRUE, TRUE);
105
106     old = focus_client;
107     focus_client = client;
108
109     /* move to the top of the list */
110     if (client != NULL)
111         push_to_top(client);
112
113     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
114     if (ob_state != OB_STATE_EXITING) {
115         active = client ? client->window : None;
116         PROP_SET32(ob_root, net_active_window, window, active);
117     }
118
119     if (focus_client != NULL)
120         dispatch_client(Event_Client_Focus, focus_client, 0, 0);
121     if (old != NULL)
122         dispatch_client(Event_Client_Unfocus, old, 0, 0);
123 }
124
125 static gboolean focus_under_pointer()
126 {
127     int x, y;
128     GList *it;
129
130     if (ob_pointer_pos(&x, &y)) {
131         for (it = stacking_list; it != NULL; it = it->next) {
132             if (WINDOW_IS_CLIENT(it->data)) {
133                 ObClient *c = WINDOW_AS_CLIENT(it->data);
134                 if (c->desktop == screen_desktop &&
135                     RECT_CONTAINS(c->frame->area, x, y))
136                     break;
137             }
138         }
139         if (it != NULL) {
140             g_assert(WINDOW_IS_CLIENT(it->data));
141             return client_normal(it->data) && client_focus(it->data);
142         }
143     }
144     return FALSE;
145 }
146
147 /* finds the first transient that isn't 'skip' and ensure's that client_normal
148  is true for it */
149 static ObClient *find_transient_recursive(ObClient *c, ObClient *top, ObClient *skip)
150 {
151     GSList *it;
152     ObClient *ret;
153
154     for (it = c->transients; it; it = it->next) {
155         if (it->data == top) return NULL;
156         ret = find_transient_recursive(it->data, top, skip);
157         if (ret && ret != skip && client_normal(ret)) return ret;
158         if (it->data != skip && client_normal(it->data)) return it->data;
159     }
160     return NULL;
161 }
162
163 static gboolean focus_fallback_transient(ObClient *top, ObClient *old)
164 {
165     ObClient *target = find_transient_recursive(top, top, old);
166     if (!target) {
167         /* make sure client_normal is true always */
168         if (!client_normal(top))
169             return FALSE;
170         target = top; /* no transient, keep the top */
171     }
172     return client_focus(target);
173 }
174
175 void focus_fallback(FallbackType type)
176 {
177     GList *it;
178     ObClient *old = NULL;
179
180     old = focus_client;
181
182     /* unfocus any focused clients.. they can be focused by Pointer events
183        and such, and then when I try focus them, I won't get a FocusIn event
184        at all for them.
185     */
186     focus_set_client(NULL);
187
188     if (!(type == Fallback_Desktop ?
189           config_focus_last_on_desktop : config_focus_last)) {
190         if (config_focus_follow) focus_under_pointer();
191         return;
192     }
193
194     if (type == Fallback_Unfocusing && old) {
195         /* try for transient relations */
196         if (old->transient_for) {
197             if (old->transient_for == TRAN_GROUP) {
198                 for (it = focus_order[screen_desktop]; it; it = it->next) {
199                     GSList *sit;
200
201                     for (sit = old->group->members; sit; sit = sit->next)
202                         if (sit->data == it->data)
203                             if (focus_fallback_transient(sit->data, old))
204                                 return;
205                 }
206             } else {
207                 if (focus_fallback_transient(old->transient_for, old))
208                     return;
209             }
210         }
211
212         /* try for group relations */
213         if (old->group) {
214             GSList *sit;
215
216             for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
217                 for (sit = old->group->members; sit; sit = sit->next)
218                     if (sit->data == it->data)
219                         if (sit->data != old && client_normal(sit->data))
220                             if (client_can_focus(sit->data)) {
221                                 gboolean r = client_focus(sit->data);
222                                 assert(r);
223                                 return;
224                             }
225         }
226     }
227
228     for (it = focus_order[screen_desktop]; it != NULL; it = it->next)
229         if (type != Fallback_Unfocusing || it->data != old)
230             if (client_normal(it->data) &&
231                 /* dont fall back to 'anonymous' fullscreen windows. theres no
232                    checks for this is in transient/group fallbacks, so they can
233                    be fallback targets there. */
234                 !((ObClient*)it->data)->fullscreen &&
235                 client_can_focus(it->data)) {
236                 gboolean r = client_focus(it->data);
237                 assert(r);
238                 return;
239             }
240
241     /* nothing to focus, and already set it to none above */
242 }
243
244 static void popup_cycle(ObClient *c, gboolean show)
245 {
246     if (!show) {
247         popup_hide(focus_cycle_popup);
248     } else {
249         Rect *a;
250         ObClient *p = c;
251         char *title;
252
253         a = screen_physical_area_monitor(0);
254         popup_position(focus_cycle_popup, CenterGravity,
255                        a->x + a->width / 2, a->y + a->height / 2);
256 /*        popup_size(focus_cycle_popup, a->height/2, a->height/16);
257         popup_show(focus_cycle_popup, c->title,
258                    client_icon(c, a->height/16, a->height/16));
259 */
260         /* XXX the size and the font extents need to be related on some level
261          */
262         popup_size(focus_cycle_popup, 320, 48);
263
264         /* use the transient's parent's title/icon */
265         while (p->transient_for && p->transient_for != TRAN_GROUP)
266             p = p->transient_for;
267
268         if (p == c)
269             title = NULL;
270         else
271             title = g_strconcat((c->iconic ? c->icon_title : c->title),
272                                 " - ",
273                                 (p->iconic ? p->icon_title : p->title),
274                                 NULL);
275
276         popup_show(focus_cycle_popup,
277                    (title ? title : (c->iconic ? c->icon_title : c->title)),
278                    client_icon(p, 48, 48));
279         g_free(title);
280     }
281 }
282
283 ObClient *focus_cycle(gboolean forward, gboolean linear, gboolean done,
284                     gboolean cancel)
285 {
286     static ObClient *first = NULL;
287     static ObClient *t = NULL;
288     static GList *order = NULL;
289     GList *it, *start, *list;
290     ObClient *ft;
291
292     if (cancel) {
293         if (focus_cycle_target)
294             frame_adjust_focus(focus_cycle_target->frame, FALSE);
295         if (focus_client)
296             frame_adjust_focus(focus_client->frame, TRUE);
297         goto done_cycle;
298     } else if (done) {
299         if (focus_cycle_target)
300             client_activate(focus_cycle_target);
301         goto done_cycle;
302     }
303     if (!first)
304         grab_pointer(TRUE, None);
305
306     if (!first) first = focus_client;
307     if (!focus_cycle_target) focus_cycle_target = focus_client;
308
309     if (linear) list = client_list;
310     else        list = focus_order[screen_desktop];
311
312     start = it = g_list_find(list, focus_cycle_target);
313     if (!start) /* switched desktops or something? */
314         start = it = forward ? g_list_last(list) : g_list_first(list);
315     if (!start) goto done_cycle;
316
317     do {
318         if (forward) {
319             it = it->next;
320             if (it == NULL) it = g_list_first(list);
321         } else {
322             it = it->prev;
323             if (it == NULL) it = g_list_last(list);
324         }
325         /*ft = client_focus_target(it->data);*/
326         ft = it->data;
327         /* we don't use client_can_focus here, because that doesn't let you
328            focus an iconic window, but we want to be able to, so we just check
329            if the focus flags on the window allow it, and its on the current
330            desktop */
331         if (ft->transients == NULL && client_normal(ft) &&
332             ((ft->can_focus || ft->focus_notify) &&
333              (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL))) {
334             if (ft != focus_cycle_target) { /* prevents flicker */
335                 if (focus_cycle_target)
336                     frame_adjust_focus(focus_cycle_target->frame, FALSE);
337                 focus_cycle_target = ft;
338                 frame_adjust_focus(focus_cycle_target->frame, TRUE);
339             }
340             popup_cycle(ft, config_focus_popup);
341             return ft;
342         }
343     } while (it != start);
344
345 done_cycle:
346     t = NULL;
347     first = NULL;
348     focus_cycle_target = NULL;
349     g_list_free(order);
350     order = NULL;
351
352     popup_cycle(ft, FALSE);
353     grab_pointer(FALSE, None);
354
355     return NULL;
356 }
357
358 void focus_order_add_new(ObClient *c)
359 {
360     guint d, i;
361
362     if (c->iconic)
363         focus_order_to_top(c);
364     else {
365         d = c->desktop;
366         if (d == DESKTOP_ALL) {
367             for (i = 0; i < screen_num_desktops; ++i) {
368                 if (focus_order[i] && ((ObClient*)focus_order[i]->data)->iconic)
369                     focus_order[i] = g_list_insert(focus_order[i], c, 0);
370                 else
371                     focus_order[i] = g_list_insert(focus_order[i], c, 1);
372             }
373         } else
374              if (focus_order[d] && ((ObClient*)focus_order[d]->data)->iconic)
375                 focus_order[d] = g_list_insert(focus_order[d], c, 0);
376             else
377                 focus_order[d] = g_list_insert(focus_order[d], c, 1);
378     }
379 }
380
381 void focus_order_remove(ObClient *c)
382 {
383     guint d, i;
384
385     d = c->desktop;
386     if (d == DESKTOP_ALL) {
387         for (i = 0; i < screen_num_desktops; ++i)
388             focus_order[i] = g_list_remove(focus_order[i], c);
389     } else
390         focus_order[d] = g_list_remove(focus_order[d], c);
391 }
392
393 static void to_top(ObClient *c, guint d)
394 {
395     focus_order[d] = g_list_remove(focus_order[d], c);
396     if (!c->iconic) {
397         focus_order[d] = g_list_prepend(focus_order[d], c);
398     } else {
399         GList *it;
400
401         /* insert before first iconic window */
402         for (it = focus_order[d];
403              it && !((ObClient*)it->data)->iconic; it = it->next);
404         g_list_insert_before(focus_order[d], it, c);
405     }
406 }
407
408 void focus_order_to_top(ObClient *c)
409 {
410     guint d, i;
411
412     d = c->desktop;
413     if (d == DESKTOP_ALL) {
414         for (i = 0; i < screen_num_desktops; ++i)
415             to_top(c, i);
416     } else
417         to_top(c, d);
418 }
419
420 static void to_bottom(ObClient *c, guint d)
421 {
422     focus_order[d] = g_list_remove(focus_order[d], c);
423     if (c->iconic) {
424         focus_order[d] = g_list_append(focus_order[d], c);
425     } else {
426         GList *it;
427
428         /* insert before first iconic window */
429         for (it = focus_order[d];
430              it && !((ObClient*)it->data)->iconic; it = it->next);
431         g_list_insert_before(focus_order[d], it, c);
432     }
433 }
434
435 void focus_order_to_bottom(ObClient *c)
436 {
437     guint d, i;
438
439     d = c->desktop;
440     if (d == DESKTOP_ALL) {
441         for (i = 0; i < screen_num_desktops; ++i)
442             to_bottom(c, i);
443     } else
444         to_bottom(c, d);
445 }