769 lines
30 KiB
C++
769 lines
30 KiB
C++
#include "connection/dbus/bluetooth.hpp"
|
|
#include "services/notificationController.hpp"
|
|
|
|
#include <spdlog/spdlog.h>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
const std::string agent_introspection_xml = R"(
|
|
<node>
|
|
<interface name="org.bluez.Agent1">
|
|
<method name="Release"/>
|
|
<method name="RequestPinCode">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="s" name="pincode" direction="out"/>
|
|
</method>
|
|
<method name="DisplayPinCode">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="s" name="pincode" direction="in"/>
|
|
</method>
|
|
<method name="RequestPasskey">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="u" name="passkey" direction="out"/>
|
|
</method>
|
|
<method name="DisplayPasskey">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="u" name="passkey" direction="in"/>
|
|
<arg type="q" name="entered" direction="in"/>
|
|
</method>
|
|
<method name="RequestConfirmation">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="u" name="passkey" direction="in"/>
|
|
</method>
|
|
<method name="RequestAuthorization">
|
|
<arg type="o" name="device" direction="in"/>
|
|
</method>
|
|
<method name="AuthorizeService">
|
|
<arg type="o" name="device" direction="in"/>
|
|
<arg type="s" name="uuid" direction="in"/>
|
|
</method>
|
|
<method name="Cancel"/>
|
|
</interface>
|
|
</node>
|
|
)";
|
|
|
|
BluetoothController::BluetoothController() {
|
|
connect_system_async(sigc::mem_fun(*this, &BluetoothController::onBusConnected));
|
|
}
|
|
|
|
sigc::signal<void(bool)> &BluetoothController::signalPoweredChanged() { return m_powered_signal; }
|
|
sigc::signal<void(bool)> &BluetoothController::signalDiscoveringChanged() { return m_discovering_signal; }
|
|
sigc::signal<void(const BluetoothDevice &)> &BluetoothController::signalDeviceAdded() { return m_device_added_signal; }
|
|
sigc::signal<void(const std::string &)> &BluetoothController::signalDeviceRemoved() { return m_device_removed_signal; }
|
|
sigc::signal<void(const BluetoothDevice &)> &BluetoothController::signalDeviceChanged() { return m_device_changed_signal; }
|
|
|
|
bool BluetoothController::isPowered() const { return m_powered; }
|
|
bool BluetoothController::isDiscovering() const { return m_discovering; }
|
|
|
|
std::vector<BluetoothDevice> BluetoothController::getDevices() const {
|
|
std::vector<BluetoothDevice> result;
|
|
result.reserve(m_devices.size());
|
|
for (const auto &[_, device] : m_devices) {
|
|
result.push_back(device);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<BluetoothDevice> BluetoothController::getPairedDevices() const {
|
|
std::vector<BluetoothDevice> result;
|
|
for (const auto &[_, device] : m_devices) {
|
|
if (device.paired) {
|
|
result.push_back(device);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void BluetoothController::setPowered(bool powered) {
|
|
spdlog::info("Bluetooth: setting powered to {}", powered);
|
|
setDbusProperty(m_adapter_path, "org.bluez.Adapter1", "Powered",
|
|
Glib::Variant<bool>::create(powered));
|
|
}
|
|
|
|
void BluetoothController::startDiscovery() {
|
|
spdlog::info("Bluetooth: starting discovery");
|
|
if (m_adapter_proxy) {
|
|
m_adapter_proxy->call("StartDiscovery");
|
|
}
|
|
}
|
|
|
|
void BluetoothController::stopDiscovery() {
|
|
spdlog::info("Bluetooth: stopping discovery");
|
|
if (m_adapter_proxy) {
|
|
m_adapter_proxy->call("StopDiscovery");
|
|
}
|
|
}
|
|
|
|
void BluetoothController::pairDevice(const std::string &object_path) {
|
|
spdlog::info("Bluetooth: pairing device {}", object_path);
|
|
auto it = m_device_proxies.find(object_path);
|
|
if (it != m_device_proxies.end() && it->second) {
|
|
it->second->call("Pair");
|
|
}
|
|
}
|
|
|
|
void BluetoothController::unpairDevice(const std::string &object_path) {
|
|
spdlog::info("Bluetooth: unpairing device {}", object_path);
|
|
if (!m_adapter_proxy)
|
|
return;
|
|
|
|
auto params = Glib::VariantContainerBase::create_tuple(
|
|
Glib::Variant<Glib::DBusObjectPathString>::create(object_path));
|
|
m_adapter_proxy->call("RemoveDevice", params);
|
|
}
|
|
|
|
void BluetoothController::connectDevice(const std::string &object_path) {
|
|
spdlog::info("Bluetooth: connecting device {}", object_path);
|
|
auto it = m_device_proxies.find(object_path);
|
|
if (it != m_device_proxies.end() && it->second) {
|
|
it->second->call("Connect");
|
|
}
|
|
}
|
|
|
|
void BluetoothController::disconnectDevice(const std::string &object_path) {
|
|
spdlog::info("Bluetooth: disconnecting device {}", object_path);
|
|
auto it = m_device_proxies.find(object_path);
|
|
if (it != m_device_proxies.end() && it->second) {
|
|
it->second->call("Disconnect");
|
|
}
|
|
}
|
|
|
|
void BluetoothController::trustDevice(const std::string &object_path, bool trusted) {
|
|
spdlog::info("Bluetooth: setting trusted={} for device {}", trusted, object_path);
|
|
setDbusProperty(object_path, "org.bluez.Device1", "Trusted",
|
|
Glib::Variant<bool>::create(trusted));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Bus connection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::onBusConnected(const Glib::RefPtr<Gio::AsyncResult> &result) {
|
|
if (!result) {
|
|
spdlog::error("Bluetooth: null async result");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
connection = Gio::DBus::Connection::get_finish(result);
|
|
|
|
m_object_manager_proxy = Gio::DBus::Proxy::create_sync(
|
|
connection,
|
|
"org.bluez",
|
|
"/",
|
|
"org.freedesktop.DBus.ObjectManager");
|
|
|
|
if (m_object_manager_proxy) {
|
|
m_object_manager_proxy->signal_signal().connect(
|
|
sigc::mem_fun(*this, &BluetoothController::onObjectManagerSignal));
|
|
|
|
enumerateObjects();
|
|
}
|
|
|
|
registerAgent();
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Bluetooth DBus Connection Error: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Enumerate existing BlueZ objects
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::enumerateObjects() {
|
|
if (!m_object_manager_proxy)
|
|
return;
|
|
|
|
try {
|
|
auto result = m_object_manager_proxy->call_sync("GetManagedObjects");
|
|
auto objects = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
|
|
result.get_child(0));
|
|
|
|
for (gsize i = 0; i < objects.get_n_children(); i++) {
|
|
auto entry = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
|
|
objects.get_child(i));
|
|
|
|
auto path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
entry.get_child(0))
|
|
.get());
|
|
|
|
parseInterfaces(path, entry.get_child(1));
|
|
}
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Bluetooth: Error enumerating objects: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parse an interfaces-and-properties dict a{sa{sv}}
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::parseInterfaces(const std::string &object_path,
|
|
const Glib::VariantBase &interfaces_var) {
|
|
auto ifaces = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(interfaces_var);
|
|
|
|
for (gsize i = 0; i < ifaces.get_n_children(); i++) {
|
|
auto entry = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
|
|
ifaces.get_child(i));
|
|
|
|
auto iface_name = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(
|
|
entry.get_child(0))
|
|
.get());
|
|
|
|
if (iface_name != "org.bluez.Device1" && iface_name != "org.bluez.Adapter1") {
|
|
continue;
|
|
}
|
|
|
|
auto props = Glib::VariantBase::cast_dynamic<Glib::Variant<PropertiesMap>>(
|
|
entry.get_child(1))
|
|
.get();
|
|
|
|
if (iface_name == "org.bluez.Device1") {
|
|
addDevice(object_path, props);
|
|
} else {
|
|
setupAdapter(object_path, props);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ObjectManager signal handler (InterfacesAdded / InterfacesRemoved)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::onObjectManagerSignal(
|
|
const Glib::ustring &,
|
|
const Glib::ustring &signal_name,
|
|
const Glib::VariantContainerBase ¶meters) {
|
|
|
|
if (signal_name == "InterfacesAdded") {
|
|
// signature: (oa{sa{sv}})
|
|
auto path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0))
|
|
.get());
|
|
|
|
parseInterfaces(path, parameters.get_child(1));
|
|
|
|
} else if (signal_name == "InterfacesRemoved") {
|
|
// signature: (oas)
|
|
auto path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0))
|
|
.get());
|
|
|
|
auto ifaces = Glib::VariantBase::cast_dynamic<
|
|
Glib::Variant<std::vector<Glib::ustring>>>(parameters.get_child(1))
|
|
.get();
|
|
|
|
for (const auto &iface : ifaces) {
|
|
if (iface == "org.bluez.Device1") {
|
|
removeDevice(path);
|
|
} else if (iface == "org.bluez.Adapter1" && path == m_adapter_path) {
|
|
m_adapter_proxy.reset();
|
|
m_adapter_path.clear();
|
|
m_powered = false;
|
|
m_discovering = false;
|
|
m_powered_signal.emit(false);
|
|
m_discovering_signal.emit(false);
|
|
spdlog::info("Bluetooth adapter removed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Adapter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::setupAdapter(const std::string &path,
|
|
const PropertiesMap &properties) {
|
|
m_adapter_path = path;
|
|
spdlog::info("Bluetooth adapter found: {}", path);
|
|
|
|
auto it = properties.find("Powered");
|
|
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
|
|
m_powered = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
|
|
m_powered_signal.emit(m_powered);
|
|
}
|
|
|
|
it = properties.find("Discovering");
|
|
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
|
|
m_discovering = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
|
|
m_discovering_signal.emit(m_discovering);
|
|
}
|
|
|
|
try {
|
|
m_adapter_proxy = Gio::DBus::Proxy::create_sync(
|
|
connection, "org.bluez", path, "org.bluez.Adapter1");
|
|
|
|
if (m_adapter_proxy) {
|
|
m_adapter_proxy->signal_properties_changed().connect(
|
|
sigc::mem_fun(*this, &BluetoothController::onAdapterPropertiesChanged));
|
|
}
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Bluetooth: Error creating adapter proxy: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
void BluetoothController::onAdapterPropertiesChanged(
|
|
const Gio::DBus::Proxy::MapChangedProperties &changed,
|
|
const std::vector<Glib::ustring> &) {
|
|
|
|
auto it = changed.find("Powered");
|
|
if (it != changed.end() && it->second.is_of_type(Glib::VariantType("b"))) {
|
|
m_powered = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
|
|
spdlog::info("Bluetooth powered: {}", m_powered);
|
|
m_powered_signal.emit(m_powered);
|
|
}
|
|
|
|
it = changed.find("Discovering");
|
|
if (it != changed.end() && it->second.is_of_type(Glib::VariantType("b"))) {
|
|
m_discovering = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
|
|
spdlog::info("Bluetooth discovering: {}", m_discovering);
|
|
m_discovering_signal.emit(m_discovering);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Device management
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::addDevice(const std::string &path,
|
|
const PropertiesMap &properties) {
|
|
auto device = parseDeviceProperties(path, properties);
|
|
m_devices[path] = device;
|
|
|
|
spdlog::info("Bluetooth device added: {} ({}) {}", device.name, device.address, device.icon);
|
|
|
|
try {
|
|
auto proxy = Gio::DBus::Proxy::create_sync(
|
|
connection, "org.bluez", path, "org.bluez.Device1");
|
|
|
|
if (proxy) {
|
|
proxy->signal_properties_changed().connect(
|
|
[this, path](const Gio::DBus::Proxy::MapChangedProperties &changed,
|
|
const std::vector<Glib::ustring> &invalidated) {
|
|
onDevicePropertiesChanged(path, changed, invalidated);
|
|
});
|
|
m_device_proxies[path] = proxy;
|
|
}
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Bluetooth: Error creating device proxy for {}: {}", path, ex.what());
|
|
}
|
|
|
|
m_device_added_signal.emit(device);
|
|
}
|
|
|
|
void BluetoothController::removeDevice(const std::string &path) {
|
|
spdlog::info("Bluetooth device removed: {}", path);
|
|
m_devices.erase(path);
|
|
m_device_proxies.erase(path);
|
|
m_device_removed_signal.emit(path);
|
|
}
|
|
|
|
void BluetoothController::onDevicePropertiesChanged(
|
|
const std::string &object_path,
|
|
const Gio::DBus::Proxy::MapChangedProperties &changed,
|
|
const std::vector<Glib::ustring> &) {
|
|
|
|
auto it = m_devices.find(object_path);
|
|
if (it == m_devices.end())
|
|
return;
|
|
|
|
auto &device = it->second;
|
|
|
|
for (const auto &[key, value] : changed) {
|
|
if (key == "Name" && value.is_of_type(Glib::VariantType("s"))) {
|
|
device.name = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
|
|
} else if (key == "Alias" && value.is_of_type(Glib::VariantType("s"))) {
|
|
// Only use Alias as fallback if Name is still the MAC address
|
|
auto alias = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
|
|
if (device.name == device.address) {
|
|
device.name = alias;
|
|
}
|
|
} else if (key == "Paired" && value.is_of_type(Glib::VariantType("b"))) {
|
|
device.paired = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
|
|
} else if (key == "Connected" && value.is_of_type(Glib::VariantType("b"))) {
|
|
device.connected = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
|
|
} else if (key == "Trusted" && value.is_of_type(Glib::VariantType("b"))) {
|
|
device.trusted = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
|
|
} else if (key == "RSSI" && value.is_of_type(Glib::VariantType("n"))) {
|
|
device.rssi = Glib::VariantBase::cast_dynamic<Glib::Variant<gint16>>(value).get();
|
|
} else if (key == "Icon" && value.is_of_type(Glib::VariantType("s"))) {
|
|
device.icon = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
|
|
}
|
|
}
|
|
|
|
m_device_changed_signal.emit(device);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Property parsing helper
|
|
// ---------------------------------------------------------------------------
|
|
|
|
BluetoothDevice BluetoothController::parseDeviceProperties(
|
|
const std::string &path,
|
|
const PropertiesMap &properties) {
|
|
|
|
BluetoothDevice device;
|
|
device.object_path = path;
|
|
|
|
auto getString = [&](const Glib::ustring &key) -> std::string {
|
|
auto it = properties.find(key);
|
|
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("s"))) {
|
|
return static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(it->second).get());
|
|
}
|
|
return "";
|
|
};
|
|
|
|
auto getBool = [&](const Glib::ustring &key) -> bool {
|
|
auto it = properties.find(key);
|
|
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
|
|
return Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
|
|
}
|
|
return false;
|
|
};
|
|
|
|
device.address = getString("Address");
|
|
device.name = getString("Name");
|
|
if (device.name.empty())
|
|
device.name = getString("Alias");
|
|
if (device.name.empty())
|
|
device.name = device.address;
|
|
device.icon = getString("Icon");
|
|
device.paired = getBool("Paired");
|
|
device.connected = getBool("Connected");
|
|
device.trusted = getBool("Trusted");
|
|
|
|
auto rssi_it = properties.find("RSSI");
|
|
if (rssi_it != properties.end() && rssi_it->second.is_of_type(Glib::VariantType("n"))) {
|
|
device.rssi = Glib::VariantBase::cast_dynamic<Glib::Variant<gint16>>(rssi_it->second).get();
|
|
}
|
|
|
|
return device;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// D-Bus property setter (calls org.freedesktop.DBus.Properties.Set)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::setDbusProperty(const std::string &object_path,
|
|
const std::string &interface,
|
|
const std::string &property,
|
|
const Glib::VariantBase &value) {
|
|
if (!connection || object_path.empty())
|
|
return;
|
|
|
|
try {
|
|
auto props_proxy = Gio::DBus::Proxy::create_sync(
|
|
connection, "org.bluez", object_path, "org.freedesktop.DBus.Properties");
|
|
|
|
auto wrapped = Glib::Variant<Glib::VariantBase>::create(value);
|
|
auto params = Glib::VariantContainerBase::create_tuple({Glib::Variant<Glib::ustring>::create(interface),
|
|
Glib::Variant<Glib::ustring>::create(property),
|
|
wrapped});
|
|
|
|
props_proxy->call_sync("Set", params);
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Error setting {}.{}: {}", interface, property, ex.what());
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Agent
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void BluetoothController::registerAgent() {
|
|
if (!connection)
|
|
return;
|
|
|
|
try {
|
|
m_node_info = Gio::DBus::NodeInfo::create_for_xml(agent_introspection_xml);
|
|
auto interface_info = m_node_info->lookup_interface("org.bluez.Agent1");
|
|
|
|
m_interface_vtable = std::make_shared<Gio::DBus::InterfaceVTable>(
|
|
sigc::mem_fun(*this, &BluetoothController::on_agent_method_call));
|
|
|
|
m_agent_id = connection->register_object("/org/bluez/bar_agent", interface_info, *m_interface_vtable);
|
|
|
|
auto agent_manager = Gio::DBus::Proxy::create_sync(
|
|
connection, "org.bluez", "/org/bluez", "org.bluez.AgentManager1");
|
|
|
|
if (agent_manager) {
|
|
agent_manager->call_sync(
|
|
"RegisterAgent",
|
|
Glib::VariantContainerBase::create_tuple(
|
|
{Glib::Variant<Glib::DBusObjectPathString>::create("/org/bluez/bar_agent"),
|
|
Glib::Variant<Glib::ustring>::create("DisplayYesNo")}));
|
|
|
|
agent_manager->call_sync(
|
|
"RequestDefaultAgent",
|
|
Glib::VariantContainerBase::create_tuple(
|
|
{Glib::Variant<Glib::DBusObjectPathString>::create("/org/bluez/bar_agent")}));
|
|
|
|
spdlog::info("Bluetooth Agent registered successfully");
|
|
}
|
|
} catch (const Glib::Error &ex) {
|
|
spdlog::error("Bluetooth: Failed to register agent: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
void BluetoothController::on_agent_method_call(
|
|
const Glib::RefPtr<Gio::DBus::Connection> &,
|
|
const Glib::ustring &,
|
|
const Glib::ustring &,
|
|
const Glib::ustring &,
|
|
const Glib::ustring &method_name,
|
|
const Glib::VariantContainerBase ¶meters,
|
|
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation) {
|
|
|
|
spdlog::info("Bluetooth Agent method called: {}", method_name.c_str());
|
|
|
|
if (method_name == "RequestConfirmation") {
|
|
// Signature: (ou)
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
|
|
guint32 passkey = Glib::VariantBase::cast_dynamic<Glib::Variant<guint32>>(
|
|
parameters.get_child(1)).get();
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
std::ostringstream ss;
|
|
ss << std::setw(6) << std::setfill('0') << passkey;
|
|
std::string passkey_str = ss.str();
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Pairing Request";
|
|
msg.body = "Device '" + device_name + "' wants to pair.\nPasskey: " + passkey_str;
|
|
msg.app_name = "Bar";
|
|
msg.actions = {"Confirm", "Confirm", "Cancel", "Cancel"};
|
|
msg.urgency = NotificationUrgency::CRITICAL;
|
|
msg.expire_timeout = -1; // No timeout, requires user interaction
|
|
|
|
// Capture invocation to respond later
|
|
// Note: invocation is a RefPtr, so copying it increases ref count
|
|
msg.on_action = [invocation](const std::string &action) {
|
|
if (action == "Confirm") {
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
} else {
|
|
invocation->return_dbus_error("org.bluez.Error.Rejected", "Rejected by user");
|
|
}
|
|
};
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
|
|
} else if (method_name == "RequestAuthorization") {
|
|
// Signature: (o)
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Authorization Request";
|
|
msg.body = "Device '" + device_name + "' requests authorization to connect.";
|
|
msg.app_name = "Bar";
|
|
msg.actions = {"Allow", "Allow", "Deny", "Deny"};
|
|
msg.urgency = NotificationUrgency::LOW;
|
|
msg.expire_timeout = -1;
|
|
|
|
msg.on_action = [invocation](const std::string &action) {
|
|
if (action == "Allow") {
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
} else {
|
|
invocation->return_dbus_error("org.bluez.Error.Rejected", "Rejected by user");
|
|
}
|
|
};
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
|
|
} else if (method_name == "AuthorizeService") {
|
|
// Signature: (os)
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
std::string uuid = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(
|
|
parameters.get_child(1)).get());
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Service Authorization";
|
|
msg.body = "Device '" + device_name + "' wants to access service: " + uuid;
|
|
msg.app_name = "Bar";
|
|
msg.actions = {"Allow", "Allow", "Deny", "Deny"};
|
|
msg.urgency = NotificationUrgency::CRITICAL;
|
|
msg.expire_timeout = -1;
|
|
|
|
msg.on_action = [invocation](const std::string &action) {
|
|
if (action == "Allow") {
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
} else {
|
|
invocation->return_dbus_error("org.bluez.Error.Rejected", "Rejected by user");
|
|
}
|
|
};
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
|
|
} else if (method_name == "DisplayPinCode") {
|
|
// Signature: (os)
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
std::string pincode = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(
|
|
parameters.get_child(1)).get());
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Pin Code";
|
|
msg.body = "Pin code for device '" + device_name + "': " + pincode;
|
|
msg.app_name = "Bar";
|
|
msg.actions = {"Dismiss"};
|
|
msg.urgency = NotificationUrgency::NORMAL;
|
|
msg.expire_timeout = 0;
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
|
|
} else if (method_name == "DisplayPasskey") {
|
|
// Signature: (ouq)
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
guint32 passkey = Glib::VariantBase::cast_dynamic<Glib::Variant<guint32>>(
|
|
parameters.get_child(1)).get();
|
|
guint16 entered = Glib::VariantBase::cast_dynamic<Glib::Variant<guint16>>(
|
|
parameters.get_child(2)).get();
|
|
|
|
// Only show on first display (entered == 0) to avoid spam
|
|
if (entered == 0) {
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
std::ostringstream ss;
|
|
ss << std::setw(6) << std::setfill('0') << passkey;
|
|
std::string passkey_str = ss.str();
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Passkey";
|
|
msg.body = "Type this passkey on '" + device_name + "': " + passkey_str;
|
|
msg.app_name = "Bar";
|
|
msg.actions = {"Dismiss"};
|
|
msg.urgency = NotificationUrgency::CRITICAL;
|
|
msg.expire_timeout = -1;
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
}
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
|
|
} else if (method_name == "RequestPinCode") {
|
|
// Signature: (o) -> s
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth PIN Request";
|
|
msg.body = "Enter PIN Code for '" + device_name + "'";
|
|
msg.app_name = "Bar";
|
|
msg.urgency = NotificationUrgency::CRITICAL;
|
|
msg.expire_timeout = -1;
|
|
msg.has_input = true;
|
|
msg.input_placeholder = "PIN Code";
|
|
|
|
msg.on_input = [invocation](const std::string &input) {
|
|
invocation->return_value(
|
|
Glib::VariantContainerBase::create_tuple(
|
|
{Glib::Variant<Glib::ustring>::create(input)}));
|
|
};
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
|
|
} else if (method_name == "RequestPasskey") {
|
|
// Signature: (o) -> u
|
|
std::string device_path = static_cast<std::string>(
|
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
|
|
parameters.get_child(0)).get());
|
|
|
|
std::string device_name = device_path;
|
|
if (m_devices.count(device_path)) {
|
|
device_name = m_devices[device_path].name;
|
|
if (device_name.empty())
|
|
device_name = m_devices[device_path].address;
|
|
}
|
|
|
|
NotifyMessage msg;
|
|
msg.summary = "Bluetooth Passkey Request";
|
|
msg.body = "Enter Passkey for '" + device_name + "'";
|
|
msg.app_name = "Bar";
|
|
msg.urgency = NotificationUrgency::CRITICAL;
|
|
msg.expire_timeout = -1;
|
|
msg.has_input = true;
|
|
msg.input_placeholder = "Passkey (0-999999)";
|
|
|
|
msg.on_input = [invocation](const std::string &input) {
|
|
try {
|
|
uint32_t passkey = std::stoul(input);
|
|
invocation->return_value(
|
|
Glib::VariantContainerBase::create_tuple(
|
|
{Glib::Variant<guint32>::create(passkey)}));
|
|
} catch (...) {
|
|
invocation->return_dbus_error("org.bluez.Error.Failed", "Invalid passkey format");
|
|
}
|
|
};
|
|
|
|
NotificationController::getInstance()->showNotificationOnAllMonitors(msg);
|
|
|
|
} else if (method_name == "Cancel" || method_name == "Release") {
|
|
// Just return
|
|
invocation->return_value(Glib::VariantContainerBase());
|
|
} else {
|
|
// Not implemented
|
|
invocation->return_dbus_error("org.bluez.Error.Rejected", "Not implemented");
|
|
}
|
|
|
|
}
|