]> Dogcows Code - chaz/openbox/blob - openbox/place.c
Big changes to placement across multiple monitors.
[chaz/openbox] / openbox / place.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 place.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 "client.h"
21 #include "group.h"
22 #include "screen.h"
23 #include "frame.h"
24 #include "focus.h"
25 #include "config.h"
26 #include "dock.h"
27 #include "debug.h"
28
29 extern ObDock *dock;
30
31 static Rect *pick_pointer_head(ObClient *c)
32 {
33 return screen_area(c->desktop, screen_monitor_pointer(), NULL);
34 }
35
36 /* use the following priority lists for pick_head()
37
38 When a window is being placed in the FOREGROUND, use a monitor chosen in
39 the following order:
40 1. same monitor as parent
41 2. primary monitor if placement=PRIMARY
42 active monitor if placement=ACTIVE
43 pointer monitor if placement=MOUSE
44 3. primary monitor
45 4. other monitors where the window has group members on the same desktop
46 5. other monitors where the window has group members on other desktops
47 6. other monitors
48
49 When a window is being placed in the BACKGROUND, use a monitor chosen in the
50 following order:
51 1. same monitor as parent
52 2. other monitors where the window has group members on the same desktop
53 2a. primary monitor in this set
54 2b. other monitors in this set
55 3. other monitors where the window has group members on other desktops
56 3a. primary monitor in this set
57 3b. other monitors in this set
58 4. other monitors
59 4a. primary monitor in this set
60 4b. other monitors in this set
61 */
62
63 /*! One for each possible head, used to sort them in order of precedence. */
64 typedef struct {
65 guint monitor;
66 guint flags;
67 } ObPlaceHead;
68
69 /*! Flags for ObPlaceHead */
70 enum {
71 HEAD_PARENT = 1 << 0, /* parent's monitor */
72 HEAD_PLACED = 1 << 1, /* chosen monitor by placement */
73 HEAD_PRIMARY = 1 << 2, /* primary monitor */
74 HEAD_GROUP_DESK = 1 << 3, /* has a group member on the same desktop */
75 HEAD_GROUP = 1 << 4, /* has a group member on another desktop */
76 };
77
78 gint cmp_foreground(const void *a, const void *b)
79 {
80 const ObPlaceHead *h1 = a;
81 const ObPlaceHead *h2 = b;
82 gint i = 0;
83
84 if (h1->monitor == h2->monitor) return 0;
85
86 if (h1->flags & HEAD_PARENT) --i;
87 if (h2->flags & HEAD_PARENT) ++i;
88 if (i) return i;
89
90 if (h1->flags & HEAD_PLACED) --i;
91 if (h2->flags & HEAD_PLACED) ++i;
92 if (i) return i;
93
94 if (h1->flags & HEAD_PRIMARY) --i;
95 if (h2->flags & HEAD_PRIMARY) ++i;
96 if (i) return i;
97
98 if (h1->flags & HEAD_GROUP_DESK) --i;
99 if (h2->flags & HEAD_GROUP_DESK) ++i;
100 if (i) return i;
101
102 if (h1->flags & HEAD_GROUP) --i;
103 if (h2->flags & HEAD_GROUP) ++i;
104 if (i) return i;
105
106 return h1->monitor - h2->monitor;
107 }
108
109 gint cmp_background(const void *a, const void *b)
110 {
111 const ObPlaceHead *h1 = a;
112 const ObPlaceHead *h2 = b;
113 gint i = 0;
114
115 if (h1->monitor == h2->monitor) return 0;
116
117 if (h1->flags & HEAD_PARENT) --i;
118 if (h2->flags & HEAD_PARENT) ++i;
119 if (i) return i;
120
121 if (h1->flags & HEAD_GROUP_DESK || h2->flags & HEAD_GROUP_DESK) {
122 if (h1->flags & HEAD_GROUP_DESK) --i;
123 if (h2->flags & HEAD_GROUP_DESK) ++i;
124 if (i) return i;
125 if (h1->flags & HEAD_PRIMARY) --i;
126 if (h2->flags & HEAD_PRIMARY) ++i;
127 if (i) return i;
128 }
129
130 if (h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
131 if (h1->flags & HEAD_GROUP) --i;
132 if (h2->flags & HEAD_GROUP) ++i;
133 if (i) return i;
134 if (h1->flags & HEAD_PRIMARY) --i;
135 if (h2->flags & HEAD_PRIMARY) ++i;
136 if (i) return i;
137 }
138
139 if (h1->flags & HEAD_PRIMARY) --i;
140 if (h2->flags & HEAD_PRIMARY) ++i;
141 if (i) return i;
142
143 return h1->monitor - h2->monitor;
144 }
145
146 /*! Pick a monitor to place a window on. */
147 static Rect *pick_head(ObClient *c, gboolean foreground)
148 {
149 Rect *area;
150 ObPlaceHead *choice;
151 guint i;
152 ObClient *p;
153 GSList *it;
154
155 choice = g_new(ObPlaceHead, screen_num_monitors);
156 for (i = 0; i < screen_num_monitors; ++i) {
157 choice[i].monitor = i;
158 choice[i].flags = 0;
159 }
160
161 /* find monitors with group members */
162 for (it = c->group->members; it; it = g_slist_next(it)) {
163 ObClient *itc = it->data;
164 if (itc != c) {
165 guint m = client_monitor(itc);
166
167 if (m < screen_num_monitors) {
168 if (screen_compare_desktops(itc->desktop, c->desktop))
169 choice[m].flags |= HEAD_GROUP_DESK;
170 else
171 choice[m].flags |= HEAD_GROUP;
172 }
173 }
174 }
175
176 i = screen_monitor_primary(FALSE);
177 if (i < screen_num_monitors) {
178 choice[i].flags |= HEAD_PRIMARY;
179 if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
180 choice[i].flags |= HEAD_PLACED;
181 }
182
183 /* direct parent takes highest precedence */
184 if ((p = client_direct_parent(c))) {
185 i = client_monitor(p);
186 if (i < screen_num_monitors)
187 choice[i].flags |= HEAD_PARENT;
188 }
189
190 qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
191 foreground ? cmp_foreground : cmp_background);
192
193 /* save the areas of the monitors in order of their being chosen */
194 for (i = 0; i < screen_num_monitors; ++i)
195 {
196 ob_debug("placement choice %d is monitor %d", i, choice[i].monitor);
197 if (choice[i].flags & HEAD_PARENT)
198 ob_debug(" - parent on monitor");
199 if (choice[i].flags & HEAD_PLACED)
200 ob_debug(" - placement choice");
201 if (choice[i].flags & HEAD_PRIMARY)
202 ob_debug(" - primary monitor");
203 if (choice[i].flags & HEAD_GROUP_DESK)
204 ob_debug(" - group on same desktop");
205 if (choice[i].flags & HEAD_GROUP)
206 ob_debug(" - group on other desktop");
207 }
208
209 area = screen_area(c->desktop, choice[0].monitor, NULL);
210
211 g_free(choice);
212
213 /* return the area for the chosen monitor */
214 return area;
215 }
216
217 static gboolean place_random(ObClient *client, Rect *area, gint *x, gint *y)
218 {
219 gint l, r, t, b;
220
221 ob_debug("placing randomly");
222
223 l = area->x;
224 t = area->y;
225 r = area->x + area->width - client->frame->area.width;
226 b = area->y + area->height - client->frame->area.height;
227
228 if (r > l) *x = g_random_int_range(l, r + 1);
229 else *x = area->x;
230 if (b > t) *y = g_random_int_range(t, b + 1);
231 else *y = area->y;
232
233 return TRUE;
234 }
235
236 static GSList* area_add(GSList *list, Rect *a)
237 {
238 Rect *r = g_slice_new(Rect);
239 *r = *a;
240 return g_slist_prepend(list, r);
241 }
242
243 static GSList* area_remove(GSList *list, Rect *a)
244 {
245 GSList *sit;
246 GSList *result = NULL;
247
248 for (sit = list; sit; sit = g_slist_next(sit)) {
249 Rect *r = sit->data;
250
251 if (!RECT_INTERSECTS_RECT(*r, *a)) {
252 result = g_slist_prepend(result, r);
253 /* dont free r, it's moved to the result list */
254 } else {
255 Rect isect, extra;
256
257 /* Use an intersection of a and r to determine the space
258 around r that we can use.
259
260 NOTE: the spaces calculated can overlap.
261 */
262
263 RECT_SET_INTERSECTION(isect, *r, *a);
264
265 if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
266 RECT_SET(extra, r->x, r->y,
267 RECT_LEFT(isect) - r->x, r->height);
268 result = area_add(result, &extra);
269 }
270
271 if (RECT_TOP(isect) > RECT_TOP(*r)) {
272 RECT_SET(extra, r->x, r->y,
273 r->width, RECT_TOP(isect) - r->y + 1);
274 result = area_add(result, &extra);
275 }
276
277 if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
278 RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
279 RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
280 result = area_add(result, &extra);
281 }
282
283 if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
284 RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
285 r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
286 result = area_add(result, &extra);
287 }
288
289 /* 'r' is not being added to the result list, so free it */
290 g_slice_free(Rect, r);
291 }
292 }
293 g_slist_free(list);
294 return result;
295 }
296
297 enum {
298 IGNORE_FULLSCREEN = 1,
299 IGNORE_MAXIMIZED = 2,
300 IGNORE_MENUTOOL = 3,
301 /*IGNORE_SHADED = 3,*/
302 IGNORE_NONGROUP = 4,
303 IGNORE_BELOW = 5,
304 /*IGNORE_NONFOCUS = 1 << 5,*/
305 IGNORE_DOCK = 6,
306 IGNORE_END = 7
307 };
308
309 static gboolean place_nooverlap(ObClient *c, Rect *area, gint *x, gint *y)
310 {
311 gint ignore;
312 gboolean ret;
313 gint maxsize;
314 GSList *spaces = NULL, *sit, *maxit;
315
316 ob_debug("placing nonoverlap");
317
318 ret = FALSE;
319 maxsize = 0;
320 maxit = NULL;
321
322 /* try ignoring different things to find empty space */
323 for (ignore = 0; ignore < IGNORE_END && !ret; ignore++) {
324 GList *it;
325
326 /* add the whole monitor */
327 spaces = area_add(spaces, area);
328
329 /* go thru all the windows */
330 for (it = client_list; it; it = g_list_next(it)) {
331 ObClient *test = it->data;
332
333 /* should we ignore this client? */
334 if (screen_showing_desktop) continue;
335 if (c == test) continue;
336 if (test->iconic) continue;
337 if (c->desktop != DESKTOP_ALL) {
338 if (test->desktop != c->desktop &&
339 test->desktop != DESKTOP_ALL) continue;
340 } else {
341 if (test->desktop != screen_desktop &&
342 test->desktop != DESKTOP_ALL) continue;
343 }
344 if (test->type == OB_CLIENT_TYPE_SPLASH ||
345 test->type == OB_CLIENT_TYPE_DESKTOP) continue;
346
347
348 if ((ignore >= IGNORE_FULLSCREEN) &&
349 test->fullscreen) continue;
350 if ((ignore >= IGNORE_MAXIMIZED) &&
351 test->max_horz && test->max_vert) continue;
352 if ((ignore >= IGNORE_MENUTOOL) &&
353 (test->type == OB_CLIENT_TYPE_MENU ||
354 test->type == OB_CLIENT_TYPE_TOOLBAR) &&
355 client_has_parent(c)) continue;
356 /*
357 if ((ignore >= IGNORE_SHADED) &&
358 test->shaded) continue;
359 */
360 if ((ignore >= IGNORE_NONGROUP) &&
361 client_has_group_siblings(c) &&
362 test->group != c->group) continue;
363 if ((ignore >= IGNORE_BELOW) &&
364 test->layer < c->layer) continue;
365 /*
366 if ((ignore >= IGNORE_NONFOCUS) &&
367 focus_client != test) continue;
368 */
369 /* don't ignore this window, so remove it from the available
370 area */
371 spaces = area_remove(spaces, &test->frame->area);
372 }
373
374 if (ignore < IGNORE_DOCK) {
375 Rect a;
376 dock_get_area(&a);
377 spaces = area_remove(spaces, &a);
378 }
379
380 for (sit = spaces; sit; sit = g_slist_next(sit)) {
381 Rect *r = sit->data;
382
383 if (r->width >= c->frame->area.width &&
384 r->height >= c->frame->area.height &&
385 r->width * r->height > maxsize)
386 {
387 maxsize = r->width * r->height;
388 maxit = sit;
389 }
390 }
391
392 if (maxit) {
393 Rect *r = maxit->data;
394
395 /* center it in the area */
396 *x = r->x;
397 *y = r->y;
398 if (config_place_center) {
399 *x += (r->width - c->frame->area.width) / 2;
400 *y += (r->height - c->frame->area.height) / 2;
401 }
402 ret = TRUE;
403 }
404
405 while (spaces) {
406 g_slice_free(Rect, spaces->data);
407 spaces = g_slist_delete_link(spaces, spaces);
408 }
409 }
410
411 return ret;
412 }
413
414 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
415 {
416 gint l, r, t, b;
417 gint px, py;
418 Rect *area;
419
420 ob_debug("placing under mouse");
421
422 if (!screen_pointer_pos(&px, &py))
423 return FALSE;
424 area = pick_pointer_head(client);
425
426 l = area->x;
427 t = area->y;
428 r = area->x + area->width - client->frame->area.width;
429 b = area->y + area->height - client->frame->area.height;
430
431 *x = px - client->area.width / 2 - client->frame->size.left;
432 *x = MIN(MAX(*x, l), r);
433 *y = py - client->area.height / 2 - client->frame->size.top;
434 *y = MIN(MAX(*y, t), b);
435
436 g_slice_free(Rect, area);
437
438 return TRUE;
439 }
440
441 static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
442 ObAppSettings *settings)
443 {
444 Rect *screen = NULL;
445
446 if (!settings || (settings && !settings->pos_given))
447 return FALSE;
448
449 ob_debug("placing by per-app settings");
450
451 /* Find which head the pointer is on */
452 if (settings->monitor == 0)
453 /* this can return NULL */
454 screen = pick_pointer_head(client);
455 else {
456 guint m = settings->monitor;
457 if (m < 1 || m > screen_num_monitors)
458 m = screen_monitor_primary(TRUE) + 1;
459 screen = screen_area(client->desktop, m - 1, NULL);
460 }
461
462 if (settings->position.x.center)
463 *x = screen->x + screen->width / 2 - client->area.width / 2;
464 else if (settings->position.x.opposite)
465 *x = screen->x + screen->width - client->frame->area.width -
466 settings->position.x.pos;
467 else
468 *x = screen->x + settings->position.x.pos;
469 if (settings->position.x.denom)
470 *x = (*x * screen->width) / settings->position.x.denom;
471
472 if (settings->position.y.center)
473 *y = screen->y + screen->height / 2 - client->area.height / 2;
474 else if (settings->position.y.opposite)
475 *y = screen->y + screen->height - client->frame->area.height -
476 settings->position.y.pos;
477 else
478 *y = screen->y + settings->position.y.pos;
479 if (settings->position.y.denom)
480 *y = (*y * screen->height) / settings->position.y.denom;
481
482 g_slice_free(Rect, screen);
483 return TRUE;
484 }
485
486 static gboolean place_transient_splash(ObClient *client, Rect *area,
487 gint *x, gint *y)
488 {
489 if (client->type == OB_CLIENT_TYPE_DIALOG) {
490 GSList *it;
491 gboolean first = TRUE;
492 gint l, r, t, b;
493
494 ob_debug("placing dialog");
495
496 for (it = client->parents; it; it = g_slist_next(it)) {
497 ObClient *m = it->data;
498 if (!m->iconic) {
499 if (first) {
500 l = RECT_LEFT(m->frame->area);
501 t = RECT_TOP(m->frame->area);
502 r = RECT_RIGHT(m->frame->area);
503 b = RECT_BOTTOM(m->frame->area);
504 first = FALSE;
505 } else {
506 l = MIN(l, RECT_LEFT(m->frame->area));
507 t = MIN(t, RECT_TOP(m->frame->area));
508 r = MAX(r, RECT_RIGHT(m->frame->area));
509 b = MAX(b, RECT_BOTTOM(m->frame->area));
510 }
511 }
512 if (!first) {
513 *x = ((r + 1 - l) - client->frame->area.width) / 2 + l;
514 *y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
515 return TRUE;
516 }
517 }
518 }
519
520 if (client->type == OB_CLIENT_TYPE_DIALOG ||
521 client->type == OB_CLIENT_TYPE_SPLASH)
522 {
523 ob_debug("placing dialog or splash");
524
525 *x = (area->width - client->frame->area.width) / 2 + area->x;
526 *y = (area->height - client->frame->area.height) / 2 + area->y;
527 return TRUE;
528 }
529
530 return FALSE;
531 }
532
533 /*! Return TRUE if openbox chose the position for the window, and FALSE if
534 the application chose it */
535 gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
536 ObAppSettings *settings)
537 {
538 Rect *area;
539 gboolean ret;
540
541 /* per-app settings override program specified position
542 * but not user specified, unless pos_force is enabled */
543 if (((client->positioned & USPosition) &&
544 !(settings && settings->pos_given && settings->pos_force)) ||
545 ((client->positioned & PPosition) &&
546 !(settings && settings->pos_given)))
547 return FALSE;
548
549 area = pick_head(client, foreground);
550
551 /* try a number of methods */
552 ret = place_per_app_setting(client, x, y, settings) ||
553 place_transient_splash(client, area, x, y) ||
554 (config_place_policy == OB_PLACE_POLICY_MOUSE &&
555 place_under_mouse(client, x, y)) ||
556 place_nooverlap(client, area, x, y) ||
557 place_random(client, area, x, y);
558 g_assert(ret);
559
560 g_slice_free(Rect, area);
561
562 /* get where the client should be */
563 frame_frame_gravity(client->frame, x, y);
564 return TRUE;
565 }
This page took 0.063251 seconds and 5 git commands to generate.