blob: 70c81149241baa23d1f54b0f242f4d1bceeefe14 [file] [log] [blame]
// Copyright 2016 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 "ui/ozone/platform/wayland/host/wayland_window.h"
#include <wayland-cursor.h>
#include <algorithm>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "build/chromeos_buildflags.h"
#include "ui/base/cursor/mojom/cursor_type.mojom.h"
#include "ui/base/cursor/ozone/bitmap_cursor_factory_ozone.h"
#include "ui/base/cursor/platform_cursor.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/wayland/common/wayland_util.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
#include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_pointer.h"
#include "ui/ozone/platform/wayland/host/wayland_subsurface.h"
#include "ui/ozone/platform/wayland/host/wayland_surface.h"
#include "ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.h"
#include "ui/ozone/public/mojom/wayland/wayland_overlay_config.mojom.h"
#include "ui/platform_window/common/platform_window_defaults.h"
#include "ui/platform_window/wm/wm_drag_handler.h"
#include "ui/platform_window/wm/wm_drop_handler.h"
namespace ui {
namespace {
using mojom::CursorType;
using mojom::DragOperation;
bool OverlayStackOrderCompare(
const ui::ozone::mojom::WaylandOverlayConfigPtr& i,
const ui::ozone::mojom::WaylandOverlayConfigPtr& j) {
return i->z_order < j->z_order;
}
} // namespace
WaylandWindow::WaylandWindow(PlatformWindowDelegate* delegate,
WaylandConnection* connection)
: delegate_(delegate),
connection_(connection),
wayland_overlay_delegation_enabled_(connection->viewporter() &&
IsWaylandOverlayDelegationEnabled()),
accelerated_widget_(
connection->wayland_window_manager()->AllocateAcceleratedWidget()),
ui_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
// Set a class property key, which allows |this| to be used for drag action.
SetWmDragHandler(this, this);
}
WaylandWindow::~WaylandWindow() {
shutting_down_ = true;
PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
if (wayland_overlay_delegation_enabled_) {
connection_->wayland_window_manager()->RemoveSubsurface(
GetWidget(), primary_subsurface_.get());
}
for (const auto& widget_subsurface : wayland_subsurfaces()) {
connection_->wayland_window_manager()->RemoveSubsurface(
GetWidget(), widget_subsurface.get());
}
if (root_surface_)
connection_->wayland_window_manager()->RemoveWindow(GetWidget());
// This might have already been hidden and another window has been shown.
// Thus, the parent will have another child window. Do not reset it.
if (parent_window_ && parent_window_->child_window() == this)
parent_window_->set_child_window(nullptr);
}
void WaylandWindow::OnWindowLostCapture() {
delegate_->OnLostCapture();
}
void WaylandWindow::UpdateWindowScale(bool update_bounds) {
DCHECK(connection_->wayland_output_manager());
auto preferred_outputs_id = GetPreferredEnteredOutputId();
if (preferred_outputs_id == 0) {
// If non of the output are entered, use primary output. This is what
// WaylandScreen returns back to ScreenOzone.
auto* primary_output =
connection_->wayland_output_manager()->GetPrimaryOutput();
// We don't know our primary output - WaylandScreen hasn't been created
// yet.
if (!primary_output)
return;
preferred_outputs_id = primary_output->output_id();
}
auto* output =
connection_->wayland_output_manager()->GetOutput(preferred_outputs_id);
// There can be a race between sending leave output event and destroying
// wl_outputs. Thus, explicitly check if the output exist.
if (!output)
return;
int32_t new_scale = output->scale_factor();
ui_scale_ = output->GetUIScaleFactor();
int32_t old_scale = window_scale();
window_scale_ = new_scale;
// We need to keep DIP size of the window the same whenever the scale changes.
if (update_bounds)
SetBoundsDip(gfx::ScaleToRoundedRect(bounds_px_, 1.0 / old_scale));
// Propagate update to the child windows
if (child_window_)
child_window_->UpdateWindowScale(update_bounds);
}
gfx::AcceleratedWidget WaylandWindow::GetWidget() const {
return accelerated_widget_;
}
void WaylandWindow::SetWindowScale(int32_t new_scale) {
DCHECK_GE(new_scale, 0);
window_scale_ = new_scale;
}
uint32_t WaylandWindow::GetPreferredEnteredOutputId() {
// Child windows don't store entered outputs. Instead, take the window's
// root parent window and use its preferred output.
if (parent_window_)
return GetRootParentWindow()->GetPreferredEnteredOutputId();
// It can be either a toplevel window that hasn't entered any outputs yet, or
// still a non toplevel window that doesn't have a parent (for example, a
// wl_surface that is being dragged).
if (root_surface_->entered_outputs().empty())
return 0;
// PlatformWindowType::kPopup are created as toplevel windows as well.
DCHECK(type() == PlatformWindowType::kWindow ||
type() == PlatformWindowType::kPopup);
// A window can be located on two or more displays. Thus, return the id of the
// output that has the biggest scale factor. Otherwise, use the very first one
// that was entered. This way, we can be sure that the contents of the Window
// are rendered at correct dpi when a user moves the window between displays.
uint32_t preferred_output_id = *root_surface_->entered_outputs().begin();
for (uint32_t output_id : root_surface_->entered_outputs()) {
auto* output_manager = connection_->wayland_output_manager();
auto* output = output_manager->GetOutput(output_id);
auto* preferred_output = output_manager->GetOutput(preferred_output_id);
if (output->scale_factor() > preferred_output->scale_factor())
preferred_output_id = output_id;
}
return preferred_output_id;
}
void WaylandWindow::SetPointerFocus(bool focus) {
has_pointer_focus_ = focus;
// Whenever the window gets the pointer focus back, the cursor shape must be
// updated. Otherwise, it is invalidated upon wl_pointer::leave and is not
// restored by the Wayland compositor.
if (has_pointer_focus_ && cursor_)
UpdateCursorShape(cursor_);
}
void WaylandWindow::RemoveEnteredOutput(uint32_t output_id) {
root_surface_->RemoveEnteredOutput(output_id);
}
bool WaylandWindow::StartDrag(const ui::OSExchangeData& data,
int operation,
mojom::DragEventSource source,
gfx::NativeCursor cursor,
bool can_grab_pointer,
WmDragHandler::Delegate* delegate) {
if (!connection_->data_drag_controller()->StartSession(data, operation,
source)) {
return false;
}
DCHECK(!drag_handler_delegate_);
drag_handler_delegate_ = delegate;
base::RunLoop drag_loop(base::RunLoop::Type::kNestableTasksAllowed);
drag_loop_quit_closure_ = drag_loop.QuitClosure();
auto alive = weak_ptr_factory_.GetWeakPtr();
drag_loop.Run();
if (!alive)
return false;
return true;
}
void WaylandWindow::CancelDrag() {
if (drag_loop_quit_closure_.is_null())
return;
std::move(drag_loop_quit_closure_).Run();
}
void WaylandWindow::Show(bool inactive) {
if (background_buffer_id_ != 0u)
should_attach_background_buffer_ = true;
}
void WaylandWindow::Hide() {
// Mutter compositor crashes if we don't remove subsurface roles when hiding.
if (primary_subsurface_)
primary_subsurface()->Hide();
for (auto& subsurface : wayland_subsurfaces_)
subsurface->Hide();
}
void WaylandWindow::Close() {
delegate_->OnClosed();
}
bool WaylandWindow::IsVisible() const {
NOTREACHED();
return false;
}
void WaylandWindow::PrepareForShutdown() {
if (drag_handler_delegate_)
OnDragSessionClose(DragOperation::kNone);
}
void WaylandWindow::SetBounds(const gfx::Rect& bounds_px) {
gfx::Rect adjusted_bounds_px = bounds_px;
if (const auto min_size = delegate_->GetMinimumSizeForWindow()) {
if (min_size->width() > 0 && adjusted_bounds_px.width() < min_size->width())
adjusted_bounds_px.set_width(min_size->width());
if (min_size->height() > 0 &&
adjusted_bounds_px.height() < min_size->height())
adjusted_bounds_px.set_height(min_size->height());
}
if (const auto max_size = delegate_->GetMaximumSizeForWindow()) {
if (max_size->width() > 0 && adjusted_bounds_px.width() > max_size->width())
adjusted_bounds_px.set_width(max_size->width());
if (max_size->height() > 0 &&
adjusted_bounds_px.height() > max_size->height())
adjusted_bounds_px.set_height(max_size->height());
}
if (bounds_px_ == adjusted_bounds_px)
return;
bounds_px_ = adjusted_bounds_px;
pending_buffer_scale_.emplace_back(
std::make_pair(bounds_px_.size(), window_scale()));
if (update_visual_size_immediately_)
UpdateVisualSize(bounds_px.size());
delegate_->OnBoundsChanged(bounds_px_);
}
gfx::Rect WaylandWindow::GetBounds() const {
return bounds_px_;
}
gfx::Rect WaylandWindow::GetBoundsInDIP() const {
return gfx::ScaleToRoundedRect(bounds_px_, 1.0 / window_scale());
}
void WaylandWindow::SetTitle(const std::u16string& title) {}
void WaylandWindow::SetCapture() {
// Wayland doesn't allow explicit grabs. Instead, it sends events to "entered"
// windows. That is, if user enters their mouse pointer to a window, that
// window starts to receive events. However, Chromium may want to reroute
// these events to another window. In this case, tell the window manager that
// this specific window has grabbed the events, and they will be rerouted in
// WaylandWindow::DispatchEvent method.
if (!HasCapture())
connection_->wayland_window_manager()->GrabLocatedEvents(this);
}
void WaylandWindow::ReleaseCapture() {
if (HasCapture())
connection_->wayland_window_manager()->UngrabLocatedEvents(this);
// See comment in SetCapture() for details on wayland and grabs.
}
bool WaylandWindow::HasCapture() const {
return connection_->wayland_window_manager()->located_events_grabber() ==
this;
}
void WaylandWindow::ToggleFullscreen() {}
void WaylandWindow::Maximize() {}
void WaylandWindow::Minimize() {}
void WaylandWindow::Restore() {}
PlatformWindowState WaylandWindow::GetPlatformWindowState() const {
// Remove normal state for all the other types of windows as it's only the
// WaylandToplevelWindow that supports state changes.
return PlatformWindowState::kNormal;
}
void WaylandWindow::Activate() {}
void WaylandWindow::Deactivate() {
NOTIMPLEMENTED_LOG_ONCE();
}
void WaylandWindow::SetUseNativeFrame(bool use_native_frame) {
// Do nothing here since only shell surfaces can handle server-side
// decoration.
}
bool WaylandWindow::ShouldUseNativeFrame() const {
// Always returns false here since only shell surfaces can handle server-side
// decoration.
return false;
}
void WaylandWindow::SetCursor(scoped_refptr<PlatformCursor> platform_cursor) {
DCHECK(platform_cursor);
if (cursor_ == platform_cursor)
return;
UpdateCursorShape(BitmapCursorOzone::FromPlatformCursor(platform_cursor));
}
void WaylandWindow::MoveCursorTo(const gfx::Point& location) {
NOTIMPLEMENTED();
}
void WaylandWindow::ConfineCursorToBounds(const gfx::Rect& bounds) {
NOTIMPLEMENTED();
}
void WaylandWindow::SetRestoredBoundsInPixels(const gfx::Rect& bounds_px) {
restored_bounds_px_ = bounds_px;
}
gfx::Rect WaylandWindow::GetRestoredBoundsInPixels() const {
return restored_bounds_px_;
}
bool WaylandWindow::ShouldWindowContentsBeTransparent() const {
// Wayland compositors always support translucency.
return true;
}
void WaylandWindow::SetAspectRatio(const gfx::SizeF& aspect_ratio) {
NOTIMPLEMENTED_LOG_ONCE();
}
bool WaylandWindow::IsTranslucentWindowOpacitySupported() const {
// Wayland compositors always support translucency.
return true;
}
void WaylandWindow::SetDecorationInsets(gfx::Insets insets_px) {
if (frame_insets_px_ == insets_px)
return;
frame_insets_px_ = insets_px;
SetWindowGeometry(gfx::ScaleToRoundedRect(GetBounds(), 1.f / window_scale()));
connection()->ScheduleFlush();
}
void WaylandWindow::SetWindowIcons(const gfx::ImageSkia& window_icon,
const gfx::ImageSkia& app_icon) {
NOTIMPLEMENTED_LOG_ONCE();
}
void WaylandWindow::SizeConstraintsChanged() {}
bool WaylandWindow::ShouldUpdateWindowShape() const {
return false;
}
bool WaylandWindow::CanDispatchEvent(const PlatformEvent& event) {
if (event->IsMouseEvent() || event->IsPinchEvent())
return has_pointer_focus_;
if (event->IsKeyEvent())
return has_keyboard_focus_;
if (event->IsTouchEvent())
return has_touch_focus_;
if (event->IsScrollEvent())
return has_pointer_focus_;
return false;
}
uint32_t WaylandWindow::DispatchEvent(const PlatformEvent& native_event) {
Event* event = static_cast<Event*>(native_event);
if (event->IsLocatedEvent()) {
auto* event_grabber =
connection_->wayland_window_manager()->located_events_grabber();
auto* root_parent_window = GetRootParentWindow();
// Wayland sends locations in DIP so they need to be translated to
// physical pixels.
UpdateCursorPositionFromEvent(Event::Clone(*event));
event->AsLocatedEvent()->set_location_f(gfx::ScalePoint(
event->AsLocatedEvent()->location_f(), window_scale(), window_scale()));
// We must reroute the events to the event grabber iff these windows belong
// to the same root parent window. For example, there are 2 top level
// Wayland windows. One of them (window_1) has a child menu window that is
// the event grabber. If the mouse is moved over the window_1, it must
// reroute the events to the event grabber. If the mouse is moved over the
// window_2, the events mustn't be rerouted, because that belongs to another
// stack of windows. Remember that Wayland sends local surface coordinates,
// and continuing rerouting all the events may result in events sent to the
// grabber even though the mouse is over another root window.
//
if (event_grabber &&
root_parent_window == event_grabber->GetRootParentWindow()) {
ConvertEventLocationToTargetWindowLocation(
event_grabber->GetBounds().origin(), GetBounds().origin(),
event->AsLocatedEvent());
return event_grabber->DispatchEventToDelegate(native_event);
}
}
// Dispatch all keyboard events to the root window.
if (event->IsKeyEvent())
return GetRootParentWindow()->DispatchEventToDelegate(event);
return DispatchEventToDelegate(native_event);
}
void WaylandWindow::HandleSurfaceConfigure(uint32_t serial) {
NOTREACHED()
<< "Only shell surfaces must receive HandleSurfaceConfigure calls.";
}
void WaylandWindow::HandleToplevelConfigure(int32_t widht,
int32_t height,
bool is_maximized,
bool is_fullscreen,
bool is_activated) {
NOTREACHED()
<< "Only shell toplevels must receive HandleToplevelConfigure calls.";
}
void WaylandWindow::HandlePopupConfigure(const gfx::Rect& bounds_dip) {
NOTREACHED() << "Only shell popups must receive HandlePopupConfigure calls.";
}
void WaylandWindow::UpdateVisualSize(const gfx::Size& size_px) {
if (visual_size_px_ == size_px)
return;
visual_size_px_ = size_px;
UpdateWindowMask();
}
void WaylandWindow::OnCloseRequest() {
delegate_->OnCloseRequest();
}
absl::optional<std::vector<gfx::Rect>> WaylandWindow::GetWindowShape() const {
return absl::nullopt;
}
void WaylandWindow::UpdateWindowMask() {
UpdateWindowShape();
root_surface_->SetOpaqueRegion({gfx::Rect(visual_size_px())});
}
void WaylandWindow::UpdateWindowShape() {}
void WaylandWindow::OnDragEnter(const gfx::PointF& point,
std::unique_ptr<OSExchangeData> data,
int operation) {
WmDropHandler* drop_handler = GetWmDropHandler(*this);
if (!drop_handler)
return;
auto location_px = gfx::ScalePoint(TranslateLocationToRootWindow(point),
window_scale(), window_scale());
// Wayland sends locations in DIP so they need to be translated to
// physical pixels.
// TODO(crbug.com/1102857): get the real event modifier here.
drop_handler->OnDragEnter(location_px, std::move(data), operation,
/*modifiers=*/0);
}
int WaylandWindow::OnDragMotion(const gfx::PointF& point, int operation) {
WmDropHandler* drop_handler = GetWmDropHandler(*this);
if (!drop_handler)
return 0;
auto location_px = gfx::ScalePoint(TranslateLocationToRootWindow(point),
window_scale(), window_scale());
// Wayland sends locations in DIP so they need to be translated to
// physical pixels.
// TODO(crbug.com/1102857): get the real event modifier here.
return drop_handler->OnDragMotion(location_px, operation,
/*modifiers=*/0);
}
void WaylandWindow::OnDragDrop() {
WmDropHandler* drop_handler = GetWmDropHandler(*this);
if (!drop_handler)
return;
// TODO(crbug.com/1102857): get the real event modifier here.
drop_handler->OnDragDrop({}, /*modifiers=*/0);
}
void WaylandWindow::OnDragLeave() {
WmDropHandler* drop_handler = GetWmDropHandler(*this);
if (!drop_handler)
return;
drop_handler->OnDragLeave();
}
void WaylandWindow::OnDragSessionClose(DragOperation operation) {
DCHECK(drag_handler_delegate_);
drag_handler_delegate_->OnDragFinished(operation);
drag_handler_delegate_ = nullptr;
connection()->event_source()->ResetPointerFlags();
std::move(drag_loop_quit_closure_).Run();
}
void WaylandWindow::SetBoundsDip(const gfx::Rect& bounds_dip) {
SetBounds(gfx::ScaleToRoundedRect(bounds_dip, window_scale()));
}
bool WaylandWindow::Initialize(PlatformWindowInitProperties properties) {
root_surface_ = std::make_unique<WaylandSurface>(connection_, this);
if (!root_surface_->Initialize()) {
LOG(ERROR) << "Failed to create wl_surface";
return false;
}
// Update visual size in tests immediately if the test config is set.
// Otherwise, such tests as interactive_ui_tests fail.
set_update_visual_size_immediately(UseTestConfigForPlatformWindows());
// Properties contain DIP bounds but the buffer scale is initially 1 so it's
// OK to assign. The bounds will be recalculated when the buffer scale
// changes.
bounds_px_ = properties.bounds;
opacity_ = properties.opacity;
type_ = properties.type;
connection_->wayland_window_manager()->AddWindow(GetWidget(), this);
if (!OnInitialize(std::move(properties)))
return false;
if (wayland_overlay_delegation_enabled_) {
primary_subsurface_ =
std::make_unique<WaylandSubsurface>(connection_, this);
if (!primary_subsurface_->surface())
return false;
connection_->wayland_window_manager()->AddSubsurface(
GetWidget(), primary_subsurface_.get());
}
connection_->ScheduleFlush();
PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
delegate_->OnAcceleratedWidgetAvailable(GetWidget());
root_surface_->SetOpaqueRegion({gfx::Rect(bounds_px_.size())});
return true;
}
void WaylandWindow::SetWindowGeometry(gfx::Rect bounds) {}
WaylandWindow* WaylandWindow::GetRootParentWindow() {
return parent_window_ ? parent_window_->GetRootParentWindow() : this;
}
void WaylandWindow::OnEnteredOutputIdAdded() {
// Wayland does weird things for menus so instead of tracking outputs that
// we entered or left, we take that from the parent window and ignore this
// event.
if (AsWaylandPopup())
return;
UpdateWindowScale(true);
}
void WaylandWindow::OnEnteredOutputIdRemoved() {
// Wayland does weird things for menus so instead of tracking outputs that
// we entered or left, we take that from the parent window and ignore this
// event.
if (AsWaylandPopup())
return;
UpdateWindowScale(true);
}
void WaylandWindow::UpdateCursorPositionFromEvent(
std::unique_ptr<Event> event) {
DCHECK(event->IsLocatedEvent());
// This is a tricky part. Initially, Wayland sends events to surfaces the
// events are targeted for. But, in order to fulfill Chromium's assumptions
// about event targets, some of the events are rerouted and their locations
// are converted. The event we got here is rerouted and it has had its
// location fixed.
//
// Basically, this method must translate coordinates of all events
// in regards to top-level windows' coordinates as it's always located at
// origin (0,0) from Chromium point of view (remember that Wayland doesn't
// provide global coordinates to its clients). And it's totally fine to use it
// as the target. Thus, the location of the |event| is always converted using
// the top-level window's bounds as the target excluding cases, when the
// mouse/touch is over a top-level window.
auto* toplevel_window = GetRootParentWindow();
if (toplevel_window != this) {
ConvertEventLocationToTargetWindowLocation(
toplevel_window->GetBounds().origin(), GetBounds().origin(),
event->AsLocatedEvent());
}
auto* cursor_position = connection_->wayland_cursor_position();
if (cursor_position) {
cursor_position->OnCursorPositionChanged(
event->AsLocatedEvent()->location());
}
}
gfx::PointF WaylandWindow::TranslateLocationToRootWindow(
const gfx::PointF& location) {
auto* root_window = GetRootParentWindow();
DCHECK(root_window);
if (root_window == this)
return location;
gfx::Vector2d offset =
GetBounds().origin() - root_window->GetBounds().origin();
return location + gfx::Vector2dF(offset);
}
WaylandWindow* WaylandWindow::GetTopMostChildWindow() {
return child_window_ ? child_window_->GetTopMostChildWindow() : this;
}
bool WaylandWindow::IsOpaqueWindow() const {
return opacity_ == ui::PlatformWindowOpacity::kOpaqueWindow;
}
bool WaylandWindow::IsActive() const {
// Please read the comment where the IsActive method is declared.
return false;
}
WaylandPopup* WaylandWindow::AsWaylandPopup() {
return nullptr;
}
uint32_t WaylandWindow::DispatchEventToDelegate(
const PlatformEvent& native_event) {
bool handled = DispatchEventFromNativeUiEvent(
native_event, base::BindOnce(&PlatformWindowDelegate::DispatchEvent,
base::Unretained(delegate_)));
return handled ? POST_DISPATCH_STOP_PROPAGATION : POST_DISPATCH_NONE;
}
std::unique_ptr<WaylandSurface> WaylandWindow::TakeWaylandSurface() {
DCHECK(shutting_down_);
DCHECK(root_surface_);
root_surface_->UnsetRootWindow();
return std::move(root_surface_);
}
bool WaylandWindow::RequestSubsurface() {
auto subsurface = std::make_unique<WaylandSubsurface>(connection_, this);
if (!subsurface->surface())
return false;
connection_->wayland_window_manager()->AddSubsurface(GetWidget(),
subsurface.get());
subsurface_stack_above_.push_back(subsurface.get());
auto result = wayland_subsurfaces_.emplace(std::move(subsurface));
DCHECK(result.second);
return true;
}
bool WaylandWindow::ArrangeSubsurfaceStack(size_t above, size_t below) {
while (wayland_subsurfaces_.size() < above + below) {
if (!RequestSubsurface())
return false;
}
DCHECK(subsurface_stack_below_.size() + subsurface_stack_above_.size() >=
above + below);
if (subsurface_stack_above_.size() < above) {
auto splice_start = subsurface_stack_below_.begin();
for (size_t i = 0; i < below; ++i)
++splice_start;
subsurface_stack_above_.splice(subsurface_stack_above_.end(),
subsurface_stack_below_, splice_start,
subsurface_stack_below_.end());
} else if (subsurface_stack_below_.size() < below) {
auto splice_start = subsurface_stack_above_.end();
for (size_t i = 0; i < below - subsurface_stack_below_.size(); ++i)
--splice_start;
subsurface_stack_below_.splice(subsurface_stack_below_.end(),
subsurface_stack_above_, splice_start,
subsurface_stack_above_.end());
}
DCHECK(subsurface_stack_below_.size() >= below);
DCHECK(subsurface_stack_above_.size() >= above);
return true;
}
bool WaylandWindow::CommitOverlays(
std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr>& overlays) {
if (overlays.empty())
return true;
// |overlays| is sorted from bottom to top.
std::sort(overlays.begin(), overlays.end(), OverlayStackOrderCompare);
// Find the location where z_oder becomes non-negative.
ozone::mojom::WaylandOverlayConfigPtr value =
ozone::mojom::WaylandOverlayConfig::New();
auto split = std::lower_bound(overlays.begin(), overlays.end(), value,
OverlayStackOrderCompare);
CHECK(split == overlays.end() || (*split)->z_order >= 0);
size_t num_primary_planes =
(split != overlays.end() && (*split)->z_order == 0) ? 1 : 0;
size_t above = (overlays.end() - split) - num_primary_planes;
size_t below = split - overlays.begin();
if (overlays.front()->z_order == INT32_MIN)
--below;
// Re-arrange the list of subsurfaces to fit the |overlays|. Request extra
// subsurfaces if needed.
if (!ArrangeSubsurfaceStack(above, below))
return false;
if (wayland_overlay_delegation_enabled_) {
primary_subsurface()->Show();
connection_->buffer_manager_host()->StartFrame(root_surface());
}
// Update buffer scale before subsurfaces are configured.
{
auto main_overlay = split;
if (split == overlays.end() && overlays.front()->z_order == INT32_MIN)
main_overlay = overlays.begin();
// Either use current scale of the window or pending scale whenever visual
// size updates. window_scale() won't be used if we are in process of
// changing bounds.
int32_t buffer_scale = window_scale();
auto result =
std::find_if(pending_buffer_scale_.begin(), pending_buffer_scale_.end(),
[&visual_size = (*main_overlay)->bounds_rect.size()](
auto& item) { return visual_size == item.first; });
if (result != pending_buffer_scale_.end()) {
buffer_scale = result->second;
pending_buffer_scale_.erase(pending_buffer_scale_.begin(), ++result);
}
root_surface()->SetSurfaceBufferScale(buffer_scale);
}
{
// Iterate through |subsurface_stack_below_|, setup subsurfaces and place
// them in corresponding order. Commit wl_buffers once a subsurface is
// configured.
auto overlay_iter = split - 1;
for (auto iter = subsurface_stack_below_.begin();
iter != subsurface_stack_below_.end(); ++iter, --overlay_iter) {
if (overlays.front()->z_order == INT32_MIN
? overlay_iter >= ++overlays.begin()
: overlay_iter >= overlays.begin()) {
WaylandSurface* reference_above = nullptr;
if (overlay_iter == split - 1) {
// It's possible that |overlays| does not contain primary plane, we
// still want to place relative to the surface with z_order=0.
reference_above = primary_subsurface_->wayland_surface();
} else {
reference_above = (*std::next(iter))->wayland_surface();
}
(*iter)->ConfigureAndShowSurface(
(*overlay_iter)->transform, (*overlay_iter)->bounds_rect,
root_surface()->buffer_scale(), (*overlay_iter)->enable_blend,
nullptr, reference_above);
(*iter)->wayland_surface()->SetViewportSource(
(*overlay_iter)->crop_rect);
(*iter)->wayland_surface()->SetViewportDestination(
(*overlay_iter)->bounds_rect.size());
connection_->buffer_manager_host()->CommitBufferInternal(
(*iter)->wayland_surface(), (*overlay_iter)->buffer_id,
(*overlay_iter)->damage_region,
/*wait_for_frame_callback=*/true,
/*commit_synced_subsurface=*/true,
std::move((*overlay_iter)->access_fence_handle));
} else {
// If there're more subsurfaces requested that we don't need at the
// moment, hide them.
(*iter)->Hide();
}
}
// Iterate through |subsurface_stack_above_|, setup subsurfaces and place
// them in corresponding order. Commit wl_buffers once a subsurface is
// configured.
overlay_iter = split + num_primary_planes;
for (auto iter = subsurface_stack_above_.begin();
iter != subsurface_stack_above_.end(); ++iter, ++overlay_iter) {
if (overlay_iter < overlays.end()) {
WaylandSurface* reference_below = nullptr;
if (overlay_iter == split + num_primary_planes) {
// It's possible that |overlays| does not contain primary plane, we
// still want to place relative to the surface with z_order=0.
reference_below = primary_subsurface_->wayland_surface();
} else {
reference_below = (*std::prev(iter))->wayland_surface();
}
(*iter)->ConfigureAndShowSurface(
(*overlay_iter)->transform, (*overlay_iter)->bounds_rect,
root_surface()->buffer_scale(), (*overlay_iter)->enable_blend,
reference_below, nullptr);
(*iter)->wayland_surface()->SetViewportSource(
(*overlay_iter)->crop_rect);
(*iter)->wayland_surface()->SetViewportDestination(
(*overlay_iter)->bounds_rect.size());
connection_->buffer_manager_host()->CommitBufferInternal(
(*iter)->wayland_surface(), (*overlay_iter)->buffer_id,
(*overlay_iter)->damage_region,
/*wait_for_frame_callback=*/true,
/*commit_synced_subsurface=*/true,
std::move((*overlay_iter)->access_fence_handle));
} else {
// If there're more subsurfaces requested that we don't need at the
// moment, hide them.
(*iter)->Hide();
}
}
}
if (split == overlays.end() && overlays.front()->z_order == INT32_MIN)
split = overlays.begin();
UpdateVisualSize((*split)->bounds_rect.size());
if (!wayland_overlay_delegation_enabled_) {
root_surface_->SetViewportSource((*split)->crop_rect);
// TODO(fangzhoug): Refactor some of this logic s.t. the decision of whether
// to apply viewport.destination is made at commit time.
root_surface_->SetViewportDestination((*split)->crop_rect ==
gfx::RectF(1.f, 1.f)
? gfx::Size()
: (*split)->bounds_rect.size());
connection_->buffer_manager_host()->CommitBufferInternal(
root_surface(), (*split)->buffer_id, (*split)->damage_region,
/*wait_for_frame_callback=*/true);
return true;
}
if (num_primary_planes) {
// Mutter has incorrect damage when processing un-cropped buffer commits
// with viewport.destination == buffer.size. So do not set
// viewport.destination to primary planes if crop_rect is uniform.
// TODO(fangzhoug): Refactor some of this logic s.t. the decision of whether
// to apply viewport.destination is made at commit time. Right now PIP
// would have incorrect size b/c it is fullscreen overlay scheduled at
// z_order=0.
primary_subsurface_->ConfigureAndShowSurface(
(*split)->transform, (*split)->bounds_rect,
root_surface()->buffer_scale(), (*split)->enable_blend, nullptr,
nullptr);
primary_subsurface_->wayland_surface()->SetViewportSource(
(*split)->crop_rect);
primary_subsurface_->wayland_surface()->SetViewportDestination(
(*split)->crop_rect == gfx::RectF(1.f, 1.f)
? gfx::Size()
: (*split)->bounds_rect.size());
connection_->buffer_manager_host()->CommitBufferInternal(
primary_subsurface_->wayland_surface(), (*split)->buffer_id,
(*split)->damage_region,
/*wait_for_frame_callback=*/true,
/*commit_synced_subsurface=*/true,
std::move((*split)->access_fence_handle));
}
gfx::Rect background_damage;
if (overlays.front()->z_order == INT32_MIN) {
background_buffer_id_ = overlays.front()->buffer_id;
background_damage = overlays.front()->damage_region;
should_attach_background_buffer_ = true;
}
root_surface_->SetViewportDestination(visual_size_px_);
if (should_attach_background_buffer_) {
connection_->buffer_manager_host()->EndFrame(background_buffer_id_,
background_damage);
should_attach_background_buffer_ = false;
} else {
// Subsurfaces are set to sync, above surface configs will only take effect
// when root_surface is committed.
connection_->buffer_manager_host()->EndFrame();
}
return true;
}
void WaylandWindow::UpdateCursorShape(scoped_refptr<BitmapCursorOzone> cursor) {
DCHECK(cursor);
absl::optional<int32_t> shape =
WaylandZcrCursorShapes::ShapeFromType(cursor->type());
// Round cursor scale factor to ceil as wl_surface.set_buffer_scale accepts
// only integers.
if (cursor->type() == CursorType::kNone) { // Hide the cursor.
connection_->SetCursorBitmap(
{}, gfx::Point(), std::ceil(cursor->cursor_image_scale_factor()));
} else if (cursor->platform_data()) { // Check for theme-provided cursor.
connection_->SetPlatformCursor(
reinterpret_cast<wl_cursor*>(cursor->platform_data()),
std::ceil(cursor->cursor_image_scale_factor()));
} else if (connection_->zcr_cursor_shapes() &&
shape.has_value()) { // Check for Wayland server-side cursor
// support (e.g. exo for lacros).
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// Lacros should not load image assets for default cursors. See
// BitmapCursorFactoryOzone::GetDefaultCursor().
DCHECK(cursor->bitmaps().empty());
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
connection_->zcr_cursor_shapes()->SetCursorShape(shape.value());
} else { // Use client-side bitmap cursors as fallback.
// Translate physical pixels to DIPs.
gfx::Point hotspot_in_dips =
gfx::ScaleToRoundedPoint(cursor->hotspot(), 1.0f / ui_scale_);
connection_->SetCursorBitmap(
cursor->bitmaps(), hotspot_in_dips,
std::ceil(cursor->cursor_image_scale_factor()));
}
// The new cursor needs to be stored last to avoid deleting the old cursor
// while it's still in use.
cursor_ = cursor;
}
} // namespace ui