diff --git a/assets/bt.png b/assets/bt.png new file mode 100644 index 0000000..6335da9 Binary files /dev/null and b/assets/bt.png differ diff --git a/src/BluetoothDevices.cpp b/src/BluetoothDevices.cpp new file mode 100644 index 0000000..33f5a22 --- /dev/null +++ b/src/BluetoothDevices.cpp @@ -0,0 +1,305 @@ +#include "BluetoothDevices.h" +#include "System.h" +#include +#include +#include +#include + +#ifdef HAS_BLUEZ +namespace BluetoothDevices +{ + namespace DynCtx + { + enum class DeviceState + { + Invalid = BIT(0), + Failed = BIT(1), // Request failed + RequestConnect = BIT(2), // If device is requested to connect + RequestDisconnect = BIT(3), // If device is requested to disconnect + Connected = BIT(4), // BlueZ says to us, that it is connected + Disconnected = BIT(5), // BlueZ says to us, that it is disconnected + }; + DEFINE_ENUM_FLAGS(DeviceState); + + struct BTDeviceWithState + { + System::BluetoothDevice device{}; + DeviceState state{}; + }; + + std::mutex deviceMutex; + std::vector devices; + Box* deviceListBox; + Window* win; + bool scanning = false; + + void OnClick(Button& button, BTDeviceWithState& device) + { + DeviceState& state = device.state; + + // Clear failed bit + state &= ~DeviceState::Failed; + button.RemoveClass("failed"); + + // Only try to connect, if we know we're already disconnected and we haven't requested before(Same for disconnect) + if (FLAG_CHECK(state, DeviceState::Disconnected) && !FLAG_CHECK(state, DeviceState::RequestConnect)) + { + button.AddClass("active"); + button.RemoveClass("inactive"); + state |= DeviceState::RequestConnect; + + System::Connect(device.device, [&dev = device, &but = button](bool success, System::BluetoothDevice&) + { + deviceMutex.lock(); + if (!success) + { + dev.state &= ~DeviceState::RequestConnect; + dev.state |= DeviceState::Failed; + but.AddClass("failed"); + } + deviceMutex.unlock(); + }); + } + else if (FLAG_CHECK(state, DeviceState::Connected) && !FLAG_CHECK(state, DeviceState::RequestDisconnect)) + { + button.AddClass("inactive"); + button.RemoveClass("active"); + state |= DeviceState::RequestDisconnect; + + System::Disconnect(device.device, [&dev = device, &but = button](bool success, System::BluetoothDevice&) + { + deviceMutex.lock(); + if (!success) + { + dev.state &= ~DeviceState::RequestDisconnect; + dev.state |= DeviceState::Failed; + but.AddClass("failed"); + } + deviceMutex.unlock(); + }); + } + } + + void UpdateDeviceUIElem(Button& button, BTDeviceWithState& device) + { + if (device.device.name.size()) + { + button.SetText(device.device.name); + } + else + { + button.SetText(device.device.mac); + } + bool requestConnect = FLAG_CHECK(device.state, DeviceState::RequestConnect); + bool requestDisconnect = FLAG_CHECK(device.state, DeviceState::RequestDisconnect); + if (requestConnect || (!requestDisconnect && FLAG_CHECK(device.state, DeviceState::Connected))) + { + button.AddClass("active"); + button.RemoveClass("inactive"); + } + else if (requestDisconnect || (!requestConnect && FLAG_CHECK(device.state, DeviceState::Disconnected))) + { + button.AddClass("inactive"); + button.RemoveClass("active"); + } + if (FLAG_CHECK(device.state, DeviceState::Failed)) + { + button.AddClass("failed"); + } + else + { + button.RemoveClass("failed"); + } + + button.OnClick( + [&dev = device](Button& button) + { + OnClick(button, dev); + }); + } + + void InvalidateDeviceUI() + { + // Shrink + if (deviceListBox->GetChilds().size() > devices.size()) + { + for (size_t i = deviceListBox->GetChilds().size() - 1; i >= devices.size(); i--) + { + deviceListBox->RemoveChild(i); + } + } + + size_t idx = 0; + for (auto& device : devices) + { + if (idx >= deviceListBox->GetChilds().size()) + { + // Create new + auto button = Widget::Create