]> Dogcows Code - chaz/openbox/blob - openbox/focus_cycle.c
make next/previouswindow and dirfocus actions show the focus_cycle_indicator even...
[chaz/openbox] / openbox / focus_cycle.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 focus_cycle.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 "focus_cycle.h"
21 #include "focus_cycle_indicator.h"
22 #include "focus_cycle_popup.h"
23 #include "client.h"
24 #include "frame.h"
25 #include "focus.h"
26 #include "screen.h"
27 #include "openbox.h"
28 #include "debug.h"
29 #include "group.h"
30
31 #include <X11/Xlib.h>
32 #include <glib.h>
33
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;
39
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,
44 ObDirection dir,
45 gboolean dock_windows,
46 gboolean desktop_windows);
47 static ObClient *focus_find_directional (ObClient *c,
48 ObDirection dir,
49 gboolean dock_windows,
50 gboolean desktop_windows);
51
52 void focus_cycle_startup(gboolean reconfig)
53 {
54 if (reconfig) return;
55 }
56
57 void focus_cycle_shutdown(gboolean reconfig)
58 {
59 if (reconfig) return;
60 }
61
62 void focus_cycle_stop(ObClient *ifclient)
63 {
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))
72 {
73 focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
74 focus_directional_cycle(0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
75 }
76 }
77
78 /*! Returns if a focus target has valid group siblings that can be cycled
79 to in its place */
80 static gboolean focus_target_has_siblings(ObClient *ft,
81 gboolean iconic_windows,
82 gboolean all_desktops)
83
84 {
85 GSList *it;
86
87 if (!ft->group) return FALSE;
88
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,
94 FALSE))
95 {
96 return TRUE;
97 }
98 }
99 return FALSE;
100 }
101
102 gboolean focus_cycle_target_valid(ObClient *ft,
103 gboolean iconic_windows,
104 gboolean all_desktops,
105 gboolean dock_windows,
106 gboolean desktop_windows)
107 {
108 gboolean ok = FALSE;
109
110 /* it's on this desktop unless you want all desktops.
111
112 do this check first because it will usually filter out the most
113 windows */
114 ok = (all_desktops || ft->desktop == screen_desktop ||
115 ft->desktop == DESKTOP_ALL);
116
117 /* the window can receive focus somehow */
118 ok = ok && (ft->can_focus || ft->focus_notify);
119
120 /* the window is not iconic, or we're allowed to go to iconic ones */
121 ok = ok && (iconic_windows || !ft->iconic);
122
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 */
129 else if (!ft->modal)
130 /* normal non-helper windows are valid targets */
131 ok = ok &&
132 ((client_normal(ft) && !client_helper(ft))
133 ||
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))));
141
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) ||
149 ft->modal ||
150 !ft->skip_taskbar);
151
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 */
155 {
156 ObClient *cft = client_focus_target(ft);
157 ok = ok && (ft == cft || !focus_cycle_target_valid(cft,
158 iconic_windows,
159 all_desktops,
160 dock_windows,
161 desktop_windows));
162 }
163
164 return ok;
165 }
166
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)
171 {
172 static ObClient *t = NULL;
173 static GList *order = NULL;
174 GList *it, *start, *list;
175 ObClient *ft = NULL;
176
177 if (interactive) {
178 if (cancel) {
179 focus_cycle_target = NULL;
180 goto done_cycle;
181 } else if (done)
182 goto done_cycle;
183
184 if (!focus_order)
185 goto done_cycle;
186
187 if (linear) list = client_list;
188 else list = focus_order;
189 } else {
190 if (!focus_order)
191 goto done_cycle;
192 list = client_list;
193 }
194
195
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);
202 } else
203 start = it = g_list_find(list, focus_cycle_target);
204
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;
208
209 do {
210 if (forward) {
211 it = it->next;
212 if (it == NULL) it = g_list_first(list);
213 } else {
214 it = it->prev;
215 if (it == NULL) it = g_list_last(list);
216 }
217 ft = it->data;
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))
223 {
224 if (interactive) {
225 if (ft != focus_cycle_target) { /* prevents flicker */
226 focus_cycle_target = ft;
227 focus_cycle_draw_indicator(ft);
228 }
229 if (dialog)
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);
236 return;
237 } else if (ft != focus_cycle_target) {
238 focus_cycle_target = ft;
239 done = TRUE;
240 break;
241 }
242 }
243 } while (it != start);
244
245 done_cycle:
246 if (done && focus_cycle_target)
247 client_activate(focus_cycle_target, FALSE, TRUE);
248
249 t = NULL;
250 focus_cycle_target = NULL;
251 g_list_free(order);
252 order = NULL;
253
254 if (interactive) {
255 focus_cycle_draw_indicator(NULL);
256 focus_cycle_popup_hide();
257 }
258
259 return;
260 }
261
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)
266 {
267 gint my_cx, my_cy, his_cx, his_cy;
268 gint offset = 0;
269 gint distance = 0;
270 gint score, best_score;
271 ObClient *best_client, *cur;
272 GList *it;
273
274 if (!client_list)
275 return NULL;
276
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;
280
281 best_score = -1;
282 best_client = c;
283
284 for (it = g_list_first(client_list); it; it = g_list_next(it)) {
285 cur = it->data;
286
287 /* the currently selected window isn't interesting */
288 if (cur == c)
289 continue;
290 if (!focus_cycle_target_valid(it->data, FALSE, FALSE, dock_windows,
291 desktop_windows))
292 continue;
293
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;
300
301 if (dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
302 dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST)
303 {
304 gint tx;
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;
312 his_cx = tx;
313 }
314
315 switch (dir) {
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) ?
323 -his_cy : his_cy);
324 break;
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) ?
332 -his_cx : his_cx);
333 break;
334 }
335
336 /* the target must be in the requested direction */
337 if (distance <= 0)
338 continue;
339
340 /* Calculate score for this window. The smaller the better. */
341 score = distance + offset;
342
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)
347 score += 1000000;
348
349 if (best_score == -1 || score < best_score)
350 best_client = cur,
351 best_score = score;
352 }
353
354 return best_client;
355 }
356
357 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
358 gboolean desktop_windows, gboolean interactive,
359 gboolean dialog, gboolean done, gboolean cancel)
360 {
361 static ObClient *first = NULL;
362 ObClient *ft = NULL;
363
364 if (!interactive)
365 return;
366
367 if (cancel) {
368 focus_cycle_target = NULL;
369 goto done_cycle;
370 } else if (done)
371 goto done_cycle;
372
373 if (!focus_order)
374 goto done_cycle;
375
376 if (focus_cycle_target == NULL) {
377 focus_cycle_iconic_windows = FALSE;
378 focus_cycle_all_desktops = FALSE;
379 focus_cycle_dock_windows = dock_windows;
380 focus_cycle_desktop_windows = desktop_windows;
381 }
382
383 if (!first) first = focus_client;
384
385 if (focus_cycle_target)
386 ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
387 desktop_windows);
388 else if (first)
389 ft = focus_find_directional(first, dir, dock_windows, desktop_windows);
390 else {
391 GList *it;
392
393 for (it = focus_order; it; it = g_list_next(it))
394 if (focus_cycle_target_valid(it->data,
395 focus_cycle_iconic_windows,
396 focus_cycle_all_desktops,
397 focus_cycle_dock_windows,
398 focus_cycle_desktop_windows))
399 ft = it->data;
400 }
401
402 if (ft) {
403 if (ft != focus_cycle_target) {/* prevents flicker */
404 focus_cycle_target = ft;
405 focus_cycle_draw_indicator(ft);
406 }
407 }
408 if (focus_cycle_target && dialog) {
409 /* same arguments as focus_target_valid */
410 focus_cycle_popup_single_show(focus_cycle_target,
411 focus_cycle_iconic_windows,
412 focus_cycle_all_desktops,
413 focus_cycle_dock_windows,
414 focus_cycle_desktop_windows);
415 return;
416 }
417
418 done_cycle:
419 if (done && focus_cycle_target)
420 client_activate(focus_cycle_target, FALSE, TRUE);
421
422 first = NULL;
423 focus_cycle_target = NULL;
424
425 focus_cycle_draw_indicator(NULL);
426 focus_cycle_popup_single_hide();
427
428 return;
429 }
This page took 0.051572 seconds and 5 git commands to generate.