feat: RDP ACL exclusivity, mobile wall, nav buttons, resolution xrandr

- RDP сервис может быть назначен только одному пользователю в ACL
- Мобильная заглушка на dashboard при ширине < 1024px
- rdp-proxy: кнопки навигации, спиннер Ожидайте, реконнект
- session_wait_page: тёмная тема, CSS спиннер
- kiosk/universal-runtime manager.py: xrandr + cvt --newmode для resolution
- Dockerfiles: x11-xserver-utils, x11-utils
This commit is contained in:
2026-04-27 18:49:06 +00:00
parent 445d025de2
commit 419b495020
11 changed files with 356 additions and 71 deletions
+47 -30
View File
@@ -68,38 +68,55 @@ def _sanitize_resolution(width: int | None, height: int | None) -> tuple[int, in
return safe_w, safe_h
def _xrandr_output_name() -> str | None:
try:
out = subprocess.run(
["xrandr", "-display", DISPLAY],
capture_output=True, text=True, check=False,
).stdout
for line in out.splitlines():
if " connected" in line:
return line.split()[0]
except Exception:
pass
return None
def _add_mode_via_cvt(width: int, height: int, output_name: str) -> bool:
try:
cvt = subprocess.run(
["cvt", str(width), str(height)],
capture_output=True, text=True, check=False,
)
if cvt.returncode != 0:
return False
modeline_line = next((l for l in cvt.stdout.splitlines() if l.startswith("Modeline")), None)
if not modeline_line:
return False
parts = modeline_line.split()
mode_name = parts[1].strip('"')
mode_params = parts[2:]
subprocess.run(["xrandr", "-display", DISPLAY, "--newmode", mode_name] + mode_params,
check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(["xrandr", "-display", DISPLAY, "--addmode", output_name, mode_name],
check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(["xrandr", "-display", DISPLAY, "--output", output_name, "--mode", mode_name],
check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except Exception:
return False
def apply_resolution(width: int | None, height: int | None) -> tuple[int, int]:
safe_w, safe_h = _sanitize_resolution(width, height)
# Best effort: Xvfb usually exposes RandR and accepts xrandr -s.
applied = False
try:
result = subprocess.run( # noqa: S603
["xrandr", "-display", DISPLAY, "-s", f"{safe_w}x{safe_h}"],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
applied = result.returncode == 0
except Exception:
applied = False
if not applied:
# Fallback to default geometry if requested mode is unsupported.
try:
fallback_w, fallback_h = [int(x) for x in CHROME_WINDOW_SIZE.split(",", 1)]
except Exception:
fallback_w, fallback_h = 1920, 1080
safe_w, safe_h = _sanitize_resolution(fallback_w, fallback_h)
try:
subprocess.run( # noqa: S603
["xrandr", "-display", DISPLAY, "-s", f"{safe_w}x{safe_h}"],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception:
pass
result = subprocess.run(
["xrandr", "-display", DISPLAY, "-s", f"{safe_w}x{safe_h}"],
check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
if result.returncode != 0:
output_name = _xrandr_output_name()
if output_name:
_add_mode_via_cvt(safe_w, safe_h, output_name)
_state["resolution"] = f"{safe_w},{safe_h}"
return safe_w, safe_h