mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-19 00:13:01 +08:00
Merge pull request #1451 from Heap-Hop/refactor_ui_interface
Refactor UI interface
This commit is contained in:
commit
82acb6a351
@ -105,43 +105,30 @@ class MainService : Service() {
|
||||
@Keep
|
||||
fun rustSetByName(name: String, arg1: String, arg2: String) {
|
||||
when (name) {
|
||||
"try_start_without_auth" -> {
|
||||
try {
|
||||
val jsonObject = JSONObject(arg1)
|
||||
val id = jsonObject["id"] as Int
|
||||
val username = jsonObject["name"] as String
|
||||
val peerId = jsonObject["peer_id"] as String
|
||||
val type = if (jsonObject["is_file_transfer"] as Boolean) {
|
||||
translate("File Connection")
|
||||
} else {
|
||||
translate("Screen Connection")
|
||||
}
|
||||
loginRequestNotification(id, type, username, peerId)
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
"on_client_authorized" -> {
|
||||
Log.d(logTag, "from rust:on_client_authorized")
|
||||
"add_connection" -> {
|
||||
try {
|
||||
val jsonObject = JSONObject(arg1)
|
||||
val id = jsonObject["id"] as Int
|
||||
val username = jsonObject["name"] as String
|
||||
val peerId = jsonObject["peer_id"] as String
|
||||
val authorized = jsonObject["authorized"] as Boolean
|
||||
val isFileTransfer = jsonObject["is_file_transfer"] as Boolean
|
||||
val type = if (isFileTransfer) {
|
||||
translate("File Connection")
|
||||
} else {
|
||||
translate("Screen Connection")
|
||||
}
|
||||
if (!isFileTransfer && !isStart) {
|
||||
startCapture()
|
||||
if (authorized) {
|
||||
if (!isFileTransfer && !isStart) {
|
||||
startCapture()
|
||||
}
|
||||
onClientAuthorizedNotification(id, type, username, peerId)
|
||||
} else {
|
||||
loginRequestNotification(id, type, username, peerId)
|
||||
}
|
||||
onClientAuthorizedNotification(id, type, username, peerId)
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
"stop_capture" -> {
|
||||
Log.d(logTag, "from rust:stop_capture")
|
||||
|
@ -168,10 +168,8 @@ class FfiModel with ChangeNotifier {
|
||||
parent.target?.fileModel.loadLastJob(evt);
|
||||
} else if (name == 'update_folder_files') {
|
||||
parent.target?.fileModel.updateFolderFiles(evt);
|
||||
} else if (name == 'try_start_without_auth') {
|
||||
parent.target?.serverModel.loginRequest(evt);
|
||||
} else if (name == 'on_client_authorized') {
|
||||
parent.target?.serverModel.onClientAuthorized(evt);
|
||||
} else if (name == 'add_connection') {
|
||||
parent.target?.serverModel.addConnection(evt);
|
||||
} else if (name == 'on_client_remove') {
|
||||
parent.target?.serverModel.onClientRemove(evt);
|
||||
} else if (name == 'update_quality_status') {
|
||||
@ -227,10 +225,8 @@ class FfiModel with ChangeNotifier {
|
||||
parent.target?.fileModel.loadLastJob(evt);
|
||||
} else if (name == 'update_folder_files') {
|
||||
parent.target?.fileModel.updateFolderFiles(evt);
|
||||
} else if (name == 'try_start_without_auth') {
|
||||
parent.target?.serverModel.loginRequest(evt);
|
||||
} else if (name == 'on_client_authorized') {
|
||||
parent.target?.serverModel.onClientAuthorized(evt);
|
||||
} else if (name == 'add_connection') {
|
||||
parent.target?.serverModel.addConnection(evt);
|
||||
} else if (name == 'on_client_remove') {
|
||||
parent.target?.serverModel.onClientRemove(evt);
|
||||
} else if (name == 'update_quality_status') {
|
||||
|
@ -100,7 +100,7 @@ class ServerModel with ChangeNotifier {
|
||||
_connectStatus = status;
|
||||
notifyListeners();
|
||||
}
|
||||
final res = await bind.mainCheckClientsLength(length: _clients.length);
|
||||
final res = await bind.cmCheckClientsLength(length: _clients.length);
|
||||
if (res != null) {
|
||||
debugPrint("clients not match!");
|
||||
updateClientState(res);
|
||||
@ -347,7 +347,7 @@ class ServerModel with ChangeNotifier {
|
||||
|
||||
// force
|
||||
updateClientState([String? json]) async {
|
||||
var res = await bind.mainGetClientsState();
|
||||
var res = await bind.cmGetClientsState();
|
||||
try {
|
||||
final List clientsJson = jsonDecode(res);
|
||||
_clients.clear();
|
||||
@ -369,21 +369,40 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void loginRequest(Map<String, dynamic> evt) {
|
||||
void addConnection(Map<String, dynamic> evt) {
|
||||
try {
|
||||
final client = Client.fromJson(jsonDecode(evt["client"]));
|
||||
if (_clients.any((c) => c.id == client.id)) {
|
||||
return;
|
||||
if (client.authorized) {
|
||||
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(client.id));
|
||||
final index = _clients.indexWhere((c) => c.id == client.id);
|
||||
if (index < 0) {
|
||||
_clients.add(client);
|
||||
} else {
|
||||
_clients[index].authorized = true;
|
||||
}
|
||||
tabController.add(
|
||||
TabInfo(
|
||||
key: client.id.toString(),
|
||||
label: client.name,
|
||||
closable: false,
|
||||
page: Desktop.buildConnectionCard(client)),
|
||||
authorized: true);
|
||||
scrollToBottom();
|
||||
notifyListeners();
|
||||
} else {
|
||||
if (_clients.any((c) => c.id == client.id)) {
|
||||
return;
|
||||
}
|
||||
_clients.add(client);
|
||||
tabController.add(TabInfo(
|
||||
key: client.id.toString(),
|
||||
label: client.name,
|
||||
closable: false,
|
||||
page: Desktop.buildConnectionCard(client)));
|
||||
scrollToBottom();
|
||||
notifyListeners();
|
||||
if (isAndroid) showLoginDialog(client);
|
||||
}
|
||||
_clients.add(client);
|
||||
tabController.add(TabInfo(
|
||||
key: client.id.toString(),
|
||||
label: client.name,
|
||||
closable: false,
|
||||
page: Desktop.buildConnectionCard(client)));
|
||||
scrollToBottom();
|
||||
notifyListeners();
|
||||
if (isAndroid) showLoginDialog(client);
|
||||
} catch (e) {
|
||||
debugPrint("Failed to call loginRequest,error:$e");
|
||||
}
|
||||
|
@ -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,10 +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};
|
||||
|
||||
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};
|
||||
@ -21,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,
|
||||
@ -42,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,
|
||||
@ -106,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));
|
||||
|
||||
@ -152,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!()
|
||||
@ -270,7 +271,6 @@ impl<T: InvokeUi> Remote<T> {
|
||||
|
||||
// TODO
|
||||
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() {
|
||||
@ -280,33 +280,17 @@ impl<T: InvokeUi> Remote<T> {
|
||||
// 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,
|
||||
);
|
||||
if !job_str.is_empty() {
|
||||
self.handler.load_last_job(cnt, job_str);
|
||||
cnt += 1;
|
||||
println!("restore read_job: {:?}", job);
|
||||
log::info!("restore read_job: {:?}", job_str);
|
||||
}
|
||||
}
|
||||
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,
|
||||
);
|
||||
if !job_str.is_empty() {
|
||||
self.handler.load_last_job(cnt, job_str);
|
||||
cnt += 1;
|
||||
println!("restore write_job: {:?}", job);
|
||||
log::info!("restore write_job: {:?}", job_str);
|
||||
}
|
||||
}
|
||||
self.handler.update_transfer_list();
|
||||
@ -373,8 +357,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)]
|
||||
@ -433,8 +422,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);
|
||||
@ -546,8 +540,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));
|
||||
}
|
||||
@ -749,28 +748,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.load_last_jobs();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@ -804,8 +803,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -823,11 +822,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,
|
||||
fd.id > 0,
|
||||
);
|
||||
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
|
||||
log::info!("job set_files: {:?}", entries);
|
||||
job.set_files(entries);
|
||||
@ -1155,7 +1156,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)
|
||||
|
@ -666,49 +666,3 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
|
||||
msg_out.set_misc(misc);
|
||||
msg_out
|
||||
}
|
||||
|
||||
pub fn make_fd_to_json(fd: FileDirectory) -> String {
|
||||
use serde_json::json;
|
||||
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())
|
||||
}
|
||||
|
795
src/flutter.rs
795
src/flutter.rs
@ -5,9 +5,12 @@ 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};
|
||||
|
||||
@ -44,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(
|
||||
@ -85,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) {
|
||||
@ -118,7 +122,14 @@ 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)]);
|
||||
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) {
|
||||
@ -128,29 +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)]);
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
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(
|
||||
@ -176,6 +201,7 @@ impl InvokeUi for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// unused in flutter
|
||||
fn adapt_size(&self) {}
|
||||
|
||||
fn on_rgba(&self, data: &[u8]) {
|
||||
@ -283,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();
|
||||
}
|
||||
|
||||
@ -316,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, UnboundedReceiver, 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, 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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -946,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())
|
||||
}
|
||||
|
@ -5,17 +5,14 @@ use std::{
|
||||
};
|
||||
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
|
||||
use serde_json::{json, Number, Value};
|
||||
use serde_json::json;
|
||||
|
||||
use hbb_common::ResultType;
|
||||
use hbb_common::{
|
||||
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
||||
config::{self, LocalConfig, PeerConfig, ONLINE},
|
||||
fs, log,
|
||||
};
|
||||
use hbb_common::{password_security, ResultType};
|
||||
|
||||
use crate::{client::file_trait::FileManager, flutter::{session_add, session_start_}};
|
||||
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;
|
||||
@ -30,6 +27,10 @@ use crate::ui_interface::{
|
||||
set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server,
|
||||
update_temporary_password, using_public_server,
|
||||
};
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
flutter::{make_fd_to_json, session_add, session_start_},
|
||||
};
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
@ -110,7 +111,11 @@ pub fn host_stop_system_key_propagate(stopped: bool) {
|
||||
|
||||
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
|
||||
// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25
|
||||
pub fn session_add_sync(id: String, is_file_transfer: bool, is_port_forward: bool) -> SyncReturn<String> {
|
||||
pub fn session_add_sync(
|
||||
id: String,
|
||||
is_file_transfer: bool,
|
||||
is_port_forward: bool,
|
||||
) -> SyncReturn<String> {
|
||||
if let Err(e) = session_add(&id, is_file_transfer, is_port_forward) {
|
||||
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
|
||||
} else {
|
||||
@ -346,10 +351,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(session) = 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()
|
||||
}
|
||||
@ -669,13 +672,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
|
||||
}
|
||||
@ -751,7 +754,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();
|
||||
}
|
||||
}
|
||||
@ -759,7 +762,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"))]
|
||||
@ -787,27 +790,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;
|
||||
|
@ -125,7 +125,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"
|
||||
|
623
src/ui/cm.rs
623
src/ui/cm.rs
@ -1,60 +1,83 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::ipc::start_pa;
|
||||
use crate::ipc::{self, new_listener, Connection, Data};
|
||||
use crate::VERSION;
|
||||
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::{
|
||||
can_enable_overwrite_detection, get_string, is_write_need_confirmation, new_send_confirm,
|
||||
DigestCheckResult,
|
||||
};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
config::Config,
|
||||
fs, get_version_number, 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 {
|
||||
@ -62,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 {
|
||||
@ -422,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! {
|
||||
@ -438,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);
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
sync::{atomic::Ordering, Arc, Mutex},
|
||||
};
|
||||
|
||||
use sciter::{
|
||||
@ -23,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, IS_IN},
|
||||
ui_session_interface::{InvokeUiSession, Session, IS_IN},
|
||||
};
|
||||
|
||||
type Video = AssetPtr<video_destination>;
|
||||
@ -38,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 {
|
||||
@ -67,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() {
|
||||
@ -155,16 +150,36 @@ impl InvokeUi for SciterHandler {
|
||||
self.call("clearAllJobs", &make_args!());
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -424,6 +439,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")]
|
||||
{
|
||||
@ -686,15 +709,18 @@ impl SciterSession {
|
||||
}
|
||||
|
||||
pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
|
||||
log::debug!("make_fd");
|
||||
let mut m = Value::map();
|
||||
m.set_item("id", id);
|
||||
let mut a = Value::array(0);
|
||||
let mut n: u64 = 0;
|
||||
for entry in entries {
|
||||
log::debug!("for");
|
||||
n += entry.size;
|
||||
if only_count {
|
||||
continue;
|
||||
}
|
||||
log::debug!("for1");
|
||||
let mut e = Value::map();
|
||||
e.set_item("name", entry.name.to_owned());
|
||||
let tmp = entry.entry_type.value();
|
||||
@ -703,11 +729,11 @@ 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);
|
||||
log::debug!("make_fd end");
|
||||
m
|
||||
}
|
||||
|
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 {
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::client::io_loop::Remote;
|
||||
use crate::client::{
|
||||
check_if_retry, handle_hash, handle_login_from_ui, handle_test_delay,
|
||||
input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key,
|
||||
LoginConfigHandler, QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::client::get_key_state;
|
||||
use crate::client::io_loop::Remote;
|
||||
use crate::client::{
|
||||
check_if_retry, handle_hash, handle_login_from_ui, handle_test_delay, input_os_password,
|
||||
load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler,
|
||||
QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED,
|
||||
};
|
||||
use crate::common;
|
||||
use crate::{client::Data, client::Interface};
|
||||
use async_trait::async_trait;
|
||||
@ -19,15 +19,18 @@ use hbb_common::{allow_err, message_proto::*};
|
||||
use hbb_common::{fs, get_version_number, log, Stream};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering, AtomicBool};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
/// IS_IN KEYBOARD_HOOKED sciter only
|
||||
pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
||||
static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(windows)]
|
||||
static mut IS_ALT_GR: bool = false;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Session<T: InvokeUi> {
|
||||
pub struct Session<T: InvokeUiSession> {
|
||||
pub cmd: String,
|
||||
pub id: String,
|
||||
pub password: String,
|
||||
@ -38,7 +41,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()
|
||||
}
|
||||
@ -135,11 +138,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()
|
||||
@ -327,11 +325,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 {
|
||||
@ -541,7 +534,7 @@ impl<T: InvokeUi> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@ -556,18 +549,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);
|
||||
@ -579,7 +571,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 {
|
||||
@ -587,16 +579,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();
|
||||
@ -604,11 +596,19 @@ impl<T: InvokeUi> Interface for Session<T> {
|
||||
}
|
||||
|
||||
fn is_file_transfer(&self) -> bool {
|
||||
self.lc.read().unwrap().conn_type.eq(&ConnType::FILE_TRANSFER)
|
||||
self.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.conn_type
|
||||
.eq(&ConnType::FILE_TRANSFER)
|
||||
}
|
||||
|
||||
fn is_port_forward(&self) -> bool {
|
||||
self.lc.read().unwrap().conn_type.eq(&ConnType::PORT_FORWARD)
|
||||
self.lc
|
||||
.read()
|
||||
.unwrap()
|
||||
.conn_type
|
||||
.eq(&ConnType::PORT_FORWARD)
|
||||
}
|
||||
|
||||
fn is_rdp(&self) -> bool {
|
||||
@ -716,7 +716,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;
|
||||
@ -951,7 +951,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;
|
||||
@ -1067,7 +1067,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,
|
||||
|
Loading…
Reference in New Issue
Block a user