| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/display/display_move_window_util.h" |
| |
| #include "ash/accelerators/accelerator_controller.h" |
| #include "ash/accelerators/accelerator_table.h" |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/accessibility/test_accessibility_controller_client.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/screen_util.h" |
| #include "ash/shelf/shelf_constants.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/wm_event.h" |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "ui/aura/test/test_windows.h" |
| #include "ui/display/display.h" |
| #include "ui/display/display_layout.h" |
| #include "ui/display/display_layout_builder.h" |
| #include "ui/display/manager/display_layout_store.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| |
| namespace display_move_window_util { |
| |
| namespace { |
| |
| // Get the default left snapped window bounds which has snapped width ratio 0.5. |
| gfx::Rect GetDefaultLeftSnappedBoundsInDisplay( |
| const display::Display& display) { |
| auto work_area = display.work_area(); |
| work_area.set_width(work_area.width() / 2); |
| return work_area; |
| } |
| |
| views::Widget* CreateTestWidgetWithParent(views::Widget::InitParams::Type type, |
| gfx::NativeView parent, |
| const gfx::Rect& bounds, |
| bool child) { |
| views::Widget::InitParams params(type); |
| params.delegate = nullptr; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.parent = parent; |
| params.bounds = bounds; |
| params.child = child; |
| views::Widget* widget = new views::Widget; |
| widget->Init(params); |
| widget->Show(); |
| return widget; |
| } |
| |
| void PerformMoveWindowAccel() { |
| Shell::Get()->accelerator_controller()->PerformActionIfEnabled( |
| MOVE_ACTIVE_WINDOW_BETWEEN_DISPLAYS); |
| } |
| |
| } // namespace |
| |
| using DisplayMoveWindowUtilTest = AshTestBase; |
| |
| TEST_F(DisplayMoveWindowUtilTest, SingleDisplay) { |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| EXPECT_FALSE(CanHandleMoveActiveWindowBetweenDisplays()); |
| } |
| |
| // Tests that window bounds are not changing after moving to another display. It |
| // is added as a child window to another root window with the same origin. |
| TEST_F(DisplayMoveWindowUtilTest, WindowBounds) { |
| // Layout: [p][1] |
| UpdateDisplay("400x300,400x300"); |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window->GetBoundsInScreen()); |
| } |
| |
| // Tests window state (maximized/fullscreen/snapped) and its bounds. |
| TEST_F(DisplayMoveWindowUtilTest, WindowState) { |
| // Layout: [p][ 1 ] |
| UpdateDisplay("400x300,800x300"); |
| |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| display::Screen* screen = display::Screen::GetScreen(); |
| ASSERT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| // Set window to maximized state. |
| window_state->Maximize(); |
| EXPECT_TRUE(window_state->IsMaximized()); |
| EXPECT_EQ(screen_util::GetMaximizedWindowBoundsInParent(window), |
| window->bounds()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| // Check that window state is maximized and has updated maximized bounds. |
| EXPECT_TRUE(window_state->IsMaximized()); |
| EXPECT_EQ(screen_util::GetMaximizedWindowBoundsInParent(window), |
| window->bounds()); |
| |
| // Set window to fullscreen state. |
| PerformMoveWindowAccel(); |
| const wm::WMEvent fullscreen(wm::WM_EVENT_TOGGLE_FULLSCREEN); |
| window_state->OnWMEvent(&fullscreen); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_TRUE(window_state->IsFullscreen()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).bounds(), |
| window->GetBoundsInScreen()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| // Check that window state is fullscreen and has updated fullscreen bounds. |
| EXPECT_TRUE(window_state->IsFullscreen()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).bounds(), |
| window->GetBoundsInScreen()); |
| |
| // Set window to left snapped state. |
| PerformMoveWindowAccel(); |
| const wm::WMEvent snap_left(wm::WM_EVENT_SNAP_LEFT); |
| window_state->OnWMEvent(&snap_left); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_TRUE(window_state->IsSnapped()); |
| EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay( |
| screen->GetDisplayNearestWindow(window)), |
| window->GetBoundsInScreen()); |
| EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| // Check that window state is snapped and has updated snapped bounds. |
| EXPECT_TRUE(window_state->IsSnapped()); |
| EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay( |
| screen->GetDisplayNearestWindow(window)), |
| window->GetBoundsInScreen()); |
| EXPECT_EQ(0.5f, *window_state->snapped_width_ratio()); |
| } |
| |
| // Tests that movement follows cycling through sorted display id list. |
| TEST_F(DisplayMoveWindowUtilTest, FourDisplays) { |
| display::Screen* screen = display::Screen::GetScreen(); |
| int64_t primary_id = screen->GetPrimaryDisplay().id(); |
| // Layout: |
| // [3][2] |
| // [1][p] |
| display::DisplayIdList list = display::test::CreateDisplayIdListN( |
| 4, primary_id, primary_id + 1, primary_id + 2, primary_id + 3); |
| display::DisplayLayoutBuilder builder(primary_id); |
| builder.AddDisplayPlacement(list[1], primary_id, |
| display::DisplayPlacement::LEFT, 0); |
| builder.AddDisplayPlacement(list[2], primary_id, |
| display::DisplayPlacement::TOP, 0); |
| builder.AddDisplayPlacement(list[3], list[2], display::DisplayPlacement::LEFT, |
| 0); |
| display_manager()->layout_store()->RegisterLayoutForDisplayIdList( |
| list, builder.Build()); |
| UpdateDisplay("400x300,400x300,400x300,400x300"); |
| EXPECT_EQ(4U, display_manager()->GetNumDisplays()); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 300), |
| display_manager()->GetDisplayAt(0).bounds()); |
| EXPECT_EQ(gfx::Rect(-400, 0, 400, 300), |
| display_manager()->GetDisplayAt(1).bounds()); |
| EXPECT_EQ(gfx::Rect(0, -300, 400, 300), |
| display_manager()->GetDisplayAt(2).bounds()); |
| EXPECT_EQ(gfx::Rect(-400, -300, 400, 300), |
| display_manager()->GetDisplayAt(3).bounds()); |
| |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| ASSERT_EQ(list[0], screen->GetDisplayNearestWindow(window).id()); |
| |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(list[1], screen->GetDisplayNearestWindow(window).id()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(list[2], screen->GetDisplayNearestWindow(window).id()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(list[3], screen->GetDisplayNearestWindow(window).id()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(list[0], screen->GetDisplayNearestWindow(window).id()); |
| } |
| |
| // Tests that a11y alert is sent on handling move window to display. |
| TEST_F(DisplayMoveWindowUtilTest, A11yAlert) { |
| // Layout: [p][1] |
| UpdateDisplay("400x300,400x300"); |
| AccessibilityController* controller = |
| Shell::Get()->accessibility_controller(); |
| TestAccessibilityControllerClient client; |
| controller->SetClient(client.CreateInterfacePtrAndBind()); |
| |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| PerformMoveWindowAccel(); |
| controller->FlushMojoForTest(); |
| EXPECT_EQ(mojom::AccessibilityAlert::WINDOW_MOVED_TO_ANOTHER_DISPLAY, |
| client.last_a11y_alert()); |
| } |
| |
| // Tests that moving window between displays is no-op if active window is not in |
| // window cycle list. |
| TEST_F(DisplayMoveWindowUtilTest, NoMovementIfNotInCycleWindowList) { |
| // Layout: [p][1] |
| UpdateDisplay("400x300,400x300"); |
| // Create a window in app list container, which would be excluded in cycle |
| // window list. |
| std::unique_ptr<aura::Window> window = CreateChildWindow( |
| Shell::GetPrimaryRootWindow(), gfx::Rect(10, 20, 200, 100), |
| kShellWindowId_AppListContainer); |
| wm::ActivateWindow(window.get()); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window.get()).id()); |
| |
| MruWindowTracker::WindowList cycle_window_list = |
| Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(); |
| EXPECT_TRUE(cycle_window_list.empty()); |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window.get()).id()); |
| } |
| |
| // Tests that window bounds should be kept if it is not changed by user, i.e. |
| // if it is changed by display area difference, it should restore to the |
| // original bounds when it is moved between displays and there is enough work |
| // area to show this window. |
| TEST_F(DisplayMoveWindowUtilTest, KeepWindowBoundsIfNotChangedByUser) { |
| // Layout: |
| // +---+---+ |
| // | p | | |
| // +---+ 1 | |
| // | | |
| // +---+ |
| UpdateDisplay("400x300,400x600"); |
| const int shelf_inset = 300 - ShelfConstants::shelf_size(); |
| // Create and activate window on display [1]. |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(410, 20, 200, 400)); |
| wm::ActivateWindow(window); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| // Move window to display [p]. Its window bounds is adjusted by available work |
| // area. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_EQ(gfx::Rect(10, 20, 200, shelf_inset), window->GetBoundsInScreen()); |
| // Move window back to display [1]. Its window bounds should be restored. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 400), window->GetBoundsInScreen()); |
| |
| // Move window to display [p] and set that its bounds is changed by user. |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| PerformMoveWindowAccel(); |
| window_state->set_bounds_changed_by_user(true); |
| // Move window back to display [1], but its bounds has been changed by user. |
| // Then window bounds should be kept the same as that in display [p]. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, shelf_inset), window->GetBoundsInScreen()); |
| } |
| |
| // Tests auto window management on moving window between displays. |
| TEST_F(DisplayMoveWindowUtilTest, AutoManaged) { |
| // Layout: [p][1] |
| UpdateDisplay("400x300,400x300"); |
| // Create and show window on display [p]. Enable auto window position managed, |
| // which will center the window on display [p]. |
| aura::Window* window1 = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::WindowState* window1_state = wm::GetWindowState(window1); |
| window1_state->SetWindowPositionManaged(true); |
| window1->Hide(); |
| window1->Show(); |
| EXPECT_EQ(gfx::Rect(100, 20, 200, 100), window1->GetBoundsInScreen()); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window1).id()); |
| |
| // Create and show window on display [p]. Enable auto window position managed, |
| // which will do auto window management (pushing the other window to side). |
| aura::Window* window2 = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::WindowState* window2_state = wm::GetWindowState(window2); |
| window2_state->SetWindowPositionManaged(true); |
| window2->Hide(); |
| window2->Show(); |
| EXPECT_EQ(gfx::Rect(0, 20, 200, 100), window1->GetBoundsInScreen()); |
| EXPECT_EQ(gfx::Rect(200, 20, 200, 100), window2->GetBoundsInScreen()); |
| // Activate window2. |
| wm::ActivateWindow(window2); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window2).id()); |
| EXPECT_TRUE(window2_state->pre_auto_manage_window_bounds()); |
| EXPECT_EQ(gfx::Rect(10, 20, 200, 100), |
| *window2_state->pre_auto_manage_window_bounds()); |
| |
| // Move window2 to display [1] and check auto window management. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window2).id()); |
| // Check |pre_added_to_workspace_window_bounds()|, which should be equal to |
| // |pre_auto_manage_window_bounds()| in this case. |
| EXPECT_EQ(*window2_state->pre_auto_manage_window_bounds(), |
| *window2_state->pre_added_to_workspace_window_bounds()); |
| // Window 1 centers on display [p] again. |
| EXPECT_EQ(gfx::Rect(100, 20, 200, 100), window1->GetBoundsInScreen()); |
| // Window 2 is positioned by |pre_added_to_workspace_window_bounds()|. |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window2->GetBoundsInScreen()); |
| } |
| |
| TEST_F(DisplayMoveWindowUtilTest, WindowWithTransientChild) { |
| UpdateDisplay("400x300,400x300"); |
| aura::Window* window = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(window); |
| |
| // Create a |child| window and make it a transient child of |window|. |
| std::unique_ptr<aura::Window> child = |
| CreateChildWindow(window, gfx::Rect(20, 30, 40, 50)); |
| ::wm::AddTransientChild(window, child.get()); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(child.get()).id()); |
| |
| // Operate moving window to right display. Check display and bounds. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(child.get()).id()); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window->GetBoundsInScreen()); |
| EXPECT_EQ(gfx::Rect(430, 50, 40, 50), child->GetBoundsInScreen()); |
| } |
| |
| // Test that when operating move window between displays on activated transient |
| // child window, its first non-transient transient-parent window should be the |
| // target instead. |
| TEST_F(DisplayMoveWindowUtilTest, ActiveTransientChildWindow) { |
| UpdateDisplay("400x300,400x300"); |
| std::unique_ptr<views::Widget> window = CreateTestWidget(); |
| window->SetBounds(gfx::Rect(10, 20, 200, 100)); |
| |
| // Create a |child| transient widget of |window|. When |child| is shown, it is |
| // activated. |
| std::unique_ptr<views::Widget> child(CreateTestWidgetWithParent( |
| views::Widget::InitParams::TYPE_WINDOW, window->GetNativeView(), |
| gfx::Rect(20, 30, 40, 50), false)); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(window->GetNativeWindow()).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(child->GetNativeWindow()).id()); |
| // Ensure |child| window is activated. |
| EXPECT_FALSE(wm::IsActiveWindow(window->GetNativeWindow())); |
| EXPECT_TRUE(wm::IsActiveWindow(child->GetNativeWindow())); |
| |
| // Operate moving window to right display. Check display and bounds. |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(window->GetNativeWindow()).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), |
| screen->GetDisplayNearestWindow(child->GetNativeWindow()).id()); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 100), |
| window->GetNativeWindow()->GetBoundsInScreen()); |
| EXPECT_EQ(gfx::Rect(420, 30, 40, 50), |
| child->GetNativeWindow()->GetBoundsInScreen()); |
| } |
| |
| // Test that when active window is transient child window, no movement if its |
| // first non-transient transient-parent window is not in window cycle list. |
| TEST_F(DisplayMoveWindowUtilTest, TransientParentNotInCycleWindowList) { |
| UpdateDisplay("400x300,400x300"); |
| aura::Window* w1 = |
| CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 50, 50)); |
| wm::ActivateWindow(w1); |
| |
| // Create a window |w2| in non-switchable window container. |
| aura::Window* setting_bubble_container = |
| Shell::GetPrimaryRootWindowController()->GetContainer( |
| kShellWindowId_SettingBubbleContainer); |
| std::unique_ptr<aura::Window> w2 = CreateChildWindow( |
| setting_bubble_container, gfx::Rect(10, 20, 200, 100), -1); |
| wm::ActivateWindow(w2.get()); |
| |
| // Create a |child| transient widget of |w2|. When |child| is shown, it is |
| // activated. |
| std::unique_ptr<views::Widget> child( |
| CreateTestWidgetWithParent(views::Widget::InitParams::TYPE_WINDOW, |
| w2.get(), gfx::Rect(20, 30, 40, 50), false)); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(w1).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(w2.get()).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(child->GetNativeWindow()).id()); |
| // Ensure |child| window is activated. |
| EXPECT_FALSE(wm::IsActiveWindow(w1)); |
| EXPECT_FALSE(wm::IsActiveWindow(w2.get())); |
| EXPECT_TRUE(wm::IsActiveWindow(child->GetNativeWindow())); |
| |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(w1).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(w2.get()).id()); |
| EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), |
| screen->GetDisplayNearestWindow(child->GetNativeWindow()).id()); |
| } |
| |
| // Tests that restore bounds is updated with window movement to another display. |
| TEST_F(DisplayMoveWindowUtilTest, RestoreMaximizedWindowAfterMovement) { |
| UpdateDisplay("400x300,400x300"); |
| aura::Window* w = |
| CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100)); |
| wm::ActivateWindow(w); |
| |
| wm::WindowState* window_state = wm::GetWindowState(w); |
| window_state->Maximize(); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 300 - ShelfConstants::shelf_size()), |
| w->GetBoundsInScreen()); |
| |
| PerformMoveWindowAccel(); |
| EXPECT_EQ(gfx::Rect(400, 0, 400, 300 - ShelfConstants::shelf_size()), |
| w->GetBoundsInScreen()); |
| window_state->Restore(); |
| EXPECT_EQ(gfx::Rect(410, 20, 200, 100), w->GetBoundsInScreen()); |
| } |
| |
| } // namespace display_move_window_util |
| |
| } // namespace ash |