From e435f2e98859a331e4447154ac822535e9fd6af5 Mon Sep 17 00:00:00 2001 From: Juergen Weigert Date: Mon, 8 Apr 2024 10:16:31 +0200 Subject: [PATCH 1/5] Fix Visicut calling inkscape in an AppImage. In Ubuntu 20.04, this worked flawlessly. But only by chance. We use libraries from the host system, together with the binary inside the mounted appimage. in Ubintu 22.04, this crashes: /tmp/.mount_inkscaYGPL8G/usr/bin/inkscape --version /tmp/.mount_inkscaYGPL8G/usr/bin/inkscape: error while loading shared libraries: libboost_filesystem.so.1.71.0: cannot open shared object file: No such file or directory The fix is to not call the binary directly, but the AppRun scrit, which nicely prepares the environment. AppRun also prints a message for the user to stdout, (instead of stderr). We now need to filter this, when parsing output from e.g. --version: /tmp/.mount_inkscaYGPL8G/AppRun --version 2>/dev/null You should not use AppImage in production, but you can speedup the AppImage by following this guide: https://inkscape.org/learn/appimage/ Inkscape 1.3.2 (091e20e, 2023-11-25) --- tools/inkscape_extension/visicut_export.py | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tools/inkscape_extension/visicut_export.py b/tools/inkscape_extension/visicut_export.py index 78c6d952..59bf7704 100755 --- a/tools/inkscape_extension/visicut_export.py +++ b/tools/inkscape_extension/visicut_export.py @@ -141,12 +141,19 @@ def is_exe(fpath): def inkscape_version(): """Return Inkscape version number as float, e.g. version "0.92.4" --> return: float 0.92""" version = subprocess.check_output([INKSCAPEBIN, "--version"], stderr=DEVNULL).decode('ASCII', 'ignore') + if not version.startswith("Inkscape "): + ## When inkscape lives in an appimage, AppRun may pollute stdout with extra information. + # Go through all the lines, and find the one that starts with Inkscape + lines = version.splitlines() + for version in lines: + if version.startswith("Inkscape "): + break assert version.startswith("Inkscape ") match = re.match("Inkscape ([0-9]+\.[0-9]+).*", version) assert match is not None version_float = float(match.group(1)) return version_float - + # Strip SVG to only contain selected elements, convert objects to paths, unlink clones @@ -157,7 +164,7 @@ def inkscape_version(): # The idea is similar to http://bazaar.launchpad.net/~nikitakit/inkscape/svg2sif/view/head:/share/extensions/synfig_prepare.py#L181 , but more primitive - there is no need for more complicated preprocessing here def stripSVG_inkscape(src, dest, elements): version = inkscape_version() - + # create temporary file for opening with inkscape. # delete this file later so that it will disappear from the "recently opened" list. tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix='temp-visicut-', suffix='.svg') @@ -199,8 +206,8 @@ def stripSVG_inkscape(src, dest, elements): verbs += ["UnhideAllInAllLayers", "EditInvertInAllLayers", "EditDelete", "EditSelectAllInAllLayers", "EditUnlinkClone", "ObjectToPath", "FileSave"] # --verb=action1;action2;... command += ["--verb=" + ";".join(verbs)] - - + + DEBUG = False if DEBUG: # Inkscape sometimes silently ignores wrong verbs, so we need to double-check that everything's right @@ -248,7 +255,7 @@ def stripSVG_inkscape(src, dest, elements): actions += ["export-area-page"] command = [INKSCAPEBIN, tmpfile, "--export-overwrite", "--actions=" + ";".join(actions)] - + try: #sys.stderr.write(" ".join(command)) # run inkscape, buffer output @@ -326,6 +333,17 @@ def get_original_filename(filename): VISICUTBIN = which("VisiCut.Linux", [VISICUTDIR, "/usr/share/visicut"]) INKSCAPEBIN = which("inkscape", [INKSCAPEDIR]) +## Test if this inkscape is in an appimage. +# We detect this by checking for an AppRun file, in one of the parent folders of our INKSCAPEBIN. +# If so, replace INKSCAPEBIN with AppRun, as this is the only safe way to call inkscape. +# (a direct call mixes libraries from the host system with the appimage, may or may not work.) +dir = os.path.split(INKSCAPEBIN)[0] +while dir != '/': + if os.path.exists(os.path.join(dir, "AppRun")): + INKSCAPEBIN = os.path.join(dir, "AppRun") + break + dir = os.path.split(dir)[0] + tmpdir = tempfile.mkdtemp(prefix='temp-visicut-') dest_filename = os.path.join(tmpdir, get_original_filename(filename)) @@ -384,3 +402,4 @@ def get_original_filename(filename): sys.exit(1) # TODO (complicated, probably WONTFIX): cleanup temporary directories -- this is really difficult because we need to make sure that visicut no longer needs the file, even for reloading! +# - maybe add the PID od the running visicut, then we can detect orphaned temp direcories. From ab42570ce77b924619145972854732e03fdfea51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Weigert?= Date: Mon, 8 Apr 2024 11:25:24 +0200 Subject: [PATCH 2/5] Update tools/inkscape_extension/visicut_export.py dirname() should be fine to. It is more readable. Thanks! (not sure if introducing a new apprun_path variable contributes to readability though ...) But I don't mind. Co-authored-by: TheAssassin --- tools/inkscape_extension/visicut_export.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/inkscape_extension/visicut_export.py b/tools/inkscape_extension/visicut_export.py index 59bf7704..60056830 100755 --- a/tools/inkscape_extension/visicut_export.py +++ b/tools/inkscape_extension/visicut_export.py @@ -337,13 +337,13 @@ def get_original_filename(filename): # We detect this by checking for an AppRun file, in one of the parent folders of our INKSCAPEBIN. # If so, replace INKSCAPEBIN with AppRun, as this is the only safe way to call inkscape. # (a direct call mixes libraries from the host system with the appimage, may or may not work.) -dir = os.path.split(INKSCAPEBIN)[0] +dir = os.path.dirname(INKSCAPEBIN) while dir != '/': - if os.path.exists(os.path.join(dir, "AppRun")): - INKSCAPEBIN = os.path.join(dir, "AppRun") + apprun_path = os.path.join(dir, "AppRun") + if os.path.exists(apprun_path): + INKSCAPEBIN = apprun_path break - dir = os.path.split(dir)[0] - + dir = os.path.dirname(dir) tmpdir = tempfile.mkdtemp(prefix='temp-visicut-') dest_filename = os.path.join(tmpdir, get_original_filename(filename)) From 64f5e4f024f7ef96b4c7f2ea16aab6c91538f55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Weigert?= Date: Mon, 8 Apr 2024 12:45:14 +0200 Subject: [PATCH 3/5] Update tools/inkscape_extension/visicut_export.py --- tools/inkscape_extension/visicut_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/inkscape_extension/visicut_export.py b/tools/inkscape_extension/visicut_export.py index 60056830..12a61b04 100755 --- a/tools/inkscape_extension/visicut_export.py +++ b/tools/inkscape_extension/visicut_export.py @@ -402,4 +402,4 @@ def get_original_filename(filename): sys.exit(1) # TODO (complicated, probably WONTFIX): cleanup temporary directories -- this is really difficult because we need to make sure that visicut no longer needs the file, even for reloading! -# - maybe add the PID od the running visicut, then we can detect orphaned temp direcories. +# - Maybe add the PID od the running visicut, then we can detect orphaned temp direcories. From 5bfbeef46eed1bf8936de601f7ecc20368558258 Mon Sep 17 00:00:00 2001 From: Juergen Weigert Date: Mon, 8 Apr 2024 22:40:39 +0200 Subject: [PATCH 4/5] rewritten as suggested --- tools/inkscape_extension/visicut_export.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tools/inkscape_extension/visicut_export.py b/tools/inkscape_extension/visicut_export.py index 12a61b04..f414f68c 100755 --- a/tools/inkscape_extension/visicut_export.py +++ b/tools/inkscape_extension/visicut_export.py @@ -140,17 +140,14 @@ def is_exe(fpath): def inkscape_version(): """Return Inkscape version number as float, e.g. version "0.92.4" --> return: float 0.92""" - version = subprocess.check_output([INKSCAPEBIN, "--version"], stderr=DEVNULL).decode('ASCII', 'ignore') - if not version.startswith("Inkscape "): - ## When inkscape lives in an appimage, AppRun may pollute stdout with extra information. - # Go through all the lines, and find the one that starts with Inkscape - lines = version.splitlines() - for version in lines: - if version.startswith("Inkscape "): - break - assert version.startswith("Inkscape ") - match = re.match("Inkscape ([0-9]+\.[0-9]+).*", version) - assert match is not None + version_raw = subprocess.check_output([INKSCAPEBIN, "--version"], stderr=DEVNULL).decode('ASCII', 'ignore') + ## When inkscape lives in an appimage, AppRun may pollute stdout with extra information. + # Go through all the lines, and find the one that starts with Inkscape + lines = version_raw.splitlines() + version = [line for line in lines if line.startswith("Inkscape ")] + assert len(version) == 1, "inkscape --version did not return a version number: " + version_raw + match = re.match("Inkscape ([0-9]+\.[0-9]+).*", version[0]) + assert match is not None, "failed to parse version number from " + version[0] version_float = float(match.group(1)) return version_float From 4a551e2fb31dc125ce0e9e7298a2642b67a2f29c Mon Sep 17 00:00:00 2001 From: Juergen Weigert Date: Wed, 10 Apr 2024 01:29:29 +0200 Subject: [PATCH 5/5] Ported the appimage patch to pathlib. Much nicer code and windows compatible. --- tools/inkscape_extension/visicut_export.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/inkscape_extension/visicut_export.py b/tools/inkscape_extension/visicut_export.py index f414f68c..97de806f 100755 --- a/tools/inkscape_extension/visicut_export.py +++ b/tools/inkscape_extension/visicut_export.py @@ -33,6 +33,7 @@ import random import string import socket +from pathlib import Path try: from os import fsencode @@ -334,13 +335,12 @@ def get_original_filename(filename): # We detect this by checking for an AppRun file, in one of the parent folders of our INKSCAPEBIN. # If so, replace INKSCAPEBIN with AppRun, as this is the only safe way to call inkscape. # (a direct call mixes libraries from the host system with the appimage, may or may not work.) -dir = os.path.dirname(INKSCAPEBIN) -while dir != '/': - apprun_path = os.path.join(dir, "AppRun") - if os.path.exists(apprun_path): - INKSCAPEBIN = apprun_path +for parent in Path(INKSCAPEBIN).parents: + apprun = parent / "AppRun" + if apprun.is_file() and os.access(apprun, os.X_OK): + INKSCAPEBIN = apprun break - dir = os.path.dirname(dir) + tmpdir = tempfile.mkdtemp(prefix='temp-visicut-') dest_filename = os.path.join(tmpdir, get_original_filename(filename))