Skip to content

fix: use clipboard paste for non-ASCII input (e.g. Chinese)#4

Merged
mcheemaa merged 2 commits intoghostwright:mainfrom
junshi5218:fix/chinese-input-paste
Mar 11, 2026
Merged

fix: use clipboard paste for non-ASCII input (e.g. Chinese)#4
mcheemaa merged 2 commits intoghostwright:mainfrom
junshi5218:fix/chinese-input-paste

Conversation

@junshi5218
Copy link

AXorcist synthetic typeText uses key codes and produces wrong output for Chinese/Unicode. For text with non-ASCII, use Cmd+V paste instead.

AXorcist synthetic typeText uses key codes and produces wrong output
for Chinese/Unicode. For text with non-ASCII, use Cmd+V paste instead.
@mcheemaa mcheemaa self-requested a review March 10, 2026 13:04
Copy link
Contributor

@mcheemaa mcheemaa left a comment

Choose a reason for hiding this comment

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

Hey @junshi5218, thank you for this contribution! This is a real problem that affects CJK users and we appreciate you taking the time to fix it.

We verified your fix against the AXorcist source. The underlying typeCharacter in AXorcist does actually use keyboardSetUnicodeString() to set Unicode characters on the CGEvent (not raw key codes), but the issue is that when a CJK Input Method Editor is active, macOS intercepts the event at the IME layer and reads the virtualKey (which is hardcoded to 0, i.e. 'a') instead of the Unicode string. That is why you get "aaaaa" for Chinese text. Your clipboard paste approach is the right fix for this.

Here is the relevant AXorcist source for reference:
https://github.com/steipete/AXorcist/blob/c75d06f7f93e264a9786edc2b78c04973061cb2f/Sources/AXorcist/Core/Element+UIAutomation.swift#L185-L211

One thing we spotted: there is a small bug in typeViaPaste with the clipboard save/restore ordering. Right now the code does:

pasteboard.clearContents()
let oldString = pasteboard.string(forType: .string)

This clears the pasteboard first and then tries to read the old content, so oldString will always be nil. Just swap those two lines:

let oldString = pasteboard.string(forType: .string)
pasteboard.clearContents()

One other small suggestion: since Cmd+V posts an async CGEvent, the target app might not have finished reading the clipboard by the time the defer block runs and restores it. Adding a small sleep before the restore would make this more robust:

defer {
    Thread.sleep(forTimeInterval: 0.05)
    pasteboard.clearContents()
    if let oldString { pasteboard.setString(oldString, forType: .string) }
}

Fix the clipboard ordering and this is good to merge. Thanks again for contributing to Ghost OS!

@junshi5218
Copy link
Author

Fixed. Thanks for pointing out the issue.

Copy link
Contributor

@mcheemaa mcheemaa left a comment

Choose a reason for hiding this comment

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

Both fixes look good. Thank you for the contribution!

@mcheemaa mcheemaa merged commit 877badf into ghostwright:main Mar 11, 2026
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