mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-23 04:53:06 +08:00
[feat] add iOS system keyboard input and improve computer keyboard
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_CONTROLLER_H_
|
||||
@@ -48,6 +48,8 @@ class KeyboardCapturer : public DeviceController {
|
||||
// Permission-denied backends can wait for an OS grant notification rather
|
||||
// than repeatedly releasing held fallback keys to retry an impossible hook.
|
||||
virtual bool ShouldRetryHook() const { return true; }
|
||||
virtual bool SupportsTextInput() const { return false; }
|
||||
virtual int SendTextCommand(const std::string&) { return -1; }
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down,
|
||||
uint32_t scan_code = 0,
|
||||
bool extended = false) = 0;
|
||||
|
||||
@@ -733,6 +733,14 @@ void SessionDeviceManager::DrainCapturedKeyboardInput() {
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionDeviceManager::SupportsTextInput() const {
|
||||
return keyboard_capturer_ && keyboard_capturer_->SupportsTextInput();
|
||||
}
|
||||
|
||||
bool SessionDeviceManager::SendTextCommand(const std::string& text) {
|
||||
return keyboard_capturer_ && keyboard_capturer_->SendTextCommand(text) == 0;
|
||||
}
|
||||
|
||||
bool SessionDeviceManager::SendKeyboardCommand(int key_code, bool is_down,
|
||||
uint32_t scan_code,
|
||||
bool extended) {
|
||||
|
||||
@@ -73,6 +73,8 @@ public:
|
||||
void ClearAudio(const std::string& remote_id);
|
||||
void PushAudio(const std::string& remote_id, const char *data, size_t size);
|
||||
|
||||
bool SupportsTextInput() const;
|
||||
bool SendTextCommand(const std::string& text);
|
||||
bool SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code,
|
||||
bool extended);
|
||||
bool SendMouseCommand(const RemoteAction &action, int selected_display);
|
||||
|
||||
@@ -268,6 +268,15 @@ void KeyboardController::ApplyRemoteEvent(const std::string& remote_id,
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardController::ApplyRemoteText(const std::string& remote_id,
|
||||
const std::string& text) {
|
||||
std::lock_guard input_lock(remote_input_mutex_);
|
||||
if (!owner_.IsAuthorizedController(remote_id) ||
|
||||
!owner_.devices_.SupportsTextInput()) return;
|
||||
if (!owner_.devices_.SendTextCommand(text))
|
||||
LOG_WARN("Failed to inject committed remote text");
|
||||
}
|
||||
|
||||
void KeyboardController::ApplyRemoteState(const std::string& remote_id,
|
||||
const RemoteAction& action) {
|
||||
std::lock_guard input_lock(remote_input_mutex_);
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
void SendHeartbeat(bool force);
|
||||
void ApplyRemoteEvent(const std::string &remote_id,
|
||||
const RemoteAction &remote_action);
|
||||
void ApplyRemoteText(const std::string& remote_id, const std::string& text);
|
||||
void ApplyRemoteState(const std::string &remote_id,
|
||||
const RemoteAction &remote_action);
|
||||
void ReleaseRemotePressedKeys(const std::string &remote_id,
|
||||
|
||||
@@ -280,6 +280,13 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
runtime->QueuePrivacyCommand(remote_id, remote_action.pc);
|
||||
return;
|
||||
}
|
||||
if (remote_action.type == ControlType::text_input) {
|
||||
// Share the ordered keyboard stream with delete/return and shortcuts.
|
||||
if (source_id == runtime->keyboard_label_ &&
|
||||
runtime->IsAuthorizedController(remote_id))
|
||||
runtime->keyboard_.ApplyRemoteText(remote_id, remote_action.text);
|
||||
return;
|
||||
}
|
||||
const bool operates_host = remote_action.type == ControlType::mouse ||
|
||||
remote_action.type == ControlType::keyboard || remote_action.type == ControlType::keyboard_state ||
|
||||
remote_action.type == ControlType::service_command || remote_action.type == ControlType::display_id ||
|
||||
|
||||
@@ -295,6 +295,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
||||
CROSSDESK_VERSION, kClientPlatform);
|
||||
remote_action.i.supports_video_settings = true;
|
||||
remote_action.i.supports_text_input = runtime->devices_.SupportsTextInput();
|
||||
remote_action.i.supports_deskhub_preview = true;
|
||||
remote_action.i.supports_deskhub_resolution = true;
|
||||
remote_action.i.supports_deskhub_fps15 = true;
|
||||
@@ -457,6 +458,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
||||
CROSSDESK_VERSION, kClientPlatform);
|
||||
remote_action.i.supports_video_settings = true;
|
||||
remote_action.i.supports_text_input = runtime->devices_.SupportsTextInput();
|
||||
remote_action.i.supports_deskhub_preview = true;
|
||||
remote_action.i.supports_deskhub_resolution = true;
|
||||
remote_action.i.supports_deskhub_fps15 = true;
|
||||
|
||||
@@ -331,4 +331,48 @@ int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
||||
|
||||
return 0;
|
||||
}
|
||||
int PlatformKeyboardCapturer::SendTextCommand(const std::string& text) {
|
||||
if (text.empty() || text.size() > kMaxTextInputBytes) return -1;
|
||||
CFStringRef string = CFStringCreateWithBytes(
|
||||
kCFAllocatorDefault, reinterpret_cast<const UInt8*>(text.data()),
|
||||
text.size(), kCFStringEncodingUTF8, false);
|
||||
if (!string) return -1;
|
||||
const CFIndex length = CFStringGetLength(string);
|
||||
std::vector<UniChar> characters(length);
|
||||
CFStringGetCharacters(string, CFRangeMake(0, length), characters.data());
|
||||
CFRelease(string);
|
||||
for (CFIndex index = 0; index < length;) {
|
||||
const UniChar character = characters[index];
|
||||
if (character == '\n' || character == '\r' || character == '\t') {
|
||||
const int key = character == '\t' ? 0x09 : 0x0D;
|
||||
SendKeyboardCommand(key, true, 0, false);
|
||||
SendKeyboardCommand(key, false, 0, false);
|
||||
++index;
|
||||
if (character == '\r' && index < length && characters[index] == '\n') ++index;
|
||||
continue;
|
||||
}
|
||||
// Keep surrogate pairs together. Separate events let apps handle long
|
||||
// dictation results without CGEvent's small inline Unicode buffer limit.
|
||||
const CFIndex count = CFStringIsSurrogateHighCharacter(character) &&
|
||||
index + 1 < length ? 2 : 1;
|
||||
CGEventRef down = CGEventCreateKeyboardEvent(nullptr, 0, true);
|
||||
CGEventRef up = CGEventCreateKeyboardEvent(nullptr, 0, false);
|
||||
if (!down || !up) {
|
||||
if (down) CFRelease(down);
|
||||
if (up) CFRelease(up);
|
||||
return -1;
|
||||
}
|
||||
for (CGEventRef event : {down, up}) {
|
||||
CGEventSetFlags(event, 0);
|
||||
CGEventKeyboardSetUnicodeString(event, count, characters.data() + index);
|
||||
CGEventSetIntegerValueField(event, kCGEventSourceUserData,
|
||||
kCrossDeskInjectedEvent);
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
CFRelease(event);
|
||||
}
|
||||
index += count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CAPTURER_H_
|
||||
@@ -22,6 +22,8 @@ class PlatformKeyboardCapturer final : public KeyboardCapturer {
|
||||
public:
|
||||
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
|
||||
virtual int Unhook();
|
||||
bool SupportsTextInput() const override { return true; }
|
||||
int SendTextCommand(const std::string& text) override;
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down,
|
||||
uint32_t scan_code = 0,
|
||||
bool extended = false);
|
||||
|
||||
Reference in New Issue
Block a user