Skip to content

Commit bbf4eb9

Browse files
committed
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.
1 parent c375504 commit bbf4eb9

5 files changed

Lines changed: 90 additions & 22 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Full mobile robot demo with autonomous navigation:
8787
```bash
8888
cd demos/turtlebot3_integration
8989
./run-demo.sh
90-
# Gazebo will open, Web UI at http://localhost:3000
90+
# Gazebo will open (on macOS / headless hosts it runs headless automatically), Web UI at http://localhost:3000
9191
# Try: ./send-nav-goal.sh 2.0 0.5
9292

9393
# To stop

demos/moveit_pick_place/run-demo.sh

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ trap cleanup EXIT
3838
# Parse arguments
3939
COMPOSE_ARGS=""
4040
BUILD_ARGS=""
41-
HEADLESS_MODE="false"
41+
HEADLESS_MODE="${HEADLESS:-false}"
4242
UPDATE_IMAGES="false"
4343
DETACH_MODE="true"
4444

@@ -102,6 +102,23 @@ if [[ -z "$COMPOSE_ARGS" ]]; then
102102
COMPOSE_ARGS="--profile cpu"
103103
fi
104104

105+
# Auto-enable headless when there is no display to render the RViz/Gazebo GUI.
106+
# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY;
107+
# in both cases the GUI cannot open and would abort the launch.
108+
# An explicit --headless (or HEADLESS=true) always wins.
109+
if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then
110+
if [[ "$(uname -s)" == "Darwin" ]]; then
111+
echo "ℹ️ macOS detected with no X display: running HEADLESS."
112+
echo " Docker Desktop on macOS cannot open the RViz/Gazebo window."
113+
else
114+
echo "ℹ️ No DISPLAY detected: running HEADLESS (no GUI window)."
115+
fi
116+
echo " MoveIt planning and ros2_medkit still run normally:"
117+
echo " REST API -> http://localhost:8080/api/v1/"
118+
echo " Web UI -> http://localhost:3000/"
119+
HEADLESS_MODE="true"
120+
fi
121+
105122
# Export for docker-compose
106123
export HEADLESS=$HEADLESS_MODE
107124
export LAUNCH_FILE="demo_gazebo.launch.py"

demos/turtlebot3_integration/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ This demo demonstrates:
2323
## Prerequisites
2424

2525
- Docker and docker-compose
26-
- X11 display server (Linux with GUI, or XQuartz on macOS)
26+
- (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.
2727
- (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html)
2828
- `curl` and `jq` (required for host-side scripts)
2929

@@ -45,6 +45,8 @@ That's it! The script will:
4545

4646
**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.
4747

48+
**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).
49+
4850
### Available Options
4951

5052
```bash

demos/turtlebot3_integration/run-demo-debounce.sh

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,20 @@ if ! command -v docker &> /dev/null; then
3030
exit 1
3131
fi
3232

33-
# Setup X11 forwarding for GUI (Gazebo)
34-
echo "Setting up X11 forwarding..."
35-
xhost +local:docker 2>/dev/null || {
36-
echo " Warning: xhost failed. GUI may not work."
37-
}
38-
39-
# Cleanup function
33+
# Cleanup function (undo the X11 grant only if we actually set it up)
34+
X11_FORWARDING="false"
4035
cleanup() {
4136
echo ""
4237
echo "Cleaning up..."
43-
xhost -local:docker 2>/dev/null || true
38+
if [[ "$X11_FORWARDING" == "true" ]]; then
39+
xhost -local:docker 2>/dev/null || true
40+
fi
4441
echo "Done!"
4542
}
4643
trap cleanup EXIT
4744

4845
# Parse arguments
49-
HEADLESS_MODE="false"
46+
HEADLESS_MODE="${HEADLESS:-false}"
5047
DETACH_MODE="true"
5148
PROFILE="cpu"
5249

