]> icculus.org git repositories - dana/openbox.git/blob - openbox/focus.c
don't do this stuff when it's already done. save bandwidth.
[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 "keyboard.h"
32 #include "focus.h"
33 #include "stacking.h"
34 #include "popup.h"
35 #include "render/render.h"
36
37 #include <X11/Xlib.h>
38 #include <glib.h>
39 #include <assert.h>
40
41 #define FOCUS_INDICATOR_WIDTH 6
42
43 ObClient *focus_client = NULL;
44 GList *focus_order = NULL;
45 ObClient *focus_cycle_target = NULL;
46
47 struct {
48     InternalWindow top;
49     InternalWindow left;
50     InternalWindow right;
51     InternalWindow bottom;
52 } focus_indicator;
53
54 RrAppearance *a_focus_indicator;
55 RrColor *color_white;
56
57 static ObIconPopup *focus_cycle_popup;
58
59 static gboolean valid_focus_target(ObClient *ft,
60                                    gboolean all_desktops,
61                                    gboolean dock_windows,
62                                    gboolean desktop_windows);
63 static void focus_cycle_destroy_notify(ObClient *client, gpointer data);
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_destroy_notify(focus_cycle_destroy_notify, 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_destroy_notify(focus_cycle_destroy_notify);
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     if (focus_client == client)
164         return;
165
166     /* uninstall the old colormap, and install the new one */
167     screen_install_colormap(focus_client, FALSE);
168     screen_install_colormap(client, TRUE);
169
170     /* in the middle of cycling..? kill it. CurrentTime is fine, time won't
171        be used.
172     */
173     if (focus_cycle_target)
174         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
175
176     focus_client = client;
177
178     if (client != NULL) {
179         /* move to the top of the list */
180         push_to_top(client);
181         /* remove hiliting from the window when it gets focused */
182         client_hilite(client, FALSE);
183     }
184
185     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
186     if (ob_state() != OB_STATE_EXITING) {
187         active = client ? client->window : None;
188         PROP_SET32(RootWindow(ob_display, ob_screen),
189                    net_active_window, window, active);
190     }
191 }
192
193 static ObClient* focus_fallback_target(gboolean allow_refocus, ObClient *old)
194 {
195     GList *it;
196     ObClient *target = NULL;
197     ObClient *desktop = NULL;
198
199     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
200     if (config_focus_follow && !config_focus_last)
201     {
202         if ((target = client_under_pointer()))
203             if (allow_refocus || target != old)
204                 if (client_normal(target) && client_can_focus(target)) {
205                     ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
206                     return target;
207                 }
208     }
209
210 #if 0
211         /* try for group relations */
212         if (old->group) {
213             GSList *sit;
214
215             for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
216                 for (sit = old->group->members; sit; sit = g_slist_next(sit))
217                     if (sit->data == it->data)
218                         if (sit->data != old && client_normal(sit->data))
219                             if (client_can_focus(sit->data))
220                                 return sit->data;
221         }
222 #endif
223
224     ob_debug_type(OB_DEBUG_FOCUS, "trying omnipresentness\n");
225     if (allow_refocus && old && old->desktop == DESKTOP_ALL &&
226         client_normal(old))
227     {
228         return old;
229     }
230
231
232     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
233     for (it = focus_order; it; it = g_list_next(it))
234         if (allow_refocus || it->data != old) {
235             ObClient *c = it->data;
236             /* fallback focus to a window if:
237                1. it is actually focusable, cuz if it's not then we're sending
238                focus off to nothing. this includes if it is visible right now
239                2. it is on the current desktop. this ignores omnipresent
240                windows, which are problematic in their own rite.
241                3. 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))
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->type == OB_CLIENT_TYPE_DESKTOP && 
251                            desktop == NULL)
252                     desktop = c;
253             }
254         }
255
256     /* as a last resort fallback to the desktop window if there is one.
257        (if there's more than one, then the one most recently focused.)
258     */
259     ob_debug_type(OB_DEBUG_FOCUS, "found desktop: \n", !!desktop);
260     return desktop;   
261 }
262
263 ObClient* focus_fallback(gboolean allow_refocus)
264 {
265     ObClient *new;
266     ObClient *old;
267
268     old = focus_client;
269     new = focus_fallback_target(allow_refocus, focus_client);
270
271     /* unfocus any focused clients.. they can be focused by Pointer events
272        and such, and then when we try focus them, we won't get a FocusIn
273        event at all for them. */
274     focus_nothing();
275
276     if (new)
277         client_focus(new);
278
279     return new;
280 }
281
282 void focus_nothing()
283 {
284     /* Install our own colormap */
285     if (focus_client != NULL) {
286         screen_install_colormap(focus_client, FALSE);
287         screen_install_colormap(NULL, TRUE);
288     }
289
290     /* Don't set focus_client to NULL here. It will be set to NULL when the
291        FocusOut event comes. Otherwise, if we focus nothing and then focus the
292        same window again, The focus code says nothing changed, but focus_client
293        ends up being NULL anyways.
294     focus_client = NULL;
295     */
296
297     /* if there is a grab going on, then we need to cancel it. if we move
298        focus during the grab, applications will get NotifyWhileGrabbed events
299        and ignore them !
300
301        actions should not rely on being able to move focus during an
302        interactive grab.
303     */
304     if (keyboard_interactively_grabbed())
305         keyboard_interactive_cancel();
306
307     /* when nothing will be focused, send focus to the backup target */
308     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
309                    event_curtime);
310 }
311
312 static gchar *popup_get_name(ObClient *c, ObClient **nametarget)
313 {
314     ObClient *p;
315     gchar *title = NULL;
316     const gchar *desk = NULL;
317     gchar *ret;
318
319     /* find our highest direct parent, including non-normal windows */
320     for (p = c; p->transient_for && p->transient_for != OB_TRAN_GROUP;
321          p = p->transient_for);
322
323     if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
324         desk = screen_desktop_names[c->desktop];
325
326     /* use the transient's parent's title/icon if we don't have one */
327     if (p != c && !strcmp("", (c->iconic ? c->icon_title : c->title)))
328         title = g_strdup(p->iconic ? p->icon_title : p->title);
329
330     if (title == NULL)
331         title = g_strdup(c->iconic ? c->icon_title : c->title);
332
333     if (desk)
334         ret = g_strdup_printf("%s [%s]", title, desk);
335     else {
336         ret = title;
337         title = NULL;
338     }
339     g_free(title);
340
341     /* set this only if we're returning true and they asked for it */
342     if (ret && nametarget) *nametarget = p;
343     return ret;
344 }
345
346 static void popup_cycle(ObClient *c, gboolean show,
347                         gboolean all_desktops, gboolean dock_windows,
348                         gboolean desktop_windows)
349 {
350     gchar *showtext = NULL;
351     ObClient *showtarget;
352
353     if (!show) {
354         icon_popup_hide(focus_cycle_popup);
355         return;
356     }
357
358     /* do this stuff only when the dialog is first showing */
359     if (!focus_cycle_popup->popup->mapped &&
360         !focus_cycle_popup->popup->delay_mapped)
361     {
362         Rect *a;
363         gchar **names;
364         GList *targets = NULL, *it;
365         gint n = 0, i;
366
367         /* position the popup */
368         a = screen_physical_area_monitor(0);
369         icon_popup_position(focus_cycle_popup, CenterGravity,
370                             a->x + a->width / 2, a->y + a->height / 2);
371         icon_popup_height(focus_cycle_popup, POPUP_HEIGHT);
372         icon_popup_min_width(focus_cycle_popup, POPUP_WIDTH);
373         icon_popup_max_width(focus_cycle_popup,
374                              MAX(a->width/3, POPUP_WIDTH));
375
376
377         /* make its width to be the width of all the possible titles */
378
379         /* build a list of all the valid focus targets */
380         for (it = focus_order; it; it = g_list_next(it)) {
381             ObClient *ft = it->data;
382             if (valid_focus_target(ft, all_desktops, dock_windows
383                                    , desktop_windows))
384             {
385                 targets = g_list_prepend(targets, ft);
386                 ++n;
387             }
388         }
389         /* make it null terminated so we can use g_strfreev */
390         names = g_new(char*, n+1);
391         for (it = targets, i = 0; it; it = g_list_next(it), ++i) {
392             ObClient *ft = it->data, *t;
393             names[i] = popup_get_name(ft, &t);
394
395             /* little optimization.. save this text and client, so we dont
396                have to get it again */
397             if (ft == c) {
398                 showtext = g_strdup(names[i]);
399                 showtarget = t;
400             }
401         }
402         names[n] = NULL;
403
404         icon_popup_text_width_to_strings(focus_cycle_popup, names, n);
405         g_strfreev(names);
406     }
407
408
409     if (!showtext) showtext = popup_get_name(c, &showtarget);
410     icon_popup_show(focus_cycle_popup, showtext,
411                     client_icon(showtarget, 48, 48));
412     g_free(showtext);
413 }
414
415 static void focus_cycle_destroy_notify(ObClient *client, gpointer data)
416 {
417     /* end cycling if the target disappears. CurrentTime is fine, time won't
418        be used
419     */
420     if (focus_cycle_target == client)
421         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
422 }
423
424 void focus_cycle_draw_indicator()
425 {
426     if (!focus_cycle_target) {
427         XUnmapWindow(ob_display, focus_indicator.top.win);
428         XUnmapWindow(ob_display, focus_indicator.left.win);
429         XUnmapWindow(ob_display, focus_indicator.right.win);
430         XUnmapWindow(ob_display, focus_indicator.bottom.win);
431
432         /* kill enter events cause by this unmapping */
433         event_ignore_queued_enters();
434     } else {
435         /*
436           if (focus_cycle_target)
437               frame_adjust_focus(focus_cycle_target->frame, FALSE);
438           frame_adjust_focus(focus_cycle_target->frame, TRUE);
439         */
440         gint x, y, w, h;
441         gint wt, wl, wr, wb;
442
443         wt = wl = wr = wb = FOCUS_INDICATOR_WIDTH;
444
445         x = focus_cycle_target->frame->area.x;
446         y = focus_cycle_target->frame->area.y;
447         w = focus_cycle_target->frame->area.width;
448         h = wt;
449
450         XMoveResizeWindow(ob_display, focus_indicator.top.win,
451                           x, y, w, h);
452         a_focus_indicator->texture[0].data.lineart.x1 = 0;
453         a_focus_indicator->texture[0].data.lineart.y1 = h-1;
454         a_focus_indicator->texture[0].data.lineart.x2 = 0;
455         a_focus_indicator->texture[0].data.lineart.y2 = 0;
456         a_focus_indicator->texture[1].data.lineart.x1 = 0;
457         a_focus_indicator->texture[1].data.lineart.y1 = 0;
458         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
459         a_focus_indicator->texture[1].data.lineart.y2 = 0;
460         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
461         a_focus_indicator->texture[2].data.lineart.y1 = 0;
462         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
463         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
464         a_focus_indicator->texture[3].data.lineart.x1 = (wl-1);
465         a_focus_indicator->texture[3].data.lineart.y1 = h-1;
466         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
467         a_focus_indicator->texture[3].data.lineart.y2 = h-1;
468         RrPaint(a_focus_indicator, focus_indicator.top.win,
469                 w, h);
470
471         x = focus_cycle_target->frame->area.x;
472         y = focus_cycle_target->frame->area.y;
473         w = wl;
474         h = focus_cycle_target->frame->area.height;
475
476         XMoveResizeWindow(ob_display, focus_indicator.left.win,
477                           x, y, w, h);
478         a_focus_indicator->texture[0].data.lineart.x1 = w-1;
479         a_focus_indicator->texture[0].data.lineart.y1 = 0;
480         a_focus_indicator->texture[0].data.lineart.x2 = 0;
481         a_focus_indicator->texture[0].data.lineart.y2 = 0;
482         a_focus_indicator->texture[1].data.lineart.x1 = 0;
483         a_focus_indicator->texture[1].data.lineart.y1 = 0;
484         a_focus_indicator->texture[1].data.lineart.x2 = 0;
485         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
486         a_focus_indicator->texture[2].data.lineart.x1 = 0;
487         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
488         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
489         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
490         a_focus_indicator->texture[3].data.lineart.x1 = w-1;
491         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
492         a_focus_indicator->texture[3].data.lineart.x2 = w-1;
493         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
494         RrPaint(a_focus_indicator, focus_indicator.left.win,
495                 w, h);
496
497         x = focus_cycle_target->frame->area.x +
498             focus_cycle_target->frame->area.width - wr;
499         y = focus_cycle_target->frame->area.y;
500         w = wr;
501         h = focus_cycle_target->frame->area.height ;
502
503         XMoveResizeWindow(ob_display, focus_indicator.right.win,
504                           x, y, w, h);
505         a_focus_indicator->texture[0].data.lineart.x1 = 0;
506         a_focus_indicator->texture[0].data.lineart.y1 = 0;
507         a_focus_indicator->texture[0].data.lineart.x2 = w-1;
508         a_focus_indicator->texture[0].data.lineart.y2 = 0;
509         a_focus_indicator->texture[1].data.lineart.x1 = w-1;
510         a_focus_indicator->texture[1].data.lineart.y1 = 0;
511         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
512         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
513         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
514         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
515         a_focus_indicator->texture[2].data.lineart.x2 = 0;
516         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
517         a_focus_indicator->texture[3].data.lineart.x1 = 0;
518         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
519         a_focus_indicator->texture[3].data.lineart.x2 = 0;
520         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
521         RrPaint(a_focus_indicator, focus_indicator.right.win,
522                 w, h);
523
524         x = focus_cycle_target->frame->area.x;
525         y = focus_cycle_target->frame->area.y +
526             focus_cycle_target->frame->area.height - wb;
527         w = focus_cycle_target->frame->area.width;
528         h = wb;
529
530         XMoveResizeWindow(ob_display, focus_indicator.bottom.win,
531                           x, y, w, h);
532         a_focus_indicator->texture[0].data.lineart.x1 = 0;
533         a_focus_indicator->texture[0].data.lineart.y1 = 0;
534         a_focus_indicator->texture[0].data.lineart.x2 = 0;
535         a_focus_indicator->texture[0].data.lineart.y2 = h-1;
536         a_focus_indicator->texture[1].data.lineart.x1 = 0;
537         a_focus_indicator->texture[1].data.lineart.y1 = h-1;
538         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
539         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
540         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
541         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
542         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
543         a_focus_indicator->texture[2].data.lineart.y2 = 0;
544         a_focus_indicator->texture[3].data.lineart.x1 = wl-1;
545         a_focus_indicator->texture[3].data.lineart.y1 = 0;
546         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
547         a_focus_indicator->texture[3].data.lineart.y2 = 0;
548         RrPaint(a_focus_indicator, focus_indicator.bottom.win,
549                 w, h);
550
551         XMapWindow(ob_display, focus_indicator.top.win);
552         XMapWindow(ob_display, focus_indicator.left.win);
553         XMapWindow(ob_display, focus_indicator.right.win);
554         XMapWindow(ob_display, focus_indicator.bottom.win);
555     }
556 }
557
558 static gboolean has_valid_group_siblings_on_desktop(ObClient *ft,
559                                                     gboolean all_desktops)
560                                                          
561 {
562     GSList *it;
563
564     if (!ft->group) return FALSE;
565
566     for (it = ft->group->members; it; it = g_slist_next(it)) {
567         ObClient *c = it->data;
568         /* check that it's not a helper window to avoid infinite recursion */
569         if (c != ft && !client_helper(c) &&
570             valid_focus_target(c, all_desktops, FALSE, FALSE))
571         {
572             return TRUE;
573         }
574     }
575     return FALSE;
576 }
577
578 /*! @param allow_helpers This is used for calling itself recursively while
579                          checking helper windows. */
580 static gboolean valid_focus_target(ObClient *ft,
581                                    gboolean all_desktops,
582                                    gboolean dock_windows,
583                                    gboolean desktop_windows)
584 {
585     gboolean ok = FALSE;
586
587     /* it's on this desktop unless you want all desktops.
588
589        do this check first because it will usually filter out the most
590        windows */
591     ok = (all_desktops || ft->desktop == screen_desktop ||
592           ft->desktop == DESKTOP_ALL);
593
594     /* the window can receive focus somehow */
595     ok = ok && (ft->can_focus || ft->focus_notify);
596
597     /* it's the right type of window */
598     if (dock_windows || desktop_windows)
599         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
600                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
601     else
602         /* normal non-helper windows are valid targets */
603         ok = ok &&
604             ((client_normal(ft) && !client_helper(ft))
605              ||
606              /* helper windows are valid targets it... */
607              (client_helper(ft) &&
608               /* ...a window in its group already has focus ... */
609               ((focus_client && ft->group == focus_client->group) ||
610                /* ... or if there are no other windows in its group 
611                   that can be cycled to instead */
612                !has_valid_group_siblings_on_desktop(ft, all_desktops))));
613
614     /* it's not set to skip the taskbar (unless it is a type that would be
615        expected to set this hint */
616     ok = ok && ((ft->type == OB_CLIENT_TYPE_DOCK ||
617                  ft->type == OB_CLIENT_TYPE_DESKTOP ||
618                  ft->type == OB_CLIENT_TYPE_TOOLBAR ||
619                  ft->type == OB_CLIENT_TYPE_MENU ||
620                  ft->type == OB_CLIENT_TYPE_UTILITY) ||
621                 !ft->skip_taskbar);
622
623     /* it's not going to just send fous off somewhere else (modal window) */
624     ok = ok && ft == client_focus_target(ft);
625
626     return ok;
627 }
628
629 void focus_cycle(gboolean forward, gboolean all_desktops,
630                  gboolean dock_windows, gboolean desktop_windows,
631                  gboolean linear, gboolean interactive,
632                  gboolean dialog, gboolean done, gboolean cancel)
633 {
634     static ObClient *first = NULL;
635     static ObClient *t = NULL;
636     static GList *order = NULL;
637     GList *it, *start, *list;
638     ObClient *ft = NULL;
639
640     if (interactive) {
641         if (cancel) {
642             focus_cycle_target = NULL;
643             goto done_cycle;
644         } else if (done)
645             goto done_cycle;
646
647         if (!focus_order)
648             goto done_cycle;
649
650         if (!first) first = focus_client;
651
652         if (linear) list = client_list;
653         else        list = focus_order;
654     } else {
655         if (!focus_order)
656             goto done_cycle;
657         list = client_list;
658     }
659     if (!focus_cycle_target) focus_cycle_target = focus_client;
660
661     start = it = g_list_find(list, focus_cycle_target);
662     if (!start) /* switched desktops or something? */
663         start = it = forward ? g_list_last(list) : g_list_first(list);
664     if (!start) goto done_cycle;
665
666     do {
667         if (forward) {
668             it = it->next;
669             if (it == NULL) it = g_list_first(list);
670         } else {
671             it = it->prev;
672             if (it == NULL) it = g_list_last(list);
673         }
674         ft = it->data;
675         if (valid_focus_target(ft, all_desktops, dock_windows,
676                                desktop_windows))
677         {
678             if (interactive) {
679                 if (ft != focus_cycle_target) { /* prevents flicker */
680                     focus_cycle_target = ft;
681                     focus_cycle_draw_indicator();
682                 }
683                 /* same arguments as valid_focus_target */
684                 popup_cycle(ft, dialog, all_desktops, dock_windows,
685                             desktop_windows);
686                 return;
687             } else if (ft != focus_cycle_target) {
688                 focus_cycle_target = ft;
689                 done = TRUE;
690                 break;
691             }
692         }
693     } while (it != start);
694
695 done_cycle:
696     if (done && focus_cycle_target)
697         client_activate(focus_cycle_target, FALSE, TRUE);
698
699     t = NULL;
700     first = NULL;
701     focus_cycle_target = NULL;
702     g_list_free(order);
703     order = NULL;
704
705     if (interactive) {
706         focus_cycle_draw_indicator();
707         popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
708     }
709
710     return;
711 }
712
713 /* this be mostly ripped from fvwm */
714 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
715                                         gboolean dock_windows,
716                                         gboolean desktop_windows) 
717 {
718     gint my_cx, my_cy, his_cx, his_cy;
719     gint offset = 0;
720     gint distance = 0;
721     gint score, best_score;
722     ObClient *best_client, *cur;
723     GList *it;
724
725     if(!client_list)
726         return NULL;
727
728     /* first, find the centre coords of the currently focused window */
729     my_cx = c->frame->area.x + c->frame->area.width / 2;
730     my_cy = c->frame->area.y + c->frame->area.height / 2;
731
732     best_score = -1;
733     best_client = NULL;
734
735     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
736         cur = it->data;
737
738         /* the currently selected window isn't interesting */
739         if(cur == c)
740             continue;
741         if (cur->type == OB_CLIENT_TYPE_DOCK && !dock_windows)
742             continue;
743         if (cur->type == OB_CLIENT_TYPE_DESKTOP && !desktop_windows)
744             continue;
745         if (!client_normal(cur) &&
746             cur->type != OB_CLIENT_TYPE_DOCK &&
747             cur->type != OB_CLIENT_TYPE_DESKTOP)
748             continue;
749         /* using c->desktop instead of screen_desktop doesn't work if the
750          * current window was omnipresent, hope this doesn't have any other
751          * side effects */
752         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
753             continue;
754         if(cur->iconic)
755             continue;
756         if(!(client_focus_target(cur) == cur &&
757              client_can_focus(cur)))
758             continue;
759
760         /* find the centre coords of this window, from the
761          * currently focused window's point of view */
762         his_cx = (cur->frame->area.x - my_cx)
763             + cur->frame->area.width / 2;
764         his_cy = (cur->frame->area.y - my_cy)
765             + cur->frame->area.height / 2;
766
767         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
768            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
769             gint tx;
770             /* Rotate the diagonals 45 degrees counterclockwise.
771              * To do this, multiply the matrix /+h +h\ with the
772              * vector (x y).                   \-h +h/
773              * h = sqrt(0.5). We can set h := 1 since absolute
774              * distance doesn't matter here. */
775             tx = his_cx + his_cy;
776             his_cy = -his_cx + his_cy;
777             his_cx = tx;
778         }
779
780         switch(dir) {
781         case OB_DIRECTION_NORTH:
782         case OB_DIRECTION_SOUTH:
783         case OB_DIRECTION_NORTHEAST:
784         case OB_DIRECTION_SOUTHWEST:
785             offset = (his_cx < 0) ? -his_cx : his_cx;
786             distance = ((dir == OB_DIRECTION_NORTH ||
787                          dir == OB_DIRECTION_NORTHEAST) ?
788                         -his_cy : his_cy);
789             break;
790         case OB_DIRECTION_EAST:
791         case OB_DIRECTION_WEST:
792         case OB_DIRECTION_SOUTHEAST:
793         case OB_DIRECTION_NORTHWEST:
794             offset = (his_cy < 0) ? -his_cy : his_cy;
795             distance = ((dir == OB_DIRECTION_WEST ||
796                          dir == OB_DIRECTION_NORTHWEST) ?
797                         -his_cx : his_cx);
798             break;
799         }
800
801         /* the target must be in the requested direction */
802         if(distance <= 0)
803             continue;
804
805         /* Calculate score for this window.  The smaller the better. */
806         score = distance + offset;
807
808         /* windows more than 45 degrees off the direction are
809          * heavily penalized and will only be chosen if nothing
810          * else within a million pixels */
811         if(offset > distance)
812             score += 1000000;
813
814         if(best_score == -1 || score < best_score)
815             best_client = cur,
816                 best_score = score;
817     }
818
819     return best_client;
820 }
821
822 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
823                              gboolean desktop_windows, gboolean interactive,
824                              gboolean dialog, gboolean done, gboolean cancel)
825 {
826     static ObClient *first = NULL;
827     ObClient *ft = NULL;
828
829     if (!interactive)
830         return;
831
832     if (cancel) {
833         focus_cycle_target = NULL;
834         goto done_cycle;
835     } else if (done)
836         goto done_cycle;
837
838     if (!focus_order)
839         goto done_cycle;
840
841     if (!first) first = focus_client;
842     if (!focus_cycle_target) focus_cycle_target = focus_client;
843
844     if (focus_cycle_target)
845         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
846                                     desktop_windows);
847     else {
848         GList *it;
849
850         for (it = focus_order; it; it = g_list_next(it))
851             if (valid_focus_target(it->data, FALSE, dock_windows,
852                                    desktop_windows))
853                 ft = it->data;
854     }
855         
856     if (ft) {
857         if (ft != focus_cycle_target) {/* prevents flicker */
858             focus_cycle_target = ft;
859             focus_cycle_draw_indicator();
860         }
861     }
862     if (focus_cycle_target) {
863         /* same arguments as valid_focus_target */
864         popup_cycle(focus_cycle_target, dialog, FALSE, dock_windows,
865                     desktop_windows);
866         if (dialog)
867             return;
868     }
869
870
871 done_cycle:
872     if (done && focus_cycle_target)
873         client_activate(focus_cycle_target, FALSE, TRUE);
874
875     first = NULL;
876     focus_cycle_target = NULL;
877
878     focus_cycle_draw_indicator();
879     popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
880
881     return;
882 }
883
884 void focus_order_add_new(ObClient *c)
885 {
886     if (c->iconic)
887         focus_order_to_top(c);
888     else {
889         g_assert(!g_list_find(focus_order, c));
890         /* if there are any iconic windows, put this above them in the order,
891            but if there are not, then put it under the currently focused one */
892         if (focus_order && ((ObClient*)focus_order->data)->iconic)
893             focus_order = g_list_insert(focus_order, c, 0);
894         else
895             focus_order = g_list_insert(focus_order, c, 1);
896     }
897 }
898
899 void focus_order_remove(ObClient *c)
900 {
901     focus_order = g_list_remove(focus_order, c);
902 }
903
904 void focus_order_to_top(ObClient *c)
905 {
906     focus_order = g_list_remove(focus_order, c);
907     if (!c->iconic) {
908         focus_order = g_list_prepend(focus_order, c);
909     } else {
910         GList *it;
911
912         /* insert before first iconic window */
913         for (it = focus_order;
914              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
915         focus_order = g_list_insert_before(focus_order, it, c);
916     }
917 }
918
919 void focus_order_to_bottom(ObClient *c)
920 {
921     focus_order = g_list_remove(focus_order, c);
922     if (c->iconic) {
923         focus_order = g_list_append(focus_order, c);
924     } else {
925         GList *it;
926
927         /* insert before first iconic window */
928         for (it = focus_order;
929              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
930         focus_order = g_list_insert_before(focus_order, it, c);
931     }
932 }
933
934 ObClient *focus_order_find_first(guint desktop)
935 {
936     GList *it;
937     for (it = focus_order; it; it = g_list_next(it)) {
938         ObClient *c = it->data;
939         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
940             return c;
941     }
942     return NULL;
943 }