1
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
3
/* Marco window placement */
6
* Copyright (C) 2001 Havoc Pennington
7
* Copyright (C) 2002, 2003 Red Hat, Inc.
8
* Copyright (C) 2003 Rob Adams
9
* Copyright (C) 2005 Elijah Newren
11
* This program is free software; you can redistribute it and/or
12
* modify it under the terms of the GNU General Public License as
13
* published by the Free Software Foundation; either version 2 of the
14
* License, or (at your option) any later version.
16
* This program is distributed in the hope that it will be useful, but
17
* WITHOUT ANY WARRANTY; without even the implied warranty of
18
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
* General Public License for more details.
21
* You should have received a copy of the GNU General Public License
22
* along with this program; if not, write to the Free Software
23
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
30
#include "workspace.h"
42
} MetaWindowDirection;
45
northwestcmp (gconstpointer a, gconstpointer b)
47
MetaWindow *aw = (gpointer) a;
48
MetaWindow *bw = (gpointer) b;
53
/* we're interested in the frame position for cascading,
54
* not meta_window_get_position()
58
ax = aw->frame->rect.x;
59
ay = aw->frame->rect.y;
69
bx = bw->frame->rect.x;
70
by = bw->frame->rect.y;
78
/* probably there's a fast good-enough-guess we could use here. */
79
from_origin_a = sqrt (ax * ax + ay * ay);
80
from_origin_b = sqrt (bx * bx + by * by);
82
if (from_origin_a < from_origin_b)
84
else if (from_origin_a > from_origin_b)
91
find_next_cascade (MetaWindow *window,
92
MetaFrameGeometry *fgeom,
93
/* visible windows on relevant workspaces */
102
int cascade_x, cascade_y;
103
int x_threshold, y_threshold;
104
int window_width, window_height;
106
MetaRectangle work_area;
107
const MetaXineramaScreenInfo* current;
109
sorted = g_list_copy (windows);
110
sorted = g_list_sort (sorted, northwestcmp);
112
/* This is a "fuzzy" cascade algorithm.
113
* For each window in the list, we find where we'd cascade a
114
* new window after it. If a window is already nearly at that
115
* position, we move on.
118
/* arbitrary-ish threshold, honors user attempts to
121
#define CASCADE_FUZZ 15
124
x_threshold = MAX (fgeom->left_width, CASCADE_FUZZ);
125
y_threshold = MAX (fgeom->top_height, CASCADE_FUZZ);
129
x_threshold = CASCADE_FUZZ;
130
y_threshold = CASCADE_FUZZ;
133
/* Find furthest-SE origin of all workspaces.
134
* cascade_x, cascade_y are the target position
135
* of NW corner of window frame.
138
current = meta_screen_get_current_xinerama (window->screen);
139
meta_window_get_work_area_for_xinerama (window, current->number, &work_area);
141
cascade_x = MAX (0, work_area.x);
142
cascade_y = MAX (0, work_area.y);
144
/* Find first cascade position that's not used. */
146
window_width = window->frame ? window->frame->rect.width : window->rect.width;
147
window_height = window->frame ? window->frame->rect.height : window->rect.height;
158
/* we want frame position, not window position */
161
wx = w->frame->rect.x;
162
wy = w->frame->rect.y;
170
if (ABS (wx - cascade_x) < x_threshold &&
171
ABS (wy - cascade_y) < y_threshold)
173
/* This window is "in the way", move to next cascade
174
* point. The new window frame should go at the origin
175
* of the client window we're stacking above.
177
meta_window_get_position (w, &wx, &wy);
181
/* If we go off the screen, start over with a new cascade */
182
if (((cascade_x + window_width) >
183
(work_area.x + work_area.width)) ||
184
((cascade_y + window_height) >
185
(work_area.y + work_area.height)))
187
cascade_x = MAX (0, work_area.x);
188
cascade_y = MAX (0, work_area.y);
190
#define CASCADE_INTERVAL 50 /* space between top-left corners of cascades */
192
cascade_x += CASCADE_INTERVAL * cascade_stage;
194
/* start over with a new cascade translated to the right, unless
195
* we are out of space
197
if ((cascade_x + window_width) <
198
(work_area.x + work_area.width))
205
/* All out of space, this cascade_x won't work */
206
cascade_x = MAX (0, work_area.x);
213
/* Keep searching for a further-down-the-diagonal window. */
219
/* cascade_x and cascade_y will match the last window in the list
220
* that was "in the way" (in the approximate cascade diagonal)
223
g_list_free (sorted);
225
/* Convert coords to position of window, not position of frame. */
233
*new_x = cascade_x + fgeom->left_width;
234
*new_y = cascade_y + fgeom->top_height;
239
find_most_freespace (MetaWindow *window,
240
MetaFrameGeometry *fgeom,
241
/* visible windows on relevant workspaces */
242
MetaWindow *focus_window,
248
MetaWindowDirection side;
250
int max_width, max_height, left, right, top, bottom;
251
int left_space, right_space, top_space, bottom_space;
252
int frame_size_left, frame_size_top;
253
MetaRectangle work_area;
257
frame_size_left = fgeom ? fgeom->left_width : 0;
258
frame_size_top = fgeom ? fgeom->top_height : 0;
260
meta_window_get_work_area_current_xinerama (focus_window, &work_area);
261
meta_window_get_outer_rect (focus_window, &avoid);
262
meta_window_get_outer_rect (window, &outer);
264
/* Find the areas of choosing the various sides of the focus window */
265
max_width = MIN (avoid.width, outer.width);
266
max_height = MIN (avoid.height, outer.height);
267
left_space = avoid.x - work_area.x;
268
right_space = work_area.width - (avoid.x + avoid.width - work_area.x);
269
top_space = avoid.y - work_area.y;
270
bottom_space = work_area.height - (avoid.y + avoid.height - work_area.y);
271
left = MIN (left_space, outer.width);
272
right = MIN (right_space, outer.width);
273
top = MIN (top_space, outer.height);
274
bottom = MIN (bottom_space, outer.height);
276
/* Find out which side of the focus_window can show the most of the window */
278
max_area = left*max_height;
279
if (right*max_height > max_area)
282
max_area = right*max_height;
284
if (top*max_width > max_area)
287
max_area = top*max_width;
289
if (bottom*max_width > max_area)
292
max_area = bottom*max_width;
295
/* Give up if there's no where to put it (i.e. focus window is maximized) */
299
/* Place the window on the relevant side; if the whole window fits,
300
* make it adjacent to the focus window; if not, make sure the
301
* window doesn't go off the edge of the screen.
306
*new_y = avoid.y + frame_size_top;
307
if (left_space > outer.width)
308
*new_x = avoid.x - outer.width + frame_size_left;
310
*new_x = work_area.x + frame_size_left;
313
*new_y = avoid.y + frame_size_top;
314
if (right_space > outer.width)
315
*new_x = avoid.x + avoid.width + frame_size_left;
317
*new_x = work_area.x + work_area.width - outer.width + frame_size_left;
320
*new_x = avoid.x + frame_size_left;
321
if (top_space > outer.height)
322
*new_y = avoid.y - outer.height + frame_size_top;
324
*new_y = work_area.y + frame_size_top;
327
*new_x = avoid.x + frame_size_left;
328
if (bottom_space > outer.height)
329
*new_y = avoid.y + avoid.height + frame_size_top;
331
*new_y = work_area.y + work_area.height - outer.height + frame_size_top;
337
avoid_being_obscured_as_second_modal_dialog (MetaWindow *window,
338
MetaFrameGeometry *fgeom,
342
/* We can't center this dialog if it was denied focus and it
343
* overlaps with the focus window and this dialog is modal and this
344
* dialog is in the same app as the focus window (*phew*...please
345
* don't make me say that ten times fast). See bug 307875 comment 11
346
* and 12 for details, but basically it means this is probably a
347
* second modal dialog for some app while the focus window is the
348
* first modal dialog. We should probably make them simultaneously
349
* visible in general, but it becomes mandatory to do so due to
350
* buggy apps (e.g. those using gtk+ *sigh*) because in those cases
351
* this second modal dialog also happens to be modal to the first
352
* dialog in addition to the main window, while it has only let us
353
* know about the modal-to-the-main-window part.
356
MetaWindow *focus_window;
357
MetaRectangle overlap;
359
focus_window = window->display->focus_window;
361
if (window->denied_focus_and_not_transient &&
362
window->wm_state_modal && /* FIXME: Maybe do this for all transients? */
363
meta_window_same_application (window, focus_window) &&
364
meta_rectangle_intersect (&window->rect,
368
find_most_freespace (window, fgeom, focus_window, *x, *y, x, y);
369
meta_topic (META_DEBUG_PLACEMENT,
370
"Dialog window %s was denied focus but may be modal "
371
"to the focus window; had to move it to avoid the "
378
rectangle_overlaps_some_window (MetaRectangle *rect,
387
MetaWindow *other = tmp->data;
388
MetaRectangle other_rect;
392
case META_WINDOW_DOCK:
393
case META_WINDOW_SPLASHSCREEN:
394
case META_WINDOW_DESKTOP:
395
case META_WINDOW_DIALOG:
396
case META_WINDOW_MODAL_DIALOG:
399
case META_WINDOW_NORMAL:
400
case META_WINDOW_UTILITY:
401
case META_WINDOW_TOOLBAR:
402
case META_WINDOW_MENU:
403
meta_window_get_outer_rect (other, &other_rect);
405
if (meta_rectangle_intersect (rect, &other_rect, &dest))
417
leftmost_cmp (gconstpointer a, gconstpointer b)
419
MetaWindow *aw = (gpointer) a;
420
MetaWindow *bw = (gpointer) b;
423
/* we're interested in the frame position for cascading,
424
* not meta_window_get_position()
427
ax = aw->frame->rect.x;
432
bx = bw->frame->rect.x;
445
topmost_cmp (gconstpointer a, gconstpointer b)
447
MetaWindow *aw = (gpointer) a;
448
MetaWindow *bw = (gpointer) b;
451
/* we're interested in the frame position for cascading,
452
* not meta_window_get_position()
455
ay = aw->frame->rect.y;
460
by = bw->frame->rect.y;
473
center_tile_rect_in_area (MetaRectangle *rect,
474
MetaRectangle *work_area)
478
/* The point here is to tile a window such that "extra"
479
* space is equal on either side (i.e. so a full screen
480
* of windows tiled this way would center the windows
484
fluff = (work_area->width % (rect->width+1)) / 2;
485
rect->x = work_area->x + fluff;
486
fluff = (work_area->height % (rect->height+1)) / 3;
487
rect->y = work_area->y + fluff;
491
center_rect_in_area (MetaRectangle *rect,
492
MetaRectangle *work_area)
494
rect->x = work_area->x + ((work_area->width - rect->width) / 2);
495
rect->y = work_area->y + ((work_area->height - rect->height) / 2);
498
/* Find the leftmost, then topmost, empty area on the workspace
499
* that can contain the new window.
501
* Cool feature to have: if we can't fit the current window size,
502
* try shrinking the window (within geometry constraints). But
503
* beware windows such as Emacs with no sane minimum size, we
504
* don't want to create a 1x1 Emacs.
507
find_first_fit (MetaWindow *window,
508
MetaFrameGeometry *fgeom,
509
/* visible windows on relevant workspaces */
517
/* This algorithm is limited - it just brute-force tries
518
* to fit the window in a small number of locations that are aligned
519
* with existing windows. It tries to place the window on
520
* the bottom of each existing window, and then to the right
521
* of each existing window, aligned with the left/top of the
522
* existing window in each of those cases.
529
MetaRectangle work_area;
533
/* Below each window */
534
below_sorted = g_list_copy (windows);
535
below_sorted = g_list_sort (below_sorted, leftmost_cmp);
536
below_sorted = g_list_sort (below_sorted, topmost_cmp);
538
/* To the right of each window */
539
right_sorted = g_list_copy (windows);
540
right_sorted = g_list_sort (right_sorted, topmost_cmp);
541
right_sorted = g_list_sort (right_sorted, leftmost_cmp);
543
rect.width = window->rect.width;
544
rect.height = window->rect.height;
548
rect.width += fgeom->left_width + fgeom->right_width;
549
rect.height += fgeom->top_height + fgeom->bottom_height;
552
#ifdef WITH_VERBOSE_MODE
554
char xinerama_location_string[RECT_LENGTH];
555
meta_rectangle_to_string (&window->screen->xinerama_infos[xinerama].rect,
556
xinerama_location_string);
557
meta_topic (META_DEBUG_XINERAMA,
558
"Natural xinerama is %s\n",
559
xinerama_location_string);
563
meta_window_get_work_area_for_xinerama (window, xinerama, &work_area);
565
if (meta_prefs_get_center_new_windows ())
566
center_rect_in_area (&rect, &work_area);
568
center_tile_rect_in_area (&rect, &work_area);
570
if (meta_rectangle_contains_rect (&work_area, &rect) &&
571
(meta_prefs_get_center_new_windows () ||
572
!rectangle_overlaps_some_window (&rect, windows)))
578
*new_x += fgeom->left_width;
579
*new_y += fgeom->top_height;
587
/* try below each window */
591
MetaWindow *w = tmp->data;
592
MetaRectangle outer_rect;
594
meta_window_get_outer_rect (w, &outer_rect);
596
rect.x = outer_rect.x;
597
rect.y = outer_rect.y + outer_rect.height;
599
if (meta_rectangle_contains_rect (&work_area, &rect) &&
600
!rectangle_overlaps_some_window (&rect, below_sorted))
606
*new_x += fgeom->left_width;
607
*new_y += fgeom->top_height;
618
/* try to the right of each window */
622
MetaWindow *w = tmp->data;
623
MetaRectangle outer_rect;
625
meta_window_get_outer_rect (w, &outer_rect);
627
rect.x = outer_rect.x + outer_rect.width;
628
rect.y = outer_rect.y;
630
if (meta_rectangle_contains_rect (&work_area, &rect) &&
631
!rectangle_overlaps_some_window (&rect, right_sorted))
637
*new_x += fgeom->left_width;
638
*new_y += fgeom->top_height;
651
g_list_free (below_sorted);
652
g_list_free (right_sorted);
657
meta_window_place (MetaWindow *window,
658
MetaFrameGeometry *fgeom,
665
const MetaXineramaScreenInfo *xi;
667
/* frame member variables should NEVER be used in here, only
668
* MetaFrameGeometry. But remember fgeom == NULL
669
* for undecorated windows. Also, this function should
670
* NEVER have side effects other than computing the
671
* placement coordinates.
674
meta_topic (META_DEBUG_PLACEMENT, "Placing window %s\n", window->desc);
678
switch (window->type)
680
/* Run placement algorithm on these. */
681
case META_WINDOW_NORMAL:
682
case META_WINDOW_DIALOG:
683
case META_WINDOW_MODAL_DIALOG:
684
case META_WINDOW_SPLASHSCREEN:
687
/* Assume the app knows best how to place these, no placement
688
* algorithm ever (other than "leave them as-is")
690
case META_WINDOW_DESKTOP:
691
case META_WINDOW_DOCK:
692
case META_WINDOW_TOOLBAR:
693
case META_WINDOW_MENU:
694
case META_WINDOW_UTILITY:
695
goto done_no_constraints;
698
if (meta_prefs_get_disable_workarounds ())
700
switch (window->type)
702
/* Only accept USPosition on normal windows because the app is full
703
* of shit claiming the user set -geometry for a dialog or dock
705
case META_WINDOW_NORMAL:
706
if (window->size_hints.flags & USPosition)
708
/* don't constrain with placement algorithm */
709
meta_topic (META_DEBUG_PLACEMENT,
710
"Honoring USPosition for %s instead of using placement algorithm\n", window->desc);
716
/* Ignore even USPosition on dialogs, splashscreen */
717
case META_WINDOW_DIALOG:
718
case META_WINDOW_MODAL_DIALOG:
719
case META_WINDOW_SPLASHSCREEN:
722
/* Assume the app knows best how to place these. */
723
case META_WINDOW_DESKTOP:
724
case META_WINDOW_DOCK:
725
case META_WINDOW_TOOLBAR:
726
case META_WINDOW_MENU:
727
case META_WINDOW_UTILITY:
728
if (window->size_hints.flags & PPosition)
730
meta_topic (META_DEBUG_PLACEMENT,
731
"Not placing non-normal non-dialog window with PPosition set\n");
732
goto done_no_constraints;
739
/* workarounds enabled */
741
if ((window->size_hints.flags & PPosition) ||
742
(window->size_hints.flags & USPosition))
744
meta_topic (META_DEBUG_PLACEMENT,
745
"Not placing window with PPosition or USPosition set\n");
746
avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y);
747
goto done_no_constraints;
751
if ((window->type == META_WINDOW_DIALOG ||
752
window->type == META_WINDOW_MODAL_DIALOG) &&
753
window->xtransient_for != None)
755
/* Center horizontally, at top of parent vertically */
760
meta_display_lookup_x_window (window->display,
761
window->xtransient_for);
767
meta_window_get_position (parent, &x, &y);
768
w = parent->rect.width;
770
/* center of parent */
772
/* center of child over center of parent */
773
x -= window->rect.width / 2;
775
/* "visually" center window over parent, leaving twice as
776
* much space below as on top.
778
y += (parent->rect.height - window->rect.height)/3;
780
/* put top of child's frame, not top of child's client */
782
y += fgeom->top_height;
784
meta_topic (META_DEBUG_PLACEMENT, "Centered window %s over transient parent\n",
787
avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y);
793
/* FIXME UTILITY with transient set should be stacked up
794
* on the sides of the parent window or something.
797
if (window->type == META_WINDOW_DIALOG ||
798
window->type == META_WINDOW_MODAL_DIALOG ||
799
window->type == META_WINDOW_SPLASHSCREEN)
801
/* Center on current xinerama (i.e. on current monitor) */
804
/* Warning, this function is a round trip! */
805
xi = meta_screen_get_current_xinerama (window->screen);
810
x = (w - window->rect.width) / 2;
811
y = (h - window->rect.height) / 2;
816
meta_topic (META_DEBUG_PLACEMENT, "Centered window %s on screen %d xinerama %d\n",
817
window->desc, window->screen->number, xi->number);
819
goto done_check_denied_focus;
822
/* Find windows that matter (not minimized, on same workspace
823
* as placed window, may be shaded - if shaded we pretend it isn't
824
* for placement purposes)
830
all_windows = meta_display_list_windows (window->display);
835
MetaWindow *w = tmp->data;
837
if (meta_window_showing_on_its_workspace (w) &&
839
(window->workspace == w->workspace ||
840
window->on_all_workspaces || w->on_all_workspaces))
841
windows = g_list_prepend (windows, w);
846
g_slist_free (all_windows);
849
/* Warning, this is a round trip! */
850
xi = meta_screen_get_current_xinerama (window->screen);
852
/* "Origin" placement algorithm */
856
if (find_first_fit (window, fgeom, windows,
859
goto done_check_denied_focus;
861
/* Maximize windows if they are too big for their work area (bit of
862
* a hack here). Assume undecorated windows probably don't intend to
865
if (window->has_maximize_func && window->decorated &&
868
MetaRectangle workarea;
871
meta_window_get_work_area_for_xinerama (window,
874
meta_window_get_outer_rect (window, &outer);
876
/* If the window is bigger than the screen, then automaximize. Do NOT
877
* auto-maximize the directions independently. See #419810.
879
if (outer.width >= workarea.width && outer.height >= workarea.height)
881
window->maximize_horizontally_after_placement = TRUE;
882
window->maximize_vertically_after_placement = TRUE;
886
/* If no placement has been done, revert to cascade to avoid
887
* fully overlapping window (e.g. starting multiple terminals)
889
if (!meta_prefs_get_center_new_windows() && (x == xi->rect.x && y == xi->rect.y))
890
find_next_cascade (window, fgeom, windows, x, y, &x, &y);
892
done_check_denied_focus:
893
/* If the window is being denied focus and isn't a transient of the
894
* focus window, we do NOT want it to overlap with the focus window
895
* if at all possible. This is guaranteed to only be called if the
896
* focus_window is non-NULL, and we try to avoid that window.
898
if (window->denied_focus_and_not_transient)
901
MetaWindow *focus_window;
902
MetaRectangle overlap;
904
focus_window = window->display->focus_window;
905
g_assert (focus_window != NULL);
907
/* No need to do anything if the window doesn't overlap at all */
908
found_fit = !meta_rectangle_intersect (&window->rect,
912
/* Try to do a first fit again, this time only taking into account the
915
if (!meta_prefs_get_center_new_windows() && !found_fit)
917
GList *focus_window_list;
918
focus_window_list = g_list_prepend (NULL, focus_window);
920
/* Reset x and y ("origin" placement algorithm) */
924
found_fit = find_first_fit (window, fgeom, focus_window_list,
927
g_list_free (focus_window_list);
930
/* If that still didn't work, just place it where we can see as much
934
find_most_freespace (window, fgeom, focus_window, x, y, &x, &y);
938
g_list_free (windows);