Compare commits
2 Commits
perf/load-
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 2515213b14 | |||
| 0376b77494 |
Binary file not shown.
@@ -1 +1 @@
|
||||
0.2
|
||||
0.2.1
|
||||
|
||||
95
src/testium/main_win/desktop_integration.py
Normal file
95
src/testium/main_win/desktop_integration.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Install a desktop entry + icon under the user's data dir so desktop shells
|
||||
show the testium icon in the task bar / dock.
|
||||
|
||||
On a native Wayland session GNOME takes a window's task-bar icon from the
|
||||
``.desktop`` file whose name (or ``StartupWMClass``) matches the window
|
||||
``app_id`` — ``QGuiApplication.setWindowIcon`` is ignored there. The portable
|
||||
channels (source checkout, PyInstaller binary, AppImage) install no system
|
||||
desktop file, so we drop an idempotent one in ``~/.local/share``. The window
|
||||
``app_id`` is set to ``testium`` (see ``QApplication.setDesktopFileName`` in
|
||||
``testium_win``), which is exactly this file's base name.
|
||||
|
||||
Flatpak ships its own ``org.testium.Testium.desktop`` and keeps its own app id,
|
||||
so the caller skips this integration there.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
|
||||
# Must match QApplication.setDesktopFileName(...) for the GUI, and is used as
|
||||
# both the desktop-file base name and the StartupWMClass.
|
||||
APP_ID = "testium"
|
||||
|
||||
|
||||
def _launch_command():
|
||||
"""Best-effort Exec= for the menu entry. Not needed for icon matching, but
|
||||
makes the entry actually launchable when possible."""
|
||||
appimage = os.environ.get("APPIMAGE")
|
||||
if appimage:
|
||||
return f'"{appimage}"'
|
||||
if getattr(sys, "frozen", False):
|
||||
return f'"{os.path.abspath(sys.executable)}"'
|
||||
argv0 = os.path.abspath(sys.argv[0]) if sys.argv and sys.argv[0] else ""
|
||||
if argv0 and os.path.exists(argv0):
|
||||
return f'"{os.path.abspath(sys.executable)}" "{argv0}"'
|
||||
return f'"{os.path.abspath(sys.executable)}" -m testium'
|
||||
|
||||
|
||||
def ensure_desktop_entry():
|
||||
"""Create (or refresh) ~/.local/share icon + desktop entry. Best-effort:
|
||||
any failure is swallowed so it can never take the GUI down.
|
||||
|
||||
Freedesktop-only: a no-op off Linux (Windows / macOS use the window icon)."""
|
||||
if not sys.platform.startswith("linux"):
|
||||
return
|
||||
try:
|
||||
data_home = os.environ.get("XDG_DATA_HOME") or os.path.join(
|
||||
os.path.expanduser("~"), ".local", "share"
|
||||
)
|
||||
icon_dir = os.path.join(data_home, "icons", "hicolor", "256x256", "apps")
|
||||
app_dir = os.path.join(data_home, "applications")
|
||||
icon_path = os.path.join(icon_dir, f"{APP_ID}.png")
|
||||
desktop_path = os.path.join(app_dir, f"{APP_ID}.desktop")
|
||||
|
||||
os.makedirs(icon_dir, exist_ok=True)
|
||||
os.makedirs(app_dir, exist_ok=True)
|
||||
|
||||
# Icon: render the bundled Qt resource to a PNG once. Requires a live
|
||||
# QGuiApplication (the caller creates it before calling us).
|
||||
if not os.path.isfile(icon_path):
|
||||
pixmap = QPixmap(u":/black/testium_logo.png")
|
||||
if not pixmap.isNull():
|
||||
pixmap = pixmap.scaled(
|
||||
256, 256,
|
||||
Qt.AspectRatioMode.KeepAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
pixmap.save(icon_path, "PNG")
|
||||
|
||||
# Absolute Icon= path so the shell resolves it without an icon-cache
|
||||
# refresh; StartupWMClass lets X11 / XWayland match too.
|
||||
desktop = (
|
||||
"[Desktop Entry]\n"
|
||||
"Type=Application\n"
|
||||
"Name=Testium\n"
|
||||
"Comment=Test sequencer\n"
|
||||
f"Icon={icon_path}\n"
|
||||
f"Exec={_launch_command()} %f\n"
|
||||
"Terminal=false\n"
|
||||
f"StartupWMClass={APP_ID}\n"
|
||||
"Categories=Utility;Development;\n"
|
||||
)
|
||||
|
||||
# Write only when missing or changed, to avoid needless menu churn.
|
||||
current = None
|
||||
if os.path.isfile(desktop_path):
|
||||
with open(desktop_path, "r") as fh:
|
||||
current = fh.read()
|
||||
if current != desktop:
|
||||
with open(desktop_path, "w") as fh:
|
||||
fh.write(desktop)
|
||||
except Exception:
|
||||
# Desktop integration is a nicety, never a hard requirement.
|
||||
pass
|
||||
@@ -678,6 +678,24 @@ def MainWin(
|
||||
debug=False,
|
||||
):
|
||||
app = QApplication(sys.argv)
|
||||
# Application identity so desktop shells (GNOME, ...) show the testium
|
||||
# icon in the task bar / dock instead of a generic one. On Wayland this
|
||||
# sets the surface app_id; on X11/XWayland it sets WM_CLASS, so the window
|
||||
# stops inheriting the launcher's class (e.g. "python3" under the AppImage,
|
||||
# which is what GNOME was keying the wrong icon off) and the window icon
|
||||
# below is used as the fallback. In Flatpak the id must be the Flatpak app
|
||||
# id so it matches the installed desktop file.
|
||||
app.setApplicationName("Testium")
|
||||
app.setApplicationDisplayName("Testium")
|
||||
app.setDesktopFileName(os.environ.get("FLATPAK_ID", "testium"))
|
||||
app.setWindowIcon(QIcon(u":/black/testium_logo.png"))
|
||||
# On native Wayland the task-bar icon comes from an installed desktop file
|
||||
# matched to the app_id, not from setWindowIcon(). Flatpak ships its own;
|
||||
# for the other Linux channels drop an idempotent one under ~/.local/share.
|
||||
# Windows / macOS use the window icon set above, so this is Linux-only.
|
||||
if sys.platform.startswith("linux") and not os.environ.get("FLATPAK_ID"):
|
||||
from main_win.desktop_integration import ensure_desktop_entry
|
||||
ensure_desktop_entry()
|
||||
ui = MainWindow(
|
||||
test_file,
|
||||
config_files,
|
||||
|
||||
Reference in New Issue
Block a user