Webview taken
from private repo
and pivoted to c++ & saucer for more code overlap. Go to that readme for more info. It could not be
included in that directory as anything in ./webview is embeddeed by saucer.
The client is a relatively simple Saucer webview-based application. It uses the shared_xmpp
libstrophe wrapper to communicate with the rover daemon. A webview was chosen due to the ability to embed a camera
webview in the UI. Any browser-compatible webui or video stream can easily be embedded in an iframe. Using a webview
also allows the client to easily be cross-platform. Generally, the client is designed to be as dynamic as possible so
that you can hopefully easily add features to the rover without recompiling the client.
Important Note! Sometimes after you edit the webview files, and compile, the CSS or js will get corrupted and break. To fix this, you simply need to restart and compile again, and it will work fine. I am not sure why this happens, but I suspect it is due to some quirk of how saucer injects the webview files, likely as a postbuild script.
The controller client is built as the client_saucer executable. It is one of the top-level CMake subprojects and
depends on the shared XMPP communication layer used by the rest of the rover system.
At a high level, the client project is organized as follows:
client/: Contains the native controller client target and client-specific C++ glue code.client/webview/: Contains the embedded frontend assets used by the client application.shared_xmpp/: Provides the reusable XMPP wrapper aroundlibstrophe. The client links against this library for rover communication.- Root
CMakeLists.txt: Fetches common dependencies, configures project-wide compiler options, and adds the client, rover, and shared XMPP subdirectories. .github/workflows/windows-build.yml: Defines the manual Windows/MSVC build used to produce the Windows client artifact.
The client target links against:
saucer::saucersaucer::embeddedshared_xmpp
The embedded frontend assets are bundled into the native executable through the Saucer embed step during the CMake build.
The client is split into a small native C++ layer and an embedded web frontend. The C++ side owns the native window, XMPP connection, rover handshake, telemetry monitoring, and command routing. The webview side owns the visible UI and calls back into C++ through Saucer bindings.
-
main.cpp: Main entry point for the controller client.- Creates the Saucer application, native window, and webview.
- Loads the embedded frontend assets.
- Exposes C++ functions to JavaScript, including login, command sending, and devtools toggling.
- Owns the high-level connection lifecycle:
- user login
- XMPP connection setup
- rover options handshake
- telemetry timeout monitoring
- reconnection/retry behavior
- Spawns background threads for blocking or long-running work so the UI thread remains responsive.
-
conn_wrapper.h: Thread-safe holder for the current XMPP client connection.- Stores the active
libstrophe_cppinstance. - Replaces old connections when a new login occurs.
- Disconnects stale clients when they are no longer current.
- Allows background retry/monitor threads to check whether they are still working with the active connection.
- Stores the active
-
misc_routing.h/misc_routing.cpp: XMPP routing helpers used by the client.- Registers the telemetry IQ handler.
- Sends the initial rover options request.
- Parses rover-provided camera feed URLs and command definitions.
- Sends rover command IQs when UI buttons are clicked.
- Logs server and rover communication details back into the webview UI.
-
CMakeLists.txt: Defines theclient_saucerexecutable.- Builds the C++ client files.
- Embeds the
webview/directory into the executable. - Links against Saucer and
shared_xmpp.
-
webview/index.html: Defines the main UI structure.- Login dialog.
- Top navigation/window controls.
- Telemetry/status panel.
- Dynamic rover control button grid.
- Camera feed area.
- Command log panel.
-
webview/js/index.js: Browser-side controller logic.- Handles login form behavior.
- Calls C++ bindings exposed through
window.saucer.exposed. - Provides JavaScript functions that C++ calls to update the UI.
- Dynamically creates rover command buttons.
- Updates telemetry rows and staleness timers.
- Manages camera iframes.
- Handles theme switching, log copying, custom window controls, and dashboard resizing.
-
webview/stylesheets/rover.css: Styling and layout for the embedded UI.- Defines light/dark theme variables.
- Lays out the dashboard columns and panels.
- Styles telemetry, controls, camera feed, login modal, log box, and window controls.
- Provides resizable panel/column UI styling.
Saucer is used as the bridge between the native C++ application and the embedded web UI.
JavaScript calls into C++ through exposed bindings such as:
Login(jid, password)SendCommand(command)toggleDevTools(enabled)
C++ calls back into JavaScript to update the UI after XMPP events, for example:
addLog(...)markRoverWaiting()markRoverReachable()markRoverUnreachable()clearControlButtons()addControlButton(...)updateTelemetry(...)setCameraIframe(...)clearAllCameraIframes()
This keeps most UI rendering logic in JavaScript while keeping XMPP, threading, and native window setup in C++.
The general runtime flow is:
- The user enters their XMPP JID and password in the webview login dialog.
- JavaScript calls the C++
Loginbinding. - C++ creates a
libstrophe_cppclient and starts the XMPP event loop on a background thread. - After XMPP login succeeds, the client repeatedly sends a
rover::getoptsIQ request until the rover responds. - When rover options are received, C++ populates the webview with:
- camera feed iframe URLs
- rover command buttons
- The client registers a telemetry handler for
rover::telemetryIQs. - Incoming telemetry is forwarded to JavaScript and displayed in the status panel.
- If telemetry stops for too long, the client marks the rover unreachable and restarts the rover options retry loop.
- When a user clicks a rover command button, JavaScript calls
SendCommand(...), and C++ sends the corresponding IQ command to the rover.
The client intentionally separates UI work from blocking network work:
- The main thread runs the Saucer application and webview event loop.
- The XMPP client runs on a background thread because the libstrophe event loop is blocking.
- A rover options retry thread repeatedly attempts the initial rover handshake until it succeeds.
- A telemetry monitor thread watches for stale telemetry and marks the rover unreachable when needed.
- A login timeout watchdog thread rejects the login attempt if the XMPP connection takes too long.
The XmppClientState wrapper is used so these detached background threads can tell whether their connection is still the
current one. If the user logs in again and a new XMPP client replaces the old one, stale retry/monitor threads exit instead
of continuing to update the UI for an old connection.
The client depends on both UI/runtime libraries and the shared XMPP communication stack.
- CMake 3.31 or newer
- A C++23-capable compiler
- Git
- A supported native build tool, such as Ninja, Make, or MSBuild depending on the platform
- Saucer: Native webview application framework used by the controller client.
- go to saucer.app for more info.
- Saucer is fetched automatically by the root CMake project through
FetchContent, so it does not need to be installed manually before configuring the project.
- Saucer embedded support: Used to package frontend assets into the native client binary.
- shared_xmpp: Internal static library used for rover/client XMPP communication.
- this is a submodule of this project and should be built and linked automatically by CMake.
Important note, Windows users will need to make sure the latest Visual C++ Redistributable is installed.
Linux users need to install libwebkitgtk-6.0-4 libadwaita-1-0
Because the client links against shared_xmpp, it also inherits the XMPP wrapper's dependencies:
- libstrophe: XMPP client library used by the wrapper.
- libxml2: XML parser used by libstrophe.
- zlib: Compression support used by libstrophe.
- TLS backend:
- On Linux, this is OpenSSL.
- On Windows, this is the native Windows Schannel stack.
The exact dependency resolution differs by platform.
On Linux, the project expects the XMPP stack to be available through the local system or user installation paths. In
particular, shared_xmpp expects a static libstrophe.a to be available.
Typical Linux development packages include:
sudo dnf install libstrophe-devel libxml2-devel zlib-devel openssl-devel pkgconf-pkg-configFor more portable Linux binaries, static archives may also be needed:
sudo dnf install libxml2-static zlib-static glibc-static libstdc++-staticDepending on distribution and architecture, libstrophe.a may need to be installed manually under ~/.local/lib or
another system library path searched by the project.
Honestly, once you have libstrophe installed, as per the build instructions on linux, you can build it and a llm should be able to help you install the dependencies. One of the decent uses of LLMs.
Welcome to hell, good luck soldier 🫡
On Windows, the GitHub Actions build uses MSVC and vcpkg-provided dependencies.
The Windows build installs:
libxml2:x64-windowszlib:x64-windows
The Windows build does not require OpenSSL. Instead, the project builds libstrophe with Windows Schannel support and
links
against native Windows networking/security libraries:
ws2_32secur32crypt32
Windows users should also make sure the latest Microsoft Visual C++ Redistributable is installed before running the built client.
A dedicated GitHub Actions workflow is available for building the Windows native client using MSVC.
Workflow name:
Windows Native Build (MSVC)
The workflow is manually triggered using the GitHub Actions UI.
The Windows build workflow performs the following steps:
- Checks out the repository.
- Restores or creates a vcpkg binary cache.
- Installs required Windows dependencies through vcpkg:
libxml2:x64-windowszlib:x64-windows
- Configures CMake with
CMAKE_PREFIX_PATHpointing to the vcpkg installation. - Builds the
client_saucertarget inRelWithDebInfomode. - Copies vcpkg DLLs into the client executable output directory.
- Uploads the Windows build outputs as a GitHub Actions artifact.
The workflow installs dependencies with:
vcpkg install libxml2:x64-windows zlib:x64-windowsThe vcpkg binary cache is stored under the GitHub workspace:
${{ github.workspace }}/vcpkg_cache
This helps reduce rebuild time by reusing previously compiled vcpkg packages.
The workflow configures the project using:
cmake -B build -DCMAKE_PREFIX_PATH="C:/vcpkg/installed/x64-windows" -DCMAKE_BUILD_TYPE=RelWithDebInfoIt also provides PKG_CONFIG_PATH for packages that expose pkg-config metadata:
C:/vcpkg/installed/x64-windows/lib/pkgconfig
The build intentionally uses CMAKE_PREFIX_PATH instead of injecting the vcpkg toolchain file. This keeps the Windows
configuration less invasive while still allowing CMake to locate vcpkg-installed packages. Allowing vcpkg to inject the
toolchain file caused significant issues with dependencies and the project structure.
The workflow currently builds the Saucer client target:
cmake --build build --config RelWithDebInfo --target client_saucerAfter building, the workflow copies vcpkg-provided runtime DLLs into the client output directory:
Copy-Item -Path "C:/vcpkg/installed/x64-windows/bin/*.dll" -Destination "build/client/RelWithDebInfo/"This ensures the uploaded client binary has the dynamic libraries it needs to run on Windows.
The final artifact is uploaded as:
HelelaniRover-Windows-Binaries
The artifact includes the Windows build output directories for both the client and rover build locations, including any
copied DLLs and debug symbols (.pdb).