From ae19f69e7bab96068238b8fdec1f6bf5cf47ffa6 Mon Sep 17 00:00:00 2001 From: NimBold Date: Sat, 13 Jun 2026 22:44:40 +0330 Subject: [PATCH] feat(core): phase 2 native modernization This commit removes the remaining osascript processes and implements native bindings using cocoa and objc crates for macOS dock badges and TCC automation prompts. It also migrates synchronous integration tests to tokio::process::Command. --- apps/desktop/src-tauri/Cargo.lock | 87 ++++++++++++++++++++++++- apps/desktop/src-tauri/Cargo.toml | 2 + apps/desktop/src-tauri/src/lib.rs | 71 +++++++++++--------- apps/desktop/src-tauri/src/scheduler.rs | 4 +- 4 files changed, 129 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index b7d7f20..5561741 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -316,6 +316,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -527,11 +533,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + [[package]] name = "combine" version = "4.6.7" @@ -607,6 +645,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics" version = "0.25.0" @@ -615,11 +666,22 @@ checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.0", "core-foundation 0.10.1", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -2383,6 +2445,15 @@ dependencies = [ "time", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "markup5ever" version = "0.38.0" @@ -2602,6 +2673,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc2" version = "0.6.4" @@ -4226,7 +4306,7 @@ dependencies = [ "bitflags 2.13.0", "block2", "core-foundation 0.10.1", - "core-graphics", + "core-graphics 0.25.0", "crossbeam-channel", "dbus", "dispatch2", @@ -4331,10 +4411,13 @@ name = "tauri-app" version = "0.7.3" dependencies = [ "axum", + "chrono", + "cocoa", "futures-util", "hmac", "keepawake", "keyring", + "objc", "open", "regex 1.12.4", "reqwest 0.12.28", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index f75adbc..0ed1590 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -46,3 +46,5 @@ tokio-tungstenite = "0.29.0" futures-util = { version = "0.3.32", features = ["sink"] } rusqlite = { version = "0.40.1", features = ["bundled"] } chrono = "0.4.38" +cocoa = "0.25" +objc = "0.2.7" diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 3a8d9dc..ee3820d 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -1,6 +1,5 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ use tauri::{Manager, Emitter}; -use std::process::Command; use tokio::process::Command as AsyncCommand; use std::process::Stdio; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -183,9 +182,10 @@ async fn test_ytdlp(app_handle: tauri::AppHandle) -> Result { let ytdlp_path = resource_dir.join("binaries").join("yt-dlp"); println!("Resolved yt-dlp path: {:?}", ytdlp_path); - let output = Command::new(&ytdlp_path) + let output = AsyncCommand::new(&ytdlp_path) .arg("--version") .output() + .await .map_err(|e| { println!("Failed to execute: {}", e); format!("Failed to execute yt-dlp: {}", e) @@ -210,9 +210,10 @@ async fn test_aria2c(app_handle: tauri::AppHandle) -> Result { let aria2c_path = resource_dir.join("binaries").join("aria2c"); println!("Resolved aria2c path: {:?}", aria2c_path); - let output = Command::new(&aria2c_path) + let output = AsyncCommand::new(&aria2c_path) .arg("--version") .output() + .await .map_err(|e| { println!("Failed to execute: {}", e); format!("Failed to execute aria2c: {}", e) @@ -240,9 +241,10 @@ async fn test_ffmpeg(app_handle: tauri::AppHandle) -> Result { let ffmpeg_path = resource_dir.join("binaries").join("ffmpeg"); println!("Resolved ffmpeg path: {:?}", ffmpeg_path); - let output = Command::new(&ffmpeg_path) + let output = AsyncCommand::new(&ffmpeg_path) .arg("-version") .output() + .await .map_err(|e| { println!("Failed to execute: {}", e); format!("Failed to execute ffmpeg: {}", e) @@ -270,9 +272,10 @@ async fn test_deno(app_handle: tauri::AppHandle) -> Result { let deno_path = resource_dir.join("binaries").join("deno"); println!("Resolved deno path: {:?}", deno_path); - let output = Command::new(&deno_path) + let output = AsyncCommand::new(&deno_path) .arg("--version") .output() + .await .map_err(|e| { println!("Failed to execute: {}", e); format!("Failed to execute deno: {}", e) @@ -859,12 +862,18 @@ async fn remove_download(state: tauri::State<'_, AppState>, id: String, filepath fn update_dock_badge(_app_handle: tauri::AppHandle, count: i32) { #[cfg(target_os = "macos")] { - let label = if count > 0 { count.to_string() } else { "".to_string() }; - let script = format!("tell application \"System Events\" to set the badge of application process \"Firelink\" to \"{}\"", label); - let _ = std::process::Command::new("osascript") - .arg("-e") - .arg(script) - .status(); + use cocoa::appkit::NSApp; + use cocoa::base::{nil, id}; + use cocoa::foundation::NSString; + use objc::{msg_send, sel, sel_impl}; + + unsafe { + let app = NSApp(); + let dock_tile: id = msg_send![app, dockTile]; + let label = if count > 0 { count.to_string() } else { "".to_string() }; + let ns_label = NSString::alloc(nil).init_str(&label); + let _: () = msg_send![dock_tile, setBadgeLabel: ns_label]; + } } } @@ -919,16 +928,21 @@ async fn set_global_speed_limit(state: tauri::State<'_, AppState>, limit: Option fn request_automation_permission() -> Result<(), String> { #[cfg(target_os = "macos")] { - let status = Command::new("osascript") - .arg("-e") - .arg("tell application \"Finder\" to get name") - .status() - .map_err(|error| error.to_string())?; - return if status.success() { - Ok(()) - } else { - Err("Automation permission was not granted".to_string()) - }; + use cocoa::foundation::NSString; + use cocoa::base::{nil, id}; + use objc::{msg_send, sel, sel_impl, class}; + + unsafe { + let script_str = NSString::alloc(nil).init_str("tell application \"Finder\" to get name"); + let ns_apple_script: id = msg_send![class!(NSAppleScript), alloc]; + let ns_apple_script: id = msg_send![ns_apple_script, initWithSource: script_str]; + let mut error_dict: id = nil; + let result: id = msg_send![ns_apple_script, executeAndReturnError: &mut error_dict]; + if result == nil { + return Err("Automation permission was not granted".to_string()); + } + } + return Ok(()); } #[cfg(not(target_os = "macos"))] @@ -936,18 +950,13 @@ fn request_automation_permission() -> Result<(), String> { } #[tauri::command] -fn open_automation_settings() -> Result<(), String> { +fn open_automation_settings(app_handle: tauri::AppHandle) -> Result<(), String> { #[cfg(target_os = "macos")] { - let status = Command::new("open") - .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") - .status() - .map_err(|error| error.to_string())?; - return if status.success() { - Ok(()) - } else { - Err("Failed to open Automation settings".to_string()) - }; + use tauri_plugin_opener::OpenerExt; + app_handle.opener().open_url("x-apple.systempreferences:com.apple.preference.security?Privacy_Automation", None::) + .map_err(|e| format!("Failed to open Automation settings: {}", e))?; + return Ok(()); } #[cfg(not(target_os = "macos"))] diff --git a/apps/desktop/src-tauri/src/scheduler.rs b/apps/desktop/src-tauri/src/scheduler.rs index 908232b..5cb9e38 100644 --- a/apps/desktop/src-tauri/src/scheduler.rs +++ b/apps/desktop/src-tauri/src/scheduler.rs @@ -50,8 +50,8 @@ pub fn spawn_scheduler(app_handle: tauri::AppHandle) { let date_key = now.format("%Y-%m-%d").to_string(); let trigger_key = format!("{}-{}", date_key, current_time); - let last_start_key = settings.get("schedulerLastStartKey").and_then(|v| v.as_str()).unwrap_or(""); - let last_stop_key = settings.get("schedulerLastStopKey").and_then(|v| v.as_str()).unwrap_or(""); + let last_start_key = settings.get("schedulerLastStartKey").and_then(|v| v.as_str()).unwrap_or("").to_string(); + let last_stop_key = settings.get("schedulerLastStopKey").and_then(|v| v.as_str()).unwrap_or("").to_string(); if scheduler.start_time == current_time && last_start_key != trigger_key { settings["schedulerLastStartKey"] = serde_json::json!(trigger_key.clone());