fix(inject/windows): map absolute input over the streamed output's rect, not the whole desktop
audit / cargo-audit (push) Successful in 2m13s
audit / bun-audit (push) Successful in 15s
arch / build-publish (push) Successful in 12m46s
windows-host / package (push) Successful in 12m53s
android / android (push) Successful in 13m48s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m9s
apple / swift (push) Successful in 15m13s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m9s
ci / bench (push) Successful in 5m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m10s
ci / rust (push) Successful in 19m38s
decky / build-publish (push) Successful in 27s
deb / build-publish (push) Successful in 8m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 44s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 1m36s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 1m39s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Failing after 1m15s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 4m54s
docker / deploy-docs (push) Skipped
deb / build-publish-host (push) Successful in 10m7s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m26s
flatpak / build-publish (push) Successful in 6m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m42s
release / apple (push) Successful in 56m19s
apple / screenshots (push) Successful in 23m11s
audit / cargo-audit (push) Successful in 2m13s
audit / bun-audit (push) Successful in 15s
arch / build-publish (push) Successful in 12m46s
windows-host / package (push) Successful in 12m53s
android / android (push) Successful in 13m48s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m9s
apple / swift (push) Successful in 15m13s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m9s
ci / bench (push) Successful in 5m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m10s
ci / rust (push) Successful in 19m38s
decky / build-publish (push) Successful in 27s
deb / build-publish (push) Successful in 8m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 44s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 1m36s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 1m39s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Failing after 1m15s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 4m54s
docker / deploy-docs (push) Skipped
deb / build-publish-host (push) Successful in 10m7s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m26s
flatpak / build-publish (push) Successful in 6m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m42s
release / apple (push) Successful in 56m19s
apple / screenshots (push) Successful in 23m11s
Pen, touch, and absolute mouse arrive normalized to the STREAMED display's frame, but pointer_windows::to_screen and sendinput's MouseMoveAbs mapped them over the whole virtual desktop — correct only when the virtual display is the sole active display (Exclusive topology). In Extend — a physical monitor kept on beside the virtual output, or an Exclusive isolate degraded to the 0x57 keep-physicals fallback — the streamed output sits at a non-zero origin, so every sample landed shifted and mis-scaled. The pen exposed it first (field report 2026-07-24): a stylus is strictly absolute, with no closed-loop correction onto the target like a cursor. New pf-inject::stream_target (Windows): the host publishes the streamed output's CCD target id at capture bring-up (one central site — capture_virtual_output covers the native and GameStream planes); mapping sites resolve its current desktop rect through pf-win-display's source_desktop_rect — the same resolver the cursor-readback poller uses, so inject and readback always agree — TTL-cached (250 ms) because a group-layout re-arrange moves a live output's origin mid-session. No target set / never resolved falls back to the whole virtual desktop (the historical mapping, still right for Exclusive topology and devtest). One change in to_screen covers pen + touch; MouseMoveAbs converts the same desktop pixel into the 0..65535 MOUSEEVENTF_VIRTUALDESK coordinate, closing the identical latent absolute-mouse bug (apollo-comparison open item #14/#30). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
//! Windows input injection via `SendInput` (Win32 KeyboardAndMouse) — the Windows analogue of
|
||||
//! [`super::wlr`]: absolute mouse normalized to the virtual desktop, relative mouse for games,
|
||||
//! scancode keyboard, scroll, buttons. Survives UAC/lock desktop switches with Sunshine's
|
||||
//! [`super::wlr`]: absolute mouse mapped over the streamed output's rect
|
||||
//! ([`crate::stream_target`]), relative mouse for games, scancode keyboard, scroll, buttons. Survives UAC/lock desktop switches with Sunshine's
|
||||
//! retry-on-failure model: the thread stays bound to its desktop and only reattaches
|
||||
//! (`OpenInputDesktop`/`SetThreadDesktop`) when `SendInput` reports a short write (the input
|
||||
//! desktop switched) — no per-event reattach overhead.
|
||||
@@ -32,14 +32,10 @@ use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK,
|
||||
MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, VIRTUAL_KEY,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
GetForegroundWindow, GetSystemMetrics, GetWindowThreadProcessId, SM_CXVIRTUALSCREEN,
|
||||
SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowThreadProcessId};
|
||||
|
||||
use super::InputInjector;
|
||||
|
||||
const ABS_MAX: f64 = 65535.0; // SendInput absolute coords are 0..65535 over the chosen surface.
|
||||
const GENERIC_ALL: u32 = 0x1000_0000;
|
||||
const XBUTTON1: u32 = 0x0001;
|
||||
const XBUTTON2: u32 = 0x0002;
|
||||
@@ -165,13 +161,16 @@ impl InputInjector for SendInputInjector {
|
||||
if w == 0 || h == 0 {
|
||||
return Ok(()); // contract: drop zero extent
|
||||
}
|
||||
let (_vx, _vy, vw, vh) = virtual_desktop_rect();
|
||||
// One virtual output spanning the virtual desktop: map client (0..w,0..h) -> 0..65535.
|
||||
// Client (0..w,0..h) → the STREAMED output's desktop rect
|
||||
// ([`crate::stream_target`]; the whole virtual desktop only as fallback) →
|
||||
// 0..65535 over the virtual desktop for MOUSEEVENTF_VIRTUALDESK. Mapping over
|
||||
// the desktop alone is the Extend-topology offset bug the pen plane exposed
|
||||
// (design/pen-tablet-input.md): correct only when the streamed display IS the
|
||||
// whole desktop.
|
||||
let cx = (event.x.clamp(0, w as i32)) as f64 / w as f64;
|
||||
let cy = (event.y.clamp(0, h as i32)) as f64 / h as f64;
|
||||
let ax = (cx * ABS_MAX).round() as i32;
|
||||
let ay = (cy * ABS_MAX).round() as i32;
|
||||
let _ = (vw, vh); // virtual-desktop rect reserved for multi-output mapping
|
||||
let px = crate::stream_target::map_normalized(cx, cy);
|
||||
let (ax, ay) = crate::stream_target::desktop_px_to_virtualdesk(px);
|
||||
let mi = MOUSEINPUT {
|
||||
dx: ax,
|
||||
dy: ay,
|
||||
@@ -378,19 +377,6 @@ fn key(ki: KEYBDINPUT) -> INPUT {
|
||||
}
|
||||
}
|
||||
|
||||
fn virtual_desktop_rect() -> (i32, i32, i32, i32) {
|
||||
// SAFETY: each `GetSystemMetrics` takes a single by-value `SYSTEM_METRICS_INDEX` constant and
|
||||
// returns an `i32`; it dereferences no pointer and has no side effects — FFI-`unsafe` only.
|
||||
unsafe {
|
||||
(
|
||||
GetSystemMetrics(SM_XVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_YVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
||||
GetSystemMetrics(SM_CYVIRTUALSCREEN),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// VKs Windows wants flagged extended even when the scancode high bits aren't set: the editing
|
||||
// cluster (Ins/Del/Home/End/PgUp/PgDn = 0x21..0x28, 0x2D, 0x2E), the Win keys (0x5B/0x5C/0x5D),
|
||||
// RCtrl (0xA3), RAlt (0xA5), Pause (0x90). MAPVK_VK_TO_VSC_EX already encodes E0 for most; this is a
|
||||
|
||||
Reference in New Issue
Block a user