/* * 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "bluetooth.h" #include "bluetoothmodel.h" #include "bluetootheventhandler.h" Bluetooth::Bluetooth(bool register_agent, QQmlContext *context, bool handle_media, QObject * parent) : QObject(parent), m_context(context), 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(); m_model->setSourceModel(m_bluetooth); m_model->setFilterFixedString("true"); context->setContextProperty("BluetoothPairedModel", m_model); m_model = new BluetoothModelFilter(); m_model->setSourceModel(m_bluetooth); m_model->setFilterFixedString("false"); context->setContextProperty("BluetoothDiscoveryModel", m_model); 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"); uuids.insert("hfp", "0000111f-0000-1000-8000-00805f9b34fb"); } Bluetooth::~Bluetooth() { } void Bluetooth::setPower(bool state) { bluez_adapter_set_powered(NULL, state ? TRUE : FALSE); } void Bluetooth::setDiscoverable(bool state) { bluez_adapter_set_discoverable(NULL, state); m_discoverable = state; emit discoverableChanged(state); } void Bluetooth::start() { // 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) { set_discovery_filter(); bluez_adapter_set_discovery(NULL, state ? TRUE : FALSE); } void Bluetooth::start_discovery() { discovery_command(true); } void Bluetooth::stop_discovery() { discovery_command(false); } void Bluetooth::remove_device(QString device) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); bluez_device_remove(device_cstr); } void Bluetooth::pair(QString device) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); bluez_device_pair(device_cstr, m_event_handler->device_pair_cb, m_event_handler); } void Bluetooth::cancel_pair(void) { bluez_cancel_pairing(); } void Bluetooth::connect(QString device, QString uuid) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); uuid = process_uuid(uuid); QByteArray uuid_ba = uuid.toLocal8Bit(); const char *uuid_cstr = uuid_ba.data(); bluez_device_connect(device_cstr, uuid_cstr, m_event_handler->device_connect_cb, m_event_handler); } void Bluetooth::connect(QString device) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); bluez_device_connect(device_cstr, NULL, m_event_handler->device_connect_cb, m_event_handler); } void Bluetooth::disconnect(QString device, QString uuid) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); uuid = process_uuid(uuid); QByteArray uuid_ba = uuid.toLocal8Bit(); const char *uuid_cstr = uuid_ba.data(); bluez_device_disconnect(device_cstr, uuid_cstr); } void Bluetooth::disconnect(QString device) { QByteArray device_ba = device.toLocal8Bit(); const char *device_cstr = device_ba.data(); bluez_device_disconnect(device_cstr, NULL); } void Bluetooth::send_confirmation(const int pincode) { QString pincode_str; pincode_str.setNum(pincode); QByteArray pincode_ba = pincode_str.toLocal8Bit(); const char *pincode_cstr = pincode_ba.data(); bluez_confirm_pairing(pincode_cstr); } 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; gboolean rc = bluez_adapter_get_state(NULL, &reply); 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 (m_power != powered) { m_power = powered; emit powerChanged(m_power); } } 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) { gboolean rc; GVariant *reply = NULL; rc = bluez_adapter_get_devices(NULL, &reply); if(!rc) return; m_bluetooth->removeAllDevices(); GVariantIter *array = NULL; g_variant_get(reply, "a{sv}", &array); const gchar *key = NULL; GVariant *var = NULL; while (g_variant_iter_next(array, "{&sv}", &key, &var)) { BluetoothDevice *device = m_bluetooth->updateDeviceProperties(nullptr, key, var); if (device) { m_bluetooth->addDevice(device); if (device->connected()) { update_connected_state(device->id(), true); } } g_variant_unref(var); } g_variant_iter_free(array); 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 values = uuids.values(); QStringListIterator eventIterator(values); gchar **uuids_array = (gchar**) g_malloc0((values.count() + 1) * sizeof(gchar*)); int i = 0; while (eventIterator.hasNext()) { QByteArray uuid_ba = eventIterator.next().toLocal8Bit(); gchar *uuid_cstr = g_strdup(uuid_ba.data()); uuids_array[i++] = uuid_cstr; } gchar *transport = g_strdup("bredr"); bluez_adapter_set_discovery_filter(NULL, uuids_array, transport); for (i = 0; i < values.count(); i++) g_free(uuids_array[i]); g_free(uuids_array); g_free(transport); } void Bluetooth::update_adapter_power(const bool powered) { if (!powered) m_bluetooth->removeAllDevices(); if (m_power != powered) { m_power = powered; emit powerChanged(powered); if (powered) refresh_device_list(); } if (!m_power) { bool discoverable = m_discoverable; m_discoverable = false; if (discoverable != m_discoverable) 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::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); emit requestConfirmationEvent(pincode_str); }