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