// Copyright (c) 2012 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/activation_controller.h" #include "ash/root_window_controller.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" #include "ash/wm/activation_controller_delegate.h" #include "ash/wm/property_util.h" #include "ash/wm/window_util.h" #include "base/auto_reset.h" #include "ui/aura/client/activation_change_observer.h" #include "ui/aura/client/activation_delegate.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/focus_client.h" #include "ui/aura/env.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" #include "ui/base/ui_base_types.h" #include "ui/compositor/layer.h" #include "ui/views/corewm/window_modality_controller.h" namespace ash { namespace internal { namespace { // These are the list of container ids of containers which may contain windows // that need to be activated in the order that they should be activated. const int kWindowContainerIds[] = { kShellWindowId_LockSystemModalContainer, kShellWindowId_SettingBubbleContainer, kShellWindowId_LockScreenContainer, kShellWindowId_SystemModalContainer, kShellWindowId_AlwaysOnTopContainer, kShellWindowId_AppListContainer, kShellWindowId_DefaultContainer, // Docked, panel, launcher and status are intentionally checked after other // containers even though these layers are higher. The user expects their // windows to be focused before these elements. kShellWindowId_DockedContainer, kShellWindowId_PanelContainer, kShellWindowId_ShelfContainer, kShellWindowId_StatusContainer, }; bool BelongsToContainerWithEqualOrGreaterId(const aura::Window* window, int container_id) { for (; window; window = window->parent()) { if (window->id() >= container_id) return true; } return false; } // Returns true if children of |window| can be activated. // These are the only containers in which windows can receive focus. bool SupportsChildActivation(aura::Window* window) { for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) { if (window->id() == kWindowContainerIds[i]) return true; } return false; } bool HasModalTransientChild(aura::Window* window) { aura::Window::Windows::const_iterator it; for (it = window->transient_children().begin(); it != window->transient_children().end(); ++it) { if ((*it)->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_WINDOW) return true; } return false; } // See description in VisibilityMatches. enum ActivateVisibilityType { TARGET_VISIBILITY, CURRENT_VISIBILITY, }; // Used by CanActivateWindowWithEvent() to test the visibility of a window. // This is used by two distinct code paths: // . when activating from an event we only care about the actual visibility. // . when activating because of a keyboard accelerator, in which case we // care about the TargetVisibility. bool VisibilityMatches(aura::Window* window, ActivateVisibilityType type) { bool visible = (type == CURRENT_VISIBILITY) ? window->IsVisible() : window->TargetVisibility(); return visible || wm::IsWindowMinimized(window) || (window->TargetVisibility() && (window->parent()->id() == kShellWindowId_DefaultContainer || window->parent()->id() == kShellWindowId_LockScreenContainer)); } // Returns true if |window| can be activated or deactivated. // A window manager typically defines some notion of "top level window" that // supports activation/deactivation. bool CanActivateWindowWithEvent(aura::Window* window, const ui::Event* event, ActivateVisibilityType visibility_type) { return window && VisibilityMatches(window, visibility_type) && (!aura::client::GetActivationDelegate(window) || aura::client::GetActivationDelegate(window)->ShouldActivate()) && SupportsChildActivation(window->parent()) && (BelongsToContainerWithEqualOrGreaterId( window, kShellWindowId_SystemModalContainer) || !Shell::GetInstance()->IsSystemModalWindowOpen()); } // When a modal window is activated, we bring its entire transient parent chain // to the front. This function must be called before the modal transient is // stacked at the top to ensure correct stacking order. void StackTransientParentsBelowModalWindow(aura::Window* window) { if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_WINDOW) return; aura::Window* transient_parent = window->transient_parent(); while (transient_parent) { transient_parent->parent()->StackChildAtTop(transient_parent); transient_parent = transient_parent->transient_parent(); } } aura::Window* FindFocusableWindowFor(aura::Window* window) { while (window && !window->CanFocus()) window = window->parent(); return window; } } // namespace //////////////////////////////////////////////////////////////////////////////// // ActivationController, public: ActivationController::ActivationController( aura::client::FocusClient* focus_client, ActivationControllerDelegate* delegate) : focus_client_(focus_client), updating_activation_(false), active_window_(NULL), observer_manager_(this), delegate_(delegate) { aura::Env::GetInstance()->AddObserver(this); focus_client_->AddObserver(this); } ActivationController::~ActivationController() { aura::Env::GetInstance()->RemoveObserver(this); focus_client_->RemoveObserver(this); } // static aura::Window* ActivationController::GetActivatableWindow( aura::Window* window, const ui::Event* event) { aura::Window* parent = window->parent(); aura::Window* child = window; while (parent) { if (CanActivateWindowWithEvent(child, event, CURRENT_VISIBILITY)) return child; // If |child| isn't activatable, but has transient parent, trace // that path instead. if (child->transient_parent()) return GetActivatableWindow(child->transient_parent(), event); parent = parent->parent(); child = child->parent(); } return NULL; } bool ActivationController::CanActivateWindow(aura::Window* window) const { return CanActivateWindowWithEvent(window, NULL, TARGET_VISIBILITY) && !HasModalTransientChild(window); } //////////////////////////////////////////////////////////////////////////////// // ActivationController, aura::client::ActivationClient implementation: void ActivationController::AddObserver( aura::client::ActivationChangeObserver* observer) { observers_.AddObserver(observer); } void ActivationController::RemoveObserver( aura::client::ActivationChangeObserver* observer) { observers_.RemoveObserver(observer); } void ActivationController::ActivateWindow(aura::Window* window) { ActivateWindowWithEvent(window, NULL); } void ActivationController::DeactivateWindow(aura::Window* window) { if (window) ActivateNextWindow(window); } aura::Window* ActivationController::GetActiveWindow() { return active_window_; } aura::Window* ActivationController::GetActivatableWindow(aura::Window* window) { return GetActivatableWindow(window, NULL); } aura::Window* ActivationController::GetToplevelWindow(aura::Window* window) { return GetActivatableWindow(window, NULL); } bool ActivationController::OnWillFocusWindow(aura::Window* window, const ui::Event* event) { return CanActivateWindowWithEvent( GetActivatableWindow(window, event), event, CURRENT_VISIBILITY); } //////////////////////////////////////////////////////////////////////////////// // ActivationController, aura::WindowObserver implementation: void ActivationController::OnWindowVisibilityChanged(aura::Window* window, bool visible) { if (!visible) { aura::Window* next_window = ActivateNextWindow(window); if (next_window && next_window->parent() == window->parent()) { // Despite the activation change, we need to keep the window being hidden // stacked above the new window so it stays on top as it animates away. window->layer()->parent()->StackAbove(window->layer(), next_window->layer()); } } } void ActivationController::OnWindowDestroying(aura::Window* window) { // Don't use wm::IsActiveWidnow in case the |window| has already been // removed from the root tree. if (active_window_ == window) { active_window_ = NULL; FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, observers_, OnWindowActivated(NULL, window)); ActivateWindow(GetTopmostWindowToActivate(window)); } observer_manager_.Remove(window); } //////////////////////////////////////////////////////////////////////////////// // ActivationController, aura::EnvObserver implementation: void ActivationController::OnWindowInitialized(aura::Window* window) { observer_manager_.Add(window); } //////////////////////////////////////////////////////////////////////////////// // ActivationController, aura::RootWindowObserver implementation: void ActivationController::OnWindowFocused(aura::Window* gained_focus, aura::Window* lost_focus) { if (gained_focus) ActivateWindow(GetActivatableWindow(gained_focus, NULL)); } //////////////////////////////////////////////////////////////////////////////// // ActivationController, ui::EventHandler implementation: void ActivationController::OnKeyEvent(ui::KeyEvent* event) { } void ActivationController::OnMouseEvent(ui::MouseEvent* event) { if (event->type() == ui::ET_MOUSE_PRESSED) FocusWindowWithEvent(event); } void ActivationController::OnScrollEvent(ui::ScrollEvent* event) { } void ActivationController::OnTouchEvent(ui::TouchEvent* event) { if (event->type() == ui::ET_TOUCH_PRESSED) FocusWindowWithEvent(event); } void ActivationController::OnGestureEvent(ui::GestureEvent* event) { if (event->type() == ui::ET_GESTURE_BEGIN && event->details().touch_points() == 1) { FocusWindowWithEvent(event); } } //////////////////////////////////////////////////////////////////////////////// // ActivationController, private: void ActivationController::ActivateWindowWithEvent(aura::Window* window, const ui::Event* event) { // Prevent recursion when called from focus. if (updating_activation_) return; base::AutoReset in_activate_window(&updating_activation_, true); // We allow the delegate to change which window gets activated, or to prevent // activation changes. aura::Window* original_active_window = window; window = delegate_->WillActivateWindow(window); // TODO(beng): note that this breaks the previous behavior where an activation // attempt by a window behind the lock screen would at least // restack that window frontmost within its container. fix this. if (!window && original_active_window != window) return; // TODO(beng): This encapsulates additional Ash-specific restrictions on // whether activation can change. Should move to the delegate. if (window && !CanActivateWindowWithEvent(window, event, CURRENT_VISIBILITY)) return; if (active_window_ == window) return; aura::Window* old_active = active_window_; active_window_ = window; if (window && !window->Contains(aura::client::GetFocusClient(window)-> GetFocusedWindow())) { aura::client::GetFocusClient(window)->FocusWindow(window); } if (window) { StackTransientParentsBelowModalWindow(window); window->parent()->StackChildAtTop(window); } FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, observers_, OnWindowActivated(window, old_active)); if (aura::client::GetActivationChangeObserver(old_active)) { aura::client::GetActivationChangeObserver(old_active)->OnWindowActivated( window, old_active); } if (aura::client::GetActivationChangeObserver(window)) { aura::client::GetActivationChangeObserver(window)->OnWindowActivated( window, old_active); } } aura::Window* ActivationController::ActivateNextWindow(aura::Window* window) { aura::Window* next_window = NULL; if (wm::IsActiveWindow(window)) { next_window = GetTopmostWindowToActivate(window); ActivateWindow(next_window); } return next_window; } aura::Window* ActivationController::GetTopmostWindowToActivate( aura::Window* ignore) const { size_t current_container_index = 0; // If the container of the window losing focus is in the list, start from that // container. aura::RootWindow* root = ignore->GetRootWindow(); if (!root) root = Shell::GetActiveRootWindow(); for (size_t i = 0; ignore && i < arraysize(kWindowContainerIds); i++) { aura::Window* container = Shell::GetContainer(root, kWindowContainerIds[i]); if (container && container->Contains(ignore)) { current_container_index = i; break; } } // Look for windows to focus in that container and below. aura::Window* window = NULL; for (; !window && current_container_index < arraysize(kWindowContainerIds); current_container_index++) { aura::Window::Windows containers = Shell::GetContainersFromAllRootWindows( kWindowContainerIds[current_container_index], root); for (aura::Window::Windows::const_iterator iter = containers.begin(); iter != containers.end() && !window; ++iter) { window = GetTopmostWindowToActivateInContainer((*iter), ignore); } } return window; } aura::Window* ActivationController::GetTopmostWindowToActivateInContainer( aura::Window* container, aura::Window* ignore) const { for (aura::Window::Windows::const_reverse_iterator i = container->children().rbegin(); i != container->children().rend(); ++i) { if (*i != ignore && CanActivateWindowWithEvent(*i, NULL, CURRENT_VISIBILITY) && !wm::IsWindowMinimized(*i)) return *i; } return NULL; } void ActivationController::FocusWindowWithEvent(const ui::Event* event) { aura::Window* window = static_cast(event->target()); window = delegate_->WillFocusWindow(window); if (GetActiveWindow() != window) { aura::client::GetFocusClient(window)->FocusWindow( FindFocusableWindowFor(window)); } } } // namespace internal } // namespace ash