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