diff options
Diffstat (limited to 'bluetooth')
-rw-r--r-- | bluetooth/bluetooth.cpp | 195 | ||||
-rw-r--r-- | bluetooth/bluetooth.h | 106 | ||||
-rw-r--r-- | bluetooth/bluetootheventhandler.cpp | 142 | ||||
-rw-r--r-- | bluetooth/bluetootheventhandler.h | 43 |
4 files changed, 427 insertions, 59 deletions
diff --git a/bluetooth/bluetooth.cpp b/bluetooth/bluetooth.cpp index c58b64b..5c7fd31 100644 --- a/bluetooth/bluetooth.cpp +++ b/bluetooth/bluetooth.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Konsulko Group + * Copyright (C) 2018-2022 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,10 +23,17 @@ #include "bluetootheventhandler.h" -Bluetooth::Bluetooth (bool register_agent, QQmlContext *context, QObject * parent) : +Bluetooth::Bluetooth(bool register_agent, + QQmlContext *context, + bool handle_media, + QObject * parent) : QObject(parent), m_context(context), - m_agent(register_agent) + m_agent(register_agent), + m_handle_media(handle_media), + m_connected(false), + m_connected_device(""), + m_media_connected(false) { m_bluetooth = new BluetoothModel(); BluetoothModelFilter *m_model = new BluetoothModelFilter(); @@ -39,7 +46,7 @@ Bluetooth::Bluetooth (bool register_agent, QQmlContext *context, QObject * paren m_model->setFilterFixedString("false"); context->setContextProperty("BluetoothDiscoveryModel", m_model); - m_event_handler = new BluetoothEventHandler(this, register_agent); + m_event_handler = new BluetoothEventHandler(this, register_agent, handle_media); uuids.insert("a2dp", "0000110a-0000-1000-8000-00805f9b34fb"); uuids.insert("avrcp", "0000110e-0000-1000-8000-00805f9b34fb"); @@ -61,12 +68,19 @@ void Bluetooth::setDiscoverable(bool state) m_discoverable = state; - emit discoverableChanged(); + emit discoverableChanged(state); } void Bluetooth::start() { - bluez_init(m_agent, m_agent, m_event_handler->init_cb, m_event_handler); + // NOTE: An explicit "start" is somewhat required at present as calling + // bluez_init in the constructor means state update signals get + // emitted before the QML in an app seems able to receive them. + // A slightly better alternative might be something like a + // "refresh" function, or documenting that apps must explicitly + // call getters of relevant values on start. + + bluez_init(m_agent, m_agent, m_event_handler->init_cb, m_event_handler); } void Bluetooth::discovery_command(bool state) @@ -153,7 +167,7 @@ void Bluetooth::disconnect(QString device) bluez_device_disconnect(device_cstr, NULL); } -void Bluetooth::send_confirmation(int pincode) +void Bluetooth::send_confirmation(const int pincode) { QString pincode_str; pincode_str.setNum(pincode); @@ -163,7 +177,75 @@ void Bluetooth::send_confirmation(int pincode) bluez_confirm_pairing(pincode_cstr); } -void Bluetooth::init_adapter_state(QString adapter) +void Bluetooth::media_control(MediaAction action) +{ + QString action_name; + bool action_allowed = true; + bluez_media_control_t bluez_action; + switch (action) { + case Connect: + bluez_action = BLUEZ_MEDIA_CONTROL_CONNECT; + action_name = "Connect"; + action_allowed = !m_media_connected; + break; + case Disconnect: + bluez_action = BLUEZ_MEDIA_CONTROL_DISCONNECT; + action_name = "Disconnect"; + action_allowed = m_media_connected; + break; + case Play: + bluez_action = BLUEZ_MEDIA_CONTROL_PLAY; + action_name = "Play"; + break; + case Pause: + bluez_action = BLUEZ_MEDIA_CONTROL_PAUSE; + action_name = "Pause"; + break; + case Stop: + bluez_action = BLUEZ_MEDIA_CONTROL_STOP; + action_name = "Stop"; + break; + case Next: + bluez_action = BLUEZ_MEDIA_CONTROL_NEXT; + action_name = "Next"; + break; + case Previous: + bluez_action = BLUEZ_MEDIA_CONTROL_PREVIOUS; + action_name = "Previous"; + break; + case FastForward: + bluez_action = BLUEZ_MEDIA_CONTROL_FASTFORWARD; + action_name = "Fastforward"; + break; + case Rewind: + bluez_action = BLUEZ_MEDIA_CONTROL_REWIND; + action_name = "Rewind"; + break; + case Loop: + // Not implemented, but potentially possible with bluez-glib addition + default: + break; + } +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "Bluetooth::media_control: enter, action = " << action_name; + qDebug() << "Bluetooth::media_control: m_connected = " << m_connected + << ", m_media_connected = " << m_media_connected; +#endif + + if (!(m_connected && action_allowed)) { + qDebug() << "Bluetooth::media_control: not connected or invalid action!"; + return; + } + + QByteArray device_ba = m_connected_device.toLocal8Bit(); + const char *device_cstr = device_ba.data(); + bluez_device_avrcp_controls(device_cstr, bluez_action); +} + + +// Private + +void Bluetooth::init_adapter_state(const QString &adapter) { // Get initial power state GVariant *reply = NULL; @@ -171,7 +253,7 @@ void Bluetooth::init_adapter_state(QString adapter) if (rc && reply) { GVariantDict *props_dict = g_variant_dict_new(reply); gboolean powered = FALSE; - if (g_variant_dict_lookup(props_dict, "Powered", "b", &powered)) { + if (g_variant_dict_lookup(props_dict, "Powered", "b", &powered)) { if (m_power != powered) { m_power = powered; emit powerChanged(m_power); @@ -179,10 +261,16 @@ void Bluetooth::init_adapter_state(QString adapter) } g_variant_dict_unref(props_dict); g_variant_unref(reply); - } + } // Get initial device list refresh_device_list(); + + // Do a refresh of the media state to handle the situation where + // a client app has been started after a phone has been connected + // (and thus misses seeing the related events go by). + if (m_handle_media) + refresh_media_state(); } void Bluetooth::refresh_device_list(void) @@ -202,8 +290,12 @@ void Bluetooth::refresh_device_list(void) GVariant *var = NULL; while (g_variant_iter_next(array, "{&sv}", &key, &var)) { BluetoothDevice *device = m_bluetooth->updateDeviceProperties(nullptr, key, var); - if (device) + if (device) { m_bluetooth->addDevice(device); + if (device->connected()) { + update_connected_state(device->id(), true); + } + } g_variant_unref(var); } @@ -211,6 +303,42 @@ void Bluetooth::refresh_device_list(void) g_variant_unref(reply); } +void Bluetooth::refresh_media_state() +{ + if (!(m_handle_media && m_connected && m_connected_device.count())) + return; + + QByteArray device_ba = m_connected_device.toLocal8Bit(); + const char *device_cstr = device_ba.data(); + + GVariant *reply = NULL; + if (!bluez_get_media_control_properties(device_cstr, &reply)) + return; + + GVariantDict *props_dict = g_variant_dict_new(reply); + if (!props_dict) { + g_variant_unref(reply); + return; + } + + gboolean connected = FALSE; + if (g_variant_dict_lookup(props_dict, "Connected", "b", &connected)) { + update_media_connected_state(connected); + + GVariant *player_reply = NULL; + if(bluez_get_media_player_properties(device_cstr, &player_reply)) { + QVariantMap tmp; + m_event_handler->parse_media_player_properties(player_reply, tmp); + if (!tmp.empty()) + update_media_properties(tmp); + + g_variant_unref(player_reply); + } + } + g_variant_dict_unref(props_dict); + g_variant_unref(reply); +} + void Bluetooth::set_discovery_filter(void) { QList<QString> values = uuids.values(); @@ -233,7 +361,7 @@ void Bluetooth::set_discovery_filter(void) g_free(transport); } -void Bluetooth::update_adapter_power(bool powered) +void Bluetooth::update_adapter_power(const bool powered) { if (!powered) m_bluetooth->removeAllDevices(); @@ -251,11 +379,50 @@ void Bluetooth::update_adapter_power(bool powered) bool discoverable = m_discoverable; m_discoverable = false; if (discoverable != m_discoverable) - emit discoverableChanged(); + emit discoverableChanged(false); + } +} + +void Bluetooth::update_connected_state(const QString &device, const bool connected) +{ +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "Bluetooth::update_connected_state: device = " << device + << ", connected = " << connected; +#endif + if (m_connected != connected) { + if (!m_connected) { + // Connecting + m_connected = true; + m_connected_device = device; + emit connectedChanged(true); + } else if (m_connected_device == device) { + // Disconnecting + m_connected = false; + m_connected_device = ""; + emit connectedChanged(false); + } else { + qDebug() << "Bluetooth::update_connected_state: ignoring " << device; + } } } -void Bluetooth::request_confirmation(int pincode) +void Bluetooth::update_media_connected_state(const bool connected) +{ +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "Bluetooth::update_media_connected_state: connected = " << connected; +#endif + if (m_media_connected != connected) { + m_media_connected = connected; + emit mediaConnectedChanged(connected); + } +} + +void Bluetooth::update_media_properties(const QVariantMap &metadata) +{ + emit mediaPropertiesChanged(metadata); +} + +void Bluetooth::request_confirmation(const int pincode) { QString pincode_str; pincode_str.setNum(pincode); diff --git a/bluetooth/bluetooth.h b/bluetooth/bluetooth.h index c7ef55c..09fb4d9 100644 --- a/bluetooth/bluetooth.h +++ b/bluetooth/bluetooth.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Konsulko Group + * Copyright (C) 2018-2022 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,64 +26,90 @@ class BluetoothEventHandler; class Bluetooth : public QObject { - Q_OBJECT - Q_PROPERTY(bool power READ power WRITE setPower NOTIFY powerChanged) - Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged) + Q_OBJECT + Q_PROPERTY(bool power READ power WRITE setPower NOTIFY powerChanged) + Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged) + Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) + Q_PROPERTY(bool mediaConnected READ mediaConnected NOTIFY mediaConnectedChanged) - public: - explicit Bluetooth(bool register_agent, QQmlContext *context, QObject * parent = Q_NULLPTR); - virtual ~Bluetooth(); +public: + explicit Bluetooth(bool register_agent, + QQmlContext *context, + bool handle_media = false, + QObject *parent = Q_NULLPTR); + virtual ~Bluetooth(); - void setPower(bool); - void setDiscoverable(bool); + void setPower(bool); + void setDiscoverable(bool); - Q_INVOKABLE void start(void); + Q_INVOKABLE void start(void); - Q_INVOKABLE void start_discovery(void); - Q_INVOKABLE void stop_discovery(void); + Q_INVOKABLE void start_discovery(void); + Q_INVOKABLE void stop_discovery(void); - Q_INVOKABLE void remove_device(QString device); - Q_INVOKABLE void pair(QString device); - Q_INVOKABLE void cancel_pair(void); + Q_INVOKABLE void remove_device(QString device); + Q_INVOKABLE void pair(QString device); + Q_INVOKABLE void cancel_pair(void); - Q_INVOKABLE void connect(QString device, QString uuid); - Q_INVOKABLE void connect(QString device); + Q_INVOKABLE void connect(QString device, QString uuid); + Q_INVOKABLE void connect(QString device); - Q_INVOKABLE void disconnect(QString device, QString uuid); - Q_INVOKABLE void disconnect(QString device); + Q_INVOKABLE void disconnect(QString device, QString uuid); + Q_INVOKABLE void disconnect(QString device); - Q_INVOKABLE void send_confirmation(int pincode); + Q_INVOKABLE void send_confirmation(int pincode); - bool power() const { return m_power; }; - bool discoverable() const { return m_discoverable; }; + enum MediaAction { + Connect, Disconnect, + Play, Pause, Stop, Next, Previous, FastForward, Rewind, Loop + }; + Q_ENUM(MediaAction) - signals: - void powerChanged(bool state); - void discoverableChanged(); + Q_INVOKABLE void media_control(MediaAction action); + Q_INVOKABLE void refresh_media_state(); - //void connectionEvent(QJsonObject data); + bool power() const { return m_power; }; + bool discoverable() const { return m_discoverable; }; + bool connected() const { return m_connected; }; + bool mediaConnected() const { return m_media_connected; }; + +signals: + void powerChanged(bool state); + void discoverableChanged(bool state); + void connectedChanged(bool state); + void mediaConnectedChanged(bool state); + + void mediaPropertiesChanged(QVariantMap metadata); void requestConfirmationEvent(QString pincode); - private: - QQmlContext *m_context; - BluetoothModel *m_bluetooth; - BluetoothEventHandler *m_event_handler; +private: + QQmlContext *m_context; + BluetoothModel *m_bluetooth; + BluetoothEventHandler *m_event_handler; bool m_agent; + bool m_handle_media; - void init_adapter_state(QString); + void init_adapter_state(const QString &adapter); void refresh_device_list(void); - void set_discovery_filter(void); - void discovery_command(bool); - void update_adapter_power(bool); - void request_confirmation(int); + void set_discovery_filter(void); + void discovery_command(const bool); + void update_adapter_power(const bool powered); + void update_connected_state(const QString &device, const bool connected); + void update_media_connected_state(const bool connected); + void update_media_properties(const QVariantMap &metadata); + void request_confirmation(const int pincode); + + QString process_uuid(QString uuid) { if (uuid.length() == 36) return uuid; return uuids.value(uuid); }; - QString process_uuid(QString uuid) { if (uuid.length() == 36) return uuid; return uuids.value(uuid); }; + // values + bool m_power; + bool m_discoverable; + bool m_connected; + bool m_media_connected; - // values - bool m_power; - bool m_discoverable; + QString m_connected_device; - QMap<QString, QString> uuids; + QMap<QString, QString> uuids; friend class BluetoothEventHandler; }; diff --git a/bluetooth/bluetootheventhandler.cpp b/bluetooth/bluetootheventhandler.cpp index 0acd5f5..a0154bb 100644 --- a/bluetooth/bluetootheventhandler.cpp +++ b/bluetooth/bluetootheventhandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Konsulko Group + * Copyright (C) 2021,2022 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,27 @@ #include <QDebug> #include <bluez-glib.h> - #include "bluetootheventhandler.h" #include "bluetooth.h" #include "bluetoothmodel.h" +#undef BLUETOOTH_EVENT_DEBUG + -BluetoothEventHandler::BluetoothEventHandler (Bluetooth *parent, bool register_agent) : +BluetoothEventHandler::BluetoothEventHandler(Bluetooth *parent, bool register_agent, bool handle_media) : m_parent(parent), m_agent(register_agent) { bluez_add_adapter_event_callback(adapter_event_cb, this); bluez_add_device_event_callback(device_event_cb, this); + if (register_agent) bluez_add_agent_event_callback(agent_event_cb, this); + + if (handle_media) { + bluez_add_media_control_event_callback(media_control_event_cb, this); + bluez_add_media_player_event_callback(media_player_event_cb, this); + } } BluetoothEventHandler::~BluetoothEventHandler() @@ -38,6 +45,10 @@ BluetoothEventHandler::~BluetoothEventHandler() void BluetoothEventHandler::handle_init_event(gchar *adapter, gboolean status) { +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "BluetoothEventHandler::handle_init_event: enter, status = " << (int) status; +#endif + if (status) m_parent->init_adapter_state(QString(adapter)); else @@ -48,6 +59,15 @@ void BluetoothEventHandler::handle_adapter_event(gchar *adapter, bluez_event_t event, GVariant *properties) { +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "BluetoothEventHandler::handle_adapter_event: enter"; + gchar *p = properties ? g_variant_print(properties, TRUE) : g_strdup("(null)"); + qDebug() << "BluetoothEventHandler::handle_adapter_event: adapter = " + << adapter << ", event = " << (int) event; + qDebug() << "BluetoothEventHandler::handle_adapter_event: properties = " << p; + g_free(p); +#endif + if (!adapter || event != BLUEZ_EVENT_CHANGE) return; @@ -71,6 +91,15 @@ void BluetoothEventHandler::handle_device_event(gchar *adapter, bluez_event_t event, GVariant *properties) { +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "BluetoothEventHandler::handle_device_event: enter"; + gchar *p = properties ? g_variant_print(properties, TRUE) : g_strdup("(null)"); + qDebug() << "BluetoothEventHandler::handle_device_event: adapter = " + << adapter << ", device = " << device << ", event = " << (int) event; + qDebug() << "BluetoothEventHandler::handle_device_event: properties = " << p; + g_free(p); +#endif + if (!device) return; @@ -91,6 +120,72 @@ void BluetoothEventHandler::handle_device_event(gchar *adapter, // device not previously in model m_parent->m_bluetooth->addDevice(new_device); } + + // Update parent's connected state + // NOTE: Currently not worrying about multiple devices being connected + GVariantDict *props_dict = g_variant_dict_new(properties); + if (props_dict) { + gboolean connected = FALSE; + if (g_variant_dict_lookup(props_dict, "Connected", "b", &connected)) { + m_parent->update_connected_state(QString(device), connected); + } + g_variant_dict_unref(props_dict); + } +} + +void BluetoothEventHandler::handle_media_control_event(gchar *adapter, + gchar *device, + bluez_event_t event, + GVariant *properties) +{ +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "BluetoothEventHandler::handle_media_control_event: enter"; + gchar *p = properties ? g_variant_print(properties, TRUE) : g_strdup("(null)"); + qDebug() << "BluetoothEventHandler::handle_media_control_event: adapter = " + << adapter << ", device = " << device << ", event = " << (int) event; + qDebug() << "BluetoothEventHandler::handle_media_control_event: properties = " << p; + g_free(p); +#endif + + if (event != BLUEZ_EVENT_CHANGE) + return; + + // Update parent's media connected state + // NOTE: Currently not worrying about multiple devices being connected + GVariantDict *props_dict = g_variant_dict_new(properties); + if (props_dict) { + gboolean connected = FALSE; + if (g_variant_dict_lookup(props_dict, "Connected", "b", &connected)) { + m_parent->update_media_connected_state(connected); + } + g_variant_dict_unref(props_dict); + } + +} + +void BluetoothEventHandler::handle_media_player_event(gchar *adapter, + gchar *device, + gchar *player, + bluez_event_t event, + GVariant *properties) +{ +#ifdef BLUETOOTH_EVENT_DEBUG + qDebug() << "BluetoothEventHandler::handle_media_player_event: enter"; + gchar *p = properties ? g_variant_print(properties, TRUE) : g_strdup("(null)"); + qDebug() << "BluetoothEventHandler::handle_media_player_event: adapter = " + << adapter << ", device = " << device << ", player = " << player + << ", event = " << (int) event; + qDebug() << "BluetoothEventHandler::handle_media_player_event: properties = " << p; + g_free(p); +#endif + + if (event != BLUEZ_EVENT_REMOVE) { + QVariantMap tmp; + parse_media_player_properties(properties, tmp); + if (!tmp.empty()) { + m_parent->update_media_properties(tmp); + } + } } void BluetoothEventHandler::handle_agent_event(gchar *device, @@ -119,3 +214,44 @@ void BluetoothEventHandler::handle_pair_event(gchar *device, gboolean status) qDebug() << "pairing failed"; } +void BluetoothEventHandler::parse_media_player_properties(GVariant *properties, QVariantMap &metadata) +{ + GVariantDict *props_dict = g_variant_dict_new(properties); + if (!props_dict) + return; + + GVariant *v = NULL; + if (g_variant_dict_lookup(props_dict, "Track", "@a{sv}", &v)) { + GVariantDict *track_dict = g_variant_dict_new(v); + QVariantMap track; + gchar *p = NULL; + + if (g_variant_dict_lookup(track_dict, "Title", "&s", &p)) + track.insert(QString("title"), QVariant(QString(p))); + + if (g_variant_dict_lookup(track_dict, "Artist", "&s", &p)) + track.insert(QString("artist"), QVariant(QString(p))); + + if (g_variant_dict_lookup(track_dict, "Album", "&s", &p)) + track.insert(QString("album"), QVariant(QString(p))); + + unsigned int duration = 0; + if (g_variant_dict_lookup(track_dict, "Duration", "u", &duration)) + track.insert(QString("duration"), QVariant(duration)); + + g_variant_dict_unref(track_dict); + + metadata.insert("track", track); + } + + unsigned int position = 0; + if (g_variant_dict_lookup(props_dict, "Position", "u", &position)) { + metadata.insert("position", QVariant(position)); + } + + gchar *p = NULL; + if (g_variant_dict_lookup(props_dict, "Status", "&s", &p)) + metadata.insert(QString("status"), QVariant(QString(p))); + + g_variant_dict_unref(props_dict); +} diff --git a/bluetooth/bluetootheventhandler.h b/bluetooth/bluetootheventhandler.h index ff970f5..c4dd67a 100644 --- a/bluetooth/bluetootheventhandler.h +++ b/bluetooth/bluetootheventhandler.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Konsulko Group + * Copyright (C) 2021,2022 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ class Bluetooth; class BluetoothEventHandler { public: - explicit BluetoothEventHandler(Bluetooth *parent, bool register_agent); + explicit BluetoothEventHandler(Bluetooth *parent, bool register_agent, bool handle_media = false); virtual ~BluetoothEventHandler(); static void init_cb(gchar *adapter, gboolean status, gpointer user_data) { @@ -40,6 +40,10 @@ class BluetoothEventHandler ((BluetoothEventHandler*) user_data)->handle_pair_event(device, status); } + // Helper function callable by parent (instead of vice versa), since Bluetooth + // object should not expose glib types. + void parse_media_player_properties(GVariant *properties, QVariantMap &metadata); + private: Bluetooth *m_parent; bool m_agent; @@ -59,11 +63,46 @@ class BluetoothEventHandler if (user_data) ((BluetoothEventHandler*) user_data)->handle_agent_event(device, event, properties); } + + static void media_control_event_cb(gchar *adapter, + gchar *device, + bluez_event_t event, + GVariant *properties, + gpointer user_data) { + if (user_data) + ((BluetoothEventHandler*) user_data)->handle_media_control_event(adapter, + device, + event, + properties); + } + + static void media_player_event_cb(gchar *adapter, + gchar *device, + gchar *player, + bluez_event_t event, + GVariant *properties, + gpointer user_data) { + if (user_data) + ((BluetoothEventHandler*) user_data)->handle_media_player_event(adapter, + device, + player, + event, + properties); + } void handle_init_event(gchar *adapter, gboolean status); void handle_adapter_event(gchar *adapter, bluez_event_t event, GVariant *properties); void handle_device_event(gchar *adapter, gchar *device, bluez_event_t event, GVariant *properties); void handle_agent_event(gchar *device, bluez_agent_event_t event, GVariant *properties); + void handle_media_control_event(gchar *adapter, + gchar *device, + bluez_event_t event, + GVariant *properties); + void handle_media_player_event(gchar *adapter, + gchar *device, + gchar *player, + bluez_event_t event, + GVariant *properties); void handle_connect_event(gchar *device, gboolean status); void handle_pair_event(gchar *device, gboolean status); }; |