Skip to content

Add Direct Typing paste method for remote desktop support - #701

Open
marib00 wants to merge 2 commits into
Beingpax:mainfrom
marib00:feature/paste-method-remote-desktop
Open

marib00 wants to merge 2 commits into
Beingpax:mainfrom
marib00:feature/paste-method-remote-desktop

Conversation

@marib00

@marib00 marib00 commented May 14, 2026

Copy link
Copy Markdown

Summary

  • Adds Direct Typing (Remote Desktop) as a third paste method on top of upstream's existing Paste Method picker (Default / AppleScript)
  • Types transcribed text character-by-character via real layout key codes (+ Shift) instead of clipboard paste — required for remote desktop / VM sessions where clipboard sync fails
  • Incorporates the RDP scancode fix from Fix Direct Typing paste so it works in Remote Desktop sessions #758: remote clients ignore Unicode payloads and forward virtual key codes, so Unicode-injection-only typing previously arrived as repeated a on Windows
  • Includes focus-settle delay, auto-send-aware newlines (Shift+Return when Enter is Auto Send), Unicode fallback for unmapped characters, backup import/export, legacy cgEvent / useAppleScriptPaste migration, and localization

Test plan

  • Standard mode pastes correctly into a local app
  • AppleScript mode pastes correctly on non-QWERTY layouts
  • Direct Typing into Microsoft Remote Desktop types the correct characters (not all a)
  • Capitals / shifted punctuation work over RDP (real Shift key events)
  • Embedded newlines: bare Return by default; Shift+Return when Auto Send is Enter
  • Clipboard is not touched when Direct Typing is selected
  • Old backup with useAppleScriptPaste: true imports as AppleScript mode

Thanks to @kdubau for validating against real RDP sessions and the key-code fix in #758.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="VoiceInk/AppDefaults.swift">

<violation number="1" location="VoiceInk/AppDefaults.swift:14">
P1: Missing runtime migration for legacy `useAppleScriptPaste` preference. Existing users who previously enabled AppleScript paste will silently revert to CGEvent on upgrade because `registerDefaults()` only supplies values for missing keys and there is no startup migration equivalent to `BackupImporter.swift:191-195`.</violation>
</file>

<file name="VoiceInk/Services/BackupImporter.swift">

<violation number="1" location="VoiceInk/Services/BackupImporter.swift:194">
P2: Legacy `useAppleScriptPaste = false` backups are not migrated, which can leave stale `pasteMethod` values and fail to restore expected paste behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread VoiceInk/AppDefaults.swift Outdated
Comment thread VoiceInk/Services/BackupImporter.swift Outdated
@kdubau

kdubau commented Jun 7, 2026

Copy link
Copy Markdown

+1!

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="VoiceInk/AppDelegate.swift">

<violation number="1" location="VoiceInk/AppDelegate.swift:19">
P2: The comment says this re-assert is meant to happen 'after a beat' to avoid racing with scene setup, but `DispatchQueue.main.async` schedules for the very next run-loop iteration with no real delay — it won't help if the actual race takes longer than one tick. Consider `asyncAfter` with a small explicit delay, or better, hook into an actual completion signal for scene/window setup instead of a timing guess.</violation>
</file>

<file name="VoiceInk/MenuBarManager.swift">

<violation number="1" location="VoiceInk/MenuBarManager.swift:74">
P3: This permanently overrides the standard macOS gesture of cmd-dragging the menu bar icon off the bar to hide it — any time the window closes or the accessory policy flips, the icon will be forced back into the menu bar even if the user intentionally removed it, not because of the reported bug.</violation>

