|  | // 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 |