diff --git a/.gitignore b/.gitignore index 38abf0a402..a9094998d7 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ dist-ssr src-tauri/resources/libonnxruntime.so src-tauri/resources/libonnxruntime.dylib src-tauri/resources/onnxruntime.dll +src-tauri/gen/ +src-tauri/libs/ \ No newline at end of file diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 7a67c398a1..cbd005e81f 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -24,7 +24,7 @@ fn download_and_verify( println!( "cargo:warning=Downloading to temporary path: {:?}", - temp_path + temp_path ); let mut response = reqwest::blocking::get(url)?; @@ -46,7 +46,7 @@ fn download_and_verify( fs::remove_file(&temp_path)?; println!( "cargo:warning=Successfully downloaded and verified {:?}.", - dest_path + dest_path ); Ok(()) } @@ -65,6 +65,8 @@ fn main() { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let (download_filename, lib_name, expected_hash) = match (target_os.as_str(), target_arch.as_str()) { ("windows", "x86_64") => ( @@ -97,12 +99,23 @@ fn main() { "libonnxruntime.dylib", "2b885992d3d6fa4130d39ec84a80d7504ff52750027c547bb22c86165f19406a", ), + ("android", "aarch64") => ( + "libonnxruntime-android-arm64-v8a.so", + "libonnxruntime.so", + "999ecfdb5b5a13e4097487773b6d71ce8a075408a237daab072e8f5e817bd78e", + ), _ => panic!("Unsupported target: {}-{}", target_os, target_arch), }; - let resources_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("resources"); - fs::create_dir_all(&resources_dir).unwrap(); - let dest_path = resources_dir.join(lib_name); + + let dest_dir = if target_os == "android" { + manifest_dir.join("libs").join("arm64-v8a") + } else { + manifest_dir.join("resources") + }; + + fs::create_dir_all(&dest_dir).unwrap(); + let dest_path = dest_dir.join(lib_name); let mut is_valid = false; if dest_path.exists() { @@ -116,14 +129,14 @@ fn main() { Ok(false) => { println!( "cargo:warning=File {:?} exists but has incorrect hash. Deleting and re-downloading.", - dest_path + dest_path ); fs::remove_file(&dest_path).unwrap(); } Err(e) => { println!( "cargo:warning=Could not verify file {:?}: {}. Re-downloading.", - dest_path, e + dest_path, e ); } } @@ -132,10 +145,9 @@ fn main() { if !is_valid { println!( "cargo:warning=Downloading ONNX Runtime library for {}-{}...", - target_os, target_arch + target_os, target_arch ); - let base_url = - "https://huggingface.co/CyberTimon/RapidRAW-Models/resolve/main/onnxruntimes-v1.22.0/"; + let base_url = "https://huggingface.co/CyberTimon/RapidRAW-Models/resolve/main/onnxruntimes-v1.22.0/"; let download_url = format!("{}{}?download=true", base_url, download_filename); println!("cargo:warning=URL: {}", download_url); @@ -144,7 +156,25 @@ fn main() { } } + if target_os == "android" { + let jni_libs_dir = manifest_dir.join("gen/android/app/src/main/jniLibs/arm64-v8a"); + fs::create_dir_all(&jni_libs_dir).unwrap(); + fs::copy(&dest_path, jni_libs_dir.join(lib_name)).unwrap(); + + println!( + "cargo:rustc-env=ORT_LIB_LOCATION={}", + dest_dir.display() + ); + println!( + "cargo:rustc-env=ORT_STRATEGY=manual" + ); + println!( + "cargo:rustc-link-search=native={}", + dest_dir.display() + ); + } + println!("cargo:rerun-if-changed=build.rs"); tauri_build::build() -} +} \ No newline at end of file diff --git a/src-tauri/src/file_management.rs b/src-tauri/src/file_management.rs index 8dae129981..181a4bd807 100644 --- a/src-tauri/src/file_management.rs +++ b/src-tauri/src/file_management.rs @@ -24,6 +24,8 @@ use serde_json::Value; use tauri::{AppHandle, Emitter, Manager}; use uuid::Uuid; use walkdir::WalkDir; +#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] +use trash; use crate::AppState; use crate::calculate_geometry_hash; @@ -1587,14 +1589,22 @@ pub fn rename_folder(path: String, new_name: String) -> Result<(), String> { #[tauri::command] pub fn delete_folder(path: String) -> Result<(), String> { - if let Err(trash_error) = trash::delete(&path) { - log::warn!( - "Failed to move folder to trash: {}. Falling back to permanent delete.", - trash_error - ); + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] + { + if let Err(trash_error) = trash::delete(&path) { + log::warn!( + "Failed to move folder to trash: {}. Falling back to permanent delete.", + trash_error + ); + fs::remove_dir_all(&path).map_err(|e| e.to_string()) + } else { + Ok(()) + } + } + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + { fs::remove_dir_all(&path).map_err(|e| e.to_string()) - } else { - Ok(()) } } @@ -1791,6 +1801,7 @@ pub fn move_files(source_paths: Vec, destination_folder: String) -> Resu all_files_to_trash.extend(files_to_move); } + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] if !all_files_to_trash.is_empty() && let Err(trash_error) = trash::delete_all(&all_files_to_trash) { @@ -1807,6 +1818,14 @@ pub fn move_files(source_paths: Vec, destination_folder: String) -> Resu } } + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + for path in all_files_to_trash { + if path.is_file() { + fs::remove_file(&path) + .map_err(|e| format!("Failed to delete source file {}: {}", path.display(), e))?; + } + } + Ok(()) } @@ -2658,6 +2677,7 @@ pub fn delete_files_from_disk(paths: Vec) -> Result<(), String> { } let final_paths_to_delete: Vec = files_to_trash.into_iter().collect(); + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] if let Err(trash_error) = trash::delete_all(&final_paths_to_delete) { log::warn!( "Failed to move files to trash: {}. Falling back to permanent delete.", @@ -2673,6 +2693,18 @@ pub fn delete_files_from_disk(paths: Vec) -> Result<(), String> { } } } + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + for path in final_paths_to_delete { + if path.is_file() { + fs::remove_file(&path) + .map_err(|e| format!("Failed to delete file {}: {}", path.display(), e))?; + } else if path.is_dir() { + fs::remove_dir_all(&path) + .map_err(|e| format!("Failed to delete directory {}: {}", path.display(), e))?; + } + } + Ok(()) } @@ -2730,6 +2762,7 @@ pub fn delete_files_with_associated(paths: Vec) -> Result<(), String> { } let final_paths_to_delete: Vec = files_to_trash.into_iter().collect(); + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] if let Err(trash_error) = trash::delete_all(&final_paths_to_delete) { log::warn!( "Failed to move files to trash: {}. Falling back to permanent delete.", @@ -2742,6 +2775,15 @@ pub fn delete_files_with_associated(paths: Vec) -> Result<(), String> { } } } + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + for path in final_paths_to_delete { + if path.is_file() { + fs::remove_file(&path) + .map_err(|e| format!("Failed to delete file {}: {}", path.display(), e))?; + } + } + Ok(()) } @@ -2888,23 +2930,34 @@ pub async fn import_files( } if settings.delete_after_import { - if let Err(trash_error) = trash::delete(&source_path) { - log::warn!( - "Failed to trash source file {}: {}. Deleting permanently.", - source_path.display(), - trash_error - ); - fs::remove_file(&source_path).map_err(|e| e.to_string())?; + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] + { + if let Err(trash_error) = trash::delete(&source_path) { + log::warn!( + "Failed to trash source file {}: {}. Deleting permanently.", + source_path.display(), + trash_error + ); + fs::remove_file(&source_path).map_err(|e| e.to_string())?; + } + if source_sidecar.exists() + && let Err(trash_error) = trash::delete(&source_sidecar) + { + log::warn!( + "Failed to trash source sidecar {}: {}. Deleting permanently.", + source_sidecar.display(), + trash_error + ); + fs::remove_file(&source_sidecar).map_err(|e| e.to_string())?; + } } - if source_sidecar.exists() - && let Err(trash_error) = trash::delete(&source_sidecar) + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] { - log::warn!( - "Failed to trash source sidecar {}: {}. Deleting permanently.", - source_sidecar.display(), - trash_error - ); - fs::remove_file(&source_sidecar).map_err(|e| e.to_string())?; + fs::remove_file(&source_path).map_err(|e| e.to_string())?; + if source_sidecar.exists() { + fs::remove_file(&source_sidecar).map_err(|e| e.to_string())?; + } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index cd72654e3b..029972cf19 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4191,62 +4191,71 @@ fn frontend_ready( let is_first_run = !state .window_setup_complete .swap(true, std::sync::atomic::Ordering::Relaxed); - let mut should_maximize = false; - let mut should_fullscreen = false; + #[cfg(target_os = "android")] + let _ = (is_first_run, &window); - if is_first_run && let Ok(config_dir) = app_handle.path().app_config_dir() { - let path = config_dir.join("window_state.json"); + #[cfg(not(target_os = "android"))] + { + let mut should_maximize = false; + let mut should_fullscreen = false; - if let Ok(contents) = std::fs::read_to_string(&path) - && let Ok(saved_state) = serde_json::from_str::(&contents) - { - #[cfg(any(windows, target_os = "linux"))] - { - should_maximize = saved_state.maximized; - should_fullscreen = saved_state.fullscreen; - } + if is_first_run && let Ok(config_dir) = app_handle.path().app_config_dir() { + let path = config_dir.join("window_state.json"); - if (should_maximize || should_fullscreen) - && let Some(monitor) = window - .current_monitor() - .ok() - .flatten() - .or_else(|| window.primary_monitor().ok().flatten()) - .or_else(|| { - window - .available_monitors() - .ok() - .and_then(|m| m.into_iter().next()) - }) + if let Ok(contents) = std::fs::read_to_string(&path) + && let Ok(saved_state) = serde_json::from_str::(&contents) { - let monitor_size = monitor.size(); - let monitor_pos = monitor.position(); - let default_width = 1280i32; - let default_height = 720i32; - let center_x = monitor_pos.x + (monitor_size.width as i32 - default_width) / 2; - let center_y = monitor_pos.y + (monitor_size.height as i32 - default_height) / 2; - - let _ = window.set_size(tauri::PhysicalSize::new( - default_width as u32, - default_height as u32, - )); - let _ = window.set_position(tauri::PhysicalPosition::new(center_x, center_y)); + #[cfg(any(windows, target_os = "linux"))] + { + should_maximize = saved_state.maximized; + should_fullscreen = saved_state.fullscreen; + } + + if (should_maximize || should_fullscreen) + && let Some(monitor) = window + .current_monitor() + .ok() + .flatten() + .or_else(|| window.primary_monitor().ok().flatten()) + .or_else(|| { + window + .available_monitors() + .ok() + .and_then(|m| m.into_iter().next()) + }) + { + let monitor_size = monitor.size(); + let monitor_pos = monitor.position(); + let default_width = 1280i32; + let default_height = 720i32; + let center_x = + monitor_pos.x + (monitor_size.width as i32 - default_width) / 2; + let center_y = + monitor_pos.y + (monitor_size.height as i32 - default_height) / 2; + + let _ = window.set_size(tauri::PhysicalSize::new( + default_width as u32, + default_height as u32, + )); + let _ = window + .set_position(tauri::PhysicalPosition::new(center_x, center_y)); + } } } - } - if let Err(e) = window.show() { - log::error!("Failed to show window: {}", e); - } - if let Err(e) = window.set_focus() { - log::error!("Failed to focus window: {}", e); - } - if is_first_run { - if should_maximize { - let _ = window.maximize(); + if let Err(e) = window.show() { + log::error!("Failed to show window: {}", e); + } + if let Err(e) = window.set_focus() { + log::error!("Failed to focus window: {}", e); } - if should_fullscreen { - let _ = window.set_fullscreen(true); + if is_first_run { + if should_maximize { + let _ = window.maximize(); + } + if should_fullscreen { + let _ = window.set_fullscreen(true); + } } } @@ -4262,8 +4271,11 @@ fn frontend_ready( #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| { + let mut builder = tauri::Builder::default(); + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + builder = builder.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| { log::info!("New instance launched with args: {:?}. Focusing main window.", argv); if let Some(window) = app.get_webview_window("main") { if let Err(e) = window.unminimize() { @@ -4280,7 +4292,10 @@ pub fn run() { log::error!("Failed to emit open-with-file from single-instance handler: {}", e); } } - })) + })); + } + + builder .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_dialog::init()) @@ -4333,19 +4348,27 @@ pub fn run() { } } - let resource_path = app_handle - .path() - .resolve("resources", tauri::path::BaseDirectory::Resource) - .expect("failed to resolve resource directory"); - - let ort_library_name = { - #[cfg(target_os = "windows")] { "onnxruntime.dll" } - #[cfg(target_os = "linux")] { "libonnxruntime.so" } - #[cfg(target_os = "macos")] { "libonnxruntime.dylib" } - }; - let ort_library_path = resource_path.join(ort_library_name); - std::env::set_var("ORT_DYLIB_PATH", &ort_library_path); - println!("Set ORT_DYLIB_PATH to: {}", ort_library_path.display()); + #[cfg(not(target_os = "android"))] + { + let resource_path = app_handle + .path() + .resolve("resources", tauri::path::BaseDirectory::Resource) + .expect("failed to resolve resource directory"); + + let ort_library_name = { + #[cfg(target_os = "windows")] + { "onnxruntime.dll" } + #[cfg(target_os = "linux")] + { "libonnxruntime.so" } + #[cfg(target_os = "macos")] + { "libonnxruntime.dylib" } + #[cfg(not(any(windows, target_os = "linux", target_os = "macos")))] + { "libonnxruntime.so" } + }; + let ort_library_path = resource_path.join(ort_library_name); + std::env::set_var("ORT_DYLIB_PATH", &ort_library_path); + println!("Set ORT_DYLIB_PATH to: {}", ort_library_path.display()); + } } setup_logging(&app_handle); @@ -4368,22 +4391,34 @@ pub fn run() { let window_cfg = app.config().app.windows.first().unwrap().clone(); let transparent = settings.transparent.unwrap_or(window_cfg.transparent); let decorations = settings.decorations.unwrap_or(window_cfg.decorations); + #[cfg(target_os = "android")] + let _ = decorations; - let main_window_cfg = app.config().app.windows.iter() + let main_window_cfg = app + .config() + .app + .windows + .iter() .find(|w| w.label == "main") .expect("Main window config not found") .clone(); - let mut window_builder = tauri::WebviewWindowBuilder::from_config(app.handle(), &main_window_cfg) - .unwrap() - .transparent(transparent) - .decorations(decorations) - .visible(false); + let mut window_builder = + tauri::WebviewWindowBuilder::from_config(app.handle(), &main_window_cfg) + .unwrap() + .transparent(transparent); + + #[cfg(not(target_os = "android"))] + { + window_builder = window_builder.decorations(decorations).visible(false); + } if !transparent { - window_builder = window_builder.background_color(tauri::window::Color(100, 100, 100, 255)); + window_builder = window_builder + .background_color(tauri::window::Color(100, 100, 100, 255)); } else { - window_builder = window_builder.background_color(tauri::window::Color(0, 0, 0, 0)); + window_builder = + window_builder.background_color(tauri::window::Color(0, 0, 0, 0)); } let window = window_builder.build().expect("Failed to build window"); @@ -4393,62 +4428,80 @@ pub fn run() { apply_window_effect(theme, &window); } - if let Ok(config_dir) = app.path().app_config_dir() { - let path = config_dir.join("window_state.json"); - if let Ok(contents) = std::fs::read_to_string(&path) { - if let Ok(state) = serde_json::from_str::(&contents) { - if state.width >= 200 && state.height >= 150 { - let _ = window.set_size(tauri::Size::Physical(tauri::PhysicalSize::new(state.width, state.height))); - let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition::new(state.x, state.y))); + #[cfg(not(target_os = "android"))] + { + if let Ok(config_dir) = app.path().app_config_dir() { + let path = config_dir.join("window_state.json"); + if let Ok(contents) = std::fs::read_to_string(&path) { + if let Ok(state) = serde_json::from_str::(&contents) { + if state.width >= 200 && state.height >= 150 { + let _ = window.set_size(tauri::Size::Physical( + tauri::PhysicalSize::new(state.width, state.height), + )); + let _ = window.set_position(tauri::Position::Physical( + tauri::PhysicalPosition::new(state.x, state.y), + )); + } else { + log::warn!( + "Saved window state had unreasonable dimensions ({}x{}), centering instead.", + state.width, + state.height + ); + let _ = window.center(); + } } else { - log::warn!("Saved window state had unreasonable dimensions ({}x{}), centering instead.", state.width, state.height); let _ = window.center(); } - } else { let _ = window.center(); } - } else { let _ = window.center(); } - } else { let _ = window.center(); } - - let window_failsafe = window.clone(); - tauri::async_runtime::spawn(async move { - tokio::time::sleep(std::time::Duration::from_secs(4)).await; - if let Ok(false) = window_failsafe.is_visible() { - log::warn!("Frontend failed to report ready within timeout. Forcing window visibility."); - let _ = window_failsafe.show(); - let _ = window_failsafe.set_focus(); + } else { + let _ = window.center(); + } + } else { + let _ = window.center(); } - }); - let pending_window_state = Arc::new(Mutex::new(None::)); - let pending_state_for_saver = pending_window_state.clone(); - let app_handle_for_saver = app.handle().clone(); + let window_failsafe = window.clone(); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(4)).await; + if let Ok(false) = window_failsafe.is_visible() { + log::warn!( + "Frontend failed to report ready within timeout. Forcing window visibility." + ); + let _ = window_failsafe.show(); + let _ = window_failsafe.set_focus(); + } + }); - tauri::async_runtime::spawn(async move { - loop { - tokio::time::sleep(Duration::from_millis(500)).await; + let pending_window_state = Arc::new(Mutex::new(None::)); + let pending_state_for_saver = pending_window_state.clone(); + let app_handle_for_saver = app.handle().clone(); - let state_to_save = { - let mut lock = pending_state_for_saver.lock().unwrap(); - lock.take() - }; + tauri::async_runtime::spawn(async move { + loop { + tokio::time::sleep(Duration::from_millis(500)).await; + + let state_to_save = { + let mut lock = pending_state_for_saver.lock().unwrap(); + lock.take() + }; - if let Some(state) = state_to_save - && let Ok(config_dir) = app_handle_for_saver.path().app_config_dir() { + if let Some(state) = state_to_save + && let Ok(config_dir) = + app_handle_for_saver.path().app_config_dir() + { let path = config_dir.join("window_state.json"); let _ = std::fs::create_dir_all(&config_dir); if let Ok(json) = serde_json::to_string(&state) { let _ = std::fs::write(&path, json); } } - } - }); + } + }); - let window_for_handler = window.clone(); - let pending_state_for_handler = pending_window_state.clone(); + let window_for_handler = window.clone(); + let pending_state_for_handler = pending_window_state.clone(); - window.on_window_event(move |event| { - match event { + window.on_window_event(move |event| match event { tauri::WindowEvent::Resized(_) | tauri::WindowEvent::Moved(_) => { - #[cfg(any(windows, target_os = "linux"))] let maximized = window_for_handler.is_maximized().unwrap_or(false); #[cfg(not(any(windows, target_os = "linux")))] @@ -4477,18 +4530,22 @@ pub fn run() { state.y = position.y; } - if !maximized && !fullscreen + if !maximized + && !fullscreen && let Ok(size) = window_for_handler.outer_size() - && size.width >= 200 && size.height >= 150 { - state.width = size.width; - state.height = size.height; - } + && size.width >= 200 + && size.height >= 150 + { + state.width = size.width; + state.height = size.height; + } *pending_state_for_handler.lock().unwrap() = Some(state); } _ => {} - } - }); + }); + } + crate::register_exit_handler(); Ok(()) })