1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
ash / wm / window_positioner.cc [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/window_positioner.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/ranges/algorithm.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace ash::window_positioner {
namespace {
// The time in milliseconds which should be used to visually move a window
// through an automatic "intelligent" window management option.
constexpr int kWindowAutoMoveDurationMS = 125;
// If set to true all window repositioning actions will be ignored. Set through
// WindowPositioner::SetIgnoreActivations().
static bool disable_auto_positioning = false;
constexpr int kWindowOffset = 32;
// Check if any management should be performed (with a given |window|).
bool UseAutoWindowManager(const aura::Window* window) {
if (disable_auto_positioning)
return false;
const WindowState* window_state = WindowState::Get(window);
return !window_state->is_dragged() &&
window_state->GetWindowPositionManaged();
}
// Check if a given |window| can be managed. This includes that its
// state is not minimized/maximized/fullscreen/the user has changed
// its size by hand already. It furthermore checks for the
// WindowIsManaged status.
bool WindowPositionCanBeManaged(const aura::Window* window) {
if (disable_auto_positioning)
return false;
const WindowState* window_state = WindowState::Get(window);
return window_state->GetWindowPositionManaged() &&
!window_state->IsMinimized() && !window_state->IsMaximized() &&
!window_state->IsFullscreen() && !window_state->IsPinned() &&
!window_state->bounds_changed_by_user();
}
// Move the given |bounds| on the available |work_area| in the direction
// indicated by |move_right|. If |move_right| is true, the rectangle gets moved
// to the right edge, otherwise to the left one.
bool MoveRectToOneSide(const gfx::Rect& work_area,
bool move_right,
gfx::Rect* bounds) {
if (move_right) {
if (work_area.right() > bounds->right()) {
bounds->set_x(work_area.right() - bounds->width());
return true;
}
} else {
if (work_area.x() < bounds->x()) {
bounds->set_x(work_area.x());
return true;
}
}
return false;
}
// Move a |window| to new |bounds|. Animate if desired by user.
// Moves the transient children of the |window| as well by the same |offset| as
// the parent |window|.
void SetBoundsAndOffsetTransientChildren(aura::Window* window,
const gfx::Rect& bounds,
const gfx::Rect& work_area,
const gfx::Vector2d& offset) {
aura::Window::Windows transient_children = ::wm::GetTransientChildren(window);
for (aura::Window* transient_child : transient_children) {
gfx::Rect child_bounds = transient_child->bounds();
gfx::Rect new_child_bounds = child_bounds + offset;
if ((child_bounds.x() <= work_area.x() &&
new_child_bounds.x() <= work_area.x()) ||
(child_bounds.right() >= work_area.right() &&
new_child_bounds.right() >= work_area.right())) {
continue;
}
if (new_child_bounds.right() > work_area.right())
new_child_bounds.set_x(work_area.right() - bounds.width());
else if (new_child_bounds.x() < work_area.x())
new_child_bounds.set_x(work_area.x());
SetBoundsAndOffsetTransientChildren(transient_child, new_child_bounds,
work_area, offset);
}
if (::wm::WindowAnimationsDisabled(window)) {
window->SetBounds(bounds);
return;
}
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.SetTransitionDuration(base::Milliseconds(kWindowAutoMoveDurationMS));
window->SetBounds(bounds);
}
// Move a |window| to new |bounds|. Animate if desired by user.
// Note: The function will do nothing if the bounds did not change.
void SetBoundsAnimated(aura::Window* window,
const gfx::Rect& bounds,
const gfx::Rect& work_area) {
gfx::Rect old_bounds = window->GetTargetBounds();
if (bounds == old_bounds)
return;
gfx::Vector2d offset(bounds.origin() - old_bounds.origin());
SetBoundsAndOffsetTransientChildren(window, bounds, work_area, offset);
}
// Move |window| into the center of the screen - or restore it to the previous
// position.
void AutoPlaceSingleWindow(aura::Window* window, bool animated) {
gfx::Rect work_area = screen_util::GetDisplayWorkAreaBoundsInParent(window);
gfx::Rect bounds = window->bounds();
const std::optional<gfx::Rect> user_defined_area =
WindowState::Get(window)->pre_auto_manage_window_bounds();
if (user_defined_area) {
bounds = *user_defined_area;
AdjustBoundsToEnsureMinimumWindowVisibility(
work_area, /*client_controlled=*/false, &bounds);
} else {
// Center the window (only in x).
bounds.set_x(work_area.x() + (work_area.width() - bounds.width()) / 2);
}
if (animated)
SetBoundsAnimated(window, bounds, work_area);
else
window->SetBounds(bounds);
}
// Get the first open (non minimized) window which is on the screen defined.
aura::Window* GetReferenceWindow(const aura::Window* root_window,
const aura::Window* exclude,
bool on_hide_remove,
bool* out_single_window) {
if (out_single_window)
*out_single_window = true;
// Get the active window.
aura::Window* active = window_util::GetActiveWindow();
if (active && active->GetRootWindow() != root_window)
active = NULL;
// Get a list of all windows.
const aura::Window::Windows windows =
Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
kActiveDesk);
if (windows.empty())
return nullptr;
int index = 0;
// Find the index of the current active window.
if (active)
index = base::ranges::find(windows, active) - windows.begin();
// Scan the cycle list backwards to see which is the second topmost window
// (and so on). Note that we might cycle a few indices twice if there is no
// suitable window. However - since the list is fairly small this should be
// very fast anyways.
aura::Window* found = nullptr;
for (int i = index + windows.size(); i >= 0; i--) {
aura::Window* window = windows[i % windows.size()];
while (::wm::GetTransientParent(window))
window = ::wm::GetTransientParent(window);
// For hiding, do not auto-position if there are any remaining windows,
// even windows that do not have auto-positioning turned on.
if (window != exclude &&
window->GetType() == aura::client::WINDOW_TYPE_NORMAL &&
window->GetRootWindow() == root_window && window->TargetVisibility() &&
(on_hide_remove ||
WindowState::Get(window)->GetWindowPositionManaged())) {
if (found && found != window) {
// no need to check !out_single_window because the function must have
// been already returned in the "if (!out_single_window)" below.
*out_single_window = false;
return found;
}
found = window;
// If there is no need to check single window, return now.
if (!out_single_window)
return found;
}
}
return found;
}
} // namespace
void GetBoundsAndShowStateForNewWindow(
bool is_saved_bounds,
ui::mojom::WindowShowState show_state_in,
gfx::Rect* bounds_in_out,
ui::mojom::WindowShowState* show_state_out) {
aura::Window* root_window = Shell::GetRootWindowForNewWindows();
aura::Window* top_window = GetReferenceWindow(
root_window, nullptr, /*on_hide_remove=*/false, nullptr);
// If there is no valid window we take and adjust the passed coordinates.
if (!top_window) {
gfx::Rect work_area = display::Screen::GetScreen()
->GetDisplayNearestWindow(root_window)
.work_area();
bounds_in_out->AdjustToFit(work_area);
return;
}
WindowState* top_window_state = WindowState::Get(top_window);
bool maximized = top_window_state->IsMaximized();
// We ignore the saved show state, but look instead for the top level
// window's show state.
if (show_state_in == ui::mojom::WindowShowState::kDefault) {
*show_state_out = maximized ? ui::mojom::WindowShowState::kMaximized
: ui::mojom::WindowShowState::kDefault;
}
if (maximized || top_window_state->IsFullscreen()) {
bool has_restore_bounds = top_window_state->HasRestoreBounds();
if (has_restore_bounds) {
// For a maximized/fullscreen window ignore the real bounds of
// the top level window and use its restore bounds
// instead. Offset the bounds to prevent the windows from
// overlapping exactly when restored.
*bounds_in_out = top_window_state->GetRestoreBoundsInScreen() +
gfx::Vector2d(kWindowOffset, kWindowOffset);
}
if (is_saved_bounds || has_restore_bounds) {
gfx::Rect work_area = display::Screen::GetScreen()
->GetDisplayNearestWindow(root_window)
.work_area();
bounds_in_out->AdjustToFit(work_area);
// Use adjusted saved bounds or restore bounds, if there is one.
return;
}
}
// Use the size of the other window. The window's bound will be rearranged
// in ash::WorkspaceLayoutManager using this location.
*bounds_in_out = top_window->GetBoundsInScreen();
}
void RearrangeVisibleWindowOnHideOrRemove(const aura::Window* removed_window) {
if (!UseAutoWindowManager(removed_window))
return;
// Find a single open browser window.
bool single_window;
aura::Window* other_shown_window =
GetReferenceWindow(removed_window->GetRootWindow(), removed_window,
/*on_hide_remove=*/true, &single_window);
if (!other_shown_window || !single_window ||
!WindowPositionCanBeManaged(other_shown_window))
return;
AutoPlaceSingleWindow(other_shown_window, true);
}
bool DisableAutoPositioning(bool ignore) {
bool old_state = disable_auto_positioning;
disable_auto_positioning = ignore;
return old_state;
}
void RearrangeVisibleWindowOnShow(aura::Window* added_window) {
WindowState* added_window_state = WindowState::Get(added_window);
// TODO(b/267884882): Temporarily disable auto positioning if there is a
// floated window on the same desk, see b/265839238.
if (auto* float_controller = Shell::Get()->float_controller()) {
auto* floated_window = float_controller->FindFloatedWindowOfDesk(
Shell::Get()->desks_controller()->active_desk());
if (floated_window && floated_window != added_window) {
// Place newly added window to the center of the screen to reduce
// chances of being hidden behind the floated window. Note that minimized
// windows and other window types should not be auto placed.
if (!added_window_state->bounds_changed_by_user() &&
added_window->GetType() == aura::client::WINDOW_TYPE_NORMAL) {
AutoPlaceSingleWindow(added_window, /*animated=*/false);
}
return;
}
}
if (!added_window->TargetVisibility() ||
!UseAutoWindowManager(added_window) ||
added_window_state->bounds_changed_by_user()) {
return;
}
// Find a single open managed window.
bool single_window;
aura::Window* other_shown_window =
GetReferenceWindow(added_window->GetRootWindow(), added_window,
/*on_hide_remove=*/false, &single_window);
if (!other_shown_window) {
// It could be that this window is the first window joining the workspace.
if (!WindowPositionCanBeManaged(added_window) || other_shown_window)
return;
// Since we might be going from 0 to 1 window, we have to arrange the new
// window to a good default.
AutoPlaceSingleWindow(added_window, false);
return;
}
gfx::Rect other_bounds = other_shown_window->bounds();
gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInParent(added_window);
bool move_other_right =
other_bounds.CenterPoint().x() > work_area.x() + work_area.width() / 2;
// Push the other window to the size only if there are two windows left.
if (single_window) {
// When going from one to two windows both windows loose their
// "positioned by user" flags.
added_window_state->SetBoundsChangedByUser(false);
WindowState* other_window_state = WindowState::Get(other_shown_window);
other_window_state->SetBoundsChangedByUser(false);
if (WindowPositionCanBeManaged(other_shown_window)) {
// Don't override pre auto managed bounds as the current bounds
// may not be original.
if (!other_window_state->pre_auto_manage_window_bounds())
other_window_state->set_pre_auto_manage_window_bounds(other_bounds);
// Push away the other window after remembering its current position.
if (MoveRectToOneSide(work_area, move_other_right, &other_bounds))
SetBoundsAnimated(other_shown_window, other_bounds, work_area);
}
}
// Remember the current location of the window if it's new and push
// it also to the opposite location if needed. Since it is just
// being shown, we do not need to animate it.
gfx::Rect added_bounds = added_window->bounds();
if (!added_window_state->pre_auto_manage_window_bounds())
added_window_state->set_pre_auto_manage_window_bounds(added_bounds);
if (MoveRectToOneSide(work_area, !move_other_right, &added_bounds))
added_window->SetBounds(added_bounds);
}
} // namespace ash::window_positioner