diff --git a/js/index.js b/js/index.js index cb1e187a..72ae4474 100644 --- a/js/index.js +++ b/js/index.js @@ -30,8 +30,10 @@ function status(text) { document.getElementById("status").textContent = text; } -// The websockify URL to connect to is the same URL we are rendering this page on! -let websockifyUrl = new URL(window.location); +// This page is served under the /desktop/, and the websockify websocket is served +// under /desktop-websockify/ with the same base url as /desktop/. We resolve it relatively +// this way. +let websockifyUrl = new URL("../desktop-websockify/", window.location); websockifyUrl.protocol = window.location.protocol === "https:" ? "wss" : "ws"; // Creating a new RFB object will start a new connection diff --git a/jupyter_remote_desktop_proxy/handlers.py b/jupyter_remote_desktop_proxy/handlers.py index 08628733..3ba4e72b 100644 --- a/jupyter_remote_desktop_proxy/handlers.py +++ b/jupyter_remote_desktop_proxy/handlers.py @@ -1,10 +1,7 @@ import os -import shlex -import tempfile -from shutil import which import jinja2 -from jupyter_server_proxy.handlers import SuperviseAndProxyHandler +from jupyter_server.base.handlers import JupyterHandler from tornado import web jinja_env = jinja2.Environment( @@ -17,66 +14,9 @@ HERE = os.path.dirname(os.path.abspath(__file__)) -def get_websockify_command(): - # make a secure temporary directory for sockets - # This is only readable, writeable & searchable by our uid - sockets_dir = tempfile.mkdtemp() - sockets_path = os.path.join(sockets_dir, 'vnc-socket') - vncserver = which('vncserver') - - if vncserver is None: - # Use bundled tigervnc - vncserver = os.path.join(HERE, 'share/tigervnc/bin/vncserver') - - # TigerVNC provides the option to connect a Unix socket. TurboVNC does not. - # TurboVNC and TigerVNC share the same origin and both use a Perl script - # as the executable vncserver. We can determine if vncserver is TigerVNC - # by searching TigerVNC string in the Perl script. - with open(vncserver) as vncserver_file: - is_tigervnc = "TigerVNC" in vncserver_file.read() - - if is_tigervnc: - vnc_args = [vncserver, '-rfbunixpath', sockets_path] - socket_args = ['--unix-target', sockets_path] - else: - vnc_args = [vncserver] - socket_args = [] - - if not os.path.exists(os.path.expanduser('~/.vnc/xstartup')): - vnc_args.extend(['-xstartup', os.path.join(HERE, 'share/xstartup')]) - - vnc_command = shlex.join( - vnc_args - + [ - '-verbose', - '-geometry', - '1680x1050', - '-SecurityTypes', - 'None', - '-fg', - ] - ) - - return ( - [ - 'websockify', - '-v', - '--heartbeat', - '30', - '{port}', - ] - + socket_args - + ['--', '/bin/sh', '-c', f'cd {os.getcwd()} && {vnc_command}'] - ) - - -class DesktopHandler(SuperviseAndProxyHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.command = get_websockify_command() - +class DesktopHandler(JupyterHandler): @web.authenticated - async def http_get(self, *args, **kwargs): + async def get(self): template_params = { 'base_url': self.base_url, } diff --git a/jupyter_remote_desktop_proxy/server_extension.py b/jupyter_remote_desktop_proxy/server_extension.py index 9a86d9c1..a998f2a7 100644 --- a/jupyter_remote_desktop_proxy/server_extension.py +++ b/jupyter_remote_desktop_proxy/server_extension.py @@ -18,12 +18,15 @@ def load_jupyter_server_extension(server_app): server_app.web_app.add_handlers( ".*", [ + # Serve our own static files ( url_path_join(base_url, "/desktop/static/(.*)"), AuthenticatedFileHandler, {"path": (str(HERE / "static"))}, ), + # To simplify URL mapping, we make sure that /desktop/ always + # has a trailing slash (url_path_join(base_url, "/desktop"), AddSlashHandler), - (url_path_join(base_url, "/desktop/()"), DesktopHandler, {'state': {}}), + (url_path_join(base_url, "/desktop/"), DesktopHandler), ], ) diff --git a/jupyter_remote_desktop_proxy/setup_websockify.py b/jupyter_remote_desktop_proxy/setup_websockify.py new file mode 100644 index 00000000..9365f924 --- /dev/null +++ b/jupyter_remote_desktop_proxy/setup_websockify.py @@ -0,0 +1,65 @@ +import os +import shlex +import tempfile +from shutil import which + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +def setup_websockify(): + # make a secure temporary directory for sockets + # This is only readable, writeable & searchable by our uid + sockets_dir = tempfile.mkdtemp() + sockets_path = os.path.join(sockets_dir, 'vnc-socket') + vncserver = which('vncserver') + + if vncserver is None: + # Use bundled tigervnc + vncserver = os.path.join(HERE, 'share/tigervnc/bin/vncserver') + + # TigerVNC provides the option to connect a Unix socket. TurboVNC does not. + # TurboVNC and TigerVNC share the same origin and both use a Perl script + # as the executable vncserver. We can determine if vncserver is TigerVNC + # by searching TigerVNC string in the Perl script. + with open(vncserver) as vncserver_file: + is_tigervnc = "TigerVNC" in vncserver_file.read() + + if is_tigervnc: + vnc_args = [vncserver, '-rfbunixpath', sockets_path] + socket_args = ['--unix-target', sockets_path] + else: + vnc_args = [vncserver] + socket_args = [] + + if not os.path.exists(os.path.expanduser('~/.vnc/xstartup')): + vnc_args.extend(['-xstartup', os.path.join(HERE, 'share/xstartup')]) + + vnc_command = shlex.join( + vnc_args + + [ + '-verbose', + '-geometry', + '1680x1050', + '-SecurityTypes', + 'None', + '-fg', + ] + ) + + return { + 'command': [ + 'websockify', + '-v', + '--heartbeat', + '30', + '{port}', + ] + + socket_args + + ['--', '/bin/sh', '-c', f'cd {os.getcwd()} && {vnc_command}'], + 'timeout': 30, + 'new_browser_window': True, + # We want the launcher entry to point to /desktop/, not to /desktop-websockify/ + # /desktop/ is the user facing URL, while /desktop-websockify/ now *only* serves + # websockets. + "launcher_entry": {"title": "Desktop", "path_info": "desktop"}, + } diff --git a/setup.py b/setup.py index 5107f853..ad636738 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,12 @@ def run(self): "Programming Language :: Python :: 3", ], description="Run a desktop environments on Jupyter", - install_requires=[ + entry_points={ + 'jupyter_serverproxy_servers': [ + 'desktop-websockify = jupyter_remote_desktop_proxy.setup_websockify:setup_websockify', + ] + }, + stall_requires=[ 'jupyter-server-proxy>=1.4.0', ], include_package_data=True,