diff --git a/robotics_application_manager/libs/process_utils.py b/robotics_application_manager/libs/process_utils.py index 56126d9..57d1ab0 100644 --- a/robotics_application_manager/libs/process_utils.py +++ b/robotics_application_manager/libs/process_utils.py @@ -130,6 +130,16 @@ def wait_for_process_to_start(process_name, timeout=60): def check_gpu_acceleration(): try: + if os.path.exists("/dev/dxg"): + result = subprocess.run( + ["nvidia-smi"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + timeout=5, + check=False, + ) + return "NVIDIA" if result.returncode == 0 else "OFF" + # Verifica si /dev/dri existe if not os.path.exists("/dev/dri"): LogManager.logger.error("/dev/dri does not exist. No direct GPU access.") diff --git a/robotics_application_manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py index e375100..eea760b 100644 --- a/robotics_application_manager/manager/launcher/launcher_gzsim.py +++ b/robotics_application_manager/manager/launcher/launcher_gzsim.py @@ -41,7 +41,16 @@ def run(self, config_file, callback): # Configure browser screen width and height for gz GUI gzclient_config_cmds = f"sed -i 's/.*<\/width>/{self.width}<\/width>/; s/.*<\/height>/{self.height}<\/height>/' {config_file};" - if ACCELERATION_ENABLED: + if os.path.exists("/dev/dxg"): + self.gz_vnc.start_vnc_wsl( + self.display, self.internal_port, self.external_port + ) + gzclient_cmd = ( + f"export DISPLAY={self.display}; {gzclient_config_cmds} " + f"export GALLIUM_DRIVER=d3d12; " + f"gz sim -g -v4 --gui-config {config_file}" + ) + elif ACCELERATION_ENABLED: # Starts xserver, x11vnc and novnc self.gz_vnc.start_vnc_gpu( self.display, self.internal_port, self.external_port, DRI_PATH diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index 9b5a44c..6df5f26 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -851,6 +851,7 @@ def on_run_application(self, event): command = ["python3", entrypoint] executable = None shell = False + environment = None if entrypoint.endswith(".launch.py"): command = [ @@ -864,6 +865,23 @@ def on_run_application(self, event): ] executable = "/bin/bash" shell = True + elif entrypoint.endswith(".py") and os.path.exists("/dev/dxg"): + command = [ + "bash", + "-c", + 'source /opt/ros/humble/setup.bash && exec python3 "$1"', + "bash", + entrypoint, + ] + environment = os.environ.copy() + environment["PYTHONPATH"] = os.pathsep.join( + [ + "/workspace/code/hal_interfaces", + "/workspace/code/gui_interfaces", + "/workspace/code/console_interfaces", + environment.get("PYTHONPATH", ""), + ] + ) proc = subprocess.Popen( command, @@ -875,6 +893,7 @@ def on_run_application(self, event): shell=shell, executable=executable, start_new_session=True, + env=environment, ) proc.send_signal(signal.SIGSTOP) diff --git a/robotics_application_manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py index da05946..4ffc27a 100755 --- a/robotics_application_manager/manager/vnc/vnc_server.py +++ b/robotics_application_manager/manager/vnc/vnc_server.py @@ -4,13 +4,15 @@ including GPU-accelerated sessions and desktop icon creation. """ -import time -import socket -from robotics_application_manager.manager.docker_thread import DockerThread -import subprocess -from typing import List, Any import os +import signal +import subprocess +import socket +import time +from typing import Any, List + from robotics_application_manager.libs import wait_for_xserver +from robotics_application_manager.manager.docker_thread import DockerThread class Vnc_server: @@ -115,6 +117,82 @@ def start_vnc_gpu(self, display, internal_port, external_port, dri_path): self.wait_for_port("localhost", internal_port) self.wait_for_port("localhost", external_port) + def start_vnc_wsl(self, display, internal_port, external_port): + """Start the WSL2 Xvfb, x11vnc, and noVNC backend.""" + self.wsl_processes = [] + + try: + self._start_wsl_process(["Xvfb", display, "-screen", "0", "1920x1080x24"]) + wait_for_xserver(display) + + self._start_wsl_process( + [ + "x11vnc", + "-display", + display, + "-rfbport", + str(internal_port), + "-nopw", + "-forever", + "-noxdamage", + "-shared", + ] + ) + + novnc_cmd = [ + "/noVNC/utils/novnc_proxy", + "--listen", + str(external_port), + "--vnc", + f"localhost:{internal_port}", + "--web", + "/noVNC", + ] + if os.path.isfile("/etc/certs/cert.pem"): + novnc_cmd += [ + "--cert", + "/etc/certs/cert.pem", + "--key", + "/etc/certs/privkey.pem", + ] + + self._start_wsl_process(novnc_cmd) + self.wait_for_port("localhost", internal_port) + self.wait_for_port("localhost", external_port) + self.running = True + except Exception: + self.terminate_wsl_processes() + raise + + def _start_wsl_process(self, command): + """Start and track one WSL VNC process.""" + self.wsl_processes.append( + subprocess.Popen( + command, + start_new_session=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + ) + + def terminate_wsl_processes(self): + """Terminate only the processes started by the WSL2 VNC backend.""" + for process in reversed(getattr(self, "wsl_processes", [])): + if process.poll() is None: + try: + process_group = os.getpgid(process.pid) + os.killpg(process_group, signal.SIGTERM) + process.wait(timeout=10) + except ProcessLookupError: + continue + except subprocess.TimeoutExpired: + try: + os.killpg(process_group, signal.SIGKILL) + except ProcessLookupError: + continue + process.wait() + self.wsl_processes = [] + def wait_for_port(self, host, port, timeout=120): """Wait for a TCP port on a host to become available within a timeout period. @@ -153,6 +231,7 @@ def is_running(self): def terminate(self): """Terminate all running threads and stop the VNC server.""" + self.terminate_wsl_processes() for thread in self.threads: if thread.is_alive(): thread.terminate()