]> Dogcows Code - chaz/openbox/blob - openbox/place.c
Add "active" and "primary" options to the <monitor> placement option for per-app...
[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 if (c->group) {
163 for (it = c->group->members; it; it = g_slist_next(it)) {
164 ObClient *itc = it->data;
165 if (itc != c) {
166 guint m = client_monitor(itc);
167
168 if (m < screen_num_monitors) {
169 if (screen_compare_desktops(itc->desktop, c->desktop))
170 choice[m].flags |= HEAD_GROUP_DESK;
171 else
172 choice[m].flags |= HEAD_GROUP;
173 }
174 }
175 }
176 }
177
178 i = screen_monitor_primary(FALSE);
179 if (i < screen_num_monitors) {
180 choice[i].flags |= HEAD_PRIMARY;
181 if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
182 choice[i].flags |= HEAD_PLACED;
183 }
184
185 /* direct parent takes highest precedence */
186 if ((p = client_direct_parent(c))) {
187 i = client_monitor(p);
188 if (i < screen_num_monitors)
189 choice[i].flags |= HEAD_PARENT;
190 }
191
192 qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
193 foreground ? cmp_foreground : cmp_background);
194
195 /* save the areas of the monitors in order of their being chosen */
196 for (i = 0; i < screen_num_monitors; ++i)
197 {
198 ob_debug("placement choice %d is monitor %d", i, choice[i].monitor);
199 if (choice[i].flags & HEAD_PARENT)
200 ob_debug(" - parent on monitor");
201 if (choice[i].flags & HEAD_PLACED)
202 ob_debug(" - placement choice");
203 if (choice[i].flags & HEAD_PRIMARY)
204 ob_debug(" - primary monitor");
205 if (choice[i].flags & HEAD_GROUP_DESK)
206 ob_debug(" - group on same desktop");
207 if (choice[i].flags & HEAD_GROUP)
208 ob_debug(" - group on other desktop");
209 }
210
211 area = screen_area(c->desktop, choice[0].monitor, NULL);
212
213 g_free(choice);
214
215 /* return the area for the chosen monitor */
216 return area;
217 }
218
219 static gboolean place_random(ObClient *client, Rect *area, gint *x, gint *y)
220 {
221 gint l, r, t, b;
222
223 ob_debug("placing randomly");
224
225 l = area->x;
226 t = area->y;
227 r = area->x + area->width - client->frame->area.width;
228 b = area->y + area->height - client->frame->area.height;
229
230 if (r > l) *x = g_random_int_range(l, r + 1);
231 else *x = area->x;
232 if (b > t) *y = g_random_int_range(t, b + 1);
233 else *y = area->y;
234
235 return TRUE;
236 }
237
238 static GSList* area_add(GSList *list, Rect *a)
239 {
240 Rect *r = g_slice_new(Rect);
241 *r = *a;
242 return g_slist_prepend(list, r);
243 }
244
245 static GSList* area_remove(GSList *list, Rect *a)
246 {
247 GSList *sit;
248 GSList *result = NULL;
249
250 for (sit = list; sit; sit = g_slist_next(sit)) {
251 Rect *r = sit->data;
252
253 if (!RECT_INTERSECTS_RECT(*r, *a)) {
254 result = g_slist_prepend(result, r);
255 /* dont free r, it's moved to the result list */
256 } else {
257 Rect isect, extra;
258
259 /* Use an intersection of a and r to determine the space
260 around r that we can use.
261
262 NOTE: the spaces calculated can overlap.
263 */
264
265 RECT_SET_INTERSECTION(isect, *r, *a);
266
267 if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
268 RECT_SET(extra, r->x, r->y,
269 RECT_LEFT(isect) - r->x, r->height);
270 result = area_add(result, &extra);
271 }
272
273 if (RECT_TOP(isect) > RECT_TOP(*r)) {
274 RECT_SET(extra, r->x, r->y,
275 r->width, RECT_TOP(isect) - r->y + 1);
276 result = area_add(result, &extra);
277 }
278
279 if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
280 RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
281 RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
282 result = area_add(result, &extra);
283 }
284
285 if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
286 RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
287 r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
288 result = area_add(result, &extra);
289 }
290
291 /* 'r' is not being added to the result list, so free it */
292 g_slice_free(Rect, r);
293 }
294 }
295 g_slist_free(list);
296 return result;
297 }
298
299 enum {
300 IGNORE_FULLSCREEN = 1,
301 IGNORE_MAXIMIZED = 2,
302 IGNORE_MENUTOOL = 3,
303 /*IGNORE_SHADED = 3,*/
304 IGNORE_NONGROUP = 4,
305 IGNORE_BELOW = 5,
306 /*IGNORE_NONFOCUS = 1 << 5,*/
307 IGNORE_DOCK = 6,
308 IGNORE_END = 7
309 };
310
311 static gboolean place_nooverlap(ObClient *c, Rect *area, gint *x, gint *y)
312 {
313 gint ignore;
314 gboolean ret;
315 gint maxsize;
316 GSList *spaces = NULL, *sit, *maxit;
317
318 ob_debug("placing nonoverlap");
319
320 ret = FALSE;
321 maxsize = 0;
322 maxit = NULL;
323
324 /* try ignoring different things to find empty space */
325 for (ignore = 0; ignore < IGNORE_END && !ret; ignore++) {
326 GList *it;
327
328 /* add the whole monitor */
329 spaces = area_add(spaces, area);
330
331 /* go thru all the windows */
332 for (it = client_list; it; it = g_list_next(it)) {
333 ObClient *test = it->data;
334
335 /* should we ignore this client? */
336 if (screen_showing_desktop) continue;
337 if (c == test) continue;
338 if (test->iconic) continue;
339 if (c->desktop != DESKTOP_ALL) {
340 if (test->desktop != c->desktop &&
341 test->desktop != DESKTOP_ALL) continue;
342 } else {
343 if (test->desktop != screen_desktop &&
344 test->desktop != DESKTOP_ALL) continue;
345 }
346 if (test->type == OB_CLIENT_TYPE_SPLASH ||
347 test->type == OB_CLIENT_TYPE_DESKTOP) continue;
348
349
350 if ((ignore >= IGNORE_FULLSCREEN) &&
351 test->fullscreen) continue;
352 if ((ignore >= IGNORE_MAXIMIZED) &&
353 test->max_horz && test->max_vert) continue;
354 if ((ignore >= IGNORE_MENUTOOL) &&
355 (test->type == OB_CLIENT_TYPE_MENU ||
356 test->type == OB_CLIENT_TYPE_TOOLBAR) &&
357 client_has_parent(c)) continue;
358 /*
359 if ((ignore >= IGNORE_SHADED) &&
360 test->shaded) continue;
361 */
362 if ((ignore >= IGNORE_NONGROUP) &&
363 client_has_group_siblings(c) &&
364 test->group != c->group) continue;
365 if ((ignore >= IGNORE_BELOW) &&
366 test->layer < c->layer) continue;
367 /*
368 if ((ignore >= IGNORE_NONFOCUS) &&
369 focus_client != test) continue;
370 */
371 /* don't ignore this window, so remove it from the available
372 area */
373 spaces = area_remove(spaces, &test->frame->area);
374 }
375
376 if (ignore < IGNORE_DOCK) {
377 Rect a;
378 dock_get_area(&a);
379 spaces = area_remove(spaces, &a);
380 }
381
382 for (sit = spaces; sit; sit = g_slist_next(sit)) {
383 Rect *r = sit->data;
384
385 if (r->width >= c->frame->area.width &&
386 r->height >= c->frame->area.height &&
387 r->width * r->height > maxsize)
388 {
389 maxsize = r->width * r->height;
390 maxit = sit;
391 }
392 }
393
394 if (maxit) {
395 Rect *r = maxit->data;
396
397 /* center it in the area */
398 *x = r->x;
399 *y = r->y;
400 if (config_place_center) {
401 *x += (r->width - c->frame->area.width) / 2;
402 *y += (r->height - c->frame->area.height) / 2;
403 }
404 ret = TRUE;
405 }
406
407 while (spaces) {
408 g_slice_free(Rect, spaces->data);
409 spaces = g_slist_delete_link(spaces, spaces);
410 }
411 }
412
413 return ret;
414 }
415
416 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
417 {
418 gint l, r, t, b;
419 gint px, py;
420 Rect *area;
421
422 ob_debug("placing under mouse");
423
424 if (!screen_pointer_pos(&px, &py))
425 return FALSE;
426 area = pick_pointer_head(client);
427
428 l = area->x;
429 t = area->y;
430 r = area->x + area->width - client->frame->area.width;
431 b = area->y + area->height - client->frame->area.height;
432
433 *x = px - client->area.width / 2 - client->frame->size.left;
434 *x = MIN(MAX(*x, l), r);
435 *y = py - client->area.height / 2 - client->frame->size.top;
436 *y = MIN(MAX(*y, t), b);
437
438 g_slice_free(Rect, area);
439
440 return TRUE;
441 }
442
443 static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
444 ObAppSettings *settings)
445 {
446 Rect *screen = NULL;
447
448 if (!settings || (settings && !settings->pos_given))
449 return FALSE;
450
451 ob_debug("placing by per-app settings");
452
453 /* Find which head the pointer is on */
454 if (settings->monitor_type == OB_APP_SETTINGS_MONITOR_PRIMARY) {
455 guint m = screen_monitor_primary(TRUE);
456 screen = screen_area(client->desktop, m, NULL);
457 }
458 else if (settings->monitor_type == OB_APP_SETTINGS_MONITOR_ACTIVE) {
459 guint m = screen_monitor_active();
460 screen = screen_area(client->desktop, m, NULL);
461 }
462 else if (settings->monitor_type == OB_APP_SETTINGS_MONITOR_MOUSE) {
463 screen = pick_pointer_head(client);
464 g_assert(screen);
465 }
466 else {
467 guint m = settings->monitor;
468 if (m < 1 || m > screen_num_monitors)
469 m = screen_monitor_primary(TRUE) + 1;
470 screen = screen_area(client->desktop, m - 1, NULL);
471 }
472
473 if (settings->position.x.center)
474 *x = screen->x + screen->width / 2 - client->area.width / 2;
475 else if (settings->position.x.opposite)
476 *x = screen->x + screen->width - client->frame->area.width -
477 settings->position.x.pos;
478 else
479 *x = screen->x + settings->position.x.pos;
480 if (settings->position.x.denom)
481 *x = (*x * screen->width) / settings->position.x.denom;
482
483 if (settings->position.y.center)
484 *y = screen->y + screen->height / 2 - client->area.height / 2;
485 else if (settings->position.y.opposite)
486 *y = screen->y + screen->height - client->frame->area.height -
487 settings->position.y.pos;
488 else
489 *y = screen->y + settings->position.y.pos;
490 if (settings->position.y.denom)
491 *y = (*y * screen->height) / settings->position.y.denom;
492
493 g_slice_free(Rect, screen);
494 return TRUE;
495 }
496
497 static gboolean place_transient_splash(ObClient *client, Rect *area,
498 gint *x, gint *y)
499 {
500 if (client->type == OB_CLIENT_TYPE_DIALOG) {
501 GSList *it;
502 gboolean first = TRUE;
503 gint l, r, t, b;
504
505 ob_debug("placing dialog");
506
507 for (it = client->parents; it; it = g_slist_next(it)) {
508 ObClient *m = it->data;
509 if (!m->iconic) {
510 if (first) {
511 l = RECT_LEFT(m->frame->area);
512 t = RECT_TOP(m->frame->area);
513 r = RECT_RIGHT(m->frame->area);
514 b = RECT_BOTTOM(m->frame->area);
515 first = FALSE;
516 } else {
517 l = MIN(l, RECT_LEFT(m->frame->area));
518 t = MIN(t, RECT_TOP(m->frame->area));
519 r = MAX(r, RECT_RIGHT(m->frame->area));
520 b = MAX(b, RECT_BOTTOM(m->frame->area));
521 }
522 }
523 if (!first) {
524 *x = ((r + 1 - l) - client->frame->area.width) / 2 + l;
525 *y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
526 return TRUE;
527 }
528 }
529 }
530
531 if (client->type == OB_CLIENT_TYPE_DIALOG ||
532 client->type == OB_CLIENT_TYPE_SPLASH)
533 {
534 ob_debug("placing dialog or splash");
535
536 *x = (area->width - client->frame->area.width) / 2 + area->x;
537 *y = (area->height - client->frame->area.height) / 2 + area->y;
538 return TRUE;
539 }
540
541 return FALSE;
542 }
543
544 /*! Return TRUE if openbox chose the position for the window, and FALSE if
545 the application chose it */
546 gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
547 ObAppSettings *settings)
548 {
549 Rect *area;
550 gboolean ret;
551
552 /* per-app settings override program specified position
553 * but not user specified, unless pos_force is enabled */
554 if (((client->positioned & USPosition) &&
555 !(settings && settings->pos_given && settings->pos_force)) ||
556 ((client->positioned & PPosition) &&
557 !(settings && settings->pos_given)))
558 return FALSE;
559
560 area = pick_head(client, foreground);
561
562 /* try a number of methods */
563 ret = place_per_app_setting(client, x, y, settings) ||
564 place_transient_splash(client, area, x, y) ||
565 (config_place_policy == OB_PLACE_POLICY_MOUSE &&
566 place_under_mouse(client, x, y)) ||
567 place_nooverlap(client, area, x, y) ||
568 place_random(client, area, x, y);
569 g_assert(ret);
570
571 g_slice_free(Rect, area);
572
573 /* get where the client should be */
574 frame_frame_gravity(client->frame, x, y);
575 return TRUE;
576 }
This page took 0.066772 seconds and 5 git commands to generate.