Fix login launch (#520), volume spike (#522), add spoken Enter keystr…#524
Fix login launch (#520), volume spike (#522), add spoken Enter keystr…#524TheJagStudio wants to merge 1 commit into
Conversation
…poken Enter keystroke (altic-dev#523) - Silent background launch at login: robust SMAppService login-launch detection, silent default, no window flash on reopen fallback (altic-dev#520) - Independent volume: compensate in-player when possible; ramp system volume smoothly only when a boost is needed (altic-dev#522) - Press Enter by Voice: opt-in spoken trigger phrase posts a real kVK_Return keystroke that also submits in terminals (altic-dev#523)
|
The PR Policy check is blocking this PR because required template information is missing. Please update the PR description with:
Visual files detected:
Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template. If this remains incomplete for 48 hours after opening, the PR may be closed. |
There was a problem hiding this comment.
Pull request overview
This PR bundles three user-facing improvements: silent/background behavior for login-item launches, a fix to avoid sudden system volume spikes during transcription cue playback, and an opt-in “Press Enter by Voice” feature that emits a native Return keystroke when a trigger phrase is spoken.
Changes:
- Adds spoken-phrase detection in
TypingServicethat splits dictated text into segments and injects native Return key events at the phrase location. - Reworks transcription cue volume handling to avoid touching system volume in the common case and to ramp volume smoothly when a boost is required.
- Improves login-launch detection for SMAppService-based login items and changes the default to not show the main window on login launches.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/Fluid/UI/SettingsView.swift | Adds Settings UI for “Press Enter by Voice” and updates the Independent Volume footnote. |
| Sources/Fluid/Services/TypingService.swift | Implements transcript splitting + native Return injection for the spoken trigger phrase. |
| Sources/Fluid/Services/TranscriptionSoundPlayer.swift | Adjusts cue playback to avoid system-volume spikes and adds smooth ramping for boosts. |
| Sources/Fluid/Persistence/SettingsStore.swift | Adds new settings (toggle + trigger phrase), includes them in backup round-trip, and flips the login-window default. |
| Sources/Fluid/Persistence/BackupService.swift | Extends backup payload schema with the new “Press Enter by Voice” fields. |
| Sources/Fluid/AppDelegate.swift | Adds SMAppService + uptime heuristic for login-launch detection and suppresses window flash on reopen fallback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for match in matches { | ||
| let before = nsText | ||
| .substring(with: NSRange(location: cursor, length: match.range.location - cursor)) | ||
| .trimmingCharacters(in: .whitespacesAndNewlines) | ||
| if !before.isEmpty { | ||
| segments.append(.text(before)) | ||
| } | ||
| segments.append(.pressEnter) | ||
| cursor = match.range.location + match.range.length | ||
| } | ||
| let tail = nsText.substring(from: cursor).trimmingCharacters(in: .whitespacesAndNewlines) | ||
| if !tail.isEmpty { | ||
| segments.append(.text(tail)) | ||
| } |
| DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.05) { [weak self] in | ||
| Self.setSystemVolume(saved) | ||
| self?.savedSystemVolume = nil | ||
| guard let self, let saved = self.savedSystemVolume else { return } | ||
| Self.rampSystemVolume(to: saved) | ||
| self.savedSystemVolume = nil | ||
| } |
| let start = self.getSystemVolume() | ||
| guard abs(start - target) > 0.001 else { return } | ||
|
|
||
| let stepDelayMicros = useconds_t((duration / Double(steps)) * 1_000_000) | ||
| DispatchQueue.global(qos: .userInteractive).async { |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0a8ba5865d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| .substring(with: NSRange(location: cursor, length: match.range.location - cursor)) | ||
| .trimmingCharacters(in: .whitespacesAndNewlines) |
There was a problem hiding this comment.
Preserve spacing added for continuous dictation
When the spoken Enter feature is used together with "Space Between Dictations", ASRService.applyContinuousDictationFormatting can intentionally prepend a space before this method sees the text. Trimming every text segment here removes that space whenever the trigger phrase is present, so dictating bar press enter after existing text foo inserts bar as foobar before sending Return instead of preserving the separator.
Useful? React with 👍 / 👎.
| Self.rampSystemVolume(to: saved) | ||
| self.savedSystemVolume = nil |
There was a problem hiding this comment.
Keep the saved volume until the restore ramp finishes
When Independent Volume needs a boost, rampSystemVolume restores asynchronously over several steps, but savedSystemVolume is cleared immediately. If another boosted cue starts during that restore ramp (for example repeated Settings previews), it records the intermediate system volume as the new baseline and later restores to that raised value instead of the user's original volume, leaving output volume stuck above where it started.
Useful? React with 👍 / 👎.
Description
Addresses three issues in one focused change set (6 files):
#523 – Native Enter keypress emulation via spoken phrase. New opt-in setting "Press Enter by Voice" with a configurable trigger phrase (default
press enter; falls back to the default if left empty, so the empty-replacement problem from the dictionary workaround in #380 can't occur). When the transcript contains the phrase,TypingServicesplits the output into segments and posts a realkVK_ReturnCGEvent keystroke (targeted at the focused app's PID, HID tap fallback) where the phrase was spoken, instead of pasting a\n. Because it is a genuine keystroke, zsh/bash with bracketed paste execute the command — this works in terminals as well as chat apps. Matching is case-insensitive, swallows punctuation the transcriber attaches around the phrase, handles a transcript consisting only of the phrase, and waits ~300 ms after paste-based insertion so the Return never fires before the text lands. The new settings round-trip through backup export/import.#522 – Independent volume selection causes system volume spike.
TranscriptionSoundPlayerpreviously always set the system volume to the cue's level and played at full player volume, spiking any other audio. Now, when the desired cue level fits under the current system volume (the common case), the system volume is never touched — loudness is compensated inside the player (playerVolume = desired / systemVolume). Only when the cue is louder than the system volume does it boost, and the boost now ramps in 8 small steps over ~120 ms up and back down instead of jumping. Overlapping sounds no longer clobber the saved pre-boost volume, so restores can't get stuck at a boosted level. Settings footnote updated accordingly.#520 – Persistent & invisible background launch on system startup. Login-launch detection relied on the legacy
keyAELaunchedAsLogInItemApple Event, which SMAppService login launches on modern macOS often don't carry — so every reboot looked like a manual launch and the main window appeared. Detection now falls back to a heuristic: the app is registered as a login item and the process started within 5 minutes of boot. The default for "Show window when launched at login" is flipped to OFF so login launches start silently in the menu bar (manual launches always show the window), and the LaunchServices reopen fallback now boots the window hidden immediately instead of flashing it until the retry loop catches it.Type of Change
Related Issue or Discussion
Closes #520
Closes #522
Closes #523
Testing
swiftlint --strict --config .swiftlint.yml Sourcesswiftformat --config .swiftformat SourcesScreenshots / Video
This PR does change Settings UI: two new rows ("Press Enter by Voice" toggle + "Trigger phrase" field) and an updated footnote on "Independent Volume".
Note:
SettingsStore.swiftis flagged by the policy check but contains only persistence changes (new UserDefaults keys, backup fields, a default-value flip) — no visual code. The visual changes are theSettingsView.swiftrows shown above.Notes
showMainWindowAtLoginLaunchdefault flip means existing installs that never touched the toggle will now start silently at login (per [BUG] Persistent & invisible Background Launch on System Startup #520's expected behavior); users can re-enable the window in App Settings.