0

bounds_tracker: Remapping/Restoring on changing the primary display

Remapping and restoring the windows inside the old and new primary
display while changing the current primary display.

Bug: b/311223959
Change-Id: I1fa5d291e0bffd6007e554182bbf8542bfb48a5b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5096901
Reviewed-by: Francois Pierre Doray <fdoray@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Commit-Queue: Min Chen <minch@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1243467}
This commit is contained in:
minch
2024-01-05 17:20:59 +00:00
committed by Chromium LUCI CQ
parent 33d119b25d
commit 67592416cf
5 changed files with 163 additions and 34 deletions

@ -907,6 +907,7 @@ void WindowTreeHostManager::SetPrimaryDisplayId(int64_t id) {
const display::Display& new_primary_display =
GetDisplayManager()->GetDisplayForId(id);
const int64_t new_primary_id = new_primary_display.id();
if (!new_primary_display.is_valid()) {
LOG(ERROR) << "Invalid or non-existent display is requested:"
<< new_primary_display.ToString();
@ -915,19 +916,25 @@ void WindowTreeHostManager::SetPrimaryDisplayId(int64_t id) {
display::DisplayManager* display_manager = GetDisplayManager();
DCHECK(new_primary_display.is_valid());
DCHECK(display_manager->GetDisplayForId(new_primary_display.id()).is_valid());
DCHECK(display_manager->GetDisplayForId(new_primary_id).is_valid());
AshWindowTreeHost* non_primary_host =
window_tree_hosts_[new_primary_display.id()];
AshWindowTreeHost* non_primary_host = window_tree_hosts_[new_primary_id];
LOG_IF(ERROR, !non_primary_host)
<< "Unknown display is requested in SetPrimaryDisplay: id="
<< new_primary_display.id();
<< new_primary_id;
if (!non_primary_host)
return;
display::Display old_primary_display =
display::Screen::GetScreen()->GetPrimaryDisplay();
DCHECK_EQ(old_primary_display.id(), primary_display_id);
const int64_t old_primary_id = old_primary_display.id();
DCHECK_EQ(old_primary_id, primary_display_id);
auto* window_bounds_tracker = Shell::Get()->window_bounds_tracker();
if (window_bounds_tracker) {
window_bounds_tracker->OnWillSwapDisplayRootWindows(old_primary_id,
new_primary_id);
}
// Swap root windows between current and new primary display.
AshWindowTreeHost* primary_host = window_tree_hosts_[primary_display_id];
@ -936,12 +943,11 @@ void WindowTreeHostManager::SetPrimaryDisplayId(int64_t id) {
aura::Window* primary_window = GetWindow(primary_host);
aura::Window* non_primary_window = GetWindow(non_primary_host);
window_tree_hosts_[new_primary_display.id()] = primary_host;
GetRootWindowSettings(primary_window)->display_id = new_primary_display.id();
window_tree_hosts_[new_primary_id] = primary_host;
GetRootWindowSettings(primary_window)->display_id = new_primary_id;
window_tree_hosts_[old_primary_display.id()] = non_primary_host;
GetRootWindowSettings(non_primary_window)->display_id =
old_primary_display.id();
window_tree_hosts_[old_primary_id] = non_primary_host;
GetRootWindowSettings(non_primary_window)->display_id = old_primary_id;
// Ensure that color spaces for the root windows reflect those of their new
// displays. If these go out of sync, we can lose the ability to composite
@ -960,16 +966,16 @@ void WindowTreeHostManager::SetPrimaryDisplayId(int64_t id) {
// The requested primary id can be same as one in the stored layout
// when the primary id is set after new displays are connected.
// Only update the layout if it is requested to swap primary display.
if (layout.primary_id != new_primary_display.id()) {
if (layout.primary_id != new_primary_id) {
std::unique_ptr<display::DisplayLayout> swapped_layout = layout.Copy();
swapped_layout->SwapPrimaryDisplay(new_primary_display.id());
swapped_layout->SwapPrimaryDisplay(new_primary_id);
display::DisplayIdList list = display_manager->GetConnectedDisplayIdList();
GetDisplayManager()->layout_store()->RegisterLayoutForDisplayIdList(
list, std::move(swapped_layout));
}
// Update the global primary_display_id.
primary_display_id = new_primary_display.id();
primary_display_id = new_primary_id;
UpdateWorkAreaOfDisplayNearestWindow(GetWindow(primary_host),
old_primary_display.GetWorkAreaInsets());
@ -984,6 +990,11 @@ void WindowTreeHostManager::SetPrimaryDisplayId(int64_t id) {
GetDisplayManager()->set_force_bounds_changed(true);
GetDisplayManager()->UpdateDisplays();
GetDisplayManager()->set_force_bounds_changed(false);
if (window_bounds_tracker) {
window_bounds_tracker->OnDisplayRootWindowsSwapped(old_primary_id,
new_primary_id);
}
}
void WindowTreeHostManager::PostDisplayConfigurationChange() {

@ -107,12 +107,12 @@ gfx::Rect AdjustBoundsForRotation(const gfx::Rect& window_bounds,
// ensures that the distance of the window's center point from the center of
// `target_work_area` is equal to the distance of the window's center point
// from the center of `source_work_area` multiplied by a *factor*.
// This factor is ratio between the target and source work area sizes, i.e.:
//
// This factor is the ratio between the target and source work area sizes, i.e.:
//
// factor_x = target_work_area.width() / source_work_area.width();
// factor_y = target_work_area.height() / source_work_area.height();
//
// Note: `source_work_area` must have already been adjusted to match
// the orientation of `target_work_area`, i.e. by calling
// `AdjustBoundsForRotation()` before this.
@ -169,23 +169,7 @@ void WindowBoundsTracker::OnWindowAddedToRootWindow(aura::Window* window) {
if (iter == bounds_database_.end()) {
return;
}
auto& window_bounds_map = iter->second.window_bounds_map;
CHECK(!window_bounds_map.empty());
display::Display target_display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window);
const WindowDisplayInfo target_window_display_info(
target_display.id(), target_display.rotation(),
target_display.GetLocalWorkArea());
const auto bounds_iter = window_bounds_map.find(target_window_display_info);
CHECK(bounds_iter != window_bounds_map.end());
window->SetBounds(bounds_iter->second.bounds_in_parent);
// Remove the stored non-restore-bounds from the database after it has been
// used. As the non-restore-bounds will never be used to restore the window
// later, the recalculation will be triggered instead.
if (!bounds_iter->second.is_restore_bounds) {
window_bounds_map.erase(bounds_iter);
}
RestoreWindowToCachedBounds(window);
}
void WindowBoundsTracker::OnWindowRemovingFromRootWindow(
@ -218,6 +202,35 @@ void WindowBoundsTracker::OnWindowRemovingFromRootWindow(
}
}
void WindowBoundsTracker::OnWillSwapDisplayRootWindows(
int64_t first_display_id,
int64_t second_display_id) {
CHECK_NE(first_display_id, second_display_id);
for (const auto& window : window_observations_.sources()) {
const int64_t window_display_id =
display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
if (window_display_id == first_display_id) {
RemapOrRestore(window, second_display_id);
} else if (window_display_id == second_display_id) {
RemapOrRestore(window, first_display_id);
}
}
}
void WindowBoundsTracker::OnDisplayRootWindowsSwapped(
int64_t first_display_id,
int64_t second_display_id) {
CHECK_NE(first_display_id, second_display_id);
for (const auto& window : window_observations_.sources()) {
const int64_t window_display_id =
display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
if (window_display_id == first_display_id ||
window_display_id == second_display_id) {
RestoreWindowToCachedBounds(window);
}
}
}
void WindowBoundsTracker::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
@ -399,4 +412,25 @@ WindowBoundsTracker::UpdateBoundsDatabaseOfWindow(
return window_bounds_entry;
}
void WindowBoundsTracker::RestoreWindowToCachedBounds(aura::Window* window) {
const auto iter = bounds_database_.find(window);
CHECK(iter != bounds_database_.end());
auto& window_bounds_map = iter->second.window_bounds_map;
CHECK(!window_bounds_map.empty());
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window);
const WindowDisplayInfo window_display_info(display.id(), display.rotation(),
display.GetLocalWorkArea());
const auto bounds_iter = window_bounds_map.find(window_display_info);
CHECK(bounds_iter != window_bounds_map.end());
window->SetBounds(bounds_iter->second.bounds_in_parent);
// Remove the stored non-restore-bounds from the database after it has been
// used. As the non-restore-bounds will never be used to restore the window
// later, the recalculation will be triggered instead.
if (!bounds_iter->second.is_restore_bounds) {
window_bounds_map.erase(bounds_iter);
}
}
} // namespace ash

@ -50,6 +50,22 @@ class WindowBoundsTracker : public aura::WindowObserver,
aura::Window* gained_active,
aura::Window* lost_active) override;
// Called before swapping the root windows of the two given displays. This
// will iterate the windows observed by `window_observations` and also inside
// these two displays, to calculate and store the window's remapping bounds in
// another display before swapping their root windows. The remapping bounds
// will be used to set the window's bounds inside another display after
// swapping the root windows of the two displays.
void OnWillSwapDisplayRootWindows(int64_t first_display_id,
int64_t second_display_id);
// Called after swapping the root windows of the two given displays. This will
// iterate the observed windows inside these two displays, and set their
// bounds to the remapping bounds calculated inside
// `OnWillSwapDisplayRootWindows` before swapping the root windows.
void OnDisplayRootWindowsSwapped(int64_t first_display_id,
int64_t second_display_id);
// Adds `window` and its host display id to `window_to_display_map_` before
// removing its host display.
void AddWindowDisplayIdOnDisplayRemoval(aura::Window* window);
@ -157,6 +173,10 @@ class WindowBoundsTracker : public aura::WindowObserver,
const gfx::Rect& bounds,
bool is_current_bounds);
// Restores the given `window` back to the stored bounds inside
// `bounds_database_` on its current `DisplayWindowInfo`.
void RestoreWindowToCachedBounds(aura::Window* window);
// Stores the window's host display id when removing its host display, which
// will be used to restore the window when its host display being reconnected
// later.

@ -471,4 +471,62 @@ TEST_F(WindowBoundsTrackerTest, RootWindowChanges) {
EXPECT_EQ(first_center_bounds, window->GetBoundsInScreen());
}
// Tests the windows remapping and restoration by changing the current primary
// display.
TEST_F(WindowBoundsTrackerTest, ChangeCurrentPrimaryDisplay) {
UpdateDisplay("400x300,600x500");
display::Display first_display = GetPrimaryDisplay();
display::Display secondary_display = GetSecondaryDisplay();
const int64_t first_display_id = first_display.id();
const int64_t secondary_display_id = secondary_display.id();
// Initially, the 1st display is the primary display.
ASSERT_EQ(first_display_id, WindowTreeHostManager::GetPrimaryDisplayId());
const gfx::Point center_point_1st = first_display.work_area().CenterPoint();
const gfx::Size window_size(200, 100);
// `w1` is at the center of the 1st display.
const gfx::Rect w1_initial_bounds(
gfx::Point(center_point_1st.x() - window_size.width() / 2,
center_point_1st.y() - window_size.height() / 2),
window_size);
// `w2` is half-offscreen inside the 2nd display.
const gfx::Rect w2_initial_bounds(gfx::Point(900, 0), window_size);
aura::Window* w1 = CreateTestWindowInShellWithBounds(w1_initial_bounds);
wm::ActivateWindow(w1);
aura::Window* w2 = CreateTestWindowInShellWithBounds(w2_initial_bounds);
wm::ActivateWindow(w2);
auto* window_tree_host_manager = Shell::Get()->window_tree_host_manager();
// Set the 2nd display as the primary display, which will swap the root
// windows of the two displays.
window_tree_host_manager->SetPrimaryDisplayId(secondary_display_id);
ASSERT_EQ(secondary_display_id, WindowTreeHostManager::GetPrimaryDisplayId());
auto* screen = display::Screen::GetScreen();
screen->GetDisplayWithDisplayId(first_display_id, &first_display);
screen->GetDisplayWithDisplayId(secondary_display_id, &secondary_display);
const gfx::Point center_point_2nd =
secondary_display.work_area().CenterPoint();
const gfx::Rect center_position_2nd(
gfx::Point(center_point_2nd.x() - window_size.width() / 2,
center_point_2nd.y() - window_size.height() / 2),
window_size);
const gfx::Rect fully_visible_bounds_1st(
gfx::Point(first_display.bounds().right() - window_size.width(), 0),
window_size);
// `w1` was at the center of the 1st display, it should stay at the center of
// the 2nd display after swapping.
EXPECT_EQ(center_position_2nd, w1->GetBoundsInScreen());
// `w2` was half-offscreen at the right top of the 2nd display, it should
// still be at the right top but fully visible after being swapped to the 1st
// display.
EXPECT_EQ(fully_visible_bounds_1st, w2->GetBoundsInScreen());
// Set the 1st display back to be the primary display, `w1` and `w2` should
// restore to their initial bounds inside the display.
window_tree_host_manager->SetPrimaryDisplayId(first_display_id);
EXPECT_EQ(w1_initial_bounds, w1->GetBoundsInScreen());
EXPECT_EQ(w2_initial_bounds, w2->GetBoundsInScreen());
}
} // namespace ash

@ -90,6 +90,12 @@ class ScopedMultiSourceObservation {
// Returns the number of sources being observed.
size_t GetSourcesCount() const { return sources_.size(); }
// Returns the sources being observed. Note: It is invalid to add or remove
// sources while iterating on it.
const std::vector<raw_ptr<Source, LeakedDanglingUntriaged>>& sources() const {
return sources_;
}
private:
using Traits = ScopedObservationTraits<Source, Observer>;