from ..structs import MonitorInfo, Modeline from ..abstracts import AbstractDisplay import os import re import subprocess import json # Matches "1920x1080 @ 60.000 Hz" or "1920x1080@60.000" MODE_REGEX = re.compile(r'(\d+)x(\d+)@(\d+\.\d+)') class Display(AbstractDisplay): def get_modes(self): # wlr-randr --json is the 'civilized' way to do this on Trixie result = subprocess.run(["wlr-randr", "--json"], capture_output=True, text=True, check=True) data = json.loads(result.stdout) monitors = [] for output in data: modes = [] for m in output.get("modes", []): modes.append(Modeline( width=int(m["width"]), height=int(m["height"]), refresh_rate=float(m["refresh"]) )) monitors.append(MonitorInfo( connector=output["name"], # Usually 'HEADLESS-1' in Cage primary=output.get("focused", False), modes=modes )) return monitors def set_mode(self, modeline, requested_connector="HEADLESS-1") -> None: # Format: 1920x1080@60Hz mode_str = f"{modeline.width}x{modeline.height}@{modeline.refresh_rate}Hz" subprocess.run(["wlr-randr", "--output", requested_connector, "--mode", mode_str], check=True) def save_current_mode(self, location): # Implementation similar to your GNOME one but using wlr-randr result = subprocess.run(["wlr-randr", "--json"], capture_output=True, text=True, check=True) data = json.loads(result.stdout)[0] # Grab first monitor curr = data["current_mode"] with open(location, "w") as f: f.write(f"{curr['width']}x{curr['height']}@{curr['refresh']}") def read_saved_mode(self, location): with open(location, "r") as f: w, h, r = MODE_REGEX.search(f.read()).groups() return Modeline(width=int(w), height=int(h), refresh_rate=float(r)) @staticmethod def detect(): # Cage doesn't always set XDG_CURRENT_DESKTOP, but it's a wlroots compositor return os.environ.get("WLR_BACKENDS") == "headless" or "cage" in os.environ.get("XDG_CURRENT_DESKTOP", "").lower()