mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-12-13 02:39:10 +08:00
Merge pull request #2496 from asur4s/master
refactor keyboard of client
This commit is contained in:
commit
658d859860
@ -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();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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")]
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
@ -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::*,
|
||||||
|
@ -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;
|
||||||
|
@ -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(())
|
||||||
|
@ -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> {
|
||||||
|
@ -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> {
|
||||||
|
204
src/hbbs_http/record_upload.rs
Normal file
204
src/hbbs_http/record_upload.rs
Normal 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
621
src/keyboard.rs
Normal 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 #2211:CAPS LOCK don't work
|
||||||
|
if key == Key::CapsLock || key == Key::NumLock {
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||||
|
client::process_event(event);
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Some(event),
|
||||||
|
};
|
||||||
|
if let Err(error) = rdev::grab(func) {
|
||||||
|
log::error!("rdev Error: {:?}", error)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
rdev::start_grab_listen(move |event: Event| match event.event_type {
|
||||||
|
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
||||||
|
if let Key::Unknown(keycode) = key {
|
||||||
|
log::error!("rdev get unknown key, keycode is : {:?}", keycode);
|
||||||
|
} else {
|
||||||
|
client::process_event(event);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => Some(event),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
GrabState::Run => {
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
KEYBOARD_HOOKED.swap(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
rdev::enable_grab().ok();
|
||||||
|
}
|
||||||
|
GrabState::Wait => {
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
KEYBOARD_HOOKED.swap(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
rdev::disable_grab().ok();
|
||||||
|
}
|
||||||
|
GrabState::Exit => {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
rdev::exit_grab_listen().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_long_press(event: &Event) -> bool {
|
||||||
|
let keys = MODIFIERS_STATE.lock().unwrap();
|
||||||
|
match event.event_type {
|
||||||
|
EventType::KeyPress(k) => {
|
||||||
|
if let Some(&state) = keys.get(&k) {
|
||||||
|
if state == true {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_remote_keys() {
|
||||||
|
// todo!: client quit suddenly, how to release keys?
|
||||||
|
let to_release = TO_RELEASE.lock().unwrap();
|
||||||
|
let keys = to_release.iter().map(|&key| key).collect::<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) {}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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")))]
|
||||||
|
@ -715,3 +715,4 @@ pub fn get_double_click_time() -> u32 {
|
|||||||
double_click_time
|
double_click_time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,3 +75,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user