diff --git a/crates/pf-inject/src/inject/linux/libei.rs b/crates/pf-inject/src/inject/linux/libei.rs index 5617f9aa..ab1e95a0 100644 --- a/crates/pf-inject/src/inject/linux/libei.rs +++ b/crates/pf-inject/src/inject/linux/libei.rs @@ -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::(), - slot.regions().first(), - ) { - (Some(p), Some(region)) if w > 0.0 && h > 0.0 => { + match slot.interface::() { + 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::(), slot.regions().first()) { - (Some(t), Some(region)) if w > 0.0 && h > 0.0 => { + match slot.interface::() { + 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 { diff --git a/crates/punktfunk-host/src/devtest.rs b/crates/punktfunk-host/src/devtest.rs index 3573b648..4595a3e0 100644 --- a/crates/punktfunk-host/src/devtest.rs +++ b/crates/punktfunk-host/src/devtest.rs @@ -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::().ok()?, h.parse::().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)" );