0

ozone/wayland: Add support for xdg-toplevel-drag

Rebased and slighty updated version of https://chromium-review.googlesource.com/c/chromium/src/+/4400967

This implements a new protocol landed in wayland-protocols 1.34 that can
be found at
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml

It is intended to be a drop-in replacement for extended-drag-unstable-v1
with some features removed that were never implemented in Exo/Chromium.

If both the `extended-drag` and `xdg-toplevel-drag` protocols are
supported by the compositor - i.e. in Exo - the former is kept being
used by default. The later can be enabled via the
`WaylandXdgToplevelDrag` feature.

Original author: David Redondo <kde@david-redondo.de>

Bug: b:315000518
Bug: b:324170129
Change-Id: If251ac9944ec4395f2d8630b7c4ac74ca56a662d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5088752
Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: Antonio Gomes <tonikitoo@igalia.com>
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Reviewed-by: Nick Yamane <nickdiego@igalia.com>
Cr-Commit-Position: refs/heads/main@{#1354718}
This commit is contained in:
Robert Mader
2024-09-12 19:11:15 +00:00
committed by Chromium LUCI CQ
parent be0d7cec46
commit 8f45d25c2f
12 changed files with 121 additions and 20 deletions

@ -337,6 +337,7 @@ David Leen <davileen@amazon.com>
David Manouchehri <david@davidmanouchehri.com>
David McAllister <mcdavid@amazon.com>
David Michael Barr <david.barr@samsung.com>
David Redondo <kde@david-redondo.de>
David Sanders <dsanders11@ucsbalum.com>
David Spellman <dspell@amazon.com>
David Valachovic <adenflorian@gmail.com>

@ -37,6 +37,18 @@ BASE_FEATURE(kWaylandFractionalScaleV1,
#endif
);
// Controls whether support for the xdg-toplevel-drag protocol should be
// enabled. On Lacros it will then be used even if the Exo-only extended-drag
// protocol is supported.
BASE_FEATURE(kWaylandXdgToplevelDrag,
"WaylandXdgToplevelDrag",
#if BUILDFLAG(IS_LINUX)
base::FEATURE_ENABLED_BY_DEFAULT
#else
base::FEATURE_DISABLED_BY_DEFAULT
#endif
);
// This debug/dev flag pretty-prints DRM modeset configuration logs for ease
// of reading. For more information, see: http://b/233006802
BASE_FEATURE(kPrettyPrintDrmModesetConfigLogs,
@ -62,6 +74,10 @@ bool IsWaylandFractionalScaleV1Enabled() {
return base::FeatureList::IsEnabled(kWaylandFractionalScaleV1);
}
bool IsWaylandXdgToplevelDragEnabled() {
return base::FeatureList::IsEnabled(kWaylandXdgToplevelDrag);
}
bool IsPrettyPrintDrmModesetConfigLogsEnabled() {
return base::FeatureList::IsEnabled(kPrettyPrintDrmModesetConfigLogs);
}

@ -18,6 +18,7 @@ BASE_DECLARE_FEATURE(kUseDynamicCursorSize);
bool IsWaylandSurfaceSubmissionInPixelCoordinatesEnabled();
bool IsWaylandOverlayDelegationEnabled();
bool IsWaylandFractionalScaleV1Enabled();
bool IsWaylandXdgToplevelDragEnabled();
bool IsPrettyPrintDrmModesetConfigLogsEnabled();
bool IsUseDynamicCursorSizeEnabled();

@ -294,6 +294,7 @@ source_set("wayland") {
"//third_party/wayland-protocols:xdg_foreign",
"//third_party/wayland-protocols:xdg_output_protocol",
"//third_party/wayland-protocols:xdg_shell_protocol",
"//third_party/wayland-protocols:xdg_toplevel_drag_protocol",
"//third_party/wayland-protocols:xdg_toplevel_icon_protocol",
"//ui/base",
"//ui/base:buildflags",

@ -44,6 +44,7 @@
#include <xdg-foreign-unstable-v2-client-protocol.h>
#include <xdg-output-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
#include <xdg-toplevel-drag-v1-client-protocol.h>
#include <xdg-toplevel-icon-v1-client-protocol.h>
#include "base/logging.h"
@ -257,6 +258,8 @@ IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_popup)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_positioner)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_surface)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_toplevel)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_toplevel_drag_v1)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_toplevel_drag_manager_v1)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_toplevel_icon_manager_v1)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_toplevel_icon_v1)
IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_wm_base)

