BluetoothGD
Cross-platform Bluetooth Classic pairing and connection for Godot 4.x.
(Godot 4.4 and 4.7 tested)
Built as a GDExtension, it lets you discover, pair, and manage Bluetooth devices (including gamepads) directly from GDScript — ideal for in-game controller pairing flows without sending players to OS settings.
Includes a reference example scene (example/scan_demo.tscn) for quick testing. A fuller demo UI is available in the GitHub repository.
Supported Platforms Supported Platforms
- Windows 10/11 (x86_64)
- Status: Supported
- Backend: WinRT
- Linux (x86_64)
- Status: Supported
- Backend: BlueZ / D-Bus
Features
- Native device discovery using platform Bluetooth APIs
- Pair / unpair, connect / disconnect devices (by MAC address or Windows device ID)
- Real-time paired and connected state tracking
- Signal-based API — easy to bind to UI
- Non-blocking worker thread backend
- Reference example with device list, scan toggle, pair/unpair, and event log
is_bluetooth_available(),get_capabilities(), and detailed error reporting
Known Limitations
- Windows:
disconnect_device()cannot force-disconnect Bluetooth HID gamepads. Power off the controller or remove the device in Windows Bluetooth settings. - Linux:
disconnect_device()may not keep HID gamepads disconnected if they reconnect when powered on. - Linux missing libdbus: The extension still loads, but Bluetooth stays unavailable until
libdbus-1is installed (is_bluetooth_available()→false).
Requirements
All platforms
- Godot 4.4+
- Prebuilt native libraries are included under
bin/:template_debug— use in the Godot editortemplate_release— use in exported / release builds
No compiler or CMake is needed to use the addon.
Linux runtime dependencies
Linux players and developers need a working BlueZ stack. Install the D-Bus client library and ensure Bluetooth is running:
| Requirement | Debian / Ubuntu | Fedora / RHEL | Notes |
|---|---|---|---|
| BlueZ daemon | bluez |
bluez |
bluetoothd must be running |
| D-Bus client library | libdbus-1-3 |
dbus-libs |
Loaded at runtime via dlopen — not a hard link dependency |
| Pair / connect permissions | sudo usermod -aG bluetooth $USER (then re-login) |
same group or polkit | Required for pair/connect operations |
Checklist for Linux:
- Bluetooth adapter present and powered on
bluetoothdrunning (systemctl status bluetooth)libdbus-1.so.3installed (package names above)- User in the
bluetoothgroup or equivalent polkit rights
If libdbus-1 is missing, the GDExtension still loads but is_bluetooth_available() returns false and operations report not_supported.
Prebuilt .so compatibility (shipped binaries): glibc symbols up to GLIBC_2.14, libstdc++ up to GLIBCXX_3.4.22 (built on Ubuntu 20.04 for broad distro support).
Windows runtime dependencies
- Windows 10 or 11 with Bluetooth support
- No extra redistributables beyond the included
.dllfiles
Building from source (optional)
- CMake 3.17+ (3.23+ for presets)
- Windows: Visual Studio 2019+ with Desktop C++ workload and Windows 10/11 SDK
- Linux (build only):
libdbus-1-dev,pkg-config, C++17 compiler; see the project README for Docker and local build scripts
Quick Start
1. Install the addon
Copy addons/bluetooth_gd/ into your Godot project. Confirm bin/ contains the native libraries for your platform (debug + release).
2. Add a BluetoothManager node
Add a BluetoothManager node to your scene as a child or autoload. Keep your UI on a separate Control node — do not attach your UI script to BluetoothManager itself.
YourScene (Control)
├── Bluetooth # BluetoothManager node
└── ... your UI ...
Alternatively, register it as an autoload named Bluetooth pointing at a scene that contains only a BluetoothManager node.
3. Connect signals and call methods
Wire up the API from your UI script:
extends Control
@onready var _bluetooth: BluetoothManager = $Bluetooth
func _ready() -> void:
_bluetooth.bluetooth_ready.connect(_on_bluetooth_ready)
_bluetooth.scan_started.connect(_on_scan_started)
_bluetooth.scan_stopped.connect(_on_scan_stopped)
_bluetooth.device_found.connect(_on_device_found)
_bluetooth.pairing_succeeded.connect(_on_pairing_succeeded)
_bluetooth.pairing_failed.connect(_on_pairing_failed)
_bluetooth.pairing_pin_requested.connect(_on_pairing_pin_requested)
_bluetooth.connection_changed.connect(_on_connection_changed)
_bluetooth.error_occurred.connect(_on_error_occurred)
func _on_bluetooth_ready() -> void:
if not _bluetooth.is_bluetooth_available():
print("Bluetooth unavailable on this system")
return
print("Bluetooth ready on %s" % _bluetooth.get_platform_name())
var paired: Array = _bluetooth.get_paired_devices()
print("Paired devices: %d" % paired.size())
func _on_start_scan_pressed() -> void:
_bluetooth.start_scan({
"named_only": false,
"gamepads_only": true,
})
func _on_stop_scan_pressed() -> void:
_bluetooth.stop_scan()
func _on_scan_started() -> void:
print("Scan started")
func _on_scan_stopped() -> void:
print("Scan stopped")
func _on_device_found(device_info: Dictionary) -> void:
# Keys: address, name, paired, connected, device_class, device_id, rssi
print("Found: %s (%s)" % [device_info.get("name", "Unknown"), device_info.get("address", "")])
func _on_pair_pressed(address: String) -> void:
_bluetooth.pair_device(address)
func _on_unpair_pressed(address: String) -> void:
_bluetooth.unpair_device(address)
func _on_pairing_pin_requested(address: String) -> void:
_bluetooth.confirm_pairing("0000") # or reject_pairing() / cancel_pairing()
func _on_pairing_succeeded(address: String) -> void:
print("Paired: %s" % address)
func _on_pairing_failed(address: String, error: String, error_code: int) -> void:
print("Pairing failed: %s (%s)" % [error, _bluetooth.get_error_code_name(error_code)])
func _on_connection_changed(address: String, connected: bool, message: String) -> void:
print("%s %s" % [address, "connected" if connected else "disconnected"])
func _on_error_occurred(operation: String, message: String, error_code: int) -> void:
print("Error [%s]: %s" % [operation, message])
Important: Wait for the bluetooth_ready signal before calling start_scan(). On Linux, also check is_bluetooth_available() — it returns false when libdbus-1 is missing or the backend cannot initialize. Scan and pairing results are delivered asynchronously via signals on the main thread.
4. Run the included example
Open and run addons/bluetooth_gd/example/scan_demo.tscn to see scanning, device listing, and pair/unpair in action.
For a fuller reference UI (smart action button, named-device filter, advanced log), see the demo project in the GitHub repository.
Documentation
Full API reference, scan options, capabilities, and platform notes: project README on GitHub
Changelog for version v0.2.3
No changelog provided for this version.