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