mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-14 18:28:11 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21ae6379ac | ||
|
|
63e06eea40 | ||
|
|
5890b52da6 |
No files matched your search
@@ -24,7 +24,7 @@ import tomlkit
|
|||||||
AppName = "SaraSuite"
|
AppName = "SaraSuite"
|
||||||
# Compared with the latest Gitea release to offer self-updates (SelfUpdate.py).
|
# Compared with the latest Gitea release to offer self-updates (SelfUpdate.py).
|
||||||
# Bump it, then run Packaging/Release.py, to publish a new SaraSuite.
|
# Bump it, then run Packaging/Release.py, to publish a new SaraSuite.
|
||||||
AppVersion = "1.2.2"
|
AppVersion = "1.2.3"
|
||||||
IsFrozen = bool(getattr(sys, "frozen", False)) # running from the AppImage / .exe
|
IsFrozen = bool(getattr(sys, "frozen", False)) # running from the AppImage / .exe
|
||||||
IsWindows = sys.platform == "win32"
|
IsWindows = sys.platform == "win32"
|
||||||
Platform = "Windows" if IsWindows else "Linux" # matches manifests' Platforms / Platform
|
Platform = "Windows" if IsWindows else "Linux" # matches manifests' Platforms / Platform
|
||||||
|
|||||||
@@ -38,6 +38,23 @@ else:
|
|||||||
_MenuLabel = "Start menu" if IsWindows else "app menu"
|
_MenuLabel = "Start menu" if IsWindows else "app menu"
|
||||||
|
|
||||||
|
|
||||||
|
def _KeepFallbackLibraries() -> None:
|
||||||
|
"""Linux AppImage: copy the bundled libsecret (and what it needs) to
|
||||||
|
~/.local/share/SaraSuite/Libraries, for FletIdentity.py to give the Flet
|
||||||
|
client of every installed program on systems without it."""
|
||||||
|
Bundled = Path(getattr(sys, "_MEIPASS", "")) / "FallbackLibs"
|
||||||
|
if IsWindows or not Bundled.is_dir():
|
||||||
|
return
|
||||||
|
Kept = DataDir / "Libraries"
|
||||||
|
try:
|
||||||
|
Kept.mkdir(parents=True, exist_ok=True)
|
||||||
|
for Library in Bundled.iterdir():
|
||||||
|
if not (Kept / Library.name).exists() or (Kept / Library.name).stat().st_size != Library.stat().st_size:
|
||||||
|
shutil.copy2(Library, Kept / Library.name)
|
||||||
|
except OSError:
|
||||||
|
pass # only a fallback
|
||||||
|
|
||||||
|
|
||||||
def _LoadFletIdentity():
|
def _LoadFletIdentity():
|
||||||
# Lives in Programs/ (bundled as data) because installed programs get a
|
# Lives in Programs/ (bundled as data) because installed programs get a
|
||||||
# copy of the same file next to them.
|
# copy of the same file next to them.
|
||||||
@@ -324,6 +341,7 @@ def Main(Page: ft.Page) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
_KeepFallbackLibraries() # also before a command (an install from the command line)
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
if IsWindows and getattr(sys, "frozen", False):
|
if IsWindows and getattr(sys, "frozen", False):
|
||||||
# The .exe is built windowed (no console of its own); write the
|
# The .exe is built windowed (no console of its own); write the
|
||||||
|
|||||||
@@ -18,8 +18,17 @@ podman run --rm --security-opt label=disable -v "$PWD:/src" -w /src \
|
|||||||
quay.io/pypa/manylinux_2_28_x86_64 bash -euo pipefail -c '
|
quay.io/pypa/manylinux_2_28_x86_64 bash -euo pipefail -c '
|
||||||
uv venv --quiet --allow-existing --managed-python --python 3.14 build/Container/venv
|
uv venv --quiet --allow-existing --managed-python --python 3.14 build/Container/venv
|
||||||
uv pip install --quiet --python build/Container/venv -r requirements.txt flet-desktop pyinstaller
|
uv pip install --quiet --python build/Container/venv -r requirements.txt flet-desktop pyinstaller
|
||||||
|
# The Flet window needs libsecret, which some systems lack: bundled as a
|
||||||
|
# fallback (RestoreLibraryPath.py uses only what the system lacks).
|
||||||
|
dnf -y -q install libsecret >/dev/null
|
||||||
|
mkdir -p build/FallbackLibs
|
||||||
|
for Library in libsecret-1.so.0 libgcrypt.so.20 libgpg-error.so.0; do cp -L /usr/lib64/$Library build/FallbackLibs/; done
|
||||||
|
# Each finds the others beside it, wherever they are put (a client lib folder).
|
||||||
|
patchelf --set-rpath "\$ORIGIN" build/FallbackLibs/libsecret-1.so.0 build/FallbackLibs/libgcrypt.so.20
|
||||||
build/Container/venv/bin/pyinstaller --noconfirm --clean --log-level WARN \
|
build/Container/venv/bin/pyinstaller --noconfirm --clean --log-level WARN \
|
||||||
--onefile --windowed --name SaraSuite --collect-data flet \
|
--onefile --windowed --name SaraSuite --collect-data flet \
|
||||||
|
--runtime-hook "$PWD/Packaging/RestoreLibraryPath.py" \
|
||||||
|
--add-data "$PWD/build/FallbackLibs:FallbackLibs" \
|
||||||
--add-data "$PWD/assets:assets" --add-data "$PWD/Programs:Programs" \
|
--add-data "$PWD/assets:assets" --add-data "$PWD/Programs:Programs" \
|
||||||
--distpath build/PyInstaller/dist --workpath build/PyInstaller/work --specpath build/PyInstaller \
|
--distpath build/PyInstaller/dist --workpath build/PyInstaller/work --specpath build/PyInstaller \
|
||||||
"$PWD/Main.py"'
|
"$PWD/Main.py"'
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# PyInstaller runtime hook. The frozen app runs with LD_LIBRARY_PATH pointing
|
||||||
|
# at its bundled libraries, and programs it starts inherit that: Flet's
|
||||||
|
# desktop client then loads the bundled (older) libstdc++ instead of the
|
||||||
|
# system's and won't start ("GLIBCXX_3.4.29 not found"). Give them the
|
||||||
|
# original value back; the app itself has already loaded what it needs.
|
||||||
|
#
|
||||||
|
# Flet's window also needs libsecret, which isn't part of GTK and some
|
||||||
|
# systems don't install ("libsecret-1.so.0: cannot open shared object file").
|
||||||
|
# The build bundles it in FallbackLibs, with libgcrypt and libgpg-error, which
|
||||||
|
# it needs; the ones this system lacks (only those) are offered to what the
|
||||||
|
# app starts, after the original path.
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def SystemHas(Name):
|
||||||
|
"""Whether the system itself has a library: asked of its linker cache
|
||||||
|
and standard folders, not dlopen, which would also find this bundle's
|
||||||
|
own copies (PyInstaller adds what bundled libraries need)."""
|
||||||
|
for Folder in ("/usr/lib64", "/usr/lib", "/lib64", "/lib", "/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu"):
|
||||||
|
if os.path.exists(os.path.join(Folder, Name)):
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
Cache = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True, timeout=10,
|
||||||
|
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"}).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return False
|
||||||
|
return any(Line.strip().startswith(Name + " ") for Line in Cache.splitlines())
|
||||||
|
|
||||||
|
Paths = [os.environ["LD_LIBRARY_PATH_ORIG"]] if os.environ.get("LD_LIBRARY_PATH_ORIG") else []
|
||||||
|
|
||||||
|
Bundle = getattr(sys, "_MEIPASS", "")
|
||||||
|
Fallback = os.path.join(Bundle, "FallbackLibs")
|
||||||
|
if sys.platform.startswith("linux") and os.path.isdir(Fallback):
|
||||||
|
InUse = os.path.join(Bundle, "FallbackLibsInUse")
|
||||||
|
os.makedirs(InUse, exist_ok=True)
|
||||||
|
for Name in ("libgpg-error.so.0", "libgcrypt.so.20", "libsecret-1.so.0"):
|
||||||
|
if not SystemHas(Name) and not os.path.lexists(os.path.join(InUse, Name)):
|
||||||
|
os.symlink(os.path.join(Fallback, Name), os.path.join(InUse, Name))
|
||||||
|
if os.listdir(InUse):
|
||||||
|
Paths.append(InUse)
|
||||||
|
|
||||||
|
if Paths:
|
||||||
|
os.environ["LD_LIBRARY_PATH"] = os.pathsep.join(Paths)
|
||||||
|
else:
|
||||||
|
os.environ.pop("LD_LIBRARY_PATH", None)
|
||||||
@@ -18,13 +18,51 @@ Used two ways:
|
|||||||
case Flet just uses its normal client.
|
case Flet just uses its normal client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
OriginalId = b"com.appveyor.flet"
|
OriginalId = b"com.appveyor.flet"
|
||||||
|
# Flet's window needs libsecret, which isn't part of GTK and some systems don't
|
||||||
|
# install. SaraSuite keeps copies of it and what it needs (from its AppImage)
|
||||||
|
# here; AddMissingLibraries puts the ones this system lacks in a fallback-libs
|
||||||
|
# folder beside the client copy, which the program's Launch.sh adds to
|
||||||
|
# LD_LIBRARY_PATH (a plugin loads libsecret, so the client's own lib folder,
|
||||||
|
# its RUNPATH, wouldn't be searched for it).
|
||||||
|
LibrariesDir = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share") / "SaraSuite" / "Libraries"
|
||||||
|
FallbackLibraries = ("libgpg-error.so.0", "libgcrypt.so.20", "libsecret-1.so.0")
|
||||||
CopiesDir = Path.home() / ".cache" / "SaraSuite" / "FletClient"
|
CopiesDir = Path.home() / ".cache" / "SaraSuite" / "FletClient"
|
||||||
|
|
||||||
|
|
||||||
|
def _SystemHas(Name: str) -> bool:
|
||||||
|
"""Whether the system has a library (its standard folders and linker
|
||||||
|
cache; the same check as the AppImages' RestoreLibraryPath.py)."""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
for Folder in ("/usr/lib64", "/usr/lib", "/lib64", "/lib", "/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu"):
|
||||||
|
if (Path(Folder) / Name).exists():
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
Cache = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True, timeout=10,
|
||||||
|
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"}).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return False
|
||||||
|
return any(Line.strip().startswith(Name + " ") for Line in Cache.splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
def AddMissingLibraries(Folder: Path) -> None:
|
||||||
|
"""Copies SaraSuite's fallback libraries the system doesn't have into
|
||||||
|
Folder (created only if one is needed)."""
|
||||||
|
if not sys.platform.startswith("linux") or not LibrariesDir.is_dir():
|
||||||
|
return
|
||||||
|
for Name in FallbackLibraries:
|
||||||
|
Source = LibrariesDir / Name
|
||||||
|
if Source.is_file() and not (Folder / Name).exists() and not _SystemHas(Name):
|
||||||
|
Folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(Source, Folder / Name)
|
||||||
|
|
||||||
|
|
||||||
def ClientFor(AppId: str) -> str:
|
def ClientFor(AppId: str) -> str:
|
||||||
"""Folder of a client copy whose app ID is AppId ("" if not possible).
|
"""Folder of a client copy whose app ID is AppId ("" if not possible).
|
||||||
AppId needs a dot (a GTK rule) and at most 17 characters, the length of
|
AppId needs a dot (a GTK rule) and at most 17 characters, the length of
|
||||||
@@ -45,6 +83,7 @@ def ClientFor(AppId: str) -> str:
|
|||||||
if Binary.count(b"\x00" + OriginalId + b"\x00") != 1:
|
if Binary.count(b"\x00" + OriginalId + b"\x00") != 1:
|
||||||
return "" # not the client layout this was written for
|
return "" # not the client layout this was written for
|
||||||
|
|
||||||
|
AddMissingLibraries(Copy.parent / "fallback-libs")
|
||||||
Target = Copy / "flet"
|
Target = Copy / "flet"
|
||||||
if not (Target.exists() and Target.read_bytes() == Patched):
|
if not (Target.exists() and Target.read_bytes() == Patched):
|
||||||
Copy.mkdir(parents=True, exist_ok=True)
|
Copy.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -64,4 +103,16 @@ def ClientFor(AppId: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(ClientFor(sys.argv[1]) if len(sys.argv) > 1 else "")
|
# Only the folder goes to stdout, which the launcher reads: Flet's own
|
||||||
|
# messages ("Preparing Flet ... for the first use", written straight to
|
||||||
|
# the file descriptor) go to stderr.
|
||||||
|
sys.stdout.flush()
|
||||||
|
Stdout = os.dup(1)
|
||||||
|
os.dup2(2, 1)
|
||||||
|
try:
|
||||||
|
Folder = ClientFor(sys.argv[1]) if len(sys.argv) > 1 else ""
|
||||||
|
finally:
|
||||||
|
sys.stdout.flush()
|
||||||
|
os.dup2(Stdout, 1)
|
||||||
|
os.close(Stdout)
|
||||||
|
print(Folder)
|
||||||
@@ -4,5 +4,9 @@
|
|||||||
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
|
# Libraries Flet's window needs that this system lacks (libsecret), from
|
||||||
|
# SaraSuite: FletIdentity.py puts them beside the client copy.
|
||||||
|
Fallback="$FLET_VIEW_PATH/../fallback-libs"
|
||||||
|
[ -n "$FLET_VIEW_PATH" ] && [ -d "$Fallback" ] && export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$Fallback"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -4,5 +4,9 @@
|
|||||||
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
|
# Libraries Flet's window needs that this system lacks (libsecret), from
|
||||||
|
# SaraSuite: FletIdentity.py puts them beside the client copy.
|
||||||
|
Fallback="$FLET_VIEW_PATH/../fallback-libs"
|
||||||
|
[ -n "$FLET_VIEW_PATH" ] && [ -d "$Fallback" ] && export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$Fallback"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -4,5 +4,9 @@
|
|||||||
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
|
# Libraries Flet's window needs that this system lacks (libsecret), from
|
||||||
|
# SaraSuite: FletIdentity.py puts them beside the client copy.
|
||||||
|
Fallback="$FLET_VIEW_PATH/../fallback-libs"
|
||||||
|
[ -n "$FLET_VIEW_PATH" ] && [ -d "$Fallback" ] && export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$Fallback"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -6,5 +6,9 @@ export FLET_DESKTOP_FLAVOR=full
|
|||||||
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
|
# Libraries Flet's window needs that this system lacks (libsecret), from
|
||||||
|
# SaraSuite: FletIdentity.py puts them beside the client copy.
|
||||||
|
Fallback="$FLET_VIEW_PATH/../fallback-libs"
|
||||||
|
[ -n "$FLET_VIEW_PATH" ] && [ -d "$Fallback" ] && export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$Fallback"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -4,5 +4,9 @@
|
|||||||
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
|
# Libraries Flet's window needs that this system lacks (libsecret), from
|
||||||
|
# SaraSuite: FletIdentity.py puts them beside the client copy.
|
||||||
|
Fallback="$FLET_VIEW_PATH/../fallback-libs"
|
||||||
|
[ -n "$FLET_VIEW_PATH" ] && [ -d "$Fallback" ] && export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$Fallback"
|
||||||
cd "{AppDir}/Client" || exit 1
|
cd "{AppDir}/Client" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
Reference in new issue
Block a user