feat(settings): add Clipboard Only text insertion mode#484
Conversation
Adds a new `clipboardOnly` case to `TextInsertionMode`. When selected, dictated text is written to the system clipboard without any keyboard or accessibility-API insertion. This avoids the accessibility-permission requirement entirely. Intended for remote desktop workflows (e.g. Sunshine/Moonlight) where a synced clipboard carries the text to the remote machine, and for users who prefer to paste manually rather than have FluidVoice type directly. The mode appears in the Text Insertion Mode picker alongside the existing Clipboard Free Insert and Clipboard Paste options.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c78a700667
ℹ️ 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".
| if mode == .clipboardOnly { | ||
| self.bench("request_return reason=clipboard_only") | ||
| ClipboardService.copyToClipboard(text) | ||
| return |
There was a problem hiding this comment.
Mark clipboard-only output as clipboard, not typed
In clipboardOnly mode this returns after writing the pasteboard, but the normal dictation caller still sets didTypeExternally = true immediately after asr.typeTextToActiveField and emits outputDelivered / markTranscriptionCompleted with AnalyticsOutputMethod.typed (ContentView.swift:2329-2346). When the backup copy setting is off, every clipboard-only dictation is recorded as typed output even though no insertion happened; when it is on, the same delivery is recorded as both clipboard and typed. Please make this path report clipboard delivery, or return a delivery result before the caller marks it typed.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 75cd207.
is now included in (so it always triggers the clipboard write + .clipboard analytics) and excluded from shouldTypeExternally (so typeTextToActiveField is never called and .typed analytics never fires for this mode). The historyOnly fallback also switched from re-reading copyTranscriptionToClipboard == false to !shouldCopyToClipboard to avoid double-counting.
The TypingService early return is kept as defensive code for callers outside the main dictation path (e.g. history re-application).
In the main dictation output path, `shouldTypeExternally` was true for all modes when Fluid wasn't frontmost, so `clipboardOnly` still called `typeTextToActiveField` and set `didTypeExternally = true` — causing the `outputDelivered` event to fire with `AnalyticsOutputMethod.typed` instead of `.clipboard`, and `markTranscriptionCompleted` to record a typed delivery. Now `clipboardOnly` mode is included in `shouldCopyToClipboard` (so clipboard delivery and `.clipboard` analytics always fire for this mode) and excluded from `shouldTypeExternally` (so no insertion and no `.typed` analytics). The `historyOnly` fallback branch uses `!shouldCopyToClipboard` instead of re-reading `copyTranscriptionToClipboard == false` to avoid double-counting when either clipboard setting triggered delivery.
|
Hey! I can merge it but want to make sure whats the name of this mode? We need a better UX for this imo. Whstd you think? |
|
Good call on the naming — agreed that "Clipboard Only" is ambiguous (reads like "only use the clipboard to paste"). I have renamed the picker label to "Copy to Clipboard Only" (commit The description under the picker already reads: "Copies transcribed text to the clipboard without inserting it. Useful for remote desktop workflows or when you prefer to paste manually." Happy to iterate further if you have a preferred wording. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e94c18ba2
ℹ️ 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".
| let shouldCopyToClipboard = shouldPersistOutputs && | ||
| SettingsStore.shared.copyTranscriptionToClipboard && | ||
| !isFluidFrontmost | ||
| !isFluidFrontmost && | ||
| (SettingsStore.shared.copyTranscriptionToClipboard || isClipboardOnlyMode) |
There was a problem hiding this comment.
Do not suppress clipboard-only delivery when Fluid is frontmost
When dictation finishes while FluidVoice is the frontmost app, .clipboardOnly also makes shouldTypeExternally false, but this predicate suppresses the only clipboard write as well. That leaves the new "Copy to Clipboard Only" mode with no clipboard delivery in that scenario; the old !isFluidFrontmost guard made sense for the backup checkbox, but the primary clipboard-only method needs to bypass it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1d88b08. shouldCopyToClipboard no longer requires !isFluidFrontmost for clipboard-only mode — it now writes the pasteboard whenever shouldPersistOutputs is true, so the primary delivery happens even when FluidVoice is frontmost. The backup-copy checkbox keeps its !isFluidFrontmost guard unchanged.
| if mode == .clipboardOnly { | ||
| self.bench("request_return reason=clipboard_only") | ||
| ClipboardService.copyToClipboard(text) | ||
| return |
There was a problem hiding this comment.
Report clipboard-only results to remaining typing callers
The normal dictation path now special-cases .clipboardOnly, but this early return is still in the shared TypingService; ContentView.processRewriteWithVoiceInstruction() and RewriteModeService.acceptRewrite() still call typeTextToActiveField/typeTextInstantly and then emit AnalyticsOutputMethod.typed. In clipboard-only mode those paths only write the pasteboard here and never insert into the target app, so Edit/Rewrite delivery is recorded as typed even though the user only got a clipboard copy.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1d88b08. Both rewrite delivery callers now report the actual method: processRewriteWithVoiceInstruction and RewriteModeService.acceptRewrite() emit AnalyticsOutputMethod.clipboard instead of .typed when textInsertionMode == .clipboardOnly. The shared TypingService early return still routes the text to the pasteboard, so Edit/Rewrite delivery is now recorded as clipboard, matching what the user receives.
…l rewrite analytics - Clipboard-only is the primary delivery method, so it must write to the pasteboard even when FluidVoice is frontmost; the previous !isFluidFrontmost guard suppressed the only delivery in that case. - Voice-rewrite and accept-rewrite paths now report .clipboard (not .typed) analytics in clipboard-only mode, matching what actually happens.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d88b08faa
ℹ️ 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".
| if mode == .clipboardOnly { | ||
| self.bench("request_return reason=clipboard_only") | ||
| ClipboardService.copyToClipboard(text) | ||
| return |
There was a problem hiding this comment.
Deliver clipboard-only output before skipped typing paths
Because the clipboard write only happens after callers enter typeTextInstantly, any path that suppresses typing when FluidVoice is frontmost still never delivers clipboard-only output. For example, applyHistoryTextOutput only copies when the backup checkbox is enabled and then skips typeTextToActiveField whenever Fluid is frontmost, so using the Undo AI/history output action with Clipboard Only + backup off leaves the clipboard unchanged. Please have those callers treat .clipboardOnly as a primary clipboard delivery before relying on this shared branch.
Useful? React with 👍 / 👎.
| "method": isClipboardOnlyMode | ||
| ? AnalyticsOutputMethod.clipboard.rawValue | ||
| : AnalyticsOutputMethod.typed.rawValue, |
There was a problem hiding this comment.
Avoid double-counting clipboard-only rewrite delivery
When the backup “Copy to Clipboard” setting is also enabled, the voice rewrite path already copies the rewritten text and emits an output_delivered event with method: clipboard in the backup block above; in Clipboard Only mode, typeTextToActiveField copies again and this new conditional emits a second clipboard delivery event for the same rewrite. That overcounts rewrite delivery metrics in exactly the clipboard-only + backup-on configuration, unlike the normal dictation path which collapses these into a single clipboard event.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 075fbc5. The backup-copy block in the voice rewrite path is now skipped when textInsertionMode == .clipboardOnly, so the delivery emits exactly one .clipboard output_delivered event even with the backup setting on — matching how the dictation path collapses these into a single event.
…e event In clipboard-only mode the rewrite delivery already writes the pasteboard and emits one .clipboard event, so skip the backup-copy block to avoid emitting a second clipboard delivery event when the backup setting is also on. Matches the dictation path, which collapses these into a single event.
Description
Adds a Clipboard Only option to the Text Insertion Mode picker in Settings. When selected, dictated text is written directly to the system clipboard without any keyboard simulation or Accessibility API insertion.
Type of Change
Related Issues
How It Works
A new
clipboardOnlycase is added toSettingsStore.TextInsertionMode. InTypingService.typeTextInstantly, an early-return guard detects this mode before any accessibility check or threading overhead fires — it callsClipboardService.copyToClipboard(_:)and returns immediately.No changes to the Settings UI are required — the mode picker already uses
ForEach(TextInsertionMode.allCases)and picks up the new case automatically.Use Case
Remote desktop workflows (e.g. Sunshine/Moonlight) where a synced clipboard carries the text to the remote machine. Also useful for users who prefer to keep FluidVoice as a pure transcription-to-clipboard tool and paste manually.
Testing
swiftlint --strict --config .swiftlint.yml Sources— 0 violationsswiftformat --config .swiftformat SourcesNotes
AXIsProcessTrusted()guard entirely — clipboard writes don't need accessibility permission.copyTranscriptionToClipboardsetting remains independent. If both are active, the text is written to the clipboard twice (second write is a no-op since it's the same content).textInsertionModefalls back to.standardfor any unrecognised raw value, so existing installs are unaffected.