d2d66e02ff8617f4ec6e331159fb7e320ea078bf
[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 static Rect *choose_pointer_monitor(ObClient *c)
31 {
32     return screen_area(c->desktop, screen_monitor_pointer(), NULL);
33 }
34
35 /* use the following priority lists for choose_monitor()
36
37    When a window is being placed in the FOREGROUND, use a monitor chosen in
38    the following order:
39    1. per-app settings
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
44    4. primary monitor
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
47    7. other monitors
48
49    When a window is being placed in the BACKGROUND, use a monitor chosen in the
50    following order:
51    1. per-app settings
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
59    5. other monitors
60     5a. primary monitor in this set
61     5b. other monitors in this set
62 */
63
64 /*! One for each possible head, used to sort them in order of precedence. */
65 typedef struct {
66     guint monitor;
67     guint flags;
68 } ObPlaceHead;
69
70 /*! Flags for ObPlaceHead */
71 enum {
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 */
78 };
79
80 gint cmp_foreground(const void *a, const void *b)
81 {
82     const ObPlaceHead *h1 = a;
83     const ObPlaceHead *h2 = b;
84     gint i = 0;
85
86     if (h1->monitor == h2->monitor) return 0;
87
88     if (h1->flags & HEAD_PERAPP) --i;
89     if (h2->flags & HEAD_PERAPP) ++i;
90     if (i) return i;
91
92     if (h1->flags & HEAD_PARENT) --i;
93     if (h2->flags & HEAD_PARENT) ++i;
94     if (i) return i;
95
96     if (h1->flags & HEAD_PLACED) --i;
97     if (h2->flags & HEAD_PLACED) ++i;
98     if (i) return i;
99
100     if (h1->flags & HEAD_PRIMARY) --i;
101     if (h2->flags & HEAD_PRIMARY) ++i;
102     if (i) return i;
103
104     if (h1->flags & HEAD_GROUP_DESK) --i;
105     if (h2->flags & HEAD_GROUP_DESK) ++i;
106     if (i) return i;
107
108     if (h1->flags & HEAD_GROUP) --i;
109     if (h2->flags & HEAD_GROUP) ++i;
110     if (i) return i;
111
112     return h1->monitor - h2->monitor;
113 }
114
115 gint cmp_background(const void *a, const void *b)
116 {
117     const ObPlaceHead *h1 = a;
118     const ObPlaceHead *h2 = b;
119     gint i = 0;
120
121     if (h1->monitor == h2->monitor) return 0;
122
123     if (h1->flags & HEAD_PERAPP) --i;
124     if (h2->flags & HEAD_PERAPP) ++i;
125     if (i) return i;
126
127     if (h1->flags & HEAD_PARENT) --i;
128     if (h2->flags & HEAD_PARENT) ++i;
129     if (i) return i;
130
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;
134         if (i) return i;
135         if (h1->flags & HEAD_PRIMARY) --i;
136         if (h2->flags & HEAD_PRIMARY) ++i;
137         if (i) return i;
138     }
139
140     if (h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
141         if (h1->flags & HEAD_GROUP) --i;
142         if (h2->flags & HEAD_GROUP) ++i;
143         if (i) return i;
144         if (h1->flags & HEAD_PRIMARY) --i;
145         if (h2->flags & HEAD_PRIMARY) ++i;
146         if (i) return i;
147     }
148
149     if (h1->flags & HEAD_PRIMARY) --i;
150     if (h2->flags & HEAD_PRIMARY) ++i;
151     if (i) return i;
152
153     return h1->monitor - h2->monitor;
154 }
155
156 /*! Pick a monitor to place a window on. */
157 static Rect* choose_monitor(ObClient *c, gboolean client_to_be_foregrounded,
158                             ObAppSettings *settings)
159 {
160     Rect *area;
161     ObPlaceHead *choice;
162     guint i;
163     ObClient *p;
164     GSList *it;
165
166     choice = g_new(ObPlaceHead, screen_num_monitors);
167     for (i = 0; i < screen_num_monitors; ++i) {
168         choice[i].monitor = i;
169         choice[i].flags = 0;
170     }
171
172     /* find monitors with group members */
173     if (c->group) {
174         for (it = c->group->members; it; it = g_slist_next(it)) {
175             ObClient *itc = it->data;
176             if (itc != c) {
177                 guint m = client_monitor(itc);
178
179                 if (m < screen_num_monitors) {
180                     if (screen_compare_desktops(itc->desktop, c->desktop))
181                         choice[m].flags |= HEAD_GROUP_DESK;
182                     else
183                         choice[m].flags |= HEAD_GROUP;
184                 }
185             }
186         }
187     }
188
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;
194         if (settings &&
195             settings->monitor_type == OB_PLACE_MONITOR_PRIMARY)
196             choice[i].flags |= HEAD_PERAPP;
197     }
198
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;
203         if (settings &&
204             settings->monitor_type == OB_PLACE_MONITOR_ACTIVE)
205             choice[i].flags |= HEAD_PERAPP;
206     }
207
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;
212         if (settings &&
213             settings->monitor_type == OB_PLACE_MONITOR_MOUSE)
214             choice[i].flags |= HEAD_PERAPP;
215     }
216
217     if (settings) {
218         i = settings->monitor - 1;
219         if (i < screen_num_monitors)
220             choice[i].flags |= HEAD_PERAPP;
221     }
222
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;
228     }
229
230     qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
231           client_to_be_foregrounded ? cmp_foreground : cmp_background);
232
233     /* save the areas of the monitors in order of their being chosen */
234     for (i = 0; i < screen_num_monitors; ++i)
235     {
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");
247     }
248
249     area = screen_area(c->desktop, choice[0].monitor, NULL);
250
251     g_free(choice);
252
253     /* return the area for the chosen monitor */
254     return area;
255 }
256
257 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y,
258                                   Size frame_size)
259 {
260     if (config_place_policy != OB_PLACE_POLICY_MOUSE)
261         return FALSE;
262
263     gint l, r, t, b;
264     gint px, py;
265     Rect *area;
266
267     ob_debug("placing under mouse");
268
269     if (!screen_pointer_pos(&px, &py))
270         return FALSE;
271     area = choose_pointer_monitor(client);
272
273     l = area->x;
274     t = area->y;
275     r = area->x + area->width - frame_size.width;
276     b = area->y + area->height - frame_size.height;
277
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);
282
283     g_slice_free(Rect, area);
284
285     return TRUE;
286 }
287
288 static gboolean place_per_app_setting_position(ObClient *client, Rect *screen,
289                                                gint *x, gint *y,
290                                                ObAppSettings *settings,
291                                                Size frame_size)
292 {
293     if (!settings || !settings->pos_given)
294         return FALSE;
295
296     ob_debug("placing by per-app settings");
297
298     if (settings->position.x.center)
299         *x = screen->x + screen->width / 2 - client->area.width / 2;
300     else if (settings->position.x.opposite)
301         *x = screen->x + screen->width - frame_size.width -
302             settings->position.x.pos;
303     else
304         *x = screen->x + settings->position.x.pos;
305     if (settings->position.x.denom)
306         *x = (*x * screen->width) / settings->position.x.denom;
307
308     if (settings->position.y.center)
309         *y = screen->y + screen->height / 2 - client->area.height / 2;
310     else if (settings->position.y.opposite)
311         *y = screen->y + screen->height - frame_size.height -
312             settings->position.y.pos;
313     else
314         *y = screen->y + settings->position.y.pos;
315     if (settings->position.y.denom)
316         *y = (*y * screen->height) / settings->position.y.denom;
317
318     return TRUE;
319 }
320
321 static void place_per_app_setting_size(ObClient *client, Rect *screen,
322                                        gint *w, gint *h,
323                                        ObAppSettings *settings)
324 {
325     if (!settings || !settings->size_given)
326         return;
327
328     ob_debug("sizing by per-app settings");
329
330     g_assert(settings->width_num > 0);
331     g_assert(settings->width_denom >= 0);
332     g_assert(settings->height_num > 0);
333     g_assert(settings->height_denom >= 0);
334
335     if (!settings->width_denom)
336         *w = settings->width_num;
337     else {
338         *w = screen->width * settings->width_num / settings->width_denom;
339         *w = MIN(*w, screen->width);
340     }
341
342     if (!settings->height_denom)
343         *h = settings->height_num;
344     else {
345         *h = screen->height * settings->height_num / settings->height_denom;
346         *h = MIN(*h, screen->height);
347     }
348 }
349
350 static gboolean place_transient_splash(ObClient *client, Rect *area,
351                                        gint *x, gint *y, Size frame_size)
352 {
353     if (client->type == OB_CLIENT_TYPE_DIALOG) {
354         GSList *it;
355         gboolean first = TRUE;
356         gint l, r, t, b;
357
358         ob_debug("placing dialog");
359
360         for (it = client->parents; it; it = g_slist_next(it)) {
361             ObClient *m = it->data;
362             if (!m->iconic) {
363                 if (first) {
364                     l = RECT_LEFT(m->frame->area);
365                     t = RECT_TOP(m->frame->area);
366                     r = RECT_RIGHT(m->frame->area);
367                     b = RECT_BOTTOM(m->frame->area);
368                     first = FALSE;
369                 } else {
370                     l = MIN(l, RECT_LEFT(m->frame->area));
371                     t = MIN(t, RECT_TOP(m->frame->area));
372                     r = MAX(r, RECT_RIGHT(m->frame->area));
373                     b = MAX(b, RECT_BOTTOM(m->frame->area));
374                 }
375             }
376             if (!first) {
377                 *x = ((r + 1 - l) - frame_size.width) / 2 + l;
378                 *y = ((b + 1 - t) - frame_size.height) / 2 + t;
379                 return TRUE;
380             }
381         }
382     }
383
384     if (client->type == OB_CLIENT_TYPE_DIALOG ||
385         client->type == OB_CLIENT_TYPE_SPLASH)
386     {
387         ob_debug("placing dialog or splash");
388
389         *x = (area->width - frame_size.width) / 2 + area->x;
390         *y = (area->height - frame_size.height) / 2 + area->y;
391         return TRUE;
392     }
393
394     return FALSE;
395 }
396
397 static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y,
398                                     Size frame_size)
399 {
400     /* Assemble the list of windows that could overlap with @c in the user's
401        current view. */
402     GSList* potential_overlap_clients = NULL;
403     gint n_client_rects = config_dock_hide ? 0 : 1;
404
405     /* if we're "showing desktop", ignore all existing windows */
406     if (!screen_showing_desktop) {
407         GList* it;
408         for (it = client_list; it != NULL; it = g_list_next(it)) {
409             ObClient* maybe_client = (ObClient*)it->data;
410             if (maybe_client == c)
411                 continue;
412             if (maybe_client->iconic)
413                 continue;
414             if (!client_occupies_space(maybe_client))
415                 continue;
416             if (c->desktop != DESKTOP_ALL) {
417                 if (maybe_client->desktop != c->desktop &&
418                     maybe_client->desktop != DESKTOP_ALL)
419                     continue;
420             } else {
421                 if (maybe_client->desktop != screen_desktop &&
422                     maybe_client->desktop != DESKTOP_ALL)
423                     continue;
424             }
425
426             potential_overlap_clients = g_slist_prepend(
427                 potential_overlap_clients, maybe_client);
428             n_client_rects += 1;
429         }
430     }
431     Rect client_rects[n_client_rects];
432
433     GSList* it;
434     guint i = 0;
435     if (!config_dock_hide)
436         dock_get_area(&client_rects[i++]);
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 }