diff options
| author | Andrew Branson <andrew.branson@cern.ch> | 2016-02-11 23:55:16 +0100 |
|---|---|---|
| committer | Andrew Branson <andrew.branson@cern.ch> | 2016-02-11 23:55:16 +0100 |
| commit | 29aaea2d80a9eb1715b6cddfac2d2aacf76358bd (patch) | |
| tree | 012795b6bec16c72f38d33cff46324c9a0225868 /rockworkd/platformintegration | |
launchpad ~mzanetti/rockwork/trunk r87
Diffstat (limited to 'rockworkd/platformintegration')
13 files changed, 1022 insertions, 0 deletions
diff --git a/rockworkd/platformintegration/testing/testingplatform.cpp b/rockworkd/platformintegration/testing/testingplatform.cpp new file mode 100644 index 0000000..aa0c45a --- /dev/null +++ b/rockworkd/platformintegration/testing/testingplatform.cpp @@ -0,0 +1,63 @@ +#include "testingplatform.h" + +#include <QQuickView> +#include <QDebug> +#include <QQmlContext> + +TestingPlatform::TestingPlatform(QObject *parent): + PlatformInterface(parent) +{ + m_view = new QQuickView(); + m_view->rootContext()->setContextProperty("handler", this); + qmlRegisterUncreatableType<Pebble>("PebbleTest", 1, 0, "Pebble", "Dont"); + m_view->setSource(QUrl("qrc:///testui/Main.qml")); + m_view->show(); +} + +void TestingPlatform::sendMusicControlCommand(MusicControlButton command) +{ + qDebug() << "Testing platform received music command from pebble" << command; +} + +MusicMetaData TestingPlatform::musicMetaData() const +{ + return MusicMetaData("TestArtist", "TestAlbum", "TestTitle"); +} + +void TestingPlatform::sendNotification(int type, const QString &from, const QString &subject, const QString &text) +{ + qDebug() << "Injecting mock notification" << type; + Notification n("test_app_" + QString::number(type)); + n.setSourceName("Test button " + QString::number(type)); + n.setSender(from); + n.setSubject(subject); + n.setBody(text); + n.setActToken("tralala"); + emit notificationReceived(n); +} + +void TestingPlatform::fakeIncomingCall(uint cookie, const QString &number, const QString &name) +{ + emit incomingCall(cookie, number, name); +} + +void TestingPlatform::endCall(uint cookie, bool missed) +{ + emit callEnded(cookie, missed); +} + +void TestingPlatform::hangupCall(uint cookie) +{ + qDebug() << "Testing platform received a hangup call event"; + emit callEnded(cookie, false); +} + +QList<CalendarEvent> TestingPlatform::organizerItems() const +{ + return QList<CalendarEvent>(); +} + +void TestingPlatform::actionTriggered(const QString &actToken) +{ + qDebug() << "action triggered" << actToken; +} diff --git a/rockworkd/platformintegration/testing/testingplatform.h b/rockworkd/platformintegration/testing/testingplatform.h new file mode 100644 index 0000000..8c820a0 --- /dev/null +++ b/rockworkd/platformintegration/testing/testingplatform.h @@ -0,0 +1,31 @@ +#ifndef TESTINGPLATFORM_H +#define TESTINGPLATFORM_H + +#include "libpebble/platforminterface.h" + +class QQuickView; + +class TestingPlatform : public PlatformInterface +{ + Q_OBJECT +public: + explicit TestingPlatform(QObject *parent = 0); + + void sendMusicControlCommand(MusicControlButton command) override; + MusicMetaData musicMetaData() const override; + + Q_INVOKABLE void sendNotification(int type, const QString &from, const QString &subject, const QString &text); + Q_INVOKABLE void fakeIncomingCall(uint cookie, const QString &number, const QString &name); + Q_INVOKABLE void endCall(uint cookie, bool missed); + + void hangupCall(uint cookie) override; + + QList<CalendarEvent> organizerItems() const override; + void actionTriggered(const QString &actToken) override; +signals: + +private: + QQuickView *m_view; +}; + +#endif // TESTINGPLATFORM_H diff --git a/rockworkd/platformintegration/testing/testui.qrc b/rockworkd/platformintegration/testing/testui.qrc new file mode 100644 index 0000000..bc0a45f --- /dev/null +++ b/rockworkd/platformintegration/testing/testui.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>testui/Main.qml</file> + <file>testui/PebbleController.qml</file> + </qresource> +</RCC> diff --git a/rockworkd/platformintegration/testing/testui/Main.qml b/rockworkd/platformintegration/testing/testui/Main.qml new file mode 100644 index 0000000..e520ca4 --- /dev/null +++ b/rockworkd/platformintegration/testing/testui/Main.qml @@ -0,0 +1,87 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import PebbleTest 1.0 + +Row { + Column { + spacing: 10 + Button { + text: "Generic Notification" + onClicked: { + handler.sendNotification(0, "Bro Coly", "TestSubject", "TestText") + } + } + Button { + text: "Email Notification" + onClicked: { + handler.sendNotification(1, "Tom Ato", "TestSubject", "TestText") + } + } + Button { + text: "SMS with no subject" + onClicked: { + handler.sendNotification(2, "Tom Ato", "", "TestText") + } + } + + Button { + text: "Facebook Notification" + onClicked: { + handler.sendNotification(3, "Cole Raby", "TestSubject", "TestText") + } + } + Button { + text: "Twitter Notification" + onClicked: { + handler.sendNotification(4, "Horse Reddish", "TestSubject", "TestText") + } + } + Button { + text: "Telegram Notification" + onClicked: { + handler.sendNotification(5, "Horse Reddish", "TestSubject", "TestText") + } + } + Button { + text: "WhatsApp Notification" + onClicked: { + handler.sendNotification(6, "Horse Reddish", "TestSubject", "TestText") + } + } + Button { + text: "Hangout Notification" + onClicked: { + handler.sendNotification(7, "Horse Reddish", "TestSubject", "TestText") + } + } + + } + + Column { + spacing: 10 + Button { + text: "Fake incoming phone call" + onClicked: { + handler.fakeIncomingCall(1, "123456789", "TestCaller") + } + } + Button { + text: "pick up incoming phone call" + onClicked: { + handler.callStarted(1) + } + } + Button { + text: "hang up incoming phone call" + onClicked: { + handler.endCall(1, false) + } + } + Button { + text: "miss incoming phone call" + onClicked: { + handler.endCall(1, true) + } + } + } +} diff --git a/rockworkd/platformintegration/testing/testui/PebbleController.qml b/rockworkd/platformintegration/testing/testui/PebbleController.qml new file mode 100644 index 0000000..78861d8 --- /dev/null +++ b/rockworkd/platformintegration/testing/testui/PebbleController.qml @@ -0,0 +1,44 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import PebbleTest 1.0 + +Column { + spacing: 10 + Label { + text: pebble.name + width: parent.width + } + + Button { + text: "Insert Timeline Pin" + onClicked: { + pebble.insertTimelinePin(); + } + } + Button { + text: "Create Reminder" + onClicked: { + pebble.insertReminder(); + } + } + Button { + text: "Clear Timeline" + onClicked: { + pebble.clearTimeline(); + } + } + Button { + text: "take screenshot" + onClicked: { + pebble.requestScreenshot(); + } + } + + Button { + text: "dump logs" + onClicked: { + pebble.dumpLogs(); + } + } +} + diff --git a/rockworkd/platformintegration/ubuntu/callchannelobserver.cpp b/rockworkd/platformintegration/ubuntu/callchannelobserver.cpp new file mode 100644 index 0000000..e3d852c --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/callchannelobserver.cpp @@ -0,0 +1,165 @@ +#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, &TelepathyMonitor::accountManagerSetup); + m_contactManager = new QContactManager("galera"); + m_contactManager->setParent(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, &TelepathyMonitor::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/ubuntu/callchannelobserver.h b/rockworkd/platformintegration/ubuntu/callchannelobserver.h new file mode 100644 index 0000000..cc2b7aa --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/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/ubuntu/organizeradapter.cpp b/rockworkd/platformintegration/ubuntu/organizeradapter.cpp new file mode 100644 index 0000000..853403a --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/organizeradapter.cpp @@ -0,0 +1,74 @@ +#include "organizeradapter.h" + +#include <QOrganizerItemFetchRequest> +#include <QDebug> +#include <QOrganizerEventOccurrence> +#include <QOrganizerItemDetail> + +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); +} + +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.generateNewUuid(); + 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/ubuntu/organizeradapter.h b/rockworkd/platformintegration/ubuntu/organizeradapter.h new file mode 100644 index 0000000..2ce8e4d --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/organizeradapter.h @@ -0,0 +1,33 @@ +#ifndef ORGANIZERADAPTER_H +#define ORGANIZERADAPTER_H + +#include "libpebble/calendarevent.h" + +#include <QObject> + +#include <QOrganizerManager> +#include <QOrganizerAbstractRequest> +#include <QOrganizerEvent> + +QTORGANIZER_USE_NAMESPACE + +class OrganizerAdapter : public QObject +{ + Q_OBJECT +public: + explicit OrganizerAdapter(QObject *parent = 0); + + QList<CalendarEvent> items() const; + +public slots: + void refresh(); + +signals: + void itemsChanged(const QList<CalendarEvent> &items); + +private: + QOrganizerManager *m_manager; + QList<CalendarEvent> m_items; +}; + +#endif // ORGANIZERADAPTER_H diff --git a/rockworkd/platformintegration/ubuntu/syncmonitorclient.cpp b/rockworkd/platformintegration/ubuntu/syncmonitorclient.cpp new file mode 100644 index 0000000..b43509e --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/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/ubuntu/syncmonitorclient.h b/rockworkd/platformintegration/ubuntu/syncmonitorclient.h new file mode 100644 index 0000000..1587ba5 --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/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/ubuntu/ubuntuplatform.cpp b/rockworkd/platformintegration/ubuntu/ubuntuplatform.cpp new file mode 100644 index 0000000..7c060b1 --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/ubuntuplatform.cpp @@ -0,0 +1,232 @@ +#include "ubuntuplatform.h" + +#include "callchannelobserver.h" +#include "organizeradapter.h" +#include "syncmonitorclient.h" + +#include <QDBusConnection> +#include <QDBusConnectionInterface> +#include <QDebug> + +// qmenumodel +#include "dbus-enums.h" +#include "liburl-dispatcher-1/url-dispatcher.h" + +UbuntuPlatform::UbuntuPlatform(QObject *parent): + PlatformInterface(parent), + m_volumeActionGroup() +{ + // 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 + setupMusicService(); + m_volumeActionGroup.setBusType(DBusEnums::SessionBus); + m_volumeActionGroup.setBusName("com.canonical.indicator.sound"); + m_volumeActionGroup.setObjectPath("/com/canonical/indicator/sound"); + m_volumeActionGroup.QDBusObject::connect(); + connect(&m_volumeActionGroup, &QDBusActionGroup::statusChanged, [this] { + if (m_volumeActionGroup.status() == DBusEnums::Connected) { + m_volumeAction = m_volumeActionGroup.action("volume"); + } + }); + + // Calls + m_telepathyMonitor = new TelepathyMonitor(this); + connect(m_telepathyMonitor, &TelepathyMonitor::incomingCall, this, &UbuntuPlatform::incomingCall); + connect(m_telepathyMonitor, &TelepathyMonitor::callStarted, this, &UbuntuPlatform::callStarted); + connect(m_telepathyMonitor, &TelepathyMonitor::callEnded, this, &UbuntuPlatform::callEnded); + + // Organizer + m_organizerAdapter = new OrganizerAdapter(this); + m_organizerAdapter->refresh(); + connect(m_organizerAdapter, &OrganizerAdapter::itemsChanged, this, &UbuntuPlatform::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 *UbuntuPlatform::interface() const +{ + return m_iface; +} + +uint UbuntuPlatform::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) +{ + Q_UNUSED(replaces_id) + // Lets directly suppress volume change notifications, network password entries and phone call snap decisions here + QStringList hiddenNotifications = {"indicator-sound", "indicator-network"}; + if (!hiddenNotifications.contains(app_name)) { + if (hints.contains("x-canonical-secondary-icon") && hints.value("x-canonical-secondary-icon").toString() == "incoming-call") { + qDebug() << "Have a phone call notification. Ignoring it..." << app_name << app_icon; + } else { + qDebug() << "Notification received" << app_name << app_icon << actions << hints << expire_timeout; + Notification n(app_name); + if (app_name.contains("twitter")) { + n.setType(Notification::NotificationTypeTwitter); + n.setSourceName("Twitter"); + } else if (app_name.contains("dekko")) { + n.setType(Notification::NotificationTypeEmail); + n.setSourceName("EMail"); + } else if (app_name.toLower().contains("gmail")) { + n.setType(Notification::NotificationTypeGMail); + n.setSourceName("GMail"); + } else if (app_name.contains("facebook")) { + n.setType(Notification::NotificationTypeFacebook); + n.setSourceName("Facebook"); + } else if (app_name.contains("telegram")) { + n.setType(Notification::NotificationTypeTelegram); + n.setSourceName("Telegram"); + } else if (app_name.toLower().contains("hangout")) { + n.setType(Notification::NotificationTypeHangout); + n.setSourceName("Hangout"); + } 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.contains(QRegExp("^[a-z]*://"))) { + n.setActToken(action); + break; + } + } + qDebug() << "have act token" << n.actToken(); + + emit notificationReceived(n); + } + } + // We never return something. We're just snooping in... + setDelayedReply(true); + return 0; +} + +void UbuntuPlatform::setupMusicService() +{ + if (!m_mprisService.isEmpty()) { + disconnect(this, SLOT(mediaPropertiesChanged(QString,QVariantMap,QStringList))); + } + + 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; + } + } +} + +void UbuntuPlatform::sendMusicControlCommand(MusicControlButton controlButton) +{ + if (m_mprisService.isEmpty()) { + setupMusicService(); + } + + 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; + } + + int volumeDiff = 0; + switch (controlButton) { + case MusicControlVolumeUp: + volumeDiff = 1; + break; + case MusicControlVolumeDown: + volumeDiff = -1; + break; + default: + ; + } + + if (m_volumeAction && volumeDiff != 0) { + m_volumeAction->activate(volumeDiff); + return; + } +} + +MusicMetaData UbuntuPlatform::musicMetaData() const +{ + return m_musicMetaData; +} + +void UbuntuPlatform::hangupCall(uint cookie) +{ + m_telepathyMonitor->hangupCall(cookie); +} + +QList<CalendarEvent> UbuntuPlatform::organizerItems() const +{ + return m_organizerAdapter->items(); +} + +void UbuntuPlatform::actionTriggered(const QString &actToken) +{ + url_dispatch_send(actToken.toStdString().c_str(), [] (const gchar *, gboolean, gpointer) {}, nullptr); +} + +void UbuntuPlatform::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, &UbuntuPlatform::fetchMusicMetadataFinished); + } +} + +void UbuntuPlatform::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 UbuntuPlatform::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/ubuntu/ubuntuplatform.h b/rockworkd/platformintegration/ubuntu/ubuntuplatform.h new file mode 100644 index 0000000..5679a42 --- /dev/null +++ b/rockworkd/platformintegration/ubuntu/ubuntuplatform.h @@ -0,0 +1,62 @@ +#ifndef UBUNTUPLATFORM_H +#define UBUNTUPLATFORM_H + +#include "libpebble/platforminterface.h" +#include "libpebble/enums.h" + +#include <QDBusInterface> +#include <TelepathyQt/AbstractClientObserver> + +#include <qdbusactiongroup.h> +#include <qstateaction.h> + +class QDBusPendingCallWatcher; +class TelepathyMonitor; +class OrganizerAdapter; +class SyncMonitorClient; + +class UbuntuPlatform : public PlatformInterface, public QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications") + Q_PROPERTY(QDBusInterface* interface READ interface) + + +public: + UbuntuPlatform(QObject *parent = 0); + QDBusInterface* interface() const; + + void sendMusicControlCommand(MusicControlButton controlButton) override; + MusicMetaData musicMetaData() const override; + + void hangupCall(uint cookie) override; + + 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 setupMusicService(); + 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; + QDBusActionGroup m_volumeActionGroup; + QStateAction *m_volumeAction = nullptr; + + TelepathyMonitor *m_telepathyMonitor; + OrganizerAdapter *m_organizerAdapter; + SyncMonitorClient *m_syncMonitorClient; + QTimer m_syncTimer; +}; + +#endif // UBUNTUPLATFORM_H |