@@ -60,6 +57,33 @@ while [[ $# -gt 0 ]]; do
6057
shift
6158
done
6259

60+
# Auto-enable headless when there is no display to render the Gazebo GUI.
61+
# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY;
62+
# in both cases the Gazebo window cannot open and would abort the whole launch.
63+
# An explicit --headless (or HEADLESS=true) always wins.
64+
if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then
65+
if [[ "$(uname -s)" == "Darwin" ]]; then
66+
echo "ℹ️ macOS detected with no X display: running HEADLESS."
67+
echo " Docker Desktop on macOS cannot open the Gazebo 3D window."
68+
else
69+
echo "ℹ️ No DISPLAY detected: running HEADLESS (no Gazebo 3D window)."
70+
fi
71+
echo " The simulation and ros2_medkit still run normally:"
72+
echo " REST API -> http://localhost:8080/api/v1/"
73+
echo " Web UI -> http://localhost:3000/"
74+
HEADLESS_MODE="true"
75+
fi
76+
77+
# Set up X11 forwarding only when a GUI will actually be shown
78+
if [[ "$HEADLESS_MODE" != "true" ]]; then
79+
echo "Setting up X11 forwarding..."
80+
if xhost +local:docker 2>/dev/null; then
81+
X11_FORWARDING="true"
82+
else
83+
echo " Warning: xhost failed. GUI may not work."
84+
fi
85+
fi
86+
6387
export HEADLESS=$HEADLESS_MODE
6488

6589
DETACH_FLAG=""

demos/turtlebot3_integration/run-demo.sh

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,22 @@ if ! command -v docker &> /dev/null; then
2121
exit 1
2222
fi
2323

24-
# Setup X11 forwarding for GUI (Gazebo, RViz)
25-
echo "Setting up X11 forwarding..."
26-
xhost +local:docker 2>/dev/null || {
27-
echo " Warning: xhost failed. GUI may not work."
28-
echo " Install with: sudo apt install x11-xserver-utils"
29-
}
30-
31-
# Cleanup function
24+
# Cleanup function (undo the X11 grant only if we actually set it up)
25+
X11_FORWARDING="false"
3226
cleanup() {
3327
echo ""
3428
echo "Cleaning up..."
35-
xhost -local:docker 2>/dev/null || true
29+
if [[ "$X11_FORWARDING" == "true" ]]; then
30+
xhost -local:docker 2>/dev/null || true
31+
fi
3632
echo "Done!"
3733
}
3834
trap cleanup EXIT
3935

4036
# Parse arguments
4137
COMPOSE_ARGS=""
4238
BUILD_ARGS=""
43-
HEADLESS_MODE="false"
39+
HEADLESS_MODE="${HEADLESS:-false}"
4440
UPDATE_IMAGES="false"
4541
DETACH_MODE="true"
4642

@@ -107,6 +103,35 @@ if [[ -z "$COMPOSE_ARGS" ]]; then
107103
COMPOSE_ARGS="--profile cpu"
108104
fi
109105

106+
# Auto-enable headless when there is no display to render the Gazebo GUI.
107+
# macOS Docker Desktop has no X server, and headless Linux hosts have no DISPLAY;
108+
# in both cases the Gazebo window cannot open and would abort the whole launch.
109+
# An explicit --headless (or HEADLESS=true) always wins.
110+
if [[ "$HEADLESS_MODE" != "true" && -z "${DISPLAY:-}" ]]; then
111+
if [[ "$(uname -s)" == "Darwin" ]]; then
112+
echo "ℹ️ macOS detected with no X display: running HEADLESS."
113+
echo " Docker Desktop on macOS cannot open the Gazebo 3D window."
114+
else
115+
echo "ℹ️ No DISPLAY detected: running HEADLESS (no Gazebo 3D window)."
116+
fi
117+
echo " The simulation, Nav2 and ros2_medkit still run normally:"
118+
echo " REST API -> http://localhost:8080/api/v1/"
119+
echo " Web UI -> http://localhost:3000/"
120+
echo " You can still send nav goals and inject faults via the helper scripts below."
121+
HEADLESS_MODE="true"
122+
fi
123+
124+
# Set up X11 forwarding only when a GUI will actually be shown
125+
if [[ "$HEADLESS_MODE" != "true" ]]; then
126+
echo "Setting up X11 forwarding..."
127+
if xhost +local:docker 2>/dev/null; then
128+
X11_FORWARDING="true"
129+
else
130+
echo " Warning: xhost failed. GUI may not work."
131+
echo " Install with: sudo apt install x11-xserver-utils"
132+
fi
133+
fi
134+
110135
# Export HEADLESS mode for docker-compose
111136
export HEADLESS=$HEADLESS_MODE
112137
echo "Gazebo mode: $([ "$HEADLESS_MODE" = "true" ] && echo "headless (no GUI)" || echo "GUI enabled")"

0 commit comments

Comments
 (0)