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('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();
},
)
];

View File

@ -692,10 +692,11 @@ class _RemotePageState extends State<RemotePage> {
}
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);
}

View File

@ -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();
});

View File

@ -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:

View File

@ -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")]

View File

@ -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<Sender<RecordState>>,
}
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<dyn RecorderApi>,
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));
}
}

View File

@ -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);
}

View File

@ -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::*,

View File

@ -217,7 +217,7 @@ pub fn core_main() -> Option<Vec<String>> {
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;

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);
let session = session.clone();
std::thread::spawn(move || {
// if flutter : disable keyboard listen
crate::client::disable_keyboard_listening();
io_loop(session);
});
Ok(())

View File

@ -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<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>> {
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<bool> {
}
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<bool> {

View File

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

View File

@ -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")))]

View File

@ -715,3 +715,4 @@ pub fn get_double_click_time() -> u32 {
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 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;
}
match evt.mode.unwrap() {
KeyboardMode::Legacy => {
legacy_keyboard_mode(evt);
if evt.down {
sync_status(evt)
}
match evt.mode.unwrap() {
KeyboardMode::Map => {
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 =
(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";
const MAX_NACK: usize = 3;
const MAX_DXGI_FAIL_TIME: usize = 5;
@ -376,7 +376,7 @@ pub mod server {
async fn run_ipc_client() {
use DataPortableService::*;
let postfix = IPC_PROFIX;
let postfix = IPC_SUFFIX;
match ipc::connect(1000, postfix).await {
Ok(mut stream) => {
@ -622,7 +622,7 @@ pub mod client {
async fn start_ipc_server_async(rx: mpsc::UnboundedReceiver<Data>) {
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();

View File

@ -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<Mutex<Option<Recorder>>> = 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<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<()> {
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
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> {
HashMap::<&str, String>::from_iter([
("id", id),

View File

@ -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<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)]
pub struct Session<T: InvokeUiSession> {
@ -92,11 +49,11 @@ impl<T: InvokeUiSession> Session<T> {
}
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<T: InvokeUiSession> Session<T> {
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 {
if is_remote {
self.peer_platform()
@ -768,6 +292,13 @@ impl<T: InvokeUiSession> Session<T> {
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<T: InvokeUiSession> Session<T> {
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<T: InvokeUiSession> Session<T> {
shift: bool,
command: bool,
) {
if HOTKEY_HOOKED.load(Ordering::SeqCst) {
return;
}
let chars: Vec<char> = name.chars().collect();
if chars.len() == 1 {
let key = Key::_Raw(chars[0] as _);
@ -897,6 +362,40 @@ impl<T: InvokeUiSession> Session<T> {
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<T: InvokeUiSession> Session<T> {
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<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 {
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<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);
// #[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<T: InvokeUiSession> Interface for Session<T> {
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<T: InvokeUiSession> Interface for Session<T> {
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
impl<T: InvokeUiSession> Session<T> {
fn handle_hotkey_event(&self, event: Event) {
// if is long press, don't do anything.
if is_long_press(&event) {
return;
pub fn lock_screen(&self) {
log::info!("Sending key even");
crate::keyboard::client::lock_screen();
}
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);
}
#[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;
}