fix(host/linux): don't normalize absolute coords into gamescope's degenerate INT32_MAX EIS region

The previous commit's no-region fallback never fired: gamescope DOES
advertise a region — (0,0,INT32_MAX,INT32_MAX), meaning "coordinates
are raw". Mapping normalized positions into it explodes a center tap
to x≈1e9, clamped by gamescope to the far corner — the cursor pinned
at (1279,799) and every degraded-touch tap landing there. Treat a
region as an output geometry only when plausibly sized (≤16384 px);
otherwise emit raw client pixels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 10:40:02 +02:00
parent cc6b37fef0
commit 0c9461242c
+20 -8
View File
@@ -391,6 +391,15 @@ struct EiState {
degraded_touch: Option<u32>, degraded_touch: Option<u32>,
} }
/// Is this EIS region a plausible OUTPUT geometry — something to map normalized coordinates
/// into? gamescope advertises a degenerate `(0,0,INT32_MAX,INT32_MAX)` "everything" region on
/// its virtual input device, meaning "absolute coordinates are raw"; normalizing into it
/// explodes a center tap to x≈1e9, which the compositor clamps to the far corner. 16384
/// comfortably covers real multi-monitor layouts while rejecting the sentinel.
fn sane_region(r: &reis::event::Region) -> bool {
r.width > 0 && r.height > 0 && r.width <= 16_384 && r.height <= 16_384
}
/// Stable small index per [`InputKind`] for the `seen_kinds` bitmask. /// Stable small index per [`InputKind`] for the `seen_kinds` bitmask.
fn kind_bit(kind: InputKind) -> u32 { fn kind_bit(kind: InputKind) -> u32 {
let i = match kind { let i = match kind {
@@ -694,14 +703,17 @@ impl EiState {
let h = (ev.flags & 0xffff) as f32; let h = (ev.flags & 0xffff) as f32;
match slot.interface::<ei::PointerAbsolute>() { match slot.interface::<ei::PointerAbsolute>() {
Some(p) if w > 0.0 && h > 0.0 => { Some(p) if w > 0.0 && h > 0.0 => {
// Map the normalized client position into the device's first region. // Map the normalized client position into the device's first region
// A device may advertise pointer_abs with NO region (gamescope's // but only when the region looks like a real output geometry.
// "Gamescope Virtual Input") — there the managed session runs at the // gamescope's "Gamescope Virtual Input" advertises a degenerate
// client's mode, so client pixels ARE output pixels: emit them raw // (0,0,INT32_MAX,INT32_MAX) region meaning "coordinates are raw":
// rather than silently dropping every absolute move. // normalizing into it explodes a center tap to x≈1e9, which gamescope
// clamps to the far corner (the observed cursor-parked-at-1279,799).
// There the managed session runs at the client's mode, so client
// pixels ARE output pixels: emit them raw.
let nx = (ev.x as f32 / w).clamp(0.0, 1.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 ny = (ev.y as f32 / h).clamp(0.0, 1.0);
let (x, y) = match slot.regions().first() { let (x, y) = match slot.regions().first().filter(|r| sane_region(r)) {
Some(region) => ( Some(region) => (
region.x as f32 + nx * region.width as f32, region.x as f32 + nx * region.width as f32,
region.y as f32 + ny * region.height as f32, region.y as f32 + ny * region.height as f32,
@@ -777,8 +789,8 @@ impl EiState {
Some(t) if w > 0.0 && h > 0.0 => { Some(t) if w > 0.0 && h > 0.0 => {
let nx = (ev.x as f32 / w).clamp(0.0, 1.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 ny = (ev.y as f32 / h).clamp(0.0, 1.0);
// Same no-region fallback as MouseMoveAbs: raw client pixels. // Same degenerate-region fallback as MouseMoveAbs: raw client pixels.
let (x, y) = match slot.regions().first() { let (x, y) = match slot.regions().first().filter(|r| sane_region(r)) {
Some(region) => ( Some(region) => (
region.x as f32 + nx * region.width as f32, region.x as f32 + nx * region.width as f32,
region.y as f32 + ny * region.height as f32, region.y as f32 + ny * region.height as f32,