feat: add GUI display availability check for SSH sessions

This commit is contained in:
2026-06-24 12:27:29 +02:00
committed by wakonig_k
parent 6dd3519dd5
commit ee360adf3a
2 changed files with 73 additions and 0 deletions
+40
View File
@@ -7,6 +7,7 @@ import os
import select
import signal
import subprocess
import sys
import threading
import time
from contextlib import contextmanager
@@ -60,6 +61,40 @@ def _filter_output(output: str) -> str:
return output
def check_gui_display_available() -> tuple[bool, str | None]:
"""
Check whether the current environment can launch the GUI.
Returns:
tuple[bool, str | None]:
- ``True, None`` when a graphical display is available.
- ``False, <message>`` when GUI startup should be blocked with a helpful message.
"""
if os.name != "posix" or sys.platform == "darwin":
return True, None
if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
return True, None
if (
os.environ.get("SSH_CONNECTION")
or os.environ.get("SSH_CLIENT")
or os.environ.get("SSH_TTY")
):
return (
False,
"Cannot start BEC GUI: no graphical display was detected for this SSH session. "
"If you want to launch widgets remotely, reconnect with X11 forwarding enabled "
"(for example `ssh -X` or `ssh -Y`) or start the GUI from a local graphical session.",
)
return (
False,
"Cannot start BEC GUI: no graphical display was detected. "
"Set `DISPLAY` or `WAYLAND_DISPLAY`, or start the GUI from a graphical session.",
)
def _get_output(process, logger, stop_event: threading.Event | None = None) -> None:
log_func = {process.stdout: logger.debug, process.stderr: logger.info}
stream_buffer = {process.stdout: [], process.stderr: []}
@@ -655,6 +690,11 @@ class BECGuiClient(RPCBase):
if self._gui_is_alive():
self._gui_started_event.set()
return
gui_available, error_message = check_gui_display_available()
if not gui_available:
logger.error(error_message)
self._startup_timeout = 0
return
if self._process is None or self._process.poll() is not None:
logger.success("GUI starting...")
self._startup_timeout = 5
+33
View File
@@ -12,6 +12,7 @@ from bec_widgets.cli.client_utils import (
BECGuiClient,
_join_process_output_thread,
_start_plot_process,
check_gui_display_available,
)
from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCResponseTimeoutError, rpc_timeout
@@ -97,6 +98,38 @@ def test_client_utils_passes_client_config_to_server(bec_dispatcher):
)
def test_check_gui_display_available_reports_missing_display_for_ssh_session():
with mock.patch.dict(
"bec_widgets.cli.client_utils.os.environ", {"SSH_CONNECTION": "1"}, clear=True
):
available, message = check_gui_display_available()
assert available is False
assert message is not None
assert "SSH session" in message
assert "ssh -X" in message
def test_client_utils_start_server_returns_when_no_display(bec_dispatcher):
gui = BECGuiClient()
gui._client = bec_dispatcher.client
gui._gui_id = "gui_id"
gui._gui_is_alive = mock.MagicMock(return_value=False)
with (
mock.patch(
"bec_widgets.cli.client_utils.check_gui_display_available",
return_value=(False, "No display available"),
),
mock.patch("bec_widgets.cli.client_utils._start_plot_process") as mock_start_plot,
mock.patch("bec_widgets.cli.client_utils.logger") as mock_logger,
):
gui._start_server(wait=False)
mock_start_plot.assert_not_called()
mock_logger.error.assert_called_once_with("No display available")
@contextmanager
def _no_wait_for_server(_client):
yield