Add single-instance file handling via GIO HANDLES_OPEN

- App flag HANDLES_OPEN enables receiving files from file manager
- connect_open handler filters for image files and activates window
- load-external-files action receives paths and adds to loaded files
- Auto-navigates to step 2 (images) when files arrive externally
- Second instance sends files to existing window instead of launching
This commit is contained in:
2026-03-06 16:58:23 +02:00
parent a7dda7ad6f
commit b9775d5632

View File

@@ -141,9 +141,55 @@ struct WizardUi {
pub fn build_app() -> adw::Application {
let app = adw::Application::builder()
.application_id(APP_ID)
.flags(gtk::gio::ApplicationFlags::HANDLES_OPEN)
.build();
app.connect_activate(build_ui);
// Handle files opened via file manager integration or command line
app.connect_open(|app, files, _hint| {
// Ensure the window exists (activate first if needed)
if app.active_window().is_none() {
app.activate();
}
// Collect valid image file paths
let image_paths: Vec<std::path::PathBuf> = files.iter()
.filter_map(|f| f.path())
.filter(|p| {
p.extension()
.and_then(|e| e.to_str())
.is_some_and(|ext| {
matches!(
ext.to_lowercase().as_str(),
"jpg" | "jpeg" | "png" | "webp" | "avif" | "gif" | "tiff" | "tif" | "bmp"
)
})
})
.collect();
if image_paths.is_empty() {
return;
}
// Find the WizardUi through the window's action group
if let Some(window) = app.active_window() {
// Store files in a global channel for the UI to pick up
if let Some(action) = window.downcast_ref::<adw::ApplicationWindow>()
.and_then(|w| w.lookup_action("load-external-files"))
{
// Serialize paths as string variant
let paths_str: String = image_paths.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join("\n");
action.downcast_ref::<gtk::gio::SimpleAction>()
.unwrap()
.activate(Some(&paths_str.to_variant()));
}
}
});
setup_shortcuts(&app);
// App-level quit action
@@ -846,6 +892,39 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
}
});
// Load external files action - used by single-instance file open
{
let ui = ui.clone();
let action = gtk::gio::SimpleAction::new("load-external-files", Some(&String::static_variant_type()));
action.connect_activate(move |_, param| {
if let Some(paths_str) = param.and_then(|v| v.get::<String>()) {
let new_files: Vec<std::path::PathBuf> = paths_str
.lines()
.map(std::path::PathBuf::from)
.filter(|p| p.exists())
.collect();
if !new_files.is_empty() {
let count = new_files.len();
ui.state.loaded_files.borrow_mut().extend(new_files);
ui.toast_overlay.add_toast(adw::Toast::new(
&format!("{} images added from file manager", count)
));
// Navigate to step 2 (images) if we're on step 1 or earlier
let current = ui.state.wizard.borrow().current_step;
if current <= 1 {
ui.state.wizard.borrow_mut().current_step = 1;
if let Some(page) = ui.pages.get(1) {
ui.nav_view.push(page);
}
}
}
}
});
action_group.add_action(&action);
}
window.insert_action_group("win", Some(&action_group));
}