@ -156,6 +156,8 @@ DECLARE_WAYLAND_OBJECT_TRAITS(xdg_popup)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_positioner)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_surface)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_toplevel)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_toplevel_drag_v1)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_toplevel_drag_manager_v1)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_toplevel_icon_manager_v1)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_toplevel_icon_v1)
DECLARE_WAYLAND_OBJECT_TRAITS(xdg_wm_base)

@ -91,6 +91,7 @@ constexpr uint32_t kMaxExplicitSyncVersion = 2;
constexpr uint32_t kMaxAlphaCompositingVersion = 1;
constexpr uint32_t kMaxXdgDecorationVersion = 1;
constexpr uint32_t kMaxExtendedDragVersion = 1;
constexpr uint32_t kMaxXdgToplevelDragVersion = 1;
constexpr uint32_t kMaxXdgOutputManagerVersion = 3;
constexpr uint32_t kMaxKeyboardShortcutsInhibitManagerVersion = 1;
constexpr uint32_t kMaxStylusVersion = 2;
@ -745,6 +746,15 @@ void WaylandConnection::HandleGlobal(wl_registry* registry,
LOG(ERROR) << "Failed to bind to zcr_extended_drag_v1 global";
return;
}
} else if (!xdg_toplevel_drag_manager_v1_ &&
strcmp(interface, "xdg_toplevel_drag_manager_v1") == 0 &&
IsWaylandXdgToplevelDragEnabled()) {
xdg_toplevel_drag_manager_v1_ = wl::Bind<::xdg_toplevel_drag_manager_v1>(
registry, name, std::min(version, kMaxXdgToplevelDragVersion));
if (!xdg_toplevel_drag_manager_v1_) {
LOG(ERROR) << "Failed to bind to xdg_toplevel_drag_manager_v1 global";
return;
}
} else if (!xdg_output_manager_ &&
strcmp(interface, "zxdg_output_manager_v1") == 0) {
// Responsibilities of zxdg_output_manager_v1 have been subsumed into the

@ -160,6 +160,9 @@ class WaylandConnection {
zcr_extended_drag_v1* extended_drag_v1() const {
return extended_drag_v1_.get();
}
xdg_toplevel_drag_manager_v1* xdg_toplevel_drag_manager_v1() const {
return xdg_toplevel_drag_manager_v1_.get();
}
zxdg_output_manager_v1* xdg_output_manager_v1() const {
return xdg_output_manager_.get();
@ -500,6 +503,7 @@ class WaylandConnection {
linux_explicit_synchronization_;
wl::Object<zxdg_decoration_manager_v1> xdg_decoration_manager_;
wl::Object<zcr_extended_drag_v1> extended_drag_v1_;
wl::Object<::xdg_toplevel_drag_manager_v1> xdg_toplevel_drag_manager_v1_;
wl::Object<zxdg_output_manager_v1> xdg_output_manager_;
wl::Object<wp_fractional_scale_manager_v1> fractional_scale_manager_v1_;
wl::Object<xdg_toplevel_icon_manager_v1> toplevel_icon_manager_v1_;

@ -858,7 +858,8 @@ void WaylandToplevelWindow::HideTooltip() {
bool WaylandToplevelWindow::IsClientControlledWindowMovementSupported() const {
auto* window_drag_controller = connection()->window_drag_controller();
DCHECK(window_drag_controller);
return window_drag_controller->IsExtendedDragAvailable();
return window_drag_controller->IsExtendedDragAvailable() ||
window_drag_controller->IsXdgToplevelDragAvailable();
}
bool WaylandToplevelWindow::ShouldReleaseCaptureForDrag(
@ -882,11 +883,11 @@ void WaylandToplevelWindow::StartWindowDraggingSessionIfNeeded(
ui::mojom::DragEventSource event_source,
bool allow_system_drag) {
DCHECK(connection()->window_drag_controller());
// If extended drag is not available and |allow_system_drag| is set, this is
// no-op and WaylandDataDragController is assumed to be used instead. i.e:
// Fallback to a simpler window drag UX based on regular system drag-and-drop.
if (!connection()->window_drag_controller()->IsExtendedDragAvailable() &&
allow_system_drag) {
// If extended-drag and xdg-toplevel-drag are not available and
// |allow_system_drag| is set, this is no-op and WaylandDataDragController is
// assumed to be used instead. i.e: Fallback to a simpler window drag UX based
// on regular system drag-and-drop.
if (!IsClientControlledWindowMovementSupported() && allow_system_drag) {
return;
}
connection()->window_drag_controller()->StartDragSession(this, event_source);

@ -6,6 +6,7 @@
#include <extended-drag-unstable-v1-client-protocol.h>
#include <wayland-client-protocol.h>
#include <xdg-toplevel-drag-v1-client-protocol.h>
#include <cstdint>
#include <memory>
@ -36,8 +37,10 @@
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/wayland/common/wayland_object.h"
#include "ui/ozone/platform/wayland/host/dump_util.h"
#include "ui/ozone/platform/wayland/host/shell_toplevel_wrapper.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_device_manager.h"
@ -51,6 +54,7 @@
#include "ui/ozone/platform/wayland/host/wayland_surface.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/host/wayland_window_manager.h"
#include "ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h"
#include "ui/platform_window/platform_window_init_properties.h"
namespace ui {
@ -101,6 +105,39 @@ class WaylandWindowDragController::ExtendedDragSource {
const raw_ref<WaylandConnection> connection_;
};
class WaylandWindowDragController::XdgToplevelDrag {
public:
XdgToplevelDrag(WaylandConnection& connection, wl_data_source* source)
: connection_(connection) {
DCHECK(connection.xdg_toplevel_drag_manager_v1());
drag_.reset(xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(
connection.xdg_toplevel_drag_manager_v1(), source));
DCHECK(drag_);
}
void SetDraggedWindow(WaylandToplevelWindow* window,
const gfx::Vector2d& offset) {
if (!window) {
// Detaching happens implicitly via wl_data_source.dnd_drop_performed (see
// OnDataSourceDropPerformed()) or when the toplevel gets unmapped.
return;
}
DCHECK(window->shell_toplevel() &&
window->shell_toplevel()->AsXDGToplevelWrapper());
auto* toplevel =
window->shell_toplevel()->AsXDGToplevelWrapper()->xdg_toplevel_.get();
DCHECK(toplevel);
xdg_toplevel_drag_v1_attach(drag_.get(), toplevel, offset.x(), offset.y());
connection_->Flush();
}
private:
wl::Object<xdg_toplevel_drag_v1> drag_;
const raw_ref<WaylandConnection> connection_;
};
WaylandWindowDragController::WaylandWindowDragController(
WaylandConnection* connection,
WaylandDataDeviceManager* device_manager,
@ -165,12 +202,16 @@ bool WaylandWindowDragController::StartDragSession(
data_source_->Offer({kMimeTypeChromiumWindow});
data_source_->SetDndActions(kDndActionWindowDrag);
if (IsExtendedDragAvailableInternal()) {
if (IsXdgToplevelDragAvailable()) {
xdg_toplevel_drag_ = std::make_unique<XdgToplevelDrag>(
*connection_, data_source_->data_source());
} else if (IsExtendedDragAvailableInternal()) {
extended_drag_source_ = std::make_unique<ExtendedDragSource>(
*connection_, data_source_->data_source());
} else {
LOG(ERROR) << "zcr_extended_drag_v1 extension not available! "
<< "Window/Tab dragging won't be fully functional.";
LOG(ERROR)
<< "zcr_extended_drag_v1 and xdg_toplevel_drag_v1 extensions "
"not available! Window/Tab dragging won't be fully functional.";
}
data_device_->StartDrag(*data_source_, *origin_window_, serial->value,
@ -441,17 +482,24 @@ void WaylandWindowDragController::OnDataSourceFinish(WaylandDataSource* source,
data_offer_.reset();
data_source_.reset();
extended_drag_source_.reset();
xdg_toplevel_drag_.reset();
origin_surface_.reset();
origin_window_ = nullptr;
has_received_enter_ = false;
// When extended-drag is available and the drop happens while a non-null
// surface was being dragged (i.e: detached mode) which had pointer focus
// before the drag session, we must reset focus to it, otherwise it would be
// wrongly kept to the latest surface received through wl_data_device::enter
// (see OnDragEnter function).
// In case of touch, though, we simply reset the focus altogether.
if (IsExtendedDragAvailableInternal() && dragged_window_) {
// When extended-drag or xdg-toplevel-drag is available and the drop happens
// while a non-null surface was being dragged (i.e: detached mode) which had
// pointer focus before the drag session, we must reset focus to it, otherwise
// it would be wrongly kept to the latest surface received through
// wl_data_device::enter (see OnDragEnter function). In case of touch, though,
// we simply reset the focus altogether.
//
// TODO(crbug.com/324170129): Move drop handling logic below into
// OnDataSourceDropPerformed instead, otherwise dropping outside target
// surfaces will results in drag cancellation when xdg-toplevel-drag is used.
bool is_protocol_available =
IsExtendedDragAvailableInternal() || IsXdgToplevelDragAvailable();
if (is_protocol_available && dragged_window_) {
if (*drag_source_ == DragEventSource::kMouse) {
// TODO: check if this usage is correct.
@ -467,9 +515,11 @@ void WaylandWindowDragController::OnDataSourceFinish(WaylandDataSource* source,
// Transition to |kDropped| state and determine the next action to take. If
// drop happened while the move loop was running (i.e: kDetached), ask to quit
// the loop, otherwise notify session end and reset state right away.
is_protocol_available =
IsExtendedDragAvailable() || IsXdgToplevelDragAvailable();
State state_when_dropped = std::exchange(
state_, completed || !IsExtendedDragAvailable() ? State::kDropped
: State::kCancelled);
state_, completed || !is_protocol_available ? State::kDropped
: State::kCancelled);
if (state_when_dropped == State::kDetached) {
VLOG(1) << "Quiting Loop : Detached";
QuitLoop();
@ -673,9 +723,12 @@ void WaylandWindowDragController::SetDraggedWindow(
dragged_window_ = window;
drag_offset_ = offset;
// TODO(crbug.com/40598679): Fallback when extended-drag is not available.
if (extended_drag_source_)
// TODO(crbug.com/40598679): Fallback when no window drag protocol available.
if (extended_drag_source_) {
extended_drag_source_->SetDraggedWindow(dragged_window_, drag_offset_);
} else if (xdg_toplevel_drag_) {
xdg_toplevel_drag_->SetDraggedWindow(dragged_window_, drag_offset_);
}
}
bool WaylandWindowDragController::IsExtendedDragAvailable() const {
@ -683,6 +736,10 @@ bool WaylandWindowDragController::IsExtendedDragAvailable() const {
IsExtendedDragAvailableInternal();
}
bool WaylandWindowDragController::IsXdgToplevelDragAvailable() const {
return !!connection_->xdg_toplevel_drag_manager_v1();
}
bool WaylandWindowDragController::IsActiveDragAndDropSession() const {
return !!data_source_;
}

@ -88,6 +88,8 @@ class WaylandWindowDragController : public WaylandDataDevice::DragDelegate,
// Tells if "extended drag" extension is available.
bool IsExtendedDragAvailable() const;
// Tells if "xdg toplevel drag" extension is available.
bool IsXdgToplevelDragAvailable() const;
// Returns true if there there is currently an active drag-and-drop session.
// This is true if the `data_source_` exists (the session ends when this is
@ -118,6 +120,7 @@ class WaylandWindowDragController : public WaylandDataDevice::DragDelegate,
private:
class ExtendedDragSource;
class XdgToplevelDrag;
friend class WaylandWindowDragControllerTest;
FRIEND_TEST_ALL_PREFIXES(WaylandWindowDragControllerTest,
@ -204,6 +207,7 @@ class WaylandWindowDragController : public WaylandDataDevice::DragDelegate,
std::unique_ptr<WaylandDataOffer> data_offer_;
std::unique_ptr<ExtendedDragSource> extended_drag_source_;
std::unique_ptr<XdgToplevelDrag> xdg_toplevel_drag_;
// The current toplevel window being dragged, when in detached mode.
raw_ptr<WaylandToplevelWindow> dragged_window_ = nullptr;

@ -88,6 +88,7 @@ class XDGToplevelWrapperImpl : public ShellToplevelWrapper {
XDGSurfaceWrapperImpl* xdg_surface_wrapper() const;
private:
friend class WaylandWindowDragController;
// xdg_toplevel_listener callbacks:
static void OnToplevelConfigure(void* data,
xdg_toplevel* toplevel,