Repository navigation
feat: 1.1.0 — respect manual layout changes, open at login, a first run that ends - #14
Merged
Merged
Conversation
Move canonical-key matching, configured-source resolution and the active-device check into DeviceKeyResolver as pure functions that take every input as a parameter. No behaviour change; AppState drops from 1,115 to 1,037 lines before the 1.1.0 features land in it.
A keyboard seen at a new location was folded into the one known key of the same model, so two identical keyboards (say a US and a German Magic Keyboard) shared one Settings row and one mapping. Fold a keyboard into a known key only if that key's recorded location no longer holds a keyboard of the same model - that is the keyboard that moved. If every recorded location is still occupied, it is another keyboard and gets its own key. This also lets one of two same-model keyboards change ports without becoming a third entry. The attached devices are only queried on this slow path, never for a known keyboard.
Every key press re-applied the keyboard's mapping, so a layout picked by hand (Ctrl-Space, the menu bar, Undo, an input method's mode toggle) was reverted on the next key - a daily fight for multilingual users. InputPilot now remembers the source it last applied. If the current one differs when it is about to switch, someone else chose it, and a new setting decides what happens: - keep it until I switch keyboards (default) - keep it until I switch back myself - switch back on the next key press (the old behaviour) - turn off automatic switching, with the reason shown Details that make the detection reliable: - a switch that did not take effect (some CJK input methods) is retried, never mistaken for a manual choice; - moving to another keyboard re-arms from the current source, so a Ctrl-Space pressed during the 400 ms debounce is still respected; - only keys that may switch count as changing keyboards, so a lone Cmd on the other keyboard does not end a hold; - settings changes end any hold and apply at once, through one helper that uses the canonical device key instead of five raw-key copies.
A menu bar utility that is not a login item silently stops working after every restart. LoginItemController wraps SMAppService.mainApp (macOS 13+) behind a small protocol, reports enabled / disabled / requires-approval, and surfaces registration errors instead of swallowing them. The UI uses it in the welcome window and in Settings.
…ents Onboarding: - The welcome window only appeared while permission was missing. After the grant-and-relaunch macOS asks for, a new user got no window, no mappings and no hint. Onboarding now lasts until its last step is done (stored as onboardingCompleted) and returns whenever permission is missing or a relaunch is still needed. - relaunch() starts a fresh copy once this process has exited, for a Relaunch button instead of a bare Quit. The bundle path and PID are passed as arguments, never spliced into the shell script. - Settings booleans also read "YES"/"NO" strings, so UI tests can start onboarding with -onboardingCompleted NO. Karabiner-Elements: - Any keyboard held exclusively by another app made IOHIDManagerOpen fail and discarded the whole monitor, with advice to relaunch that could never help. On kIOReturnExclusiveAccess the monitor now keeps running for the other keyboards and explains how to release the held one. - Only when permission was granted at launch: the open error comes from an unordered set of devices, so right after a fresh grant it could hide the permission failure that a relaunch fixes.
Emoji & Symbols, Dictation, Press and Hold and the emoji Touch Bar item are enabled, selectable input sources of the palette category. They appeared in every picker; mapped to a keyboard, selecting one opened a panel and never became the current keyboard source, so it was selected again after every cooldown. Lists and the enabled check now keep only the keyboard category.
- Status reads "Input Monitoring: not granted yet" and "Keyboard detection: running" instead of "Permission: unknown" / "Monitor". - Device details read "USB · Vendor 1452 · Product 832"; the location only appears when it tells two keyboards of the same model apart. - Conflicts explain themselves instead of "missing/disabled". - useCurrentInputSourceForActiveDevice() maps the keyboard in use to the source selected right now, for a one-click action in the menu. - The view-facing strings move into AppState+Display.swift, so AppState shrinks to 1,150 lines instead of growing with the new UI.
Menu: what you are typing on and what it maps to comes first, with a one-click "Use <source> for <keyboard>"; then switching, pause and undo. Hardware IDs and the debug log move into a Diagnostics submenu, Quit goes last, and every window it opens is brought to the front - a menu bar app is never frontmost, so Settings could open unreachable behind the active app. Settings: keyboards first (with an empty state that explains the second keyboard), then switching with Open at login and the new manual-change policy, fallbacks, updates with a toggle for Sparkle's daily check, and status. The 365-day "Pause" toggle becomes a plain on/off switch. Welcome: a denied permission gets System Settings as the main button, the post-grant state gets a Relaunch button instead of Quit, and the last step offers Open at login, pre-checked, before Done. Accessibility: the menu bar icon is labelled, per-keyboard controls name their keyboard, decorative images are hidden from VoiceOver. The empty launch UI test is replaced by a regression test for 1.0.0's missing first-run window.
- README: the manual-change setting, Open at login, same-model keyboards, the new Settings layout, Relaunch, and Karabiner-Elements. - Site: three new FAQ entries (login, manual changes, Karabiner) in both the page and its structured data, the Relaunch answer, the version, and the re-measured size (about 3,900 lines of Swift). - SECURITY.md: the storage list includes the two new settings and says that Open at login lives with macOS, not InputPilot. - CHANGELOG: the 1.1.0 entry, folding in the unreleased fixes.
- A failed login-item registration left a stale error that kept the welcome window's Done from closing it, even after unchecking the box. - Changing the manual-change policy re-evaluated and switched away from the layout the user was holding; it now applies from the next key. - The hold note is hidden while automatic switching is off, and cleared when the keyboard disconnects. - relaunch() waits at most 10 s for the old process, then lets Launch Services settle, instead of waiting without bound.
- Same-model keyboards are told apart by port: say USB keyboards on different ports, and that swapping two identical keyboards swaps their mappings. - The updates footer no longer says "nothing else is sent"; GitHub does see an IP address and user agent. It says nothing about keyboards or typing is sent. - Note that existing users see the welcome window once after updating. - The input-source smoke test now fails on an empty list instead of passing vacuously, and a resolver comment states the real cost.
The app target uses SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, and every existing suite is @mainactor. Three new suites were not, so Swift Testing ran them on background threads, where they used main-actor-isolated conformances (KeyboardDeviceKey's Equatable) and the Text Input Sources API. Swift's runtime isolation checks then terminated the test host on CI - locally they passed only by scheduling luck. The compiler's warnings came from macro-expanded files and slipped past a too-narrow filter. Also build the default login-item service inside the main-actor init rather than as a default argument, which is evaluated outside it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The 1.1.0 product work from the post-launch audit: the gaps a new user hits in the first hour.
Added
SMAppService.mainApp, pre-checked at the end of the first run and as a Settings switch.Fixed
kIOReturnExclusiveAccessthe monitor keeps running — only when permission was granted at launch, so a fresh grant still asks for a relaunch.Changed
DeviceKeyResolver, view strings toAppState+Display.Test plan
defaults delete com.lucagerlich.InputPilot onboardingCompleted, pickers without Emoji/Dictation, and Karabiner-Elements with "Modify events" on for one keyboard only.