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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user