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