cycling between dock windows now possible with the <panels>yes</panels> option
[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 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_size(focus_cycle_popup, a->height/2, a->height/16);
308         icon_popup_show(focus_cycle_popup, c->title,
309                         client_icon(c, a->height/16, a->height/16));
310 */
311         /* XXX the size and the font extents need to be related on some level
312          */
313         icon_popup_size(focus_cycle_popup, POPUP_WIDTH, POPUP_HEIGHT);
314
315         /* use the transient's parent's title/icon */
316         while (p->transient_for && p->transient_for != OB_TRAN_GROUP)
317             p = p->transient_for;
318
319         if (p != c && !strcmp("", (c->iconic ? c->icon_title : c->title)))
320             title = g_strdup(p->iconic ? p->icon_title : p->title);
321             /*title = g_strconcat((c->iconic ? c->icon_title : c->title),
322                                 " - ",
323                                 (p->iconic ? p->icon_title : p->title),
324                                 NULL);
325             */
326         icon_popup_show(focus_cycle_popup,
327                         (title ? title :
328                          (c->iconic ? c->icon_title : c->title)),
329                         client_icon(p, 48, 48));
330         g_free(title);
331     }
332 }
333
334 void focus_cycle_draw_indicator()
335 {
336     if (!focus_cycle_target) {
337         XUnmapWindow(ob_display, focus_indicator.top.win);
338         XUnmapWindow(ob_display, focus_indicator.left.win);
339         XUnmapWindow(ob_display, focus_indicator.right.win);
340         XUnmapWindow(ob_display, focus_indicator.bottom.win);
341
342         /* kill enter events cause by this unmapping */
343         event_ignore_queued_enters();
344     } else {
345         /*
346           if (focus_cycle_target)
347               frame_adjust_focus(focus_cycle_target->frame, FALSE);
348           frame_adjust_focus(focus_cycle_target->frame, TRUE);
349         */
350         gint x, y, w, h;
351         gint wt, wl, wr, wb;
352
353         wt = wl = wr = wb = MAX(3,
354                                 MAX(1, MAX(ob_rr_theme->paddingx,
355                                            ob_rr_theme->paddingy)) * 2 +
356                                 ob_rr_theme->fbwidth * 2);
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, gboolean dock_windows)
472 {
473     gboolean ok = FALSE;
474     /* we don't use client_can_focus here, because that doesn't let you
475        focus an iconic window, but we want to be able to, so we just check
476        if the focus flags on the window allow it, and its on the current
477        desktop */
478     if (dock_windows)
479         ok = ft->type == OB_CLIENT_TYPE_DOCK;
480     else
481         ok = (ft->type == OB_CLIENT_TYPE_NORMAL ||
482               ft->type == OB_CLIENT_TYPE_DIALOG ||
483               (!client_has_group_siblings(ft) &&
484                (ft->type == OB_CLIENT_TYPE_TOOLBAR ||
485                 ft->type == OB_CLIENT_TYPE_MENU ||
486                 ft->type == OB_CLIENT_TYPE_UTILITY)));
487     ok = ok && (ft->can_focus || ft->focus_notify);
488     ok = ok && !ft->skip_pager;
489     ok = ok && (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL);
490     ok = ok && ft == client_focus_target(ft);
491     return ok;
492 /*
493     {
494         GSList *it;
495
496         for (it = ft->transients; it; it = g_slist_next(it)) {
497             ObClient *c = it->data;
498
499             if (c->frame->visible)
500                 return FALSE;
501         }
502         return TRUE;
503     }
504 */
505 }
506
507 void focus_cycle(gboolean forward, gboolean dock_windows,
508                  gboolean linear, gboolean interactive,
509                  gboolean dialog, gboolean done, gboolean cancel)
510 {
511     static ObClient *first = NULL;
512     static ObClient *t = NULL;
513     static GList *order = NULL;
514     GList *it, *start, *list;
515     ObClient *ft = NULL;
516
517     if (interactive) {
518         if (cancel) {
519             focus_cycle_target = NULL;
520             goto done_cycle;
521         } else if (done)
522             goto done_cycle;
523
524         if (!focus_order)
525             goto done_cycle;
526
527         if (!first) first = focus_client;
528
529         if (linear) list = client_list;
530         else        list = focus_order;
531     } else {
532         if (!focus_order)
533             goto done_cycle;
534         list = client_list;
535     }
536     if (!focus_cycle_target) focus_cycle_target = focus_client;
537
538     start = it = g_list_find(list, focus_cycle_target);
539     if (!start) /* switched desktops or something? */
540         start = it = forward ? g_list_last(list) : g_list_first(list);
541     if (!start) goto done_cycle;
542
543     do {
544         if (forward) {
545             it = it->next;
546             if (it == NULL) it = g_list_first(list);
547         } else {
548             it = it->prev;
549             if (it == NULL) it = g_list_last(list);
550         }
551         ft = it->data;
552         if (valid_focus_target(ft, dock_windows)) {
553             if (interactive) {
554                 if (ft != focus_cycle_target) { /* prevents flicker */
555                     focus_cycle_target = ft;
556                     focus_cycle_draw_indicator();
557                 }
558                 popup_cycle(ft, dialog);
559                 return;
560             } else if (ft != focus_cycle_target) {
561                 focus_cycle_target = ft;
562                 done = TRUE;
563                 break;
564             }
565         }
566     } while (it != start);
567
568 done_cycle:
569     if (done && focus_cycle_target)
570         client_activate(focus_cycle_target, FALSE, TRUE);
571
572     t = NULL;
573     first = NULL;
574     focus_cycle_target = NULL;
575     g_list_free(order);
576     order = NULL;
577
578     if (interactive) {
579         focus_cycle_draw_indicator();
580         popup_cycle(ft, FALSE);
581     }
582
583     return;
584 }
585
586 /* this be mostly ripped from fvwm */
587 ObClient *focus_find_directional(ObClient *c, ObDirection dir,
588                                  gboolean dock_windows) 
589 {
590     gint my_cx, my_cy, his_cx, his_cy;
591     gint offset = 0;
592     gint distance = 0;
593     gint score, best_score;
594     ObClient *best_client, *cur;
595     GList *it;
596
597     if(!client_list)
598         return NULL;
599
600     /* first, find the centre coords of the currently focused window */
601     my_cx = c->frame->area.x + c->frame->area.width / 2;
602     my_cy = c->frame->area.y + c->frame->area.height / 2;
603
604     best_score = -1;
605     best_client = NULL;
606
607     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
608         cur = it->data;
609
610         /* the currently selected window isn't interesting */
611         if(cur == c)
612             continue;
613         if (!dock_windows && !client_normal(cur))
614             continue;
615         if (dock_windows && cur->type != OB_CLIENT_TYPE_DOCK)
616             continue;
617         /* using c->desktop instead of screen_desktop doesn't work if the
618          * current window was omnipresent, hope this doesn't have any other
619          * side effects */
620         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
621             continue;
622         if(cur->iconic)
623             continue;
624         if(!(client_focus_target(cur) == cur &&
625              client_can_focus(cur)))
626             continue;
627
628         /* find the centre coords of this window, from the
629          * currently focused window's point of view */
630         his_cx = (cur->frame->area.x - my_cx)
631             + cur->frame->area.width / 2;
632         his_cy = (cur->frame->area.y - my_cy)
633             + cur->frame->area.height / 2;
634
635         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
636            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
637             gint tx;
638             /* Rotate the diagonals 45 degrees counterclockwise.
639              * To do this, multiply the matrix /+h +h\ with the
640              * vector (x y).                   \-h +h/
641              * h = sqrt(0.5). We can set h := 1 since absolute
642              * distance doesn't matter here. */
643             tx = his_cx + his_cy;
644             his_cy = -his_cx + his_cy;
645             his_cx = tx;
646         }
647
648         switch(dir) {
649         case OB_DIRECTION_NORTH:
650         case OB_DIRECTION_SOUTH:
651         case OB_DIRECTION_NORTHEAST:
652         case OB_DIRECTION_SOUTHWEST:
653             offset = (his_cx < 0) ? -his_cx : his_cx;
654             distance = ((dir == OB_DIRECTION_NORTH ||
655                          dir == OB_DIRECTION_NORTHEAST) ?
656                         -his_cy : his_cy);
657             break;
658         case OB_DIRECTION_EAST:
659         case OB_DIRECTION_WEST:
660         case OB_DIRECTION_SOUTHEAST:
661         case OB_DIRECTION_NORTHWEST:
662             offset = (his_cy < 0) ? -his_cy : his_cy;
663             distance = ((dir == OB_DIRECTION_WEST ||
664                          dir == OB_DIRECTION_NORTHWEST) ?
665                         -his_cx : his_cx);
666             break;
667         }
668
669         /* the target must be in the requested direction */
670         if(distance <= 0)
671             continue;
672
673         /* Calculate score for this window.  The smaller the better. */
674         score = distance + offset;
675
676         /* windows more than 45 degrees off the direction are
677          * heavily penalized and will only be chosen if nothing
678          * else within a million pixels */
679         if(offset > distance)
680             score += 1000000;
681
682         if(best_score == -1 || score < best_score)
683             best_client = cur,
684                 best_score = score;
685     }
686
687     return best_client;
688 }
689
690 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
691                              gboolean interactive,
692                              gboolean dialog, gboolean done, gboolean cancel)
693 {
694     static ObClient *first = NULL;
695     ObClient *ft = NULL;
696
697     if (!interactive)
698         return;
699
700     if (cancel) {
701         focus_cycle_target = NULL;
702         goto done_cycle;
703     } else if (done)
704         goto done_cycle;
705
706     if (!focus_order)
707         goto done_cycle;
708
709     if (!first) first = focus_client;
710     if (!focus_cycle_target) focus_cycle_target = focus_client;
711
712     if (focus_cycle_target)
713         ft = focus_find_directional(focus_cycle_target, dir, dock_windows);
714     else {
715         GList *it;
716
717         for (it = focus_order; it; it = g_list_next(it))
718             if (valid_focus_target(it->data, dock_windows))
719                 ft = it->data;
720     }
721         
722     if (ft) {
723         if (ft != focus_cycle_target) {/* prevents flicker */
724             focus_cycle_target = ft;
725             focus_cycle_draw_indicator();
726         }
727     }
728     if (focus_cycle_target) {
729         popup_cycle(focus_cycle_target, dialog);
730         if (dialog)
731             return;
732     }
733
734
735 done_cycle:
736     if (done && focus_cycle_target)
737         client_activate(focus_cycle_target, FALSE, TRUE);
738
739     first = NULL;
740     focus_cycle_target = NULL;
741
742     focus_cycle_draw_indicator();
743     popup_cycle(ft, FALSE);
744
745     return;
746 }
747
748 void focus_order_add_new(ObClient *c)
749 {
750     if (c->iconic)
751         focus_order_to_top(c);
752     else {
753         g_assert(!g_list_find(focus_order, c));
754         /* if there are any iconic windows, put this above them in the order,
755            but if there are not, then put it under the currently focused one */
756         if (focus_order && ((ObClient*)focus_order->data)->iconic)
757             focus_order = g_list_insert(focus_order, c, 0);
758         else
759             focus_order = g_list_insert(focus_order, c, 1);
760     }
761 }
762
763 void focus_order_remove(ObClient *c)
764 {
765     focus_order = g_list_remove(focus_order, c);
766 }
767
768 void focus_order_to_top(ObClient *c)
769 {
770     focus_order = g_list_remove(focus_order, c);
771     if (!c->iconic) {
772         focus_order = g_list_prepend(focus_order, c);
773     } else {
774         GList *it;
775
776         /* insert before first iconic window */
777         for (it = focus_order;
778              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
779         focus_order = g_list_insert_before(focus_order, it, c);
780     }
781 }
782
783 void focus_order_to_bottom(ObClient *c)
784 {
785     focus_order = g_list_remove(focus_order, c);
786     if (c->iconic) {
787         focus_order = g_list_append(focus_order, c);
788     } else {
789         GList *it;
790
791         /* insert before first iconic window */
792         for (it = focus_order;
793              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
794         focus_order = g_list_insert_before(focus_order, it, c);
795     }
796 }
797
798 ObClient *focus_order_find_first(guint desktop)
799 {
800     GList *it;
801     for (it = focus_order; it; it = g_list_next(it)) {
802         ObClient *c = it->data;
803         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
804             return c;
805     }
806     return NULL;
807 }