diff options
Diffstat (limited to 'rockworkd')
| -rw-r--r-- | rockworkd/core.cpp | 4 | ||||
| -rw-r--r-- | rockworkd/libpebble/jskit/typedarray.js | 61 | ||||
| -rw-r--r-- | rockworkd/libpebble/pebble.cpp | 2 | ||||
| -rw-r--r-- | rockworkd/pebblemanager.cpp | 2 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/callchannelobserver.cpp | 165 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/callchannelobserver.h | 74 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/organizeradapter.cpp | 73 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/organizeradapter.h | 33 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/sailfishplatform.cpp | 249 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/sailfishplatform.h | 58 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/syncmonitorclient.cpp | 100 | ||||
| -rw-r--r-- | rockworkd/platformintegration/sailfish/syncmonitorclient.h | 51 | ||||
| -rw-r--r-- | rockworkd/rockpoold.service | 11 |
13 files changed, 845 insertions, 38 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/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 5655cc7..e421545 100644 --- a/rockworkd/libpebble/pebble.cpp +++ b/rockworkd/libpebble/pebble.cpp @@ -133,7 +133,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/pebblemanager.cpp b/rockworkd/pebblemanager.cpp index 126000e..a119812 100644 --- a/rockworkd/pebblemanager.cpp +++ b/rockworkd/pebblemanager.cpp @@ -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..534c360 --- /dev/null +++ b/rockworkd/platformintegration/sailfish/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, SLOT(accountManagerSetup)); + m_contactManager = new QContactManager("org.nemomobile.contacts.sqlite"); + 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, SLOT(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/sailfish/callchannelobserver.h b/rockworkd/platformintegration/sailfish/callchannelobserver.h new file mode 100644 index 0000000..cc2b7aa --- /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..8851fa5 --- /dev/null +++ b/rockworkd/platformintegration/sailfish/organizeradapter.cpp @@ -0,0 +1,73 @@ +#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.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..2ce8e4d --- /dev/null +++ b/rockworkd/platformintegration/sailfish/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/sailfish/sailfishplatform.cpp b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp new file mode 100644 index 0000000..e31d65b --- /dev/null +++ b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp @@ -0,0 +1,249 @@ +#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) +{ + // 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 << replaces_id << app_icon << summary << body << 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.toLower().contains("telegram") || app_name.toLower().contains("sailorgram")) { + n.setType(Notification::NotificationTypeTelegram); + n.setSourceName("Telegram"); + } else if (app_name.toLower().contains("hangish")) { + 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 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) +{ + Q_UNUSED(actToken); + //url_dispatch_send(actToken.toStdString().c_str(), [] (const char *, boolean, pointer) {}, nullptr); +} + +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..a95f433 --- /dev/null +++ b/rockworkd/platformintegration/sailfish/sailfishplatform.h @@ -0,0 +1,58 @@ +#ifndef UBUNTUPLATFORM_H +#define UBUNTUPLATFORM_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; + + 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 // UBUNTUPLATFORM_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/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 |
