Skip to content

feat(settings): add Clipboard Only text insertion mode#484

Open
devzahirul wants to merge 5 commits into
altic-dev:mainfrom
devzahirul:feat/481-clipboard-only-insertion-mode
Open

feat(settings): add Clipboard Only text insertion mode#484
devzahirul wants to merge 5 commits into
altic-dev:mainfrom
devzahirul:feat/481-clipboard-only-insertion-mode

Conversation

@devzahirul

Copy link
Copy Markdown

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

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🧹 Chore
  • 📝 Documentation update

Related Issues

How It Works

A new clipboardOnly case is added to SettingsStore.TextInsertionMode. In TypingService.typeTextInstantly, an early-return guard detects this mode before any accessibility check or threading overhead fires — it calls ClipboardService.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

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS 15.5 Sequoia
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources0 violations
  • Ran formatter locally: swiftformat --config .swiftformat Sources

Notes

  • The mode skips the AXIsProcessTrusted() guard entirely — clipboard writes don't need accessibility permission.
  • The existing copyTranscriptionToClipboard setting 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).
  • No settings migration needed — textInsertionMode falls back to .standard for any unrecognised raw value, so existing installs are unaffected.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +282 to +285
if mode == .clipboardOnly {
self.bench("request_return reason=clipboard_only")
ClipboardService.copyToClipboard(text)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@altic-dev

Copy link
Copy Markdown
Owner

Hey!

I can merge it but want to make sure whats the name of this mode?
Clipboard only could mean use clipboard only for pasting.

We need a better UX for this imo. Whstd you think?

@devzahirul

Copy link
Copy Markdown
Author

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 0e94c18), which makes it explicit that the text is copied and nothing is auto-inserted. The clipboardOnly raw value is unchanged, so no settings migration is needed and existing installs are unaffected.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Sources/Fluid/ContentView.swift Outdated
Comment on lines +2292 to +2294
let shouldCopyToClipboard = shouldPersistOutputs &&
SettingsStore.shared.copyTranscriptionToClipboard &&
!isFluidFrontmost
!isFluidFrontmost &&
(SettingsStore.shared.copyTranscriptionToClipboard || isClipboardOnlyMode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +282 to +285
if mode == .clipboardOnly {
self.bench("request_return reason=clipboard_only")
ClipboardService.copyToClipboard(text)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +282 to +285
if mode == .clipboardOnly {
self.bench("request_return reason=clipboard_only")
ClipboardService.copyToClipboard(text)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +2702 to +2704
"method": isClipboardOnlyMode
? AnalyticsOutputMethod.clipboard.rawValue
: AnalyticsOutputMethod.typed.rawValue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
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.

[✨ FEATURE] Add an option of "None" for Text Insertion Mode

2 participants