Fix: first launch never asked for Input Monitoring - #5
Merged
Conversation
A new user saw nothing at all on first launch. The launch path only called IOHIDCheckAccess, which queries permission and never prompts; the only call that can raise the system dialog, IOHIDRequestAccess, sat behind a button in the menu bar menu. With LSUIElement there is no Dock icon and no window, so unless the user thought to open the menu, macOS never asked and the app was silently inert - and an app that never requests access does not appear in Privacy & Security either, so it could not be granted manually. Adds a first-run window explaining what InputPilot does, why it needs Input Monitoring, and that it never reads typed text, with a button that triggers the system prompt. It also covers the state macOS leaves behind right after a grant, where permission reads as granted but HID stays blocked until relaunch, by offering to quit so the user is not left with an app that looks permitted but does nothing. Verified by launching the built app: the window server reports the window on screen at launch when permission is missing.
Every multi-line Text now refuses horizontal shrink, and the actions stack gets a width cap so it cannot take its widest child's single-line ideal width and let the window frame clip it. Without both, SwiftUI picked one text to truncate: first the caption, then the bullet once the caption was pinned.
There was a problem hiding this comment.
Pull request overview
This PR fixes a first-launch dead-end for the menu bar app by introducing a first-run Welcome window that explains Input Monitoring, provides a button to trigger the system prompt, and handles the post-grant “relaunch required” state.
Changes:
- Add a new
WelcomeViewonboarding window with permission + settings actions and a relaunch flow. - Add
AppStateflags (needsPermissionOnboarding,needsRelaunchAfterGrant) to drive onboarding UI state. - Add unit tests covering the onboarding/relaunch decision logic.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| InputPilotTests/AppStateFailurePathTests.swift | Adds unit tests validating onboarding/relaunch state decisions. |
| InputPilot/UI/WelcomeView.swift | Introduces the first-run Welcome window UI and actions. |
| InputPilot/App/InputPilotApp.swift | Adds the Welcome window scene and opens it from the menu bar label on first launch. |
| InputPilot/App/AppState.swift | Adds computed flags used to drive onboarding and relaunch messaging. |
| InputPilot.xcodeproj/project.pbxproj | Reorders an existing PBXBuildFile section (no functional change). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+196
to
+200
| /// macOS usually only lets a process read HID events after a relaunch that | ||
| /// follows the grant, so a granted-but-stopped monitor means "restart me". | ||
| var needsRelaunchAfterGrant: Bool { | ||
| isInputMonitoringGranted && !hidKeyboardMonitor.isRunning | ||
| } |
Comment on lines
+44
to
+54
| var body: some View { | ||
| Image(systemName: "keyboard") | ||
| .onAppear { | ||
| guard appState.needsPermissionOnboarding else { | ||
| return | ||
| } | ||
|
|
||
| openWindow(id: "welcome") | ||
| NSApp.activate(ignoringOtherApps: true) | ||
| } | ||
| } |
Comment on lines
+73
to
+76
| // Without a width cap the stack takes its widest child's ideal | ||
| // single-line width, and the window frame clips the caption | ||
| // instead of letting it wrap. | ||
| .frame(maxWidth: .infinity) |
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.
Reported after installing the 1.0.0 release: the app installs and launches fine, but no permission is ever requested.
Root cause
The launch path only ever checked permission.
AppState.init→refreshPermissionStatus()→IOHIDCheckAccess, which is a read-only query that by design never prompts. The only call that can raise the system dialog,IOHIDRequestAccess, sat behind the "Request Permission" button in the menu bar menu. The other implicit trigger — opening the HID manager — is guarded byupdateKeyboardMonitoringState(), which bails out when permission is missing, so it was never reached either.With
LSUIElement = YESthere is no Dock icon and no window, so a first-time user double-clicked the app and saw nothing at all. Worse, an app that never requests access does not appear under Privacy & Security → Input Monitoring, so it could not be granted manually either.Fix
A first-run window (
WelcomeView) that explains what InputPilot does, why it needs Input Monitoring, and that it never reads typed text, with a button that triggers the system prompt and a link to System Settings as a fallback.It also handles the state macOS leaves behind immediately after a grant, where permission reads as granted but HID access stays blocked until relaunch: the window then says so and offers to quit, instead of leaving the user with an app that looks permitted but does nothing.
Verification
420x442 "Welcome to InputPilot").