lots of fixes for the iconify animation. i think it should all work perfectly now ?
[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_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_show(focus_cycle_popup,
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               ((ft->type == OB_CLIENT_TYPE_TOOLBAR ||
479                 ft->type == OB_CLIENT_TYPE_MENU ||
480                 ft->type == OB_CLIENT_TYPE_UTILITY) &&
481                /* let alt-tab go to these windows when a window in its group
482                   already has focus ... */
483                ((focus_client && ft->group == focus_client->group) ||
484                 /* ... or if there are no application windows in its group */
485                 !client_has_application_group_siblings(ft))));
486     ok = ok && (ft->can_focus || ft->focus_notify);
487     if (!dock_windows && /* use dock windows that skip taskbar too */
488         !(ft->type == OB_CLIENT_TYPE_TOOLBAR || /* also, if we actually are */
489           ft->type == OB_CLIENT_TYPE_MENU ||    /* being allowed to target */
490           ft->type == OB_CLIENT_TYPE_UTILITY))  /* one of these, don't let */
491         ok = ok && !ft->skip_taskbar;           /*  skip taskbar stop us */
492     ok = ok && (ft->desktop == screen_desktop || ft->desktop == DESKTOP_ALL);
493     ok = ok && ft == client_focus_target(ft);
494     return ok;
495 /*
496     {
497         GSList *it;
498
499         for (it = ft->transients; it; it = g_slist_next(it)) {
500             ObClient *c = it->data;
501
502             if (frame_visible(c->frame))
503                 return FALSE;
504         }
505         return TRUE;
506     }
507 */
508 }
509
510 void focus_cycle(gboolean forward, gboolean dock_windows,
511                  gboolean linear, gboolean interactive,
512                  gboolean dialog, gboolean done, gboolean cancel)
513 {
514     static ObClient *first = NULL;
515     static ObClient *t = NULL;
516     static GList *order = NULL;
517     GList *it, *start, *list;
518     ObClient *ft = NULL;
519
520     if (interactive) {
521         if (cancel) {
522             focus_cycle_target = NULL;
523             goto done_cycle;
524         } else if (done)
525             goto done_cycle;
526
527         if (!focus_order)
528             goto done_cycle;
529
530         if (!first) first = focus_client;
531
532         if (linear) list = client_list;
533         else        list = focus_order;
534     } else {
535         if (!focus_order)
536             goto done_cycle;
537         list = client_list;
538     }
539     if (!focus_cycle_target) focus_cycle_target = focus_client;
540
541     start = it = g_list_find(list, focus_cycle_target);
542     if (!start) /* switched desktops or something? */
543         start = it = forward ? g_list_last(list) : g_list_first(list);
544     if (!start) goto done_cycle;
545
546     do {
547         if (forward) {
548             it = it->next;
549             if (it == NULL) it = g_list_first(list);
550         } else {
551             it = it->prev;
552             if (it == NULL) it = g_list_last(list);
553         }
554         ft = it->data;
555         if (valid_focus_target(ft, dock_windows)) {
556             if (interactive) {
557                 if (ft != focus_cycle_target) { /* prevents flicker */
558                     focus_cycle_target = ft;
559                     focus_cycle_draw_indicator();
560                 }
561                 popup_cycle(ft, dialog);
562                 return;
563             } else if (ft != focus_cycle_target) {
564                 focus_cycle_target = ft;
565                 done = TRUE;
566                 break;
567             }
568         }
569     } while (it != start);
570
571 done_cycle:
572     if (done && focus_cycle_target)
573         client_activate(focus_cycle_target, FALSE, TRUE);
574
575     t = NULL;
576     first = NULL;
577     focus_cycle_target = NULL;
578     g_list_free(order);
579     order = NULL;
580
581     if (interactive) {
582         focus_cycle_draw_indicator();
583         popup_cycle(ft, FALSE);
584     }
585
586     return;
587 }
588
589 /* this be mostly ripped from fvwm */
590 ObClient *focus_find_directional(ObClient *c, ObDirection dir,
591                                  gboolean dock_windows) 
592 {
593     gint my_cx, my_cy, his_cx, his_cy;
594     gint offset = 0;
595     gint distance = 0;
596     gint score, best_score;
597     ObClient *best_client, *cur;
598     GList *it;
599
600     if(!client_list)
601         return NULL;
602
603     /* first, find the centre coords of the currently focused window */
604     my_cx = c->frame->area.x + c->frame->area.width / 2;
605     my_cy = c->frame->area.y + c->frame->area.height / 2;
606
607     best_score = -1;
608     best_client = NULL;
609
610     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
611         cur = it->data;
612
613         /* the currently selected window isn't interesting */
614         if(cur == c)
615             continue;
616         if (!dock_windows && !client_normal(cur))
617             continue;
618         if (dock_windows && cur->type != OB_CLIENT_TYPE_DOCK)
619             continue;
620         /* using c->desktop instead of screen_desktop doesn't work if the
621          * current window was omnipresent, hope this doesn't have any other
622          * side effects */
623         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
624             continue;
625         if(cur->iconic)
626             continue;
627         if(!(client_focus_target(cur) == cur &&
628              client_can_focus(cur)))
629             continue;
630
631         /* find the centre coords of this window, from the
632          * currently focused window's point of view */
633         his_cx = (cur->frame->area.x - my_cx)
634             + cur->frame->area.width / 2;
635         his_cy = (cur->frame->area.y - my_cy)
636             + cur->frame->area.height / 2;
637
638         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
639            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
640             gint tx;
641             /* Rotate the diagonals 45 degrees counterclockwise.
642              * To do this, multiply the matrix /+h +h\ with the
643              * vector (x y).                   \-h +h/
644              * h = sqrt(0.5). We can set h := 1 since absolute
645              * distance doesn't matter here. */
646             tx = his_cx + his_cy;
647             his_cy = -his_cx + his_cy;
648             his_cx = tx;
649         }
650
651         switch(dir) {
652         case OB_DIRECTION_NORTH:
653         case OB_DIRECTION_SOUTH:
654         case OB_DIRECTION_NORTHEAST:
655         case OB_DIRECTION_SOUTHWEST:
656             offset = (his_cx < 0) ? -his_cx : his_cx;
657             distance = ((dir == OB_DIRECTION_NORTH ||
658                          dir == OB_DIRECTION_NORTHEAST) ?
659                         -his_cy : his_cy);
660             break;
661         case OB_DIRECTION_EAST:
662         case OB_DIRECTION_WEST:
663         case OB_DIRECTION_SOUTHEAST:
664         case OB_DIRECTION_NORTHWEST:
665             offset = (his_cy < 0) ? -his_cy : his_cy;
666             distance = ((dir == OB_DIRECTION_WEST ||
667                          dir == OB_DIRECTION_NORTHWEST) ?
668                         -his_cx : his_cx);
669             break;
670         }
671
672         /* the target must be in the requested direction */
673         if(distance <= 0)
674             continue;
675
676         /* Calculate score for this window.  The smaller the better. */
677         score = distance + offset;
678
679         /* windows more than 45 degrees off the direction are
680          * heavily penalized and will only be chosen if nothing
681          * else within a million pixels */
682         if(offset > distance)
683             score += 1000000;
684
685         if(best_score == -1 || score < best_score)
686             best_client = cur,
687                 best_score = score;
688     }
689
690     return best_client;
691 }
692
693 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
694                              gboolean interactive,
695                              gboolean dialog, gboolean done, gboolean cancel)
696 {
697     static ObClient *first = NULL;
698     ObClient *ft = NULL;
699
700     if (!interactive)
701         return;
702
703     if (cancel) {
704         focus_cycle_target = NULL;
705         goto done_cycle;
706     } else if (done)
707         goto done_cycle;
708
709     if (!focus_order)
710         goto done_cycle;
711
712     if (!first) first = focus_client;
713     if (!focus_cycle_target) focus_cycle_target = focus_client;
714
715     if (focus_cycle_target)
716         ft = focus_find_directional(focus_cycle_target, dir, dock_windows);
717     else {
718         GList *it;
719
720         for (it = focus_order; it; it = g_list_next(it))
721             if (valid_focus_target(it->data, dock_windows))
722                 ft = it->data;
723     }
724         
725     if (ft) {
726         if (ft != focus_cycle_target) {/* prevents flicker */
727             focus_cycle_target = ft;
728             focus_cycle_draw_indicator();
729         }
730     }
731     if (focus_cycle_target) {
732         popup_cycle(focus_cycle_target, dialog);
733         if (dialog)
734             return;
735     }
736
737
738 done_cycle:
739     if (done && focus_cycle_target)
740         client_activate(focus_cycle_target, FALSE, TRUE);
741
742     first = NULL;
743     focus_cycle_target = NULL;
744
745     focus_cycle_draw_indicator();
746     popup_cycle(ft, FALSE);
747
748     return;
749 }
750
751 void focus_order_add_new(ObClient *c)
752 {
753     if (c->iconic)
754         focus_order_to_top(c);
755     else {
756         g_assert(!g_list_find(focus_order, c));
757         /* if there are any iconic windows, put this above them in the order,
758            but if there are not, then put it under the currently focused one */
759         if (focus_order && ((ObClient*)focus_order->data)->iconic)
760             focus_order = g_list_insert(focus_order, c, 0);
761         else
762             focus_order = g_list_insert(focus_order, c, 1);
763     }
764 }
765
766 void focus_order_remove(ObClient *c)
767 {
768     focus_order = g_list_remove(focus_order, c);
769 }
770
771 void focus_order_to_top(ObClient *c)
772 {
773     focus_order = g_list_remove(focus_order, c);
774     if (!c->iconic) {
775         focus_order = g_list_prepend(focus_order, c);
776     } else {
777         GList *it;
778
779         /* insert before first iconic window */
780         for (it = focus_order;
781              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
782         focus_order = g_list_insert_before(focus_order, it, c);
783     }
784 }
785
786 void focus_order_to_bottom(ObClient *c)
787 {
788     focus_order = g_list_remove(focus_order, c);
789     if (c->iconic) {
790         focus_order = g_list_append(focus_order, c);
791     } else {
792         GList *it;
793
794         /* insert before first iconic window */
795         for (it = focus_order;
796              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
797         focus_order = g_list_insert_before(focus_order, it, c);
798     }
799 }
800
801 ObClient *focus_order_find_first(guint desktop)
802 {
803     GList *it;
804     for (it = focus_order; it; it = g_list_next(it)) {
805         ObClient *c = it->data;
806         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
807             return c;
808     }
809     return NULL;
810 }