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