Skip to content

fix(ui): persist hardware-reported DPI so on-mouse changes survive restart#180

Open
hughesyadaddy wants to merge 1 commit into
TomBadash:masterfrom
hughesyadaddy:fix/ui-persist-hardware-reported-dpi
Open

fix(ui): persist hardware-reported DPI so on-mouse changes survive restart#180
hughesyadaddy wants to merge 1 commit into
TomBadash:masterfrom
hughesyadaddy:fix/ui-persist-hardware-reported-dpi

Conversation

@hughesyadaddy
Copy link
Copy Markdown
Contributor

@hughesyadaddy hughesyadaddy commented May 18, 2026

Summary

When the engine reads the device's current DPI on connect (or after a profile / connection event), the backend overwrites settings.dpi in-memory but never calls save_config. A DPI change made on the mouse hardware (the on-device cycle button, Logi Options+ briefly stealing the device, or a parallel Mouser session) is visible to the running UI but reverts on the next launch.

The unsaved mutation also leaves engine.cfg and the on-disk config out of sync, so any later setter (setDpi, cycleDpiPreset, setDpiPreset) writes the same stale value back through.

Fix

Route _handleDpiRead through the same persistence path the user-driven setters use:

  1. clamp_dpi against the resolved connected device so the floor/ceiling stay device-aware (previously the raw HID value was stored without validation).
  2. save_config and propagate into engine.cfg so subsequent _resolved_connected_device() reads observe the same value.
  3. Skip the write entirely when the device-reported value matches the current settings.dpi -- the handler used to emit settingsChanged on every poll, churning every QML binding that depends on the settings keypath.

Crucially, no HID round-trip is added: _handleDpiRead reacts to a value the device already reports, so echoing it back via engine.set_dpi would only generate a redundant HID++ call.

Test plan

New BackendHandleDpiReadTests in tests/test_backend.py (6 cases):

  • test_persists_new_device_dpi_to_disk -- new value flushed via save_config
  • test_clamps_overrange_dpi_to_device_max / test_clamps_underrange_dpi_to_device_min -- clamp covers both ends
  • test_no_change_skips_save -- redundant reads do not churn disk or signals
  • test_syncs_engine_cached_config -- engine.cfg mirrors the persisted value
  • test_emits_dpi_from_device_with_clamped_value -- the dpiFromDevice signal carries the clamped value
  • Full pytest tests/ -q -- 501 passed, 1 skipped, 170 subtests passed

Out of scope

  • Detecting which side initiated the DPI change (device vs Mouser) -- the current behavior already trusts the latest read.
  • Hardening clamp_dpi itself against malformed config (e.g. stringified values from hand-edited JSON) -- separate fix.

When the engine read the device's current DPI on connect, the backend
overwrote ``settings.dpi`` in-memory but never called ``save_config`` --
so a DPI change made on the mouse hardware (via the on-device cycle
button, Logi Options+ briefly stealing the device, or another Mouser
session) was visible to the running session but reverted on the next
launch. The unsaved mutation also left ``engine.cfg`` and disk out of
sync, so any later setter (``setDpi`` itself, ``cycleDpiPreset``) wrote
the same stale value back.

Three concrete changes:

* Route the device-reported value through ``clamp_dpi`` against the
  resolved connected device so the floor/ceiling stay device-aware.
  The previous handler stored the raw HID value with no validation.
* Persist via ``save_config`` and propagate the new config into
  ``engine.cfg`` so subsequent ``_resolved_connected_device()`` reads
  observe the same value.
* Skip the write entirely when the device-reported value matches the
  current ``settings.dpi`` -- the handler used to emit ``settingsChanged``
  on every poll, churning every QML binding that depends on the
  ``settings`` keypath.

No HID round-trip is added: this handler reacts to a value the device
already reports, so calling ``engine.set_dpi`` here would only echo it
back.

Tests
- ``test_persists_new_device_dpi_to_disk`` -- new value flushed via
  ``save_config``.
- ``test_clamps_overrange_dpi_to_device_max`` /
  ``test_clamps_underrange_dpi_to_device_min`` -- clamp covers both
  ends of the range.
- ``test_no_change_skips_save`` -- redundant reads do not churn disk
  or signals.
- ``test_syncs_engine_cached_config`` -- ``engine.cfg`` mirrors the
  persisted value.
- ``test_emits_dpi_from_device_with_clamped_value`` -- the
  ``dpiFromDevice`` signal carries the clamped value, not the raw one.
@hughesyadaddy hughesyadaddy force-pushed the fix/ui-persist-hardware-reported-dpi branch from 3507483 to 116e3ac Compare May 18, 2026 15:21
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.

1 participant