summaryrefslogtreecommitdiff
path: root/rockworkd
diff options
context:
space:
mode:
Diffstat (limited to 'rockworkd')
-rw-r--r--rockworkd/core.cpp4
-rw-r--r--rockworkd/jsfiles.qrc2
-rw-r--r--rockworkd/libpebble/bluez/bluezclient.cpp87
-rw-r--r--rockworkd/libpebble/bluez/bluezclient.h11
-rw-r--r--rockworkd/libpebble/bluez/device.cpp498
-rw-r--r--rockworkd/libpebble/bluez/device.h172
-rw-r--r--rockworkd/libpebble/jskit/typedarray.js61
-rw-r--r--rockworkd/libpebble/pebble.cpp2
-rw-r--r--rockworkd/libpebble/watchconnection.cpp18
-rw-r--r--rockworkd/pebblemanager.cpp8
-rw-r--r--rockworkd/platformintegration/sailfish/callchannelobserver.cpp166
-rw-r--r--rockworkd/platformintegration/sailfish/callchannelobserver.h74
-rw-r--r--rockworkd/platformintegration/sailfish/organizeradapter.cpp97
-rw-r--r--rockworkd/platformintegration/sailfish/organizeradapter.h44
-rw-r--r--rockworkd/platformintegration/sailfish/sailfishplatform.cpp313
-rw-r--r--rockworkd/platformintegration/sailfish/sailfishplatform.h58
-rw-r--r--rockworkd/platformintegration/sailfish/syncmonitorclient.cpp100
-rw-r--r--rockworkd/platformintegration/sailfish/syncmonitorclient.h51
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallhandler.cpp372
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallhandler.h96
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallmanager.cpp315
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallmanager.h111
-rw-r--r--rockworkd/rockpoold.service11
-rw-r--r--rockworkd/rockworkd.pro145
24 files changed, 2756 insertions, 60 deletions
diff --git a/rockworkd/core.cpp b/rockworkd/core.cpp
index 38a25c5..eb98dfd 100644
--- a/rockworkd/core.cpp
+++ b/rockworkd/core.cpp
@@ -3,7 +3,7 @@
#include "pebblemanager.h"
#include "dbusinterface.h"
-#include "platformintegration/ubuntu/ubuntuplatform.h"
+#include "platformintegration/sailfish/sailfishplatform.h"
#ifdef ENABLE_TESTING
#include "platformintegration/testing/testingplatform.h"
#endif
@@ -41,7 +41,7 @@ void Core::init()
#ifdef ENABLE_TESTING
m_platform = new TestingPlatform(this);
#else
- m_platform = new UbuntuPlatform(this);
+ m_platform = new SailfishPlatform(this);
#endif
m_pebbleManager = new PebbleManager(this);
diff --git a/rockworkd/jsfiles.qrc b/rockworkd/jsfiles.qrc
deleted file mode 100644
index 807350d..0000000
--- a/rockworkd/jsfiles.qrc
+++ /dev/null
@@ -1,2 +0,0 @@
-<RCC/>
-
diff --git a/rockworkd/libpebble/bluez/bluezclient.cpp b/rockworkd/libpebble/bluez/bluezclient.cpp
index 8cdf848..313c540 100644
--- a/rockworkd/libpebble/bluez/bluezclient.cpp
+++ b/rockworkd/libpebble/bluez/bluezclient.cpp
@@ -1,5 +1,6 @@
#include "bluezclient.h"
#include "dbus-shared.h"
+#include "device.h"
#include <QDBusConnection>
#include <QDBusReply>
@@ -26,32 +27,84 @@ BluezClient::BluezClient(QObject *parent):
InterfaceList ifaces = objectList.value(path);
if (ifaces.contains(BLUEZ_DEVICE_IFACE)) {
QString candidatePath = path.path();
+ qDebug() << "have device" << candidatePath;
auto properties = ifaces.value(BLUEZ_DEVICE_IFACE);
addDevice(path, properties);
}
}
+
+ if (m_devices.isEmpty()) {
+ // Try with bluez 4
+ QDBusConnection system = QDBusConnection::systemBus();
+
+ QDBusReply<QList<QDBusObjectPath> > listAdaptersReply = system.call(
+ QDBusMessage::createMethodCall("org.bluez", "/", "org.bluez.Manager",
+ "ListAdapters"));
+ if (!listAdaptersReply.isValid()) {
+ qWarning() << listAdaptersReply.error().message();
+ return;
+ }
+
+ QList<QDBusObjectPath> adapters = listAdaptersReply.value();
+
+ if (adapters.isEmpty()) {
+ qWarning() << "No BT adapters found";
+ return;
+ }
+
+ QDBusReply<QVariantMap> adapterPropertiesReply = system.call(
+ QDBusMessage::createMethodCall("org.bluez", adapters[0].path(), "org.bluez.Adapter",
+ "GetProperties"));
+ if (!adapterPropertiesReply.isValid()) {
+ qWarning() << adapterPropertiesReply.error().message();
+ return;
+ }
+
+ QList<QDBusObjectPath> devices;
+ adapterPropertiesReply.value()["Devices"].value<QDBusArgument>() >> devices;
+
+ foreach (QDBusObjectPath path, devices) {
+ QDBusReply<QVariantMap> devicePropertiesReply = system.call(
+ QDBusMessage::createMethodCall("org.bluez", path.path(), "org.bluez.Device",
+ "GetProperties"));
+ if (!devicePropertiesReply.isValid()) {
+ qCritical() << devicePropertiesReply.error().message();
+ continue;
+ }
+
+ const QVariantMap &dict = devicePropertiesReply.value();
+
+ QString name = dict["Name"].toString();
+ if (name.startsWith("Pebble") && !name.startsWith("Pebble Time LE") && !name.startsWith("Pebble-LE")) {
+ qDebug() << "Found Pebble:" << name;
+ addDevice(path, dict);
+ }
+ }
+ }
}
}
-QList<Device> BluezClient::pairedPebbles() const
+QList<BluezDevice> BluezClient::pairedPebbles() const
{
- QList<Device> ret;
- if (m_bluezManager.isValid()) {
- foreach (const Device &dev, m_devices) {
- ret << dev;
- }
+ QList<BluezDevice> ret;
+
+ foreach (const BluezDevice &dev, m_devices) {
+ ret << dev;
}
+
return ret;
+
}
void BluezClient::addDevice(const QDBusObjectPath &path, const QVariantMap &properties)
{
QString address = properties.value("Address").toString();
QString name = properties.value("Name").toString();
+ qDebug() << "Adding device" << address << name;
if (name.startsWith("Pebble") && !name.startsWith("Pebble Time LE") && !name.startsWith("Pebble-LE") && !m_devices.contains(address)) {
qDebug() << "Found new Pebble:" << address << name;
- Device device;
+ BluezDevice device;
device.address = QBluetoothAddress(address);
device.name = name;
device.path = path.path();
@@ -70,6 +123,26 @@ void BluezClient::slotInterfacesAdded(const QDBusObjectPath &path, InterfaceList
}
}
+void BluezClient::slotDevicePairingDone(bool success)
+{
+ qDebug() << "pairing done" << success;
+ if (!success) {
+ return;
+ }
+
+ Device *device = static_cast<Device*>(sender());
+ device->deleteLater();
+
+ if (!m_devices.contains(device->getAddress())) {
+ BluezDevice bluezDevice;
+ bluezDevice.address = QBluetoothAddress(device->getAddress());
+ bluezDevice.name = device->getName();
+ bluezDevice.path = device->getPath();
+ m_devices.insert(device->getAddress(), bluezDevice);
+ emit devicesChanged();
+ }
+}
+
void BluezClient::slotInterfacesRemoved(const QDBusObjectPath &path, const QStringList &ifaces)
{
qDebug() << "interfaces removed" << path.path() << ifaces;
diff --git a/rockworkd/libpebble/bluez/bluezclient.h b/rockworkd/libpebble/bluez/bluezclient.h
index f8e5749..cbe7c0f 100644
--- a/rockworkd/libpebble/bluez/bluezclient.h
+++ b/rockworkd/libpebble/bluez/bluezclient.h
@@ -11,7 +11,7 @@
#include "bluez_adapter1.h"
#include "bluez_agentmanager1.h"
-class Device {
+class BluezDevice {
public:
QBluetoothAddress address;
QString name;
@@ -26,14 +26,15 @@ public:
BluezClient(QObject *parent = 0);
- QList<Device> pairedPebbles() const;
+ QList<BluezDevice> pairedPebbles() const;
private slots:
void addDevice(const QDBusObjectPath &path, const QVariantMap &properties);
void slotInterfacesAdded(const QDBusObjectPath&path, InterfaceList ifaces);
- void slotInterfacesRemoved(const QDBusObjectPath&path, const QStringList &ifaces);
-
+ void slotDevicePairingDone(bool success);
+ void slotInterfacesRemoved(const QDBusObjectPath&path, const QStringList &ifaces);
+
signals:
void devicesChanged();
@@ -45,7 +46,7 @@ private:
FreeDesktopProperties *m_bluezAdapterProperties = nullptr;
- QHash<QString, Device> m_devices;
+ QHash<QString, BluezDevice> m_devices;
};
#endif // BLUEZCLIENT_H
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;
+}
diff --git a/rockworkd/libpebble/bluez/device.h b/rockworkd/libpebble/bluez/device.h
new file mode 100644
index 0000000..bcc044b
--- /dev/null
+++ b/rockworkd/libpebble/bluez/device.h
@@ -0,0 +1,172 @@
+/*
+ * 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>
+ */
+
+#ifndef USS_BLUETOOTH_DEVICE_H
+#define USS_BLUETOOTH_DEVICE_H
+
+#include <QDBusConnection>
+#include <QDBusInterface>
+#include <QDBusPendingCallWatcher>
+#include <QSharedPointer>
+#include <QString>
+
+#include "freedesktop_properties.h"
+#include "bluez_device1.h"
+
+struct Device: QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString path
+ READ getPath
+ NOTIFY pathChanged)
+
+ Q_PROPERTY(QString name
+ READ getName
+ NOTIFY nameChanged)
+
+ Q_PROPERTY(QString iconName
+ READ getIconName
+ NOTIFY iconNameChanged)
+
+ Q_PROPERTY(QString address
+ READ getAddress
+ NOTIFY addressChanged)
+
+ Q_PROPERTY(Type type
+ READ getType
+ NOTIFY typeChanged)
+
+ Q_PROPERTY(bool paired
+ READ isPaired
+ NOTIFY pairedChanged)
+
+ Q_PROPERTY(bool trusted
+ READ isTrusted
+ WRITE makeTrusted
+ NOTIFY trustedChanged)
+
+ Q_PROPERTY(Connection connection
+ READ getConnection
+ NOTIFY connectionChanged)
+
+ Q_PROPERTY(Strength strength
+ READ getStrength
+ NOTIFY strengthChanged)
+
+public:
+
+ enum Type { Other, Computer, Cellular, Smartphone, Phone, Modem, Network,
+ Headset, Speakers, Headphones, Video, OtherAudio, Joypad,
+ Keypad, Keyboard, Tablet, Mouse, Printer, Camera, Carkit, Watch };
+
+ enum Strength { None, Poor, Fair, Good, Excellent };
+
+ enum Connection { Disconnected=1, Connecting=2,
+ Connected=4, Disconnecting=8 };
+
+ Q_ENUMS(Type Strength Connection)
+
+ Q_DECLARE_FLAGS(Connections, Connection)
+
+Q_SIGNALS:
+ void pathChanged();
+ void nameChanged();
+ void iconNameChanged();
+ void addressChanged();
+ void typeChanged();
+ void pairedChanged();
+ void trustedChanged();
+ void connectionChanged();
+ void strengthChanged();
+ void deviceChanged(); // catchall for any change
+ void pairingDone(bool success);
+
+public:
+ const QString& getName() const { return m_name; }
+ const QString& getAddress() const { return m_address; }
+ const QString& getIconName() const { return m_iconName; }
+ Type getType() const { return m_type; }
+ bool isPaired() const { return m_paired; }
+ bool isTrusted() const { return m_trusted; }
+ Connection getConnection() const { return m_connection; }
+ Strength getStrength() const { return m_strength; }
+ QString getPath() const { return m_bluezDevice ? m_bluezDevice->path() : QString(); }
+
+ private:
+ QString m_name;
+ QString m_state;
+ QString m_address;
+ QString m_iconName;
+ QString m_fallbackIconName;
+ Type m_type = Type::Other;
+ bool m_paired = false;
+ bool m_trusted = false;
+ Connection m_connection = Connection::Disconnected;
+ Strength m_strength = Strength::None;
+ bool m_isConnected = false;
+ bool m_connectAfterPairing = false;
+ QScopedPointer<BluezDevice1> m_bluezDevice;
+ QScopedPointer<FreeDesktopProperties> m_bluezDeviceProperties;
+ bool m_isPairing = false;
+
+ protected:
+ void setName(const QString &name);
+ void setIconName(const QString &name);
+ void setAddress(const QString &address);
+ void setType(Type type);
+ void setPaired(bool paired);
+ void setTrusted(bool trusted);
+ void setConnection(Connection connection);
+ void setStrength(Strength strength);
+ void updateIcon();
+ void updateConnection();
+
+ public:
+ Device() {}
+ Device(const QString &path, QDBusConnection &bus);
+ ~Device() {}
+ bool isValid() const { return getType() != Type::Other; }
+ void pair();
+ Q_INVOKABLE void cancelPairing();
+ void connect();
+ void makeTrusted(bool trusted);
+ void disconnect();
+ void setProperties(const QMap<QString,QVariant> &properties);
+ void setConnectAfterPairing(bool value);
+
+ private Q_SLOTS:
+ void slotPropertiesChanged(const QString &interface, const QVariantMap &changedProperties,
+ const QStringList &invalidatedProperties);
+ void slotMakeTrustedDone(QDBusPendingCallWatcher *call);
+
+ private:
+ void initDevice(const QString &path, QDBusConnection &bus);
+ void updateProperties(QSharedPointer<QDBusInterface>);
+ void updateProperty(const QString &key, const QVariant &value);
+ static Type getTypeFromClass(quint32 bluetoothClass);
+ Device::Strength getStrengthFromRssi(int rssi);
+ void connectAfterPairing();
+};
+
+Q_DECLARE_METATYPE(Device*)
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Device::Connections)
+
+#endif // USS_BLUETOOTH_DEVICE_H
diff --git a/rockworkd/libpebble/jskit/typedarray.js b/rockworkd/libpebble/jskit/typedarray.js
index d4e00c6..eec78a2 100644
--- a/rockworkd/libpebble/jskit/typedarray.js
+++ b/rockworkd/libpebble/jskit/typedarray.js
@@ -61,7 +61,7 @@
return Object(v);
}
function ToInt32(v) { return v >> 0; }
- function ToUint32(v) { return v >> 0; } //ROCKWORK HACK ALERT: it appears that QT doesn't do the >>> properly, using >> here instead (should be close enough)
+ function ToUint32(v) { return v >>> 0; }
// Snapshot intrinsics
var LN2 = Math.LN2,
@@ -135,21 +135,23 @@
function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; }
- function packI16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
- function unpackI16(bytes) { return as_signed(bytes[1] << 8 | bytes[0], 16); }
+ function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
+ function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); }
- function packU16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
- function unpackU16(bytes) { return as_unsigned(bytes[1] << 8 | bytes[0], 16); }
+ function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
+ function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); }
- function packI32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
- function unpackI32(bytes) { return as_signed(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
+ function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
+ function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
- function packU32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
- function unpackU32(bytes) { return as_unsigned(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
+ function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
+ function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
function packIEEE754(v, ebits, fbits) {
- var bias = (1 << (ebits - 1)) - 1;
+ var bias = (1 << (ebits - 1)) - 1,
+ s, e, f, ln,
+ i, bits, str, bytes;
function roundToEven(n) {
var w = floor(n), f = n - w;
@@ -161,7 +163,6 @@
}
// Compute sign, exponent, fraction
- var s, e, f;
if (v !== v) {
// NaN
// http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
@@ -175,28 +176,20 @@
v = abs(v);
if (v >= pow(2, 1 - bias)) {
- // Normalized
e = min(floor(log(v) / LN2), 1023);
- var significand = v / pow(2, e);
- if (significand < 1) {
- e -= 1;
- significand *= 2;
+ f = roundToEven(v / pow(2, e) * pow(2, fbits));
+ if (f / pow(2, fbits) >= 2) {
+ e = e + 1;
+ f = 1;
}
- if (significand >= 2) {
- e += 1;
- significand /= 2;
- }
- var d = pow(2, fbits);
- f = roundToEven(significand * d) - d;
- e += bias;
- if (f / d >= 1) {
- e += 1;
- f = 0;
- }
- if (e > 2 * bias) {
+ if (e > bias) {
// Overflow
e = (1 << ebits) - 1;
f = 0;
+ } else {
+ // Normalized
+ e = e + bias;
+ f = f - pow(2, fbits);
}
} else {
// Denormalized
@@ -206,17 +199,17 @@
}
// Pack sign, exponent, fraction
- var bits = [], i;
+ bits = [];
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); }
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); }
bits.push(s ? 1 : 0);
bits.reverse();
- var str = bits.join('');
+ str = bits.join('');
// Bits to bytes
- var bytes = [];
+ bytes = [];
while (str.length) {
- bytes.unshift(parseInt(str.substring(0, 8), 2));
+ bytes.push(parseInt(str.substring(0, 8), 2));
str = str.substring(8);
}
return bytes;
@@ -227,8 +220,8 @@
var bits = [], i, j, b, str,
bias, s, e, f;
- for (i = 0; i < bytes.length; ++i) {
- b = bytes[i];
+ for (i = bytes.length; i; i -= 1) {
+ b = bytes[i - 1];
for (j = 8; j; j -= 1) {
bits.push(b % 2 ? 1 : 0); b = b >> 1;
}
diff --git a/rockworkd/libpebble/pebble.cpp b/rockworkd/libpebble/pebble.cpp
index 0f76097..d2e44f5 100644
--- a/rockworkd/libpebble/pebble.cpp
+++ b/rockworkd/libpebble/pebble.cpp
@@ -134,7 +134,7 @@ bool Pebble::connected() const
void Pebble::connect()
{
- qDebug() << "Connecting to Pebble:" << m_name << m_address;
+ qDebug() << "Connecting to Pebble:" << m_name << m_address.toString();
m_connection->connectPebble(m_address);
}
diff --git a/rockworkd/libpebble/watchconnection.cpp b/rockworkd/libpebble/watchconnection.cpp
index 0778a1d..dabacf4 100644
--- a/rockworkd/libpebble/watchconnection.cpp
+++ b/rockworkd/libpebble/watchconnection.cpp
@@ -32,7 +32,7 @@ UploadManager *WatchConnection::uploadManager() const
void WatchConnection::scheduleReconnect()
{
- if (m_connectionAttempts == 0) {
+ if (m_connectionAttempts < 2) {
reconnect();
} else if (m_connectionAttempts < 25) {
qDebug() << "Attempting to reconnect in 10 seconds";
@@ -49,21 +49,27 @@ void WatchConnection::scheduleReconnect()
void WatchConnection::reconnect()
{
QBluetoothLocalDevice localBtDev;
+ qDebug() << "Reconnection";
if (localBtDev.pairingStatus(m_pebbleAddress) == QBluetoothLocalDevice::Unpaired) {
// Try again in one 10 secs, give the user some time to pair it
+ qDebug() << "Unpaired.";
m_connectionAttempts = 1;
scheduleReconnect();
return;
}
if (m_socket) {
+ qDebug() << "Socket exists.";
if (m_socket->state() == QBluetoothSocket::ConnectedState) {
qDebug() << "Already connected.";
return;
}
- delete m_socket;
+ m_socket->deleteLater();
}
+ m_connectionAttempts++;
+
+ qDebug() << "Creating socket.";
m_socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this);
connect(m_socket, &QBluetoothSocket::connected, this, &WatchConnection::pebbleConnected);
connect(m_socket, &QBluetoothSocket::readyRead, this, &WatchConnection::readyRead);
@@ -71,7 +77,6 @@ void WatchConnection::reconnect()
connect(m_socket, &QBluetoothSocket::disconnected, this, &WatchConnection::pebbleDisconnected);
//connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(onBytesWritten(qint64)));
- m_connectionAttempts++;
// FIXME: Assuming port 1 (with Pebble)
m_socket->connectToService(m_pebbleAddress, 1);
@@ -141,8 +146,12 @@ void WatchConnection::pebbleConnected()
void WatchConnection::pebbleDisconnected()
{
qDebug() << "Disconnected";
- m_socket->close();
+
emit watchDisconnected();
+ QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
+ if (!socket) return;
+
+ socket->deleteLater();
if (!m_reconnectTimer.isActive()) {
scheduleReconnect();
}
@@ -152,7 +161,6 @@ void WatchConnection::socketError(QBluetoothSocket::SocketError error)
{
Q_UNUSED(error); // We seem to get UnknownError anyways all the time
qDebug() << "SocketError" << error;
- m_socket->close();
emit watchConnectionFailed();
if (!m_reconnectTimer.isActive()) {
scheduleReconnect();
diff --git a/rockworkd/pebblemanager.cpp b/rockworkd/pebblemanager.cpp
index 126000e..842dff0 100644
--- a/rockworkd/pebblemanager.cpp
+++ b/rockworkd/pebblemanager.cpp
@@ -26,8 +26,8 @@ QList<Pebble *> PebbleManager::pebbles() const
void PebbleManager::loadPebbles()
{
- QList<Device> pairedPebbles = m_bluezClient->pairedPebbles();
- foreach (const Device &device, pairedPebbles) {
+ QList<BluezDevice> pairedPebbles = m_bluezClient->pairedPebbles();
+ foreach (const BluezDevice &device, pairedPebbles) {
qDebug() << "loading pebble" << device.address.toString();
Pebble *pebble = get(device.address);
if (!pebble) {
@@ -46,7 +46,7 @@ void PebbleManager::loadPebbles()
QList<Pebble*> pebblesToRemove;
foreach (Pebble *pebble, m_pebbles) {
bool found = false;
- foreach (const Device &dev, pairedPebbles) {
+ foreach (const BluezDevice &dev, pairedPebbles) {
if (dev.address == pebble->address()) {
found = true;
break;
@@ -59,7 +59,7 @@ void PebbleManager::loadPebbles()
while (!pebblesToRemove.isEmpty()) {
Pebble *pebble = pebblesToRemove.takeFirst();
- qDebug() << "Removing pebble" << pebble->address();
+ qDebug() << "Removing pebble" << pebble->address().toString();
m_pebbles.removeOne(pebble);
emit pebbleRemoved(pebble);
pebble->deleteLater();
diff --git a/rockworkd/platformintegration/sailfish/callchannelobserver.cpp b/rockworkd/platformintegration/sailfish/callchannelobserver.cpp
new file mode 100644
index 0000000..a9f41f3
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/callchannelobserver.cpp
@@ -0,0 +1,166 @@
+#include "callchannelobserver.h"
+
+#include <TelepathyQt/Contact>
+#include <TelepathyQt/PendingContactInfo>
+
+#include <QContactFetchRequest>
+#include <QContactPhoneNumber>
+#include <QContactFilter>
+#include <QContactDetail>
+#include <QContactDisplayLabel>
+
+QTCONTACTS_USE_NAMESPACE
+
+TelepathyMonitor::TelepathyMonitor(QObject *parent):
+ QObject(parent)
+{
+ Tp::registerTypes();
+ QTimer::singleShot(0, this, SLOT(accountManagerSetup()));
+ QMap<QString, QString> parameters;
+ parameters.insert(QString::fromLatin1("mergePresenceChanges"), QString::fromLatin1("false"));
+ m_contactManager = new QContactManager("", parameters, this);
+}
+
+void TelepathyMonitor::hangupCall(uint cookie)
+{
+ if (m_currentCalls.contains(cookie)) {
+ m_currentCalls.value(cookie)->hangup();
+ }
+}
+
+void TelepathyMonitor::accountManagerSetup()
+{
+ m_accountManager = Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
+ Tp::Account::FeatureCore),
+ Tp::ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Tp::Connection::FeatureCore));
+ connect(m_accountManager->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountManagerReady(Tp::PendingOperation*)));
+}
+
+void TelepathyMonitor::accountManagerReady(Tp::PendingOperation* operation)
+{
+ if (operation->isError()) {
+ qDebug() << "TelepathyMonitor: accountManager init error.";
+ QTimer::singleShot(1000, this, SLOT(accountManagerSetup())); // again
+ return;
+ }
+ qDebug() << "Telepathy account manager ready";
+
+ foreach (const Tp::AccountPtr& account, m_accountManager->allAccounts()) {
+ connect(account->becomeReady(Tp::Account::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountReady(Tp::PendingOperation*)));
+ }
+
+ connect(m_accountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(newAccount(Tp::AccountPtr)));
+}
+
+void TelepathyMonitor::newAccount(const Tp::AccountPtr& account)
+{
+ connect(account->becomeReady(Tp::Account::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountReady(Tp::PendingOperation*)));
+}
+
+void TelepathyMonitor::accountReady(Tp::PendingOperation* operation)
+{
+ if (operation->isError()) {
+ qDebug() << "TelepathyAccount: Operation failed (accountReady)";
+ return;
+ }
+
+ Tp::PendingReady* pendingReady = qobject_cast<Tp::PendingReady*>(operation);
+ if (pendingReady == 0) {
+ qDebug() << "Rejecting account because could not understand ready status";
+ return;
+ }
+ checkAndAddAccount(Tp::AccountPtr::qObjectCast(pendingReady->proxy()));
+}
+
+void TelepathyMonitor::onCallStarted(Tp::CallChannelPtr callChannel)
+{
+ // Haven't figured how to send outgoing calls to pebble yet... discard it
+ if (callChannel->initiatorContact()->id().isEmpty()) {
+ qWarning() << "ignoring phone call. looks like it's an outgoing one";
+ return;
+ }
+
+ m_cookie++;
+ m_currentCalls.insert(m_cookie, callChannel.data());
+ m_currentCallStates.insert(m_cookie, Tp::CallStateInitialising);
+
+ callChannel->becomeReady(Tp::CallChannel::FeatureCallState);
+
+ connect(callChannel.data(), &Tp::CallChannel::callStateChanged, this, &TelepathyMonitor::callStateChanged);
+
+ QString number = callChannel->initiatorContact()->id();
+ qDebug() << "call started" << number;
+
+ // try to match the contact info
+ QContactFetchRequest *request = new QContactFetchRequest(this);
+ request->setFilter(QContactPhoneNumber::match(number));
+
+ // lambda function to update the notification
+ QObject::connect(request, &QContactAbstractRequest::stateChanged, [this, request, number](QContactAbstractRequest::State state) {
+ qDebug() << "request returned";
+ if (!request || state != QContactAbstractRequest::FinishedState) {
+ qDebug() << "error fetching contact" << state;
+ return;
+ }
+
+ QContact contact;
+
+ // create the snap decision only after the contact match finishes
+ if (request->contacts().size() > 0) {
+ // use the first match
+ contact = request->contacts().at(0);
+
+ qDebug() << "have contact" << contact.detail<QContactDisplayLabel>().label();
+ emit this->incomingCall(m_cookie, number, contact.detail<QContactDisplayLabel>().label());
+ } else {
+ qDebug() << "unknown contact" << number;
+ emit this->incomingCall(m_cookie, number, QString());
+ }
+ });
+
+ request->setManager(m_contactManager);
+ request->start();
+}
+
+void TelepathyMonitor::callStateChanged(Tp::CallState state)
+{
+ qDebug() << "call state changed1";
+ Tp::CallChannel *channel = qobject_cast<Tp::CallChannel*>(sender());
+ uint cookie = m_currentCalls.key(channel);
+
+ qDebug() << "call state changed2" << state << "cookie:" << cookie;
+
+ switch (state) {
+ case Tp::CallStateActive:
+ emit callStarted(cookie);
+ m_currentCallStates[cookie] = Tp::CallStateActive;
+ break;
+ case Tp::CallStateEnded: {
+ Tp::CallState oldState = m_currentCallStates.value(cookie);
+ emit callEnded(cookie, oldState != Tp::CallStateActive);
+ m_currentCalls.take(cookie);
+ m_currentCallStates.take(cookie);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void TelepathyMonitor::checkAndAddAccount(const Tp::AccountPtr& account)
+{
+ Tp::ConnectionCapabilities caps = account->capabilities();
+ // TODO: Later on we will need to filter for the right capabilities, and also allow dynamic account detection
+ // Don't check caps for now as a workaround for https://bugs.launchpad.net/ubuntu/+source/media-hub/+bug/1409125
+ // at least until we are able to find out the root cause of it (check rev 107 for the caps check)
+ auto tcm = new TelepathyCallMonitor(account);
+ connect(tcm, &TelepathyCallMonitor::callStarted, this, &TelepathyMonitor::onCallStarted);
+ m_callMonitors.append(tcm);
+}
diff --git a/rockworkd/platformintegration/sailfish/callchannelobserver.h b/rockworkd/platformintegration/sailfish/callchannelobserver.h
new file mode 100644
index 0000000..ba3415d
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/callchannelobserver.h
@@ -0,0 +1,74 @@
+#ifndef CALLCHANNELOBSERVER_H
+#define CALLCHANNELOBSERVER_H
+
+#include <TelepathyQt/AccountManager>
+#include <TelepathyQt/SimpleCallObserver>
+#include <TelepathyQt/PendingOperation>
+#include <TelepathyQt/PendingReady>
+#include <TelepathyQt/PendingAccount>
+#include <TelepathyQt/CallChannel>
+
+#include <QContactManager>
+
+QTCONTACTS_USE_NAMESPACE
+
+class TelepathyCallMonitor : public QObject
+{
+ Q_OBJECT
+public:
+ TelepathyCallMonitor(const Tp::AccountPtr& account):
+ mAccount(account),
+ mCallObserver(Tp::SimpleCallObserver::create(mAccount)) {
+ connect(mCallObserver.data(), SIGNAL(callStarted(Tp::CallChannelPtr)), SIGNAL(callStarted(Tp::CallChannelPtr)));
+// connect(mCallObserver.data(), SIGNAL(callEnded(Tp::CallChannelPtr,QString,QString)), SIGNAL(callEnded()));
+// connect(mCallObserver.data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)), SIGNAL(offHook()));
+// connect(mCallObserver.data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)), SIGNAL(onHook()));
+ }
+
+signals:
+ void callStarted(Tp::CallChannelPtr callChannel);
+ void callEnded();
+
+private:
+ Tp::AccountPtr mAccount;
+ Tp::SimpleCallObserverPtr mCallObserver;
+};
+
+class TelepathyMonitor: public QObject
+{
+ Q_OBJECT
+public:
+ TelepathyMonitor(QObject *parent = 0);
+
+ void hangupCall(uint cookie);
+
+private slots:
+ void accountManagerSetup();
+ void accountManagerReady(Tp::PendingOperation* operation);
+
+ void newAccount(const Tp::AccountPtr& account);
+ void accountReady(Tp::PendingOperation* operation);
+
+ void onCallStarted(Tp::CallChannelPtr callChannel);
+ void callStateChanged(Tp::CallState state);
+
+signals:
+ void incomingCall(uint cookie, const QString &number, const QString &name);
+ void callStarted(uint cookie);
+ void callEnded(uint cookie, bool missed);
+
+private:
+ void checkAndAddAccount(const Tp::AccountPtr& account);
+
+private:
+ Tp::AccountManagerPtr m_accountManager;
+ QList<TelepathyCallMonitor*> m_callMonitors;
+ QContactManager *m_contactManager;
+
+ QHash<uint, Tp::CallChannel*> m_currentCalls;
+ QHash<uint, Tp::CallState> m_currentCallStates;
+
+ uint m_cookie = 0;
+};
+
+#endif // CALLCHANNELOBSERVER_H
diff --git a/rockworkd/platformintegration/sailfish/organizeradapter.cpp b/rockworkd/platformintegration/sailfish/organizeradapter.cpp
new file mode 100644
index 0000000..416aad2
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/organizeradapter.cpp
@@ -0,0 +1,97 @@
+#include "organizeradapter.h"
+
+#include <QOrganizerItemFetchRequest>
+#include <QDebug>
+#include <QOrganizerEventOccurrence>
+#include <QOrganizerItemDetail>
+# include <extendedcalendar.h>
+# include <extendedstorage.h>
+
+QTORGANIZER_USE_NAMESPACE
+
+#define MANAGER "eds"
+#define MANAGER_FALLBACK "memory"
+
+OrganizerAdapter::OrganizerAdapter(QObject *parent) : QObject(parent)
+{
+ QString envManager(qgetenv("ALARM_BACKEND"));
+ if (envManager.isEmpty())
+ envManager = MANAGER;
+ if (!QOrganizerManager::availableManagers().contains(envManager)) {
+ envManager = MANAGER_FALLBACK;
+ }
+ m_manager = new QOrganizerManager(envManager);
+ m_manager->setParent(this);
+ connect(m_manager, &QOrganizerManager::dataChanged, this, &OrganizerAdapter::refresh);
+
+ mKCal::ExtendedCalendar::Ptr calendar = mKCal::ExtendedCalendar::Ptr ( new mKCal::ExtendedCalendar( QLatin1String( "UTC" ) ) );
+ mKCal::ExtendedStorage::Ptr storage = mKCal::ExtendedCalendar::defaultStorage( calendar );
+ if (storage->open()) {
+ mKCal::Notebook::List notebooks = storage->notebooks();
+ qDebug()<< "Notebooks: " + notebooks.count();
+ for (int ii = 0; ii < notebooks.count(); ++ii) {
+ if (!notebooks.at(ii)->isReadOnly()) {
+ m_calendars << CalendarInfo(normalizeCalendarName(notebooks.at(ii)->name()), notebooks.at(ii)->uid());
+ qDebug()<< "Notebook: " << notebooks.at(ii)->name() << notebooks.at(ii)->uid();
+ }
+ }
+ }
+}
+
+QString OrganizerAdapter::normalizeCalendarName(QString name)
+{
+ if (name == "qtn_caln_personal_caln") {
+ return tr("Personal");
+ }
+
+ return name;
+}
+
+void OrganizerAdapter::refresh()
+{
+ QList<CalendarEvent> items;
+ foreach (const QOrganizerItem &item, m_manager->items()) {
+ QOrganizerEvent organizerEvent(item);
+ if (organizerEvent.displayLabel().isEmpty()) {
+ continue;
+ }
+ CalendarEvent event;
+ event.setId(organizerEvent.id().toString());
+ event.setTitle(organizerEvent.displayLabel());
+ event.setDescription(organizerEvent.description());
+ event.setStartTime(organizerEvent.startDateTime());
+ event.setEndTime(organizerEvent.endDateTime());
+ event.setLocation(organizerEvent.location());
+ event.setComment(organizerEvent.comments().join(";"));
+ QStringList attendees;
+ foreach (const QOrganizerItemDetail &attendeeDetail, organizerEvent.details(QOrganizerItemDetail::TypeEventAttendee)) {
+ attendees.append(attendeeDetail.value(QOrganizerItemDetail::TypeEventAttendee + 1).toString());
+ }
+ event.setGuests(attendees);
+ event.setRecurring(organizerEvent.recurrenceRules().count() > 0);
+
+ items.append(event);
+
+ quint64 startTimestamp = QDateTime::currentMSecsSinceEpoch();
+ startTimestamp -= (1000 * 60 * 60 * 24 * 7);
+
+ foreach (const QOrganizerItem &occurranceItem, m_manager->itemOccurrences(item, QDateTime::fromMSecsSinceEpoch(startTimestamp), QDateTime::currentDateTime().addDays(7))) {
+ QOrganizerEventOccurrence organizerOccurrance(occurranceItem);
+ event.setId(organizerOccurrance.id().toString());
+ event.setStartTime(organizerOccurrance.startDateTime());
+ event.setEndTime(organizerOccurrance.endDateTime());
+ items.append(event);
+ }
+ }
+
+ if (m_items != items) {
+ m_items = items;
+ emit itemsChanged(m_items);
+ }
+
+}
+
+QList<CalendarEvent> OrganizerAdapter::items() const
+{
+ return m_items;
+}
diff --git a/rockworkd/platformintegration/sailfish/organizeradapter.h b/rockworkd/platformintegration/sailfish/organizeradapter.h
new file mode 100644
index 0000000..04ebfa3
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/organizeradapter.h
@@ -0,0 +1,44 @@
+#ifndef ORGANIZERADAPTER_H
+#define ORGANIZERADAPTER_H
+
+#include "libpebble/calendarevent.h"
+
+#include <QObject>
+
+#include <QOrganizerManager>
+#include <QOrganizerAbstractRequest>
+#include <QOrganizerEvent>
+
+QTORGANIZER_USE_NAMESPACE
+
+struct CalendarInfo
+{
+ QString name;
+ QString notebookUID;
+
+ CalendarInfo(const QString &name, const QString &notebookUID = QString())
+ : name(name), notebookUID(notebookUID) {}
+};
+
+class OrganizerAdapter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OrganizerAdapter(QObject *parent = 0);
+
+ QList<CalendarEvent> items() const;
+ QString normalizeCalendarName(QString name);
+
+public slots:
+ void refresh();
+
+signals:
+ void itemsChanged(const QList<CalendarEvent> &items);
+
+private:
+ QOrganizerManager *m_manager;
+ QList<CalendarEvent> m_items;
+ QList<CalendarInfo> m_calendars;
+};
+
+#endif // ORGANIZERADAPTER_H
diff --git a/rockworkd/platformintegration/sailfish/sailfishplatform.cpp b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp
new file mode 100644
index 0000000..09e1426
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp
@@ -0,0 +1,313 @@
+#include "sailfishplatform.h"
+
+#include "callchannelobserver.h"
+#include "organizeradapter.h"
+#include "syncmonitorclient.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+
+SailfishPlatform::SailfishPlatform(QObject *parent):
+ PlatformInterface(parent),
+ _pulseBus(NULL),
+ _maxVolume(0)
+{
+ // Notifications
+ QDBusConnection::sessionBus().registerObject("/org/freedesktop/Notifications", this, QDBusConnection::ExportAllSlots);
+ m_iface = new QDBusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");
+ m_iface->call("AddMatch", "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'");
+ m_iface->call("AddMatch", "interface='org.freedesktop.Notifications',member='CloseNotification',type='method_call',eavesdrop='true'");
+
+ // Music
+ QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface();
+ const QStringList &services = iface->registeredServiceNames();
+ foreach (QString service, services) {
+ if (service.startsWith("org.mpris.MediaPlayer2.")) {
+ qDebug() << "have mpris service" << service;
+ m_mprisService = service;
+ fetchMusicMetadata();
+ QDBusConnection::sessionBus().connect(m_mprisService, "/org/mpris/MediaPlayer2", "", "PropertiesChanged", this, SLOT(mediaPropertiesChanged(QString,QVariantMap,QStringList)));
+ break;
+ }
+ }
+
+ QDBusMessage call = QDBusMessage::createMethodCall("org.PulseAudio1", "/org/pulseaudio/server_lookup1", "org.freedesktop.DBus.Properties", "Get" );
+ call << "org.PulseAudio.ServerLookup1" << "Address";
+ QDBusReply<QDBusVariant> lookupReply = QDBusConnection::sessionBus().call(call);
+ if (lookupReply.isValid()) {
+ //
+ qDebug() << "PulseAudio Bus address: " << lookupReply.value().variant().toString();
+ _pulseBus = new QDBusConnection(QDBusConnection::connectToPeer(lookupReply.value().variant().toString(), "org.PulseAudio1"));
+ }
+ // Query max volume
+ call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Get");
+ call << "com.Meego.MainVolume2" << "StepCount";
+ QDBusReply<QDBusVariant> volumeMaxReply = _pulseBus->call(call);
+ if (volumeMaxReply.isValid()) {
+ _maxVolume = volumeMaxReply.value().variant().toUInt();
+ qDebug() << "Max volume: " << _maxVolume;
+ }
+ else {
+ qWarning() << "Could not read volume max, cannot adjust volume: " << volumeMaxReply.error().message();
+ }
+
+ // Calls
+ m_telepathyMonitor = new TelepathyMonitor(this);
+ connect(m_telepathyMonitor, &TelepathyMonitor::incomingCall, this, &SailfishPlatform::incomingCall);
+ connect(m_telepathyMonitor, &TelepathyMonitor::callStarted, this, &SailfishPlatform::callStarted);
+ connect(m_telepathyMonitor, &TelepathyMonitor::callEnded, this, &SailfishPlatform::callEnded);
+
+ // Organizer
+ m_organizerAdapter = new OrganizerAdapter(this);
+ m_organizerAdapter->refresh();
+ connect(m_organizerAdapter, &OrganizerAdapter::itemsChanged, this, &SailfishPlatform::organizerItemsChanged);
+ m_syncMonitorClient = new SyncMonitorClient(this);
+ connect(m_syncMonitorClient, &SyncMonitorClient::stateChanged, [this]() { if (m_syncMonitorClient->state() == "idle") m_organizerAdapter->refresh();});
+ m_syncTimer.start(1000 * 60 * 60);
+ connect(&m_syncTimer, &QTimer::timeout, [this]() { m_syncMonitorClient->sync({"calendar"});});
+ m_syncMonitorClient->sync({"calendar"});
+}
+
+QDBusInterface *SailfishPlatform::interface() const
+{
+ return m_iface;
+}
+
+uint SailfishPlatform::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantHash &hints, int expire_timeout)
+{
+ qDebug() << "Notification received" << app_name << replaces_id << app_icon << summary << body << actions << hints << expire_timeout;
+ QString owner = hints.value("x-nemo-owner", "").toString();
+
+ // Look up the notification category and its parameters
+ QString category = hints.value("category", "").toString();
+ QHash<QString, QString> categoryParams = this->getCategoryParams(category);
+
+ // Ignore transient and hidden notifications (notif hints override category hints)
+ // Hack this to accept transient -preview and -summary notifications, as we don't know how to decode the actual notifs yet
+ if (hints.value("transient", categoryParams.value("transient", "false")).toString() == "true") {
+ qDebug() << "Ignoring transient notification from " << owner;
+ return 0;
+ }
+ else if (hints.value("x-nemo-hidden", "false").toString() == "true" ) {
+ qDebug() << "Ignoring hidden notification from " << owner;
+ return 0;
+ }
+
+ Notification n(app_name);
+ if (owner == "twitter-notifications-client") {
+ n.setType(Notification::NotificationTypeTwitter);
+ n.setSourceName("Twitter");
+ } else if (category == "x-nemo.email") {
+ if (app_name.toLower().contains("gmail")) {
+ n.setType(Notification::NotificationTypeGMail);
+ n.setSourceName("GMail");
+ }
+ else {
+ n.setType(Notification::NotificationTypeEmail);
+ n.setSubject(app_name);
+ }
+ } else if (owner == "facebook-notifications-client") {
+ n.setType(Notification::NotificationTypeFacebook);
+ n.setSourceName("Facebook");
+ } else if (hints.value("x-nemo-origin-package").toString() == "org.telegram.messenger"
+ || category.startsWith("harbour.sailorgram")) {
+ n.setType(Notification::NotificationTypeTelegram);
+ n.setSourceName("Telegram");
+ } else if (hints.value("x-nemo-origin-package").toString() == "com.google.android.apps.babel"
+ || owner == "harbour-hangish") {
+ n.setType(Notification::NotificationTypeHangout);
+ n.setSourceName("Hangouts");
+ } else if (hints.value("x-nemo-origin-package").toString() == "com.whatsapp"
+ || owner.toLower().contains("whatsup")) {
+ n.setType(Notification::NotificationTypeWhatsApp);
+ n.setSourceName("Whatsapp");
+ } else if (app_name.contains("indicator-datetime")) {
+ n.setType(Notification::NotificationTypeReminder);
+ n.setSourceName("reminders");
+ } else {
+ n.setType(Notification::NotificationTypeGeneric);
+ }
+ n.setSender(summary);
+ n.setBody(body);
+ foreach (const QString &action, actions) {
+ if (action == "default") {
+ n.setActToken(hints.value("x-nemo-remote-action-default").toString());
+ break;
+ }
+ }
+ qDebug() << "have act token" << n.actToken();
+
+ emit notificationReceived(n);
+ // We never return something. We're just snooping in...
+ setDelayedReply(true);
+ return 0;
+}
+
+ QHash<QString, QString> SailfishPlatform::getCategoryParams(QString category)
+ {
+ if (!category.isEmpty()) {
+ QString categoryConfigFile = QString("/usr/share/lipstick/notificationcategories/%1.conf").arg(category);
+ QFile testFile(categoryConfigFile);
+ if (testFile.exists()) {
+ QHash<QString, QString> categories;
+ QSettings settings(categoryConfigFile, QSettings::IniFormat);
+ const QStringList settingKeys = settings.allKeys();
+ foreach (const QString &settingKey, settingKeys) {
+ categories[settingKey] = settings.value(settingKey).toString();
+ }
+ return categories;
+ }
+ }
+ return QHash<QString, QString>();
+ }
+
+void SailfishPlatform::sendMusicControlCommand(MusicControlButton controlButton)
+{
+ QString method;
+ switch (controlButton) {
+ case MusicControlPlayPause:
+ method = "PlayPause";
+ break;
+ case MusicControlSkipBack:
+ method = "Previous";
+ break;
+ case MusicControlSkipNext:
+ method = "Next";
+ break;
+ default:
+ ;
+ }
+
+ if (!method.isEmpty()) {
+ QDBusMessage call = QDBusMessage::createMethodCall(m_mprisService, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method);
+ QDBusError err = QDBusConnection::sessionBus().call(call);
+
+ if (err.isValid()) {
+ qWarning() << "Error calling mpris method on" << m_mprisService << ":" << err.message();
+ }
+ return;
+ }
+
+ if (controlButton == MusicControlVolumeUp || controlButton == MusicControlVolumeDown) {
+ QDBusMessage call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Get");
+ call << "com.Meego.MainVolume2" << "CurrentStep";
+
+ QDBusReply<QDBusVariant> volumeReply = _pulseBus->call(call);
+ if (volumeReply.isValid()) {
+ // Decide the new value for volume, taking limits into account
+ uint volume = volumeReply.value().variant().toUInt();
+ uint newVolume;
+ qDebug() << "Current volume: " << volumeReply.value().variant().toUInt();
+ if (controlButton == MusicControlVolumeUp && volume < _maxVolume-1 ) {
+ newVolume = volume + 1;
+ }
+ else if (controlButton == MusicControlVolumeDown && volume > 0) {
+ newVolume = volume - 1;
+ }
+ else {
+ qDebug() << "Volume already at limit";
+ newVolume = volume;
+ }
+
+ // If we have a new volume level, change it
+ if (newVolume != volume) {
+ qDebug() << "Setting volume: " << newVolume;
+
+ call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Set");
+ call << "com.Meego.MainVolume2" << "CurrentStep" << QVariant::fromValue(QDBusVariant(newVolume));
+
+ QDBusError err = _pulseBus->call(call);
+ if (err.isValid()) {
+ qWarning() << err.message();
+ }
+ }
+ }
+ }
+}
+
+MusicMetaData SailfishPlatform::musicMetaData() const
+{
+ return m_musicMetaData;
+}
+
+void SailfishPlatform::hangupCall(uint cookie)
+{
+ m_telepathyMonitor->hangupCall(cookie);
+}
+
+QList<CalendarEvent> SailfishPlatform::organizerItems() const
+{
+ return m_organizerAdapter->items();
+}
+
+void SailfishPlatform::actionTriggered(const QString &actToken)
+{
+ QVariantMap action;
+ // Extract the element of the DBus call
+ QStringList elements(actToken.split(' ', QString::SkipEmptyParts));
+ if (elements.size() <= 3) {
+ qWarning() << "Unable to decode invalid remote action:" << actToken;
+ } else {
+ int index = 0;
+ action.insert(QStringLiteral("service"), elements.at(index++));
+ action.insert(QStringLiteral("path"), elements.at(index++));
+ action.insert(QStringLiteral("iface"), elements.at(index++));
+ action.insert(QStringLiteral("method"), elements.at(index++));
+
+ if (index < elements.size()) {
+ QVariantList args;
+ while (index < elements.size()) {
+ const QString &arg(elements.at(index++));
+ const QByteArray buffer(QByteArray::fromBase64(arg.toUtf8()));
+
+ QDataStream stream(buffer);
+ QVariant var;
+ stream >> var;
+ args.append(var);
+ }
+ action.insert(QStringLiteral("arguments"), args);
+ }
+ qDebug() << "Calling: " << action;
+ QDBusMessage call = QDBusMessage::createMethodCall(action.value("service").toString(), action.value("path").toString(), action.value("iface").toString(), action.value("method").toString());
+ if (action.contains("arguments")) call.setArguments(action.value("arguments").toList());
+ QDBusConnection::sessionBus().call(call);
+ }
+}
+
+void SailfishPlatform::fetchMusicMetadata()
+{
+ if (!m_mprisService.isEmpty()) {
+ QDBusMessage call = QDBusMessage::createMethodCall(m_mprisService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
+ call << "org.mpris.MediaPlayer2.Player" << "Metadata";
+ QDBusPendingCall pcall = QDBusConnection::sessionBus().asyncCall(call);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this, &SailfishPlatform::fetchMusicMetadataFinished);
+ }
+}
+
+void SailfishPlatform::fetchMusicMetadataFinished(QDBusPendingCallWatcher *watcher)
+{
+ watcher->deleteLater();
+ QDBusReply<QDBusVariant> reply = watcher->reply();
+ if (reply.isValid()) {
+ QVariantMap curMetadata = qdbus_cast<QVariantMap>(reply.value().variant().value<QDBusArgument>());
+ m_musicMetaData.artist = curMetadata.value("xesam:artist").toString();
+ m_musicMetaData.album = curMetadata.value("xesam:album").toString();
+ m_musicMetaData.title = curMetadata.value("xesam:title").toString();
+ emit musicMetadataChanged(m_musicMetaData);
+ } else {
+ qWarning() << reply.error().message();
+ }
+}
+
+void SailfishPlatform::mediaPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
+{
+ Q_UNUSED(interface)
+ Q_UNUSED(changedProps)
+ Q_UNUSED(invalidatedProps)
+ fetchMusicMetadata();
+}
diff --git a/rockworkd/platformintegration/sailfish/sailfishplatform.h b/rockworkd/platformintegration/sailfish/sailfishplatform.h
new file mode 100644
index 0000000..e18b986
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/sailfishplatform.h
@@ -0,0 +1,58 @@
+#ifndef SAILFISHPLATFORM_H
+#define SAILFISHPLATFORM_H
+
+#include "libpebble/platforminterface.h"
+#include "libpebble/enums.h"
+
+#include <QDBusInterface>
+#include <TelepathyQt/AbstractClientObserver>
+
+class QDBusPendingCallWatcher;
+class TelepathyMonitor;
+class OrganizerAdapter;
+class SyncMonitorClient;
+
+class SailfishPlatform : public PlatformInterface, public QDBusContext
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications")
+ Q_PROPERTY(QDBusInterface* interface READ interface)
+
+
+public:
+ SailfishPlatform(QObject *parent = 0);
+ QDBusInterface* interface() const;
+
+ void sendMusicControlCommand(MusicControlButton controlButton) override;
+ MusicMetaData musicMetaData() const override;
+ void hangupCall(uint cookie) override;
+ QHash<QString, QString> getCategoryParams(QString category);
+
+ QList<CalendarEvent> organizerItems() const override;
+
+ void actionTriggered(const QString &actToken) override;
+
+public slots:
+ uint Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantHash &hints, int expire_timeout);
+
+
+private slots:
+ void fetchMusicMetadata();
+ void fetchMusicMetadataFinished(QDBusPendingCallWatcher *watcher);
+ void mediaPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
+
+private:
+ QDBusInterface *m_iface;
+
+ QString m_mprisService;
+ MusicMetaData m_musicMetaData;
+ QDBusConnection *_pulseBus;
+ uint _maxVolume;
+
+ TelepathyMonitor *m_telepathyMonitor;
+ OrganizerAdapter *m_organizerAdapter;
+ SyncMonitorClient *m_syncMonitorClient;
+ QTimer m_syncTimer;
+};
+
+#endif // SAILFISHPLATFORM_H
diff --git a/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp b/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp
new file mode 100644
index 0000000..b43509e
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This file is part of sync-monitor.
+ *
+ * sync-monitor is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * contact-service-app 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/>.
+ */
+
+#include <QDebug>
+#include <QTimer>
+
+#include "syncmonitorclient.h"
+
+#define SYNCMONITOR_DBUS_SERVICE_NAME "com.canonical.SyncMonitor"
+#define SYNCMONITOR_DBUS_OBJECT_PATH "/com/canonical/SyncMonitor"
+#define SYNCMONITOR_DBUS_INTERFACE "com.canonical.SyncMonitor"
+
+
+SyncMonitorClient::SyncMonitorClient(QObject *parent)
+ : QObject(parent),
+ m_iface(0)
+{
+ m_iface = new QDBusInterface(SYNCMONITOR_DBUS_SERVICE_NAME,
+ SYNCMONITOR_DBUS_OBJECT_PATH,
+ SYNCMONITOR_DBUS_INTERFACE);
+ if (m_iface->lastError().isValid()) {
+ qWarning() << "Fail to connect with sync monitor:" << m_iface->lastError();
+ return;
+ }
+
+ connect(m_iface, SIGNAL(stateChanged()), SIGNAL(stateChanged()));
+ connect(m_iface, SIGNAL(enabledServicesChanged()), SIGNAL(enabledServicesChanged()));
+ m_iface->call("attach");
+}
+
+SyncMonitorClient::~SyncMonitorClient()
+{
+ if (m_iface) {
+ m_iface->call("detach");
+ delete m_iface;
+ m_iface = 0;
+ }
+}
+
+QString SyncMonitorClient::state() const
+{
+ if (m_iface) {
+ return m_iface->property("state").toString();
+ } else {
+ return "";
+ }
+}
+
+QStringList SyncMonitorClient::enabledServices() const
+{
+ if (m_iface) {
+ return m_iface->property("enabledServices").toStringList();
+ } else {
+ return QStringList();
+ }
+}
+
+/*!
+ Start a new sync for specified services
+*/
+void SyncMonitorClient::sync(const QStringList &services)
+{
+ if (m_iface) {
+ qDebug() << "starting sync!";
+ m_iface->call("sync", services);
+ }
+}
+
+/*!
+ Cancel current sync for specified services
+*/
+void SyncMonitorClient::cancel(const QStringList &services)
+{
+ if (m_iface) {
+ m_iface->call("cancel", services);
+ }
+}
+
+/*!
+ Chek if a specific service is enabled or not
+*/
+bool SyncMonitorClient::serviceIsEnabled(const QString &service)
+{
+ return enabledServices().contains(service);
+}
diff --git a/rockworkd/platformintegration/sailfish/syncmonitorclient.h b/rockworkd/platformintegration/sailfish/syncmonitorclient.h
new file mode 100644
index 0000000..1587ba5
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/syncmonitorclient.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This file is part of sync-monitor.
+ *
+ * sync-monitor is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * contact-service-app 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/>.
+ */
+
+#ifndef SYNCMONITOR_QML_H
+#define SYNCMONITOR_QML_H
+
+#include <QObject>
+#include <QDBusInterface>
+
+class SyncMonitorClient : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString state READ state NOTIFY stateChanged)
+ Q_PROPERTY(QStringList enabledServices READ enabledServices NOTIFY enabledServicesChanged)
+
+public:
+ SyncMonitorClient(QObject *parent = 0);
+ ~SyncMonitorClient();
+
+ QString state() const;
+ QStringList enabledServices() const;
+
+Q_SIGNALS:
+ void stateChanged();
+ void enabledServicesChanged();
+
+public Q_SLOTS:
+ void sync(const QStringList &services);
+ void cancel(const QStringList &services);
+ bool serviceIsEnabled(const QString &service);
+
+private:
+ QDBusInterface *m_iface;
+};
+
+#endif
diff --git a/rockworkd/platformintegration/sailfish/voicecallhandler.cpp b/rockworkd/platformintegration/sailfish/voicecallhandler.cpp
new file mode 100644
index 0000000..2ae5087
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallhandler.cpp
@@ -0,0 +1,372 @@
+#include "voicecallhandler.h"
+
+#include <QDebug>
+#include <QDBusInterface>
+#include <QDBusPendingReply>
+#include <QDBusReply>
+#include <QVariantMap>
+
+/*!
+ \class VoiceCallHandler
+ \brief This is the D-Bus proxy for communicating with the voice call manager
+ from a declarative context, this interface specifically interfaces with
+ the managers' voice call handler instances.
+*/
+class VoiceCallHandlerPrivate
+{
+ Q_DECLARE_PUBLIC(VoiceCallHandler)
+
+public:
+ VoiceCallHandlerPrivate(VoiceCallHandler *q, const QString &pHandlerId)
+ : q_ptr(q), handlerId(pHandlerId), interface(NULL)
+ , duration(0), status(0), emergency(false), incoming(false)
+ , multiparty(false) , forwarded(false), remoteHeld(false)
+ { /* ... */ }
+
+ VoiceCallHandler *q_ptr;
+
+ QString handlerId;
+
+ QDBusInterface *interface;
+
+ int duration;
+ int status;
+ QString statusText;
+ QString lineId;
+ QString providerId;
+ QDateTime startedAt;
+ bool emergency;
+ bool incoming;
+ bool multiparty;
+ bool forwarded;
+ bool remoteHeld;
+};
+
+/*!
+ Constructs a new proxy interface for the provided voice call handlerId.
+*/
+VoiceCallHandler::VoiceCallHandler(const QString &handlerId, QObject *parent)
+ : QObject(parent), l(metaObject()->className()), d_ptr(new VoiceCallHandlerPrivate(this, handlerId))
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << QString("Creating D-Bus interface to: ") + handlerId;
+ d->interface = new QDBusInterface("org.nemomobile.voicecall",
+ "/calls/" + handlerId,
+ "org.nemomobile.voicecall.VoiceCall",
+ QDBusConnection::sessionBus(),
+ this);
+ this->initialize(true);
+}
+
+VoiceCallHandler::~VoiceCallHandler()
+{
+ Q_D(VoiceCallHandler);
+ delete d;
+}
+
+void VoiceCallHandler::initialize(bool notifyError)
+{
+ Q_D(VoiceCallHandler);
+
+ if (d->interface->isValid()) {
+ if (getProperties()) {
+ emit durationChanged();
+ emit statusChanged();
+ emit lineIdChanged();
+ emit startedAtChanged();
+ emit multipartyChanged();
+ emit emergencyChanged();
+ emit forwardedChanged();
+
+ connect(d->interface, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ connect(d->interface, SIGNAL(statusChanged(int, QString)), SLOT(onStatusChanged(int, QString)));
+ connect(d->interface, SIGNAL(lineIdChanged(QString)), SLOT(onLineIdChanged(QString)));
+ connect(d->interface, SIGNAL(durationChanged(int)), SLOT(onDurationChanged(int)));
+ connect(d->interface, SIGNAL(startedAtChanged(QDateTime)), SLOT(onStartedAtChanged(QDateTime)));
+ connect(d->interface, SIGNAL(emergencyChanged(bool)), SLOT(onEmergencyChanged(bool)));
+ connect(d->interface, SIGNAL(multipartyChanged(bool)), SLOT(onMultipartyChanged(bool)));
+ connect(d->interface, SIGNAL(forwardedChanged(bool)), SLOT(onForwardedChanged(bool)));
+ connect(d->interface, SIGNAL(remoteHeldChanged(bool)), SLOT(onRemoteHeldChanged(bool)));
+ }
+ else {
+ if (notifyError) emit this->error("Failed to get VoiceCall properties from VCM D-Bus service.");
+ }
+ }
+ else {
+ qCCritical(l) << d->interface->lastError().name() << d->interface->lastError().message();
+ }
+}
+
+bool VoiceCallHandler::getProperties()
+{
+ Q_D(VoiceCallHandler);
+
+ QDBusInterface props(d->interface->service(), d->interface->path(),
+ "org.freedesktop.DBus.Properties", d->interface->connection());
+
+ QDBusReply<QVariantMap> reply = props.call("GetAll", d->interface->interface());
+ if (reply.isValid()) {
+ QVariantMap props = reply.value();
+ qCDebug(l) << props;
+ d->providerId = props["providerId"].toString();
+ d->duration = props["duration"].toInt();
+ d->status = props["status"].toInt();
+ d->statusText = props["statusText"].toString();
+ d->lineId = props["lineId"].toString();
+ d->startedAt = QDateTime::fromMSecsSinceEpoch(props["startedAt"].toULongLong());
+ d->multiparty = props["isMultiparty"].toBool();
+ d->emergency = props["isEmergency"].toBool();
+ d->forwarded = props["isForwarded"].toBool();
+ d->incoming = props["isIncoming"].toBool();
+ d->remoteHeld = props["isIncoming"].toBool();
+ return true;
+ }
+ else {
+ qCCritical(l) << "Failed to get VoiceCall properties from VCM D-Bus service.";
+ return false;
+ }
+}
+
+void VoiceCallHandler::onDurationChanged(int duration)
+{
+ Q_D(VoiceCallHandler);
+ //qCDebug(l) <<"onDurationChanged"<<duration;
+ d->duration = duration;
+ emit durationChanged();
+}
+
+void VoiceCallHandler::onStatusChanged(int status, QString statusText)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) <<"onStatusChanged" << status << statusText;
+ d->status = status;
+ d->statusText = statusText;
+ if (status) getProperties(); // make sure all properties are present
+ emit statusChanged();
+}
+
+void VoiceCallHandler::onLineIdChanged(QString lineId)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onLineIdChanged" << lineId;
+ d->lineId = lineId;
+ emit lineIdChanged();
+}
+
+void VoiceCallHandler::onStartedAtChanged(const QDateTime &startedAt)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onStartedAtChanged" << startedAt;
+ d->startedAt = d->interface->property("startedAt").toDateTime();
+ emit startedAtChanged();
+}
+
+void VoiceCallHandler::onEmergencyChanged(bool isEmergency)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onEmergencyChanged" << isEmergency;
+ d->emergency = isEmergency;
+ emit emergencyChanged();
+}
+
+void VoiceCallHandler::onMultipartyChanged(bool isMultiparty)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onMultipartyChanged" << isMultiparty;
+ d->multiparty = isMultiparty;
+ emit multipartyChanged();
+}
+
+void VoiceCallHandler::onForwardedChanged(bool isForwarded)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onForwardedChanged" << isForwarded;
+ d->forwarded = isForwarded;
+ emit forwardedChanged();
+}
+
+void VoiceCallHandler::onRemoteHeldChanged(bool isRemoteHeld)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onRemoteHeldChanged" << isRemoteHeld;
+ d->forwarded = isRemoteHeld;
+ emit remoteHeldChanged();
+}
+
+/*!
+ Returns this voice calls' handler id.
+ */
+QString VoiceCallHandler::handlerId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->handlerId;
+}
+
+/*!
+ Returns this voice calls' provider id.
+ */
+QString VoiceCallHandler::providerId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->providerId;
+}
+
+/*!
+ Returns this voice calls' call status.
+ */
+int VoiceCallHandler::status() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->status;
+}
+
+/*!
+ Returns this voice calls' call status as a symbolic string.
+ */
+QString VoiceCallHandler::statusText() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->statusText;
+}
+
+/*!
+ Returns this voice calls' remote end-point line id.
+ */
+QString VoiceCallHandler::lineId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->lineId;
+}
+
+/*!
+ Returns this voice calls' started at property.
+ */
+QDateTime VoiceCallHandler::startedAt() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->startedAt;
+}
+
+/*!
+ Returns this voice calls' duration property.
+ */
+int VoiceCallHandler::duration() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->duration;
+}
+
+/*!
+ Returns this voice calls' incoming call flag property.
+ */
+bool VoiceCallHandler::isIncoming() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->incoming;
+}
+
+/*!
+ Returns this voice calls' multiparty flag property.
+ */
+bool VoiceCallHandler::isMultiparty() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->multiparty;
+}
+
+/*!
+ Returns this voice calls' forwarded flag property.
+ */
+bool VoiceCallHandler::isForwarded() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->forwarded;
+}
+
+/*!
+ Returns this voice calls' emergency flag property.
+ */
+bool VoiceCallHandler::isEmergency() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->emergency;
+}
+
+/*!
+ Returns this voice calls' remoteHeld flag property.
+ */
+bool VoiceCallHandler::isRemoteHeld() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->remoteHeld;
+}
+
+/*!
+ Initiates answering this call, if the call is an incoming call.
+ */
+void VoiceCallHandler::answer()
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("answer");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates droping the call, unless the call is disconnected.
+ */
+void VoiceCallHandler::hangup()
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("hangup");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates holding the call, unless the call is disconnected.
+ */
+void VoiceCallHandler::hold(bool on)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("hold", on);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates deflecting the call to the provided target phone number.
+ */
+void VoiceCallHandler::deflect(const QString &target)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("deflect", target);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallHandler::sendDtmf(const QString &tones)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("sendDtmf", tones);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallHandler::onPendingCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<bool> reply = *watcher;
+
+ if (reply.isError()) {
+ qCCritical(l) << QString::fromLatin1("Received error reply for member: %1 (%2)").arg(reply.reply().member()).arg(reply.error().message());
+ emit this->error(reply.error().message());
+ watcher->deleteLater();
+ } else {
+ qCDebug(l) << QString::fromLatin1("Received successful reply for member: %1").arg(reply.reply().member());
+ }
+}
diff --git a/rockworkd/platformintegration/sailfish/voicecallhandler.h b/rockworkd/platformintegration/sailfish/voicecallhandler.h
new file mode 100644
index 0000000..e718abb
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallhandler.h
@@ -0,0 +1,96 @@
+#ifndef VOICECALLHANDLER_H
+#define VOICECALLHANDLER_H
+
+#include <QObject>
+#include <QDateTime>
+#include <QDBusPendingCallWatcher>
+#include <QLoggingCategory>
+
+class VoiceCallHandler : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ Q_ENUMS(VoiceCallStatus)
+
+ Q_PROPERTY(QString handlerId READ handlerId CONSTANT)
+ Q_PROPERTY(QString providerId READ providerId CONSTANT)
+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
+ Q_PROPERTY(QString statusText READ statusText NOTIFY statusChanged)
+ Q_PROPERTY(QString lineId READ lineId NOTIFY lineIdChanged)
+ Q_PROPERTY(QDateTime startedAt READ startedAt NOTIFY startedAtChanged)
+ Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
+ Q_PROPERTY(bool isIncoming READ isIncoming CONSTANT)
+ Q_PROPERTY(bool isEmergency READ isEmergency NOTIFY emergencyChanged)
+ Q_PROPERTY(bool isMultiparty READ isMultiparty NOTIFY multipartyChanged)
+ Q_PROPERTY(bool isForwarded READ isForwarded NOTIFY forwardedChanged)
+ Q_PROPERTY(bool isRemoteHeld READ isRemoteHeld NOTIFY remoteHeldChanged)
+
+public:
+ enum VoiceCallStatus {
+ STATUS_NULL,
+ STATUS_ACTIVE,
+ STATUS_HELD,
+ STATUS_DIALING,
+ STATUS_ALERTING,
+ STATUS_INCOMING,
+ STATUS_WAITING,
+ STATUS_DISCONNECTED
+ };
+
+ explicit VoiceCallHandler(const QString &handlerId, QObject *parent = 0);
+ ~VoiceCallHandler();
+
+ QString handlerId() const;
+ QString providerId() const;
+ int status() const;
+ QString statusText() const;
+ QString lineId() const;
+ QDateTime startedAt() const;
+ int duration() const;
+ bool isIncoming() const;
+ bool isMultiparty() const;
+ bool isEmergency() const;
+ bool isForwarded() const;
+ bool isRemoteHeld() const;
+
+Q_SIGNALS:
+ void error(const QString &error);
+ void statusChanged();
+ void lineIdChanged();
+ void durationChanged();
+ void startedAtChanged();
+ void emergencyChanged();
+ void multipartyChanged();
+ void forwardedChanged();
+ void remoteHeldChanged();
+
+public Q_SLOTS:
+ void answer();
+ void hangup();
+ void hold(bool on);
+ void deflect(const QString &target);
+ void sendDtmf(const QString &tones);
+
+protected Q_SLOTS:
+ void initialize(bool notifyError = false);
+ bool getProperties();
+
+ void onPendingCallFinished(QDBusPendingCallWatcher *watcher);
+ void onDurationChanged(int duration);
+ void onStatusChanged(int status, QString statusText);
+ void onLineIdChanged(QString lineId);
+ void onStartedAtChanged(const QDateTime &startedAt);
+ void onEmergencyChanged(bool isEmergency);
+ void onMultipartyChanged(bool isMultiparty);
+ void onForwardedChanged(bool isForwarded);
+ void onRemoteHeldChanged(bool isRemoteHeld);
+
+private:
+ class VoiceCallHandlerPrivate *d_ptr;
+
+ Q_DISABLE_COPY(VoiceCallHandler)
+ Q_DECLARE_PRIVATE(VoiceCallHandler)
+};
+
+#endif // VOICECALLHANDLER_H
diff --git a/rockworkd/platformintegration/sailfish/voicecallmanager.cpp b/rockworkd/platformintegration/sailfish/voicecallmanager.cpp
new file mode 100644
index 0000000..afb3629
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallmanager.cpp
@@ -0,0 +1,315 @@
+#include "voicecallmanager.h"
+
+#include <QDebug>
+#include <QTimer>
+#include <QDBusInterface>
+#include <QDBusPendingReply>
+
+class VoiceCallManagerPrivate
+{
+ Q_DECLARE_PUBLIC(VoiceCallManager)
+
+public:
+ VoiceCallManagerPrivate(VoiceCallManager *q)
+ : q_ptr(q),
+ interface(NULL),
+ activeVoiceCall(NULL),
+ connected(false)
+ { /*...*/ }
+
+ VoiceCallManager *q_ptr;
+
+ QDBusInterface *interface;
+
+ QList<VoiceCallHandler*> voicecalls;
+ QHash<QString,VoiceCallProviderData> providers;
+
+ VoiceCallHandler* activeVoiceCall;
+
+ bool connected;
+};
+
+VoiceCallManager::VoiceCallManager(Settings *settings, QObject *parent)
+ : QObject(parent), l(metaObject()->className()), d_ptr(new VoiceCallManagerPrivate(this)), settings(settings)
+{
+ this->initialize();
+}
+
+VoiceCallManager::~VoiceCallManager()
+{
+ Q_D(VoiceCallManager);
+ delete d;
+}
+
+void VoiceCallManager::initialize(bool notifyError)
+{
+ Q_D(VoiceCallManager);
+ bool success = false;
+
+ delete d->interface;
+ d->interface = new QDBusInterface("org.nemomobile.voicecall",
+ "/",
+ "org.nemomobile.voicecall.VoiceCallManager",
+ QDBusConnection::sessionBus(),
+ this);
+
+ if(d->interface->isValid())
+ {
+ success = true;
+ success &= (bool)QObject::connect(d->interface, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(voiceCallsChanged()), SLOT(onVoiceCallsChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(providersChanged()), SLOT(onProvidersChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(audioModeChanged()), SIGNAL(audioModeChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(audioRoutedChanged()), SIGNAL(audioRoutedChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(microphoneMutedChanged()), SIGNAL(microphoneMutedChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(speakerMutedChanged()), SIGNAL(speakerMutedChanged()));
+
+ onVoiceCallsChanged();
+ onActiveVoiceCallChanged();
+ }
+
+ if(!(d->connected = success))
+ {
+ QTimer::singleShot(2000, this, SLOT(initialize()));
+ if(notifyError) emit this->error("Failed to connect to VCM D-Bus service.");
+ }
+}
+
+QDBusInterface* VoiceCallManager::interface() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface;
+}
+
+VoiceCallHandlerList VoiceCallManager::voiceCalls() const
+{
+ Q_D(const VoiceCallManager);
+ return d->voicecalls;
+}
+
+VoiceCallProviderHash VoiceCallManager::providers() const
+{
+ Q_D(const VoiceCallManager);
+ return d->providers;
+}
+
+QString VoiceCallManager::defaultProviderId() const
+{
+ Q_D(const VoiceCallManager);
+ if(d->providers.count() == 0) {
+ qCDebug(l) << Q_FUNC_INFO << "No provider added";
+ return QString::null;
+ }
+
+ QStringList keys = d->providers.keys();
+ qSort(keys);
+
+ VoiceCallProviderData provider = d->providers.value(keys.value(0));
+ return provider.id;
+}
+
+VoiceCallHandler* VoiceCallManager::activeVoiceCall() const
+{
+ Q_D(const VoiceCallManager);
+ return d->activeVoiceCall;
+}
+
+QString VoiceCallManager::audioMode() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("audioMode").toString();
+}
+
+bool VoiceCallManager::isAudioRouted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isAudioRouted").toBool();
+}
+
+bool VoiceCallManager::isMicrophoneMuted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isMicrophoneMuted").toBool();
+}
+
+bool VoiceCallManager::isSpeakerMuted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isSpeakerMuted").toBool();
+}
+
+void VoiceCallManager::dial(const QString &provider, const QString &msisdn)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingCall call = d->interface->asyncCall("dial", provider, msisdn);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallManager::hangupAll()
+{
+ foreach (VoiceCallHandler* handler, voiceCalls()) {
+ handler->hangup();
+ }
+}
+
+void VoiceCallManager::silenceRingtone()
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingCall call = d->interface->asyncCall("silenceRingtone");
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingSilenceFinished(QDBusPendingCallWatcher*)));
+}
+
+/*
+ - Use of method calls instead of property setters to allow status checking.
+ */
+bool VoiceCallManager::setAudioMode(const QString &mode)
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setAudioMode", mode);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setAudioRouted(bool on)
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setAudioRouted", on);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setMuteMicrophone(bool on)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setMuteMicrophone", on);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setMuteSpeaker(bool on)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setMuteSpeaker", on);
+ return reply.isError() ? false : reply.value();
+}
+
+void VoiceCallManager::onVoiceCallsChanged()
+{
+ Q_D(VoiceCallManager);
+ QStringList nIds = d->interface->property("voiceCalls").toStringList();
+ QStringList oIds;
+
+ QStringList added;
+ QStringList removed;
+
+ // Map current call handlers to handler ids for easy indexing.
+ foreach(VoiceCallHandler *handler, d->voicecalls)
+ {
+ oIds.append(handler->handlerId());
+ }
+
+ // Index new handlers to be added.
+ foreach(QString nId, nIds)
+ {
+ if(!oIds.contains(nId)) added.append(nId);
+ }
+
+ // Index old handlers to be removed.
+ foreach(QString oId, oIds)
+ {
+ if(!nIds.contains(oId)) removed.append(oId);
+ }
+
+ // Remove handlers that need to be removed.
+ foreach(QString removeId, removed)
+ {
+ for (int i = 0; i < d->voicecalls.count(); ++i) {
+ VoiceCallHandler *handler = d->voicecalls.at(i);
+ if(handler->handlerId() == removeId)
+ {
+ handler->disconnect(this);
+ d->voicecalls.removeAt(i);
+ handler->deleteLater();
+ break;
+ }
+ }
+ }
+
+ // Add handlers that need to be added.
+ foreach(QString addId, added)
+ {
+ VoiceCallHandler *handler = new VoiceCallHandler(addId, this);
+ d->voicecalls.append(handler);
+ }
+
+ emit this->voiceCallsChanged();
+}
+
+void VoiceCallManager::onProvidersChanged()
+{
+ Q_D(VoiceCallManager);
+ d->providers.clear();
+ foreach(QString provider, d->interface->property("providers").toStringList())
+ {
+ QStringList parts = provider.split(':');
+ d->providers.insert(parts.first(), VoiceCallProviderData(parts.first(),
+ parts.last(),
+ parts.first()));
+ }
+
+ emit this->providersChanged();
+}
+
+void VoiceCallManager::onActiveVoiceCallChanged()
+{
+ Q_D(VoiceCallManager);
+ QString voiceCallId = d->interface->property("activeVoiceCall").toString();
+
+ if(d->voicecalls.count() == 0 || voiceCallId.isNull() || voiceCallId.isEmpty())
+ {
+ d->activeVoiceCall = NULL;
+ }
+ else
+ {
+ bool found = false;
+ d->activeVoiceCall = NULL;
+ foreach(VoiceCallHandler* handler, d->voicecalls)
+ {
+ if(handler->handlerId() == voiceCallId)
+ {
+ d->activeVoiceCall = handler;
+ found = true;
+ }
+ if(!found) d->activeVoiceCall = NULL;
+ }
+ }
+
+ emit this->activeVoiceCallChanged();
+}
+
+void VoiceCallManager::onPendingCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<bool> reply = *watcher;
+
+ if (reply.isError()) {
+ emit this->error(reply.error().message());
+ } else {
+ qCDebug(l) << QString("Received successful reply for member: ") + reply.reply().member();
+ }
+
+ watcher->deleteLater();
+}
+
+void VoiceCallManager::onPendingSilenceFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<> reply = *watcher;
+
+ if (reply.isError()) {
+ emit this->error(reply.error().message());
+ } else {
+ qCDebug(l) << QString("Received successful reply for member: ") + reply.reply().member();
+ }
+
+ watcher->deleteLater();
+}
diff --git a/rockworkd/platformintegration/sailfish/voicecallmanager.h b/rockworkd/platformintegration/sailfish/voicecallmanager.h
new file mode 100644
index 0000000..ec51230
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallmanager.h
@@ -0,0 +1,111 @@
+#ifndef VOICECALLMANAGER_H
+#define VOICECALLMANAGER_H
+
+#include "voicecallhandler.h"
+#include "settings.h"
+
+#include <QObject>
+#include <QDBusInterface>
+#include <QDBusPendingCallWatcher>
+#include <QLoggingCategory>
+
+class VoiceCallProviderData
+{
+public:
+ VoiceCallProviderData() {/*..*/}
+ VoiceCallProviderData(const QString &pId, const QString &pType, const QString &pLabel)
+ : id(pId), type(pType), label(pLabel) {/*...*/}
+
+ QString id;
+ QString type;
+ QString label;
+};
+
+typedef QHash<QString,VoiceCallProviderData> VoiceCallProviderHash;
+
+typedef QList<VoiceCallHandler*> VoiceCallHandlerList;
+
+class VoiceCallManager : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ Q_PROPERTY(QDBusInterface* interface READ interface)
+
+ Q_PROPERTY(VoiceCallHandlerList voiceCalls READ voiceCalls NOTIFY voiceCallsChanged)
+ Q_PROPERTY(VoiceCallProviderHash providers READ providers NOTIFY providersChanged)
+
+ Q_PROPERTY(QString defaultProviderId READ defaultProviderId NOTIFY defaultProviderChanged)
+
+ Q_PROPERTY(VoiceCallHandler* activeVoiceCall READ activeVoiceCall NOTIFY activeVoiceCallChanged)
+
+ Q_PROPERTY(QString audioMode READ audioMode WRITE setAudioMode NOTIFY audioModeChanged)
+ Q_PROPERTY(bool isAudioRouted READ isAudioRouted WRITE setAudioRouted NOTIFY audioRoutedChanged)
+ Q_PROPERTY(bool isMicrophoneMuted READ isMicrophoneMuted WRITE setMuteMicrophone NOTIFY microphoneMutedChanged)
+ Q_PROPERTY(bool isSpeakerMuted READ isSpeakerMuted WRITE setMuteSpeaker NOTIFY speakerMutedChanged)
+
+public:
+ explicit VoiceCallManager(Settings *settings, QObject *parent = 0);
+ ~VoiceCallManager();
+
+ QDBusInterface* interface() const;
+
+ VoiceCallHandlerList voiceCalls() const;
+ VoiceCallProviderHash providers() const;
+
+ QString defaultProviderId() const;
+
+ VoiceCallHandler* activeVoiceCall() const;
+
+ QString audioMode() const;
+ bool isAudioRouted() const;
+
+ bool isMicrophoneMuted() const;
+ bool isSpeakerMuted() const;
+
+Q_SIGNALS:
+ void error(const QString &message);
+
+ void providersChanged();
+ void voiceCallsChanged();
+
+ void defaultProviderChanged();
+
+ void activeVoiceCallChanged();
+
+ void audioModeChanged();
+ void audioRoutedChanged();
+ void microphoneMutedChanged();
+ void speakerMutedChanged();
+
+public Q_SLOTS:
+ void dial(const QString &providerId, const QString &msisdn);
+ void hangupAll();
+
+ void silenceRingtone();
+
+ bool setAudioMode(const QString &mode);
+ bool setAudioRouted(bool on);
+ bool setMuteMicrophone(bool on = true);
+ bool setMuteSpeaker(bool on = true);
+
+protected Q_SLOTS:
+ void initialize(bool notifyError = false);
+
+ void onProvidersChanged();
+ void onVoiceCallsChanged();
+ void onActiveVoiceCallChanged();
+
+ void onPendingCallFinished(QDBusPendingCallWatcher *watcher);
+ void onPendingSilenceFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ class VoiceCallManagerPrivate *d_ptr;
+
+ Settings *settings;
+
+ Q_DISABLE_COPY(VoiceCallManager)
+ Q_DECLARE_PRIVATE(VoiceCallManager)
+};
+
+#endif // VOICECALLMANAGER_H
diff --git a/rockworkd/rockpoold.service b/rockworkd/rockpoold.service
new file mode 100644
index 0000000..8f87f15
--- /dev/null
+++ b/rockworkd/rockpoold.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Rockpool daemon
+Requires=dbus.socket bluetooth.target
+After=pre-user-session.target lipstick.service dbus.socket bluetooth.target
+
+[Service]
+ExecStart=/usr/bin/rockworkd
+Restart=always
+
+[Install]
+WantedBy=user-session.target
diff --git a/rockworkd/rockworkd.pro b/rockworkd/rockworkd.pro
index edb97f9..53e37ff 100644
--- a/rockworkd/rockworkd.pro
+++ b/rockworkd/rockworkd.pro
@@ -1,3 +1,147 @@
+<<<<<<< HEAD
+QT += core bluetooth dbus network contacts qml location organizer
+QT -= gui
+
+include(../version.pri)
+
+TARGET = rockpoold
+
+CONFIG += c++11
+CONFIG += console
+CONFIG += link_pkgconfig
+
+INCLUDEPATH += /usr/include/telepathy-qt5/ $$[QT_HOST_PREFIX]/include/quazip/
+LIBS += -lquazip -ltelepathy-qt5
+
+PKGCONFIG += libmkcal-qt5 libkcalcoren-qt5
+INCLUDEPATH += /usr/include/mkcal-qt5 /usr/include/kcalcoren-qt5
+
+SOURCES += main.cpp \
+ libpebble/watchconnection.cpp \
+ libpebble/pebble.cpp \
+ libpebble/watchdatareader.cpp \
+ libpebble/watchdatawriter.cpp \
+ libpebble/notificationendpoint.cpp \
+ libpebble/musicendpoint.cpp \
+ libpebble/phonecallendpoint.cpp \
+ libpebble/musicmetadata.cpp \
+ libpebble/jskit/jskitmanager.cpp \
+ libpebble/jskit/jskitconsole.cpp \
+ libpebble/jskit/jskitgeolocation.cpp \
+ libpebble/jskit/jskitlocalstorage.cpp \
+ libpebble/jskit/jskitpebble.cpp \
+ libpebble/jskit/jskitxmlhttprequest.cpp \
+ libpebble/jskit/jskittimer.cpp \
+ libpebble/jskit/jskitperformance.cpp \
+ libpebble/appinfo.cpp \
+ libpebble/appmanager.cpp \
+ libpebble/appmsgmanager.cpp \
+ libpebble/uploadmanager.cpp \
+ libpebble/bluez/bluezclient.cpp \
+ libpebble/bluez/bluez_agentmanager1.cpp \
+ libpebble/bluez/bluez_adapter1.cpp \
+ libpebble/bluez/bluez_device1.cpp \
+ libpebble/bluez/freedesktop_objectmanager.cpp \
+ libpebble/bluez/freedesktop_properties.cpp \
+ libpebble/bluez/device.cpp \
+ core.cpp \
+ pebblemanager.cpp \
+ dbusinterface.cpp \
+# Platform integration part
+ platformintegration/sailfish/sailfishplatform.cpp \
+ platformintegration/sailfish/callchannelobserver.cpp \
+# platformintegration/sailfish/voicecallmanager.cpp \
+# platformintegration/sailfish/voicecallhandler.cpp \
+ libpebble/blobdb.cpp \
+ libpebble/timelineitem.cpp \
+ libpebble/notification.cpp \
+ platformintegration/sailfish/organizeradapter.cpp \
+ libpebble/calendarevent.cpp \
+ platformintegration/sailfish/syncmonitorclient.cpp \
+ libpebble/appmetadata.cpp \
+ libpebble/appdownloader.cpp \
+ libpebble/screenshotendpoint.cpp \
+ libpebble/firmwaredownloader.cpp \
+ libpebble/bundle.cpp \
+ libpebble/watchlogendpoint.cpp \
+ libpebble/ziphelper.cpp \
+ libpebble/healthparams.cpp \
+ libpebble/dataloggingendpoint.cpp
+
+HEADERS += \
+ libpebble/watchconnection.h \
+ libpebble/pebble.h \
+ libpebble/watchdatareader.h \
+ libpebble/watchdatawriter.h \
+ libpebble/notificationendpoint.h \
+ libpebble/musicendpoint.h \
+ libpebble/musicmetadata.h \
+ libpebble/phonecallendpoint.h \
+ libpebble/platforminterface.h \
+ libpebble/jskit/jskitmanager.h \
+ libpebble/jskit/jskitconsole.h \
+ libpebble/jskit/jskitgeolocation.h \
+ libpebble/jskit/jskitlocalstorage.h \
+ libpebble/jskit/jskitpebble.h \
+ libpebble/jskit/jskitxmlhttprequest.h \
+ libpebble/jskit/jskittimer.h \
+ libpebble/jskit/jskitperformance.h \
+ libpebble/appinfo.h \
+ libpebble/appmanager.h \
+ libpebble/appmsgmanager.h \
+ libpebble/uploadmanager.h \
+ libpebble/bluez/bluezclient.h \
+ libpebble/bluez/bluez_agentmanager1.h \
+ libpebble/bluez/bluez_adapter1.h \
+ libpebble/bluez/bluez_device1.h \
+ libpebble/bluez/freedesktop_objectmanager.h \
+ libpebble/bluez/freedesktop_properties.h \
+ libpebble/bluez/device.h \
+ core.h \
+ pebblemanager.h \
+ dbusinterface.h \
+# Platform integration part
+ platformintegration/sailfish/sailfishplatform.h \
+ platformintegration/sailfish/callchannelobserver.h \
+# platformintegration/sailfish/voicecallmanager.h \
+# platformintegration/sailfish/voicecallhandler.h \
+ libpebble/blobdb.h \
+ libpebble/timelineitem.h \
+ libpebble/notification.h \
+ platformintegration/sailfish/organizeradapter.h \
+ libpebble/calendarevent.h \
+ platformintegration/sailfish/syncmonitorclient.h \
+ libpebble/appmetadata.h \
+ libpebble/appdownloader.h \
+ libpebble/enums.h \
+ libpebble/screenshotendpoint.h \
+ libpebble/firmwaredownloader.h \
+ libpebble/bundle.h \
+ libpebble/watchlogendpoint.h \
+ libpebble/ziphelper.h \
+ libpebble/healthparams.h \
+ libpebble/dataloggingendpoint.h
+
+testing: {
+ SOURCES += platformintegration/testing/testingplatform.cpp
+ HEADERS += platformintegration/testing/testingplatform.h
+ RESOURCES += platformintegration/testing/testui.qrc
+ DEFINES += ENABLE_TESTING
+ QT += qml quick
+}
+
+INSTALLS += target systemd
+
+systemd.files = $${TARGET}.service
+systemd.path = /usr/lib/systemd/user
+
+# Default rules for deployment.
+target.path = /usr/bin
+
+RESOURCES += \
+ libpebble/jskit/jsfiles.qrc
+
+=======
QT += core bluetooth dbus network contacts qml location organizer websockets
QT -= gui
@@ -146,3 +290,4 @@ QMAKE_POST_LINK = sed -i s/@VERSION@/$$VERSION/g $$OUT_PWD/../manifest.json || e
RESOURCES += \
libpebble/jskit/jsfiles.qrc
+>>>>>>> refs/heads/rockwork