mod fig_container; mod fonts; mod menu; mod menu_events; mod window; use fig_container::build_fig_file; use fonts::{list_system_fonts, load_system_font}; use menu::install_app_menu; use menu_events::handle_menu_event; use std::{ path::{Path, PathBuf}, sync::Mutex, }; use tauri::{Emitter, Manager}; use tauri_plugin_fs::FsExt; use window::show_main_window; #[derive(Clone, serde::Serialize)] struct PendingOpenFile { path: String, } struct PendingOpen(Mutex>); #[tauri::command] fn take_pending_open(state: tauri::State) -> Vec { state .0 .lock() .map(|mut pending| pending.drain(..).collect()) .unwrap_or_default() } fn file_association_path(path: PathBuf) -> Option { let path = path.canonicalize().ok()?; if !path.is_file() { return None; } let ext = path.extension()?.to_string_lossy().to_lowercase(); matches!(ext.as_str(), "fig" | "pen").then_some(path) } fn path_from_arg(arg: String, cwd: &Path) -> Option { if arg.starts_with('-') { return None; } if let Ok(url) = tauri::Url::parse(&arg) { if let Ok(path) = url.to_file_path() { return Some(path); } } let path = PathBuf::from(arg); Some(if path.is_absolute() { path } else { cwd.join(path) }) } fn open_paths_from_args(args: Vec, cwd: &Path) -> Vec { args.into_iter() .filter_map(|arg| path_from_arg(arg, cwd)) .filter_map(file_association_path) .collect() } fn queue_open_paths(app: &tauri::AppHandle, paths: Vec) { let files = paths .into_iter() .filter_map(|path| { let _ = app.fs_scope().allow_file(&path); Some(PendingOpenFile { path: path.to_string_lossy().into_owned(), }) }) .collect::>(); if files.is_empty() { return; } if let Ok(mut pending) = app.state::().0.lock() { pending.extend(files); } let _ = app.emit("open-associated-files", ()); if let Some(window) = app.get_webview_window("main") { let _ = window.set_focus(); } } fn startup_open_paths() -> Vec { let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); open_paths_from_args(std::env::args().skip(1).collect(), &cwd) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let _ = fix_path_env::fix(); let mut builder = tauri::Builder::default(); #[cfg(any(target_os = "macos", windows, target_os = "linux"))] { builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, cwd| { queue_open_paths(app, open_paths_from_args(args, Path::new(&cwd))); })); } builder .manage(PendingOpen(Mutex::new(Vec::new()))) .invoke_handler(tauri::generate_handler![ build_fig_file, list_system_fonts, load_system_font, take_pending_open ]) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_process::init()) .on_menu_event(|app, event| { handle_menu_event(app, event.id().0.as_str()); }) .setup(|app| { queue_open_paths(app.handle(), startup_open_paths()); Ok(install_app_menu(app)?) }) .build(tauri::generate_context!()) .expect("error while building tauri application") .run(|app, event| match event { tauri::RunEvent::Opened { urls } => { let paths = urls .into_iter() .filter_map(|url| url.to_file_path().ok()) .filter_map(file_association_path) .collect(); queue_open_paths(app, paths); } #[cfg(target_os = "macos")] tauri::RunEvent::Reopen { has_visible_windows, .. } => { if !has_visible_windows { show_main_window(app); } } _ => {} }); }