Merge pull request #2496 from asur4s/master

refactor keyboard of client
This commit is contained in:
RustDesk 2022-12-09 16:46:24 +08:00 committed by GitHub
commit 658d859860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1086 additions and 959 deletions

View File

@ -1189,11 +1189,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'), MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
], ],
curOptionGetter: () async => curOptionGetter: () async {
await bind.sessionGetKeyboardName(id: widget.id), return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
},
optionSetter: (String oldValue, String newValue) async { optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode( await bind.sessionSetKeyboardMode(id: widget.id, value: newValue);
id: widget.id, keyboardMode: newValue); widget.ffi.canvasModel.updateViewStyle();
}, },
) )
]; ];

View File

@ -692,10 +692,11 @@ class _RemotePageState extends State<RemotePage> {
} }
void changePhysicalKeyboardInputMode() async { 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) { gFFI.dialogManager.show((setState, close) {
void setMode(String? v) async { 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 ?? ''); setState(() => current = v ?? '');
Future.delayed(Duration(milliseconds: 300), close); Future.delayed(Duration(milliseconds: 300), close);
} }

View File

@ -54,7 +54,7 @@ class InputModel {
InputModel(this.parent); InputModel(this.parent);
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) { KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
bind.sessionGetKeyboardName(id: id).then((result) { bind.sessionGetKeyboardMode(id: id).then((result) {
keyboardMode = result.toString(); keyboardMode = result.toString();
}); });

View File

@ -7,7 +7,7 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "50.0.0" version: "49.0.0"
after_layout: after_layout:
dependency: transitive dependency: transitive
description: description:
@ -21,7 +21,7 @@ packages:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.0" version: "5.1.0"
animations: animations:
dependency: transitive dependency: transitive
description: description:
@ -35,7 +35,7 @@ packages:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.5" version: "3.3.1"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -63,7 +63,7 @@ packages:
name: back_button_interceptor name: back_button_interceptor
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.2" version: "6.0.1"
bot_toast: bot_toast:
dependency: "direct main" dependency: "direct main"
description: description:
@ -84,7 +84,7 @@ packages:
name: build_config name: build_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
@ -105,14 +105,14 @@ packages:
name: build_runner name: build_runner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.2" version: "2.2.1"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "7.2.7" version: "7.2.4"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -126,7 +126,7 @@ packages:
name: built_value name: built_value
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.4.2" version: "8.4.1"
cached_network_image: cached_network_image:
dependency: transitive dependency: transitive
description: description:
@ -203,7 +203,7 @@ packages:
name: convert name: convert
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.0.2"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -352,7 +352,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.2" version: "5.2.1"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -443,7 +443,7 @@ packages:
name: flutter_svg name: flutter_svg
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.6" version: "1.1.5"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -455,21 +455,21 @@ packages:
name: freezed name: freezed
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.1" version: "2.1.1"
freezed_annotation: freezed_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
name: freezed_annotation name: freezed_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "2.1.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
name: frontend_server_client name: frontend_server_client
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.0" version: "2.1.3"
get: get:
dependency: "direct main" dependency: "direct main"
description: description:
@ -483,21 +483,21 @@ packages:
name: glob name: glob
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
name: graphs name: graphs
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "2.1.0"
html: html:
dependency: transitive dependency: transitive
description: description:
name: html name: html
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.15.1" version: "0.15.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -518,7 +518,7 @@ packages:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.2" version: "4.0.1"
icons_launcher: icons_launcher:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -532,7 +532,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.2" version: "3.2.0"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -602,7 +602,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -735,7 +735,7 @@ packages:
name: path_provider_android name: path_provider_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.22" version: "2.0.20"
path_provider_ios: path_provider_ios:
dependency: transitive dependency: transitive
description: description:
@ -799,13 +799,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -826,14 +819,14 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.4" version: "6.0.3"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.1"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
@ -854,7 +847,7 @@ packages:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.27.7" version: "0.27.5"
screen_retriever: screen_retriever:
dependency: transitive dependency: transitive
description: description:
@ -891,7 +884,7 @@ packages:
name: shelf_web_socket name: shelf_web_socket
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.2"
simple_observable: simple_observable:
dependency: transitive dependency: transitive
description: description:
@ -910,7 +903,7 @@ packages:
name: source_gen name: source_gen
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.6" version: "1.2.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -931,7 +924,7 @@ packages:
name: sqflite_common name: sqflite_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.0" version: "2.3.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -952,7 +945,7 @@ packages:
name: stream_transform name: stream_transform
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.0.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -1043,14 +1036,14 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.7" version: "6.1.6"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.22" version: "6.0.19"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -1099,7 +1092,7 @@ packages:
name: uuid name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.7" version: "3.0.6"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -1113,7 +1106,7 @@ packages:
name: video_player name: video_player
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.8" version: "2.4.7"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:
@ -1190,7 +1183,7 @@ packages:
name: watcher name: watcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -1204,7 +1197,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.2" version: "3.0.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:

View File

@ -203,6 +203,8 @@ pub struct PeerConfig {
pub enable_file_transfer: bool, pub enable_file_transfer: bool,
#[serde(default)] #[serde(default)]
pub show_quality_monitor: bool, pub show_quality_monitor: bool,
#[serde(default)]
pub keyboard_mode: String,
// The other scalar value must before this // The other scalar value must before this
#[serde(default, deserialize_with = "PeerConfig::deserialize_options")] #[serde(default, deserialize_with = "PeerConfig::deserialize_options")]

View File

@ -12,11 +12,10 @@ use hwcodec::mux::{MuxContext, Muxer};
use std::{ use std::{
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io, io,
time::Instant,
};
use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::PathBuf, path::PathBuf,
sync::mpsc::Sender,
time::Instant,
}; };
use webm::mux::{self, Segment, Track, VideoTrack, Writer}; use webm::mux::{self, Segment, Track, VideoTrack, Writer};
@ -31,12 +30,14 @@ pub enum RecordCodecID {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RecorderContext { pub struct RecorderContext {
pub server: bool,
pub id: String, pub id: String,
pub default_dir: String, pub default_dir: String,
pub filename: String, pub filename: String,
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub codec_id: RecordCodecID, pub codec_id: RecordCodecID,
pub tx: Option<Sender<RecordState>>,
} }
impl RecorderContext { impl RecorderContext {
@ -52,7 +53,8 @@ impl RecorderContext {
std::fs::create_dir_all(&dir)?; 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() + &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
+ if self.codec_id == RecordCodecID::VP9 { + if self.codec_id == RecordCodecID::VP9 {
".webm" ".webm"
@ -60,7 +62,7 @@ impl RecorderContext {
".mp4" ".mp4"
}; };
self.filename = PathBuf::from(&dir).join(file).to_string_lossy().to_string(); 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(()) Ok(())
} }
} }
@ -75,6 +77,14 @@ pub trait RecorderApi {
fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool; fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool;
} }
#[derive(Debug)]
pub enum RecordState {
NewFile(String),
NewFrame,
WriteTail,
RemoveFile,
}
pub struct Recorder { pub struct Recorder {
pub inner: Box<dyn RecorderApi>, pub inner: Box<dyn RecorderApi>,
ctx: RecorderContext, ctx: RecorderContext,
@ -110,6 +120,7 @@ impl Recorder {
#[cfg(not(feature = "hwcodec"))] #[cfg(not(feature = "hwcodec"))]
_ => bail!("unsupported codec type"), _ => bail!("unsupported codec type"),
}; };
recorder.send_state(RecordState::NewFile(recorder.ctx.filename.clone()));
Ok(recorder) Ok(recorder)
} }
@ -123,6 +134,7 @@ impl Recorder {
_ => bail!("unsupported codec type"), _ => bail!("unsupported codec type"),
}; };
self.ctx = ctx; self.ctx = ctx;
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
Ok(()) Ok(())
} }
@ -171,8 +183,13 @@ impl Recorder {
} }
_ => bail!("unsupported frame type"), _ => bail!("unsupported frame type"),
} }
self.send_state(RecordState::NewFrame);
Ok(()) Ok(())
} }
fn send_state(&self, state: RecordState) {
self.ctx.tx.as_ref().map(|tx| tx.send(state));
}
} }
struct WebmRecorder { struct WebmRecorder {
@ -237,9 +254,12 @@ impl RecorderApi for WebmRecorder {
impl Drop for WebmRecorder { impl Drop for WebmRecorder {
fn drop(&mut self) { fn drop(&mut self) {
std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None)); 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 { if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
std::fs::remove_file(&self.ctx.filename).ok(); 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 { impl Drop for HwRecorder {
fn drop(&mut self) { fn drop(&mut self) {
self.muxer.write_tail().ok(); self.muxer.write_tail().ok();
let mut state = RecordState::WriteTail;
if !self.written || self.start.elapsed().as_secs() < MIN_SECS { if !self.written || self.start.elapsed().as_secs() < MIN_SECS {
std::fs::remove_file(&self.ctx.filename).ok(); std::fs::remove_file(&self.ctx.filename).ok();
} state = RecordState::RemoveFile;
}
self.ctx.tx.as_ref().map(|tx| tx.send(state));
} }
} }

View File

@ -6,8 +6,6 @@ use cpal::{
}; };
use magnum_opus::{Channels::*, Decoder as AudioDecoder}; use magnum_opus::{Channels::*, Decoder as AudioDecoder};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::sync::atomic::Ordering;
use std::{ use std::{
collections::HashMap, collections::HashMap,
net::SocketAddr, net::SocketAddr,
@ -49,10 +47,7 @@ pub use super::lang::*;
pub mod file_trait; pub mod file_trait;
pub mod helper; pub mod helper;
pub mod io_loop; pub mod io_loop;
use crate::{ use crate::server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED};
server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED},
ui_session_interface::global_save_keyboard_mode,
};
pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
@ -863,12 +858,14 @@ impl VideoHandler {
self.record = false; self.record = false;
if start { if start {
self.recorder = Recorder::new(RecorderContext { self.recorder = Recorder::new(RecorderContext {
server: false,
id, id,
default_dir: crate::ui_interface::default_video_save_directory(), default_dir: crate::ui_interface::default_video_save_directory(),
filename: "".to_owned(), filename: "".to_owned(),
width: w as _, width: w as _,
height: h as _, height: h as _,
codec_id: scrap::record::RecordCodecID::VP9, codec_id: scrap::record::RecordCodecID::VP9,
tx: None,
}) })
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))); .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
} else { } else {
@ -987,6 +984,17 @@ impl LoginConfigHandler {
self.save_config(config); 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. /// Save scroll style to the current config.
/// ///
/// # Arguments /// # Arguments
@ -1380,9 +1388,6 @@ impl LoginConfigHandler {
if !pi.version.is_empty() { if !pi.version.is_empty() {
self.version = hbb_common::get_version_number(&pi.version); 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(); self.features = pi.features.clone().into_option();
let serde = PeerInfoSerde { let serde = PeerInfoSerde {
username: pi.username.clone(), username: pi.username.clone(),
@ -1405,6 +1410,14 @@ impl LoginConfigHandler {
log::debug!("remove password of {}", self.id); 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; self.conn_id = pi.conn_id;
// no matter if change, for update file time // no matter if change, for update file time
self.save_config(config); self.save_config(config);
@ -2022,8 +2035,3 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
bail!("Wrong public length"); 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);
}

View File

@ -3,6 +3,14 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
#[derive(Debug, Eq, PartialEq)]
pub enum GrabState {
Ready,
Run,
Wait,
Exit,
}
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use arboard::Clipboard as ClipboardContext; pub use arboard::Clipboard as ClipboardContext;
@ -11,7 +19,7 @@ use hbb_common::compress::decompress;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
anyhow::bail, anyhow::bail,
compress::{compress as compress_func}, compress::compress as compress_func,
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT}, config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
get_version_number, log, get_version_number, log,
message_proto::*, message_proto::*,

View File

@ -217,7 +217,7 @@ pub fn core_main() -> Option<Vec<String>> {
if crate::platform::is_root() { if crate::platform::is_root() {
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap(); crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
} else { } else {
log::info!("Permission denied!"); println!("Administrative privileges required!");
} }
} }
return None; return None;

View File

@ -422,8 +422,6 @@ pub fn session_start_(id: &str, event_stream: StreamSink<EventToUI>) -> ResultTy
*session.event_stream.write().unwrap() = Some(event_stream); *session.event_stream.write().unwrap() = Some(event_stream);
let session = session.clone(); let session = session.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
// if flutter : disable keyboard listen
crate::client::disable_keyboard_listening();
io_loop(session); io_loop(session);
}); });
Ok(()) Ok(())

View File

@ -18,7 +18,7 @@ use hbb_common::{
use crate::flutter::{self, SESSIONS}; use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *}; use crate::ui_interface::{self, *};
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::CUR_SESSION; use crate::keyboard::CUR_SESSION;
use crate::{ use crate::{
client::file_trait::FileManager, client::file_trait::FileManager,
flutter::{make_fd_to_json, session_add, session_start_}, 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<String> {
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<Vec<i32>> { pub fn session_get_custom_image_quality(id: String) -> Option<Vec<i32>> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_custom_image_quality()) 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()); *CUR_SESSION.lock().unwrap() = Some(session.clone());
session.enter(); session.enter();
} else { } else {
*CUR_SESSION.lock().unwrap() = None;
session.leave(); session.leave();
} }
} }
@ -299,12 +312,14 @@ pub fn session_input_key(
command: bool, command: bool,
) { ) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { 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); session.input_key(&name, down, press, alt, ctrl, shift, command);
} }
} }
pub fn session_input_string(id: String, value: String) { pub fn session_input_string(id: String, value: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
// #[cfg(any(target_os = "android", target_os = "ios"))]
session.input_string(&value); session.input_string(&value);
} }
} }
@ -329,19 +344,6 @@ pub fn session_get_peer_option(id: String, name: String) -> String {
"".to_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) { pub fn session_input_os_password(id: String, value: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.input_os_password(value, true); session.input_os_password(value, true);
@ -1083,8 +1085,7 @@ pub fn main_is_installed() -> SyncReturn<bool> {
} }
pub fn main_start_grab_keyboard() { pub fn main_start_grab_keyboard() {
#[cfg(not(any(target_os = "android", target_os = "ios")))] crate::keyboard::client::start_grab_loop();
crate::ui_session_interface::global_grab_keyboard();
} }
pub fn main_is_installed_lower_version() -> SyncReturn<bool> { pub fn main_is_installed_lower_version() -> SyncReturn<bool> {

View File

@ -4,6 +4,7 @@ use serde_json::{Map, Value};
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
pub mod account; pub mod account;
pub mod record_upload;
#[derive(Debug)] #[derive(Debug)]
pub enum HbbHttpResponse<T> { pub enum HbbHttpResponse<T> {

View File

@ -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<Mutex<bool>> = Default::default();
}
pub fn is_enable() -> bool {
ENABLE.lock().unwrap().clone()
}
pub fn run(rx: Receiver<RecordState>) {
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<Q, B>(&self, query: &Q, body: B) -> ResultType<()>
where
Q: Serialize + ?Sized,
B: Into<Body>,
{
match self
.client
.post(format!("{}/api/record", self.api_server))
.query(query)
.body(body)
.send()
{
Ok(resp) => {
if let Ok(m) = resp.json::<Map<String, serde_json::Value>>() {
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(())
}
}

621
src/keyboard.rs Normal file
View File

@ -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<Mutex<Option<mpsc::Sender<GrabState>>>> = Default::default();
}
#[cfg(feature = "flutter")]
lazy_static::lazy_static! {
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<FlutterHandler>>>> = Default::default();
}
#[cfg(not(feature = "flutter"))]
lazy_static::lazy_static! {
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<SciterHandler>>>> = Default::default();
}
lazy_static::lazy_static! {
static ref TO_RELEASE: Arc<Mutex<HashSet<Key>>> = Arc::new(Mutex::new(HashSet::<Key>::new()));
static ref MODIFIERS_STATE: Mutex<HashMap<Key, bool>> = {
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::<GrabState>();
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<GrabState>) {
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 #2211CAPS 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::<Vec<Key>>();
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) {}

View File

@ -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"), ("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."), ("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"), ("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(); ].iter().cloned().collect();
} }

View File

@ -1,6 +1,7 @@
#[cfg(not(any(target_os = "ios")))] #[cfg(not(any(target_os = "ios")))]
/// cbindgen:ignore /// cbindgen:ignore
pub mod platform; pub mod platform;
mod keyboard;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service}; pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service};
#[cfg(not(any(target_os = "ios")))] #[cfg(not(any(target_os = "ios")))]

View File

@ -715,3 +715,4 @@ pub fn get_double_click_time() -> u32 {
double_click_time double_click_time
} }
} }

