1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 place.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
28 #include "place_overlap.h"
30 static Rect *choose_pointer_monitor(ObClient *c)
32 return screen_area(c->desktop, screen_monitor_pointer(), NULL);
35 /* use the following priority lists for choose_monitor()
37 When a window is being placed in the FOREGROUND, use a monitor chosen in
40 2. same monitor as parent
41 3. primary monitor if placement=PRIMARY
42 active monitor if placement=ACTIVE
43 pointer monitor if placement=MOUSE
45 5. other monitors where the window has group members on the same desktop
46 6. other monitors where the window has group members on other desktops
49 When a window is being placed in the BACKGROUND, use a monitor chosen in the
52 2. same monitor as parent
53 3. other monitors where the window has group members on the same desktop
54 3a. primary monitor in this set
55 3b. other monitors in this set
56 4. other monitors where the window has group members on other desktops
57 4a. primary monitor in this set
58 4b. other monitors in this set
60 5a. primary monitor in this set
61 5b. other monitors in this set
64 /*! One for each possible head, used to sort them in order of precedence. */
70 /*! Flags for ObPlaceHead */
72 HEAD_PARENT = 1 << 0, /* parent's monitor */
73 HEAD_PLACED = 1 << 1, /* chosen monitor by placement */
74 HEAD_PRIMARY = 1 << 2, /* primary monitor */
75 HEAD_GROUP_DESK = 1 << 3, /* has a group member on the same desktop */
76 HEAD_GROUP = 1 << 4, /* has a group member on another desktop */
77 HEAD_PERAPP = 1 << 5, /* chosen by per-app settings */
80 gint cmp_foreground(const void *a, const void *b)
82 const ObPlaceHead *h1 = a;
83 const ObPlaceHead *h2 = b;
86 if (h1->monitor == h2->monitor) return 0;
88 if (h1->flags & HEAD_PERAPP) --i;
89 if (h2->flags & HEAD_PERAPP) ++i;
92 if (h1->flags & HEAD_PARENT) --i;
93 if (h2->flags & HEAD_PARENT) ++i;
96 if (h1->flags & HEAD_PLACED) --i;
97 if (h2->flags & HEAD_PLACED) ++i;
100 if (h1->flags & HEAD_PRIMARY) --i;
101 if (h2->flags & HEAD_PRIMARY) ++i;
104 if (h1->flags & HEAD_GROUP_DESK) --i;
105 if (h2->flags & HEAD_GROUP_DESK) ++i;
108 if (h1->flags & HEAD_GROUP) --i;
109 if (h2->flags & HEAD_GROUP) ++i;
112 return h1->monitor - h2->monitor;
115 gint cmp_background(const void *a, const void *b)
117 const ObPlaceHead *h1 = a;
118 const ObPlaceHead *h2 = b;
121 if (h1->monitor == h2->monitor) return 0;
123 if (h1->flags & HEAD_PERAPP) --i;
124 if (h2->flags & HEAD_PERAPP) ++i;
127 if (h1->flags & HEAD_PARENT) --i;
128 if (h2->flags & HEAD_PARENT) ++i;
131 if (h1->flags & HEAD_GROUP_DESK || h2->flags & HEAD_GROUP_DESK) {
132 if (h1->flags & HEAD_GROUP_DESK) --i;
133 if (h2->flags & HEAD_GROUP_DESK) ++i;
135 if (h1->flags & HEAD_PRIMARY) --i;
136 if (h2->flags & HEAD_PRIMARY) ++i;
140 if (h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
141 if (h1->flags & HEAD_GROUP) --i;
142 if (h2->flags & HEAD_GROUP) ++i;
144 if (h1->flags & HEAD_PRIMARY) --i;
145 if (h2->flags & HEAD_PRIMARY) ++i;
149 if (h1->flags & HEAD_PRIMARY) --i;
150 if (h2->flags & HEAD_PRIMARY) ++i;
153 return h1->monitor - h2->monitor;
156 /*! Pick a monitor to place a window on. */
157 static Rect* choose_monitor(ObClient *c, gboolean client_to_be_foregrounded,
158 ObAppSettings *settings)
166 choice = g_new(ObPlaceHead, screen_num_monitors);
167 for (i = 0; i < screen_num_monitors; ++i) {
168 choice[i].monitor = i;
172 /* find monitors with group members */
174 for (it = c->group->members; it; it = g_slist_next(it)) {
175 ObClient *itc = it->data;
177 guint m = client_monitor(itc);
179 if (m < screen_num_monitors) {
180 if (screen_compare_desktops(itc->desktop, c->desktop))
181 choice[m].flags |= HEAD_GROUP_DESK;
183 choice[m].flags |= HEAD_GROUP;
189 i = screen_monitor_primary(FALSE);
190 if (i < screen_num_monitors) {
191 choice[i].flags |= HEAD_PRIMARY;
192 if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
193 choice[i].flags |= HEAD_PLACED;
195 settings->monitor_type == OB_PLACE_MONITOR_PRIMARY)
196 choice[i].flags |= HEAD_PERAPP;
199 i = screen_monitor_active();
200 if (i < screen_num_monitors) {
201 if (config_place_monitor == OB_PLACE_MONITOR_ACTIVE)
202 choice[i].flags |= HEAD_PLACED;
204 settings->monitor_type == OB_PLACE_MONITOR_ACTIVE)
205 choice[i].flags |= HEAD_PERAPP;
208 i = screen_monitor_pointer();
209 if (i < screen_num_monitors) {
210 if (config_place_monitor == OB_PLACE_MONITOR_MOUSE)
211 choice[i].flags |= HEAD_PLACED;
213 settings->monitor_type == OB_PLACE_MONITOR_MOUSE)
214 choice[i].flags |= HEAD_PERAPP;
218 i = settings->monitor - 1;
219 if (i < screen_num_monitors)
220 choice[i].flags |= HEAD_PERAPP;
223 /* direct parent takes highest precedence */
224 if ((p = client_direct_parent(c))) {
225 i = client_monitor(p);
226 if (i < screen_num_monitors)
227 choice[i].flags |= HEAD_PARENT;
230 qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
231 client_to_be_foregrounded ? cmp_foreground : cmp_background);
233 /* save the areas of the monitors in order of their being chosen */
234 for (i = 0; i < screen_num_monitors; ++i)
236 ob_debug("placement choice %d is monitor %d", i, choice[i].monitor);
237 if (choice[i].flags & HEAD_PARENT)
238 ob_debug(" - parent on monitor");
239 if (choice[i].flags & HEAD_PLACED)
240 ob_debug(" - placement choice");
241 if (choice[i].flags & HEAD_PRIMARY)
242 ob_debug(" - primary monitor");
243 if (choice[i].flags & HEAD_GROUP_DESK)
244 ob_debug(" - group on same desktop");
245 if (choice[i].flags & HEAD_GROUP)
246 ob_debug(" - group on other desktop");
249 area = screen_area(c->desktop, choice[0].monitor, NULL);
253 /* return the area for the chosen monitor */
257 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y,
264 if (config_place_policy != OB_PLACE_POLICY_MOUSE)
267 ob_debug("placing under mouse");
269 if (!screen_pointer_pos(&px, &py))
271 area = choose_pointer_monitor(client);
275 r = area->x + area->width - frame_size.width;
276 b = area->y + area->height - frame_size.height;
278 *x = px - frame_size.width / 2;
279 *x = MIN(MAX(*x, l), r);
280 *y = py - frame_size.height / 2;
281 *y = MIN(MAX(*y, t), b);
283 g_slice_free(Rect, area);
288 static gboolean place_per_app_setting_position(ObClient *client, Rect *screen,
290 ObAppSettings *settings,
293 if (!settings || !settings->pos_given)
296 ob_debug("placing by per-app settings");
298 if (settings->position.x.center)
299 *x = screen->width / 2 - client->area.width / 2;
301 *x = settings->position.x.pos;
302 if (settings->position.x.denom)
303 *x = (*x * screen->width) / settings->position.x.denom;
304 if (settings->position.x.opposite)
305 *x = screen->width - frame_size.width - *x;
308 if (settings->position.y.center)
309 *y = screen->height / 2 - client->area.height / 2;
311 *y = settings->position.y.pos;
312 if (settings->position.y.denom)
313 *y = (*y * screen->height) / settings->position.y.denom;
314 if (settings->position.y.opposite)
315 *y = screen->height - frame_size.height - *y;
324 static void place_per_app_setting_size(ObClient *client, Rect *screen,
326 ObAppSettings *settings)
331 g_assert(settings->width_num >= 0);
332 g_assert(settings->width_denom >= 0);
333 g_assert(settings->height_num >= 0);
334 g_assert(settings->height_denom >= 0);
336 if (settings->width_num) {
337 ob_debug("setting width by per-app settings");
338 if (!settings->width_denom)
339 *w = settings->width_num;
341 *w = screen->width * settings->width_num / settings->width_denom;
342 *w = MIN(*w, screen->width);
346 if (settings->height_num) {
347 ob_debug("setting height by per-app settings");
348 if (!settings->height_denom)
349 *h = settings->height_num;
351 *h = screen->height * settings->height_num / settings->height_denom;
352 *h = MIN(*h, screen->height);
357 static gboolean place_transient_splash(ObClient *client, Rect *area,
358 gint *x, gint *y, Size frame_size)
360 if (client->type == OB_CLIENT_TYPE_DIALOG) {
362 gboolean first = TRUE;
365 ob_debug("placing dialog");
367 for (it = client->parents; it; it = g_slist_next(it)) {
368 ObClient *m = it->data;
371 l = RECT_LEFT(m->frame->area);
372 t = RECT_TOP(m->frame->area);
373 r = RECT_RIGHT(m->frame->area);
374 b = RECT_BOTTOM(m->frame->area);
377 l = MIN(l, RECT_LEFT(m->frame->area));
378 t = MIN(t, RECT_TOP(m->frame->area));
379 r = MAX(r, RECT_RIGHT(m->frame->area));
380 b = MAX(b, RECT_BOTTOM(m->frame->area));
384 *x = ((r + 1 - l) - frame_size.width) / 2 + l;
385 *y = ((b + 1 - t) - frame_size.height) / 2 + t;
391 if (client->type == OB_CLIENT_TYPE_DIALOG ||
392 client->type == OB_CLIENT_TYPE_SPLASH)
394 ob_debug("placing dialog or splash");
396 *x = (area->width - frame_size.width) / 2 + area->x;
397 *y = (area->height - frame_size.height) / 2 + area->y;
404 static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y,
407 /* Assemble the list of windows that could overlap with @c in the user's
409 GSList* potential_overlap_clients = NULL;
410 gint n_client_rects = config_dock_hide ? 0 : 1;
412 /* If we're "showing desktop", and going to allow this window to
413 be shown now, then ignore all existing windows */
414 gboolean ignore_windows = FALSE;
415 switch (screen_show_desktop_mode) {
416 case SCREEN_SHOW_DESKTOP_NO:
417 case SCREEN_SHOW_DESKTOP_UNTIL_WINDOW:
419 case SCREEN_SHOW_DESKTOP_UNTIL_TOGGLE:
420 ignore_windows = TRUE;
424 if (!ignore_windows) {
426 for (it = client_list; it != NULL; it = g_list_next(it)) {
427 ObClient* maybe_client = (ObClient*)it->data;
428 if (maybe_client == c)
430 if (maybe_client->iconic)
432 if (!client_occupies_space(maybe_client))
434 if (c->desktop != DESKTOP_ALL) {
435 if (maybe_client->desktop != c->desktop &&
436 maybe_client->desktop != DESKTOP_ALL)
439 if (maybe_client->desktop != screen_desktop &&
440 maybe_client->desktop != DESKTOP_ALL)
444 potential_overlap_clients = g_slist_prepend(
445 potential_overlap_clients, maybe_client);
450 if (n_client_rects) {
451 Rect client_rects[n_client_rects];
456 if (!config_dock_hide)
457 dock_get_area(&client_rects[i++]);
458 for (it = potential_overlap_clients; it != NULL; it = g_slist_next(it)) {
459 ObClient* potential_overlap_client = (ObClient*)it->data;
460 client_rects[i] = potential_overlap_client->frame->area;
463 g_slist_free(potential_overlap_clients);
465 place_overlap_find_least_placement(client_rects, n_client_rects, head,
466 &frame_size, &result);
474 static gboolean should_set_client_position(ObClient *client,
475 ObAppSettings *settings)
477 gboolean has_position = settings && settings->pos_given;
478 gboolean has_forced_position = has_position && settings->pos_force;
480 gboolean user_positioned = client->positioned & USPosition;
481 if (user_positioned && !has_forced_position)
484 gboolean program_positioned = client->positioned & PPosition;
485 if (program_positioned && !has_position)
491 gboolean place_client(ObClient *client, gboolean client_to_be_foregrounded,
492 Rect* client_area, ObAppSettings *settings)
499 monitor_area = choose_monitor(client, client_to_be_foregrounded, settings);
501 w = &client_area->width;
502 h = &client_area->height;
503 place_per_app_setting_size(client, monitor_area, w, h, settings);
505 if (!should_set_client_position(client, settings))
512 *w + client->frame->size.left + client->frame->size.right,
513 *h + client->frame->size.top + client->frame->size.bottom);
516 place_per_app_setting_position(client, monitor_area, x, y, settings,
518 place_transient_splash(client, monitor_area, x, y, frame_size) ||
519 place_under_mouse(client, x, y, frame_size) ||
520 place_least_overlap(client, monitor_area, x, y, frame_size);
523 g_slice_free(Rect, monitor_area);
525 /* get where the client should be */
526 frame_frame_gravity(client->frame, x, y);