// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h"
#include <memory>
#include <string>
#include <vector>
#include "base/logging.h"
#include "chromeos/ui/base/chromeos_ui_constants.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_targeter.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_target.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/platform_window/extensions/desk_extension.h"
#include "ui/platform_window/extensions/pinned_mode_extension.h"
#include "ui/platform_window/extensions/system_modal_extension.h"
#include "ui/platform_window/extensions/wayland_extension.h"
#include "ui/platform_window/platform_window.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/platform_window/wm/wm_move_resize_handler.h"
#include "ui/views/corewm/tooltip_controller.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/desktop_aura/window_event_filter_lacros.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
namespace {
chromeos::WindowStateType ToChromeosWindowStateType(
ui::PlatformWindowState state) {
switch (state) {
case ui::PlatformWindowState::kUnknown:
return chromeos::WindowStateType::kDefault;
case ui::PlatformWindowState::kMaximized:
return chromeos::WindowStateType::kMaximized;
case ui::PlatformWindowState::kMinimized:
return chromeos::WindowStateType::kMinimized;
case ui::PlatformWindowState::kNormal:
return chromeos::WindowStateType::kNormal;
case ui::PlatformWindowState::kFullScreen:
return chromeos::WindowStateType::kFullscreen;
case ui::PlatformWindowState::kSnappedPrimary:
return chromeos::WindowStateType::kPrimarySnapped;
case ui::PlatformWindowState::kSnappedSecondary:
return chromeos::WindowStateType::kSecondarySnapped;
case ui::PlatformWindowState::kFloated:
return chromeos::WindowStateType::kFloated;
case ui::PlatformWindowState::kPip:
return chromeos::WindowStateType::kPip;
case ui::PlatformWindowState::kPinnedFullscreen:
return chromeos::WindowStateType::kPinned;
case ui::PlatformWindowState::kTrustedPinnedFullscreen:
return chromeos::WindowStateType::kTrustedPinned;
}
}
// Chrome do not expect the pointer (mouse/touch) events are dispatched to
// chrome during move loop. The mouse events are already consumed by
// ozone-wayland but touch events are sent to the `aura::WindowEventDispatcher`
// to update the touch location. Consume touch events at system handler level so
// that chrome will not see the touch events.
class ScopedTouchEventDisabler : public ui::EventHandler {
public:
ScopedTouchEventDisabler() {
aura::Env::GetInstance()->AddPreTargetHandler(
this, ui::EventTarget::Priority::kSystem);
}
ScopedTouchEventDisabler(const ScopedTouchEventDisabler&) = delete;
ScopedTouchEventDisabler& operator=(const ScopedTouchEventDisabler&) = delete;
~ScopedTouchEventDisabler() override {
aura::Env::GetInstance()->RemovePreTargetHandler(this);
}
// ui::EventHandler:
void OnTouchEvent(ui::TouchEvent* event) override { event->SetHandled(); }
};
bool IsImmersive(ui::PlatformFullscreenType type) {
return type == ui::PlatformFullscreenType::kImmersive;
}
gfx::RoundedCornersF GetWindowCornerRadii(
aura::Window* window,
ui::WaylandToplevelExtension* wayland_extension) {
if (!wayland_extension) {
return gfx::RoundedCornersF();
}
// If window is a pip, ignore the window radii specified by the server. Window
// radii specified by the server is for a window in normal window state.
const auto window_state = window->GetProperty(chromeos::kWindowStateTypeKey);
if (window_state == chromeos::WindowStateType::kPip) {
return gfx::RoundedCornersF(chromeos::kPipRoundedCornerRadius);
}
return wayland_extension->GetWindowCornersRadii();
}
} // namespace
namespace views {
DesktopWindowTreeHostLacros::DesktopWindowTreeHostLacros(
internal::NativeWidgetDelegate* native_widget_delegate,
DesktopNativeWidgetAura* desktop_native_widget_aura)
: DesktopWindowTreeHostPlatform(native_widget_delegate,
desktop_native_widget_aura) {
CHECK(GetContentWindow());
content_window_observation_.Observe(GetContentWindow());
}
DesktopWindowTreeHostLacros::~DesktopWindowTreeHostLacros() = default;
ui::WaylandToplevelExtension*
DesktopWindowTreeHostLacros::GetWaylandToplevelExtension() {
return platform_window()
? ui::GetWaylandToplevelExtension(*(platform_window()))
: nullptr;
}
const ui::WaylandToplevelExtension*
DesktopWindowTreeHostLacros::GetWaylandToplevelExtension() const {
return platform_window()
? ui::GetWaylandToplevelExtension(*(platform_window()))
: nullptr;
}
void DesktopWindowTreeHostLacros::OnNativeWidgetCreated(
const Widget::InitParams& params) {
CreateNonClientEventFilter();
DesktopWindowTreeHostPlatform::OnNativeWidgetCreated(params);
platform_window()->SetUseNativeFrame(false);
}
void DesktopWindowTreeHostLacros::InitModalType(
ui::mojom::ModalType modal_type) {
if (ui::GetSystemModalExtension(*(platform_window()))) {
ui::GetSystemModalExtension(*(platform_window()))
->SetSystemModal(modal_type == ui::mojom::ModalType::kSystem);
}
switch (modal_type) {
case ui::mojom::ModalType::kNone:
case ui::mojom::ModalType::kSystem:
break;
default:
// TODO(erg): Figure out under what situations |modal_type| isn't
// none. The comment in desktop_native_widget_aura.cc suggests that this
// is rare.
NOTIMPLEMENTED();
}
}
void DesktopWindowTreeHostLacros::OnClosed() {
DestroyNonClientEventFilter();
DesktopWindowTreeHostPlatform::OnClosed();
}
void DesktopWindowTreeHostLacros::OnWindowStateChanged(
ui::PlatformWindowState old_state,
ui::PlatformWindowState new_state) {
DesktopWindowTreeHostPlatform::OnWindowStateChanged(old_state, new_state);
GetContentWindow()->SetProperty(chromeos::kWindowStateTypeKey,
ToChromeosWindowStateType(new_state));
UpdateWindowHints();
}
void DesktopWindowTreeHostLacros::OnFullscreenTypeChanged(
ui::PlatformFullscreenType old_type,
ui::PlatformFullscreenType new_type) {
// Keep in sync with ImmersiveFullscreenController::Enable for widget. See
// comment there for details.
if (IsImmersive(old_type) != IsImmersive(new_type)) {
GetContentWindow()->SetProperty(chromeos::kImmersiveIsActive,
IsImmersive(new_type));
}
}
void DesktopWindowTreeHostLacros::OnOverviewModeChanged(bool in_overview) {
GetContentWindow()->SetProperty(chromeos::kIsShowingInOverviewKey,
in_overview);
// Window corner radius depends on whether the window is in overview mode or
// not. Once the overview property has been updated, the browser window
// corners need to be updated.
// See `chromeos::GetFrameCornerRadius()` for more details.
UpdateWindowHints();
}
void DesktopWindowTreeHostLacros::OnTooltipShownOnServer(
const std::u16string& text,
const gfx::Rect& bounds) {
if (tooltip_controller()) {
tooltip_controller()->OnTooltipShownOnServer(GetContentWindow(), text,
bounds);
}
}
void DesktopWindowTreeHostLacros::OnTooltipHiddenOnServer() {
if (tooltip_controller()) {
tooltip_controller()->OnTooltipHiddenOnServer();
}
}
void DesktopWindowTreeHostLacros::OnBoundsChanged(const BoundsChange& change) {
// DesktopWindowTreeHostPlatform::OnBoundsChanged() may result in |this| being
// deleted. As an extra safety guard, keep track of `this` with a weak
// pointer, and only call UpdateWindowHints() if it still exists.
auto weak_this = weak_factory_.GetWeakPtr();
DesktopWindowTreeHostPlatform::OnBoundsChanged(change);
if (weak_this.get()) {
UpdateWindowHints();
}
}
void DesktopWindowTreeHostLacros::AddAdditionalInitProperties(
const Widget::InitParams& params,
ui::PlatformWindowInitProperties* properties) {
properties->icon = ViewsDelegate::GetInstance()->GetDefaultWindowIcon();
properties->wayland_app_id = params.wayland_app_id;
}
Widget::MoveLoopResult DesktopWindowTreeHostLacros::RunMoveLoop(
const gfx::Vector2d& drag_offset,
Widget::MoveLoopSource source,
Widget::MoveLoopEscapeBehavior escape_behavior) {
ScopedTouchEventDisabler touch_event_disabler;
return DesktopWindowTreeHostPlatform::RunMoveLoop(drag_offset, source,
escape_behavior);
}
void DesktopWindowTreeHostLacros::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
CHECK_EQ(GetContentWindow(), window);
if (key == aura::client::kTopViewInset) {
if (auto* wayland_extension = GetWaylandToplevelExtension()) {
wayland_extension->SetTopInset(
GetContentWindow()->GetProperty(aura::client::kTopViewInset));
}
}
}
void DesktopWindowTreeHostLacros::OnWindowDestroying(aura::Window* window) {
CHECK_EQ(GetContentWindow(), window);
content_window_observation_.Reset();
}
void DesktopWindowTreeHostLacros::OnWidgetInitDone() {
DesktopWindowTreeHostPlatform::OnWidgetInitDone();
UpdateWindowHints();
}
void DesktopWindowTreeHostLacros::CreateNonClientEventFilter() {
DCHECK(!non_client_window_event_filter_);
non_client_window_event_filter_ = std::make_unique<WindowEventFilterLacros>(
this, GetWmMoveResizeHandler(*platform_window()));
}
void DesktopWindowTreeHostLacros::DestroyNonClientEventFilter() {
non_client_window_event_filter_.reset();
}
void DesktopWindowTreeHostLacros::UpdateWindowHints() {
const float scale = device_scale_factor();
const gfx::Size widget_size_px =
platform_window()->GetBoundsInPixels().size();
// Content window can have a window_targeter that allows located events fall
// to the window underneath it. There is no window underneath the content
// window from aura's point of view so wayland platform needs to know about
// it.
gfx::Rect hit_test_rect_mouse_dp{platform_window()->GetBoundsInDIP().size()};
if (GetContentWindow()->targeter()) {
gfx::Rect hit_test_rect_touch_dp = gfx::Rect{hit_test_rect_mouse_dp};
GetContentWindow()->targeter()->GetHitTestRects(
GetContentWindow(), &hit_test_rect_mouse_dp, &hit_test_rect_touch_dp);
}
const gfx::Rect hit_test_rect_px =
ConvertRectToPixels(hit_test_rect_mouse_dp);
aura::Window* native_window = GetWidget()->GetNativeWindow();
auto* wayland_extension = ui::GetWaylandToplevelExtension(*platform_window());
const gfx::RoundedCornersF window_radii =
GetWindowCornerRadii(native_window, wayland_extension);
const bool should_have_rounded_window =
views::ViewsDelegate::GetInstance()->ShouldWindowHaveRoundedCorners(
native_window);
std::vector<gfx::Rect> input_region;
if (should_have_rounded_window) {
GetContentWindow()->layer()->SetRoundedCornerRadius(window_radii);
GetContentWindow()->layer()->SetIsFastRoundedCorner(true);
auto to_rect = [](float origin_x, float origin_y, float size) {
gfx::RectF rect(origin_x, origin_y, size, size);
return gfx::ToEnclosingRectIgnoringError(rect);
};
cc::Region region(hit_test_rect_px);
const int width = widget_size_px.width(), height = widget_size_px.height();
const float upper_left_px = window_radii.upper_left() * scale;
region.Subtract(to_rect(0, 0, upper_left_px));
const float upper_right_px = window_radii.upper_right() * scale;
region.Subtract(to_rect(width - upper_right_px * scale, 0, upper_right_px));
const float lower_left_px = window_radii.lower_left() * scale;
region.Subtract(to_rect(0, height - lower_left_px, lower_left_px));
const float lower_right_px = window_radii.lower_right() * scale;
region.Subtract(to_rect(width - lower_right_px, height - lower_right_px,
lower_right_px));
// Convert the region to a list of rectangles.
for (gfx::Rect i : region) {
input_region.push_back(i);
}
} else {
GetContentWindow()->layer()->SetRoundedCornerRadius({});
GetContentWindow()->layer()->SetIsFastRoundedCorner(false);
input_region.push_back(hit_test_rect_px);
}
// TODO(crbug.com/40218466): Instead of setting in pixels, set in dp.
platform_window()->SetInputRegion(input_region);
// If the window is rounded, we hint the platform to match the drop shadow's
// radii to the window's radii. Otherwise, we allow the platform to
// determine the drop shadow's radii.
if (should_have_rounded_window && wayland_extension) {
wayland_extension->SetShadowCornersRadii(window_radii);
}
}
// static
DesktopWindowTreeHostLacros* DesktopWindowTreeHostLacros::From(
WindowTreeHost* wth) {
DCHECK(has_open_windows()) << "Calling this method from non-Platform based "
"platform.";
for (auto widget : open_windows()) {
DesktopWindowTreeHostPlatform* wth_platform =
DesktopWindowTreeHostPlatform::GetHostForWidget(widget);
if (wth_platform != wth)
continue;
return static_cast<views::DesktopWindowTreeHostLacros*>(wth_platform);
}
return nullptr;
}
ui::DeskExtension* DesktopWindowTreeHostLacros::GetDeskExtension() {
return ui::GetDeskExtension(*(platform_window()));
}
const ui::DeskExtension* DesktopWindowTreeHostLacros::GetDeskExtension() const {
return ui::GetDeskExtension(*(platform_window()));
}
ui::PinnedModeExtension* DesktopWindowTreeHostLacros::GetPinnedModeExtension() {
return ui::GetPinnedModeExtension(*(platform_window()));
}
const ui::PinnedModeExtension*
DesktopWindowTreeHostLacros::GetPinnedModeExtension() const {
return ui::GetPinnedModeExtension(*(platform_window()));
}
// static
DesktopWindowTreeHost* DesktopWindowTreeHost::Create(
internal::NativeWidgetDelegate* native_widget_delegate,
DesktopNativeWidgetAura* desktop_native_widget_aura) {
return new DesktopWindowTreeHostLacros(native_widget_delegate,
desktop_native_widget_aura);
}
} // namespace views