]> Dogcows Code - chaz/openbox/blob - openbox/place.c
Allow application rules to control window size (Fix bug 4661)
[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 "place_overlap.h"
29
30 extern ObDock *dock;
31
32 static Rect *choose_pointer_monitor(ObClient *c)
33 {
34 return screen_area(c->desktop, screen_monitor_pointer(), NULL);
35 }
36
37 /* use the following priority lists for choose_monitor()
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* choose_monitor(ObClient *c, gboolean client_to_be_foregrounded,
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 client_to_be_foregrounded ? 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_under_mouse(ObClient *client, gint *x, gint *y,
260 Size frame_size)
261 {
262 if (config_place_policy != OB_PLACE_POLICY_MOUSE)
263 return FALSE;
264
265 gint l, r, t, b;
266 gint px, py;
267 Rect *area;
268
269 ob_debug("placing under mouse");
270
271 if (!screen_pointer_pos(&px, &py))
272 return FALSE;
273 area = choose_pointer_monitor(client);
274
275 l = area->x;
276 t = area->y;
277 r = area->x + area->width - frame_size.width;
278 b = area->y + area->height - frame_size.height;
279
280 *x = px - frame_size.width / 2;
281 *x = MIN(MAX(*x, l), r);
282 *y = py - frame_size.height / 2;
283 *y = MIN(MAX(*y, t), b);
284
285 g_slice_free(Rect, area);
286
287 return TRUE;
288 }
289
290 static gboolean place_per_app_setting_position(ObClient *client, Rect *screen,
291 gint *x, gint *y,
292 ObAppSettings *settings,
293 Size frame_size)
294 {
295 if (!settings || !settings->pos_given)
296 return FALSE;
297
298 ob_debug("placing by per-app settings");
299
300 if (settings->position.x.center)
301 *x = screen->x + screen->width / 2 - client->area.width / 2;
302 else if (settings->position.x.opposite)
303 *x = screen->x + screen->width - frame_size.width -
304 settings->position.x.pos;
305 else
306 *x = screen->x + settings->position.x.pos;
307 if (settings->position.x.denom)
308 *x = (*x * screen->width) / settings->position.x.denom;
309
310 if (settings->position.y.center)
311 *y = screen->y + screen->height / 2 - client->area.height / 2;
312 else if (settings->position.y.opposite)
313 *y = screen->y + screen->height - frame_size.height -
314 settings->position.y.pos;
315 else
316 *y = screen->y + settings->position.y.pos;
317 if (settings->position.y.denom)
318 *y = (*y * screen->height) / settings->position.y.denom;
319
320 return TRUE;
321 }
322
323 static void place_per_app_setting_size(ObClient *client, Rect *screen,
324 gint *w, gint *h,
325 ObAppSettings *settings)
326 {
327 if (!settings || !settings->size_given)
328 return;
329
330 ob_debug("sizing by per-app settings");
331
332 g_assert(settings->width_num > 0);
333 g_assert(settings->width_denom >= 0);
334 g_assert(settings->height_num > 0);
335 g_assert(settings->height_denom >= 0);
336
337 if (!settings->width_denom)
338 *w = settings->width_num;
339 else {
340 *w = screen->width * settings->width_num / settings->width_denom;
341 *w = MIN(*w, screen->width);
342 }
343
344 if (!settings->height_denom)
345 *h = settings->height_num;
346 else {
347 *h = screen->height * settings->height_num / settings->height_denom;
348 *h = MIN(*h, screen->height);
349 }
350 }
351
352 static gboolean place_transient_splash(ObClient *client, Rect *area,
353 gint *x, gint *y, Size frame_size)
354 {
355 if (client->type == OB_CLIENT_TYPE_DIALOG) {
356 GSList *it;
357 gboolean first = TRUE;
358 gint l, r, t, b;
359
360 ob_debug("placing dialog");
361
362 for (it = client->parents; it; it = g_slist_next(it)) {
363 ObClient *m = it->data;
364 if (!m->iconic) {
365 if (first) {
366 l = RECT_LEFT(m->frame->area);
367 t = RECT_TOP(m->frame->area);
368 r = RECT_RIGHT(m->frame->area);
369 b = RECT_BOTTOM(m->frame->area);
370 first = FALSE;
371 } else {
372 l = MIN(l, RECT_LEFT(m->frame->area));
373 t = MIN(t, RECT_TOP(m->frame->area));
374 r = MAX(r, RECT_RIGHT(m->frame->area));
375 b = MAX(b, RECT_BOTTOM(m->frame->area));
376 }
377 }
378 if (!first) {
379 *x = ((r + 1 - l) - frame_size.width) / 2 + l;
380 *y = ((b + 1 - t) - frame_size.height) / 2 + t;
381 return TRUE;
382 }
383 }
384 }
385
386 if (client->type == OB_CLIENT_TYPE_DIALOG ||
387 client->type == OB_CLIENT_TYPE_SPLASH)
388 {
389 ob_debug("placing dialog or splash");
390
391 *x = (area->width - frame_size.width) / 2 + area->x;
392 *y = (area->height - frame_size.height) / 2 + area->y;
393 return TRUE;
394 }
395
396 return FALSE;
397 }
398
399 static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y,
400 Size frame_size)
401 {
402 /* Assemble the list of windows that could overlap with @c in the user's
403 current view. */
404 GSList* potential_overlap_clients = NULL;
405 gint n_client_rects = 0;
406
407 /* if we're "showing desktop", ignore all existing windows */
408 if (!screen_showing_desktop) {
409 GList* it;
410 for (it = client_list; it != NULL; it = g_list_next(it)) {
411 ObClient* maybe_client = (ObClient*)it->data;
412 if (maybe_client == c)
413 continue;
414 if (maybe_client->iconic)
415 continue;
416 if (!client_occupies_space(maybe_client))
417 continue;
418 if (c->desktop != DESKTOP_ALL) {
419 if (maybe_client->desktop != c->desktop &&
420 maybe_client->desktop != DESKTOP_ALL)
421 continue;
422 } else {
423 if (maybe_client->desktop != screen_desktop &&
424 maybe_client->desktop != DESKTOP_ALL)
425 continue;
426 }
427
428 potential_overlap_clients = g_slist_prepend(
429 potential_overlap_clients, maybe_client);
430 n_client_rects += 1;
431 }
432 }
433 Rect client_rects[n_client_rects];
434
435 GSList* it;
436 guint i = 0;
437 for (it = potential_overlap_clients; it != NULL; it = g_slist_next(it)) {
438 ObClient* potential_overlap_client = (ObClient*)it->data;
439 client_rects[i] = potential_overlap_client->frame->area;
440 i += 1;
441 }
442 g_slist_free(potential_overlap_clients);
443
444 Point result;
445 place_overlap_find_least_placement(client_rects, n_client_rects, head,
446 &frame_size, &result);
447 *x = result.x;
448 *y = result.y;
449
450 return TRUE;
451 }
452
453 static gboolean should_set_client_position(ObClient *client,
454 ObAppSettings *settings)
455 {
456 gboolean has_position = settings && settings->pos_given;
457 gboolean has_forced_position = has_position && settings->pos_force;
458
459 gboolean user_positioned = client->positioned & USPosition;
460 if (user_positioned && !has_forced_position)
461 return FALSE;
462
463 gboolean program_positioned = client->positioned & PPosition;
464 if (program_positioned && !has_position)
465 return FALSE;
466
467 return TRUE;
468 }
469
470 gboolean place_client(ObClient *client, gboolean client_to_be_foregrounded,
471 Rect* client_area, ObAppSettings *settings)
472 {
473 gboolean ret;
474 Rect *monitor_area;
475 int *x, *y, *w, *h;
476 Size frame_size;
477
478 monitor_area = choose_monitor(client, client_to_be_foregrounded, settings);
479
480 w = &client_area->width;
481 h = &client_area->height;
482 place_per_app_setting_size(client, monitor_area, w, h, settings);
483
484 if (!should_set_client_position(client, settings))
485 return FALSE;
486
487 x = &client_area->x;
488 y = &client_area->y;
489
490 SIZE_SET(frame_size,
491 *w + client->frame->size.left + client->frame->size.right,
492 *h + client->frame->size.top + client->frame->size.bottom);
493
494 ret =
495 place_per_app_setting_position(client, monitor_area, x, y, settings,
496 frame_size) ||
497 place_transient_splash(client, monitor_area, x, y, frame_size) ||
498 place_under_mouse(client, x, y, frame_size) ||
499 place_least_overlap(client, monitor_area, x, y, frame_size);
500 g_assert(ret);
501
502 g_slice_free(Rect, monitor_area);
503
504 /* get where the client should be */
505 frame_frame_gravity(client->frame, x, y);
506 return TRUE;
507 }
This page took 0.054985 seconds and 4 git commands to generate.