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/io_loop.rs b/src/client/io_loop.rs index 0913251dd..54c3be26e 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -6,7 +6,7 @@ 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}; @@ -29,7 +29,7 @@ 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, @@ -49,7 +49,7 @@ pub struct Remote { video_format: CodecFormat, } -impl Remote { +impl Remote { pub fn new( handler: Session, video_sender: MediaSender, diff --git a/src/flutter.rs b/src/flutter.rs index a2f03d2ff..53b79949a 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -10,7 +10,7 @@ use hbb_common::{ }; 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}; @@ -47,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( @@ -338,625 +338,82 @@ 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, - }, - }; #[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::{ + ipc::start_pa, + 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(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) { + use crate::ui_cm_interface::start_listen; std::thread::spawn(move || start_listen(rx, tx)); } - - #[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, - ) { - let mut client = Client { - id, - authorized, - is_file_transfer, - name: name.clone(), - peer_id: peer_id.clone(), - keyboard, - clipboard, - audio, - file, - restart, - tx, - }; - 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), - } - } } #[inline] diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 032be5b1f..2a41264f0 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -13,7 +13,6 @@ use hbb_common::{ fs, log, }; -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; @@ -673,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 } @@ -791,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/remote.rs b/src/ui/remote.rs index b377b8583..f6b3acec6 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -2,7 +2,7 @@ use std::{ collections::HashMap, ops::{Deref, DerefMut}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::Ordering, Arc, Mutex, }, }; @@ -31,7 +31,7 @@ use hbb_common::{ use crate::clipboard_file::*; use crate::{ client::*, - ui_session_interface::{InvokeUi, Session, IS_IN}, + ui_session_interface::{InvokeUiSession, Session, IS_IN}, }; type Video = AssetPtr; @@ -68,7 +68,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() { @@ -445,6 +445,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")] { diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs new file mode 100644 index 000000000..8a26a9558 --- /dev/null +++ b/src/ui_cm_interface.rs @@ -0,0 +1,665 @@ +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 + .iter() + .filter(|(_k, v)| !v.is_file_transfer) + .next() + .is_none() + { + if let Err(e) = 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; + } + // 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 { + 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(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 }) => { + cm.new_message(conn_id, text); + } + Some(Data::FS(fs)) => { + handle_fs(fs, &mut write_jobs, &tx).await; + } + Some(Data::Close) => { + break; + } + None => { + break; + } + _ => {} + } + } + 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 = 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_session_interface.rs b/src/ui_session_interface.rs index f117aae6d..9fca2dfbb 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -27,7 +27,7 @@ pub static IS_IN: AtomicBool = AtomicBool::new(false); static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); #[derive(Clone, Default)] -pub struct Session { +pub struct Session { pub cmd: String, pub id: String, pub password: String, @@ -38,7 +38,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 +135,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 +322,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 +531,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); @@ -578,7 +568,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 { @@ -586,16 +576,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(); @@ -723,7 +713,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; @@ -958,7 +948,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; @@ -1074,7 +1064,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,