119 lines
3.7 KiB
Python
Executable file
119 lines
3.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Print the primary monitor's geometry as `WxH+X+Y`.
|
|
|
|
Queries GNOME Mutter via DBus (authoritative on Wayland sessions),
|
|
falls back to xrandr if Mutter isn't available. Used by
|
|
`tools/ai-arena.sh` to position arena windows on the primary monitor
|
|
specifically — not whatever monitor the mouse happens to focus.
|
|
|
|
Exits 0 with geometry on stdout on success, exits 1 with an empty
|
|
stdout on failure (caller should apply its own fallback).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def detect_via_mutter() -> str | None:
|
|
"""Ask GNOME Mutter's DisplayConfig DBus interface for the primary
|
|
monitor. Parses the text form that `gdbus call` returns — the structured
|
|
tuple reply is nontrivial to deserialise without pydbus, so we walk the
|
|
repr with regex. The logical-monitor tuple shape is
|
|
(x, y, scale, uint32 transform, primary_bool, [(connector, ...)], ...)
|
|
and the physical-monitor section carries the current-mode WxH."""
|
|
if not shutil.which("gdbus"):
|
|
return None
|
|
try:
|
|
raw = subprocess.run(
|
|
[
|
|
"gdbus",
|
|
"call",
|
|
"--session",
|
|
"--dest",
|
|
"org.gnome.Mutter.DisplayConfig",
|
|
"--object-path",
|
|
"/org/gnome/Mutter/DisplayConfig",
|
|
"--method",
|
|
"org.gnome.Mutter.DisplayConfig.GetCurrentState",
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
).stdout
|
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
return None
|
|
if not raw:
|
|
return None
|
|
|
|
logical_pat = re.compile(
|
|
r"\((\d+),\s*(\d+),\s*([\d.]+),\s*uint32\s*\d+,\s*(true|false),"
|
|
r"\s*\[\('([^']+)',"
|
|
)
|
|
primary = None
|
|
for m in logical_pat.finditer(raw):
|
|
x, y, scale_s, prim, connector = m.groups()
|
|
if prim == "true":
|
|
primary = (int(x), int(y), float(scale_s), connector)
|
|
break
|
|
if not primary:
|
|
return None
|
|
|
|
px, py, pscale, pconn = primary
|
|
# Find the primary connector's entry in the monitors array and look up
|
|
# its is-current mode to get the pixel dimensions.
|
|
conn_idx = raw.find(f"('{pconn}', ")
|
|
if conn_idx < 0:
|
|
return None
|
|
mode_pat = re.compile(
|
|
r"'(\d+)x(\d+)@[\d.]+',\s*(\d+),\s*(\d+)[^}]*'is-current':\s*<true>"
|
|
)
|
|
mm = mode_pat.search(raw, conn_idx)
|
|
if not mm:
|
|
return None
|
|
w, h = int(mm.group(3)), int(mm.group(4))
|
|
if pscale != 1.0:
|
|
# Logical monitor size under HiDPI scaling — divide physical by scale.
|
|
w = int(w / pscale)
|
|
h = int(h / pscale)
|
|
return f"{w}x{h}+{px}+{py}"
|
|
|
|
|
|
def detect_via_xrandr() -> str | None:
|
|
"""Fallback for X11 / XWayland systems with xrandr installed."""
|
|
if not shutil.which("xrandr"):
|
|
return None
|
|
try:
|
|
out = subprocess.run(
|
|
["xrandr", "--query"],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
).stdout
|
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
return None
|
|
# Line looks like:
|
|
# DP-1 connected primary 3440x1440+0+0 (normal ...)
|
|
geom_pat = re.compile(r"(\d+x\d+\+\d+\+\d+)")
|
|
for line in out.splitlines():
|
|
if " connected primary " in line:
|
|
m = geom_pat.search(line)
|
|
if m:
|
|
return m.group(1)
|
|
return None
|
|
|
|
|
|
def main() -> int:
|
|
geom = detect_via_mutter() or detect_via_xrandr()
|
|
if not geom:
|
|
return 1
|
|
print(geom)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|