Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 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
1ae283b
fix: preserve cross-axis cursor position across machine transitions
jondkinney Apr 30, 2026
ff35685
fix(capture/layer_shell): report screen-space cursor position on Enter
jondkinney Apr 30, 2026
0d88e66
fix(capture): warp host cursor to guest position on release
jondkinney Apr 30, 2026
44e9a6d
style: apply cargo fmt
jondkinney Apr 30, 2026
39b6683
proto: add CursorPos event variant for bounds-free warps
jondkinney Apr 30, 2026
3af2e03
input-capture: add host_normalized_cursor helper
jondkinney Apr 30, 2026
58ae9ce
fix(capture): emit CursorPos alongside MotionAbsolute on Begin
jondkinney Apr 30, 2026
3dc11b4
fix(emulation): handle CursorPos to warp without prior Bounds exchange
jondkinney Apr 30, 2026
8c9f038
fix(capture): normalize cursor relative to display union origin
jondkinney Apr 30, 2026
7a97db3
ref(cursor-sync): drop MotionAbsolute, keep only CursorPos warps
jondkinney Apr 30, 2026
553f6a5
fix(capture): seed virtual_cursor retroactively when Bounds arrive late
jondkinney Apr 30, 2026
750f981
fix(capture): re-apply display_origin in host_warp_target_on_release
jondkinney Apr 30, 2026
d412267
debug: log CursorPos send/recv to diagnose entry-warp path
jondkinney May 1, 2026
b27c0a4
style: apply cargo fmt
jondkinney May 1, 2026
1a2d993
fix(emulation/wlroots): use compositor logical bounds via xdg_output
jondkinney May 1, 2026
12206d9
fix(capture): suppress host-warp on handover release
jondkinney May 1, 2026
38092ae
fix(capture): route peer-Leave release through handover path too
jondkinney May 1, 2026
94caa54
fix(emulation): drop entry-edge midpoint warp on Enter
jondkinney May 1, 2026
a03cb06
fix(capture): gate wall-press auto-release behind a peer-Leave deadline
jondkinney May 5, 2026
2b54f40
fix(capture): replace wall_press_pending_at Option<Instant> with bool
jondkinney May 6, 2026
3d8f86f
feat(capture): suppress crossings while host screen is locked (mac/win)
jondkinney May 5, 2026
78eca65
fix(capture/macos): poll CGSession for screen-lock state
jondkinney May 5, 2026
7451d1e
refactor(capture/macos): move screen-lock poll to cross-decision point
jondkinney May 5, 2026
81138e1
chore: cargo fmt for host-lock additions
jondkinney May 6, 2026
b7535a1
fix(proto): tolerate undecodable peer datagrams instead of disconnecting
jondkinney Apr 29, 2026
c397bee
feat: peer version exchange with soft-warn UI indicator
jondkinney May 4, 2026
ffee424
ui(client_row): sentence-case "Peer version" and "Ours" labels
jondkinney May 4, 2026
1ac1c78
fix(version-exchange): also store peer commit on the listen side
jondkinney May 5, 2026
9989579
chore: cargo fmt for peer-version code
jondkinney May 6, 2026
8453318
ui: wrap window content in GtkScrolledWindow
jondkinney Apr 29, 2026
96e5fe0
fix(dns): resolve hostnames via the OS resolver instead of pure DNS
jondkinney May 5, 2026
de7287f
fix(listen): bind one DTLS listener per local IPv4 address
jondkinney May 5, 2026
362d43b
fix(listen): periodic reconciliation drops stale per-IP listeners
jondkinney May 5, 2026
7513ccf
fix(listen): use Entry::Vacant API instead of allow-attribute
jondkinney May 6, 2026
5787812
fix(listen): drop redundant is_some() and add MissedTickBehavior::Skip
jondkinney May 6, 2026
8ee2bd2
feat(discovery): mDNS-SD primary-IP hints for service-order-aware dia…
jondkinney May 5, 2026
43db00c
fix(connect): exponential backoff for empty-candidate dials, mDNS-awa…
jondkinney May 6, 2026
c7ff97a
fix(discovery): cache primary IP by service instance, not SRV target
jondkinney May 6, 2026
caf2fc1
fix(discovery): normalize Bonjour names by stripping `.local`
jondkinney May 6, 2026
1f87587
chore: cargo fmt + clippy clean for CI (round 2)
jondkinney May 6, 2026
cfc11e6
fix(discovery,service): drop unused method, add MissedTickBehavior::Skip
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
698 changes: 494 additions & 204 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ lan-mouse-ipc = { path = "lan-mouse-ipc", version = "0.2.0" }
lan-mouse-proto = { path = "lan-mouse-proto", version = "0.2.0" }
shadow-rs = { version = "1.2.0", features = ["metadata"] }

hickory-resolver = "0.25.2"
toml = "0.8"
toml_edit = { version = "0.22", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
Expand Down Expand Up @@ -68,6 +67,11 @@ rustls = { version = "0.23.12", default-features = false, features = [
rcgen = "0.13.1"
sha2 = "0.10.8"
notify = "8.2.0"
if-addrs = "0.13"
if-watch = { version = "3.2", features = ["tokio"] }
mdns-sd = "0.19"
netdev = "0.43"
hostname = "0.4"

[target.'cfg(unix)'.dependencies]
libc = "0.2.148"
Expand Down
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