Description
Changelog
Reviews (0)

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-1 is installed (is_bluetooth_available()false).

Requirements

All platforms

  • Godot 4.4+
  • Prebuilt native libraries are included under bin/:
    • template_debug — use in the Godot editor
    • template_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:

  1. Bluetooth adapter present and powered on
  2. bluetoothd running (systemctl status bluetooth)
  3. libdbus-1.so.3 installed (package names above)
  4. User in the bluetooth group 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 .dll files

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.

Reviews

BluetoothGD has no reviews yet.

Login to write a review.