Files
DroidDeck/tools/linuxfs/desktop/droiddeck-desktop-gpu
T
The412Banner d984a4ba31 Desktop GPU: menu games run in their own gamescope; labwc on the patched wlroots
droiddeck-gpu runs a program in a gamescope of its own on the app's compositor
(wayland-0), full screen over the desktop - the path the rail's play chips use,
where Vulkan works. droiddeck-desktop-gpu gives every Game/Emulator menu entry a
per-user copy through it (auto: while the desktop is on pixman), keeping 'Run in
a desktop window' as an action.

droiddeck-desktop loads /usr/local/lib/droiddeck-wlroots for the vulkan/gles2
renderers and falls back to pixman when labwc stops at start. The build stages
the wlroots release named by tools/wlroots/release.env, once published.
2026-09-23 21:02:24 -04:00

124 lines
4.7 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=auto (default: on while the desktop is composited by pixman) | on | off
import os
import re
HOME = os.environ.get("HOME", "/root")
SYSTEM_DIR = "/usr/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 (bannerlator-steam-launch).
SKIP = re.compile(r"^(steam|droiddeck-game-|droiddeck-steam|steamdeck-)", re.I)
def enabled():
mode = os.environ.get("BL_DESKTOP_GPU", "auto").strip().lower()
if mode in ("on", "1", "yes", "true"):
return True
if mode in ("off", "0", "no", "false"):
return False
return os.environ.get("WLR_RENDERER", "pixman") == "pixman"
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 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=droiddeck-gpu " + 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=droiddeck-gpu " + 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()
if on and os.path.isdir(SYSTEM_DIR):
for name in sorted(os.listdir(SYSTEM_DIR)):
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(SYSTEM_DIR, 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()