mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 11:01:07 +00:00
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.
124 lines
4.7 KiB
Python
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()
|