mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-14 02:28:11 +02:00
119 lines
5.1 KiB
Python
119 lines
5.1 KiB
Python
"""Give a Flet program's window its own app ID on Linux, so the desktop can
|
|
match it to the program's .desktop entry (icon, name) instead of showing
|
|
every Flet program as "com.appveyor.flet" with a generic icon.
|
|
|
|
The Flet client's ID is a string built into its small launcher binary
|
|
(`flet`, ~24 KB); the client sets it as both its GTK application ID and its
|
|
process name, so Flet's own FLET_APP_ID (which only changes argv[0]) can't
|
|
override it, on Wayland or X11. Instead this makes a per-program copy of the
|
|
client in ~/.cache/SaraSuite/FletClient/ with that string replaced; the big
|
|
parts (lib/, data/, share/) are symlinks to the original, so a copy costs
|
|
~24 KB. Pointing FLET_VIEW_PATH at it makes Flet use the copy.
|
|
|
|
Used two ways:
|
|
- SaraSuite imports it and calls ClientFor() before ft.run.
|
|
- Installed programs' launchers run it with their own venv's python:
|
|
export FLET_VIEW_PATH="$(python FletIdentity.py srhtnin.SaraFem)"
|
|
It prints the copy's folder, or nothing if anything goes wrong, in which
|
|
case Flet just uses its normal client.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
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"
|
|
|
|
|
|
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:
|
|
"""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
|
|
the original it replaces in place."""
|
|
try:
|
|
Encoded = AppId.encode("ascii")
|
|
if "." not in AppId or not 0 < len(Encoded) <= len(OriginalId):
|
|
return ""
|
|
from flet_desktop import ensure_client_cached # the program's own Flet version
|
|
|
|
Original = Path(ensure_client_cached()) / "flet"
|
|
Binary = (Original / "flet").read_bytes()
|
|
Copy = CopiesDir / f"{Original.parent.name}-{AppId}" / "flet"
|
|
Patched = Binary.replace(
|
|
b"\x00" + OriginalId + b"\x00",
|
|
b"\x00" + Encoded.ljust(len(OriginalId), b"\x00") + b"\x00",
|
|
)
|
|
if Binary.count(b"\x00" + OriginalId + b"\x00") != 1:
|
|
return "" # not the client layout this was written for
|
|
|
|
AddMissingLibraries(Copy.parent / "fallback-libs")
|
|
Target = Copy / "flet"
|
|
if not (Target.exists() and Target.read_bytes() == Patched):
|
|
Copy.mkdir(parents=True, exist_ok=True)
|
|
for Part in ("lib", "data", "share"):
|
|
Link = Copy / Part
|
|
if Link.is_symlink() or Link.exists():
|
|
Link.unlink()
|
|
if (Original / Part).exists():
|
|
Link.symlink_to(Original / Part)
|
|
Temporary = Target.with_suffix(".new")
|
|
Temporary.write_bytes(Patched)
|
|
Temporary.chmod(0o755)
|
|
Temporary.replace(Target)
|
|
return str(Copy)
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 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)
|