diff --git a/Cargo.lock b/Cargo.lock index 987b6fdc4..8959e3b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1150,6 +1150,25 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1733,6 +1752,45 @@ dependencies = [ "tiff", ] +[[package]] +name = "include_dir" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e074c19deab2501407c91ba1860fa3d6820bfde307db6d8cb851b55a10be89b" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -2095,6 +2153,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + [[package]] name = "mio-named-pipes" version = "0.1.7" @@ -2308,6 +2378,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "notify" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -3230,6 +3318,7 @@ dependencies = [ "flexi_logger", "hbb_common", "hound", + "include_dir", "lazy_static", "libc", "libpulse-binding", @@ -3237,6 +3326,7 @@ dependencies = [ "mac_address", "machine-uid", "magnum-opus", + "notify", "objc", "parity-tokio-ipc", "psutil", diff --git a/Cargo.toml b/Cargo.toml index 6a9325e25..4f10ec7d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,8 @@ cocoa = "0.24" dispatch = "0.2" core-foundation = "0.9" core-graphics = "0.22" - +notify = "4.0.17" +include_dir = "0.7.2" [target.'cfg(target_os = "linux")'.dependencies] libpulse-simple-binding = "2.24" libpulse-binding = "2.25" diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 21ca74a06..85dcfd984 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -190,6 +190,11 @@ impl Config2 { Config::load_::("2") } + fn reload(&mut self) { + let new_config = Config2::load(); + *self = new_config; + } + fn store(&self) { Config::store_(self, "2"); } @@ -214,6 +219,11 @@ impl Config { cfg } + fn reload(&mut self) { + let new_config = Config::load(); + *self = new_config; + } + fn store_(config: &T, suffix: &str) { let file = Self::file_(suffix); if let Err(err) = confy::store_path(file, config) { @@ -272,7 +282,7 @@ impl Config { } } - fn path>(p: P) -> PathBuf { + pub fn path>(p: P) -> PathBuf { #[cfg(any(target_os = "android", target_os = "ios"))] { let mut path: PathBuf = APP_DIR.read().unwrap().clone().into(); @@ -659,6 +669,93 @@ impl Config { Some(_) => NetworkType::ProxySocks, } } + + pub fn sync_config_to_user>(target_username: String, to_dir: P) -> bool { + let config1_root_file_path = Config::file_(""); + let config1_filename = config1_root_file_path.file_name(); + + let config2_root_file_path = Config::file_("2"); + let config2_filename = config2_root_file_path.file_name(); + + let config1_to_file_path = to_dir + .as_ref() + .join(PathBuf::from(&config1_filename.unwrap())); + let config2_to_file_path = to_dir + .as_ref() + .join(PathBuf::from(&config2_filename.unwrap())); + + log::info!( + "config1_root_path:{}", + &config1_root_file_path.as_path().to_str().unwrap() + ); + log::info!( + "config2_root_path:{}", + &config2_root_file_path.as_path().to_str().unwrap() + ); + log::info!( + "config1_to_path:{}", + &config1_to_file_path.as_path().to_str().unwrap() + ); + log::info!( + "config2_to_path:{}", + &config2_to_file_path.as_path().to_str().unwrap() + ); + + match std::fs::copy(&config1_root_file_path, &config1_to_file_path) { + Err(e) => log::error!( + "copy config {} to user failed: {}", + config1_filename.unwrap().to_str().unwrap(), + e + ), + _ => {} + } + + match std::fs::copy(&config2_root_file_path, &config2_to_file_path) { + Err(e) => log::error!( + "copy config {} to user failed: {}", + config2_filename.unwrap().to_str().unwrap(), + e + ), + _ => {} + } + + let success = std::process::Command::new("chown") + .arg(&target_username.to_string()) + .arg(&config1_to_file_path.to_str().unwrap().to_string()) + .arg(&config2_to_file_path.to_str().unwrap().to_string()) + .spawn() + .is_ok(); + + if success { + CONFIG.write().unwrap().reload(); + CONFIG2.write().unwrap().reload(); + } + + return success; + } + + pub fn sync_config_to_root>(from_file_path: P) -> bool { + if let Some(filename) = from_file_path.as_ref().file_name() { + let to = Config::path(filename); + return match std::fs::copy(from_file_path, &to) { + Ok(count) => { + if count > 0 { + return std::process::Command::new("chown") + .arg("root") + .arg(&to.to_str().unwrap().to_string()) + .spawn() + .is_ok(); + } + false + } + Err(e) => { + log::error!("sync_config_to_root failed: {}", e); + false + } + }; + } + false + } } const PEERS: &str = "peers"; diff --git a/src/ipc.rs b/src/ipc.rs index e197d804d..8bd206f67 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -92,6 +92,15 @@ pub enum Data { Socks(Option), FS(FS), Test, + SyncConfigToRootReq { + from: String, + }, + SyncConfigToRootResp(bool), + SyncConfigToUserReq { + username: String, + to: String, + }, + SyncConfigToUserResp(bool), } #[tokio::main(flavor = "current_thread")] @@ -252,6 +261,24 @@ async fn handle(data: Data, stream: &mut Connection) { let t = Config::get_nat_type(); allow_err!(stream.send(&Data::NatType(Some(t))).await); } + Data::SyncConfigToRootReq { from } => { + allow_err!( + stream + .send(&Data::SyncConfigToRootResp(Config::sync_config_to_root( + from + ))) + .await + ); + } + Data::SyncConfigToUserReq { username, to } => { + allow_err!( + stream + .send(&Data::SyncConfigToUserResp(Config::sync_config_to_user( + username, to + ))) + .await + ); + } _ => {} } } diff --git a/src/main.rs b/src/main.rs index e2d00d0c5..215aa67ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,6 +102,13 @@ fn main() { } return; } + + #[cfg(target_os = "macos")] + if args[0] == "--daemon" { + log::info!("start --daemon"); + crate::platform::start_daemon(); + return; + } } ui::start(&mut args[..]); } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 5834fd024..92369d258 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -17,9 +17,12 @@ use core_graphics::{ window::{kCGWindowName, kCGWindowOwnerPID}, }; use hbb_common::{allow_err, bail, log}; +use include_dir::{include_dir, Dir}; use objc::{class, msg_send, sel, sel_impl}; use scrap::{libc::c_void, quartz::ffi::*}; +static PRIVILEGES_SCRIPTS_DIR: Dir = + include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts"); static mut LATEST_SEED: i32 = 0; extern "C" { @@ -98,6 +101,79 @@ pub fn is_can_screen_recording(prompt: bool) -> bool { can_record_screen } +pub fn is_installed_daemon(prompt: bool) -> bool { + if !prompt { + if !std::path::Path::new("/Library/LaunchDaemons/com.carriez.rustdesk.daemon.plist") + .exists() + { + return false; + } + + if !std::path::Path::new("/Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist") + .exists() + { + return false; + } + + if !std::path::Path::new("/Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist") + .exists() + { + return false; + } + + return true; + } + + let install_script = PRIVILEGES_SCRIPTS_DIR.get_file("install.scpt").unwrap(); + let install_script_body = install_script.contents_utf8().unwrap(); + + let daemon_plist = PRIVILEGES_SCRIPTS_DIR + .get_file("com.carriez.rustdesk.daemon.plist") + .unwrap(); + let daemon_plist_body = daemon_plist.contents_utf8().unwrap(); + + let root_agent_plist = PRIVILEGES_SCRIPTS_DIR + .get_file("com.carriez.rustdesk.agent.root.plist") + .unwrap(); + let root_agent_plist_body = root_agent_plist.contents_utf8().unwrap(); + + let user_agent_plist = PRIVILEGES_SCRIPTS_DIR + .get_file("com.carriez.rustdesk.agent.user.plist") + .unwrap(); + let user_agent_plist_body = user_agent_plist.contents_utf8().unwrap(); + + match std::process::Command::new("osascript") + .arg("-e") + .arg(install_script_body) + .arg(daemon_plist_body) + .arg(root_agent_plist_body) + .arg(user_agent_plist_body) + .spawn() + { + Ok(mut proc) => proc.wait().is_ok(), + Err(e) => { + log::error!("run osascript failed: {}", e); + false + }, + } +} + +pub fn launch_or_stop_daemon(launch: bool) { + let mut script_filename = "launch_service.scpt"; + if !launch { + script_filename = "stop_service.scpt"; + } + + let script_file = PRIVILEGES_SCRIPTS_DIR.get_file(script_filename).unwrap(); + let script_body = script_file.contents_utf8().unwrap(); + + std::process::Command::new("osascript") + .arg("-e") + .arg(script_body) + .spawn() + .ok(); +} + pub fn get_cursor_pos() -> Option<(i32, i32)> { unsafe { let e = CGEventCreate(0 as _); @@ -333,3 +409,11 @@ pub fn block_input(_v: bool) { pub fn is_installed() -> bool { true } + +pub fn start_daemon() { + log::info!("{}", crate::username()); + if let Err(err) = crate::ipc::start("_daemon") { + log::error!("Failed to start ipc_daemon: {}", err); + std::process::exit(-1); + } +} diff --git a/src/platform/privileges_scripts/com.carriez.rustdesk.agent.root.plist b/src/platform/privileges_scripts/com.carriez.rustdesk.agent.root.plist new file mode 100644 index 000000000..b77a31f54 --- /dev/null +++ b/src/platform/privileges_scripts/com.carriez.rustdesk.agent.root.plist @@ -0,0 +1,28 @@ + + + + + Label + com.carriez.rustdesk.agent.root + LimitLoadToSessionType + + LoginWindow + + KeepAlive + + SuccessfulExit + + AfterInitialDemand + + + RunAtLoad + + ProgramArguments + + /Applications/RustDesk.app/Contents/MacOS/rustdesk + --server + + WorkingDirectory + /Applications/RustDesk.app/Contents/MacOS/ + + \ No newline at end of file diff --git a/src/platform/privileges_scripts/com.carriez.rustdesk.agent.user.plist b/src/platform/privileges_scripts/com.carriez.rustdesk.agent.user.plist new file mode 100644 index 000000000..fc13fa1f5 --- /dev/null +++ b/src/platform/privileges_scripts/com.carriez.rustdesk.agent.user.plist @@ -0,0 +1,28 @@ + + + + + Label + com.carriez.rustdesk.agent.user + LimitLoadToSessionType + + Aqua + + KeepAlive + + SuccessfulExit + + AfterInitialDemand + + + RunAtLoad + + ProgramArguments + + /Applications/RustDesk.app/Contents/MacOS/rustdesk + --server + + WorkingDirectory + /Applications/RustDesk.app/Contents/MacOS/ + + \ No newline at end of file diff --git a/src/platform/privileges_scripts/com.carriez.rustdesk.daemon.plist b/src/platform/privileges_scripts/com.carriez.rustdesk.daemon.plist new file mode 100644 index 000000000..529af8c5a --- /dev/null +++ b/src/platform/privileges_scripts/com.carriez.rustdesk.daemon.plist @@ -0,0 +1,19 @@ + + + + + Label + com.carriez.rustdesk.daemon + KeepAlive + + ProgramArguments + + /Applications/RustDesk.app/Contents/MacOS/rustdesk + --daemon + + RunAtLoad + + WorkingDirectory + /Applications/RustDesk.app/Contents/MacOS/ + + \ No newline at end of file diff --git a/src/platform/privileges_scripts/install.scpt b/src/platform/privileges_scripts/install.scpt new file mode 100644 index 000000000..9d96fa9de --- /dev/null +++ b/src/platform/privileges_scripts/install.scpt @@ -0,0 +1,19 @@ +on run {daemon_file, root_agent_file, user_agent_file} + + set sh1 to "echo " & quoted form of daemon_file & " > /Library/LaunchDaemons/com.carriez.rustdesk.daemon.plist && chown root:wheel /Library/LaunchDaemons/com.carriez.rustdesk.daemon.plist;" + + set sh2 to "echo " & quoted form of root_agent_file & " > /Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist && chown root:wheel /Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist;" + + set sh3 to "echo " & quoted form of user_agent_file & " > /Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist && chown root:wheel /Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist;" + + set sh4 to "launchctl load -w /Library/LaunchDaemons/com.carriez.rustdesk.daemon.plist;" + + set sh5 to "launchctl load -w /Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist;" + + set sh6 to "launchctl load -w /Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist;" + + set sh to sh1 & sh2 & sh3 & sh4 & sh5 &sh6 + + log (sh) + do shell script sh with prompt "RustDesk 需要安装服务" with administrator privileges +end run \ No newline at end of file diff --git a/src/platform/privileges_scripts/launch_service.scpt b/src/platform/privileges_scripts/launch_service.scpt new file mode 100644 index 000000000..3a0b530b0 --- /dev/null +++ b/src/platform/privileges_scripts/launch_service.scpt @@ -0,0 +1,7 @@ +set sh1 to "launchctl load -w /Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist;" + +set sh2 to "launchctl load -w /Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist;" + +set sh to sh1 & sh2 + +do shell script sh with prompt "RustDesk 需要停止服务" with administrator privileges \ No newline at end of file diff --git a/src/platform/privileges_scripts/stop_service.scpt b/src/platform/privileges_scripts/stop_service.scpt new file mode 100644 index 000000000..58be8292f --- /dev/null +++ b/src/platform/privileges_scripts/stop_service.scpt @@ -0,0 +1,7 @@ +set sh1 to "launchctl unload -w /Library/LaunchAgents/com.carriez.rustdesk.agent.root.plist;" + +set sh2 to "launchctl unload -w /Library/LaunchAgents/com.carriez.rustdesk.agent.user.plist;" + +set sh to sh1 & sh2 + +do shell script sh with prompt "RustDesk 需要停止服务" with administrator privileges \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 451c18e1b..70e254fb2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,4 @@ -use crate::ipc::Data; +use crate::ipc::{ConnectionTmpl, Data}; use connection::{ConnInner, Connection}; use hbb_common::{ allow_err, @@ -9,12 +9,16 @@ use hbb_common::{ message_proto::*, protobuf::{Message as _, ProtobufEnum}, rendezvous_proto::*, - sleep, + sleep, socket_client, sodiumoxide::crypto::{box_, secretbox, sign}, timeout, tokio, ResultType, Stream, - socket_client, }; +#[cfg(target_os = "macos")] +use notify::{watcher, RecursiveMode, Watcher}; +use parity_tokio_ipc::ConnectionClient; use service::{GenericService, Service, ServiceTmpl, Subscriber}; +use std::path::PathBuf; +use std::time::Duration; use std::{ collections::HashMap, net::SocketAddr, @@ -268,6 +272,10 @@ pub async fn start_server(is_server: bool, _tray: bool) { log::info!("DISPLAY={:?}", std::env::var("DISPLAY")); log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY")); } + + #[cfg(target_os = "macos")] + sync_and_watch_config_dir().await; + if is_server { std::thread::spawn(move || { if let Err(err) = crate::ipc::start("") { @@ -317,3 +325,108 @@ pub async fn start_server(is_server: bool, _tray: bool) { } } } + +#[cfg(target_os = "macos")] +async fn sync_and_watch_config_dir() { + if crate::username() == "root" { + return; + } + + match crate::ipc::connect(1000, "_daemon").await { + Ok(mut conn) => { + match sync_config_to_user(&mut conn).await { + Err(e) => log::error!("sync config to user failed:{}", e), + _ => {} + } + + tokio::spawn(async move { + log::info!( + "watching config dir: {}", + Config::path("").to_str().unwrap().to_string() + ); + + let (tx, rx) = std::sync::mpsc::channel(); + let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); + watcher + .watch(Config::path("").as_path(), RecursiveMode::Recursive) + .unwrap(); + + loop { + let ev = rx.recv(); + match ev { + Ok(event) => match event { + notify::DebouncedEvent::Write(path) => { + log::info!( + "config file changed, call ipc_daemon to sync: {}", + path.to_str().unwrap().to_string() + ); + + match sync_config_to_root(&mut conn, path).await { + Err(e) => log::error!("sync config to root failed: {}", e), + _ => {} + } + } + x => { + log::debug!("another {:?}", x) + } + }, + Err(e) => println!("watch error: {:?}", e), + } + } + }); + } + Err(_) => { + log::info!("connect ipc_daemon failed, skip config sync"); + return; + } + } +} + +#[cfg(target_os = "macos")] +async fn sync_config_to_user(conn: &mut ConnectionTmpl) -> ResultType<()> { + allow_err!( + conn.send(&Data::SyncConfigToUserReq { + username: crate::username(), + to: Config::path("").to_str().unwrap().to_string(), + }) + .await + ); + + if let Some(data) = conn.next_timeout(2000).await? { + match data { + Data::SyncConfigToUserResp(success) => { + log::info!("copy and reload config dir success: {:?}", success); + } + _ => {} + }; + }; + + Ok(()) +} + +#[cfg(target_os = "macos")] +async fn sync_config_to_root( + conn: &mut ConnectionTmpl, + from: PathBuf, +) -> ResultType<()> { + allow_err!( + conn.send(&Data::SyncConfigToRootReq { + from: from.to_str().unwrap().to_string() + }) + .await + ); + + // todo: this code will block outer loop, resolve it later. + // if let Some(data) = conn.next_timeout(2000).await? { + // match data { + // Data::SyncConfigToRootResp(success) => { + // log::info!("copy config to root dir success: {:?}", success); + // } + // x => { + // log::info!("receive another {:?}", x) + // } + // }; + // }; + + Ok(()) +} diff --git a/src/ui.rs b/src/ui.rs index 7255c47b7..6502ec729 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -360,9 +360,14 @@ impl UI { if value.is_empty() { options.remove(&key); } else { - options.insert(key, value); + options.insert(key.clone(), value.clone()); } ipc::set_options(options.clone()).ok(); + + #[cfg(target_os = "macos")] + if &key == "stop-service" { + crate::platform::macos::launch_or_stop_daemon(value != "Y"); + } } fn install_path(&mut self) -> String { @@ -525,6 +530,13 @@ impl UI { return true; } + fn is_installed_daemon(&mut self, _prompt: bool) -> bool { + #[cfg(target_os = "macos")] + return crate::platform::macos::is_installed_daemon(_prompt); + #[cfg(not(target_os = "macos"))] + return true; + } + fn get_error(&mut self) -> String { #[cfg(target_os = "linux")] { @@ -668,6 +680,7 @@ impl sciter::EventHandler for UI { fn goto_install(); fn is_process_trusted(bool); fn is_can_screen_recording(bool); + fn is_installed_daemon(bool); fn get_error(); fn is_login_wayland(); fn fix_login_wayland(); diff --git a/src/ui/index.tis b/src/ui/index.tis index a69e5b014..d58497b3d 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -307,6 +307,7 @@ class App: Reactor.Component {handler.is_installed() && !software_update_url && handler.is_installed_lower_version() ? : ""} {is_can_screen_recording ? "": } {is_can_screen_recording && !handler.is_process_trusted(false) ? : ""} + {is_can_screen_recording && handler.is_process_trusted(false) && !handler.is_installed_daemon(false) ? : ""} {system_error ? : ""} {!system_error && handler.is_login_wayland() && !handler.current_is_wayland() ? : ""} {!system_error && handler.current_is_wayland() ? : ""} @@ -491,6 +492,21 @@ class CanScreenRecording: Reactor.Component { } } +class InstallDaemon: Reactor.Component { + function render() { + return
+
{translate('Configuration Permissions')}
+
{translate('install_daemon')}
+
{translate('Configure')}
+
; + } + + event click $(#install-daemon) { + handler.is_installed_daemon(true); + watch_trust(); + } +} + class FixWayland: Reactor.Component { function render() { return