Skip to content

fix(device): re-apply profile when a transport re-attaches - #159

Open
m-wilkowski wants to merge 1 commit into
mmaher88:masterfrom
m-wilkowski:fix/reapply-profile-on-transport-reattach
Open

fix(device): re-apply profile when a transport re-attaches#159
m-wilkowski wants to merge 1 commit into
mmaher88:masterfrom
m-wilkowski:fix/reapply-profile-on-transport-reattach

Conversation

@m-wilkowski

Copy link
Copy Markdown

Split out of #133 as requested in review: this is the fix on its own, no UI or branding changes. The QML, Theme.qml, the icon/logo replacement and the tst_SideNav.qml margin expectation will follow in a second PR.

Fixes #132.

The two bugs

1. Nothing re-applies the profile when a transport re-attaches.

DeviceManager::probeDevice attaches the new transport to the existing PhysicalDevice and stops there. physicalDeviceAdded only fires for a serial the manager has not seen before, so it does not fire on re-attach. The transportSetupComplete fan-in does not cover it either, because DeviceSession::enumerateAndSetup() emits setupComplete() before attachTransport() has connected to it — the signal is emitted into a connection that does not exist yet.

New DeviceManager::physicalDeviceTransportReady signal covers that path, with AppRoot::onPhysicalDeviceTransportReady forwarding to the orchestrator.

AppRoot additionally subscribes to logind's PrepareForSleep. On resume it waits 2 s for HID++ to settle and re-applies every stored profile. That covers the case where the Bolt receiver sends no 0x41 reconnect notification after wake, which is the one I originally hit.

2. Use-after-free in DeviceSession::enumerateAndSetup.

The function builds a fresh FeatureDispatcher on every run. The old guard was if (!m_commandProcessor && ...), so on re-enumeration the existing CommandProcessor was kept while the dispatcher it points at got rebuilt underneath it. CommandProcessor holds the dispatcher by raw pointer, so from the second enumeration onward that pointer was dangling and the next queued command read through it.

The rebuild moved into DeviceSession::rebuildCommandProcessor(), which tears the old processor down (clear, stop, reset) before constructing one against the current dispatcher. Extracting it also makes it reachable from a test — enumerateAndSetup() itself bails early without hardware.

Tests

Five new tests, all hardware-free. Reverting either fix fails four of them; the fifth is the first-enumeration baseline that passes either way.

tests/test_device_reconnect.cpp — new AppRootFixture::emitTransportReady() helper raises the DeviceManager signal (probeDevice runs off a udev callback and needs a real /dev node, so the tests start one step downstream):

  • TransportReadyReappliesStoredProfile
  • TransportReadyReappliesActiveAppProfile — the per-app profile that was active before the drop is the one that comes back, not default

tests/test_device_session.cpp with a new tests/helpers/DeviceSessionFixture.h:

  • FirstEnumerationBuildsProcessorForCurrentDispatcher
  • ReEnumerationRepointsProcessorAtNewDispatcher — the regression guard for the dangling pointer
  • RebuildDropsCommandsQueuedAgainstTheOldDispatcher

CommandProcessor::features() is a new const accessor. Without it the test can only compare processor addresses, which proves less and risks the allocator handing back the same address.

Verification

758 / 759 tests pass

The one failure is HyprlandDesktopFocus.ActiveWindowEventEmitsFocusSignal, which also fails on a clean master at 6c1d66f. Unrelated to this branch.

Rebased on current master, so the AppRoot::wireSignals overlap with #148 is already resolved — it merged cleanly, no conflict.

Not smoke-tested on an MX Master 3S. I have an MX Master 4 here and can run it against that if you want hardware confirmation before merge.

Two separate problems left a device sitting on its firmware defaults after
sleep/wake or a receiver reconnect.

DeviceManager::probeDevice attached the new transport to the existing
PhysicalDevice without telling anyone. physicalDeviceAdded only fires for a
serial the manager has not seen before, and the transportSetupComplete
fan-in does not cover it either: DeviceSession::enumerateAndSetup emits
setupComplete() before attachTransport() has connected to it, so the signal
goes nowhere. Nothing re-applied the profile. A new
physicalDeviceTransportReady signal covers that path. AppRoot also listens
for logind's PrepareForSleep(false) and re-applies every stored profile on
resume, for the case where the receiver sends no reconnect notification at
all.

enumerateAndSetup builds a fresh FeatureDispatcher on every run but kept the
CommandProcessor from the previous one, which holds the dispatcher by raw
pointer. From the second enumeration onward that pointer was dangling and
the next queued command read through it. The rebuild now lives in
rebuildCommandProcessor(), which tears the old processor down before
building one against the current dispatcher.

Both changes are covered: reverting either one fails the new tests.

Fixes mmaher88#132
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.

Pointer speed not restored after laptop wakes from sleep

1 participant