123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // Copyright 2014 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/window_cycle/window_cycle_list.h"
- #include "ash/accessibility/accessibility_controller_impl.h"
- #include "ash/app_list/app_list_controller_impl.h"
- #include "ash/constants/ash_features.h"
- #include "ash/frame_throttler/frame_throttling_controller.h"
- #include "ash/public/cpp/shell_window_ids.h"
- #include "ash/public/cpp/window_properties.h"
- #include "ash/root_window_controller.h"
- #include "ash/shell.h"
- #include "ash/shell_delegate.h"
- #include "ash/system/unified/unified_system_tray.h"
- #include "ash/wm/mru_window_tracker.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "ash/wm/window_cycle/window_cycle_controller.h"
- #include "ash/wm/window_state.h"
- #include "ash/wm/window_util.h"
- #include "base/check.h"
- #include "base/location.h"
- #include "ui/aura/scoped_window_targeter.h"
- #include "ui/aura/window.h"
- #include "ui/aura/window_targeter.h"
- #include "ui/compositor/layer_type.h"
- #include "ui/display/display.h"
- #include "ui/display/display_observer.h"
- #include "ui/display/screen.h"
- #include "ui/events/event.h"
- #include "ui/views/controls/label.h"
- #include "ui/views/widget/widget.h"
- #include "ui/wm/core/coordinate_conversion.h"
- #include "ui/wm/core/window_animations.h"
- namespace ash {
- namespace {
- bool g_disable_initial_delay = false;
- // Delay before the UI fade in animation starts. This is so users can switch
- // quickly between windows without bringing up the UI.
- constexpr base::TimeDelta kShowDelayDuration = base::Milliseconds(150);
- // The alt-tab cycler widget is not activatable (except when ChromeVox is on),
- // so we use WindowTargeter to send input events to the widget.
- class CustomWindowTargeter : public aura::WindowTargeter {
- public:
- explicit CustomWindowTargeter(aura::Window* tab_cycler)
- : tab_cycler_(tab_cycler) {}
- CustomWindowTargeter(const CustomWindowTargeter&) = delete;
- CustomWindowTargeter& operator=(const CustomWindowTargeter&) = delete;
- ~CustomWindowTargeter() override = default;
- // aura::WindowTargeter:
- ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
- ui::Event* event) override {
- if (event->IsLocatedEvent())
- return aura::WindowTargeter::FindTargetForEvent(root, event);
- return tab_cycler_;
- }
- private:
- aura::Window* tab_cycler_;
- };
- gfx::Point ConvertEventToScreen(const ui::LocatedEvent* event) {
- aura::Window* target = static_cast<aura::Window*>(event->target());
- aura::Window* event_root = target->GetRootWindow();
- gfx::Point event_screen_point = event->root_location();
- wm::ConvertPointToScreen(event_root, &event_screen_point);
- return event_screen_point;
- }
- aura::Window* GetRootWindowForCycleView() {
- // Returns the root window for initializing cycle view if tablet mode is
- // enabled, or if the feature for alt-tab to follow the cursor is disabled.
- if (Shell::Get()->tablet_mode_controller()->InTabletMode() ||
- !features::DoWindowsFollowCursor()) {
- return Shell::GetRootWindowForNewWindows();
- }
- // Return the root window the cursor is currently on.
- return Shell::GetRootWindowForDisplayId(
- Shell::Get()->cursor_manager()->GetDisplay().id());
- }
- } // namespace
- WindowCycleList::WindowCycleList(const WindowList& windows)
- : windows_(windows) {
- if (!ShouldShowUi())
- Shell::Get()->mru_window_tracker()->SetIgnoreActivations(true);
- active_window_before_window_cycle_ = window_util::GetActiveWindow();
- for (auto* window : windows_)
- window->AddObserver(this);
- if (ShouldShowUi()) {
- // Disable the tab scrubber so three finger scrolling doesn't scrub tabs as
- // well.
- Shell::Get()->shell_delegate()->SetTabScrubberChromeOSEnabled(false);
- if (g_disable_initial_delay) {
- InitWindowCycleView();
- } else {
- show_ui_timer_.Start(FROM_HERE, kShowDelayDuration, this,
- &WindowCycleList::InitWindowCycleView);
- }
- }
- }
- WindowCycleList::~WindowCycleList() {
- if (!ShouldShowUi())
- Shell::Get()->mru_window_tracker()->SetIgnoreActivations(false);
- Shell::Get()->shell_delegate()->SetTabScrubberChromeOSEnabled(true);
- for (auto* window : windows_)
- window->RemoveObserver(this);
- if (cycle_ui_widget_)
- cycle_ui_widget_->Close();
- // Store the target window before |cycle_view_| is destroyed.
- aura::Window* target_window = nullptr;
- // |this| is responsible for notifying |cycle_view_| when windows are
- // destroyed. Since |this| is going away, clobber |cycle_view_|. Otherwise
- // there will be a race where a window closes after now but before the
- // Widget::Close() call above actually destroys |cycle_view_|. See
- // crbug.com/681207
- if (cycle_view_) {
- target_window = GetTargetWindow();
- cycle_view_->DestroyContents();
- }
- // While the cycler widget is shown, the windows listed in the cycler is
- // marked as force-visible and don't contribute to occlusion. In order to
- // work occlusion calculation properly, we need to activate a window after
- // the widget has been destroyed. See b/138914552.
- if (!windows_.empty() && user_did_accept_) {
- if (!target_window)
- target_window = windows_[current_index_];
- SelectWindow(target_window);
- }
- Shell::Get()->frame_throttling_controller()->EndThrottling();
- }
- aura::Window* WindowCycleList::GetTargetWindow() {
- return cycle_view_->target_window();
- }
- void WindowCycleList::ReplaceWindows(const WindowList& windows) {
- RemoveAllWindows();
- windows_ = windows;
- for (auto* new_window : windows_)
- new_window->AddObserver(this);
- if (cycle_view_)
- cycle_view_->UpdateWindows(windows_);
- }
- void WindowCycleList::Step(
- WindowCycleController::WindowCyclingDirection direction,
- bool starting_alt_tab_or_switching_mode) {
- if (windows_.empty())
- return;
- // If the position of the window cycle list is out-of-sync with the currently
- // selected item, scroll to the selected item and then step.
- if (cycle_view_) {
- aura::Window* selected_window = GetTargetWindow();
- if (selected_window)
- Scroll(GetIndexOfWindow(selected_window) - current_index_);
- }
- int offset =
- direction == WindowCycleController::WindowCyclingDirection::kForward ? 1
- : -1;
- // When the window highlight should be reset and the first window in the MRU
- // cycle list is not the latest active one before entering alt-tab, highlight
- // it instead of the second window. This occurs when the user is in overview
- // mode, all windows are minimized, or all windows are in other desks.
- //
- // Note: Simply checking the active status of the first window won't work
- // because when the ChromeVox is enabled, the widget is activatable, so the
- // first window in MRU becomes inactive.
- if (starting_alt_tab_or_switching_mode &&
- direction == WindowCycleController::WindowCyclingDirection::kForward &&
- active_window_before_window_cycle_ != windows_[0]) {
- offset = 0;
- current_index_ = 0;
- }
- SetFocusedWindow(windows_[GetOffsettedWindowIndex(offset)]);
- Scroll(offset);
- }
- void WindowCycleList::Drag(float delta_x) {
- DCHECK(cycle_view_);
- cycle_view_->Drag(delta_x);
- }
- void WindowCycleList::StartFling(float velocity_x) {
- DCHECK(cycle_view_);
- cycle_view_->StartFling(velocity_x);
- }
- void WindowCycleList::SetFocusedWindow(aura::Window* window) {
- if (windows_.empty())
- return;
- if (ShouldShowUi() && cycle_view_)
- cycle_view_->SetTargetWindow(windows_[GetIndexOfWindow(window)]);
- }
- void WindowCycleList::SetFocusTabSlider(bool focus) {
- DCHECK(cycle_view_);
- cycle_view_->SetFocusTabSlider(focus);
- }
- bool WindowCycleList::IsTabSliderFocused() {
- DCHECK(cycle_view_);
- return cycle_view_->IsTabSliderFocused();
- }
- bool WindowCycleList::IsEventInCycleView(const ui::LocatedEvent* event) {
- return cycle_view_ &&
- cycle_view_->GetBoundsInScreen().Contains(ConvertEventToScreen(event));
- }
- aura::Window* WindowCycleList::GetWindowAtPoint(const ui::LocatedEvent* event) {
- return cycle_view_
- ? cycle_view_->GetWindowAtPoint(ConvertEventToScreen(event))
- : nullptr;
- }
- bool WindowCycleList::IsEventInTabSliderContainer(
- const ui::LocatedEvent* event) {
- return cycle_view_ &&
- cycle_view_->IsEventInTabSliderContainer(ConvertEventToScreen(event));
- }
- bool WindowCycleList::ShouldShowUi() {
- // Show alt-tab when there are at least two windows to pick from alt-tab, or
- // when there is at least a window to switch to by switching to the different
- // mode.
- if (!Shell::Get()
- ->window_cycle_controller()
- ->IsInteractiveAltTabModeAllowed()) {
- return windows_.size() > 1u;
- }
- int total_window_in_all_desks = GetNumberOfWindowsAllDesks();
- return windows_.size() > 1u ||
- (windows_.size() <= 1u &&
- static_cast<size_t>(total_window_in_all_desks) > windows_.size());
- }
- void WindowCycleList::OnModePrefsChanged() {
- if (cycle_view_)
- cycle_view_->OnModePrefsChanged();
- }
- // static
- void WindowCycleList::SetDisableInitialDelayForTesting(bool disabled) {
- g_disable_initial_delay = disabled;
- }
- void WindowCycleList::OnWindowDestroying(aura::Window* window) {
- window->RemoveObserver(this);
- WindowList::iterator i = std::find(windows_.begin(), windows_.end(), window);
- // TODO(oshima): Change this back to DCHECK once crbug.com/483491 is fixed.
- CHECK(i != windows_.end());
- int removed_index = static_cast<int>(i - windows_.begin());
- windows_.erase(i);
- if (current_index_ > removed_index ||
- current_index_ == static_cast<int>(windows_.size())) {
- current_index_--;
- }
- // Reset |active_window_before_window_cycle_| to avoid a dangling pointer.
- if (window == active_window_before_window_cycle_)
- active_window_before_window_cycle_ = nullptr;
- if (cycle_view_) {
- auto* new_target_window =
- windows_.empty() ? nullptr : windows_[current_index_];
- cycle_view_->HandleWindowDestruction(window, new_target_window);
- if (windows_.empty()) {
- // This deletes us.
- Shell::Get()->window_cycle_controller()->CancelCycling();
- return;
- }
- }
- }
- void WindowCycleList::OnDisplayMetricsChanged(const display::Display& display,
- uint32_t changed_metrics) {
- if (cycle_ui_widget_ &&
- display.id() ==
- display::Screen::GetScreen()
- ->GetDisplayNearestWindow(cycle_ui_widget_->GetNativeWindow())
- .id() &&
- (changed_metrics & (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION))) {
- Shell::Get()->window_cycle_controller()->CancelCycling();
- // |this| is deleted.
- return;
- }
- }
- void WindowCycleList::RemoveAllWindows() {
- for (auto* window : windows_) {
- window->RemoveObserver(this);
- if (cycle_view_)
- cycle_view_->HandleWindowDestruction(window, nullptr);
- }
- windows_.clear();
- current_index_ = 0;
- window_selected_ = false;
- }
- void WindowCycleList::InitWindowCycleView() {
- if (cycle_view_)
- return;
- aura::Window* root_window = GetRootWindowForCycleView();
- // Close the system quick settings tray before creating the cycle view.
- UnifiedSystemTray* tray = RootWindowController::ForWindow(root_window)
- ->GetStatusAreaWidget()
- ->unified_system_tray();
- if (tray->IsBubbleShown())
- tray->CloseBubble();
- cycle_view_ = new WindowCycleView(root_window, windows_);
- const bool is_interactive_alt_tab_mode_allowed =
- Shell::Get()->window_cycle_controller()->IsInteractiveAltTabModeAllowed();
- DCHECK(!windows_.empty() || is_interactive_alt_tab_mode_allowed);
- // Only set target window and scroll to the window when alt-tab is not empty.
- if (!windows_.empty()) {
- DCHECK(static_cast<int>(windows_.size()) > current_index_);
- cycle_view_->SetTargetWindow(windows_[current_index_]);
- cycle_view_->ScrollToWindow(windows_[current_index_]);
- }
- // We need to activate the widget if ChromeVox is enabled as ChromeVox
- // relies on activation.
- const bool spoken_feedback_enabled =
- Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
- views::Widget* widget = new views::Widget();
- views::Widget::InitParams params;
- params.delegate = cycle_view_;
- params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
- params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
- params.layer_type = ui::LAYER_NOT_DRAWN;
- // Don't let the alt-tab cycler be activatable. This lets the currently
- // activated window continue to be in the foreground. This may affect
- // things such as video automatically pausing/playing.
- if (!spoken_feedback_enabled)
- params.activatable = views::Widget::InitParams::Activatable::kNo;
- params.accept_events = true;
- params.name = "WindowCycleList (Alt+Tab)";
- // TODO(estade): make sure nothing untoward happens when the lock screen
- // or a system modal dialog is shown.
- params.parent = root_window->GetChildById(kShellWindowId_OverlayContainer);
- params.bounds = cycle_view_->GetTargetBounds();
- widget->Init(std::move(params));
- widget->Show();
- cycle_view_->FadeInLayer();
- cycle_ui_widget_ = widget;
- // Since this window is not activated, grab events.
- if (!spoken_feedback_enabled) {
- window_targeter_ = std::make_unique<aura::ScopedWindowTargeter>(
- widget->GetNativeWindow()->GetRootWindow(),
- std::make_unique<CustomWindowTargeter>(widget->GetNativeWindow()));
- }
- // Close the app list, if it's open in clamshell mode.
- if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
- Shell::Get()->app_list_controller()->DismissAppList();
- Shell::Get()->frame_throttling_controller()->StartThrottling(windows_);
- }
- void WindowCycleList::SelectWindow(aura::Window* window) {
- // If the list has only one window, the window can be selected twice (in
- // Scroll() and the destructor). This causes ARC PIP windows to be restored
- // twice, which leads to a wrong window state.
- if (window_selected_)
- return;
- if (window->GetProperty(kPipOriginalWindowKey)) {
- window_util::ExpandArcPipWindow();
- } else {
- window->Show();
- WindowState::Get(window)->Activate();
- }
- window_selected_ = true;
- }
- void WindowCycleList::Scroll(int offset) {
- if (windows_.size() == 1)
- SelectWindow(windows_[0]);
- if (!ShouldShowUi()) {
- // When there is only one window, we should give feedback to the user. If
- // the window is minimized, we should also show it.
- if (windows_.size() == 1)
- ::wm::AnimateWindow(windows_[0], ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
- return;
- }
- DCHECK(static_cast<size_t>(current_index_) < windows_.size());
- current_index_ = GetOffsettedWindowIndex(offset);
- if (current_index_ > 1)
- InitWindowCycleView();
- // The windows should not shift position when selecting when there's enough
- // room to display all windows.
- if (cycle_view_ && cycle_view_->CalculatePreferredSize().width() ==
- cycle_view_->CalculateMaxWidth()) {
- cycle_view_->ScrollToWindow(windows_[current_index_]);
- }
- }
- int WindowCycleList::GetOffsettedWindowIndex(int offset) const {
- DCHECK(!windows_.empty());
- const int offsetted_index =
- (current_index_ + offset + windows_.size()) % windows_.size();
- DCHECK(windows_[offsetted_index]);
- return offsetted_index;
- }
- int WindowCycleList::GetIndexOfWindow(aura::Window* window) const {
- auto target_window = std::find(windows_.begin(), windows_.end(), window);
- DCHECK(target_window != windows_.end());
- return std::distance(windows_.begin(), target_window);
- }
- int WindowCycleList::GetNumberOfWindowsAllDesks() const {
- // If alt-tab mode is not available, the alt-tab defaults to all-desks mode
- // and can obtain the number of all windows easily from `windows_.size()`.
- DCHECK(Shell::Get()
- ->window_cycle_controller()
- ->IsInteractiveAltTabModeAllowed());
- return Shell::Get()
- ->mru_window_tracker()
- ->BuildWindowForCycleWithPipList(kAllDesks)
- .size();
- }
- } // namespace ash
|