Skip to content

Commit

Permalink
steamcompmgr: Use XInput2 RawMotion events as a hint to update cursor…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
misyltoad committed Dec 27, 2023
1 parent d434d86 commit d7758a8
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 74 deletions.
3 changes: 2 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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,
)
Expand Down
169 changes: 104 additions & 65 deletions src/steamcompmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <X11/Xlib.h>
#include <X11/Xcursor/Xcursor.h>
#include <X11/extensions/xfixeswire.h>
#include <X11/extensions/XInput2.h>
#include <cstdint>
#include <drm_mode.h>
#include <memory>
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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 )
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1987,12 +1953,15 @@ bool MouseCursor::getTexture()

m_dirty = false;
updateCursorFeedback();
UpdateXInputMotionMasks();

if (m_imageEmpty) {

return false;
}

UpdatePosition();

CVulkanTexture::createFlags texCreateFlags;
if ( BIsNested() == false )
{
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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<int>( cursor->x(), pInputWindow->xwayland().a.x, pInputWindow->xwayland().a.x + pInputWindow->xwayland().a.width );
int nY = std::clamp<int>( 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 )
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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())
{
Expand Down
30 changes: 22 additions & 8 deletions src/steamcompmgr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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();
Expand All @@ -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)
{
Expand All @@ -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();

Expand All @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/xwayland_ctx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit d7758a8

Please sign in to comment.