blob: df6c434d821e17e5ad3f67beacf218217db030a7 [file] [log] [blame]
// 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/ash_focus_rules.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/container_finder.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_state.h"
#include "base/stl_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/events/event.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
bool BelongsToContainerWithEqualOrGreaterId(const aura::Window* window,
int container_id) {
for (; window; window = window->parent()) {
if (window->id() >= container_id)
return true;
}
return false;
}
bool BelongsToContainerWithId(const aura::Window* window, int container_id) {
for (; window; window = window->parent()) {
if (window->id() == container_id)
return true;
}
return false;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// AshFocusRules, public:
AshFocusRules::AshFocusRules()
: activatable_container_ids_(GetActivatableShellWindowIds()) {}
AshFocusRules::~AshFocusRules() = default;
////////////////////////////////////////////////////////////////////////////////
// AshFocusRules, ::wm::FocusRules:
bool AshFocusRules::IsToplevelWindow(const aura::Window* window) const {
DCHECK(window);
// The window must be in a valid hierarchy.
if (!window->GetRootWindow() || !window->parent())
return false;
// The window must exist within a container that supports activation.
// The window cannot be blocked by a modal transient.
return base::ContainsValue(activatable_container_ids_,
window->parent()->id());
}
bool AshFocusRules::SupportsChildActivation(const aura::Window* window) const {
return base::ContainsValue(activatable_container_ids_, window->id());
}
bool AshFocusRules::IsWindowConsideredVisibleForActivation(
const aura::Window* window) const {
DCHECK(window);
// If the |window| doesn't belong to the current active user and also doesn't
// show for the current active user, then it should not be activated.
if (!Shell::Get()->shell_delegate()->CanShowWindowForUser(window))
return false;
if (window->IsVisible())
return true;
const aura::Window* const parent = window->parent();
const bool is_on_active_desk = desks_util::IsActiveDeskContainer(parent);
// Minimized windows are hidden in their minimized state, but they can always
// be activated, given that they're on the active desk.
if (wm::GetWindowState(window)->IsMinimized() && is_on_active_desk)
return true;
if (!window->TargetVisibility())
return false;
return is_on_active_desk ||
parent->id() == kShellWindowId_LockScreenContainer;
}
bool AshFocusRules::CanActivateWindow(const aura::Window* window) const {
// Clearing activation is always permissible.
if (!window)
return true;
if (!BaseFocusRules::CanActivateWindow(window))
return false;
// Special case to allow the login shelf to be activatable when the OOBE
// modal is visible. See http://crbug/871184
// TODO: remove this special case once login shelf is moved into a child
// widget of the lock screen (https://crbug.com/767235).
if (Shell::Get()->session_controller()->IsUserSessionBlocked() &&
BelongsToContainerWithId(window, kShellWindowId_ShelfContainer)) {
return true;
}
int modal_container_id = Shell::GetOpenSystemModalWindowContainerId();
if (modal_container_id >= 0)
return BelongsToContainerWithEqualOrGreaterId(window, modal_container_id);
return true;
}
bool AshFocusRules::CanFocusWindow(const aura::Window* window,
const ui::Event* event) const {
if (!window)
return true;
if (event && (event->IsMouseEvent() || event->IsGestureEvent()) &&
!window->GetProperty(aura::client::kActivateOnPointerKey)) {
return false;
}
return BaseFocusRules::CanFocusWindow(window, event);
}
aura::Window* AshFocusRules::GetNextActivatableWindow(
aura::Window* ignore) const {
DCHECK(ignore);
// If the window that just lost focus |ignore| has a transient parent, then
// start from the container of that parent, otherwise start from the container
// of the most-recently-used window. If the list of MRU windows is empty, then
// start from the container of |ignore|.
aura::Window* starting_window = nullptr;
aura::Window* transient_parent = ::wm::GetTransientParent(ignore);
if (transient_parent) {
starting_window = transient_parent;
} else {
MruWindowTracker* mru = Shell::Get()->mru_window_tracker();
aura::Window::Windows windows = mru->BuildMruWindowList();
starting_window = windows.empty() ? ignore : windows[0];
}
DCHECK(starting_window);
// Look for windows to focus in |starting_window|'s container. If none are
// found, we look in all the containers in front of |starting_window|'s
// container, then all behind.
int starting_container_index = 0;
aura::Window* root = starting_window->GetRootWindow();
if (!root)
root = Shell::GetRootWindowForNewWindows();
int container_count = activatable_container_ids_.size();
for (int i = 0; i < container_count; i++) {
aura::Window* container =
Shell::GetContainer(root, activatable_container_ids_[i]);
if (container && container->Contains(starting_window)) {
starting_container_index = i;
break;
}
}
aura::Window* window = nullptr;
for (int i = starting_container_index; !window && i < container_count; i++)
window = GetTopmostWindowToActivateForContainerIndex(i, ignore);
if (!window && starting_container_index > 0) {
for (int i = starting_container_index - 1; !window && i >= 0; i--)
window = GetTopmostWindowToActivateForContainerIndex(i, ignore);
}
return window;
}
////////////////////////////////////////////////////////////////////////////////
// AshFocusRules, private:
aura::Window* AshFocusRules::GetTopmostWindowToActivateForContainerIndex(
int index,
aura::Window* ignore) const {
aura::Window* window = nullptr;
aura::Window* root = ignore ? ignore->GetRootWindow() : nullptr;
aura::Window::Windows containers = wm::GetContainersFromAllRootWindows(
activatable_container_ids_[index], root);
for (aura::Window* container : containers) {
window = GetTopmostWindowToActivateInContainer(container, ignore);
if (window)
return window;
}
return window;
}
aura::Window* AshFocusRules::GetTopmostWindowToActivateInContainer(
aura::Window* container,
aura::Window* ignore) const {
for (aura::Window::Windows::const_reverse_iterator i =
container->children().rbegin();
i != container->children().rend(); ++i) {
wm::WindowState* window_state = wm::GetWindowState(*i);
if (*i != ignore && window_state->CanActivate() &&
!window_state->IsMinimized())
return *i;
}
return nullptr;
}
} // namespace ash