Init
This commit is contained in:
108
ptlinux/desktop_entry.py
Normal file
108
ptlinux/desktop_entry.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Install a desktop entry that does not depend on the GUI session's PATH."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
EXECUTABLE_TOKEN = "@PTLINUX_EXECUTABLE@"
|
||||
ICON_TOKEN = "@PTLINUX_ICON@"
|
||||
|
||||
|
||||
class DesktopEntryError(ValueError):
|
||||
"""Raised when a safe desktop entry cannot be rendered."""
|
||||
|
||||
|
||||
def quote_exec_argument(value: str) -> str:
|
||||
"""Quote one Desktop Entry ``Exec`` argument without field-code expansion."""
|
||||
|
||||
if not value or any(character in value for character in ("\x00", "\n", "\r")):
|
||||
raise DesktopEntryError("desktop executable path contains invalid characters")
|
||||
escaped = value.replace("\\", "\\\\\\\\")
|
||||
escaped = escaped.replace('"', '\\\\"')
|
||||
escaped = escaped.replace("`", "\\\\`")
|
||||
escaped = escaped.replace("$", "\\\\$")
|
||||
escaped = escaped.replace("%", "%%")
|
||||
return f'"{escaped}"'
|
||||
|
||||
|
||||
def escape_icon_path(value: str) -> str:
|
||||
"""Escape one absolute Desktop Entry ``Icon`` path as a string value."""
|
||||
|
||||
if not value or any(character in value for character in ("\x00", "\n", "\r")):
|
||||
raise DesktopEntryError("desktop icon path contains invalid characters")
|
||||
return value.replace("\\", "\\\\")
|
||||
|
||||
|
||||
def render_desktop_entry(template: str, executable: Path, icon: Path) -> str:
|
||||
"""Replace launcher and icon tokens with validated absolute paths."""
|
||||
|
||||
if template.count(EXECUTABLE_TOKEN) != 1:
|
||||
raise DesktopEntryError("desktop template must contain exactly one executable token")
|
||||
if template.count(ICON_TOKEN) != 1:
|
||||
raise DesktopEntryError("desktop template must contain exactly one icon token")
|
||||
launcher = executable.expanduser()
|
||||
if not launcher.is_absolute():
|
||||
raise DesktopEntryError("desktop launcher path must be absolute")
|
||||
if not launcher.is_file() or not os.access(launcher, os.X_OK):
|
||||
raise DesktopEntryError(f"desktop launcher is missing or not executable: {launcher}")
|
||||
icon_path = icon.expanduser()
|
||||
if not icon_path.is_absolute():
|
||||
raise DesktopEntryError("desktop icon path must be absolute")
|
||||
if not icon_path.is_file():
|
||||
raise DesktopEntryError(f"desktop icon is missing: {icon_path}")
|
||||
rendered = template.replace(EXECUTABLE_TOKEN, quote_exec_argument(str(launcher)))
|
||||
rendered = rendered.replace(ICON_TOKEN, escape_icon_path(str(icon_path)))
|
||||
return rendered if rendered.endswith("\n") else rendered + "\n"
|
||||
|
||||
|
||||
def install_desktop_entry(
|
||||
template: Path,
|
||||
executable: Path,
|
||||
icon: Path,
|
||||
destination: Path,
|
||||
) -> None:
|
||||
"""Render and atomically install one user-level desktop entry."""
|
||||
|
||||
rendered = render_desktop_entry(
|
||||
template.read_text(encoding="utf-8"),
|
||||
executable,
|
||||
icon,
|
||||
)
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
|
||||
try:
|
||||
temporary.write_text(rendered, encoding="utf-8")
|
||||
temporary.chmod(0o644)
|
||||
temporary.replace(destination)
|
||||
except BaseException:
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="install the PristonTale EU desktop entry")
|
||||
parser.add_argument("template", type=Path)
|
||||
parser.add_argument("executable", type=Path)
|
||||
parser.add_argument("icon", type=Path)
|
||||
parser.add_argument("destination", type=Path)
|
||||
arguments = parser.parse_args(argv)
|
||||
try:
|
||||
install_desktop_entry(
|
||||
arguments.template,
|
||||
arguments.executable,
|
||||
arguments.icon,
|
||||
arguments.destination,
|
||||
)
|
||||
except (DesktopEntryError, OSError) as exc:
|
||||
print(f"Could not install desktop shortcut: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user