Fix an if statement getting moved into a loop it shouldn't have
[mikachu/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     screen_apply_gravity_point(x, y, frame_size.width, frame_size.height,
299                                &settings->position, screen);
300
301     return TRUE;
302 }
303
304 static void place_per_app_setting_size(ObClient *client, Rect *screen,
305                                        gint *w, gint *h,
306                                        ObAppSettings *settings)
307 {
308     if (!settings)
309         return;
310
311     g_assert(settings->width_num >= 0);
312     g_assert(settings->width_denom >= 0);
313     g_assert(settings->height_num >= 0);
314     g_assert(settings->height_denom >= 0);
315
316     if (settings->width_num) {
317         ob_debug("setting width by per-app settings");
318         if (!settings->width_denom)
319             *w = settings->width_num;
320         else {
321             *w = screen->width * settings->width_num / settings->width_denom;
322             *w = MIN(*w, screen->width);
323         }
324     }
325
326     if (settings->height_num) {
327         ob_debug("setting height by per-app settings");
328         if (!settings->height_denom)
329             *h = settings->height_num;
330         else {
331             *h = screen->height * settings->height_num / settings->height_denom;
332             *h = MIN(*h, screen->height);
333         }
334     }
335 }
336
337 static gboolean place_transient_splash(ObClient *client, Rect *area,
338                                        gint *x, gint *y, Size frame_size)
339 {
340     if (client->type == OB_CLIENT_TYPE_DIALOG) {
341         GSList *it;
342         gboolean first = TRUE;
343         gint l, r, t, b;
344
345         ob_debug("placing dialog");
346
347         for (it = client->parents; it; it = g_slist_next(it)) {
348             ObClient *m = it->data;
349             if (!m->iconic) {
350                 if (first) {
351                     l = RECT_LEFT(m->frame->area);
352                     t = RECT_TOP(m->frame->area);
353                     r = RECT_RIGHT(m->frame->area);
354                     b = RECT_BOTTOM(m->frame->area);
355                     first = FALSE;
356                 } else {
357                     l = MIN(l, RECT_LEFT(m->frame->area));
358                     t = MIN(t, RECT_TOP(m->frame->area));
359                     r = MAX(r, RECT_RIGHT(m->frame->area));
360                     b = MAX(b, RECT_BOTTOM(m->frame->area));
361                 }
362             }
363         }
364         if (!first) {
365             *x = ((r + 1 - l) - frame_size.width) / 2 + l;
366             *y = ((b + 1 - t) - frame_size.height) / 2 + t;
367             return TRUE;
368         }
369     }
370
371     if (client->type == OB_CLIENT_TYPE_DIALOG ||
372         client->type == OB_CLIENT_TYPE_SPLASH)
373     {
374         ob_debug("placing dialog or splash");
375
376         *x = (area->width - frame_size.width) / 2 + area->x;
377         *y = (area->height - frame_size.height) / 2 + area->y;
378         return TRUE;
379     }
380
381     return FALSE;
382 }
383
384 static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y,
385                                     Size frame_size)
386 {
387     /* Assemble the list of windows that could overlap with @c in the user's
388        current view. */
389     GSList* potential_overlap_clients = NULL;
390     gint n_client_rects = config_dock_hide ? 0 : 1;
391
392     /* If we're "showing desktop", and going to allow this window to
393        be shown now, then ignore all existing windows */
394     gboolean ignore_windows = FALSE;
395     switch (screen_show_desktop_mode) {
396     case SCREEN_SHOW_DESKTOP_NO:
397     case SCREEN_SHOW_DESKTOP_UNTIL_WINDOW:
398         break;
399     case SCREEN_SHOW_DESKTOP_UNTIL_TOGGLE:
400         ignore_windows = TRUE;
401         break;
402     }
403
404     if (!ignore_windows) {
405         GList* it;
406         for (it = client_list; it != NULL; it = g_list_next(it)) {
407             ObClient* maybe_client = (ObClient*)it->data;
408             if (maybe_client == c)
409                 continue;
410             if (maybe_client->iconic)
411                 continue;
412             if (!client_occupies_space(maybe_client))
413                 continue;
414             if (c->desktop != DESKTOP_ALL) {
415                 if (maybe_client->desktop != c->desktop &&
416                     maybe_client->desktop != DESKTOP_ALL)
417                     continue;
418             } else {
419                 if (maybe_client->desktop != screen_desktop &&
420                     maybe_client->desktop != DESKTOP_ALL)
421                     continue;
422             }
423
424             potential_overlap_clients = g_slist_prepend(
425                 potential_overlap_clients, maybe_client);
426             n_client_rects += 1;
427         }
428     }
429
430     if (n_client_rects) {
431         Rect client_rects[n_client_rects];
432         GSList* it;
433         Point result;
434         guint i = 0;
435
436         if (!config_dock_hide)
437             dock_get_area(&client_rects[i++]);
438         for (it = potential_overlap_clients; it != NULL; it = g_slist_next(it)) {
439             ObClient* potential_overlap_client = (ObClient*)it->data;
440             client_rects[i] = potential_overlap_client->frame->area;
441             i += 1;
442         }
443         g_slist_free(potential_overlap_clients);
444
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
451     return TRUE;
452 }
453
454 static gboolean should_set_client_position(ObClient *client,
455                                            ObAppSettings *settings)
456 {
457     gboolean has_position = settings && settings->pos_given;
458     gboolean has_forced_position = has_position && settings->pos_force;
459
460     gboolean user_positioned = client->positioned & USPosition;
461     if (user_positioned && !has_forced_position)
462         return FALSE;
463
464     gboolean program_positioned = client->positioned & PPosition;
465     if (program_positioned && !has_position)
466         return FALSE;
467
468     return TRUE;
469 }
470
471 gboolean place_client(ObClient *client, gboolean client_to_be_foregrounded,
472                       Rect* client_area, ObAppSettings *settings)
473 {
474     gboolean ret;
475     Rect *monitor_area;
476     int *x, *y, *w, *h;
477     Size frame_size;
478
479     monitor_area = choose_monitor(client, client_to_be_foregrounded, settings);
480
481     w = &client_area->width;
482     h = &client_area->height;
483     place_per_app_setting_size(client, monitor_area, w, h, settings);
484
485     if (!should_set_client_position(client, settings))
486         return FALSE;
487
488     x = &client_area->x;
489     y = &client_area->y;
490
491     SIZE_SET(frame_size,
492              *w + client->frame->size.left + client->frame->size.right,
493              *h + client->frame->size.top + client->frame->size.bottom);
494
495     ret =
496         place_per_app_setting_position(client, monitor_area, x, y, settings,
497                                        frame_size) ||
498         place_transient_splash(client, monitor_area, x, y, frame_size) ||
499         place_under_mouse(client, x, y, frame_size) ||
500         place_least_overlap(client, monitor_area, x, y, frame_size);
501     g_assert(ret);
502
503     g_slice_free(Rect, monitor_area);
504
505     /* get where the client should be */
506     frame_frame_gravity(client->frame, x, y);
507     return TRUE;
508 }