Skip to content

fix: menu-bar icon draws blank in the error states, and a missing grant reports as a missing pad - #9

Open
mmorrison wants to merge 2 commits into
schacon:mainfrom
mmorrison:fix/menu-bar-icon-and-permission-diagnosis
Open

mmorrison wants to merge 2 commits into
schacon:mainfrom
mmorrison:fix/menu-bar-icon-and-permission-diagnosis

Conversation

@mmorrison

@mmorrison mmorrison commented Sep 1, 2026 •

Copy link
Copy Markdown

Two independent bugs, both found while setting the app up on a Creator Micro 2. Each is one commit, and neither changes any behaviour that currently works.

1. The menu-bar icon draws blank in the error states

keyboard.badge.exclamationmark is not an SF Symbol. Not "not on older systems" - it is absent from name_availability.plist entirely, so NSImage(systemSymbolName:) returns nil for it everywhere, and the compose path in MenuBarIcon.image(for:) falls back to an empty NSImage. The menu bar ends up showing the status dot on its own with no keyboard behind it.

It only affects .permissionDenied and .deviceMissing. Every healthy state uses keyboard (macOS 10.15) or keyboard.fill (12.0), which both resolve, so it stays invisible in normal use and appears precisely when something has gone wrong and you are hunting for the icon to find out what.

What this does:

  • moves the symbol names into WLKit as IconSymbols, so a test can assert every name the icon draws actually resolves
  • uses keyboard.badge.ellipsis for the problem states: available since macOS 11.0, comfortably under the package's macOS 13 floor, and it keeps the keyboard shape so the icon stays findable in a crowded menu bar
  • falls back to the plain keyboard instead of a blank image, so an unresolvable name can never blank the icon again

2. A missing Input Monitoring grant reports as a missing pad

IOHIDDeviceOpen answers kIOReturnNotPermitted (0xE00002E2) both when the grant is missing and when the pad is already held or has wedged, so the error code alone cannot tell them apart. openDevice() only recognises the older 0xE00002C1, so on current systems a permissions failure falls through to the generic branch and the menu says "Looking for the pad".

That is an expensive wrong answer: it sends you checking cables and reseating hardware when the fix is a checkbox in System Settings. I lost a while to it before reading the source.

The 0xE00002C1 reading also looks like a leftover from before d2e7c86 ("open the HID device shared instead of seized"), where that code meant a refused seize rather than a refused grant.

What this does:

  • consults IOHIDCheckAccess, the only API that actually knows the state of the grant, instead of pattern-matching the error code
  • still believes an error that names a permission outright, in case the access check is stale
  • gives each case a message that names the next action rather than printing a bare error code

Testing

swift test passes locally: 89 tests, 4 skipped (the live-device ones, no pad attached to the test binary). The two new suites are 7 of those.

Both fixes were verified against the failing case first. The icon test fails on keyboard.badge.exclamationmark before the change, and the classifier test covers the case that misled me, which is the same error code with the grant already in place resolving to a device problem rather than a permission one.

On CI specifically: the icon test asserts symbol availability on whatever it runs on, so I checked the versions rather than assuming. Every symbol it now names is available on macOS 11.0 or earlier, so it holds on the macos-14 runner and on the package's macOS 13 minimum.

Happy to split these into two PRs if you would rather take them separately.

keyboard.badge.exclamationmark is not an SF Symbol. It is absent from
CoreGlyphs' name_availability.plist entirely, so NSImage(systemSymbolName:)
returns nil for it on every macOS, and the compose path falls back to an
empty NSImage - leaving the menu bar showing the status dot on its own
with no keyboard behind it.

It only affects .permissionDenied and .deviceMissing, which are the two
states where the icon most needs to be findable, so the app appears to
vanish from the menu bar at exactly the moment something is wrong. The
healthy states use keyboard (10.15) and keyboard.fill (12.0), which is
why this stays hidden in normal use.

Move the symbol names to WLKit so a test can assert they all resolve on
the running system, use keyboard.badge.ellipsis for the problem states
(macOS 11.0, under the package's 13.0 floor), and fall back to the plain
keyboard rather than to a blank image so an unresolvable name can never
blank the icon again.
@mmorrison
mmorrison force-pushed the fix/menu-bar-icon-and-permission-diagnosis branch from 7f2489e to 28c11b4 Compare September 1, 2026 14:33
IOHIDDeviceOpen answers kIOReturnNotPermitted (0xE00002E2) both when the
Input Monitoring grant is missing and when the pad is already held or has
wedged, so the error code cannot be what decides between them. The check
here only recognised the older 0xE00002C1, so on current systems a
permissions failure fell through to the generic branch and the menu said
"Looking for the pad" - which sends you hunting for a cable when the
answer is a checkbox.

The 0xE00002C1 reading is also a leftover from before d2e7c86 ("open the
HID device shared instead of seized"), where that code meant a refused
seize rather than a refused grant.

Consult IOHIDCheckAccess, which is the only caller that actually knows
the state of the grant, and keep believing an error that names a
permission outright in case the check is stale. Each case now names what
to do next rather than printing a bare error code.

This branch has not been deployed

No deployments
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