From 5fef356ff3fbcb76a5ec44a81a8d54d29a42f25d Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 23 Mar 2015 03:28:00 +0100 Subject: initial import --- rpm/saltoq.yaml | 41 +++++ saltoq.pro | 6 + saltoqd/commmanager.cpp | 93 ++++++++++ saltoqd/commmanager.h | 32 ++++ saltoqd/main.cpp | 12 ++ saltoqd/musicmanager.cpp | 199 +++++++++++++++++++++ saltoqd/musicmanager.h | 39 ++++ saltoqd/notificationmanager.cpp | 7 + saltoqd/notificationmanager.h | 16 ++ saltoqd/notificationmonitor.cpp | 79 ++++++++ saltoqd/notificationmonitor.h | 28 +++ saltoqd/org.freedesktop.Notifications.xml | 17 ++ .../org.nemomobile.voicecall.VoiceCallManager.xml | 53 ++++++ saltoqd/saltoqd.pro | 38 ++++ saltoqd/storagemanager.cpp | 17 ++ saltoqd/storagemanager.h | 29 +++ saltoqd/systemmanager.cpp | 55 ++++++ saltoqd/systemmanager.h | 22 +++ saltoqd/toqconnection.cpp | 187 +++++++++++++++++++ saltoqd/toqconnection.h | 94 ++++++++++ saltoqd/toqmanager.cpp | 73 ++++++++ saltoqd/toqmanager.h | 54 ++++++ saltoqd/versionmanager.cpp | 32 ++++ saltoqd/versionmanager.h | 21 +++ saltoqd/voicecallmanager.cpp | 73 ++++++++ saltoqd/voicecallmanager.h | 26 +++ 26 files changed, 1343 insertions(+) create mode 100644 rpm/saltoq.yaml create mode 100644 saltoq.pro create mode 100644 saltoqd/commmanager.cpp create mode 100644 saltoqd/commmanager.h create mode 100644 saltoqd/main.cpp create mode 100644 saltoqd/musicmanager.cpp create mode 100644 saltoqd/musicmanager.h create mode 100644 saltoqd/notificationmanager.cpp create mode 100644 saltoqd/notificationmanager.h create mode 100644 saltoqd/notificationmonitor.cpp create mode 100644 saltoqd/notificationmonitor.h create mode 100644 saltoqd/org.freedesktop.Notifications.xml create mode 100644 saltoqd/org.nemomobile.voicecall.VoiceCallManager.xml create mode 100644 saltoqd/saltoqd.pro create mode 100644 saltoqd/storagemanager.cpp create mode 100644 saltoqd/storagemanager.h create mode 100644 saltoqd/systemmanager.cpp create mode 100644 saltoqd/systemmanager.h create mode 100644 saltoqd/toqconnection.cpp create mode 100644 saltoqd/toqconnection.h create mode 100644 saltoqd/toqmanager.cpp create mode 100644 saltoqd/toqmanager.h create mode 100644 saltoqd/versionmanager.cpp create mode 100644 saltoqd/versionmanager.h create mode 100644 saltoqd/voicecallmanager.cpp create mode 100644 saltoqd/voicecallmanager.h diff --git a/rpm/saltoq.yaml b/rpm/saltoq.yaml new file mode 100644 index 0000000..a90944b --- /dev/null +++ b/rpm/saltoq.yaml @@ -0,0 +1,41 @@ +Name: saltoq +Summary: Qualcomm Toq manager application +Version: 0.0.1 +Release: 1 +Group: Communications/Bluetooth +URL: https://gitorious.org/javispedro-jolla-misc/salmeta/ +License: GPLv3 +Sources: +- '%{name}-%{version}.tar.bz2' +Description: | + Qualcomm Toq. +Configure: none +Builder: qtc5 + +PkgConfigBR: + - sailfishapp >= 1.0.2 + - Qt5Core >= 5.2 + - Qt5Qml + - Qt5Quick + - Qt5DBus + - Qt5Bluetooth >= 5.2 + - mlite5 + - libiphb + +PkgBR: +# Workaround current sailfish qt5connectivity packaging bug + - qt5-qtconnectivity-qtbluetooth-devel + +Requires: + - sailfishsilica-qt5 >= 0.10.9 + - sailfish-components-bluetooth-qt5 + - nemo-qml-plugin-configuration-qt5 + - systemd + - systemd-user-session-targets + +Files: + - '%{_bindir}' +# - '%{_datadir}/%{name}' +# - '%{_datadir}/applications/%{name}.desktop' +# - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' +# - '%{_libdir}/systemd/user/salmeta.service' diff --git a/saltoq.pro b/saltoq.pro new file mode 100644 index 0000000..83f6021 --- /dev/null +++ b/saltoq.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + saltoqd + +OTHER_FILES = rpm/saltoq.yaml rpm/saltoq.spec diff --git a/saltoqd/commmanager.cpp b/saltoqd/commmanager.cpp new file mode 100644 index 0000000..271749a --- /dev/null +++ b/saltoqd/commmanager.cpp @@ -0,0 +1,93 @@ +#include "commmanager.h" + +using namespace CommHistory; + +CommManager::CommManager(StorageManager *storage, ToqManager *toq) : + QObject(toq), _toq(toq), _storage(storage), + _calls(new CallModel(this)), + _convs(new GroupModel(this)), + _refreshTimer(new QTimer(this)) +{ + _calls->setTreeMode(false); + _calls->setQueryMode(EventModel::AsyncQuery); + _calls->setSorting(CallModel::SortByTime); + _calls->setResolveContacts(true); + _calls->setLimit(20); + + _convs->setQueryMode(EventModel::AsyncQuery); + _convs->setLimit(20); + + _refreshTimer->setSingleShot(true); + _refreshTimer->setInterval(1000); + + connect(_calls, SIGNAL(modelReady(bool)), + this, SLOT(scheduleRefresh())); + connect(_calls, SIGNAL(modelReset()), + this, SLOT(scheduleRefresh())); + connect(_calls, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(scheduleRefresh())); + connect(_calls, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(scheduleRefresh())); + + connect(_convs, SIGNAL(modelReady(bool)), + this, SLOT(scheduleRefresh())); + + connect(_refreshTimer, &QTimer::timeout, + this, &CommManager::refresh); + + if (!_calls->getEvents()) { + qWarning() << "Could not get the call log"; + } + if (!_convs->getGroups()) { + qWarning() << "Could not get conversation groups"; + } +} + +void CommManager::scheduleRefresh() +{ + if (!_refreshTimer->isActive()) { + _refreshTimer->start(); + } +} + +void CommManager::refresh() +{ + qDebug() << "refreshing now"; + QMultiMap events; + + int rows = _calls->rowCount(); + for (int i = 0; i < rows; i++) { + Event e = _calls->event(i); + qDebug() << "Got call" << e.toString(); + QDateTime dt = e.startTime(); + QJsonObject obj; + obj.insert("CommsType", QLatin1String("Call")); + obj.insert("ReceivedTime", qint64(dt.toTime_t())); + obj.insert("CallerId", e.contactName()); + obj.insert("ItemId", e.id()); + QJsonObject details; + details.insert("Duration", e.startTime().secsTo(e.endTime())); + details.insert("PhoneType", QLatin1String("Other")); // TODO + switch (e.direction()) { + case Event::Inbound: + details.insert("Direction", QLatin1String("Incoming")); + break; + case Event::Outbound: + details.insert("Direction", QLatin1String("Outgoing")); + break; + default: + details.insert("Direction", QLatin1String("Unknown")); + break; + } + details.insert("IsMissedCall", e.isMissedCall()); + obj.insert("CommsDetails", details); + + events.insert(dt, obj); + } + + rows = _convs->rowCount(); + for (int i = 0; i < rows; i++) { + Group g = _convs->group(_convs->index(i, 0)); + qDebug() << "Chat" << g.contactName(); + } +} diff --git a/saltoqd/commmanager.h b/saltoqd/commmanager.h new file mode 100644 index 0000000..05d2147 --- /dev/null +++ b/saltoqd/commmanager.h @@ -0,0 +1,32 @@ +#ifndef COMMMANAGER_H +#define COMMMANAGER_H + +#include "storagemanager.h" +#include +#include + +class CommManager : public QObject +{ + Q_OBJECT +public: + explicit CommManager(StorageManager *storage, ToqManager *toq); + +public slots: + void scheduleRefresh(); + +signals: + +private slots: + void refresh(); + +private: + ToqManager *_toq; + StorageManager *_storage; + + CommHistory::CallModel *_calls; + CommHistory::GroupModel *_convs; + + QTimer *_refreshTimer; +}; + +#endif // COMMMANAGER_H diff --git a/saltoqd/main.cpp b/saltoqd/main.cpp new file mode 100644 index 0000000..1f34c0d --- /dev/null +++ b/saltoqd/main.cpp @@ -0,0 +1,12 @@ +#include +#include "toqmanager.h" + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QBluetoothAddress addr("64:9C:81:00:13:EF"); + QScopedPointer manager(new ToqManager(addr)); + + return app.exec(); +} diff --git a/saltoqd/musicmanager.cpp b/saltoqd/musicmanager.cpp new file mode 100644 index 0000000..8b1e9a9 --- /dev/null +++ b/saltoqd/musicmanager.cpp @@ -0,0 +1,199 @@ +#include +#include "musicmanager.h" + +MusicManager::MusicManager(ToqManager *toq) : + QObject(toq), _toq(toq), _watcher(new QDBusServiceWatcher(this)), _isPlayerVisible(false) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusConnectionInterface *bus_iface = bus.interface(); + + // This watcher will be used to find when the current MPRIS service dies + // (and thus we must clear the metadata) + _watcher->setConnection(bus); + connect(_watcher, &QDBusServiceWatcher::serviceOwnerChanged, + this, &MusicManager::handleMprisServiceOwnerChanged); + + // Try to find an active MPRIS service to initially connect to + const QStringList &services = bus_iface->registeredServiceNames(); + foreach (QString service, services) { + if (service.startsWith("org.mpris.MediaPlayer2.")) { + switchToService(service); + fetchMetadataFromService(); + // The watch is not connected by this point, + // so we don't send the current metadata. + break; + } + } + + // Even if we didn't find any service, we still listen for metadataChanged signals + // from every MPRIS-compatible player + // If such a signal comes in, we will connect to the source service for that signal + bus.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", + this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); + + _toq->setEndpointListener(ToqConnection::MusicEndpoint, this); +} + +void MusicManager::handleMessage(const ToqConnection::Message &msg) +{ + Q_ASSERT(msg.destination == ToqConnection::MusicEndpoint); + switch (msg.type) { + case 0: + handleGetPlayerStatusMessage(msg); + _isPlayerVisible = true; + break; + case 1: + callMprisMethod("Play"); + // TODO Reply with new metadata! + break; + case 2: + callMprisMethod("Pause"); + break; + case 3: + callMprisMethod("Next"); + break; + case 4: + callMprisMethod("Previous"); + break; + case 0x8004: // Player closing? + _isPlayerVisible = false; + break; + case 5: //Volume up/down + case 6: + // Not yet implemented + default: + qWarning() << "Unknown message type" << msg.type; + break; + } +} + +void MusicManager::handleGetPlayerStatusMessage(const ToqConnection::Message &msg) +{ + QJsonObject reply; + reply.insert("result", int(0)); + reply.insert("description", QLatin1String("PlayerStatus Request received")); + reply.insert("artist", _curMetadata.value("xesam:artist").toString()); + reply.insert("album", _curMetadata.value("xesam:album").toString()); + reply.insert("title", _curMetadata.value("xesam:title").toString()); + reply.insert("state", _curPlaybackStatus == "Playing" ? QLatin1String("playing") : QLatin1String("paused")); + reply.insert("album-art", QLatin1String("")); + reply.insert("playlist", int(1)); // TODO + reply.insert("player", _curService); + reply.insert("version", QLatin1String("1.0")); + reply.insert("controller", QLatin1String("SERIAL")); + + _toq->sendReply(msg, 0x4000, reply); +} + +void MusicManager::switchToService(const QString &service) +{ + if (_curService != service) { + qDebug() << "switching to mpris service" << service; + _curService = service; + + if (_curService.isEmpty()) { + _watcher->setWatchedServices(QStringList()); + } else { + _watcher->setWatchedServices(QStringList(_curService)); + } + } +} + +void MusicManager::fetchMetadataFromService() +{ + _curMetadata.clear(); + + if (!_curService.isEmpty()) { + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + call << "org.mpris.MediaPlayer2.Player" << "Metadata"; + QDBusReply reply = QDBusConnection::sessionBus().call(call); + if (reply.isValid()) { + qDebug() << "got mpris metadata from service" << _curService; + _curMetadata = qdbus_cast(reply.value().variant().value()); + } else { + qWarning() << reply.error().message(); + } + } +} + +void MusicManager::sendCurrentMprisMetadata() +{ + Q_ASSERT(_toq->isConnected()); + + QJsonObject obj; + fillWithMetadata(obj); + + _toq->sendMessage(ToqConnection::MusicEndpoint, ToqConnection::MusicEndpoint + 1, 0x8003, obj); +} + +void MusicManager::fillWithMetadata(QJsonObject &obj) +{ + obj.insert("artist", _curMetadata.value("xesam:artist").toString()); + obj.insert("album", _curMetadata.value("xesam:album").toString()); + obj.insert("title", _curMetadata.value("xesam:title").toString()); + obj.insert("state", _curPlaybackStatus == "Playing" ? QLatin1String("playing") : QLatin1String("paused")); + obj.insert("album-art", QLatin1String("")); + obj.insert("playlist", int(1)); // TODO + obj.insert("player", _curService); + obj.insert("version", QLatin1String("1.0")); + obj.insert("controller", QLatin1String("SERIAL")); +} + +void MusicManager::callMprisMethod(const QString &method) +{ + Q_ASSERT(!method.isEmpty()); + Q_ASSERT(!_curService.isEmpty()); + + qDebug() << _curService << "->" << method; + + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, + "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", + method); + + QDBusError err = bus.call(call); + + if (err.isValid()) { + qWarning() << "while calling mpris method on" << _curService << ":" << err.message(); + } +} + +void MusicManager::handleMprisServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) +{ + Q_UNUSED(oldOwner); + if (name == _curService && newOwner.isEmpty()) { + // Oops, current service is going away + switchToService(QString()); + _curMetadata.clear(); + if (_toq->isConnected() && _isPlayerVisible) { + sendCurrentMprisMetadata(); + } + } +} + +void MusicManager::handleMprisPropertiesChanged(const QString &interface, const QMap &changed, const QStringList &invalidated) +{ + Q_ASSERT(calledFromDBus()); + Q_UNUSED(interface); + Q_UNUSED(invalidated); + + if (changed.contains("Metadata")) { + QVariantMap metadata = qdbus_cast(changed.value("Metadata").value()); + qDebug() << "received new metadata" << metadata; + _curMetadata = metadata; + } + + if (changed.contains("PlaybackStatus")) { + _curPlaybackStatus = changed.value("PlaybackStatus").toString(); + if (_curPlaybackStatus == "Stopped") { + _curMetadata.clear(); + } + } + + if (_toq->isConnected() && _isPlayerVisible) { + sendCurrentMprisMetadata(); + } + + switchToService(message().service()); +} diff --git a/saltoqd/musicmanager.h b/saltoqd/musicmanager.h new file mode 100644 index 0000000..5bae89b --- /dev/null +++ b/saltoqd/musicmanager.h @@ -0,0 +1,39 @@ +#ifndef MUSICMANAGER_H +#define MUSICMANAGER_H + +#include +#include +#include "toqmanager.h" + +class MusicManager : public QObject, public ToqManager::EndpointHandler, protected QDBusContext +{ + Q_OBJECT +public: + explicit MusicManager(ToqManager *toq); + + void handleMessage(const ToqConnection::Message &msg) Q_DECL_OVERRIDE; + +private: + void handleGetPlayerStatusMessage(const ToqConnection::Message &msg); + + void switchToService(const QString &service); + void fetchMetadataFromService(); + void sendCurrentMprisMetadata(); + void fillWithMetadata(QJsonObject &obj); + void callMprisMethod(const QString &method); + +private slots: + void handleMprisServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); + void handleMprisPropertiesChanged(const QString &interface, const QMap &changed, const QStringList &invalidated); + +private: + ToqManager *_toq; + QDBusServiceWatcher *_watcher; + QString _curService; + QVariantMap _curMetadata; + QString _curPlaybackStatus; + bool _isPlayerVisible; +}; + + +#endif // MUSICMANAGER_H diff --git a/saltoqd/notificationmanager.cpp b/saltoqd/notificationmanager.cpp new file mode 100644 index 0000000..fb52785 --- /dev/null +++ b/saltoqd/notificationmanager.cpp @@ -0,0 +1,7 @@ +#include "notificationmanager.h" + +NotificationManager::NotificationManager(ToqManager *toq) : + QObject(toq), _toq(toq) +{ + +} diff --git a/saltoqd/notificationmanager.h b/saltoqd/notificationmanager.h new file mode 100644 index 0000000..1889336 --- /dev/null +++ b/saltoqd/notificationmanager.h @@ -0,0 +1,16 @@ +#ifndef NOTIFICATIONMANAGER_H +#define NOTIFICATIONMANAGER_H + +#include "toqmanager.h" + +class NotificationManager : public QObject +{ + Q_OBJECT +public: + explicit NotificationManager(ToqManager *toq); + +private: + ToqManager *_toq; +}; + +#endif // NOTIFICATIONMANAGER_H diff --git a/saltoqd/notificationmonitor.cpp b/saltoqd/notificationmonitor.cpp new file mode 100644 index 0000000..f22ce02 --- /dev/null +++ b/saltoqd/notificationmonitor.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include + +#include "notificationmonitor.h" +#include "notifications_adaptor.h" + +static NotificationMonitor *global_monitor = 0; + +NotificationMonitor::NotificationMonitor(QObject *parent) : + QObject(parent) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusConnectionInterface *dbus = bus.interface(); + dbus->call("AddMatch", + "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); + new NotificationsAdaptor(this); + bus.registerObject("/org/freedesktop/Notifications", this); +} + +NotificationMonitor::~NotificationMonitor() +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusConnectionInterface *dbus = bus.interface(); + dbus->call("RemoveMatch", + "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); +} + +NotificationMonitor *NotificationMonitor::instance() +{ + if (!global_monitor) { + global_monitor = new NotificationMonitor; + } + return global_monitor; +} + +uint NotificationMonitor::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) +{ + QIcon icon; + + qDebug() << "Got notification" << app_name << app_icon << summary << body; + + // Avoid sending a reply for this method call, since we've received it + // because we're eavesdropping. + // The actual target of the method call will send the proper reply, not us. + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + + // If the notification mentions a specific icon, then use it. + // But otherwise let's prefer our builtin icons. + if (app_icon.startsWith("/")) { + icon = QIcon(app_icon); + } else if (app_icon.startsWith("file:")) { + QUrl url(app_icon); + icon = QIcon(url.toLocalFile()); + } else { + QString category = hints.value("category").toString(); + // Let's hardcode a few categories for now.. + if (!category.isEmpty()) { + qDebug() << "TODO: Category icons"; + } + } + + int count = hints.value("x-nemo-item-count").toInt(); + QDateTime dateTime = hints.value("x-nemo-timestamp").toDateTime(); + if (!dateTime.isValid()) { + dateTime = QDateTime::currentDateTime(); + } + + if (summary.isEmpty() && body.isEmpty()) { + // Avoid sending empty notifications to watch. + return 0; + } + + emit incomingNotification(app_name, icon, summary, count, body, dateTime); + + return 0; +} diff --git a/saltoqd/notificationmonitor.h b/saltoqd/notificationmonitor.h new file mode 100644 index 0000000..c0f7691 --- /dev/null +++ b/saltoqd/notificationmonitor.h @@ -0,0 +1,28 @@ +#ifndef NOTIFICATIONMONITOR_H +#define NOTIFICATIONMONITOR_H + +#include +#include +#include + +class NotificationMonitor : public QObject, protected QDBusContext +{ + Q_OBJECT + +public: + ~NotificationMonitor(); + + static NotificationMonitor *instance(); + +signals: + void incomingNotification(const QString &sender, const QIcon &icon, const QString &summary, int count, const QString &body, const QDateTime &dateTime); + +private: + explicit NotificationMonitor(QObject *parent = 0); + +private slots: + friend class NotificationsAdaptor; + 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); +}; + +#endif // NOTIFICATIONMONITOR_H diff --git a/saltoqd/org.freedesktop.Notifications.xml b/saltoqd/org.freedesktop.Notifications.xml new file mode 100644 index 0000000..d3f8bef --- /dev/null +++ b/saltoqd/org.freedesktop.Notifications.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/saltoqd/org.nemomobile.voicecall.VoiceCallManager.xml b/saltoqd/org.nemomobile.voicecall.VoiceCallManager.xml new file mode 100644 index 0000000..cfdbae5 --- /dev/null +++ b/saltoqd/org.nemomobile.voicecall.VoiceCallManager.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/saltoqd/saltoqd.pro b/saltoqd/saltoqd.pro new file mode 100644 index 0000000..2c50db5 --- /dev/null +++ b/saltoqd/saltoqd.pro @@ -0,0 +1,38 @@ +TEMPLATE = app +CONFIG += console +QT -= qml +QT += dbus bluetooth + +CONFIG += c++11 link_pkgconfig + +PKGCONFIG += zlib commhistory-qt5 +INCLUDEPATH += /usr/include/commhistory-qt5 + +SOURCES += main.cpp \ + toqconnection.cpp \ + toqmanager.cpp \ + versionmanager.cpp \ + systemmanager.cpp \ + musicmanager.cpp \ + storagemanager.cpp \ + commmanager.cpp \ + notificationmanager.cpp notificationmonitor.cpp \ + voicecallmanager.cpp + +HEADERS += \ + toqconnection.h \ + toqmanager.h \ + versionmanager.h \ + systemmanager.h \ + musicmanager.h \ + storagemanager.h \ + commmanager.h \ + notificationmanager.h notificationmonitor.h \ + voicecallmanager.h + +DBUS_ADAPTORS += org.freedesktop.Notifications.xml + +DBUS_INTERFACES += org.nemomobile.voicecall.VoiceCallManager.xml + +target.path = /usr/bin +INSTALLS += target diff --git a/saltoqd/storagemanager.cpp b/saltoqd/storagemanager.cpp new file mode 100644 index 0000000..c2b9808 --- /dev/null +++ b/saltoqd/storagemanager.cpp @@ -0,0 +1,17 @@ +#include "storagemanager.h" + +StorageManager::StorageManager(ToqManager *toq) : + QObject(toq), _toq(toq) +{ + _toq->setEndpointListener(ToqConnection::StorageServiceEndpoint, this); +} + +void StorageManager::handleMessage(const ToqConnection::Message &msg) +{ + Q_ASSERT(msg.destination == ToqConnection::StorageServiceEndpoint); + switch (msg.type) { // TODO + default: + qWarning() << "Unknown message" << msg.type; + break; + } +} diff --git a/saltoqd/storagemanager.h b/saltoqd/storagemanager.h new file mode 100644 index 0000000..fa97a8d --- /dev/null +++ b/saltoqd/storagemanager.h @@ -0,0 +1,29 @@ +#ifndef STORAGEMANAGER_H +#define STORAGEMANAGER_H + +#include "toqmanager.h" + +class StorageManager : public QObject, public ToqManager::EndpointHandler +{ + Q_OBJECT +public: + explicit StorageManager(ToqManager *toq); + + void handleMessage(const ToqConnection::Message &msg) Q_DECL_OVERRIDE; + + void updateStore(const QString &id, const QByteArray &data); + +private: + void handleGetStoreMessage(const ToqConnection::Message &msg); + + struct Store { + QByteArray data; + quint32 checksum; + }; + +private: + ToqManager *_toq; + QHash _stores; +}; + +#endif // STORAGEMANAGER_H diff --git a/saltoqd/systemmanager.cpp b/saltoqd/systemmanager.cpp new file mode 100644 index 0000000..4f74ea4 --- /dev/null +++ b/saltoqd/systemmanager.cpp @@ -0,0 +1,55 @@ +#include +#include +#include "systemmanager.h" +#include "voicecallmanager.h" + +SystemManager::SystemManager(ToqManager *toq) : + QObject(toq), _toq(toq) +{ + _toq->setEndpointListener(ToqConnection::SystemEndpoint, this); +} + +void SystemManager::handleMessage(const ToqConnection::Message &msg) +{ + Q_ASSERT(msg.destination == ToqConnection::SystemEndpoint); + switch (msg.type) { + case 0: + handleGetTimeMessage(msg); + break; + case 7: + handleSilenceMessage(msg); + break; + default: + qWarning() << "Unknown system message" << msg.type; + break; + } +} + +void SystemManager::handleGetTimeMessage(const ToqConnection::Message &msg) +{ + QJsonObject reply, time; + QDateTime dt = QDateTime::currentDateTime(); + QTimeZone tz = dt.timeZone(); + + time.insert("epoch_time", qint64(dt.toTime_t())); + time.insert("time_zone", tz.standardTimeOffset(dt)); + time.insert("dst", tz.isDaylightTime(dt) ? 1 : 0); + + reply.insert("result", int(0)); + reply.insert("description", QLatin1String("current time")); + reply.insert("time", time); + + _toq->sendReply(msg, 0x4000, reply); +} + +void SystemManager::handleSilenceMessage(const ToqConnection::Message &msg) +{ + QJsonObject reply; + + reply.insert("result", int(0)); + reply.insert("description", QLatin1String("Set to Silence Mode Request received")); + + VoiceCallManager::setSilentMode(msg.payload.object()["silence_mode"].toInt()); + + _toq->sendReply(msg, 0x4007, reply); +} diff --git a/saltoqd/systemmanager.h b/saltoqd/systemmanager.h new file mode 100644 index 0000000..9f7a005 --- /dev/null +++ b/saltoqd/systemmanager.h @@ -0,0 +1,22 @@ +#ifndef SYSTEMMANAGER_H +#define SYSTEMMANAGER_H + +#include "toqmanager.h" + +class SystemManager : public QObject, public ToqManager::EndpointHandler +{ + Q_OBJECT +public: + explicit SystemManager(ToqManager *toq); + + void handleMessage(const ToqConnection::Message &msg) Q_DECL_OVERRIDE; + +private: + void handleGetTimeMessage(const ToqConnection::Message &msg); + void handleSilenceMessage(const ToqConnection::Message &msg); + +private: + ToqManager *_toq; +}; + +#endif // SYSTEMMANAGER_H diff --git a/saltoqd/toqconnection.cpp b/saltoqd/toqconnection.cpp new file mode 100644 index 0000000..c2e2349 --- /dev/null +++ b/saltoqd/toqconnection.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include + +#include "toqconnection.h" + +static const int HEADER_LENGTH = 10; + +ToqConnection::ToqConnection(const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + _address(address), _socket(0), + _reconnectTimer(new QTimer(this)), + _lastTransactionId(0) +{ + connect(_reconnectTimer, &QTimer::timeout, + this, &ToqConnection::tryConnect); + + _reconnectTimer->setSingleShot(true); + _reconnectTimer->setInterval(1000); + _reconnectTimer->start(); +} + +const char * ToqConnection::nameOfEndpoint(Endpoint ep) +{ + int index = staticMetaObject.indexOfEnumerator("CoreEndpoints"); + QMetaEnum epEnum = staticMetaObject.enumerator(index); + return epEnum.valueToKey(ep); +} + +quint32 ToqConnection::checksum(const QByteArray &data) +{ + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, reinterpret_cast(data.constData()), data.size()); + return crc; +} + +quint32 ToqConnection::checksum(QIODevice *dev) +{ + uLong crc = crc32(0L, Z_NULL, 0); + char buffer[4 * 1024]; + qint64 read; + while ((read = dev->read(buffer, sizeof(buffer))) > 0) { + crc = crc32(crc, reinterpret_cast(&buffer[0]), read); + } + return crc; +} + +bool ToqConnection::isConnected() const +{ + return _socket && _socket->state() == QBluetoothSocket::ConnectedState; +} + +quint16 ToqConnection::nextTransactionId() +{ + if (_lastTransactionId >= 0xFFFA) { + // The last transaction ids (as well as 0) seem to be reserved + // Avoid using them + _lastTransactionId = 0; + } + + return ++_lastTransactionId; +} + +void ToqConnection::sendMessage(const Message &msg) +{ + if (_socket) { + _socket->write(packMessage(msg)); + } else { + qWarning() << "Discarding message because connection is broken"; + } +} + +ToqConnection::Message ToqConnection::unpackMessage(const QByteArray &data) +{ + Message msg; + + Q_ASSERT(data.length() >= HEADER_LENGTH); + const uchar *header = reinterpret_cast(data.constData()); + + quint16 message_length = qFromBigEndian(&header[2]); + Q_ASSERT(data.length() == message_length + HEADER_LENGTH - 4); + msg.source = header[0]; + msg.destination = header[1]; + msg.transactionId = qFromBigEndian(&header[4]); + msg.type = qFromBigEndian(&header[6]); + + if (!data.isEmpty()) { + QJsonParseError error; + msg.payload = QJsonDocument::fromJson(data.mid(HEADER_LENGTH), &error); + if (error.error) { + qWarning() << "Failure while parsing message JSON payload: " << error.errorString(); + } + } + + return msg; +} + +QByteArray ToqConnection::packMessage(const Message &msg) +{ + QByteArray payload = msg.payload.toJson(QJsonDocument::Compact); + uchar header[HEADER_LENGTH]; + + header[0] = msg.source; + header[1] = msg.destination; + qToBigEndian(payload.length() + 4, &header[2]); + qToBigEndian(msg.transactionId, &header[4]); + qToBigEndian(msg.type, &header[6]); + + payload.prepend(reinterpret_cast(&header[0]), HEADER_LENGTH); + + return payload; +} + +void ToqConnection::tryConnect() +{ + Q_ASSERT(!_socket); + + _socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this); + connect(_socket, &QBluetoothSocket::connected, + this, &ToqConnection::handleSocketConnected); + connect(_socket, &QBluetoothSocket::disconnected, + this, &ToqConnection::handleSocketDisconnected); + connect(_socket, (void (QBluetoothSocket::*)(QBluetoothSocket::SocketError))&QBluetoothSocket::error, + this, &ToqConnection::handleSocketError); + connect(_socket, &QBluetoothSocket::readyRead, + this, &ToqConnection::handleSocketData); + + qDebug() << "Connecting to" << _address.toString(); + + _socket->connectToService(_address, 1, QIODevice::ReadWrite); +} + +void ToqConnection::handleSocketConnected() +{ + qDebug() << "Connected"; + Q_ASSERT(_socket); + _reconnectTimer->stop(); + emit connected(); +} + +void ToqConnection::handleSocketDisconnected() +{ + qDebug() << "Disconnected"; + Q_ASSERT(_socket); + _socket->deleteLater(); + _socket = 0; + _reconnectTimer->start(); + emit disconnected(); +} + +void ToqConnection::handleSocketError(QBluetoothSocket::SocketError error) +{ + qWarning() << error << _socket->errorString(); +} + +void ToqConnection::handleSocketData() +{ + // Keep attempting to read messages as long as at least a header is present + while (_socket->bytesAvailable() >= HEADER_LENGTH) { + // Take a look at the header, but do not remove it from the socket input buffer. + // We will only remove it once we're sure the entire packet is in the buffer. + uchar header[HEADER_LENGTH]; + _socket->peek(reinterpret_cast(header), HEADER_LENGTH); + + quint16 message_length = qFromBigEndian(&header[2]); + + // Sanity checks on the message_length + if (message_length == 0) { + qWarning() << "received empty message"; + _socket->read(HEADER_LENGTH); // skip this header + continue; // check if there are additional headers. + } + + // Now wait for the entire message + if (_socket->bytesAvailable() < HEADER_LENGTH + message_length - 4) { + qDebug() << "incomplete msg body in read buffer"; + return; // try again once more data comes in + } + + // We can now safely remove the message from the input buffer, + // as we know the entire message is in the input buffer. + QByteArray data = _socket->read(HEADER_LENGTH + message_length - 4); + emit messageReceived(unpackMessage(data)); + } +} + diff --git a/saltoqd/toqconnection.h b/saltoqd/toqconnection.h new file mode 100644 index 0000000..47a3132 --- /dev/null +++ b/saltoqd/toqconnection.h @@ -0,0 +1,94 @@ +#ifndef TOQCONNECTION_H +#define TOQCONNECTION_H + +#include +#include +#include +#include + +class ToqConnection : public QObject +{ + Q_OBJECT + Q_ENUMS(CoreEndpoints) + Q_PROPERTY(bool connected READ isConnected) + +public: + explicit ToqConnection(const QBluetoothAddress &address, QObject *parent = 0); + + typedef quint8 Endpoint; + + enum CoreEndpoints + { + VersionEndpoint = 0, + VoiceCallEndpoint = 1, + SMSEndpoint = 3, + SystemEndpoint = 5, + PopUpEndpoint = 7, + StorageServiceEndpoint = 9, + TFTPEndpoint = 15, + FMSEndpoint = 17, + EPCommunicationEndpoint = 19, + MusicEndpoint = 24, + AppMessagingEndpoint = 26, + SpeechEndpoint = 28, + ActivityMonitoringEndpoint = 30, + FTSEndpoint = 32, + AppLoggingEndpoint = 34 + }; + + struct Message + { + Message(); + Message(Endpoint source, Endpoint destination, quint16 transactionId, quint32 type, QJsonDocument payload); + + Endpoint source; + Endpoint destination; + quint16 transactionId; + quint32 type; + QJsonDocument payload; + }; + + static const char * nameOfEndpoint(Endpoint ep); + + static quint32 checksum(const QByteArray &data); + static quint32 checksum(QIODevice *dev); + + bool isConnected() const; + quint16 nextTransactionId(); + +public slots: + void sendMessage(const Message &msg); + +signals: + void connected(); + void disconnected(); + void messageReceived(const Message &msg); + +private: + Message unpackMessage(const QByteArray &data); + QByteArray packMessage(const Message &msg); + +private slots: + void tryConnect(); + void handleSocketConnected(); + void handleSocketDisconnected(); + void handleSocketError(QBluetoothSocket::SocketError error); + void handleSocketData(); + +private: + QBluetoothAddress _address; + QBluetoothSocket *_socket; + QTimer *_reconnectTimer; + quint16 _lastTransactionId; +}; + +inline ToqConnection::Message::Message() +{ +} + +inline ToqConnection::Message::Message(Endpoint source, Endpoint destination, quint16 transactionId, quint32 type, QJsonDocument payload) + : source(source), destination(destination), transactionId(transactionId), type(type), payload(payload) +{ +} + +#endif // TOQCONNECTION_H diff --git a/saltoqd/toqmanager.cpp b/saltoqd/toqmanager.cpp new file mode 100644 index 0000000..2e0d967 --- /dev/null +++ b/saltoqd/toqmanager.cpp @@ -0,0 +1,73 @@ +#include "toqmanager.h" + +#include "versionmanager.h" +#include "systemmanager.h" +#include "storagemanager.h" +#include "musicmanager.h" +#include "commmanager.h" + +ToqManager::ToqManager(const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + _conn(new ToqConnection(address, this)), + _versionManager(new VersionManager(this)), + _systemManager(new SystemManager(this)), + _storageManager(new StorageManager(this)), + _musicManager(new MusicManager(this)), + _commManager(new CommManager(_storageManager, this)) +{ + connect(_conn, &ToqConnection::messageReceived, + this, &ToqManager::handleToqMessage); +} + +void ToqManager::setEndpointListener(ToqConnection::Endpoint ep, EndpointHandler *handler) +{ + Q_ASSERT(!_handlers.contains(ep)); + _handlers.insert(ep, handler); +} + +void ToqManager::sendMessage(const ToqConnection::Message &msg) +{ + if (1) { + QString json = QString::fromUtf8(msg.payload.toJson(QJsonDocument::Compact)); + qDebug() << "Sending message to" << ToqConnection::nameOfEndpoint(msg.destination) << "from" << ToqConnection::nameOfEndpoint(msg.destination) << "type" << msg.type << json; + } + _conn->sendMessage(msg); +} + +void ToqManager::sendMessage(ToqConnection::Endpoint source, ToqConnection::Endpoint destination, quint16 transactionId, quint32 type, const QJsonObject &payload) +{ + QJsonDocument doc(payload); + ToqConnection::Message msg(source, destination, transactionId, type, doc); + _conn->sendMessage(msg); +} + +quint16 ToqManager::sendMessage(ToqConnection::Endpoint source, ToqConnection::Endpoint destination, quint32 type, const QJsonObject &payload) +{ + QJsonDocument doc(payload); + quint16 transactionId = _conn->nextTransactionId(); + ToqConnection::Message msg(source, destination, transactionId, type, doc); + _conn->sendMessage(msg); + return transactionId; +} + +void ToqManager::sendReply(const ToqConnection::Message &msg, quint32 type, const QJsonObject &payload) +{ + ToqConnection::Message reply(msg.destination, msg.source, msg.transactionId, type, QJsonDocument(payload)); + _conn->sendMessage(reply); +} + +void ToqManager::handleToqMessage(const ToqConnection::Message &msg) +{ + EndpointHandler *handler = _handlers.value(msg.destination, 0); + + if (1) { + QString json = QString::fromUtf8(msg.payload.toJson(QJsonDocument::Compact)); + qDebug() << "Received message to" << ToqConnection::nameOfEndpoint(msg.destination) << "from" << ToqConnection::nameOfEndpoint(msg.destination) << "type" << msg.type << json; + } + + if (handler) { + handler->handleMessage(msg); + } else { + qWarning() << "No registered handler for endpoint" << ToqConnection::nameOfEndpoint(msg.destination); + } +} diff --git a/saltoqd/toqmanager.h b/saltoqd/toqmanager.h new file mode 100644 index 0000000..0edd6ec --- /dev/null +++ b/saltoqd/toqmanager.h @@ -0,0 +1,54 @@ +#ifndef TOQMANAGER_H +#define TOQMANAGER_H + +#include +#include + +#include "toqconnection.h" + +class VersionManager; +class SystemManager; +class StorageManager; +class MusicManager; +class CommManager; + +class ToqManager : public QObject +{ + Q_OBJECT +public: + explicit ToqManager(const QBluetoothAddress &address, QObject *parent = 0); + + struct EndpointHandler { + virtual void handleMessage(const ToqConnection::Message &msg) = 0; + }; + + void setEndpointListener(ToqConnection::Endpoint ep, EndpointHandler *handler); + + bool isConnected() const; + void sendMessage(const ToqConnection::Message &msg); + void sendMessage(ToqConnection::Endpoint source, ToqConnection::Endpoint destination, + quint16 transactionId, quint32 type, const QJsonObject &payload); + quint16 sendMessage(ToqConnection::Endpoint source, ToqConnection::Endpoint destination, + quint32 type, const QJsonObject &payload); + void sendReply(const ToqConnection::Message &msg, quint32 type, const QJsonObject &payload); + +private slots: + void handleToqMessage(const ToqConnection::Message &msg); + +private: + ToqConnection *_conn; + QHash _handlers; + + VersionManager *_versionManager; + SystemManager *_systemManager; + StorageManager *_storageManager; + MusicManager *_musicManager; + CommManager *_commManager; +}; + +inline bool ToqManager::isConnected() const +{ + return _conn->isConnected(); +} + +#endif // TOQMANAGER_H diff --git a/saltoqd/versionmanager.cpp b/saltoqd/versionmanager.cpp new file mode 100644 index 0000000..8c88992 --- /dev/null +++ b/saltoqd/versionmanager.cpp @@ -0,0 +1,32 @@ +#include "versionmanager.h" + +VersionManager::VersionManager(ToqManager *toq) : + QObject(toq), _toq(toq) +{ + _toq->setEndpointListener(ToqConnection::VersionEndpoint, this); +} + +void VersionManager::handleMessage(const ToqConnection::Message &msg) +{ + Q_ASSERT(msg.destination == ToqConnection::VersionEndpoint); + switch (msg.type) { + case 0: + handleVersionMessage(msg); + break; + default: + qWarning() << "Unknown version message" << msg.type; + break; + } +} + +void VersionManager::handleVersionMessage(const ToqConnection::Message &msg) +{ + QJsonObject root = msg.payload.object(); + qDebug() << "Remote AlohaVersion: " << root["AlohaVersion"].toString(); + + QJsonObject reply; + reply.insert("PhoneType", QJsonValue(QLatin1String("Android"))); + reply.insert("SoftwareRelease", QJsonValue(QLatin1String("4.4.2"))); + + _toq->sendReply(msg, 1, reply); +} diff --git a/saltoqd/versionmanager.h b/saltoqd/versionmanager.h new file mode 100644 index 0000000..11c0173 --- /dev/null +++ b/saltoqd/versionmanager.h @@ -0,0 +1,21 @@ +#ifndef VERSIONMANAGER_H +#define VERSIONMANAGER_H + +#include "toqmanager.h" + +class VersionManager : public QObject, public ToqManager::EndpointHandler +{ + Q_OBJECT +public: + explicit VersionManager(ToqManager *toq); + + void handleMessage(const ToqConnection::Message &msg) Q_DECL_OVERRIDE; + +private: + void handleVersionMessage(const ToqConnection::Message &msg); + +private: + ToqManager *_toq; +}; + +#endif // VERSIONMANAGER_H diff --git a/saltoqd/voicecallmanager.cpp b/saltoqd/voicecallmanager.cpp new file mode 100644 index 0000000..3ef9223 --- /dev/null +++ b/saltoqd/voicecallmanager.cpp @@ -0,0 +1,73 @@ +#include +#include "voicecallmanager.h" +#include "voicecallmanager_interface.h" + +static OrgNemomobileVoicecallVoiceCallManagerInterface *vcm = NULL; + +VoiceCallManager::VoiceCallManager(ToqManager *toq) : + QObject(toq), _toq(toq) +{ + if (!vcm) { + vcm = new OrgNemomobileVoicecallVoiceCallManagerInterface("org.nemomobile.voicecall", + "/", + QDBusConnection::sessionBus()); + } + + _toq->setEndpointListener(ToqConnection::VoiceCallEndpoint, this); +} + +void VoiceCallManager::handleMessage(const ToqConnection::Message &msg) +{ + Q_ASSERT(msg.destination == ToqConnection::VoiceCallEndpoint); + switch (msg.type) { + case 15: + handleGetPhoneStatusMessage(msg); + break; + default: + qWarning() << "Unknown message" << msg.type; + break; + } +} + +void VoiceCallManager::setSilentMode(bool silent) +{ + setProfile(silent ? "silent" : "ambience"); +} + +void VoiceCallManager::handleGetPhoneStatusMessage(const ToqConnection::Message &msg) +{ + QJsonObject obj; + obj.insert("service", int(1)); + obj.insert("call_status", int(0)); + obj.insert("call_setup_status", int(0)); + obj.insert("silence_mode", getCurrentProfile() == "silent" ? 1 : 0); + + _toq->sendReply(msg, 0x400F, obj); +} + +QString VoiceCallManager::getCurrentProfile() +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusReply resp = bus.call(QDBusMessage::createMethodCall("com.nokia.profiled", + "/com/nokia/profiled", + "com.nokia.profiled", + "get_profile")); + if (resp.isValid()) { + return resp.value(); + } else { + qWarning() << resp.error().message(); + return QString(); + } +} + +void VoiceCallManager::setProfile(const QString &name) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusReply resp = bus.call(QDBusMessage::createMethodCall("com.nokia.profiled", + "/com/nokia/profiled", + "com.nokia.profiled", + "set_profile") << name); + if (!resp.isValid()) { + qWarning() << resp.error().message(); + } +} diff --git a/saltoqd/voicecallmanager.h b/saltoqd/voicecallmanager.h new file mode 100644 index 0000000..eb9e38e --- /dev/null +++ b/saltoqd/voicecallmanager.h @@ -0,0 +1,26 @@ +#ifndef VOICECALLMANAGER_H +#define VOICECALLMANAGER_H + +#include "toqmanager.h" + +class VoiceCallManager : public QObject, public ToqManager::EndpointHandler +{ + Q_OBJECT +public: + explicit VoiceCallManager(ToqManager *toq); + + void handleMessage(const ToqConnection::Message &msg) Q_DECL_OVERRIDE; + + static void setSilentMode(bool silent); + +private: + void handleGetPhoneStatusMessage(const ToqConnection::Message &msg); + + static QString getCurrentProfile(); + static void setProfile(const QString &name); + +private: + ToqManager *_toq; +}; + +#endif // VOICECALLMANAGER_H -- cgit v1.2.3