]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus.c
add a test for focus falling back to a window thats gone
[mikachu/openbox.git] / openbox / focus.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    focus.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "event.h"
22 #include "openbox.h"
23 #include "grab.h"
24 #include "framerender.h"
25 #include "client.h"
26 #include "config.h"
27 #include "frame.h"
28 #include "screen.h"
29 #include "group.h"
30 #include "prop.h"
31 #include "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 /*! This variable is used for focus fallback. If we fallback to a window, we
48   set this to the window. And when focus goes somewhere after that, it will
49   be set to NULL. If between falling back to that window and something
50   getting focused, the window gets unmanaged, then if there are no incoming
51   FocusIn events, we fallback again because focus has just gotten itself lost.
52  */
53 static ObClient *focus_tried = NULL;
54
55 struct {
56     InternalWindow top;
57     InternalWindow left;
58     InternalWindow right;
59     InternalWindow bottom;
60 } focus_indicator;
61
62 RrAppearance *a_focus_indicator;
63 RrColor *color_white;
64
65 static ObIconPopup *focus_cycle_popup;
66
67 static gboolean valid_focus_target(ObClient *ft,
68                                    gboolean all_desktops,
69                                    gboolean dock_windows,
70                                    gboolean desktop_windows);
71 static void focus_cycle_destroy_notify(ObClient *client, gpointer data);
72 static void focus_tried_hide_notify(ObClient *client, gpointer data);
73
74 static Window createWindow(Window parent, gulong mask,
75                            XSetWindowAttributes *attrib)
76 {
77     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
78                          RrDepth(ob_rr_inst), InputOutput,
79                          RrVisual(ob_rr_inst), mask, attrib);
80                        
81 }
82
83 void focus_startup(gboolean reconfig)
84 {
85     focus_cycle_popup = icon_popup_new(TRUE);
86
87     if (!reconfig) {
88         XSetWindowAttributes attr;
89
90         client_add_destroy_notify(focus_cycle_destroy_notify, NULL);
91         client_add_destroy_notify(focus_tried_hide_notify, NULL);
92         client_add_hide_notify(focus_tried_hide_notify, NULL);
93
94         /* start with nothing focused */
95         focus_nothing();
96
97         focus_indicator.top.obwin.type = Window_Internal;
98         focus_indicator.left.obwin.type = Window_Internal;
99         focus_indicator.right.obwin.type = Window_Internal;
100         focus_indicator.bottom.obwin.type = Window_Internal;
101
102         attr.override_redirect = True;
103         attr.background_pixel = BlackPixel(ob_display, ob_screen);
104         focus_indicator.top.win =
105             createWindow(RootWindow(ob_display, ob_screen),
106                          CWOverrideRedirect | CWBackPixel, &attr);
107         focus_indicator.left.win =
108             createWindow(RootWindow(ob_display, ob_screen),
109                          CWOverrideRedirect | CWBackPixel, &attr);
110         focus_indicator.right.win =
111             createWindow(RootWindow(ob_display, ob_screen),
112                          CWOverrideRedirect | CWBackPixel, &attr);
113         focus_indicator.bottom.win =
114             createWindow(RootWindow(ob_display, ob_screen),
115                          CWOverrideRedirect | CWBackPixel, &attr);
116
117         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.top));
118         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.left));
119         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.right));
120         stacking_add(INTERNAL_AS_WINDOW(&focus_indicator.bottom));
121
122         color_white = RrColorNew(ob_rr_inst, 0xff, 0xff, 0xff);
123
124         a_focus_indicator = RrAppearanceNew(ob_rr_inst, 4);
125         a_focus_indicator->surface.grad = RR_SURFACE_SOLID;
126         a_focus_indicator->surface.relief = RR_RELIEF_FLAT;
127         a_focus_indicator->surface.primary = RrColorNew(ob_rr_inst,
128                                                         0, 0, 0);
129         a_focus_indicator->texture[0].type = RR_TEXTURE_LINE_ART;
130         a_focus_indicator->texture[0].data.lineart.color = color_white;
131         a_focus_indicator->texture[1].type = RR_TEXTURE_LINE_ART;
132         a_focus_indicator->texture[1].data.lineart.color = color_white;
133         a_focus_indicator->texture[2].type = RR_TEXTURE_LINE_ART;
134         a_focus_indicator->texture[2].data.lineart.color = color_white;
135         a_focus_indicator->texture[3].type = RR_TEXTURE_LINE_ART;
136         a_focus_indicator->texture[3].data.lineart.color = color_white;
137     }
138 }
139
140 void focus_shutdown(gboolean reconfig)
141 {
142     icon_popup_free(focus_cycle_popup);
143
144     if (!reconfig) {
145         client_remove_destroy_notify(focus_cycle_destroy_notify);
146         client_remove_destroy_notify(focus_tried_hide_notify);
147         client_remove_hide_notify(focus_tried_hide_notify);
148
149         /* reset focus to root */
150         XSetInputFocus(ob_display, PointerRoot, RevertToNone, CurrentTime);
151
152         RrColorFree(color_white);
153
154         RrAppearanceFree(a_focus_indicator);
155
156         XDestroyWindow(ob_display, focus_indicator.top.win);
157         XDestroyWindow(ob_display, focus_indicator.left.win);
158         XDestroyWindow(ob_display, focus_indicator.right.win);
159         XDestroyWindow(ob_display, focus_indicator.bottom.win);
160     }
161 }
162
163 static void push_to_top(ObClient *client)
164 {
165     focus_order = g_list_remove(focus_order, client);
166     focus_order = g_list_prepend(focus_order, client);
167 }
168
169 void focus_set_client(ObClient *client)
170 {
171     Window active;
172
173     ob_debug_type(OB_DEBUG_FOCUS,
174                   "focus_set_client 0x%lx\n", client ? client->window : 0);
175
176     /* uninstall the old colormap, and install the new one */
177     screen_install_colormap(focus_client, FALSE);
178     screen_install_colormap(client, TRUE);
179
180     /* in the middle of cycling..? kill it. CurrentTime is fine, time won't
181        be used.
182     */
183     if (focus_cycle_target)
184         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
185
186     focus_client = client;
187
188     if (client != NULL) {
189         /* move to the top of the list */
190         push_to_top(client);
191         /* remove hiliting from the window when it gets focused */
192         client_hilite(client, FALSE);
193     }
194
195     /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
196     if (ob_state() != OB_STATE_EXITING) {
197         active = client ? client->window : None;
198         PROP_SET32(RootWindow(ob_display, ob_screen),
199                    net_active_window, window, active);
200     }
201
202
203     focus_tried = NULL; /* focus isn't "trying" to go anywhere now */
204 }
205
206 static ObClient* focus_fallback_target(gboolean allow_refocus, ObClient *old)
207 {
208     GList *it;
209     ObClient *target = NULL;
210     ObClient *desktop = NULL;
211
212     ob_debug_type(OB_DEBUG_FOCUS, "trying pointer stuff\n");
213     if (config_focus_follow && !config_focus_last)
214     {
215         if ((target = client_under_pointer()))
216             if (allow_refocus || target != old)
217                 if (client_normal(target) && client_can_focus(target)) {
218                     ob_debug_type(OB_DEBUG_FOCUS, "found in pointer stuff\n");
219                     return target;
220                 }
221     }
222
223 #if 0
224         /* try for group relations */
225         if (old->group) {
226             GSList *sit;
227
228             for (it = focus_order[screen_desktop]; it; it = g_list_next(it))
229                 for (sit = old->group->members; sit; sit = g_slist_next(sit))
230                     if (sit->data == it->data)
231                         if (sit->data != old && client_normal(sit->data))
232                             if (client_can_focus(sit->data))
233                                 return sit->data;
234         }
235 #endif
236
237     ob_debug_type(OB_DEBUG_FOCUS, "trying omnipresentness\n");
238     if (allow_refocus && old && old->desktop == DESKTOP_ALL &&
239         client_normal(old))
240     {
241         return old;
242     }
243
244
245     ob_debug_type(OB_DEBUG_FOCUS, "trying the focus order\n");
246     for (it = focus_order; it; it = g_list_next(it))
247         if (allow_refocus || it->data != old) {
248             ObClient *c = it->data;
249             /* fallback focus to a window if:
250                1. it is actually focusable, cuz if it's not then we're sending
251                focus off to nothing. this includes if it is visible right now
252                2. it is on the current desktop. this ignores omnipresent
253                windows, which are problematic in their own rite.
254                3. it is a normal type window, don't fall back onto a dock or
255                a splashscreen or a desktop window (save the desktop as a
256                backup fallback though)
257             */
258             if (client_can_focus(c))
259             {
260                 if (c->desktop == screen_desktop && client_normal(c)) {
261                     ob_debug_type(OB_DEBUG_FOCUS, "found in focus order\n");
262                     return it->data;
263                 } else if (c->type == OB_CLIENT_TYPE_DESKTOP && 
264                            desktop == NULL)
265                     desktop = c;
266             }
267         }
268
269     /* as a last resort fallback to the desktop window if there is one.
270        (if there's more than one, then the one most recently focused.)
271     */
272     ob_debug_type(OB_DEBUG_FOCUS, "found desktop: \n", !!desktop);
273     return desktop;   
274 }
275
276 ObClient* focus_fallback(gboolean allow_refocus)
277 {
278     ObClient *new;
279     ObClient *old;
280
281     old = focus_client;
282     new = focus_fallback_target(allow_refocus, focus_client);
283
284     /* unfocus any focused clients.. they can be focused by Pointer events
285        and such, and then when we try focus them, we won't get a FocusIn
286        event at all for them. */
287     focus_nothing();
288
289     if (new) {
290         client_focus(new);
291         /* remember that we tried to send focus here */
292         focus_tried = new;
293     }
294
295     return new;
296 }
297
298 void focus_nothing()
299 {
300     /* Install our own colormap */
301     if (focus_client != NULL) {
302         screen_install_colormap(focus_client, FALSE);
303         screen_install_colormap(NULL, TRUE);
304     }
305
306     /* Don't set focus_client to NULL here. It will be set to NULL when the
307        FocusOut event comes. Otherwise, if we focus nothing and then focus the
308        same window again, The focus code says nothing changed, but focus_client
309        ends up being NULL anyways.
310     focus_client = NULL;
311     */
312
313     focus_tried = NULL; /* focus isn't "trying" to go anywhere now */
314
315     /* if there is a grab going on, then we need to cancel it. if we move
316        focus during the grab, applications will get NotifyWhileGrabbed events
317        and ignore them !
318
319        actions should not rely on being able to move focus during an
320        interactive grab.
321     */
322     if (keyboard_interactively_grabbed())
323         keyboard_interactive_cancel();
324
325     /* when nothing will be focused, send focus to the backup target */
326     XSetInputFocus(ob_display, screen_support_win, RevertToPointerRoot,
327                    event_curtime);
328 }
329
330 static gchar *popup_get_name(ObClient *c, ObClient **nametarget)
331 {
332     ObClient *p;
333     gchar *title = NULL;
334     const gchar *desk = NULL;
335     gchar *ret;
336
337     /* find our highest direct parent, including non-normal windows */
338     for (p = c; p->transient_for && p->transient_for != OB_TRAN_GROUP;
339          p = p->transient_for);
340
341     if (c->desktop != DESKTOP_ALL && c->desktop != screen_desktop)
342         desk = screen_desktop_names[c->desktop];
343
344     /* use the transient's parent's title/icon if we don't have one */
345     if (p != c && !strcmp("", (c->iconic ? c->icon_title : c->title)))
346         title = g_strdup(p->iconic ? p->icon_title : p->title);
347
348     if (title == NULL)
349         title = g_strdup(c->iconic ? c->icon_title : c->title);
350
351     if (desk)
352         ret = g_strdup_printf("%s [%s]", title, desk);
353     else {
354         ret = title;
355         title = NULL;
356     }
357     g_free(title);
358
359     /* set this only if we're returning true and they asked for it */
360     if (ret && nametarget) *nametarget = p;
361     return ret;
362 }
363
364 static void popup_cycle(ObClient *c, gboolean show,
365                         gboolean all_desktops, gboolean dock_windows,
366                         gboolean desktop_windows)
367 {
368     gchar *showtext = NULL;
369     ObClient *showtarget;
370
371     if (!show) {
372         icon_popup_hide(focus_cycle_popup);
373         return;
374     }
375
376     /* do this stuff only when the dialog is first showing */
377     if (!focus_cycle_popup->popup->mapped &&
378         !focus_cycle_popup->popup->delay_mapped)
379     {
380         Rect *a;
381         gchar **names;
382         GList *targets = NULL, *it;
383         gint n = 0, i;
384
385         /* position the popup */
386         a = screen_physical_area_monitor(0);
387         icon_popup_position(focus_cycle_popup, CenterGravity,
388                             a->x + a->width / 2, a->y + a->height / 2);
389         icon_popup_height(focus_cycle_popup, POPUP_HEIGHT);
390         icon_popup_min_width(focus_cycle_popup, POPUP_WIDTH);
391         icon_popup_max_width(focus_cycle_popup,
392                              MAX(a->width/3, POPUP_WIDTH));
393
394
395         /* make its width to be the width of all the possible titles */
396
397         /* build a list of all the valid focus targets */
398         for (it = focus_order; it; it = g_list_next(it)) {
399             ObClient *ft = it->data;
400             if (valid_focus_target(ft, all_desktops, dock_windows
401                                    , desktop_windows))
402             {
403                 targets = g_list_prepend(targets, ft);
404                 ++n;
405             }
406         }
407         /* make it null terminated so we can use g_strfreev */
408         names = g_new(char*, n+1);
409         for (it = targets, i = 0; it; it = g_list_next(it), ++i) {
410             ObClient *ft = it->data, *t;
411             names[i] = popup_get_name(ft, &t);
412
413             /* little optimization.. save this text and client, so we dont
414                have to get it again */
415             if (ft == c) {
416                 showtext = g_strdup(names[i]);
417                 showtarget = t;
418             }
419         }
420         names[n] = NULL;
421
422         icon_popup_text_width_to_strings(focus_cycle_popup, names, n);
423         g_strfreev(names);
424     }
425
426
427     if (!showtext) showtext = popup_get_name(c, &showtarget);
428     icon_popup_show(focus_cycle_popup, showtext,
429                     client_icon(showtarget, 48, 48));
430     g_free(showtext);
431 }
432
433 static void focus_cycle_destroy_notify(ObClient *client, gpointer data)
434 {
435     /* end cycling if the target disappears. CurrentTime is fine, time won't
436        be used
437     */
438     if (focus_cycle_target == client)
439         focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
440 }
441
442 void focus_cycle_draw_indicator()
443 {
444     if (!focus_cycle_target) {
445         XUnmapWindow(ob_display, focus_indicator.top.win);
446         XUnmapWindow(ob_display, focus_indicator.left.win);
447         XUnmapWindow(ob_display, focus_indicator.right.win);
448         XUnmapWindow(ob_display, focus_indicator.bottom.win);
449
450         /* kill enter events cause by this unmapping */
451         event_ignore_queued_enters();
452     } else {
453         /*
454           if (focus_cycle_target)
455               frame_adjust_focus(focus_cycle_target->frame, FALSE);
456           frame_adjust_focus(focus_cycle_target->frame, TRUE);
457         */
458         gint x, y, w, h;
459         gint wt, wl, wr, wb;
460
461         wt = wl = wr = wb = FOCUS_INDICATOR_WIDTH;
462
463         x = focus_cycle_target->frame->area.x;
464         y = focus_cycle_target->frame->area.y;
465         w = focus_cycle_target->frame->area.width;
466         h = wt;
467
468         XMoveResizeWindow(ob_display, focus_indicator.top.win,
469                           x, y, w, h);
470         a_focus_indicator->texture[0].data.lineart.x1 = 0;
471         a_focus_indicator->texture[0].data.lineart.y1 = h-1;
472         a_focus_indicator->texture[0].data.lineart.x2 = 0;
473         a_focus_indicator->texture[0].data.lineart.y2 = 0;
474         a_focus_indicator->texture[1].data.lineart.x1 = 0;
475         a_focus_indicator->texture[1].data.lineart.y1 = 0;
476         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
477         a_focus_indicator->texture[1].data.lineart.y2 = 0;
478         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
479         a_focus_indicator->texture[2].data.lineart.y1 = 0;
480         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
481         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
482         a_focus_indicator->texture[3].data.lineart.x1 = (wl-1);
483         a_focus_indicator->texture[3].data.lineart.y1 = h-1;
484         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
485         a_focus_indicator->texture[3].data.lineart.y2 = h-1;
486         RrPaint(a_focus_indicator, focus_indicator.top.win,
487                 w, h);
488
489         x = focus_cycle_target->frame->area.x;
490         y = focus_cycle_target->frame->area.y;
491         w = wl;
492         h = focus_cycle_target->frame->area.height;
493
494         XMoveResizeWindow(ob_display, focus_indicator.left.win,
495                           x, y, w, h);
496         a_focus_indicator->texture[0].data.lineart.x1 = w-1;
497         a_focus_indicator->texture[0].data.lineart.y1 = 0;
498         a_focus_indicator->texture[0].data.lineart.x2 = 0;
499         a_focus_indicator->texture[0].data.lineart.y2 = 0;
500         a_focus_indicator->texture[1].data.lineart.x1 = 0;
501         a_focus_indicator->texture[1].data.lineart.y1 = 0;
502         a_focus_indicator->texture[1].data.lineart.x2 = 0;
503         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
504         a_focus_indicator->texture[2].data.lineart.x1 = 0;
505         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
506         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
507         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
508         a_focus_indicator->texture[3].data.lineart.x1 = w-1;
509         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
510         a_focus_indicator->texture[3].data.lineart.x2 = w-1;
511         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
512         RrPaint(a_focus_indicator, focus_indicator.left.win,
513                 w, h);
514
515         x = focus_cycle_target->frame->area.x +
516             focus_cycle_target->frame->area.width - wr;
517         y = focus_cycle_target->frame->area.y;
518         w = wr;
519         h = focus_cycle_target->frame->area.height ;
520
521         XMoveResizeWindow(ob_display, focus_indicator.right.win,
522                           x, y, w, h);
523         a_focus_indicator->texture[0].data.lineart.x1 = 0;
524         a_focus_indicator->texture[0].data.lineart.y1 = 0;
525         a_focus_indicator->texture[0].data.lineart.x2 = w-1;
526         a_focus_indicator->texture[0].data.lineart.y2 = 0;
527         a_focus_indicator->texture[1].data.lineart.x1 = w-1;
528         a_focus_indicator->texture[1].data.lineart.y1 = 0;
529         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
530         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
531         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
532         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
533         a_focus_indicator->texture[2].data.lineart.x2 = 0;
534         a_focus_indicator->texture[2].data.lineart.y2 = h-1;
535         a_focus_indicator->texture[3].data.lineart.x1 = 0;
536         a_focus_indicator->texture[3].data.lineart.y1 = wt-1;
537         a_focus_indicator->texture[3].data.lineart.x2 = 0;
538         a_focus_indicator->texture[3].data.lineart.y2 = h - wb;
539         RrPaint(a_focus_indicator, focus_indicator.right.win,
540                 w, h);
541
542         x = focus_cycle_target->frame->area.x;
543         y = focus_cycle_target->frame->area.y +
544             focus_cycle_target->frame->area.height - wb;
545         w = focus_cycle_target->frame->area.width;
546         h = wb;
547
548         XMoveResizeWindow(ob_display, focus_indicator.bottom.win,
549                           x, y, w, h);
550         a_focus_indicator->texture[0].data.lineart.x1 = 0;
551         a_focus_indicator->texture[0].data.lineart.y1 = 0;
552         a_focus_indicator->texture[0].data.lineart.x2 = 0;
553         a_focus_indicator->texture[0].data.lineart.y2 = h-1;
554         a_focus_indicator->texture[1].data.lineart.x1 = 0;
555         a_focus_indicator->texture[1].data.lineart.y1 = h-1;
556         a_focus_indicator->texture[1].data.lineart.x2 = w-1;
557         a_focus_indicator->texture[1].data.lineart.y2 = h-1;
558         a_focus_indicator->texture[2].data.lineart.x1 = w-1;
559         a_focus_indicator->texture[2].data.lineart.y1 = h-1;
560         a_focus_indicator->texture[2].data.lineart.x2 = w-1;
561         a_focus_indicator->texture[2].data.lineart.y2 = 0;
562         a_focus_indicator->texture[3].data.lineart.x1 = wl-1;
563         a_focus_indicator->texture[3].data.lineart.y1 = 0;
564         a_focus_indicator->texture[3].data.lineart.x2 = w - wr;
565         a_focus_indicator->texture[3].data.lineart.y2 = 0;
566         RrPaint(a_focus_indicator, focus_indicator.bottom.win,
567                 w, h);
568
569         XMapWindow(ob_display, focus_indicator.top.win);
570         XMapWindow(ob_display, focus_indicator.left.win);
571         XMapWindow(ob_display, focus_indicator.right.win);
572         XMapWindow(ob_display, focus_indicator.bottom.win);
573     }
574 }
575
576 static gboolean has_valid_group_siblings_on_desktop(ObClient *ft,
577                                                     gboolean all_desktops)
578                                                          
579 {
580     GSList *it;
581
582     if (!ft->group) return FALSE;
583
584     for (it = ft->group->members; it; it = g_slist_next(it)) {
585         ObClient *c = it->data;
586         /* check that it's not a helper window to avoid infinite recursion */
587         if (c != ft && !client_helper(c) &&
588             valid_focus_target(c, all_desktops, FALSE, FALSE))
589         {
590             return TRUE;
591         }
592     }
593     return FALSE;
594 }
595
596 /*! @param allow_helpers This is used for calling itself recursively while
597                          checking helper windows. */
598 static gboolean valid_focus_target(ObClient *ft,
599                                    gboolean all_desktops,
600                                    gboolean dock_windows,
601                                    gboolean desktop_windows)
602 {
603     gboolean ok = FALSE;
604
605     /* it's on this desktop unless you want all desktops.
606
607        do this check first because it will usually filter out the most
608        windows */
609     ok = (all_desktops || ft->desktop == screen_desktop ||
610           ft->desktop == DESKTOP_ALL);
611
612     /* the window can receive focus somehow */
613     ok = ok && (ft->can_focus || ft->focus_notify);
614
615     /* it's the right type of window */
616     if (dock_windows || desktop_windows)
617         ok = ok && ((dock_windows && ft->type == OB_CLIENT_TYPE_DOCK) ||
618                     (desktop_windows && ft->type == OB_CLIENT_TYPE_DESKTOP));
619     else
620         /* normal non-helper windows are valid targets */
621         ok = ok &&
622             ((client_normal(ft) && !client_helper(ft))
623              ||
624              /* helper windows are valid targets it... */
625              (client_helper(ft) &&
626               /* ...a window in its group already has focus ... */
627               ((focus_client && ft->group == focus_client->group) ||
628                /* ... or if there are no other windows in its group 
629                   that can be cycled to instead */
630                !has_valid_group_siblings_on_desktop(ft, all_desktops))));
631
632     /* it's not set to skip the taskbar (unless it is a type that would be
633        expected to set this hint */
634     ok = ok && ((ft->type == OB_CLIENT_TYPE_DOCK ||
635                  ft->type == OB_CLIENT_TYPE_DESKTOP ||
636                  ft->type == OB_CLIENT_TYPE_TOOLBAR ||
637                  ft->type == OB_CLIENT_TYPE_MENU ||
638                  ft->type == OB_CLIENT_TYPE_UTILITY) ||
639                 !ft->skip_taskbar);
640
641     /* it's not going to just send fous off somewhere else (modal window) */
642     ok = ok && ft == client_focus_target(ft);
643
644     return ok;
645 }
646
647 void focus_cycle(gboolean forward, gboolean all_desktops,
648                  gboolean dock_windows, gboolean desktop_windows,
649                  gboolean linear, gboolean interactive,
650                  gboolean dialog, gboolean done, gboolean cancel)
651 {
652     static ObClient *first = NULL;
653     static ObClient *t = NULL;
654     static GList *order = NULL;
655     GList *it, *start, *list;
656     ObClient *ft = NULL;
657
658     if (interactive) {
659         if (cancel) {
660             focus_cycle_target = NULL;
661             goto done_cycle;
662         } else if (done)
663             goto done_cycle;
664
665         if (!focus_order)
666             goto done_cycle;
667
668         if (!first) first = focus_client;
669
670         if (linear) list = client_list;
671         else        list = focus_order;
672     } else {
673         if (!focus_order)
674             goto done_cycle;
675         list = client_list;
676     }
677     if (!focus_cycle_target) focus_cycle_target = focus_client;
678
679     start = it = g_list_find(list, focus_cycle_target);
680     if (!start) /* switched desktops or something? */
681         start = it = forward ? g_list_last(list) : g_list_first(list);
682     if (!start) goto done_cycle;
683
684     do {
685         if (forward) {
686             it = it->next;
687             if (it == NULL) it = g_list_first(list);
688         } else {
689             it = it->prev;
690             if (it == NULL) it = g_list_last(list);
691         }
692         ft = it->data;
693         if (valid_focus_target(ft, all_desktops, dock_windows,
694                                desktop_windows))
695         {
696             if (interactive) {
697                 if (ft != focus_cycle_target) { /* prevents flicker */
698                     focus_cycle_target = ft;
699                     focus_cycle_draw_indicator();
700                 }
701                 /* same arguments as valid_focus_target */
702                 popup_cycle(ft, dialog, all_desktops, dock_windows,
703                             desktop_windows);
704                 return;
705             } else if (ft != focus_cycle_target) {
706                 focus_cycle_target = ft;
707                 done = TRUE;
708                 break;
709             }
710         }
711     } while (it != start);
712
713 done_cycle:
714     if (done && focus_cycle_target)
715         client_activate(focus_cycle_target, FALSE, TRUE);
716
717     t = NULL;
718     first = NULL;
719     focus_cycle_target = NULL;
720     g_list_free(order);
721     order = NULL;
722
723     if (interactive) {
724         focus_cycle_draw_indicator();
725         popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
726     }
727
728     return;
729 }
730
731 /* this be mostly ripped from fvwm */
732 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
733                                         gboolean dock_windows,
734                                         gboolean desktop_windows) 
735 {
736     gint my_cx, my_cy, his_cx, his_cy;
737     gint offset = 0;
738     gint distance = 0;
739     gint score, best_score;
740     ObClient *best_client, *cur;
741     GList *it;
742
743     if(!client_list)
744         return NULL;
745
746     /* first, find the centre coords of the currently focused window */
747     my_cx = c->frame->area.x + c->frame->area.width / 2;
748     my_cy = c->frame->area.y + c->frame->area.height / 2;
749
750     best_score = -1;
751     best_client = NULL;
752
753     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
754         cur = it->data;
755
756         /* the currently selected window isn't interesting */
757         if(cur == c)
758             continue;
759         if (cur->type == OB_CLIENT_TYPE_DOCK && !dock_windows)
760             continue;
761         if (cur->type == OB_CLIENT_TYPE_DESKTOP && !desktop_windows)
762             continue;
763         if (!client_normal(cur) &&
764             cur->type != OB_CLIENT_TYPE_DOCK &&
765             cur->type != OB_CLIENT_TYPE_DESKTOP)
766             continue;
767         /* using c->desktop instead of screen_desktop doesn't work if the
768          * current window was omnipresent, hope this doesn't have any other
769          * side effects */
770         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
771             continue;
772         if(cur->iconic)
773             continue;
774         if(!(client_focus_target(cur) == cur &&
775              client_can_focus(cur)))
776             continue;
777
778         /* find the centre coords of this window, from the
779          * currently focused window's point of view */
780         his_cx = (cur->frame->area.x - my_cx)
781             + cur->frame->area.width / 2;
782         his_cy = (cur->frame->area.y - my_cy)
783             + cur->frame->area.height / 2;
784
785         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
786            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
787             gint tx;
788             /* Rotate the diagonals 45 degrees counterclockwise.
789              * To do this, multiply the matrix /+h +h\ with the
790              * vector (x y).                   \-h +h/
791              * h = sqrt(0.5). We can set h := 1 since absolute
792              * distance doesn't matter here. */
793             tx = his_cx + his_cy;
794             his_cy = -his_cx + his_cy;
795             his_cx = tx;
796         }
797
798         switch(dir) {
799         case OB_DIRECTION_NORTH:
800         case OB_DIRECTION_SOUTH:
801         case OB_DIRECTION_NORTHEAST:
802         case OB_DIRECTION_SOUTHWEST:
803             offset = (his_cx < 0) ? -his_cx : his_cx;
804             distance = ((dir == OB_DIRECTION_NORTH ||
805                          dir == OB_DIRECTION_NORTHEAST) ?
806                         -his_cy : his_cy);
807             break;
808         case OB_DIRECTION_EAST:
809         case OB_DIRECTION_WEST:
810         case OB_DIRECTION_SOUTHEAST:
811         case OB_DIRECTION_NORTHWEST:
812             offset = (his_cy < 0) ? -his_cy : his_cy;
813             distance = ((dir == OB_DIRECTION_WEST ||
814                          dir == OB_DIRECTION_NORTHWEST) ?
815                         -his_cx : his_cx);
816             break;
817         }
818
819         /* the target must be in the requested direction */
820         if(distance <= 0)
821             continue;
822
823         /* Calculate score for this window.  The smaller the better. */
824         score = distance + offset;
825
826         /* windows more than 45 degrees off the direction are
827          * heavily penalized and will only be chosen if nothing
828          * else within a million pixels */
829         if(offset > distance)
830             score += 1000000;
831
832         if(best_score == -1 || score < best_score)
833             best_client = cur,
834                 best_score = score;
835     }
836
837     return best_client;
838 }
839
840 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
841                              gboolean desktop_windows, gboolean interactive,
842                              gboolean dialog, gboolean done, gboolean cancel)
843 {
844     static ObClient *first = NULL;
845     ObClient *ft = NULL;
846
847     if (!interactive)
848         return;
849
850     if (cancel) {
851         focus_cycle_target = NULL;
852         goto done_cycle;
853     } else if (done)
854         goto done_cycle;
855
856     if (!focus_order)
857         goto done_cycle;
858
859     if (!first) first = focus_client;
860     if (!focus_cycle_target) focus_cycle_target = focus_client;
861
862     if (focus_cycle_target)
863         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
864                                     desktop_windows);
865     else {
866         GList *it;
867
868         for (it = focus_order; it; it = g_list_next(it))
869             if (valid_focus_target(it->data, FALSE, dock_windows,
870                                    desktop_windows))
871                 ft = it->data;
872     }
873         
874     if (ft) {
875         if (ft != focus_cycle_target) {/* prevents flicker */
876             focus_cycle_target = ft;
877             focus_cycle_draw_indicator();
878         }
879     }
880     if (focus_cycle_target) {
881         /* same arguments as valid_focus_target */
882         popup_cycle(focus_cycle_target, dialog, FALSE, dock_windows,
883                     desktop_windows);
884         if (dialog)
885             return;
886     }
887
888
889 done_cycle:
890     if (done && focus_cycle_target)
891         client_activate(focus_cycle_target, FALSE, TRUE);
892
893     first = NULL;
894     focus_cycle_target = NULL;
895
896     focus_cycle_draw_indicator();
897     popup_cycle(ft, FALSE, FALSE, FALSE, FALSE);
898
899     return;
900 }
901
902 void focus_order_add_new(ObClient *c)
903 {
904     if (c->iconic)
905         focus_order_to_top(c);
906     else {
907         g_assert(!g_list_find(focus_order, c));
908         /* if there are any iconic windows, put this above them in the order,
909            but if there are not, then put it under the currently focused one */
910         if (focus_order && ((ObClient*)focus_order->data)->iconic)
911             focus_order = g_list_insert(focus_order, c, 0);
912         else
913             focus_order = g_list_insert(focus_order, c, 1);
914     }
915 }
916
917 void focus_order_remove(ObClient *c)
918 {
919     focus_order = g_list_remove(focus_order, c);
920 }
921
922 void focus_order_to_top(ObClient *c)
923 {
924     focus_order = g_list_remove(focus_order, c);
925     if (!c->iconic) {
926         focus_order = g_list_prepend(focus_order, c);
927     } else {
928         GList *it;
929
930         /* insert before first iconic window */
931         for (it = focus_order;
932              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
933         focus_order = g_list_insert_before(focus_order, it, c);
934     }
935 }
936
937 void focus_order_to_bottom(ObClient *c)
938 {
939     focus_order = g_list_remove(focus_order, c);
940     if (c->iconic) {
941         focus_order = g_list_append(focus_order, c);
942     } else {
943         GList *it;
944
945         /* insert before first iconic window */
946         for (it = focus_order;
947              it && !((ObClient*)it->data)->iconic; it = g_list_next(it));
948         focus_order = g_list_insert_before(focus_order, it, c);
949     }
950 }
951
952 ObClient *focus_order_find_first(guint desktop)
953 {
954     GList *it;
955     for (it = focus_order; it; it = g_list_next(it)) {
956         ObClient *c = it->data;
957         if (c->desktop == desktop || c->desktop == DESKTOP_ALL)
958             return c;
959     }
960     return NULL;
961 }
962
963 static void focus_tried_hide_notify(ObClient *client, gpointer data)
964 {
965     XEvent ce;
966
967     if (client == focus_tried) {
968         /* we were trying to focus this window but it's gone */
969
970         focus_tried = NULL;
971
972         ob_debug_type(OB_DEBUG_FOCUS, "Tried to focus window 0x%x and it "
973                       "is being unmanaged:\n");
974         if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,NULL))
975         {
976             XPutBackEvent(ob_display, &ce);
977             ob_debug_type(OB_DEBUG_FOCUS, "  but another FocusIn is coming\n");
978         } else {
979             ob_debug_type(OB_DEBUG_FOCUS, "  so falling back focus again.\n");
980             focus_fallback(TRUE);
981         }
982     }
983 }