openpencil/desktop/src/lib.rs
2026-05-18 23:23:20 +03:00

160 lines
4.4 KiB
Rust

mod fig_container;
mod fonts;
mod menu;
mod menu_events;
#[cfg(target_os = "macos")]
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;
#[cfg(target_os = "macos")]
use window::show_main_window;
#[derive(Clone, serde::Serialize)]
struct PendingOpenFile {
path: String,
}
struct PendingOpen(Mutex<Vec<PendingOpenFile>>);
#[tauri::command]
fn take_pending_open(state: tauri::State<PendingOpen>) -> Vec<PendingOpenFile> {
state
.0
.lock()
.map(|mut pending| pending.drain(..).collect())
.unwrap_or_default()
}
fn file_association_path(path: PathBuf) -> Option<PathBuf> {
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<PathBuf> {
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<String>, cwd: &Path) -> Vec<PathBuf> {
args.into_iter()
.filter_map(|arg| path_from_arg(arg, cwd))
.filter_map(file_association_path)
.collect()
}
fn queue_open_paths<R: tauri::Runtime>(app: &tauri::AppHandle<R>, paths: Vec<PathBuf>) {
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::<Vec<_>>();
if files.is_empty() {
return;
}
if let Ok(mut pending) = app.state::<PendingOpen>().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<PathBuf> {
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 {
#[cfg(target_os = "macos")]
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);
}
}
_ => {}
});
}