1+ """VNC server management module for RoboticsApplicationManager.
2+
3+ Provides classes and functions to start and manage VNC and noVNC servers,
4+ including GPU-accelerated sessions and desktop icon creation.
5+ """
6+
17import time
28import socket
39from manager .manager .docker_thread .docker_thread import DockerThread
814
915
1016class Vnc_server :
17+ """Class to manage VNC and noVNC server sessions for RoboticsApplicationManager."""
18+
1119 threads : List [Any ] = []
1220 running : bool = False
1321
1422 def start_vnc (self , display , internal_port , external_port ):
23+ """Start a VNC and noVNC server session.
24+
25+ Args:
26+ display (str): X display identifier.
27+ internal_port (int): Port for the VNC server.
28+ external_port (int): Port for the noVNC server.
29+ """
1530 # Start X server in display
16- xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf { display } "
31+ xserver_cmd = (
32+ f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR "
33+ f"+extension RENDER -logfile ./xdummy.log -config ./xorg.conf { display } "
34+ )
1735 xserver_thread = DockerThread (xserver_cmd )
1836 xserver_thread .start ()
1937 self .threads .append (xserver_thread )
2038 wait_for_xserver (display )
2139
2240 # Start VNC server without password, forever running in background
23- x11vnc_cmd = f"x11vnc -repeat -quiet -display { display } -nopw -forever -xkb -bg -rfbport { internal_port } "
41+ x11vnc_cmd = (
42+ f"x11vnc -repeat -quiet -display { display } "
43+ f"-nopw -forever -xkb -bg -rfbport { internal_port } "
44+ )
2445 x11vnc_thread = DockerThread (x11vnc_cmd )
2546 x11vnc_thread .start ()
2647 self .threads .append (x11vnc_thread )
2748
2849 # Start noVNC with default port 6080 listening to VNC server on 5900
2950 if self .get_ros_version () == "2" :
30- novnc_cmd = f"/noVNC/utils/novnc_proxy --listen { external_port } --vnc localhost:{ internal_port } "
51+ novnc_cmd = (
52+ f"/noVNC/utils/novnc_proxy --listen { external_port } "
53+ f"--vnc localhost:{ internal_port } "
54+ )
3155 else :
32- novnc_cmd = f"/noVNC/utils/launch.sh --listen { external_port } --vnc localhost:{ internal_port } "
56+ novnc_cmd = (
57+ f"/noVNC/utils/launch.sh --listen { external_port } "
58+ f"--vnc localhost:{ internal_port } "
59+ )
3360
3461 novnc_thread = DockerThread (novnc_cmd )
3562 novnc_thread .start ()
@@ -40,18 +67,38 @@ def start_vnc(self, display, internal_port, external_port):
4067 self .wait_for_port ("localhost" , external_port )
4168
4269 def start_vnc_gpu (self , display , internal_port , external_port , dri_path ):
70+ """Start a GPU-accelerated VNC and noVNC server session.
71+
72+ Args:
73+ display (str): X display identifier.
74+ internal_port (int): Port for the VNC server.
75+ external_port (int): Port for the noVNC server.
76+ dri_path (str): Path to the GPU device for hardware acceleration.
77+ """
4378 # Start X and VNC servers
44- turbovnc_cmd = f"export VGL_DISPLAY={ dri_path } && export TVNC_WM=startlxde && /opt/TurboVNC/bin/vncserver { display } -geometry '1920x1080' -vgl -noreset -SecurityTypes None -rfbport { internal_port } "
79+ turbovnc_cmd = (
80+ f"export VGL_DISPLAY={ dri_path } && "
81+ f"export TVNC_WM=startlxde && "
82+ f"/opt/TurboVNC/bin/vncserver { display } "
83+ f"-geometry '1920x1080' -vgl -noreset "
84+ f"-SecurityTypes None -rfbport { internal_port } "
85+ )
4586 turbovnc_thread = DockerThread (turbovnc_cmd )
4687 turbovnc_thread .start ()
4788 self .threads .append (turbovnc_thread )
4889 wait_for_xserver (display )
4990
5091 # Start noVNC with default port 6080 listening to VNC server on 5900
5192 if self .get_ros_version () == "2" :
52- novnc_cmd = f"/noVNC/utils/novnc_proxy --listen { external_port } --vnc localhost:{ internal_port } "
93+ novnc_cmd = (
94+ f"/noVNC/utils/novnc_proxy --listen { external_port } "
95+ f"--vnc localhost:{ internal_port } "
96+ )
5397 else :
54- novnc_cmd = f"/noVNC/utils/launch.sh --listen { external_port } --vnc localhost:{ internal_port } "
98+ novnc_cmd = (
99+ f"/noVNC/utils/launch.sh --listen { external_port } "
100+ f"--vnc localhost:{ internal_port } "
101+ )
55102
56103 novnc_thread = DockerThread (novnc_cmd )
57104 novnc_thread .start ()
@@ -65,11 +112,24 @@ def start_vnc_gpu(self, display, internal_port, external_port, dri_path):
65112 self .create_gzclient_icon ()
66113
67114 def wait_for_port (self , host , port , timeout = 20 ):
115+ """Wait for a TCP port on a host to become available within a timeout period.
116+
117+ Args:
118+ host (str): Hostname or IP address to check.
119+ port (int): Port number to check.
120+ timeout (int, optional): Maximum time to wait in seconds. Defaults to 20.
121+
122+ Raises:
123+ TimeoutError: If the port does not become available within the timeout.
124+ """
68125 start_time = time .time ()
69126 while True :
70127 if time .time () - start_time > timeout :
71128 raise TimeoutError (
72- f"Port { port } on { host } didn't become available within { timeout } seconds."
129+ (
130+ f"Port { port } on { host } didn't become available "
131+ f"within { timeout } seconds."
132+ )
73133 )
74134 try :
75135 with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as sock :
@@ -80,9 +140,15 @@ def wait_for_port(self, host, port, timeout=20):
80140 time .sleep (1 )
81141
82142 def is_running (self ):
143+ """Check if the VNC server is currently running.
144+
145+ Returns:
146+ bool: True if running, False otherwise.
147+ """
83148 return self .running
84149
85150 def terminate (self ):
151+ """Terminate all running threads and stop the VNC server."""
86152 for thread in self .threads :
87153 if thread .is_alive ():
88154 thread .terminate ()
@@ -91,10 +157,16 @@ def terminate(self):
91157 self .running = False
92158
93159 def get_ros_version (self ):
160+ """Get the current ROS version from the environment.
161+
162+ Returns:
163+ str: The ROS version as a string.
164+ """
94165 output = subprocess .check_output (["bash" , "-c" , "echo $ROS_VERSION" ])
95166 return output .decode ("utf-8" ).strip ()
96167
97168 def create_desktop_icon (self ):
169+ """Create a desktop icon to launch a terminal application."""
98170 try :
99171 desktop_dir = os .path .expanduser ("~/Desktop" )
100172 if not os .path .exists (desktop_dir ):
@@ -116,6 +188,7 @@ def create_desktop_icon(self):
116188 print (err )
117189
118190 def create_gzclient_icon (self ):
191+ """Create a desktop icon to launch the Gazebo client application."""
119192 desktop_dir = os .path .expanduser ("~/Desktop" )
120193 if not os .path .exists (desktop_dir ):
121194 os .makedirs (desktop_dir )
0 commit comments