// Copyright 2016 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/wm/screen_pinning_controller.h" #include #include #include #include "ash/public/cpp/shell_window_ids.h" #include "ash/shell.h" #include "ash/window_user_data.h" #include "ash/wm/always_on_top_controller.h" #include "ash/wm/container_finder.h" #include "ash/wm/window_dimmer.h" #include "ash/wm/window_state.h" #include "base/auto_reset.h" #include "base/logging.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" #include "ui/compositor/layer.h" #include "ui/wm/core/window_util.h" namespace ash { namespace { // Returns a list of Windows corresponding to SystemModalContainers, // except ones whose root is shared with |pinned_window|. aura::Window::Windows GetSystemModalWindowsExceptPinned( aura::Window* pinned_window) { aura::Window* pinned_root = pinned_window->GetRootWindow(); aura::Window::Windows result; for (aura::Window* system_modal : GetContainersForAllRootWindows(kShellWindowId_SystemModalContainer)) { if (system_modal->GetRootWindow() == pinned_root) continue; result.push_back(system_modal); } return result; } void AddObserverToChildren(aura::Window* container, aura::WindowObserver* observer) { for (aura::Window* child : container->children()) child->AddObserver(observer); } void RemoveObserverFromChildren(aura::Window* container, aura::WindowObserver* observer) { for (aura::Window* child : container->children()) child->RemoveObserver(observer); } // Returns true if the aura::Window from a WindowDimmer is visible. bool IsWindowDimmerWindowVisible(WindowDimmer* window_dimmer) { return window_dimmer->window()->layer()->GetTargetVisibility(); } } // namespace // Adapter to fire OnPinnedContainerWindowStackingChanged(). // TODO(oshima): Consider using aura::clinet::WindowStakingClient instead. class ScreenPinningController::PinnedContainerChildWindowObserver : public aura::WindowObserver { public: explicit PinnedContainerChildWindowObserver( ScreenPinningController* controller) : controller_(controller) {} PinnedContainerChildWindowObserver( const PinnedContainerChildWindowObserver&) = delete; PinnedContainerChildWindowObserver& operator=( const PinnedContainerChildWindowObserver&) = delete; void OnWindowStackingChanged(aura::Window* window) override { controller_->OnPinnedContainerWindowStackingChanged(window); } private: ScreenPinningController* controller_; }; // Adapter to translate OnWindowAdded/OnWillRemoveWindow for the container // containing the pinned window, to the corresponding controller's methods. class ScreenPinningController::PinnedContainerWindowObserver : public aura::WindowObserver { public: explicit PinnedContainerWindowObserver(ScreenPinningController* controller) : controller_(controller) {} PinnedContainerWindowObserver(const PinnedContainerWindowObserver&) = delete; PinnedContainerWindowObserver& operator=( const PinnedContainerWindowObserver&) = delete; void OnWindowAdded(aura::Window* new_window) override { controller_->OnWindowAddedToPinnedContainer(new_window); } void OnWillRemoveWindow(aura::Window* window) override { controller_->OnWillRemoveWindowFromPinnedContainer(window); } void OnWindowDestroying(aura::Window* window) override { // Just in case. There is nothing we can do here. window->RemoveObserver(this); } private: ScreenPinningController* controller_; }; // Adapter to fire OnSystemModalContainerWindowStackingChanged(). class ScreenPinningController::SystemModalContainerChildWindowObserver : public aura::WindowObserver { public: explicit SystemModalContainerChildWindowObserver( ScreenPinningController* controller) : controller_(controller) {} SystemModalContainerChildWindowObserver( const SystemModalContainerChildWindowObserver&) = delete; SystemModalContainerChildWindowObserver& operator=( const SystemModalContainerChildWindowObserver&) = delete; void OnWindowStackingChanged(aura::Window* window) override { controller_->OnSystemModalContainerWindowStackingChanged(window); } private: ScreenPinningController* controller_; }; // Adapter to translate OnWindowAdded/OnWillRemoveWindow for the // SystemModalContainer to the corresponding controller's methods. class ScreenPinningController::SystemModalContainerWindowObserver : public aura::WindowObserver { public: explicit SystemModalContainerWindowObserver( ScreenPinningController* controller) : controller_(controller) {} SystemModalContainerWindowObserver( const SystemModalContainerWindowObserver&) = delete; SystemModalContainerWindowObserver& operator=( const SystemModalContainerWindowObserver&) = delete; void OnWindowAdded(aura::Window* new_window) override { controller_->OnWindowAddedToSystemModalContainer(new_window); } void OnWillRemoveWindow(aura::Window* window) override { controller_->OnWillRemoveWindowFromSystemModalContainer(window); } void OnWindowDestroying(aura::Window* window) override { // Just in case. There is nothing we can do here. window->RemoveObserver(this); } private: ScreenPinningController* controller_; }; ScreenPinningController::ScreenPinningController() : window_dimmers_(std::make_unique>()), pinned_container_window_observer_( std::make_unique(this)), pinned_container_child_window_observer_( std::make_unique(this)), system_modal_container_window_observer_( std::make_unique(this)), system_modal_container_child_window_observer_( std::make_unique(this)) { Shell::Get()->window_tree_host_manager()->AddObserver(this); } ScreenPinningController::~ScreenPinningController() { Shell::Get()->window_tree_host_manager()->RemoveObserver(this); if (pinned_window_) pinned_window_->RemoveObserver(this); pinned_window_ = nullptr; } bool ScreenPinningController::IsPinned() const { return pinned_window_ != nullptr; } void ScreenPinningController::SetPinnedWindow(aura::Window* pinned_window) { if (WindowState::Get(pinned_window)->IsPinned()) { if (pinned_window_) { LOG(DFATAL) << "Pinned mode is enabled, while it is already in " << "the pinned mode"; return; } aura::Window* container = pinned_window->parent(); aura::Window::Windows system_modal_containers = GetSystemModalWindowsExceptPinned(pinned_window); // Set up the container which has the pinned window. pinned_window_ = pinned_window; // To monitor destruction. pinned_window_->AddObserver(this); AlwaysOnTopController::SetDisallowReparent(pinned_window); container->StackChildAtTop(pinned_window); container->StackChildBelow(CreateWindowDimmer(container), pinned_window); // Set the dim windows to the system containers, other than the one which // the root window of the pinned window holds. for (aura::Window* system_modal : system_modal_containers) system_modal->StackChildAtBottom(CreateWindowDimmer(system_modal)); // Set observers. container->AddObserver(pinned_container_window_observer_.get()); AddObserverToChildren(container, pinned_container_child_window_observer_.get()); for (aura::Window* system_modal : system_modal_containers) { system_modal->AddObserver(system_modal_container_window_observer_.get()); AddObserverToChildren( system_modal, system_modal_container_child_window_observer_.get()); } } else { if (pinned_window != pinned_window_) { LOG(DFATAL) << "Pinned mode is being disabled, but for the different " << "target window."; return; } ResetWindowPinningState(); } Shell::Get()->NotifyPinnedStateChanged(pinned_window); } void ScreenPinningController::OnWindowAddedToPinnedContainer( aura::Window* new_window) { KeepPinnedWindowOnTop(); new_window->AddObserver(pinned_container_child_window_observer_.get()); } void ScreenPinningController::OnWillRemoveWindowFromPinnedContainer( aura::Window* window) { window->RemoveObserver(pinned_container_child_window_observer_.get()); } void ScreenPinningController::OnPinnedContainerWindowStackingChanged( aura::Window* window) { KeepPinnedWindowOnTop(); } void ScreenPinningController::OnWindowAddedToSystemModalContainer( aura::Window* new_window) { KeepDimWindowAtBottom(new_window->parent()); new_window->AddObserver(system_modal_container_child_window_observer_.get()); } void ScreenPinningController::OnWillRemoveWindowFromSystemModalContainer( aura::Window* window) { window->RemoveObserver(system_modal_container_child_window_observer_.get()); } void ScreenPinningController::OnSystemModalContainerWindowStackingChanged( aura::Window* window) { KeepDimWindowAtBottom(window->parent()); } aura::Window* ScreenPinningController::CreateWindowDimmer( aura::Window* container) { std::unique_ptr window_dimmer = std::make_unique(container); window_dimmer->SetDimOpacity(1); // Fully opaque. AlwaysOnTopController::SetDisallowReparent(window_dimmer->window()); ::wm::SetWindowFullscreen(window_dimmer->window(), true); window_dimmer->window()->Show(); aura::Window* window = window_dimmer->window(); window_dimmers_->Set(container, std::move(window_dimmer)); return window; } void ScreenPinningController::ResetWindowPinningState() { aura::Window* container = pinned_window_->parent(); aura::Window::Windows system_modal_containers = GetSystemModalWindowsExceptPinned(pinned_window_); // Unset observers. for (aura::Window* system_modal : GetSystemModalWindowsExceptPinned(pinned_window_)) { RemoveObserverFromChildren( system_modal, system_modal_container_child_window_observer_.get()); system_modal->RemoveObserver(system_modal_container_window_observer_.get()); } RemoveObserverFromChildren(container, pinned_container_child_window_observer_.get()); container->RemoveObserver(pinned_container_window_observer_.get()); window_dimmers_->clear(); pinned_window_->RemoveObserver(this); pinned_window_ = nullptr; } void ScreenPinningController::OnDisplayConfigurationChanged() { // Note: this is called on display attached or detached. if (!IsPinned()) return; // On display detaching, all necessary windows are transferred to the // primary display's tree, and called this. // So, delete WindowDimmers which are not a part of target system modal // container. // On display attaching, the new system modal container does not have the // WindowDimmer. So create it. // First, delete unnecessary WindowDimmers. for (aura::Window* container : window_dimmers_->GetWindows()) { if (container != pinned_window_->parent() && !IsWindowDimmerWindowVisible(window_dimmers_->Get(container))) { window_dimmers_->Set(container, nullptr); } } // Then, create missing WindowDimmers. aura::Window::Windows system_modal_containers = GetSystemModalWindowsExceptPinned(pinned_window_); for (aura::Window* system_modal : system_modal_containers) { if (window_dimmers_->Get(system_modal)) { // |system_modal| already has a WindowDimmer. continue; } // This is the new system modal dialog. system_modal->StackChildAtBottom(CreateWindowDimmer(system_modal)); // Set observers to the tree. system_modal->AddObserver(system_modal_container_window_observer_.get()); AddObserverToChildren(system_modal, system_modal_container_child_window_observer_.get()); } } void ScreenPinningController::OnWindowDestroying(aura::Window* window) { DCHECK_EQ(pinned_window_, window); WindowState::Get(window)->Restore(); // |pinned_window_| isn't cleared, which means the call to restore window // didn't unpin itself. This is possible because the window is being // destroyed and some requests are ignored, but we still want to restore // the internal state of |ScreenPinningController| so that other windows // can be pinned again. if (pinned_window_) ResetWindowPinningState(); } void ScreenPinningController::KeepPinnedWindowOnTop() { if (in_restacking_) return; base::AutoReset auto_reset(&in_restacking_, true); aura::Window* container = pinned_window_->parent(); container->StackChildAtTop(pinned_window_); WindowDimmer* pinned_window_dimmer = window_dimmers_->Get(container); if (pinned_window_dimmer && pinned_window_dimmer->window()) container->StackChildBelow(pinned_window_dimmer->window(), pinned_window_); } void ScreenPinningController::KeepDimWindowAtBottom(aura::Window* container) { if (in_restacking_) return; WindowDimmer* window_dimmer = window_dimmers_->Get(container); if (window_dimmer) { base::AutoReset auto_reset(&in_restacking_, true); container->StackChildAtBottom(window_dimmer->window()); } } } // namespace ash