From d7758a8d7eae69384a33263eee267d60fdcc98f9 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Tue, 26 Dec 2023 15:07:43 +0000 Subject: [PATCH] steamcompmgr: Use XInput2 RawMotion events as a hint to update cursor pos Taking a page out of XEyes' book to get the cursor position (even when the cursor is grabbed, etc) by only doing so when we get a RawMotion event -- and only register for those when we know there's a cursor image that we would want to render. This is a kinda crap workaround for the fact that we cannot directly just recieve information about the cursor from the XServer, even as the X11 WM due to cursor grabbing, input masking and other such nonsense. An alternative would be using something like libei and having XTest funnel through that -- however that needs work to be functional on nested compositors. --- src/meson.build | 3 +- src/steamcompmgr.cpp | 169 ++++++++++++++++++++++++++----------------- src/steamcompmgr.hpp | 30 ++++++-- src/xwayland_ctx.hpp | 1 + 4 files changed, 129 insertions(+), 74 deletions(-) diff --git a/src/meson.build b/src/meson.build index 1df4f027b5..4cb5466960 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ dep_xxf86vm = dependency('xxf86vm') dep_xtst = dependency('xtst') dep_xres = dependency('xres') dep_xmu = dependency('xmu') +dep_xi = dependency('xi') drm_dep = dependency('libdrm', version: '>= 2.4.113') @@ -136,7 +137,7 @@ endif dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, xkbcommon, thread_dep, sdl_dep, wlroots_dep, vulkan_dep, liftoff_dep, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep, - stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, + stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi ], install: true, ) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 86b57796f2..c24ba58bad 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1579,35 +1580,9 @@ MouseCursor::MouseCursor(xwayland_ctx_t *ctx) updateCursorFeedback( true ); } -void MouseCursor::queryPositions(int &rootX, int &rootY, int &winX, int &winY) -{ - Window window, child; - unsigned int mask; - - XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, - &rootX, &rootY, &winX, &winY, &mask); - -} - -void MouseCursor::queryGlobalPosition(int &x, int &y) -{ - int winX, winY; - queryPositions(x, y, winX, winY); -} - -void MouseCursor::queryButtonMask(unsigned int &mask) -{ - Window window, child; - int rootX, rootY, winX, winY; - - XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, - &rootX, &rootY, &winX, &winY, &mask); -} - void MouseCursor::checkSuspension() { - unsigned int buttonMask; - queryButtonMask(buttonMask); + unsigned int buttonMask = 0; bool bWasHidden = m_hideForMovement; @@ -1666,11 +1641,6 @@ void MouseCursor::warp(int x, int y) XWarpPointer(m_ctx->dpy, None, x11_win(m_ctx->focus.inputFocusWindow), 0, 0, 0, 0, x, y); } -void MouseCursor::resetPosition() -{ - warp(m_x, m_y); -} - void MouseCursor::setDirty() { // We can't prove it's empty until checking again @@ -1767,23 +1737,28 @@ bool MouseCursor::setCursorImageByName(const char *name) void MouseCursor::constrainPosition() { - int i; steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; steamcompmgr_win_t *override = m_ctx->focus.overrideWindow; if (window == override) window = m_ctx->focus.focusWindow; - // If we had barriers before, get rid of them. - for (i = 0; i < 4; i++) { - if (m_scaledFocusBarriers[i] != None) { - XFixesDestroyPointerBarrier(m_ctx->dpy, m_scaledFocusBarriers[i]); - m_scaledFocusBarriers[i] = None; + if (!window) + return; + + auto barricade = [this](CursorBarrier& barrier, const CursorBarrierInfo& info) { + if (barrier.info.x1 == info.x1 && barrier.info.x2 == info.x2 && + barrier.info.y1 == info.y1 && barrier.info.y2 == info.y2) + return; + + if (barrier.obj != None) + { + XFixesDestroyPointerBarrier(m_ctx->dpy, barrier.obj); + barrier.obj = None; } - } - auto barricade = [this](int x1, int y1, int x2, int y2) { - return XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), - x1, y1, x2, y2, 0, 0, NULL); + barrier.obj = XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), + info.x1, info.y1, info.x2, info.y2, 0, 0, NULL); + barrier.info = info; }; int x1 = window->xwayland().a.x; @@ -1802,14 +1777,13 @@ void MouseCursor::constrainPosition() } // Constrain it to the window; careful, the corners will leak due to a known X server bug. - m_scaledFocusBarriers[0] = barricade(0, y1, m_ctx->root_width, y1); - m_scaledFocusBarriers[1] = barricade(x2, 0, x2, m_ctx->root_height); - m_scaledFocusBarriers[2] = barricade(m_ctx->root_width, y2, 0, y2); - m_scaledFocusBarriers[3] = barricade(x1, m_ctx->root_height, x1, 0); + barricade(m_barriers[0], CursorBarrierInfo{ 0, y1, m_ctx->root_width, y1 }); + barricade(m_barriers[1], CursorBarrierInfo{ x2, 0, x2, m_ctx->root_height }); + barricade(m_barriers[2], CursorBarrierInfo{ m_ctx->root_width, y2, 0, y2 }); + barricade(m_barriers[3], CursorBarrierInfo{ x1, m_ctx->root_height, x1, 0 }); // Make sure the cursor is somewhere in our jail - int rootX, rootY; - queryGlobalPosition(rootX, rootY); + int rootX = m_x, rootY = m_y; if ( rootX >= x2 || rootY >= y2 || rootX < x1 || rootY < y1 ) { if ( window_wants_no_focus_when_mouse_hidden( window ) && m_hideForMovement ) @@ -1865,14 +1839,6 @@ void MouseCursor::move(int x, int y) updateCursorFeedback(); } -void MouseCursor::updatePosition() -{ - int x,y; - queryGlobalPosition(x, y); - move(x, y); - checkSuspension(); -} - int MouseCursor::x() const { return m_x; @@ -1987,12 +1953,15 @@ bool MouseCursor::getTexture() m_dirty = false; updateCursorFeedback(); + UpdateXInputMotionMasks(); if (m_imageEmpty) { return false; } + UpdatePosition(); + CVulkanTexture::createFlags texCreateFlags; if ( BIsNested() == false ) { @@ -2022,15 +1991,46 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) nHeight = nSize; } +void MouseCursor::UpdateXInputMotionMasks() +{ + bool bShouldMotionMask = !imageEmpty(); + + if ( m_bMotionMaskEnabled != bShouldMotionMask ) + { + XIEventMask xi_eventmask; + unsigned char xi_mask[ ( XI_LASTEVENT + 7 ) / 8 ]{}; + xi_eventmask.deviceid = XIAllDevices; + xi_eventmask.mask_len = sizeof( xi_mask ); + xi_eventmask.mask = xi_mask; + if ( bShouldMotionMask ) + XISetMask( xi_mask, XI_RawMotion ); + XISelectEvents( m_ctx->dpy, m_ctx->root, &xi_eventmask, 1 ); + + m_bMotionMaskEnabled = bShouldMotionMask; + } +} + +void MouseCursor::UpdatePosition() +{ + Window root_return, child_return; + int root_x_return, root_y_return; + int win_x_return, win_y_return; + unsigned int mask_return; + XQueryPointer(m_ctx->dpy, m_ctx->root, &root_return, &child_return, + &root_x_return, &root_y_return, + &win_x_return, &win_y_return, + &mask_return); + + move(root_x_return, root_y_return); +} + void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) { if ( m_hideForMovement || m_imageEmpty ) { return; } - int rootX, rootY, winX, winY; - queryPositions(rootX, rootY, winX, winY); - move(rootX, rootY); + int winX = m_x, winY = m_y; // Also need new texture if (!getTexture()) { @@ -6996,6 +6996,7 @@ void xwayland_ctx_t::Dispatch() MouseCursor *cursor = ctx->cursor.get(); bool bShouldResetCursor = false; bool bSetFocus = false; + bool bShouldUpdateCursor = false; while (XPending(ctx->dpy)) { @@ -7135,6 +7136,16 @@ void xwayland_ctx_t::Dispatch() case SelectionRequest: handle_selection_request(ctx, &ev.xselectionrequest); break; + case GenericEvent: + if (ev.xcookie.extension == ctx->xinput_opcode) + { + if (ev.xcookie.evtype == XI_RawMotion) + { + bShouldUpdateCursor = true; + } + } + break; + default: if (ev.type == ctx->damage_event + XDamageNotify) { @@ -7153,12 +7164,24 @@ void xwayland_ctx_t::Dispatch() XFlush(ctx->dpy); } - if ( bShouldResetCursor ) + if ( bShouldUpdateCursor ) { - // This shouldn't happen due to our pointer barriers, - // but there is a known X server bug; warp to last good - // position. - cursor->resetPosition(); + cursor->UpdatePosition(); + + if ( bShouldResetCursor ) + { + // This shouldn't happen due to our pointer barriers, + // but there is a known X server bug; warp to last good + // position. + steamcompmgr_win_t *pInputWindow = ctx->focus.inputFocusWindow; + int nX = std::clamp( cursor->x(), pInputWindow->xwayland().a.x, pInputWindow->xwayland().a.x + pInputWindow->xwayland().a.width ); + int nY = std::clamp( cursor->y(), pInputWindow->xwayland().a.y, pInputWindow->xwayland().a.y + pInputWindow->xwayland().a.height ); + + if ( cursor->x() != nX || cursor->y() != nY ) + { + cursor->forcePosition( nX, nY ); + } + } } if ( bSetFocus ) @@ -7319,6 +7342,18 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ xwm_log.errorf("Unsupported XRes version: have %d.%d, want 1.2", xres_major, xres_minor); exit(1); } + if (!XQueryExtension(ctx->dpy, + "XInputExtension", + &ctx->xinput_opcode, + &ctx->xinput_event, + &ctx->xinput_error)) + { + xwm_log.errorf("No XInput extension"); + exit(1); + } + int xi_major = 2; + int xi_minor = 0; + XIQueryVersion(ctx->dpy, &xi_major, &xi_minor); if (!register_cm(ctx)) { @@ -7543,6 +7578,9 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ } } + ctx->cursor->undirty(); + ctx->cursor->UpdateXInputMotionMasks(); + XFlush(ctx->dpy); } @@ -8239,7 +8277,8 @@ steamcompmgr_main(int argc, char **argv) if (global_focus.cursor) { - global_focus.cursor->updatePosition(); + global_focus.cursor->constrainPosition(); + global_focus.cursor->checkSuspension(); if (global_focus.cursor->needs_server_flush()) { diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 59ac3b283d..16ef13273a 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -49,6 +49,20 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; // use the proper libliftoff composite plane system. static constexpr bool kDisablePartialComposition = true; +struct CursorBarrierInfo +{ + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; +}; + +struct CursorBarrier +{ + PointerBarrier obj = None; + CursorBarrierInfo info = {}; +}; + class MouseCursor { public: @@ -58,9 +72,7 @@ class MouseCursor int y() const; void move(int x, int y); - void updatePosition(); void constrainPosition(); - void resetPosition(); void paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, FrameInfo_t *frameInfo); void setDirty(); @@ -72,6 +84,7 @@ class MouseCursor void hide() { m_lastMovedTime = 0; checkSuspension(); } bool isHidden() { return m_hideForMovement || m_imageEmpty; } + bool imageEmpty() const { return m_imageEmpty; } void forcePosition(int x, int y) { @@ -89,13 +102,12 @@ class MouseCursor void GetDesiredSize( int& nWidth, int &nHeight ); + void UpdateXInputMotionMasks(); + void UpdatePosition(); + + void checkSuspension(); private: void warp(int x, int y); - void checkSuspension(); - - void queryGlobalPosition(int &x, int &y); - void queryPositions(int &rootX, int &rootY, int &winX, int &winY); - void queryButtonMask(unsigned int &mask); bool getTexture(); @@ -111,7 +123,9 @@ class MouseCursor unsigned int m_lastMovedTime = 0; bool m_hideForMovement; - PointerBarrier m_scaledFocusBarriers[4] = { None }; + bool m_bMotionMaskEnabled = false; + + CursorBarrier m_barriers[4] = {}; xwayland_ctx_t *m_ctx; diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index 7a4030f5d2..11f9335def 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -70,6 +70,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable int render_event, render_error; int xshape_event, xshape_error; int composite_opcode; + int xinput_opcode, xinput_event, xinput_error; Window ourWindow; focus_t focus;