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
*t
= NULL
;
173 static GList
*order
= NULL
;
174 GList
*it
, *start
, *list
;
179 focus_cycle_target
= NULL
;
187 if (linear
) list
= client_list
;
188 else list
= focus_order
;
196 if (focus_cycle_target
== NULL
) {
197 focus_cycle_iconic_windows
= TRUE
;
198 focus_cycle_all_desktops
= all_desktops
;
199 focus_cycle_dock_windows
= dock_windows
;
200 focus_cycle_desktop_windows
= desktop_windows
;
201 start
= it
= g_list_find(list
, focus_client
);
203 start
= it
= g_list_find(list
, focus_cycle_target
);
205 if (!start
) /* switched desktops or something? */
206 start
= it
= forward
? g_list_last(list
) : g_list_first(list
);
207 if (!start
) goto done_cycle
;
212 if (it
== NULL
) it
= g_list_first(list
);
215 if (it
== NULL
) it
= g_list_last(list
);
218 if (focus_cycle_target_valid(ft
,
219 focus_cycle_iconic_windows
,
220 focus_cycle_all_desktops
,
221 focus_cycle_dock_windows
,
222 focus_cycle_desktop_windows
))
225 if (ft
!= focus_cycle_target
) { /* prevents flicker */
226 focus_cycle_target
= ft
;
227 focus_cycle_draw_indicator(ft
);
230 /* same arguments as focus_target_valid */
231 focus_cycle_popup_show(ft
,
232 focus_cycle_iconic_windows
,
233 focus_cycle_all_desktops
,
234 focus_cycle_dock_windows
,
235 focus_cycle_desktop_windows
);
237 } else if (ft
!= focus_cycle_target
) {
238 focus_cycle_target
= ft
;
243 } while (it
!= start
);
246 if (done
&& focus_cycle_target
)
247 client_activate(focus_cycle_target
, FALSE
, TRUE
);
250 focus_cycle_target
= NULL
;
255 focus_cycle_draw_indicator(NULL
);
256 focus_cycle_popup_hide();
262 /* this be mostly ripped from fvwm */
263 static ObClient
*focus_find_directional(ObClient
*c
, ObDirection dir
,
264 gboolean dock_windows
,
265 gboolean desktop_windows
)
267 gint my_cx
, my_cy
, his_cx
, his_cy
;
270 gint score
, best_score
;
271 ObClient
*best_client
, *cur
;
277 /* first, find the centre coords of the currently focused window */
278 my_cx
= c
->frame
->area
.x
+ c
->frame
->area
.width
/ 2;
279 my_cy
= c
->frame
->area
.y
+ c
->frame
->area
.height
/ 2;
284 for (it
= g_list_first(client_list
); it
; it
= g_list_next(it
)) {
287 /* the currently selected window isn't interesting */
290 if (!focus_cycle_target_valid(it
->data
, FALSE
, FALSE
, dock_windows
,
294 /* find the centre coords of this window, from the
295 * currently focused window's point of view */
296 his_cx
= (cur
->frame
->area
.x
- my_cx
)
297 + cur
->frame
->area
.width
/ 2;
298 his_cy
= (cur
->frame
->area
.y
- my_cy
)
299 + cur
->frame
->area
.height
/ 2;
301 if (dir
== OB_DIRECTION_NORTHEAST
|| dir
== OB_DIRECTION_SOUTHEAST
||
302 dir
== OB_DIRECTION_SOUTHWEST
|| dir
== OB_DIRECTION_NORTHWEST
)
305 /* Rotate the diagonals 45 degrees counterclockwise.
306 * To do this, multiply the matrix /+h +h\ with the
307 * vector (x y). \-h +h/
308 * h = sqrt(0.5). We can set h := 1 since absolute
309 * distance doesn't matter here. */
310 tx
= his_cx
+ his_cy
;
311 his_cy
= -his_cx
+ his_cy
;
316 case OB_DIRECTION_NORTH
:
317 case OB_DIRECTION_SOUTH
:
318 case OB_DIRECTION_NORTHEAST
:
319 case OB_DIRECTION_SOUTHWEST
:
320 offset
= (his_cx
< 0) ? -his_cx
: his_cx
;
321 distance
= ((dir
== OB_DIRECTION_NORTH
||
322 dir
== OB_DIRECTION_NORTHEAST
) ?
325 case OB_DIRECTION_EAST
:
326 case OB_DIRECTION_WEST
:
327 case OB_DIRECTION_SOUTHEAST
:
328 case OB_DIRECTION_NORTHWEST
:
329 offset
= (his_cy
< 0) ? -his_cy
: his_cy
;
330 distance
= ((dir
== OB_DIRECTION_WEST
||
331 dir
== OB_DIRECTION_NORTHWEST
) ?
336 /* the target must be in the requested direction */
340 /* Calculate score for this window. The smaller the better. */
341 score
= distance
+ offset
;
343 /* windows more than 45 degrees off the direction are
344 * heavily penalized and will only be chosen if nothing
345 * else within a million pixels */
346 if (offset
> distance
)
349 if (best_score
== -1 || score
< best_score
) {
358 void focus_directional_cycle(ObDirection dir
, gboolean dock_windows
,
359 gboolean desktop_windows
, gboolean interactive
,
360 gboolean dialog
, gboolean done
, gboolean cancel
)
362 static ObClient
*first
= NULL
;
369 focus_cycle_target
= NULL
;
377 if (focus_cycle_target
== NULL
) {
378 focus_cycle_iconic_windows
= FALSE
;
379 focus_cycle_all_desktops
= FALSE
;
380 focus_cycle_dock_windows
= dock_windows
;
381 focus_cycle_desktop_windows
= desktop_windows
;
384 if (!first
) first
= focus_client
;
386 if (focus_cycle_target
)
387 ft
= focus_find_directional(focus_cycle_target
, dir
, dock_windows
,
390 ft
= focus_find_directional(first
, dir
, dock_windows
, desktop_windows
);
394 for (it
= focus_order
; it
; it
= g_list_next(it
))
395 if (focus_cycle_target_valid(it
->data
,
396 focus_cycle_iconic_windows
,
397 focus_cycle_all_desktops
,
398 focus_cycle_dock_windows
,
399 focus_cycle_desktop_windows
))
404 if (ft
!= focus_cycle_target
) {/* prevents flicker */
405 focus_cycle_target
= ft
;
406 focus_cycle_draw_indicator(ft
);
409 if (focus_cycle_target
&& dialog
) {
410 /* same arguments as focus_target_valid */
411 focus_cycle_popup_single_show(focus_cycle_target
,
412 focus_cycle_iconic_windows
,
413 focus_cycle_all_desktops
,
414 focus_cycle_dock_windows
,
415 focus_cycle_desktop_windows
);
420 if (done
&& focus_cycle_target
)
421 client_activate(focus_cycle_target
, FALSE
, TRUE
);
424 focus_cycle_target
= NULL
;
426 focus_cycle_draw_indicator(NULL
);
427 focus_cycle_popup_single_hide();