]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/focus_cycle.c
Allow moving fullscreen windows between monitors
[mikachu/openbox.git] / 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 "client.h"
23 #include "frame.h"
24 #include "focus.h"
25 #include "screen.h"
26 #include "openbox.h"
27 #include "debug.h"
28 #include "config.h"
29
30 #include <X11/Xlib.h>
31 #include <glib.h>
32
33 typedef enum {
34     OB_CYCLE_NONE = 0,
35     OB_CYCLE_NORMAL,
36     OB_CYCLE_DIRECTIONAL
37 } ObCycleType;
38
39 ObClient       *focus_cycle_target = NULL;
40 static ObCycleType focus_cycle_type = OB_CYCLE_NONE;
41 static gboolean focus_cycle_linear;
42 static gboolean focus_cycle_iconic_windows;
43 static gboolean focus_cycle_all_desktops;
44 static gboolean focus_cycle_nonhilite_windows;
45 static gboolean focus_cycle_dock_windows;
46 static gboolean focus_cycle_desktop_windows;
47
48 static ObClient *focus_find_directional(ObClient *c,
49                                         ObDirection dir,
50                                         gboolean dock_windows,
51                                         gboolean desktop_windows);
52
53 void focus_cycle_startup(gboolean reconfig)
54 {
55     if (reconfig) return;
56 }
57
58 void focus_cycle_shutdown(gboolean reconfig)
59 {
60     if (reconfig) return;
61 }
62
63 void focus_cycle_addremove(ObClient *c, gboolean redraw)
64 {
65     if (!focus_cycle_type)
66         return;
67
68     if (focus_cycle_type == OB_CYCLE_DIRECTIONAL) {
69         if (c && focus_cycle_target == c) {
70             focus_directional_cycle(0, TRUE, TRUE, TRUE, TRUE,
71                                     TRUE, TRUE, TRUE);
72         }
73     }
74     else if (c && redraw) {
75         gboolean v, s;
76
77         v = focus_cycle_valid(c);
78         s = focus_cycle_popup_is_showing(c) || c == focus_cycle_target;
79
80         if (v != s)
81             focus_cycle_reorder();
82     }
83     else if (redraw) {
84         focus_cycle_reorder();
85     }
86 }
87
88 void focus_cycle_reorder()
89 {
90     if (focus_cycle_type == OB_CYCLE_NORMAL) {
91         focus_cycle_target = focus_cycle_popup_refresh(focus_cycle_target,
92                                                        TRUE,
93                                                        focus_cycle_linear);
94         focus_cycle_update_indicator(focus_cycle_target);
95         if (!focus_cycle_target)
96             focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
97                         TRUE, OB_FOCUS_CYCLE_POPUP_MODE_NONE,
98                         TRUE, TRUE);
99     }
100 }
101
102 ObClient* focus_cycle(gboolean forward, gboolean all_desktops,
103                       gboolean nonhilite_windows,
104                       gboolean dock_windows, gboolean desktop_windows,
105                       gboolean linear, gboolean showbar,
106                       ObFocusCyclePopupMode mode,
107                       gboolean done, gboolean cancel)
108 {
109     static GList *order = NULL;
110     GList *it, *start, *list;
111     ObClient *ft = NULL;
112     ObClient *ret = NULL;
113
114     if (cancel) {
115         focus_cycle_target = NULL;
116         goto done_cycle;
117     } else if (done)
118         goto done_cycle;
119
120     if (!focus_order)
121         goto done_cycle;
122
123     if (linear) list = client_list;
124     else        list = focus_order;
125
126     if (focus_cycle_target == NULL) {
127         focus_cycle_linear = linear;
128         focus_cycle_iconic_windows = TRUE;
129         focus_cycle_all_desktops = all_desktops;
130         focus_cycle_nonhilite_windows = nonhilite_windows;
131         focus_cycle_dock_windows = dock_windows;
132         focus_cycle_desktop_windows = desktop_windows;
133         start = it = g_list_find(list, focus_client);
134     } else
135         start = it = g_list_find(list, focus_cycle_target);
136
137     if (!start) /* switched desktops or something? */
138         start = it = forward ? g_list_last(list) : g_list_first(list);
139     if (!start) goto done_cycle;
140
141     do {
142         if (forward) {
143             it = it->next;
144             if (it == NULL) it = g_list_first(list);
145         } else {
146             it = it->prev;
147             if (it == NULL) it = g_list_last(list);
148         }
149         ft = it->data;
150         if (focus_cycle_valid(ft)) {
151             if (ft != focus_cycle_target) { /* prevents flicker */
152                 focus_cycle_target = ft;
153                 focus_cycle_type = OB_CYCLE_NORMAL;
154                 focus_cycle_draw_indicator(showbar ? ft : NULL);
155             }
156             /* same arguments as focus_target_valid */
157             focus_cycle_popup_show(ft, mode, focus_cycle_linear);
158             return focus_cycle_target;
159         }
160     } while (it != start);
161
162 done_cycle:
163     if (done && !cancel) ret = focus_cycle_target;
164
165     focus_cycle_target = NULL;
166     focus_cycle_type = OB_CYCLE_NONE;
167     g_list_free(order);
168     order = NULL;
169
170     focus_cycle_draw_indicator(NULL);
171     focus_cycle_popup_hide();
172
173     return ret;
174 }
175
176 /* this be mostly ripped from fvwm */
177 static ObClient *focus_find_directional(ObClient *c, ObDirection dir,
178                                         gboolean dock_windows,
179                                         gboolean desktop_windows)
180 {
181     gint my_cx, my_cy, his_cx, his_cy;
182     gint offset = 0;
183     gint distance = 0;
184     gint score, best_score;
185     ObClient *best_client, *cur;
186     GList *it;
187
188     if (!client_list)
189         return NULL;
190
191     /* first, find the centre coords of the currently focused window */
192     my_cx = c->frame->area.x + c->frame->area.width / 2;
193     my_cy = c->frame->area.y + c->frame->area.height / 2;
194
195     best_score = -1;
196     best_client = c;
197
198     for (it = g_list_first(client_list); it; it = g_list_next(it)) {
199         cur = it->data;
200
201         /* the currently selected window isn't interesting */
202         if (cur == c)
203             continue;
204         if (!focus_cycle_valid(it->data))
205             continue;
206
207         /* find the centre coords of this window, from the
208          * currently focused window's point of view */
209         his_cx = (cur->frame->area.x - my_cx)
210             + cur->frame->area.width / 2;
211         his_cy = (cur->frame->area.y - my_cy)
212             + cur->frame->area.height / 2;
213
214         if (dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
215             dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST)
216         {
217             gint tx;
218             /* Rotate the diagonals 45 degrees counterclockwise.
219              * To do this, multiply the matrix /+h +h\ with the
220              * vector (x y).                   \-h +h/
221              * h = sqrt(0.5). We can set h := 1 since absolute
222              * distance doesn't matter here. */
223             tx = his_cx + his_cy;
224             his_cy = -his_cx + his_cy;
225             his_cx = tx;
226         }
227
228         switch (dir) {
229         case OB_DIRECTION_NORTH:
230         case OB_DIRECTION_SOUTH:
231         case OB_DIRECTION_NORTHEAST:
232         case OB_DIRECTION_SOUTHWEST:
233             offset = (his_cx < 0) ? -his_cx : his_cx;
234             distance = ((dir == OB_DIRECTION_NORTH ||
235                          dir == OB_DIRECTION_NORTHEAST) ?
236                         -his_cy : his_cy);
237             break;
238         case OB_DIRECTION_EAST:
239         case OB_DIRECTION_WEST:
240         case OB_DIRECTION_SOUTHEAST:
241         case OB_DIRECTION_NORTHWEST:
242             offset = (his_cy < 0) ? -his_cy : his_cy;
243             distance = ((dir == OB_DIRECTION_WEST ||
244                          dir == OB_DIRECTION_NORTHWEST) ?
245                         -his_cx : his_cx);
246             break;
247         }
248
249         /* the target must be in the requested direction */
250         if (distance <= 0)
251             continue;
252
253         /* Calculate score for this window.  The smaller the better. */
254         score = (distance * config_directional_distance_weight
255                  + offset * config_directional_angle_weight);
256
257         /* windows more than 45 degrees off the direction are
258          * heavily penalized and will only be chosen if nothing
259          * else within a million pixels */
260         if (offset > distance)
261             score += 1000000;
262
263         if (best_score == -1 || score < best_score) {
264             best_client = cur;
265             best_score = score;
266         }
267     }
268
269     return best_client;
270 }
271
272 ObClient* focus_directional_cycle(ObDirection dir, gboolean dock_windows,
273                                   gboolean desktop_windows,
274                                   gboolean interactive,
275                                   gboolean showbar, gboolean dialog,
276                                   gboolean done, gboolean cancel)
277 {
278     static ObClient *first = NULL;
279     ObClient *ft = NULL;
280     ObClient *ret = NULL;
281
282     if (cancel) {
283         focus_cycle_target = NULL;
284         goto done_cycle;
285     } else if (done && interactive)
286         goto done_cycle;
287
288     if (!focus_order)
289         goto done_cycle;
290
291     if (focus_cycle_target == NULL) {
292         focus_cycle_linear = FALSE;
293         focus_cycle_iconic_windows = FALSE;
294         focus_cycle_all_desktops = FALSE;
295         focus_cycle_nonhilite_windows = TRUE;
296         focus_cycle_dock_windows = dock_windows;
297         focus_cycle_desktop_windows = desktop_windows;
298     }
299
300     if (!first) first = focus_client;
301
302     if (focus_cycle_target)
303         ft = focus_find_directional(focus_cycle_target, dir, dock_windows,
304                                     desktop_windows);
305     else if (first)
306         ft = focus_find_directional(first, dir, dock_windows, desktop_windows);
307     else {
308         GList *it;
309
310         for (it = focus_order; it; it = g_list_next(it))
311             if (focus_cycle_valid(it->data)) {
312                 ft = it->data;
313                 break;
314             }
315     }
316
317     if (ft && ft != focus_cycle_target) {/* prevents flicker */
318         focus_cycle_target = ft;
319         focus_cycle_type = OB_CYCLE_DIRECTIONAL;
320         if (!interactive)
321             goto done_cycle;
322         focus_cycle_draw_indicator(showbar ? ft : NULL);
323     }
324     if (focus_cycle_target && dialog)
325         /* same arguments as focus_target_valid */
326         focus_cycle_popup_single_show(focus_cycle_target);
327     return focus_cycle_target;
328
329 done_cycle:
330     if (done && !cancel) ret = focus_cycle_target;
331
332     first = NULL;
333     focus_cycle_target = NULL;
334     focus_cycle_type = OB_CYCLE_NONE;
335
336     focus_cycle_draw_indicator(NULL);
337     focus_cycle_popup_single_hide();
338
339     return ret;
340 }
341
342 gboolean focus_cycle_valid(struct _ObClient *client)
343 {
344     return focus_valid_target(client, screen_desktop, TRUE,
345                               focus_cycle_iconic_windows,
346                               focus_cycle_all_desktops,
347                               focus_cycle_nonhilite_windows,
348                               focus_cycle_dock_windows,
349                               focus_cycle_desktop_windows,
350                               FALSE);
351 }