mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
* feat: replace the LXQt desktop with SteamOS-style KDE Plasma * feat: leaving the KDE desktop logs out through Plasma and returns to gaming mode * feat: DroidDeck logo wallpaper as the KDE desktop default * feat: build the KDE wallpaper from the DroidDeck mark artwork * ci: keep the built desktop package as a short-lived artifact for device testing * fix: the desktop loading screen no longer promises Steam on the desktop * docs: the output resize comments name the desktop's KWin * fix: the desktop package verifies every package's Arch Linux ARM signature and never replaces a published tag * fix: programs sent to the GPU from the desktop drop its software rendering overrides * fix: an interrupted desktop install no longer counts as installed * feat: pin the desktop package to its DroidDeck-Components release instead of the catalog
132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
#!/usr/bin/python3
|
|
# The desktop's games and emulators, started on the GPU. Every menu entry whose categories say
|
|
# Game or Emulator gets a copy of its own in ~/.local/share/applications (which wins over
|
|
# /usr/share/applications) whose Exec runs it through droiddeck-gpu: full screen on the app's
|
|
# compositor, where Vulkan and OpenGL work. The entry keeps the old way as a second action, "Run in
|
|
# a desktop window". Run at every desktop start; copies whose original is gone, or all of them when
|
|
# the switch is off, are removed.
|
|
#
|
|
# BL_DESKTOP_GPU=on (default: the desktop composites in software) | off
|
|
import os
|
|
import re
|
|
|
|
HOME = os.environ.get("HOME", "/root")
|
|
SYSTEM_DIR = "/usr/share/applications"
|
|
# The user's own AppImages (the front end's Add AppImage).
|
|
LOCAL_DIR = "/usr/local/share/applications"
|
|
# Flathub's games, as Flatpak exports them; the copies here win over these as they do over the system's.
|
|
FLATPAK_DIR = os.path.join(HOME, ".local/share/flatpak/exports/share/applications")
|
|
USER_DIR = os.path.join(HOME, ".local/share/applications")
|
|
MARK = "X-DroidDeck-GPU=1"
|
|
# The client and its games already hand themselves to the app (droiddeck-steam-launch).
|
|
SKIP = re.compile(r"^(steam|droiddeck-game-|droiddeck-steam|steamdeck-)", re.I)
|
|
|
|
|
|
def enabled():
|
|
return os.environ.get("BL_DESKTOP_GPU", "on").strip().lower() not in ("off", "0", "no", "false")
|
|
|
|
|
|
def main_group(text):
|
|
"""The [Desktop Entry] group's lines, and everything after it."""
|
|
lines = text.splitlines()
|
|
start = next((i for i, l in enumerate(lines) if l.strip() == "[Desktop Entry]"), None)
|
|
if start is None:
|
|
return None, None, None
|
|
end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("[")), len(lines))
|
|
return lines[:start + 1], lines[start + 1:end], lines[end:]
|
|
|
|
|
|
def gpu_exec(exec_line):
|
|
"""The Exec that runs a program on the GPU; a Flatpak app goes through its launcher, which
|
|
keeps it on gamescope's X server instead of the desktop's Wayland socket."""
|
|
if exec_line.startswith("/usr/bin/flatpak run ") or exec_line.startswith("flatpak run "):
|
|
exec_line = "/usr/local/bin/droiddeck-flatpak-run " + exec_line.split(" run ", 1)[1]
|
|
return "droiddeck-gpu " + exec_line
|
|
|
|
|
|
def wrap(text):
|
|
head, group, rest = main_group(text)
|
|
if group is None:
|
|
return None
|
|
keys = {}
|
|
for l in group:
|
|
if "=" in l and not l.lstrip().startswith("#"):
|
|
k, v = l.split("=", 1)
|
|
keys.setdefault(k.strip(), v)
|
|
cats = set(filter(None, keys.get("Categories", "").split(";")))
|
|
if not cats & {"Game", "Emulator"}:
|
|
return None
|
|
if keys.get("Type", "Application") != "Application" or keys.get("NoDisplay", "").lower() == "true":
|
|
return None
|
|
exec_line = keys.get("Exec", "").strip()
|
|
if not exec_line or exec_line.startswith("droiddeck-gpu "):
|
|
return None
|
|
out_group = []
|
|
actions = keys.get("Actions", "")
|
|
for l in group:
|
|
k = l.split("=", 1)[0].strip() if "=" in l else None
|
|
if k == "Exec":
|
|
out_group.append("Exec=" + gpu_exec(exec_line))
|
|
elif k == "Actions":
|
|
continue
|
|
else:
|
|
out_group.append(l)
|
|
while out_group and not out_group[-1].strip():
|
|
out_group.pop()
|
|
out_group.append("Actions=" + ("droiddeck-window;" + actions if actions else "droiddeck-window;"))
|
|
out_group.append(MARK)
|
|
window = ["", "[Desktop Action droiddeck-window]", "Name=Run in a desktop window", "Exec=" + exec_line]
|
|
# The entry's own actions (new window, a different mode...) run on the GPU as well.
|
|
wrapped_rest = []
|
|
for l in rest:
|
|
if l.startswith("Exec=") and not l[5:].startswith("droiddeck-gpu "):
|
|
l = "Exec=" + gpu_exec(l[5:])
|
|
wrapped_rest.append(l)
|
|
if wrapped_rest and wrapped_rest[0].strip():
|
|
wrapped_rest.insert(0, "")
|
|
return "\n".join(head + out_group + window + wrapped_rest) + "\n"
|
|
|
|
|
|
def ours(path):
|
|
try:
|
|
with open(path, encoding="utf-8", errors="replace") as f:
|
|
return MARK in f.read()
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def main():
|
|
on = enabled()
|
|
os.makedirs(USER_DIR, exist_ok=True)
|
|
written = set()
|
|
sources = [d for d in (SYSTEM_DIR, LOCAL_DIR, FLATPAK_DIR) if os.path.isdir(d)] if on else []
|
|
for source in sources:
|
|
for name in sorted(os.listdir(source)):
|
|
if not name.endswith(".desktop") or SKIP.match(name):
|
|
continue
|
|
target = os.path.join(USER_DIR, name)
|
|
# A copy the user made or edited themselves is theirs.
|
|
if os.path.exists(target) and not ours(target):
|
|
continue
|
|
try:
|
|
with open(os.path.join(source, name), encoding="utf-8", errors="replace") as f:
|
|
text = wrap(f.read())
|
|
except OSError:
|
|
continue
|
|
if text is None:
|
|
continue
|
|
with open(target + ".tmp", "w", encoding="utf-8") as f:
|
|
f.write(text)
|
|
os.replace(target + ".tmp", target)
|
|
written.add(name)
|
|
removed = 0
|
|
for name in os.listdir(USER_DIR):
|
|
path = os.path.join(USER_DIR, name)
|
|
if name.endswith(".desktop") and name not in written and ours(path):
|
|
os.remove(path)
|
|
removed += 1
|
|
print(f"droiddeck-desktop-gpu: {'on' if on else 'off'}, {len(written)} entries on the GPU, {removed} removed")
|
|
|
|
|
|
main()
|