mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-15 02:33:17 +02:00
Per-program Flet client app IDs so the dock shows the right icons
This commit is contained in:
1 parent
19bbad33a4
commit
56f10885de
12 files changed
+114
-18
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
"""SaraSuite entry point: installs, updates and removes Sara programs.
|
"""SaraSuite entry point: installs, updates and removes Sara programs.
|
||||||
With arguments it runs the command-line mode instead (see Cli.py)."""
|
With arguments it runs the command-line mode instead (see Cli.py)."""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
@@ -11,7 +12,7 @@ import flet as ft
|
|||||||
|
|
||||||
from Core import (
|
from Core import (
|
||||||
AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, ChipButtonStyle, DataDir,
|
AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, ChipButtonStyle, DataDir,
|
||||||
DataHome, EffectiveSettings, LoadConfig, Palette,
|
DataHome, EffectiveSettings, LoadConfig, ManifestsDir, Palette,
|
||||||
)
|
)
|
||||||
from Dialogs import ShowInstallDialog, ShowOptionsDialog, ShowRemoveDialog, ShowUpdateDialog
|
from Dialogs import ShowInstallDialog, ShowOptionsDialog, ShowRemoveDialog, ShowUpdateDialog
|
||||||
from Installer import Apply, Fetch, FetchCommit, GetStatus, PlanConfigs, PlanRemoval, Remove, SourceVersion
|
from Installer import Apply, Fetch, FetchCommit, GetStatus, PlanConfigs, PlanRemoval, Remove, SourceVersion
|
||||||
@@ -27,6 +28,15 @@ _MenuEntry = DataHome / "applications" / "SaraSuite.desktop"
|
|||||||
_MenuIcon = DataDir / "SaraSuite.png"
|
_MenuIcon = DataDir / "SaraSuite.png"
|
||||||
|
|
||||||
|
|
||||||
|
def _LoadFletIdentity():
|
||||||
|
# Lives in Programs/ (bundled as data) because installed programs get a
|
||||||
|
# copy of the same file next to them.
|
||||||
|
Spec = importlib.util.spec_from_file_location("FletIdentity", ManifestsDir / "FletIdentity.py")
|
||||||
|
Module = importlib.util.module_from_spec(Spec)
|
||||||
|
Spec.loader.exec_module(Module)
|
||||||
|
return Module
|
||||||
|
|
||||||
|
|
||||||
def _SetMenuEntry(Enabled: bool) -> None:
|
def _SetMenuEntry(Enabled: bool) -> None:
|
||||||
if not Enabled:
|
if not Enabled:
|
||||||
_MenuEntry.unlink(missing_ok=True)
|
_MenuEntry.unlink(missing_ok=True)
|
||||||
@@ -39,7 +49,7 @@ def _SetMenuEntry(Enabled: bool) -> None:
|
|||||||
"[Desktop Entry]\nType=Application\nName=SaraSuite\n"
|
"[Desktop Entry]\nType=Application\nName=SaraSuite\n"
|
||||||
"Comment=Install, update and remove Sara programs\n"
|
"Comment=Install, update and remove Sara programs\n"
|
||||||
f"Exec={_AppImage}\nIcon={_MenuIcon}\nTerminal=false\n"
|
f"Exec={_AppImage}\nIcon={_MenuIcon}\nTerminal=false\n"
|
||||||
f"Categories=System;\nStartupNotify=true\nStartupWMClass={AppName}\n"
|
f"Categories=System;\nStartupNotify=true\nStartupWMClass=srhtnin.{AppName}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -234,7 +244,8 @@ if __name__ == "__main__":
|
|||||||
from Cli import Run
|
from Cli import Run
|
||||||
|
|
||||||
sys.exit(Run(sys.argv[1:]))
|
sys.exit(Run(sys.argv[1:]))
|
||||||
# The window's app ID, so the desktop matches it to SaraSuite.desktop
|
# A Flet client copy with the window app ID srhtnin.SaraSuite, so the
|
||||||
# (icon, name) instead of showing it as Flet's generic "com.appveyor.flet".
|
# desktop matches the window to SaraSuite.desktop (StartupWMClass) instead
|
||||||
os.environ.setdefault("FLET_APP_ID", AppName)
|
# of showing it as "com.appveyor.flet". See Programs/FletIdentity.py.
|
||||||
|
os.environ.setdefault("FLET_VIEW_PATH", _LoadFletIdentity().ClientFor(f"srhtnin.{AppName}"))
|
||||||
ft.run(Main, assets_dir=str(AssetsDir))
|
ft.run(Main, assets_dir=str(AssetsDir))
|
||||||
@@ -30,7 +30,7 @@ Exec=SaraSuite
|
|||||||
Icon=SaraSuite
|
Icon=SaraSuite
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=System;
|
Categories=System;
|
||||||
StartupWMClass=SaraSuite
|
StartupWMClass=srhtnin.SaraSuite
|
||||||
DESKTOP
|
DESKTOP
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"""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 sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
OriginalId = b"com.appveyor.flet"
|
||||||
|
CopiesDir = Path.home() / ".cache" / "SaraSuite" / "FletClient"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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__":
|
||||||
|
print(ClientFor(sys.argv[1]) if len(sys.argv) > 1 else "")
|
||||||
@@ -65,3 +65,8 @@ Note = "thumbnail cache"
|
|||||||
Path = "{ConfigHome}/SaraFem"
|
Path = "{ConfigHome}/SaraFem"
|
||||||
Kind = "Config"
|
Kind = "Config"
|
||||||
Note = "old pre-SaraSuite config location"
|
Note = "old pre-SaraSuite config location"
|
||||||
|
|
||||||
|
# Shared with the other Flet programs; see Programs/FletIdentity.py.
|
||||||
|
[[Templates]]
|
||||||
|
Source = "../../FletIdentity.py"
|
||||||
|
Target = "{ProgramDir}/FletIdentity.py"
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
||||||
# The window's app ID, so the desktop matches it to {Name}.desktop (icon, name)
|
# A Flet client copy with the window app ID srhtnin.{Name}, so the desktop
|
||||||
# instead of showing every Flet program as "com.appveyor.flet".
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
export FLET_APP_ID="{Name}"
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -8,4 +8,4 @@ Terminal=false
|
|||||||
Categories=System;FileManager;
|
Categories=System;FileManager;
|
||||||
MimeType=inode/directory;
|
MimeType=inode/directory;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
StartupWMClass={Name}
|
StartupWMClass=srhtnin.{Name}
|
||||||
@@ -25,3 +25,8 @@ Target = "{ApplicationsDir}/SaraSort.desktop"
|
|||||||
Path = "{ConfigDir}"
|
Path = "{ConfigDir}"
|
||||||
Kind = "Config"
|
Kind = "Config"
|
||||||
Note = "SaraSort's settings, flowchart layout and your templates"
|
Note = "SaraSort's settings, flowchart layout and your templates"
|
||||||
|
|
||||||
|
# Shared with the other Flet programs; see Programs/FletIdentity.py.
|
||||||
|
[[Templates]]
|
||||||
|
Source = "../../FletIdentity.py"
|
||||||
|
Target = "{ProgramDir}/FletIdentity.py"
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
||||||
# The window's app ID, so the desktop matches it to {Name}.desktop (icon, name)
|
# A Flet client copy with the window app ID srhtnin.{Name}, so the desktop
|
||||||
# instead of showing every Flet program as "com.appveyor.flet".
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
export FLET_APP_ID="{Name}"
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -7,4 +7,4 @@ Icon={AppDir}/assets/Icon.png
|
|||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Utility;
|
Categories=Utility;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
StartupWMClass={Name}
|
StartupWMClass=srhtnin.{Name}
|
||||||
@@ -35,3 +35,8 @@ Target = "{ApplicationsDir}/SaraTag.desktop"
|
|||||||
Path = "{ConfigDir}"
|
Path = "{ConfigDir}"
|
||||||
Kind = "Config"
|
Kind = "Config"
|
||||||
Note = "SaraTag's settings, Records, Libraries, history and Lexicons (Books next to your files are never touched)"
|
Note = "SaraTag's settings, Records, Libraries, history and Lexicons (Books next to your files are never touched)"
|
||||||
|
|
||||||
|
# Shared with the other Flet programs; see Programs/FletIdentity.py.
|
||||||
|
[[Templates]]
|
||||||
|
Source = "../../FletIdentity.py"
|
||||||
|
Target = "{ProgramDir}/FletIdentity.py"
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
# Written by SaraSuite. Runs from the app folder so Flet finds assets/.
|
||||||
# The window's app ID, so the desktop matches it to {Name}.desktop (icon, name)
|
# A Flet client copy with the window app ID srhtnin.{Name}, so the desktop
|
||||||
# instead of showing every Flet program as "com.appveyor.flet".
|
# matches the window to {Name}.desktop (StartupWMClass) instead of showing it
|
||||||
export FLET_APP_ID="{Name}"
|
# as "com.appveyor.flet". Empty if that fails: Flet then uses its own client.
|
||||||
|
export FLET_VIEW_PATH="$("{Python}" "{ProgramDir}/FletIdentity.py" "srhtnin.{Name}")"
|
||||||
cd "{AppDir}" || exit 1
|
cd "{AppDir}" || exit 1
|
||||||
exec "{Python}" Main.py "$@"
|
exec "{Python}" Main.py "$@"
|
||||||
@@ -7,4 +7,4 @@ Icon={AppDir}/assets/Icon.png
|
|||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Utility;Graphics;
|
Categories=Utility;Graphics;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
StartupWMClass={Name}
|
StartupWMClass=srhtnin.{Name}
|
||||||
Reference in new issue
Block a user