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