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