View File

@ -75,3 +75,4 @@ mod tests {
} }
} }
} }

View File

@ -6,6 +6,7 @@ use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown}; use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown};
use rdev::{simulate, EventType, Key as RdevKey}; use rdev::{simulate, EventType, Key as RdevKey};
use std::time::Duration;
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
ops::Sub, ops::Sub,
@ -753,24 +754,17 @@ fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) {
true => EventType::KeyPress(key), true => EventType::KeyPress(key),
false => EventType::KeyRelease(key), false => EventType::KeyRelease(key),
}; };
let delay = std::time::Duration::from_millis(20);
match simulate(&event_type) { match simulate(&event_type) {
Ok(()) => (), Ok(()) => (),
Err(_simulate_error) => { Err(_simulate_error) => {
log::error!("Could not send {:?}", &event_type); log::error!("Could not send {:?}", &event_type);
} }
} }
// Let ths OS catchup (at least MacOS) #[cfg(target_os = "macos")]
std::thread::sleep(delay); std::thread::sleep(Duration::from_millis(20));
} }
fn rdev_key_click(key: RdevKey) { fn sync_status(evt: &KeyEvent) {
rdev_key_down_or_up(key, true);
rdev_key_down_or_up(key, false);
}
fn sync_status(evt: &KeyEvent) -> (bool, bool) {
/* todo! Shift+delete */
let mut en = ENIGO.lock().unwrap(); let mut en = ENIGO.lock().unwrap();
// remote caps status // remote caps status
@ -808,7 +802,18 @@ fn sync_status(evt: &KeyEvent) -> (bool, bool) {
_ => click_numlock, _ => 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) { fn map_keyboard_mode(evt: &KeyEvent) {
@ -816,25 +821,12 @@ fn map_keyboard_mode(evt: &KeyEvent) {
#[cfg(windows)] #[cfg(windows)]
crate::platform::windows::try_change_desktop(); crate::platform::windows::try_change_desktop();
let (click_capslock, click_numlock) = sync_status(evt);
// Wayland // Wayland
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if !*IS_X11.lock().unwrap() { if !*IS_X11.lock().unwrap() {
let mut en = ENIGO.lock().unwrap(); let mut en = ENIGO.lock().unwrap();
let code = evt.chr() as u16; 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 { if evt.down {
en.key_down(enigo::Key::Raw(code)).ok(); en.key_down(enigo::Key::Raw(code)).ok();
} else { } else {
@ -843,35 +835,14 @@ fn map_keyboard_mode(evt: &KeyEvent) {
return; 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); rdev_key_down_or_up(RdevKey::Unknown(evt.chr()), evt.down);
return; return;
} }
fn legacy_keyboard_mode(evt: &KeyEvent) { fn legacy_keyboard_mode(evt: &KeyEvent) {
let (click_capslock, click_numlock) = sync_status(evt);
#[cfg(windows)] #[cfg(windows)]
crate::platform::windows::try_change_desktop(); crate::platform::windows::try_change_desktop();
let mut en = ENIGO.lock().unwrap(); 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, // disable numlock if press home etc when numlock is on,
// because we will get numpad value (7,8,9 etc) if not // because we will get numpad value (7,8,9 etc) if not
#[cfg(windows)] #[cfg(windows)]
@ -1001,10 +972,10 @@ pub fn handle_key_(evt: &KeyEvent) {
return; return;
} }
match evt.mode.unwrap() { if evt.down {
KeyboardMode::Legacy => { sync_status(evt)
legacy_keyboard_mode(evt);
} }
match evt.mode.unwrap() {
KeyboardMode::Map => { KeyboardMode::Map => {
map_keyboard_mode(evt); map_keyboard_mode(evt);
} }

View File

@ -44,7 +44,7 @@ const ADDR_CAPTURE_FRAME_COUNTER: usize = ADDR_CAPTURE_WOULDBLOCK + size_of::<i3
const ADDR_CAPTURE_FRAME: usize = const ADDR_CAPTURE_FRAME: usize =
(ADDR_CAPTURE_FRAME_COUNTER + SIZE_COUNTER + FRAME_ALIGN - 1) / FRAME_ALIGN * FRAME_ALIGN; (ADDR_CAPTURE_FRAME_COUNTER + SIZE_COUNTER + FRAME_ALIGN - 1) / FRAME_ALIGN * FRAME_ALIGN;
const IPC_PROFIX: &str = "_portable_service"; const IPC_SUFFIX: &str = "_portable_service";
pub const SHMEM_NAME: &str = "_portable_service"; pub const SHMEM_NAME: &str = "_portable_service";
const MAX_NACK: usize = 3; const MAX_NACK: usize = 3;
const MAX_DXGI_FAIL_TIME: usize = 5; const MAX_DXGI_FAIL_TIME: usize = 5;
@ -376,7 +376,7 @@ pub mod server {
async fn run_ipc_client() { async fn run_ipc_client() {
use DataPortableService::*; use DataPortableService::*;
let postfix = IPC_PROFIX; let postfix = IPC_SUFFIX;
match ipc::connect(1000, postfix).await { match ipc::connect(1000, postfix).await {
Ok(mut stream) => { Ok(mut stream) => {
@ -622,7 +622,7 @@ pub mod client {
async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver<Data>) { async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver<Data>) {
use DataPortableService::*; use DataPortableService::*;
let rx = Arc::new(tokio::sync::Mutex::new(rx)); let rx = Arc::new(tokio::sync::Mutex::new(rx));
let postfix = IPC_PROFIX; let postfix = IPC_SUFFIX;
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
let quick_support = { let quick_support = {
let args: Vec<_> = std::env::args().collect(); let args: Vec<_> = std::env::args().collect();

View File

@ -481,22 +481,7 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)] #[cfg(windows)]
log::info!("gdi: {}", c.is_gdi()); log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::current_hw_encoder_name(); let codec_name = Encoder::current_hw_encoder_name();
#[cfg(not(target_os = "ios"))] let recorder = get_recorder(c.width, c.height, &codec_name);
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<Mutex<Option<Recorder>>> = Default::default();
#[cfg(windows)] #[cfg(windows)]
start_uac_elevation_check(); start_uac_elevation_check();
@ -673,6 +658,53 @@ fn run(sp: GenericService) -> ResultType<()> {
Ok(()) Ok(())
} }
fn get_recorder(
width: usize,
height: usize,
codec_name: &Option<String>,
) -> Arc<Mutex<Option<Recorder>>> {
#[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<Mutex<Option<Recorder>>> = Default::default();
recorder
}
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> { fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap(); let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
if privacy_mode_id != privacy_mode_id_2 { if privacy_mode_id != privacy_mode_id_2 {

View File

@ -685,6 +685,7 @@ pub fn discover() {
}); });
} }
#[cfg(feature = "flutter")]
pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> { pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
HashMap::<&str, String>::from_iter([ HashMap::<&str, String>::from_iter([
("id", id), ("id", id),

View File

@ -4,10 +4,7 @@ use crate::client::{
load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler, load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler,
QualityStatus, KEY_MAP, QualityStatus, KEY_MAP,
}; };
#[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::common::GrabState;
use crate::client::{get_key_state, SERVER_KEYBOARD_ENABLED};
#[cfg(target_os = "linux")]
use crate::common::IS_X11;
use crate::{client::Data, client::Interface}; use crate::{client::Data, client::Interface};
use async_trait::async_trait; use async_trait::async_trait;
use hbb_common::config::{Config, LocalConfig, PeerConfig}; 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::tokio::{self, sync::mpsc};
use hbb_common::{allow_err, message_proto::*}; use hbb_common::{allow_err, message_proto::*};
use hbb_common::{fs, get_version_number, log, Stream}; use hbb_common::{fs, get_version_number, log, Stream};
use rdev::{Event, EventType, EventType::*, Key as RdevKey}; use std::collections::HashMap;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use rdev::{Keyboard as RdevKeyboard, KeyboardState};
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use crate::keyboard;
/// IS_IN KEYBOARD_HOOKED sciter only use rdev::{Event, EventType::*};
pub static IS_IN: AtomicBool = AtomicBool::new(false); 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<Mutex<HashSet<RdevKey>>> = Arc::new(Mutex::new(HashSet::<RdevKey>::new()));
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref KEYBOARD: Arc<Mutex<RdevKeyboard>> = 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<Mutex<Option<Session<FlutterHandler>>>> = Default::default();
}
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)
};
}
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Session<T: InvokeUiSession> { pub struct Session<T: InvokeUiSession> {
@ -92,11 +49,11 @@ impl<T: InvokeUiSession> Session<T> {
} }
pub fn get_keyboard_mode(&self) -> String { 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) { pub fn save_keyboard_mode(&mut self, value: String) {
global_save_keyboard_mode(value); self.lc.write().unwrap().save_keyboard_mode(value);
} }
pub fn save_view_style(&mut self, value: String) { pub fn save_view_style(&mut self, value: String) {
@ -307,439 +264,6 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().info.platform.clone() 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<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
};
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<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,
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 { pub fn get_platform(&self, is_remote: bool) -> String {
if is_remote { if is_remote {
self.peer_platform() self.peer_platform()
@ -768,6 +292,13 @@ impl<T: InvokeUiSession> Session<T> {
return "".to_owned(); 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) { pub fn send_chat(&self, text: String) {
let mut misc = Misc::new(); let mut misc = Misc::new();
misc.set_chat_message(ChatMessage { misc.set_chat_message(ChatMessage {
@ -790,77 +321,14 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg_out)); 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) { pub fn enter(&self) {
IS_IN.store(true, Ordering::SeqCst); IS_IN.store(true, Ordering::SeqCst);
#[cfg(target_os = "linux")] keyboard::client::change_grab_status(GrabState::Run);
self.grab_hotkeys(true);
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
} }
pub fn leave(&self) { pub fn leave(&self) {
IS_IN.store(false, Ordering::SeqCst); IS_IN.store(false, Ordering::SeqCst);
#[cfg(target_os = "linux")] keyboard::client::change_grab_status(GrabState::Wait);
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)
} }
// flutter only TODO new input // flutter only TODO new input
@ -874,9 +342,6 @@ impl<T: InvokeUiSession> Session<T> {
shift: bool, shift: bool,
command: bool, command: bool,
) { ) {
if HOTKEY_HOOKED.load(Ordering::SeqCst) {
return;
}
let chars: Vec<char> = name.chars().collect(); let chars: Vec<char> = name.chars().collect();
if chars.len() == 1 { if chars.len() == 1 {
let key = Key::_Raw(chars[0] as _); let key = Key::_Raw(chars[0] as _);
@ -897,6 +362,40 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg_out)); 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 // flutter only TODO new input
fn _input_key( fn _input_key(
&self, &self,
@ -921,25 +420,6 @@ impl<T: InvokeUiSession> Session<T> {
key_event.set_chr(chr); key_event.set_chr(chr);
} }
Key::ControlKey(key) => { 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_event.set_control_key(key.clone());
} }
Key::_Raw(raw) => { Key::_Raw(raw) => {
@ -947,17 +427,15 @@ impl<T: InvokeUiSession> Session<T> {
} }
} }
#[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 { if v == 1 {
key_event.down = true; key_event.down = true;
} else if v == 3 { } else if v == 3 {
key_event.press = true; 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( pub fn send_mouse(
@ -979,8 +457,9 @@ impl<T: InvokeUiSession> Session<T> {
} }
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] // #[cfg(not(any(target_os = "android", target_os = "ios")))]
let (alt, ctrl, shift, command) = get_all_hotkey_state(alt, ctrl, shift, command); let (alt, ctrl, shift, command) =
keyboard::client::get_modifiers_state(alt, ctrl, shift, command);
send_mouse(mask, x, y, alt, ctrl, shift, command, self); 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 // on macos, ctrl + left button down = right button down, up won't emit, so we need to
@ -1243,23 +722,6 @@ impl<T: InvokeUiSession> Interface for Session<T> {
crate::platform::windows::add_recent_document(&path); 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) { async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream) {
@ -1300,113 +762,14 @@ impl<T: InvokeUiSession> Interface for Session<T> {
} }
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))]
impl<T: InvokeUiSession> Session<T> { impl<T: InvokeUiSession> Session<T> {
fn handle_hotkey_event(&self, event: Event) { pub fn lock_screen(&self) {
// if is long press, don't do anything. log::info!("Sending key even");
if is_long_press(&event) { crate::keyboard::client::lock_screen();
return;
} }
pub fn ctrl_alt_del(&self) {
let (key, down) = match event.event_type { log::info!("Sending key even");
EventType::KeyPress(key) => (key, true), crate::keyboard::client::lock_screen();
EventType::KeyRelease(key) => (key, false),
_ => return,
};
self.key_down_or_up(down, key, event);
}
#[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);
}
});
} }
} }
@ -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 }); let body = serde_json::json!({ "id": id, "Id": conn_id, "note": note });
allow_err!(crate::post_request(url, body.to_string(), "").await); 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;
}