| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/exo/shell_surface_util.h" |
| |
| #include <memory> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "base/feature_list.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "components/exo/client_controlled_shell_surface.h" |
| #include "components/exo/permission.h" |
| #include "components/exo/shell_surface_base.h" |
| #include "components/exo/surface.h" |
| #include "components/exo/window_properties.h" |
| #include "components/exo/wm_helper.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/capture_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/events/ozone/events_ozone.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace exo { |
| |
| namespace { |
| |
| DEFINE_UI_CLASS_PROPERTY_KEY(Surface*, kRootSurfaceKey, nullptr) |
| |
| // Startup Id set by the client. |
| DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::string, kStartupIdKey) |
| |
| // A property key containing the client controlled shell surface. |
| DEFINE_UI_CLASS_PROPERTY_KEY(ClientControlledShellSurface*, |
| kClientControlledShellSurface, |
| nullptr) |
| |
| // Returns true if the component for a located event should be taken care of |
| // by the window system. |
| bool ShouldHTComponentBlocked(int component) { |
| if (ui::IsResizingComponent(component)) { |
| return true; |
| } |
| |
| switch (component) { |
| case HTCAPTION: |
| case HTCLOSE: |
| case HTMAXBUTTON: |
| case HTMINBUTTON: |
| case HTMENU: |
| case HTSYSMENU: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Find the lowest targeter in the parent chain. |
| aura::WindowTargeter* FindTargeter(ui::EventTarget* target) { |
| do { |
| ui::EventTargeter* targeter = target->GetEventTargeter(); |
| if (targeter) |
| return static_cast<aura::WindowTargeter*>(targeter); |
| target = target->GetParentTarget(); |
| } while (target); |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| void SetShellApplicationId(ui::PropertyHandler* property_handler, |
| const std::optional<std::string>& id) { |
| TRACE_EVENT1("exo", "SetApplicationId", "application_id", id ? *id : "null"); |
| |
| if (id) |
| property_handler->SetProperty(kApplicationIdKey, *id); |
| else |
| property_handler->ClearProperty(kApplicationIdKey); |
| } |
| |
| const std::string* GetShellApplicationId(const aura::Window* property_handler) { |
| return property_handler->GetProperty(kApplicationIdKey); |
| } |
| |
| void SetShellStartupId(ui::PropertyHandler* property_handler, |
| const std::optional<std::string>& id) { |
| TRACE_EVENT1("exo", "SetStartupId", "startup_id", id ? *id : "null"); |
| |
| if (id) |
| property_handler->SetProperty(kStartupIdKey, *id); |
| else |
| property_handler->ClearProperty(kStartupIdKey); |
| } |
| |
| const std::string* GetShellStartupId(const aura::Window* window) { |
| return window->GetProperty(kStartupIdKey); |
| } |
| |
| void SetShellUseImmersiveForFullscreen(aura::Window* window, bool value) { |
| window->SetProperty(chromeos::kImmersiveImpliedByFullscreen, value); |
| |
| // Ensure the shelf is fully hidden in plain fullscreen, but shown |
| // (auto-hides based on mouse movement) when in immersive fullscreen. |
| window->SetProperty(chromeos::kHideShelfWhenFullscreenKey, !value); |
| } |
| |
| void SetShellClientAccessibilityId(aura::Window* window, |
| const std::optional<int32_t>& id) { |
| TRACE_EVENT1("exo", "SetClientAccessibilityId", "id", |
| id ? base::NumberToString(*id) : "null"); |
| |
| if (id) |
| window->SetProperty(ash::kClientAccessibilityIdKey, *id); |
| else |
| window->ClearProperty(ash::kClientAccessibilityIdKey); |
| } |
| |
| const std::optional<int32_t> GetShellClientAccessibilityId( |
| aura::Window* window) { |
| auto id = window->GetProperty(ash::kClientAccessibilityIdKey); |
| if (id < 0) |
| return std::nullopt; |
| else |
| return id; |
| } |
| |
| void SetShellClientControlledShellSurface( |
| ui::PropertyHandler* property_handler, |
| const std::optional<ClientControlledShellSurface*>& shell_surface) { |
| if (shell_surface) |
| property_handler->SetProperty(kClientControlledShellSurface, |
| shell_surface.value()); |
| else |
| property_handler->ClearProperty(kClientControlledShellSurface); |
| } |
| |
| ClientControlledShellSurface* GetShellClientControlledShellSurface( |
| ui::PropertyHandler* property_handler) { |
| return property_handler->GetProperty(kClientControlledShellSurface); |
| } |
| |
| int GetWindowDeskStateChanged(const aura::Window* window) { |
| constexpr int kToggleVisibleOnAllWorkspacesValue = -1; |
| if (ash::desks_util::IsWindowVisibleOnAllWorkspaces(window)) |
| return kToggleVisibleOnAllWorkspacesValue; |
| |
| int workspace = window->GetProperty(aura::client::kWindowWorkspaceKey); |
| // If workspace is unassigned, returns the active desk index. |
| if (workspace == aura::client::kWindowWorkspaceUnassignedWorkspace) |
| workspace = ash::DesksController::Get()->GetActiveDeskIndex(); |
| return workspace; |
| } |
| |
| void SetShellRootSurface(ui::PropertyHandler* property_handler, |
| Surface* surface) { |
| property_handler->SetProperty(kRootSurfaceKey, surface); |
| } |
| |
| Surface* GetShellRootSurface(const aura::Window* window) { |
| return window->GetProperty(kRootSurfaceKey); |
| } |
| |
| ShellSurfaceBase* GetShellSurfaceBaseForWindow(const aura::Window* window) { |
| // Only windows with a surface can have a shell surface. |
| if (!GetShellRootSurface(window)) |
| return nullptr; |
| // This is safe to const-cast for Aura. |
| const views::Widget* widget = views::Widget::GetWidgetForNativeWindow( |
| const_cast<aura::Window*>(window)); |
| if (!widget) |
| return nullptr; |
| ShellSurfaceBase* shell_surface_base = |
| static_cast<ShellSurfaceBase*>(widget->widget_delegate()); |
| // We can obtain widget from native window, but not |shell_surface_base|. |
| // This means we are in the process of destroying this surface so we should |
| // return nullptr. |
| if (!shell_surface_base || !shell_surface_base->GetWidget()) |
| return nullptr; |
| return shell_surface_base; |
| } |
| |
| Surface* GetTargetSurfaceForLocatedEvent( |
| const ui::LocatedEvent* original_event) { |
| aura::Window* window = |
| WMHelper::GetInstance()->GetCaptureClient()->GetCaptureWindow(); |
| Surface* root_surface = nullptr; |
| |
| if (!window) { |
| auto* target_window = static_cast<aura::Window*>(original_event->target()); |
| auto* target_surface = Surface::AsSurface(target_window); |
| if (target_surface) { |
| return target_surface; |
| } |
| // The target can be a window of the shell surface, if it was |
| // capture but released during event dispatching. |
| root_surface = GetShellRootSurface(target_window); |
| if (!root_surface) { |
| return nullptr; |
| } |
| window = target_window; |
| } else { |
| root_surface = GetShellRootSurface(window); |
| } |
| |
| // Skip if the event is captured by non exo windows. |
| if (!root_surface) { |
| auto* widget = views::Widget::GetTopLevelWidgetForNativeView(window); |
| if (!widget) |
| return nullptr; |
| root_surface = GetShellRootSurface(widget->GetNativeWindow()); |
| if (!root_surface) |
| return nullptr; |
| |
| ShellSurfaceBase* shell_surface_base = |
| GetShellSurfaceBaseForWindow(widget->GetNativeWindow()); |
| // Check if it's overlay window. |
| if (!shell_surface_base->host_window()->Contains(window) && |
| shell_surface_base->GetWidget()->GetNativeWindow() != window) { |
| return nullptr; |
| } |
| } |
| |
| // Create a clone of the event as targeter may update it during the |
| // search. |
| auto cloned = original_event->Clone(); |
| ui::LocatedEvent* event = cloned->AsLocatedEvent(); |
| while (true) { |
| gfx::PointF location_in_target_f = event->location_f(); |
| gfx::Point location_in_target = event->location(); |
| ui::EventTarget* event_target = window; |
| aura::WindowTargeter* targeter = FindTargeter(event_target); |
| DCHECK(targeter); |
| |
| aura::Window* focused = |
| static_cast<aura::Window*>(targeter->FindTargetForEvent(window, event)); |
| if (focused) { |
| Surface* surface = Surface::AsSurface(focused); |
| if (focused != window) |
| return surface; |
| else if (surface && surface->HitTest(location_in_target)) { |
| // If the targeting fallback to the root (first) window, test the |
| // hit region again. |
| return surface; |
| } |
| } |
| |
| // If the event falls into the place where the window system should care |
| // about (i.e. window caption), do not check the transient parent but just |
| // return nullptr. See b/149517682. |
| if (window->delegate() && |
| ShouldHTComponentBlocked( |
| window->delegate()->GetNonClientComponent(location_in_target))) { |
| return nullptr; |
| } |
| |
| aura::Window* parent_window = wm::GetTransientParent(window); |
| |
| if (!parent_window) |
| return root_surface; |
| |
| event->set_location_f(location_in_target_f); |
| event_target->ConvertEventToTarget(parent_window, event); |
| window = parent_window; |
| } |
| } |
| |
| Surface* GetTargetSurfaceForKeyboardFocus(aura::Window* window) { |
| if (!window) |
| return nullptr; |
| // The keyboard focus should be set to the root surface. |
| ShellSurfaceBase* shell_surface_base = nullptr; |
| for (auto* current = window; current && !shell_surface_base; |
| current = current->parent()) { |
| shell_surface_base = GetShellSurfaceBaseForWindow(current); |
| } |
| // Make sure the |window| is the toplevel or a host window, but not |
| // another window added to the toplevel. |
| if (shell_surface_base && !shell_surface_base->HasOverlay() && |
| (shell_surface_base->GetWidget()->GetNativeWindow() == window || |
| shell_surface_base->host_window()->Contains(window))) { |
| return shell_surface_base->root_surface(); |
| } |
| |
| // Fallback to the window's surface if any. This is used for |
| // notifications. |
| return Surface::AsSurface(window); |
| } |
| |
| void GrantPermissionToActivate(aura::Window* window, base::TimeDelta timeout) { |
| // Activation is the only permission, so just set the property. The window |
| // owns the Permission object. |
| window->SetProperty( |
| kPermissionKey, |
| std::make_unique<Permission>(Permission::Capability::kActivate, timeout)); |
| } |
| |
| void GrantPermissionToActivateIndefinitely(aura::Window* window) { |
| // Activation is the only permission, so just set the property. The window |
| // owns the Permission object. |
| window->SetProperty(kPermissionKey, std::make_unique<Permission>( |
| Permission::Capability::kActivate)); |
| } |
| |
| void RevokePermissionToActivate(aura::Window* window) { |
| // Activation is the only permission, so just clear the property. |
| window->ClearProperty(kPermissionKey); |
| } |
| |
| bool HasPermissionToActivate(aura::Window* window) { |
| Permission* permission = window->GetProperty(kPermissionKey); |
| return permission && permission->Check(Permission::Capability::kActivate); |
| } |
| |
| bool ConsumedByIme(const ui::KeyEvent& event) { |
| return ui::GetKeyboardImeFlags(event) & ui::kPropertyKeyboardImeHandledFlag; |
| } |
| |
| void SetSkipImeProcessingToDescendentSurfaces(aura::Window* window, |
| bool value) { |
| if (Surface::AsSurface(window)) |
| window->SetProperty(aura::client::kSkipImeProcessing, value); |
| for (aura::Window* child : window->children()) |
| SetSkipImeProcessingToDescendentSurfaces(child, value); |
| } |
| |
| } // namespace exo |