Skip to content

Fix hook, display and daemon reliability issues - #513

Open
MathieuARS wants to merge 9 commits into
mgth:masterfrom
MathieuARS:fix/audit-2026
Open

Fix hook, display and daemon reliability issues#513
MathieuARS wants to merge 9 commits into
mgth:masterfrom
MathieuARS:fix/audit-2026

Conversation

@MathieuARS

Copy link
Copy Markdown
Contributor

This is a larger fix pass over the problems that have accumulated around the mouse hook, games, display changes and daemon recovery.

Main changes:

  • make the Rust hook handle fast mixed-DPI crossings, injected movement, game freelook and exclusions more predictably
  • replace the old local transport with bounded per-user IPC and atomic recovery files
  • make display changes, suspend/resume and daemon restarts settle without stale layouts or duplicate hook processes
  • restore reliable Start and Stop behavior, including a safe fallback when IPC is unavailable
  • harden VCP, Smart TV, updater and process-lifecycle handling
  • publish the Windows UI as a least-privilege self-contained application and tighten the release workflow
  • repair the Linux solution filter and add cross-platform quality checks

Existing settings and recovery files remain compatible.

Tested locally:

  • cargo fmt --check
  • cargo test --all-targets (41 tests)
  • cargo clippy --all-targets --all-features -- -D warnings
  • Rust release build and cargo audit
  • full .NET Debug and Release builds
  • full .NET test suite in Debug and Release (157 tests each)
  • Linux solution-filter build
  • NuGet vulnerability scan
  • packaged Windows x64 cold start, daemon restart, display-change storms, Run/Stop state and cursor-clip release
  • manual multi-monitor crossing plus tray Start and Stop on a mixed-DPI Windows setup

The installer/signing jobs and native Linux runtime still need to run in CI.

Preserve physical edge crossings at high velocity, defer resume re-hooking until the UI reloads the topology, and bind local IPC inside its Tokio runtime with a readiness handshake. Add regression coverage for crossing, resume, and synchronous daemon startup.
Bound cold-start IPC attempts so the UI can launch a missing daemon, initialize state conservatively, serialize daemon creation, and provide a session-scoped Stop fallback. Restore functional display-event debouncing, safe resume reconciliation, and persistent tray Start behavior, with regression coverage for missing-daemon startup.
Remove the obsolete UI loader project from the Linux solution filter so it matches the main solution and builds successfully.
mgth added a commit that referenced this pull request Jul 25, 2026
…513)

Adopts the transport rework from #513 (commits 3fdf340/6641ae5), reviewed
fix by fix and rebased on the master elevation and exclusion models.

Why the TCP transport had to go: port 25196 was open to every local
process (any user could command the daemon or read FocusChanged paths),
a squatted port made the daemon panic at boot, the client dispatched
events out of order via Task.Run, swallowed send failures after 10
retries (a Stop could silently leave the hook capturing the mouse), and
matched events by substring inside payloads.

