mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge pull request #964 from asur4s/master
Feat: Support new keyboard mode
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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", "1:1传输"),
|
||||
("Translate mode", "翻译模式"),
|
||||
("Use temporary password", "使用临时密码"),
|
||||
("Use permanent password", "使用固定密码"),
|
||||
("Use both passwords", "同时使用两种密码"),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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", "どちらのパスワードも使用"),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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", "Использовать оба пароля"),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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", ""),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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", "1:1傳輸"),
|
||||
("Translate mode", "翻譯模式"),
|
||||
("Use temporary password", "使用臨時密碼"),
|
||||
("Use permanent password", "使用固定密碼"),
|
||||
("Use both passwords", "同時使用兩種密碼"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
28
src/ui.rs
28
src/ui.rs
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user