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