feat, update, win, macos (#11618)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-05-04 07:32:47 +08:00
committed by GitHub
parent 62276b4f4f
commit ca00706a38
72 changed files with 2128 additions and 69 deletions

View File

@@ -27,12 +27,18 @@ use include_dir::{include_dir, Dir};
use objc::rc::autoreleasepool;
use objc::{class, msg_send, sel, sel_impl};
use scrap::{libc::c_void, quartz::ffi::*};
use std::path::{Path, PathBuf};
use std::{
os::unix::process::CommandExt,
path::{Path, PathBuf},
process::{Command, Stdio},
};
static PRIVILEGES_SCRIPTS_DIR: Dir =
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
static mut LATEST_SEED: i32 = 0;
const UPDATE_TEMP_DIR: &str = "/tmp/.rustdeskupdate";
extern "C" {
fn CGSCurrentCursorSeed() -> i32;
fn CGEventCreate(r: *const c_void) -> *const c_void;
@@ -155,6 +161,9 @@ pub fn install_service() -> bool {
is_installed_daemon(false)
}
// Remember to check if `update_daemon_agent()` need to be changed if changing `is_installed_daemon()`.
// No need to merge the existing dup code, because the code in these two functions are too critical.
// New code should be written in a common function.
pub fn is_installed_daemon(prompt: bool) -> bool {
let daemon = format!("{}_service.plist", crate::get_full_name());
let agent = format!("{}_server.plist", crate::get_full_name());
@@ -218,6 +227,70 @@ pub fn is_installed_daemon(prompt: bool) -> bool {
false
}
fn update_daemon_agent(agent_plist_file: String, update_source_dir: String, sync: bool) {
let update_script_file = "update.scpt";
let Some(update_script) = PRIVILEGES_SCRIPTS_DIR.get_file(update_script_file) else {
return;
};
let Some(update_script_body) = update_script.contents_utf8().map(correct_app_name) else {
return;
};
let Some(daemon_plist) = PRIVILEGES_SCRIPTS_DIR.get_file("daemon.plist") else {
return;
};
let Some(daemon_plist_body) = daemon_plist.contents_utf8().map(correct_app_name) else {
return;
};
let Some(agent_plist) = PRIVILEGES_SCRIPTS_DIR.get_file("agent.plist") else {
return;
};
let Some(agent_plist_body) = agent_plist.contents_utf8().map(correct_app_name) else {
return;
};
let func = move || {
let mut binding = std::process::Command::new("osascript");
let mut cmd = binding
.arg("-e")
.arg(update_script_body)
.arg(daemon_plist_body)
.arg(agent_plist_body)
.arg(&get_active_username())
.arg(std::process::id().to_string())
.arg(update_source_dir);
match cmd.status() {
Err(e) => {
log::error!("run osascript failed: {}", e);
}
_ => {
let installed = std::path::Path::new(&agent_plist_file).exists();
log::info!("Agent file {} installed: {}", &agent_plist_file, installed);
if installed {
// Unload first, or load may not work if already loaded.
// We hope that the load operation can immediately trigger a start.
std::process::Command::new("launchctl")
.args(&["unload", "-w", &agent_plist_file])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
let status = std::process::Command::new("launchctl")
.args(&["load", "-w", &agent_plist_file])
.status();
log::info!("launch server, status: {:?}", &status);
}
}
}
};
if sync {
func();
} else {
std::thread::spawn(func);
}
}
fn correct_app_name(s: &str) -> String {
let s = s.replace("rustdesk", &crate::get_app_name().to_lowercase());
let s = s.replace("RustDesk", &crate::get_app_name());
@@ -634,6 +707,140 @@ pub fn quit_gui() {
};
}
pub fn update_me() -> ResultType<()> {
let is_installed_daemon = is_installed_daemon(false);
let option_stop_service = "stop-service";
let is_service_stopped = hbb_common::config::option2bool(
option_stop_service,
&crate::ui_interface::get_option(option_stop_service),
);
let cmd = std::env::current_exe()?;
// RustDesk.app/Contents/MacOS/RustDesk
let app_dir = cmd
.parent()
.and_then(|p| p.parent())
.and_then(|p| p.parent())
.map(|d| d.to_string_lossy().to_string());
let Some(app_dir) = app_dir else {
bail!("Unknown app directory of current exe file: {:?}", cmd);
};
if is_installed_daemon && !is_service_stopped {
let agent = format!("{}_server.plist", crate::get_full_name());
let agent_plist_file = format!("/Library/LaunchAgents/{}", agent);
std::process::Command::new("launchctl")
.args(&["unload", "-w", &agent_plist_file])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok();
update_daemon_agent(agent_plist_file, app_dir, true);
} else {
// `kill -9` may not work without "administrator privileges"
let update_body = format!(
r#"
do shell script "
pgrep -x 'RustDesk' | grep -v {} | xargs kill -9 && rm -rf /Applications/RustDesk.app && cp -R '{}' /Applications/ && chown -R {}:staff /Applications/RustDesk.app
" with prompt "RustDesk wants to update itself" with administrator privileges
"#,
std::process::id(),
app_dir,
get_active_username()
);
match Command::new("osascript")
.arg("-e")
.arg(update_body)
.status()
{
Ok(status) if !status.success() => {
log::error!("osascript execution failed with status: {}", status);
}
Err(e) => {
log::error!("run osascript failed: {}", e);
}
_ => {}
}
}
std::process::Command::new("open")
.arg("-n")
.arg(&format!("/Applications/{}.app", crate::get_app_name()))
.spawn()
.ok();
// leave open a little time
std::thread::sleep(std::time::Duration::from_millis(300));
Ok(())
}
pub fn update_to(file: &str) -> ResultType<()> {
extract_dmg(file, UPDATE_TEMP_DIR)?;
update_extracted(UPDATE_TEMP_DIR)?;
Ok(())
}
fn extract_dmg(dmg_path: &str, target_dir: &str) -> ResultType<()> {
let mount_point = "/Volumes/RustDeskUpdate";
let target_path = Path::new(target_dir);
if target_path.exists() {
std::fs::remove_dir_all(target_path)?;
}
std::fs::create_dir_all(target_path)?;
Command::new("hdiutil")
.args(&["attach", "-nobrowse", "-mountpoint", mount_point, dmg_path])
.status()?;
struct DmgGuard(&'static str);
impl Drop for DmgGuard {
fn drop(&mut self) {
let _ = Command::new("hdiutil")
.args(&["detach", self.0, "-force"])
.status();
}
}
let _guard = DmgGuard(mount_point);
let app_name = "RustDesk.app";
let src_path = format!("{}/{}", mount_point, app_name);
let dest_path = format!("{}/{}", target_dir, app_name);
let copy_status = Command::new("cp")
.args(&["-R", &src_path, &dest_path])
.status()?;
if !copy_status.success() {
bail!("Failed to copy application {:?}", copy_status);
}
if !Path::new(&dest_path).exists() {
bail!(
"Copy operation failed - destination not found at {}",
dest_path
);
}
Ok(())
}
fn update_extracted(target_dir: &str) -> ResultType<()> {
let exe_path = format!("{}/RustDesk.app/Contents/MacOS/RustDesk", target_dir);
let _child = unsafe {
Command::new(&exe_path)
.arg("--update")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.pre_exec(|| {
hbb_common::libc::setsid();
Ok(())
})
.spawn()?
};
Ok(())
}
pub fn get_double_click_time() -> u32 {
// to-do: https://github.com/servo/core-foundation-rs/blob/786895643140fa0ee4f913d7b4aeb0c4626b2085/cocoa/src/appkit.rs#L2823
500 as _