summaryrefslogtreecommitdiff
path: root/rockworkd/libpebble/bluez/device.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rockworkd/libpebble/bluez/device.cpp')
-rw-r--r--rockworkd/libpebble/bluez/device.cpp498
1 files changed, 498 insertions, 0 deletions
diff --git a/rockworkd/libpebble/bluez/device.cpp b/rockworkd/libpebble/bluez/device.cpp
new file mode 100644
index 0000000..9a0e9c3
--- /dev/null
+++ b/rockworkd/libpebble/bluez/device.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2013-2015 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include "device.h"
+
+#include <QDBusReply>
+#include <QDebug> // qWarning()
+#include <QThread>
+#include <QTimer>
+
+#include "dbus-shared.h"
+
+Device::Device(const QString &path, QDBusConnection &bus) :
+ m_strength(Device::None)
+{
+ initDevice(path, bus);
+}
+
+void Device::initDevice(const QString &path, QDBusConnection &bus)
+{
+ /* whenever any of the properties changes,
+ trigger the catch-all deviceChanged() signal */
+ QObject::connect(this, SIGNAL(nameChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(iconNameChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(addressChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(pairedChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(trustedChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(typeChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(connectionChanged()), this, SIGNAL(deviceChanged()));
+ QObject::connect(this, SIGNAL(strengthChanged()), this, SIGNAL(deviceChanged()));
+
+ m_bluezDevice.reset(new BluezDevice1(BLUEZ_SERVICE, path, bus));
+ /* Give our calls a bit more time than the default 25 seconds to
+ * complete whatever they are doing. In some situations (e.g. with
+ * specific devices) the default doesn't seem to be enough to. */
+ m_bluezDevice->setTimeout(60 * 1000 /* 60 seconds */);
+
+ m_bluezDeviceProperties.reset(new FreeDesktopProperties(BLUEZ_SERVICE, path, bus));
+
+ QObject::connect(m_bluezDeviceProperties.data(), SIGNAL(PropertiesChanged(const QString&, const QVariantMap&, const QStringList&)),
+ this, SLOT(slotPropertiesChanged(const QString&, const QVariantMap&, const QStringList&)));
+
+ Q_EMIT(pathChanged());
+
+ watchCall(m_bluezDeviceProperties->GetAll(BLUEZ_DEVICE_IFACE), [=](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (reply.isError()) {
+ qWarning() << "Failed to retrieve properties for device" << m_bluezDevice->path();
+ watcher->deleteLater();
+ return;
+ }
+
+ auto properties = reply.argumentAt<0>();
+ setProperties(properties);
+
+ watcher->deleteLater();
+ });
+}
+
+void Device::slotPropertiesChanged(const QString &interface, const QVariantMap &changedProperties,
+ const QStringList &invalidatedProperties)
+{
+ Q_UNUSED(invalidatedProperties);
+
+ if (interface != BLUEZ_DEVICE_IFACE)
+ return;
+
+ setProperties(changedProperties);
+}
+
+void Device::setProperties(const QMap<QString,QVariant> &properties)
+{
+ QMapIterator<QString,QVariant> it(properties);
+ while (it.hasNext()) {
+ it.next();
+ updateProperty(it.key(), it.value());
+ }
+}
+
+void Device::setConnectAfterPairing(bool value)
+{
+ if (m_connectAfterPairing == value)
+ return;
+
+ m_connectAfterPairing = value;
+}
+
+void Device::disconnect()
+{
+ setConnection(Device::Disconnecting);
+
+ QDBusPendingCall call = m_bluezDevice->Disconnect();
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<void> reply = *watcher;
+
+ if (reply.isError()) {
+ qWarning() << "Could not disconnect device:"
+ << reply.error().message();
+
+ // Make sure we switch the connection indicator back to
+ // a sane state
+ updateConnection();
+ }
+
+ watcher->deleteLater();
+ });
+}
+
+void Device::connectAfterPairing()
+{
+ if (!m_connectAfterPairing)
+ return;
+
+ connect();
+}
+
+void Device::pair()
+{
+ if (m_paired) {
+ // If we are already paired we just have to make sure we
+ // trigger the connection process if we have to
+ connectAfterPairing();
+ return;
+ }
+
+ setConnection(Device::Connecting);
+
+ m_isPairing = true;
+
+ auto call = m_bluezDevice->asyncCall("Pair");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<void> reply = *watcher;
+ bool success = true;
+
+ if (reply.isError()) {
+ qWarning() << "Failed to pair with device:"
+ << reply.error().message();
+ updateConnection();
+ success = false;
+ }
+
+ m_isPairing = false;
+
+ Q_EMIT(pairingDone(success));
+
+ watcher->deleteLater();
+ });
+}
+
+void Device::cancelPairing()
+{
+ if (!m_isPairing)
+ return;
+
+ auto call = m_bluezDevice->asyncCall("CancelPairing");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<void> reply = *watcher;
+
+ if (reply.isError()) {
+ qWarning() << "Failed to cancel pairing attempt with device:"
+ << reply.error().message();
+ updateConnection();
+ } else {
+ // Only mark us a not pairing when call succeeded
+ m_isPairing = false;
+ }
+
+ watcher->deleteLater();
+ });
+}
+
+void Device::connect()
+{
+ // If we have just paired then the device switched to connected = true for
+ // a short moment as BlueZ opened up a RFCOMM channel to perform SDP. If
+ // we should connect with the device on specific profiles now we go ahead
+ // here even if we're marked as connected as this still doesn't mean we're
+ // connected on any profile. Calling org.bluez.Device1.Connect multiple
+ // times doesn't hurt an will not fail.
+ if (m_isConnected && !m_connectAfterPairing)
+ return;
+
+ setConnection(Device::Connecting);
+
+ QDBusPendingCall call = m_bluezDevice->asyncCall("Connect");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
+ QDBusPendingReply<void> reply = *watcher;
+
+ if (reply.isError()) {
+ qWarning() << "Could not connect device:"
+ << reply.error().message();
+ } else {
+ makeTrusted(true);
+ }
+
+ // Regardless if the Connected property has changed or not we update
+ // the connection state here as the connection process is over now
+ // and we should have received any state change already at this
+ // point.
+ updateConnection();
+
+ watcher->deleteLater();
+ });
+}
+
+void Device::slotMakeTrustedDone(QDBusPendingCallWatcher *call)
+{
+ QDBusPendingReply<void> reply = *call;
+
+ if (reply.isError()) {
+ qWarning() << "Could not mark device as trusted:"
+ << reply.error().message();
+ }
+
+ call->deleteLater();
+}
+
+void Device::makeTrusted(bool trusted)
+{
+ auto call = m_bluezDeviceProperties->Set(BLUEZ_DEVICE_IFACE, "Trusted", QDBusVariant(trusted));
+
+ auto watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this, SLOT(slotMakeTrustedDone(QDBusPendingCallWatcher*)));
+}
+
+void Device::setName(const QString &name)
+{
+ if (m_name != name) {
+ m_name = name;
+ Q_EMIT(nameChanged());
+ }
+}
+
+void Device::setIconName(const QString &iconName)
+{
+ if (m_iconName != iconName) {
+ m_iconName = iconName;
+ Q_EMIT(iconNameChanged());
+ }
+}
+
+void Device::setAddress(const QString &address)
+{
+ if (m_address != address) {
+ m_address = address;
+ Q_EMIT(addressChanged());
+ }
+}
+
+void Device::setType(Type type)
+{
+ if (m_type != type) {
+ m_type = type;
+ Q_EMIT(typeChanged());
+ updateIcon();
+ }
+}
+
+void Device::setPaired(bool paired)
+{
+ if (m_paired != paired) {
+ m_paired = paired;
+ Q_EMIT(pairedChanged());
+ }
+}
+
+void Device::setTrusted(bool trusted)
+{
+ if (m_trusted != trusted) {
+ m_trusted = trusted;
+ Q_EMIT(trustedChanged());
+ }
+}
+
+void Device::setConnection(Connection connection)
+{
+ if (m_connection != connection) {
+ m_connection = connection;
+ Q_EMIT(connectionChanged());
+ }
+}
+
+void Device::updateIcon()
+{
+ /* bluez-provided icon is unreliable? In testing I'm getting
+ an "audio-card" icon from bluez for my NoiseHush N700 headset.
+ Try to guess the icon from the device type,
+ and use the bluez-provided icon as a fallback */
+
+ const auto type = getType();
+
+ switch (type) {
+ case Type::Headset:
+ setIconName("image://theme/audio-headset-symbolic");
+ break;
+ case Type::Headphones:
+ setIconName("image://theme/audio-headphones-symbolic");
+ break;
+ case Type::Carkit:
+ setIconName("image://theme/audio-carkit-symbolic");
+ break;
+ case Type::Speakers:
+ case Type::OtherAudio:
+ setIconName("image://theme/audio-speakers-symbolic");
+ break;
+ case Type::Mouse:
+ setIconName("image://theme/input-mouse-symbolic");
+ break;
+ case Type::Keyboard:
+ setIconName("image://theme/input-keyboard-symbolic");
+ break;
+ case Type::Cellular:
+ setIconName("image://theme/phone-cellular-symbolic");
+ break;
+ case Type::Smartphone:
+ setIconName("image://theme/phone-smartphone-symbolic");
+ break;
+ case Type::Phone:
+ setIconName("image://theme/phone-uncategorized-symbolic");
+ break;
+ case Type::Computer:
+ setIconName("image://theme/computer-symbolic");
+ break;
+ default:
+ setIconName(QString("image://theme/%1").arg(m_fallbackIconName));
+ }
+}
+
+void Device::updateConnection()
+{
+ Connection c;
+
+ c = m_isConnected ? Connection::Connected : Connection::Disconnected;
+
+ setConnection(c);
+}
+
+void Device::updateProperty(const QString &key, const QVariant &value)
+{
+ if (key == "Name") {
+ setName(value.toString());
+ } else if (key == "Address") {
+ setAddress(value.toString());
+ } else if (key == "Connected") {
+ m_isConnected = value.toBool();
+ updateConnection();
+ } else if (key == "Class") {
+ setType(getTypeFromClass(value.toUInt()));
+ } else if (key == "Paired") {
+ setPaired(value.toBool());
+
+ if (m_paired && m_connectAfterPairing) {
+ connectAfterPairing();
+ return;
+ }
+
+ updateConnection();
+ } else if (key == "Trusted") {
+ setTrusted(value.toBool());
+ } else if (key == "Icon") {
+ m_fallbackIconName = value.toString();
+ updateIcon ();
+ } else if (key == "RSSI") {
+ m_strength = getStrengthFromRssi(value.toInt());
+ Q_EMIT(strengthChanged());
+ }
+}
+
+/* Determine the Type from the bits in the Class of Device (CoD) field.
+ https://www.bluetooth.org/en-us/specification/assigned-numbers/baseband */
+Device::Type Device::getTypeFromClass (quint32 c)
+{
+ switch ((c & 0x1f00) >> 8) {
+ case 0x01:
+ return Type::Computer;
+
+ case 0x02:
+ switch ((c & 0xfc) >> 2) {
+ case 0x01:
+ return Type::Cellular;
+ case 0x03:
+ return Type::Smartphone;
+ case 0x04:
+ return Type::Modem;
+ default:
+ return Type::Phone;
+ }
+ break;
+
+ case 0x03:
+ return Type::Network;
+
+ case 0x04:
+ switch ((c & 0xfc) >> 2) {
+ case 0x01:
+ case 0x02:
+ return Type::Headset;
+
+ case 0x05:
+ return Type::Speakers;
+
+ case 0x06:
+ return Type::Headphones;
+
+ case 0x08:
+ return Type::Carkit;
+
+ case 0x0b: // vcr
+ case 0x0c: // video camera
+ case 0x0d: // camcorder
+ return Type::Video;
+
+ default:
+ return Type::OtherAudio;
+ }
+ break;
+
+ case 0x05:
+ switch ((c & 0xc0) >> 6) {
+ case 0x00:
+ switch ((c & 0x1e) >> 2) {
+ case 0x01:
+ case 0x02:
+ return Type::Joypad;
+ }
+ break;
+
+ case 0x01:
+ return Type::Keyboard;
+
+ case 0x02:
+ switch ((c & 0x1e) >> 2) {
+ case 0x05:
+ return Type::Tablet;
+ default:
+ return Type::Mouse;
+ }
+ }
+ break;
+
+ case 0x06:
+ if ((c & 0x80) != 0)
+ return Type::Printer;
+ if ((c & 0x20) != 0)
+ return Type::Camera;
+ break;
+
+ case 0x07:
+ if ((c & 0x4) != 0)
+ return Type::Watch;
+ break;
+ }
+
+ return Type::Other;
+}
+
+Device::Strength Device::getStrengthFromRssi(int rssi)
+{
+ /* Modelled similar to what Mac OS X does.
+ * See http://www.cnet.com/how-to/how-to-check-bluetooth-connection-strength-in-os-x/ */
+
+ if (rssi >= -60)
+ return Excellent;
+ else if (rssi < -60 && rssi >= -70)
+ return Good;
+ else if (rssi < -70 && rssi >= -90)
+ return Fair;
+ else if (rssi < -90)
+ return Poor;
+
+ return None;
+}