Fix negative relative coordinate placement
[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     gint l, r, t, b;
261     gint px, py;
262     Rect *area;
263
264     if (config_place_policy != OB_PLACE_POLICY_MOUSE)
265         return FALSE;
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->width / 2 - client->area.width / 2;
300     else {
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;
306     }
307
308     if (settings->position.y.center)
309         *y = screen->height / 2 - client->area.height / 2;
310     else {
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;
316     }
317
318     *x += screen->x;
319     *y += screen->y;
320
321     return TRUE;
322 }
323
324 static void place_per_app_setting_size(ObClient *client, Rect *screen,
325                                        gint *w, gint *h,
326                                        ObAppSettings *settings)
327 {
328     if (!settings)
329         return;
330
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);
335
336     if (settings->width_num) {
337         ob_debug("setting width by per-app settings");
338         if (!settings->width_denom)
339             *w = settings->width_num;
340         else {
341             *w = screen->width * settings->width_num / settings->width_denom;
342             *w = MIN(*w, screen->width);
343         }
344     }
345
346     if (settings->height_num) {
347         ob_debug("setting height by per-app settings");
348         if (!settings->height_denom)
349             *h = settings->height_num;
350         else {
351             *h = screen->height * settings->height_num / settings->height_denom;
352             *h = MIN(*h, screen->height);
353         }
354     }
355 }
356
357 static gboolean place_transient_splash(ObClient *client, Rect *area,
358                                        gint *x, gint *y, Size frame_size)
359 {
360     if (client->type == OB_CLIENT_TYPE_DIALOG) {
361         GSList *it;
362         gboolean first = TRUE;
363         gint l, r, t, b;
364
365         ob_debug("placing dialog");
366
367         for (it = client->parents; it; it = g_slist_next(it)) {
368             ObClient *m = it->data;
369             if (!m->iconic) {
370                 if (first) {
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);
375                     first = FALSE;
376                 } else {
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));
381                 }
382             }
383             if (!first) {
384                 *x = ((r + 1 - l) - frame_size.width) / 2 + l;
385                 *y = ((b + 1 - t) - frame_size.height) / 2 + t;
386                 return TRUE;
387             }
388         }
389     }
390
391     if (client->type == OB_CLIENT_TYPE_DIALOG ||
392         client->type == OB_CLIENT_TYPE_SPLASH)
393     {
394         ob_debug("placing dialog or splash");
395
396         *x = (area->width - frame_size.width) / 2 + area->x;
397         *y = (area->height - frame_size.height) / 2 + area->y;
398         return TRUE;
399     }
400
401     return FALSE;
402 }
403
404 static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y,
405                                     Size frame_size)
406 {
407     /* Assemble the list of windows that could overlap with @c in the user's
408        current view. */
409     GSList* potential_overlap_clients = NULL;
410     gint n_client_rects = config_dock_hide ? 0 : 1;
411
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:
418         break;
419     case SCREEN_SHOW_DESKTOP_UNTIL_TOGGLE:
420         ignore_windows = TRUE;
421         break;
422     }
423
424     if (!ignore_windows) {
425         GList* it;
426         for (it = client_list; it != NULL; it = g_list_next(it)) {
427             ObClient* maybe_client = (ObClient*)it->data;
428             if (maybe_client == c)
429                 continue;
430             if (maybe_client->iconic)
431                 continue;
432             if (!client_occupies_space(maybe_client))
433                 continue;
434             if (c->desktop != DESKTOP_ALL) {
435                 if (maybe_client->desktop != c->desktop &&
436                     maybe_client->desktop != DESKTOP_ALL)
437                     continue;
438             } else {
439                 if (maybe_client->desktop != screen_desktop &&
440                     maybe_client->desktop != DESKTOP_ALL)
441                     continue;
442             }
443
444             potential_overlap_clients = g_slist_prepend(
445                 potential_overlap_clients, maybe_client);
446             n_client_rects += 1;
447         }
448     }
449
450     if (n_client_rects) {
451         Rect client_rects[n_client_rects];
452         GSList* it;
453         Point result;
454         guint i = 0;
455
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;
461             i += 1;
462         }
463         g_slist_free(potential_overlap_clients);
464
465         place_overlap_find_least_placement(client_rects, n_client_rects, head,
466                                            &frame_size, &result);
467         *x = result.x;
468         *y = result.y;
469     }
470
471     return TRUE;
472 }
473
474 static gboolean should_set_client_position(ObClient *client,
475                                            ObAppSettings *settings)
476 {
477     gboolean has_position = settings && settings->pos_given;
478     gboolean has_forced_position = has_position && settings->pos_force;
479
480     gboolean user_positioned = client->positioned & USPosition;
481     if (user_positioned && !has_forced_position)
482         return FALSE;
483
484     gboolean program_positioned = client->positioned & PPosition;
485     if (program_positioned && !has_position)
486         return FALSE;
487
488     return TRUE;
489 }
490
491 gboolean place_client(ObClient *client, gboolean client_to_be_foregrounded,
492                       Rect* client_area, ObAppSettings *settings)
493 {
494     gboolean ret;
495     Rect *monitor_area;
496     int *x, *y, *w, *h;
497     Size frame_size;
498
499     monitor_area = choose_monitor(client, client_to_be_foregrounded, settings);
500
501     w = &client_area->width;
502     h = &client_area->height;
503     place_per_app_setting_size(client, monitor_area, w, h, settings);
504
505     if (!should_set_client_position(client, settings))
506         return FALSE;
507
508     x = &client_area->x;
509     y = &client_area->y;
510
511     SIZE_SET(frame_size,
512              *w + client->frame->size.left + client->frame->size.right,
513              *h + client->frame->size.top + client->frame->size.bottom);
514
515     ret =
516         place_per_app_setting_position(client, monitor_area, x, y, settings,
517                                        frame_size) ||
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);
521     g_assert(ret);
522
523     g_slice_free(Rect, monitor_area);
524
525     /* get where the client should be */
526     frame_frame_gravity(client->frame, x, y);
527     return TRUE;
528 }