Merge pull request #964 from asur4s/master

Feat: Support new keyboard mode
This commit is contained in:
RustDesk
2022-09-07 21:34:29 +08:00
committed by GitHub
74 changed files with 1864 additions and 1493 deletions

View File

@@ -154,7 +154,8 @@ impl Client {
return Err(err);
}
}
Ok(x) => Ok(x),
Ok(x) => {
Ok(x)},
}
}
@@ -1707,7 +1708,7 @@ pub enum Data {
}
/// Keycode for key events.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Key {
ControlKey(ControlKey),
Chr(u32),

View File

@@ -666,3 +666,14 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
msg_out.set_misc(misc);
msg_out
}
#[cfg(not(target_os = "linux"))]
lazy_static::lazy_static! {
pub static ref IS_X11: Mutex<bool> = Mutex::new(false);
}
#[cfg(target_os = "linux")]
lazy_static::lazy_static! {
pub static ref IS_X11: Mutex<bool> = Mutex::new("x11" == hbb_common::platform::linux::get_display_server());
}

View File

@@ -475,4 +475,4 @@ pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> S
}
m.insert("total_size".into(), json!(n as f64));
serde_json::to_string(&m).unwrap_or("".into())
}
}

View File

@@ -17,7 +17,7 @@ use crate::flutter::{self, SESSIONS};
use crate::start_server;
use crate::ui_interface;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
use crate::ui_interface::change_id;
use crate::ui_interface::{
check_mouse_time, check_super_user_permission, discover, forget_password, get_api_server,
get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers,
@@ -104,9 +104,9 @@ pub fn stop_global_event_stream(app_type: String) {
.remove(&app_type);
}
pub fn host_stop_system_key_propagate(stopped: bool) {
pub fn host_stop_system_key_propagate(_stopped: bool) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(stopped);
crate::platform::windows::stop_system_key_propagate(_stopped);
}
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
@@ -233,6 +233,28 @@ pub fn session_switch_display(id: String, value: i32) {
}
}
pub fn session_handle_flutter_key_event(
id: String,
name: String,
keycode: i32,
scancode: i32,
down_or_up: bool,
) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.handle_flutter_key_event(&name, keycode, scancode, down_or_up);
}
}
pub fn session_enter_or_leave(id: String, enter: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
if enter {
session.enter();
} else {
session.leave();
}
}
}
pub fn session_input_key(
id: String,
name: String,
@@ -274,6 +296,19 @@ pub fn session_get_peer_option(id: String, name: String) -> String {
"".to_string()
}
pub fn session_get_keyboard_name(id: String) -> String {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
return session.get_keyboard_mode();
}
"legacy".to_string()
}
pub fn session_set_keyboard_mode(id: String, keyboard_mode: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.save_keyboard_mode(keyboard_mode);
}
}
pub fn session_input_os_password(id: String, value: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.input_os_password(value, true);

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能请在接下来的RustDesk应用设置页面中找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Connection not allowed", "对方不允许连接"),
("Legacy mode", "传统模式"),
("Map mode", "11传输"),
("Translate mode", "翻译模式"),
("Use temporary password", "使用临时密码"),
("Use permanent password", "使用固定密码"),
("Use both passwords", "同时使用两种密码"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Batterieoptimierung ignorieren"),
("android_open_battery_optimizations_tip", "Möchten Sie die Batterieopimierungs-Einstellungen öffnen?"),
("Connection not allowed", "Verbindung abgelehnt"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Temporäres Passwort verwenden"),
("Use permanent password", "Dauerhaftes Passwort verwenden"),
("Use both passwords", "Beide Passwörter verwenden"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Conexión no disponible"),
("Use temporary password", "Usar contraseña temporal"),
("Use permanent password", "Usar contraseña permamente"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Use temporary password", "Gunakan kata sandi sementara"),
("Use permanent password", "Gunakan kata sandi permanaen"),

View File

@@ -318,5 +318,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Connessione insicura"),
("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
].iter().cloned().collect();
}

View File

@@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
("Connection not allowed", "接続が許可されていません"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "使い捨てのパスワードを使用"),
("Use permanent password", "固定のパスワードを使用"),
("Use both passwords", "どちらのパスワードも使用"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек "),
("Connection not allowed", "Подключение не разрешено"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Использовать временный пароль"),
("Use permanent password", "Использовать постоянный пароль"),
("Use both passwords", "Использовать оба пароля"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Use temporary password", "Geçici şifre kullan"),
("Use permanent password", "Kalıcı şifre kullan"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Connection not allowed", "對方不允許連接"),
("Legacy mode", "傳統模式"),
("Map mode", "11傳輸"),
("Translate mode", "翻譯模式"),
("Use temporary password", "使用臨時密碼"),
("Use permanent password", "使用固定密碼"),
("Use both passwords", "同時使用兩種密碼"),

View File

@@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
("Connection not allowed", "Kết nối không đuợc phép"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Sử dụng mật khẩu tạm thời"),
("Use permanent password", "Sử dụng mật khẩu vĩnh viễn"),
("Use both passwords", "Sử dụng cả hai mật khẩu"),

View File

@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::{
net::SocketAddr,
sync::{
@@ -10,12 +9,10 @@ use std::{
use uuid::Uuid;
use hbb_common::config::DiscoveryPeer;
use hbb_common::tcp::FramedStream;
use hbb_common::{
allow_err,
anyhow::bail,
config,
config::{Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
@@ -571,6 +568,7 @@ pub fn get_mac() -> String {
"".to_owned()
}
#[allow(dead_code)]
fn lan_discovery() -> ResultType<()> {
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
let socket = std::net::UdpSocket::bind(addr)?;
@@ -640,7 +638,7 @@ pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<S
}
async fn create_online_stream() -> ResultType<FramedStream> {
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
let (rendezvous_server, _servers, _contained) = crate::get_rendezvous_server(1_000).await;
let tmp: Vec<&str> = rendezvous_server.split(":").collect();
if tmp.len() != 2 {
bail!("Invalid server address: {}", rendezvous_server);

View File

@@ -448,11 +448,12 @@ impl Connection {
handle_mouse(&msg, id);
}
MessageInput::Key((mut msg, press)) => {
if press {
// todo: press and down have similar meanings.
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
msg.down = true;
}
handle_key(&msg);
if press {
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
msg.down = false;
handle_key(&msg);
}
@@ -632,7 +633,7 @@ impl Connection {
let mut pi = PeerInfo {
username: username.clone(),
conn_id: self.inner.id,
version: crate::VERSION.to_owned(),
version: VERSION.to_owned(),
..Default::default()
};

View File

@@ -1,8 +1,10 @@
use super::*;
use crate::common::IS_X11;
#[cfg(target_os = "macos")]
use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{config::COMPRESS_LEVEL, protobuf::EnumOrUnknown};
use rdev::{simulate, EventType, Key as RdevKey};
use std::{
convert::TryFrom,
sync::atomic::{AtomicBool, Ordering},
@@ -21,8 +23,6 @@ impl super::service::Reset for StateCursor {
*self = Default::default();
crate::platform::reset_input_cache();
fix_key_down_timeout(true);
#[cfg(target_os = "linux")]
ENIGO.lock().unwrap().reset();
}
}
@@ -145,7 +145,8 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
msg = cached.clone();
} else {
let mut data = crate::get_cursor_data(hcursor)?;
data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
data.colors =
hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
let mut tmp = Message::new();
tmp.set_cursor_data(data);
msg = Arc::new(tmp);
@@ -166,13 +167,6 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
lazy_static::lazy_static! {
static ref ENIGO: Arc<Mutex<Enigo>> = {
#[cfg(target_os = "linux")]
{
if crate::platform::is_root() {
std::env::set_var("PYNPUT_USERNAME", crate::platform::linux::get_active_username());
std::env::set_var("PYNPUT_USERID", crate::platform::linux::get_active_userid());
}
}
Arc::new(Mutex::new(Enigo::new()))
};
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
@@ -466,10 +460,14 @@ pub async fn lock_screen() {
// loginctl lock-session also not work, they both work run rustdesk from cmd
std::thread::spawn(|| {
let mut key_event = KeyEvent::new();
key_event.down = true;
key_event.set_chr('l' as _);
key_event.modifiers.push(ControlKey::Meta.into());
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = true;
handle_key(&key_event);
key_event.down = false;
handle_key(&key_event);
});
@@ -477,10 +475,13 @@ pub async fn lock_screen() {
// CGSession -suspend not real lock screen, it is user switch
std::thread::spawn(|| {
let mut key_event = KeyEvent::new();
key_event.down = true;
key_event.set_chr('q' as _);
key_event.modifiers.push(ControlKey::Meta.into());
key_event.modifiers.push(ControlKey::Control.into());
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = true;
handle_key(&key_event);
key_event.down = false;
handle_key(&key_event);
@@ -597,25 +598,125 @@ pub fn handle_key(evt: &KeyEvent) {
handle_key_(evt);
}
fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) {
let event_type = match down_or_up {
true => EventType::KeyPress(key),
false => EventType::KeyRelease(key),
};
let delay = std::time::Duration::from_millis(20);
match simulate(&event_type) {
Ok(()) => (),
Err(_simulate_error) => {
log::error!("Could not send {:?}", &event_type);
}
}
// Let ths OS catchup (at least MacOS)
std::thread::sleep(delay);
}
fn rdev_key_click(key: RdevKey) {
rdev_key_down_or_up(key, true);
rdev_key_down_or_up(key, false);
}
fn sync_status(evt: &KeyEvent) -> (bool, bool) {
let mut en = ENIGO.lock().unwrap();
// remote caps status
let caps_locking = evt
.modifiers
.iter()
.position(|&r| r == ControlKey::CapsLock.into())
.is_some();
// remote numpad status
let num_locking = evt
.modifiers
.iter()
.position(|&r| r == ControlKey::NumLock.into())
.is_some();
let click_capslock = (caps_locking && !en.get_key_state(enigo::Key::CapsLock))
|| (!caps_locking && en.get_key_state(enigo::Key::CapsLock));
let click_numlock = (num_locking && !en.get_key_state(enigo::Key::NumLock))
|| (!num_locking && en.get_key_state(enigo::Key::NumLock));
return (click_capslock, click_numlock);
}
fn map_keyboard_mode(evt: &KeyEvent) {
// map mode(1): Send keycode according to the peer platform.
let (click_capslock, click_numlock) = sync_status(evt);
// Wayland
#[cfg(target_os = "linux")]
if !*IS_X11.lock().unwrap() {
let mut en = ENIGO.lock().unwrap();
let code = evt.chr() as u16;
#[cfg(not(target_os = "macos"))]
if click_capslock {
en.key_click(enigo::Key::CapsLock);
}
#[cfg(not(target_os = "macos"))]
if click_numlock {
en.key_click(enigo::Key::NumLock);
}
#[cfg(target_os = "macos")]
en.key_down(enigo::Key::CapsLock);
if evt.down {
en.key_down(enigo::Key::Raw(code)).ok();
} else {
en.key_up(enigo::Key::Raw(code));
}
return;
}
#[cfg(not(target_os = "macos"))]
if click_capslock {
rdev_key_click(RdevKey::CapsLock);
}
#[cfg(not(target_os = "macos"))]
if click_numlock {
rdev_key_click(RdevKey::NumLock);
}
#[cfg(target_os = "macos")]
if evt.down && click_capslock {
rdev_key_down_or_up(RdevKey::CapsLock, evt.down);
}
rdev_key_down_or_up(RdevKey::Unknown(evt.chr()), evt.down);
return;
}
fn legacy_keyboard_mode(evt: &KeyEvent) {
let (click_capslock, click_numlock) = sync_status(evt);
#[cfg(windows)]
crate::platform::windows::try_change_desktop();
let mut en = ENIGO.lock().unwrap();
if click_capslock {
en.key_click(Key::CapsLock);
}
if click_numlock {
en.key_click(Key::NumLock);
}
// disable numlock if press home etc when numlock is on,
// because we will get numpad value (7,8,9 etc) if not
#[cfg(windows)]
let mut disable_numlock = false;
#[cfg(target_os = "macos")]
en.reset_flag();
// When long-pressed the command key, then press and release
// the Tab key, there should be CGEventFlagCommand in the flag.
#[cfg(target_os = "macos")]
for ck in evt.modifiers.iter() {
if let Some(key) = KEY_MAP.get(&ck.value()) {
en.add_flag(key);
}
}
#[cfg(not(target_os = "macos"))]
let mut to_release = Vec::new();
#[cfg(not(target_os = "macos"))]
let mut has_cap = false;
#[cfg(windows)]
let mut has_numlock = false;
if evt.down {
let ck = if let Some(key_event::Union::ControlKey(ck)) = evt.union {
ck.value()
@@ -637,40 +738,16 @@ fn handle_key_(evt: &KeyEvent) {
continue;
}
}
#[cfg(target_os = "macos")]
en.add_flag(key);
#[cfg(not(target_os = "macos"))]
{
if key == &Key::CapsLock {
has_cap = true;
} else if key == &Key::NumLock {
#[cfg(windows)]
{
has_numlock = true;
}
} else {
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);
}
}
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);
}
}
}
}
#[cfg(not(target_os = "macos"))]
if has_cap != en.get_key_state(Key::CapsLock) {
en.key_down(Key::CapsLock).ok();
en.key_up(Key::CapsLock);
}
#[cfg(windows)]
if crate::common::valid_for_numlock(evt) {
if has_numlock != en.get_key_state(Key::NumLock) {
en.key_down(Key::NumLock).ok();
en.key_up(Key::NumLock);
}
}
match evt.union {
Some(key_event::Union::ControlKey(ck)) => {
if let Some(key) = KEY_MAP.get(&ck.value()) {
@@ -683,7 +760,7 @@ fn handle_key_(evt: &KeyEvent) {
}
}
if evt.down {
allow_err!(en.key_down(key.clone()));
en.key_down(key.clone()).ok();
KEYS_DOWN
.lock()
.unwrap()
@@ -719,6 +796,10 @@ fn handle_key_(evt: &KeyEvent) {
en.key_sequence(&x);
}
}
KEYS_DOWN
.lock()
.unwrap()
.insert(chr as u64 + KEY_CHAR_START, Instant::now());
} else {
en.key_up(get_layout(chr));
KEYS_DOWN
@@ -741,10 +822,26 @@ fn handle_key_(evt: &KeyEvent) {
for key in to_release {
en.key_up(key.clone());
}
#[cfg(windows)]
if disable_numlock {
en.key_down(Key::NumLock).ok();
en.key_up(Key::NumLock);
}
fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
return;
}
match evt.mode.unwrap() {
KeyboardMode::Legacy => {
legacy_keyboard_mode(evt);
}
KeyboardMode::Map => {
map_keyboard_mode(evt);
}
KeyboardMode::Translate => {
legacy_keyboard_mode(evt);
}
_ => {
legacy_keyboard_mode(evt);
}
}
}
@@ -759,3 +856,52 @@ async fn send_sas() -> ResultType<()> {
timeout(1000, stream.send(&crate::ipc::Data::SAS)).await??;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use rdev::{listen, Event, EventType, Key};
use std::sync::mpsc;
#[test]
fn test_handle_key() {
// listen
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
std::env::set_var("KEYBOARD_ONLY", "y");
let func = move |event: Event| {
tx.send(event).ok();
};
if let Err(error) = listen(func) {
println!("Error: {:?}", error);
}
});
// set key/char base on char
let mut evt = KeyEvent::new();
evt.set_chr(66);
evt.mode = KeyboardMode::Legacy.into();
evt.modifiers.push(ControlKey::CapsLock.into());
// press
evt.down = true;
handle_key(&evt);
if let Ok(listen_evt) = rx.recv() {
assert_eq!(listen_evt.event_type, EventType::KeyPress(Key::Num1))
}
// release
evt.down = false;
handle_key(&evt);
if let Ok(listen_evt) = rx.recv() {
assert_eq!(listen_evt.event_type, EventType::KeyRelease(Key::Num1))
}
}
#[test]
fn test_get_key_state() {
let mut en = ENIGO.lock().unwrap();
println!(
"[*] test_get_key_state: {:?}",
en.get_key_state(enigo::Key::NumLock)
);
}
}

View File

@@ -350,6 +350,14 @@ pub mod service {
DataKeyboard::Sequence(_seq) => {
// ignore
}
DataKeyboard::KeyDown(enigo::Key::Raw(code)) => {
let down_event = InputEvent::new(EventType::KEY, *code - 8, 1);
allow_err!(keyboard.emit(&[down_event]));
}
DataKeyboard::KeyUp(enigo::Key::Raw(code)) => {
let down_event = InputEvent::new(EventType::KEY, *code - 8, 0);
allow_err!(keyboard.emit(&[down_event]));
}
DataKeyboard::KeyDown(key) => {
if let Ok(k) = map_key(key) {
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
@@ -378,6 +386,14 @@ pub mod service {
false
}
}
} else if enigo::Key::NumLock == *key {
match keyboard.get_led_state() {
Ok(leds) => leds.contains(evdev::LedType::LED_NUML),
Err(_e) => {
// log::debug!("Failed to get led state {}", &_e);
false
}
}
} else {
match keyboard.get_key_state() {
Ok(keys) => match key {
@@ -393,7 +409,6 @@ pub mod service {
keys.contains(evdev::Key::KEY_LEFTALT)
|| keys.contains(evdev::Key::KEY_RIGHTALT)
}
enigo::Key::NumLock => keys.contains(evdev::Key::KEY_NUMLOCK),
enigo::Key::Meta => {
keys.contains(evdev::Key::KEY_LEFTMETA)
|| keys.contains(evdev::Key::KEY_RIGHTMETA)

View File

@@ -9,17 +9,16 @@ use sciter::Value;
use hbb_common::{
allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
config::{self, Config, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
protobuf::Message as _,
rendezvous_proto::*,
sleep,
tcp::FramedStream,
tokio::{self, sync::mpsc, time},
};
use crate::common::{get_app_name, SOFTWARE_UPDATE_URL};
use crate::common::{get_app_name};
use crate::ipc;
use crate::ui_interface::{
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
@@ -59,6 +58,27 @@ lazy_static::lazy_static! {
struct UIHostHandler;
fn check_connect_status(
reconnect: bool,
) -> (
Arc<Mutex<Status>>,
Arc<Mutex<HashMap<String, String>>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
) {
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
let options = Arc::new(Mutex::new(Config::get_options()));
let cloned = status.clone();
let cloned_options = options.clone();
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
let password = Arc::new(Mutex::new(String::default()));
let cloned_password = password.clone();
std::thread::spawn(move || {
crate::ui_interface::check_connect_status_(reconnect, rx)
});
(status, options, tx, password)
}
pub fn start(args: &mut [String]) {
#[cfg(target_os = "macos")]
if args.len() == 1 && args[0] == "--server" {
@@ -87,7 +107,7 @@ pub fn start(args: &mut [String]) {
}
#[cfg(windows)]
if args.len() > 0 && args[0] == "--tray" {
let options = crate::ui_interface::check_connect_status(false).1;
let options = check_connect_status(false).1;
crate::tray::start_tray(options);
return;
}

View File

@@ -155,6 +155,7 @@ var svg_send = <svg viewBox="0 0 448 448">
var svg_chat = <svg viewBox="0 0 511.07 511.07">
<path d="m74.39 480.54h-36.213l25.607-25.607c13.807-13.807 22.429-31.765 24.747-51.246-36.029-23.644-62.375-54.751-76.478-90.425-14.093-35.647-15.864-74.888-5.121-113.48 12.89-46.309 43.123-88.518 85.128-118.85 45.646-32.963 102.47-50.387 164.33-50.387 77.927 0 143.61 22.389 189.95 64.745 41.744 38.159 64.734 89.63 64.734 144.93 0 26.868-5.471 53.011-16.26 77.703-11.165 25.551-27.514 48.302-48.593 67.619-46.399 42.523-112.04 65-189.83 65-28.877 0-59.01-3.855-85.913-10.929-25.465 26.123-59.972 40.929-96.086 40.929zm182-420c-124.04 0-200.15 73.973-220.56 147.28-19.284 69.28 9.143 134.74 76.043 175.12l7.475 4.511-0.23 8.727c-0.456 17.274-4.574 33.912-11.945 48.952 17.949-6.073 34.236-17.083 46.99-32.151l6.342-7.493 9.405 2.813c26.393 7.894 57.104 12.241 86.477 12.241 154.37 0 224.68-93.473 224.68-180.32 0-46.776-19.524-90.384-54.976-122.79-40.713-37.216-99.397-56.888-169.71-56.888z"/>
</svg>;
var svg_keyboard = <svg viewBox="0 0 511.07 511.07"><path d="M491.979 217.631H110.205a48.41 48.41 0 0 1 .637-4.061c4.408-21.755 23.676-38.152 46.71-38.152h149.314c39.306 0 71.282-32.246 71.282-71.552 0-11.28-9.145-20.56-20.426-20.56s-20.426 9.077-20.426 20.359c0 3.419-.575 6.941-1.619 10.01-4.082 11.998-15.451 20.893-28.812 20.893H157.553c-46.995 0-85.535 36.766-88.331 83.064H20.021C8.739 217.631 0 226.296 0 237.578v170.345c0 11.28 8.739 20.773 20.021 20.773H491.98c11.28 0 20.021-9.492 20.021-20.773V237.578c-.001-11.282-8.74-19.947-20.022-19.947zm-20.83 170.213H40.851V258.482h430.298v129.362z"/><path d="M113.021 273.461H89.872c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM190.638 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM268.255 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM345.872 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM423.489 273.461H400.34c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM113.021 325.206H89.872c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.425 20.426 20.425h23.149c11.28 0 20.426-9.145 20.426-20.425s-9.145-20.426-20.426-20.426zM423.489 325.206H400.34c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.425 20.426 20.425h23.149c11.28 0 20.426-9.145 20.426-20.425s-9.145-20.426-20.426-20.426zM345.872 329.291H167.489c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h178.383c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426z"/></svg>;
function scrollToBottom(el) {
var y = el.box(#height, #content) - el.box(#height, #client);

View File

@@ -139,11 +139,22 @@ class Header: Reactor.Component {
<span #chat>{svg_chat}</span>
<span #action>{svg_action}</span>
<span #display>{svg_display}</span>
<span #keyboard>{svg_keyboard}</span>
{this.renderKeyboardPop()}
{this.renderDisplayPop()}
{this.renderActionPop()}
</div>;
}
function renderKeyboardPop(){
return <popup>
<menu.context #keyboard-options>
<li #legacy><span>{svg_checkmark}</span>{translate('Legacy mode')}</li>
<li #map><span>{svg_checkmark}</span>{translate('Map mode')}</li>
</menu>
</popup>;
}
function renderDisplayPop() {
var codecs = handler.supported_hwcodec();
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
@@ -263,6 +274,11 @@ class Header: Reactor.Component {
me.popup(menu);
}
event click $(#keyboard) (_, me) {
var menu = $(menu#keyboard-options);
me.popup(menu);
}
event click $(#screen) (_, me) {
if (pi.current_display == me.index) return;
handler.switch_display(me.index);
@@ -352,6 +368,17 @@ class Header: Reactor.Component {
toggleMenuState();
}
}
event click $(menu#keyboard-options>li) (_, me) {
if (me.id == "legacy") {
handler.save_keyboard_mode("legacy");
} else if (me.id == "map") {
handler.save_keyboard_mode("map");
} else if (me.id == "translate") {
handler.save_keyboard_mode("translate");
}
toggleMenuState()
}
}
function handle_custom_image_quality() {
@@ -375,12 +402,17 @@ function toggleMenuState() {
var s = handler.get_view_style();
if (!s) s = "original";
values.push(s);
var k = handler.get_keyboard_mode();
values.push(k);
var c = handler.get_option("codec-preference");
if (!c) c = "auto";
values.push(c);
for (var el in $$(menu#display-options li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
for (var el in $$(menu#keyboard-options>li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
var el = self.select('#' + id);
if (el) {

View File

@@ -1,7 +1,7 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{atomic::Ordering, Arc, Mutex},
sync::{Arc, Mutex},
};
use sciter::{
@@ -28,7 +28,7 @@ use hbb_common::{
use crate::clipboard_file::*;
use crate::{
client::*,
ui_session_interface::{InvokeUiSession, Session, IS_IN},
ui_session_interface::{InvokeUiSession, Session},
};
type Video = AssetPtr<video_destination>;
@@ -399,6 +399,8 @@ impl sciter::EventHandler for SciterSession {
fn get_remember();
fn peer_platform();
fn set_write_override(i32, i32, bool, bool, bool);
fn get_keyboard_mode();
fn save_keyboard_mode(String);
fn has_hwcodec();
fn supported_hwcodec();
fn change_prefer_codec();
@@ -560,18 +562,6 @@ impl SciterSession {
self.close_state.insert(k, v);
}
fn enter(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
IS_IN.store(true, Ordering::SeqCst);
}
fn leave(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(false);
IS_IN.store(false, Ordering::SeqCst);
}
fn get_key_event(&self, down_or_up: i32, name: &str, code: i32) -> Option<KeyEvent> {
let mut key_event = KeyEvent::new();
if down_or_up == 2 {
@@ -732,4 +722,4 @@ pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
m.set_item("num_entries", entries.len() as i32);
m.set_item("total_size", n as f64);
m
}
}

View File

@@ -683,6 +683,7 @@ pub fn check_super_user_permission() -> bool {
true
}
#[allow(dead_code)]
pub fn check_zombie(childs: Childs) {
let mut deads = Vec::new();
loop {
@@ -714,7 +715,7 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
// notice: avoiding create ipc connecton repeatly,
// because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")]
async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
pub(crate) async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
let mut key_confirmed = false;
let mut rx = rx;
let mut mouse_time = 0;

View File

@@ -6,18 +6,16 @@ use crate::client::{
load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler,
QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED,
};
use crate::common;
use crate::{client::Data, client::Interface};
use async_trait::async_trait;
use hbb_common::config::{Config, LocalConfig, PeerConfig};
use hbb_common::rendezvous_proto::ConnType;
use hbb_common::tokio::{self, sync::mpsc};
use rdev::{Event, EventType::*, Key as RdevKey, Keyboard as RdevKeyboard, KeyboardState};
use hbb_common::{allow_err, message_proto::*};
use hbb_common::{fs, get_version_number, log, Stream};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@@ -29,6 +27,12 @@ static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
#[cfg(windows)]
static mut IS_ALT_GR: bool = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref TO_RELEASE: Arc<Mutex<HashSet<RdevKey>>> = Arc::new(Mutex::new(HashSet::<RdevKey>::new()));
static ref KEYBOARD: Arc<Mutex<RdevKeyboard>> = Arc::new(Mutex::new(RdevKeyboard::new().unwrap()));
}
#[derive(Clone, Default)]
pub struct Session<T: InvokeUiSession> {
pub cmd: String,
@@ -49,12 +53,21 @@ impl<T: InvokeUiSession> Session<T> {
pub fn get_image_quality(&self) -> String {
self.lc.read().unwrap().image_quality.clone()
}
/// Get custom image quality.
pub fn get_custom_image_quality(&self) -> Vec<i32> {
self.lc.read().unwrap().custom_image_quality.clone()
}
pub fn get_keyboard_mode(&self) -> String {
return std::env::var("KEYBOARD_MODE")
.unwrap_or(String::from("legacy"))
.to_lowercase();
}
pub fn save_keyboard_mode(&self, value: String) {
std::env::set_var("KEYBOARD_MODE", value);
}
pub fn save_view_style(&mut self, value: String) {
self.lc.write().unwrap().save_view_style(value);
}
@@ -233,25 +246,144 @@ impl<T: InvokeUiSession> Session<T> {
if self.peer_platform() == "Windows" {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::CtrlAltDel);
self.key_down_or_up(1, key_event, false, false, false, false);
// todo
key_event.down = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
} else {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::Delete);
self.key_down_or_up(3, key_event, true, true, false, false);
self.legacy_modifiers(&mut key_event, true, true, false, false);
// todo
key_event.press = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
}
}
pub fn key_down_or_up(
fn send_key_event(&self, mut evt: KeyEvent, keyboard_mode: KeyboardMode) {
// mode: legacy(0), map(1), translate(2), auto(3)
evt.mode = keyboard_mode.into();
let mut msg_out = Message::new();
msg_out.set_key_event(evt);
self.send(Data::Message(msg_out));
}
#[allow(dead_code)]
fn convert_numpad_keys(&self, key: RdevKey) -> RdevKey {
if get_key_state(enigo::Key::NumLock) {
return key;
}
match key {
RdevKey::Kp0 => RdevKey::Insert,
RdevKey::KpDecimal => RdevKey::Delete,
RdevKey::Kp1 => RdevKey::End,
RdevKey::Kp2 => RdevKey::DownArrow,
RdevKey::Kp3 => RdevKey::PageDown,
RdevKey::Kp4 => RdevKey::LeftArrow,
RdevKey::Kp5 => RdevKey::Clear,
RdevKey::Kp6 => RdevKey::RightArrow,
RdevKey::Kp7 => RdevKey::Home,
RdevKey::Kp8 => RdevKey::UpArrow,
RdevKey::Kp9 => RdevKey::PageUp,
_ => key,
}
}
fn map_keyboard_mode(&self, down_or_up: bool, key: RdevKey, _evt: Option<Event>) {
// map mode(1): Send keycode according to the peer platform.
#[cfg(target_os = "windows")]
let key = if let Some(e) = _evt {
rdev::get_win_key(e.code.into(), e.scan_code)
} else {
key
};
#[cfg(not(windows))]
let key = self.convert_numpad_keys(key);
let peer = self.peer_platform();
let mut key_event = KeyEvent::new();
// According to peer platform.
let keycode: u32 = if peer == "Linux" {
rdev::linux_keycode_from_key(key).unwrap_or_default().into()
} else if peer == "Windows" {
rdev::win_keycode_from_key(key).unwrap_or_default().into()
} else {
// Without Clear Key on Mac OS
if key == rdev::Key::Clear {
return;
}
rdev::macos_keycode_from_key(key).unwrap_or_default().into()
};
key_event.set_chr(keycode);
key_event.down = down_or_up;
if get_key_state(enigo::Key::CapsLock) {
key_event.modifiers.push(ControlKey::CapsLock.into());
}
if get_key_state(enigo::Key::NumLock) {
key_event.modifiers.push(ControlKey::NumLock.into());
}
self.send_key_event(key_event, KeyboardMode::Map);
}
fn translate_keyboard_mode(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// translate mode(2): locally generated characters are send to the peer.
// get char
let string = match KEYBOARD.lock() {
Ok(mut keyboard) => {
let string = keyboard.add(&evt.event_type).unwrap_or_default();
if keyboard.is_dead() && string == "" && down_or_up == true {
return;
}
string
}
Err(_) => "".to_owned(),
};
// maybe two string
let chars = if string == "" {
None
} else {
let chars: Vec<char> = string.chars().collect();
Some(chars)
};
if let Some(chars) = chars {
for chr in chars {
let mut key_event = KeyEvent::new();
key_event.set_chr(chr as _);
key_event.down = true;
key_event.press = false;
self.send_key_event(key_event, KeyboardMode::Translate);
}
} else {
let success = if down_or_up == true {
TO_RELEASE.lock().unwrap().insert(key)
} else {
TO_RELEASE.lock().unwrap().remove(&key)
};
// AltGr && LeftControl(SpecialKey) without action
if key == RdevKey::AltGr || evt.scan_code == 541 {
return;
}
if success {
self.map_keyboard_mode(down_or_up, key, None);
}
}
}
fn legacy_modifiers(
&self,
down_or_up: i32,
evt: KeyEvent,
key_event: &mut KeyEvent,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
let mut key_event = evt;
if alt
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
@@ -276,25 +408,253 @@ impl<T: InvokeUiSession> Session<T> {
{
key_event.modifiers.push(ControlKey::Meta.into());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if get_key_state(enigo::Key::CapsLock) {
key_event.modifiers.push(ControlKey::CapsLock.into());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.peer_platform() != "Mac OS" {
if get_key_state(enigo::Key::NumLock) && common::valid_for_numlock(&key_event) {
if get_key_state(enigo::Key::NumLock) {
key_event.modifiers.push(ControlKey::NumLock.into());
}
}
if down_or_up == 1 {
key_event.down = true;
} else if down_or_up == 3 {
key_event.press = true;
}
fn legacy_keyboard_mode(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// legacy mode(0): Generate characters locally, look for keycode on other side.
let peer = self.peer_platform();
let is_win = peer == "Windows";
let alt = get_key_state(enigo::Key::Alt);
#[cfg(windows)]
let ctrl = {
let mut tmp =
get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl);
unsafe {
if IS_ALT_GR {
if alt || key == RdevKey::AltGr {
if tmp {
tmp = false;
}
} else {
IS_ALT_GR = false;
}
}
}
tmp
};
#[cfg(not(windows))]
let ctrl = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl);
let shift = get_key_state(enigo::Key::Shift) || get_key_state(enigo::Key::RightShift);
#[cfg(windows)]
let command = crate::platform::windows::get_win_key_state();
#[cfg(not(windows))]
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
RdevKey::Alt => Some(ControlKey::Alt),
RdevKey::AltGr => Some(ControlKey::RAlt),
RdevKey::Backspace => Some(ControlKey::Backspace),
RdevKey::ControlLeft => {
// when pressing AltGr, an extra VK_LCONTROL with a special
// scancode with bit 9 set is sent, let's ignore this.
#[cfg(windows)]
if evt.scan_code & 0x200 != 0 {
unsafe {
IS_ALT_GR = true;
}
return;
}
Some(ControlKey::Control)
}
RdevKey::ControlRight => Some(ControlKey::RControl),
RdevKey::DownArrow => Some(ControlKey::DownArrow),
RdevKey::Escape => Some(ControlKey::Escape),
RdevKey::F1 => Some(ControlKey::F1),
RdevKey::F10 => Some(ControlKey::F10),
RdevKey::F11 => Some(ControlKey::F11),
RdevKey::F12 => Some(ControlKey::F12),
RdevKey::F2 => Some(ControlKey::F2),
RdevKey::F3 => Some(ControlKey::F3),
RdevKey::F4 => Some(ControlKey::F4),
RdevKey::F5 => Some(ControlKey::F5),
RdevKey::F6 => Some(ControlKey::F6),
RdevKey::F7 => Some(ControlKey::F7),
RdevKey::F8 => Some(ControlKey::F8),
RdevKey::F9 => Some(ControlKey::F9),
RdevKey::LeftArrow => Some(ControlKey::LeftArrow),
RdevKey::MetaLeft => Some(ControlKey::Meta),
RdevKey::MetaRight => Some(ControlKey::RWin),
RdevKey::Return => Some(ControlKey::Return),
RdevKey::RightArrow => Some(ControlKey::RightArrow),
RdevKey::ShiftLeft => Some(ControlKey::Shift),
RdevKey::ShiftRight => Some(ControlKey::RShift),
RdevKey::Space => Some(ControlKey::Space),
RdevKey::Tab => Some(ControlKey::Tab),
RdevKey::UpArrow => Some(ControlKey::UpArrow),
RdevKey::Delete => {
if is_win && ctrl && alt {
self.ctrl_alt_del();
return;
}
Some(ControlKey::Delete)
}
RdevKey::Apps => Some(ControlKey::Apps),
RdevKey::Cancel => Some(ControlKey::Cancel),
RdevKey::Clear => Some(ControlKey::Clear),
RdevKey::Kana => Some(ControlKey::Kana),
RdevKey::Hangul => Some(ControlKey::Hangul),
RdevKey::Junja => Some(ControlKey::Junja),
RdevKey::Final => Some(ControlKey::Final),
RdevKey::Hanja => Some(ControlKey::Hanja),
RdevKey::Hanji => Some(ControlKey::Hanja),
RdevKey::Convert => Some(ControlKey::Convert),
RdevKey::Print => Some(ControlKey::Print),
RdevKey::Select => Some(ControlKey::Select),
RdevKey::Execute => Some(ControlKey::Execute),
RdevKey::PrintScreen => Some(ControlKey::Snapshot),
RdevKey::Help => Some(ControlKey::Help),
RdevKey::Sleep => Some(ControlKey::Sleep),
RdevKey::Separator => Some(ControlKey::Separator),
RdevKey::KpReturn => Some(ControlKey::NumpadEnter),
RdevKey::Kp0 => Some(ControlKey::Numpad0),
RdevKey::Kp1 => Some(ControlKey::Numpad1),
RdevKey::Kp2 => Some(ControlKey::Numpad2),
RdevKey::Kp3 => Some(ControlKey::Numpad3),
RdevKey::Kp4 => Some(ControlKey::Numpad4),
RdevKey::Kp5 => Some(ControlKey::Numpad5),
RdevKey::Kp6 => Some(ControlKey::Numpad6),
RdevKey::Kp7 => Some(ControlKey::Numpad7),
RdevKey::Kp8 => Some(ControlKey::Numpad8),
RdevKey::Kp9 => Some(ControlKey::Numpad9),
RdevKey::KpDivide => Some(ControlKey::Divide),
RdevKey::KpMultiply => Some(ControlKey::Multiply),
RdevKey::KpDecimal => Some(ControlKey::Decimal),
RdevKey::KpMinus => Some(ControlKey::Subtract),
RdevKey::KpPlus => Some(ControlKey::Add),
RdevKey::CapsLock | RdevKey::NumLock | RdevKey::ScrollLock => {
return;
}
RdevKey::Home => Some(ControlKey::Home),
RdevKey::End => Some(ControlKey::End),
RdevKey::Insert => Some(ControlKey::Insert),
RdevKey::PageUp => Some(ControlKey::PageUp),
RdevKey::PageDown => Some(ControlKey::PageDown),
RdevKey::Pause => Some(ControlKey::Pause),
_ => None,
};
let mut key_event = KeyEvent::new();
if let Some(k) = control_key {
key_event.set_control_key(k);
} else {
let mut chr = match evt.name {
Some(ref s) => {
if s.len() <= 2 {
// exclude chinese characters
s.chars().next().unwrap_or('\0')
} else {
'\0'
}
}
_ => '\0',
};
if chr == '·' {
// special for Chinese
chr = '`';
}
if chr == '\0' {
chr = match key {
RdevKey::Num1 => '1',
RdevKey::Num2 => '2',
RdevKey::Num3 => '3',
RdevKey::Num4 => '4',
RdevKey::Num5 => '5',
RdevKey::Num6 => '6',
RdevKey::Num7 => '7',
RdevKey::Num8 => '8',
RdevKey::Num9 => '9',
RdevKey::Num0 => '0',
RdevKey::KeyA => 'a',
RdevKey::KeyB => 'b',
RdevKey::KeyC => 'c',
RdevKey::KeyD => 'd',
RdevKey::KeyE => 'e',
RdevKey::KeyF => 'f',
RdevKey::KeyG => 'g',
RdevKey::KeyH => 'h',
RdevKey::KeyI => 'i',
RdevKey::KeyJ => 'j',
RdevKey::KeyK => 'k',
RdevKey::KeyL => 'l',
RdevKey::KeyM => 'm',
RdevKey::KeyN => 'n',
RdevKey::KeyO => 'o',
RdevKey::KeyP => 'p',
RdevKey::KeyQ => 'q',
RdevKey::KeyR => 'r',
RdevKey::KeyS => 's',
RdevKey::KeyT => 't',
RdevKey::KeyU => 'u',
RdevKey::KeyV => 'v',
RdevKey::KeyW => 'w',
RdevKey::KeyX => 'x',
RdevKey::KeyY => 'y',
RdevKey::KeyZ => 'z',
RdevKey::Comma => ',',
RdevKey::Dot => '.',
RdevKey::SemiColon => ';',
RdevKey::Quote => '\'',
RdevKey::LeftBracket => '[',
RdevKey::RightBracket => ']',
RdevKey::BackSlash => '\\',
RdevKey::Minus => '-',
RdevKey::Equal => '=',
RdevKey::BackQuote => '`',
_ => '\0',
}
}
if chr != '\0' {
if chr == 'l' && is_win && command {
self.lock_screen();
return;
}
key_event.set_chr(chr as _);
} else {
log::error!("Unknown key {:?}", evt);
return;
}
}
self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
if down_or_up == true {
key_event.down = true;
}
self.send_key_event(key_event, KeyboardMode::Legacy)
}
fn key_down_or_up(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// Call different functions according to keyboard mode.
let mode = match self.get_keyboard_mode().as_str() {
"map" => KeyboardMode::Map,
"legacy" => KeyboardMode::Legacy,
"translate" => KeyboardMode::Translate,
_ => KeyboardMode::Legacy,
};
match mode {
KeyboardMode::Map => {
if down_or_up == true {
TO_RELEASE.lock().unwrap().insert(key);
} else {
TO_RELEASE.lock().unwrap().remove(&key);
}
self.map_keyboard_mode(down_or_up, key, Some(evt));
}
KeyboardMode::Legacy => self.legacy_keyboard_mode(down_or_up, key, evt),
KeyboardMode::Translate => {
self.translate_keyboard_mode(down_or_up, key, evt);
}
_ => self.legacy_keyboard_mode(down_or_up, key, evt),
}
let mut msg_out = Message::new();
msg_out.set_key_event(key_event);
log::debug!("{:?}", msg_out);
self.send(Data::Message(msg_out));
}
pub fn get_platform(&self, is_remote: bool) -> String {
@@ -350,7 +710,59 @@ impl<T: InvokeUiSession> Session<T> {
pub fn lock_screen(&self) {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::LockScreen);
self.key_down_or_up(1, key_event, false, false, false, false);
// todo
key_event.down = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
}
pub fn enter(&self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
IS_IN.store(true, Ordering::SeqCst);
}
pub fn leave(&self) {
for key in TO_RELEASE.lock().unwrap().iter() {
self.map_keyboard_mode(false, *key, None)
}
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(false);
IS_IN.store(false, Ordering::SeqCst);
}
pub fn handle_flutter_key_event(
&self,
name: &str,
keycode: i32,
scancode: i32,
down_or_up: bool,
) {
if scancode < 0 || keycode < 0 {
return;
}
let keycode: u32 = keycode as u32;
let scancode: u32 = scancode as u32;
#[cfg(not(target_os = "windows"))]
let key = rdev::key_from_scancode(scancode) as RdevKey;
// Windows requires special handling
#[cfg(target_os = "windows")]
let key = rdev::get_win_key(keycode, scancode);
let event_type = if down_or_up {
KeyPress(key)
} else {
KeyRelease(key)
};
let evt = Event {
time: std::time::SystemTime::now(),
name: Option::Some(name.to_owned()),
code: keycode as _,
scan_code: scancode as _,
event_type: event_type,
};
self.key_down_or_up(down_or_up, key, evt)
}
// flutter only TODO new input
@@ -426,7 +838,14 @@ impl<T: InvokeUiSession> Session<T> {
}
}
self.key_down_or_up(v, key_event, alt, ctrl, shift, command);
self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
if v == 1 {
key_event.down = true;
} else if v == 3 {
key_event.press = true;
}
self.send_key_event(key_event, KeyboardMode::Legacy);
}
pub fn send_mouse(
@@ -752,222 +1171,52 @@ impl<T: InvokeUiSession> Session<T> {
}
log::info!("keyboard hooked");
let me = self.clone();
let peer = self.peer_platform();
let is_win = peer == "Windows";
#[cfg(windows)]
crate::platform::windows::enable_lowlevel_keyboard(std::ptr::null_mut() as _);
std::thread::spawn(move || {
// This will block.
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
use rdev::{EventType::*, *};
std::env::set_var("KEYBOARD_ONLY", "y");
lazy_static::lazy_static! {
static ref MUTEX_SPECIAL_KEYS: Mutex<HashMap<RdevKey, bool>> = {
let mut m = HashMap::new();
m.insert(RdevKey::ShiftLeft, false);
m.insert(RdevKey::ShiftRight, false);
m.insert(RdevKey::ControlLeft, false);
m.insert(RdevKey::ControlRight, false);
m.insert(RdevKey::Alt, false);
m.insert(RdevKey::AltGr, false);
m.insert(RdevKey::MetaLeft, false);
m.insert(RdevKey::MetaRight, false);
Mutex::new(m)
};
}
let func = move |evt: Event| {
if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
{
return;
}
let (key, down) = match evt.event_type {
KeyPress(k) => (k, 1),
KeyRelease(k) => (k, 0),
let (_key, down) = match evt.event_type {
KeyPress(k) => {
// keyboard long press
if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) {
if *MUTEX_SPECIAL_KEYS.lock().unwrap().get(&k).unwrap() {
return;
}
MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, true);
}
(k, true)
}
KeyRelease(k) => {
// keyboard long press
if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) {
MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, false);
}
(k, false)
}
_ => return,
};
let alt = get_key_state(enigo::Key::Alt);
#[cfg(windows)]
let ctrl = {
let mut tmp = get_key_state(enigo::Key::Control);
unsafe {
if IS_ALT_GR {
if alt || key == Key::AltGr {
if tmp {
tmp = false;
}
} else {
IS_ALT_GR = false;
}
}
}
tmp
};
#[cfg(not(windows))]
let ctrl = get_key_state(enigo::Key::Control);
let shift = get_key_state(enigo::Key::Shift);
#[cfg(windows)]
let command = crate::platform::windows::get_win_key_state();
#[cfg(not(windows))]
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
Key::Alt => Some(ControlKey::Alt),
Key::AltGr => Some(ControlKey::RAlt),
Key::Backspace => Some(ControlKey::Backspace),
Key::ControlLeft => {
// when pressing AltGr, an extra VK_LCONTROL with a special
// scancode with bit 9 set is sent, let's ignore this.
#[cfg(windows)]
if evt.scan_code & 0x200 != 0 {
unsafe {
IS_ALT_GR = true;
}
return;
}
Some(ControlKey::Control)
}
Key::ControlRight => Some(ControlKey::RControl),
Key::DownArrow => Some(ControlKey::DownArrow),
Key::Escape => Some(ControlKey::Escape),
Key::F1 => Some(ControlKey::F1),
Key::F10 => Some(ControlKey::F10),
Key::F11 => Some(ControlKey::F11),
Key::F12 => Some(ControlKey::F12),
Key::F2 => Some(ControlKey::F2),
Key::F3 => Some(ControlKey::F3),
Key::F4 => Some(ControlKey::F4),
Key::F5 => Some(ControlKey::F5),
Key::F6 => Some(ControlKey::F6),
Key::F7 => Some(ControlKey::F7),
Key::F8 => Some(ControlKey::F8),
Key::F9 => Some(ControlKey::F9),
Key::LeftArrow => Some(ControlKey::LeftArrow),
Key::MetaLeft => Some(ControlKey::Meta),
Key::MetaRight => Some(ControlKey::RWin),
Key::Return => Some(ControlKey::Return),
Key::RightArrow => Some(ControlKey::RightArrow),
Key::ShiftLeft => Some(ControlKey::Shift),
Key::ShiftRight => Some(ControlKey::RShift),
Key::Space => Some(ControlKey::Space),
Key::Tab => Some(ControlKey::Tab),
Key::UpArrow => Some(ControlKey::UpArrow),
Key::Delete => {
if is_win && ctrl && alt {
me.ctrl_alt_del();
return;
}
Some(ControlKey::Delete)
}
Key::Apps => Some(ControlKey::Apps),
Key::Cancel => Some(ControlKey::Cancel),
Key::Clear => Some(ControlKey::Clear),
Key::Kana => Some(ControlKey::Kana),
Key::Hangul => Some(ControlKey::Hangul),
Key::Junja => Some(ControlKey::Junja),
Key::Final => Some(ControlKey::Final),
Key::Hanja => Some(ControlKey::Hanja),
Key::Hanji => Some(ControlKey::Hanja),
Key::Convert => Some(ControlKey::Convert),
Key::Print => Some(ControlKey::Print),
Key::Select => Some(ControlKey::Select),
Key::Execute => Some(ControlKey::Execute),
Key::PrintScreen => Some(ControlKey::Snapshot),
Key::Help => Some(ControlKey::Help),
Key::Sleep => Some(ControlKey::Sleep),
Key::Separator => Some(ControlKey::Separator),
Key::KpReturn => Some(ControlKey::NumpadEnter),
Key::Kp0 => Some(ControlKey::Numpad0),
Key::Kp1 => Some(ControlKey::Numpad1),
Key::Kp2 => Some(ControlKey::Numpad2),
Key::Kp3 => Some(ControlKey::Numpad3),
Key::Kp4 => Some(ControlKey::Numpad4),
Key::Kp5 => Some(ControlKey::Numpad5),
Key::Kp6 => Some(ControlKey::Numpad6),
Key::Kp7 => Some(ControlKey::Numpad7),
Key::Kp8 => Some(ControlKey::Numpad8),
Key::Kp9 => Some(ControlKey::Numpad9),
Key::KpDivide => Some(ControlKey::Divide),
Key::KpMultiply => Some(ControlKey::Multiply),
Key::KpDecimal => Some(ControlKey::Decimal),
Key::KpMinus => Some(ControlKey::Subtract),
Key::KpPlus => Some(ControlKey::Add),
Key::CapsLock | Key::NumLock | Key::ScrollLock => {
return;
}
Key::Home => Some(ControlKey::Home),
Key::End => Some(ControlKey::End),
Key::Insert => Some(ControlKey::Insert),
Key::PageUp => Some(ControlKey::PageUp),
Key::PageDown => Some(ControlKey::PageDown),
Key::Pause => Some(ControlKey::Pause),
_ => None,
};
let mut key_event = KeyEvent::new();
if let Some(k) = control_key {
key_event.set_control_key(k);
} else {
let mut chr = match evt.name {
Some(ref s) => {
if s.len() <= 2 {
// exclude chinese characters
s.chars().next().unwrap_or('\0')
} else {
'\0'
}
}
_ => '\0',
};
if chr == '·' {
// special for Chinese
chr = '`';
}
if chr == '\0' {
chr = match key {
Key::Num1 => '1',
Key::Num2 => '2',
Key::Num3 => '3',
Key::Num4 => '4',
Key::Num5 => '5',
Key::Num6 => '6',
Key::Num7 => '7',
Key::Num8 => '8',
Key::Num9 => '9',
Key::Num0 => '0',
Key::KeyA => 'a',
Key::KeyB => 'b',
Key::KeyC => 'c',
Key::KeyD => 'd',
Key::KeyE => 'e',
Key::KeyF => 'f',
Key::KeyG => 'g',
Key::KeyH => 'h',
Key::KeyI => 'i',
Key::KeyJ => 'j',
Key::KeyK => 'k',
Key::KeyL => 'l',
Key::KeyM => 'm',
Key::KeyN => 'n',
Key::KeyO => 'o',
Key::KeyP => 'p',
Key::KeyQ => 'q',
Key::KeyR => 'r',
Key::KeyS => 's',
Key::KeyT => 't',
Key::KeyU => 'u',
Key::KeyV => 'v',
Key::KeyW => 'w',
Key::KeyX => 'x',
Key::KeyY => 'y',
Key::KeyZ => 'z',
Key::Comma => ',',
Key::Dot => '.',
Key::SemiColon => ';',
Key::Quote => '\'',
Key::LeftBracket => '[',
Key::RightBracket => ']',
Key::BackSlash => '\\',
Key::Minus => '-',
Key::Equal => '=',
Key::BackQuote => '`',
_ => '\0',
}
}
if chr != '\0' {
if chr == 'l' && is_win && command {
me.lock_screen();
return;
}
key_event.set_chr(chr as _);
} else {
log::error!("Unknown key {:?}", evt);
return;
}
}
me.key_down_or_up(down, key_event, alt, ctrl, shift, command); // TODO
me.key_down_or_up(down, _key, evt);
};
if let Err(error) = rdev::listen(func) {
log::error!("rdev: {:?}", error);