2021-03-29 15:59:14 +08:00
|
|
|
// https://developer.apple.com/documentation/appkit/nscursor
|
|
|
|
// https://github.com/servo/core-foundation-rs
|
|
|
|
// https://github.com/rust-windowing/winit
|
|
|
|
|
|
|
|
use super::{CursorData, ResultType};
|
|
|
|
use cocoa::{
|
2023-01-09 13:59:33 +08:00
|
|
|
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*},
|
2021-03-29 15:59:14 +08:00
|
|
|
base::{id, nil, BOOL, NO, YES},
|
|
|
|
foundation::{NSDictionary, NSPoint, NSSize, NSString},
|
|
|
|
};
|
|
|
|
use core_foundation::{
|
|
|
|
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
|
|
|
dictionary::CFDictionaryRef,
|
|
|
|
string::CFStringRef,
|
|
|
|
};
|
|
|
|
use core_graphics::{
|
|
|
|
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
|
|
|
|
window::{kCGWindowName, kCGWindowOwnerPID},
|
|
|
|
};
|
2024-04-23 13:52:18 +08:00
|
|
|
use hbb_common::sysinfo::{Pid, Process, ProcessRefreshKind, System};
|
2024-03-27 03:36:33 +08:00
|
|
|
use hbb_common::{anyhow::anyhow, bail, log, message_proto::Resolution};
|
2022-01-14 03:17:36 +08:00
|
|
|
use include_dir::{include_dir, Dir};
|
2024-03-27 14:24:14 +08:00
|
|
|
use objc::{class, msg_send, sel, sel_impl};
|
2021-03-29 15:59:14 +08:00
|
|
|
use scrap::{libc::c_void, quartz::ffi::*};
|
2023-06-08 00:35:11 +08:00
|
|
|
use std::path::PathBuf;
|
2021-03-29 15:59:14 +08:00
|
|
|
|
2022-01-14 03:17:36 +08:00
|
|
|
static PRIVILEGES_SCRIPTS_DIR: Dir =
|
|
|
|
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
|
2021-03-29 15:59:14 +08:00
|
|
|
static mut LATEST_SEED: i32 = 0;
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
fn CGSCurrentCursorSeed() -> i32;
|
|
|
|
fn CGEventCreate(r: *const c_void) -> *const c_void;
|
|
|
|
fn CGEventGetLocation(e: *const c_void) -> CGPoint;
|
|
|
|
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
|
|
|
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
2023-01-06 12:20:26 +08:00
|
|
|
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
2023-07-10 17:31:04 +08:00
|
|
|
fn IsCanScreenRecording(_: BOOL) -> BOOL;
|
|
|
|
fn CanUseNewApiForScreenCaptureCheck() -> BOOL;
|
2023-02-25 20:55:37 +08:00
|
|
|
fn MacCheckAdminAuthorization() -> BOOL;
|
2023-02-12 16:09:41 +08:00
|
|
|
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
|
|
|
|
fn MacGetModes(
|
|
|
|
display: u32,
|
|
|
|
widths: *mut u32,
|
|
|
|
heights: *mut u32,
|
|
|
|
max: u32,
|
|
|
|
numModes: *mut u32,
|
|
|
|
) -> BOOL;
|
2024-02-16 12:29:32 +08:00
|
|
|
fn majorVersion() -> u32;
|
2023-02-12 16:09:41 +08:00
|
|
|
fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL;
|
|
|
|
fn MacSetMode(display: u32, width: u32, height: u32) -> BOOL;
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
|
2024-02-16 12:29:32 +08:00
|
|
|
pub fn major_version() -> u32 {
|
|
|
|
unsafe { majorVersion() }
|
|
|
|
}
|
|
|
|
|
2021-03-29 15:59:14 +08:00
|
|
|
pub fn is_process_trusted(prompt: bool) -> bool {
|
|
|
|
unsafe {
|
|
|
|
let value = if prompt { YES } else { NO };
|
|
|
|
let value: id = msg_send![class!(NSNumber), numberWithBool: value];
|
|
|
|
let options = NSDictionary::dictionaryWithObject_forKey_(
|
|
|
|
nil,
|
|
|
|
value,
|
|
|
|
kAXTrustedCheckOptionPrompt as _,
|
|
|
|
);
|
|
|
|
AXIsProcessTrustedWithOptions(options as _) == YES
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 12:20:26 +08:00
|
|
|
pub fn is_can_input_monitoring(prompt: bool) -> bool {
|
|
|
|
unsafe {
|
|
|
|
let value = if prompt { YES } else { NO };
|
|
|
|
InputMonitoringAuthStatus(value) == YES
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 15:59:14 +08:00
|
|
|
// macOS >= 10.15
|
|
|
|
// https://stackoverflow.com/questions/56597221/detecting-screen-recording-settings-on-macos-catalina/
|
|
|
|
// remove just one app from all the permissions: tccutil reset All com.carriez.rustdesk
|
|
|
|
pub fn is_can_screen_recording(prompt: bool) -> bool {
|
2023-07-10 17:31:04 +08:00
|
|
|
// we got some report that we show no permission even after set it, so we try to use new api for screen recording check
|
2023-07-22 14:16:41 +08:00
|
|
|
// the new api is only available on macOS >= 10.15, but on stackoverflow, some people said it works on >= 10.16 (crash on 10.15),
|
2023-07-10 17:31:04 +08:00
|
|
|
// but also some said it has bug on 10.16, so we just use it on 11.0.
|
|
|
|
unsafe {
|
|
|
|
if CanUseNewApiForScreenCaptureCheck() == YES {
|
|
|
|
return IsCanScreenRecording(if prompt { YES } else { NO }) == YES;
|
|
|
|
}
|
|
|
|
}
|
2021-03-29 15:59:14 +08:00
|
|
|
let mut can_record_screen: bool = false;
|
|
|
|
unsafe {
|
|
|
|
let our_pid: i32 = std::process::id() as _;
|
|
|
|
let our_pid: id = msg_send![class!(NSNumber), numberWithInteger: our_pid];
|
|
|
|
let window_list =
|
|
|
|
CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
|
|
|
let n = CFArrayGetCount(window_list);
|
|
|
|
let dock = NSString::alloc(nil).init_str("Dock");
|
|
|
|
for i in 0..n {
|
|
|
|
let w: id = CFArrayGetValueAtIndex(window_list, i) as _;
|
|
|
|
let name: id = msg_send![w, valueForKey: kCGWindowName as id];
|
|
|
|
if name.is_null() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let pid: id = msg_send![w, valueForKey: kCGWindowOwnerPID as id];
|
|
|
|
let is_me: BOOL = msg_send![pid, isEqual: our_pid];
|
|
|
|
if is_me == YES {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let pid: i32 = msg_send![pid, intValue];
|
|
|
|
let p: id = msg_send![
|
|
|
|
class!(NSRunningApplication),
|
|
|
|
runningApplicationWithProcessIdentifier: pid
|
|
|
|
];
|
|
|
|
if p.is_null() {
|
|
|
|
// ignore processes we don't have access to, such as WindowServer, which manages the windows named "Menubar" and "Backstop Menubar"
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let url: id = msg_send![p, executableURL];
|
|
|
|
let exe_name: id = msg_send![url, lastPathComponent];
|
|
|
|
if exe_name.is_null() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let is_dock: BOOL = msg_send![exe_name, isEqual: dock];
|
|
|
|
if is_dock == YES {
|
|
|
|
// ignore the Dock, which provides the desktop picture
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
can_record_screen = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !can_record_screen && prompt {
|
|
|
|
use scrap::{Capturer, Display};
|
|
|
|
if let Ok(d) = Display::primary() {
|
2023-10-27 15:44:07 +08:00
|
|
|
Capturer::new(d).ok();
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
can_record_screen
|
|
|
|
}
|
|
|
|
|
2023-07-27 16:49:19 +08:00
|
|
|
pub fn install_service() -> bool {
|
|
|
|
is_installed_daemon(false)
|
|
|
|
}
|
|
|
|
|
2022-01-13 19:23:41 +08:00
|
|
|
pub fn is_installed_daemon(prompt: bool) -> bool {
|
2022-01-17 12:05:06 +08:00
|
|
|
let daemon = format!("{}_service.plist", crate::get_full_name());
|
|
|
|
let agent = format!("{}_server.plist", crate::get_full_name());
|
2022-04-26 11:19:45 +08:00
|
|
|
let agent_plist_file = format!("/Library/LaunchAgents/{}", agent);
|
2022-01-13 19:23:41 +08:00
|
|
|
if !prompt {
|
2022-01-17 12:05:06 +08:00
|
|
|
if !std::path::Path::new(&format!("/Library/LaunchDaemons/{}", daemon)).exists() {
|
2022-01-13 19:23:41 +08:00
|
|
|
return false;
|
|
|
|
}
|
2022-04-26 11:19:45 +08:00
|
|
|
if !std::path::Path::new(&agent_plist_file).exists() {
|
2022-01-13 19:23:41 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-07-22 14:16:41 +08:00
|
|
|
let Some(install_script) = PRIVILEGES_SCRIPTS_DIR.get_file("install.scpt") else {
|
|
|
|
return false;
|
|
|
|
};
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(install_script_body) = install_script.contents_utf8().map(correct_app_name) else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2022-01-14 03:17:36 +08:00
|
|
|
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(daemon_plist) = PRIVILEGES_SCRIPTS_DIR.get_file("daemon.plist") else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(daemon_plist_body) = daemon_plist.contents_utf8().map(correct_app_name) else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2022-01-14 03:17:36 +08:00
|
|
|
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(agent_plist) = PRIVILEGES_SCRIPTS_DIR.get_file("agent.plist") else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(agent_plist_body) = agent_plist.contents_utf8().map(correct_app_name) else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2022-01-14 03:17:36 +08:00
|
|
|
|
2022-04-26 11:19:45 +08:00
|
|
|
std::thread::spawn(move || {
|
|
|
|
match std::process::Command::new("osascript")
|
|
|
|
.arg("-e")
|
|
|
|
.arg(install_script_body)
|
|
|
|
.arg(daemon_plist_body)
|
|
|
|
.arg(agent_plist_body)
|
|
|
|
.arg(&get_active_username())
|
2022-04-28 03:25:39 +08:00
|
|
|
.status()
|
2022-04-26 11:19:45 +08:00
|
|
|
{
|
|
|
|
Err(e) => {
|
|
|
|
log::error!("run osascript failed: {}", e);
|
|
|
|
}
|
|
|
|
_ => {
|
2022-04-28 04:22:46 +08:00
|
|
|
let installed = std::path::Path::new(&agent_plist_file).exists();
|
|
|
|
log::info!("Agent file {} installed: {}", agent_plist_file, installed);
|
|
|
|
if installed {
|
|
|
|
log::info!("launch server");
|
|
|
|
std::process::Command::new("launchctl")
|
|
|
|
.args(&["load", "-w", &agent_plist_file])
|
|
|
|
.status()
|
2022-04-26 11:19:45 +08:00
|
|
|
.ok();
|
2022-04-29 16:21:18 +08:00
|
|
|
std::process::Command::new("sh")
|
|
|
|
.arg("-c")
|
|
|
|
.arg(&format!(
|
|
|
|
"sleep 0.5; open -n /Applications/{}.app",
|
|
|
|
crate::get_app_name(),
|
|
|
|
))
|
|
|
|
.spawn()
|
|
|
|
.ok();
|
2022-04-28 12:10:14 +08:00
|
|
|
quit_gui();
|
2022-04-26 11:19:45 +08:00
|
|
|
}
|
|
|
|
}
|
2022-01-14 18:28:39 +08:00
|
|
|
}
|
2022-04-26 11:19:45 +08:00
|
|
|
});
|
|
|
|
false
|
2022-01-14 03:17:36 +08:00
|
|
|
}
|
|
|
|
|
2024-02-25 13:29:06 +08:00
|
|
|
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());
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
2024-03-27 00:38:13 +08:00
|
|
|
pub fn uninstall_service(show_new_window: bool, sync: bool) -> bool {
|
2022-01-17 12:05:06 +08:00
|
|
|
// to-do: do together with win/linux about refactory start/stop service
|
2022-04-26 11:19:45 +08:00
|
|
|
if !is_installed_daemon(false) {
|
|
|
|
return false;
|
2022-01-14 03:17:36 +08:00
|
|
|
}
|
|
|
|
|
2023-07-22 14:16:41 +08:00
|
|
|
let Some(script_file) = PRIVILEGES_SCRIPTS_DIR.get_file("uninstall.scpt") else {
|
|
|
|
return false;
|
|
|
|
};
|
2024-02-25 13:29:06 +08:00
|
|
|
let Some(script_body) = script_file.contents_utf8().map(correct_app_name) else {
|
2023-07-22 14:16:41 +08:00
|
|
|
return false;
|
|
|
|
};
|
2022-01-13 19:23:41 +08:00
|
|
|
|
2024-03-27 00:38:13 +08:00
|
|
|
let func = move || {
|
2022-04-26 11:19:45 +08:00
|
|
|
match std::process::Command::new("osascript")
|
|
|
|
.arg("-e")
|
|
|
|
.arg(script_body)
|
2022-04-28 03:25:39 +08:00
|
|
|
.status()
|
2022-04-26 11:19:45 +08:00
|
|
|
{
|
|
|
|
Err(e) => {
|
|
|
|
log::error!("run osascript failed: {}", e);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let agent = format!("{}_server.plist", crate::get_full_name());
|
|
|
|
let agent_plist_file = format!("/Library/LaunchAgents/{}", agent);
|
2022-04-28 04:22:46 +08:00
|
|
|
let uninstalled = !std::path::Path::new(&agent_plist_file).exists();
|
|
|
|
log::info!(
|
|
|
|
"Agent file {} uninstalled: {}",
|
|
|
|
agent_plist_file,
|
|
|
|
uninstalled
|
|
|
|
);
|
|
|
|
if uninstalled {
|
2023-09-04 16:22:56 +08:00
|
|
|
if !show_new_window {
|
|
|
|
let _ = crate::ipc::close_all_instances();
|
|
|
|
// leave ipc a little time
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(300));
|
|
|
|
}
|
2022-04-26 11:19:45 +08:00
|
|
|
crate::ipc::set_option("stop-service", "Y");
|
2022-04-28 04:22:46 +08:00
|
|
|
std::process::Command::new("launchctl")
|
|
|
|
.args(&["remove", &format!("{}_server", crate::get_full_name())])
|
|
|
|
.status()
|
|
|
|
.ok();
|
2023-02-23 20:01:50 +08:00
|
|
|
if show_new_window {
|
|
|
|
std::process::Command::new("sh")
|
|
|
|
.arg("-c")
|
|
|
|
.arg(&format!(
|
|
|
|
"sleep 0.5; open /Applications/{}.app",
|
|
|
|
crate::get_app_name(),
|
|
|
|
))
|
|
|
|
.spawn()
|
|
|
|
.ok();
|
|
|
|
}
|
2022-04-28 12:10:14 +08:00
|
|
|
quit_gui();
|
2022-04-26 11:19:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-27 00:38:13 +08:00
|
|
|
};
|
|
|
|
if sync {
|
|
|
|
func();
|
|
|
|
} else {
|
|
|
|
std::thread::spawn(func);
|
|
|
|
}
|
2022-04-26 11:19:45 +08:00
|
|
|
true
|
2022-01-13 19:23:41 +08:00
|
|
|
}
|
|
|
|
|
2021-03-29 15:59:14 +08:00
|
|
|
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
|
|
|
unsafe {
|
|
|
|
let e = CGEventCreate(0 as _);
|
|
|
|
let point = CGEventGetLocation(e);
|
|
|
|
CFRelease(e);
|
|
|
|
Some((point.x as _, point.y as _))
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
let mut pt: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] };
|
|
|
|
let screen: id = unsafe { msg_send![class!(NSScreen), currentScreenForMouseLocation] };
|
|
|
|
let frame: NSRect = unsafe { msg_send![screen, frame] };
|
|
|
|
pt.x -= frame.origin.x;
|
|
|
|
pt.y -= frame.origin.y;
|
|
|
|
Some((pt.x as _, pt.y as _))
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_cursor() -> ResultType<Option<u64>> {
|
|
|
|
unsafe {
|
|
|
|
let seed = CGSCurrentCursorSeed();
|
|
|
|
if seed == LATEST_SEED {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
LATEST_SEED = seed;
|
|
|
|
}
|
|
|
|
let c = get_cursor_id()?;
|
|
|
|
Ok(Some(c.1))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reset_input_cache() {
|
|
|
|
unsafe {
|
|
|
|
LATEST_SEED = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_cursor_id() -> ResultType<(id, u64)> {
|
|
|
|
unsafe {
|
|
|
|
let c: id = msg_send![class!(NSCursor), currentSystemCursor];
|
|
|
|
if c == nil {
|
|
|
|
bail!("Failed to call [NSCursor currentSystemCursor]");
|
|
|
|
}
|
|
|
|
let hotspot: NSPoint = msg_send![c, hotSpot];
|
|
|
|
let img: id = msg_send![c, image];
|
|
|
|
if img == nil {
|
|
|
|
bail!("Failed to call [NSCursor image]");
|
|
|
|
}
|
|
|
|
let size: NSSize = msg_send![img, size];
|
|
|
|
let tif: id = msg_send![img, TIFFRepresentation];
|
|
|
|
if tif == nil {
|
|
|
|
bail!("Failed to call [NSImage TIFFRepresentation]");
|
|
|
|
}
|
|
|
|
let rep: id = msg_send![class!(NSBitmapImageRep), imageRepWithData: tif];
|
|
|
|
if rep == nil {
|
|
|
|
bail!("Failed to call [NSBitmapImageRep imageRepWithData]");
|
|
|
|
}
|
|
|
|
let rep_size: NSSize = msg_send![rep, size];
|
|
|
|
let mut hcursor =
|
|
|
|
size.width + size.height + hotspot.x + hotspot.y + rep_size.width + rep_size.height;
|
|
|
|
let x = (rep_size.width * hotspot.x / size.width) as usize;
|
|
|
|
let y = (rep_size.height * hotspot.y / size.height) as usize;
|
|
|
|
for i in 0..2 {
|
|
|
|
let mut x2 = x + i;
|
|
|
|
if x2 >= rep_size.width as usize {
|
|
|
|
x2 = rep_size.width as usize - 1;
|
|
|
|
}
|
|
|
|
let mut y2 = y + i;
|
|
|
|
if y2 >= rep_size.height as usize {
|
|
|
|
y2 = rep_size.height as usize - 1;
|
|
|
|
}
|
|
|
|
let color: id = msg_send![rep, colorAtX:x2 y:y2];
|
|
|
|
if color != nil {
|
|
|
|
let r: f64 = msg_send![color, redComponent];
|
|
|
|
let g: f64 = msg_send![color, greenComponent];
|
|
|
|
let b: f64 = msg_send![color, blueComponent];
|
|
|
|
let a: f64 = msg_send![color, alphaComponent];
|
|
|
|
hcursor += (r + g + b + a) * (255 << i) as f64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok((c, hcursor as _))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/stweil/OSXvnc/blob/master/OSXvnc-server/mousecursor.c
|
|
|
|
pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|
|
|
|
unsafe {
|
|
|
|
let (c, hcursor2) = get_cursor_id()?;
|
|
|
|
if hcursor != hcursor2 {
|
|
|
|
bail!("cursor changed");
|
|
|
|
}
|
|
|
|
let hotspot: NSPoint = msg_send![c, hotSpot];
|
|
|
|
let img: id = msg_send![c, image];
|
|
|
|
let size: NSSize = msg_send![img, size];
|
|
|
|
let reps: id = msg_send![img, representations];
|
|
|
|
if reps == nil {
|
|
|
|
bail!("Failed to call [NSImage representations]");
|
|
|
|
}
|
|
|
|
let nreps: usize = msg_send![reps, count];
|
|
|
|
if nreps == 0 {
|
|
|
|
bail!("Get empty [NSImage representations]");
|
|
|
|
}
|
|
|
|
let rep: id = msg_send![reps, objectAtIndex: 0];
|
|
|
|
/*
|
|
|
|
let n: id = msg_send![class!(NSNumber), numberWithFloat:1.0];
|
|
|
|
let props: id = msg_send![class!(NSDictionary), dictionaryWithObject:n forKey:NSString::alloc(nil).init_str("NSImageCompressionFactor")];
|
|
|
|
let image_data: id = msg_send![rep, representationUsingType:2 properties:props];
|
|
|
|
let () = msg_send![image_data, writeToFile:NSString::alloc(nil).init_str("cursor.jpg") atomically:0];
|
|
|
|
*/
|
|
|
|
let mut colors: Vec<u8> = Vec::new();
|
|
|
|
colors.reserve((size.height * size.width) as usize * 4);
|
2023-01-09 15:39:49 +08:00
|
|
|
// TIFF is rgb colorspace, no need to convert
|
2021-03-29 15:59:14 +08:00
|
|
|
// let cs: id = msg_send![class!(NSColorSpace), sRGBColorSpace];
|
|
|
|
for y in 0..(size.height as _) {
|
|
|
|
for x in 0..(size.width as _) {
|
2023-06-10 05:52:42 +08:00
|
|
|
let color: id = msg_send![rep, colorAtX:x as cocoa::foundation::NSInteger y:y as cocoa::foundation::NSInteger];
|
2021-03-29 15:59:14 +08:00
|
|
|
// let color: id = msg_send![color, colorUsingColorSpace: cs];
|
|
|
|
if color == nil {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let r: f64 = msg_send![color, redComponent];
|
|
|
|
let g: f64 = msg_send![color, greenComponent];
|
|
|
|
let b: f64 = msg_send![color, blueComponent];
|
|
|
|
let a: f64 = msg_send![color, alphaComponent];
|
|
|
|
colors.push((r * 255.) as _);
|
|
|
|
colors.push((g * 255.) as _);
|
|
|
|
colors.push((b * 255.) as _);
|
|
|
|
colors.push((a * 255.) as _);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(CursorData {
|
|
|
|
id: hcursor,
|
2022-07-17 00:59:56 +08:00
|
|
|
colors: colors.into(),
|
2021-03-29 15:59:14 +08:00
|
|
|
hotx: hotspot.x as _,
|
|
|
|
hoty: hotspot.y as _,
|
|
|
|
width: size.width as _,
|
|
|
|
height: size.height as _,
|
|
|
|
..Default::default()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_active_user(t: &str) -> String {
|
|
|
|
if let Ok(output) = std::process::Command::new("ls")
|
|
|
|
.args(vec![t, "/dev/console"])
|
|
|
|
.output()
|
|
|
|
{
|
|
|
|
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
|
|
|
if let Some(n) = line.split_whitespace().nth(2) {
|
|
|
|
return n.to_owned();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"".to_owned()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_active_username() -> String {
|
|
|
|
get_active_user("-l")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_active_userid() -> String {
|
|
|
|
get_active_user("-n")
|
|
|
|
}
|
|
|
|
|
2022-10-12 16:06:15 +08:00
|
|
|
pub fn get_active_user_home() -> Option<PathBuf> {
|
|
|
|
let username = get_active_username();
|
|
|
|
if !username.is_empty() {
|
|
|
|
let home = PathBuf::from(format!("/Users/{}", username));
|
|
|
|
if home.exists() {
|
|
|
|
return Some(home);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-03-29 15:59:14 +08:00
|
|
|
pub fn is_prelogin() -> bool {
|
|
|
|
get_active_userid() == "0"
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_root() -> bool {
|
|
|
|
crate::username() == "root"
|
|
|
|
}
|
|
|
|
|
2022-11-27 12:31:53 +08:00
|
|
|
pub fn run_as_user(arg: Vec<&str>) -> ResultType<Option<std::process::Child>> {
|
2021-03-29 15:59:14 +08:00
|
|
|
let uid = get_active_userid();
|
|
|
|
let cmd = std::env::current_exe()?;
|
2022-11-27 12:31:53 +08:00
|
|
|
let mut args = vec!["asuser", &uid, cmd.to_str().unwrap_or("")];
|
|
|
|
args.append(&mut arg.clone());
|
|
|
|
let task = std::process::Command::new("launchctl").args(args).spawn()?;
|
2021-03-29 15:59:14 +08:00
|
|
|
Ok(Some(task))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lock_screen() {
|
|
|
|
std::process::Command::new(
|
|
|
|
"/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession",
|
|
|
|
)
|
|
|
|
.arg("-suspend")
|
|
|
|
.output()
|
|
|
|
.ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_os_service() {
|
2024-03-26 22:31:58 +08:00
|
|
|
crate::platform::macos::hide_dock();
|
|
|
|
log::info!("Username: {}", crate::username());
|
2024-04-22 21:31:21 +08:00
|
|
|
let mut sys = System::new();
|
|
|
|
let path =
|
|
|
|
std::fs::canonicalize(std::env::current_exe().unwrap_or_default()).unwrap_or_default();
|
2024-04-23 13:52:18 +08:00
|
|
|
let mut server = get_server_start_time(&mut sys, &path);
|
2024-04-22 21:31:21 +08:00
|
|
|
let my_start_time = sys
|
|
|
|
.process((std::process::id() as usize).into())
|
|
|
|
.map(|p| p.start_time())
|
|
|
|
.unwrap_or_default() as i64;
|
2024-04-23 13:52:18 +08:00
|
|
|
log::info!("Startime: {my_start_time} vs {:?}", server);
|
2022-04-26 11:19:45 +08:00
|
|
|
|
|
|
|
std::thread::spawn(move || loop {
|
2024-04-23 13:52:18 +08:00
|
|
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
|
|
if server.is_none() {
|
|
|
|
server = get_server_start_time(&mut sys, &path);
|
|
|
|
}
|
|
|
|
if let Some((start_time, pid)) = server {
|
2024-04-22 21:31:21 +08:00
|
|
|
if my_start_time <= start_time {
|
2024-03-27 03:02:36 +08:00
|
|
|
// I tried add delegate (using tao and with its main loop0, but it works in normal mode, but not work as daemon
|
|
|
|
log::info!(
|
2024-04-22 21:31:21 +08:00
|
|
|
"Agent start later, {my_start_time} vs {start_time}, will restart --service to make delegate work",
|
2024-03-27 03:02:36 +08:00
|
|
|
);
|
2022-04-28 03:25:39 +08:00
|
|
|
std::process::exit(0);
|
2022-04-26 11:19:45 +08:00
|
|
|
}
|
2024-04-23 13:52:18 +08:00
|
|
|
// only refresh this pid and check if valid, no need to refresh all processes since refreshing all is expensive, about 10ms on my machine
|
2024-04-23 20:25:44 +08:00
|
|
|
if !sys.refresh_process_specifics(pid, ProcessRefreshKind::new()) {
|
2024-04-23 13:52:18 +08:00
|
|
|
server = None;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let Some(p) = sys.process(pid.into()) {
|
|
|
|
if let Some(p) = get_server_start_time_of(p, &path) {
|
|
|
|
server = Some((p, pid));
|
|
|
|
} else {
|
|
|
|
server = None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
server = None;
|
|
|
|
}
|
2022-04-26 11:19:45 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-17 12:05:06 +08:00
|
|
|
if let Err(err) = crate::ipc::start("_service") {
|
|
|
|
log::error!("Failed to start ipc_service: {}", err);
|
|
|
|
}
|
|
|
|
|
2022-04-26 14:09:47 +08:00
|
|
|
/* // mouse/keyboard works in prelogin now with launchctl asuser.
|
|
|
|
// below can avoid multi-users logged in problem, but having its own below problem.
|
|
|
|
// Not find a good way to start --cm without root privilege (affect file transfer).
|
|
|
|
// one way is to start with `launchctl asuser <uid> open -n -a /Applications/RustDesk.app/ --args --cm`,
|
|
|
|
// this way --cm is started with the user privilege, but we will have problem to start another RustDesk.app
|
|
|
|
// with open in explorer.
|
2022-01-17 12:05:06 +08:00
|
|
|
use std::sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
|
|
|
Arc,
|
|
|
|
};
|
|
|
|
let running = Arc::new(AtomicBool::new(true));
|
|
|
|
let r = running.clone();
|
|
|
|
let mut uid = "".to_owned();
|
|
|
|
let mut server: Option<std::process::Child> = None;
|
|
|
|
if let Err(err) = ctrlc::set_handler(move || {
|
|
|
|
r.store(false, Ordering::SeqCst);
|
|
|
|
}) {
|
|
|
|
println!("Failed to set Ctrl-C handler: {}", err);
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
2022-01-17 12:05:06 +08:00
|
|
|
while running.load(Ordering::SeqCst) {
|
|
|
|
let tmp = get_active_userid();
|
|
|
|
let mut start_new = false;
|
|
|
|
if tmp != uid && !tmp.is_empty() {
|
|
|
|
uid = tmp;
|
|
|
|
log::info!("active uid: {}", uid);
|
|
|
|
if let Some(ps) = server.as_mut() {
|
|
|
|
hbb_common::allow_err!(ps.kill());
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
}
|
2022-01-17 12:05:06 +08:00
|
|
|
if let Some(ps) = server.as_mut() {
|
|
|
|
match ps.try_wait() {
|
|
|
|
Ok(Some(_)) => {
|
|
|
|
server = None;
|
|
|
|
start_new = true;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
start_new = true;
|
|
|
|
}
|
|
|
|
if start_new {
|
|
|
|
match run_as_user("--server") {
|
|
|
|
Ok(Some(ps)) => server = Some(ps),
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("Failed to start server: {}", err);
|
|
|
|
}
|
2022-12-26 01:21:13 +08:00
|
|
|
_ => { /*no happen*/ }
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
}
|
2022-01-17 12:05:06 +08:00
|
|
|
std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL));
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
2022-01-17 12:05:06 +08:00
|
|
|
|
|
|
|
if let Some(ps) = server.take().as_mut() {
|
|
|
|
hbb_common::allow_err!(ps.kill());
|
|
|
|
}
|
|
|
|
log::info!("Exit");
|
|
|
|
*/
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
|
2022-01-15 16:31:21 +08:00
|
|
|
pub fn toggle_blank_screen(_v: bool) {
|
2021-03-29 15:59:14 +08:00
|
|
|
// https://unix.stackexchange.com/questions/17115/disable-keyboard-mouse-temporarily
|
|
|
|
}
|
|
|
|
|
2023-09-17 10:53:04 +08:00
|
|
|
pub fn block_input(_v: bool) -> (bool, String) {
|
|
|
|
(true, "".to_owned())
|
2021-03-29 15:59:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_installed() -> bool {
|
2022-01-17 12:05:06 +08:00
|
|
|
if let Ok(p) = std::env::current_exe() {
|
2022-04-28 03:25:39 +08:00
|
|
|
return p
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or_default()
|
2022-04-28 04:22:46 +08:00
|
|
|
.starts_with(&format!("/Applications/{}.app", crate::get_app_name()));
|
2022-01-13 15:26:57 +08:00
|
|
|
}
|
2022-01-17 12:05:06 +08:00
|
|
|
false
|
2022-01-13 15:26:57 +08:00
|
|
|
}
|
2022-04-28 12:10:14 +08:00
|
|
|
|
2022-05-10 01:08:21 +08:00
|
|
|
pub fn quit_gui() {
|
2022-04-28 12:10:14 +08:00
|
|
|
unsafe {
|
|
|
|
let () = msg_send!(NSApp(), terminate: nil);
|
|
|
|
};
|
|
|
|
}
|
2022-11-02 11:32:30 +08:00
|
|
|
|
|
|
|
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 _
|
|
|
|
}
|
2023-01-09 13:59:33 +08:00
|
|
|
|
|
|
|
pub fn hide_dock() {
|
|
|
|
unsafe {
|
|
|
|
NSApp().setActivationPolicy_(NSApplicationActivationPolicyAccessory);
|
|
|
|
}
|
|
|
|
}
|
2023-01-20 01:25:15 +08:00
|
|
|
|
2024-04-22 21:31:21 +08:00
|
|
|
#[inline]
|
2024-04-23 13:52:18 +08:00
|
|
|
fn get_server_start_time_of(p: &Process, path: &PathBuf) -> Option<i64> {
|
|
|
|
let cmd = p.cmd();
|
|
|
|
if cmd.len() <= 1 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if &cmd[1] != "--server" {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let Ok(cur) = std::fs::canonicalize(p.exe()) else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
if &cur != path {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(p.start_time() as _)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_server_start_time(sys: &mut System, path: &PathBuf) -> Option<(i64, Pid)> {
|
2024-04-22 21:31:21 +08:00
|
|
|
sys.refresh_processes_specifics(ProcessRefreshKind::new());
|
2024-03-26 22:31:58 +08:00
|
|
|
for (_, p) in sys.processes() {
|
2024-04-23 13:52:18 +08:00
|
|
|
if let Some(t) = get_server_start_time_of(p, path) {
|
|
|
|
return Some((t, p.pid() as _));
|
2024-03-26 22:31:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
2023-02-09 18:11:32 +08:00
|
|
|
}
|
|
|
|
|
2023-02-09 21:28:42 +08:00
|
|
|
pub fn handle_application_should_open_untitled_file() {
|
2023-02-09 18:11:32 +08:00
|
|
|
hbb_common::log::debug!("icon clicked on finder");
|
|
|
|
let x = std::env::args().nth(1).unwrap_or_default();
|
2023-02-09 21:28:42 +08:00
|
|
|
if x == "--server" || x == "--cm" || x == "--tray" {
|
2024-03-26 22:31:58 +08:00
|
|
|
std::thread::spawn(move || crate::handle_url_scheme("".to_lowercase()));
|
2023-02-09 18:11:32 +08:00
|
|
|
}
|
2023-01-20 01:25:15 +08:00
|
|
|
}
|
2023-02-12 16:09:41 +08:00
|
|
|
|
|
|
|
pub fn resolutions(name: &str) -> Vec<Resolution> {
|
|
|
|
let mut v = vec![];
|
|
|
|
if let Ok(display) = name.parse::<u32>() {
|
|
|
|
let mut num = 0;
|
|
|
|
unsafe {
|
|
|
|
if YES == MacGetModeNum(display, &mut num) {
|
|
|
|
let (mut widths, mut heights) = (vec![0; num as _], vec![0; num as _]);
|
2023-02-25 22:39:12 +08:00
|
|
|
let mut real_num = 0;
|
2023-02-12 16:09:41 +08:00
|
|
|
if YES
|
|
|
|
== MacGetModes(
|
|
|
|
display,
|
|
|
|
widths.as_mut_ptr(),
|
|
|
|
heights.as_mut_ptr(),
|
|
|
|
num,
|
2023-02-25 22:39:12 +08:00
|
|
|
&mut real_num,
|
2023-02-12 16:09:41 +08:00
|
|
|
)
|
|
|
|
{
|
2023-02-25 22:39:12 +08:00
|
|
|
if real_num <= num {
|
|
|
|
for i in 0..real_num {
|
2023-02-12 16:09:41 +08:00
|
|
|
let resolution = Resolution {
|
|
|
|
width: widths[i as usize] as _,
|
|
|
|
height: heights[i as usize] as _,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
if !v.contains(&resolution) {
|
|
|
|
v.push(resolution);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
|
|
|
|
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
|
|
|
|
unsafe {
|
|
|
|
let (mut width, mut height) = (0, 0);
|
|
|
|
if NO == MacGetMode(display, &mut width, &mut height) {
|
|
|
|
bail!("MacGetMode failed");
|
|
|
|
}
|
|
|
|
Ok(Resolution {
|
|
|
|
width: width as _,
|
|
|
|
height: height as _,
|
|
|
|
..Default::default()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 22:50:53 +08:00
|
|
|
pub fn change_resolution_directly(name: &str, width: usize, height: usize) -> ResultType<()> {
|
2023-02-12 16:09:41 +08:00
|
|
|
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
|
|
|
|
unsafe {
|
|
|
|
if NO == MacSetMode(display, width as _, height as _) {
|
|
|
|
bail!("MacSetMode failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-02-25 20:55:37 +08:00
|
|
|
|
|
|
|
pub fn check_super_user_permission() -> ResultType<bool> {
|
2023-03-10 23:41:01 +08:00
|
|
|
unsafe { Ok(MacCheckAdminAuthorization() == YES) }
|
2023-02-25 20:55:37 +08:00
|
|
|
}
|
2023-05-11 22:54:22 +08:00
|
|
|
|
2023-05-15 21:26:25 +08:00
|
|
|
pub fn elevate(args: Vec<&str>, prompt: &str) -> ResultType<bool> {
|
2023-05-11 22:54:22 +08:00
|
|
|
let cmd = std::env::current_exe()?;
|
|
|
|
match cmd.to_str() {
|
|
|
|
Some(cmd) => {
|
2023-05-15 21:26:25 +08:00
|
|
|
let mut cmd_with_args = cmd.to_string();
|
|
|
|
for arg in args {
|
|
|
|
cmd_with_args = format!("{} {}", cmd_with_args, arg);
|
2023-05-11 22:54:22 +08:00
|
|
|
}
|
2023-05-15 21:26:25 +08:00
|
|
|
let script = format!(
|
|
|
|
r#"do shell script "{}" with prompt "{}" with administrator privileges"#,
|
|
|
|
cmd_with_args, prompt
|
|
|
|
);
|
|
|
|
match std::process::Command::new("osascript")
|
|
|
|
.arg("-e")
|
|
|
|
.arg(script)
|
|
|
|
.arg(&get_active_username())
|
|
|
|
.status()
|
|
|
|
{
|
|
|
|
Err(e) => {
|
|
|
|
bail!("Failed to run osascript: {}", e);
|
2023-05-11 22:54:22 +08:00
|
|
|
}
|
2023-05-15 21:26:25 +08:00
|
|
|
Ok(status) => Ok(status.success() && status.code() == Some(0)),
|
2023-05-11 22:54:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
bail!("Failed to get current exe str");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-05 20:24:16 +08:00
|
|
|
|
|
|
|
pub struct WakeLock(Option<keepawake::AwakeHandle>);
|
|
|
|
|
|
|
|
impl WakeLock {
|
|
|
|
pub fn new(display: bool, idle: bool, sleep: bool) -> Self {
|
|
|
|
WakeLock(
|
|
|
|
keepawake::Builder::new()
|
|
|
|
.display(display)
|
|
|
|
.idle(idle)
|
|
|
|
.sleep(sleep)
|
|
|
|
.create()
|
|
|
|
.ok(),
|
|
|
|
)
|
|
|
|
}
|
2023-12-05 21:32:44 +08:00
|
|
|
|
|
|
|
pub fn set_display(&mut self, display: bool) -> ResultType<()> {
|
|
|
|
self.0
|
|
|
|
.as_mut()
|
|
|
|
.map(|h| h.set_display(display))
|
|
|
|
.ok_or(anyhow!("no AwakeHandle"))?
|
|
|
|
}
|
2023-06-05 20:24:16 +08:00
|
|
|
}
|