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