From bbf4eb90e502543d51534052a4a7faa18e318e07 Mon Sep 17 00:00:00 2001 From: Michal Faferek Date: Sat, 30 May 2026 22:04:32 +0200 Subject: [PATCH 1/2] fix(demos): auto-headless when no display (macOS, headless hosts) The GUI run mode launched the Gazebo/RViz client even with no X server, which aborts the whole required launch on macOS Docker Desktop. Detect a missing DISPLAY and fall back to headless, printing what still works (REST API, Web UI). Covers turtlebot3 and moveit. --- README.md | 2 +- demos/moveit_pick_place/run-demo.sh | 19 +++++++- demos/turtlebot3_integration/README.md | 4 +- .../run-demo-debounce.sh | 42 +++++++++++++---- demos/turtlebot3_integration/run-demo.sh | 45 ++++++++++++++----- 5 files changed, 90 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c21c948..b91eef5 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Full mobile robot demo with autonomous navigation: ```bash cd demos/turtlebot3_integration ./run-demo.sh -# Gazebo will open, Web UI at http://localhost:3000 +# Gazebo will open (on macOS / headless hosts it runs headless automatically), Web UI at http://localhost:3000 # Try: ./send-nav-goal.sh 2.0 0.5 # To stop diff --git a/demos/moveit_pick_place/run-demo.sh b/demos/moveit_pick_place/run-demo.sh index 6b7a0a5..8562f5d 100755 --- a/demos/moveit_pick_place/run-demo.sh +++ b/demos/moveit_pick_place/run-demo.sh @@ -38,7 +38,7 @@ trap cleanup EXIT # Parse arguments COMPOSE_ARGS="" BUILD_ARGS="" -HEADLESS_MODE="false" +HEADLESS_MODE="${HEADLESS:-false}" UPDATE_IMAGES="false" DETACH_MODE="true" @@ -102,6 +102,23 @@ if [[ -z "$COMPOSE_ARGS" ]]; then COMPOSE_ARGS="--profile cpu" fi +# Auto-enable headless when there is no display to render the RViz/Gazebo GUI. +# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY; +# in both cases the GUI cannot open and would abort the launch. +# An explicit --headless (or HEADLESS=true) always wins. +if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then + if [[ "$(uname -s)" == "Darwin" ]]; then + echo "ℹ️ macOS detected with no X display: running HEADLESS." + echo " Docker Desktop on macOS cannot open the RViz/Gazebo window." + else + echo "ℹ️ No DISPLAY detected: running HEADLESS (no GUI window)." + fi + echo " MoveIt planning and ros2_medkit still run normally:" + echo " REST API -> http://localhost:8080/api/v1/" + echo " Web UI -> http://localhost:3000/" + HEADLESS_MODE="true" +fi + # Export for docker-compose export HEADLESS=$HEADLESS_MODE export LAUNCH_FILE="demo_gazebo.launch.py" diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 2487961..a2c0566 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -23,7 +23,7 @@ This demo demonstrates: ## Prerequisites - Docker and docker-compose -- X11 display server (Linux with GUI, or XQuartz on macOS) +- (Optional) X11 display server for the Gazebo GUI on Linux with a desktop. Not needed on macOS or headless hosts - the demo falls back to headless automatically. - (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) - `curl` and `jq` (required for host-side scripts) @@ -45,6 +45,8 @@ That's it! The script will: **Note:** By default, the demo runs in **daemon mode** with **Gazebo GUI** enabled. This allows you to interact with ROS 2 while the demo is running. +**On macOS:** Docker Desktop has no X server, so `run-demo.sh` detects this and starts **headless automatically** - no XQuartz needed. The Gazebo 3D window is not shown, but the simulation, Nav2, ros2_medkit, the REST API (`http://localhost:8080`) and the Web UI (`http://localhost:3000`) all run. Drive the demo from the Web UI and the helper scripts (`./send-nav-goal.sh`, fault injection). The same auto-fallback applies to any host without a `DISPLAY` (for example a headless Linux server). + ### Available Options ```bash diff --git a/demos/turtlebot3_integration/run-demo-debounce.sh b/demos/turtlebot3_integration/run-demo-debounce.sh index eb541db..1f5b37f 100755 --- a/demos/turtlebot3_integration/run-demo-debounce.sh +++ b/demos/turtlebot3_integration/run-demo-debounce.sh @@ -30,23 +30,20 @@ if ! command -v docker &> /dev/null; then exit 1 fi -# Setup X11 forwarding for GUI (Gazebo) -echo "Setting up X11 forwarding..." -xhost +local:docker 2>/dev/null || { - echo " Warning: xhost failed. GUI may not work." -} - -# Cleanup function +# Cleanup function (undo the X11 grant only if we actually set it up) +X11_FORWARDING="false" cleanup() { echo "" echo "Cleaning up..." - xhost -local:docker 2>/dev/null || true + if [[ "$X11_FORWARDING" == "true" ]]; then + xhost -local:docker 2>/dev/null || true + fi echo "Done!" } trap cleanup EXIT # Parse arguments -HEADLESS_MODE="false" +HEADLESS_MODE="${HEADLESS:-false}" DETACH_MODE="true" PROFILE="cpu" @@ -60,6 +57,33 @@ while [[ $# -gt 0 ]]; do shift done +# Auto-enable headless when there is no display to render the Gazebo GUI. +# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY; +# in both cases the Gazebo window cannot open and would abort the whole launch. +# An explicit --headless (or HEADLESS=true) always wins. +if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then + if [[ "$(uname -s)" == "Darwin" ]]; then + echo "ℹ️ macOS detected with no X display: running HEADLESS." + echo " Docker Desktop on macOS cannot open the Gazebo 3D window." + else + echo "ℹ️ No DISPLAY detected: running HEADLESS (no Gazebo 3D window)." + fi + echo " The simulation and ros2_medkit still run normally:" + echo " REST API -> http://localhost:8080/api/v1/" + echo " Web UI -> http://localhost:3000/" + HEADLESS_MODE="true" +fi + +# Set up X11 forwarding only when a GUI will actually be shown +if [[ "$HEADLESS_MODE" != "true" ]]; then + echo "Setting up X11 forwarding..." + if xhost +local:docker 2>/dev/null; then + X11_FORWARDING="true" + else + echo " Warning: xhost failed. GUI may not work." + fi +fi + export HEADLESS=$HEADLESS_MODE DETACH_FLAG="" diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh index 0b0dbf2..71ace08 100755 --- a/demos/turtlebot3_integration/run-demo.sh +++ b/demos/turtlebot3_integration/run-demo.sh @@ -21,18 +21,14 @@ if ! command -v docker &> /dev/null; then exit 1 fi -# Setup X11 forwarding for GUI (Gazebo, RViz) -echo "Setting up X11 forwarding..." -xhost +local:docker 2>/dev/null || { - echo " Warning: xhost failed. GUI may not work." - echo " Install with: sudo apt install x11-xserver-utils" -} - -# Cleanup function +# Cleanup function (undo the X11 grant only if we actually set it up) +X11_FORWARDING="false" cleanup() { echo "" echo "Cleaning up..." - xhost -local:docker 2>/dev/null || true + if [[ "$X11_FORWARDING" == "true" ]]; then + xhost -local:docker 2>/dev/null || true + fi echo "Done!" } trap cleanup EXIT @@ -40,7 +36,7 @@ trap cleanup EXIT # Parse arguments COMPOSE_ARGS="" BUILD_ARGS="" -HEADLESS_MODE="false" +HEADLESS_MODE="${HEADLESS:-false}" UPDATE_IMAGES="false" DETACH_MODE="true" @@ -107,6 +103,35 @@ if [[ -z "$COMPOSE_ARGS" ]]; then COMPOSE_ARGS="--profile cpu" fi +# Auto-enable headless when there is no display to render the Gazebo GUI. +# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY; +# in both cases the Gazebo window cannot open and would abort the whole launch. +# An explicit --headless (or HEADLESS=true) always wins. +if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then + if [[ "$(uname -s)" == "Darwin" ]]; then + echo "ℹ️ macOS detected with no X display: running HEADLESS." + echo " Docker Desktop on macOS cannot open the Gazebo 3D window." + else + echo "ℹ️ No DISPLAY detected: running HEADLESS (no Gazebo 3D window)." + fi + echo " The simulation, Nav2 and ros2_medkit still run normally:" + echo " REST API -> http://localhost:8080/api/v1/" + echo " Web UI -> http://localhost:3000/" + echo " You can still send nav goals and inject faults via the helper scripts below." + HEADLESS_MODE="true" +fi + +# Set up X11 forwarding only when a GUI will actually be shown +if [[ "$HEADLESS_MODE" != "true" ]]; then + echo "Setting up X11 forwarding..." + if xhost +local:docker 2>/dev/null; then + X11_FORWARDING="true" + else + echo " Warning: xhost failed. GUI may not work." + echo " Install with: sudo apt install x11-xserver-utils" + fi +fi + # Export HEADLESS mode for docker-compose export HEADLESS=$HEADLESS_MODE echo "Gazebo mode: $([ "$HEADLESS_MODE" = "true" ] && echo "headless (no GUI)" || echo "GUI enabled")" From b8a1f9dd5bf8e53d89909ad5efc5ad4eaa12578a Mon Sep 17 00:00:00 2001 From: Michal Faferek Date: Sat, 30 May 2026 22:15:59 +0200 Subject: [PATCH 2/2] docs(moveit): document macOS auto-headless behavior Mirror the turtlebot3 README updates for the MoveIt demo: the runner now falls back to headless when no display is present, so the docs no longer tell macOS users to set up X11/XQuartz. --- README.md | 2 +- demos/moveit_pick_place/README.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b91eef5..40e601b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Panda robot arm demo with pick-and-place manipulation: ```bash cd demos/moveit_pick_place ./run-demo.sh -# RViz will open with Panda arm (or use --headless), Web UI at http://localhost:3000 +# RViz will open with Panda arm (on macOS / headless hosts it runs headless automatically), Web UI at http://localhost:3000 # Move the arm: ./move-arm.sh demo # Inject faults: ./inject-planning-failure.sh # Check faults: ./check-faults.sh diff --git a/demos/moveit_pick_place/README.md b/demos/moveit_pick_place/README.md index 61aeab8..11fe34e 100644 --- a/demos/moveit_pick_place/README.md +++ b/demos/moveit_pick_place/README.md @@ -24,7 +24,7 @@ This demo demonstrates: - Docker and docker-compose - `curl` and `jq` installed on the host (required for host-side scripts) -- X11 display server (for Gazebo GUI) or `--headless` mode +- (Optional) X11 display server for the RViz/Gazebo GUI on Linux with a desktop. Not needed on macOS or headless hosts - the demo falls back to headless automatically. - (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) - recommended for smooth Gazebo rendering - ~7 GB disk space for Docker image @@ -46,6 +46,8 @@ That's it! The script will: **REST API:** http://localhost:8080/api/v1/ **Web UI:** http://localhost:3000/ +**On macOS:** Docker Desktop has no X server, so `run-demo.sh` detects this and starts **headless automatically** - no XQuartz needed. The RViz/Gazebo window is not shown, but MoveIt planning, ros2_medkit, the REST API (`http://localhost:8080`) and the Web UI (`http://localhost:3000`) all run. Drive the demo from the Web UI and the helper scripts (`./move-arm.sh`, `./inject-planning-failure.sh`). The same auto-fallback applies to any host without a `DISPLAY` (for example a headless Linux server). + ### 2. Available Options ```bash @@ -437,7 +439,7 @@ Container scripts are stored under `/var/lib/ros2_medkit/scripts/moveit-planning | Problem | Cause | Solution | |---------|-------|----------| -| RViz window doesn't appear | X11 not set up | Run `xhost +local:docker` or use `--headless` | +| RViz window doesn't appear | No X display (e.g. macOS Docker Desktop) | On macOS / headless hosts the demo runs headless automatically; on Linux with a desktop run `xhost +local:docker` | | "Package not found" error | Build failed | Rebuild with `./run-demo.sh --no-cache` | | No faults appearing | Monitor not connected | Check `ros2 node list` includes `manipulation_monitor` | | Docker build fails | Apt package missing | Check if MoveIt 2 Jazzy packages are available |