Allow application rules to control window size (Fix bug 4661)
[dana/openbox.git] / 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 }