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