Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
719190c
feat: cross-platform wall-press auto-release fallback
jondkinney Apr 29, 2026
f9208ff
proto: add Bounds(width, height) event variant
jondkinney Apr 29, 2026
7e78def
feat(emulation): display_bounds + warp_cursor for all backends
jondkinney Apr 29, 2026
084c2a8
feat(emulation): send Bounds + warp cursor on Enter
jondkinney Apr 29, 2026
a9c17c9
feat(capture): cache peer bounds and use as wall-press upper clamp
jondkinney Apr 29, 2026
b84cca7
ui: rename Auto-Release group to scope it to outgoing capture
jondkinney Apr 30, 2026
9f5bec0
ui: ignore scroll-wheel on the release-threshold slider
jondkinney Apr 30, 2026
9eec092
ui: tighten Outgoing Auto-Release description and reframe as fallback
jondkinney May 5, 2026
98ba5a6
fix(ui): scroll passthrough on the release-threshold slider
jondkinney May 5, 2026
7ee7ee7
chore(capture/windows): rewrite message-loop as `while let` for clippy
jondkinney May 5, 2026
283a95c
chore: cargo fmt + import wayland_client::Proxy
jondkinney May 6, 2026
c68945b
ci: retrigger after Swatinem/rust-cache@v2 cache-save flake
jondkinney May 7, 2026
028ecb9
fix: preserve cross-axis cursor position across machine transitions
jondkinney Apr 30, 2026
fcc6e90
fix(capture/layer_shell): report screen-space cursor position on Enter
jondkinney Apr 30, 2026
f38f4f5
fix(capture): warp host cursor to guest position on release
jondkinney Apr 30, 2026
f84a62e
style: apply cargo fmt
jondkinney Apr 30, 2026
3d3c0bc
proto: add CursorPos event variant for bounds-free warps
jondkinney Apr 30, 2026
8eac6b3
input-capture: add host_normalized_cursor helper
jondkinney Apr 30, 2026
1db67fb
fix(capture): emit CursorPos alongside MotionAbsolute on Begin
jondkinney Apr 30, 2026
93e31f9
fix(emulation): handle CursorPos to warp without prior Bounds exchange
jondkinney Apr 30, 2026
304e278
fix(capture): normalize cursor relative to display union origin
jondkinney Apr 30, 2026
7bdac7b
ref(cursor-sync): drop MotionAbsolute, keep only CursorPos warps
jondkinney Apr 30, 2026
80309d1
fix(capture): seed virtual_cursor retroactively when Bounds arrive late
jondkinney Apr 30, 2026
40af6d5
fix(capture): re-apply display_origin in host_warp_target_on_release
jondkinney Apr 30, 2026
a6a89c0
debug: log CursorPos send/recv to diagnose entry-warp path
jondkinney May 1, 2026
b13f754
style: apply cargo fmt
jondkinney May 1, 2026
a4628c1
fix(emulation/wlroots): use compositor logical bounds via xdg_output
jondkinney May 1, 2026
2282afd
fix(capture): suppress host-warp on handover release
jondkinney May 1, 2026
651a791
fix(capture): route peer-Leave release through handover path too
jondkinney May 1, 2026
8b66785
fix(emulation): drop entry-edge midpoint warp on Enter
jondkinney May 1, 2026
35f83dc
fix(capture): gate wall-press auto-release behind a peer-Leave deadline
jondkinney May 5, 2026
4594e37
fix(capture): replace wall_press_pending_at Option<Instant> with bool
jondkinney May 6, 2026
a8e592e
feat(capture): suppress crossings while host screen is locked (mac/win)
jondkinney May 5, 2026
c401a91
fix(capture/macos): poll CGSession for screen-lock state
jondkinney May 5, 2026
c397a81
refactor(capture/macos): move screen-lock poll to cross-decision point
jondkinney May 5, 2026
854d6af
chore: cargo fmt for host-lock additions
jondkinney May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions input-capture/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ bitflags = "2.6.0"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.2", features = [
"Win32_System_LibraryLoader",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_Foundation",
"Win32_Graphics",
Expand Down
4 changes: 2 additions & 2 deletions input-capture/src/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Capture for DummyInputCapture {
Ok(())
}

