mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-20 03:11:02 +03:00
* add the base crate and repoint the moved modules at it `libs/base` (crate `base`) takes the parts of hbb_common that only this app uses: `fs`, `platform`, `keyboard`, `message.proto`, and 145 of the 177 `config::keys` constants. hbb_common keeps what the server names, and the 32 keys it reads itself are re-exported from `base::config::keys` so call sites still see the full set through one path. Sources move verbatim. The only edits inside them are `crate::` prefixes that now have to say `hbb_common::`; `keyboard.rs` and `platform/windows.rs` are byte-identical. The crate stays on edition 2018, the edition the moved code was written under. `log`, `lazy_static` and `anyhow` become direct dependencies so the bare paths in that code resolve exactly as before, and its winapi features are spelled out rather than left to feature unification. Two call sites outside Rust and Cargo had to follow the move: the Android protobuf source dir, which still pointed at hbb_common/protos for message.proto, and the three AGENTS.md entries that named hbb_common for options, protos and file transfer. `scrap`'s `drm` feature now forwards to `base/wayland_probe`. Left pointing at hbb_common it would still have compiled, silently dropping the Wayland socket-probe fallback, so that forward is verified by a build with and without the feature. `config::keys` carries a test asserting its names stay disjoint from the ones hbb_common kept: the glob re-export and the local constants share a namespace, and Rust prefers the local item silently, so a name added to both sides would otherwise let client and server disagree with no diagnostic. Verified: macOS and Linux, debug and release, `--all-targets`; the 177 key constants diffed name-for-name and value-for-value; the generated protobuf types compared before and after; every `#[cfg]` gate on a moved import checked against its original; and every file that was `rustfmt`-clean before this change still is, compared against master file by file. Windows is checked by inspection only -- it cannot be compiled here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * one `use` per crate, and write the rule down `fs.rs` came out of the move with two ungated `use hbb_common::` statements, because the original single `use crate::{...}` had to give up `message_proto` to the new crate and the rest was left in a second block. Fold it back into one. A scan of the whole tree for the same shape finds nothing else: every other file with more than one top-level `use base::` or `use hbb_common::` is split by a `#[cfg]` that does not cover the whole block, or by `pub use` next to `use`. Those are the cases that cannot merge, so AGENTS.md now states both the rule and the exemption. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
945 lines
31 KiB
Rust
945 lines
31 KiB
Rust
use std::{
|
||
collections::HashMap,
|
||
ops::{Deref, DerefMut},
|
||
sync::{atomic::AtomicUsize, Arc, Mutex, RwLock},
|
||
};
|
||
|
||
use sciter::{
|
||
dom::{
|
||
event::{EventReason, BEHAVIOR_EVENTS, EVENT_GROUPS, PHASE_MASK},
|
||
Element, HELEMENT,
|
||
},
|
||
make_args,
|
||
video::{video_destination, AssetPtr, COLOR_SPACE},
|
||
Value,
|
||
};
|
||
|
||
use hbb_common::{
|
||
allow_err, log, rendezvous_proto::ConnType,
|
||
};
|
||
use base::{
|
||
fs::TransferJobMeta, message_proto::*,
|
||
};
|
||
|
||
use crate::{
|
||
client::*,
|
||
ui_session_interface::{InvokeUiSession, Session},
|
||
};
|
||
|
||
type Video = AssetPtr<video_destination>;
|
||
|
||
lazy_static::lazy_static! {
|
||
static ref VIDEO: Arc<Mutex<Option<Video>>> = Default::default();
|
||
}
|
||
|
||
/// SciterHandler
|
||
/// * element
|
||
/// * close_state for file path when close
|
||
#[derive(Clone, Default)]
|
||
pub struct SciterHandler {
|
||
element: Arc<Mutex<Option<Element>>>,
|
||
close_state: HashMap<String, String>,
|
||
}
|
||
|
||
impl SciterHandler {
|
||
#[inline]
|
||
fn call(&self, func: &str, args: &[Value]) {
|
||
if let Some(ref e) = self.element.lock().unwrap().as_ref() {
|
||
allow_err!(e.call_method(func, args));
|
||
}
|
||
}
|
||
|
||
#[inline]
|
||
fn call2(&self, func: &str, args: &[Value]) {
|
||
if let Some(ref e) = self.element.lock().unwrap().as_ref() {
|
||
allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..]));
|
||
}
|
||
}
|
||
|
||
fn make_displays_array(displays: &Vec<DisplayInfo>) -> Value {
|
||
let mut displays_value = Value::array(0);
|
||
for d in displays.iter() {
|
||
let mut display = Value::map();
|
||
display.set_item("x", d.x);
|
||
display.set_item("y", d.y);
|
||
display.set_item("width", d.width);
|
||
display.set_item("height", d.height);
|
||
display.set_item("cursor_embedded", d.cursor_embedded);
|
||
displays_value.push(display);
|
||
}
|
||
displays_value
|
||
}
|
||
|
||
fn make_platform_additions(data: &str) -> Option<Value> {
|
||
if let Ok(v2) = serde_json::from_str::<HashMap<String, serde_json::Value>>(data) {
|
||
let mut value = Value::map();
|
||
for (k, v) in v2 {
|
||
match v {
|
||
serde_json::Value::String(s) => {
|
||
value.set_item(k, s);
|
||
}
|
||
serde_json::Value::Number(n) => {
|
||
if let Some(n) = n.as_i64() {
|
||
value.set_item(k, n as i32);
|
||
} else if let Some(n) = n.as_f64() {
|
||
value.set_item(k, n);
|
||
}
|
||
}
|
||
serde_json::Value::Bool(b) => {
|
||
value.set_item(k, b);
|
||
}
|
||
serde_json::Value::Array(arr) if k == "supported_privacy_mode_impl" => {
|
||
let mut impls = Value::array(0);
|
||
for item in arr {
|
||
if let serde_json::Value::Array(entry) = item {
|
||
let impl_key = entry.get(0).and_then(|v| v.as_str());
|
||
let impl_name = entry.get(1).and_then(|v| v.as_str());
|
||
if let (Some(impl_key), Some(impl_name)) = (impl_key, impl_name) {
|
||
let mut impl_item = Value::array(0);
|
||
impl_item.push(impl_key);
|
||
impl_item.push(impl_name);
|
||
impls.push(impl_item);
|
||
}
|
||
}
|
||
}
|
||
value.set_item(k, impls);
|
||
}
|
||
_ => {
|
||
// ignore for now
|
||
}
|
||
}
|
||
}
|
||
if value.len() > 0 {
|
||
return Some(value);
|
||
} else {
|
||
None
|
||
}
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
}
|
||
|
||
impl InvokeUiSession for SciterHandler {
|
||
fn set_cursor_data(&self, cd: CursorData) {
|
||
let mut colors = hbb_common::compress::decompress(&cd.colors);
|
||
if colors.iter().filter(|x| **x != 0).next().is_none() {
|
||
log::info!("Fix transparent");
|
||
// somehow all 0 images shows black rect, here is a workaround
|
||
colors[3] = 1;
|
||
}
|
||
let mut png = Vec::new();
|
||
if let Ok(()) = repng::encode(&mut png, cd.width as _, cd.height as _, &colors) {
|
||
self.call(
|
||
"setCursorData",
|
||
&make_args!(
|
||
cd.id.to_string(),
|
||
cd.hotx,
|
||
cd.hoty,
|
||
cd.width,
|
||
cd.height,
|
||
&png[..]
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embedded: bool, scale: f64) {
|
||
let scale = if scale <= 0.0 { 1.0 } else { scale };
|
||
self.call("setDisplay", &make_args!(x, y, w, h, cursor_embedded, scale));
|
||
// https://sciter.com/forums/topic/color_spaceiyuv-crash
|
||
// Nothing spectacular in decoder – done on CPU side.
|
||
// So if you can do BGRA translation on your side – the better.
|
||
// BGRA is used as internal image format so it will not require additional transformations.
|
||
VIDEO.lock().unwrap().as_mut().map(|v| {
|
||
v.stop_streaming().ok();
|
||
let ok = v.start_streaming((w, h), COLOR_SPACE::Rgb32, None);
|
||
log::info!("[video] reinitialized: {:?}", ok);
|
||
});
|
||
}
|
||
|
||
fn update_privacy_mode(&self) {
|
||
self.call("updatePrivacyMode", &[]);
|
||
}
|
||
|
||
fn set_permission(&self, name: &str, value: bool) {
|
||
self.call2("setPermission", &make_args!(name, value));
|
||
}
|
||
|
||
fn close_success(&self) {
|
||
self.call2("closeSuccess", &make_args!());
|
||
}
|
||
|
||
fn update_quality_status(&self, status: QualityStatus) {
|
||
self.call2(
|
||
"updateQualityStatus",
|
||
&make_args!(
|
||
status.speed.map_or(Value::null(), |it| it.into()),
|
||
status
|
||
.fps
|
||
.iter()
|
||
.next()
|
||
.map_or(Value::null(), |(_, v)| (*v).into()),
|
||
status.delay.map_or(Value::null(), |it| it.into()),
|
||
status.target_bitrate.map_or(Value::null(), |it| it.into()),
|
||
status
|
||
.codec_format
|
||
.map_or(Value::null(), |it| it.to_string().into()),
|
||
status.chroma.map_or(Value::null(), |it| it.into())
|
||
),
|
||
);
|
||
}
|
||
|
||
fn set_cursor_id(&self, id: String) {
|
||
self.call("setCursorId", &make_args!(id));
|
||
}
|
||
|
||
fn set_cursor_position(&self, cp: CursorPosition) {
|
||
self.call("setCursorPosition", &make_args!(cp.x, cp.y));
|
||
}
|
||
|
||
fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str) {
|
||
self.call(
|
||
"setConnectionType",
|
||
&make_args!(is_secured, direct, stream_type.to_string()),
|
||
);
|
||
}
|
||
|
||
fn set_fingerprint(&self, _fingerprint: String) {}
|
||
|
||
fn job_error(&self, id: i32, err: String, file_num: i32) {
|
||
self.call("jobError", &make_args!(id, err, file_num));
|
||
}
|
||
|
||
fn job_done(&self, id: i32, file_num: i32) {
|
||
self.call("jobDone", &make_args!(id, file_num));
|
||
}
|
||
|
||
fn clear_all_jobs(&self) {
|
||
self.call("clearAllJobs", &make_args!());
|
||
}
|
||
|
||
fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool) {
|
||
let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(job_json);
|
||
if let Ok(job) = job {
|
||
let path;
|
||
let to;
|
||
if job.is_remote {
|
||
path = job.remote.clone();
|
||
to = job.to.clone();
|
||
} else {
|
||
path = job.to.clone();
|
||
to = job.remote.clone();
|
||
}
|
||
self.call(
|
||
"addJob",
|
||
&make_args!(
|
||
cnt,
|
||
path,
|
||
to,
|
||
job.file_num,
|
||
job.show_hidden,
|
||
job.is_remote,
|
||
auto_start
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
fn update_folder_files(
|
||
&self,
|
||
id: i32,
|
||
entries: &Vec<FileEntry>,
|
||
path: String,
|
||
_is_local: bool,
|
||
only_count: bool,
|
||
) {
|
||
let mut m = make_fd(id, entries, only_count);
|
||
m.set_item("path", path);
|
||
self.call("updateFolderFiles", &make_args!(m));
|
||
}
|
||
|
||
fn update_transfer_list(&self) {
|
||
self.call("updateTransferList", &make_args!());
|
||
}
|
||
|
||
fn confirm_delete_files(&self, id: i32, i: i32, name: String) {
|
||
self.call("confirmDeleteFiles", &make_args!(id, i, name));
|
||
}
|
||
|
||
fn override_file_confirm(
|
||
&self,
|
||
id: i32,
|
||
file_num: i32,
|
||
to: String,
|
||
is_upload: bool,
|
||
is_identical: bool,
|
||
) {
|
||
self.call(
|
||
"overrideFileConfirm",
|
||
&make_args!(id, file_num, to, is_upload, is_identical),
|
||
);
|
||
}
|
||
|
||
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64) {
|
||
self.call(
|
||
"jobProgress",
|
||
&make_args!(id, file_num, speed, finished_size),
|
||
);
|
||
}
|
||
|
||
fn adapt_size(&self) {
|
||
self.call("adaptSize", &make_args!());
|
||
}
|
||
|
||
fn on_rgba(&self, _display: usize, rgba: &mut scrap::ImageRgb) {
|
||
VIDEO
|
||
.lock()
|
||
.unwrap()
|
||
.as_mut()
|
||
.map(|v| v.render_frame(&rgba.raw).ok());
|
||
}
|
||
|
||
fn set_peer_info(&self, pi: &PeerInfo) {
|
||
let mut pi_sciter = Value::map();
|
||
pi_sciter.set_item("username", pi.username.clone());
|
||
pi_sciter.set_item("hostname", pi.hostname.clone());
|
||
pi_sciter.set_item("platform", pi.platform.clone());
|
||
pi_sciter.set_item("sas_enabled", pi.sas_enabled);
|
||
pi_sciter.set_item("displays", Self::make_displays_array(&pi.displays));
|
||
pi_sciter.set_item("current_display", pi.current_display);
|
||
pi_sciter.set_item("version", pi.version.clone());
|
||
if let Some(v) = Self::make_platform_additions(&pi.platform_additions) {
|
||
pi_sciter.set_item("platform_additions", v);
|
||
}
|
||
self.call("updatePi", &make_args!(pi_sciter));
|
||
}
|
||
|
||
fn set_displays(&self, displays: &Vec<DisplayInfo>) {
|
||
self.call(
|
||
"updateDisplays",
|
||
&make_args!(Self::make_displays_array(displays)),
|
||
);
|
||
}
|
||
|
||
fn set_platform_additions(&self, _data: &str) {
|
||
// Ignore for sciter version.
|
||
}
|
||
|
||
fn set_current_display(&self, _disp_idx: i32) {
|
||
self.call("setCurrentDisplay", &make_args!(_disp_idx));
|
||
}
|
||
|
||
fn set_multiple_windows_session(&self, sessions: Vec<WindowsSession>) {
|
||
let mut v = Value::array(0);
|
||
let mut sessions = sessions;
|
||
for s in sessions.drain(..) {
|
||
let mut obj = Value::map();
|
||
obj.set_item("sid", s.sid.to_string());
|
||
obj.set_item("name", s.name);
|
||
v.push(obj);
|
||
}
|
||
self.call("setMultipleWindowsSession", &make_args!(v));
|
||
}
|
||
|
||
fn on_connected(&self, conn_type: ConnType) {
|
||
match conn_type {
|
||
ConnType::DEFAULT_CONN => {
|
||
crate::keyboard::client::start_grab_loop();
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
||
self.call2(
|
||
"msgbox_retry",
|
||
&make_args!(msgtype, title, text, link, retry),
|
||
);
|
||
}
|
||
|
||
fn cancel_msgbox(&self, tag: &str) {
|
||
self.call("cancel_msgbox", &make_args!(tag));
|
||
}
|
||
|
||
fn new_message(&self, msg: String) {
|
||
self.call("newMessage", &make_args!(msg));
|
||
}
|
||
|
||
fn switch_display(&self, display: &SwitchDisplay) {
|
||
self.call("switchDisplay", &make_args!(display.display));
|
||
}
|
||
|
||
fn update_block_input_state(&self, on: bool) {
|
||
self.call("updateBlockInputState", &make_args!(on));
|
||
}
|
||
|
||
fn switch_back(&self, _id: &str) {}
|
||
|
||
fn portable_service_running(&self, _running: bool) {}
|
||
|
||
fn on_voice_call_started(&self) {
|
||
self.call("onVoiceCallStart", &make_args!());
|
||
}
|
||
|
||
fn on_voice_call_closed(&self, reason: &str) {
|
||
self.call("onVoiceCallClosed", &make_args!(reason));
|
||
}
|
||
|
||
fn on_voice_call_waiting(&self) {
|
||
self.call("onVoiceCallWaiting", &make_args!());
|
||
}
|
||
|
||
fn on_voice_call_incoming(&self) {
|
||
self.call("onVoiceCallIncoming", &make_args!());
|
||
}
|
||
|
||
/// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui.
|
||
fn get_rgba(&self, _display: usize) -> *const u8 {
|
||
std::ptr::null()
|
||
}
|
||
|
||
fn next_rgba(&self, _display: usize) {}
|
||
|
||
fn update_record_status(&self, start: bool) {
|
||
self.call("updateRecordStatus", &make_args!(start));
|
||
}
|
||
|
||
fn printer_request(&self, id: i32, path: String) {
|
||
self.call("printerRequest", &make_args!(id, path));
|
||
}
|
||
|
||
fn handle_screenshot_resp(&self, _sid: String, msg: String) {
|
||
self.call("screenshot", &make_args!(msg));
|
||
}
|
||
|
||
fn handle_terminal_response(&self, _response: TerminalResponse) {
|
||
// Terminal support is not implemented for Sciter UI
|
||
// This is a stub implementation to satisfy the trait requirements
|
||
}
|
||
}
|
||
|
||
pub struct SciterSession(Session<SciterHandler>);
|
||
|
||
impl Deref for SciterSession {
|
||
type Target = Session<SciterHandler>;
|
||
fn deref(&self) -> &Self::Target {
|
||
&self.0
|
||
}
|
||
}
|
||
|
||
impl DerefMut for SciterSession {
|
||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||
&mut self.0
|
||
}
|
||
}
|
||
|
||
impl sciter::EventHandler for SciterSession {
|
||
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
|
||
Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT)
|
||
}
|
||
|
||
fn attached(&mut self, root: HELEMENT) {
|
||
*self.element.lock().unwrap() = Some(Element::from(root));
|
||
}
|
||
|
||
fn detached(&mut self, _root: HELEMENT) {
|
||
*self.element.lock().unwrap() = None;
|
||
self.sender.write().unwrap().take().map(|sender| {
|
||
sender.send(Data::Close).ok();
|
||
});
|
||
}
|
||
|
||
// https://github.com/sciter-sdk/rust-sciter/blob/master/examples/video.rs
|
||
fn on_event(
|
||
&mut self,
|
||
_root: HELEMENT,
|
||
source: HELEMENT,
|
||
_target: HELEMENT,
|
||
code: BEHAVIOR_EVENTS,
|
||
phase: PHASE_MASK,
|
||
reason: EventReason,
|
||
) -> bool {
|
||
if phase != PHASE_MASK::BUBBLING {
|
||
return false;
|
||
}
|
||
match code {
|
||
BEHAVIOR_EVENTS::VIDEO_BIND_RQ => {
|
||
let source = Element::from(source);
|
||
log::debug!("[video] {:?} {} ({:?})", code, source, reason);
|
||
if let EventReason::VideoBind(ptr) = reason {
|
||
if ptr.is_null() {
|
||
return true;
|
||
}
|
||
let site = AssetPtr::adopt(ptr as *mut video_destination);
|
||
log::debug!("[video] start video");
|
||
*VIDEO.lock().unwrap() = Some(site);
|
||
self.reconnect(false);
|
||
}
|
||
}
|
||
BEHAVIOR_EVENTS::VIDEO_INITIALIZED => {
|
||
log::debug!("[video] {:?}", code);
|
||
}
|
||
BEHAVIOR_EVENTS::VIDEO_STARTED => {
|
||
log::debug!("[video] {:?}", code);
|
||
let source = Element::from(source);
|
||
use sciter::dom::ELEMENT_AREAS;
|
||
let flags = ELEMENT_AREAS::CONTENT_BOX as u32 | ELEMENT_AREAS::SELF_RELATIVE as u32;
|
||
let rc = source.get_location(flags).unwrap_or_default();
|
||
log::debug!(
|
||
"[video] start video thread on <{}> which is about {:?} pixels",
|
||
source,
|
||
rc.size()
|
||
);
|
||
}
|
||
BEHAVIOR_EVENTS::VIDEO_STOPPED => {
|
||
log::debug!("[video] {:?}", code);
|
||
}
|
||
_ => return false,
|
||
};
|
||
return true;
|
||
}
|
||
|
||
sciter::dispatch_script_call! {
|
||
fn get_audit_server(String);
|
||
fn send_note(String);
|
||
fn is_xfce();
|
||
fn get_id();
|
||
fn get_default_pi();
|
||
fn get_option(String);
|
||
fn get_local_option(String);
|
||
fn t(String);
|
||
fn set_option(String, String);
|
||
fn input_os_password(String, bool);
|
||
fn save_close_state(String, String);
|
||
fn is_file_transfer();
|
||
fn is_port_forward();
|
||
fn is_rdp();
|
||
fn login(String, String, String, bool);
|
||
fn send2fa(String, bool);
|
||
fn continue_insecure_connection(bool);
|
||
fn get_enable_trusted_devices();
|
||
fn new_rdp();
|
||
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
|
||
fn enter(String);
|
||
fn leave(String);
|
||
fn ctrl_alt_del();
|
||
fn transfer_file();
|
||
fn tunnel();
|
||
fn lock_screen();
|
||
fn reconnect(bool);
|
||
fn get_chatbox();
|
||
fn get_icon();
|
||
fn get_home_dir();
|
||
fn get_next_job_id();
|
||
fn update_next_job_id(i32);
|
||
fn read_dir(String, bool);
|
||
fn remove_dir(i32, String, bool);
|
||
fn create_dir(i32, String, bool);
|
||
fn remove_file(i32, String, i32, bool);
|
||
fn read_remote_dir(String, bool);
|
||
fn send_chat(String);
|
||
fn switch_display(i32);
|
||
fn remove_dir_all(i32, String, bool, bool);
|
||
fn confirm_delete_files(i32, i32);
|
||
fn set_no_confirm(i32);
|
||
fn cancel_job(i32);
|
||
fn send_files(i32, i32, String, String, i32, bool, bool);
|
||
fn add_job(i32, i32, String, String, i32, bool, bool);
|
||
fn resume_job(i32, bool);
|
||
fn get_platform(bool);
|
||
fn get_path_sep(bool);
|
||
fn get_icon_path(i32, String);
|
||
fn get_char(String, i32);
|
||
fn get_size();
|
||
fn get_port_forwards();
|
||
fn remove_port_forward(i32);
|
||
fn get_args();
|
||
fn add_port_forward(i32, String, i32);
|
||
fn save_size(i32, i32, i32, i32);
|
||
fn get_view_style();
|
||
fn get_image_quality();
|
||
fn get_custom_image_quality();
|
||
fn save_view_style(String);
|
||
fn save_image_quality(String);
|
||
fn save_custom_image_quality(i32);
|
||
fn refresh_video(i32);
|
||
fn record_screen(bool);
|
||
fn is_screenshot_supported();
|
||
fn take_screenshot(i32, String);
|
||
fn handle_screenshot(String);
|
||
fn get_toggle_option(String);
|
||
fn is_privacy_mode_supported();
|
||
fn toggle_option(String);
|
||
fn toggle_privacy_mode(String, bool);
|
||
fn get_remember();
|
||
fn peer_platform();
|
||
fn set_write_override(i32, i32, bool, bool, bool);
|
||
fn get_keyboard_mode();
|
||
fn is_keyboard_mode_supported(String);
|
||
fn save_keyboard_mode(String);
|
||
fn alternative_codecs();
|
||
fn update_supported_decodings();
|
||
fn restart_remote_device();
|
||
fn request_voice_call();
|
||
fn close_voice_call();
|
||
fn version_cmp(String, String);
|
||
fn set_selected_windows_session_id(String);
|
||
fn is_recording();
|
||
fn has_file_clipboard();
|
||
fn get_printer_names();
|
||
fn on_printer_selected(i32, String, String);
|
||
}
|
||
}
|
||
|
||
impl SciterSession {
|
||
pub fn new(cmd: String, id: String, password: String, args: Vec<String>) -> Self {
|
||
let force_relay = args.contains(&"--relay".to_string());
|
||
let session: Session<SciterHandler> = Session {
|
||
password: password.clone(),
|
||
args,
|
||
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
||
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
||
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
||
reconnect_count: Arc::new(AtomicUsize::new(0)),
|
||
..Default::default()
|
||
};
|
||
|
||
let conn_type = if cmd.eq("--file-transfer") {
|
||
ConnType::FILE_TRANSFER
|
||
} else if cmd.eq("--view-camera") {
|
||
ConnType::VIEW_CAMERA
|
||
} else if cmd.eq("--port-forward") {
|
||
ConnType::PORT_FORWARD
|
||
} else if cmd.eq("--rdp") {
|
||
ConnType::RDP
|
||
} else {
|
||
ConnType::DEFAULT_CONN
|
||
};
|
||
|
||
session
|
||
.lc
|
||
.write()
|
||
.unwrap()
|
||
.initialize(id, conn_type, None, force_relay, None, None, None);
|
||
|
||
Self(session)
|
||
}
|
||
|
||
pub fn inner(&self) -> Session<SciterHandler> {
|
||
self.0.clone()
|
||
}
|
||
|
||
fn get_custom_image_quality(&mut self) -> Value {
|
||
let mut v = Value::array(0);
|
||
for x in self.lc.read().unwrap().custom_image_quality.iter() {
|
||
v.push(x);
|
||
}
|
||
v
|
||
}
|
||
|
||
pub fn t(&self, name: String) -> String {
|
||
crate::client::translate(name)
|
||
}
|
||
|
||
pub fn get_local_option(&self, key: String) -> String {
|
||
crate::ui_interface::get_local_option(key)
|
||
}
|
||
|
||
pub fn get_icon(&self) -> String {
|
||
super::get_icon()
|
||
}
|
||
|
||
fn alternative_codecs(&self) -> Value {
|
||
let (vp8, av1, h264, h265) = self.0.alternative_codecs();
|
||
let mut v = Value::array(0);
|
||
v.push(vp8);
|
||
v.push(av1);
|
||
v.push(h264);
|
||
v.push(h265);
|
||
v
|
||
}
|
||
|
||
fn save_size(&mut self, x: i32, y: i32, w: i32, h: i32) {
|
||
let size = (x, y, w, h);
|
||
let mut config = self.load_config();
|
||
if self.is_file_transfer() {
|
||
let close_state = self.close_state.clone();
|
||
let mut has_change = false;
|
||
for (k, mut v) in close_state {
|
||
if k == "remote_dir" {
|
||
v = self.lc.read().unwrap().get_all_remote_dir(v);
|
||
}
|
||
let v2 = if v.is_empty() { None } else { Some(&v) };
|
||
if v2 != config.options.get(&k) {
|
||
has_change = true;
|
||
if v2.is_none() {
|
||
config.options.remove(&k);
|
||
} else {
|
||
config.options.insert(k, v);
|
||
}
|
||
}
|
||
}
|
||
if size == config.size_ft && !has_change {
|
||
return;
|
||
}
|
||
config.size_ft = size;
|
||
} else if self.is_port_forward() {
|
||
if size == config.size_pf {
|
||
return;
|
||
}
|
||
config.size_pf = size;
|
||
} else {
|
||
if size == config.size {
|
||
return;
|
||
}
|
||
config.size = size;
|
||
}
|
||
self.save_config(config);
|
||
log::info!("size saved");
|
||
}
|
||
|
||
fn set_selected_windows_session_id(&mut self, u_sid: String) {
|
||
self.send_selected_session_id(u_sid);
|
||
}
|
||
|
||
fn has_file_clipboard(&self) -> bool {
|
||
cfg!(any(target_os = "windows", feature = "unix-file-copy-paste"))
|
||
}
|
||
|
||
fn get_port_forwards(&mut self) -> Value {
|
||
let port_forwards = self.lc.read().unwrap().port_forwards.clone();
|
||
let mut v = Value::array(0);
|
||
for (port, remote_host, remote_port) in port_forwards {
|
||
let mut v2 = Value::array(0);
|
||
v2.push(port);
|
||
v2.push(remote_host);
|
||
v2.push(remote_port);
|
||
v.push(v2);
|
||
}
|
||
v
|
||
}
|
||
|
||
fn get_args(&mut self) -> Value {
|
||
let mut v = Value::array(0);
|
||
for x in self.args.iter() {
|
||
v.push(x);
|
||
}
|
||
v
|
||
}
|
||
|
||
fn get_size(&mut self) -> Value {
|
||
let s = if self.is_file_transfer() {
|
||
self.lc.read().unwrap().size_ft
|
||
} else if self.is_port_forward() {
|
||
self.lc.read().unwrap().size_pf
|
||
} else {
|
||
self.lc.read().unwrap().size
|
||
};
|
||
let mut v = Value::array(0);
|
||
v.push(s.0);
|
||
v.push(s.1);
|
||
v.push(s.2);
|
||
v.push(s.3);
|
||
v
|
||
}
|
||
|
||
fn get_default_pi(&mut self) -> Value {
|
||
let mut pi = Value::map();
|
||
let info = self.lc.read().unwrap().info.clone();
|
||
pi.set_item("username", info.username.clone());
|
||
pi.set_item("hostname", info.hostname.clone());
|
||
pi.set_item("platform", info.platform.clone());
|
||
pi
|
||
}
|
||
|
||
fn save_close_state(&mut self, k: String, v: String) {
|
||
self.close_state.insert(k, v);
|
||
}
|
||
|
||
fn get_key_event(&self, down_or_up: i32, name: &str, code: i32) -> Option<KeyEvent> {
|
||
let mut key_event = KeyEvent::new();
|
||
if down_or_up == 2 {
|
||
/* windows send both keyup/keydown and keychar, so here we avoid keychar
|
||
for <= 0xFF, best practice should only avoid those not on keyboard, but
|
||
for now, we have no way to test, so avoid <= 0xFF totally
|
||
*/
|
||
if code <= 0xFF {
|
||
return None;
|
||
}
|
||
key_event.set_unicode(code.clone() as _);
|
||
} else if let Some(key) = KEY_MAP.get(name) {
|
||
match key {
|
||
Key::Chr(chr) => {
|
||
key_event.set_chr(chr.clone());
|
||
}
|
||
Key::ControlKey(key) => {
|
||
key_event.set_control_key(key.clone());
|
||
}
|
||
_ => {}
|
||
}
|
||
} else {
|
||
if cfg!(target_os = "macos") {
|
||
match code {
|
||
0x4C => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
|
||
0x69 => key_event.set_control_key(ControlKey::Snapshot),
|
||
0x72 => key_event.set_control_key(ControlKey::Help),
|
||
0x6E => key_event.set_control_key(ControlKey::Apps),
|
||
0x47 => {
|
||
key_event.set_control_key(if self.peer_platform() == "Mac OS" {
|
||
ControlKey::Clear
|
||
} else {
|
||
ControlKey::NumLock
|
||
});
|
||
}
|
||
0x51 => key_event.set_control_key(ControlKey::Equals),
|
||
0x2F => key_event.set_chr('.' as _),
|
||
0x32 => key_event.set_chr('`' as _),
|
||
_ => {
|
||
log::error!("Unknown key code {}", code);
|
||
return None;
|
||
}
|
||
}
|
||
} else if cfg!(windows) {
|
||
match code {
|
||
0x2C => key_event.set_control_key(ControlKey::Snapshot),
|
||
0x91 => key_event.set_control_key(ControlKey::Scroll),
|
||
0x90 => key_event.set_control_key(ControlKey::NumLock),
|
||
0x5C => key_event.set_control_key(ControlKey::RWin),
|
||
0x5B => key_event.set_control_key(ControlKey::Meta),
|
||
0x5D => key_event.set_control_key(ControlKey::Apps),
|
||
0xBE => key_event.set_chr('.' as _),
|
||
0xC0 => key_event.set_chr('`' as _),
|
||
_ => {
|
||
log::error!("Unknown key code {}", code);
|
||
return None;
|
||
}
|
||
}
|
||
} else if cfg!(target_os = "linux") {
|
||
match code {
|
||
65300 => key_event.set_control_key(ControlKey::Scroll),
|
||
65421 => key_event.set_control_key(ControlKey::NumpadEnter), // numpad enter
|
||
65407 => key_event.set_control_key(ControlKey::NumLock),
|
||
65515 => key_event.set_control_key(ControlKey::Meta),
|
||
65516 => key_event.set_control_key(ControlKey::RWin),
|
||
65513 => key_event.set_control_key(ControlKey::Alt),
|
||
65514 => key_event.set_control_key(ControlKey::RAlt),
|
||
65508 => key_event.set_control_key(ControlKey::RControl),
|
||
65506 => key_event.set_control_key(ControlKey::RShift),
|
||
96 => key_event.set_chr('`' as _),
|
||
46 => key_event.set_chr('.' as _),
|
||
126 => key_event.set_chr('`' as _),
|
||
33 => key_event.set_chr('1' as _),
|
||
64 => key_event.set_chr('2' as _),
|
||
35 => key_event.set_chr('3' as _),
|
||
36 => key_event.set_chr('4' as _),
|
||
37 => key_event.set_chr('5' as _),
|
||
94 => key_event.set_chr('6' as _),
|
||
38 => key_event.set_chr('7' as _),
|
||
42 => key_event.set_chr('8' as _),
|
||
40 => key_event.set_chr('9' as _),
|
||
41 => key_event.set_chr('0' as _),
|
||
95 => key_event.set_chr('-' as _),
|
||
43 => key_event.set_chr('=' as _),
|
||
123 => key_event.set_chr('[' as _),
|
||
125 => key_event.set_chr(']' as _),
|
||
124 => key_event.set_chr('\\' as _),
|
||
58 => key_event.set_chr(';' as _),
|
||
34 => key_event.set_chr('\'' as _),
|
||
60 => key_event.set_chr(',' as _),
|
||
62 => key_event.set_chr('.' as _),
|
||
63 => key_event.set_chr('/' as _),
|
||
_ => {
|
||
log::error!("Unknown key code {}", code);
|
||
return None;
|
||
}
|
||
}
|
||
} else {
|
||
log::error!("Unknown key code {}", code);
|
||
return None;
|
||
}
|
||
}
|
||
Some(key_event)
|
||
}
|
||
|
||
fn get_char(&mut self, name: String, code: i32) -> String {
|
||
if let Some(key_event) = self.get_key_event(1, &name, code) {
|
||
match key_event.union {
|
||
Some(key_event::Union::Chr(chr)) => {
|
||
if let Some(chr) = std::char::from_u32(chr as _) {
|
||
return chr.to_string();
|
||
}
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
"".to_owned()
|
||
}
|
||
|
||
fn transfer_file(&mut self) {
|
||
let id = self.get_id();
|
||
let args = vec!["--file-transfer", &id, &self.password];
|
||
if let Err(err) = crate::run_me(args) {
|
||
log::error!("Failed to spawn file transfer: {}", err);
|
||
}
|
||
}
|
||
|
||
fn tunnel(&mut self) {
|
||
let id = self.get_id();
|
||
let args = vec!["--port-forward", &id, &self.password];
|
||
if let Err(err) = crate::run_me(args) {
|
||
log::error!("Failed to spawn IP tunneling: {}", err);
|
||
}
|
||
}
|
||
|
||
fn version_cmp(&self, v1: String, v2: String) -> i32 {
|
||
(hbb_common::get_version_number(&v1) - hbb_common::get_version_number(&v2)) as i32
|
||
}
|
||
|
||
fn get_printer_names(&self) -> Value {
|
||
#[cfg(target_os = "windows")]
|
||
let printer_names = crate::platform::windows::get_printer_names().unwrap_or_default();
|
||
#[cfg(not(target_os = "windows"))]
|
||
let printer_names: Vec<String> = vec![];
|
||
let mut v = Value::array(0);
|
||
for name in printer_names {
|
||
v.push(name);
|
||
}
|
||
v
|
||
}
|
||
|
||
fn on_printer_selected(&self, id: i32, path: String, printer_name: String) {
|
||
self.printer_response(id, path, printer_name);
|
||
}
|
||
|
||
fn handle_screenshot(&self, action: String) -> String {
|
||
crate::client::screenshot::handle_screenshot(action)
|
||
}
|
||
}
|
||
|
||
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
|
||
let mut m = Value::map();
|
||
m.set_item("id", id);
|
||
let mut a = Value::array(0);
|
||
let mut n: u64 = 0;
|
||
for entry in entries {
|
||
n += entry.size;
|
||
if only_count {
|
||
continue;
|
||
}
|
||
let mut e = Value::map();
|
||
e.set_item("name", entry.name.to_owned());
|
||
let tmp = entry.entry_type.value();
|
||
e.set_item("type", if tmp == 0 { 1 } else { tmp });
|
||
e.set_item("time", entry.modified_time as f64);
|
||
e.set_item("size", entry.size as f64);
|
||
a.push(e);
|
||
}
|
||
if !only_count {
|
||
m.set_item("entries", a);
|
||
}
|
||
m.set_item("num_entries", entries.len() as i32);
|
||
m.set_item("total_size", n as f64);
|
||
m
|
||
}
|