Skip to content

0.9.6

Choose a tag to compare

@github-actions github-actions released this 03 Aug 12:34
· 8 commits to dev since this release
09f171a

What's Changed

fix: global hotkeys never reached the app in game, and elevation was never the cause

RegisterHotKey delivers through the system hotkey table, and a foreground
application can take that table out of service for everyone (Raw Input's own
RIDEV_NOHOTKEYS does exactly that). The symptom was the worst kind: registration
succeeded, nothing reported an error anywhere, the keys worked on the desktop,
and nothing arrived once Star Citizen had focus.

It was first misdiagnosed as UIPI, and an elevation workaround was built for it.
Running as administrator does not fix it — verified in the field — so AppElevation
and the Settings "Restart as administrator" row are removed rather than kept.

Delivery is now a strategy behind IHotkeyBackend:

  • RawInputBackend (default) subscribes a keyboard usage page with RIDEV_INPUTSINK,
    so the system posts WM_INPUT straight to the sink whoever is in front.
  • RegisterHotkeyBackend (fallback) is the old path, kept because it is the only one
    that claims a combination exclusively.
  • OverlaySettings.HotkeyBackend forces one; Auto prefers Raw Input and falls back.
    The live backend is named in the log at startup.

Two consequences, both user-visible. Raw Input does not claim the combination, so
the game still receives the same keystroke — the Settings hint said the opposite
and is corrected in both languages. And the sink sees every keystroke on the
machine, which is why HotkeyLookup.IsTrigger runs first: unrelated input is dropped
in one set lookup, before anything is read, stored or logged.

The sink window is no longer message-only. WM_HOTKEY would reach one, but a Raw
Input sink registered against a message-only window never receives WM_INPUT.

Still no injection, no SetWindowsHookEx, no reading SC memory. Raw Input is passive:
it cannot swallow, alter or inject a keystroke, which is what separates it from a
hook.

Phase 4 is closed — in-game verification confirmed by the user, unelevated.
Tests: 378 green.

fix: sync scenarios waited on the flag instead of the cards they then asserted on

All three CatalogSyncScenarios released the held enrichment and then waited on
!IsSyncing before reading the rebuilt card list. Those are two different events.

ContractCatalogService raises SyncStateChanged(Idle) before CatalogUpdated, and
deliberately so — a CatalogUpdated subscriber must never observe the catalog as
still syncing. Between those two dispatcher hops the page is idle while still
holding the pre-enrichment cards, whose category is Unknown, so the category
filter matches none of them and Assert.Single sees an empty list.

Caught as one intermittent failure of the category-filter scenario during a full
run right after a build. Both waits now go through WaitForRebuildAsync, which
listens for CatalogUpdated: the view model subscribed to it first and rebuilds
inside a blocking Dispatcher.Invoke, so by the time the test's handler runs the
new cards are already in place.

Verified with five consecutive full Release runs, 378 green each.