chromium/components/exo/shell_surface_util.cc

// 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, nullptr)

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