mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Match the legacy entry against this checkout's desktop-run.sh instead of any desktop-run.sh, and delete the legacy icon files only after that entry or the old artwork proves ownership. Quote and escape Exec arguments as the Desktop Entry specification requires, so a checkout path with reserved characters no longer produces an invalid entry, and escape the Path value. Use StartupWMClass=RiotClientServices in both entries: Darling derives WM_CLASS from -[NSProcessInfo processName], the basename of argv[0], and the launcher starts .../RiotClientServices. "Riot Client" is only the bundle display name, so the checkout entry never matched the window. Both entries now share Name/GenericName/Comment/Icon/Keywords/Categories and a test keeps them in sync; Terminal stays different because the checkout entry logs to build/desktop-launch.log while the packaged entry runs the session in a terminal. Also drop the stray top-level icon copies, handle dangling legacy symlinks, name the icon for assistive technology, document the alias and menu entry, and cover the helper with disposable-HOME tests.
158 lines
7.0 KiB
Python
158 lines
7.0 KiB
Python
"""Verify the desktop-entry helper against a disposable HOME."""
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
HELPER = ROOT / 'tools/desktop-entry.py'
|
|
FLAKE = ROOT / 'flake.nix'
|
|
OLD_SVG = '<svg><linearGradient id="goldGrad"/><path d="M172 108 L242 108"/></svg>'
|
|
IDENTITY_KEYS = ('Name', 'GenericName', 'Comment', 'Icon', 'Keywords', 'Categories', 'StartupWMClass')
|
|
|
|
|
|
def parse_entry(text):
|
|
entry = {}
|
|
for line in text.splitlines():
|
|
if '=' in line and not line.startswith(('#', '[')):
|
|
key, value = line.split('=', 1)
|
|
entry[key] = value
|
|
return entry
|
|
|
|
|
|
class DesktopEntryTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory(prefix='desktop entry ', dir=os.environ.get('TMPDIR'))
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
self.home = self.root / 'home'
|
|
self.home.mkdir()
|
|
|
|
def project(self, name='project'):
|
|
project = self.root / name
|
|
project.mkdir(parents=True)
|
|
shutil.copy2(ROOT / 'darling-lol.svg', project / 'darling-lol.svg')
|
|
shutil.copy2(ROOT / 'darling-lol.png', project / 'darling-lol.png')
|
|
return project
|
|
|
|
def install(self, project):
|
|
script = project / 'desktop-entry.py'
|
|
script.write_text(HELPER.read_text()
|
|
.replace('@PROJECT_ROOT@', str(project))
|
|
.replace('@HOST_HOME@', str(self.home)))
|
|
return subprocess.run([sys.executable, str(script)], capture_output=True, text=True,
|
|
env=dict(os.environ, HOME=str(self.home)), check=False)
|
|
|
|
def entry(self):
|
|
return self.home / '.local/share/applications/darling-launcher.desktop'
|
|
|
|
def icons(self):
|
|
base = self.home / '.local/share/icons'
|
|
return base / 'hicolor/scalable/apps/darling-launcher.svg', base / 'hicolor/128x128/apps/darling-launcher.png'
|
|
|
|
def test_installs_neutral_entry_and_icons(self):
|
|
project = self.project()
|
|
result = self.install(project)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
text = self.entry().read_text()
|
|
self.assertIn('Name=Darling Launcher\n', text)
|
|
self.assertIn('Icon=darling-launcher\n', text)
|
|
self.assertIn(f'Path={project}\n', text)
|
|
for field in ('Name', 'Comment', 'GenericName', 'Keywords'):
|
|
line = next(line for line in text.splitlines() if line.startswith(field + '='))
|
|
for brand in ('League', 'LoL', 'Riot', 'Vanguard', 'anti-cheat'):
|
|
self.assertNotIn(brand.lower(), line.lower())
|
|
svg, png = self.icons()
|
|
self.assertTrue(svg.is_file())
|
|
self.assertTrue(png.is_file())
|
|
self.assertEqual(png.read_bytes(), (project / 'darling-lol.png').read_bytes())
|
|
|
|
def test_preserves_entries_and_icons_it_did_not_create(self):
|
|
project = self.project()
|
|
other = self.root / 'elsewhere'
|
|
(self.home / '.local/share/applications').mkdir(parents=True)
|
|
legacy_entry = self.home / '.local/share/applications/league-of-legends.desktop'
|
|
legacy_entry.write_text(f'[Desktop Entry]\nExec={other}/scripts/desktop-run.sh\n')
|
|
(self.home / 'Desktop').mkdir()
|
|
desktop_entry = self.home / 'Desktop/league-of-legends.desktop'
|
|
desktop_entry.write_text(f'[Desktop Entry]\nExec={other}/scripts/desktop-run.sh\n')
|
|
icons = self.home / '.local/share/icons'
|
|
icons.mkdir(parents=True)
|
|
(icons / 'league-of-legends.png').write_text('user icon')
|
|
result = self.install(project)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertTrue(legacy_entry.is_file())
|
|
self.assertTrue(desktop_entry.is_file())
|
|
self.assertEqual((icons / 'league-of-legends.png').read_text(), 'user icon')
|
|
self.assertTrue(self.entry().is_file())
|
|
|
|
def test_removes_legacy_artifacts_it_created(self):
|
|
project = self.project()
|
|
applications = self.home / '.local/share/applications'
|
|
applications.mkdir(parents=True)
|
|
owned = f'[Desktop Entry]\nExec="{project}/scripts/desktop-run.sh"\n'
|
|
legacy_entry = applications / 'league-of-legends.desktop'
|
|
legacy_entry.write_text(owned)
|
|
(self.home / 'Desktop').mkdir()
|
|
desktop_link = self.home / 'Desktop/league-of-legends.desktop'
|
|
desktop_link.symlink_to(project / 'scripts/desktop-run.sh')
|
|
icons = self.home / '.local/share/icons'
|
|
scalable = icons / 'hicolor/scalable/apps'
|
|
legacy_png_dir = icons / 'hicolor/512x512/apps'
|
|
scalable.mkdir(parents=True)
|
|
legacy_png_dir.mkdir(parents=True)
|
|
legacy_svg = scalable / 'league-of-legends.svg'
|
|
legacy_svg.write_text(OLD_SVG)
|
|
legacy_png = legacy_png_dir / 'league-of-legends.png'
|
|
legacy_png.write_bytes(b'old client icon')
|
|
result = self.install(project)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertFalse(legacy_entry.exists())
|
|
self.assertFalse(desktop_link.is_symlink())
|
|
self.assertFalse(legacy_svg.exists())
|
|
self.assertFalse(legacy_png.exists())
|
|
self.assertTrue(self.entry().is_file())
|
|
|
|
def test_quotes_exec_arguments_with_reserved_characters(self):
|
|
project = self.project('proj $dollar#hash\'quote')
|
|
result = self.install(project)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
text = self.entry().read_text()
|
|
exec_line = next(line for line in text.splitlines() if line.startswith('Exec='))
|
|
self.assertTrue(exec_line.startswith('Exec="'), exec_line)
|
|
self.assertTrue(exec_line.endswith('"'), exec_line)
|
|
self.assertIn('\\$dollar', exec_line)
|
|
if shutil.which('desktop-file-validate'):
|
|
check = subprocess.run(['desktop-file-validate', str(self.entry())],
|
|
capture_output=True, text=True, check=False)
|
|
self.assertEqual(check.returncode, 0, check.stdout + check.stderr)
|
|
|
|
def test_identity_fields_match_the_packaged_entry(self):
|
|
project = self.project()
|
|
result = self.install(project)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
rendered = parse_entry(self.entry().read_text())
|
|
packaged_here = re.search(
|
|
r"cat << 'DESKTOP_EOF' > \$out/share/applications/darling-launcher\.desktop\n(.*?)\n\s*DESKTOP_EOF",
|
|
FLAKE.read_text(), re.S)
|
|
self.assertIsNotNone(packaged_here, 'packaged desktop entry not found in flake.nix')
|
|
packaged = parse_entry(packaged_here.group(1))
|
|
for key in IDENTITY_KEYS:
|
|
self.assertIn(key, packaged)
|
|
self.assertEqual(rendered.get(key), packaged[key], key)
|
|
|
|
def test_missing_icon_fails(self):
|
|
project = self.project()
|
|
(project / 'darling-lol.png').unlink()
|
|
result = self.install(project)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn('launcher icon is missing', result.stderr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|