]> Dogcows Code - chaz/openbox/blob - openbox/focus_cycle.c
add a comment and make it smarter about when to let you focus cycle to windows with...
[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 *first = NULL;
173 static ObClient *t = NULL;
174 static GList *order = NULL;
175 GList *it, *start, *list;
176 ObClient *ft = NULL;
177
178 if (interactive) {
179 if (cancel) {
180 focus_cycle_target = NULL;
181 goto done_cycle;
182 } else if (done)
183 goto done_cycle;
184
185 if (!focus_order)
186 goto done_cycle;
187
188 if (!first) first = focus_client;
189
190 if (linear) list = client_list;
191 else list = focus_order;
192 } else {
193 if (!focus_order)
194 goto done_cycle;
195 list = client_list;
196 }
197
198
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;
205 }
206
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;
211
212 do {
213 if (forward) {
214 it = it->next;
215 if (it == NULL) it = g_list_first(list);
216 } else {
217 it = it->prev;
218 if (it == NULL) it = g_list_last(list);
219 }
220 ft = it->data;
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))
226 {
227 if (interactive) {
228 if (ft != focus_cycle_target) { /* prevents flicker */
229 focus_cycle_target = ft;
230 focus_cycle_draw_indicator(ft);
231 }
232 if (dialog)
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);
239 return;
240 } else if (ft != focus_cycle_target) {
241 focus_cycle_target = ft;
242 done = TRUE;
243 break;
244 }
245 }
246 } while (it != start);
247
248 done_cycle:
249 if (done && focus_cycle_target)
250 client_activate(focus_cycle_target, FALSE, TRUE);
251
252 t = NULL;
253 first = NULL;
254 focus_cycle_target = NULL;
255 g_list_free(order);
256 order = NULL;
257
258 if (interactive) {
259 focus_cycle_draw_indicator(NULL);
260 focus_cycle_popup_hide();
261 }
262
263 return;
264 }
265
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)
270 {
271 gint my_cx, my_cy, his_cx, his_cy;
272 gint offset = 0;
273 gint distance = 0;
274 gint score, best_score;
275 ObClient *best_client, *cur;
276 GList *it;
277
278 if(!client_list)
279 return NULL;
280
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;
284
285 best_score = -1;
286 best_client = NULL;
287
288 for(it = g_list_first(client_list); it; it = g_list_next(it)) {
289 cur = it->data;
290
291 /* the currently selected window isn't interesting */
292 if(cur == c)
293 continue;
294 if (!focus_cycle_target_valid(it->data, FALSE, FALSE, dock_windows,
295 desktop_windows))
296 continue;
297
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;
304
305 if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
306 dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
307 gint tx;
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;
315 his_cx = tx;
316 }
317
318 switch(dir) {
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) ?
326 -his_cy : his_cy);
327 break;
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) ?
335 -his_cx : his_cx);
336 break;
337 }
338
339 /* the target must be in the requested direction */
340 if(distance <= 0)
341 continue;
342
343 /* Calculate score for this window. The smaller the better. */
344 score = distance + offset;
345
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)
350 score += 1000000;
351
352 if(best_score == -1 || score < best_score)
353 best_client = cur,
354 best_score = score;
355 }
356
357 return best_client;
358 }
359
360 void focus_directional_cycle(ObDirection dir, gboolean dock_windows,
361 gboolean desktop_windows, gboolean interactive,
362 gboolean dialog, gboolean done, gboolean cancel)
363 {
364 static ObClient *first = NULL;
365 ObClient *ft = NULL;
366
367 if (!interactive)
368 return;
369
370 if (cancel) {
371 focus_cycle_target = NULL;
372 goto done_cycle;
373 } else if (done)
374 goto done_cycle;
375
376 if (!focus_order)
377 goto done_cycle;
378
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;
385 }
386
387 if (!first) first = focus_client;
388
389 if (focus_cycle_target)
390 ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
391 desktop_windows);
392 else {
393 GList *it;
394
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))
401 ft = it->data;
402 }
403
404 if (ft) {
405 if (ft != focus_cycle_target) {/* prevents flicker */
406 focus_cycle_target = ft;
407 focus_cycle_draw_indicator(ft);
408 }
409 }
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);
417 return;
418 }
419
420 done_cycle:
421 if (done && focus_cycle_target)
422 client_activate(focus_cycle_target, FALSE, TRUE);
423
424 first = NULL;
425 focus_cycle_target = NULL;
426
427 focus_cycle_draw_indicator(NULL);
428 focus_cycle_popup_single_hide();
429
430 return;
431 }
This page took 0.057413 seconds and 5 git commands to generate.