async fn release(&mut self) -> Result<(), CaptureError> {
async fn release(&mut self, _warp_target: Option<(i32, i32)>) -> Result<(), CaptureError> {
Ok(())
}

Expand All @@ -62,7 +62,7 @@ impl Stream for DummyInputCapture {
let event = match self.start {
None => {
self.start.replace(current);
CaptureEvent::Begin
CaptureEvent::Begin { cursor: None }
}
Some(start) => {
let elapsed = start.elapsed();
Expand Down
84 changes: 67 additions & 17 deletions input-capture/src/layer_shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ struct Window {
surface: WlSurface,
layer_surface: ZwlrLayerSurfaceV1,
pos: Position,
/// Output's top-left corner in compositor coordinate space —
/// used together with `wl_pointer::Enter`'s surface-local coords
/// to recover the host screen-space cursor position at the moment
/// of crossing, so we can populate `CaptureEvent::Begin { cursor }`
/// for cross-axis preservation.
output_pos: (i32, i32),
output_size: (i32, i32),
}

impl Window {
Expand All @@ -157,6 +164,7 @@ impl Window {
qh: &QueueHandle<State>,
output: &WlOutput,
pos: Position,
output_pos: (i32, i32),
size: (i32, i32),
) -> Window {
log::debug!("creating window output: {output:?}, size: {size:?}");
Expand Down Expand Up @@ -208,6 +216,8 @@ impl Window {
buffer,
surface,
layer_surface,
output_pos,
output_size: size,
}
}
}
Expand All @@ -221,6 +231,22 @@ impl Drop for Window {
}
}

/// Translate `wl_pointer.enter` surface-local coords into the host's
/// compositor coordinate space, using the layer-surface's anchor edge
/// and the output it's attached to. Layer surfaces here are 1 px on
/// the on-axis dimension and span the cross-axis, so the surface-local
/// cross-axis coord is the screen offset directly.
fn surface_to_screen(window: &Window, surface_x: f64, surface_y: f64) -> (i32, i32) {
let (ox, oy) = window.output_pos;
let (ow, oh) = window.output_size;
match window.pos {
Position::Left => (ox, oy + surface_y as i32),
Position::Right => (ox + ow.saturating_sub(1), oy + surface_y as i32),
Position::Top => (ox + surface_x as i32, oy),
Position::Bottom => (ox + surface_x as i32, oy + oh.saturating_sub(1)),
}
}

fn get_edges(outputs: &[Output], pos: Position) -> Vec<(Output, i32)> {
outputs
.iter()
Expand Down Expand Up @@ -525,7 +551,8 @@ impl State {
);
outputs.iter().for_each(|o| {
if let Some(info) = o.info.as_ref() {
let window = Window::new(self, &self.qh, &o.wl_output, pos, info.size);
let window =
Window::new(self, &self.qh, &o.wl_output, pos, info.position, info.size);
let window = Arc::new(window);
self.active_windows.push(window);
}
Expand Down Expand Up @@ -628,7 +655,7 @@ impl Capture for LayerShellInputCapture {
Ok(inner.flush_events()?)
}

async fn release(&mut self) -> Result<(), CaptureError> {
async fn release(&mut self, _warp_target: Option<(i32, i32)>) -> Result<(), CaptureError> {
log::debug!("releasing pointer");
let inner = self.0.get_mut();
inner.state.ungrab();
Expand All @@ -638,6 +665,28 @@ impl Capture for LayerShellInputCapture {
async fn terminate(&mut self) -> Result<(), CaptureError> {
Ok(())
}

fn display_bounds(&self) -> Option<(u32, u32)> {
// Union of every active output's rectangle in compositor
// coords. Mirrors the macOS impl so MotionAbsolute scaling
// stays consistent: cursor coords reported in this same
// space normalize cleanly against the returned dimensions.
let outputs = &self.0.get_ref().state.outputs;
let mut xmin = i32::MAX;
let mut ymin = i32::MAX;
let mut xmax = i32::MIN;
let mut ymax = i32::MIN;
for info in outputs.iter().filter_map(|o| o.info.as_ref()) {
xmin = xmin.min(info.position.0);
ymin = ymin.min(info.position.1);
xmax = xmax.max(info.position.0 + info.size.0);
ymax = ymax.max(info.position.1 + info.size.1);
}
if xmax <= xmin || ymax <= ymin {
return None;
}
Some(((xmax - xmin) as u32, (ymax - ymin) as u32))
}
}

impl Stream for LayerShellInputCapture {
Expand Down Expand Up @@ -735,25 +784,26 @@ impl Dispatch<WlPointer, ()> for State {
wl_pointer::Event::Enter {
serial,
surface,
surface_x: _,
surface_y: _,
surface_x,
surface_y,
} => {
// get client corresponding to the focused surface
{
if let Some(window) = app.active_windows.iter().find(|w| w.surface == surface) {
app.focused = Some(window.clone());
app.grab(&surface, pointer, serial, qh);
} else {
return;
}
}
let pos = app
let Some(window) = app
.active_windows
.iter()
.find(|w| w.surface == surface)
.map(|w| w.pos)
.unwrap();
app.pending_events.push_back((pos, CaptureEvent::Begin));
.cloned()
else {
return;
};
app.focused = Some(window.clone());
app.grab(&surface, pointer, serial, qh);
let cursor = surface_to_screen(&window, surface_x, surface_y);
app.pending_events.push_back((
window.pos,
CaptureEvent::Begin {
cursor: Some(cursor),
},
));
}
wl_pointer::Event::Leave { .. } => {
/* There are rare cases, where when a window is opened in
Expand Down
Loading
Loading