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