From 50176b78eeaa46559aa63fcd53aa7186521e05bb Mon Sep 17 00:00:00 2001 From: Nic O'Connor Date: Sat, 10 Oct 2026 17:06:34 -0400 Subject: [PATCH] Sync omarchy-air's handoff work: pointer patch, firewall, case fix Everything from the air side of lan-mouse that was living only on that machine, plus corrections to what this repo already said. New: handoff/patch/ is a two-line source patch making the daemon forward the ACCELERATED pointer deltas. Upstream 0.11.0 forwards dx_unaccel and discards the accelerated pair in the same event, and the wlr receiver applies no curve either, so remote motion arrives with the sender's acceleration stripped and nothing replacing it -- and sensitivity / accel_profile are inert on both ends, so no config can fix it. The patch also adds LAN_MOUSE_POINTER_SCALE for the residual logical-ppi gap (0.7 for air's 140.1ppi against the LG's ~95.8). install.sh builds it per-machine; the unit now runs the result. This laptop is still unpatched, so its forwarded motion is still wrong until the handoff step runs here. New: install.sh --handoff opens 4242/udp for the peer in ufw. ufw is default-deny inbound and a dropped inbound packet presents IDENTICALLY to a bad certificate; this cost a day. Nothing in the repo covered it. Fixed: cmd_restart bypassed the systemd unit with pkill + uwsm-app, which races Restart=on-failure and can leave two daemons on 4242. It now prefers the unit, matching what cmd_log already assumed, and keeps the hand-start path only for when no unit exists. Documented, and this was the actual root cause of a day of dead crossings: fingerprints are CASE-SENSITIVE. lan-mouse generates them lowercase and compares with a plain map lookup, while openssl prints uppercase and cli authorize-key stores what it is handed. An uppercase digest is the right digest, valid TOML, non-empty, passes every guard, and never matches -- the handshake dies bad_certificate. Also documented: authorization is receiver-side only (connect.rs sets insecure_skip_verify), so each direction depends solely on the receiver's list and a sender cannot tell whether it is talking to the real peer. Corrected: README claimed the unknown-device* accel rule was needed because the receiver double-accelerates already-accelerated deltas. That is backwards and the rule is a measured no-op -- the device matches, but sensitivity and accel_profile are libinput settings and a wlr-virtual-pointer is not a libinput device. Measured on both machines: 398x198px travel at sensitivity 0 and identical at -0.9, exactly 1:1. Lines kept (harmless, and the naming note is worth having) with the real mechanism written down. ~/.config/lan-mouse/ stays out of the repo as before; handoff/config/ holds example configs carrying the backend, edge and lowercase rules instead. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LrgrMhPG8CTo9XZF326rjZ --- README.md | 61 ++++++++++++++++++---- bin/omarchy-handoff | 24 ++++++--- handoff/config/config.toml.example-air | 32 ++++++++++++ handoff/config/config.toml.example-laptop | 17 +++++++ handoff/patch/REBUILD.md | 59 +++++++++++++++++++++ handoff/patch/accelerated-deltas.patch | 48 ++++++++++++++++++ handoff/patch/build.sh | 33 ++++++++++++ install.sh | 62 ++++++++++++++++++++++- packages.txt | 3 ++ systemd/user/lan-mouse.service | 22 ++++++-- 10 files changed, 339 insertions(+), 22 deletions(-) create mode 100644 handoff/config/config.toml.example-air create mode 100644 handoff/config/config.toml.example-laptop create mode 100644 handoff/patch/REBUILD.md create mode 100644 handoff/patch/accelerated-deltas.patch create mode 100755 handoff/patch/build.sh diff --git a/README.md b/README.md index 933f835..993e134 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ into the repo so this stays the source of truth. | `plugins.txt` | The 10 Omarchy shell plugins, each pinned to a commit. | | `plugin-patches/` | Local plugin work that exists on no remote (see below). | | `bin/` | `omarchy-handoff` and `fix-stuck-displays`. | +| `handoff/` | The lan-mouse source patch that fixes pointer feel, plus example `config.toml`s. Not a `~/.config` mirror. | | `systemd/user/` | `lan-mouse.service`, `voxtype.service`. | | `system/` | `/etc` drop-ins: the ARP sysctl and `hid_apple` fnmode. | | `packages.txt` | Packages beyond the Omarchy baseline, each with a reason. | @@ -62,6 +63,33 @@ reversed form fails silently, and the log variable is `LAN_MOUSE_LOG_LEVEL`, not `o.launch_on_start` sends both streams to `/dev/null`, hiding every pairing failure. +Three further facts were only established after the above was first written, and +they are what actually made it work: + +- **Fingerprints are case-sensitive.** lan-mouse generates them lowercase and + compares with a plain map lookup — no normalisation — while `openssl` prints + uppercase and `cli authorize-key` stores whatever it is handed. An uppercase + digest is the *correct* digest, valid TOML, non-empty, passes every guard, and + never matches: the DTLS handshake dies `bad_certificate`, indistinguishable + from a wrong key or a dropped packet. This was the root cause of a full day of + dead crossings. `omarchy-handoff fixcase` repairs it; `doctor` now fails on it. +- **Authorization is receiver-side only, not mutual.** `connect.rs` sets + `insecure_skip_verify`, so the *connecting* side never checks the peer's + certificate — only the listener consults `authorized_fingerprints`. Each + direction therefore depends solely on the receiver's list, two-way control + needs both lists, and a sender cannot tell whether it is talking to the real + peer. Input goes to whatever answers on that IP:port. +- **`ufw` is default-deny inbound**, and a dropped inbound 4242 presents + *identically* to a bad certificate. `install.sh --handoff` opens it for the + peer's addresses. Check it first: `journalctl -k -g 'UFW BLOCK'`. + +`handoff/patch/` carries a two-line source patch so the daemon forwards the +accelerated deltas, which is the only way to make the sender's `sensitivity` and +`accel_profile` affect remote pointer feel at all; it also adds +`LAN_MOUSE_POINTER_SCALE` for the residual difference in logical pixel density +between the two screens. `install.sh` builds it per-machine — the two laptops are +different architectures — and `systemd/user/lan-mouse.service` runs the result. + `system/sysctl.d/99-omarchy-handoff-arp.conf` goes with it: Wi-Fi and the dock's ethernet both sit on `192.168.1.0/24`, and with the default ARP settings the subnet's mapping collapses onto whichever NIC replied last. @@ -76,13 +104,23 @@ stock ChatGPT webapp to Claude, and adds four Herdr HUD bindings plus Handoff. **Input** (`config/hypr/input.lua`) enables the three-finger horizontal workspace gesture that ships commented out, and flattens pointer acceleration for -`unknown-device*`. That second rule belongs to Handoff: lan-mouse forwards -relative deltas the *sending* compositor has already accelerated, and since -Omarchy leaves `accel_profile` adaptive the receiver amplifies them a second time, -so a forwarded pointer arrives hypersensitive while the local trackpad feels -normal. `wlr-virtual-pointer` gives Hyprland no device name — virtual keyboards -get named, pointers don't — hence matching on `unknown-device`; no real pointer on -this machine uses that name, so the rule cannot touch the trackpad. +`unknown-device*`. + +That second rule is **a no-op, and the reasoning originally given for it was +wrong.** It was added believing lan-mouse forwards deltas the sending compositor +had already accelerated, which the receiver then amplified a second time. Upstream +does the opposite: it forwards the *unaccelerated* deltas and discards the +accelerated pair in the same event (`handoff/patch/REBUILD.md`). And the rule +cannot act either way — `hl.device({name="unknown-device"})` does match the +virtual pointer (provable by toggling `scroll_factor`), but `sensitivity` and +`accel_profile` are libinput pointer-acceleration settings and a +`wlr-virtual-pointer` is not a libinput device, so they have nothing to act on. +Measured 2026-10-10 on both machines with `lan-mouse test-emulation` + `hyprctl +cursorpos`: travel was 398x198px at `sensitivity = 0` and identical at `-0.9`, +exactly 1:1 with the raw deltas. The lines are kept because they are harmless and +the `unknown-device` naming note is worth preserving; the real fix is +`handoff/patch/`. Do not add sensitivity or accel_profile rules here expecting +them to change remote pointer feel. **Terminals** are all dropped to a smaller font (9 -> 8) except ghostty, which went the other way to 12. `shell.toml` sets the bar font base size to 10. @@ -109,8 +147,13 @@ back into the repo. - **Secrets.** `~/.config/lan-mouse/` holds a private key and per-machine fingerprints; `~/.config/novnc/auth.env`, SSH keys and the git credential - helpers are likewise machine-local. Pair a new machine with - `omarchy-handoff peer ` and `omarchy-handoff doctor`. + helpers are likewise machine-local. Start from + `handoff/config/config.toml.example-{air,laptop}` (which carry the + `capture_backend`, edge and lowercase-fingerprint rules), then pair with + `omarchy-handoff pair ` and `omarchy-handoff doctor`. + A fresh install regenerates the certificate, so this machine's fingerprint + changes and the *peer* has to re-pair. Back up `lan-mouse.pem` beforehand if + you would rather keep it stable — somewhere private, not here. - **Shell, git and dev tooling.** That is already its own repo, `dotfiles` (`git.nic-oconnor.com/public/dotfiles`), which handles zsh, mise, 1Password and the `~/.local/bin` tool shims. The only overlap avoided here is diff --git a/bin/omarchy-handoff b/bin/omarchy-handoff index c936c02..11c946c 100755 --- a/bin/omarchy-handoff +++ b/bin/omarchy-handoff @@ -228,17 +228,25 @@ print(n) } cmd_restart() { - pkill -x lan-mouse 2>/dev/null || true - mkdir -p "$(dirname "$LOG")" - # Keep the log. Discarding it cost real debugging time once: the portal - # backend's silent no-op is invisible without these lines. - if command -v uwsm-app >/dev/null; then - uwsm-app -- lan-mouse daemon >>"$LOG" 2>&1 & + # Prefer the user unit, matching cmd_log's assumption. pkill + relaunch races + # the unit's Restart=on-failure and can leave TWO daemons contending for UDP + # 4242 and the socket, so only fall back to a hand-start when no unit exists. + if systemctl --user --quiet is-enabled lan-mouse.service 2>/dev/null \ + || systemctl --user --quiet is-active lan-mouse.service 2>/dev/null; then + systemctl --user restart lan-mouse.service else - setsid lan-mouse daemon >>"$LOG" 2>&1 & + pkill -x lan-mouse 2>/dev/null || true + mkdir -p "$(dirname "$LOG")" + # Keep the log. Discarding it cost real debugging time once: the portal + # backend's silent no-op is invisible without these lines. + if command -v uwsm-app >/dev/null; then + uwsm-app -- lan-mouse daemon >>"$LOG" 2>&1 & + else + setsid lan-mouse daemon >>"$LOG" 2>&1 & + fi fi sleep 2 - [[ -S $SOCKET ]] || die "daemon failed to start (see $LOG)" + [[ -S $SOCKET ]] || die "daemon failed to start (omarchy-handoff log)" notify "Handoff daemon restarted" } diff --git a/handoff/config/config.toml.example-air b/handoff/config/config.toml.example-air new file mode 100644 index 0000000..03645f0 --- /dev/null +++ b/handoff/config/config.toml.example-air @@ -0,0 +1,32 @@ +# omarchy-air side. Install to ~/.config/lan-mouse/config.toml +# +# NOTE: the daemon OWNS this file. It rewrites it on cli activate/deactivate and +# strips every comment, so treat this copy as the documented source and expect the +# live file to lose the comments on first use. `lan-mouse cli list` is the only +# trustworthy view of runtime state. + +# input-capture-portal is a SILENT no-op on Hyprland 0.56.2: it initializes and +# advertises on D-Bus, then creates ZERO barriers. Never leave this unset -- unset +# defaults to the portal. See README.md. +capture_backend = "layer-shell" +port = 4242 + +[[clients]] +hostname = "omarchy-laptop" +# Both of the peer's paths. .24 is Wi-Fi, .86 is the dock NIC. Keep BOTH: .86 +# disappears on undock. Expect a ~5s stall on .24 while docked -- the laptop's dock +# route wins (metric 100 vs 600) so its replies leave as .86 and we drop the +# unknown 4-tuple. Structural, not a fault. +ips = ["192.168.1.24", "192.168.1.86"] +# air sits UNDERNEATH the LG IPS236, so the peer is off our TOP edge. +position = "top" +# Ships DISARMED. Arm only after the PEER authorizes this machine's fingerprint +# (see README "Pairing"), then: omarchy-handoff on +activate_on_startup = false + +[authorized_fingerprints] +# Keyed BY FINGERPRINT, value is the description -- the reverse of the intuitive +# order. MUST be lowercase: lan-mouse generates lowercase and compares with a plain +# map lookup, so an uppercase digest is correct, valid, and never matches. +# This is the LAPTOP's cert digest; it stays valid unless the laptop is rebuilt. +"7c:ea:b6:0d:5d:12:6b:c8:b3:2f:9b:21:f2:20:41:fc:b5:8a:b0:67:06:d8:f9:89:0b:43:b0:ce:10:f9:b6:1e" = "omarchy-laptop" diff --git a/handoff/config/config.toml.example-laptop b/handoff/config/config.toml.example-laptop new file mode 100644 index 0000000..bc2e519 --- /dev/null +++ b/handoff/config/config.toml.example-laptop @@ -0,0 +1,17 @@ +# omarchy-laptop side. Mirror of config.toml.air. +# input-capture-portal is a SILENT no-op on Hyprland 0.56.2 -- see ../../README.md +capture_backend = "layer-shell" +port = 4242 + +[[clients]] +hostname = "omarchy-air" +ips = ["192.168.1.49"] +# air sits UNDERNEATH the LG IPS236, so from the laptop it is off the BOTTOM edge. +position = "bottom" +# Leave FALSE until this machine holds the peer's fingerprint (see README). +activate_on_startup = false + +[authorized_fingerprints] +# MUST be lowercase. Fill in with: +# omarchy-handoff pair omarchy-air +# Air's fingerprint CHANGES whenever air's cert is regenerated (e.g. a fresh install). diff --git a/handoff/patch/REBUILD.md b/handoff/patch/REBUILD.md new file mode 100644 index 0000000..178b401 --- /dev/null +++ b/handoff/patch/REBUILD.md @@ -0,0 +1,59 @@ +# Patched lan-mouse — forward accelerated pointer deltas + +## Why + +Upstream lan-mouse 0.11.0 (`input-capture/src/layer_shell.rs`) captures pointer motion from +`zwp_relative_pointer_v1` and forwards the **unaccelerated** deltas: + +```rust +dx_unaccel: dx, +dy_unaccel: dy, +.. +``` + +The `..` discards the accelerated `dx`/`dy` carried in the same event. The receiving end emulates +through `wlr-virtual-pointer`, which is not a libinput device and therefore applies no +acceleration curve of its own. Net effect: the sender's acceleration curve is stripped in transit +and nothing replaces it, so the pointer is far too fast on the remote machine. + +Critically, this also means `sensitivity` / `accel_profile` are inert for forwarded motion on +**both** ends — the receiver because a virtual pointer isn't a libinput device, the sender because +unaccelerated deltas bypass the curve by definition. There is no configuration fix; that is why +this patch exists. + +## What the patch does + +1. Forwards the accelerated `dx`/`dy` instead. This restores the sending machine's curve, and + makes the sender's `sensitivity`/`accel_profile` actually control how the remote pointer feels. +2. Adds `LAN_MOUSE_POINTER_SCALE` (float, default `1.0`) as a multiplier, for the residual + difference in logical pixel density between the two screens. Unset = upstream scaling. + +## Rebuild + +```sh +git clone https://github.com/feschber/lan-mouse.git +cd lan-mouse +git checkout 0d2190e787db9e482e17d4c16fb7c00ef4841bcb # == Arch extra 0.11.0-1 +git apply ~/.local/share/lan-mouse-patched/accelerated-deltas.patch +CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 \ + cargo build --release --locked --no-default-features \ + --features layer_shell_capture,wlroots_emulation +install -m755 target/release/lan-mouse ~/.local/bin/lan-mouse-patched +systemctl --user restart lan-mouse +``` + +Build deps: `rust` (installed from Arch `extra`). GTK is deliberately excluded — the daemon does +not need the GUI, and the signed package at `/usr/bin/lan-mouse` still provides it. + +## Install layout + +- Patched daemon: `~/.local/bin/lan-mouse-patched` (named so it cannot shadow `lan-mouse` on PATH) +- Unit: `~/.config/systemd/user/lan-mouse.service` -> `ExecStart` points at the patched binary +- The signed Arch package stays installed and untouched; no pacman divergence, no `IgnorePkg`. +- Cert/config path is unchanged (`~/.config/lan-mouse/`), so the DTLS fingerprint is unchanged and + the peer's `[authorized_fingerprints]` still matches. + +## Upstreaming + +Worth reporting to https://github.com/feschber/lan-mouse — the unaccel choice is defensible when +the receiver applies its own acceleration, but no Wayland emulation backend in lan-mouse does. diff --git a/handoff/patch/accelerated-deltas.patch b/handoff/patch/accelerated-deltas.patch new file mode 100644 index 0000000..9ff7532 --- /dev/null +++ b/handoff/patch/accelerated-deltas.patch @@ -0,0 +1,48 @@ +diff --git i/input-capture/src/layer_shell.rs w/input-capture/src/layer_shell.rs +index 698c584..7d81d37 100644 +--- i/input-capture/src/layer_shell.rs ++++ w/input-capture/src/layer_shell.rs +@@ -70,6 +70,21 @@ use super::{ + error::{LayerShellCaptureCreationError, WaylandBindError}, + }; + ++/// Multiplier applied to forwarded pointer motion, from `LAN_MOUSE_POINTER_SCALE`. ++/// ++/// Defaults to 1.0. Useful when the two machines differ in logical pixel density, so the ++/// same logical delta covers a different physical distance on each screen. ++fn pointer_scale() -> f64 { ++ static SCALE: std::sync::OnceLock = std::sync::OnceLock::new(); ++ *SCALE.get_or_init(|| { ++ env::var("LAN_MOUSE_POINTER_SCALE") ++ .ok() ++ .and_then(|v| v.parse::().ok()) ++ .filter(|v| v.is_finite() && *v > 0.0) ++ .unwrap_or(1.0) ++ }) ++} ++ + struct Globals { + compositor: wl_compositor::WlCompositor, + pointer_constraints: ZwpPointerConstraintsV1, +@@ -887,13 +902,19 @@ impl Dispatch for State { + if let zwp_relative_pointer_v1::Event::RelativeMotion { + utime_hi, + utime_lo, +- dx_unaccel: dx, +- dy_unaccel: dy, ++ dx, ++ dy, + .. + } = event + { + if let Some(window) = &app.focused { + let time = ((((utime_hi as u64) << 32) | utime_lo as u64) / 1000) as u32; ++ // Forward the ACCELERATED deltas. The unaccelerated ones bypass the sending ++ // compositor's pointer-acceleration curve, and the receiving end emulates via ++ // wlr-virtual-pointer, which is not a libinput device and so applies no curve ++ // of its own -- the acceleration would be lost entirely in transit. ++ let scale = pointer_scale(); ++ let (dx, dy) = (dx * scale, dy * scale); + app.pending_events.push_back(( + window.pos, + CaptureEvent::Input(Event::Pointer(PointerEvent::Motion { time, dx, dy })), diff --git a/handoff/patch/build.sh b/handoff/patch/build.sh new file mode 100755 index 0000000..fd32a21 --- /dev/null +++ b/handoff/patch/build.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Build the patched lan-mouse daemon into ~/.local/bin/lan-mouse-patched. +# Rationale and the upstream bug are in REBUILD.md. Called by ../install.sh. + +set -euo pipefail + +# Arch extra 0.11.0-1. Pinned because the patch is against these exact line numbers; +# a newer upstream may have fixed this, in which case check before repinning. +commit=0d2190e787db9e482e17d4c16fb7c00ef4841bcb +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) + +command -v cargo >/dev/null || { + echo "cargo not found -- install the 'rust' package first" >&2 + exit 1 +} + +# Build in a throwaway dir: the source tree is a build input, not something to keep. +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +git clone --quiet https://github.com/feschber/lan-mouse.git "$work/lan-mouse" +cd "$work/lan-mouse" +git checkout --quiet "$commit" +git apply "$here/accelerated-deltas.patch" + +# GTK deliberately excluded: the daemon needs no GUI, and the signed Arch package +# still provides it at /usr/bin/lan-mouse. LTO off keeps this at ~1 min. +CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 \ + cargo build --release --locked --no-default-features \ + --features layer_shell_capture,wlroots_emulation + +install -Dm755 target/release/lan-mouse ~/.local/bin/lan-mouse-patched +echo "installed ~/.local/bin/lan-mouse-patched" diff --git a/install.sh b/install.sh index 0b73dd0..dc09efe 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,13 @@ set -uo pipefail REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" STAMP="$(date +%s)" DRY=0 -STEPS=(packages config themes plugins bin services system) +STEPS=(packages config themes plugins bin handoff services system) + +# omarchy-handoff peer addresses to open the firewall for. The peer is dual-homed +# (Wi-Fi + dock NIC) and the dock address disappears on undock, so list both. +if [[ -z ${HANDOFF_PEER_IPS+x} ]]; then + HANDOFF_PEER_IPS=(192.168.1.24 192.168.1.86) +fi c_ok=$'\033[32m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_err=$'\033[31m'; c_hd=$'\033[1m'; c_off=$'\033[0m' [[ -t 1 ]] || { c_ok=; c_skip=; c_warn=; c_err=; c_hd=; c_off=; } @@ -264,6 +270,60 @@ step_services() { say " rather than now. 'systemctl --user start ' to bring one up early." } +# ---------------------------------------------------------------- handoff +step_handoff() { + head_ "omarchy-handoff (lan-mouse)" + + # 1. The patched daemon. systemd/user/lan-mouse.service points at this path, so + # it has to exist before the services step enables the unit. Per-architecture: + # this laptop is x86_64 and omarchy-air is aarch64, so each builds its own. + if [[ -x $HOME/.local/bin/lan-mouse-patched ]]; then + skip "~/.local/bin/lan-mouse-patched (delete it to force a rebuild)" + elif ! command -v cargo >/dev/null; then + warn "cargo missing - run the packages step first, or: omarchy pkg add rust" + elif (( DRY )); then + printf ' %s? would build ~/.local/bin/lan-mouse-patched from handoff/patch/%s\n' "$c_skip" "$c_off" + else + say " building patched daemon from handoff/patch/ (~1 min; see handoff/patch/REBUILD.md for why)" + if "$REPO/handoff/patch/build.sh"; then ok "~/.local/bin/lan-mouse-patched" + else err "build failed - the unit will not start until this succeeds"; fi + fi + + # 2. The firewall. THE bug that cost a full day: ufw is default-deny inbound, so + # the peer's DTLS handshake dies at the wire and presents EXACTLY like a bad + # certificate. Authorization is receiver-side only (connect.rs sets + # insecure_skip_verify), so the RECEIVER's firewall must allow 4242/udp or + # that direction is silently dead. + if ! command -v ufw >/dev/null; then + skip "ufw not installed - nothing to open" + else + local ip rules="" + # Reading the ruleset needs root, so don't probe it on a dry run. + (( DRY )) || rules=$(sudo ufw status 2>/dev/null || true) + for ip in "${HANDOFF_PEER_IPS[@]}"; do + if [[ $rules == *"4242"*"$ip"* ]]; then + skip "ufw allows 4242/udp from $ip" + elif (( DRY )); then + printf ' %s? would allow 4242/udp from %s%s\n' "$c_skip" "$ip" "$c_off" + else + sudo ufw allow from "$ip" to any port 4242 proto udp \ + comment "lan-mouse peer $ip" >/dev/null && ok "ufw: 4242/udp from $ip" + fi + done + say "" + say " Verify a blocked handshake with: journalctl -k -g 'UFW BLOCK'" + fi + + say "" + say " ~/.config/lan-mouse/ is NOT in this repo (private key + per-machine" + say " fingerprints). Start from handoff/config/config.toml.example-* and:" + say " omarchy-handoff fingerprint # give it to the peer, LOWERCASE" + say " omarchy-handoff pair " + say " omarchy-handoff doctor # must pass before arming" + say " omarchy-handoff on" + say " A fresh install regenerates the cert, so the PEER must re-pair." +} + # ---------------------------------------------------------------- system step_system() { head_ "System drop-ins (needs sudo)" diff --git a/packages.txt b/packages.txt index 5bb232f..d5ee060 100644 --- a/packages.txt +++ b/packages.txt @@ -14,6 +14,9 @@ # pipewire-debug) which is out of this repo's scope. [repo] +# --- omarchy-handoff: builds the patched lan-mouse daemon (handoff/patch/) --- +rust # only to compile handoff/patch/; ~250MiB installed, removable after + # --- hardware: this laptop is Intel + an Anker USB-C dock --- linux-omarchy # kernel this install actually boots linux-omarchy-headers # needed to build out-of-tree modules against it diff --git a/systemd/user/lan-mouse.service b/systemd/user/lan-mouse.service index 1492e48..2b06693 100644 --- a/systemd/user/lan-mouse.service +++ b/systemd/user/lan-mouse.service @@ -6,10 +6,24 @@ Requisite=graphical-session.target [Service] Type=simple -# LAN_MOUSE_LOG_LEVEL (NOT RUST_LOG -- lan-mouse ignores that) is what makes auth rejections visible at all; lan-mouse logs the -# offending fingerprint only at debug level. Drop to "info" once paired. -Environment=LAN_MOUSE_LOG_LEVEL=debug -ExecStart=/usr/bin/lan-mouse daemon +# LAN_MOUSE_LOG_LEVEL, NOT RUST_LOG -- lan-mouse silently ignores RUST_LOG. +# Note there is NO auth-rejection message at any level: rejection surfaces only +# as a frontend event, so debug gives connect attempts and DTLS alert text only. +# To see WHICH side rejected a handshake you need the wire, not the log. +Environment=LAN_MOUSE_LOG_LEVEL=info +# Patched daemon: upstream 0.11.0 forwards UNACCELERATED pointer deltas +# (input-capture/src/layer_shell.rs discards the accelerated dx/dy in the same +# event), and the wlr-virtual-pointer receiver applies no curve of its own, so +# the remote pointer is far too fast and sensitivity/accel_profile are inert on +# BOTH ends. Built by install.sh from handoff/patch/ -- see its REBUILD.md. +# Falls back to nothing: if the build is missing the unit fails loudly rather +# than silently running the unpatched binary with the wrong pointer feel. +ExecStart=%h/.local/bin/lan-mouse-patched daemon +# Multiplier for the residual logical-ppi difference between the two screens +# (default 1.0 = upstream). omarchy-air is 140.1 logical ppi at scale 1.6 vs +# ~95.8 for the 23" LG, so 0.7 makes motion cover the same PHYSICAL distance. +# Per-machine: adjust or drop this line on the other host. +Environment=LAN_MOUSE_POINTER_SCALE=0.7 Restart=on-failure RestartSec=2 # Output goes to the journal on purpose. The previous autostart.lua path sent