<violation number="2" location="VoiceInk/MenuBarManager.swift:101">
P2: The 2000-point threshold used to detect a 'corrupted' status item position is a heuristic guess; on wide/multi-monitor setups where the menu bar spans across displays, a legitimately saved preferred position could exceed 2000 and get silently reset every launch, causing the icon to jump position repeatedly for those users.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread VoiceInk/AppDelegate.swift Outdated
menuBarManager?.ensureMenuBarExtraVisible()
menuBarManager?.applyActivationPolicy()
// Policy changes can race with scene setup; re-assert after a beat.
DispatchQueue.main.async { [weak self] in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The comment says this re-assert is meant to happen 'after a beat' to avoid racing with scene setup, but DispatchQueue.main.async schedules for the very next run-loop iteration with no real delay — it won't help if the actual race takes longer than one tick. Consider asyncAfter with a small explicit delay, or better, hook into an actual completion signal for scene/window setup instead of a timing guess.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At VoiceInk/AppDelegate.swift, line 19:

<comment>The comment says this re-assert is meant to happen 'after a beat' to avoid racing with scene setup, but `DispatchQueue.main.async` schedules for the very next run-loop iteration with no real delay — it won't help if the actual race takes longer than one tick. Consider `asyncAfter` with a small explicit delay, or better, hook into an actual completion signal for scene/window setup instead of a timing guess.</comment>

<file context>
@@ -12,7 +12,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
+        menuBarManager?.ensureMenuBarExtraVisible()
         menuBarManager?.applyActivationPolicy()
+        // Policy changes can race with scene setup; re-assert after a beat.
+        DispatchQueue.main.async { [weak self] in
+            self?.menuBarManager?.ensureMenuBarExtraVisible()
+        }
</file context>

Comment thread VoiceInk/MenuBarManager.swift Outdated

// Positions for real menu-bar slots are typically low hundreds; values in the
// thousands usually mean the item was shoved into the overflow / off-screen.
if let position = defaults.object(forKey: positionKey) as? Double, position > 2000 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The 2000-point threshold used to detect a 'corrupted' status item position is a heuristic guess; on wide/multi-monitor setups where the menu bar spans across displays, a legitimately saved preferred position could exceed 2000 and get silently reset every launch, causing the icon to jump position repeatedly for those users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At VoiceInk/MenuBarManager.swift, line 101:

<comment>The 2000-point threshold used to detect a 'corrupted' status item position is a heuristic guess; on wide/multi-monitor setups where the menu bar spans across displays, a legitimately saved preferred position could exceed 2000 and get silently reset every launch, causing the icon to jump position repeatedly for those users.</comment>

<file context>
@@ -50,6 +61,48 @@ class MenuBarManager: ObservableObject {
+
+        // Positions for real menu-bar slots are typically low hundreds; values in the
+        // thousands usually mean the item was shoved into the overflow / off-screen.
+        if let position = defaults.object(forKey: positionKey) as? Double, position > 2000 {
+            defaults.removeObject(forKey: positionKey)
+        } else if let position = defaults.object(forKey: positionKey) as? Int, position > 2000 {
</file context>

Comment thread VoiceInk/MenuBarManager.swift Outdated
Self.repairMenuBarStatusItemDefaults()
if !self.isMenuBarExtraInserted {
self.logger.notice("🧭 Re-inserting MenuBarExtra into the system menu bar.")
self.isMenuBarExtraInserted = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This permanently overrides the standard macOS gesture of cmd-dragging the menu bar icon off the bar to hide it — any time the window closes or the accessory policy flips, the icon will be forced back into the menu bar even if the user intentionally removed it, not because of the reported bug.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At VoiceInk/MenuBarManager.swift, line 74:

<comment>This permanently overrides the standard macOS gesture of cmd-dragging the menu bar icon off the bar to hide it — any time the window closes or the accessory policy flips, the icon will be forced back into the menu bar even if the user intentionally removed it, not because of the reported bug.</comment>

<file context>
@@ -50,6 +61,48 @@ class MenuBarManager: ObservableObject {
+            Self.repairMenuBarStatusItemDefaults()
+            if !self.isMenuBarExtraInserted {
+                self.logger.notice("🧭 Re-inserting MenuBarExtra into the system menu bar.")
+                self.isMenuBarExtraInserted = true
+            }
+        }
</file context>

@marib00
marib00 force-pushed the feature/paste-method-remote-desktop branch from f8be65f to bc91716 Compare July 31, 2026 06:29
@marib00
marib00 force-pushed the feature/paste-method-remote-desktop branch from bc91716 to 80e2983 Compare August 17, 2026 22:03

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/reapply-direct-typing.md">

<violation number="1" location="docs/reapply-direct-typing.md:17">
P3: The grep alternatives are redundant: `directTyping` already matches any line containing `case directTyping`, so the `case directTyping` alternation adds no matches. The stop-condition text already relies on the plain `directTyping` token, so the second alternative is dead. Use the single token.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic


```bash
git fetch origin main
git grep -n 'directTyping\|case directTyping' origin/main -- '*.swift'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The grep alternatives are redundant: directTyping already matches any line containing case directTyping, so the case directTyping alternation adds no matches. The stop-condition text already relies on the plain directTyping token, so the second alternative is dead. Use the single token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/reapply-direct-typing.md, line 17:

<comment>The grep alternatives are redundant: `directTyping` already matches any line containing `case directTyping`, so the `case directTyping` alternation adds no matches. The stop-condition text already relies on the plain `directTyping` token, so the second alternative is dead. Use the single token.</comment>

<file context>
@@ -1,22 +1,38 @@
 ```bash
 git fetch origin main
-git grep -n 'directTyping\|Direct Typing' origin/main -- '*.swift'
+git grep -n 'directTyping\|case directTyping' origin/main -- '*.swift'
+git show origin/main:VoiceInk/Paste/PasteMethod.swift
+```
</file context>
Suggested change
git grep -n 'directTyping\|case directTyping' origin/main -- '*.swift'
git grep -n 'directTyping' origin/main -- '*.swift'

Comment thread docs/reapply-direct-typing.md Outdated
…eive real key codes after the source reorganization.
@marib00
marib00 force-pushed the feature/paste-method-remote-desktop branch from f76af31 to d07ff58 Compare September 14, 2026 08:23
Playbook now forbids git push origin/upstream and PRs against
Beingpax/VoiceInk. Fetch origin/main only to skip work if they
already merged the feature.
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.

2 participants