Skip to content

fix(core,agent): stop force-resetting native scroll inversion on reconnect - #1235

Open
4ni1ak wants to merge 3 commits into
AprilNEA:masterfrom
4ni1ak:fix/dpi-reset-on-reboot
Open

fix(core,agent): stop force-resetting native scroll inversion on reconnect#1235
4ni1ak wants to merge 3 commits into
AprilNEA:masterfrom
4ni1ak:fix/dpi-reset-on-reboot

Conversation

@4ni1ak

@4ni1ak 4ni1ak commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • DeviceConfig::invert_scroll is a bare bool (unlike dpi/smartshift,
    which are Option<T>), so a device-level false is indistinguishable
    from "never touched by the user".
  • configured_wheel_mode() in the orchestrator didn't account for that
    ambiguity: it resolved the reapply target's inverted value to
    Some(effective_invert_scroll(..)) for any device that merely exposes
    the native scroll-inversion capability, regardless of whether the user
    had ever opened OpenLogi's scroll-direction setting.
  • Because the agent's in-memory device list is empty on every process
    start — which happens on every host reboot — every online device is
    treated as newly-sighted at boot, so the reapply-on-reconnect path
    (reapply_volatile_settings / apply_native_wheel_modes) unconditionally
    wrote the native HID++ invert bit to false on any capable mouse on every
    reboot, silently overwriting whatever inversion state the device was
    actually in.

This matches what #1205 reports: settings the user never configured in
OpenLogi getting reset after every reboot.

The codebase already recognized this exact ambiguity in the legacy-config
migration fold (config/identity.rs, "invert_scroll is a bare bool, so
false is indistinguishable from never-set") but that care wasn't applied
to the reapply path.

Changes

  • crates/openlogi-core: add DeviceConfig::configured_invert_scroll(),
    returning None unless there is real evidence of user intent — a link
    override (Option<bool>, so it can carry a deliberate false) or a
    device-level true. Unit tests cover the never-touched, device-level-true,
    and link-override-wins-even-when-false cases.
  • crates/openlogi-agent-core: configured_wheel_mode() now calls
    configured_invert_scroll() instead of effective_invert_scroll(), so an
    untouched device's native inversion is left alone on reconnect/reboot —
    matching the "None means preserve the device's current value" contract
    the function's own doc comment already states, and that dpi/smartshift
    already honored. Added a regression test
    (configured_wheel_mode_leaves_unset_inversion_unmanaged) for the
    never-configured + capability-present case.

Testing

  • cargo test -p openlogi-core configured_invert_scroll — new unit tests, pass.
  • cargo test -p openlogi-agent-core configured_wheel_mode — existing +
    new regression test, pass.
  • cargo fmt --all -- --check — clean.
  • cargo clippy --all-targets -- -D warnings for the full affected package
    set (openlogi-core, openlogi-agent-core, openlogi-agent,
    openlogi-cli, openlogi-desktop, openlogi-device, openlogi-hid,
    openlogi-hook, openlogi-inject, openlogi-ipc, openlogi-overlay,
    openlogi-permissions, openlogi-ui, openlogi, xtask) — clean.
  • cargo test for that same affected package set — all pass.
  • Not runtime-tested on real hardware. This bug only manifests across an
    actual host reboot with a device whose native inversion state was set
    outside OpenLogi (or persisted from a prior host); that isn't something
    that can be verified from this environment. Reviewers with a mouse
    exposing HID++ 0x2121 invert bit (e.g. MX Master 3) can verify by: (1)
    setting the mouse's native scroll direction via another tool/host without
    ever touching OpenLogi's scroll-direction toggle, (2) confirming
    config.toml has no invert_scroll = true and no per-link
    invert_scroll override for that device, (3) rebooting with OpenLogi
    running, and (4) confirming the device's native inversion state is
    unchanged (pre-fix, it would reset to non-inverted).

Fixes #1205

@4ni1ak
4ni1ak requested a review from AprilNEA as a code owner September 1, 2026 22:18
@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown

Greptile Summary

The PR stops reconnect handling from treating an ambiguous device-level invert_scroll = false as an instruction to overwrite native device state.

  • Adds an intent-aware inversion accessor that preserves explicit per-link overrides.
  • Uses that accessor when resolving reconnect and configuration-reload wheel settings.
  • Adds regression coverage for untouched, enabled, and explicitly disabled inversion states.
  • Serializes macOS workspace-notification tests that share the process-global notification center.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/openlogi-core/src/config/device.rs Adds intent-aware inversion resolution while retaining the existing effective-value accessor.
crates/openlogi-agent-core/src/orchestrator.rs Uses configured inversion intent when determining which native wheel fields to reapply.
crates/openlogi-agent-core/src/orchestrator/tests.rs Adds regression coverage ensuring untouched inversion remains unmanaged.
crates/openlogi-core/src/config/tests.rs Covers untouched inversion, device-level true, and explicit per-link false precedence.
crates/openlogi-agent/src/tray.rs Serializes the two tests that post process-global macOS workspace notifications.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Device reconnect or config reload] --> B[Resolve current route and capabilities]
    B --> C{Explicit link override?}
    C -->|Yes| D[Reapply explicit true or false]
    C -->|No| E{Device-level inversion true?}
    E -->|Yes| F[Reapply inversion enabled]
    E -->|No| G[Leave native inversion unchanged]
    D --> H[Write supported wheel fields]
    F --> H
