[client] X11: match Wayland pointer input behaviour

Make cooked mouse input independent of keyboard focus.

Preserve button state across pointer leave events.

Match Wayland event order, wheel steps, and raw motion delivery.
This commit is contained in:
Geoffrey McRae
2026-08-10 17:20:47 +10:00
parent b8955fd012
commit f8c4ef3360
4 changed files with 59 additions and 99 deletions

View File

@@ -63,43 +63,33 @@ bool x11InputPointerEnter(X11Input * input, bool mainWindow,
if (input->entered || !mainWindow || !normal) if (input->entered || !mainWindow || !normal)
return false; return false;
input->sink->position(input->opaque, x, y);
input->sink->enter(input->opaque, true);
input->entered = true; input->entered = true;
input->sink->enter(input->opaque, true);
input->sink->position(input->opaque, x, y);
return true; return true;
} }
bool x11InputPointerLeave(X11Input * input, bool mainWindow, bool x11InputPointerLeave(X11Input * input, bool mainWindow,
bool normal, bool captureMode, double x, double y) bool normal, bool captureMode)
{ {
if (!input->entered || !mainWindow || input->buttons || captureMode || if (!input->entered || !mainWindow || input->buttons || captureMode ||
!normal) !normal)
return false; return false;
input->sink->position(input->opaque, x, y);
input->sink->enter(input->opaque, false);
input->entered = false; input->entered = false;
input->sink->enter(input->opaque, false);
return true; return true;
} }
void x11InputPointerMotion(X11Input * input, double x, double y, void x11InputPointerMotion(X11Input * input, double x, double y)
bool pointerGrabbed)
{ {
input->sink->position(input->opaque, x, y); input->sink->position(input->opaque, x, y);
if (!pointerGrabbed)
input->sink->relative(input->opaque, 0.0, 0.0, 0.0, 0.0);
} }
void x11InputPointerButton(X11Input * input, unsigned int detail, void x11InputPointerButton(X11Input * input, unsigned int detail,
bool pressed, bool raw, uint32_t time, bool inputActive) bool pressed, bool raw)
{ {
if (raw) if (!raw && !input->entered)
{
if (!inputActive)
return;
}
else if (!input->focused || !input->entered)
return; return;
const unsigned int button = mapButton(detail); const unsigned int button = mapButton(detail);
@@ -108,26 +98,18 @@ void x11InputPointerButton(X11Input * input, unsigned int detail,
if (!raw) if (!raw)
{ {
if (pressed && button == 4) if (button == 4 || button == 5)
input->sink->wheel(input->opaque, -0.5); {
else if (pressed && button == 5) if (pressed)
input->sink->wheel(input->opaque, 0.5); {
else if (button != 4 && button != 5) input->sink->button(input->opaque, button, true);
input->sink->button(input->opaque, button, pressed); input->sink->button(input->opaque, button, false);
return; input->sink->wheel(input->opaque, button == 4 ? -1.0 : 1.0);
}
return;
}
} }
uint32_t * previousTime = pressed ?
&input->previousPressTime : &input->previousReleaseTime;
uint32_t * previousButton = pressed ?
&input->previousPressButton : &input->previousReleaseButton;
if (time == *previousTime && detail == *previousButton)
return;
*previousTime = time;
*previousButton = detail;
const uint32_t mask = UINT32_C(1) << button; const uint32_t mask = UINT32_C(1) << button;
if (pressed) if (pressed)
input->buttons |= mask; input->buttons |= mask;
@@ -137,20 +119,9 @@ void x11InputPointerButton(X11Input * input, unsigned int detail,
input->sink->button(input->opaque, button, pressed); input->sink->button(input->opaque, button, pressed);
} }
void x11InputRelativeMotion(X11Input * input, uint32_t time, void x11InputRelativeMotion(X11Input * input,
double x, double y, double rawX, double rawY, bool inputActive) double x, double y, double rawX, double rawY)
{ {
if (!inputActive)
return;
if (time == input->previousMotionTime &&
x == input->previousMotionX && y == input->previousMotionY)
return;
input->previousMotionTime = time;
input->previousMotionX = x;
input->previousMotionY = y;
input->sink->relative(input->opaque, x, y, rawX, rawY); input->sink->relative(input->opaque, x, y, rawX, rawY);
} }

View File

@@ -32,13 +32,6 @@ typedef struct X11Input
const LG_DSInputSink * sink; const LG_DSInputSink * sink;
void * opaque; void * opaque;
uint32_t buttons; uint32_t buttons;
uint32_t previousPressTime;
uint32_t previousReleaseTime;
uint32_t previousPressButton;
uint32_t previousReleaseButton;
uint32_t previousMotionTime;
double previousMotionX;
double previousMotionY;
bool entered; bool entered;
bool focused; bool focused;
} }
@@ -52,13 +45,12 @@ bool x11InputFocus(X11Input * input, bool focused, double x, double y,
bool x11InputPointerEnter(X11Input * input, bool mainWindow, bool x11InputPointerEnter(X11Input * input, bool mainWindow,
bool normal, double x, double y); bool normal, double x, double y);
bool x11InputPointerLeave(X11Input * input, bool mainWindow, bool x11InputPointerLeave(X11Input * input, bool mainWindow,
bool normal, bool captureMode, double x, double y); bool normal, bool captureMode);
void x11InputPointerMotion(X11Input * input, double x, double y, void x11InputPointerMotion(X11Input * input, double x, double y);
bool pointerGrabbed);
void x11InputPointerButton(X11Input * input, unsigned int button, void x11InputPointerButton(X11Input * input, unsigned int button,
bool pressed, bool raw, uint32_t time, bool inputActive); bool pressed, bool raw);
void x11InputRelativeMotion(X11Input * input, uint32_t time, void x11InputRelativeMotion(X11Input * input,
double x, double y, double rawX, double rawY, bool inputActive); double x, double y, double rawX, double rawY);
void x11InputKeyboardKey(X11Input * input, unsigned int keycode, void x11InputKeyboardKey(X11Input * input, unsigned int keycode,
int minKeycode, bool pressed, bool raw, bool inputActive, int minKeycode, bool pressed, bool raw, bool inputActive,
const char * text); const char * text);

View File

@@ -1373,8 +1373,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
XILeaveEvent *xie = cookie->data; XILeaveEvent *xie = cookie->data;
if (!x11InputPointerLeave(&x11.input, xie->event == x11.window, if (!x11InputPointerLeave(&x11.input, xie->event == x11.window,
xie->mode != NotifyGrab, app_isCaptureMode(), xie->mode != NotifyGrab, app_isCaptureMode()))
xie->event_x, xie->event_y))
return; return;
/** /**
@@ -1520,46 +1519,52 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
case XI_ButtonPress: case XI_ButtonPress:
{ {
XIDeviceEvent *device = cookie->data; XIDeviceEvent *device = cookie->data;
x11InputPointerButton(&x11.input, device->detail, true, false, x11InputPointerButton(&x11.input, device->detail, true, false);
device->time, true);
return; return;
} }
case XI_ButtonRelease: case XI_ButtonRelease:
{ {
XIDeviceEvent *device = cookie->data; XIDeviceEvent *device = cookie->data;
x11InputPointerButton(&x11.input, device->detail, false, false, x11InputPointerButton(&x11.input, device->detail, false, false);
device->time, true);
return; return;
} }
case XI_RawButtonPress: case XI_RawButtonPress:
{ {
if (!x11.input.entered ||
!atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire))
return;
XIRawEvent *raw = cookie->data; XIRawEvent *raw = cookie->data;
x11InputPointerButton(&x11.input, raw->detail, true, true, x11InputPointerButton(&x11.input, raw->detail, true, true);
raw->time, x11.input.focused && x11.input.entered);
return; return;
} }
case XI_RawButtonRelease: case XI_RawButtonRelease:
{ {
if (!x11.input.entered ||
!atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire))
return;
XIRawEvent *raw = cookie->data; XIRawEvent *raw = cookie->data;
x11InputPointerButton(&x11.input, raw->detail, false, true, x11InputPointerButton(&x11.input, raw->detail, false, true);
raw->time, x11.input.focused && x11.input.entered);
return; return;
} }
case XI_Motion: case XI_Motion:
{ {
XIDeviceEvent *device = cookie->data; XIDeviceEvent *device = cookie->data;
x11InputPointerMotion(&x11.input, device->event_x, device->event_y, x11InputPointerMotion(&x11.input, device->event_x, device->event_y);
atomic_load_explicit(&x11.pointerGrabbed,
memory_order_acquire));
return; return;
} }
case XI_RawMotion: case XI_RawMotion:
{ {
if (!x11.input.entered ||
!atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire))
return;
XIRawEvent *raw = cookie->data; XIRawEvent *raw = cookie->data;
double raw_axis[2] = { 0 }; double raw_axis[2] = { 0 };
double axis[2] = { 0 }; double axis[2] = { 0 };
@@ -1594,9 +1599,8 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
if (!has_axes) if (!has_axes)
return; return;
x11InputRelativeMotion(&x11.input, raw->time, axis[0], axis[1], x11InputRelativeMotion(&x11.input, axis[0], axis[1],
raw_axis[0], raw_axis[1], raw_axis[0], raw_axis[1]);
x11.input.focused && x11.input.entered);
return; return;
} }
} }

View File

@@ -168,7 +168,6 @@ struct Fixture
{ {
enum Backend backend; enum Backend backend;
struct TraceLog trace; struct TraceLog trace;
uint32_t time;
union union
{ {
@@ -278,7 +277,6 @@ static void initFixture(struct Fixture * fixture, enum Backend backend)
{ {
memset(fixture, 0, sizeof(*fixture)); memset(fixture, 0, sizeof(*fixture));
fixture->backend = backend; fixture->backend = backend;
fixture->time = 1;
if (backend == BACKEND_WAYLAND) if (backend == BACKEND_WAYLAND)
wlInputInit(&fixture->input.wayland, &sink, &fixture->trace); wlInputInit(&fixture->input.wayland, &sink, &fixture->trace);
@@ -455,14 +453,12 @@ static void pointerEnter(struct Fixture * fixture, bool mainSurface,
x11InputPointerEnter(&fixture->input.x11, mainSurface, true, x, y); x11InputPointerEnter(&fixture->input.x11, mainSurface, true, x, y);
} }
static void pointerLeave(struct Fixture * fixture, bool mainSurface, static void pointerLeave(struct Fixture * fixture, bool mainSurface)
double x, double y)
{ {
if (fixture->backend == BACKEND_WAYLAND) if (fixture->backend == BACKEND_WAYLAND)
wlInputPointerLeave(&fixture->input.wayland, mainSurface); wlInputPointerLeave(&fixture->input.wayland, mainSurface);
else else
x11InputPointerLeave(&fixture->input.x11, mainSurface, true, false, x11InputPointerLeave(&fixture->input.x11, mainSurface, true, false);
x, y);
} }
static void pointerMotion(struct Fixture * fixture, double x, double y) static void pointerMotion(struct Fixture * fixture, double x, double y)
@@ -470,7 +466,7 @@ static void pointerMotion(struct Fixture * fixture, double x, double y)
if (fixture->backend == BACKEND_WAYLAND) if (fixture->backend == BACKEND_WAYLAND)
wlInputPointerMotion(&fixture->input.wayland, x, y); wlInputPointerMotion(&fixture->input.wayland, x, y);
else else
x11InputPointerMotion(&fixture->input.x11, x, y, false); x11InputPointerMotion(&fixture->input.x11, x, y);
} }
static unsigned int nativeButton(enum Backend backend, static unsigned int nativeButton(enum Backend backend,
@@ -501,8 +497,7 @@ static void pointerButton(struct Fixture * fixture, unsigned int button,
if (fixture->backend == BACKEND_WAYLAND) if (fixture->backend == BACKEND_WAYLAND)
wlInputPointerButton(&fixture->input.wayland, native, pressed); wlInputPointerButton(&fixture->input.wayland, native, pressed);
else else
x11InputPointerButton(&fixture->input.x11, native, pressed, false, x11InputPointerButton(&fixture->input.x11, native, pressed, false);
fixture->time++, true);
} }
static void unmappedButton(struct Fixture * fixture) static void unmappedButton(struct Fixture * fixture)
@@ -510,8 +505,7 @@ static void unmappedButton(struct Fixture * fixture)
if (fixture->backend == BACKEND_WAYLAND) if (fixture->backend == BACKEND_WAYLAND)
wlInputPointerButton(&fixture->input.wayland, BTN_STYLUS, true); wlInputPointerButton(&fixture->input.wayland, BTN_STYLUS, true);
else else
x11InputPointerButton(&fixture->input.x11, 6, true, false, x11InputPointerButton(&fixture->input.x11, 6, true, false);
fixture->time++, true);
} }
static void wheelStep(struct Fixture * fixture, bool down) static void wheelStep(struct Fixture * fixture, bool down)
@@ -520,18 +514,17 @@ static void wheelStep(struct Fixture * fixture, bool down)
wlInputPointerAxis(&fixture->input.wayland, true, down ? 15.0 : -15.0); wlInputPointerAxis(&fixture->input.wayland, true, down ? 15.0 : -15.0);
else else
x11InputPointerButton(&fixture->input.x11, down ? 5 : 4, true, x11InputPointerButton(&fixture->input.x11, down ? 5 : 4, true,
false, fixture->time++, true); false);
} }
static void relativeMotion(struct Fixture * fixture, bool active, static void relativeMotion(struct Fixture * fixture, bool active,
uint32_t time, double x, double y, double rawX, double rawY) double x, double y, double rawX, double rawY)
{ {
if (fixture->backend == BACKEND_WAYLAND) if (fixture->backend == BACKEND_WAYLAND)
wlInputRelativeMotion(&fixture->input.wayland, active, wlInputRelativeMotion(&fixture->input.wayland, active,
x, y, rawX, rawY); x, y, rawX, rawY);
else else if (active)
x11InputRelativeMotion(&fixture->input.x11, time, x11InputRelativeMotion(&fixture->input.x11, x, y, rawX, rawY);
x, y, rawX, rawY, active);
} }
static void keyboardEnter(struct Fixture * fixture, bool mainSurface, static void keyboardEnter(struct Fixture * fixture, bool mainSurface,
@@ -594,8 +587,8 @@ static void testPointerEvents(enum Backend backend)
pointerEnter(&fixture, false, 1.0, 2.0); pointerEnter(&fixture, false, 1.0, 2.0);
pointerEnter(&fixture, true, 10.0, 20.0); pointerEnter(&fixture, true, 10.0, 20.0);
pointerMotion(&fixture, 11.5, 21.25); pointerMotion(&fixture, 11.5, 21.25);
pointerLeave(&fixture, false, 4.0, 5.0); pointerLeave(&fixture, false);
pointerLeave(&fixture, true, 12.0, 22.0); pointerLeave(&fixture, true);
static const struct Trace expected[] = static const struct Trace expected[] =
{ {
@@ -645,7 +638,7 @@ static void testPointerRelease(enum Backend backend)
/* Wayland's implicit pointer grab suppresses this native leave. */ /* Wayland's implicit pointer grab suppresses this native leave. */
if (backend == BACKEND_X11) if (backend == BACKEND_X11)
pointerLeave(&fixture, true, 100.0, 100.0); pointerLeave(&fixture, true);
pointerButton(&fixture, 1, false); pointerButton(&fixture, 1, false);
@@ -728,9 +721,9 @@ static void testRelativeMotion(enum Backend backend)
initFixture(&fixture, backend); initFixture(&fixture, backend);
activateInput(&fixture); activateInput(&fixture);
relativeMotion(&fixture, false, 50, 1.0, 2.0, 3.0, 4.0); relativeMotion(&fixture, false, 1.0, 2.0, 3.0, 4.0);
relativeMotion(&fixture, true, 51, 1.0, 2.0, 3.0, 4.0); relativeMotion(&fixture, true, 1.0, 2.0, 3.0, 4.0);
relativeMotion(&fixture, true, 51, 1.0, 2.0, 3.0, 4.0); relativeMotion(&fixture, true, 1.0, 2.0, 3.0, 4.0);
static const struct Trace expected[] = static const struct Trace expected[] =
{ {