Fix drag-and-drop and file manager integration

Drag-and-drop from Nautilus on Wayland was broken by two issues:
- DropTarget only accepted COPY action, but Wayland compositor
  pre-selects MOVE, causing GTK4 to silently reject all drops
- Two competing DropTarget controllers on the same widget caused
  the gchararray target to match Nautilus formats first, swallowing
  the drop before the FileList target could receive it

Merged both drop targets into a single controller that tries
FileList first, then File, then falls back to URI text parsing.

File manager integration was broken by wrong command syntax
(non-existent --files flag) and wrong binary path resolution
inside AppImage. Split into separate GTK/CLI binary resolution
with APPIMAGE env var detection.
This commit is contained in:
2026-03-08 16:53:30 +02:00
parent 0eb7d24411
commit bcc53a0dc1
2 changed files with 124 additions and 109 deletions

View File

@@ -84,15 +84,14 @@ fn shell_safe(s: &str) -> String {
.collect()
}
fn pixstrip_bin() -> String {
// Try to find the pixstrip binary path
/// Path to the GTK binary for "Open in Pixstrip" actions.
fn pixstrip_gtk_bin() -> String {
// When running from AppImage, use the AppImage path directly
if let Ok(appimage) = std::env::var("APPIMAGE") {
return appimage;
}
if let Ok(exe) = std::env::current_exe() {
// If running from the GTK app, find the CLI sibling
let dir = exe.parent().unwrap_or(Path::new("/usr/bin"));
let cli_path = dir.join("pixstrip");
if cli_path.exists() {
return cli_path.display().to_string();
}
let gtk_path = dir.join("pixstrip-gtk");
if gtk_path.exists() {
return gtk_path.display().to_string();
@@ -102,6 +101,22 @@ fn pixstrip_bin() -> String {
"pixstrip-gtk".into()
}
/// Path to the CLI binary for preset processing actions.
fn pixstrip_cli_bin() -> String {
// When running from AppImage, use the AppImage path directly
if let Ok(appimage) = std::env::var("APPIMAGE") {
return appimage;
}
if let Ok(exe) = std::env::current_exe() {
let dir = exe.parent().unwrap_or(Path::new("/usr/bin"));
let cli_path = dir.join("pixstrip");
if cli_path.exists() {
return cli_path.display().to_string();
}
}
"pixstrip".into()
}
fn get_preset_names() -> Vec<String> {
let mut names: Vec<String> = Preset::all_builtins()
.into_iter()
@@ -138,7 +153,8 @@ fn install_nautilus() -> Result<()> {
let dir = nautilus_extension_dir();
std::fs::create_dir_all(&dir)?;
let bin = pixstrip_bin();
let gtk_bin = pixstrip_gtk_bin();
let cli_bin = pixstrip_cli_bin();
let presets = get_preset_names();
let mut preset_items = String::new();
@@ -156,7 +172,8 @@ fn install_nautilus() -> Result<()> {
));
}
let escaped_bin = bin.replace('\\', "\\\\").replace('\'', "\\'");
let escaped_gtk_bin = gtk_bin.replace('\\', "\\\\").replace('\'', "\\'");
let escaped_cli_bin = cli_bin.replace('\\', "\\\\").replace('\'', "\\'");
let script = format!(
r#"import subprocess
from gi.repository import Nautilus, GObject
@@ -204,14 +221,15 @@ class PixstripExtension(GObject.GObject, Nautilus.MenuProvider):
def _on_open(self, menu, files):
paths = [f.get_location().get_path() for f in files if f.get_location()]
subprocess.Popen(['{bin}', '--files'] + paths)
subprocess.Popen(['{gtk_bin}'] + paths)
def _on_preset(self, menu, preset_name, files):
paths = [f.get_location().get_path() for f in files if f.get_location()]
subprocess.Popen(['{bin}', '--preset', preset_name, '--files'] + paths)
subprocess.Popen(['{cli_bin}', 'process', '--preset', preset_name] + paths)
"#,
preset_items = preset_items,
bin = escaped_bin,
gtk_bin = escaped_gtk_bin,
cli_bin = escaped_cli_bin,
);
atomic_write(&nautilus_extension_path(), &script)?;
@@ -245,19 +263,20 @@ fn install_nemo() -> Result<()> {
let dir = nemo_action_dir();
std::fs::create_dir_all(&dir)?;
let bin = pixstrip_bin();
let gtk_bin = pixstrip_gtk_bin();
let cli_bin = pixstrip_cli_bin();
// Main "Open in Pixstrip" action
let open_action = format!(
"[Nemo Action]\n\
Name=Open in Pixstrip...\n\
Comment=Process images with Pixstrip\n\
Exec={bin} --files %F\n\
Exec={gtk_bin} %F\n\
Icon-Name=applications-graphics-symbolic\n\
Selection=Any\n\
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
Mimetypes=image/*;\n",
bin = bin,
gtk_bin = gtk_bin,
);
atomic_write(&nemo_action_path(), &open_action)?;
@@ -270,14 +289,14 @@ fn install_nemo() -> Result<()> {
"[Nemo Action]\n\
Name=Pixstrip: {name}\n\
Comment=Process with {name} preset\n\
Exec={bin} --preset \"{safe_label}\" --files %F\n\
Exec={cli_bin} process --preset \"{safe_label}\" %F\n\
Icon-Name=applications-graphics-symbolic\n\
Selection=Any\n\
Extensions=jpg;jpeg;png;webp;gif;tiff;tif;avif;bmp;\n\
Mimetypes=image/*;\n",
name = name,
safe_label = shell_safe(name),
bin = bin,
cli_bin = cli_bin,
);
atomic_write(&action_path, &action)?;
}
@@ -324,7 +343,8 @@ fn install_thunar() -> Result<()> {
let dir = thunar_action_dir();
std::fs::create_dir_all(&dir)?;
let bin = pixstrip_bin();
let gtk_bin = pixstrip_gtk_bin();
let cli_bin = pixstrip_cli_bin();
let presets = get_preset_names();
let mut actions = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<actions>\n");
@@ -334,13 +354,13 @@ fn install_thunar() -> Result<()> {
" <action>\n\
\x20 <icon>applications-graphics-symbolic</icon>\n\
\x20 <name>Open in Pixstrip...</name>\n\
\x20 <command>{bin} --files %F</command>\n\
\x20 <command>{gtk_bin} %F</command>\n\
\x20 <description>Process images with Pixstrip</description>\n\
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
\x20 <image-files/>\n\
\x20 <directories/>\n\
</action>\n",
bin = bin,
gtk_bin = gtk_bin,
));
for name in &presets {
@@ -348,7 +368,7 @@ fn install_thunar() -> Result<()> {
" <action>\n\
\x20 <icon>applications-graphics-symbolic</icon>\n\
\x20 <name>Pixstrip: {xml_name}</name>\n\
\x20 <command>{bin} --preset \"{safe_label}\" --files %F</command>\n\
\x20 <command>{cli_bin} process --preset \"{safe_label}\" %F</command>\n\
\x20 <description>Process with {xml_name} preset</description>\n\
\x20 <patterns>*.jpg;*.jpeg;*.png;*.webp;*.gif;*.tiff;*.tif;*.avif;*.bmp</patterns>\n\
\x20 <image-files/>\n\
@@ -356,7 +376,7 @@ fn install_thunar() -> Result<()> {
</action>\n",
xml_name = name.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;"),
safe_label = shell_safe(name),
bin = bin,
cli_bin = cli_bin,
));
}
@@ -392,7 +412,8 @@ fn install_dolphin() -> Result<()> {
let dir = dolphin_service_dir();
std::fs::create_dir_all(&dir)?;
let bin = pixstrip_bin();
let gtk_bin = pixstrip_gtk_bin();
let cli_bin = pixstrip_cli_bin();
let presets = get_preset_names();
let mut desktop = format!(
@@ -406,8 +427,8 @@ fn install_dolphin() -> Result<()> {
[Desktop Action Open]\n\
Name=Open in Pixstrip...\n\
Icon=applications-graphics-symbolic\n\
Exec={bin} --files %F\n\n",
bin = bin,
Exec={gtk_bin} %F\n\n",
gtk_bin = gtk_bin,
preset_actions = presets
.iter()
.enumerate()
@@ -421,11 +442,11 @@ fn install_dolphin() -> Result<()> {
"[Desktop Action Preset{i}]\n\
Name={name}\n\
Icon=applications-graphics-symbolic\n\
Exec={bin} --preset \"{safe_label}\" --files %F\n\n",
Exec={cli_bin} process --preset \"{safe_label}\" %F\n\n",
i = i,
name = name,
safe_label = shell_safe(name),
bin = bin,
cli_bin = cli_bin,
));
}