From cf121bdf47550df9725b604e340e1fa4491cafd4 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 22:27:11 +0800 Subject: [PATCH 01/10] win, translate mode, not debug yet Signed-off-by: fufesou --- Cargo.lock | 26 ++++++++- Cargo.toml | 2 +- src/keyboard.rs | 105 ++++++++++++++++++++++++++++-------- src/server/input_service.rs | 17 +++++- src/ui_session_interface.rs | 21 ++++++++ 5 files changed, 144 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e15641363..4ac2720be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev", + "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,6 +4401,28 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdev" +version = "0.5.0-2" +dependencies = [ + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "enum-map", + "epoll", + "inotify", + "lazy_static", + "libc", + "log", + "mio 0.8.5", + "strum 0.24.1", + "strum_macros 0.24.3", + "widestring 1.0.2", + "winapi 0.3.9", + "x11 2.20.1", +] + [[package]] name = "rdev" version = "0.5.0-2" @@ -4709,7 +4731,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev", + "rdev 0.5.0-2", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 936b9e349..5d75b7a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { git = "https://github.com/fufesou/rdev" } +rdev = { path = "../rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/src/keyboard.rs b/src/keyboard.rs index 054a39580..bcb0650ac 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -92,7 +92,8 @@ pub mod client { if is_long_press(&event) { return; } - if let Some(key_event) = event_to_key_event(&event, lock_modes) { + + for key_event in event_to_key_events(&event, lock_modes) { send_key_event(&key_event); } } @@ -341,7 +342,7 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_event(event: &Event, lock_modes: Option) -> Option { +pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -357,28 +358,38 @@ pub fn event_to_key_event(event: &Event, lock_modes: Option) -> Option map_keyboard_mode(event, key_event)?, - KeyboardMode::Translate => translate_keyboard_mode(event, key_event)?, + let mut key_events = match keyboard_mode { + KeyboardMode::Map => match map_keyboard_mode(event, key_event) { + Some(event) => [event].to_vec(), + None => Vec::new(), + }, + KeyboardMode::Translate => translate_keyboard_mode(event, key_event), _ => { #[cfg(not(any(target_os = "android", target_os = "ios")))] { - legacy_keyboard_mode(event, key_event)? + legacy_keyboard_mode(event, key_event) } #[cfg(any(target_os = "android", target_os = "ios"))] { - None? + Vec::new() } } }; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(lock_modes) = lock_modes { - add_numlock_capslock_with_lock_modes(&mut key_event, lock_modes); - } else { - add_numlock_capslock_status(&mut key_event); + + if keyboard_mode != KeyboardMode::Translate { + for key_event in &mut key_events { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if let Some(lock_modes) = lock_modes { + add_numlock_capslock_with_lock_modes(key_event, lock_modes); + } else { + add_numlock_capslock_status(key_event); + } + } } - return Some(key_event); + println!("REMOVE ME ========================= key_events {:?}", &key_events); + + key_events } pub fn event_type_to_event(event_type: EventType) -> Event { @@ -386,6 +397,7 @@ pub fn event_type_to_event(event_type: EventType) -> Event { event_type, time: SystemTime::now(), name: None, + unicode: Vec::new(), code: 0, scan_code: 0, } @@ -423,13 +435,14 @@ pub fn get_peer_platform() -> String { } #[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option { +pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { + let mut events = Vec::new(); // legacy mode(0): Generate characters locally, look for keycode on other side. let (mut key, down_or_up) = match event.event_type { EventType::KeyPress(key) => (key, true), EventType::KeyRelease(key) => (key, false), _ => { - return None; + return events; } }; @@ -475,7 +488,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option { if is_win && ctrl && alt { client::ctrl_alt_del(); - return None; + return events; } Some(ControlKey::Delete) } @@ -545,7 +558,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Some(ControlKey::Subtract), Key::KpPlus => Some(ControlKey::Add), Key::CapsLock | Key::NumLock | Key::ScrollLock => { - return None; + return events; } Key::Home => Some(ControlKey::Home), Key::End => Some(ControlKey::End), @@ -628,12 +641,12 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option Option { @@ -703,6 +717,51 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option { - None +#[cfg(target_os = "windows")] +fn is_modifier_code(scan_code: u32) -> bool { + match scan_code { + // Alt | AltGr | ControlLeft | ControlRight | ShiftLeft | ShiftRight | MetaLeft | MetaRight + 0x38 | 0xE038 | 0x1D | 0xE01D | 0x2A | 0x36 | 0xE05B | 0xE05C => true, + _ => false, + } +} + +#[cfg(target_os = "linux")] +fn is_modifier_code(key_code: u32) -> bool { + match scan_code { + 64 | 108 | 37 | 105 | 50 | 62 | 133 | 134 => true, + _ => false, + } +} + +#[cfg(target_os = "macos")] +fn is_modifier_code(key_code: u32) -> bool { + match scan_code { + 0x3A | 0x3D | 0x3B | 0x3E | 0x38 | 0x3C | 0x37 | 0x36 => true, + _ => false, + } +} + +pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { + #[cfg(target_os = "windows")] + let is_modifier = is_modifier_code(event.scan_code); + #[cfg(target_os = "linux")] + let is_modifier = is_modifier_code(event.key_code); + #[cfg(target_os = "macos")] + let is_modifier = is_modifier_code(event.key_code); + + let mut events: Vec = Vec::new(); + if is_modifier { + if let Some(evt) = map_keyboard_mode(event, key_event) { + events.push(evt); + } + return events; + } + + for unicode in &event.unicode { + let mut evt = key_event.clone(); + evt.set_unicode(*unicode as _); + events.push(evt); + } + events } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 2715a2643..072ef53fb 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1067,6 +1067,21 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { release_keys(&mut en, &to_release); } +fn translate_keyboard_mode(evt: &KeyEvent) { + match evt.union { + Some(key_event::Union::Unicode(unicode)) => { + println!("REMOVE ME ========================= simulate_unicode {}", unicode); + allow_err!(rdev::simulate_unicode(unicode as _)); + }, + Some(key_event::Union::Chr(..)) => { + map_keyboard_mode(evt) + } + _ => { + log::debug!("Unreachable. Unexpected key event {:?}", &evt); + } + } +} + pub fn handle_key_(evt: &KeyEvent) { if EXITING.load(Ordering::SeqCst) { return; @@ -1080,7 +1095,7 @@ pub fn handle_key_(evt: &KeyEvent) { map_keyboard_mode(evt); } KeyboardMode::Translate => { - legacy_keyboard_mode(evt); + translate_keyboard_mode(evt); } _ => { legacy_keyboard_mode(evt); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 4fc5db743..95b8cdbd0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -361,11 +361,31 @@ impl Session { } pub fn enter(&self) { + #[cfg(target_os = "windows")] + { + match &self.lc.read().unwrap().keyboard_mode as _ { + "legacy" => { + println!("REMOVE ME =========================== enter legacy "); + rdev::set_get_key_name(true); + } + "translate" => { + println!("REMOVE ME =========================== enter translate "); + rdev::set_get_key_name(true); + } + _ => {} + } + } + IS_IN.store(true, Ordering::SeqCst); keyboard::client::change_grab_status(GrabState::Run); } pub fn leave(&self) { + #[cfg(target_os = "windows")] + { + println!("REMOVE ME =========================== leave "); + rdev::set_get_key_name(false); + } IS_IN.store(false, Ordering::SeqCst); keyboard::client::change_grab_status(GrabState::Wait); } @@ -429,6 +449,7 @@ impl Session { let event = Event { time: std::time::SystemTime::now(), name: Option::Some(name.to_owned()), + unicode: Vec::new(), code: keycode as _, scan_code: scancode as _, event_type: event_type, From 6e54cd2e6b7a7e207b13e31f2668db4df98f13ee Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 10:41:47 +0800 Subject: [PATCH 02/10] win, translate mode, check dead code Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_menubar.dart | 29 +++++---- src/common.rs | 4 +- src/keyboard.rs | 63 ++++++------------- src/ui_session_interface.rs | 5 +- 4 files changed, 41 insertions(+), 60 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 36b9504c0..4fd702ad8 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1382,25 +1382,23 @@ class _RemoteMenubarState extends State { text: translate('Ratio'), optionsGetter: () { List list = []; - List modes = ["legacy"]; + List modes = [ + KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'), + KeyboardModeMenu(key: 'map', menu: 'Map mode'), + KeyboardModeMenu(key: 'translate', menu: 'Translate mode'), + ]; - if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) { - modes.add("map"); - } - - for (String mode in modes) { - if (mode == "legacy") { + for (KeyboardModeMenu mode in modes) { + if (bind.sessionIsKeyboardModeSupported( + id: widget.id, mode: mode.key)) { list.add(MenuEntryRadioOption( - text: translate('Legacy mode'), value: 'legacy')); - } else if (mode == "map") { - list.add(MenuEntryRadioOption( - text: translate('Map mode'), value: 'map')); + text: translate(mode.menu), value: mode.key)); } } return list; }, curOptionGetter: () async { - return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy"; + return await bind.sessionGetKeyboardMode(id: widget.id) ?? 'legacy'; }, optionSetter: (String oldValue, String newValue) async { await bind.sessionSetKeyboardMode(id: widget.id, value: newValue); @@ -1689,3 +1687,10 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ); } } + +class KeyboardModeMenu { + final String key; + final String menu; + + KeyboardModeMenu({required this.key, required this.menu}); +} diff --git a/src/common.rs b/src/common.rs index c2d5a81f0..8f8ce8dec 100644 --- a/src/common.rs +++ b/src/common.rs @@ -671,8 +671,8 @@ pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: match keyboard_mode { KeyboardMode::Legacy => true, KeyboardMode::Map => version_number >= hbb_common::get_version_number("1.2.0"), - KeyboardMode::Translate => false, - KeyboardMode::Auto => false, + KeyboardMode::Translate => version_number >= hbb_common::get_version_number("1.2.0"), + KeyboardMode::Auto => version_number >= hbb_common::get_version_number("1.2.0"), } } diff --git a/src/keyboard.rs b/src/keyboard.rs index bcb0650ac..7d5f36af2 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -387,7 +387,10 @@ pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec Event { Event { event_type, time: SystemTime::now(), - name: None, - unicode: Vec::new(), + unicode: None, code: 0, scan_code: 0, } @@ -571,7 +573,8 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { if s.len() <= 2 { // exclude chinese characters @@ -717,51 +720,25 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option bool { - match scan_code { - // Alt | AltGr | ControlLeft | ControlRight | ShiftLeft | ShiftRight | MetaLeft | MetaRight - 0x38 | 0xE038 | 0x1D | 0xE01D | 0x2A | 0x36 | 0xE05B | 0xE05C => true, - _ => false, - } -} - -#[cfg(target_os = "linux")] -fn is_modifier_code(key_code: u32) -> bool { - match scan_code { - 64 | 108 | 37 | 105 | 50 | 62 | 133 | 134 => true, - _ => false, - } -} - -#[cfg(target_os = "macos")] -fn is_modifier_code(key_code: u32) -> bool { - match scan_code { - 0x3A | 0x3D | 0x3B | 0x3E | 0x38 | 0x3C | 0x37 | 0x36 => true, - _ => false, - } -} - pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { - #[cfg(target_os = "windows")] - let is_modifier = is_modifier_code(event.scan_code); - #[cfg(target_os = "linux")] - let is_modifier = is_modifier_code(event.key_code); - #[cfg(target_os = "macos")] - let is_modifier = is_modifier_code(event.key_code); - let mut events: Vec = Vec::new(); - if is_modifier { + match &event.unicode { + Some(unicode_info) => { + if !unicode_info.is_dead { + for code in &unicode_info.unicode { + let mut evt = key_event.clone(); + evt.set_unicode(*code as _); + events.push(evt); + } + } + } + None => {} + } + if events.is_empty() { if let Some(evt) = map_keyboard_mode(event, key_event) { events.push(evt); } return events; } - - for unicode in &event.unicode { - let mut evt = key_event.clone(); - evt.set_unicode(*unicode as _); - events.push(evt); - } events } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 95b8cdbd0..3801eda67 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -423,7 +423,7 @@ impl Session { pub fn handle_flutter_key_event( &self, - name: &str, + _name: &str, keycode: i32, scancode: i32, lock_modes: i32, @@ -448,8 +448,7 @@ impl Session { }; let event = Event { time: std::time::SystemTime::now(), - name: Option::Some(name.to_owned()), - unicode: Vec::new(), + unicode: None, code: keycode as _, scan_code: scancode as _, event_type: event_type, From 6eec0041bd7038a062a250b7f6dbd8bb3b4569d1 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 6 Feb 2023 16:26:27 +0800 Subject: [PATCH 03/10] win, tranlsate mode, handle shift Signed-off-by: fufesou --- src/keyboard.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 7d5f36af2..08ab23b1b 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -88,14 +88,17 @@ pub mod client { } } - pub fn process_event(event: &Event, lock_modes: Option) { + pub fn process_event(event: &Event, lock_modes: Option) -> KeyboardMode { + let keyboard_mode = get_keyboard_mode_enum(); + if is_long_press(&event) { - return; + return keyboard_mode; } - for key_event in event_to_key_events(&event, lock_modes) { + for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) { send_key_event(&key_event); } + keyboard_mode } pub fn get_modifiers_state( @@ -205,7 +208,14 @@ pub fn start_grab_loop() { return Some(event); } if KEYBOARD_HOOKED.load(Ordering::SeqCst) { - client::process_event(&event, None); + let keyboard_mode = client::process_event(&event, None); + if keyboard_mode == KeyboardMode::Translate { + // shift + if event.scan_code == 0x2A { + return Some(event); + } + } + if is_press { return None; } else { @@ -342,7 +352,7 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec { +pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_modes: Option) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -356,7 +366,6 @@ pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec {} } - let keyboard_mode = get_keyboard_mode_enum(); key_event.mode = keyboard_mode.into(); let mut key_events = match keyboard_mode { KeyboardMode::Map => match map_keyboard_mode(event, key_event) { From ddc9792d15420a2a3e56cc6b37cc89a064c5013b Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 6 Feb 2023 18:13:17 +0800 Subject: [PATCH 04/10] win, translate mode, debug almost done Signed-off-by: fufesou --- Cargo.lock | 28 ++----------------- Cargo.toml | 2 +- .../lib/desktop/widgets/remote_menubar.dart | 6 ++++ src/keyboard.rs | 9 ++---- src/server/input_service.rs | 1 - src/ui_session_interface.rs | 11 ++------ 6 files changed, 14 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ac2720be..988363019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", + "rdev", "serde 1.0.149", "serde_derive", "tfc", @@ -4404,29 +4404,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -dependencies = [ - "cocoa", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", - "core-graphics 0.22.3", - "enum-map", - "epoll", - "inotify", - "lazy_static", - "libc", - "log", - "mio 0.8.5", - "strum 0.24.1", - "strum_macros 0.24.3", - "widestring 1.0.2", - "winapi 0.3.9", - "x11 2.20.1", -] - -[[package]] -name = "rdev" -version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#238c9778da40056e2efda1e4264355bc89fb6358" +source = "git+https://github.com/fufesou/rdev#77b45e9e43f713851874c7fbb8e7149ab4f2e6a1" dependencies = [ "cocoa", "core-foundation 0.9.3", @@ -4731,7 +4709,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev 0.5.0-2", + "rdev", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 5d75b7a23..936b9e349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { path = "../rdev" } +rdev = { git = "https://github.com/fufesou/rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 4fd702ad8..c3c8ce3fe 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1391,6 +1391,12 @@ class _RemoteMenubarState extends State { for (KeyboardModeMenu mode in modes) { if (bind.sessionIsKeyboardModeSupported( id: widget.id, mode: mode.key)) { + if (mode.key == 'translate') { + if (!Platform.isWindows || + widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { + continue; + } + } list.add(MenuEntryRadioOption( text: translate(mode.menu), value: mode.key)); } diff --git a/src/keyboard.rs b/src/keyboard.rs index 08ab23b1b..fd9514427 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -210,8 +210,8 @@ pub fn start_grab_loop() { if KEYBOARD_HOOKED.load(Ordering::SeqCst) { let keyboard_mode = client::process_event(&event, None); if keyboard_mode == KeyboardMode::Translate { - // shift - if event.scan_code == 0x2A { + // SHIFT(0x2A) RSHIFT(0x36) + if event.scan_code == 0x2A || event.scan_code == 0x36 { return Some(event); } } @@ -396,11 +396,6 @@ pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_mode } } - println!( - "REMOVE ME ========================= key_events {:?}", - &key_events - ); - key_events } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 072ef53fb..1d7d4773d 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1070,7 +1070,6 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { - println!("REMOVE ME ========================= simulate_unicode {}", unicode); allow_err!(rdev::simulate_unicode(unicode as _)); }, Some(key_event::Union::Chr(..)) => { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 3801eda67..12412d7cd 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -364,14 +364,8 @@ impl Session { #[cfg(target_os = "windows")] { match &self.lc.read().unwrap().keyboard_mode as _ { - "legacy" => { - println!("REMOVE ME =========================== enter legacy "); - rdev::set_get_key_name(true); - } - "translate" => { - println!("REMOVE ME =========================== enter translate "); - rdev::set_get_key_name(true); - } + "legacy" => rdev::set_get_key_name(true), + "translate" => rdev::set_get_key_name(true), _ => {} } } @@ -383,7 +377,6 @@ impl Session { pub fn leave(&self) { #[cfg(target_os = "windows")] { - println!("REMOVE ME =========================== leave "); rdev::set_get_key_name(false); } IS_IN.store(false, Ordering::SeqCst); From 347add18744358b9d07247bee458f2a4ca17c0f8 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 09:48:04 +0800 Subject: [PATCH 05/10] win, translate mode, to debug Signed-off-by: fufesou --- Cargo.lock | 26 +++++++++- Cargo.toml | 2 +- src/keyboard.rs | 100 ++++++++++++++++++++++++++++++++---- src/server/input_service.rs | 13 ++++- 4 files changed, 128 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 988363019..f5ffa7f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev", + "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,6 +4401,28 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdev" +version = "0.5.0-2" +dependencies = [ + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "enum-map", + "epoll", + "inotify", + "lazy_static", + "libc", + "log", + "mio 0.8.5", + "strum 0.24.1", + "strum_macros 0.24.3", + "widestring 1.0.2", + "winapi 0.3.9", + "x11 2.20.1", +] + [[package]] name = "rdev" version = "0.5.0-2" @@ -4709,7 +4731,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev", + "rdev 0.5.0-2", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 936b9e349..5d75b7a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { git = "https://github.com/fufesou/rdev" } +rdev = { path = "../rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/src/keyboard.rs b/src/keyboard.rs index fd9514427..492314ab6 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -199,6 +199,9 @@ pub fn update_grab_get_key_name() { }; } +#[cfg(target_os = "windows")] +static mut IS_LAST_0X021D: bool = false; + pub fn start_grab_loop() { #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { @@ -210,12 +213,22 @@ pub fn start_grab_loop() { if KEYBOARD_HOOKED.load(Ordering::SeqCst) { let keyboard_mode = client::process_event(&event, None); if keyboard_mode == KeyboardMode::Translate { - // SHIFT(0x2A) RSHIFT(0x36) - if event.scan_code == 0x2A || event.scan_code == 0x36 { - return Some(event); + #[cfg(target_os = "windows")] + match event.scan_code { + 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), + 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), + 0x38 => rdev::set_modifier(Key::Alt, is_press), + 0xE038 => rdev::set_modifier(Key::AltGr, is_press), + 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), + 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), + _ => {} + } + #[cfg(target_os = "windows")] + unsafe { + IS_LAST_0X021D = event.scan_code == 0x021D; } } - + if is_press { return None; } else { @@ -352,7 +365,11 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_modes: Option) -> Vec { +pub fn event_to_key_events( + event: &Event, + keyboard_mode: KeyboardMode, + lock_modes: Option, +) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -577,7 +594,10 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { if s.len() <= 2 { @@ -724,8 +744,7 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Vec { - let mut events: Vec = Vec::new(); +fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec) { match &event.unicode { Some(unicode_info) => { if !unicode_info.is_dead { @@ -738,8 +757,71 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec {} } +} + +#[cfg(target_os = "windows")] +fn is_hot_key_modifiers_down() -> bool { + if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) { + return true; + } + if rdev::get_modifier(Key::Alt) || rdev::get_modifier(Key::AltGr) { + return true; + } + if rdev::get_modifier(Key::MetaLeft) || rdev::get_modifier(Key::MetaRight) { + return true; + } + return false; +} + +pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Option { + match event.event_type { + EventType::KeyPress(..) => { + key_event.down = true; + } + EventType::KeyRelease(..) => { + key_event.down = false; + } + _ => return None, + }; + + let mut peer = get_peer_platform().to_lowercase(); + peer.retain(|c| !c.is_whitespace()); + + // #[cfg(target_os = "windows")] + // let keycode = match peer.as_str() { + // "windows" => event.code, + // "macos" => { + // if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { + // rdev::win_scancode_to_macos_iso_code(event.scan_code)? + // } else { + // rdev::win_scancode_to_macos_code(event.scan_code)? + // } + // } + // _ => rdev::win_scancode_to_linux_code(event.scan_code)?, + // }; + + key_event.set_chr(event.code as _); + Some(key_event) +} + +pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { + let mut events: Vec = Vec::new(); + #[cfg(target_os = "windows")] + unsafe { + if IS_LAST_0X021D { + if event.scan_code == 0xE038 { + return events; + } + } + } + + #[cfg(target_os = "windows")] + if !is_hot_key_modifiers_down() { + try_fill_unicode(event, &key_event, &mut events); + } + if events.is_empty() { - if let Some(evt) = map_keyboard_mode(event, key_event) { + if let Some(evt) = translate_virtual_keycode(event, key_event) { events.push(evt); } return events; diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 1d7d4773d..133b9a830 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1067,13 +1067,24 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { release_keys(&mut en, &to_release); } +#[cfg(target_os = "windows")] +fn translate_process_virtual_keycode(vk: u32, down: bool) { + let scancode = rdev::vk_to_scancode(vk); + // map mode(1): Send keycode according to the peer platform. + record_pressed_key(scancode as u64 + KEY_CHAR_START, down); + + crate::platform::windows::try_change_desktop(); + sim_rdev_rawkey(scancode, down); +} + fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { allow_err!(rdev::simulate_unicode(unicode as _)); }, Some(key_event::Union::Chr(..)) => { - map_keyboard_mode(evt) + #[cfg(target_os = "windows")] + translate_process_virtual_keycode(evt.chr(), evt.down) } _ => { log::debug!("Unreachable. Unexpected key event {:?}", &evt); From 1294103ba778016a118ba51cbf9a3d61f1db5212 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 13:31:49 +0800 Subject: [PATCH 06/10] win, translate mode, debug Signed-off-by: fufesou --- src/keyboard.rs | 62 +++++++++++++++++++------------- src/server/input_service.rs | 71 ++++++++++++++++++++++--------------- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 492314ab6..bdf1c5c1b 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -200,7 +200,7 @@ pub fn update_grab_get_key_name() { } #[cfg(target_os = "windows")] -static mut IS_LAST_0X021D: bool = false; +static mut IS_0X021D_DOWN: bool = false; pub fn start_grab_loop() { #[cfg(any(target_os = "windows", target_os = "macos"))] @@ -210,33 +210,43 @@ pub fn start_grab_loop() { if key == Key::CapsLock || key == Key::NumLock { return Some(event); } - if KEYBOARD_HOOKED.load(Ordering::SeqCst) { - let keyboard_mode = client::process_event(&event, None); - if keyboard_mode == KeyboardMode::Translate { - #[cfg(target_os = "windows")] - match event.scan_code { - 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), - 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), - 0x38 => rdev::set_modifier(Key::Alt, is_press), - 0xE038 => rdev::set_modifier(Key::AltGr, is_press), - 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), - 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), - _ => {} - } - #[cfg(target_os = "windows")] - unsafe { - IS_LAST_0X021D = event.scan_code == 0x021D; - } - } + let mut _keyboard_mode = KeyboardMode::Map; + let scan_code = event.scan_code; + let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) { + _keyboard_mode = client::process_event(&event, None); if is_press { - return None; + None } else { - return Some(event); + Some(event) } } else { - return Some(event); + Some(event) + }; + + #[cfg(target_os = "windows")] + match scan_code { + 0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press), + 0xE01D => rdev::set_modifier(Key::ControlRight, is_press), + 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), + 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), + 0x38 => rdev::set_modifier(Key::Alt, is_press), + // Right Alt + 0xE038 => rdev::set_modifier(Key::AltGr, is_press), + 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), + 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), + _ => {} } + + #[cfg(target_os = "windows")] + unsafe { + // AltGr + if scan_code == 0x021D { + IS_0X021D_DOWN = is_press; + } + } + + return res; }; let func = move |event: Event| match event.event_type { EventType::KeyPress(key) => try_handle_keyboard(event, key, true), @@ -808,7 +818,11 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec = Vec::new(); #[cfg(target_os = "windows")] unsafe { - if IS_LAST_0X021D { + if event.scan_code == 0x021D { + return events; + } + + if IS_0X021D_DOWN { if event.scan_code == 0xE038 { return events; } @@ -816,7 +830,7 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec ResultType<()> Ok(()) } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum KeysDown { + RdevKey(RawKey), + EnigoKey(u64) +} + lazy_static::lazy_static! { static ref ENIGO: Arc> = { Arc::new(Mutex::new(Enigo::new())) }; - static ref KEYS_DOWN: Arc>> = Default::default(); + static ref KEYS_DOWN: Arc>> = Default::default(); static ref LATEST_PEER_INPUT_CURSOR: Arc> = Default::default(); static ref LATEST_SYS_CURSOR_POS: Arc> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0)))); } @@ -375,12 +380,7 @@ fn record_key_is_control_key(record_key: u64) -> bool { #[inline] fn record_key_is_chr(record_key: u64) -> bool { - KEY_RDEV_START <= record_key && record_key < KEY_CHAR_START -} - -#[inline] -fn record_key_is_rdev_layout(record_key: u64) -> bool { - KEY_CHAR_START <= record_key + record_key < KEY_CHAR_START } #[inline] @@ -396,15 +396,18 @@ fn record_key_to_key(record_key: u64) -> Option { } #[inline] -fn release_record_key(record_key: u64) { +fn release_record_key(record_key: KeysDown) { let func = move || { - if record_key_is_rdev_layout(record_key) { - simulate_(&EventType::KeyRelease(RdevKey::Unknown( - (record_key - KEY_RDEV_START) as _, - ))); - } else if let Some(key) = record_key_to_key(record_key) { - ENIGO.lock().unwrap().key_up(key); - log::debug!("Fixed {:?} timeout", key); + match record_key { + KeysDown::RdevKey(raw_key) => { + simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); + } + KeysDown::EnigoKey(key) => { + if let Some(key) = record_key_to_key(key) { + ENIGO.lock().unwrap().key_up(key); + log::debug!("Fixed {:?} timeout", key); + } + } } }; @@ -733,7 +736,7 @@ pub fn reset_input_ondisconn() { } } -fn sim_rdev_rawkey(code: u32, keydown: bool) { +fn sim_rdev_rawkey_position(code: u32, keydown: bool) { #[cfg(target_os = "windows")] let rawkey = RawKey::ScanCode(code); #[cfg(target_os = "linux")] @@ -744,6 +747,23 @@ fn sim_rdev_rawkey(code: u32, keydown: bool) { #[cfg(target_os = "macos")] let rawkey = RawKey::MacVirtualKeycode(code); + // map mode(1): Send keycode according to the peer platform. + record_pressed_key(KeysDown::RdevKey(rawkey), keydown); + + let event_type = if keydown { + EventType::KeyPress(RdevKey::RawKey(rawkey)) + } else { + EventType::KeyRelease(RdevKey::RawKey(rawkey)) + }; + simulate_(&event_type); +} + +fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) { + #[cfg(target_os = "windows")] + let rawkey = RawKey::WinVirtualKeycode(code); + + record_pressed_key(KeysDown::RdevKey(rawkey), keydown); + let event_type = if keydown { EventType::KeyPress(RdevKey::RawKey(rawkey)) } else { @@ -874,9 +894,6 @@ fn sync_numlock_capslock_status(key_event: &KeyEvent) { } fn map_keyboard_mode(evt: &KeyEvent) { - // map mode(1): Send keycode according to the peer platform. - record_pressed_key(evt.chr() as u64 + KEY_CHAR_START, evt.down); - #[cfg(windows)] crate::platform::windows::try_change_desktop(); @@ -894,7 +911,7 @@ fn map_keyboard_mode(evt: &KeyEvent) { return; } - sim_rdev_rawkey(evt.chr(), evt.down); + sim_rdev_rawkey_position(evt.chr(), evt.down); } #[cfg(target_os = "macos")] @@ -1011,7 +1028,7 @@ fn release_keys(en: &mut Enigo, to_release: &Vec) { } } -fn record_pressed_key(record_key: u64, down: bool) { +fn record_pressed_key(record_key: KeysDown, down: bool) { let mut key_down = KEYS_DOWN.lock().unwrap(); if down { key_down.insert(record_key, Instant::now()); @@ -1050,12 +1067,12 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { return; } let record_key = ck.value() as u64; - record_pressed_key(record_key, down); + record_pressed_key(KeysDown::EnigoKey(record_key), down); process_control_key(&mut en, &ck, down) } Some(key_event::Union::Chr(chr)) => { let record_key = chr as u64 + KEY_CHAR_START; - record_pressed_key(record_key, down); + record_pressed_key(KeysDown::EnigoKey(record_key), down); process_chr(&mut en, chr, down) } Some(key_event::Union::Unicode(chr)) => process_unicode(&mut en, chr), @@ -1069,12 +1086,8 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] fn translate_process_virtual_keycode(vk: u32, down: bool) { - let scancode = rdev::vk_to_scancode(vk); - // map mode(1): Send keycode according to the peer platform. - record_pressed_key(scancode as u64 + KEY_CHAR_START, down); - crate::platform::windows::try_change_desktop(); - sim_rdev_rawkey(scancode, down); + sim_rdev_rawkey_virtual(vk, down); } fn translate_keyboard_mode(evt: &KeyEvent) { From 5c7f2678fa870427975bdc98c29c7126bb35ba58 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:23:05 +0800 Subject: [PATCH 07/10] update rdev Signed-off-by: fufesou --- Cargo.lock | 26 ++------------------------ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5ffa7f9d..988363019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", + "rdev", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,28 +4401,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdev" -version = "0.5.0-2" -dependencies = [ - "cocoa", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", - "core-graphics 0.22.3", - "enum-map", - "epoll", - "inotify", - "lazy_static", - "libc", - "log", - "mio 0.8.5", - "strum 0.24.1", - "strum_macros 0.24.3", - "widestring 1.0.2", - "winapi 0.3.9", - "x11 2.20.1", -] - [[package]] name = "rdev" version = "0.5.0-2" @@ -4731,7 +4709,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev 0.5.0-2", + "rdev", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 5d75b7a23..936b9e349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { path = "../rdev" } +rdev = { git = "https://github.com/fufesou/rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } From 01f762ffdb57d7dee4a17110d293252b1621c49a Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:14:13 +0800 Subject: [PATCH 08/10] build linux Signed-off-by: fufesou --- src/keyboard.rs | 5 ++++- src/server/input_service.rs | 33 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index bdf1c5c1b..91480ba30 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -830,10 +830,13 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec ResultType<()> #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum KeysDown { RdevKey(RawKey), - EnigoKey(u64) + EnigoKey(u64), } lazy_static::lazy_static! { @@ -397,16 +397,14 @@ fn record_key_to_key(record_key: u64) -> Option { #[inline] fn release_record_key(record_key: KeysDown) { - let func = move || { - match record_key { - KeysDown::RdevKey(raw_key) => { - simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); - } - KeysDown::EnigoKey(key) => { - if let Some(key) = record_key_to_key(key) { - ENIGO.lock().unwrap().key_up(key); - log::debug!("Fixed {:?} timeout", key); - } + let func = move || match record_key { + KeysDown::RdevKey(raw_key) => { + simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); + } + KeysDown::EnigoKey(key) => { + if let Some(key) = record_key_to_key(key) { + ENIGO.lock().unwrap().key_up(key); + log::debug!("Fixed {:?} timeout", key); } } }; @@ -758,12 +756,10 @@ fn sim_rdev_rawkey_position(code: u32, keydown: bool) { simulate_(&event_type); } +#[cfg(target_os = "windows")] fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) { - #[cfg(target_os = "windows")] let rawkey = RawKey::WinVirtualKeycode(code); - record_pressed_key(KeysDown::RdevKey(rawkey), keydown); - let event_type = if keydown { EventType::KeyPress(RdevKey::RawKey(rawkey)) } else { @@ -941,10 +937,11 @@ fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) { #[cfg(target_os = "linux")] fn is_altgr_pressed() -> bool { + let altgr_rawkey = RawKey::LinuxXorgKeycode(ControlKey::RAlt.value() as _); KEYS_DOWN .lock() .unwrap() - .get(&(ControlKey::RAlt.value() as _)) + .get(&KeysDown::RdevKey(altgr_rawkey)) .is_some() } @@ -1093,9 +1090,11 @@ fn translate_process_virtual_keycode(vk: u32, down: bool) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { + #[cfg(target_os = "windows")] allow_err!(rdev::simulate_unicode(unicode as _)); - }, - Some(key_event::Union::Chr(..)) => { + } + Some(key_event::Union::Chr(..)) => + { #[cfg(target_os = "windows")] translate_process_virtual_keycode(evt.chr(), evt.down) } From 586f0a272663222c6d013aca3129c4be0dfd0fae Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:17:37 +0800 Subject: [PATCH 09/10] compile macos Signed-off-by: fufesou --- src/server/input_service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 7b6130ad1..edf0ef497 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1089,9 +1089,9 @@ fn translate_process_virtual_keycode(vk: u32, down: bool) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { - Some(key_event::Union::Unicode(unicode)) => { + Some(key_event::Union::Unicode(_unicode)) => { #[cfg(target_os = "windows")] - allow_err!(rdev::simulate_unicode(unicode as _)); + allow_err!(rdev::simulate_unicode(_unicode as _)); } Some(key_event::Union::Chr(..)) => { From d263d1892bf6fc1258c5b922b8cbd3b0922deebe Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:34:52 +0800 Subject: [PATCH 10/10] update rdev Signed-off-by: fufesou --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 988363019..93b40ca3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4404,7 +4404,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#77b45e9e43f713851874c7fbb8e7149ab4f2e6a1" +source = "git+https://github.com/fufesou/rdev#4d8231f05e14c5a04cd7d2c1288e87ad52d39e4c" dependencies = [ "cocoa", "core-foundation 0.9.3",