LEGAL
How to use a modified Qt, PySide6, Shiboken6 or GEOS with StitchoMR (macOS, Apple silicon)
Published by Darwich Technologies LLC for StitchoMR (macOS 15 or later, Apple silicon; PES and DST export).
This page applies to the LGPL-3.0 libraries Qt 6.11.2, PySide6 6.11.2 and Shiboken6 6.11.2 and to GEOS 3.14.1 (LGPL-2.1). StitchoMR's own code and licence check stay proprietary; this procedure only replaces the open-source libraries. You do not need any key, certificate or other secret from Darwich Technologies LLC.
The procedure was tried on 2026-10-29 on a copy of the notarized application (never the installed one) on macOS 27.0.1, Apple silicon, with System Integrity Protection on, using a build of StitchoMR 0.9.8 that contains the same Qt, PySide6, Shiboken6 and GEOS versions as the released application. Section 7 states exactly what was and was not tested. These instructions are provided as they are; a copy you have modified is not an official build. Source code: source code for the LGPL libraries. Notices: third-party and open-source notices.
0. Code signing: what actually happens (tested)
The distributed app is signed with a Developer ID certificate (Team 7SHHUHZ572), has the hardened runtime flag (flags=0x10000) and no entitlements, so library validation is on, and it is notarized. Results on the tested copy:
Change a file inside the signed app, leave everything signed as shipped
Result: Does not run. A one-byte change in the QtCore binary: the process is killed at start (SIGKILL, AppleSystemPolicy: Security policy would not allow process); codesign --verify --deep --strict says the main executable fails strict validation in the QtCore subcomponent. A different (rebuilt) GEOS or a QtCore framework that was only re-signed on its own, with the app seal left untouched: the process was held at its first instruction (_dyld_start) and never started within a 120 s watchdog; codesign --verify says "nested code is modified or invalid".
Re-sign the whole copy ad hoc with --options runtime (hardened runtime on)
Result: Does not run: Failed to load Python shared library ... libpython3.12.dylib ... code signature ... not valid for use in process: mapping process and mapped file (non-platform) have different Team IDs (library validation rejects the ad-hoc libraries).
Re-sign the whole copy ad hoc without --options runtime (section 5)
Result: Works. codesign --verify --deep --strict passes; the app starts; a locally rebuilt GEOS and a byte-modified Qt library were both loaded from the copy.
Same but one-shot codesign --force --deep --sign -
Result: Works as well (verified and started). Apple discourages --deep for distribution; for a local copy it was fine.
Ad hoc with --options runtime plus the entitlement com.apple.security.cs.disable-library-validation
Result: Works (hardened runtime kept, library validation switched off in your own copy).
Gatekeeper (spctl) on the re-signed copy
Result: spctl --assess says rejected for the re-signed copy whether or not it carries com.apple.quarantine (the original says accepted, Notarized Developer ID). This is expected: an ad-hoc signed copy has no Developer ID and no Apple notarization ticket, so it can never pass that assessment. It does not stop the copy from running: a copy without the quarantine attribute (a copy made on your own Mac, after xattr -cr) started when its executable was launched from Terminal. A copy that still carries com.apple.quarantine (for example one that arrived by browser download, AirDrop or a mounted download) may be refused or questioned by macOS: clear the attribute with xattr -cr (steps 2 and 5) or approve the app under System Settings > Privacy & Security. Opening the copy by double-click in Finder and the System Settings approval path were not tried.
Conclusion: you cannot keep Darwich's signature (any change breaks it), so the working route is to copy the app, replace files, and re-sign everything ad hoc without the hardened runtime. Darwich's signature, notarization and support terms do not apply to such a copy. This section only reports what happens technically; it makes no statement about legal requirements.
1. What you need
- An Apple-silicon Mac on macOS 15 or later (tested on macOS 27.0.1 only), and the Xcode command line tools (
xcode-select --install) forcodesign,otool,xattrandinstall_name_tool. - The StitchoMR version you want to modify, and the matching source from source code for the LGPL libraries (Qt, PySide6/Shiboken6 and GEOS tarballs with SHA-256 checksums).
- Replacement libraries that are interface-compatible with the shipped ones: for Qt, PySide6 and Shiboken6 the same 6.11.x release of all three, built for arm64 macOS 15 or later, with the same framework layout and install names. Building Qt and PySide6 from source is documented by The Qt Company (https://doc.qt.io/qt-6/build-sources.html, https://doc.qt.io/qtforpython-6/building_from_source/index.html). The configuration of the Qt Company's binary packages is not part of the source tarballs, and a Qt, PySide6 or Shiboken6 rebuild was not performed in this test.
- For GEOS, CMake and a C++ compiler (tested: CMake 4.4.4, Ninja, Apple clang).
2. Make a working copy (never edit the installed app)
SRC="/Applications/StitchoMR.app" # or copy the app out of the mounted disk image instead
WORK="$HOME/StitchoMR-custom"
mkdir -p "$WORK"
ditto "$SRC" "$WORK/StitchoMR.app" # ditto keeps symlinks and framework structure
APP="$WORK/StitchoMR.app"
xattr -cr "$APP" # removes com.apple.quarantine and other attributes
3. Find the libraries
All real files are under Contents/Frameworks/. Many entries in Contents/Frameworks/ and Contents/Resources/ are symlinks into those real locations; replace the real file, not the symlink.
cd "$APP/Contents/Frameworks"
ls PySide6/Qt/lib # QtCore, QtDBus, QtGui, QtNetwork, QtSvg, QtTest, QtWidgets (.framework, real)
ls PySide6/Qt/plugins/* # platforms, styles, tls, imageformats, iconengines, generic, networkinformation
ls PySide6/*.so PySide6/libpyside6*.dylib # PySide6 bindings
ls shiboken6 # Shiboken.abi3.so, libshiboken6.abi3.6.11.dylib
ls shapely/__dot__dylibs # libgeos.3.14.1.dylib, libgeos_c.1.20.5.dylib (real files)
ls -l shapely/.dylibs # symlink to __dot__dylibs (both names work)
ls -l libgeos*.dylib # symlinks: Frameworks/libgeos*.dylib -> shapely/.dylibs/... (how libgeos_c finds libgeos)
otool -L PySide6/QtCore.abi3.so | head # @rpath names that must keep resolving
Everything above is an ordinary file in the bundle (PyInstaller one-directory layout). StitchoMR's own Python code lives inside the main executable's archive; you do not need to touch it.
4. Swap the libraries
# Qt framework (keep the framework structure: Versions/A, Current symlink)
rm -rf "$APP/Contents/Frameworks/PySide6/Qt/lib/QtCore.framework"
ditto "$HOME/qt-6.11.2-custom/lib/QtCore.framework" "$APP/Contents/Frameworks/PySide6/Qt/lib/QtCore.framework"
# A Qt plugin
cp "$HOME/qt-6.11.2-custom/plugins/imageformats/libqjpeg.dylib" \
"$APP/Contents/Frameworks/PySide6/Qt/plugins/imageformats/libqjpeg.dylib"
# GEOS: the file names must stay exactly libgeos.3.14.1.dylib and libgeos_c.1.20.5.dylib
cp /path/to/your/libgeos.3.14.1.dylib "$APP/Contents/Frameworks/shapely/__dot__dylibs/"
cp /path/to/your/libgeos_c.1.20.5.dylib "$APP/Contents/Frameworks/shapely/__dot__dylibs/"
Tested GEOS rebuild from the GEOS tarball on the source page (about two minutes on 8 cores):
tar -xjf geos-3.14.1.tar.bz2 && mkdir build && cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=ON \
-DBUILD_DOCUMENTATION=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 ../geos-3.14.1
ninja geos geos_c # produces lib/libgeos.3.14.1.dylib and lib/libgeos_c.1.20.5.dylib
Two things found in testing:
- Fix the run-path of a CMake build. CMake writes the build directory into libgeos_c's
LC_RPATH. Left as it is, the copy's libgeos_c quietly loaded libgeos from the build directory instead of the bundle (observed withlsof). Point it where the shipped one points, then check:
install_name_tool -rpath "$PWD/lib" "@loader_path/../.." lib/libgeos_c.1.20.5.dylib
otool -l lib/libgeos_c.1.20.5.dylib | grep -A2 LC_RPATH # expect @loader_path/../..
- Install names need not match. The CMake build uses
@rpath/libgeos_c.1.dylibas install name, the shipped file uses@rpath/libgeos_c.1.20.5.dylib. This did not matter: the Shapely extension asks for the file namelibgeos_c.1.20.5.dyliband dyld loads that file. What must stay the same is the file name and, forlibgeos_c, the dependency name@rpath/libgeos.3.14.1.dylib. Useotool -Landotool -Dto compare;install_name_tool -idand-changefix any difference.
If you rebuild PySide6 and Shiboken6, replace the matching *.abi3.so and libpyside6*.dylib and libshiboken6*.dylib files together with Qt; mixing different 6.11.x versions is unsupported by upstream and was not tested.
5. Re-sign (ad hoc) and clear quarantine
Do not add --options runtime (the hardened runtime would switch library validation on and the app would not start, section 0). Sign nested code first, then the app (tested exactly in this order):
cd "$APP"
find Contents -type f \( -name '*.dylib' -o -name '*.so' \) -print0 | xargs -0 -n1 codesign --force --sign -
find Contents -type d -name '*.framework' -print0 | xargs -0 -n1 codesign --force --sign -
codesign --force --sign - "$APP"
xattr -cr "$APP"
# one-shot alternative (also tested): codesign --force --deep --sign - "$APP"
These two find commands cover every Mach-O file in the tested app: there are 86, of which 78 are *.dylib or *.so files, 7 are Qt framework binaries (signed through the .framework directory) and 1 is the main executable (signed by the last command).
6. Verify
codesign --verify --deep --strict --verbose=2 "$APP" # expect: valid on disk / satisfies its Designated Requirement
codesign -dvv "$APP" 2>&1 | grep -E 'Signature|flags|TeamIdentifier' # expect Signature=adhoc, flags=0x2(adhoc), no 'runtime'
"$APP/Contents/MacOS/StitchoMR" & # launch from Terminal to see loader errors
sleep 8; lsof -p $(pgrep -n StitchoMR) | grep -E 'QtCore|libgeos' # every path must be inside YOUR copy
kill %1
spctl --assess --type execute "$APP" is expected to print rejected for your re-signed copy even when everything above is correct (see the Gatekeeper row in section 0); it does not mean the signature is broken, and the codesign --verify check above is the one that matters for a local copy. If macOS reports the copy as damaged or from an unidentified developer, you probably left com.apple.quarantine on it: run xattr -cr "$APP" again, or approve the app in System Settings > Privacy & Security.
For debugging your own library modifications you may inspect or modify the StitchoMR executable and files only as far as needed; the End User License Agreement presented with the application states that it does not restrict this.
7. What was tested, and what was not (2026-10-29)
Tested on a scratch copy, QT_QPA_PLATFORM=offscreen, clean HOME, with the original signed copy as baseline (baseline: --self-test exits 1 with status failed/OutputDenied because it is signed out, which is the expected result; --opencv-smoke exits 0; the GUI process stays alive and maps the bundle's Qt and GEOS libraries):
- Real rebuild of GEOS 3.14.1 from the upstream tarball (with one marker string changed in
GEOSversion()), installed per sections 4 and 5 into the copy: verify OK, same self-test and smoke results as the baseline, libgeos and libgeos_c both mapped from inside the copy. - Qt replacement: not a rebuild. QtGui was byte-patched (one same-length message string changed) and the copy re-signed per section 5: the patched message appeared on stderr, proving the modified Qt library is the code that runs; all other Qt libraries and plugins were re-signed unchanged. Whether a genuinely rebuilt Qt 6.11.2 loads (build configuration, symbols, plugin ABI) was not tested.
- The failure cases in section 0.
Not tested: a Qt, PySide6 or Shiboken6 rebuild; mixing versions; macOS versions other than 27.0.1; Macs without Apple silicon; the interactive Gatekeeper approval flow; sign-in and licence checks of a re-signed copy (only the signed-out path ran, with identical results to the baseline; the stored-credential behaviour of a changed code identity is unknown); the full GUI beyond a 10 s offscreen start.
8. Known limits
- A re-signed copy has a different code identity. Stored sign-in data or Keychain items created by the signed app may not be available to it. This is a consequence of local re-signing, not a restriction added to open-source rights; Darwich has not checked whether a re-signed copy can complete a licence check.
- The re-signed copy is not notarized and is not an official build; support may not cover it.
- The Qt Company's build configuration for its binary packages is not part of upstream's source release.
Support: support@stitchomr.com.