diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 2ae5b96c4..c1a7bdce2 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1189,11 +1189,12 @@ class _RemoteMenubarState extends State { MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'), MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), ], - curOptionGetter: () async => - await bind.sessionGetKeyboardName(id: widget.id), + curOptionGetter: () async { + return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy"; + }, optionSetter: (String oldValue, String newValue) async { - await bind.sessionSetKeyboardMode( - id: widget.id, keyboardMode: newValue); + await bind.sessionSetKeyboardMode(id: widget.id, value: newValue); + widget.ffi.canvasModel.updateViewStyle(); }, ) ]; diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index fcfb8ad60..d0388b8fe 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -692,10 +692,11 @@ class _RemotePageState extends State { } void changePhysicalKeyboardInputMode() async { - var current = await bind.sessionGetKeyboardName(id: widget.id); + var current = await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy"; gFFI.dialogManager.show((setState, close) { void setMode(String? v) async { - await bind.sessionSetKeyboardMode(id: widget.id, keyboardMode: v ?? ''); + await bind.sessionPeerOption( + id: widget.id, name: "keyboard-mode", value: v ?? ""); setState(() => current = v ?? ''); Future.delayed(Duration(milliseconds: 300), close); } diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index bd1131c7a..b488f30f3 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -54,7 +54,7 @@ class InputModel { InputModel(this.parent); KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) { - bind.sessionGetKeyboardName(id: id).then((result) { + bind.sessionGetKeyboardMode(id: id).then((result) { keyboardMode = result.toString(); }); diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 625862dbf..d79ff0595 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -7,7 +7,7 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "50.0.0" + version: "49.0.0" after_layout: dependency: transitive description: @@ -21,7 +21,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "5.2.0" + version: "5.1.0" animations: dependency: transitive description: @@ -35,7 +35,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.3.5" + version: "3.3.1" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: back_button_interceptor url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.1" bot_toast: dependency: "direct main" description: @@ -84,7 +84,7 @@ packages: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" build_daemon: dependency: transitive description: @@ -105,14 +105,14 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.3.2" + version: "2.2.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "7.2.7" + version: "7.2.4" built_collection: dependency: transitive description: @@ -126,7 +126,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.4.2" + version: "8.4.1" cached_network_image: dependency: transitive description: @@ -203,7 +203,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.0.2" cross_file: dependency: transitive description: @@ -352,7 +352,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "5.2.2" + version: "5.2.1" fixnum: dependency: transitive description: @@ -443,7 +443,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.5" flutter_web_plugins: dependency: transitive description: flutter @@ -455,21 +455,21 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.1.1" freezed_annotation: dependency: "direct main" description: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.1.0" frontend_server_client: dependency: transitive description: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "2.1.3" get: dependency: "direct main" description: @@ -483,21 +483,21 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.0" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.1.0" html: dependency: transitive description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.15.1" + version: "0.15.0" http: dependency: "direct main" description: @@ -518,7 +518,7 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "4.0.1" icons_launcher: dependency: "direct dev" description: @@ -532,7 +532,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "3.2.2" + version: "3.2.0" image_picker: dependency: "direct main" description: @@ -602,7 +602,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.0" logging: dependency: transitive description: @@ -735,7 +735,7 @@ packages: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.22" + version: "2.0.20" path_provider_ios: dependency: transitive description: @@ -799,13 +799,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" - pointycastle: - dependency: transitive - description: - name: pointycastle - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.2" pool: dependency: transitive description: @@ -826,14 +819,14 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.4" + version: "6.0.3" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.1" pubspec_parse: dependency: transitive description: @@ -854,7 +847,7 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.27.7" + version: "0.27.5" screen_retriever: dependency: transitive description: @@ -891,7 +884,7 @@ packages: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.2" simple_observable: dependency: transitive description: @@ -910,7 +903,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.2.6" + version: "1.2.5" source_span: dependency: transitive description: @@ -931,7 +924,7 @@ packages: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.3.0" stack_trace: dependency: transitive description: @@ -952,7 +945,7 @@ packages: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.0.1" string_scanner: dependency: transitive description: @@ -1043,14 +1036,14 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.7" + version: "6.1.6" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.22" + version: "6.0.19" url_launcher_ios: dependency: transitive description: @@ -1099,7 +1092,7 @@ packages: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "3.0.7" + version: "3.0.6" vector_math: dependency: transitive description: @@ -1113,7 +1106,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.4.8" + version: "2.4.7" video_player_android: dependency: transitive description: @@ -1190,7 +1183,7 @@ packages: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.1" web_socket_channel: dependency: transitive description: @@ -1204,7 +1197,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "3.1.2" + version: "3.0.0" win32_registry: dependency: transitive description: diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 328a1ea59..d4701aada 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -203,6 +203,8 @@ pub struct PeerConfig { pub enable_file_transfer: bool, #[serde(default)] pub show_quality_monitor: bool, + #[serde(default)] + pub keyboard_mode: String, // The other scalar value must before this #[serde(default, deserialize_with = "PeerConfig::deserialize_options")] diff --git a/libs/scrap/src/common/record.rs b/libs/scrap/src/common/record.rs index 83bd9eee7..9f38f2d6a 100644 --- a/libs/scrap/src/common/record.rs +++ b/libs/scrap/src/common/record.rs @@ -12,11 +12,10 @@ use hwcodec::mux::{MuxContext, Muxer}; use std::{ fs::{File, OpenOptions}, io, - time::Instant, -}; -use std::{ ops::{Deref, DerefMut}, path::PathBuf, + sync::mpsc::Sender, + time::Instant, }; use webm::mux::{self, Segment, Track, VideoTrack, Writer}; @@ -31,12 +30,14 @@ pub enum RecordCodecID { #[derive(Debug, Clone)] pub struct RecorderContext { + pub server: bool, pub id: String, pub default_dir: String, pub filename: String, pub width: usize, pub height: usize, pub codec_id: RecordCodecID, + pub tx: Option>, } impl RecorderContext { @@ -52,7 +53,8 @@ impl RecorderContext { std::fs::create_dir_all(&dir)?; } } - let file = self.id.clone() + let file = if self.server { "s" } else { "c" }.to_string() + + &self.id.clone() + &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string() + if self.codec_id == RecordCodecID::VP9 { ".webm" @@ -60,7 +62,7 @@ impl RecorderContext { ".mp4" }; self.filename = PathBuf::from(&dir).join(file).to_string_lossy().to_string(); - log::info!("video save to:{}", self.filename); + log::info!("video will save to:{}", self.filename); Ok(()) } } @@ -75,6 +77,14 @@ pub trait RecorderApi { fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool; } +#[derive(Debug)] +pub enum RecordState { + NewFile(String), + NewFrame, + WriteTail, + RemoveFile, +} + pub struct Recorder { pub inner: Box, ctx: RecorderContext, @@ -110,6 +120,7 @@ impl Recorder { #[cfg(not(feature = "hwcodec"))] _ => bail!("unsupported codec type"), }; + recorder.send_state(RecordState::NewFile(recorder.ctx.filename.clone())); Ok(recorder) } @@ -123,6 +134,7 @@ impl Recorder { _ => bail!("unsupported codec type"), }; self.ctx = ctx; + self.send_state(RecordState::NewFile(self.ctx.filename.clone())); Ok(()) } @@ -171,8 +183,13 @@ impl Recorder { } _ => bail!("unsupported frame type"), } + self.send_state(RecordState::NewFrame); Ok(()) } + + fn send_state(&self, state: RecordState) { + self.ctx.tx.as_ref().map(|tx| tx.send(state)); + } } struct WebmRecorder { @@ -237,9 +254,12 @@ impl RecorderApi for WebmRecorder { impl Drop for WebmRecorder { fn drop(&mut self) { std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None)); + let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { std::fs::remove_file(&self.ctx.filename).ok(); + state = RecordState::RemoveFile; } + self.ctx.tx.as_ref().map(|tx| tx.send(state)); } } @@ -292,8 +312,11 @@ impl RecorderApi for HwRecorder { impl Drop for HwRecorder { fn drop(&mut self) { self.muxer.write_tail().ok(); + let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { std::fs::remove_file(&self.ctx.filename).ok(); + state = RecordState::RemoveFile; } + self.ctx.tx.as_ref().map(|tx| tx.send(state)); } } diff --git a/src/client.rs b/src/client.rs index c646b2b7f..03bbf5918 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,8 +6,6 @@ use cpal::{ }; use magnum_opus::{Channels::*, Decoder as AudioDecoder}; use sha2::{Digest, Sha256}; -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -use std::sync::atomic::Ordering; use std::{ collections::HashMap, net::SocketAddr, @@ -49,10 +47,7 @@ pub use super::lang::*; pub mod file_trait; pub mod helper; pub mod io_loop; -use crate::{ - server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}, - ui_session_interface::global_save_keyboard_mode, -}; +use crate::server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}; pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); @@ -863,12 +858,14 @@ impl VideoHandler { self.record = false; if start { self.recorder = Recorder::new(RecorderContext { + server: false, id, default_dir: crate::ui_interface::default_video_save_directory(), filename: "".to_owned(), width: w as _, height: h as _, codec_id: scrap::record::RecordCodecID::VP9, + tx: None, }) .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))); } else { @@ -987,6 +984,17 @@ impl LoginConfigHandler { self.save_config(config); } + /// Save keyboard mode to the current config. + /// + /// # Arguments + /// + /// * `value` - The view style to be saved. + pub fn save_keyboard_mode(&mut self, value: String) { + let mut config = self.load_config(); + config.keyboard_mode = value; + self.save_config(config); + } + /// Save scroll style to the current config. /// /// # Arguments @@ -1380,9 +1388,6 @@ impl LoginConfigHandler { if !pi.version.is_empty() { self.version = hbb_common::get_version_number(&pi.version); } - if hbb_common::get_version_number(&pi.version) < hbb_common::get_version_number("1.2.0") { - global_save_keyboard_mode("legacy".to_owned()); - } self.features = pi.features.clone().into_option(); let serde = PeerInfoSerde { username: pi.username.clone(), @@ -1405,6 +1410,14 @@ impl LoginConfigHandler { log::debug!("remove password of {}", self.id); } } + if config.keyboard_mode == "" { + if hbb_common::get_version_number(&pi.version) < hbb_common::get_version_number("1.2.0") + { + config.keyboard_mode = "legacy".to_string(); + } else { + config.keyboard_mode = "map".to_string(); + } + } self.conn_id = pi.conn_id; // no matter if change, for update file time self.save_config(config); @@ -2022,8 +2035,3 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8 bail!("Wrong public length"); } } - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -pub fn disable_keyboard_listening() { - crate::ui_session_interface::KEYBOARD_HOOKED.store(true, Ordering::SeqCst); -} diff --git a/src/common.rs b/src/common.rs index ea02cf810..9023780f4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,6 +3,14 @@ use std::{ sync::{Arc, Mutex}, }; +#[derive(Debug, Eq, PartialEq)] +pub enum GrabState { + Ready, + Run, + Wait, + Exit, +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub use arboard::Clipboard as ClipboardContext; @@ -11,7 +19,7 @@ use hbb_common::compress::decompress; use hbb_common::{ allow_err, anyhow::bail, - compress::{compress as compress_func}, + compress::compress as compress_func, config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT}, get_version_number, log, message_proto::*, diff --git a/src/core_main.rs b/src/core_main.rs index d0ce9e0d1..342f438ee 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -217,7 +217,7 @@ pub fn core_main() -> Option> { if crate::platform::is_root() { crate::ipc::set_permanent_password(args[1].to_owned()).unwrap(); } else { - log::info!("Permission denied!"); + println!("Administrative privileges required!"); } } return None; diff --git a/src/flutter.rs b/src/flutter.rs index a2c307f5a..2bb7a9faf 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -422,8 +422,6 @@ pub fn session_start_(id: &str, event_stream: StreamSink) -> ResultTy *session.event_stream.write().unwrap() = Some(event_stream); let session = session.clone(); std::thread::spawn(move || { - // if flutter : disable keyboard listen - crate::client::disable_keyboard_listening(); io_loop(session); }); Ok(()) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 1250f7e19..0c90c3131 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -18,7 +18,7 @@ use hbb_common::{ use crate::flutter::{self, SESSIONS}; use crate::ui_interface::{self, *}; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::ui_session_interface::CUR_SESSION; +use crate::keyboard::CUR_SESSION; use crate::{ client::file_trait::FileManager, flutter::{make_fd_to_json, session_add, session_start_}, @@ -225,6 +225,20 @@ pub fn session_set_image_quality(id: String, value: String) { } } +pub fn session_get_keyboard_mode(id: String) -> Option { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_keyboard_mode()) + } else { + None + } +} + +pub fn session_set_keyboard_mode(id: String, value: String) { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.save_keyboard_mode(value); + } +} + pub fn session_get_custom_image_quality(id: String) -> Option> { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_custom_image_quality()) @@ -282,7 +296,6 @@ pub fn session_enter_or_leave(id: String, enter: bool) { *CUR_SESSION.lock().unwrap() = Some(session.clone()); session.enter(); } else { - *CUR_SESSION.lock().unwrap() = None; session.leave(); } } @@ -299,12 +312,14 @@ pub fn session_input_key( command: bool, ) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { + // #[cfg(any(target_os = "android", target_os = "ios"))] session.input_key(&name, down, press, alt, ctrl, shift, command); } } pub fn session_input_string(id: String, value: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { + // #[cfg(any(target_os = "android", target_os = "ios"))] session.input_string(&value); } } @@ -329,19 +344,6 @@ 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); @@ -1083,8 +1085,7 @@ pub fn main_is_installed() -> SyncReturn { } pub fn main_start_grab_keyboard() { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - crate::ui_session_interface::global_grab_keyboard(); + crate::keyboard::client::start_grab_loop(); } pub fn main_is_installed_lower_version() -> SyncReturn { diff --git a/src/hbbs_http.rs b/src/hbbs_http.rs index ceb3a6081..08ad36eb9 100644 --- a/src/hbbs_http.rs +++ b/src/hbbs_http.rs @@ -4,6 +4,7 @@ use serde_json::{Map, Value}; #[cfg(feature = "flutter")] pub mod account; +pub mod record_upload; #[derive(Debug)] pub enum HbbHttpResponse { diff --git a/src/hbbs_http/record_upload.rs b/src/hbbs_http/record_upload.rs new file mode 100644 index 000000000..93bc745c2 --- /dev/null +++ b/src/hbbs_http/record_upload.rs @@ -0,0 +1,204 @@ +use bytes::Bytes; +use hbb_common::{bail, config::Config, lazy_static, log, ResultType}; +use reqwest::blocking::{Body, Client}; +use scrap::record::RecordState; +use serde::Serialize; +use serde_json::Map; +use std::{ + fs::File, + io::{prelude::*, SeekFrom}, + sync::{mpsc::Receiver, Arc, Mutex}, + time::{Duration, Instant}, +}; + +const MAX_HEADER_LEN: usize = 1024; +const SHOULD_SEND_TIME: Duration = Duration::from_secs(1); +const SHOULD_SEND_SIZE: u64 = 1024 * 1024; + +lazy_static::lazy_static! { + static ref ENABLE: Arc> = Default::default(); +} + +pub fn is_enable() -> bool { + ENABLE.lock().unwrap().clone() +} + +pub fn run(rx: Receiver) { + let mut uploader = RecordUploader { + client: Client::new(), + api_server: crate::get_api_server( + Config::get_option("api-server"), + Config::get_option("custom-rendezvous-server"), + ), + filepath: Default::default(), + filename: Default::default(), + upload_size: Default::default(), + running: Default::default(), + last_send: Instant::now(), + }; + std::thread::spawn(move || loop { + if let Err(e) = match rx.recv() { + Ok(state) => match state { + RecordState::NewFile(filepath) => uploader.handle_new_file(filepath), + RecordState::NewFrame => { + if uploader.running { + uploader.handle_frame(false) + } else { + Ok(()) + } + } + RecordState::WriteTail => { + if uploader.running { + uploader.handle_tail() + } else { + Ok(()) + } + } + RecordState::RemoveFile => { + if uploader.running { + uploader.handle_remove() + } else { + Ok(()) + } + } + }, + Err(e) => { + log::trace!("upload thread stop:{}", e); + break; + } + } { + uploader.running = false; + log::error!("upload stop:{}", e); + } + }); +} + +struct RecordUploader { + client: Client, + api_server: String, + filepath: String, + filename: String, + upload_size: u64, + running: bool, + last_send: Instant, +} +impl RecordUploader { + fn send(&self, query: &Q, body: B) -> ResultType<()> + where + Q: Serialize + ?Sized, + B: Into, + { + match self + .client + .post(format!("{}/api/record", self.api_server)) + .query(query) + .body(body) + .send() + { + Ok(resp) => { + if let Ok(m) = resp.json::>() { + if let Some(e) = m.get("error") { + bail!(e.to_string()); + } + } + Ok(()) + } + Err(e) => bail!(e.to_string()), + } + } + + fn handle_new_file(&mut self, filepath: String) -> ResultType<()> { + match std::path::PathBuf::from(&filepath).file_name() { + Some(filename) => match filename.to_owned().into_string() { + Ok(filename) => { + self.filename = filename.clone(); + self.filepath = filepath.clone(); + self.upload_size = 0; + self.running = true; + self.last_send = Instant::now(); + self.send(&[("type", "new"), ("file", &filename)], Bytes::new())?; + Ok(()) + } + Err(_) => bail!("can't parse filename:{:?}", filename), + }, + None => bail!("can't parse filepath:{}", filepath), + } + } + + fn handle_frame(&mut self, flush: bool) -> ResultType<()> { + if !flush && self.last_send.elapsed() < SHOULD_SEND_TIME { + return Ok(()); + } + match File::open(&self.filepath) { + Ok(mut file) => match file.metadata() { + Ok(m) => { + let len = m.len(); + if len <= self.upload_size { + return Ok(()); + } + if !flush && len - self.upload_size < SHOULD_SEND_SIZE { + return Ok(()); + } + let mut buf = Vec::new(); + match file.seek(SeekFrom::Start(self.upload_size)) { + Ok(_) => match file.read_to_end(&mut buf) { + Ok(length) => { + self.send( + &[ + ("type", "part"), + ("file", &self.filename), + ("offset", &self.upload_size.to_string()), + ("length", &length.to_string()), + ], + buf, + )?; + self.upload_size = len; + self.last_send = Instant::now(); + Ok(()) + } + Err(e) => bail!(e.to_string()), + }, + Err(e) => bail!(e.to_string()), + } + } + Err(e) => bail!(e.to_string()), + }, + Err(e) => bail!(e.to_string()), + } + } + + fn handle_tail(&mut self) -> ResultType<()> { + self.handle_frame(true)?; + match File::open(&self.filepath) { + Ok(mut file) => { + let mut buf = vec![0u8; MAX_HEADER_LEN]; + match file.read(&mut buf) { + Ok(length) => { + buf.truncate(length); + self.send( + &[ + ("type", "tail"), + ("file", &self.filename), + ("offset", "0"), + ("length", &length.to_string()), + ], + buf, + )?; + log::info!("upload success, file:{}", self.filename); + Ok(()) + } + Err(e) => bail!(e.to_string()), + } + } + Err(e) => bail!(e.to_string()), + } + } + + fn handle_remove(&mut self) -> ResultType<()> { + self.send( + &[("type", "remove"), ("file", &self.filename)], + Bytes::new(), + )?; + Ok(()) + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 000000000..f0c14a1e9 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,621 @@ +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::client::get_key_state; +use crate::common::GrabState; +#[cfg(feature = "flutter")] +use crate::flutter::FlutterHandler; +#[cfg(not(feature = "flutter"))] +use crate::ui::remote::SciterHandler; +use crate::ui_session_interface::Session; +use hbb_common::{log, message_proto::*}; +use rdev::{Event, EventType, Key}; +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::AtomicBool; +#[cfg(any(target_os = "windows", target_os = "macos"))] +use std::sync::atomic::Ordering; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::SystemTime; + +static mut IS_ALT_GR: bool = false; +pub static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); + +lazy_static::lazy_static! { + pub static ref GRAB_SENDER: Arc>>> = Default::default(); +} + +#[cfg(feature = "flutter")] +lazy_static::lazy_static! { + pub static ref CUR_SESSION: Arc>>> = Default::default(); +} + +#[cfg(not(feature = "flutter"))] +lazy_static::lazy_static! { + pub static ref CUR_SESSION: Arc>>> = Default::default(); +} + +lazy_static::lazy_static! { + static ref TO_RELEASE: Arc>> = Arc::new(Mutex::new(HashSet::::new())); + static ref MODIFIERS_STATE: Mutex> = { + let mut m = HashMap::new(); + m.insert(Key::ShiftLeft, false); + m.insert(Key::ShiftRight, false); + m.insert(Key::ControlLeft, false); + m.insert(Key::ControlRight, false); + m.insert(Key::Alt, false); + m.insert(Key::AltGr, false); + m.insert(Key::MetaLeft, false); + m.insert(Key::MetaRight, false); + Mutex::new(m) + }; + +} + +pub mod client { + use super::*; + + pub fn get_keyboard_mode() -> String { + if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() { + handler.get_keyboard_mode() + } else { + "legacy".to_string() + } + } + + pub fn start_grab_loop() { + let (sender, receiver) = mpsc::channel::(); + + grab_loop(receiver); + *GRAB_SENDER.lock().unwrap() = Some(sender); + + change_grab_status(GrabState::Ready); + } + + pub fn change_grab_status(state: GrabState) { + if GrabState::Wait == state { + release_remote_keys(); + } + if let Some(sender) = &*GRAB_SENDER.lock().unwrap() { + sender.send(state).ok(); + } + } + + pub fn process_event(event: Event) { + if is_long_press(&event) { + return; + } + let key_event = event_to_key_event(&event); + send_key_event(&key_event); + } + + pub fn get_modifiers_state( + alt: bool, + ctrl: bool, + shift: bool, + command: bool, + ) -> (bool, bool, bool, bool) { + let modifiers_lock = MODIFIERS_STATE.lock().unwrap(); + let ctrl = *modifiers_lock.get(&Key::ControlLeft).unwrap() + || *modifiers_lock.get(&Key::ControlRight).unwrap() + || ctrl; + let shift = *modifiers_lock.get(&Key::ShiftLeft).unwrap() + || *modifiers_lock.get(&Key::ShiftRight).unwrap() + || shift; + let command = *modifiers_lock.get(&Key::MetaLeft).unwrap() + || *modifiers_lock.get(&Key::MetaRight).unwrap() + || command; + let alt = *modifiers_lock.get(&Key::Alt).unwrap() + || *modifiers_lock.get(&Key::AltGr).unwrap() + || alt; + + (alt, ctrl, shift, command) + } + + pub fn legacy_modifiers( + key_event: &mut KeyEvent, + alt: bool, + ctrl: bool, + shift: bool, + command: bool, + ) { + if alt + && !crate::is_control_key(&key_event, &ControlKey::Alt) + && !crate::is_control_key(&key_event, &ControlKey::RAlt) + { + key_event.modifiers.push(ControlKey::Alt.into()); + } + if shift + && !crate::is_control_key(&key_event, &ControlKey::Shift) + && !crate::is_control_key(&key_event, &ControlKey::RShift) + { + key_event.modifiers.push(ControlKey::Shift.into()); + } + if ctrl + && !crate::is_control_key(&key_event, &ControlKey::Control) + && !crate::is_control_key(&key_event, &ControlKey::RControl) + { + key_event.modifiers.push(ControlKey::Control.into()); + } + if command + && !crate::is_control_key(&key_event, &ControlKey::Meta) + && !crate::is_control_key(&key_event, &ControlKey::RWin) + { + key_event.modifiers.push(ControlKey::Meta.into()); + } + } + + pub fn lock_screen() { + let mut key_event = KeyEvent::new(); + key_event.set_control_key(ControlKey::LockScreen); + key_event.down = true; + key_event.mode = KeyboardMode::Legacy.into(); + send_key_event(&key_event); + } + + pub fn ctrl_alt_del() { + let mut key_event = KeyEvent::new(); + if get_peer_platform() == "Windows" { + key_event.set_control_key(ControlKey::CtrlAltDel); + key_event.down = true; + } else { + key_event.set_control_key(ControlKey::Delete); + legacy_modifiers(&mut key_event, true, true, false, false); + key_event.press = true; + } + key_event.mode = KeyboardMode::Legacy.into(); + send_key_event(&key_event); + } +} + +pub fn grab_loop(recv: mpsc::Receiver) { + thread::spawn(move || loop { + if let Some(state) = recv.recv().ok() { + match state { + GrabState::Ready => { + #[cfg(any(target_os = "windows", target_os = "macos"))] + std::thread::spawn(move || { + let func = move |event: Event| match event.event_type { + EventType::KeyPress(key) | EventType::KeyRelease(key) => { + // fix #2211:CAPS LOCK don't work + if key == Key::CapsLock || key == Key::NumLock { + return Some(event); + } + if KEYBOARD_HOOKED.load(Ordering::SeqCst) { + client::process_event(event); + return None; + } else { + return Some(event); + } + } + _ => Some(event), + }; + if let Err(error) = rdev::grab(func) { + log::error!("rdev Error: {:?}", error) + } + }); + + #[cfg(target_os = "linux")] + rdev::start_grab_listen(move |event: Event| match event.event_type { + EventType::KeyPress(key) | EventType::KeyRelease(key) => { + if let Key::Unknown(keycode) = key { + log::error!("rdev get unknown key, keycode is : {:?}", keycode); + } else { + client::process_event(event); + } + None + } + _ => Some(event), + }); + } + GrabState::Run => { + #[cfg(any(target_os = "windows", target_os = "macos"))] + KEYBOARD_HOOKED.swap(true, Ordering::SeqCst); + + #[cfg(target_os = "linux")] + rdev::enable_grab().ok(); + } + GrabState::Wait => { + #[cfg(any(target_os = "windows", target_os = "macos"))] + KEYBOARD_HOOKED.swap(false, Ordering::SeqCst); + + #[cfg(target_os = "linux")] + rdev::disable_grab().ok(); + } + GrabState::Exit => { + #[cfg(target_os = "linux")] + rdev::exit_grab_listen().ok(); + } + } + } + }); +} + +pub fn is_long_press(event: &Event) -> bool { + let keys = MODIFIERS_STATE.lock().unwrap(); + match event.event_type { + EventType::KeyPress(k) => { + if let Some(&state) = keys.get(&k) { + if state == true { + return true; + } + } + } + _ => {} + }; + return false; +} + +pub fn release_remote_keys() { + // todo!: client quit suddenly, how to release keys? + let to_release = TO_RELEASE.lock().unwrap(); + let keys = to_release.iter().map(|&key| key).collect::>(); + drop(to_release); + for key in keys { + let event_type = EventType::KeyRelease(key); + let event = event_type_to_event(event_type); + client::process_event(event); + } +} + +pub fn get_keyboard_mode_enum() -> KeyboardMode { + match client::get_keyboard_mode().as_str() { + "map" => KeyboardMode::Map, + "translate" => KeyboardMode::Translate, + _ => KeyboardMode::Legacy, + } +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn add_numlock_capslock_state(key_event: &mut KeyEvent) { + 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()); + } +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn convert_numpad_keys(key: Key) -> Key { + if get_key_state(enigo::Key::NumLock) { + return key; + } + match key { + Key::Kp0 => Key::Insert, + Key::KpDecimal => Key::Delete, + Key::Kp1 => Key::End, + Key::Kp2 => Key::DownArrow, + Key::Kp3 => Key::PageDown, + Key::Kp4 => Key::LeftArrow, + Key::Kp5 => Key::Clear, + Key::Kp6 => Key::RightArrow, + Key::Kp7 => Key::Home, + Key::Kp8 => Key::UpArrow, + Key::Kp9 => Key::PageUp, + _ => key, + } +} + +fn update_modifiers_state(event: &Event) { + // for mouse + let mut keys = MODIFIERS_STATE.lock().unwrap(); + match event.event_type { + EventType::KeyPress(k) => { + if keys.contains_key(&k) { + keys.insert(k, true); + } + } + EventType::KeyRelease(k) => { + if keys.contains_key(&k) { + keys.insert(k, false); + } + } + _ => {} + }; +} + +pub fn event_to_key_event(event: &Event) -> KeyEvent { + let mut key_event = KeyEvent::new(); + update_modifiers_state(event); + + let mut to_release = TO_RELEASE.lock().unwrap(); + match event.event_type { + EventType::KeyPress(key) => { + to_release.insert(key); + } + EventType::KeyRelease(key) => { + to_release.remove(&key); + } + _ => {} + } + drop(to_release); + + let keyboard_mode = get_keyboard_mode_enum(); + key_event.mode = keyboard_mode.into(); + match keyboard_mode { + KeyboardMode::Map => { + map_keyboard_mode(event, &mut key_event); + } + KeyboardMode::Translate => { + translate_keyboard_mode(event, &mut key_event); + } + _ => { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + legacy_keyboard_mode(event, &mut key_event); + } + }; + #[cfg(not(any(target_os = "android", target_os = "ios")))] + add_numlock_capslock_state(&mut key_event); + + return key_event; +} + +pub fn event_type_to_event(event_type: EventType) -> Event { + Event { + event_type, + time: SystemTime::now(), + name: None, + code: 0, + scan_code: 0, + } +} + +pub fn send_key_event(key_event: &KeyEvent) { + if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() { + handler.send_key_event(key_event); + } +} + +pub fn get_peer_platform() -> String { + if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() { + handler.peer_platform() + } else { + log::error!("get peer platform error"); + "Windows".to_string() + } +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn legacy_keyboard_mode(event: &Event, key_event: &mut KeyEvent) { + // legacy mode(0): Generate characters locally, look for keycode on other side. + let (mut key, down_or_up) = match event.event_type { + EventType::KeyPress(key) => (key, true), + EventType::KeyRelease(key) => (key, false), + _ => { + return; + } + }; + + let peer = get_peer_platform(); + let is_win = peer == "Windows"; + if is_win { + key = convert_numpad_keys(key); + } + + 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 == Key::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 { + 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 event.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 { + client::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, + }; + if let Some(k) = control_key { + key_event.set_control_key(k); + } else { + let mut chr = match event.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::Slash => '/', + Key::BackSlash => '\\', + Key::Minus => '-', + Key::Equal => '=', + Key::BackQuote => '`', + _ => '\0', + } + } + if chr != '\0' { + if chr == 'l' && is_win && command { + client::lock_screen(); + return; + } + key_event.set_chr(chr as _); + } else { + log::error!("Unknown key {:?}", &event); + return; + } + } + let (alt, ctrl, shift, command) = client::get_modifiers_state(alt, ctrl, shift, command); + client::legacy_modifiers(key_event, alt, ctrl, shift, command); + + if down_or_up == true { + key_event.down = true; + } +} + +pub fn map_keyboard_mode(event: &Event, key_event: &mut KeyEvent) { + let peer = get_peer_platform(); + + let key = match event.event_type { + EventType::KeyPress(key) => { + key_event.down = true; + key + } + EventType::KeyRelease(key) => { + key_event.down = false; + key + } + _ => return, + }; + let keycode: u32 = match peer.as_str() { + "Windows" => rdev::win_keycode_from_key(key).unwrap_or_default().into(), + "MacOS" => rdev::macos_keycode_from_key(key).unwrap_or_default().into(), + _ => rdev::linux_keycode_from_key(key).unwrap_or_default().into(), + }; + key_event.set_chr(keycode); +} + +pub fn translate_keyboard_mode(_event: &Event, _key_event: &mut KeyEvent) {} diff --git a/src/lang/es.rs b/src/lang/es.rs index 1069b4905..17c3ddf07 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -399,6 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"), ("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."), ("Right click to select tabs", "Clic derecho para seleccionar pestañas"), - ("Add to Address Book", ""), + ("Add to Address Book", "Añadir a la libreta de direcciones"), ].iter().cloned().collect(); } diff --git a/src/lib.rs b/src/lib.rs index eb8a876ec..7b94c8a2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #[cfg(not(any(target_os = "ios")))] /// cbindgen:ignore pub mod platform; +mod keyboard; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service}; #[cfg(not(any(target_os = "ios")))] diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 82d6592db..664fb9c7e 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -715,3 +715,4 @@ pub fn get_double_click_time() -> u32 { double_click_time } } + diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f6b79da59..ed5fcfaa1 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -75,3 +75,4 @@ mod tests { } } } + diff --git a/src/server/input_service.rs b/src/server/input_service.rs index b465658bb..4d4389870 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -6,6 +6,7 @@ use dispatch::Queue; use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown}; use rdev::{simulate, EventType, Key as RdevKey}; +use std::time::Duration; use std::{ convert::TryFrom, ops::Sub, @@ -753,24 +754,17 @@ fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) { 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); + #[cfg(target_os = "macos")] + std::thread::sleep(Duration::from_millis(20)); } -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) { - /* todo! Shift+delete */ +fn sync_status(evt: &KeyEvent) { let mut en = ENIGO.lock().unwrap(); // remote caps status @@ -808,7 +802,18 @@ fn sync_status(evt: &KeyEvent) -> (bool, bool) { _ => click_numlock, } }; - return (click_capslock, click_numlock); + + if click_capslock { + #[cfg(not(target_os = "macos"))] + en.key_click(enigo::Key::CapsLock); + #[cfg(target_os = "macos")] + en.key_down(enigo::Key::CapsLock); + } + + if click_numlock { + #[cfg(not(target_os = "macos"))] + en.key_click(enigo::Key::NumLock); + } } fn map_keyboard_mode(evt: &KeyEvent) { @@ -816,25 +821,12 @@ fn map_keyboard_mode(evt: &KeyEvent) { #[cfg(windows)] crate::platform::windows::try_change_desktop(); - 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 { @@ -843,35 +835,14 @@ fn map_keyboard_mode(evt: &KeyEvent) { 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)] @@ -1001,10 +972,10 @@ pub fn handle_key_(evt: &KeyEvent) { return; } + if evt.down { + sync_status(evt) + } match evt.mode.unwrap() { - KeyboardMode::Legacy => { - legacy_keyboard_mode(evt); - } KeyboardMode::Map => { map_keyboard_mode(evt); } diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs index ace70e1bd..6d2e92ae3 100644 --- a/src/server/portable_service.rs +++ b/src/server/portable_service.rs @@ -44,7 +44,7 @@ const ADDR_CAPTURE_FRAME_COUNTER: usize = ADDR_CAPTURE_WOULDBLOCK + size_of:: { @@ -622,7 +622,7 @@ pub mod client { async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver) { use DataPortableService::*; let rx = Arc::new(tokio::sync::Mutex::new(rx)); - let postfix = IPC_PROFIX; + let postfix = IPC_SUFFIX; #[cfg(feature = "flutter")] let quick_support = { let args: Vec<_> = std::env::args().collect(); diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 28b73cf7c..686e28f35 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -481,22 +481,7 @@ fn run(sp: GenericService) -> ResultType<()> { #[cfg(windows)] log::info!("gdi: {}", c.is_gdi()); let codec_name = Encoder::current_hw_encoder_name(); - #[cfg(not(target_os = "ios"))] - let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() { - Recorder::new(RecorderContext { - id: "local".to_owned(), - default_dir: crate::ui_interface::default_video_save_directory(), - filename: "".to_owned(), - width: c.width, - height: c.height, - codec_id: scrap::record::RecordCodecID::VP9, - }) - .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))) - } else { - Default::default() - }; - #[cfg(target_os = "ios")] - let recorder: Arc>> = Default::default(); + let recorder = get_recorder(c.width, c.height, &codec_name); #[cfg(windows)] start_uac_elevation_check(); @@ -673,6 +658,53 @@ fn run(sp: GenericService) -> ResultType<()> { Ok(()) } +fn get_recorder( + width: usize, + height: usize, + codec_name: &Option, +) -> Arc>> { + #[cfg(not(target_os = "ios"))] + let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() { + use crate::hbbs_http::record_upload; + use scrap::record::RecordCodecID::*; + + let tx = if record_upload::is_enable() { + let (tx, rx) = std::sync::mpsc::channel(); + record_upload::run(rx); + Some(tx) + } else { + None + }; + let codec_id = match codec_name { + Some(name) => { + if name.contains("264") { + H264 + } else { + H265 + } + } + None => VP9, + }; + Recorder::new(RecorderContext { + server: true, + id: Config::get_id(), + default_dir: crate::ui_interface::default_video_save_directory(), + filename: "".to_owned(), + width, + height, + codec_id, + tx, + }) + .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))) + } else { + Default::default() + }; + #[cfg(target_os = "ios")] + let recorder: Arc>> = Default::default(); + + recorder +} + fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> { let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap(); if privacy_mode_id != privacy_mode_id_2 { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 59082d00d..604d2e222 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -685,6 +685,7 @@ pub fn discover() { }); } +#[cfg(feature = "flutter")] pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> { HashMap::<&str, String>::from_iter([ ("id", id), diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 6b635436d..6bdf88b11 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -4,10 +4,7 @@ use crate::client::{ load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler, QualityStatus, KEY_MAP, }; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::client::{get_key_state, SERVER_KEYBOARD_ENABLED}; -#[cfg(target_os = "linux")] -use crate::common::IS_X11; +use crate::common::GrabState; use crate::{client::Data, client::Interface}; use async_trait::async_trait; use hbb_common::config::{Config, LocalConfig, PeerConfig}; @@ -15,53 +12,13 @@ use hbb_common::rendezvous_proto::ConnType; use hbb_common::tokio::{self, sync::mpsc}; use hbb_common::{allow_err, message_proto::*}; use hbb_common::{fs, get_version_number, log, Stream}; -use rdev::{Event, EventType, EventType::*, Key as RdevKey}; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use rdev::{Keyboard as RdevKeyboard, KeyboardState}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, RwLock}; - -/// IS_IN KEYBOARD_HOOKED sciter only +use crate::keyboard; +use rdev::{Event, EventType::*}; pub static IS_IN: AtomicBool = AtomicBool::new(false); -pub static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); -pub static HOTKEY_HOOKED: AtomicBool = AtomicBool::new(false); -#[cfg(windows)] -static mut IS_ALT_GR: bool = false; -#[cfg(feature = "flutter")] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::flutter::FlutterHandler; - -lazy_static::lazy_static! { - static ref TO_RELEASE: Arc>> = Arc::new(Mutex::new(HashSet::::new())); -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -lazy_static::lazy_static! { - static ref KEYBOARD: Arc> = Arc::new(Mutex::new(RdevKeyboard::new().unwrap())); -} - -#[cfg(feature = "flutter")] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -lazy_static::lazy_static! { - pub static ref CUR_SESSION: Arc>>> = Default::default(); -} - -lazy_static::lazy_static! { - static ref MUTEX_SPECIAL_KEYS: Mutex> = { - 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) - }; -} #[derive(Clone, Default)] pub struct Session { @@ -92,11 +49,11 @@ impl Session { } pub fn get_keyboard_mode(&self) -> String { - global_get_keyboard_mode() + self.lc.read().unwrap().keyboard_mode.clone() } - pub fn save_keyboard_mode(&self, value: String) { - global_save_keyboard_mode(value); + pub fn save_keyboard_mode(&mut self, value: String) { + self.lc.write().unwrap().save_keyboard_mode(value); } pub fn save_view_style(&mut self, value: String) { @@ -307,439 +264,6 @@ impl Session { self.lc.read().unwrap().info.platform.clone() } - pub fn ctrl_alt_del(&self) { - if self.peer_platform() == "Windows" { - let mut key_event = KeyEvent::new(); - key_event.set_control_key(ControlKey::CtrlAltDel); - // 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.legacy_modifiers(&mut key_event, true, true, false, false); - // todo - key_event.press = true; - self.send_key_event(key_event, KeyboardMode::Legacy); - } - } - - 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 { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 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) { - // 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 - }; - - 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; - #[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 get_key_state(enigo::Key::NumLock) { - key_event.modifiers.push(ControlKey::NumLock.into()); - } - self.send_key_event(key_event, KeyboardMode::Map); - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 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 = 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, - key_event: &mut KeyEvent, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, - ) { - if alt - && !crate::is_control_key(&key_event, &ControlKey::Alt) - && !crate::is_control_key(&key_event, &ControlKey::RAlt) - { - key_event.modifiers.push(ControlKey::Alt.into()); - } - if shift - && !crate::is_control_key(&key_event, &ControlKey::Shift) - && !crate::is_control_key(&key_event, &ControlKey::RShift) - { - key_event.modifiers.push(ControlKey::Shift.into()); - } - if ctrl - && !crate::is_control_key(&key_event, &ControlKey::Control) - && !crate::is_control_key(&key_event, &ControlKey::RControl) - { - key_event.modifiers.push(ControlKey::Control.into()); - } - if command - && !crate::is_control_key(&key_event, &ControlKey::Meta) - && !crate::is_control_key(&key_event, &ControlKey::RWin) - { - 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) { - key_event.modifiers.push(ControlKey::NumLock.into()); - } - } - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 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::Slash => '/', - 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; - } - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let (alt, ctrl, shift, command) = get_all_hotkey_state(alt, ctrl, shift, command); - 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, - }; - - #[cfg(not(windows))] - let key = self.convert_numpad_keys(key); - - let mut to_release = TO_RELEASE.lock().unwrap(); - match mode { - KeyboardMode::Map => { - if down_or_up == true { - to_release.insert(key); - } else { - to_release.remove(&key); - } - self.map_keyboard_mode(down_or_up, key, Some(evt)); - } - KeyboardMode::Legacy => - { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - self.legacy_keyboard_mode(down_or_up, key, evt) - } - KeyboardMode::Translate => { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - self.translate_keyboard_mode(down_or_up, key, evt); - } - _ => - { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - self.legacy_keyboard_mode(down_or_up, key, evt) - } - } - } - pub fn get_platform(&self, is_remote: bool) -> String { if is_remote { self.peer_platform() @@ -768,6 +292,13 @@ impl Session { return "".to_owned(); } + pub fn send_key_event(&self, evt: &KeyEvent) { + // mode: legacy(0), map(1), translate(2), auto(3) + let mut msg_out = Message::new(); + msg_out.set_key_event(evt.clone()); + self.send(Data::Message(msg_out)); + } + pub fn send_chat(&self, text: String) { let mut misc = Misc::new(); misc.set_chat_message(ChatMessage { @@ -790,77 +321,14 @@ impl Session { self.send(Data::Message(msg_out)); } - pub fn lock_screen(&self) { - let mut key_event = KeyEvent::new(); - key_event.set_control_key(ControlKey::LockScreen); - // todo - key_event.down = true; - self.send_key_event(key_event, KeyboardMode::Legacy); - } - pub fn enter(&self) { IS_IN.store(true, Ordering::SeqCst); - #[cfg(target_os = "linux")] - self.grab_hotkeys(true); - - #[cfg(windows)] - crate::platform::windows::stop_system_key_propagate(true); + keyboard::client::change_grab_status(GrabState::Run); } pub fn leave(&self) { IS_IN.store(false, Ordering::SeqCst); - #[cfg(target_os = "linux")] - self.grab_hotkeys(false); - - 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); - } - - #[cfg(target_os = "linux")] - pub fn grab_hotkeys(&self, _grab: bool) { - if _grab { - rdev::enable_grab().ok(); - } else { - rdev::disable_grab().ok(); - } - } - - 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) + keyboard::client::change_grab_status(GrabState::Wait); } // flutter only TODO new input @@ -874,9 +342,6 @@ impl Session { shift: bool, command: bool, ) { - if HOTKEY_HOOKED.load(Ordering::SeqCst) { - return; - } let chars: Vec = name.chars().collect(); if chars.len() == 1 { let key = Key::_Raw(chars[0] as _); @@ -897,6 +362,40 @@ impl Session { self.send(Data::Message(msg_out)); } + 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 rdev::Key; + // 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 event = Event { + time: std::time::SystemTime::now(), + name: Option::Some(name.to_owned()), + code: keycode as _, + scan_code: scancode as _, + event_type: event_type, + }; + keyboard::client::process_event(event); + } + // flutter only TODO new input fn _input_key( &self, @@ -921,25 +420,6 @@ impl Session { key_event.set_chr(chr); } Key::ControlKey(key) => { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let key = if !get_key_state(enigo::Key::NumLock) { - match key { - ControlKey::Numpad0 => ControlKey::Insert, - ControlKey::Decimal => ControlKey::Delete, - ControlKey::Numpad1 => ControlKey::End, - ControlKey::Numpad2 => ControlKey::DownArrow, - ControlKey::Numpad3 => ControlKey::PageDown, - ControlKey::Numpad4 => ControlKey::LeftArrow, - ControlKey::Numpad5 => ControlKey::Clear, - ControlKey::Numpad6 => ControlKey::RightArrow, - ControlKey::Numpad7 => ControlKey::Home, - ControlKey::Numpad8 => ControlKey::UpArrow, - ControlKey::Numpad9 => ControlKey::PageUp, - _ => key, - } - } else { - key - }; key_event.set_control_key(key.clone()); } Key::_Raw(raw) => { @@ -947,17 +427,15 @@ impl Session { } } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let (alt, ctrl, shift, command) = get_all_hotkey_state(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; } + keyboard::client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command); + key_event.mode = KeyboardMode::Legacy.into(); - self.send_key_event(key_event, KeyboardMode::Legacy); + self.send_key_event(&key_event); } pub fn send_mouse( @@ -979,8 +457,9 @@ impl Session { } } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let (alt, ctrl, shift, command) = get_all_hotkey_state(alt, ctrl, shift, command); + // #[cfg(not(any(target_os = "android", target_os = "ios")))] + let (alt, ctrl, shift, command) = + keyboard::client::get_modifiers_state(alt, ctrl, shift, command); send_mouse(mask, x, y, alt, ctrl, shift, command, self); // on macos, ctrl + left button down = right button down, up won't emit, so we need to @@ -1243,23 +722,6 @@ impl Interface for Session { crate::platform::windows::add_recent_document(&path); } } - // only run in sciter - #[cfg(not(feature = "flutter"))] - { - // rdev::grab and rdev::listen use the same api in macOS & Windows - /* todo! Unused */ - #[cfg(not(any( - target_os = "android", - target_os = "ios", - target_os = "macos", - target_os = "windows", - target_os = "linux", - )))] - self.start_keyboard_hook(); - /* todo! (sciter) Only one device can be connected at the same time in linux */ - #[cfg(not(any(target_os = "android", target_os = "ios")))] - self.start_grab_hotkey(); - } } async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) { @@ -1300,113 +762,14 @@ impl Interface for Session { } } -#[cfg(not(any(target_os = "android", target_os = "ios")))] impl Session { - fn handle_hotkey_event(&self, event: Event) { - // if is long press, don't do anything. - if is_long_press(&event) { - return; - } - - let (key, down) = match event.event_type { - EventType::KeyPress(key) => (key, true), - EventType::KeyRelease(key) => (key, false), - _ => return, - }; - - self.key_down_or_up(down, key, event); + pub fn lock_screen(&self) { + log::info!("Sending key even"); + crate::keyboard::client::lock_screen(); } - - #[allow(dead_code)] - fn start_grab_hotkey(&self) { - if self.is_port_forward() || self.is_file_transfer() { - return; - } - #[cfg(target_os = "linux")] - if !*IS_X11.lock().unwrap() { - return; - } - if HOTKEY_HOOKED.swap(true, Ordering::SeqCst) { - return; - } - - log::info!("starting grab hotkeys"); - let me = self.clone(); - - #[cfg(target_os = "linux")] - { - let func = move |event: Event| match event.event_type { - EventType::KeyPress(_key) | EventType::KeyRelease(_key) => { - me.handle_hotkey_event(event); - None - } - _ => Some(event), - }; - rdev::start_grab_listen(func) - } - #[cfg(any(target_os = "windows", target_os = "macos"))] - std::thread::spawn(move || { - let func = move |event: Event| match event.event_type { - EventType::KeyPress(..) | EventType::KeyRelease(..) => { - // grab all keys - if !IS_IN.load(Ordering::SeqCst) - || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) - { - return Some(event); - } else { - me.handle_hotkey_event(event); - return None; - } - } - _ => Some(event), - }; - if let Err(error) = rdev::grab(func) { - log::error!("Error: {:?}", error) - } - }); - } - - #[allow(dead_code)] - fn start_keyboard_hook(&self) { - // only run in sciter - if self.is_port_forward() || self.is_file_transfer() { - return; - } - if KEYBOARD_HOOKED.swap(true, Ordering::SeqCst) { - return; - } - log::info!("keyboard hooked"); - - let me = self.clone(); - #[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"); - - let func = move |evt: Event| { - /* todo! IS_IN can't determine if the user is focused on remote page */ - if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) - { - return; - } - if is_long_press(&evt) { - return; - } - let (key, down) = match evt.event_type { - EventType::KeyPress(key) => (key, true), - EventType::KeyRelease(key) => (key, false), - _ => return, - }; - me.key_down_or_up(down, key, evt); - }; - /* todo!: Shift + a -> AA in sciter - * rdev::listen and rdev::grab both send a - */ - if let Err(error) = rdev::listen(func) { - log::error!("rdev: {:?}", error); - } - }); + pub fn ctrl_alt_del(&self) { + log::info!("Sending key even"); + crate::keyboard::client::lock_screen(); } } @@ -1560,107 +923,3 @@ async fn send_note(url: String, id: String, conn_id: i32, note: String) { let body = serde_json::json!({ "id": id, "Id": conn_id, "note": note }); allow_err!(crate::post_request(url, body.to_string(), "").await); } - -fn get_hotkey_state(key: RdevKey) -> bool { - if let Some(&state) = MUTEX_SPECIAL_KEYS.lock().unwrap().get(&key) { - return state; - } else { - return false; - } -} - -fn get_all_hotkey_state( - alt: bool, - ctrl: bool, - shift: bool, - command: bool, -) -> (bool, bool, bool, bool) { - let ctrl = - get_hotkey_state(RdevKey::ControlLeft) || get_hotkey_state(RdevKey::ControlRight) || ctrl; - let shift = - get_hotkey_state(RdevKey::ShiftLeft) || get_hotkey_state(RdevKey::ShiftRight) || shift; - let command = - get_hotkey_state(RdevKey::MetaLeft) || get_hotkey_state(RdevKey::MetaRight) || command; - let alt = get_hotkey_state(RdevKey::Alt) || get_hotkey_state(RdevKey::AltGr) || alt; - - (alt, ctrl, shift, command) -} - -#[cfg(feature = "flutter")] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn send_key_event_to_session(event: rdev::Event) { - if let Some(handler) = CUR_SESSION.lock().unwrap().as_ref() { - handler.handle_hotkey_event(event); - } -} - -#[cfg(feature = "flutter")] -pub fn global_grab_keyboard() { - if HOTKEY_HOOKED.swap(true, Ordering::SeqCst) { - return; - } - log::info!("starting global grab keyboard"); - - #[cfg(target_os = "linux")] - { - let func = move |event: Event| match event.event_type { - EventType::KeyPress(_key) | EventType::KeyRelease(_key) => { - send_key_event_to_session(event); - None - } - _ => Some(event), - }; - rdev::start_grab_listen(func) - } - - #[cfg(any(target_os = "windows", target_os = "macos"))] - std::thread::spawn(move || { - let func = move |event: Event| match event.event_type { - EventType::KeyPress(..) | EventType::KeyRelease(..) => { - // grab all keys - if !IS_IN.load(Ordering::SeqCst) { - return Some(event); - } else { - send_key_event_to_session(event); - return None; - } - } - _ => Some(event), - }; - if let Err(error) = rdev::grab(func) { - log::error!("Error: {:?}", error) - } - }); -} - -pub fn global_get_keyboard_mode() -> String { - return std::env::var("KEYBOARD_MODE") - .unwrap_or(String::from("map")) - .to_lowercase(); -} - -pub fn global_save_keyboard_mode(value: String) { - std::env::set_var("KEYBOARD_MODE", value); -} - -fn is_long_press(event: &Event) -> bool { - let mut keys = MUTEX_SPECIAL_KEYS.lock().unwrap(); - match event.event_type { - EventType::KeyPress(k) => { - if let Some(&state) = keys.get(&k) { - if state == true { - return true; - } else { - keys.insert(k, true); - } - } - } - EventType::KeyRelease(k) => { - if keys.contains_key(&k) { - keys.insert(k, false); - } - } - _ => {} - }; - return false; -}