2cd21bb0379778c3c9a9b947a996b798944c0d93
[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
29 extern ObDock *dock;
30
31 static Rect *pick_pointer_head(ObClient *c)
32 {
33     return screen_area(c->desktop, screen_monitor_pointer(), NULL);
34 }
35
36 /* use the following priority lists for pick_head()
37
38    When a window is being placed in the FOREGROUND, use a monitor chosen in
39    the following order:
40    1. per-app settings
41    2. same monitor as parent
42    3. primary monitor if placement=PRIMARY
43       active monitor if placement=ACTIVE
44       pointer monitor if placement=MOUSE
45    4. primary monitor
46    5. other monitors where the window has group members on the same desktop
47    6. other monitors where the window has group members on other desktops
48    7. other monitors
49
50    When a window is being placed in the BACKGROUND, use a monitor chosen in the
51    following order:
52    1. per-app settings
53    2. same monitor as parent
54    3. other monitors where the window has group members on the same desktop
55     3a. primary monitor in this set
56     3b. other monitors in this set
57    4. other monitors where the window has group members on other desktops
58     4a. primary monitor in this set
59     4b. other monitors in this set
60    5. other monitors
61     5a. primary monitor in this set
62     5b. other monitors in this set
63 */
64
65 /*! One for each possible head, used to sort them in order of precedence. */
66 typedef struct {
67     guint monitor;
68     guint flags;
69 } ObPlaceHead;
70
71 /*! Flags for ObPlaceHead */
72 enum {
73     HEAD_PARENT = 1 << 0, /* parent's monitor */
74     HEAD_PLACED = 1 << 1, /* chosen monitor by placement */
75     HEAD_PRIMARY = 1 << 2, /* primary monitor */
76     HEAD_GROUP_DESK = 1 << 3, /* has a group member on the same desktop */
77     HEAD_GROUP = 1 << 4, /* has a group member on another desktop */
78     HEAD_PERAPP = 1 << 5, /* chosen by per-app settings */
79 };
80
81 gint cmp_foreground(const void *a, const void *b)
82 {
83     const ObPlaceHead *h1 = a;
84     const ObPlaceHead *h2 = b;
85     gint i = 0;
86
87     if (h1->monitor == h2->monitor) return 0;
88
89     if (h1->flags & HEAD_PERAPP) --i;
90     if (h2->flags & HEAD_PERAPP) ++i;
91     if (i) return i;
92
93     if (h1->flags & HEAD_PARENT) --i;
94     if (h2->flags & HEAD_PARENT) ++i;
95     if (i) return i;
96
97     if (h1->flags & HEAD_PLACED) --i;
98     if (h2->flags & HEAD_PLACED) ++i;
99     if (i) return i;
100
101     if (h1->flags & HEAD_PRIMARY) --i;
102     if (h2->flags & HEAD_PRIMARY) ++i;
103     if (i) return i;
104
105     if (h1->flags & HEAD_GROUP_DESK) --i;
106     if (h2->flags & HEAD_GROUP_DESK) ++i;
107     if (i) return i;
108
109     if (h1->flags & HEAD_GROUP) --i;
110     if (h2->flags & HEAD_GROUP) ++i;
111     if (i) return i;
112
113     return h1->monitor - h2->monitor;
114 }
115
116 gint cmp_background(const void *a, const void *b)
117 {
118     const ObPlaceHead *h1 = a;
119     const ObPlaceHead *h2 = b;
120     gint i = 0;
121
122     if (h1->monitor == h2->monitor) return 0;
123
124     if (h1->flags & HEAD_PERAPP) --i;
125     if (h2->flags & HEAD_PERAPP) ++i;
126     if (i) return i;
127
128     if (h1->flags & HEAD_PARENT) --i;
129     if (h2->flags & HEAD_PARENT) ++i;
130     if (i) return i;
131
132     if (h1->flags & HEAD_GROUP_DESK || h2->flags & HEAD_GROUP_DESK) {
133         if (h1->flags & HEAD_GROUP_DESK) --i;
134         if (h2->flags & HEAD_GROUP_DESK) ++i;
135         if (i) return i;
136         if (h1->flags & HEAD_PRIMARY) --i;
137         if (h2->flags & HEAD_PRIMARY) ++i;
138         if (i) return i;
139     }
140
141     if (h1->flags & HEAD_GROUP || h2->flags & HEAD_GROUP) {
142         if (h1->flags & HEAD_GROUP) --i;
143         if (h2->flags & HEAD_GROUP) ++i;
144         if (i) return i;
145         if (h1->flags & HEAD_PRIMARY) --i;
146         if (h2->flags & HEAD_PRIMARY) ++i;
147         if (i) return i;
148     }
149
150     if (h1->flags & HEAD_PRIMARY) --i;
151     if (h2->flags & HEAD_PRIMARY) ++i;
152     if (i) return i;
153
154     return h1->monitor - h2->monitor;
155 }
156
157 /*! Pick a monitor to place a window on. */
158 static Rect *pick_head(ObClient *c, gboolean foreground,
159                        ObAppSettings *settings)
160 {
161     Rect *area;
162     ObPlaceHead *choice;
163     guint i;
164     ObClient *p;
165     GSList *it;
166
167     choice = g_new(ObPlaceHead, screen_num_monitors);
168     for (i = 0; i < screen_num_monitors; ++i) {
169         choice[i].monitor = i;
170         choice[i].flags = 0;
171     }
172
173     /* find monitors with group members */
174     if (c->group) {
175         for (it = c->group->members; it; it = g_slist_next(it)) {
176             ObClient *itc = it->data;
177             if (itc != c) {
178                 guint m = client_monitor(itc);
179
180                 if (m < screen_num_monitors) {
181                     if (screen_compare_desktops(itc->desktop, c->desktop))
182                         choice[m].flags |= HEAD_GROUP_DESK;
183                     else
184                         choice[m].flags |= HEAD_GROUP;
185                 }
186             }
187         }
188     }
189
190     i = screen_monitor_primary(FALSE);
191     if (i < screen_num_monitors) {
192         choice[i].flags |= HEAD_PRIMARY;
193         if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
194             choice[i].flags |= HEAD_PLACED;
195         if (settings &&
196             settings->monitor_type == OB_PLACE_MONITOR_PRIMARY)
197             choice[i].flags |= HEAD_PERAPP;
198     }
199
200     i = screen_monitor_active();
201     if (i < screen_num_monitors) {
202         if (config_place_monitor == OB_PLACE_MONITOR_ACTIVE)
203             choice[i].flags |= HEAD_PLACED;
204         if (settings &&
205             settings->monitor_type == OB_PLACE_MONITOR_ACTIVE)
206             choice[i].flags |= HEAD_PERAPP;
207     }
208
209     i = screen_monitor_pointer();
210     if (i < screen_num_monitors) {
211         if (config_place_monitor == OB_PLACE_MONITOR_MOUSE)
212             choice[i].flags |= HEAD_PLACED;
213         if (settings &&
214             settings->monitor_type == OB_PLACE_MONITOR_MOUSE)
215             choice[i].flags |= HEAD_PERAPP;
216     }
217
218     if (settings) {
219         i = settings->monitor - 1;
220         if (i < screen_num_monitors)
221             choice[i].flags |= HEAD_PERAPP;
222     }
223
224     /* direct parent takes highest precedence */
225     if ((p = client_direct_parent(c))) {
226         i = client_monitor(p);
227         if (i < screen_num_monitors)
228             choice[i].flags |= HEAD_PARENT;
229     }
230
231     qsort(choice, screen_num_monitors, sizeof(ObPlaceHead),
232           foreground ? cmp_foreground : cmp_background);
233
234     /* save the areas of the monitors in order of their being chosen */
235     for (i = 0; i < screen_num_monitors; ++i)
236     {
237         ob_debug("placement choice %d is monitor %d", i, choice[i].monitor);
238         if (choice[i].flags & HEAD_PARENT)
239             ob_debug("  - parent on monitor");
240         if (choice[i].flags & HEAD_PLACED)
241             ob_debug("  - placement choice");
242         if (choice[i].flags & HEAD_PRIMARY)
243             ob_debug("  - primary monitor");
244         if (choice[i].flags & HEAD_GROUP_DESK)
245             ob_debug("  - group on same desktop");
246         if (choice[i].flags & HEAD_GROUP)
247             ob_debug("  - group on other desktop");
248     }
249
250     area = screen_area(c->desktop, choice[0].monitor, NULL);
251
252     g_free(choice);
253
254     /* return the area for the chosen monitor */
255     return area;
256 }
257
258 static gboolean place_random(ObClient *client, Rect *area, gint *x, gint *y)
259 {
260     gint l, r, t, b;
261
262     ob_debug("placing randomly");
263
264     l = area->x;
265     t = area->y;
266     r = area->x + area->width - client->frame->area.width;
267     b = area->y + area->height - client->frame->area.height;
268
269     if (r > l) *x = g_random_int_range(l, r + 1);
270     else       *x = area->x;
271     if (b > t) *y = g_random_int_range(t, b + 1);
272     else       *y = area->y;
273
274     return TRUE;
275 }
276
277 static GSList* area_add(GSList *list, Rect *a)
278 {
279     Rect *r = g_slice_new(Rect);
280     *r = *a;
281     return g_slist_prepend(list, r);
282 }
283
284 static GSList* area_remove(GSList *list, Rect *a)
285 {
286     GSList *sit;
287     GSList *result = NULL;
288
289     for (sit = list; sit; sit = g_slist_next(sit)) {
290         Rect *r = sit->data;
291
292         if (!RECT_INTERSECTS_RECT(*r, *a)) {
293             result = g_slist_prepend(result, r);
294             /* dont free r, it's moved to the result list */
295         } else {
296             Rect isect, extra;
297
298             /* Use an intersection of a and r to determine the space
299                around r that we can use.
300
301                NOTE: the spaces calculated can overlap.
302             */
303
304             RECT_SET_INTERSECTION(isect, *r, *a);
305
306             if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
307                 RECT_SET(extra, r->x, r->y,
308                          RECT_LEFT(isect) - r->x, r->height);
309                 result = area_add(result, &extra);
310             }
311
312             if (RECT_TOP(isect) > RECT_TOP(*r)) {
313                 RECT_SET(extra, r->x, r->y,
314                          r->width, RECT_TOP(isect) - r->y + 1);
315                 result = area_add(result, &extra);
316             }
317
318             if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
319                 RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
320                          RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
321                 result = area_add(result, &extra);
322             }
323
324             if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
325                 RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
326                          r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
327                 result = area_add(result, &extra);
328             }
329
330             /* 'r' is not being added to the result list, so free it */
331             g_slice_free(Rect, r);
332         }
333     }
334     g_slist_free(list);
335     return result;
336 }
337
338 enum {
339     IGNORE_FULLSCREEN = 1,
340     IGNORE_MAXIMIZED  = 2,
341     IGNORE_MENUTOOL   = 3,
342     /*IGNORE_SHADED     = 3,*/
343     IGNORE_NONGROUP   = 4,
344     IGNORE_BELOW      = 5,
345     /*IGNORE_NONFOCUS   = 1 << 5,*/
346     IGNORE_DOCK       = 6,
347     IGNORE_END        = 7
348 };
349
350 static gboolean place_nooverlap(ObClient *c, Rect *area, gint *x, gint *y)
351 {
352     gint ignore;
353     gboolean ret;
354     gint maxsize;
355     GSList *spaces = NULL, *sit, *maxit;
356
357     ob_debug("placing nonoverlap");
358
359     ret = FALSE;
360     maxsize = 0;
361     maxit = NULL;
362
363     /* try ignoring different things to find empty space */
364     for (ignore = 0; ignore < IGNORE_END && !ret; ignore++) {
365         GList *it;
366
367         /* add the whole monitor */
368         spaces = area_add(spaces, area);
369
370         /* go thru all the windows */
371         for (it = client_list; it; it = g_list_next(it)) {
372             ObClient *test = it->data;
373
374             /* should we ignore this client? */
375             if (screen_showing_desktop) continue;
376             if (c == test) continue;
377             if (test->iconic) continue;
378             if (c->desktop != DESKTOP_ALL) {
379                 if (test->desktop != c->desktop &&
380                     test->desktop != DESKTOP_ALL) continue;
381             } else {
382                 if (test->desktop != screen_desktop &&
383                     test->desktop != DESKTOP_ALL) continue;
384             }
385             if (test->type == OB_CLIENT_TYPE_SPLASH ||
386                 test->type == OB_CLIENT_TYPE_DESKTOP) continue;
387
388
389             if ((ignore >= IGNORE_FULLSCREEN) &&
390                 test->fullscreen) continue;
391             if ((ignore >= IGNORE_MAXIMIZED) &&
392                 test->max_horz && test->max_vert) continue;
393             if ((ignore >= IGNORE_MENUTOOL) &&
394                 (test->type == OB_CLIENT_TYPE_MENU ||
395                  test->type == OB_CLIENT_TYPE_TOOLBAR) &&
396                 client_has_parent(c)) continue;
397             /*
398               if ((ignore >= IGNORE_SHADED) &&
399               test->shaded) continue;
400             */
401             if ((ignore >= IGNORE_NONGROUP) &&
402                 client_has_group_siblings(c) &&
403                 test->group != c->group) continue;
404             if ((ignore >= IGNORE_BELOW) &&
405                 test->layer < c->layer) continue;
406             /*
407               if ((ignore >= IGNORE_NONFOCUS) &&
408               focus_client != test) continue;
409             */
410             /* don't ignore this window, so remove it from the available
411                area */
412             spaces = area_remove(spaces, &test->frame->area);
413         }
414
415         if (ignore < IGNORE_DOCK) {
416             Rect a;
417             dock_get_area(&a);
418             spaces = area_remove(spaces, &a);
419         }
420
421         for (sit = spaces; sit; sit = g_slist_next(sit)) {
422             Rect *r = sit->data;
423
424             if (r->width >= c->frame->area.width &&
425                 r->height >= c->frame->area.height &&
426                 r->width * r->height > maxsize)
427             {
428                 maxsize = r->width * r->height;
429                 maxit = sit;
430             }
431         }
432
433         if (maxit) {
434             Rect *r = maxit->data;
435
436             /* center it in the area */
437             *x = r->x;
438             *y = r->y;
439             if (config_place_center) {
440                 *x += (r->width - c->frame->area.width) / 2;
441                 *y += (r->height - c->frame->area.height) / 2;
442             }
443             ret = TRUE;
444         }
445
446         while (spaces) {
447             g_slice_free(Rect, spaces->data);
448             spaces = g_slist_delete_link(spaces, spaces);
449         }
450     }
451
452     return ret;
453 }
454
455 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
456 {
457     gint l, r, t, b;
458     gint px, py;
459     Rect *area;
460
461     ob_debug("placing under mouse");
462
463     if (!screen_pointer_pos(&px, &py))
464         return FALSE;
465     area = pick_pointer_head(client);
466
467     l = area->x;
468     t = area->y;
469     r = area->x + area->width - client->frame->area.width;
470     b = area->y + area->height - client->frame->area.height;
471
472     *x = px - client->area.width / 2 - client->frame->size.left;
473     *x = MIN(MAX(*x, l), r);
474     *y = py - client->area.height / 2 - client->frame->size.top;
475     *y = MIN(MAX(*y, t), b);
476
477     g_slice_free(Rect, area);
478
479     return TRUE;
480 }
481
482 static gboolean place_per_app_setting(ObClient *client, Rect *screen,
483                                       gint *x, gint *y,
484                                       ObAppSettings *settings)
485 {
486     if (!settings || (settings && !settings->pos_given))
487         return FALSE;
488
489     ob_debug("placing by per-app settings");
490
491     if (settings->position.x.center)
492         *x = screen->x + screen->width / 2 - client->area.width / 2;
493     else if (settings->position.x.opposite)
494         *x = screen->x + screen->width - client->frame->area.width -
495             settings->position.x.pos;
496     else
497         *x = screen->x + settings->position.x.pos;
498     if (settings->position.x.denom)
499         *x = (*x * screen->width) / settings->position.x.denom;
500
501     if (settings->position.y.center)
502         *y = screen->y + screen->height / 2 - client->area.height / 2;
503     else if (settings->position.y.opposite)
504         *y = screen->y + screen->height - client->frame->area.height -
505             settings->position.y.pos;
506     else
507         *y = screen->y + settings->position.y.pos;
508     if (settings->position.y.denom)
509         *y = (*y * screen->height) / settings->position.y.denom;
510
511     return TRUE;
512 }
513
514 static gboolean place_transient_splash(ObClient *client, Rect *area,
515                                        gint *x, gint *y)
516 {
517     if (client->type == OB_CLIENT_TYPE_DIALOG) {
518         GSList *it;
519         gboolean first = TRUE;
520         gint l, r, t, b;
521
522         ob_debug("placing dialog");
523
524         for (it = client->parents; it; it = g_slist_next(it)) {
525             ObClient *m = it->data;
526             if (!m->iconic) {
527                 if (first) {
528                     l = RECT_LEFT(m->frame->area);
529                     t = RECT_TOP(m->frame->area);
530                     r = RECT_RIGHT(m->frame->area);
531                     b = RECT_BOTTOM(m->frame->area);
532                     first = FALSE;
533                 } else {
534                     l = MIN(l, RECT_LEFT(m->frame->area));
535                     t = MIN(t, RECT_TOP(m->frame->area));
536                     r = MAX(r, RECT_RIGHT(m->frame->area));
537                     b = MAX(b, RECT_BOTTOM(m->frame->area));
538                 }
539             }
540             if (!first) {
541                 *x = ((r + 1 - l) - client->frame->area.width) / 2 + l;
542                 *y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
543                 return TRUE;
544             }
545         }
546     }
547
548     if (client->type == OB_CLIENT_TYPE_DIALOG ||
549         client->type == OB_CLIENT_TYPE_SPLASH)
550     {
551         ob_debug("placing dialog or splash");
552
553         *x = (area->width - client->frame->area.width) / 2 + area->x;
554         *y = (area->height - client->frame->area.height) / 2 + area->y;
555         return TRUE;
556     }
557
558     return FALSE;
559 }
560
561 /*! Return TRUE if openbox chose the position for the window, and FALSE if
562   the application chose it */
563 gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
564                       ObAppSettings *settings)
565 {
566     Rect *area;
567     gboolean ret;
568
569     /* per-app settings override program specified position
570      * but not user specified, unless pos_force is enabled */
571     if (((client->positioned & USPosition) &&
572          !(settings && settings->pos_given && settings->pos_force)) ||
573         ((client->positioned & PPosition) &&
574          !(settings && settings->pos_given)))
575         return FALSE;
576
577     area = pick_head(client, foreground, settings);
578
579     /* try a number of methods */
580     ret = place_per_app_setting(client, area, x, y, settings) ||
581         place_transient_splash(client, area, x, y) ||
582         (config_place_policy == OB_PLACE_POLICY_MOUSE &&
583          place_under_mouse(client, x, y)) ||
584         place_nooverlap(client, area, x, y) ||
585         place_random(client, area, x, y);
586     g_assert(ret);
587
588     g_slice_free(Rect, area);
589
590     /* get where the client should be */
591     frame_frame_gravity(client->frame, x, y);
592     return TRUE;
593 }