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