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