1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 focus_cycle.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
20 #include "focus_cycle.h"
21 #include "focus_cycle_indicator.h"
22 #include "focus_cycle_popup.h"
34 ObClient
*focus_cycle_target
= NULL
;
35 static gboolean focus_cycle_iconic_windows
;
36 static gboolean focus_cycle_all_desktops
;
37 static gboolean focus_cycle_dock_windows
;
38 static gboolean focus_cycle_desktop_windows
;
40 static gboolean
focus_target_has_siblings (ObClient
*ft
,
41 gboolean iconic_windows
,
42 gboolean all_desktops
);
43 static ObClient
*focus_find_directional (ObClient
*c
,
45 gboolean dock_windows
,
46 gboolean desktop_windows
);
48 void focus_cycle_startup(gboolean reconfig
)
53 void focus_cycle_shutdown(gboolean reconfig
)
58 void focus_cycle_stop(ObClient
*ifclient
)
60 /* stop focus cycling if the given client is a valid focus target,
61 and so the cycling is being disrupted */
62 if (focus_cycle_target
&& ifclient
&&
63 focus_cycle_target_valid(ifclient
,
64 focus_cycle_iconic_windows
,
65 focus_cycle_all_desktops
,
66 focus_cycle_dock_windows
,
67 focus_cycle_desktop_windows
))
69 focus_cycle(TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
70 focus_directional_cycle(0, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
74 /*! Returns if a focus target has valid group siblings that can be cycled
76 static gboolean
focus_target_has_siblings(ObClient
*ft
,
77 gboolean iconic_windows
,
78 gboolean all_desktops
)
83 if (!ft
->group
) return FALSE
;
85 for (it
= ft
->group
->members
; it
; it
= g_slist_next(it
)) {
86 ObClient
*c
= it
->data
;
87 /* check that it's not a helper window to avoid infinite recursion */
88 if (c
!= ft
&& c
->type
== OB_CLIENT_TYPE_NORMAL
&&
89 focus_cycle_target_valid(c
, iconic_windows
, all_desktops
, FALSE
,
98 gboolean
focus_cycle_target_valid(ObClient
*ft
,
99 gboolean iconic_windows
,
100 gboolean all_desktops
,
101 gboolean dock_windows
,
102 gboolean desktop_windows
)
106 /* it's on this desktop unless you want all desktops.
108 do this check first because it will usually filter out the most
110 ok
= (all_desktops
|| ft
->desktop
== screen_desktop
||
111 ft
->desktop
== DESKTOP_ALL
);
113 /* the window can receive focus somehow */
114 ok
= ok
&& (ft
->can_focus
|| ft
->focus_notify
);
116 /* the window is not iconic, or we're allowed to go to iconic ones */
117 ok
= ok
&& (iconic_windows
|| !ft
->iconic
);
119 /* it's the right type of window */
120 if (dock_windows
|| desktop_windows
)
121 ok
= ok
&& ((dock_windows
&& ft
->type
== OB_CLIENT_TYPE_DOCK
) ||
122 (desktop_windows
&& ft
->type
== OB_CLIENT_TYPE_DESKTOP
));
123 /* modal windows are important and can always get focus if they are
124 visible and stuff, so don't change 'ok' based on their type */
126 /* normal non-helper windows are valid targets */
128 ((client_normal(ft
) && !client_helper(ft
))
130 /* helper windows are valid targets if... */
131 (client_helper(ft
) &&
132 /* ...a window in its group already has focus ... */
133 ((focus_client
&& ft
->group
== focus_client
->group
) ||
134 /* ... or if there are no other windows in its group
135 that can be cycled to instead */
136 !focus_target_has_siblings(ft
, iconic_windows
, all_desktops
))));
138 /* it's not set to skip the taskbar (unless it is a type that would be
139 expected to set this hint, or modal) */
140 ok
= ok
&& ((ft
->type
== OB_CLIENT_TYPE_DOCK
||
141 ft
->type
== OB_CLIENT_TYPE_DESKTOP
||
142 ft
->type
== OB_CLIENT_TYPE_TOOLBAR
||
143 ft
->type
== OB_CLIENT_TYPE_MENU
||
144 ft
->type
== OB_CLIENT_TYPE_UTILITY
) ||
148 /* it's not going to just send focus off somewhere else (modal window),
149 unless that modal window is not one of our valid targets, then let
150 you choose this window and bring the modal one here */
152 ObClient
*cft
= client_focus_target(ft
);
153 ok
= ok
&& (ft
== cft
|| !focus_cycle_target_valid(cft
,
163 void focus_cycle(gboolean forward
, gboolean all_desktops
,
164 gboolean dock_windows
, gboolean desktop_windows
,
165 gboolean linear
, gboolean interactive
,
166 gboolean dialog
, gboolean done
, gboolean cancel
)
168 static ObClient
*t
= NULL
;
169 static GList
*order
= NULL
;
170 GList
*it
, *start
, *list
;
175 focus_cycle_target
= NULL
;
183 if (linear
) list
= client_list
;
184 else list
= focus_order
;
192 if (focus_cycle_target
== NULL
) {
193 focus_cycle_iconic_windows
= TRUE
;
194 focus_cycle_all_desktops
= all_desktops
;
195 focus_cycle_dock_windows
= dock_windows
;
196 focus_cycle_desktop_windows
= desktop_windows
;
197 start
= it
= g_list_find(list
, focus_client
);
199 start
= it
= g_list_find(list
, focus_cycle_target
);
201 if (!start
) /* switched desktops or something? */
202 start
= it
= forward
? g_list_last(list
) : g_list_first(list
);
203 if (!start
) goto done_cycle
;
208 if (it
== NULL
) it
= g_list_first(list
);
211 if (it
== NULL
) it
= g_list_last(list
);
214 if (focus_cycle_target_valid(ft
,
215 focus_cycle_iconic_windows
,
216 focus_cycle_all_desktops
,
217 focus_cycle_dock_windows
,
218 focus_cycle_desktop_windows
))
221 if (ft
!= focus_cycle_target
) { /* prevents flicker */
222 focus_cycle_target
= ft
;
223 focus_cycle_draw_indicator(ft
);
226 /* same arguments as focus_target_valid */
227 focus_cycle_popup_show(ft
,
228 focus_cycle_iconic_windows
,
229 focus_cycle_all_desktops
,
230 focus_cycle_dock_windows
,
231 focus_cycle_desktop_windows
);
233 } else if (ft
!= focus_cycle_target
) {
234 focus_cycle_target
= ft
;
239 } while (it
!= start
);
242 if (done
&& focus_cycle_target
)
243 client_activate(focus_cycle_target
, FALSE
, TRUE
);
246 focus_cycle_target
= NULL
;
251 focus_cycle_draw_indicator(NULL
);
252 focus_cycle_popup_hide();
258 /* this be mostly ripped from fvwm */
259 static ObClient
*focus_find_directional(ObClient
*c
, ObDirection dir
,
260 gboolean dock_windows
,
261 gboolean desktop_windows
)
263 gint my_cx
, my_cy
, his_cx
, his_cy
;
266 gint score
, best_score
;
267 ObClient
*best_client
, *cur
;
273 /* first, find the centre coords of the currently focused window */
274 my_cx
= c
->frame
->area
.x
+ c
->frame
->area
.width
/ 2;
275 my_cy
= c
->frame
->area
.y
+ c
->frame
->area
.height
/ 2;
280 for (it
= g_list_first(client_list
); it
; it
= g_list_next(it
)) {
283 /* the currently selected window isn't interesting */
286 if (!focus_cycle_target_valid(it
->data
, FALSE
, FALSE
, dock_windows
,
290 /* find the centre coords of this window, from the
291 * currently focused window's point of view */
292 his_cx
= (cur
->frame
->area
.x
- my_cx
)
293 + cur
->frame
->area
.width
/ 2;
294 his_cy
= (cur
->frame
->area
.y
- my_cy
)
295 + cur
->frame
->area
.height
/ 2;
297 if (dir
== OB_DIRECTION_NORTHEAST
|| dir
== OB_DIRECTION_SOUTHEAST
||
298 dir
== OB_DIRECTION_SOUTHWEST
|| dir
== OB_DIRECTION_NORTHWEST
)
301 /* Rotate the diagonals 45 degrees counterclockwise.
302 * To do this, multiply the matrix /+h +h\ with the
303 * vector (x y). \-h +h/
304 * h = sqrt(0.5). We can set h := 1 since absolute
305 * distance doesn't matter here. */
306 tx
= his_cx
+ his_cy
;
307 his_cy
= -his_cx
+ his_cy
;
312 case OB_DIRECTION_NORTH
:
313 case OB_DIRECTION_SOUTH
:
314 case OB_DIRECTION_NORTHEAST
:
315 case OB_DIRECTION_SOUTHWEST
:
316 offset
= (his_cx
< 0) ? -his_cx
: his_cx
;
317 distance
= ((dir
== OB_DIRECTION_NORTH
||
318 dir
== OB_DIRECTION_NORTHEAST
) ?
321 case OB_DIRECTION_EAST
:
322 case OB_DIRECTION_WEST
:
323 case OB_DIRECTION_SOUTHEAST
:
324 case OB_DIRECTION_NORTHWEST
:
325 offset
= (his_cy
< 0) ? -his_cy
: his_cy
;
326 distance
= ((dir
== OB_DIRECTION_WEST
||
327 dir
== OB_DIRECTION_NORTHWEST
) ?
332 /* the target must be in the requested direction */
336 /* Calculate score for this window. The smaller the better. */
337 score
= distance
+ offset
;
339 /* windows more than 45 degrees off the direction are
340 * heavily penalized and will only be chosen if nothing
341 * else within a million pixels */
342 if (offset
> distance
)
345 if (best_score
== -1 || score
< best_score
) {
354 void focus_directional_cycle(ObDirection dir
, gboolean dock_windows
,
355 gboolean desktop_windows
, gboolean interactive
,
356 gboolean dialog
, gboolean done
, gboolean cancel
)
358 static ObClient
*first
= NULL
;
362 focus_cycle_target
= NULL
;
364 } else if (done
&& interactive
)
370 if (focus_cycle_target
== NULL
) {
371 focus_cycle_iconic_windows
= FALSE
;
372 focus_cycle_all_desktops
= FALSE
;
373 focus_cycle_dock_windows
= dock_windows
;
374 focus_cycle_desktop_windows
= desktop_windows
;
377 if (!first
) first
= focus_client
;
379 if (focus_cycle_target
)
380 ft
= focus_find_directional(focus_cycle_target
, dir
, dock_windows
,
383 ft
= focus_find_directional(first
, dir
, dock_windows
, desktop_windows
);
387 for (it
= focus_order
; it
; it
= g_list_next(it
))
388 if (focus_cycle_target_valid(it
->data
,
389 focus_cycle_iconic_windows
,
390 focus_cycle_all_desktops
,
391 focus_cycle_dock_windows
,
392 focus_cycle_desktop_windows
))
396 if (ft
&& ft
!= focus_cycle_target
) {/* prevents flicker */
397 focus_cycle_target
= ft
;
400 focus_cycle_draw_indicator(ft
);
402 if (focus_cycle_target
&& dialog
)
403 /* same arguments as focus_target_valid */
404 focus_cycle_popup_single_show(focus_cycle_target
,
405 focus_cycle_iconic_windows
,
406 focus_cycle_all_desktops
,
407 focus_cycle_dock_windows
,
408 focus_cycle_desktop_windows
);
412 if (done
&& focus_cycle_target
)
413 client_activate(focus_cycle_target
, FALSE
, TRUE
);
416 focus_cycle_target
= NULL
;
418 focus_cycle_draw_indicator(NULL
);
419 focus_cycle_popup_single_hide();