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:
@@ -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 {
|
||||
|
||||
@@ -30,6 +30,36 @@ pub fn input_test() -> Result<()> {
|
||||
y,
|
||||
flags: 0,
|
||||
};
|
||||
// `PUNKTFUNK_INPUT_TEST_ABS=WxH` (e.g. 1280x800): exercise ABSOLUTE pointer moves instead —
|
||||
// steps through the corners + center of the given surface, 1s apart, so an observer
|
||||
// (`DISPLAY=:0 xdotool getmouselocation`) can verify each jump. This is the degraded-touch
|
||||
// path (touch → MouseMoveAbs), so it validates game-mode touch without a client.
|
||||
if let Ok(dims) = std::env::var("PUNKTFUNK_INPUT_TEST_ABS") {
|
||||
let (w, h) = dims
|
||||
.split_once('x')
|
||||
.and_then(|(w, h)| Some((w.parse::<u32>().ok()?, h.parse::<u32>().ok()?)))
|
||||
.unwrap_or((1280, 800));
|
||||
let flags = (w << 16) | (h & 0xffff);
|
||||
let pts = [
|
||||
(100, 100),
|
||||
(w as i32 - 100, 100),
|
||||
(w as i32 - 100, h as i32 - 100),
|
||||
(100, h as i32 - 100),
|
||||
(w as i32 / 2, h as i32 / 2),
|
||||
];
|
||||
tracing::info!(w, h, "input-test: ABS mode — corners + center, 1s apart");
|
||||
for (x, y) in pts {
|
||||
let mut e = ev(InputKind::MouseMoveAbs, 0, x, y);
|
||||
e.flags = flags;
|
||||
if let Err(err) = inj.inject(&e) {
|
||||
tracing::warn!(error = %format!("{err:#}"), "input-test: abs inject failed");
|
||||
}
|
||||
tracing::info!(x, y, "input-test: abs move emitted");
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
tracing::info!("input-test: done (abs)");
|
||||
return Ok(());
|
||||
}
|
||||
tracing::info!(
|
||||
"input-test: injecting a mouse square + 'A'/click taps for ~8s (watch wev / focused app)"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user