Loading

Reviews (4): Last reviewed commit: "fix(agent): serialize the macOS tray tes..." | Re-trigger Greptile

…nnect

DeviceConfig::invert_scroll is a bare bool (unlike dpi/smartshift's
Option<T>), so a device-level `false` is indistinguishable from
never-set. configured_wheel_mode() didn't account for this: it
resolved `inverted` to Some(effective_invert_scroll(..)) whenever a
device merely had the scroll_inversion capability, regardless of
whether the user had ever touched OpenLogi's invert-scroll setting.

Because every online device is treated as newly-sighted whenever the
agent process (re)starts - which happens on every host reboot - this
meant the reapply-on-reconnect path silently wrote the native HID++
invert bit to `false` to any capable mouse on every boot, even for
users who never opened OpenLogi's scroll-direction setting. That
overwrites whatever inversion state the device itself was actually
in, which is what issue AprilNEA#1205 reports as "settings overwritten after
every reboot".

Add DeviceConfig::configured_invert_scroll(), returning None unless
there is real evidence of user intent: a link override (Option<bool>,
so it can represent an explicit `false`) or a device-level `true`.
This mirrors the ambiguity already called out in the identity-fold
migration code, applied to the reapply path it was missing from.
configured_wheel_mode() now uses it, so an untouched device's native
inversion is left alone - matching the "None means preserve the
device's current value" contract dpi/smartshift already had.

Fixes AprilNEA#1205
@4ni1ak
4ni1ak force-pushed the fix/dpi-reset-on-reboot branch from 982f065 to ef59a97 Compare September 1, 2026 22:23
…roll

rustdoc rejects a public doc comment linking to a private item
(identity is a private module) under -D warnings — spell it out as
plain text instead of an intra-doc link.
…enter

startup_stays_suspended_when_the_display_is_already_asleep and
overlapping_suspend_sources_all_clear_before_device_io_resumes both
register an ActivityTarget observer on
NSWorkspace::sharedWorkspace().notificationCenter() — a process-global
singleton. A notification either test posts is delivered to every
live observer on that center, this test module's included, regardless
of which test posted it. Run in parallel (Rust's default), one test's
posts can flip the other's device-IO gate mid-assertion.

Same flaky-CI root cause fixed independently in AprilNEA#1236 — applying here
too so this PR's own CI isn't blocked by the unrelated race.
@davidbudnick davidbudnick added type: bug Something is broken or behaves incorrectly platform: all Cross-platform issue labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: all Cross-platform issue type: bug Something is broken or behaves incorrectly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenLogi overrides DPI settings after every reboot

2 participants