From 6b7dfdd53cd70fcbbfbbfeef2c6f1099a3227b15 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sun, 28 Jun 2026 07:55:00 +0800 Subject: [PATCH] feat(plugin): add system info reader (hardware, OS, Proton, launch options) --- plugins/decky-vault/main.py | 86 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/plugins/decky-vault/main.py b/plugins/decky-vault/main.py index 66f0ba7..0c4d9d1 100644 --- a/plugins/decky-vault/main.py +++ b/plugins/decky-vault/main.py @@ -224,4 +224,88 @@ benchmark_percentiles=97,AVG,1,0.1 os.remove(log_path) return {"success": True} except Exception as e: - return {"success": False, "error": str(e)} \ No newline at end of file + return {"success": False, "error": str(e)} + + async def get_hardware_info(self) -> dict: + """RPC: Detect hardware model from DMI. Returns {slug, name, raw}.""" + # Steam Deck models: Jupiter = LCD, Galileo = OLED + product_name = "" + try: + with open("/sys/class/dmi/id/product_name", 'r') as f: + product_name = f.read().strip() + except (IOError, FileNotFoundError): + pass + + slug = "unknown" + name = "Unknown Device" + + if product_name == "Jupiter": + slug = "steamdeck-lcd" + name = "Steam Deck LCD" + elif product_name == "Galileo": + slug = "steamdeck-oled" + name = "Steam Deck OLED" + elif product_name: + name = product_name + slug = product_name.lower().replace(" ", "-") + + return {"slug": slug, "name": name, "raw": product_name} + + async def get_os_version(self) -> str: + """RPC: Read OS version from /etc/os-release.""" + try: + with open("/etc/os-release", 'r') as f: + for line in f: + if line.startswith("PRETTY_NAME="): + return line.split("=", 1)[1].strip().strip('"') + return "unknown" + except (IOError, FileNotFoundError): + return "unknown" + + async def get_proton_version(self, app_id: int) -> str: + """RPC: Attempt to read the Proton version for a Steam app. + Reads from the Steam compatdata directory.""" + try: + home = os.path.expanduser("~") + # Steam compat data lives in ~/.steam/steam/steamapps/compatdata// + compat_path = os.path.join(home, ".steam", "steam", "steamapps", "compatdata", str(app_id)) + version_file = os.path.join(compat_path, "version") + if os.path.exists(version_file): + with open(version_file, 'r') as f: + return f.read().strip() + return "" + except (IOError, FileNotFoundError): + return "" + + async def get_launch_options(self, app_id: int) -> str: + """RPC: Read launch options for a Steam app from localconfig.vdf. + This is best-effort — the VDF format is not officially documented.""" + try: + home = os.path.expanduser("~") + # localconfig.vdf path varies; try common locations + config_paths = [ + os.path.join(home, ".steam", "steam", "usercfg", "localconfig.vdf"), + os.path.join(home, ".local", "share", "Steam", "usercfg", "localconfig.vdf"), + ] + for config_path in config_paths: + if os.path.exists(config_path): + with open(config_path, 'r') as f: + content = f.read() + # Best-effort: look for LaunchOptions near the app ID + # This is a simple heuristic — VDF parsing is complex + app_str = f'"{app_id}"' + idx = content.find(app_str) + if idx != -1: + # Search for LaunchOptions within ~2000 chars after app ID + search_region = content[idx:idx + 2000] + lo_idx = search_region.find('"LaunchOptions"') + if lo_idx != -1: + # Extract the value between quotes + value_start = search_region.find('"', lo_idx + len('"LaunchOptions"')) + 1 + value_end = search_region.find('"', value_start) + if value_start > 0 and value_end > value_start: + return search_region[value_start:value_end] + return "" + return "" + except (IOError, FileNotFoundError): + return "" \ No newline at end of file