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.
This commit is contained in:
NimBold
2026-06-13 22:44:40 +03:30
parent d6fa8b6865
commit ae19f69e7b
4 changed files with 129 additions and 35 deletions
+85 -2
View File
@@ -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",
+2
View File
@@ -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"
+40 -31
View File
@@ -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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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::<String>)
.map_err(|e| format!("Failed to open Automation settings: {}", e))?;
return Ok(());
}
#[cfg(not(target_os = "macos"))]
+2 -2
View File
@@ -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());