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