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
);
47 static ObClient
*focus_find_directional (ObClient
*c
,
49 gboolean dock_windows
,
50 gboolean desktop_windows
);
52 void focus_cycle_startup(gboolean reconfig
)
57 void focus_cycle_shutdown(gboolean reconfig
)
62 void focus_cycle_stop(ObClient
*ifclient
)
64 /* stop focus cycling if the given client is a valid focus target,
65 and so the cycling is being disrupted */
66 if (focus_cycle_target
&& ifclient
&&
67 focus_cycle_target_valid(ifclient
,
68 focus_cycle_iconic_windows
,
69 focus_cycle_all_desktops
,
70 focus_cycle_dock_windows
,
71 focus_cycle_desktop_windows
))
73 focus_cycle(TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
74 focus_directional_cycle(0, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
78 /*! Returns if a focus target has valid group siblings that can be cycled
80 static gboolean
focus_target_has_siblings(ObClient
*ft
,
81 gboolean iconic_windows
,
82 gboolean all_desktops
)
87 if (!ft
->group
) return FALSE
;
89 for (it
= ft
->group
->members
; it
; it
= g_slist_next(it
)) {
90 ObClient
*c
= it
->data
;
91 /* check that it's not a helper window to avoid infinite recursion */
92 if (c
!= ft
&& !client_helper(c
) &&
93 focus_cycle_target_valid(c
, iconic_windows
, all_desktops
, FALSE
,
102 gboolean
focus_cycle_target_valid(ObClient
*ft
,
103 gboolean iconic_windows
,
104 gboolean all_desktops
,
105 gboolean dock_windows
,
106 gboolean desktop_windows
)
110 /* it's on this desktop unless you want all desktops.
112 do this check first because it will usually filter out the most
114 ok
= (all_desktops
|| ft
->desktop
== screen_desktop
||
115 ft
->desktop
== DESKTOP_ALL
);
117 /* the window can receive focus somehow */
118 ok
= ok
&& (ft
->can_focus
|| ft
->focus_notify
);
120 /* the window is not iconic, or we're allowed to go to iconic ones */
121 ok
= ok
&& (iconic_windows
|| !ft
->iconic
);
123 /* it's the right type of window */
124 if (dock_windows
|| desktop_windows
)
125 ok
= ok
&& ((dock_windows
&& ft
->type
== OB_CLIENT_TYPE_DOCK
) ||
126 (desktop_windows
&& ft
->type
== OB_CLIENT_TYPE_DESKTOP
));
127 /* modal windows are important and can always get focus if they are
128 visible and stuff, so don't change 'ok' based on their type */
130 /* normal non-helper windows are valid targets */
132 ((client_normal(ft
) && !client_helper(ft
))
134 /* helper windows are valid targets it... */
135 (client_helper(ft
) &&
136 /* ...a window in its group already has focus ... */
137 ((focus_client
&& ft
->group
== focus_client
->group
) ||
138 /* ... or if there are no other windows in its group
139 that can be cycled to instead */
140 !focus_target_has_siblings(ft
, iconic_windows
, all_desktops
))));
142 /* it's not set to skip the taskbar (unless it is a type that would be
143 expected to set this hint, or modal) */
144 ok
= ok
&& ((ft
->type
== OB_CLIENT_TYPE_DOCK
||
145 ft
->type
== OB_CLIENT_TYPE_DESKTOP
||
146 ft
->type
== OB_CLIENT_TYPE_TOOLBAR
||
147 ft
->type
== OB_CLIENT_TYPE_MENU
||
148 ft
->type
== OB_CLIENT_TYPE_UTILITY
) ||
152 /* it's not going to just send focus off somewhere else (modal window),
153 unless that modal window is not one of our valid targets, then let
154 you choose this window and bring the modal one here */
156 ObClient
*cft
= client_focus_target(ft
);
157 ok
= ok
&& (ft
== cft
|| !focus_cycle_target_valid(cft
,
167 void focus_cycle(gboolean forward
, gboolean all_desktops
,
168 gboolean dock_windows
, gboolean desktop_windows
,
169 gboolean linear
, gboolean interactive
,
170 gboolean dialog
, gboolean done
, gboolean cancel
)
172 static ObClient
*first
= NULL
;
173 static ObClient
*t
= NULL
;
174 static GList
*order
= NULL
;
175 GList
*it
, *start
, *list
;
180 focus_cycle_target
= NULL
;
188 if (!first
) first
= focus_client
;
190 if (linear
) list
= client_list
;
191 else list
= focus_order
;
199 if (focus_cycle_target
== NULL
) {
200 focus_cycle_iconic_windows
= TRUE
;
201 focus_cycle_all_desktops
= all_desktops
;
202 focus_cycle_dock_windows
= dock_windows
;
203 focus_cycle_desktop_windows
= desktop_windows
;
204 focus_cycle_target
= focus_client
;
207 start
= it
= g_list_find(list
, focus_cycle_target
);
208 if (!start
) /* switched desktops or something? */
209 start
= it
= forward
? g_list_last(list
) : g_list_first(list
);
210 if (!start
) goto done_cycle
;
215 if (it
== NULL
) it
= g_list_first(list
);
218 if (it
== NULL
) it
= g_list_last(list
);
221 if (focus_cycle_target_valid(ft
,
222 focus_cycle_iconic_windows
,
223 focus_cycle_all_desktops
,
224 focus_cycle_dock_windows
,
225 focus_cycle_desktop_windows
))
228 if (ft
!= focus_cycle_target
) { /* prevents flicker */
229 focus_cycle_target
= ft
;
230 focus_cycle_draw_indicator(ft
);
233 /* same arguments as focus_target_valid */
234 focus_cycle_popup_show(ft
,
235 focus_cycle_iconic_windows
,
236 focus_cycle_all_desktops
,
237 focus_cycle_dock_windows
,
238 focus_cycle_desktop_windows
);
240 } else if (ft
!= focus_cycle_target
) {
241 focus_cycle_target
= ft
;
246 } while (it
!= start
);
249 if (done
&& focus_cycle_target
)
250 client_activate(focus_cycle_target
, FALSE
, TRUE
);
254 focus_cycle_target
= NULL
;
259 focus_cycle_draw_indicator(NULL
);
260 focus_cycle_popup_hide();
266 /* this be mostly ripped from fvwm */
267 static ObClient
*focus_find_directional(ObClient
*c
, ObDirection dir
,
268 gboolean dock_windows
,
269 gboolean desktop_windows
)
271 gint my_cx
, my_cy
, his_cx
, his_cy
;
274 gint score
, best_score
;
275 ObClient
*best_client
, *cur
;
281 /* first, find the centre coords of the currently focused window */
282 my_cx
= c
->frame
->area
.x
+ c
->frame
->area
.width
/ 2;
283 my_cy
= c
->frame
->area
.y
+ c
->frame
->area
.height
/ 2;
288 for(it
= g_list_first(client_list
); it
; it
= g_list_next(it
)) {
291 /* the currently selected window isn't interesting */
294 if (!focus_cycle_target_valid(it
->data
, FALSE
, FALSE
, dock_windows
,
298 /* find the centre coords of this window, from the
299 * currently focused window's point of view */
300 his_cx
= (cur
->frame
->area
.x
- my_cx
)
301 + cur
->frame
->area
.width
/ 2;
302 his_cy
= (cur
->frame
->area
.y
- my_cy
)
303 + cur
->frame
->area
.height
/ 2;
305 if(dir
== OB_DIRECTION_NORTHEAST
|| dir
== OB_DIRECTION_SOUTHEAST
||
306 dir
== OB_DIRECTION_SOUTHWEST
|| dir
== OB_DIRECTION_NORTHWEST
) {
308 /* Rotate the diagonals 45 degrees counterclockwise.
309 * To do this, multiply the matrix /+h +h\ with the
310 * vector (x y). \-h +h/
311 * h = sqrt(0.5). We can set h := 1 since absolute
312 * distance doesn't matter here. */
313 tx
= his_cx
+ his_cy
;
314 his_cy
= -his_cx
+ his_cy
;
319 case OB_DIRECTION_NORTH
:
320 case OB_DIRECTION_SOUTH
:
321 case OB_DIRECTION_NORTHEAST
:
322 case OB_DIRECTION_SOUTHWEST
:
323 offset
= (his_cx
< 0) ? -his_cx
: his_cx
;
324 distance
= ((dir
== OB_DIRECTION_NORTH
||
325 dir
== OB_DIRECTION_NORTHEAST
) ?
328 case OB_DIRECTION_EAST
:
329 case OB_DIRECTION_WEST
:
330 case OB_DIRECTION_SOUTHEAST
:
331 case OB_DIRECTION_NORTHWEST
:
332 offset
= (his_cy
< 0) ? -his_cy
: his_cy
;
333 distance
= ((dir
== OB_DIRECTION_WEST
||
334 dir
== OB_DIRECTION_NORTHWEST
) ?
339 /* the target must be in the requested direction */
343 /* Calculate score for this window. The smaller the better. */
344 score
= distance
+ offset
;
346 /* windows more than 45 degrees off the direction are
347 * heavily penalized and will only be chosen if nothing
348 * else within a million pixels */
349 if(offset
> distance
)
352 if(best_score
== -1 || score
< best_score
)
360 void focus_directional_cycle(ObDirection dir
, gboolean dock_windows
,
361 gboolean desktop_windows
, gboolean interactive
,
362 gboolean dialog
, gboolean done
, gboolean cancel
)
364 static ObClient
*first
= NULL
;
371 focus_cycle_target
= NULL
;
379 if (focus_cycle_target
== NULL
) {
380 focus_cycle_iconic_windows
= FALSE
;
381 focus_cycle_all_desktops
= FALSE
;
382 focus_cycle_dock_windows
= dock_windows
;
383 focus_cycle_desktop_windows
= desktop_windows
;
384 focus_cycle_target
= focus_client
;
387 if (!first
) first
= focus_client
;
389 if (focus_cycle_target
)
390 ft
= focus_find_directional(focus_cycle_target
, dir
, dock_windows
,
395 for (it
= focus_order
; it
; it
= g_list_next(it
))
396 if (focus_cycle_target_valid(it
->data
,
397 focus_cycle_iconic_windows
,
398 focus_cycle_all_desktops
,
399 focus_cycle_dock_windows
,
400 focus_cycle_desktop_windows
))
405 if (ft
!= focus_cycle_target
) {/* prevents flicker */
406 focus_cycle_target
= ft
;
407 focus_cycle_draw_indicator(ft
);
410 if (focus_cycle_target
&& dialog
) {
411 /* same arguments as focus_target_valid */
412 focus_cycle_popup_single_show(focus_cycle_target
,
413 focus_cycle_iconic_windows
,
414 focus_cycle_all_desktops
,
415 focus_cycle_dock_windows
,
416 focus_cycle_desktop_windows
);
421 if (done
&& focus_cycle_target
)
422 client_activate(focus_cycle_target
, FALSE
, TRUE
);
425 focus_cycle_target
= NULL
;
427 focus_cycle_draw_indicator(NULL
);
428 focus_cycle_popup_single_hide();