Merge pull request #117 from 3AvisD/chore/launcher-name

launcher: use a neutral Darling Launcher name and icon
This commit is contained in:
Kalah authored and GitHub committed 2026-10-21 17:45:18 +02:00
commit bb5320e167
4 files changed
+146 -221

No files matched your search

+24 -18
View File
@@ -1,5 +1,5 @@
{
description = "Unofficial Darling League of Legends - experimental macOS LoL runner on Linux via Darling";
description = "Unofficial Darling launcher for the macOS game client on Linux";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
@@ -136,7 +136,7 @@
pythonEnv
];
# FHS environment for building and running Darling and League of Legends.
# FHS environment for building and running Darling and the macOS game client.
# Darling requires root/multitasking mount & namespace semantics, /usr/local layout,
# and dynamically compiled overlays. An FHS environment provides the standard standard
# filesystem hierarchies (/usr/include, /usr/lib, /usr/bin) and native dynamic linker.
@@ -223,10 +223,10 @@
mkdir -p $out/bin $out/share/darling-lol $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/128x128/apps $out/share/pixmaps $out/share/applications
cp -r * $out/share/darling-lol/
cp darling-lol.svg $out/share/icons/hicolor/scalable/apps/darling-lol.svg
cp darling-lol.png $out/share/icons/hicolor/128x128/apps/darling-lol.png
cp darling-lol.png $out/share/pixmaps/darling-lol.png
cp darling-lol.svg $out/share/pixmaps/darling-lol.svg
cp darling-lol.svg $out/share/icons/hicolor/scalable/apps/darling-launcher.svg
cp darling-lol.png $out/share/icons/hicolor/128x128/apps/darling-launcher.png
cp darling-lol.png $out/share/pixmaps/darling-launcher.png
cp darling-lol.svg $out/share/pixmaps/darling-launcher.svg
# Install configured launcher
substitute ./darling-lol-launcher.sh $out/bin/darling-lol \
@@ -235,6 +235,7 @@
--replace-fail "@UNSHARE_BIN@" "${pkgs.util-linux}/bin/unshare"
chmod +x $out/bin/darling-lol
wrapProgram $out/bin/darling-lol --prefix PATH : ${pkgs.lib.makeBinPath [ pythonEnv pkgs.coreutils pkgs.util-linux pkgs.gawk ]}
ln -s darling-lol $out/bin/darling-launcher
makeWrapper $out/bin/darling-lol $out/bin/darling-lol-installer \
--add-flags "--installer"
@@ -245,15 +246,15 @@
makeWrapper $out/bin/darling-lol $out/bin/darling-lol-build \
--add-flags "./scripts/build.sh"
cat << 'DESKTOP_EOF' > $out/share/applications/darling-lol.desktop
cat << 'DESKTOP_EOF' > $out/share/applications/darling-launcher.desktop
[Desktop Entry]
Categories=Game;
Comment=Unofficial League of Legends macOS client compatibility layer for Linux via Darling
Exec=darling-lol
GenericName=MOBA Game
Icon=darling-lol
Keywords=League;LoL;Riot;Darling;
Name=League of Legends (unofficial)
Comment=Unofficial launcher for the macOS game client via Darling
Exec=darling-launcher
GenericName=Game
Icon=darling-launcher
Keywords=Darling;Launcher;Compatibility;
Name=Darling Launcher
StartupWMClass=RiotClientServices
Terminal=true
Type=Application
@@ -264,7 +265,7 @@ DESKTOP_EOF
'';
meta = with pkgs.lib; {
description = "Unofficial tooling and patches to run macOS League of Legends on Linux with Darling";
description = "Unofficial tooling and patches to run the macOS game client on Linux with Darling";
homepage = "https://github.com/lucannez64/darling-lol";
license = licenses.gpl3Only;
platforms = [ "x86_64-linux" ];
@@ -334,7 +335,12 @@ DESKTOP_EOF
# Tests use private stub binaries/repositories, never Darling itself.
python3 -m unittest discover -s tests -v
python3 scripts/validate.py
grep -q '^Terminal=true$' ${darlingLol}/share/applications/darling-lol.desktop
grep -q '^Terminal=true$' ${darlingLol}/share/applications/darling-launcher.desktop
grep -q '^Name=Darling Launcher$' ${darlingLol}/share/applications/darling-launcher.desktop
grep -q '^Icon=darling-launcher$' ${darlingLol}/share/applications/darling-launcher.desktop
grep -q '^Keywords=Darling;Launcher;Compatibility;$' ${darlingLol}/share/applications/darling-launcher.desktop
! grep -qiE '^(Name|Comment|GenericName|Keywords)=.*(League|LoL|Riot|Vanguard|anti-cheat)' ${darlingLol}/share/applications/darling-launcher.desktop
test -x ${darlingLol}/bin/darling-launcher
touch "$out"
'';
};
@@ -349,7 +355,7 @@ DESKTOP_EOF
default = {
type = "app";
program = "${darlingLol}/bin/darling-lol";
meta.description = "Launch League of Legends via Darling";
meta.description = "Launch the macOS game client via Darling";
};
shell = {
type = "app";
@@ -359,7 +365,7 @@ DESKTOP_EOF
installer = {
type = "app";
program = "${darlingLol}/bin/darling-lol-installer";
meta.description = "Run the Darling and League of Legends rootless installer";
meta.description = "Run the Darling rootless installer";
};
build = {
type = "app";
@@ -371,7 +377,7 @@ DESKTOP_EOF
devShells.${system}.default = pkgs.mkShell {
packages = buildDeps ++ [ darlingLolFhs ];
shellHook = ''
echo "Darling LoL build & development environment loaded."
echo "Darling build & development environment loaded."
echo "Tip: Run 'darling-lol-fhs' to enter the FHS build sandbox if standard paths (/usr/include, /usr/lib) are needed."
'';
};
+1 -1
View File
@@ -4,4 +4,4 @@ set -eu
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$repo"
python3 scripts/configure.py >/dev/null
python3 build/runtime/tools/riot-desktop-entry.py
python3 build/runtime/tools/desktop-entry.py
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Create and install the GNOME / FreeDesktop desktop entry and launcher icon."""
import os
import shutil
import subprocess
from pathlib import Path
PROJECT_ROOT = Path("@PROJECT_ROOT@")
HOST_HOME = Path("@HOST_HOME@")
def remove_legacy_entry(path: Path) -> None:
"""Remove a previous launcher entry only when this project created it."""
try:
if path.exists() and "desktop-run.sh" in path.read_text(errors="ignore"):
path.unlink()
except OSError:
pass
def main():
icons_base = HOST_HOME / ".local/share/icons"
apps_base = HOST_HOME / ".local/share/applications"
scalable_dir = icons_base / "hicolor/scalable/apps"
png_dir = icons_base / "hicolor/128x128/apps"
scalable_dir.mkdir(parents=True, exist_ok=True)
png_dir.mkdir(parents=True, exist_ok=True)
apps_base.mkdir(parents=True, exist_ok=True)
source_svg = PROJECT_ROOT / "darling-lol.svg"
source_png = PROJECT_ROOT / "darling-lol.png"
if not source_svg.is_file() or not source_png.is_file():
raise SystemExit("launcher icon is missing: expected darling-lol.svg and darling-lol.png")
# Install the project's original icon under a neutral name. Do not extract
# or install an icon from the game/client installation.
svg_target = scalable_dir / "darling-launcher.svg"
png_target = png_dir / "darling-launcher.png"
shutil.copyfile(source_svg, svg_target)
shutil.copyfile(source_png, png_target)
shutil.copyfile(source_svg, icons_base / "darling-launcher.svg")
shutil.copyfile(source_png, icons_base / "darling-launcher.png")
# Remove artifacts created by older versions of this helper.
remove_legacy_entry(apps_base / "league-of-legends.desktop")
for legacy_icon in (
icons_base / "league-of-legends.svg",
icons_base / "league-of-legends.png",
scalable_dir / "league-of-legends.svg",
icons_base / "hicolor/512x512/apps/league-of-legends.png",
):
try:
if legacy_icon.exists():
legacy_icon.unlink()
except OSError:
pass
# Create the desktop entry. Keep StartupWMClass: it is the client's real
# window class and is required for the compositor to match the window.
run_script = PROJECT_ROOT / "scripts/desktop-run.sh"
exec_path = str(run_script).replace("%", "%%")
exec_cmd = f'"{exec_path}"' if " " in exec_path else exec_path
desktop_content = f"""[Desktop Entry]
Version=1.0
Type=Application
Name=Darling Launcher
GenericName=Game
Comment=Unofficial launcher for the macOS game client via Darling
Exec={exec_cmd}
Path={PROJECT_ROOT}
Icon=darling-launcher
Terminal=false
Categories=Game;ActionGame;StrategyGame;
Keywords=darling;launcher;compatibility;
StartupWMClass=Riot Client
StartupNotify=false
"""
desktop_file = apps_base / "darling-launcher.desktop"
desktop_file.write_text(desktop_content)
os.chmod(desktop_file, 0o755)
print(f"Created application launcher: {desktop_file}")
target_desktop_dirs = []
try:
res = subprocess.run(["xdg-user-dir", "DESKTOP"], capture_output=True, text=True, check=False)
if res.returncode == 0 and res.stdout.strip():
d = Path(res.stdout.strip())
if d.is_dir() and d not in target_desktop_dirs:
target_desktop_dirs.append(d)
except Exception:
pass
for cand in [HOST_HOME / "Desktop"]:
if cand.is_dir() and cand not in target_desktop_dirs:
target_desktop_dirs.append(cand)
for ddir in target_desktop_dirs:
remove_legacy_entry(ddir / "league-of-legends.desktop")
desktop_shortcut = ddir / "darling-launcher.desktop"
desktop_shortcut.write_text(desktop_content)
os.chmod(desktop_shortcut, 0o755)
gio = shutil.which("gio")
if gio:
subprocess.run([gio, "set", str(desktop_shortcut), "metadata::trusted", "true"], check=False)
print(f"Created desktop shortcut: {desktop_shortcut}")
# Refresh icon and desktop databases
update_desktop = shutil.which("update-desktop-database")
if update_desktop:
subprocess.run([update_desktop, str(apps_base)], check=False)
update_icons = shutil.which("gtk-update-icon-cache")
if update_icons:
subprocess.run([update_icons, "-f", "-t", str(icons_base / "hicolor")], check=False)
print("GNOME application launcher and icon installed successfully.")
if __name__ == "__main__":
main()
-202
View File
@@ -1,202 +0,0 @@
#!/usr/bin/env python3
"""Create and install the GNOME / FreeDesktop desktop entry and launcher icon."""
import os
import shutil
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path("@PROJECT_ROOT@")
HOST_HOME = Path("@HOST_HOME@")
DPREFIX = Path("@DPREFIX@")
SVG_ICON_CONTENT = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#091428"/>
<stop offset="50%" stop-color="#0a1c36"/>
<stop offset="100%" stop-color="#030811"/>
</linearGradient>
<linearGradient id="borderGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#f0e6d2"/>
<stop offset="40%" stop-color="#c8aa6e"/>
<stop offset="80%" stop-color="#785a28"/>
<stop offset="100%" stop-color="#c89b3c"/>
</linearGradient>
<linearGradient id="goldGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#fff2c6"/>
<stop offset="25%" stop-color="#f0e6d2"/>
<stop offset="50%" stop-color="#c8aa6e"/>
<stop offset="85%" stop-color="#9a7a35"/>
<stop offset="100%" stop-color="#785a28"/>
</linearGradient>
<linearGradient id="goldHighlight" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#c8aa6e" stop-opacity="0"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="8" stdDeviation="14" flood-color="#000000" flood-opacity="0.85"/>
</filter>
</defs>
<!-- Background Shield / Squircle -->
<rect x="24" y="24" width="464" height="464" rx="104" fill="url(#bgGrad)" stroke="url(#borderGrad)" stroke-width="12" filter="url(#shadow)"/>
<rect x="40" y="40" width="432" height="432" rx="90" fill="none" stroke="#c8aa6e" stroke-width="2" stroke-opacity="0.4"/>
<!-- Crest Emblem -->
<g filter="url(#glow)">
<path d="M172 108 L242 108 L242 316 L364 316 L352 388 L156 388 L156 124 Z"
fill="url(#goldGrad)" stroke="#463714" stroke-width="3"/>
<path d="M 180 120 L 230 120 L 230 250 L 180 200 Z"
fill="url(#goldHighlight)"/>
<path d="M 242 328 L 348 328 L 340 376 L 172 376 L 172 356 L 242 356 Z"
fill="#c8aa6e" opacity="0.45"/>
<polygon points="256,76 264,96 284,96 268,108 274,128 256,116 238,128 244,108 228,96 248,96" fill="#f0e6d2"/>
</g>
</svg>
"""
def extract_best_png_from_icns(icns_path: Path) -> bytes | None:
"""Extract the highest-resolution embedded PNG from an Apple ICNS file."""
try:
data = icns_path.read_bytes()
if not data.startswith(b"icns"):
return None
offset = 8
best_png = None
best_len = 0
while offset + 8 <= len(data):
size = int.from_bytes(data[offset + 4 : offset + 8], "big")
if size < 8 or offset + size > len(data):
break
chunk = data[offset + 8 : offset + size]
if chunk.startswith(b"\x89PNG\r\n\x1a\n") and len(chunk) > best_len:
best_png = chunk
best_len = len(chunk)
offset += size
return best_png
except Exception:
return None
def find_candidate_icns() -> Path | None:
"""Find any available Riot or League .icns icon files."""
candidates = [
*list((PROJECT_ROOT / "build/installer").glob("*League of Legends*.app/Contents/Resources/*.icns")),
*list(DPREFIX.glob("Users/Shared/Riot Games/Riot Client.app/Contents/Resources/*.icns")),
*list(DPREFIX.glob("Applications/Riot Client.app/Contents/Resources/*.icns")),
*list(DPREFIX.glob("Applications/League of Legends.app/Contents/Resources/*.icns")),
]
for c in candidates:
if c.is_file() and c.stat().st_size > 1024:
return c
return None
def main():
icons_base = HOST_HOME / ".local/share/icons"
apps_base = HOST_HOME / ".local/share/applications"
desktop_dir = HOST_HOME / "Desktop"
scalable_dir = icons_base / "hicolor/scalable/apps"
png_dir = icons_base / "hicolor/512x512/apps"
scalable_dir.mkdir(parents=True, exist_ok=True)
png_dir.mkdir(parents=True, exist_ok=True)
apps_base.mkdir(parents=True, exist_ok=True)
# 1. Install scalable SVG icon
svg_target = scalable_dir / "league-of-legends.svg"
svg_target.write_text(SVG_ICON_CONTENT)
shutil.copyfile(svg_target, icons_base / "league-of-legends.svg")
# 2. Extract or generate PNG icon
png_target = png_dir / "league-of-legends.png"
icns_file = find_candidate_icns()
extracted = False
if icns_file:
png_data = extract_best_png_from_icns(icns_file)
if png_data:
png_target.write_bytes(png_data)
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
extracted = True
print(f"Extracted official icon from {icns_file.name} to {png_target}")
if not extracted and not png_target.exists():
# Try to rasterize SVG via rsvg-convert or magick
rsvg = shutil.which("rsvg-convert")
magick = shutil.which("magick") or shutil.which("convert")
if rsvg:
subprocess.run([rsvg, "-w", "512", "-h", "512", str(svg_target), "-o", str(png_target)], check=False)
if png_target.exists():
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
elif magick:
subprocess.run([magick, "-background", "none", "-resize", "512x512", str(svg_target), str(png_target)], check=False)
if png_target.exists():
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
# 3. Create .desktop file
run_script = PROJECT_ROOT / "scripts/desktop-run.sh"
exec_path = str(run_script).replace("%", "%%")
exec_cmd = f'"{exec_path}"' if " " in exec_path else exec_path
desktop_content = f"""[Desktop Entry]
Version=1.0
Type=Application
Name=League of Legends
GenericName=MOBA Game
Comment=Play League of Legends on Linux with darling-lol
Exec={exec_cmd}
Path={PROJECT_ROOT}
Icon=league-of-legends
Terminal=false
Categories=Game;ActionGame;StrategyGame;
Keywords=riot;league;lol;darling;games;
StartupWMClass=Riot Client
StartupNotify=false
"""
desktop_file = apps_base / "league-of-legends.desktop"
desktop_file.write_text(desktop_content)
os.chmod(desktop_file, 0o755)
print(f"Created application launcher: {desktop_file}")
target_desktop_dirs = []
try:
res = subprocess.run(["xdg-user-dir", "DESKTOP"], capture_output=True, text=True, check=False)
if res.returncode == 0 and res.stdout.strip():
d = Path(res.stdout.strip())
if d.is_dir() and d not in target_desktop_dirs:
target_desktop_dirs.append(d)
except Exception:
pass
for cand in [HOST_HOME / "Desktop"]:
if cand.is_dir() and cand not in target_desktop_dirs:
target_desktop_dirs.append(cand)
for ddir in target_desktop_dirs:
desktop_shortcut = ddir / "league-of-legends.desktop"
desktop_shortcut.write_text(desktop_content)
os.chmod(desktop_shortcut, 0o755)
gio = shutil.which("gio")
if gio:
subprocess.run([gio, "set", str(desktop_shortcut), "metadata::trusted", "true"], check=False)
print(f"Created desktop shortcut: {desktop_shortcut}")
# 4. Refresh icon and desktop databases
update_desktop = shutil.which("update-desktop-database")
if update_desktop:
subprocess.run([update_desktop, str(apps_base)], check=False)
update_icons = shutil.which("gtk-update-icon-cache")
if update_icons:
subprocess.run([update_icons, "-f", "-t", str(icons_base / "hicolor")], check=False)
print("GNOME application launcher and icon installed successfully.")
if __name__ == "__main__":
main()