From 7d85983699e1d190bfa3df112306f46b9898ad5b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 12 Apr 2026 20:31:49 +0800 Subject: [PATCH] feat(macos): implement Unicode text input Implement the `platf::unicode()` function for macOS, which was previously a stub that only logged a "not yet implemented" message. The implementation converts incoming UTF-8 text to UTF-16 via CFString, then injects each character as a CGEvent keyboard event using `CGEventKeyboardSetUnicodeString()`. Surrogate pairs are handled correctly for characters outside the Basic Multilingual Plane. This enables Moonlight clients to send composed text (e.g., CJK characters from mobile IME) directly to the macOS host, matching the existing functionality on Windows (`SendInput` + `KEYEVENTF_UNICODE`) and Linux (`Ctrl+Shift+U` + hex code). --- src/platform/macos/input.cpp | 38 +++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index d3bf20057ba..173ec209089 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // platform includes #include @@ -295,7 +296,42 @@ const KeyCodeMap kKeyCodesMap[] = { } void unicode(input_t &input, char *utf8, int size) { - BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv; + auto macos_input = static_cast(input.get()); + + // Convert UTF-8 to UTF-16 via CFString + CFStringRef cfStr = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8 *) utf8, size, kCFStringEncodingUTF8, false); + if (!cfStr) { + BOOST_LOG(warning) << "unicode: Failed to convert UTF-8 input"sv; + return; + } + + CFIndex length = CFStringGetLength(cfStr); + std::vector utf16(length); + CFStringGetCharacters(cfStr, CFRangeMake(0, length), utf16.data()); + CFRelease(cfStr); + + // Inject each character (or surrogate pair) as a key down/up event + for (CFIndex i = 0; i < length;) { + UniCharCount charLen = 1; + if (i + 1 < length && + CFStringIsSurrogateHighCharacter(utf16[i]) && + CFStringIsSurrogateLowCharacter(utf16[i + 1])) { + charLen = 2; + } + + CGEventRef keyDown = CGEventCreateKeyboardEvent(macos_input->source, 0, true); + CGEventKeyboardSetUnicodeString(keyDown, charLen, &utf16[i]); + CGEventPost(kCGHIDEventTap, keyDown); + CFRelease(keyDown); + + CGEventRef keyUp = CGEventCreateKeyboardEvent(macos_input->source, 0, false); + CGEventKeyboardSetUnicodeString(keyUp, charLen, &utf16[i]); + CGEventPost(kCGHIDEventTap, keyUp); + CFRelease(keyUp); + + i += charLen; + } } int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {