fix(host/linux): emit absolute pointer/touch on region-less EIS devices + abs input-test probe

gamescope's "Gamescope Virtual Input" advertises pointer_abs but no
region, so every MouseMoveAbs (and the degraded-touch moves built on
it) was silently dropped (emitted=false) — clicks then landed at a
stale cursor position. With no region, the managed session runs at the
client's mode, so client pixels are output pixels: emit them raw.
Also: log region count/dims at device-resume, and add
PUNKTFUNK_INPUT_TEST_ABS=WxH to `input-test` (corners + center, 1s
apart) so the degraded-touch path is verifiable with xdotool alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 10:36:05 +02:00
co-authored by Claude Fable 5
parent 1927077cbe
commit cc6b37fef0
2 changed files with 55 additions and 11 deletions
+25 -11
View File
@@ -514,6 +514,8 @@ impl EiState {
keyboard = dev.has_capability(DeviceCapability::Keyboard),
button = dev.has_capability(DeviceCapability::Button),
scroll = dev.has_capability(DeviceCapability::Scroll),
regions = dev.regions().len(),
region0 = ?dev.regions().first().map(|r| (r.x, r.y, r.width, r.height)),
"libei: device RESUMED (now emittable)"
);
}
@@ -690,16 +692,22 @@ impl EiState {
InputKind::MouseMoveAbs => {
let w = ((ev.flags >> 16) & 0xffff) as f32;
let h = (ev.flags & 0xffff) as f32;
match (
slot.interface::<ei::PointerAbsolute>(),
slot.regions().first(),
) {
(Some(p), Some(region)) if w > 0.0 && h > 0.0 => {
match slot.interface::<ei::PointerAbsolute>() {
Some(p) if w > 0.0 && h > 0.0 => {
// Map the normalized client position into the device's first region.
// A device may advertise pointer_abs with NO region (gamescope's
// "Gamescope Virtual Input") — there the managed session runs at the
// client's mode, so client pixels ARE output pixels: emit them raw
// rather than silently dropping every absolute move.
let nx = (ev.x as f32 / w).clamp(0.0, 1.0);
let ny = (ev.y as f32 / h).clamp(0.0, 1.0);
let x = region.x as f32 + nx * region.width as f32;
let y = region.y as f32 + ny * region.height as f32;
let (x, y) = match slot.regions().first() {
Some(region) => (
region.x as f32 + nx * region.width as f32,
region.y as f32 + ny * region.height as f32,
),
None => (ev.x as f32, ev.y as f32),
};
p.motion_absolute(x, y);
}
_ => emitted = false,
@@ -765,12 +773,18 @@ impl EiState {
InputKind::TouchDown | InputKind::TouchMove => {
let w = ((ev.flags >> 16) & 0xffff) as f32;
let h = (ev.flags & 0xffff) as f32;
match (slot.interface::<ei::Touchscreen>(), slot.regions().first()) {
(Some(t), Some(region)) if w > 0.0 && h > 0.0 => {
match slot.interface::<ei::Touchscreen>() {
Some(t) if w > 0.0 && h > 0.0 => {
let nx = (ev.x as f32 / w).clamp(0.0, 1.0);
let ny = (ev.y as f32 / h).clamp(0.0, 1.0);
let x = region.x as f32 + nx * region.width as f32;
let y = region.y as f32 + ny * region.height as f32;
// Same no-region fallback as MouseMoveAbs: raw client pixels.
let (x, y) = match slot.regions().first() {
Some(region) => (
region.x as f32 + nx * region.width as f32,
region.y as f32 + ny * region.height as f32,
),
None => (ev.x as f32, ev.y as f32),
};
if ev.kind == InputKind::TouchDown {
t.down(ev.code, x, y);
} else {