Mobipick Labs Docker GUI
The Mobipick Labs Docker GUI is a PyQt5 desktop application that controls the Docker-based Mobipick Labs ROS 1 simulation. It wraps Docker Compose commands, sets the environment required by the selected Docker image and ROS workspace, streams process output into GUI tabs, and performs cleanup when the application closes.
Related project and distribution links:
- Mobipick Labs repository: https://github.com/DFKI-NI/mobipick_labs
- PyPI package: https://pypi.org/project/mobipick-labs-docker-gui/
This README is developer documentation for maintaining and extending the
application. User-facing button and menu documentation lives in the
gui_user_documentation.md
resource and is rendered in the application from Help > Documentation.
Repository layout
.
|-- gui.py # Legacy shim that forwards to mobipick_gui.cli
|-- mobipick_gui/
| |-- cli.py # QApplication setup and CLI parsing
| |-- main_window.py # Main PyQt window and Docker/ROS orchestration
| |-- process_tab.py # QProcess plus log widget wrapper
| |-- log_widget.py # Buffered QTextEdit for high-volume logs
| |-- documentation_dialog.py # Rendered user documentation and search
| |-- flow_layout.py # Wrapping row layout used by the toolbar rows
| |-- bug_report.py # Diagnostic report builder
| |-- setup_wizard.py # Image setup and custom image choices
| |-- workspace_dialog.py # Workspace manager dialog
| |-- workspaces.py # Workspace registry and runtime env model
| |-- settings_transfer.py # Portable import/export of GUI settings
| |-- window_control.py # window backends: wmctrl/xprop (X11), GNOME Shell extension (Wayland)
| |-- window_layout.py # window layout capture and replay helper
| |-- remote_control.py # HTTP remote-control server, events, shell sessions
| |-- remote_adapter.py # MainWindow bridge used by the remote-control server
| |-- remote_client.py # mobipick-labs-docker-gui-remote CLI client
| |-- config.py # Bundled/user config loading and defaults
| |-- launch_arguments_dialog.py # Profile-wide launch option editor
| `-- resources/
| |-- docker-compose.yml
| |-- custom_entrypoint.sh
| |-- clean.bash
| |-- gui_user_documentation.md
| |-- config/
| | |-- gui_settings.yaml
| | |-- button_commands_labs.yaml
| | |-- worlds.yaml
| | `-- docker_cp_image_tag.yaml
| `-- scripts/
| |-- enter_host_shell.py
| |-- terminal.bashrc
| `-- ros_workspace_setup.bash
|-- tests/mobipick_gui/ # pytest regression tests
|-- pyproject.toml # Packaging metadata and package data
|-- MANIFEST.in # Source distribution manifest
`-- doc/ # Project imagery
Private experiments and templates under mobipick_gui/resources/private/ are
excluded from package data and source distribution output.
Runtime architecture
mobipick_gui.cli.main() creates a QApplication, instantiates
MainWindow, and forwards unknown arguments to Qt. MainWindow owns the GUI
state, menu actions, process tabs, workspace/image selection, recording state,
and shutdown sequence. Before creating the main window, startup acquires a
per-user runtime lock. If another Mobipick Labs Control GUI is already running,
the new process shows an error and exits instead of opening a second window.
At startup, the GUI refreshes its per-user desktop entry at
~/.local/share/applications/mobipick-labs-docker-gui.desktop (or below
XDG_DATA_HOME). Its desktop-file ID and StartupWMClass match the stable Qt
application identity, allowing X11 window managers and Wayland compositors to
associate console-launched windows with the bundled Mobipick icon.
That refresh rewrites the entry's Exec from the running invocation
(desktop_launch_command), so a pinned dock icon keeps following the way the
GUI is actually started. python -m mobipick_gui is the exception: it leaves
the package's __main__.py in argv[0], and running that file as a script
fails with attempted relative import with no known parent package. The
launcher therefore records the gui.py shim next to the package, or
python -m mobipick_gui when no shim exists, so a debugging run never leaves
the dock icon pointing at a command that cannot start.
The same refresh installs hidden mobipick-rviz.desktop, mobipick-rqt.desktop,
and mobipick-gazebo.desktop entries next to it. GNOME Shell 45 and newer
ignore the _NET_WM_ICON that RViz, rqt, and Gazebo set on their own windows,
and containers cannot register desktop entries on the host, so without these
entries the dock and Alt-Tab switcher show a generic gear for every container
tool window. Each entry is NoDisplay, has an inert Exec, and matches
windows by identity: StartupWMClass=rviz and StartupWMClass=gazebo cover
RViz and the Gazebo client (both always X11/XWayland because of OGRE; gzclient
names its Qt application gazebo), while the RQt entry uses
StartupWMClass=python3, the app_id that Qt 5.12 reports for every
Python-based rqt tool on native Wayland. GNOME resolves a StartupWMClass hit
on either WM_CLASS part before it falls back to the desktop-file ID, so GUI
launches use the ID as a fallback identity: the RQt Tables button, custom
buttons and Custom Command entries that invoke an rqt tool
(desktop_launcher.desktop_entry_for_command), and the Sim button set
RESOURCE_NAME=mobipick-rqt in the container. Qt's XCB platform uses that as
the WM_CLASS instance, so rqt plugin windows (rqt_tables_demo, rqt_graph,
the sim's rqt_robot_steering, ...) resolve to the RQt entry while Gazebo and
RViz windows from the same launch keep their own icons. Windows launched from
terminal tabs keep their own WM_CLASS and therefore only match when it equals
one of the StartupWMClass values above.
Matching has a second precondition on X11 and XWayland: Mutter marks a window
whose WM_CLIENT_MACHINE differs from the compositor's hostname as remote, and
GNOME Shell never associates remote windows with desktop entries. Qt fills that
property from the container hostname, so the GUI exports
MOBIPICK_CONTAINER_HOSTNAME=<host hostname> to every docker compose
process; the mobipick_cmd service interpolates it as
hostname: ${MOBIPICK_CONTAINER_HOSTNAME:-} and the mobipick simulation
service as hostname: ${MOBIPICK_CONTAINER_HOSTNAME:-mobipick}. Manual Compose
invocations leave the variable unset and keep the previous hostnames. Because
GAZEBO_MASTER_URI and the fallback ROS_MASTER_URI point at mobipick, the
GUI starts the simulation with docker compose run --use-aliases so the
service's mobipick network alias resolves inside the sim container and from
the tool containers (Docker's embedded DNS otherwise resolves it through the
container hostname). The variable is only exported while MOBIPICK_ROS_USE_IP
is 1 (the default), because nodes that advertise hostnames instead of IPs
must keep a hostname their peers can resolve. As a visible side effect, prompts
inside container terminals show the host's hostname. The tool entries live in
mobipick_gui.desktop_launcher.TOOL_DESKTOP_ENTRIES; extend that table when
adding a new container tool launch. The bundled rviz_icon.png,
rqt_icon.png, and gazebo_icon.svg are copies of the upstream ROS Noetic
rviz, rqt_gui, and Gazebo 11 icons.
To also add the launcher to the Ubuntu/GNOME dock for one-click startup, run the checkout helper:
./install_desktop_launcher.sh
Package installs provide the equivalent command:
mobipick-labs-docker-gui --install-desktop-launcher
The command preserves the existing dock favorites, is safe to run repeatedly, and only changes files and settings for the current user. The Setup Wizard offers the same action as a checked-by-default setup choice.
Runtime commands are executed in two ways:
- Long-running tasks use
QProcessthroughProcessTab. Output is merged, buffered to complete lines, sanitized for terminal escape sequences, converted from ANSI color to HTML when needed, and flushed intoLogTextEdit. Uncolored ROS warning lines receive a yellow fallback. - Short helper commands use
subprocess.run()throughMainWindow._sp_run(), which injects the same runtime environment and logs the command to the GUI.
Docker Compose is always invoked by the GUI with the bundled compose file and a fixed project name. The compose file is not intended to be run directly during normal GUI use, because the GUI also tracks process state, xhost access, tabs, recording, and cleanup.
Prerequisites
The application targets Linux desktops with X11 or Wayland.
- Python 3.8 or newer.
- PyQt5 5.15 or newer.
- Docker Engine and the Docker Compose plugin available to the current user.
- An X11, XWayland, or native Wayland desktop path for Gazebo, RViz, and RQt. Screen recording still requires X11/XWayland.
- Optional but recommended: NVIDIA Container Toolkit for GPU-accelerated simulation.
- Optional tools for specific features:
wmctrlandxpropfor window layout capture/replay on X11 sessions.- On GNOME Wayland sessions, the bundled GNOME Shell extension instead
(
mobipick-labs-docker-gui --install-gnome-window-extension, then log out and back in), becausewmctrlcannot see native Wayland windows. graphvizfor workspace graph rendering.ffmpegfor Auto Launch screen recording.
Common Ubuntu setup:
Use Tools > Setup Wizard and copy the Host Dependencies script. The script
is interactive: before each major step it explains what will run, why it is
needed, prints the commands, and asks for y/N confirmation. The guarded steps
install apt prerequisites, replace the Docker apt repository key/source, verify
Docker package candidates, install Docker Engine and the Compose plugin,
install selected optional tools, restart Docker services, configure docker
group access, and test Docker with both sudo and the current user.
On GNOME Wayland, the same dependency step includes the one-time bundled
window-extension install command and reminds the user to log out and back in.
Log out and back in after changing Docker group membership, or start a shell
with newgrp docker.
When no matching Mobipick Labs image is installed, the GUI opens the setup wizard so you can pull one on the host PC with streamed output. If you choose the manual wizard option, run one of these commands and confirm in the wizard when it finishes:
docker pull ozkrelo/x_mobipick_labs:noetic-v1.1
docker pull ozkrelo/x_mobipick_labs:noetic-v2.0
Installation
Install the released package from PyPI:
python -m pip install mobipick-labs-docker-gui
If you installed an older released version, upgrade it in the same Python environment:
python -m pip install --upgrade mobipick-labs-docker-gui
python -m pip show mobipick-labs-docker-gui
If the command was installed with --user and your shell cannot find
mobipick-labs-docker-gui, make sure Python's user script directory is on
PATH, for example ~/.local/bin on many Linux systems.
Development setup
Create an editable install from the checkout:
python -m pip install -e .
Install test-only tools when working on regression tests:
python -m pip install pytest pytest-qt
Launch the GUI from the checkout:
mobipick-labs-docker-gui --verbose 2
Equivalent debug entry points:
python -m mobipick_gui --verbose 2
python gui.py --verbose 2
The verbosity option accepts levels 1 through 3. Unknown CLI arguments are
passed to Qt, for example -platform offscreen in headless checks.
Tests
Run the full test suite:
pytest
Most GUI tests set QT_QPA_PLATFORM=offscreen and stub Docker discovery or
process methods. Keep new tests Docker-independent unless the test explicitly
targets Docker command construction.
Current coverage includes:
- configuration loading and writable user config paths;
- workspace registry, switching, import/export, and image/workspace matching;
- setup wizard and custom image profile persistence;
- remote ROS master behavior;
- Auto Launch and recording state transitions;
- menu-only status/image controls and menu tooltips;
- bug report formatting;
- documentation dialog rendering and keyword search.
Packaging
The package version is declared in pyproject.toml. The fallback version used
when running directly from an unpackaged source tree lives in
mobipick_gui/version.py; keep both values in sync for releases.
Build source and wheel artifacts with:
python -m pip install --upgrade build
python -m build
Optionally validate the artifacts before uploading:
python -m pip install --upgrade twine
python -m twine check dist/*
Package data is declared in both pyproject.toml and MANIFEST.in. When adding
new runtime assets under mobipick_gui/resources/, update both files so editable
installs, wheels, and source distributions all behave the same.
The console scripts installed by the package are:
mobipick-labs-docker-gui = mobipick_gui.cli:main
mobipick-labs-docker-gui-remote = mobipick_gui.remote_client:main
PyPI release flow
Publishing is handled by .github/workflows/python-publish.yml. The workflow
builds the distributions and uploads them to PyPI when a GitHub Release is
published. Merging to main alone does not publish a new PyPI version.
Recommended release steps:
- Run the tests and build locally.
- Merge the release branch to
main. - Create and publish a GitHub Release from
mainwith./release.sh. The script suggests the next patch tag from existing releases/tags and updates, commits, and pushes the package version bump before tagging when needed. Explicit--targetreleases still require the package version to already match the tag. - Confirm the workflow succeeds, then verify the new release on https://pypi.org/project/mobipick-labs-docker-gui/.
Configuration model
Bundled defaults live under mobipick_gui/resources/config/.
mobipick_gui.config.CONFIG_DEFAULTS supplies hard defaults, then
config/gui_settings.yaml is merged over them, then the per-user
gui_settings.yaml is merged last.
Important local configuration and environment overrides:
MOBIPICK_GUI_DATA_ROOTpoints the package at an alternate resources root.MOBIPICK_GUI_CONFIGpoints to an alternate per-user GUI settings file.launch_sequence.robot_race: truein the per-usergui_settings.yamlreplaces Auto Launch progress bars with full-resolution synchronized robot animations.ROBOT_RACE=trueremains available as an environment override. The default isfalse; the setting can also be changed under Tools > Automation > Use Robot Race Animations.MOBIPICK_WORKSPACE_CONFIGpoints to an alternate workspace registry.XDG_CONFIG_HOMEandXDG_DATA_HOMEcontrol the default per-user roots.
Default per-user state locations:
~/.config/mobipick-labs-docker-gui/gui_settings.yaml
~/.config/mobipick-labs-docker-gui/workspaces.yaml
~/.config/mobipick-labs-docker-gui/window_layouts/{workspace}.yaml
~/.config/mobipick-labs-docker-gui/docker_cp_image_tag.yaml
~/.config/mobipick-labs-docker-gui/docker_cp_profiles/{workspace}_docker_cp_image_tag.yaml
~/.config/mobipick-labs-docker-gui/launch_sequences/
~/.config/mobipick-labs-docker-gui/profiles/
~/.local/share/mobipick-labs-docker-gui/recordings/
~/.local/share/mobipick-labs-docker-gui/image_builds/
Keep bundled resource files immutable at runtime. User edits should be written to per-user config/data paths.
The top-level Settings menu exposes migration and troubleshooting actions:
Export All Settings... writes the workspace registry, per-user GUI settings,
and workspace profiles to one portable YAML file; Import All Settings...
restores that file under a chosen workspace master folder; Show Configuration
Paths displays the writable config/data paths that the GUI manages; Copy
Full Reset Command... shows a destructive warning and copies an opt-in
terminal command for deleting all per-user GUI config/data roots from the PC.
The reset command requires typing DELETE_MOBIPICK_GUI_CONFIG in the terminal
before it runs rm -rf; it does not remove Docker images, Docker containers,
ROS workspaces, the source checkout, or bundled package defaults.
Docker and ROS services
The bundled compose file defines three services:
mobipickruns the simulator.mobipick_cmdruns local ROS tools, scripts, terminals, and custom commands.mobipick_remote_cmdruns tools with host networking for external ROS master mode.
The GUI injects these important values into Docker commands:
MOBIPICK_IMAGEselected from the image combo box.MOBIPICK_WORLDselected fromworlds.yaml.MOBIPICK_CONTAINER_USER,MOBIPICK_CONTAINER_ENTRYPOINT, andMOBIPICK_CONTAINER_WORKDIRderived from image profiles.MOBIPICK_UID,MOBIPICK_GID,MOBIPICK_HOST_USER,MOBIPICK_HOST_GROUP, andMOBIPICK_HOST_HOMEderived from the host user.- Workspace mount and ROS environment values from
WorkspaceRegistry. ROS_MASTER_URIfrom local Roscore or remote ROS master mode.
In remote ROS master mode no local Roscore runs, so the GUI creates the
external mobipick network itself before starting any container (compose
otherwise fails with network mobipick declared as external, but could not be found), and a button that pins service: mobipick_cmd is run as
mobipick_remote_cmd instead: the bridge addresses of mobipick_cmd are
unreachable from the robot, while the remote service is the same tool on host
networking.
The GUI creates or reuses the external Docker network named mobipick and
labels one-off containers with mobipick.exec and mobipick.tab so they can be
found and stopped reliably.
Workspace model
WorkspaceRegistry stores host catkin workspaces, their inheritance, optional
workspace-specific Docker images, button profiles, auto-launch profiles, and
simulator command overrides.
For host workspace mode, the registry mounts the common workspace root once
inside Docker at the canonical container root ~/ros_ws. It maps selected
workspaces and underlays into that root, exports MOBIPICK_WORKSPACE_*
variables, and provides fallback source paths when a workspace is not built.
Interactive terminals pin catkin build to the selected workspace root, so
package-local operations such as catkin build --this are not redirected by
stale nested .catkin_tools metadata. An explicit --workspace option still
takes precedence.
WorkspaceManagerDialog is the UI for:
- choosing or creating a master folder;
- discovering child workspaces with
src/; - adding or creating standalone workspaces;
- editing inheritance and image/button/launch profiles;
- building the active workspace inside Docker;
- exporting/importing portable GUI settings;
- rendering the workspace graph.
The main window keeps the workspace manager alive as an independent non-modal top-level window, allowing it and the main log window to be minimized or used separately.
Builds use catkin build inside the selected development image. Public root
images are configured as image-default only and do not mount host workspaces.
Image profiles and setup wizard
Image behavior is controlled by images in gui_settings.yaml.
defaultis the preferred Docker image.discovery_filterscontrols which local images appear in the combo box. The Configure Image Filters dialog shows these filters next to the blacklist and previews which local Docker images will be used, ignored, or hidden.blacklistcontains image refs or glob patterns ignored after discovery filtering.profilesmaps image refs or glob patterns to container user behavior, workspace support, compatible workspaces, working directory, entrypoint, and tooltip description.
On startup, the setup wizard opens automatically only when no Docker image
matching images.discovery_filters is installed. A missing configured default
does not reopen the wizard: the GUI uses an installed workspace image, then a
compatible host-user image, then another discovered Mobipick Labs image for
that session without changing the saved default. Missing optional host tools
are reported in the GUI Log tab instead. Set
MOBIPICK_GUI_SUPPRESS_OPTIONAL_DEPENDENCY_WARNINGS=1 to suppress those
warnings.
The setup wizard first explains operating system and hardware compatibility,
including the tested Ubuntu releases, the dedicated CUDA-capable NVIDIA GPU
requirement, and reference GPU memory usage. It then checks common Ubuntu host
dependencies and shows one checkbox per package so users can copy a selected
set of short, commented apt commands into a terminal, install the tools
themselves, and mark the step done. Docker setup uses explicit repository,
package installation, and user-group commands without wrapping them in an
interactive Bash script. The following
NVIDIA Container Toolkit page links to the official installation guide, can
copy the guide URL or the elevated terminal test command, and runs a
non-blocking current-user Docker GPU test. That test passes only when the
container exits successfully and reports NVIDIA-SMI, driver, CUDA, and GPU
table evidence. The wizard can then
pull public images on the host PC with streamed output, pause for a manual pull
confirmation, choose a default image, build a host-user development image, and
clone/build DFKI-NI/mobipick_labs from source in a host-mounted workspace.
Its setup choices also include installing the application launcher and adding
it to the Ubuntu/GNOME dock. This choice is enabled by default and can be
cleared on non-GNOME desktops or when launchers are managed separately.
Each optional wizard page has a skip button.
The summary page can launch the stock Docker Compose simulation as an isolated
display smoke test and capture its combined terminal output. It disables host
workspace mounting, runs as root against the workspace baked into the image,
and prefers the simplest locally available public image in this order:
mobipick_labs, then x_mobipick_labs. If neither family is available
locally, it uses the Docker image selected as the wizard default.
This keeps private workspace commands and host-user image settings out of the
basic display test. If Gazebo is not visible, I Cannot See the Simulation
stops the test and opens a
privacy-scrubbed bug report with the output and relevant host, GPU, image,
workspace, and GUI diagnostics selected for a GitHub issue.
It does not enable docker cp paths by default; configure any copy rules later
from Tools > Docker > Configure Docker cp Paths.
The custom image builder writes a Docker build context under the per-user data
directory, copies custom_entrypoint.sh, adds a host-matching user, installs
passwordless sudo, and tags the result according to the wizard fields.
The source install step creates
<master folder>/clean_mobipick_labs_ws/src/mobipick_labs by default, runs the
work inside Docker with the workspace mounted to the host, sources
/opt/ros/noetic/setup.bash, then executes ./install-deps.sh and ./build.sh.
Output streams into an Install Source tab. Existing git checkouts are
updated; existing non-git paths stop the step with an explicit error.
Button profiles
Default toolbar buttons are loaded from
resources/config/button_commands_labs.yaml. load_button_layout() supports
workspace-specific replacements through the workspace registry.
Use Tools > Configure Toolbar Buttons to edit the active profile from the
GUI. Workspace edits are saved as writable per-user copies named for the active
workspace, and that workspace is updated to point at its copy. Packaged global
profiles are also copied before saving. The dialog shows the editable button
key, label, command, stop command, compose service, host execution, and
tooltip; other execution fields are preserved when saving. The stop-command
column is shown only when at least one button uses host execution. Its cells
are enabled only for host command buttons, and the command runs after the GUI
sends SIGINT to the original command process.
Toolbar profiles remain editable while workspace processes are running. Saved
changes are used by later launches without rebuilding process tabs. A running
command retains the launch and stop settings with which it was started; if its
button is removed from the profile, the button remains available until that
command stops.
Use Import from Workspace... to select another registered workspace and choose the individual toolbar buttons to copy. Imported buttons replace active buttons with the same key, while new keys are appended; buttons not selected for import remain unchanged. Saving writes an independent profile for the active workspace, so later edits do not change the source workspace. Use Load Profile and Export Profile to move complete button configurations as one YAML file. The automatic save location is the per-user XDG config directory so an installed package is never modified at runtime; exporting is the way to place a profile in a private repository or share it with another setup.
Button entries can be:
kind: builtinwith actions such assim,tables_demo,rviz, andrqt_tables;kind: commandwith an arbitrary command executed either in Docker or on the host.
Every editable top-row button has a command. Default commands for the bundled
sim, tables, rviz, and rqt buttons live in the button profile and are
used by their builtin start/stop wrappers. Legacy workspace sim_command
values remain supported and are written into the button profile when saved from
the GUI editor.
Check Host in the editor to run a command directly on the host and save
host: true in the button profile. Unchecked commands continue to run in the
configured Mobipick Docker service. Stopping Roscore shuts down the dependent
Docker stack but leaves running host commands alive. Host commands also start
directly without checking or automatically starting Roscore. When the GUI's
local Roscore is already running, a newly started host command receives
ROS_MASTER_URI pointing to that container and ROS_IP set to the host side
of the mobipick Docker bridge. In remote ROS master mode a host command
instead receives the configured remote ROS_MASTER_URI and the host address
that routes to it. These overrides apply only to host commands.
A command (and a stop_command) is a Bash command line: host commands run
through bash -lc and Docker commands through bash -c inside the container,
after the selected generic arguments are appended. To give one button its own
environment, prefix the command with shell assignments, which is also the way
to set a per-machine path without editing the script it starts:
- key: disc
kind: command
host: true
command: DISC_DATASET_ROOT=$HOME/datasets/scene_graph $HOME/ros1_ws/amenable_ws/src/disc_mapping_ros/scripts/disc.sh
The script also inherits the environment the GUI was started with, so a variable exported before launching the GUI works as well, but it is invisible in the profile; prefer the explicit prefix.
Command entries can declare:
requires_roscore;reuse_tab;world_config_requiredandworld_arg_name;setuporpre_command;host;stop_command;log_command;pass_ros_master_uri;service, for choosing the compose service used by Docker command buttons. Leave it empty for the normal tool service, or usemobipickfor launch files that start Gazebo themselves and need the simulator service identity;opens_window,truefor a button whose process exists to show a window (viewers, rqt tools). Headless mode's Auto Launch skips it. Unset, only the builtin RViz and RQt buttons count as windows;headless_args, a mapping of ROS arguments the button gets while headless mode is on, e.g.{gui: 'false'}for the simulator. A name that is also a generic argument slot replaces the selected value; the others are appended.
Headless mode
The Headless switch next to Auto Launch (remote: POST /headless) starts
nothing that opens a window: Auto Launch skips the opens_window buttons (a
process that depended on a skipped one inherits that one's dependency),
does not replay the window layout (window_layout_applied still fires at the
same moment, with skipped: headless), omits the Auto Launch progress window,
and every button start receives its headless_args. It applies to the next
launches, not to running processes,
and a window button pressed by hand still opens its window. The choice is
remembered across restarts; headless.enabled_by_default in
gui_settings.yaml sets it for a fresh configuration.
Profiles can also define as many as 64 generic ROS arguments on each button.
For slot 1, set arg_1_name to the argument name, provide the dropdown values
in arg_1_options, and set arg_1_applies: true on every button that should
receive it; slots 2 to 64 use the corresponding arg_2_* to arg_64_* fields.
A slot must use the same name and option list throughout one profile. Named
slots appear as combo boxes in the main GUI or Advanced Launch Options dialog,
and an enabled button command receives the selected name:=value. If no slot
has a name, no generic argument controls are shown. Select a button in
Configure Toolbar Buttons, then use Configure Arguments... to edit the
names, comma-separated combo options, advanced placement, and per-button flags
in a separate dialog. Argument details remain out of the main profile table
and travel with loaded or exported profiles.
Set arg_N_advanced: true to place a selector in the advanced dialog.
Set arg_N_advanced: false to keep a selector on the main window, including
one that defaults to advanced placement. arg_N_description is optional
multi-line help text; arg_N_option_descriptions maps selectable values to
their tooltips. A profile can omit either field. The description appears on
the selector and its label, and its first line appears below advanced options.
The remote GET /args response includes description and
option_descriptions.
The Advanced Launch Options dialog lays its options out in columns, each filled from top to bottom before the next
(one column up to 4 options, two up to 16, three above) and scrolls when the screen is smaller than the options.
The Advanced Launch Options dialog has Edit..., which opens one editor for
every argument in the loaded button profile. It edits names, choices, help
text, placement, and the buttons each argument applies to, and can add or
remove arguments. Save uses the same workspace profile path and reload as
Configure Toolbar Buttons. The editor enforces the six argument selectors
plus world limit on the main window.
The Gazebo client (gui), use_mtc, jev_min_confidence, and
voice_languages selectors use that dialog by default. The main window
shows at most six generic arguments alongside the world selector; additional
ones appear in the advanced dialog. Their selected values still use the
normal command, option-rule, saved-selection, and remote API paths.
A search box at the top of the advanced dialog (focused on open) filters the
options live by name, description or current value, case-insensitive; Esc
clears it, and hidden options keep their selected values.
Option rules
A profile can make dropdown options invalid under conditions without any
workspace-specific GUI code. mobipick_gui/option_rules.py always loads
resources/config/option_rules.yaml, then loads <profile stem>_rules.yaml
beside the button profile or option_rules.yaml in the same directory.
The shared rules keep remote-master mode on cic_tables, disc_mode=cpu,
and anygrasp_mode=real when those selectors are present. All invalid
selections are switched automatically and reported in one warning. For
example, profile-specific rules can add other conditions:
rules:
- when:
remote_master: true # "Use remote ROS master" is on
only:
world: [cic_tables] # every other world becomes invalid
reason: the real robot only runs the cic_tables environment
- when:
model_profile: [o3, o3-jev] # a list matches any entry
invalid:
disc_mode: [cpu]
- when:
remote_master: true
running.tables_demo_bringup: false
block_start: all # or a list of button keys
except: [tables_demo_bringup]
reason: on the real robot start tables_demo_bringup first
- when:
remote_master: true
remind_start: [tables_demo_bringup] # or all
notice: launch rgbd_snapshot_server.py on the real robot
clipboard: rgbd_snapshot_server # optional
- when:
remote_master: true
start_args: # button key -> launch arguments
disc_ros:
start_keyframe_node: false # appended as start_keyframe_node:='false'
reason: the keyframe node runs on the robot PC
when conditions and the invalid / only lists name world or any generic
argument name; when can also read remote_master and
running.<button key>. Every condition of a rule must hold. Invalid options
are greyed out with the reason as tooltip. A selection that becomes invalid
switches to the first valid option; when the user's own change (a dropdown or
the remote master checkbox) caused it, a non-modal popup explains it.
block_start refuses starting toolbar buttons, Roscore and Terminal (never
stopping them) with a popup. Pressing Auto Launch checks every step first,
treating the run's own steps as running (so a rule waiting for a step of
the same run passes), and refuses the whole run with a popup when any step
stays blocked. A step still blocked when its turn comes is logged and never
marked ready. POST /args rejects invalid values and a button press returns
accepted: false with the rule's reason instead of opening a popup.
remind_start does not refuse anything: when a named toolbar button starts
(by click, remote control or Auto Launch) the GUI logs the notice, shows it
in an information popup and copies clipboard, if set, to the clipboard.
start_args adds name:=value launch arguments to a command button while
its rule holds, after the toolbar arguments, so one button runs correctly in
simulation and on the real robot. The arguments are read when the button
starts (a running process keeps what it started with) and show in the
remote API's full_command.
running.* conditions are read when a start or dropdown change is evaluated,
not polled. Malformed rules are skipped and reported in the GUI log. The
profile editor does not rewrite the rules file.
The GUI normalizes all entries and creates matching process tabs and start/stop visual state.
roscore and terminal are fixed top-row buttons and are not stored in button
profiles. sim and rviz are required profile buttons: they cannot be
removed, but their command fields can override the default simulation and
RViz launch commands. Other profile buttons can be added, removed, reordered,
or changed.
Auto Launch
load_launch_sequence_plan() resolves Auto Launch YAML from a workspace
profile, per-user launch sequence directory, or fallback filenames derived from
the active button profile.
The saved format stores:
mode:legacyoradvanced;timeline: legacy button key plus fixedat_seconds;processes: advanced button definitions withduration_seconds, optionaldepends_on,dependency_type(hardorsoft), andready_percentage;process_settings: settings for all advanced rows, including disabled rows, so temporarily unused dependency choices are retained;shutdown.order: reverse or custom stop order;- optional
shutdown.skip; - optional button text/tooltip metadata;
recording.start_delay_seconds.
AutoLaunchWizard keeps the fixed-delay editor in its Legacy tab and provides
a dependency-aware Advanced tab. Old YAML without mode or processes is
loaded as Legacy. In Advanced mode, hard dependents wait for the dependency's
full readiness duration, while soft dependents wait for the configured
percentage. Processes already running at the start are ready immediately.
Each Advanced row also has an interactive readiness measurement: Measure
launches the process at time zero, and Ready records the user's confirmation
into duration_seconds, rounded upward to one decimal place.
Import... and Export... move a plan between the wizard and an
arbitrary YAML file. Export writes the shown state with
save_launch_sequence_plan(); import parses the file with
read_launch_sequence_file() (the strict parser load_launch_sequence_plan()
also uses) and only refills the widgets, so the active plan changes when the
user presses Save. Imported button text overrides are kept for that save.
In remote ROS master mode the local Roscore and simulation entries are dropped
from the sequence and from the progress window, since that master already
provides them; advanced dependents of the simulation start at time zero, and a
shortened legacy timeline is shifted so its first remaining entry starts right
away.
Starting Auto Launch displays an always-on-top readiness progress window. Its
duration is the latest legacy timeline offset or the effective advanced
dependency schedule (including already-running process shortcuts). It reports
per-process progress, includes saved window-layout replay as a final milestone,
reports completion for one second, and then hides automatically. With
window_layout.apply_delay_ms: auto, layout replay runs one second after every
process is ready. Unmatched saved entries are retried for 30 seconds rather
than polled indefinitely; launching another managed window starts a fresh
retry window.
Profiles are saved to a writable per-user path when the source is a packaged
resource. Auto Launch can also coordinate window layout replay and delayed
recording startup.
Responsive main-window layout
Every control row of the main window (workspace, ROS master, toolbar buttons,
Auto Launch/argument row, scripts, custom command, bottom controls, and search)
uses FlowLayout from flow_layout.py instead of QHBoxLayout. A
QHBoxLayout makes a window at least as wide as the sum of its children, so a
workspace with many button profiles pinned the main window to a minimum width
wider than a laptop panel: the window could neither be shrunk nor usefully
maximized. FlowLayout wraps its items onto additional lines, so the minimum
width is only that of the widest single item; items whose size policy expands
still share the slack of their own line, and a zero-width expanding spacer on a
line keeps that slack instead of stretching the widgets next to it. Nested
wrapping rows work because the layout clamps each item to the line width and
asks it for heightForWidth.
Two helpers in main_window.py support this: _configure_shrinkable_combo
gives combo boxes a minimum contents length (image references, workspace paths,
and script names are otherwise wider than the screen), and _labeled_control
groups a label with its control so a wrap never separates them.
window_utils.fit_geometry_to_screen clamps a restored geometry to the
available area of the screen the window will appear on. Geometry saved on a
large external monitor is both too big and off-screen on a laptop panel, which
leaves the window unreachable. On Wayland only the size is applied, as before.
When adding controls to the main window, add them to the existing wrapping rows and verify at a narrow width (for example 800 px) that nothing is clipped.
Recording and window layout
Recording captures X11 screen video with ffmpeg -f x11grab. It is armed by the
GUI checkbox and starts only after Auto Launch begins and the timeline/layout
delay has elapsed, or at once through POST /recording/start on the remote
API. Recording sessions create timestamped folders containing the MP4,
ffmpeg.log, and saved HTML logs when requested.
A recording is a list of segments (segments/segment_NNN.mp4): Pause
(Recording Control window or POST /recording/pause) ends the running ffmpeg
segment and Resume starts the next one, so idle time is never captured.
Stop concatenates the segments into <name>.mp4 and renders
<name>_<speedup>x.mp4 next to it (recording.speedup, default 4); the API
emits recording_exported with both paths when the export is done.
Window layout capture uses WindowLayoutManager on top of a backend from
window_control.py. On X11 sessions the backend shells out to wmctrl and
xprop. On Wayland sessions those tools only see XWayland windows, so the
backend talks over D-Bus to the GNOME Shell extension shipped in
mobipick_gui/resources/gnome-shell-extension/ (installed with
mobipick-labs-docker-gui --install-gnome-window-extension; rerun this after
GUI upgrades because GNOME Shell only loads extension changes after a fresh
login). The extension exposes ListWindows, MoveResize, SetWorkspace,
Activate, ClearAttention, Unmaximize, and SetAbove on
/org/gnome/Shell/Extensions/MobipickWinCtl, and windows are addressed by the
Mutter window id. MainWindow.keep_window_above uses
SetAbove for the always-on-top helper windows, since Wayland ignores
Qt.WindowStaysOnTopHint. When the extension is not available on Wayland the manager
falls back to wmctrl for XWayland windows.
The manager records the baseline windows present when the GUI starts, excludes
the GUI/helper windows during capture, stores a separate layout for each active
workspace, and applies saved positions to matching new windows after the
configured delay. The window_layout.state_file setting may include
{workspace} or {workspace_slug}; paths without a placeholder are treated as
a base location and expanded into one YAML file per workspace.
Remote control API
The GUI can expose a JSON-over-HTTP API so another machine, or an automation agent such as Claude Code, can press toolbar buttons, wait until a launch has settled, read log tabs, and run commands in persistent ROS 1 shells inside the Mobipick containers. The server is off by default. Enable it with any of:
mobipick-labs-docker-gui --remote-control [--remote-host H] [--remote-port P] [--remote-token T]MOBIPICK_GUI_REMOTE_CONTROL=1plus optionalMOBIPICK_GUI_REMOTE_HOST,MOBIPICK_GUI_REMOTE_PORT, andMOBIPICK_GUI_REMOTE_TOKEN- Tools > Remote Control > Enable Remote Control API at runtime
Remote control is opt-in for each launch. A persisted
remote_control.enabled: true setting is ignored by the GUI command unless
--remote-control or MOBIPICK_GUI_REMOTE_CONTROL=1 enables the API.
The GUI normally runs on the host and the processes it launches are mostly
containers, though configured buttons can also run host processes. The
default bind address is 0.0.0.0:8765 so other machines on the network can
reach the API; use 127.0.0.1 for a local-only agent.
Anyone who can reach the port can run commands inside the containers, so set
remote_control.token on shared networks; clients then send
Authorization: Bearer <token> (or ?token=).
Implementation lives in mobipick_gui/remote_control.py (server, event bus,
shell sessions), mobipick_gui/remote_adapter.py (the MainWindow bridge and
its thread-safe status snapshot), and mobipick_gui/remote_client.py (the
mobipick-labs-docker-gui-remote CLI, standard library only). Configuration
keys are documented in config/gui_settings.yaml under remote_control.
Docker, host subprocess, network discovery, and window-manager commands never
run on the Qt event thread. Their results return through Qt signals, so a slow
or unreachable Docker daemon leaves an operation pending without freezing the
window. GET /status, GET /buttons, and GET /tabs read the last published
snapshot directly on HTTP worker threads, as do presence and event requests.
Endpoints that must inspect or change widgets are marshalled to Qt and return
a clear 504 error after one second if Qt cannot service the action.
Endpoints
GET / returns this list as JSON. Responses are {"ok": true, ..., "seq": N}
where seq is the newest event sequence number, so a client can chain a
click with a wait without missing events.
| Method and path | Purpose |
|---|---|
GET /status |
Workspace, image, world, cached roscore/sim state, buttons, tabs, shells, active dialog. |
GET /buttons |
Toolbar buttons with state (red stopped, green running, yellow busy, grey unavailable), tooltip, runs_on (host or container), the log tab key, the toolbar args the button receives and the resulting full_command, plus readiness from the auto-launch estimates: startup_seconds (the plan's duration_seconds, null when the button has none), started_at, ready_at, ready_in_s and ready (running and past the estimate). |
POST /buttons/{key}/click, /start, /stop |
Press a button. start/stop are idempotent. Body may contain args ({"anygrasp_mode": "real"}, selected before the press), wait_for (event names) and timeout. Keyed events (button_state, button_ready, process_finished) only match this button; waiting for button_ready on a button that is already running and past its estimate returns at once with already_ready: true. |
GET /args, POST /args |
The launch argument selectors (generic arg_N slots from the button profile) and world: name, current value, options, description, option_descriptions, and applicable buttons. POST selects values by name ({"anygrasp_mode": "real", "world": "moelk_tables"}); unknown names or values are rejected. invalid maps choices forbidden by option rules to their reasons, and POST rejects them too. |
GET /presence, POST /presence, DELETE /presence |
Declare that a client is using the GUI ({"name": "<agent>", "ttl_s": 600, "note": ""}; the name is chosen by the client, so any agent can use its own, and several may be present at once) or withdraw it. The first POST for a name returns a token that the DELETE must carry ({"name": ..., "token": ...}); a refresh by a namesake gets no token and its bye is refused with 409, so two agents that picked the same name cannot withdraw and clean up after each other. While a client is present the window icon glows bright and the GUI log records who is working. Presence is kept alive by activity, not by heartbeats: every request the client sends refreshes it (with several clients present, name yourself with the X-Client-Name header or a client field; an anonymous request refreshes the client present the longest, the one that also owns new processes), and so does a shell command it started that is still running or an follow=1 stream it keeps open. Only ttl_s (default 10 min, max 30 min) of complete idleness lapses it. The server remembers every button, custom command, and shell the client started; when the client withdraws (without "keep": true) or lapses, the GUI stops those and logs the cleanup, so a crashed agent cannot leave the simulator running. Clients should not run a separate presence-refresh process: one that outlives its owner keeps a dead agent "present" for hours. |
POST /reload |
Re-read gui_settings.yaml and the workspace button profile without restarting the GUI. Button commands, labels, tooltips and argument slots are picked up for the next press; running processes and their tabs are preserved. |
GET /recording, POST /recording/{start|pause|resume|stop} |
Screen recording state (active, paused, segments, recorded_s, video_path, video_speedup_path) and its control without Auto Launch. A recording started by a client is stopped when that client leaves. |
GET /headless, POST /headless |
Headless mode (see above): enabled, the window buttons Auto Launch skips (skipped_by_auto_launch) and each button's headless_args. POST {"enabled": true} switches it for the next launches and emits headless_changed. |
POST /tabs/{key}/stop |
Stop the process behind a log tab: a button process (same as /buttons/{key}/stop), a customN command started with /command, or a remote shell. |
GET /events?since=N&names=a,b |
Event history; add follow=1&timeout=s to stream NDJSON. |
POST /wait |
Block until one of events arrives after since (default: now) or timeout; key restricts keyed events to one button or tab. |
GET /tabs, GET /tabs/{key}?tail=N&grep=RE |
Log tab list and plain-text tab contents. |
GET /dialogs, POST /dialogs/dismiss |
Inspect or close the active modal dialog ({"button": "Continue"}, accept, reject). |
POST /command |
Run text through the GUI custom command box. |
POST /shell |
Open a shell session ({"name", "stream", "root", "robot"}); blocks until ready. The ROS tool container is the default; "robot": true ssh-es onto the robot PC instead (remote ROS master mode only) to debug that machine. GET /status reports the choice under shell. |
POST /shell/{id}/exec |
Run a command: {"command", "stream", "tail", "grep", "max_lines", "timeout", "wait"}. |
GET /shell/{id}/output?since=N&command=ID&tail=N&grep=RE |
Buffered output; follow=1 streams NDJSON until the command finishes. |
POST /shell/{id}/interrupt |
Send INT (default), TERM, KILL, or HUP to the foreground command. |
POST /shell/{id}/settings, DELETE /shell/{id} |
Change the session stream default; close the session and its container (a robot shell: end the ssh connection and kill what it left running). |
POST /quit |
Close the GUI with its normal container cleanup. |
Events: button_state, button_ready (the button has been running for its
configured duration_seconds; immediate for buttons without an estimate),
process_finished, auto_launch_started,
auto_launch_ready, auto_launch_complete (every process in the plan reached
its ready time), window_layout_applied (the saved layout was replayed, which
is the usual "everything is up" signal), auto_launch_stopped,
shell_opened, shell_exited, shell_closed, client_connected,
client_disconnected (with expired: true when a TTL ran out), and
gui_closing.
Window icon glow
While the API is listening the main window icon carries a light-blue halo:
light blue while nobody is connected, green while a client has declared
presence with POST /presence, and pulsing while a request is being served.
The colour change (not just brightness) is what makes "an agent is driving
this GUI" readable at a glance on the dock. On GNOME
the dock icon is styled through the bundled shell extension
(SetAppGlow, protocol version 4); on other desktops setWindowIcon is used.
Shell sessions and output streaming
A session is docker compose run --rm -T ... <tool service> python3 enter_host_shell.py bash --noprofile --norc with terminal.bashrc sourced on
start, so it has the same ROS environment and user as Open Terminal.
{"robot": true} opens the session on the robot PC instead, as ssh -o BatchMode=yes -o ConnectTimeout=10 <user>@<host> bash --noprofile --norc. That
shell is for debugging the robot machine itself - its processes, drivers, logs,
services, disks - which a container cannot see. ROS work belongs in the
container shell, which carries the workspace chain and reaches the same
master over the network, so the container stays the default even in remote ROS
master mode; ros.robot_shell_by_default: true reverses that, and
{"robot": false} always forces the container. {"robot": true} outside
remote master mode is refused: there is no robot to reach.
Settings: ros.robot_ssh_user, ros.robot_ssh_host (empty means the host of
ROS_MASTER_URI), ros.robot_ssh_options, ros.robot_shell_by_default. The
ssh key must be in place: BatchMode=yes turns a missing one into a clear
startup error instead of a password prompt. The session's startup output names
the host, user, ROS_DISTRO and ROS_MASTER_URI it found; the robot's own
environment comes from /etc/profile and ~/.bashrc, and ROS_MASTER_URI is
only filled in when the robot leaves it unset. describe() and GET /shell
report runs_on (robot/container) and the target, and GET /status
carries a shell block (default, container_service, robot_available,
robot_target, hint) so a client can see where a new shell would land. The
shell is stateful (cd, source, exported variables persist) and its output
is mirrored into a closable Remote Shell N tab. Each command is wrapped
with a base64 eval and a completion marker, so quoting and multi-line
commands are safe and the API knows the exit code. Standard input is
/dev/null; interactive prompts fail fast instead of hanging.
Output is buffered per session with sequence numbers. The stream flag
decides whether an exec response carries the lines at all: stream: false
returns only the exit code and line count, and the output stays retrievable
through /output with tail, grep, since, or command. The per-session
default can be changed with /settings. Long-running commands use
wait: false and are polled or followed; interrupt sends SIGINT to the
foreground process group child of the session shell through docker exec, or
through the same ssh target for a robot shell (ssh joins its remote arguments
with spaces, so that command is passed as one already-quoted word).
Client and agent workflow
export MOBIPICK_GUI_REMOTE_URL=http://<gui-host>:8765 # and MOBIPICK_GUI_REMOTE_TOKEN
mobipick-labs-docker-gui-remote hello claude --note "tables demo" # icon glows until bye; repeat every <10 min
mobipick-labs-docker-gui-remote status
mobipick-labs-docker-gui-remote click auto_launch --wait window_layout_applied,auto_launch_complete --timeout 240
mobipick-labs-docker-gui-remote --text tab sim --tail 40 --grep "ERROR|WARN"
mobipick-labs-docker-gui-remote shell open
mobipick-labs-docker-gui-remote --text shell exec 1 "rostopic list" --tail 20
mobipick-labs-docker-gui-remote shell exec 1 "rosrun tables_demo_planning tables_demo_node.py" --no-wait
mobipick-labs-docker-gui-remote stop-tab custom1 # stop a /command launch
mobipick-labs-docker-gui-remote reload # after editing a button profile
mobipick-labs-docker-gui-remote bye claude # stops anything claude left running
mobipick-labs-docker-gui-remote --text shell output 1 --follow --grep "ERROR|Success" --timeout 120
mobipick-labs-docker-gui-remote shell interrupt 1
mobipick-labs-docker-gui-remote shell close 1
A Claude Code skill describing this workflow with plain curl is kept in
two identical copies: .claude/skills/mobipick-gui-remote/SKILL.md (loaded
automatically in this checkout) and
mobipick_gui/resources/skills/mobipick-gui-remote/SKILL.md (shipped in the
package). Port it to another machine or project with:
mobipick-labs-docker-gui-remote skill --install ~/.claude/skills # user-wide
mobipick-labs-docker-gui-remote skill --install /path/to/repo/.claude/skills
mobipick-labs-docker-gui-remote skill # print it
A test fails when the two copies drift apart.
tests/mobipick_gui/test_remote_live.py holds opt-in checks against a running
GUI: with MOBIPICK_GUI_REMOTE_LIVE=1 (and MOBIPICK_GUI_REMOTE_URL when the
port differs) it opens a fresh remote shell, verifies that TCPROS
subscriptions receive /clock and joint states from the simulator, and closes
the shell. It starts and stops nothing.
If a request reports HTTP 504 with a dialog entry, a modal dialog (for
example the workspace mismatch warning) is waiting; answer it with
dismiss <button text> and retry. Requests are served even while a dialog is
open because Qt modal loops keep processing queued calls.
Container display backends
display.mode in gui_settings.yaml accepts auto, x11, or wayland.
Automatic mode exposes every valid host display socket to one-off containers,
and selects the backend matching the host session: native Wayland on Wayland
and X11 on Xorg. If the native transport is unavailable, automatic mode falls
back to the other transport. Set display.mode: x11 for an older image that
does not contain Qt's Wayland platform plugin.
Gazebo and RViz from ROS Noetic are exceptions. Their OGRE 1.9 renderer uses
GLX and requires an X11 parent window, so the GUI launches those applications
through XWayland on a Wayland desktop. On NVIDIA hosts it also enables PRIME
render offload and selects the NVIDIA GLX vendor. This avoids both the native
Wayland Invalid parentWindowHandle failure and the accelerated XWayland
viewport appearing black.
The GUI adds display mounts to each docker compose run; the compose file no
longer mounts all of /run/user. X11 authorization uses a mounted Xauthority
cookie when one is available and otherwise grants the selected container user
temporary access with xhost. Native Wayland requires the image to contain
Qt's Wayland platform plugin. Host-user images newly built by the setup wizard
install qtwayland5.
Focal-based NVIDIA images also need Wayland client 1.20 or newer. Current
NVIDIA Container Toolkit releases inject libnvidia-egl-wayland2.so.1, which
uses wl_proxy_marshal_flags; Focal's Wayland 1.18 does not export that
symbol. The Mobipick Noetic base-image hierarchy supplies the compatible
runtime. Rebuild the hierarchy after updating that base image.
The GUI also reads the owning groups of the host's /dev/dri/renderD* and
/dev/dri/card* devices and adds those numeric groups to every Compose
service. This lets the host-matching non-root container user open the GPU
devices even when the host and image assign different IDs to render and
video.
The container entrypoint creates a private XDG_RUNTIME_DIR with mode 0700
for the effective container user. When an interactive terminal changes from
root to the host-matching user, its privilege-drop helper creates a new runtime
directory for that UID before starting the shell. Every child process inherits
the corrected environment, whether or not it uses Qt. For native Wayland, the
GUI mounts only the selected host socket and links it into the active user's
private directory. This prevents Qt runtime ownership warnings without
exposing the rest of the host user's runtime directory.
Container launches also receive an unreachable D-Bus session address. This
prevents applications opened by the GUI, including commands started in its
container terminals, from delivering desktop notifications on the host. The
GUI applies this isolation after command-specific environment overrides.
During GUI-managed launches, the window controller also clears attention from
new windows. This suppresses desktop-generated "application is ready" banners,
which do not travel over the application's D-Bus connection. Xorg uses
wmctrl; Wayland uses the bundled GNOME Shell extension.
Recreate already-running GUI containers and terminals after upgrading so they start with the updated entrypoint and privilege-drop helper.
See Wayland and RViz troubleshooting for display and OpenGL diagnostics, including the Mesa loader/code 139 failure.
Remote ROS master mode
Remote mode is controlled by the hidden Remote ROS Master view controls. When enabled:
- local Roscore and simulation actions are disabled;
- tools, scripts, terminals, configured commands, and custom commands use
mobipick_remote_cmd; ROS_MASTER_URIis normalized and passed into containers;- host networking is used so ROS 1 callbacks can reach the nodes.
Changing remote mode or the URI is blocked while workspace processes are running.
Docker cp profiles
docker_cp_image_tag.yaml defines optional copy rules keyed by default or
by ROS workspace name.
host_to_containerentries run automatically after eligible containers appear.container_to_hostentries run from Tools > Docker > Execute Docker cp for the current running tab.
When the Docker image default workspace is active, user edits are saved to
~/.config/mobipick-labs-docker-gui/docker_cp_image_tag.yaml. When a ROS
workspace is active, edits are saved to
~/.config/mobipick-labs-docker-gui/docker_cp_profiles/{workspace}_docker_cp_image_tag.yaml.
No copy rules are enabled in the bundled defaults, so fresh installs do not copy
pick_n_place.rviz or any other host file unless the user adds rows here.
The editor shows workspaces rather than Docker image tags. Add Row opens a
path setup dialog; the host side uses a local file picker, and the container
side can use a selected running setup container or manual path entry. Empty
profiles in the active writable file override bundled entries.
Logging and reports
Every process tab uses LogTextEdit, which buffers updates to keep high-volume
process output responsive. The log widget keeps only the configured maximum
block count. ROS messages use readable wall-clock time and identify their node,
for example [WARN] [14:26:56] [/pose_selector]: Clearing planning scene.
GUI-originated messages and executed commands are written to the Log tab. Users can save the current tab, save all tabs, or load a saved HTML log into a closable tab. Ctrl+W closes the current tab when it has an X close button. The bottom search row searches only the current log tab.
The bug report dialog collects selected diagnostic sections, including GUI version, selected workspace, selected image/workspace match, optional command outputs, workspace graph, log tab text, and user notes. Keep new diagnostics optional so report generation remains useful on machines without every tool installed. Report rendering passes every section through the same anonymizer before preview, copy, save, email, or GitHub issue creation. It removes local user and computer names, filesystem paths, network identifiers, and common secret assignments; numeric identifiers are replaced by stars so their digit count remains available for diagnosis.
GitHub issue links are kept below 2,000 encoded characters for broad browser compatibility. If a report is too large, the dialog opens GitHub with the largest safe first part, explains the truncation, and changes Copy to Copy Remaining so the user can paste the omitted tail into the issue.
User documentation dialog
The Help documentation window renders
resources/gui_user_documentation.md with QTextBrowser. It supports keyword
search from a line edit plus Find and Previous buttons. Matching keywords are
highlighted and the current match is selected and scrolled into view.
When editing user documentation, keep the text task-oriented and avoid developer internals. Developer details belong in this README or code comments.
Shutdown behavior
Closing the GUI starts a controlled shutdown:
- cancel Auto Launch timers and pending recording start;
- stop screen recording if active;
- stop the external terminal container;
- collect stop commands only for active configurable toolbar processes;
- kill GUI-owned background
QProcessinstances; - stop simulator and related Mobipick containers;
- run
clean.bashwhen available; - revoke temporary X11 access;
- quit the Qt application.
Interactive container stops use the configured ROS shutdown grace by default.
The Fast stop checkbox selects a zero-second grace for the session. Local
roscore shutdown always uses zero grace because the master and its registration
database are going away together. When a remote master survives a fast stop,
the GUI enables Clean stale ROS nodes, which runs rosnode cleanup from the
remote ROS tool service after an explicit confirmation.
Avoid adding early returns in shutdown paths unless they still leave the GUI in a recoverable state.
Development guidelines
- Keep user-visible defaults in
resources/config/and hard fallbacks inconfig.py. - Keep per-user writes out of packaged resources.
- Prefer extending existing helper methods in
MainWindowbefore adding a new orchestration path. - Add tests under
tests/mobipick_gui/for new behavior. - Stub Docker and external tools in tests unless the test only validates command construction.
- For new package resources, update
pyproject.tomlandMANIFEST.in. - For new user-visible controls, update
resources/gui_user_documentation.md. - For new developer-facing configuration, update this README.
Troubleshooting for developers
- If no images appear, open Configure Image Filters to inspect
images.discovery_filters,images.blacklist, and the preview of local images fromdocker images. - If GUI tests create real dialogs unexpectedly, set
QT_QPA_PLATFORM=offscreenand monkeypatch Docker discovery. - The CLI filters Qt 5's
QSocketNotifier: Can only be used with threads started with QThreadmessage only while constructingQApplication. A copy emitted later is not filtered and should be investigated as an application threading problem. - On Wayland, the CLI also filters Qt 5's benign
QWindow::requestActivate()platform warning. Other Qt Wayland warnings remain visible. - If a workspace does not mount, check the selected image profile for
supports_host_workspaces. - If RViz or Gazebo windows do not open, inspect the detected display variables, Docker GPU access, and OpenGL renderer. See Wayland and RViz troubleshooting.
- If recordings produce no MP4, inspect the session
ffmpeg.logand the configured display/resolution. - If window layout replay does nothing on X11, install
wmctrlandxpropand save a layout after simulator windows are visible. On a Wayland session runmobipick-labs-docker-gui --install-gnome-window-extension, log out and back in, and checkgnome-extensions info winctl@mobipick-labs-docker-gui.
Metadata
Release files for mobipick-labs-docker-gui 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mobipick_labs_docker_gui-0.1.3.tar.gz | 447.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mobipick_labs_docker_gui-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 861.6 kB
Release files / mobipick_labs_docker_gui-0.1.3.tar.gz
| Download URL | mobipick_labs_docker_gui-0.1.3.tar.gz |
|---|---|
| Size | 447.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ad594cafe6b70347b5534241aa8c582f50dad9d2764bd61e9f2da0a4a3fceb56
|
|
BLAKE2b-256 checksum How to use checksums |
496e1a7a103bca9c6a717614a1f768c7a4897e444e019df8dc322c0dbd220952
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logRelease files / mobipick_labs_docker_gui-0.1.3-py3-none-any.whl
| Download URL | mobipick_labs_docker_gui-0.1.3-py3-none-any.whl |
|---|---|
| Size | 414.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1ab418e0704f3338ccf9dcacfa9632c9e7c3fbbc2596299a9503b2f9bcf95b27
|
|
BLAKE2b-256 checksum How to use checksums |
2d80e9bcf54e511da9c038cbd411ef9b72a41e23dafde7423c7cfdb3c580d1c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency log