]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
likewise if a window is unmanaged and we were trying to give it focus, we want to...
[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) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "event.h"
22 #include "openbox.h"
23 #include "grab.h"
24 #include "framerender.h"
25 #include "client.h"
26 #include "config.h"
27 #include "frame.h"
28 #include "screen.h"
29 #include "group.h"
30 #include "prop.h"
31 #include "focus.h"
32 #include "stacking.h"
33 #include "popup.h"
34 #include "render/render.h"
35
36 #include <X11/Xlib.h>
37 #include <glib.h>
38 #include <assert.h>
39
40 #define FOCUS_INDICATOR_WIDTH 6
41
42 ObClient *focus_client = NULL;
43 GList *focus_order = NULL;
44 ObClient *focus_cycle_target = NULL;
45
46 /*! This variable is used for focus fallback. If we fallback to a window, we
47   set this to the window. And when focus goes somewhere after that, it will
48   be set to NULL. If between falling back to that window and something
49   getting focused, the window gets unmanaged, then if there are no incoming
50   FocusIn events, we fallback again because focus has just gotten itself lost.
51  */
52 static ObClient *focus_tried = NULL;
53
54 struct {
55     InternalWindow top;
56     InternalWindow left;
57     InternalWindow right;
58     InternalWindow bottom;
59 } focus_indicator;
60
61 RrAppearance *a_focus_indicator;
62 RrColor *color_white;
63
64 static ObIconPopup *focus_cycle_popup;
65
66 static gboolean valid_focus_target(ObClient *ft,
67                                    gboolean all_desktops,
68                                    gboolean dock_windows,
69                                    gboolean desktop_windows);
70 static void focus_cycle_destructor(ObClient *client, gpointer data);
71 static void focus_tried_hide_notify(ObClient *client, gpointer data);
72
73 static Window createWindow(Window parent, gulong mask,
74                            XSetWindowAttributes *attrib)
75 {
76     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
77                          RrDepth(ob_rr_inst), InputOutput,
78                          RrVisual(ob_rr_inst), mask, attrib);
79                        
80 }
81
82 void focus_startup(gboolean reconfig)
83 {
84     focus_cycle_popup = icon_popup_new(TRUE);
85
86     if (!reconfig) {
87         XSetWindowAttributes attr;
88
89         client_add_destructor(focus_cycle_destructor, NULL);
90         client_add_hide_notify(focus_tried_hide_notify, NULL);
91
92         /* start with nothing focused */
93         focus_nothing();
94
95         focus_indicator.top.obwin.type = Window_Internal;
96         focus_indicator.left.obwin.type = Window_Internal;
97         focus_indicator.right.obwin.type = Window_Internal;
98         focus_indicator.bottom.obwin.type = Window_Internal;
99
100         attr.override_redirect = True;
101         attr.background_pixel = BlackPixel(ob_display, ob_screen);
102         focus_indicator.top.win =
103             createWindow(RootWindow(ob_display, ob_screen),
104                          CWOverrideRedirect | CWBackPixel, &attr);
105         focus_indicator.left.win =
106             createWindow(RootWindow(ob_display, ob_screen),
107                          CWOverrideRedirect | CWBackPixel, &attr);
108         focus_indicator.right.win =
109             createWindow(RootWindow(ob_display, ob_screen),
110                          CWOverrideRedirect | CWBackPixel, &attr);
111         focus_indicator.bottom.win =
112             createWindow(RootWindow(ob_display, ob_screen),
113                          CWOverrideRedirect | CWBackPixel, &attr);
114
115         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.top));
116         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.left));
117         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.right));
118         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.bottom));
119
120         color_white = RrColorNew(ob_rr_inst, 0xff, 0xff, 0xff);
121
122         a_focus_indicator = RrAppearanceNew(ob_rr_inst, 4);
123         a_focus_indicator->surface.grad = RR_SURFACE_SOLID;
124         a_focus_indicator->surface.relief = RR_RELIEF_FLAT;
125         a_focus_indicator->surface.primary = RrColorNew(ob_rr_inst,
126                                                         0, 0, 0);
127         a_focus_indicator->texture[0].type = RR_TEXTURE_LINE_ART;
128         a_focus_indicator->texture[0].data.lineart.color = color_white;
129         a_focus_indicator->texture[1].type = RR_TEXTURE_LINE_ART;
130         a_focus_indicator->texture[1].data.lineart.color = color_white;
131         a_focus_indicator->texture[2].type = RR_TEXTURE_LINE_ART;
132         a_focus_indicator->texture[2].data.lineart.color = color_white;
133         a_focus_indicator->texture[3].type = RR_TEXTURE_LINE_ART;
134         a_focus_indicator->texture[3].data.lineart.color = color_white;
135     }
136 }
137
138 void focus_shutdown(gboolean reconfig)
139 {
140     icon_popup_free(focus_cycle_popup);
141
142     if (!reconfig) {
143         client_remove_destructor(focus_cycle_destructor);
144         client_remove_hide_notify(focus_tried_hide_notify);
145
146         /* reset focus to root */
147         XSetInputFocus(ob_display, PointerRoot, RevertToNone, CurrentTime);
148
149         RrColorFree(color_white);
150
151         RrAppearanceFree(a_focus_indicator);
152
153         XDestroyWindow(ob_display, focus_indicator.top.win);
154         XDestroyWindow(ob_display, focus_indicator.left.win);
155         XDestroyWindow(ob_display, focus_indicator.right.win);
156         XDestroyWindow(ob_display, focus_indicator.bottom.win);
157     }
158 }
159
160 static void push_to_top(ObClient *client)
161 {
162     focus_order = g_list_remove(focus_order, client);
163     focus_order = g_list_prepend(focus_order, client);
164 }
165
166 void focus_set_client(ObClient *client)
167 {
168     Window active;
169
170     ob_debug_type(OB_DEBUG_FOCUS,
171                   "focus_set_client 0x%lx\n", client ? client->window : 0);
172
173     /* uninstall the old colormap, and install the new one */
174     screen_install_colormap(focus_client, FALSE);
175     screen_install_colormap(client, TRUE);
176
177     /* in the middle of cycling..? kill it. CurrentTime is fine, time won't
178        be used.
179     */
180     if (focus_cycle_target)
181         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
182
183     focus_client = client;
184
185     if (client != NULL) {
186         /* move to the top of the list */
187         push_to_top(client);
188         /* remove hiliting from the window when it gets focused */
189         client_hilite(client, FALSE);
190     }
191
192     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
193     if (ob_state() != OB_STATE_EXITING) {
194         active = client ? client->window : None;
195         PROP_SET32(RootWindow(ob_display, ob_screen),
196                    net_active_window, window, active);
197     }
198
199
200     focus_tried = NULL; /* focus isn't "trying" to go anywhere now */
201 }
202
203 static ObClient* focus_fallback_target(gboolean allow_refocus, ObClient *old)
204 {
205     GList *it;
206     ObClient *target = NULL;
207     ObClient *desktop = NULL;
208
209     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
210     if (config_focus_follow && !config_focus_last)
211     {
212         if ((target = client_under_pointer()))
213             if (allow_refocus || target != old)
214                 if (client_normal(target) && client_can_focus(target)) {
215                     ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
216                     return target;
217                 }
218     }
219
220 #if 0
221         /* try for group relations */
222         if (old->group) {
223             GSList *sit;
224
225             for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
226                 for (sit = old->group->members; sit; sit = g_slist_next(sit))
227                     if (sit->data == it->data)
228                         if (sit->data != old && client_normal(sit->data))
229                             if (client_can_focus(sit->data))
230                                 return sit->data;
231         }
232 #endif
233
234     ob_debug_type(OB_DEBUG_FOCUS, "trying omnipresentness\n");
235     if (allow_refocus && old && old->desktop == DESKTOP_ALL &&
236         client_normal(old))
237     {
238         return old;
239     }
240
241
242     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
243     for (it = focus_order; it; it = g_list_next(it))
244         if (allow_refocus || it->data != old) {
245             ObClient *c = it->data;
246             /* fallback focus to a window if:
247                1. it is actually focusable, cuz if it's not then we're sending
248                focus off to nothing. this includes if it is visible right now
249                2. it is on the current desktop. this ignores omnipresent
250                windows, which are problematic in their own rite.
251                3. it is a normal type window, don't fall back onto a dock or
252                a splashscreen or a desktop window (save the desktop as a
253                backup fallback though)
254             */
255             if (client_can_focus(c))
256             {
257                 if (c->desktop == screen_desktop && client_normal(c)) {
258                     ob_debug_type(OB_DEBUG_FOCUS, "found in focus order\n");
259                     return it->data;
260                 } else if (c->type == OB_CLIENT_TYPE_DESKTOP && 
261                            desktop == NULL)
262                     desktop = c;
263             }
264         }
265
266     /* as a last resort fallback to the desktop window if there is one.
267        (if there's more than one, then the one most recently focused.)
268     */
269     ob_debug_type(OB_DEBUG_FOCUS, "found desktop: \n", !!desktop);
270     return desktop;   
271 }
272
273 ObClient* focus_fallback(gboolean allow_refocus)
274 {
275     ObClient *new;
276     ObClient *old;
277
278     old = focus_client;
279     new = focus_fallback_target(allow_refocus, focus_client);
280
281     /* send focus somewhere if it is moving or if it was NULL before,
282        in which case it may not even be on the screen */
283     if (!old || new != old) {
284         /* unfocus any focused clients.. they can be focused by Pointer events
285            and such, and then when we try focus them, we won't get a FocusIn
286            event at all for them. */
287         focus_nothing();
288
289         if (new) {
290             client_focus(new);
291             /* remember that we tried to send focus here */
292             focus_tried = new;
293         }
294     }
295
296     return new;
297 }
298
299 void focus_nothing()
300 {
301     /* Install our own colormap */
302     if (focus_client != NULL) {
303         screen_install_colormap(focus_client, FALSE);
304         screen_install_colormap(NULL, TRUE);
305     }
306
307     focus_client = NULL;
308     focus_tried = NULL; /* focus isn't "trying" to go anywhere now */
309
310     /* when nothing will be focused, send focus to the backup target */
311     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
312                    event_curtime);
313 }
314
315 static gchar *popup_get_name(ObClient *c, ObClient **nametarget)
316 {
317     ObClient *p;
318     gchar *title = NULL;
319     const gchar *desk = NULL;
320     gchar *ret;
321
322     /* find our highest direct parent, including non-normal windows */
323     for (p = c; p->transient_for && p->transient_for != OB_TRAN_GROUP;
324          p = p->transient_for);
325
326     if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
327         desk = screen_desktop_names[c->desktop];
328
329     /* use the transient's parent's title/icon if we don't have one */
330     if (p != c && !strcmp("", (c->iconic ? c->icon_title : c->title)))
331         title = g_strdup(p->iconic ? p->icon_title : p->title);
332
333     if (title == NULL)
334         title = g_strdup(c->iconic ? c->icon_title : c->title);
335
336     if (desk)
337         ret = g_strdup_printf("%s [%s]", title, desk);
338     else {
339         ret = title;
340         title = NULL;
341     }
342     g_free(title);
343
344     /* set this only if we're returning true and they asked for it */
345     if (ret && nametarget) *nametarget = p;
346     return ret;
347 }
348
349 static void popup_cycle(ObClient *c, gboolean show,
350                         gboolean all_desktops, gboolean dock_windows,
351                         gboolean desktop_windows)
352 {
353     gchar *showtext = NULL;
354     ObClient *showtarget;
355
356     if (!show) {
357         icon_popup_hide(focus_cycle_popup);
358         return;
359     }
360
361     /* do this stuff only when the dialog is first showing */
362     if (!focus_cycle_popup->popup->mapped &&
363         !focus_cycle_popup->popup->delay_mapped)
364     {
365         Rect *a;
366         gchar **names;
367         GList *targets = NULL, *it;
368         gint n = 0, i;
369
370         /* position the popup */
371         a = screen_physical_area_monitor(0);
372         icon_popup_position(focus_cycle_popup, CenterGravity,
373                             a->x + a->width / 2, a->y + a->height / 2);
374         icon_popup_height(focus_cycle_popup, POPUP_HEIGHT);
375         icon_popup_min_width(focus_cycle_popup, POPUP_WIDTH);
376         icon_popup_max_width(focus_cycle_popup,
377                              MAX(a->width/3, POPUP_WIDTH));
378
379
380         /* make its width to be the width of all the possible titles */
381
382         /* build a list of all the valid focus targets */
383         for (it = focus_order; it; it = g_list_next(it)) {
384             ObClient *ft = it->data;
385             if (valid_focus_target(ft, all_desktops, dock_windows
386                                    , desktop_windows))
387             {
388                 targets = g_list_prepend(targets, ft);
389                 ++n;
390             }
391         }
392         /* make it null terminated so we can use g_strfreev */
393         names = g_new(char*, n+1);
394         for (it = targets, i = 0; it; it = g_list_next(it), ++i) {
395             ObClient *ft = it->data, *t;
396             names[i] = popup_get_name(ft, &t);
397
398             /* little optimization.. save this text and client, so we dont
399                have to get it again */
400             if (ft == c) {
401                 showtext = g_strdup(names[i]);
402                 showtarget = t;
403             }
404         }
405         names[n] = NULL;
406
407         icon_popup_text_width_to_strings(focus_cycle_popup, names, n);
408         g_strfreev(names);
409     }
410
411
412     if (!showtext) showtext = popup_get_name(c, &showtarget);
413     icon_popup_show(focus_cycle_popup, showtext,
414                     client_icon(showtarget, 48, 48));
415     g_free(showtext);
416 }
417
418 static void focus_cycle_destructor(ObClient *client, gpointer data)
419 {
420     /* end cycling if the target disappears. CurrentTime is fine, time won't
421        be used
422     */
423     if (focus_cycle_target == client)
424         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
425 }
426
427 void focus_cycle_draw_indicator()
428 {
429     if (!focus_cycle_target) {
430         XUnmapWindow(ob_display, focus_indicator.top.win);
431         XUnmapWindow(ob_display, focus_indicator.left.win);
432         XUnmapWindow(ob_display, focus_indicator.right.win);
433         XUnmapWindow(ob_display, focus_indicator.bottom.win);
434
435         /* kill enter events cause by this unmapping */
436         event_ignore_queued_enters();
437     } else {
438         /*
439           if (focus_cycle_target)
440               frame_adjust_focus(focus_cycle_target->frame, FALSE);
441           frame_adjust_focus(focus_cycle_target->frame, TRUE);
442         */
443         gint x, y, w, h;
444         gint wt, wl, wr, wb;
445
446         wt = wl = wr = wb = FOCUS_INDICATOR_WIDTH;
447
448         x = focus_cycle_target->frame->area.x;
449         y = focus_cycle_target->frame->area.y;
450         w = focus_cycle_target->frame->area.width;
451         h = wt;
452
453         XMoveResizeWindow(ob_display, focus_indicator.top.win,
454                           x, y, w, h);
455         a_focus_indicator->texture[0].data.lineart.x1 = 0;
456         a_focus_indicator->texture[0].data.lineart.y1 = h-1;
457         a_focus_indicator->texture[0].data.lineart.x2 = 0;
458         a_focus_indicator->texture[0].data.lineart.y2 = 0;
459         a_focus_indicator->texture[1].data.lineart.x1 = 0;
460         a_focus_indicator->texture[1].data.lineart.y1 = 0;
461         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
462         a_focus_indicator->texture[1].data.lineart.y2 = 0;
463         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
464         a_focus_indicator->texture[2].data.lineart.y1 = 0;
465         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
466         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
467         a_focus_indicator->texture[3].data.lineart.x1 = (wl-1);
468         a_focus_indicator->texture[3].data.lineart.y1 = h-1;
469         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
470         a_focus_indicator->texture[3].data.lineart.y2 = h-1;
471         RrPaint(a_focus_indicator, focus_indicator.top.win,
472                 w, h);
473
474         x = focus_cycle_target->frame->area.x;
475         y = focus_cycle_target->frame->area.y;
476         w = wl;
477         h = focus_cycle_target->frame->area.height;
478
479         XMoveResizeWindow(ob_display, focus_indicator.left.win,
480                           x, y, w, h);
481         a_focus_indicator->texture[0].data.lineart.x1 = w-1;
482         a_focus_indicator->texture[0].data.lineart.y1 = 0;
483         a_focus_indicator->texture[0].data.lineart.x2 = 0;
484         a_focus_indicator->texture[0].data.lineart.y2 = 0;
485         a_focus_indicator->texture[1].data.lineart.x1 = 0;
486         a_focus_indicator->texture[1].data.lineart.y1 = 0;
487         a_focus_indicator->texture[1].data.lineart.x2 = 0;
488         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
489         a_focus_indicator->texture[2].data.lineart.x1 = 0;
490         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
491         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
492         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
493         a_focus_indicator->texture[3].data.lineart.x1 = w-1;
494         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
495         a_focus_indicator->texture[3].data.lineart.x2 = w-1;
496         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
497         RrPaint(a_focus_indicator, focus_indicator.left.win,
498                 w, h);
499
500         x = focus_cycle_target->frame->area.x +
501             focus_cycle_target->frame->area.width - wr;
502         y = focus_cycle_target->frame->area.y;
503         w = wr;
504         h = focus_cycle_target->frame->area.height ;
505
506         XMoveResizeWindow(ob_display, focus_indicator.right.win,
507                           x, y, w, h);
508         a_focus_indicator->texture[0].data.lineart.x1 = 0;
509         a_focus_indicator->texture[0].data.lineart.y1 = 0;
510         a_focus_indicator->texture[0].data.lineart.x2 = w-1;
511         a_focus_indicator->texture[0].data.lineart.y2 = 0;
512         a_focus_indicator->texture[1].data.lineart.x1 = w-1;
513         a_focus_indicator->texture[1].data.lineart.y1 = 0;
514         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
515         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
516         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
517         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
518         a_focus_indicator->texture[2].data.lineart.x2 = 0;
519         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
520         a_focus_indicator->texture[3].data.lineart.x1 = 0;
521         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
522         a_focus_indicator->texture[3].data.lineart.x2 = 0;
523         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
524         RrPaint(a_focus_indicator, focus_indicator.right.win,
525                 w, h);
526
527         x = focus_cycle_target->frame->area.x;
528         y = focus_cycle_target->frame->area.y +
529             focus_cycle_target->frame->area.height - wb;
530         w = focus_cycle_target->frame->area.width;
531         h = wb;
532
533         XMoveResizeWindow(ob_display, focus_indicator.bottom.win,
534                           x, y, w, h);
535         a_focus_indicator->texture[0].data.lineart.x1 = 0;
536         a_focus_indicator->texture[0].data.lineart.y1 = 0;
537         a_focus_indicator->texture[0].data.lineart.x2 = 0;
538         a_focus_indicator->texture[0].data.lineart.y2 = h-1;
539         a_focus_indicator->texture[1].data.lineart.x1 = 0;
540         a_focus_indicator->texture[1].data.lineart.y1 = h-1;
541         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
542         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
543         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
544         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
545         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
546         a_focus_indicator->texture[2].data.lineart.y2 = 0;
547         a_focus_indicator->texture[3].data.lineart.x1 = wl-1;
548         a_focus_indicator->texture[3].data.lineart.y1 = 0;
549         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
550         a_focus_indicator->texture[3].data.lineart.y2 = 0;
551         RrPaint(a_focus_indicator, focus_indicator.bottom.win,
552                 w, h);
553
554         XMapWindow(ob_display, focus_indicator.top.win);
555         XMapWindow(ob_display, focus_indicator.left.win);
556         XMapWindow(ob_display, focus_indicator.right.win);
557         XMapWindow(ob_display, focus_indicator.bottom.win);
558     }
559 }
560
561 static gboolean has_valid_group_siblings_on_desktop(ObClient *ft,
562                                                     gboolean all_desktops)
563                                                          
564 {
565     GSList *it;
566
567     if (!ft->group) return FALSE;
568
569     for (it = ft->group->members; it; it = g_slist_next(it)) {
570         ObClient *c = it->data;
571         /* check that it's not a helper window to avoid infinite recursion */
572         if (c != ft && !client_helper(c) &&
573             valid_focus_target(c, all_desktops, FALSE, FALSE))
574         {
575             return TRUE;
576         }
577     }
578     return FALSE;
579 }
580
581 /*! @param allow_helpers This is used for calling itself recursively while
582                          checking helper windows. */
583 static gboolean valid_focus_target(ObClient *ft,
584                                    gboolean all_desktops,
585                                    gboolean dock_windows,
586                                    gboolean desktop_windows)
587 {
588     gboolean ok = FALSE;
589
590     /* it's on this desktop unless you want all desktops.
591
592        do this check first because it will usually filter out the most
593        windows */
594     ok = (all_desktops || ft->desktop == screen_desktop ||
595           ft->desktop == DESKTOP_ALL);
596
597     /* the window can receive focus somehow */
598     ok = ok && (ft->can_focus || ft->focus_notify);
599
600     /* it's the right type of window */
601     if (dock_windows || desktop_windows)
602         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
603                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
604     else
605         /* normal non-helper windows are valid targets */
606         ok = ok &&
607             ((client_normal(ft) && !client_helper(ft))
608              ||
609              /* helper windows are valid targets it... */
610              (client_helper(ft) &&
611               /* ...a window in its group already has focus ... */
612               ((focus_client && ft->group == focus_client->group) ||
613                /* ... or if there are no other windows in its group 
614                   that can be cycled to instead */
615                !has_valid_group_siblings_on_desktop(ft, all_desktops))));
616
617     /* it's not set to skip the taskbar (unless it is a type that would be
618        expected to set this hint */
619     ok = ok && ((ft->type == OB_CLIENT_TYPE_DOCK ||
620                  ft->type == OB_CLIENT_TYPE_DESKTOP ||
621                  ft->type == OB_CLIENT_TYPE_TOOLBAR ||
622                  ft->type == OB_CLIENT_TYPE_MENU ||
623                  ft->type == OB_CLIENT_TYPE_UTILITY) ||
624                 !ft->skip_taskbar);
625
626     /* it's not going to just send fous off somewhere else (modal window) */
627     ok = ok && ft == client_focus_target(ft);
628
629     return ok;
630 }
631
632 void focus_cycle(gboolean forward, gboolean all_desktops,
633                  gboolean dock_windows, gboolean desktop_windows,
634                  gboolean linear, gboolean interactive,
635                  gboolean dialog, gboolean done, gboolean cancel)
636 {
637     static ObClient *first = NULL;
638     static ObClient *t = NULL;
639     static GList *order = NULL;
640     GList *it, *start, *list;
641     ObClient *ft = NULL;
642
643     if (interactive) {
644         if (cancel) {
645             focus_cycle_target = NULL;
646             goto done_cycle;
647         } else if (done)
648             goto done_cycle;
649
650         if (!focus_order)
651             goto done_cycle;
652
653         if (!first) first = focus_client;
654
655         if (linear) list = client_list;
656         else        list = focus_order;
657     } else {
658         if (!focus_order)
659             goto done_cycle;
660         list = client_list;
661     }
662     if (!focus_cycle_target) focus_cycle_target = focus_client;
663
664     start = it = g_list_find(list, focus_cycle_target);
665     if (!start) /* switched desktops or something? */
666         start = it = forward ? g_list_last(list) : g_list_first(list);
667     if (!start) goto done_cycle;
668
669     do {
670         if (forward) {
671             it = it->next;
672             if (it == NULL) it = g_list_first(list);
673         } else {
674             it = it->prev;
675             if (it == NULL) it = g_list_last(list);
676         }
677         ft = it->data;
678         if (valid_focus_target(ft, all_desktops, dock_windows,
679                                desktop_windows))
680         {
681             if (interactive) {
682                 if (ft != focus_cycle_target) { /* prevents flicker */
683                     focus_cycle_target = ft;
684                     focus_cycle_draw_indicator();
685                 }
686                 /* same arguments as valid_focus_target */
687                 popup_cycle(ft, dialog, all_desktops, dock_windows,
688                             desktop_windows);
689                 return;
690             } else if (ft != focus_cycle_target) {
691                 focus_cycle_target = ft;
692                 done = TRUE;
693                 break;
694             }
695         }
696     } while (it != start);
697
698 done_cycle:
699     if (done && focus_cycle_target)
700         client_activate(focus_cycle_target, FALSE, TRUE);
701
702     t = NULL;
703     first = NULL;
704     focus_cycle_target = NULL;
705     g_list_free(order);
706     order = NULL;
707
708     if (interactive) {
709         focus_cycle_draw_indicator();
710         popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
711     }
712
713     return;
714 }
715
716 /* this be mostly ripped from fvwm */
717 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
718                                         gboolean dock_windows,
719                                         gboolean desktop_windows) 
720 {
721     gint my_cx, my_cy, his_cx, his_cy;
722     gint offset = 0;
723     gint distance = 0;
724     gint score, best_score;
725     ObClient *best_client, *cur;
726     GList *it;
727
728     if(!client_list)
729         return NULL;
730
731     /* first, find the centre coords of the currently focused window */
732     my_cx = c->frame->area.x + c->frame->area.width / 2;
733     my_cy = c->frame->area.y + c->frame->area.height / 2;
734
735     best_score = -1;
736     best_client = NULL;
737
738     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
739         cur = it->data;
740
741         /* the currently selected window isn't interesting */
742         if(cur == c)
743             continue;
744         if (!dock_windows && !desktop_windows && !client_normal(cur))
745             continue;
746         if (!(dock_windows && cur->type == OB_CLIENT_TYPE_DOCK) ||
747             (desktop_windows && cur->type == OB_CLIENT_TYPE_DESKTOP))
748             continue;
749         /* using c->desktop instead of screen_desktop doesn't work if the
750          * current window was omnipresent, hope this doesn't have any other
751          * side effects */
752         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
753             continue;
754         if(cur->iconic)
755             continue;
756         if(!(client_focus_target(cur) == cur &&
757              client_can_focus(cur)))
758             continue;
759
760         /* find the centre coords of this window, from the
761          * currently focused window's point of view */
762         his_cx = (cur->frame->area.x - my_cx)
763             + cur->frame->area.width / 2;
764         his_cy = (cur->frame->area.y - my_cy)
765             + cur->frame->area.height / 2;
766
767         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
768            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
769             gint tx;
770             /* Rotate the diagonals 45 degrees counterclockwise.
771              * To do this, multiply the matrix /+h +h\ with the
772              * vector (x y).                   \-h +h/
773              * h = sqrt(0.5). We can set h := 1 since absolute
774              * distance doesn't matter here. */
775             tx = his_cx + his_cy;
776             his_cy = -his_cx + his_cy;
777             his_cx = tx;
778         }
779
780         switch(dir) {
781         case OB_DIRECTION_NORTH:
782         case OB_DIRECTION_SOUTH:
783         case OB_DIRECTION_NORTHEAST:
784         case OB_DIRECTION_SOUTHWEST:
785             offset = (his_cx < 0) ? -his_cx : his_cx;
786             distance = ((dir == OB_DIRECTION_NORTH ||
787                          dir == OB_DIRECTION_NORTHEAST) ?
788                         -his_cy : his_cy);
789             break;
790         case OB_DIRECTION_EAST:
791         case OB_DIRECTION_WEST:
792         case OB_DIRECTION_SOUTHEAST:
793         case OB_DIRECTION_NORTHWEST:
794             offset = (his_cy < 0) ? -his_cy : his_cy;
795             distance = ((dir == OB_DIRECTION_WEST ||
796                          dir == OB_DIRECTION_NORTHWEST) ?
797                         -his_cx : his_cx);
798             break;
799         }
800
801         /* the target must be in the requested direction */
802         if(distance <= 0)
803             continue;
804
805         /* Calculate score for this window.  The smaller the better. */
806         score = distance + offset;
807
808         /* windows more than 45 degrees off the direction are
809          * heavily penalized and will only be chosen if nothing
810          * else within a million pixels */
811         if(offset > distance)
812             score += 1000000;
813
814         if(best_score == -1 || score < best_score)
815             best_client = cur,
816                 best_score = score;
817     }
818
819     return best_client;
820 }
821
822 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
823                              gboolean desktop_windows, gboolean interactive,
824                              gboolean dialog, gboolean done, gboolean cancel)
825 {
826     static ObClient *first = NULL;
827     ObClient *ft = NULL;
828
829     if (!interactive)
830         return;
831
832     if (cancel) {
833         focus_cycle_target = NULL;
834         goto done_cycle;
835     } else if (done)
836         goto done_cycle;
837
838     if (!focus_order)
839         goto done_cycle;
840
841     if (!first) first = focus_client;
842     if (!focus_cycle_target) focus_cycle_target = focus_client;
843
844     if (focus_cycle_target)
845         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
846                                     desktop_windows);
847     else {
848         GList *it;
849
850         for (it = focus_order; it; it = g_list_next(it))
851             if (valid_focus_target(it->data, FALSE, dock_windows,
852                                    desktop_windows))
853                 ft = it->data;
854     }
855         
856     if (ft) {
857         if (ft != focus_cycle_target) {/* prevents flicker */
858             focus_cycle_target = ft;
859             focus_cycle_draw_indicator();
860         }
861     }
862     if (focus_cycle_target) {
863         /* same arguments as valid_focus_target */
864         popup_cycle(focus_cycle_target, dialog, FALSE, dock_windows,
865                     desktop_windows);
866         if (dialog)
867             return;
868     }
869
870
871 done_cycle:
872     if (done && focus_cycle_target)
873         client_activate(focus_cycle_target, FALSE, TRUE);
874
875     first = NULL;
876     focus_cycle_target = NULL;
877
878     focus_cycle_draw_indicator();
879     popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
880
881     return;
882 }
883
884 void focus_order_add_new(ObClient *c)
885 {
886     if (c->iconic)
887         focus_order_to_top(c);
888     else {
889         g_assert(!g_list_find(focus_order, c));
890         /* if there are any iconic windows, put this above them in the order,
891            but if there are not, then put it under the currently focused one */
892         if (focus_order && ((ObClient*)focus_order->data)->iconic)
893             focus_order = g_list_insert(focus_order, c, 0);
894         else
895             focus_order = g_list_insert(focus_order, c, 1);
896     }
897 }
898
899 void focus_order_remove(ObClient *c)
900 {
901     focus_order = g_list_remove(focus_order, c);
902 }
903
904 void focus_order_to_top(ObClient *c)
905 {
906     focus_order = g_list_remove(focus_order, c);
907     if (!c->iconic) {
908         focus_order = g_list_prepend(focus_order, c);
909     } else {
910         GList *it;
911
912         /* insert before first iconic window */
913         for (it = focus_order;
914              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
915         focus_order = g_list_insert_before(focus_order, it, c);
916     }
917 }
918
919 void focus_order_to_bottom(ObClient *c)
920 {
921     focus_order = g_list_remove(focus_order, c);
922     if (c->iconic) {
923         focus_order = g_list_append(focus_order, c);
924     } else {
925         GList *it;
926
927         /* insert before first iconic window */
928         for (it = focus_order;
929              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
930         focus_order = g_list_insert_before(focus_order, it, c);
931     }
932 }
933
934 ObClient *focus_order_find_first(guint desktop)
935 {
936     GList *it;
937     for (it = focus_order; it; it = g_list_next(it)) {
938         ObClient *c = it->data;
939         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
940             return c;
941     }
942     return NULL;
943 }
944
945 static void focus_tried_hide_notify(ObClient *client, gpointer data)
946 {
947     XEvent ce;
948
949     if (client == focus_tried) {
950         /* we were trying to focus this window but it's gone */
951
952         focus_tried = NULL;
953
954         ob_debug_type(OB_DEBUG_FOCUS, "Tried to focus window 0x%x and it "
955                       "is being unmanaged:\n");
956         if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,NULL))
957         {
958             XPutBackEvent(ob_display, &ce);
959             ob_debug_type(OB_DEBUG_FOCUS, "  but another FocusIn is coming\n");
960         } else {
961             ob_debug_type(OB_DEBUG_FOCUS, "  so falling back focus again.\n");
962             focus_fallback(TRUE);
963         }
964     }
965 }