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