The replacement: a per-session named pipe with a user/SYSTEM DACL and
session check on Windows, a 0600 Unix socket in XDG_RUNTIME_DIR on
Linux; u32-length-prefixed UTF-8 frames capped at 1 MiB; bounded queues;
a serialized command worker preserving Load-then-Run order; four-client
cap. Same XML message contract - settings and Current.xml stay
compatible; DaemonPort remains as a dead property so existing settings
deserialize. Current.xml is now written atomically with a .bak the
daemon falls back to only when the primary is unreadable or fails to
parse - a valid stopped state no longer triggers recovery, and a Run
without a successful Load is ignored. Stop gets a safety net: if IPC is
down, known hook processes of this session (this UI's child on Unix)
are killed rather than left routing input.

On top of the #513 version, three of its bugs are fixed here:
- the Windows accept loop died permanently on a transient create_pipe
  error; it now logs, backs off 500 ms and retries
- the listener raised ConnectionFailed only once per connect cycle, so a
  daemon that crashed right after launch was never relaunched; it now
  re-raises every ~5 s (LaunchDaemon's already-running check makes that
  idempotent)
- UnauthorizedAccessException or a malformed frame killed the event
  listener silently; both now reconnect like I/O failures
And two of its regressions are not taken: the resume watchdog stays
ungated (the #513 gating reintroduced the stale-layout-on-wake bug), and
daemon launch keeps the #512 elevation model (no runas).

The Unix bind also no longer chmods a parent directory it did not
create (EPERM on shared parents like /tmp).

LBM_HOOK_ENDPOINT replaces LBM_HOOK_PORT for side-by-side testing. The
C++ LittleBigMouse.Hook daemon, which only speaks TCP, is retired from
distributable builds (decision from the #513 review).

Wire contract covered by tests on both sides, including full-duplex
under load (events streaming while commands flow) and listener
reconnection - the historical pipe failure modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: MathieuARS <mathieu.maik.15@gmail.com>
@mgth

mgth commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Thank you very much for the work done. It's very useful and most work will be integrated, but I cannot merge as is. So I'll cherry-pick. For future collaboration you may want to split work in more mergeable work. But thanks again, its very welcome and useful.

mgth pushed a commit that referenced this pull request Jul 25, 2026
Cherry-picked from #513 (2764856), with two adaptations to ShowFailureAsync:
OS-agnostic wording (the controller also targets Linux/kscreen), and Escape
kept working by moving IsCancel to the visible OK button since CancelButton
is hidden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mgth added a commit that referenced this pull request Jul 25, 2026
Two independent fixes extracted from #513 (commits 8d2f7cc/6d5ccd8),
without the injected-event resync mechanism they were entangled with:

- The C++-inherited filter (wparam & WM_MOUSEMOVE) matched every mouse
  message (they all carry bit 0x200): clicks and wheel events entered the
  engine as moves, and a click landing on a border mid-crossing could be
  swallowed by the LRESULT(1) return. Exact match only, with a unit test.

- exe_path_from_pid opened processes with PROCESS_QUERY_INFORMATION |
  PROCESS_VM_READ, which is denied on elevated or anticheat-protected
  processes — exactly the games exclusions target. LIMITED is enough for
  QueryFullProcessImageNameW; the ERROR_INSUFFICIENT_BUFFER loop covers
  paths beyond MAX_PATH.

- Examples gated cfg(linux) so cargo build --examples works on Windows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: MathieuARS <mathieu.maik.15@gmail.com>
mgth pushed a commit that referenced this pull request Jul 25, 2026
InvokeCommand on a ReactiveCommand drops values emitted while an
execution is in flight: during a display-change burst (resume, topology
change) the final wallpaper could be lost, and a bitmap replaced from an
older render was never disposed. LatestResourceSlot serializes this:
stale generations are rejected and disposed instead of replacing newer
state, and disposal invalidates in-flight renders. Rendering failures now
fall back to no wallpaper instead of an unobserved task exception.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mgth pushed a commit that referenced this pull request Jul 25, 2026
…lock (from #513)

The close confirmation showed OkCancel buttons but tested the result
against ButtonResult.Yes/No, which OkCancel can never return: the Close
button silently did nothing. It also blocked the UI thread on
ShowAsync().Result. Async Yes/No dialog with an honest message (the old
'Save your changes' text promised a save that has been commented out for
years), shutdown on Yes only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mgth added a commit that referenced this pull request Jul 25, 2026
…513)

Adopts the transport rework from #513 (commits 3fdf340/6641ae5), reviewed
fix by fix and rebased on the master elevation and exclusion models.

Why the TCP transport had to go: port 25196 was open to every local
process (any user could command the daemon or read FocusChanged paths),
a squatted port made the daemon panic at boot, the client dispatched
events out of order via Task.Run, swallowed send failures after 10
retries (a Stop could silently leave the hook capturing the mouse), and
matched events by substring inside payloads.

The replacement: a per-session named pipe with a user/SYSTEM DACL and
session check on Windows, a 0600 Unix socket in XDG_RUNTIME_DIR on
Linux; u32-length-prefixed UTF-8 frames capped at 1 MiB; bounded queues;
a serialized command worker preserving Load-then-Run order; four-client
cap. Same XML message contract - settings and Current.xml stay
compatible; DaemonPort remains as a dead property so existing settings
deserialize. Current.xml is now written atomically with a .bak the
daemon falls back to only when the primary is unreadable or fails to
parse - a valid stopped state no longer triggers recovery, and a Run
without a successful Load is ignored. Stop gets a safety net: if IPC is
down, known hook processes of this session (this UI's child on Unix)
are killed rather than left routing input.

On top of the #513 version, three of its bugs are fixed here:
- the Windows accept loop died permanently on a transient create_pipe
  error; it now logs, backs off 500 ms and retries
- the listener raised ConnectionFailed only once per connect cycle, so a
  daemon that crashed right after launch was never relaunched; it now
  re-raises every ~5 s (LaunchDaemon's already-running check makes that
  idempotent)
- UnauthorizedAccessException or a malformed frame killed the event
  listener silently; both now reconnect like I/O failures
And two of its regressions are not taken: the resume watchdog stays
ungated (the #513 gating reintroduced the stale-layout-on-wake bug), and
daemon launch keeps the #512 elevation model (no runas).

The Unix bind also no longer chmods a parent directory it did not
create (EPERM on shared parents like /tmp).

LBM_HOOK_ENDPOINT replaces LBM_HOOK_PORT for side-by-side testing. The
C++ LittleBigMouse.Hook daemon, which only speaks TCP, is retired from
distributable builds (decision from the #513 review).

Wire contract covered by tests on both sides, including full-duplex
under load (events streaming while commands flow) and listener
reconnection - the historical pipe failure modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: MathieuARS <mathieu.maik.15@gmail.com>
pull Bot pushed a commit to Mu-L/LittleBigMouse that referenced this pull request Aug 1, 2026
The bounded KEY_NAME_INFORMATION decoding introduced by the mgth#513 review
now lives in HLab.Core (WinReg.GetHKeyName/DecodeKeyNameInformation), so
every caller of RegistryKey(nint) gets the fix, not just GetEdid. Drop
the local duplicate in MonitorDeviceHelper and point the safety test at
the shared helper.

The shared version is also stricter on the sizing probe: it requires
STATUS_BUFFER_TOO_SMALL rather than merely a non-zero status before
allocating.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants