diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt index b9f9ff872..ac736ffdc 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt @@ -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") diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 384d7692a..1993145e7 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -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') { diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index f78f8cf70..9d921ef48 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -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 evt) { + void addConnection(Map 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"); } diff --git a/src/client/file_trait.rs b/src/client/file_trait.rs index d2f7b1648..b94177c51 100644 --- a/src/client/file_trait.rs +++ b/src/client/file_trait.rs @@ -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(), } } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index e61690c32..552fea7a8 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -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 { +pub struct Remote { handler: Session, video_sender: MediaSender, audio_sender: MediaSender, @@ -42,13 +43,13 @@ pub struct Remote { last_update_jobs_status: (Instant, HashMap), first_frame: bool, #[cfg(windows)] - clipboard_file_context: Option>, + clipboard_file_context: Option>, data_count: Arc, frame_count: Arc, video_format: CodecFormat, } -impl Remote { +impl Remote { pub fn new( handler: Session, video_sender: MediaSender, @@ -106,7 +107,7 @@ impl Remote { #[cfg(not(windows))] let (_tx_holder, mut rx_clip_client) = mpsc::unbounded_channel::(); #[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 Remote { #[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 Remote { // 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 Remote { // TODO: can add a confirm dialog let mut cnt = 1; for job_str in pc.transfer.read_jobs.iter() { - let job: Result = 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 = 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 Remote { 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 Remote { 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 Remote { } 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 Remote { } 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 Remote { 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 Remote { 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 Remote { && 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) diff --git a/src/common.rs b/src/common.rs index 5c387c07e..471d6d4e2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -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, 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()) -} diff --git a/src/flutter.rs b/src/flutter.rs index b84e91ce8..1c4ed8869 100644 --- a/src/flutter.rs +++ b/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, 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) -> 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, + #[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> = 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::(); - // #[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::(); - let mut write_jobs: Vec = 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, tx: UnboundedSender) { - 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, tx: UnboundedSender) { - let mut current_id = 0; - let mut write_jobs: Vec = 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, + pub fn start_channel( + rx: UnboundedReceiver, + tx: UnboundedSender, ) { - 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, - tx: &UnboundedSender, - ) { - 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) { - 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( - res: std::result::Result, S>, - id: i32, - file_num: i32, - tx: &UnboundedSender, - ) { - 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) { - 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) { - 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) { - 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) { - 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, -// 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) -> 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, 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()) +} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 6a3d19880..5da94c3c1 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -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 { +pub fn session_add_sync( + id: String, + is_file_transfer: bool, + is_port_forward: bool, +) -> SyncReturn { 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 { - if length != get_clients_length() { - Some(get_clients_state()) +pub fn cm_check_clients_length(length: usize) -> Option { + 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 { diff --git a/src/lib.rs b/src/lib.rs index f554d447e..b427c3301 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ mod tray; mod ui_interface; mod ui_session_interface; +mod ui_cm_interface; #[cfg(windows)] pub mod clipboard_file; diff --git a/src/ui.rs b/src/ui.rs index b66d1453b..b8b136c45 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -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" diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 222b9b5c9..2b1e3e791 100644 --- a/src/ui/cm.rs +++ b/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, - senders: HashMap>, - 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>>, } -#[derive(Clone)] -pub struct ConnectionManager(Arc>); +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>; + 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); + +impl Deref for SciterConnectionManager { + type Target = ConnectionManager; 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, - ) { - 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, - write_jobs: &mut Vec, - 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( - res: std::result::Result, 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::(); - #[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::(); - let mut write_jobs: Vec = 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, -) { - 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())); - } - } -} diff --git a/src/ui/file_transfer.tis b/src/ui/file_transfer.tis index f32540b33..38c6321dc 100644 --- a/src/ui/file_transfer.tis +++ b/src/ui/file_transfer.tis @@ -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); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 7e2c5cd9c..08430110c 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -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; @@ -38,12 +37,8 @@ lazy_static::lazy_static! { static ref VIDEO: Arc>> = 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 = 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, 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, 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, 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 } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs new file mode 100644 index 000000000..d416fdd63 --- /dev/null +++ b/src/ui_cm_interface.rs @@ -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, +} + +lazy_static::lazy_static! { + static ref CLIENTS: RwLock> = Default::default(); + static ref CLICK_TIME: AtomicI64 = AtomicI64::new(0); +} + +#[derive(Clone)] +pub struct ConnectionManager { + 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 Deref for ConnectionManager { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.ui_handler + } +} + +impl DerefMut for ConnectionManager { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ui_handler + } +} + +impl ConnectionManager { + 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, + ) { + 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(cm: ConnectionManager) { + let (tx_file, _rx_file) = mpsc::unbounded_channel::(); + #[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::(); + let mut write_jobs: Vec = 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( + cm: ConnectionManager, + mut rx: mpsc::UnboundedReceiver, + tx: mpsc::UnboundedSender, +) { + let mut current_id = 0; + let mut write_jobs: Vec = 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, tx: &UnboundedSender) { + 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) { + 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( + res: std::result::Result, S>, + id: i32, + file_num: i32, + tx: &UnboundedSender, +) { + 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) { + 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) { + 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) { + 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) { + 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( + cm: ConnectionManager, + mut rx: mpsc::UnboundedReceiver, +) { + 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())); + } + } +} diff --git a/src/ui_interface.rs b/src/ui_interface.rs index a8e3be980..b8d59ac8f 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -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 { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 5ab6089a0..717963561 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -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 { +pub struct Session { pub cmd: String, pub id: String, pub password: String, @@ -38,7 +41,7 @@ pub struct Session { pub ui_handler: T, } -impl Session { +impl Session { pub fn get_view_style(&self) -> String { self.lc.read().unwrap().view_style.clone() } @@ -135,11 +138,6 @@ impl Session { 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 Session { 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 Session { } } -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, + 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 Deref for Session { +impl Deref for Session { type Target = T; fn deref(&self) -> &Self::Target { @@ -587,16 +579,16 @@ impl Deref for Session { } } -impl DerefMut for Session { +impl DerefMut for Session { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.ui_handler } } -impl FileManager for Session {} +impl FileManager for Session {} #[async_trait] -impl Interface for Session { +impl Interface for Session { fn send(&self, data: Data) { if let Some(sender) = self.sender.read().unwrap().as_ref() { sender.send(data).ok(); @@ -604,11 +596,19 @@ impl Interface for Session { } 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 Interface for Session { // TODO use event callbcak // sciter only #[cfg(not(any(target_os = "android", target_os = "ios")))] -impl Session { +impl Session { fn start_keyboard_hook(&self) { if self.is_port_forward() || self.is_file_transfer() { return; @@ -951,7 +951,7 @@ impl Session { } #[tokio::main(flavor = "current_thread")] -pub async fn io_loop(handler: Session) { +pub async fn io_loop(handler: Session) { let (sender, mut receiver) = mpsc::unbounded_channel::(); *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(handler: Session) { } #[cfg(not(any(target_os = "android", target_os = "ios")))] -async fn start_one_port_forward( +async fn start_one_port_forward( handler: Session, port: i32, remote_host: String,