blob: 290579a12df160dffd2c9400a2d2e3d18c99833d [file] [log] [blame]
// Copyright 2019 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/desks/desk.h"
#include <utility>
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_transient_descendant_iterator.h"
#include "ash/wm/window_util.h"
namespace ash {
class DeskContainerObserver : public aura::WindowObserver {
public:
DeskContainerObserver(Desk* owner, aura::Window* container)
: owner_(owner), container_(container) {
DCHECK_EQ(container_->id(), owner_->container_id());
container->AddObserver(this);
}
~DeskContainerObserver() override { container_->RemoveObserver(this); }
// aura::WindowObserver:
void OnWindowAdded(aura::Window* new_window) override {
// TODO(afakhry): Overview mode creates a new widget for each window under
// the same parent for the CaptionContainerView. We will be notified with
// this window addition here. Consider ignoring these windows if they cause
// problems.
owner_->AddWindowToDesk(new_window);
}
void OnWindowDestroyed(aura::Window* window) override {
// We should never get here. We should be notified in
// `OnRootWindowClosing()` before the child containers of the root window
// are destroyed, and this object should have already been destroyed.
NOTREACHED();
}
private:
Desk* const owner_;
aura::Window* const container_;
DISALLOW_COPY_AND_ASSIGN(DeskContainerObserver);
};
// -----------------------------------------------------------------------------
// Desk:
Desk::Desk(int associated_container_id)
: container_id_(associated_container_id) {
// For the very first default desk added during initialization, there won't be
// any root windows yet. That's OK, OnRootWindowAdded() will be called
// explicitly by the RootWindowController when they're initialized.
for (aura::Window* root : Shell::GetAllRootWindows())
OnRootWindowAdded(root);
}
Desk::~Desk() {
DCHECK(windows_.empty()) << "DesksController should remove my windows first.";
for (auto& observer : observers_) {
observers_.RemoveObserver(&observer);
observer.OnDeskDestroyed(this);
}
}
void Desk::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void Desk::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void Desk::OnRootWindowAdded(aura::Window* root) {
DCHECK(!roots_to_containers_observers_.count(root));
aura::Window* desk_container = root->GetChildById(container_id_);
DCHECK(desk_container);
auto container_observer =
std::make_unique<DeskContainerObserver>(this, desk_container);
roots_to_containers_observers_.emplace(root, std::move(container_observer));
}
void Desk::OnRootWindowClosing(aura::Window* root) {
const size_t count = roots_to_containers_observers_.erase(root);
DCHECK(count);
}
void Desk::AddWindowToDesk(aura::Window* window) {
if (windows_.count(window))
return;
for (auto* transient_window : wm::GetTransientTreeIterator(window)) {
const auto result = windows_.emplace(transient_window);
// GetTransientTreeIterator() starts iterating the transient tree hierarchy
// from the root transient parent, which may include windows that we already
// track from before.
if (result.second)
transient_window->AddObserver(this);
}
NotifyContentChanged();
}
base::AutoReset<bool> Desk::GetScopedNotifyContentChangedDisabler() {
return base::AutoReset<bool>(&should_notify_content_changed_, false);
}
void Desk::Activate(bool update_window_activation) {
// Show the associated containers on all roots.
for (aura::Window* root : Shell::GetAllRootWindows())
root->GetChildById(container_id_)->Show();
is_active_ = true;
if (!update_window_activation || windows_.empty())
return;
// Activate the window on this desk that was most recently used right before
// the user switched to another desk, so as not to break the user's workflow.
for (auto* window :
Shell::Get()->mru_window_tracker()->BuildMruWindowList()) {
if (!windows_.contains(window))
continue;
// Do not activate minimized windows, otherwise they will unminimize.
if (wm::GetWindowState(window)->IsMinimized())
continue;
wm::ActivateWindow(window);
return;
}
}
void Desk::Deactivate(bool update_window_activation) {
auto* active_window = wm::GetActiveWindow();
// Hide the associated containers on all roots.
for (aura::Window* root : Shell::GetAllRootWindows())
root->GetChildById(container_id_)->Hide();
is_active_ = false;
if (!update_window_activation)
return;
// Deactivate the active window (if it belongs to this desk; active window may
// be on a different container, or one of the widgets created by overview mode
// which are not considered desk windows) after this desk's associated
// containers have been hidden. This is to prevent the focus controller from
// activating another window on the same desk when the active window loses
// focus.
if (active_window && windows_.contains(active_window))
wm::DeactivateWindow(active_window);
}
void Desk::MoveWindowsToDesk(Desk* target_desk) {
DCHECK(target_desk);
{
// Throttle notifying the observers, while we move those windows and notify
// them only once when done.
auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
auto target_desk_throttled =
target_desk->GetScopedNotifyContentChangedDisabler();
for (auto* window : windows_)
MoveWindowToDeskInternal(window, target_desk);
windows_.clear();
}
NotifyContentChanged();
target_desk->NotifyContentChanged();
}
void Desk::MoveWindowToDesk(aura::Window* window, Desk* target_desk) {
DCHECK(target_desk);
DCHECK(window);
DCHECK(windows_.contains(window));
DCHECK(this != target_desk);
{
// TODO(afakhry): Throttling here should not be necessary for a single
// window. It should be possible to rely on
// DeskContainerObserver::OnWindowAdded() but the problem is we don't have
// aura::WindowObserver::OnWindowRemoved(); we instead only have
// aura::WindowObserver::OnWillRemoveWindow(). This won't work as we should
// only notify and refresh the mini_views once the window is no longer in
// the tree. Two possible solutions:
// - Add aura::WindowObserver::OnWindowRemoved(), or
// - Remove `DeskContainerObserver` entirely and rely on
// `WorkspaceLayoutManager`, which already has OnWindowAddedToLayout(),
// and OnWindowRemovedFromLayout().
auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
auto target_desk_throttled =
target_desk->GetScopedNotifyContentChangedDisabler();
MoveWindowToDeskInternal(window, target_desk);
windows_.erase(window);
}
NotifyContentChanged();
target_desk->NotifyContentChanged();
}
aura::Window* Desk::GetDeskContainerForRoot(aura::Window* root) const {
DCHECK(root);
return root->GetChildById(container_id_);
}
void Desk::OnWindowDestroyed(aura::Window* window) {
// We listen to `OnWindowDestroyed()` as opposed to `OnWindowDestroying()`
// since we want to refresh the mini_views only after the window has been
// removed from the window tree hierarchy.
const size_t count = windows_.erase(window);
DCHECK(count);
// No need to refresh the mini_views if the destroyed window doesn't show up
// there in the first place.
if (!window->GetProperty(kHideInDeskMiniViewKey))
NotifyContentChanged();
}
void Desk::NotifyContentChanged() {
if (!should_notify_content_changed_)
return;
for (auto& observer : observers_)
observer.OnContentChanged();
}
void Desk::MoveWindowToDeskInternal(aura::Window* window, Desk* target_desk) {
DCHECK(windows_.contains(window));
window->RemoveObserver(this);
// Add the window to the target desk before reparenting such that when the
// target desk's DeskContainerObserver notices this reparenting and calls
// AddWindowToDesk(), the window had already been added and there's no
// need to iterate over its transient hierarchy.
target_desk->windows_.insert(window);
window->AddObserver(target_desk);
// Reparent windows to the target desk's container in the same root
// window. Note that `windows_` may contain transient children, which may
// not have the same parent as the source desk's container. So, only
// reparent the windows which are direct children of the source desks'
// container.
// TODO(afakhry): Check if this is necessary.
aura::Window* root = window->GetRootWindow();
aura::Window* source_container = GetDeskContainerForRoot(root);
aura::Window* target_container = target_desk->GetDeskContainerForRoot(root);
if (window->parent() == source_container)
target_container->AddChild(window);
}
} // namespace ash