| // Copyright 2012 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_util.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <tuple> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/multi_user/multi_user_window_manager.h" |
| #include "ash/public/cpp/app_types_util.h" |
| #include "ash/public/cpp/input_device_settings_controller.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/public/mojom/input_device_settings.mojom.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/screen_util.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/float/float_controller.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_grid.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/scoped_windows_mover.h" |
| #include "ash/wm/snap_group/snap_group.h" |
| #include "ash/wm/snap_group/snap_group_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_overview_session.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/window_positioning_utils.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/wm_constants.h" |
| #include "ash/wm/wm_event.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "chromeos/ui/base/app_types.h" |
| #include "chromeos/ui/base/chromeos_ui_constants.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "chromeos/ui/base/window_state_type.h" |
| #include "chromeos/ui/frame/caption_buttons/snap_controller.h" |
| #include "chromeos/ui/frame/interior_resize_handler_targeter.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/capture_client.h" |
| #include "ui/aura/client/focus_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/mojom/ui_base_types.mojom-shared.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/compositor/compositor.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_tree_owner.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/gfx/geometry/transform_util.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/easy_resize_window_targeter.h" |
| #include "ui/wm/core/scoped_animation_disabler.h" |
| #include "ui/wm/core/window_animations.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace ash::window_util { |
| namespace { |
| |
| // Returns true if `window` has any descendant that is a system modal window or |
| // is itself a system modal window. |
| bool ContainsSystemModalWindow(const aura::Window* window) { |
| if (!window) { |
| return false; |
| } |
| |
| if (window->GetProperty(aura::client::kModalKey) == |
| ui::mojom::ModalType::kSystem) { |
| return true; |
| } |
| |
| for (const aura::Window* child : window->children()) { |
| if (ContainsSystemModalWindow(child)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Returns the lowest common parent of the given `windows` by traversing up from |
| // one of the windows' direct parent and check if the intermediate parent |
| // contains all the `windows`. If yes, it will be the lowest common parent. |
| aura::Window* FindLowestCommonParent(const aura::Window::Windows& windows) { |
| if (windows.empty()) { |
| return nullptr; |
| } |
| |
| auto contains_all = [&](aura::Window* parent) { |
| for (aura::Window* window : windows) { |
| if (!parent->Contains(window)) { |
| return false; |
| } |
| } |
| |
| return true; |
| }; |
| |
| // As a window can `Contains` itself, which is not the common parent, we start |
| // traversing from its parent instead. |
| for (aura::Window* parent = windows.front()->parent(); parent; |
| parent = parent->parent()) { |
| if (contains_all(parent)) { |
| return parent; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| // Uses DFS to find the topmost child of the `parent` that is included in |
| // `windows`. With the reverse traversing of the children, the first observed |
| // window found will be the topmost one. |
| aura::Window* FindTopMostChild(aura::Window* parent, |
| const aura::Window::Windows& windows) { |
| for (aura::Window* child : base::Reversed(parent->children())) { |
| for (aura::Window* window : windows) { |
| if (child == window && !window->is_destroying()) { |
| return window; |
| } |
| if (child->Contains(window)) { |
| return FindTopMostChild(child, windows); |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| gfx::RoundedCornersF GetMiniWindowRoundedCorners(const aura::Window* window, |
| bool include_header_rounding, |
| std::optional<float> scale) { |
| const int corner_radius = kWindowMiniViewCornerRadius; |
| const float scaled_corner_radius = corner_radius / scale.value_or(1.0f); |
| |
| if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) { |
| if (SnapGroup* snap_group = |
| snap_group_controller->GetSnapGroupForGivenWindow(window)) { |
| const bool is_in_horizontal_snap_group = |
| snap_group->IsSnapGroupLayoutHorizontal(); |
| if (window == snap_group->GetPhysicallyLeftOrTopWindow()) { |
| return is_in_horizontal_snap_group |
| ? gfx::RoundedCornersF( |
| /*upper_left=*/include_header_rounding |
| ? scaled_corner_radius |
| : 0, |
| /*upper_right=*/0, /*lower_right=*/0, |
| /*lower_left=*/scaled_corner_radius) |
| : gfx::RoundedCornersF( |
| /*upper_left=*/include_header_rounding |
| ? scaled_corner_radius |
| : 0, |
| /*upper_right=*/ |
| include_header_rounding ? scaled_corner_radius : 0, |
| /*lower_right=*/0, |
| /*lower_left=*/0); |
| } |
| |
| return is_in_horizontal_snap_group |
| ? gfx::RoundedCornersF( |
| /*upper_left=*/0, |
| /*upper_right=*/ |
| include_header_rounding ? scaled_corner_radius : 0, |
| /*lower_right=*/ |
| scaled_corner_radius, |
| /*lower_left=*/0) |
| : gfx::RoundedCornersF( |
| /*upper_left=*/0, |
| /*upper_right=*/0, |
| /*lower_right=*/ |
| scaled_corner_radius, |
| /*lower_left=*/scaled_corner_radius); |
| } |
| } |
| |
| return gfx::RoundedCornersF( |
| /*upper_left=*/include_header_rounding ? scaled_corner_radius : 0, |
| /*upper_right=*/include_header_rounding ? scaled_corner_radius : 0, |
| /*lower_right=*/scaled_corner_radius, |
| /*lower_left=*/scaled_corner_radius); |
| } |
| |
| aura::Window* GetActiveWindow() { |
| if (auto* activation_client = |
| wm::GetActivationClient(Shell::GetPrimaryRootWindow())) { |
| return activation_client->GetActiveWindow(); |
| } |
| return nullptr; |
| } |
| |
| aura::Window* GetFocusedWindow() { |
| return aura::client::GetFocusClient(Shell::GetPrimaryRootWindow()) |
| ->GetFocusedWindow(); |
| } |
| |
| bool IsStackedBelow(aura::Window* win1, aura::Window* win2) { |
| CHECK_NE(win1, win2); |
| CHECK_EQ(win1->parent(), win2->parent()); |
| |
| const auto& children = win1->parent()->children(); |
| auto win1_iter = std::ranges::find(children, win1); |
| auto win2_iter = std::ranges::find(children, win2); |
| CHECK(win1_iter != children.end()); |
| CHECK(win2_iter != children.end()); |
| return win1_iter < win2_iter; |
| } |
| |
| aura::Window* GetTopMostWindow(const aura::Window::Windows& windows) { |
| aura::Window* lowest_common_parent = FindLowestCommonParent(windows); |
| if (!lowest_common_parent) { |
| return nullptr; |
| } |
| |
| return FindTopMostChild(lowest_common_parent, windows); |
| } |
| |
| std::vector<aura::Window*> SortWindowsBottomToTop( |
| std::set<raw_ptr<aura::Window, SetExperimental>> window_set) { |
| std::vector<aura::Window*> ordered; |
| std::vector<aura::Window*> root_windows; |
| std::stack<aura::Window*> stack; |
| |
| // Collect unique root windows and put them on the stack. |
| for (aura::Window* window : window_set) { |
| // The call to `GetRootWindow` here traverses up the window tree to the |
| // root, so this is technically quadratic time in the worst case, but is |
| // effectively linear time for shallow trees, which are common. |
| root_windows.push_back(window->GetRootWindow()); |
| } |
| for (auto* window : base::flat_set<aura::Window*>(std::move(root_windows))) { |
| stack.push(window); |
| } |
| |
| // Pre-order DFS from bottom-most to top-most windows. |
| while (!stack.empty()) { |
| auto* window = stack.top(); |
| stack.pop(); |
| |
| if (window_set.erase(window)) { |
| ordered.push_back(window); |
| } |
| |
| // Push so bottom-most is on the top of the stack. |
| for (aura::Window* child : base::Reversed(window->children())) { |
| stack.push(child); |
| } |
| } |
| |
| return ordered; |
| } |
| |
| aura::Window* GetCaptureWindow() { |
| return aura::client::GetCaptureWindow(Shell::GetPrimaryRootWindow()); |
| } |
| |
| void GetBlockingContainersForRoot(aura::Window* root_window, |
| aura::Window** min_container, |
| aura::Window** system_modal_container) { |
| if (Shell::Get()->session_controller()->IsUserSessionBlocked()) { |
| *min_container = |
| root_window->GetChildById(kShellWindowId_LockScreenContainersContainer); |
| *system_modal_container = |
| root_window->GetChildById(kShellWindowId_LockSystemModalContainer); |
| } else if (aura::Window* const help_bubble_container = |
| root_window->GetChildById(kShellWindowId_HelpBubbleContainer); |
| ContainsSystemModalWindow(help_bubble_container)) { |
| *min_container = help_bubble_container; |
| *system_modal_container = nullptr; |
| } else { |
| *min_container = nullptr; |
| *system_modal_container = |
| root_window->GetChildById(kShellWindowId_SystemModalContainer); |
| } |
| } |
| |
| bool IsWindowUserPositionable(aura::Window* window) { |
| return window->GetType() == aura::client::WINDOW_TYPE_NORMAL; |
| } |
| |
| void PinWindow(aura::Window* window, bool trusted) { |
| WMEvent event(trusted ? WM_EVENT_TRUSTED_PIN : WM_EVENT_PIN); |
| WindowState::Get(window)->OnWMEvent(&event); |
| } |
| |
| void SetAutoHideShelf(aura::Window* window, bool autohide) { |
| WindowState::Get(window)->set_autohide_shelf_when_maximized_or_fullscreen( |
| autohide); |
| for (aura::Window* root_window : Shell::GetAllRootWindows()) |
| Shelf::ForWindow(root_window)->UpdateVisibilityState(); |
| } |
| |
| bool MoveWindowToDisplay(aura::Window* window, int64_t display_id) { |
| DCHECK(window); |
| |
| aura::Window* root = Shell::GetRootWindowForDisplayId(display_id); |
| if (!root || root == window->GetRootWindow()) { |
| NOTREACHED(); |
| } |
| |
| ScopedWindowsMover mover(display_id); |
| // If snapped , breake it. |
| if (auto* snap_group = |
| SnapGroupController::Get()->GetSnapGroupForGivenWindow(window)) { |
| mover.add_window(snap_group->window1() == window ? snap_group->window2() |
| : snap_group->window1()); |
| SnapGroupController::Get()->RemoveSnapGroup( |
| snap_group, SnapGroupExitPoint::kMoveToAnotherDisplay); |
| } |
| |
| WindowState* window_state = WindowState::Get(window); |
| if (window_state->allow_set_bounds_direct()) { |
| display::Display display; |
| if (!display::Screen::Get()->GetDisplayWithDisplayId(display_id, |
| &display)) { |
| return false; |
| } |
| gfx::Rect bounds = window->bounds(); |
| gfx::Rect work_area_in_display(display.size()); |
| work_area_in_display.Inset(display.GetWorkAreaInsets()); |
| AdjustBoundsToEnsureMinimumWindowVisibility( |
| work_area_in_display, /*client_controlled=*/false, &bounds); |
| SetBoundsWMEvent event(bounds, display_id); |
| window_state->OnWMEvent(&event); |
| return true; |
| } |
| |
| // Moves |window| to the given |root| window's corresponding container. |
| aura::Window* container = RootWindowController::ForWindow(root)->GetContainer( |
| window->parent()->GetId()); |
| if (!container) |
| return false; |
| |
| // Update restore bounds to target root window. |
| if (window_state->HasRestoreBounds()) { |
| gfx::Rect restore_bounds = window_state->GetRestoreBoundsInParent(); |
| ::wm::ConvertRectToScreen(root, &restore_bounds); |
| window_state->SetRestoreBoundsInScreen(restore_bounds); |
| } |
| |
| container->AddChild(window); |
| return true; |
| } |
| |
| int GetNonClientComponent(aura::Window* window, const gfx::Point& location) { |
| return window->delegate() |
| ? window->delegate()->GetNonClientComponent(location) |
| : HTNOWHERE; |
| } |
| |
| void SetChildrenUseExtendedHitRegionForWindow(aura::Window* window) { |
| gfx::Insets mouse_extend(-chromeos::kResizeOutsideBoundsSize); |
| gfx::Insets touch_extend = gfx::ScaleToFlooredInsets( |
| mouse_extend, chromeos::kResizeOutsideBoundsScaleForTouch); |
| window->SetEventTargeter(std::make_unique<::wm::EasyResizeWindowTargeter>( |
| mouse_extend, touch_extend)); |
| } |
| |
| void CloseWidgetForWindow(aura::Window* window) { |
| views::Widget* widget = views::Widget::GetWidgetForNativeView(window); |
| DCHECK(widget); |
| widget->Close(); |
| } |
| |
| void InstallResizeHandleWindowTargeterForWindow(aura::Window* window) { |
| window->SetEventTargeter( |
| std::make_unique<chromeos::InteriorResizeHandleTargeter>( |
| base::BindRepeating([](const aura::Window* window) { |
| const WindowState* window_state = WindowState::Get(window); |
| return window_state ? window_state->GetStateType() |
| : chromeos::WindowStateType::kDefault; |
| }))); |
| } |
| |
| bool IsDraggingTabs(const aura::Window* window) { |
| return window->GetProperty(ash::kIsDraggingTabsKey); |
| } |
| |
| bool ShouldExcludeForCycleList(const aura::Window* window) { |
| // Exclude windows: |
| // - non user positionable windows, such as extension popups. |
| // - windows being dragged |
| // - pip windows |
| const WindowState* state = WindowState::Get(window); |
| if (!state->IsUserPositionable() || state->is_dragged() || state->IsPip()) |
| return true; |
| |
| // Exclude the AppList window, which will hide as soon as cycling starts |
| // anyway. It doesn't make sense to count it as a "switchable" window, yet |
| // a lot of code relies on the MRU list returning the app window. If we |
| // don't manually remove it, the window cycling UI won't crash or misbehave, |
| // but there will be a flicker as the target window changes. Also exclude |
| // unselectable windows such as extension popups. |
| for (auto* parent = window->parent(); parent; parent = parent->parent()) { |
| if (parent->GetId() == kShellWindowId_AppListContainer) |
| return true; |
| } |
| |
| return window->GetProperty(kHideInOverviewKey); |
| } |
| |
| bool ShouldExcludeForOverview(const aura::Window* window) { |
| if (ShouldExcludeForCycleList(window)) { |
| return true; |
| } |
| |
| if (display::Screen::Get()->InTabletMode()) { |
| return window == SplitViewController::Get(window->GetRootWindow()) |
| ->GetDefaultSnappedWindow(); |
| } |
| |
| // A window should be excluded from being shown in Overview in clamshell mode |
| // when: |
| // 1. The window itself is the snapped window in partial Overview; |
| SplitViewController* split_view_controller = SplitViewController::Get(window); |
| SplitViewController::State split_view_state = split_view_controller->state(); |
| const bool in_partial_overview = |
| split_view_state == SplitViewController::State::kPrimarySnapped || |
| split_view_state == SplitViewController::State::kSecondarySnapped; |
| if (in_partial_overview && |
| window == split_view_controller->GetDefaultSnappedWindow()) { |
| return true; |
| } |
| |
| // 2. The given `window` or its transient parent is not the most recently used |
| // (MRU) window within its snap group i.e. the corresponding Overview item |
| // representation for the snap group has been created. Note that the |
| // activatable transient window is included in the window cycle list |
| if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) { |
| SnapGroup* snap_group = |
| snap_group_controller->GetSnapGroupForGivenWindow(window); |
| const aura::Window* transient_parent = wm::GetTransientParent(window); |
| if (!snap_group) { |
| snap_group = |
| snap_group_controller->GetSnapGroupForGivenWindow(transient_parent); |
| } |
| |
| if (snap_group) { |
| const aura::Window* top_most_window_in_snap_group = |
| snap_group->GetTopMostWindowInGroup(); |
| return window != top_most_window_in_snap_group && |
| (!transient_parent || |
| transient_parent != top_most_window_in_snap_group); |
| } |
| } |
| |
| return false; |
| } |
| |
| void EnsureTransientRoots( |
| std::vector<raw_ptr<aura::Window, VectorExperimental>>* out_window_list) { |
| for (auto it = out_window_list->begin(); it != out_window_list->end();) { |
| aura::Window* transient_root = ::wm::GetTransientRoot(*it); |
| if (*it != transient_root) { |
| if (base::Contains(*out_window_list, transient_root)) { |
| it = out_window_list->erase(it); |
| } else { |
| *it = transient_root; |
| ++it; |
| } |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void MinimizeAndHideWithoutAnimation( |
| const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) { |
| for (aura::Window* window : windows) { |
| wm::ScopedAnimationDisabler disable(window); |
| |
| // ARC windows are minimized asynchronously, so we hide them after |
| // minimization. We minimize ARC windows first so they receive occlusion |
| // updates before losing focus from being hidden. See crbug.com/910304. |
| // TODO(oshima): Investigate better way to handle ARC apps immediately. |
| |
| // Suspect some callsites may use this on a window without a window state |
| // (`aura::client::WINDOW_TYPE_CONTROL`) or windows that cannot be |
| // minimized. See https://crbug.com/1200596. |
| auto* window_state = WindowState::Get(window); |
| if (window_state && window_state->CanMinimize()) |
| window_state->Minimize(); |
| |
| window->Hide(); |
| } |
| |
| if (windows.size()) { |
| // Disabling the animations using `ScopedAnimationDisabler` will skip |
| // detaching the resources associated with the layer. So we have to trick |
| // the compositor into releasing the resources. |
| // crbug.com/924802. |
| auto* compositor = windows[0]->layer()->GetCompositor(); |
| bool was_visible = compositor->IsVisible(); |
| compositor->SetVisible(false); |
| compositor->SetVisible(was_visible); |
| } |
| } |
| |
| aura::Window* GetRootWindowAt(const gfx::Point& point_in_screen) { |
| const display::Display& display = |
| display::Screen::Get()->GetDisplayNearestPoint(point_in_screen); |
| DCHECK(display.is_valid()); |
| RootWindowController* root_window_controller = |
| Shell::GetRootWindowControllerWithDisplayId(display.id()); |
| return root_window_controller ? root_window_controller->GetRootWindow() |
| : nullptr; |
| } |
| |
| aura::Window* GetRootWindowMatching(const gfx::Rect& rect_in_screen) { |
| const display::Display& display = |
| display::Screen::Get()->GetDisplayMatching(rect_in_screen); |
| RootWindowController* root_window_controller = |
| Shell::GetRootWindowControllerWithDisplayId(display.id()); |
| return root_window_controller ? root_window_controller->GetRootWindow() |
| : nullptr; |
| } |
| |
| bool IsArcPipWindow(const aura::Window* window) { |
| return IsArcWindow(window) && WindowState::Get(window)->IsPip(); |
| } |
| |
| void ExpandArcPipWindow() { |
| auto* pip_container = Shell::GetContainer(Shell::GetPrimaryRootWindow(), |
| kShellWindowId_PipContainer); |
| if (!pip_container) |
| return; |
| |
| auto pip_window_iter = |
| std::ranges::find_if(pip_container->children(), IsArcPipWindow); |
| if (pip_window_iter == pip_container->children().end()) |
| return; |
| |
| auto* window_state = WindowState::Get(*pip_window_iter); |
| window_state->Restore(); |
| } |
| |
| bool IsAnyWindowDragged() { |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (overview_controller->InOverviewSession() && |
| overview_controller->overview_session() |
| ->GetCurrentDraggedOverviewItem()) { |
| return true; |
| } |
| |
| for (aura::Window* window : |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) { |
| if (WindowState::Get(window)->is_dragged()) |
| return true; |
| } |
| return false; |
| } |
| |
| void FixWindowStackingAccordingToGlobalMru(aura::Window* window_to_fix) { |
| aura::Window* container = window_to_fix->parent(); |
| DCHECK(desks_util::IsDeskContainer(container)); |
| DCHECK_EQ(window_to_fix, wm::GetTransientRoot(window_to_fix)); |
| |
| const auto mru_windows = |
| Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal( |
| DesksMruType::kAllDesks); |
| // Find the closest sibling that is not a transient descendant, which |
| // `window_to_fix` should be stacked below. |
| aura::Window* closest_sibling_above_window = nullptr; |
| for (aura::Window* window : mru_windows) { |
| if (window == window_to_fix) { |
| if (closest_sibling_above_window) { |
| container->StackChildBelow(window_to_fix, closest_sibling_above_window); |
| } |
| return; |
| } |
| |
| if (window->parent() == container && |
| !wm::HasTransientAncestor(window, window_to_fix)) { |
| closest_sibling_above_window = window; |
| } |
| } |
| } |
| |
| aura::Window* GetTopWindow() { |
| MruWindowTracker::WindowList windows = |
| Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk); |
| |
| return windows.empty() ? nullptr : windows[0]; |
| } |
| |
| aura::Window* GetTopNonFloatedWindow() { |
| MruWindowTracker::WindowList windows = |
| Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk); |
| for (aura::Window* window : windows) { |
| if (!WindowState::Get(window)->IsFloated()) |
| return window; |
| } |
| return nullptr; |
| } |
| |
| aura::Window* GetFloatedWindowForActiveDesk() { |
| return Shell::Get()->float_controller()->FindFloatedWindowOfDesk( |
| DesksController::Get()->GetTargetActiveDesk()); |
| } |
| |
| bool ShouldMinimizeTopWindowOnBack() { |
| Shell* shell = Shell::Get(); |
| // We never want to minimize the main app window in the Kiosk session. |
| if (shell->session_controller()->IsRunningInAppMode()) { |
| return false; |
| } |
| |
| if (!display::Screen::Get()->InTabletMode()) { |
| return false; |
| } |
| |
| aura::Window* window = GetTopWindow(); |
| if (!window) { |
| return false; |
| } |
| |
| // Do not minimize the window if it is in overview. This can avoid unnecessary |
| // window minimize animation. |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (overview_controller->InOverviewSession() && |
| overview_controller->overview_session()->IsWindowInOverview(window)) { |
| return false; |
| } |
| |
| // ARC and crostini apps will handle the back event that follows on the client |
| // side and will minimize/close the window there. |
| const chromeos::AppType app_type = window->GetProperty(chromeos::kAppTypeKey); |
| if (app_type == chromeos::AppType::ARC_APP || |
| app_type == chromeos::AppType::CROSTINI_APP) { |
| return false; |
| } |
| |
| // Use the value of |kMinimizeOnBackKey| if it is provided. It can be provided |
| // by windows with custom web contents. |
| bool* can_minimize_on_back_key = window->GetProperty(kMinimizeOnBackKey); |
| if (can_minimize_on_back_key) |
| return *can_minimize_on_back_key; |
| |
| // Minimize the window if it is at the bottom page. |
| return !shell->shell_delegate()->CanGoBack(window); |
| } |
| |
| bool IsMinimizedOrTucked(aura::Window* window) { |
| DCHECK(window->parent()); |
| |
| WindowState* window_state = WindowState::Get(window); |
| if (!window_state) { |
| return false; |
| } |
| if (window_state->IsFloated()) { |
| return !window->is_destroying() && |
| Shell::Get()->float_controller()->IsFloatedWindowTuckedForTablet( |
| window); |
| } |
| return window_state->IsMinimized(); |
| } |
| |
| void SendBackKeyEvent(aura::Window* root_window) { |
| // Send up event as well as down event as ARC++ clients expect this |
| // sequence. |
| // TODO: Investigate if we should be using the current modifiers. |
| ui::KeyEvent press_key_event(ui::EventType::kKeyPressed, |
| ui::VKEY_BROWSER_BACK, ui::EF_NONE); |
| std::ignore = root_window->GetHost()->SendEventToSink(&press_key_event); |
| ui::KeyEvent release_key_event(ui::EventType::kKeyReleased, |
| ui::VKEY_BROWSER_BACK, ui::EF_NONE); |
| std::ignore = root_window->GetHost()->SendEventToSink(&release_key_event); |
| } |
| |
| WindowTransientDescendantIteratorRange GetVisibleTransientTreeIterator( |
| aura::Window* window) { |
| auto hide_predicate = [](aura::Window* window) { |
| return window->GetProperty(kHideInOverviewKey); |
| }; |
| return GetTransientTreeIterator(window, base::BindRepeating(hide_predicate)); |
| } |
| |
| void SetTransform(aura::Window* window, const gfx::Transform& transform) { |
| const gfx::PointF target_origin( |
| GetUnionScreenBoundsForWindow(window).origin()); |
| for (auto* window_iter : |
| window_util::GetVisibleTransientTreeIterator(window)) { |
| if (window_iter->GetProperty(kExcludeFromTransientTreeTransformKey)) { |
| continue; |
| } |
| aura::Window* parent_window = window_iter->parent(); |
| gfx::RectF original_bounds(window_iter->GetTargetBounds()); |
| ::wm::TranslateRectToScreen(parent_window, &original_bounds); |
| const gfx::Transform new_transform = TransformAboutPivot( |
| gfx::PointF(target_origin.x() - original_bounds.x(), |
| target_origin.y() - original_bounds.y()), |
| transform); |
| window_iter->SetTransform(new_transform); |
| } |
| } |
| |
| gfx::RectF GetTransformedBounds(aura::Window* transformed_window, |
| int top_inset) { |
| gfx::RectF bounds; |
| for (auto* window : GetVisibleTransientTreeIterator(transformed_window)) { |
| // Ignore other window types when computing bounding box of overview target |
| // item. |
| if (window != transformed_window && |
| window->GetType() != aura::client::WINDOW_TYPE_NORMAL) { |
| continue; |
| } |
| gfx::RectF window_bounds(window->GetTargetBounds()); |
| const gfx::Transform new_transform = TransformAboutPivot( |
| window_bounds.origin(), window->layer()->GetTargetTransform()); |
| window_bounds = new_transform.MapRect(window_bounds); |
| |
| // The preview title is shown above the preview window. Hide the window |
| // header for apps or browser windows with no tabs (web apps) to avoid |
| // showing both the window header and the preview title. |
| if (top_inset > 0) { |
| gfx::RectF header_bounds = window_bounds; |
| header_bounds.set_height(top_inset); |
| header_bounds = new_transform.MapRect(header_bounds); |
| window_bounds.Inset(gfx::InsetsF::TLBR(header_bounds.height(), 0, 0, 0)); |
| } |
| wm::TranslateRectToScreen(window->parent(), &window_bounds); |
| bounds.Union(window_bounds); |
| } |
| return bounds; |
| } |
| |
| views::BubbleDialogDelegate* AsBubbleDialogDelegate( |
| aura::Window* transient_window) { |
| views::Widget* widget = |
| views::Widget::GetWidgetForNativeWindow(transient_window); |
| if (!widget || !widget->widget_delegate()) { |
| return nullptr; |
| } |
| return widget->widget_delegate()->AsBubbleDialogDelegate(); |
| } |
| |
| views::DialogDelegate* AsDialogDelegate(aura::Window* transient_window) { |
| views::Widget* widget = |
| views::Widget::GetWidgetForNativeWindow(transient_window); |
| if (!widget || !widget->widget_delegate()) { |
| return nullptr; |
| } |
| |
| return widget->widget_delegate()->AsDialogDelegate(); |
| } |
| |
| bool ShouldShowForCurrentUser(aura::Window* window) { |
| MultiUserWindowManager* multi_user_window_manager = |
| MultiUserWindowManager::Get(); |
| if (!multi_user_window_manager) |
| return true; |
| |
| const AccountId account_id = |
| multi_user_window_manager->GetUserPresentingWindow(window); |
| // An empty account ID is returned if the window is presented for all users. |
| if (!account_id.is_valid()) |
| return true; |
| |
| return account_id == multi_user_window_manager->CurrentAccountId(); |
| } |
| |
| aura::Window* GetEventHandlerForEvent(const ui::LocatedEvent& event) { |
| gfx::Point location_in_screen = event.location(); |
| ::wm::ConvertPointToScreen(static_cast<aura::Window*>(event.target()), |
| &location_in_screen); |
| aura::Window* root_window_at_point = GetRootWindowAt(location_in_screen); |
| gfx::Point location_in_root = location_in_screen; |
| ::wm::ConvertPointFromScreen(root_window_at_point, &location_in_root); |
| return root_window_at_point->GetEventHandlerForPoint(location_in_root); |
| } |
| |
| bool IsNaturalScrollOn() { |
| PrefService* pref = |
| Shell::Get()->session_controller()->GetActivePrefService(); |
| return pref->GetBoolean(prefs::kTouchpadEnabled) && |
| pref->GetBoolean(prefs::kNaturalScroll); |
| } |
| |
| bool IsNaturalScrollOn(const ui::ScrollEvent& event) { |
| if (auto* touchpad_device_settings = |
| InputDeviceSettingsController::Get()->GetTouchpadSettings( |
| event.source_device_id())) { |
| return touchpad_device_settings->reverse_scrolling; |
| } |
| // This case mainly happens in the unit tests which generate fake touch |
| // events but have no touch devices. |
| // TODO(zxdan): We should `CHECK` the setting's ptr after we fix corresponding |
| // unit tests. |
| return IsNaturalScrollOn(); |
| } |
| |
| bool ShouldRoundThumbnailWindow(views::View* backdrop_view, |
| const gfx::RectF& thumbnail_bounds_in_screen) { |
| // If the backdrop is not created or not visible, round the thumbnail. |
| if (!backdrop_view || !backdrop_view->GetVisible()) { |
| return true; |
| } |
| |
| CHECK(backdrop_view->layer()); |
| // Get the bounds of the backdrop as a rounded rect object. This will allow us |
| // to use `gfx::RRectF::Contains` to check if `thumbnail_bounds_in_screen` is |
| // inside the rounding. For example, if the x,y,w,h all match and the rounding |
| // is non-zero, this will return false as the thumbnails corners will be |
| // considered out of bounds. |
| const gfx::RRectF backdrop_bounds_in_screen( |
| gfx::RRectF(gfx::RectF(backdrop_view->GetBoundsInScreen()), |
| backdrop_view->layer()->rounded_corner_radii())); |
| return !backdrop_bounds_in_screen.Contains(thumbnail_bounds_in_screen); |
| } |
| |
| float GetSnapRatioForWindow(aura::Window* window) { |
| WindowState* window_state = WindowState::Get(window); |
| return window_state->snap_ratio().value_or(chromeos::kDefaultSnapRatio); |
| } |
| |
| void RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref(prefs::kSnapWindowSuggestions, true); |
| } |
| |
| bool IsInFasterSplitScreenSetupSession(const aura::Window* window) { |
| SplitViewOverviewSession* split_view_overview_session = |
| RootWindowController::ForWindow(window)->split_view_overview_session(); |
| return !Shell::Get()->IsInTabletMode() && split_view_overview_session && |
| split_view_overview_session->setup_type() == |
| SplitViewOverviewSetupType::kSnapThenAutomaticOverview; |
| } |
| |
| bool IsInFasterSplitScreenSetupSession() { |
| if (!IsInOverviewSession() || display::Screen::Get()->InTabletMode()) { |
| return false; |
| } |
| auto* overview_session = GetOverviewSession(); |
| for (const auto& grid : overview_session->grid_list()) { |
| // Return true if any grid is in faster splitscreen setup. |
| if (IsInFasterSplitScreenSetupSession(grid->root_window())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| gfx::Rect GetTargetScreenBounds(aura::Window* window) { |
| gfx::Rect bounds_in_screen(window->GetTargetBounds()); |
| wm::ConvertRectToScreen(window->parent(), &bounds_in_screen); |
| return bounds_in_screen; |
| } |
| |
| } // namespace ash::window_util |