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:
@ -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>;
|
||||
|
||||
|
Reference in New Issue
Block a user