From 395be2269b721da0ec1674fd8df4ba8c92e112cb Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Sat, 24 Feb 2024 01:24:59 +0000 Subject: [PATCH] bootmanager: Add wayland protocols * zwp_relative_pointer_v1 + zwp_locked_pointer_v1 for mouse panning * wp_viewporter for compositor side scaling --- src/core/frontend/emu_window.h | 3 + src/yuzu/CMakeLists.txt | 27 +++++++ src/yuzu/bootmanager.cpp | 141 +++++++++++++++++++++++++++++++++ src/yuzu/bootmanager.h | 32 ++++++++ src/yuzu/qt_common.cpp | 1 + 5 files changed, 204 insertions(+) diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index c7b48a58d7..0b6788daa6 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -60,6 +60,9 @@ public: // Connection to a display server. This is used on X11 and Wayland platforms. void* display_connection = nullptr; + // Mouse pointer. This is used on Wayland platforms. + void* mouse_pointer = nullptr; + // Render surface. This is a pointer to the native window handle, which depends // on the platform. e.g. HWND for Windows, Window for X11. If the surface is // set to nullptr, the video backend will run in headless mode. diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0259a8c293..b56f94c519 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -393,6 +393,33 @@ if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::DBus) endif() +if (UNIX AND NOT APPLE) + find_package(ECM NO_MODULE) + if (ECM_FOUND) + list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + find_package(Wayland COMPONENTS Client) + if (Wayland_FOUND) + find_package(WaylandScanner REQUIRED) + find_package(WaylandProtocols 1.15 REQUIRED) + ecm_add_wayland_client_protocol(WAYLAND_PROTOCOL_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/relative-pointer/relative-pointer-unstable-v1.xml + BASENAME relative-pointer-unstable-v1) + ecm_add_wayland_client_protocol(WAYLAND_PROTOCOL_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml + BASENAME pointer-constraints-unstable-v1) + ecm_add_wayland_client_protocol(WAYLAND_PROTOCOL_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml + BASENAME viewporter) + + target_link_libraries(yuzu PRIVATE Wayland::Client) + target_sources(yuzu PRIVATE ${WAYLAND_PROTOCOL_SRCS}) + set_source_files_properties(${WAYLAND_PROTOCOL_SRCS} PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE) + + target_compile_definitions(yuzu PRIVATE HAS_WAYLAND) + endif() + endif() +endif() + target_compile_definitions(yuzu PRIVATE # Use QStringBuilder for string concatenation to reduce # the overall number of temporary strings created. diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index ed57501552..bea189abb9 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -33,6 +33,11 @@ #include #include +#ifdef HAS_WAYLAND +#include +#include +#endif + #ifdef HAS_OPENGL #include #include @@ -354,6 +359,17 @@ void GRenderWindow::OnFramebufferSizeChanged() { const qreal pixel_ratio = windowPixelRatio(); const u32 width = this->width() * pixel_ratio; const u32 height = this->height() * pixel_ratio; + +#ifdef HAS_WAYLAND + if (viewport) { + if (width > 0 && height > 0) { + wp_viewport_set_destination(viewport, this->width(), this->height()); + } else { + wp_viewport_set_destination(viewport, -1, -1); + } + } +#endif // HAS_WAYLAND + UpdateCurrentFramebufferLayout(width, height); } @@ -661,7 +677,99 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) { emit MouseActivity(); } +#ifdef HAS_WAYLAND +void GRenderWindow::GlobalAddHandler(void* data, wl_registry* registry, u32 name, + const char* interface, u32 version) { + GRenderWindow* render_window = static_cast(data); + + if (std::strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0 && + version == static_cast(zwp_pointer_constraints_v1_interface.version)) { + render_window->pointer_constraints = static_cast( + wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, version)); + } else if (std::strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0 && + version == static_cast(zwp_relative_pointer_manager_v1_interface.version)) { + render_window->relative_pointer_manager = static_cast( + wl_registry_bind(registry, name, &zwp_relative_pointer_manager_v1_interface, version)); + } else if (std::strcmp(interface, wp_viewporter_interface.name) == 0) { + render_window->viewporter = static_cast( + wl_registry_bind(registry, name, &wp_viewporter_interface, version)); + } +} + +void GRenderWindow::GlobalRemoveHandler(void* data, wl_registry* registry, u32 name) {} + +void GRenderWindow::RelativePointerMotionHandler(void* data, + zwp_relative_pointer_v1* zwp_relative_pointer_v1, + u32 utime_hi, u32 utime_lo, wl_fixed_t dx, + wl_fixed_t dy, wl_fixed_t dx_unaccel, + wl_fixed_t dy_unaccel) { + GRenderWindow* render_window = static_cast(data); + render_window->CheckWaylandPointerLock(); + if (!render_window->IsWaylandPointerLocked()) + return; + + render_window->input_subsystem->GetMouse()->Move(wl_fixed_to_int(dx_unaccel), + wl_fixed_to_int(dy_unaccel), 0, 0); +} + +void GRenderWindow::PointerLockedHandler(void* data, zwp_locked_pointer_v1*) { + GRenderWindow* render_window = static_cast(data); + render_window->setCursor(QCursor(Qt::BlankCursor)); +} +void GRenderWindow::PointerUnlockedHandler(void* data, zwp_locked_pointer_v1*) { + GRenderWindow* render_window = static_cast(data); + render_window->locked_pointer = nullptr; + render_window->unsetCursor(); +} + +void GRenderWindow::CheckWaylandPointerLock() { + if (!pointer_constraints || !relative_pointer_manager) + return; + + bool mouse_panning = Settings::values.mouse_panning && !Settings::values.mouse_enabled; + + if (IsWaylandPointerLocked() != mouse_panning) { + if (mouse_panning) { + LockWaylandPointer(); + } else { + UnlockWaylandPointer(); + } + } +} + +bool GRenderWindow::IsWaylandPointerLocked() { + return locked_pointer; +} + +void GRenderWindow::LockWaylandPointer() { + if (pointer_constraints && relative_pointer && !locked_pointer) { + locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + pointer_constraints, static_cast(window_info.render_surface), + static_cast(window_info.mouse_pointer), nullptr, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); + + static constexpr zwp_locked_pointer_v1_listener locked_pointer_listener = { + .locked = PointerLockedHandler, + .unlocked = PointerUnlockedHandler, + }; + zwp_locked_pointer_v1_add_listener(locked_pointer, &locked_pointer_listener, this); + } +} + +void GRenderWindow::UnlockWaylandPointer() { + if (locked_pointer) { + zwp_locked_pointer_v1_destroy(locked_pointer); + locked_pointer = nullptr; + } +} +#endif // HAS_WAYLAND + void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { +#ifdef HAS_WAYLAND + if (IsWaylandPointerLocked()) + return; +#endif // HAS_WAYLAND + // Touch input is handled in TouchUpdateEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; @@ -941,6 +1049,39 @@ bool GRenderWindow::InitRenderTarget() { // Update the Window System information with the new render target window_info = QtCommon::GetWindowSystemInfo(child_widget->windowHandle()); +#ifdef HAS_WAYLAND + if (window_info.type == Core::Frontend::WindowSystemType::Wayland) { + wl_display* display = static_cast(window_info.display_connection); + + wl_registry* registry = wl_display_get_registry(display); + + static constexpr wl_registry_listener registryListener = { + .global = GlobalAddHandler, + .global_remove = GlobalRemoveHandler, + }; + wl_registry_add_listener(registry, ®istryListener, this); + + // This should run GlobalAddHandler getting the protocol globals + wl_display_roundtrip(display); + + if (viewporter) { + viewport = wp_viewporter_get_viewport( + viewporter, static_cast(window_info.render_surface)); + } + + if (relative_pointer_manager) { + relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + relative_pointer_manager, static_cast(window_info.mouse_pointer)); + + static const zwp_relative_pointer_v1_listener relative_pointer_listener = { + .relative_motion = RelativePointerMotionHandler, + }; + zwp_relative_pointer_v1_add_listener(relative_pointer, &relative_pointer_listener, + this); + } + } +#endif // HAS_WAYLAND + child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); layout()->addWidget(child_widget); // Reset minimum required size to avoid resizing issues on the main window after restarting. diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index ae12b34818..698aea314d 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -23,6 +23,12 @@ #include #include +#ifdef HAS_WAYLAND +#include +#include +#include +#endif + #include "common/common_types.h" #include "common/logging/log.h" #include "common/polyfill_thread.h" @@ -227,6 +233,32 @@ signals: void TasPlaybackStateChanged(); private: +#ifdef HAS_WAYLAND + static void GlobalAddHandler(void* data, wl_registry* registry, u32 name, const char* interface, + u32 version); + static void GlobalRemoveHandler(void* data, wl_registry* registry, u32 name); + static void RelativePointerMotionHandler(void* data, + zwp_relative_pointer_v1* zwp_relative_pointer_v1, + u32 utime_hi, u32 utime_lo, wl_fixed_t dx, + wl_fixed_t dy, wl_fixed_t dx_unaccel, + wl_fixed_t dy_unaccel); + static void PointerLockedHandler(void* data, zwp_locked_pointer_v1* zwp_relative_pointer_v1); + static void PointerUnlockedHandler(void* data, zwp_locked_pointer_v1* zwp_relative_pointer_v1); + + void CheckWaylandPointerLock(); + bool IsWaylandPointerLocked(); + void LockWaylandPointer(); + void UnlockWaylandPointer(); + + zwp_pointer_constraints_v1* pointer_constraints = nullptr; + zwp_relative_pointer_manager_v1* relative_pointer_manager = nullptr; + wp_viewporter* viewporter = nullptr; + + zwp_relative_pointer_v1* relative_pointer = nullptr; + zwp_locked_pointer_v1* locked_pointer = nullptr; + wp_viewport* viewport = nullptr; +#endif + void TouchBeginEvent(const QTouchEvent* event); void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); diff --git a/src/yuzu/qt_common.cpp b/src/yuzu/qt_common.cpp index 413402165c..4cfde01ec1 100644 --- a/src/yuzu/qt_common.cpp +++ b/src/yuzu/qt_common.cpp @@ -48,6 +48,7 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) #else QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); wsi.display_connection = pni->nativeResourceForWindow("display", window); + wsi.mouse_pointer = pni->nativeResourceForIntegration("wl_pointer"); if (wsi.type == Core::Frontend::WindowSystemType::Wayland) wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr; else