popups fixes. if the text for the popup is empty now, there wont be extra padding...
[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 5
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 void 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 }
272
273 void focus_nothing()
274 {
275     /* Install our own colormap */
276     if (focus_client != NULL) {
277         screen_install_colormap(focus_client, FALSE);
278         screen_install_colormap(NULL, TRUE);
279     }
280
281     focus_client = NULL;
282
283     /* when nothing will be focused, send focus to the backup target */
284     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
285                    event_curtime);
286 }
287
288 static gchar *popup_get_name(ObClient *c, ObClient **nametarget)
289 {
290     ObClient *p;
291     gchar *title = NULL;
292     const gchar *desk = NULL;
293     gchar *ret;
294
295     /* find our highest direct parent, including non-normal windows */
296     for (p = c; p->transient_for && p->transient_for != OB_TRAN_GROUP;
297          p = p->transient_for);
298     if (nametarget) *nametarget = p;
299
300     if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
301         desk = screen_desktop_names[c->desktop];
302
303     /* use the transient's parent's title/icon if we don't have one */
304     if (p != c && !strcmp("", (c->iconic ? c->icon_title : c->title)))
305         title = g_strdup(p->iconic ? p->icon_title : p->title);
306
307     if (title == NULL)
308         title = g_strdup(c->iconic ? c->icon_title : c->title);
309
310     if (desk)
311         ret = g_strdup_printf("%s [%s]", title, desk);
312     else {
313         ret = title;
314         title = NULL;
315     }
316     g_free(title);
317
318     return ret;
319 }
320
321 static void popup_cycle(ObClient *c, gboolean show,
322                         gboolean all_desktops, gboolean dock_windows)
323 {
324     gchar *showtext = NULL;
325     ObClient *showtarget;
326
327     if (!show) {
328         icon_popup_hide(focus_cycle_popup);
329         return;
330     }
331
332     /* do this stuff only when the dialog is first showing */
333     if (!focus_cycle_popup->popup->mapped &&
334         !focus_cycle_popup->popup->delay_mapped)
335     {
336         Rect *a;
337         gchar **names;
338         GList *targets = NULL, *it;
339         gint n = 0, i;
340
341         /* position the popup */
342         a = screen_physical_area_monitor(0);
343         icon_popup_position(focus_cycle_popup, CenterGravity,
344                             a->x + a->width / 2, a->y + a->height / 2);
345         icon_popup_height(focus_cycle_popup, POPUP_HEIGHT);
346         icon_popup_min_width(focus_cycle_popup, POPUP_WIDTH);
347         icon_popup_max_width(focus_cycle_popup,
348                              MAX(a->width/3, POPUP_WIDTH));
349
350
351         /* make its width to be the width of all the possible titles */
352
353         /* build a list of all the valid focus targets */
354         for (it = focus_order; it; it = g_list_next(it)) {
355             ObClient *ft = it->data;
356             if (valid_focus_target(ft, all_desktops, dock_windows)) {
357                 targets = g_list_prepend(targets, ft);
358                 ++n;
359             }
360         }
361         /* make it null terminated so we can use g_strfreev */
362         names = g_new(char*, n+1);
363         for (it = targets, i = 0; it; it = g_list_next(it), ++i) {
364             ObClient *ft = it->data;
365             names[i] = popup_get_name(ft, &showtarget);
366
367             /* little optimization.. save this text so we dont have to get it
368                again */
369             if (ft == c)
370                 showtext = g_strdup(names[i]);
371         }
372         names[n] = NULL;
373
374         icon_popup_text_width_to_strings(focus_cycle_popup, names, n);
375         g_strfreev(names);
376     }
377
378
379     if (!showtext) showtext = popup_get_name(c, &showtarget);
380     icon_popup_show(focus_cycle_popup, showtext,
381                     client_icon(showtarget, 48, 48));
382     g_free(showtext);
383 }
384
385 static void focus_cycle_destructor(ObClient *client, gpointer data)
386 {
387     /* end cycling if the target disappears. CurrentTime is fine, time won't
388        be used
389     */
390     if (focus_cycle_target == client)
391         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
392 }
393
394 void focus_cycle_draw_indicator()
395 {
396     if (!focus_cycle_target) {
397         XUnmapWindow(ob_display, focus_indicator.top.win);
398         XUnmapWindow(ob_display, focus_indicator.left.win);
399         XUnmapWindow(ob_display, focus_indicator.right.win);
400         XUnmapWindow(ob_display, focus_indicator.bottom.win);
401
402         /* kill enter events cause by this unmapping */
403         event_ignore_queued_enters();
404     } else {
405         /*
406           if (focus_cycle_target)
407               frame_adjust_focus(focus_cycle_target->frame, FALSE);
408           frame_adjust_focus(focus_cycle_target->frame, TRUE);
409         */
410         gint x, y, w, h;
411         gint wt, wl, wr, wb;
412
413         wt = wl = wr = wb = FOCUS_INDICATOR_WIDTH;
414
415         x = focus_cycle_target->frame->area.x;
416         y = focus_cycle_target->frame->area.y;
417         w = focus_cycle_target->frame->area.width;
418         h = wt;
419
420         XMoveResizeWindow(ob_display, focus_indicator.top.win,
421                           x, y, w, h);
422         a_focus_indicator->texture[0].data.lineart.x1 = 0;
423         a_focus_indicator->texture[0].data.lineart.y1 = h-1;
424         a_focus_indicator->texture[0].data.lineart.x2 = 0;
425         a_focus_indicator->texture[0].data.lineart.y2 = 0;
426         a_focus_indicator->texture[1].data.lineart.x1 = 0;
427         a_focus_indicator->texture[1].data.lineart.y1 = 0;
428         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
429         a_focus_indicator->texture[1].data.lineart.y2 = 0;
430         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
431         a_focus_indicator->texture[2].data.lineart.y1 = 0;
432         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
433         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
434         a_focus_indicator->texture[3].data.lineart.x1 = (wl-1);
435         a_focus_indicator->texture[3].data.lineart.y1 = h-1;
436         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
437         a_focus_indicator->texture[3].data.lineart.y2 = h-1;
438         RrPaint(a_focus_indicator, focus_indicator.top.win,
439                 w, h);
440
441         x = focus_cycle_target->frame->area.x;
442         y = focus_cycle_target->frame->area.y;
443         w = wl;
444         h = focus_cycle_target->frame->area.height;
445
446         XMoveResizeWindow(ob_display, focus_indicator.left.win,
447                           x, y, w, h);
448         a_focus_indicator->texture[0].data.lineart.x1 = w-1;
449         a_focus_indicator->texture[0].data.lineart.y1 = 0;
450         a_focus_indicator->texture[0].data.lineart.x2 = 0;
451         a_focus_indicator->texture[0].data.lineart.y2 = 0;
452         a_focus_indicator->texture[1].data.lineart.x1 = 0;
453         a_focus_indicator->texture[1].data.lineart.y1 = 0;
454         a_focus_indicator->texture[1].data.lineart.x2 = 0;
455         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
456         a_focus_indicator->texture[2].data.lineart.x1 = 0;
457         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
458         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
459         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
460         a_focus_indicator->texture[3].data.lineart.x1 = w-1;
461         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
462         a_focus_indicator->texture[3].data.lineart.x2 = w-1;
463         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
464         RrPaint(a_focus_indicator, focus_indicator.left.win,
465                 w, h);
466
467         x = focus_cycle_target->frame->area.x +
468             focus_cycle_target->frame->area.width - wr;
469         y = focus_cycle_target->frame->area.y;
470         w = wr;
471         h = focus_cycle_target->frame->area.height ;
472
473         XMoveResizeWindow(ob_display, focus_indicator.right.win,
474                           x, y, w, h);
475         a_focus_indicator->texture[0].data.lineart.x1 = 0;
476         a_focus_indicator->texture[0].data.lineart.y1 = 0;
477         a_focus_indicator->texture[0].data.lineart.x2 = w-1;
478         a_focus_indicator->texture[0].data.lineart.y2 = 0;
479         a_focus_indicator->texture[1].data.lineart.x1 = w-1;
480         a_focus_indicator->texture[1].data.lineart.y1 = 0;
481         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
482         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
483         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
484         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
485         a_focus_indicator->texture[2].data.lineart.x2 = 0;
486         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
487         a_focus_indicator->texture[3].data.lineart.x1 = 0;
488         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
489         a_focus_indicator->texture[3].data.lineart.x2 = 0;
490         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
491         RrPaint(a_focus_indicator, focus_indicator.right.win,
492                 w, h);
493
494         x = focus_cycle_target->frame->area.x;
495         y = focus_cycle_target->frame->area.y +
496             focus_cycle_target->frame->area.height - wb;
497         w = focus_cycle_target->frame->area.width;
498         h = wb;
499
500         XMoveResizeWindow(ob_display, focus_indicator.bottom.win,
501                           x, y, w, h);
502         a_focus_indicator->texture[0].data.lineart.x1 = 0;
503         a_focus_indicator->texture[0].data.lineart.y1 = 0;
504         a_focus_indicator->texture[0].data.lineart.x2 = 0;
505         a_focus_indicator->texture[0].data.lineart.y2 = h-1;
506         a_focus_indicator->texture[1].data.lineart.x1 = 0;
507         a_focus_indicator->texture[1].data.lineart.y1 = h-1;
508         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
509         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
510         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
511         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
512         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
513         a_focus_indicator->texture[2].data.lineart.y2 = 0;
514         a_focus_indicator->texture[3].data.lineart.x1 = wl-1;
515         a_focus_indicator->texture[3].data.lineart.y1 = 0;
516         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
517         a_focus_indicator->texture[3].data.lineart.y2 = 0;
518         RrPaint(a_focus_indicator, focus_indicator.bottom.win,
519                 w, h);
520
521         XMapWindow(ob_display, focus_indicator.top.win);
522         XMapWindow(ob_display, focus_indicator.left.win);
523         XMapWindow(ob_display, focus_indicator.right.win);
524         XMapWindow(ob_display, focus_indicator.bottom.win);
525     }
526 }
527
528 static gboolean valid_focus_target(ObClient *ft,
529                                    gboolean all_desktops,
530                                    gboolean dock_windows)
531 {
532     gboolean ok = FALSE;
533
534     /* it's on this desktop unless you want all desktops.
535
536        do this check first because it will usually filter out the most
537        windows */
538     ok = (all_desktops || ft->desktop == screen_desktop ||
539           ft->desktop == DESKTOP_ALL);
540
541     /* the window can receive focus somehow */
542     ok = ok && (ft->can_focus || ft->focus_notify);
543
544     /* it's the right type of window */
545     if (dock_windows)
546         ok = ok && ft->type == OB_CLIENT_TYPE_DOCK;
547     else
548         ok = ok && (ft->type == OB_CLIENT_TYPE_NORMAL ||
549                     ft->type == OB_CLIENT_TYPE_DIALOG ||
550                     ((ft->type == OB_CLIENT_TYPE_TOOLBAR ||
551                       ft->type == OB_CLIENT_TYPE_MENU ||
552                       ft->type == OB_CLIENT_TYPE_UTILITY) &&
553                      /* let alt-tab go to these windows when a window in its
554                         group already has focus ... */
555                      ((focus_client && ft->group == focus_client->group) ||
556                       /* ... or if there are no application windows in its
557                          group */
558                       !client_has_application_group_siblings(ft))));
559
560     /* it's not set to skip the taskbar (unless it is a type that would be
561        expected to set this hint */
562     ok = ok && (!(ft->type == OB_CLIENT_TYPE_DOCK ||
563                   ft->type == OB_CLIENT_TYPE_TOOLBAR ||
564                   ft->type == OB_CLIENT_TYPE_MENU ||
565                   ft->type == OB_CLIENT_TYPE_UTILITY) ||
566                 !ft->skip_taskbar);
567
568     /* it's not going to just send fous off somewhere else (modal window) */
569     ok = ok && ft == client_focus_target(ft);
570
571     return ok;
572 }
573
574 void focus_cycle(gboolean forward, gboolean all_desktops,
575                  gboolean dock_windows,
576                  gboolean linear, gboolean interactive,
577                  gboolean dialog, gboolean done, gboolean cancel)
578 {
579     static ObClient *first = NULL;
580     static ObClient *t = NULL;
581     static GList *order = NULL;
582     GList *it, *start, *list;
583     ObClient *ft = NULL;
584
585     if (interactive) {
586         if (cancel) {
587             focus_cycle_target = NULL;
588             goto done_cycle;
589         } else if (done)
590             goto done_cycle;
591
592         if (!focus_order)
593             goto done_cycle;
594
595         if (!first) first = focus_client;
596
597         if (linear) list = client_list;
598         else        list = focus_order;
599     } else {
600         if (!focus_order)
601             goto done_cycle;
602         list = client_list;
603     }
604     if (!focus_cycle_target) focus_cycle_target = focus_client;
605
606     start = it = g_list_find(list, focus_cycle_target);
607     if (!start) /* switched desktops or something? */
608         start = it = forward ? g_list_last(list) : g_list_first(list);
609     if (!start) goto done_cycle;
610
611     do {
612         if (forward) {
613             it = it->next;
614             if (it == NULL) it = g_list_first(list);
615         } else {
616             it = it->prev;
617             if (it == NULL) it = g_list_last(list);
618         }
619         ft = it->data;
620         if (valid_focus_target(ft, all_desktops, dock_windows)) {
621             if (interactive) {
622                 if (ft != focus_cycle_target) { /* prevents flicker */
623                     focus_cycle_target = ft;
624                     focus_cycle_draw_indicator();
625                 }
626                 /* same arguments as valid_focus_target */
627                 popup_cycle(ft, dialog, all_desktops, dock_windows);
628                 return;
629             } else if (ft != focus_cycle_target) {
630                 focus_cycle_target = ft;
631                 done = TRUE;
632                 break;
633             }
634         }
635     } while (it != start);
636
637 done_cycle:
638     if (done && focus_cycle_target)
639         client_activate(focus_cycle_target, FALSE, TRUE);
640
641     t = NULL;
642     first = NULL;
643     focus_cycle_target = NULL;
644     g_list_free(order);
645     order = NULL;
646
647     if (interactive) {
648         focus_cycle_draw_indicator();
649         popup_cycle(ft, FALSE, FALSE, FALSE);
650     }
651
652     return;
653 }
654
655 /* this be mostly ripped from fvwm */
656 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
657                                  gboolean dock_windows) 
658 {
659     gint my_cx, my_cy, his_cx, his_cy;
660     gint offset = 0;
661     gint distance = 0;
662     gint score, best_score;
663     ObClient *best_client, *cur;
664     GList *it;
665
666     if(!client_list)
667         return NULL;
668
669     /* first, find the centre coords of the currently focused window */
670     my_cx = c->frame->area.x + c->frame->area.width / 2;
671     my_cy = c->frame->area.y + c->frame->area.height / 2;
672
673     best_score = -1;
674     best_client = NULL;
675
676     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
677         cur = it->data;
678
679         /* the currently selected window isn't interesting */
680         if(cur == c)
681             continue;
682         if (!dock_windows && !client_normal(cur))
683             continue;
684         if (dock_windows && cur->type != OB_CLIENT_TYPE_DOCK)
685             continue;
686         /* using c->desktop instead of screen_desktop doesn't work if the
687          * current window was omnipresent, hope this doesn't have any other
688          * side effects */
689         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
690             continue;
691         if(cur->iconic)
692             continue;
693         if(!(client_focus_target(cur) == cur &&
694              client_can_focus(cur)))
695             continue;
696
697         /* find the centre coords of this window, from the
698          * currently focused window's point of view */
699         his_cx = (cur->frame->area.x - my_cx)
700             + cur->frame->area.width / 2;
701         his_cy = (cur->frame->area.y - my_cy)
702             + cur->frame->area.height / 2;
703
704         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
705            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
706             gint tx;
707             /* Rotate the diagonals 45 degrees counterclockwise.
708              * To do this, multiply the matrix /+h +h\ with the
709              * vector (x y).                   \-h +h/
710              * h = sqrt(0.5). We can set h := 1 since absolute
711              * distance doesn't matter here. */
712             tx = his_cx + his_cy;
713             his_cy = -his_cx + his_cy;
714             his_cx = tx;
715         }
716
717         switch(dir) {
718         case OB_DIRECTION_NORTH:
719         case OB_DIRECTION_SOUTH:
720         case OB_DIRECTION_NORTHEAST:
721         case OB_DIRECTION_SOUTHWEST:
722             offset = (his_cx < 0) ? -his_cx : his_cx;
723             distance = ((dir == OB_DIRECTION_NORTH ||
724                          dir == OB_DIRECTION_NORTHEAST) ?
725                         -his_cy : his_cy);
726             break;
727         case OB_DIRECTION_EAST:
728         case OB_DIRECTION_WEST:
729         case OB_DIRECTION_SOUTHEAST:
730         case OB_DIRECTION_NORTHWEST:
731             offset = (his_cy < 0) ? -his_cy : his_cy;
732             distance = ((dir == OB_DIRECTION_WEST ||
733                          dir == OB_DIRECTION_NORTHWEST) ?
734                         -his_cx : his_cx);
735             break;
736         }
737
738         /* the target must be in the requested direction */
739         if(distance <= 0)
740             continue;
741
742         /* Calculate score for this window.  The smaller the better. */
743         score = distance + offset;
744
745         /* windows more than 45 degrees off the direction are
746          * heavily penalized and will only be chosen if nothing
747          * else within a million pixels */
748         if(offset > distance)
749             score += 1000000;
750
751         if(best_score == -1 || score < best_score)
752             best_client = cur,
753                 best_score = score;
754     }
755
756     return best_client;
757 }
758
759 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
760                              gboolean interactive,
761                              gboolean dialog, gboolean done, gboolean cancel)
762 {
763     static ObClient *first = NULL;
764     ObClient *ft = NULL;
765
766     if (!interactive)
767         return;
768
769     if (cancel) {
770         focus_cycle_target = NULL;
771         goto done_cycle;
772     } else if (done)
773         goto done_cycle;
774
775     if (!focus_order)
776         goto done_cycle;
777
778     if (!first) first = focus_client;
779     if (!focus_cycle_target) focus_cycle_target = focus_client;
780
781     if (focus_cycle_target)
782         ft = focus_find_directional(focus_cycle_target, dir, dock_windows);
783     else {
784         GList *it;
785
786         for (it = focus_order; it; it = g_list_next(it))
787             if (valid_focus_target(it->data, FALSE, dock_windows))
788                 ft = it->data;
789     }
790         
791     if (ft) {
792         if (ft != focus_cycle_target) {/* prevents flicker */
793             focus_cycle_target = ft;
794             focus_cycle_draw_indicator();
795         }
796     }
797     if (focus_cycle_target) {
798         /* same arguments as valid_focus_target */
799         popup_cycle(focus_cycle_target, dialog, FALSE, dock_windows);
800         if (dialog)
801             return;
802     }
803
804
805 done_cycle:
806     if (done && focus_cycle_target)
807         client_activate(focus_cycle_target, FALSE, TRUE);
808
809     first = NULL;
810     focus_cycle_target = NULL;
811
812     focus_cycle_draw_indicator();
813     popup_cycle(ft, FALSE, FALSE, FALSE);
814
815     return;
816 }
817
818 void focus_order_add_new(ObClient *c)
819 {
820     if (c->iconic)
821         focus_order_to_top(c);
822     else {
823         g_assert(!g_list_find(focus_order, c));
824         /* if there are any iconic windows, put this above them in the order,
825            but if there are not, then put it under the currently focused one */
826         if (focus_order && ((ObClient*)focus_order->data)->iconic)
827             focus_order = g_list_insert(focus_order, c, 0);
828         else
829             focus_order = g_list_insert(focus_order, c, 1);
830     }
831 }
832
833 void focus_order_remove(ObClient *c)
834 {
835     focus_order = g_list_remove(focus_order, c);
836 }
837
838 void focus_order_to_top(ObClient *c)
839 {
840     focus_order = g_list_remove(focus_order, c);
841     if (!c->iconic) {
842         focus_order = g_list_prepend(focus_order, c);
843     } else {
844         GList *it;
845
846         /* insert before first iconic window */
847         for (it = focus_order;
848              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
849         focus_order = g_list_insert_before(focus_order, it, c);
850     }
851 }
852
853 void focus_order_to_bottom(ObClient *c)
854 {
855     focus_order = g_list_remove(focus_order, c);
856     if (c->iconic) {
857         focus_order = g_list_append(focus_order, c);
858     } else {
859         GList *it;
860
861         /* insert before first iconic window */
862         for (it = focus_order;
863              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
864         focus_order = g_list_insert_before(focus_order, it, c);
865     }
866 }
867
868 ObClient *focus_order_find_first(guint desktop)
869 {
870     GList *it;
871     for (it = focus_order; it; it = g_list_next(it)) {
872         ObClient *c = it->data;
873         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
874             return c;
875     }
876     return NULL;
877 }