mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'master' of https://github.com/rustdesk/rustdesk
This commit is contained in:
@@ -1666,6 +1666,7 @@ pub async fn handle_login_from_ui(
|
||||
/// Interface for client to send data and commands.
|
||||
#[async_trait]
|
||||
pub trait Interface: Send + Clone + 'static + Sized {
|
||||
/// Send message data to remote peer.
|
||||
fn send(&self, data: Data);
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str);
|
||||
fn handle_login_error(&mut self, err: &str) -> bool;
|
||||
|
||||
@@ -22,9 +22,9 @@ pub trait FileManager: Interface {
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||
fn read_dir(&self, path: &str, include_hidden: bool) -> String {
|
||||
use crate::common::make_fd_to_json;
|
||||
use crate::flutter::make_fd_to_json;
|
||||
match fs::read_dir(&fs::get_path(path), include_hidden) {
|
||||
Ok(fd) => make_fd_to_json(fd),
|
||||
Ok(fd) => make_fd_to_json(fd.id, fd.path, &fd.entries),
|
||||
Err(_) => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,11 @@ use crate::client::{
|
||||
Client, CodecFormat, FileManager, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
|
||||
SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED,
|
||||
};
|
||||
use crate::common;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
|
||||
|
||||
#[cfg(windows)]
|
||||
use clipboard::{
|
||||
cliprdr::CliprdrClientContext, create_cliprdr_context as create_clipboard_file_context,
|
||||
get_rx_clip_client, server_clip_file,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use crate::clipboard_file::*;
|
||||
use crate::ui_session_interface::{InvokeUi, Session};
|
||||
use crate::ui_session_interface::{InvokeUiSession, Session};
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
||||
@@ -28,14 +22,14 @@ use hbb_common::tokio::{
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
};
|
||||
use hbb_common::{allow_err, message_proto::*};
|
||||
use hbb_common::{allow_err, message_proto::*, sleep};
|
||||
use hbb_common::{fs, log, Stream};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct Remote<T: InvokeUi> {
|
||||
pub struct Remote<T: InvokeUiSession> {
|
||||
handler: Session<T>,
|
||||
video_sender: MediaSender,
|
||||
audio_sender: MediaSender,
|
||||
@@ -49,13 +43,13 @@ pub struct Remote<T: InvokeUi> {
|
||||
last_update_jobs_status: (Instant, HashMap<i32, u64>),
|
||||
first_frame: bool,
|
||||
#[cfg(windows)]
|
||||
clipboard_file_context: Option<Box<CliprdrClientContext>>,
|
||||
clipboard_file_context: Option<Box<clipboard::cliprdr::CliprdrClientContext>>,
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count: Arc<AtomicUsize>,
|
||||
video_format: CodecFormat,
|
||||
}
|
||||
|
||||
impl<T: InvokeUi> Remote<T> {
|
||||
impl<T: InvokeUiSession> Remote<T> {
|
||||
pub fn new(
|
||||
handler: Session<T>,
|
||||
video_sender: MediaSender,
|
||||
@@ -113,7 +107,7 @@ impl<T: InvokeUi> Remote<T> {
|
||||
#[cfg(not(windows))]
|
||||
let (_tx_holder, mut rx_clip_client) = mpsc::unbounded_channel::<i32>();
|
||||
#[cfg(windows)]
|
||||
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
||||
let mut rx_clip_client = clipboard::get_rx_clip_client().lock().await;
|
||||
|
||||
let mut status_timer = time::interval(Duration::new(1, 0));
|
||||
|
||||
@@ -159,7 +153,7 @@ impl<T: InvokeUi> Remote<T> {
|
||||
#[cfg(windows)]
|
||||
match _msg {
|
||||
Some((_, clip)) => {
|
||||
allow_err!(peer.send(&clip_2_msg(clip)).await);
|
||||
allow_err!(peer.send(&crate::clipboard_file::clip_2_msg(clip)).await);
|
||||
}
|
||||
None => {
|
||||
// unreachable!()
|
||||
@@ -275,51 +269,6 @@ impl<T: InvokeUi> Remote<T> {
|
||||
Some(tx)
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[allow(dead_code)]
|
||||
fn load_last_jobs(&mut self) {
|
||||
log::info!("start load last jobs");
|
||||
self.handler.clear_all_jobs();
|
||||
let pc = self.handler.load_config();
|
||||
if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() {
|
||||
// no last jobs
|
||||
return;
|
||||
}
|
||||
// TODO: can add a confirm dialog
|
||||
let mut cnt = 1;
|
||||
for job_str in pc.transfer.read_jobs.iter() {
|
||||
let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(&job_str);
|
||||
if let Ok(job) = job {
|
||||
self.handler.add_job(
|
||||
cnt,
|
||||
job.to.clone(),
|
||||
job.remote.clone(),
|
||||
job.file_num,
|
||||
job.show_hidden,
|
||||
false,
|
||||
);
|
||||
cnt += 1;
|
||||
println!("restore read_job: {:?}", job);
|
||||
}
|
||||
}
|
||||
for job_str in pc.transfer.write_jobs.iter() {
|
||||
let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(&job_str);
|
||||
if let Ok(job) = job {
|
||||
self.handler.add_job(
|
||||
cnt,
|
||||
job.remote.clone(),
|
||||
job.to.clone(),
|
||||
job.file_num,
|
||||
job.show_hidden,
|
||||
true,
|
||||
);
|
||||
cnt += 1;
|
||||
println!("restore write_job: {:?}", job);
|
||||
}
|
||||
}
|
||||
self.handler.update_transfer_list();
|
||||
}
|
||||
|
||||
async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool {
|
||||
match data {
|
||||
Data::Close => {
|
||||
@@ -381,8 +330,13 @@ impl<T: InvokeUi> Remote<T> {
|
||||
to,
|
||||
job.files().len()
|
||||
);
|
||||
// let m = make_fd(job.id(), job.files(), true);
|
||||
// self.handler.call("updateFolderFiles", &make_args!(m)); // TODO
|
||||
self.handler.update_folder_files(
|
||||
job.id(),
|
||||
job.files(),
|
||||
path,
|
||||
!is_remote,
|
||||
true,
|
||||
);
|
||||
#[cfg(not(windows))]
|
||||
let files = job.files().clone();
|
||||
#[cfg(windows)]
|
||||
@@ -441,8 +395,13 @@ impl<T: InvokeUi> Remote<T> {
|
||||
to,
|
||||
job.files().len()
|
||||
);
|
||||
// let m = make_fd(job.id(), job.files(), true);
|
||||
// self.handler.call("updateFolderFiles", &make_args!(m));
|
||||
self.handler.update_folder_files(
|
||||
job.id(),
|
||||
job.files(),
|
||||
path,
|
||||
!is_remote,
|
||||
true,
|
||||
);
|
||||
job.is_last_job = true;
|
||||
self.read_jobs.push(job);
|
||||
self.timer = time::interval(MILLI1);
|
||||
@@ -493,7 +452,6 @@ impl<T: InvokeUi> Remote<T> {
|
||||
file_num,
|
||||
job.files[i].name.clone(),
|
||||
);
|
||||
self.handler.confirm_delete_files(id, file_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,8 +512,13 @@ impl<T: InvokeUi> Remote<T> {
|
||||
} else {
|
||||
match fs::get_recursive_files(&path, include_hidden) {
|
||||
Ok(entries) => {
|
||||
// let m = make_fd(id, &entries, true);
|
||||
// self.handler.call("updateFolderFiles", &make_args!(m));
|
||||
self.handler.update_folder_files(
|
||||
id,
|
||||
&entries,
|
||||
path.clone(),
|
||||
!is_remote,
|
||||
false,
|
||||
);
|
||||
self.remove_jobs
|
||||
.insert(id, RemoveJob::new(entries, path, sep, is_remote));
|
||||
}
|
||||
@@ -757,28 +720,28 @@ impl<T: InvokeUi> Remote<T> {
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
self.handler.handle_peer_info(pi);
|
||||
// self.check_clipboard_file_context();
|
||||
// if !(self.handler.is_file_transfer()
|
||||
// || self.handler.is_port_forward()
|
||||
// || !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
// || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
// || self.handler.lc.read().unwrap().disable_clipboard)
|
||||
// {
|
||||
// let txt = self.old_clipboard.lock().unwrap().clone();
|
||||
// if !txt.is_empty() {
|
||||
// let msg_out = crate::create_clipboard_msg(txt);
|
||||
// let sender = self.sender.clone();
|
||||
// tokio::spawn(async move {
|
||||
// // due to clipboard service interval time
|
||||
// sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await;
|
||||
// sender.send(Data::Message(msg_out)).ok();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer()
|
||||
|| self.handler.is_port_forward()
|
||||
|| !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|
||||
|| self.handler.lc.read().unwrap().disable_clipboard)
|
||||
{
|
||||
let txt = self.old_clipboard.lock().unwrap().clone();
|
||||
if !txt.is_empty() {
|
||||
let msg_out = crate::create_clipboard_msg(txt);
|
||||
let sender = self.sender.clone();
|
||||
tokio::spawn(async move {
|
||||
// due to clipboard service interval time
|
||||
sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await;
|
||||
sender.send(Data::Message(msg_out)).ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if self.handler.is_file_transfer() {
|
||||
// self.load_last_jobs().await;
|
||||
// }
|
||||
if self.handler.is_file_transfer() {
|
||||
self.handler.load_last_jobs();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -812,8 +775,8 @@ impl<T: InvokeUi> Remote<T> {
|
||||
Some(message::Union::Cliprdr(clip)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard {
|
||||
if let Some(context) = &mut self.clipboard_file_context {
|
||||
if let Some(clip) = msg_2_clip(clip) {
|
||||
server_clip_file(context, 0, clip);
|
||||
if let Some(clip) = crate::clipboard_file::msg_2_clip(clip) {
|
||||
clipboard::server_clip_file(context, 0, clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -831,11 +794,13 @@ impl<T: InvokeUi> Remote<T> {
|
||||
fs::transform_windows_path(&mut entries);
|
||||
}
|
||||
}
|
||||
// let mut m = make_fd(fd.id, &entries, fd.id > 0);
|
||||
// if fd.id <= 0 {
|
||||
// m.set_item("path", fd.path);
|
||||
// }
|
||||
// self.handler.call("updateFolderFiles", &make_args!(m));
|
||||
self.handler.update_folder_files(
|
||||
fd.id,
|
||||
&entries,
|
||||
fd.path,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
|
||||
log::info!("job set_files: {:?}", entries);
|
||||
job.set_files(entries);
|
||||
@@ -1163,7 +1128,7 @@ impl<T: InvokeUi> Remote<T> {
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer;
|
||||
if enabled == self.clipboard_file_context.is_none() {
|
||||
self.clipboard_file_context = if enabled {
|
||||
match create_clipboard_file_context(true, false) {
|
||||
match clipboard::create_cliprdr_context(true, false) {
|
||||
Ok(context) => {
|
||||
log::info!("clipboard context for file transfer created.");
|
||||
Some(context)
|
||||
|
||||
@@ -667,51 +667,6 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
|
||||
msg_out
|
||||
}
|
||||
|
||||
pub fn make_fd_to_json(fd: FileDirectory) -> String {
|
||||
let mut fd_json = serde_json::Map::new();
|
||||
fd_json.insert("id".into(), json!(fd.id));
|
||||
fd_json.insert("path".into(), json!(fd.path));
|
||||
|
||||
let mut entries = vec![];
|
||||
for entry in fd.entries {
|
||||
let mut entry_map = serde_json::Map::new();
|
||||
entry_map.insert("entry_type".into(), json!(entry.entry_type.value()));
|
||||
entry_map.insert("name".into(), json!(entry.name));
|
||||
entry_map.insert("size".into(), json!(entry.size));
|
||||
entry_map.insert("modified_time".into(), json!(entry.modified_time));
|
||||
entries.push(entry_map);
|
||||
}
|
||||
fd_json.insert("entries".into(), json!(entries));
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> String {
|
||||
let mut m = serde_json::Map::new();
|
||||
m.insert("id".into(), json!(id));
|
||||
let mut a = vec![];
|
||||
let mut n: u64 = 0;
|
||||
for entry in entries {
|
||||
n += entry.size;
|
||||
if only_count {
|
||||
continue;
|
||||
}
|
||||
let mut e = serde_json::Map::new();
|
||||
e.insert("name".into(), json!(entry.name.to_owned()));
|
||||
let tmp = entry.entry_type.value();
|
||||
e.insert("type".into(), json!(if tmp == 0 { 1 } else { tmp }));
|
||||
e.insert("time".into(), json!(entry.modified_time as f64));
|
||||
e.insert("size".into(), json!(entry.size as f64));
|
||||
a.push(e);
|
||||
}
|
||||
if only_count {
|
||||
m.insert("num_entries".into(), json!(entries.len() as i32));
|
||||
} else {
|
||||
m.insert("entries".into(), json!(a));
|
||||
}
|
||||
m.insert("total_size".into(), json!(n as f64));
|
||||
serde_json::to_string(&m).unwrap_or("".into())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_X11: Mutex<bool> = Mutex::new(false);
|
||||
@@ -721,4 +676,4 @@ lazy_static::lazy_static! {
|
||||
#[cfg(target_os = "linux")]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_X11: Mutex<bool> = Mutex::new("x11" == hbb_common::platform::linux::get_display_server());
|
||||
}
|
||||
}
|
||||
801
src/flutter.rs
801
src/flutter.rs
@@ -5,16 +5,17 @@ use std::{
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||
|
||||
use hbb_common::{bail, config::LocalConfig, message_proto::*, ResultType, rendezvous_proto::ConnType};
|
||||
use hbb_common::{
|
||||
bail, config::LocalConfig, message_proto::*, rendezvous_proto::ConnType, ResultType,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::ui_session_interface::{io_loop, InvokeUi, Session};
|
||||
use crate::ui_session_interface::{io_loop, InvokeUiSession, Session};
|
||||
|
||||
use crate::{client::*, flutter_ffi::EventToUI};
|
||||
|
||||
pub(super) const APP_TYPE_MAIN: &str = "main";
|
||||
#[allow(dead_code)]
|
||||
pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||
#[allow(dead_code)]
|
||||
pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -46,7 +47,7 @@ impl FlutterHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl InvokeUi for FlutterHandler {
|
||||
impl InvokeUiSession for FlutterHandler {
|
||||
fn set_cursor_data(&self, cd: CursorData) {
|
||||
let colors = hbb_common::compress::decompress(&cd.colors);
|
||||
self.push_event(
|
||||
@@ -87,6 +88,7 @@ impl InvokeUi for FlutterHandler {
|
||||
self.push_event("permission", vec![(name, &value.to_string())]);
|
||||
}
|
||||
|
||||
// unused in flutter
|
||||
fn close_success(&self) {}
|
||||
|
||||
fn update_quality_status(&self, status: QualityStatus) {
|
||||
@@ -119,8 +121,15 @@ impl InvokeUi for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn job_error(&self, id: i32, err: String, _file_num: i32) {
|
||||
self.push_event("job_error", vec![("id", &id.to_string()), ("err", &err)]);
|
||||
fn job_error(&self, id: i32, err: String, file_num: i32) {
|
||||
self.push_event(
|
||||
"job_error",
|
||||
vec![
|
||||
("id", &id.to_string()),
|
||||
("err", &err),
|
||||
("file_num", &file_num.to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn job_done(&self, id: i32, file_num: i32) {
|
||||
@@ -130,31 +139,43 @@ impl InvokeUi for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn clear_all_jobs(&self) {
|
||||
// todo!()
|
||||
// unused in flutter
|
||||
fn clear_all_jobs(&self) {}
|
||||
|
||||
fn load_last_job(&self, _cnt: i32, job_json: &str) {
|
||||
self.push_event("load_last_job", vec![("value", job_json)]);
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn add_job(
|
||||
fn update_folder_files(
|
||||
&self,
|
||||
id: i32,
|
||||
entries: &Vec<FileEntry>,
|
||||
path: String,
|
||||
to: String,
|
||||
file_num: i32,
|
||||
show_hidden: bool,
|
||||
is_remote: bool,
|
||||
is_local: bool,
|
||||
only_count: bool,
|
||||
) {
|
||||
// todo!()
|
||||
// TODO opt
|
||||
if only_count {
|
||||
self.push_event(
|
||||
"update_folder_files",
|
||||
vec![("info", &make_fd_flutter(id, entries, only_count))],
|
||||
);
|
||||
} else {
|
||||
self.push_event(
|
||||
"file_dir",
|
||||
vec![
|
||||
("value", &make_fd_to_json(id, path, entries)),
|
||||
("is_local", "false"),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_transfer_list(&self) {
|
||||
// todo!()
|
||||
}
|
||||
// unused in flutter
|
||||
fn update_transfer_list(&self) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn confirm_delete_files(&self, id: i32, i: i32, name: String) {
|
||||
// todo!()
|
||||
}
|
||||
// unused in flutter // TEST flutter
|
||||
fn confirm_delete_files(&self, _id: i32, _i: i32, _name: String) {}
|
||||
|
||||
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool) {
|
||||
self.push_event(
|
||||
@@ -180,6 +201,7 @@ impl InvokeUi for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// unused in flutter
|
||||
fn adapt_size(&self) {}
|
||||
|
||||
fn on_rgba(&self, data: &[u8]) {
|
||||
@@ -287,11 +309,7 @@ pub fn session_add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> R
|
||||
.unwrap()
|
||||
.initialize(session_id, conn_type);
|
||||
|
||||
if let Some(same_id_session) = SESSIONS
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(id.to_owned(), session)
|
||||
{
|
||||
if let Some(same_id_session) = SESSIONS.write().unwrap().insert(id.to_owned(), session) {
|
||||
same_id_session.close();
|
||||
}
|
||||
|
||||
@@ -320,624 +338,88 @@ pub fn session_start_(id: &str, event_stream: StreamSink<EventToUI>) -> ResultTy
|
||||
// Server Side
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
pub mod connection_manager {
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::FromIterator,
|
||||
sync::{
|
||||
atomic::{AtomicI64, Ordering},
|
||||
RwLock,
|
||||
},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::Config,
|
||||
fs::is_write_need_confirmation,
|
||||
fs::{self, get_string, new_send_confirm, DigestCheckResult},
|
||||
log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc::{self, UnboundedSender},
|
||||
task::spawn_blocking,
|
||||
},
|
||||
};
|
||||
use hbb_common::log;
|
||||
#[cfg(any(target_os = "android"))]
|
||||
use scrap::android::call_main_service_set_by_name;
|
||||
|
||||
use crate::ipc::Data;
|
||||
use crate::ipc::{self, new_listener, Connection};
|
||||
use crate::ui_cm_interface::InvokeUiCM;
|
||||
|
||||
use super::GLOBAL_EVENT_STREAM;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct Client {
|
||||
id: i32,
|
||||
pub authorized: bool,
|
||||
is_file_transfer: bool,
|
||||
name: String,
|
||||
peer_id: String,
|
||||
keyboard: bool,
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
#[serde(skip)]
|
||||
tx: UnboundedSender<Data>,
|
||||
#[derive(Clone)]
|
||||
struct FlutterHandler {}
|
||||
|
||||
impl InvokeUiCM for FlutterHandler {
|
||||
//TODO port_forward
|
||||
fn add_connection(&self, client: &crate::ui_cm_interface::Client) {
|
||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||
// send to Android service, active notification no matter UI is shown or not.
|
||||
#[cfg(any(target_os = "android"))]
|
||||
if let Err(e) =
|
||||
call_main_service_set_by_name("add_connection", Some(&client_json), None)
|
||||
{
|
||||
log::debug!("call_service_set_by_name fail,{}", e);
|
||||
}
|
||||
// send to UI, refresh widget
|
||||
self.push_event("add_connection", vec![("client", &client_json)]); // TODO use add_connection
|
||||
}
|
||||
|
||||
fn remove_connection(&self, id: i32) {
|
||||
self.push_event("on_client_remove", vec![("id", &id.to_string())]);
|
||||
}
|
||||
|
||||
fn new_message(&self, id: i32, text: String) {
|
||||
self.push_event(
|
||||
"chat_server_mode",
|
||||
vec![("id", &id.to_string()), ("text", &text)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CLIENTS: RwLock<HashMap<i32,Client>> = Default::default();
|
||||
impl FlutterHandler {
|
||||
fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
|
||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||
assert!(h.get("name").is_none());
|
||||
h.insert("name", name);
|
||||
|
||||
if let Some(s) = GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(super::APP_TYPE_MAIN)
|
||||
{
|
||||
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
|
||||
// // TODO clipboard_file
|
||||
// enum ClipboardFileData {
|
||||
// #[cfg(windows)]
|
||||
// Clip((i32, ipc::ClipbaordFile)),
|
||||
// Enable((i32, bool)),
|
||||
// }
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start_listen_ipc_thread() {
|
||||
std::thread::spawn(move || start_ipc());
|
||||
}
|
||||
use crate::ui_cm_interface::{start_ipc, ConnectionManager};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_ipc() {
|
||||
// TODO clipboard_file
|
||||
// let (tx_file, _rx_file) = mpsc::unbounded_channel::<ClipboardFileData>();
|
||||
// #[cfg(windows)]
|
||||
// let cm_clip = cm.clone();
|
||||
// #[cfg(windows)]
|
||||
// std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file));
|
||||
#[cfg(target_os = "linux")]
|
||||
std::thread::spawn(crate::ipc::start_pa);
|
||||
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
log::info!("try create privacy mode window");
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||
log::warn!(
|
||||
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
allow_err!(crate::ui::win_privacy::start());
|
||||
});
|
||||
|
||||
match new_listener("_cm").await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection");
|
||||
let mut stream = Connection::new(stream);
|
||||
// let tx_file = tx_file.clone();
|
||||
tokio::spawn(async move {
|
||||
// for tmp use, without real conn id
|
||||
let conn_id_tmp = -1;
|
||||
let mut conn_id: i32 = 0;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("cm ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart} => {
|
||||
log::debug!("conn_id: {}", id);
|
||||
conn_id = id;
|
||||
// tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||
on_login(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
||||
}
|
||||
Data::Close => {
|
||||
// tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||
log::info!("cm ipc connection closed from connection request");
|
||||
break;
|
||||
}
|
||||
Data::PrivacyModeState((_, _)) => {
|
||||
conn_id = conn_id_tmp;
|
||||
allow_err!(tx.send(data));
|
||||
}
|
||||
Data::ClickTime(ms) => {
|
||||
CLICK_TIME.store(ms, Ordering::SeqCst);
|
||||
}
|
||||
Data::ChatMessage { text } => {
|
||||
handle_chat(conn_id, text);
|
||||
}
|
||||
Data::FS(fs) => {
|
||||
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||
}
|
||||
// TODO ClipbaordFile
|
||||
// #[cfg(windows)]
|
||||
// Data::ClipbaordFile(_clip) => {
|
||||
// tx_file
|
||||
// .send(ClipboardFileData::Clip((id, _clip)))
|
||||
// .ok();
|
||||
// }
|
||||
// #[cfg(windows)]
|
||||
// Data::ClipboardFileEnabled(enabled) => {
|
||||
// tx_file
|
||||
// .send(ClipboardFileData::Enable((id, enabled)))
|
||||
// .ok();
|
||||
// }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(data) = rx.recv() => {
|
||||
if stream.send(&data).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if conn_id != conn_id_tmp {
|
||||
remove_connection(conn_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get cm client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start cm ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
// crate::platform::quit_gui();
|
||||
// TODO flutter quit_gui
|
||||
let cm = ConnectionManager {
|
||||
ui_handler: FlutterHandler {},
|
||||
};
|
||||
std::thread::spawn(move || start_ipc(cm));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn start_channel(rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
||||
std::thread::spawn(move || start_listen(rx, tx));
|
||||
}
|
||||
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_listen(mut rx: UnboundedReceiver<Data>, tx: UnboundedSender<Data>) {
|
||||
let mut current_id = 0;
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Some(Data::Login {
|
||||
id,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
peer_id,
|
||||
name,
|
||||
authorized,
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
..
|
||||
}) => {
|
||||
current_id = id;
|
||||
on_login(
|
||||
id,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
peer_id,
|
||||
name,
|
||||
authorized,
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
tx.clone(),
|
||||
);
|
||||
}
|
||||
Some(Data::ChatMessage { text }) => {
|
||||
handle_chat(current_id, text);
|
||||
}
|
||||
Some(Data::FS(fs)) => {
|
||||
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||
}
|
||||
Some(Data::Close) => {
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
remove_connection(current_id);
|
||||
}
|
||||
|
||||
fn on_login(
|
||||
id: i32,
|
||||
is_file_transfer: bool,
|
||||
_port_forward: String,
|
||||
peer_id: String,
|
||||
name: String,
|
||||
authorized: bool,
|
||||
keyboard: bool,
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
pub fn start_channel(
|
||||
rx: UnboundedReceiver<crate::ipc::Data>,
|
||||
tx: UnboundedSender<crate::ipc::Data>,
|
||||
) {
|
||||
let mut client = Client {
|
||||
id,
|
||||
authorized,
|
||||
is_file_transfer,
|
||||
name: name.clone(),
|
||||
peer_id: peer_id.clone(),
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
tx,
|
||||
use crate::ui_cm_interface::start_listen;
|
||||
let cm = crate::ui_cm_interface::ConnectionManager {
|
||||
ui_handler: FlutterHandler {},
|
||||
};
|
||||
if authorized {
|
||||
client.authorized = true;
|
||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||
// send to Android service, active notification no matter UI is shown or not.
|
||||
#[cfg(any(target_os = "android"))]
|
||||
if let Err(e) =
|
||||
call_main_service_set_by_name("on_client_authorized", Some(&client_json), None)
|
||||
{
|
||||
log::debug!("call_service_set_by_name fail,{}", e);
|
||||
}
|
||||
// send to UI, refresh widget
|
||||
push_event("on_client_authorized", vec![("client", &client_json)]);
|
||||
} else {
|
||||
let client_json = serde_json::to_string(&client).unwrap_or("".into());
|
||||
// send to Android service, active notification no matter UI is shown or not.
|
||||
#[cfg(any(target_os = "android"))]
|
||||
if let Err(e) =
|
||||
call_main_service_set_by_name("try_start_without_auth", Some(&client_json), None)
|
||||
{
|
||||
log::debug!("call_service_set_by_name fail,{}", e);
|
||||
}
|
||||
// send to UI, refresh widget
|
||||
push_event("try_start_without_auth", vec![("client", &client_json)]);
|
||||
}
|
||||
CLIENTS.write().unwrap().insert(id, client);
|
||||
}
|
||||
|
||||
fn push_event(name: &str, event: Vec<(&str, &str)>) {
|
||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||
assert!(h.get("name").is_none());
|
||||
h.insert("name", name);
|
||||
|
||||
if let Some(s) = GLOBAL_EVENT_STREAM
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(super::APP_TYPE_MAIN)
|
||||
{
|
||||
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_click_time() -> i64 {
|
||||
CLICK_TIME.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn check_click_time(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::ClickTime(0)));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn switch_permission(id: i32, name: String, enabled: bool) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::SwitchPermission { name, enabled }));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_clients_state() -> String {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
let res = Vec::from_iter(clients.values().cloned());
|
||||
serde_json::to_string(&res).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn get_clients_length() -> usize {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
clients.len()
|
||||
}
|
||||
|
||||
pub fn close_conn(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::Close));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn on_login_res(id: i32, res: bool) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
if res {
|
||||
allow_err!(client.tx.send(Data::Authorize));
|
||||
client.authorized = true;
|
||||
} else {
|
||||
allow_err!(client.tx.send(Data::Close));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn remove_connection(id: i32) {
|
||||
let mut clients = CLIENTS.write().unwrap();
|
||||
clients.remove(&id);
|
||||
|
||||
if clients
|
||||
.iter()
|
||||
.filter(|(_k, v)| !v.is_file_transfer)
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
#[cfg(any(target_os = "android"))]
|
||||
if let Err(e) = call_main_service_set_by_name("stop_capture", None, None) {
|
||||
log::debug!("stop_capture err:{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
push_event("on_client_remove", vec![("id", &id.to_string())]);
|
||||
}
|
||||
|
||||
// server mode handle chat from other peers
|
||||
fn handle_chat(id: i32, text: String) {
|
||||
push_event(
|
||||
"chat_server_mode",
|
||||
vec![("id", &id.to_string()), ("text", &text)],
|
||||
);
|
||||
}
|
||||
|
||||
// server mode send chat to peer
|
||||
pub fn send_chat(id: i32, text: String) {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
if let Some(client) = clients.get(&id) {
|
||||
allow_err!(client.tx.send(Data::ChatMessage { text }));
|
||||
}
|
||||
}
|
||||
|
||||
// handle FS server
|
||||
async fn handle_fs(
|
||||
fs: ipc::FS,
|
||||
write_jobs: &mut Vec<fs::TransferJob>,
|
||||
tx: &UnboundedSender<Data>,
|
||||
) {
|
||||
match fs {
|
||||
ipc::FS::ReadDir {
|
||||
dir,
|
||||
include_hidden,
|
||||
} => {
|
||||
read_dir(&dir, include_hidden, tx).await;
|
||||
}
|
||||
ipc::FS::RemoveDir {
|
||||
path,
|
||||
id,
|
||||
recursive,
|
||||
} => {
|
||||
remove_dir(path, id, recursive, tx).await;
|
||||
}
|
||||
ipc::FS::RemoveFile { path, id, file_num } => {
|
||||
remove_file(path, id, file_num, tx).await;
|
||||
}
|
||||
ipc::FS::CreateDir { path, id } => {
|
||||
create_dir(path, id, tx).await;
|
||||
}
|
||||
ipc::FS::NewWrite {
|
||||
path,
|
||||
id,
|
||||
file_num,
|
||||
mut files,
|
||||
overwrite_detection,
|
||||
} => {
|
||||
write_jobs.push(fs::TransferJob::new_write(
|
||||
id,
|
||||
"".to_string(),
|
||||
path,
|
||||
file_num,
|
||||
false,
|
||||
false,
|
||||
files
|
||||
.drain(..)
|
||||
.map(|f| FileEntry {
|
||||
name: f.0,
|
||||
modified_time: f.1,
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
overwrite_detection,
|
||||
));
|
||||
}
|
||||
ipc::FS::CancelWrite { id } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.remove_download_file();
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteDone { id, file_num } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.modify_time();
|
||||
send_raw(fs::new_done(id, file_num), tx);
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
} => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
if let Err(err) = job
|
||||
.write(
|
||||
FileTransferBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_raw(fs::new_error(id, err, file_num), &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
ipc::FS::CheckDigest {
|
||||
id,
|
||||
file_num,
|
||||
file_size,
|
||||
last_modified,
|
||||
is_upload,
|
||||
} => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
let mut req = FileTransferSendConfirmRequest {
|
||||
id,
|
||||
file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
let digest = FileTransferDigest {
|
||||
id,
|
||||
file_num,
|
||||
last_modified,
|
||||
file_size,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(file) = job.files().get(file_num as usize) {
|
||||
let path = get_string(&job.join(&file.name));
|
||||
match is_write_need_confirmation(&path, &digest) {
|
||||
Ok(digest_result) => {
|
||||
match digest_result {
|
||||
DigestCheckResult::IsSame => {
|
||||
req.set_skip(true);
|
||||
let msg_out = new_send_confirm(req);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
DigestCheckResult::NeedConfirm(mut digest) => {
|
||||
// upload to server, but server has the same file, request
|
||||
digest.is_upload = is_upload;
|
||||
let mut msg_out = Message::new();
|
||||
let mut fr = FileResponse::new();
|
||||
fr.set_digest(digest);
|
||||
msg_out.set_file_response(fr);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
DigestCheckResult::NoSuchFile => {
|
||||
let msg_out = new_send_confirm(req);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
send_raw(fs::new_error(id, err, file_num), &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = {
|
||||
if dir.is_empty() {
|
||||
Config::get_home()
|
||||
} else {
|
||||
fs::get_path(dir)
|
||||
}
|
||||
};
|
||||
if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_dir(fd);
|
||||
msg_out.set_file_response(file_response);
|
||||
send_raw(msg_out, tx);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_result<F: std::fmt::Display, S: std::fmt::Display>(
|
||||
res: std::result::Result<std::result::Result<(), F>, S>,
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
tx: &UnboundedSender<Data>,
|
||||
) {
|
||||
match res {
|
||||
Err(err) => {
|
||||
send_raw(fs::new_error(id, err, file_num), tx);
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
send_raw(fs::new_error(id, err, file_num), tx);
|
||||
}
|
||||
Ok(Ok(())) => {
|
||||
send_raw(fs::new_done(id, file_num), tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_file(path: String, id: i32, file_num: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::remove_file(&path)).await,
|
||||
id,
|
||||
file_num,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn create_dir(path: String, id: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::create_dir(&path)).await,
|
||||
id,
|
||||
0,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = fs::get_path(&path);
|
||||
handle_result(
|
||||
spawn_blocking(move || {
|
||||
if recursive {
|
||||
fs::remove_all_empty_dir(&path)
|
||||
} else {
|
||||
std::fs::remove_dir(&path).map_err(|err| err.into())
|
||||
}
|
||||
})
|
||||
.await,
|
||||
id,
|
||||
0,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn send_raw(msg: Message, tx: &UnboundedSender<Data>) {
|
||||
match msg.write_to_bytes() {
|
||||
Ok(bytes) => {
|
||||
allow_err!(tx.send(Data::RawMessage(bytes)));
|
||||
}
|
||||
err => allow_err!(err),
|
||||
}
|
||||
std::thread::spawn(move || start_listen(cm, rx, tx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,30 +432,47 @@ pub fn get_session_id(id: String) -> String {
|
||||
};
|
||||
}
|
||||
|
||||
// async fn start_one_port_forward(
|
||||
// handler: Session,
|
||||
// port: i32,
|
||||
// remote_host: String,
|
||||
// remote_port: i32,
|
||||
// receiver: mpsc::UnboundedReceiver<Data>,
|
||||
// key: &str,
|
||||
// token: &str,
|
||||
// ) {
|
||||
// if let Err(err) = crate::port_forward::listen(
|
||||
// handler.id.clone(),
|
||||
// String::new(), // TODO
|
||||
// port,
|
||||
// handler.clone(),
|
||||
// receiver,
|
||||
// key,
|
||||
// token,
|
||||
// handler.lc.clone(),
|
||||
// remote_host,
|
||||
// remote_port,
|
||||
// )
|
||||
// .await
|
||||
// {
|
||||
// handler.on_error(&format!("Failed to listen on {}: {}", port, err));
|
||||
// }
|
||||
// log::info!("port forward (:{}) exit", port);
|
||||
// }
|
||||
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
||||
let mut fd_json = serde_json::Map::new();
|
||||
fd_json.insert("id".into(), json!(id));
|
||||
fd_json.insert("path".into(), json!(path));
|
||||
|
||||
let mut entries_out = vec![];
|
||||
for entry in entries {
|
||||
let mut entry_map = serde_json::Map::new();
|
||||
entry_map.insert("entry_type".into(), json!(entry.entry_type.value()));
|
||||
entry_map.insert("name".into(), json!(entry.name));
|
||||
entry_map.insert("size".into(), json!(entry.size));
|
||||
entry_map.insert("modified_time".into(), json!(entry.modified_time));
|
||||
entries_out.push(entry_map);
|
||||
}
|
||||
fd_json.insert("entries".into(), json!(entries_out));
|
||||
serde_json::to_string(&fd_json).unwrap_or("".into())
|
||||
}
|
||||
|
||||
pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> String {
|
||||
let mut m = serde_json::Map::new();
|
||||
m.insert("id".into(), json!(id));
|
||||
let mut a = vec![];
|
||||
let mut n: u64 = 0;
|
||||
for entry in entries {
|
||||
n += entry.size;
|
||||
if only_count {
|
||||
continue;
|
||||
}
|
||||
let mut e = serde_json::Map::new();
|
||||
e.insert("name".into(), json!(entry.name.to_owned()));
|
||||
let tmp = entry.entry_type.value();
|
||||
e.insert("type".into(), json!(if tmp == 0 { 1 } else { tmp }));
|
||||
e.insert("time".into(), json!(entry.modified_time as f64));
|
||||
e.insert("size".into(), json!(entry.size as f64));
|
||||
a.push(e);
|
||||
}
|
||||
if only_count {
|
||||
m.insert("num_entries".into(), json!(entries.len() as i32));
|
||||
} else {
|
||||
m.insert("entries".into(), json!(a));
|
||||
}
|
||||
m.insert("total_size".into(), json!(n as f64));
|
||||
serde_json::to_string(&m).unwrap_or("".into())
|
||||
}
|
||||
@@ -13,8 +13,6 @@ use hbb_common::{
|
||||
fs, log,
|
||||
};
|
||||
|
||||
use crate::common::make_fd_to_json;
|
||||
use crate::flutter::connection_manager::{self, get_clients_length, get_clients_state};
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
use crate::start_server;
|
||||
use crate::ui_interface;
|
||||
@@ -31,7 +29,7 @@ use crate::ui_interface::{
|
||||
};
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
flutter::{session_add, session_start_},
|
||||
flutter::{make_fd_to_json, session_add, session_start_},
|
||||
};
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
@@ -388,10 +386,8 @@ pub fn session_create_dir(id: String, act_id: i32, path: String, is_remote: bool
|
||||
}
|
||||
|
||||
pub fn session_read_local_dir_sync(id: String, path: String, show_hidden: bool) -> String {
|
||||
if let Some(_) = SESSIONS.read().unwrap().get(&id) {
|
||||
if let Ok(fd) = fs::read_dir(&fs::get_path(&path), show_hidden) {
|
||||
return make_fd_to_json(fd);
|
||||
}
|
||||
if let Ok(fd) = fs::read_dir(&fs::get_path(&path), show_hidden) {
|
||||
return make_fd_to_json(fd.id, path, &fd.entries);
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
@@ -404,8 +400,8 @@ pub fn session_get_platform(id: String, is_remote: bool) -> String {
|
||||
}
|
||||
|
||||
pub fn session_load_last_transfer_jobs(id: String) {
|
||||
if let Some(_) = SESSIONS.read().unwrap().get(&id) {
|
||||
// return session.load_last_jobs();
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||
return session.load_last_jobs();
|
||||
} else {
|
||||
// a tip for flutter dev
|
||||
eprintln!(
|
||||
@@ -711,13 +707,13 @@ pub fn main_get_online_statue() -> i64 {
|
||||
ONLINE.lock().unwrap().values().max().unwrap_or(&0).clone()
|
||||
}
|
||||
|
||||
pub fn main_get_clients_state() -> String {
|
||||
get_clients_state()
|
||||
pub fn cm_get_clients_state() -> String {
|
||||
crate::ui_cm_interface::get_clients_state()
|
||||
}
|
||||
|
||||
pub fn main_check_clients_length(length: usize) -> Option<String> {
|
||||
if length != get_clients_length() {
|
||||
Some(get_clients_state())
|
||||
pub fn cm_check_clients_length(length: usize) -> Option<String> {
|
||||
if length != crate::ui_cm_interface::get_clients_length() {
|
||||
Some(crate::ui_cm_interface::get_clients_state())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -793,7 +789,7 @@ pub fn main_set_home_dir(home: String) {
|
||||
pub fn main_stop_service() {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
Config::set_option("stop-service".into(), "Y".into());
|
||||
config::Config::set_option("stop-service".into(), "Y".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
}
|
||||
@@ -801,7 +797,7 @@ pub fn main_stop_service() {
|
||||
pub fn main_start_service() {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
Config::set_option("stop-service".into(), "".into());
|
||||
config::Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -829,27 +825,31 @@ pub fn main_get_mouse_time() -> f64 {
|
||||
}
|
||||
|
||||
pub fn cm_send_chat(conn_id: i32, msg: String) {
|
||||
connection_manager::send_chat(conn_id, msg);
|
||||
crate::ui_cm_interface::send_chat(conn_id, msg);
|
||||
}
|
||||
|
||||
pub fn cm_login_res(conn_id: i32, res: bool) {
|
||||
connection_manager::on_login_res(conn_id, res);
|
||||
if res {
|
||||
crate::ui_cm_interface::authorize(conn_id);
|
||||
} else {
|
||||
crate::ui_cm_interface::close(conn_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cm_close_connection(conn_id: i32) {
|
||||
connection_manager::close_conn(conn_id);
|
||||
crate::ui_cm_interface::close(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_check_click_time(conn_id: i32) {
|
||||
connection_manager::check_click_time(conn_id)
|
||||
crate::ui_cm_interface::check_click_time(conn_id)
|
||||
}
|
||||
|
||||
pub fn cm_get_click_time() -> f64 {
|
||||
connection_manager::get_click_time() as _
|
||||
crate::ui_cm_interface::get_click_time() as _
|
||||
}
|
||||
|
||||
pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) {
|
||||
connection_manager::switch_permission(conn_id, name, enabled)
|
||||
crate::ui_cm_interface::switch_permission(conn_id, name, enabled)
|
||||
}
|
||||
|
||||
pub fn main_get_icon() -> String {
|
||||
|
||||
@@ -49,6 +49,7 @@ mod tray;
|
||||
|
||||
mod ui_interface;
|
||||
mod ui_session_interface;
|
||||
mod ui_cm_interface;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod clipboard_file;
|
||||
|
||||
100
src/ui.rs
100
src/ui.rs
@@ -14,7 +14,6 @@ use hbb_common::{
|
||||
log,
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
sleep,
|
||||
tcp::FramedStream,
|
||||
tokio::{self, sync::mpsc, time},
|
||||
};
|
||||
@@ -87,7 +86,7 @@ pub fn start(args: &mut [String]) {
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if args.len() > 0 && args[0] == "--tray" {
|
||||
let options = check_connect_status(false).1;
|
||||
let options = crate::ui_interface::check_connect_status(false).1;
|
||||
crate::tray::start_tray(options);
|
||||
return;
|
||||
}
|
||||
@@ -125,7 +124,7 @@ pub fn start(args: &mut [String]) {
|
||||
page = "install.html";
|
||||
} else if args[0] == "--cm" {
|
||||
frame.register_behavior("connection-manager", move || {
|
||||
Box::new(cm::ConnectionManager::new())
|
||||
Box::new(cm::SciterConnectionManager::new())
|
||||
});
|
||||
page = "cm.html";
|
||||
} else if (args[0] == "--connect"
|
||||
@@ -664,79 +663,6 @@ pub fn check_zombie(childs: Childs) {
|
||||
}
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connecton repeatly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn check_connect_status_(
|
||||
reconnect: bool,
|
||||
status: Arc<Mutex<Status>>,
|
||||
options: Arc<Mutex<HashMap<String, String>>>,
|
||||
rx: mpsc::UnboundedReceiver<ipc::Data>,
|
||||
password: Arc<Mutex<String>>,
|
||||
) {
|
||||
let mut key_confirmed = false;
|
||||
let mut rx = rx;
|
||||
let mut mouse_time = 0;
|
||||
let mut id = "".to_owned();
|
||||
loop {
|
||||
if let Ok(mut c) = ipc::connect(1000, "").await {
|
||||
let mut timer = time::interval(time::Duration::from_secs(1));
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = c.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::error!("ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(ipc::Data::MouseMoveTime(v))) => {
|
||||
mouse_time = v;
|
||||
status.lock().unwrap().2 = v;
|
||||
}
|
||||
Ok(Some(ipc::Data::Options(Some(v)))) => {
|
||||
*options.lock().unwrap() = v
|
||||
}
|
||||
Ok(Some(ipc::Data::Config((name, Some(value))))) => {
|
||||
if name == "id" {
|
||||
id = value;
|
||||
} else if name == "temporary-password" {
|
||||
*password.lock().unwrap() = value;
|
||||
}
|
||||
}
|
||||
Ok(Some(ipc::Data::OnlineStatus(Some((mut x, c))))) => {
|
||||
if x > 0 {
|
||||
x = 1
|
||||
}
|
||||
key_confirmed = c;
|
||||
*status.lock().unwrap() = (x as _, key_confirmed, mouse_time, id.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(data) = rx.recv() => {
|
||||
allow_err!(c.send(&data).await);
|
||||
}
|
||||
_ = timer.tick() => {
|
||||
c.send(&ipc::Data::OnlineStatus(None)).await.ok();
|
||||
c.send(&ipc::Data::Options(None)).await.ok();
|
||||
c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok();
|
||||
c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reconnect {
|
||||
options
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("ipc-closed".to_owned(), "Y".to_owned());
|
||||
break;
|
||||
}
|
||||
*status.lock().unwrap() = (-1, key_confirmed, mouse_time, id.clone());
|
||||
sleep(1.).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn get_sound_inputs() -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
@@ -763,28 +689,6 @@ fn get_sound_inputs() -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn check_connect_status(
|
||||
reconnect: bool,
|
||||
) -> (
|
||||
Arc<Mutex<Status>>,
|
||||
Arc<Mutex<HashMap<String, String>>>,
|
||||
mpsc::UnboundedSender<ipc::Data>,
|
||||
Arc<Mutex<String>>,
|
||||
) {
|
||||
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
|
||||
let options = Arc::new(Mutex::new(Config::get_options()));
|
||||
let cloned = status.clone();
|
||||
let cloned_options = options.clone();
|
||||
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
|
||||
let password = Arc::new(Mutex::new(String::default()));
|
||||
let cloned_password = password.clone();
|
||||
std::thread::spawn(move || {
|
||||
check_connect_status_(reconnect, cloned, cloned_options, rx, cloned_password)
|
||||
});
|
||||
(status, options, tx, password)
|
||||
}
|
||||
|
||||
const INVALID_FORMAT: &'static str = "Invalid format";
|
||||
const UNKNOWN_ERROR: &'static str = "Unknown error";
|
||||
|
||||
|
||||
622
src/ui/cm.rs
622
src/ui/cm.rs
@@ -1,59 +1,83 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::ipc::start_pa;
|
||||
use crate::ipc::{self, new_listener, Connection, Data};
|
||||
use crate::ui_cm_interface::{start_ipc, ConnectionManager, InvokeUiCM};
|
||||
|
||||
#[cfg(windows)]
|
||||
use clipboard::{
|
||||
create_cliprdr_context, empty_clipboard, get_rx_clip_client, server_clip_file, set_conn_enabled,
|
||||
};
|
||||
use hbb_common::fs::{
|
||||
get_string, is_write_need_confirmation, new_send_confirm,
|
||||
DigestCheckResult,
|
||||
};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::Config,
|
||||
fs, log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
tokio::{self, sync::mpsc, task::spawn_blocking},
|
||||
};
|
||||
use sciter::{make_args, Element, Value, HELEMENT};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub struct ConnectionManagerInner {
|
||||
root: Option<Element>,
|
||||
senders: HashMap<i32, mpsc::UnboundedSender<Data>>,
|
||||
click_time: i64,
|
||||
use hbb_common::{allow_err, log};
|
||||
use sciter::{make_args, Element, Value, HELEMENT};
|
||||
use std::sync::Mutex;
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SciterHandler {
|
||||
pub element: Arc<Mutex<Option<Element>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectionManager(Arc<RwLock<ConnectionManagerInner>>);
|
||||
impl InvokeUiCM for SciterHandler {
|
||||
fn add_connection(&self, client: &crate::ui_cm_interface::Client) {
|
||||
self.call(
|
||||
"addConnection",
|
||||
&make_args!(
|
||||
client.id,
|
||||
client.is_file_transfer,
|
||||
client.port_forward.clone(),
|
||||
client.peer_id.clone(),
|
||||
client.name.clone(),
|
||||
client.authorized,
|
||||
client.keyboard,
|
||||
client.clipboard,
|
||||
client.audio,
|
||||
client.file,
|
||||
client.restart
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
impl Deref for ConnectionManager {
|
||||
type Target = Arc<RwLock<ConnectionManagerInner>>;
|
||||
fn remove_connection(&self, id: i32) {
|
||||
self.call("removeConnection", &make_args!(id));
|
||||
if crate::ui_cm_interface::get_clients_length().eq(&0) {
|
||||
crate::platform::quit_gui();
|
||||
}
|
||||
}
|
||||
|
||||
fn new_message(&self, id: i32, text: String) {
|
||||
self.call("newMessage", &make_args!(id, text));
|
||||
}
|
||||
}
|
||||
|
||||
impl SciterHandler {
|
||||
#[inline]
|
||||
fn call(&self, func: &str, args: &[Value]) {
|
||||
if let Some(e) = self.element.lock().unwrap().as_ref() {
|
||||
allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SciterConnectionManager(ConnectionManager<SciterHandler>);
|
||||
|
||||
impl Deref for SciterConnectionManager {
|
||||
type Target = ConnectionManager<SciterHandler>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionManager {
|
||||
impl SciterConnectionManager {
|
||||
pub fn new() -> Self {
|
||||
#[cfg(target_os = "linux")]
|
||||
std::thread::spawn(start_pa);
|
||||
let inner = ConnectionManagerInner {
|
||||
root: None,
|
||||
senders: HashMap::new(),
|
||||
click_time: Default::default(),
|
||||
let cm = ConnectionManager {
|
||||
ui_handler: SciterHandler::default(),
|
||||
};
|
||||
let cm = Self(Arc::new(RwLock::new(inner)));
|
||||
let cloned = cm.clone();
|
||||
std::thread::spawn(move || start_ipc(cloned));
|
||||
cm
|
||||
SciterConnectionManager(cm)
|
||||
}
|
||||
|
||||
fn get_icon(&mut self) -> String {
|
||||
@@ -61,359 +85,27 @@ impl ConnectionManager {
|
||||
}
|
||||
|
||||
fn check_click_time(&mut self, id: i32) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(Data::ClickTime(0)));
|
||||
}
|
||||
crate::ui_cm_interface::check_click_time(id);
|
||||
}
|
||||
|
||||
fn get_click_time(&self) -> f64 {
|
||||
self.read().unwrap().click_time as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&self, func: &str, args: &[Value]) {
|
||||
let r = self.read().unwrap();
|
||||
if let Some(ref e) = r.root {
|
||||
allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..]));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_connection(
|
||||
&self,
|
||||
id: i32,
|
||||
is_file_transfer: bool,
|
||||
port_forward: String,
|
||||
peer_id: String,
|
||||
name: String,
|
||||
authorized: bool,
|
||||
keyboard: bool,
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
self.call(
|
||||
"addConnection",
|
||||
&make_args!(
|
||||
id,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
peer_id,
|
||||
name,
|
||||
authorized,
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart
|
||||
),
|
||||
);
|
||||
self.write().unwrap().senders.insert(id, tx);
|
||||
}
|
||||
|
||||
fn remove_connection(&self, id: i32) {
|
||||
self.write().unwrap().senders.remove(&id);
|
||||
if self.read().unwrap().senders.len() == 0 {
|
||||
crate::platform::quit_gui();
|
||||
}
|
||||
self.call("removeConnection", &make_args!(id));
|
||||
}
|
||||
|
||||
async fn handle_data(
|
||||
&self,
|
||||
id: i32,
|
||||
data: Data,
|
||||
_tx_clip_file: &mpsc::UnboundedSender<ClipboardFileData>,
|
||||
write_jobs: &mut Vec<fs::TransferJob>,
|
||||
conn: &mut Connection,
|
||||
) {
|
||||
match data {
|
||||
Data::ChatMessage { text } => {
|
||||
self.call("newMessage", &make_args!(id, text));
|
||||
}
|
||||
Data::ClickTime(ms) => {
|
||||
self.write().unwrap().click_time = ms;
|
||||
}
|
||||
Data::FS(v) => match v {
|
||||
ipc::FS::ReadDir {
|
||||
dir,
|
||||
include_hidden,
|
||||
} => {
|
||||
Self::read_dir(&dir, include_hidden, conn).await;
|
||||
}
|
||||
ipc::FS::RemoveDir {
|
||||
path,
|
||||
id,
|
||||
recursive,
|
||||
} => {
|
||||
Self::remove_dir(path, id, recursive, conn).await;
|
||||
}
|
||||
ipc::FS::RemoveFile { path, id, file_num } => {
|
||||
Self::remove_file(path, id, file_num, conn).await;
|
||||
}
|
||||
ipc::FS::CreateDir { path, id } => {
|
||||
Self::create_dir(path, id, conn).await;
|
||||
}
|
||||
ipc::FS::NewWrite {
|
||||
path,
|
||||
id,
|
||||
file_num,
|
||||
mut files,
|
||||
overwrite_detection,
|
||||
} => {
|
||||
// cm has no show_hidden context
|
||||
// dummy remote, show_hidden, is_remote
|
||||
write_jobs.push(fs::TransferJob::new_write(
|
||||
id,
|
||||
"".to_string(),
|
||||
path,
|
||||
file_num,
|
||||
false,
|
||||
false,
|
||||
files
|
||||
.drain(..)
|
||||
.map(|f| FileEntry {
|
||||
name: f.0,
|
||||
modified_time: f.1,
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
overwrite_detection,
|
||||
));
|
||||
}
|
||||
ipc::FS::CancelWrite { id } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.remove_download_file();
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteDone { id, file_num } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.modify_time();
|
||||
Self::send(fs::new_done(id, file_num), conn).await;
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::CheckDigest {
|
||||
id,
|
||||
file_num,
|
||||
file_size,
|
||||
last_modified,
|
||||
is_upload,
|
||||
} => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
let mut req = FileTransferSendConfirmRequest {
|
||||
id,
|
||||
file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
let digest = FileTransferDigest {
|
||||
id,
|
||||
file_num,
|
||||
last_modified,
|
||||
file_size,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(file) = job.files().get(file_num as usize) {
|
||||
let path = get_string(&job.join(&file.name));
|
||||
match is_write_need_confirmation(&path, &digest) {
|
||||
Ok(digest_result) => {
|
||||
match digest_result {
|
||||
DigestCheckResult::IsSame => {
|
||||
req.set_skip(true);
|
||||
let msg_out = new_send_confirm(req);
|
||||
Self::send(msg_out, conn).await;
|
||||
}
|
||||
DigestCheckResult::NeedConfirm(mut digest) => {
|
||||
// upload to server, but server has the same file, request
|
||||
digest.is_upload = is_upload;
|
||||
let mut msg_out = Message::new();
|
||||
let mut fr = FileResponse::new();
|
||||
fr.set_digest(digest);
|
||||
msg_out.set_file_response(fr);
|
||||
Self::send(msg_out, conn).await;
|
||||
}
|
||||
DigestCheckResult::NoSuchFile => {
|
||||
let msg_out = new_send_confirm(req);
|
||||
Self::send(msg_out, conn).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
Self::send(fs::new_error(id, err, file_num), conn).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
} => {
|
||||
let raw = if let Ok(bytes) = conn.next_raw().await {
|
||||
Some(bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
if let Err(err) = job
|
||||
.write(
|
||||
FileTransferBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
..Default::default()
|
||||
},
|
||||
raw.as_ref().map(|x| &x[..]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Self::send(fs::new_error(id, err, file_num), conn).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteOffset {
|
||||
id: _,
|
||||
file_num: _,
|
||||
offset_blk: _,
|
||||
} => {}
|
||||
},
|
||||
#[cfg(windows)]
|
||||
Data::ClipbaordFile(_clip) => {
|
||||
_tx_clip_file
|
||||
.send(ClipboardFileData::Clip((id, _clip)))
|
||||
.ok();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Data::ClipboardFileEnabled(enabled) => {
|
||||
_tx_clip_file
|
||||
.send(ClipboardFileData::Enable((id, enabled)))
|
||||
.ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_dir(dir: &str, include_hidden: bool, conn: &mut Connection) {
|
||||
let path = {
|
||||
if dir.is_empty() {
|
||||
Config::get_home()
|
||||
} else {
|
||||
fs::get_path(dir)
|
||||
}
|
||||
};
|
||||
if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_dir(fd);
|
||||
msg_out.set_file_response(file_response);
|
||||
Self::send(msg_out, conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_result<F: std::fmt::Display, S: std::fmt::Display>(
|
||||
res: std::result::Result<std::result::Result<(), F>, S>,
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
conn: &mut Connection,
|
||||
) {
|
||||
match res {
|
||||
Err(err) => {
|
||||
Self::send(fs::new_error(id, err, file_num), conn).await;
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
Self::send(fs::new_error(id, err, file_num), conn).await;
|
||||
}
|
||||
Ok(Ok(())) => {
|
||||
Self::send(fs::new_done(id, file_num), conn).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_file(path: String, id: i32, file_num: i32, conn: &mut Connection) {
|
||||
Self::handle_result(
|
||||
spawn_blocking(move || fs::remove_file(&path)).await,
|
||||
id,
|
||||
file_num,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn create_dir(path: String, id: i32, conn: &mut Connection) {
|
||||
Self::handle_result(
|
||||
spawn_blocking(move || fs::create_dir(&path)).await,
|
||||
id,
|
||||
0,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn remove_dir(path: String, id: i32, recursive: bool, conn: &mut Connection) {
|
||||
let path = fs::get_path(&path);
|
||||
Self::handle_result(
|
||||
spawn_blocking(move || {
|
||||
if recursive {
|
||||
fs::remove_all_empty_dir(&path)
|
||||
} else {
|
||||
std::fs::remove_dir(&path).map_err(|err| err.into())
|
||||
}
|
||||
})
|
||||
.await,
|
||||
id,
|
||||
0,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn send(msg: Message, conn: &mut Connection) {
|
||||
match msg.write_to_bytes() {
|
||||
Ok(bytes) => allow_err!(conn.send(&Data::RawMessage(bytes)).await),
|
||||
err => allow_err!(err),
|
||||
}
|
||||
crate::ui_cm_interface::get_click_time() as _
|
||||
}
|
||||
|
||||
fn switch_permission(&self, id: i32, name: String, enabled: bool) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(Data::SwitchPermission { name, enabled }));
|
||||
}
|
||||
crate::ui_cm_interface::switch_permission(id, name, enabled);
|
||||
}
|
||||
|
||||
fn close(&self, id: i32) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(Data::Close));
|
||||
}
|
||||
}
|
||||
|
||||
fn send_msg(&self, id: i32, text: String) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(Data::ChatMessage { text }));
|
||||
}
|
||||
}
|
||||
|
||||
fn send_data(&self, id: i32, data: Data) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(data));
|
||||
}
|
||||
crate::ui_cm_interface::close(id);
|
||||
}
|
||||
|
||||
fn authorize(&self, id: i32) {
|
||||
let lock = self.read().unwrap();
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(Data::Authorize));
|
||||
}
|
||||
crate::ui_cm_interface::authorize(id);
|
||||
}
|
||||
|
||||
fn send_msg(&self, id: i32, text: String) {
|
||||
crate::ui_cm_interface::send_chat(id, text);
|
||||
}
|
||||
|
||||
fn t(&self, name: String) -> String {
|
||||
@@ -421,9 +113,9 @@ impl ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl sciter::EventHandler for ConnectionManager {
|
||||
impl sciter::EventHandler for SciterConnectionManager {
|
||||
fn attached(&mut self, root: HELEMENT) {
|
||||
self.write().unwrap().root = Some(Element::from(root));
|
||||
*self.ui_handler.element.lock().unwrap() = Some(Element::from(root));
|
||||
}
|
||||
|
||||
sciter::dispatch_script_call! {
|
||||
@@ -437,179 +129,3 @@ impl sciter::EventHandler for ConnectionManager {
|
||||
fn send_msg(i32, String);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ClipboardFileData {
|
||||
#[cfg(windows)]
|
||||
Clip((i32, ipc::ClipbaordFile)),
|
||||
Enable((i32, bool)),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_ipc(cm: ConnectionManager) {
|
||||
let (tx_file, _rx_file) = mpsc::unbounded_channel::<ClipboardFileData>();
|
||||
#[cfg(windows)]
|
||||
let cm_clip = cm.clone();
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file));
|
||||
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
log::info!("try create privacy mode window");
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||
log::warn!(
|
||||
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
allow_err!(crate::ui::win_privacy::start());
|
||||
});
|
||||
|
||||
match new_listener("_cm").await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection");
|
||||
let mut stream = Connection::new(stream);
|
||||
let cm = cm.clone();
|
||||
let tx_file = tx_file.clone();
|
||||
tokio::spawn(async move {
|
||||
// for tmp use, without real conn id
|
||||
let conn_id_tmp = -1;
|
||||
let mut conn_id: i32 = 0;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("cm ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart} => {
|
||||
log::debug!("conn_id: {}", id);
|
||||
conn_id = id;
|
||||
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
||||
}
|
||||
Data::Close => {
|
||||
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||
log::info!("cm ipc connection closed from connection request");
|
||||
break;
|
||||
}
|
||||
Data::PrivacyModeState((id, _)) => {
|
||||
conn_id = conn_id_tmp;
|
||||
cm.send_data(id, data)
|
||||
}
|
||||
_ => {
|
||||
cm.handle_data(conn_id, data, &tx_file, &mut write_jobs, &mut stream).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(data) = rx.recv() => {
|
||||
if stream.send(&data).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if conn_id != conn_id_tmp {
|
||||
cm.remove_connection(conn_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get cm client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start cm ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
crate::platform::quit_gui();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_clipboard_file(
|
||||
cm: ConnectionManager,
|
||||
mut rx: mpsc::UnboundedReceiver<ClipboardFileData>,
|
||||
) {
|
||||
let mut cliprdr_context = None;
|
||||
let mut rx_clip_client = get_rx_clip_client().lock().await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
clip_file = rx_clip_client.recv() => match clip_file {
|
||||
Some((conn_id, clip)) => {
|
||||
cmd_inner_send(
|
||||
&cm,
|
||||
conn_id,
|
||||
Data::ClipbaordFile(clip)
|
||||
);
|
||||
}
|
||||
None => {
|
||||
//
|
||||
}
|
||||
},
|
||||
server_msg = rx.recv() => match server_msg {
|
||||
Some(ClipboardFileData::Clip((conn_id, clip))) => {
|
||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||
server_clip_file(ctx, conn_id, clip);
|
||||
}
|
||||
}
|
||||
Some(ClipboardFileData::Enable((id, enabled))) => {
|
||||
if enabled && cliprdr_context.is_none() {
|
||||
cliprdr_context = Some(match create_cliprdr_context(true, false) {
|
||||
Ok(context) => {
|
||||
log::info!("clipboard context for file transfer created.");
|
||||
context
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Create clipboard context for file transfer: {}",
|
||||
err.to_string()
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
set_conn_enabled(id, enabled);
|
||||
if !enabled {
|
||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||
empty_clipboard(ctx, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn cmd_inner_send(cm: &ConnectionManager, id: i32, data: Data) {
|
||||
let lock = cm.read().unwrap();
|
||||
if id != 0 {
|
||||
if let Some(s) = lock.senders.get(&id) {
|
||||
allow_err!(s.send(data));
|
||||
}
|
||||
} else {
|
||||
for s in lock.senders.values() {
|
||||
allow_err!(s.send(data.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +695,7 @@ handler.clearAllJobs = function() {
|
||||
file_transfer.job_table.clearAllJobs();
|
||||
}
|
||||
|
||||
handler.addJob = function (id, path, to, file_num, show_hidden, is_remote) {
|
||||
handler.addJob = function (id, path, to, file_num, show_hidden, is_remote) { // load last job
|
||||
// stdout.println("restore job: " + is_remote);
|
||||
file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote);
|
||||
}
|
||||
|
||||
@@ -1055,6 +1055,7 @@ function showSettings() {
|
||||
}
|
||||
|
||||
function checkConnectStatus() {
|
||||
handler.check_mouse_time(); // trigger connection status updater
|
||||
self.timer(1s, function() {
|
||||
var tmp = !!handler.get_option("stop-service");
|
||||
if (tmp != service_stopped) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use sciter::{
|
||||
@@ -22,13 +20,15 @@ use clipboard::{
|
||||
get_rx_clip_client, server_clip_file,
|
||||
};
|
||||
|
||||
use hbb_common::{allow_err, log, message_proto::*, rendezvous_proto::ConnType};
|
||||
use hbb_common::{
|
||||
allow_err, fs::TransferJobMeta, log, message_proto::*, rendezvous_proto::ConnType,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::clipboard_file::*;
|
||||
use crate::{
|
||||
client::*,
|
||||
ui_session_interface::{InvokeUi, Session},
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
|
||||
type Video = AssetPtr<video_destination>;
|
||||
@@ -37,12 +37,8 @@ lazy_static::lazy_static! {
|
||||
static ref VIDEO: Arc<Mutex<Option<Video>>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
static mut IS_ALT_GR: bool = false;
|
||||
|
||||
/// SciterHandler
|
||||
/// * element
|
||||
/// * thread TODO check if flutter need
|
||||
/// * close_state for file path when close
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SciterHandler {
|
||||
@@ -66,7 +62,7 @@ impl SciterHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl InvokeUi for SciterHandler {
|
||||
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() {
|
||||
@@ -154,17 +150,36 @@ impl InvokeUi for SciterHandler {
|
||||
self.call("clearAllJobs", &make_args!());
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn add_job(
|
||||
fn load_last_job(&self, cnt: i32, job_json: &str) {
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_folder_files(
|
||||
&self,
|
||||
id: i32,
|
||||
entries: &Vec<FileEntry>,
|
||||
path: String,
|
||||
to: String,
|
||||
file_num: i32,
|
||||
show_hidden: bool,
|
||||
is_remote: bool,
|
||||
_is_local: bool,
|
||||
only_count: bool,
|
||||
) {
|
||||
todo!()
|
||||
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) {
|
||||
@@ -426,6 +441,14 @@ impl SciterSession {
|
||||
v
|
||||
}
|
||||
|
||||
pub fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
|
||||
pub fn get_icon(&self) -> String {
|
||||
crate::get_icon()
|
||||
}
|
||||
|
||||
fn supported_hwcodec(&self) -> Value {
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
@@ -693,11 +716,10 @@ pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
|
||||
e.set_item("size", entry.size as f64);
|
||||
a.push(e);
|
||||
}
|
||||
if only_count {
|
||||
m.set_item("num_entries", entries.len() as i32);
|
||||
} else {
|
||||
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
|
||||
}
|
||||
@@ -67,6 +67,7 @@ function adaptDisplay() {
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshCursor();
|
||||
handler.style.set {
|
||||
width: w / scaleFactor + "px",
|
||||
height: h / scaleFactor + "px",
|
||||
@@ -98,6 +99,7 @@ var acc_wheel_delta_y0 = 0;
|
||||
var total_wheel_time = 0;
|
||||
var wheeling = false;
|
||||
var dragging = false;
|
||||
var is_mouse_event_triggered = false;
|
||||
|
||||
// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum
|
||||
function resetWheel() {
|
||||
@@ -139,6 +141,7 @@ function accWheel(v, is_x) {
|
||||
|
||||
function handler.onMouse(evt)
|
||||
{
|
||||
is_mouse_event_triggered = true;
|
||||
if (is_file_transfer || is_port_forward) return false;
|
||||
if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) {
|
||||
var dy = evt.y - scroll_body.scroll(#top);
|
||||
@@ -317,6 +320,7 @@ function handler.onMouse(evt)
|
||||
return true;
|
||||
};
|
||||
|
||||
var cur_id = -1;
|
||||
var cur_hotx = 0;
|
||||
var cur_hoty = 0;
|
||||
var cur_img = null;
|
||||
@@ -345,7 +349,7 @@ function scaleCursorImage(img) {
|
||||
var useSystemCursor = true;
|
||||
function updateCursor(system=false) {
|
||||
stdout.println("Update cursor, system: " + system);
|
||||
useSystemCursor= system;
|
||||
useSystemCursor = system;
|
||||
if (system) {
|
||||
handler.style#cursor = undefined;
|
||||
} else if (cur_img) {
|
||||
@@ -353,6 +357,12 @@ function updateCursor(system=false) {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshCursor() {
|
||||
if (cur_id != -1) {
|
||||
handler.setCursorId(cur_id);
|
||||
}
|
||||
}
|
||||
|
||||
handler.setCursorData = function(id, hotx, hoty, width, height, colors) {
|
||||
cur_hotx = hotx;
|
||||
cur_hoty = hoty;
|
||||
@@ -360,8 +370,9 @@ handler.setCursorData = function(id, hotx, hoty, width, height, colors) {
|
||||
if (img) {
|
||||
image_binded = true;
|
||||
cursors[id] = [img, hotx, hoty, width, height];
|
||||
cur_id = id;
|
||||
img = scaleCursorImage(img);
|
||||
if (cursor_img.style#display == 'none') {
|
||||
if (!first_mouse_event_triggered || cursor_img.style#display == 'none') {
|
||||
self.timer(1ms, updateCursor);
|
||||
}
|
||||
cur_img = img;
|
||||
@@ -371,11 +382,12 @@ handler.setCursorData = function(id, hotx, hoty, width, height, colors) {
|
||||
handler.setCursorId = function(id) {
|
||||
var img = cursors[id];
|
||||
if (img) {
|
||||
cur_id = id;
|
||||
image_binded = true;
|
||||
cur_hotx = img[1];
|
||||
cur_hoty = img[2];
|
||||
img = scaleCursorImage(img[0]);
|
||||
if (cursor_img.style#display == 'none') {
|
||||
if (!first_mouse_event_triggered || cursor_img.style#display == 'none') {
|
||||
self.timer(1ms, updateCursor);
|
||||
}
|
||||
cur_img = img;
|
||||
|
||||
671
src/ui_cm_interface.rs
Normal file
671
src/ui_cm_interface.rs
Normal file
@@ -0,0 +1,671 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::FromIterator,
|
||||
sync::{
|
||||
atomic::{AtomicI64, Ordering},
|
||||
RwLock,
|
||||
},
|
||||
};
|
||||
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use crate::ipc::Data;
|
||||
use crate::ipc::{self, new_listener, Connection};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::Config,
|
||||
fs::is_write_need_confirmation,
|
||||
fs::{self, get_string, new_send_confirm, DigestCheckResult},
|
||||
log,
|
||||
message_proto::*,
|
||||
protobuf::Message as _,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc::{self, UnboundedSender},
|
||||
task::spawn_blocking,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Client {
|
||||
pub id: i32,
|
||||
pub authorized: bool,
|
||||
pub is_file_transfer: bool,
|
||||
pub port_forward: String,
|
||||
pub name: String,
|
||||
pub peer_id: String,
|
||||
pub keyboard: bool,
|
||||
pub clipboard: bool,
|
||||
pub audio: bool,
|
||||
pub file: bool,
|
||||
pub restart: bool,
|
||||
#[serde(skip)]
|
||||
tx: UnboundedSender<Data>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CLIENTS: RwLock<HashMap<i32,Client>> = Default::default();
|
||||
static ref CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectionManager<T: InvokeUiCM> {
|
||||
pub ui_handler: T,
|
||||
}
|
||||
|
||||
pub trait InvokeUiCM: Send + Clone + 'static + Sized {
|
||||
fn add_connection(&self, client: &Client);
|
||||
|
||||
fn remove_connection(&self, id: i32);
|
||||
|
||||
fn new_message(&self, id: i32, text: String);
|
||||
}
|
||||
|
||||
impl<T: InvokeUiCM> Deref for ConnectionManager<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ui_handler
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InvokeUiCM> DerefMut for ConnectionManager<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.ui_handler
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
fn add_connection(
|
||||
&self,
|
||||
id: i32,
|
||||
is_file_transfer: bool,
|
||||
port_forward: String,
|
||||
peer_id: String,
|
||||
name: String,
|
||||
authorized: bool,
|
||||
keyboard: bool,
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
file: bool,
|
||||
restart: bool,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
let client = Client {
|
||||
id,
|
||||
authorized,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
name: name.clone(),
|
||||
peer_id: peer_id.clone(),
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
tx,
|
||||
};
|
||||
self.ui_handler.add_connection(&client);
|
||||
CLIENTS.write().unwrap().insert(id, client);
|
||||
}
|
||||
|
||||
fn remove_connection(&self, id: i32) {
|
||||
CLIENTS.write().unwrap().remove(&id);
|
||||
|
||||
#[cfg(any(target_os = "android"))]
|
||||
if CLIENTS
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(_k, v)| !v.is_file_transfer)
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
if let Err(e) =
|
||||
scrap::android::call_main_service_set_by_name("stop_capture", None, None)
|
||||
{
|
||||
log::debug!("stop_capture err:{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
self.ui_handler.remove_connection(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_click_time(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::ClickTime(0)));
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_click_time() -> i64 {
|
||||
CLICK_TIME.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn authorize(id: i32) {
|
||||
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
|
||||
client.authorized = true;
|
||||
allow_err!(client.tx.send(Data::Authorize));
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn close(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::Close));
|
||||
};
|
||||
}
|
||||
|
||||
// server mode send chat to peer
|
||||
#[inline]
|
||||
pub fn send_chat(id: i32, text: String) {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
if let Some(client) = clients.get(&id) {
|
||||
allow_err!(client.tx.send(Data::ChatMessage { text }));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn switch_permission(id: i32, name: String, enabled: bool) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::SwitchPermission { name, enabled }));
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_clients_state() -> String {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
let res = Vec::from_iter(clients.values().cloned());
|
||||
serde_json::to_string(&res).unwrap_or("".into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_clients_length() -> usize {
|
||||
let clients = CLIENTS.read().unwrap();
|
||||
clients.len()
|
||||
}
|
||||
|
||||
pub enum ClipboardFileData {
|
||||
#[cfg(windows)]
|
||||
Clip((i32, ipc::ClipbaordFile)),
|
||||
Enable((i32, bool)),
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
|
||||
let (tx_file, _rx_file) = mpsc::unbounded_channel::<ClipboardFileData>();
|
||||
#[cfg(windows)]
|
||||
let cm_clip = cm.clone();
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file));
|
||||
|
||||
#[cfg(windows)]
|
||||
std::thread::spawn(move || {
|
||||
log::info!("try create privacy mode window");
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Err(e) = crate::platform::windows::check_update_broker_process() {
|
||||
log::warn!(
|
||||
"Failed to check update broker process. Privacy mode may not work properly. {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
allow_err!(crate::ui::win_privacy::start());
|
||||
});
|
||||
|
||||
match new_listener("_cm").await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection");
|
||||
let mut stream = Connection::new(stream);
|
||||
let cm = cm.clone();
|
||||
let tx_file = tx_file.clone();
|
||||
tokio::spawn(async move {
|
||||
// for tmp use, without real conn id
|
||||
let conn_id_tmp = -1;
|
||||
let mut conn_id: i32 = 0;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::info!("cm ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match data {
|
||||
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled, restart} => {
|
||||
log::debug!("conn_id: {}", id);
|
||||
conn_id = id;
|
||||
tx_file.send(ClipboardFileData::Enable((id, file_transfer_enabled))).ok();
|
||||
cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, tx.clone());
|
||||
}
|
||||
Data::Close => {
|
||||
tx_file.send(ClipboardFileData::Enable((conn_id, false))).ok();
|
||||
log::info!("cm ipc connection closed from connection request");
|
||||
break;
|
||||
}
|
||||
Data::PrivacyModeState((id, _)) => {
|
||||
conn_id = conn_id_tmp;
|
||||
allow_err!(tx.send(data));
|
||||
}
|
||||
Data::ClickTime(ms) => {
|
||||
CLICK_TIME.store(ms, Ordering::SeqCst);
|
||||
}
|
||||
Data::ChatMessage { text } => {
|
||||
cm.new_message(conn_id, text);
|
||||
}
|
||||
Data::FS(fs) => {
|
||||
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Data::ClipbaordFile(_clip) => {
|
||||
tx_file
|
||||
.send(ClipboardFileData::Clip((conn_id, _clip)))
|
||||
.ok();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Data::ClipboardFileEnabled(enabled) => {
|
||||
tx_file
|
||||
.send(ClipboardFileData::Enable((conn_id, enabled)))
|
||||
.ok();
|
||||
}
|
||||
_ => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some(data) = rx.recv() => {
|
||||
if stream.send(&data).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if conn_id != conn_id_tmp {
|
||||
cm.remove_connection(conn_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get cm client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start cm ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
crate::platform::quit_gui();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_listen<T: InvokeUiCM>(
|
||||
cm: ConnectionManager<T>,
|
||||
mut rx: mpsc::UnboundedReceiver<Data>,
|
||||
tx: mpsc::UnboundedSender<Data>,
|
||||
) {
|
||||
let mut current_id = 0;
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Some(Data::Login {
|
||||
id,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
peer_id,
|
||||
name,
|
||||
authorized,
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
..
|
||||
}) => {
|
||||
current_id = id;
|
||||
cm.add_connection(
|
||||
id,
|
||||
is_file_transfer,
|
||||
port_forward,
|
||||
peer_id,
|
||||
name,
|
||||
authorized,
|
||||
keyboard,
|
||||
clipboard,
|
||||
audio,
|
||||
file,
|
||||
restart,
|
||||
tx.clone(),
|
||||
);
|
||||
}
|
||||
Some(Data::ChatMessage { text }) => {
|
||||
cm.new_message(current_id, text);
|
||||
}
|
||||
Some(Data::FS(fs)) => {
|
||||
handle_fs(fs, &mut write_jobs, &tx).await;
|
||||
}
|
||||
Some(Data::Close) => {
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
cm.remove_connection(current_id);
|
||||
}
|
||||
|
||||
async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &UnboundedSender<Data>) {
|
||||
match fs {
|
||||
ipc::FS::ReadDir {
|
||||
dir,
|
||||
include_hidden,
|
||||
} => {
|
||||
read_dir(&dir, include_hidden, tx).await;
|
||||
}
|
||||
ipc::FS::RemoveDir {
|
||||
path,
|
||||
id,
|
||||
recursive,
|
||||
} => {
|
||||
remove_dir(path, id, recursive, tx).await;
|
||||
}
|
||||
ipc::FS::RemoveFile { path, id, file_num } => {
|
||||
remove_file(path, id, file_num, tx).await;
|
||||
}
|
||||
ipc::FS::CreateDir { path, id } => {
|
||||
create_dir(path, id, tx).await;
|
||||
}
|
||||
ipc::FS::NewWrite {
|
||||
path,
|
||||
id,
|
||||
file_num,
|
||||
mut files,
|
||||
overwrite_detection,
|
||||
} => {
|
||||
// cm has no show_hidden context
|
||||
// dummy remote, show_hidden, is_remote
|
||||
write_jobs.push(fs::TransferJob::new_write(
|
||||
id,
|
||||
"".to_string(),
|
||||
path,
|
||||
file_num,
|
||||
false,
|
||||
false,
|
||||
files
|
||||
.drain(..)
|
||||
.map(|f| FileEntry {
|
||||
name: f.0,
|
||||
modified_time: f.1,
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
overwrite_detection,
|
||||
));
|
||||
}
|
||||
ipc::FS::CancelWrite { id } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.remove_download_file();
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteDone { id, file_num } => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
job.modify_time();
|
||||
send_raw(fs::new_done(id, file_num), tx);
|
||||
fs::remove_job(id, write_jobs);
|
||||
}
|
||||
}
|
||||
ipc::FS::WriteBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
} => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
if let Err(err) = job
|
||||
.write(
|
||||
FileTransferBlock {
|
||||
id,
|
||||
file_num,
|
||||
data,
|
||||
compressed,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_raw(fs::new_error(id, err, file_num), &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
ipc::FS::CheckDigest {
|
||||
id,
|
||||
file_num,
|
||||
file_size,
|
||||
last_modified,
|
||||
is_upload,
|
||||
} => {
|
||||
if let Some(job) = fs::get_job(id, write_jobs) {
|
||||
let mut req = FileTransferSendConfirmRequest {
|
||||
id,
|
||||
file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
let digest = FileTransferDigest {
|
||||
id,
|
||||
file_num,
|
||||
last_modified,
|
||||
file_size,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(file) = job.files().get(file_num as usize) {
|
||||
let path = get_string(&job.join(&file.name));
|
||||
match is_write_need_confirmation(&path, &digest) {
|
||||
Ok(digest_result) => {
|
||||
match digest_result {
|
||||
DigestCheckResult::IsSame => {
|
||||
req.set_skip(true);
|
||||
let msg_out = new_send_confirm(req);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
DigestCheckResult::NeedConfirm(mut digest) => {
|
||||
// upload to server, but server has the same file, request
|
||||
digest.is_upload = is_upload;
|
||||
let mut msg_out = Message::new();
|
||||
let mut fr = FileResponse::new();
|
||||
fr.set_digest(digest);
|
||||
msg_out.set_file_response(fr);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
DigestCheckResult::NoSuchFile => {
|
||||
let msg_out = new_send_confirm(req);
|
||||
send_raw(msg_out, &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
send_raw(fs::new_error(id, err, file_num), &tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = {
|
||||
if dir.is_empty() {
|
||||
Config::get_home()
|
||||
} else {
|
||||
fs::get_path(dir)
|
||||
}
|
||||
};
|
||||
if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_dir(fd);
|
||||
msg_out.set_file_response(file_response);
|
||||
send_raw(msg_out, tx);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_result<F: std::fmt::Display, S: std::fmt::Display>(
|
||||
res: std::result::Result<std::result::Result<(), F>, S>,
|
||||
id: i32,
|
||||
file_num: i32,
|
||||
tx: &UnboundedSender<Data>,
|
||||
) {
|
||||
match res {
|
||||
Err(err) => {
|
||||
send_raw(fs::new_error(id, err, file_num), tx);
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
send_raw(fs::new_error(id, err, file_num), tx);
|
||||
}
|
||||
Ok(Ok(())) => {
|
||||
send_raw(fs::new_done(id, file_num), tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_file(path: String, id: i32, file_num: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::remove_file(&path)).await,
|
||||
id,
|
||||
file_num,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn create_dir(path: String, id: i32, tx: &UnboundedSender<Data>) {
|
||||
handle_result(
|
||||
spawn_blocking(move || fs::create_dir(&path)).await,
|
||||
id,
|
||||
0,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender<Data>) {
|
||||
let path = fs::get_path(&path);
|
||||
handle_result(
|
||||
spawn_blocking(move || {
|
||||
if recursive {
|
||||
fs::remove_all_empty_dir(&path)
|
||||
} else {
|
||||
std::fs::remove_dir(&path).map_err(|err| err.into())
|
||||
}
|
||||
})
|
||||
.await,
|
||||
id,
|
||||
0,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn send_raw(msg: Message, tx: &UnboundedSender<Data>) {
|
||||
match msg.write_to_bytes() {
|
||||
Ok(bytes) => {
|
||||
allow_err!(tx.send(Data::RawMessage(bytes)));
|
||||
}
|
||||
err => allow_err!(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn start_clipboard_file<T: InvokeUiCM>(
|
||||
cm: ConnectionManager<T>,
|
||||
mut rx: mpsc::UnboundedReceiver<ClipboardFileData>,
|
||||
) {
|
||||
let mut cliprdr_context = None;
|
||||
let mut rx_clip_client = clipboard::get_rx_clip_client().lock().await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
clip_file = rx_clip_client.recv() => match clip_file {
|
||||
Some((conn_id, clip)) => {
|
||||
cmd_inner_send(
|
||||
conn_id,
|
||||
Data::ClipbaordFile(clip)
|
||||
);
|
||||
}
|
||||
None => {
|
||||
//
|
||||
}
|
||||
},
|
||||
server_msg = rx.recv() => match server_msg {
|
||||
Some(ClipboardFileData::Clip((conn_id, clip))) => {
|
||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||
clipboard::server_clip_file(ctx, conn_id, clip);
|
||||
}
|
||||
}
|
||||
Some(ClipboardFileData::Enable((id, enabled))) => {
|
||||
if enabled && cliprdr_context.is_none() {
|
||||
cliprdr_context = Some(match clipboard::create_cliprdr_context(true, false) {
|
||||
Ok(context) => {
|
||||
log::info!("clipboard context for file transfer created.");
|
||||
context
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Create clipboard context for file transfer: {}",
|
||||
err.to_string()
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
clipboard::set_conn_enabled(id, enabled);
|
||||
if !enabled {
|
||||
if let Some(ctx) = cliprdr_context.as_mut() {
|
||||
clipboard::empty_clipboard(ctx, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn cmd_inner_send(id: i32, data: Data) {
|
||||
let lock = CLIENTS.read().unwrap();
|
||||
if id != 0 {
|
||||
if let Some(s) = lock.get(&id) {
|
||||
allow_err!(s.tx.send(data));
|
||||
}
|
||||
} else {
|
||||
for s in lock.values() {
|
||||
allow_err!(s.tx.send(data.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,10 +372,11 @@ pub fn get_mouse_time() -> f64 {
|
||||
return res;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn check_mouse_time() {
|
||||
let sender = SENDER.lock().unwrap();
|
||||
allow_err!(sender.send(ipc::Data::MouseMoveTime(0)));
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]{
|
||||
let sender = SENDER.lock().unwrap();
|
||||
allow_err!(sender.send(ipc::Data::MouseMoveTime(0)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_connect_status() -> Status {
|
||||
|
||||
@@ -34,7 +34,7 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Session<T: InvokeUi> {
|
||||
pub struct Session<T: InvokeUiSession> {
|
||||
pub cmd: String,
|
||||
pub id: String,
|
||||
pub password: String,
|
||||
@@ -45,7 +45,7 @@ pub struct Session<T: InvokeUi> {
|
||||
pub ui_handler: T,
|
||||
}
|
||||
|
||||
impl<T: InvokeUi> Session<T> {
|
||||
impl<T: InvokeUiSession> Session<T> {
|
||||
pub fn get_view_style(&self) -> String {
|
||||
self.lc.read().unwrap().view_style.clone()
|
||||
}
|
||||
@@ -151,11 +151,6 @@ impl<T: InvokeUi> Session<T> {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn t(&self, name: String) -> String {
|
||||
crate::client::translate(name)
|
||||
}
|
||||
|
||||
pub fn get_audit_server(&self) -> String {
|
||||
if self.lc.read().unwrap().conn_id <= 0
|
||||
|| LocalConfig::get_option("access_token").is_empty()
|
||||
@@ -690,11 +685,6 @@ impl<T: InvokeUi> Session<T> {
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_icon(&self) -> String {
|
||||
crate::get_icon()
|
||||
}
|
||||
|
||||
pub fn send_chat(&self, text: String) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_chat_message(ChatMessage {
|
||||
@@ -961,9 +951,35 @@ impl<T: InvokeUi> Session<T> {
|
||||
pub fn close(&self) {
|
||||
self.send(Data::Close);
|
||||
}
|
||||
|
||||
pub fn load_last_jobs(&self) {
|
||||
self.clear_all_jobs();
|
||||
let pc = self.load_config();
|
||||
if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() {
|
||||
// no last jobs
|
||||
return;
|
||||
}
|
||||
// TODO: can add a confirm dialog
|
||||
let mut cnt = 1;
|
||||
for job_str in pc.transfer.read_jobs.iter() {
|
||||
if !job_str.is_empty() {
|
||||
self.load_last_job(cnt, job_str);
|
||||
cnt += 1;
|
||||
log::info!("restore read_job: {:?}", job_str);
|
||||
}
|
||||
}
|
||||
for job_str in pc.transfer.write_jobs.iter() {
|
||||
if !job_str.is_empty() {
|
||||
self.load_last_job(cnt, job_str);
|
||||
cnt += 1;
|
||||
log::info!("restore write_job: {:?}", job_str);
|
||||
}
|
||||
}
|
||||
self.update_transfer_list();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InvokeUi: Send + Sync + Clone + 'static + Sized + Default {
|
||||
pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
||||
fn set_cursor_data(&self, cd: CursorData);
|
||||
fn set_cursor_id(&self, id: String);
|
||||
fn set_cursor_position(&self, cp: CursorPosition);
|
||||
@@ -978,18 +994,17 @@ pub trait InvokeUi: Send + Sync + Clone + 'static + Sized + Default {
|
||||
fn job_error(&self, id: i32, err: String, file_num: i32);
|
||||
fn job_done(&self, id: i32, file_num: i32);
|
||||
fn clear_all_jobs(&self);
|
||||
fn add_job(
|
||||
&self,
|
||||
id: i32,
|
||||
path: String,
|
||||
to: String,
|
||||
file_num: i32,
|
||||
show_hidden: bool,
|
||||
is_remote: bool,
|
||||
);
|
||||
fn new_message(&self, msg: String);
|
||||
fn update_transfer_list(&self);
|
||||
// fn update_folder_files(&self); // TODO flutter with file_dir and update_folder_files
|
||||
fn load_last_job(&self, cnt: i32, job_json: &str);
|
||||
fn update_folder_files(
|
||||
&self,
|
||||
id: i32,
|
||||
entries: &Vec<FileEntry>,
|
||||
path: String,
|
||||
is_local: bool,
|
||||
only_count: bool,
|
||||
);
|
||||
fn confirm_delete_files(&self, id: i32, i: i32, name: String);
|
||||
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool);
|
||||
fn update_block_input_state(&self, on: bool);
|
||||
@@ -1001,7 +1016,7 @@ pub trait InvokeUi: Send + Sync + Clone + 'static + Sized + Default {
|
||||
fn clipboard(&self, content: String);
|
||||
}
|
||||
|
||||
impl<T: InvokeUi> Deref for Session<T> {
|
||||
impl<T: InvokeUiSession> Deref for Session<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -1009,16 +1024,16 @@ impl<T: InvokeUi> Deref for Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InvokeUi> DerefMut for Session<T> {
|
||||
impl<T: InvokeUiSession> DerefMut for Session<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.ui_handler
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InvokeUi> FileManager for Session<T> {}
|
||||
impl<T: InvokeUiSession> FileManager for Session<T> {}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: InvokeUi> Interface for Session<T> {
|
||||
impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
fn send(&self, data: Data) {
|
||||
if let Some(sender) = self.sender.read().unwrap().as_ref() {
|
||||
sender.send(data).ok();
|
||||
@@ -1146,7 +1161,7 @@ impl<T: InvokeUi> Interface for Session<T> {
|
||||
// TODO use event callbcak
|
||||
// sciter only
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl<T: InvokeUi> Session<T> {
|
||||
impl<T: InvokeUiSession> Session<T> {
|
||||
fn start_keyboard_hook(&self) {
|
||||
if self.is_port_forward() || self.is_file_transfer() {
|
||||
return;
|
||||
@@ -1211,7 +1226,7 @@ impl<T: InvokeUi> Session<T> {
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn io_loop<T: InvokeUi>(handler: Session<T>) {
|
||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
|
||||
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
|
||||
*handler.sender.write().unwrap() = Some(sender.clone());
|
||||
let mut options = crate::ipc::get_options_async().await;
|
||||
@@ -1327,7 +1342,7 @@ pub async fn io_loop<T: InvokeUi>(handler: Session<T>) {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
async fn start_one_port_forward<T: InvokeUi>(
|
||||
async fn start_one_port_forward<T: InvokeUiSession>(
|
||||
handler: Session<T>,
|
||||
port: i32,
|
||||
remote_host: String,
|
||||
|
||||
Reference in New Issue
Block a user