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