From 67e85358bc789e93f3f17527d9f721d6a9e5f94d Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Nov 2014 21:08:56 +0100 Subject: introduce the appmanager --- daemon/appmanager.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 daemon/appmanager.cpp (limited to 'daemon/appmanager.cpp') diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp new file mode 100644 index 0000000..b4a3d68 --- /dev/null +++ b/daemon/appmanager.cpp @@ -0,0 +1,15 @@ +#include +#include +#include "appmanager.h" + +AppManager::AppManager(QObject *parent) + : QObject(parent) +{ +} + +QString AppManager::getAppDir(const QUuid& uuid) const +{ + return QStandardPaths::locate(QStandardPaths::DataLocation, + QString("apps/%1").arg(uuid.toString()), + QStandardPaths::LocateDirectory); +} -- cgit v1.2.3 From 49f1261bf9d635d5e3d881e87a93ed4e76abfe90 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 17:27:08 +0100 Subject: allow receiving responses to commands in watchconnector * the skeleton is in place for watchconnector to allow query->response messages. I've used call/cc style because it is impossible to make QBluetoothSocket synchronous (waitForReadyRead() is a no-op) * remove watchcommands, instead create musicmanager to listen for the music endpoint. The other (simpler) endpoints are now listened in watchconnector itself. hangupAll() slot is moved to voicecallmanager. * instead of emitting signals for each received message, listeners can now register for receiving messages targeted towards a given endpoint * when reading from bluetoothsocket, properly handle short reads * remove useless 'watch' namespace * create appmanager, which mantains a database of installed apps (installed on the phone, that is; watch installed apps will come later) * all the *Managers are now instantiated by the main Manager itself * introduce Unpacker helper class for decoding watch messages * implement getAppbankStatus and getAppbankUuids messages and response parsers * remove file logging for now (20MB is bad for eMMC!) * use dbus object path /org/pebbled instead of / --- app/pebbledinterface.cpp | 2 +- daemon/appmanager.cpp | 109 +++++++++++++++++++++-- daemon/appmanager.h | 31 ++++++- daemon/daemon.cpp | 10 +-- daemon/daemon.pro | 9 +- daemon/dbusadaptor.cpp | 5 +- daemon/dbusadaptor.h | 2 + daemon/manager.cpp | 66 ++++++++++---- daemon/manager.h | 21 +++-- daemon/musicmanager.cpp | 97 ++++++++++++++++++++ daemon/musicmanager.h | 25 ++++++ daemon/unpacker.h | 87 ++++++++++++++++++ daemon/voicecallmanager.cpp | 7 ++ daemon/voicecallmanager.h | 1 + daemon/watchcommands.cpp | 122 ------------------------- daemon/watchcommands.h | 31 ------- daemon/watchconnector.cpp | 211 ++++++++++++++++++++++++++++++++++++-------- daemon/watchconnector.h | 57 +++++++----- log4qt-debug.conf | 4 +- log4qt-release.conf | 4 +- 20 files changed, 637 insertions(+), 264 deletions(-) create mode 100644 daemon/musicmanager.cpp create mode 100644 daemon/musicmanager.h create mode 100644 daemon/unpacker.h delete mode 100644 daemon/watchcommands.cpp delete mode 100644 daemon/watchcommands.h (limited to 'daemon/appmanager.cpp') diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 05ca614..c6f5674 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -2,7 +2,7 @@ QString PebbledInterface::PEBBLED_SYSTEMD_UNIT("pebbled.service"); QString PebbledInterface::PEBBLED_DBUS_SERVICE("org.pebbled"); -QString PebbledInterface::PEBBLED_DBUS_PATH("/"); +QString PebbledInterface::PEBBLED_DBUS_PATH("/org/pebbled"); QString PebbledInterface::PEBBLED_DBUS_IFACE("org.pebbled"); #define PebbledDbusInterface QDBusInterface(PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE) diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index b4a3d68..34af3af 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -1,15 +1,112 @@ #include -#include +#include +#include +#include #include "appmanager.h" AppManager::AppManager(QObject *parent) - : QObject(parent) + : QObject(parent), + _watcher(new QFileSystemWatcher(this)) { + connect(_watcher, &QFileSystemWatcher::directoryChanged, + this, &AppManager::rescan); + + QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + if (!dataDir.exists("apps")) { + if (!dataDir.mkdir("apps")) { + logger()->warn() << "could not create dir" << dataDir.absoluteFilePath("apps"); + } + } + logger()->debug() << "install apps in" << dataDir.absoluteFilePath("apps"); + + rescan(); +} + +QStringList AppManager::appPaths() const +{ + return QStandardPaths::locateAll(QStandardPaths::DataLocation, + QLatin1String("apps"), + QStandardPaths::LocateDirectory); +} + +void AppManager::rescan() +{ + QStringList watchedDirs = _watcher->directories(); + if (!watchedDirs.isEmpty()) _watcher->removePaths(watchedDirs); + QStringList watchedFiles = _watcher->files(); + if (!watchedFiles.isEmpty()) _watcher->removePaths(watchedFiles); + _apps.clear(); + _names.clear(); + + Q_FOREACH(const QString &path, appPaths()) { + QDir dir(path); + _watcher->addPath(dir.absolutePath()); + logger()->debug() << "scanning dir" << dir.absolutePath(); + QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Executable); + logger()->debug() << "scanning dir results" << entries; + Q_FOREACH(const QString &path, entries) { + QString appPath = dir.absoluteFilePath(path); + _watcher->addPath(appPath); + if (dir.exists(path + "/appinfo.json")) { + _watcher->addPath(appPath + "/appinfo.json"); + scanApp(appPath); + } + } + } + + logger()->debug() << "now watching" << _watcher->directories() << _watcher->files(); } -QString AppManager::getAppDir(const QUuid& uuid) const +void AppManager::scanApp(const QString &path) { - return QStandardPaths::locate(QStandardPaths::DataLocation, - QString("apps/%1").arg(uuid.toString()), - QStandardPaths::LocateDirectory); + logger()->debug() << "scanning app" << path; + QDir appDir(path); + if (!appDir.isReadable()) { + logger()->warn() << "app" << appDir.absolutePath() << "is not readable"; + return; + } + + QFile appInfoFile(path + "/appinfo.json"); + if (!appInfoFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + logger()->warn() << "cannot open app info file" << appInfoFile.fileName() << ":" + << appInfoFile.errorString(); + return; + } + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(appInfoFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + logger()->warn() << "cannot parse app info file" << appInfoFile.fileName() << ":" + << parseError.errorString(); + return; + } + + const QJsonObject root = doc.object(); + AppInfo info; + info.uuid = QUuid(root["uuid"].toString()); + info.shortName = root["shortName"].toString(); + info.longName = root["longName"].toString(); + info.company = root["companyName"].toString(); + info.versionCode = root["versionCode"].toInt(); + info.versionLabel = root["versionLabel"].toString(); + + const QJsonObject watchapp = root["watchapp"].toObject(); + info.isWatchface = watchapp["watchface"].toBool(); + info.isJSKit = appDir.exists("pebble-js-app.js"); + + const QJsonObject appkeys = root["appKeys"].toObject(); + for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { + info.appKeys.insert(it.key(), it.value().toInt()); + } + + if (info.uuid.isNull() || info.shortName.isEmpty()) { + logger()->warn() << "invalid or empty uuid/name in" << appInfoFile.fileName(); + return; + } + + _apps.insert(info.uuid, info); + _names.insert(info.shortName, info.uuid); + + const char *type = info.isWatchface ? "watchface" : "app"; + logger()->debug() << "found installed" << type << info.shortName << info.versionLabel << "with uuid" << info.uuid.toString(); } diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 59d0bd7..5e150ab 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -2,19 +2,46 @@ #define APPMANAGER_H #include +#include +#include +#include +#include class AppManager : public QObject { Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER public: explicit AppManager(QObject *parent = 0); + struct AppInfo { + QUuid uuid; + QString shortName; + QString longName; + QString company; + int versionCode; + QString versionLabel; + bool isWatchface; + bool isJSKit; + QHash appKeys; + QString path; + }; + + QStringList appPaths() const; + bool installPebbleApp(const QString &pbwFile); - QList allInstalledApps() const; +public slots: + void rescan(); + +private: + void scanApp(const QString &path); - QString getAppDir(const QUuid &uuid) const; +private: + QFileSystemWatcher *_watcher; + QHash _apps; + QHash _names; }; #endif // APPMANAGER_H diff --git a/daemon/daemon.cpp b/daemon/daemon.cpp index 80ae667..c9456c6 100644 --- a/daemon/daemon.cpp +++ b/daemon/daemon.cpp @@ -78,17 +78,11 @@ int main(int argc, char *argv[]) Log4Qt::Logger::logger(QLatin1String("Main Logger"))->info() << argv[0] << APP_VERSION; Settings settings; - watch::WatchConnector watch; - DBusConnector dbus; - VoiceCallManager voice(&settings); - NotificationManager notifications(&settings); - AppManager apps(&settings); - Manager manager(&watch, &dbus, &voice, ¬ifications, &apps, &settings); + Manager manager(&settings); + Q_UNUSED(manager); signal(SIGINT, signalhandler); signal(SIGTERM, signalhandler); - QObject::connect(&app, SIGNAL(aboutToQuit()), &watch, SLOT(endPhoneCall())); - QObject::connect(&app, SIGNAL(aboutToQuit()), &watch, SLOT(disconnect())); return app.exec(); } diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 1799d18..768b50e 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -21,8 +21,8 @@ SOURCES += \ watchconnector.cpp \ dbusconnector.cpp \ dbusadaptor.cpp \ - watchcommands.cpp \ - appmanager.cpp + appmanager.cpp \ + musicmanager.cpp HEADERS += \ manager.h \ @@ -32,9 +32,10 @@ HEADERS += \ watchconnector.h \ dbusconnector.h \ dbusadaptor.h \ - watchcommands.h \ settings.h \ - appmanager.h + appmanager.h \ + musicmanager.h \ + unpacker.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/dbusadaptor.cpp b/daemon/dbusadaptor.cpp index 3332551..7bbf623 100644 --- a/daemon/dbusadaptor.cpp +++ b/daemon/dbusadaptor.cpp @@ -75,10 +75,13 @@ void PebbledAdaptor::time() QMetaObject::invokeMethod(parent(), "time"); } - void PebbledAdaptor::reconnect() { // handle method call org.pebbled.reconnect QMetaObject::invokeMethod(parent(), "reconnect"); } +void PebbledAdaptor::test() +{ + QMetaObject::invokeMethod(parent(), "test"); +} diff --git a/daemon/dbusadaptor.h b/daemon/dbusadaptor.h index 715a41b..54a0963 100644 --- a/daemon/dbusadaptor.h +++ b/daemon/dbusadaptor.h @@ -46,6 +46,7 @@ class PebbledAdaptor: public QDBusAbstractAdaptor " \n" " \n" " \n" +" \n" " \n" "") public: @@ -70,6 +71,7 @@ public Q_SLOTS: // METHODS void ping(int val); void time(); void reconnect(); + void test(); Q_SIGNALS: // SIGNALS void connectedChanged(); void pebbleChanged(); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 7761864..8a8acf4 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -5,9 +5,15 @@ #include #include -Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, AppManager *apps, Settings *settings) : - QObject(0), watch(watch), dbus(dbus), voice(voice), notifications(notifications), apps(apps), - commands(new WatchCommands(watch, this)), settings(settings), notification(MNotification::DeviceEvent) +Manager::Manager(Settings *settings, QObject *parent) : + QObject(parent), settings(settings), + watch(new WatchConnector(this)), + dbus(new DBusConnector(this)), + voice(new VoiceCallManager(settings, this)), + notifications(new NotificationManager(settings, this)), + music(new MusicManager(watch, this)), + apps(new AppManager(this)), + notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged())); @@ -22,6 +28,24 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan numberFilter.setMatchFlags(QContactFilter::MatchPhoneNumber); connect(watch, SIGNAL(connectedChanged()), SLOT(onConnectedChanged())); + watch->setEndpointHandler(WatchConnector::watchPHONE_VERSION, + [this](const QByteArray& data) { + Q_UNUSED(data); + watch->sendPhoneVersion(); + return true; + }); + watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, + [this](const QByteArray& data) { + if (data.at(0) == WatchConnector::callHANGUP) { + voice->hangupAll(); + } + return true; + }); + watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, + [this](const QByteArray& data) { + //logger()->debug() << data.toHex(); + return true; + }); connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); @@ -32,13 +56,10 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan connect(notifications, SIGNAL(twitterNotify(const QString &,const QString &)), SLOT(onTwitterNotify(const QString &,const QString &))); connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); - connect(watch, SIGNAL(messageDecoded(uint,QByteArray)), commands, SLOT(processMessage(uint,QByteArray))); - connect(commands, SIGNAL(hangup()), SLOT(hangupAll())); - PebbledProxy *proxy = new PebbledProxy(this); PebbledAdaptor *adaptor = new PebbledAdaptor(proxy); QDBusConnection session = QDBusConnection::sessionBus(); - session.registerObject("/", proxy); + session.registerObject("/org/pebbled", proxy); session.registerService("org.pebbled"); connect(dbus, SIGNAL(pebbleChanged()), adaptor, SIGNAL(pebbleChanged())); connect(watch, SIGNAL(connectedChanged()), adaptor, SIGNAL(connectedChanged())); @@ -52,7 +73,7 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onMprisPropertiesChanged(QString,QMap,QStringList))); - connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), commands, SLOT(onMprisMetadataChanged(QVariantMap))); + connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), music, SLOT(onMprisMetadataChanged(QVariantMap))); // Set BT icon for notification notification.setImage("icon-system-bluetooth-device"); @@ -62,7 +83,10 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan connect(dbus, SIGNAL(pebbleChanged()), SLOT(onPebbleChanged())); dbus->findPebble(); } +} +Manager::~Manager() +{ } void Manager::onSettingChanged(const QString &key) @@ -247,13 +271,6 @@ void Manager::onEmailNotify(const QString &sender, const QString &data,const QSt watch->sendEmailNotification(sender, data, subject); } -void Manager::hangupAll() -{ - foreach (VoiceCallHandler* handler, voice->voiceCalls()) { - handler->hangup(); - } -} - void Manager::onMprisPropertiesChanged(QString interface, QMap changed, QStringList invalidated) { logger()->debug() << interface << changed << invalidated; @@ -369,3 +386,22 @@ void Manager::transliterateMessage(const QString &text) logger()->debug() << "String after transliteration:" << text; } } + +bool Manager::uploadApp(const QUuid &uuid, int slot) +{ + // TODO + return false; +} + +void Manager::test() +{ + logger()->debug() << "Starting test"; + + watch->getAppbankStatus([this](const QString &s) { + logger()->debug() << "Callback invoked" << s; + }); + + watch->getAppbankUuids([this](const QList &uuids) { + logger()->debug() << "Callback invoked. UUIDs:" << uuids.size(); + }); +} diff --git a/daemon/manager.h b/daemon/manager.h index 5946bf0..b210700 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -5,8 +5,8 @@ #include "dbusconnector.h" #include "voicecallmanager.h" #include "notificationmanager.h" +#include "musicmanager.h" #include "appmanager.h" -#include "watchcommands.h" #include "settings.h" #include @@ -35,16 +35,15 @@ class Manager : QBluetoothLocalDevice btDevice; - watch::WatchConnector *watch; + Settings *settings; + + WatchConnector *watch; DBusConnector *dbus; VoiceCallManager *voice; NotificationManager *notifications; + MusicManager *music; AppManager *apps; - WatchCommands *commands; - - Settings *settings; - MNotification notification; QContactManager *contacts; @@ -57,7 +56,8 @@ class Manager : QScopedPointer transliterator; public: - explicit Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, AppManager *apps, Settings *settings); + explicit Manager(Settings *settings, QObject *parent = 0); + ~Manager(); Q_INVOKABLE QString findPersonByNumber(QString number); Q_INVOKABLE QString getCurrentProfile(); @@ -65,6 +65,8 @@ public: QVariantMap mprisMetadata; QVariantMap getMprisMetadata() { return mprisMetadata; } + Q_INVOKABLE bool uploadApp(const QUuid &uuid, int slot = -1); + protected: void transliterateMessage(const QString &text); @@ -72,10 +74,10 @@ signals: void mprisMetadataChanged(QVariantMap); public slots: - void hangupAll(); void applyProfile(); -protected slots: +private slots: + void test(); void onSettingChanged(const QString &key); void onSettingsChanged(); void onPebbleChanged(); @@ -114,6 +116,7 @@ public slots: void time() { static_cast(parent())->watch->time(); } void disconnect() { static_cast(parent())->watch->disconnect(); } void reconnect() { static_cast(parent())->watch->reconnect(); } + void test() { static_cast(parent())->test(); } }; diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp new file mode 100644 index 0000000..abea715 --- /dev/null +++ b/daemon/musicmanager.cpp @@ -0,0 +1,97 @@ +#include +#include "musicmanager.h" + +MusicManager::MusicManager(WatchConnector *watch, QObject *parent) + : QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchMUSIC_CONTROL, [this](const QByteArray& data) { + musicControl(WatchConnector::MusicControl(data.at(0))); + return true; + }); +} + +void MusicManager::onMprisMetadataChanged(QVariantMap metadata) +{ + QString track = metadata.value("xesam:title").toString(); + QString album = metadata.value("xesam:album").toString(); + QString artist = metadata.value("xesam:artist").toString(); + logger()->debug() << __FUNCTION__ << track << album << artist; + watch->sendMusicNowPlaying(track, album, artist); +} + +void MusicManager::musicControl(WatchConnector::MusicControl operation) +{ + logger()->debug() << "Operation:" << operation; + + QString mpris = parent()->property("mpris").toString(); + if (mpris.isEmpty()) { + logger()->debug() << "No mpris interface active"; + return; + } + + QString method; + + switch(operation) { + case WatchConnector::musicPLAY_PAUSE: + method = "PlayPause"; + break; + case WatchConnector::musicPAUSE: + method = "Pause"; + break; + case WatchConnector::musicPLAY: + method = "Play"; + break; + case WatchConnector::musicNEXT: + method = "Next"; + break; + case WatchConnector::musicPREVIOUS: + method = "Previous"; + break; + case WatchConnector::musicVOLUME_UP: + case WatchConnector::musicVOLUME_DOWN: { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusReply VolumeReply = bus.call( + QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get") + << "org.mpris.MediaPlayer2.Player" << "Volume"); + if (VolumeReply.isValid()) { + double volume = VolumeReply.value().variant().toDouble(); + if (operation == WatchConnector::musicVOLUME_UP) { + volume += 0.1; + } + else { + volume -= 0.1; + } + logger()->debug() << "Setting volume" << volume; + QDBusError err = QDBusConnection::sessionBus().call( + QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set") + << "org.mpris.MediaPlayer2.Player" << "Volume" << QVariant::fromValue(QDBusVariant(volume))); + if (err.isValid()) { + logger()->error() << err.message(); + } + } else { + logger()->error() << VolumeReply.error().message(); + } + } + return; + case WatchConnector::musicGET_NOW_PLAYING: + onMprisMetadataChanged(parent()->property("mprisMetadata").toMap()); + return; + + case WatchConnector::musicSEND_NOW_PLAYING: + logger()->warn() << "Operation" << operation << "not supported"; + return; + } + + if (method.isEmpty()) { + logger()->error() << "Requested unsupported operation" << operation; + return; + } + + logger()->debug() << operation << "->" << method; + + QDBusError err = QDBusConnection::sessionBus().call( + QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method)); + if (err.isValid()) { + logger()->error() << err.message(); + } +} diff --git a/daemon/musicmanager.h b/daemon/musicmanager.h new file mode 100644 index 0000000..ca86ce3 --- /dev/null +++ b/daemon/musicmanager.h @@ -0,0 +1,25 @@ +#ifndef MUSICMANAGER_H +#define MUSICMANAGER_H + +#include +#include "watchconnector.h" + +class MusicManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit MusicManager(WatchConnector *watch, QObject *parent = 0); + +private: + void musicControl(WatchConnector::MusicControl operation); + +private slots: + void onMprisMetadataChanged(QVariantMap metadata); + +private: + WatchConnector *watch; +}; + +#endif // MUSICMANAGER_H diff --git a/daemon/unpacker.h b/daemon/unpacker.h new file mode 100644 index 0000000..94908cb --- /dev/null +++ b/daemon/unpacker.h @@ -0,0 +1,87 @@ +#ifndef UNPACKER_H +#define UNPACKER_H + +#include +#include +#include +#include + +class Unpacker +{ +public: + Unpacker(const QByteArray &data); + + template + T read(); + + QString readFixedString(int n); + + QUuid readUuid(); + + void skip(int n); + + bool bad() const; + +private: + const uchar * p(); + bool checkBad(int n = 0); + + const QByteArray &_buf; + int _offset; + bool _bad; +}; + +inline Unpacker::Unpacker(const QByteArray &data) + : _buf(data), _offset(0), _bad(false) +{ +} + +template +inline T Unpacker::read() +{ + if (checkBad(sizeof(T))) return 0; + const uchar *u = p(); + _offset += sizeof(T); + return qFromBigEndian(u); +} + +inline QString Unpacker::readFixedString(int n) +{ + if (checkBad(n)) return QString(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QString::fromUtf8(u, strnlen(u, n)); +} + +inline QUuid Unpacker::readUuid() +{ + if (checkBad(16)) return QString(); + _offset += 16; + return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); +} + +inline void Unpacker::skip(int n) +{ + _offset += n; + checkBad(); +} + +inline bool Unpacker::bad() const +{ + return _bad; +} + +inline const uchar * Unpacker::p() +{ + return reinterpret_cast(&_buf.constData()[_offset]); +} + +inline bool Unpacker::checkBad(int n) +{ + if (_offset + n > _buf.size()) { + _bad = true; + } + return _bad; +} + +#endif // UNPACKER_H diff --git a/daemon/voicecallmanager.cpp b/daemon/voicecallmanager.cpp index ac03b51..9fd4339 100644 --- a/daemon/voicecallmanager.cpp +++ b/daemon/voicecallmanager.cpp @@ -148,6 +148,13 @@ void VoiceCallManager::dial(const QString &provider, const QString &msisdn) QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*))); } +void VoiceCallManager::hangupAll() +{ + foreach (VoiceCallHandler* handler, voiceCalls()) { + handler->hangup(); + } +} + void VoiceCallManager::silenceRingtone() { Q_D(const VoiceCallManager); diff --git a/daemon/voicecallmanager.h b/daemon/voicecallmanager.h index b7241ef..5c21269 100644 --- a/daemon/voicecallmanager.h +++ b/daemon/voicecallmanager.h @@ -80,6 +80,7 @@ Q_SIGNALS: public Q_SLOTS: void dial(const QString &providerId, const QString &msisdn); + void hangupAll(); void silenceRingtone(); diff --git a/daemon/watchcommands.cpp b/daemon/watchcommands.cpp deleted file mode 100644 index b16efb5..0000000 --- a/daemon/watchcommands.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "watchcommands.h" - -#include -#include -#include - -using namespace watch; - -WatchCommands::WatchCommands(WatchConnector *watch, QObject *parent) : - QObject(parent), watch(watch) -{} - -void WatchCommands::processMessage(uint endpoint, QByteArray data) -{ - logger()->debug() << __FUNCTION__ << endpoint << "/" << data.toHex() << data.length(); - switch (endpoint) { - case WatchConnector::watchPHONE_VERSION: - watch->sendPhoneVersion(); - break; - case WatchConnector::watchPHONE_CONTROL: - if (data.at(0) == WatchConnector::callHANGUP) { - emit hangup(); - } - break; - case WatchConnector::watchMUSIC_CONTROL: - musicControl(WatchConnector::MusicControl(data.at(0))); - break; - case WatchConnector::watchSYSTEM_MESSAGE: - logger()->info() << "Got SYSTEM_MESSAGE" << WatchConnector::SystemMessage(data.at(0)); - // TODO: handle systemBLUETOOTH_START_DISCOVERABLE/systemBLUETOOTH_END_DISCOVERABLE - break; - - default: - logger()->info() << __FUNCTION__ << "endpoint" << endpoint << "not supported yet"; - } -} - -void WatchCommands::onMprisMetadataChanged(QVariantMap metadata) -{ - QString track = metadata.value("xesam:title").toString(); - QString album = metadata.value("xesam:album").toString(); - QString artist = metadata.value("xesam:artist").toString(); - logger()->debug() << __FUNCTION__ << track << album << artist; - watch->sendMusicNowPlaying(track, album, artist); -} - -void WatchCommands::musicControl(WatchConnector::MusicControl operation) -{ - logger()->debug() << "Operation:" << operation; - - QString mpris = parent()->property("mpris").toString(); - if (mpris.isEmpty()) { - logger()->debug() << "No mpris interface active"; - return; - } - - QString method; - - switch(operation) { - case WatchConnector::musicPLAY_PAUSE: - method = "PlayPause"; - break; - case WatchConnector::musicPAUSE: - method = "Pause"; - break; - case WatchConnector::musicPLAY: - method = "Play"; - break; - case WatchConnector::musicNEXT: - method = "Next"; - break; - case WatchConnector::musicPREVIOUS: - method = "Previous"; - break; - case WatchConnector::musicVOLUME_UP: - case WatchConnector::musicVOLUME_DOWN: { - QDBusReply VolumeReply = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get") - << "org.mpris.MediaPlayer2.Player" << "Volume"); - if (VolumeReply.isValid()) { - double volume = VolumeReply.value().variant().toDouble(); - if (operation == WatchConnector::musicVOLUME_UP) { - volume += 0.1; - } - else { - volume -= 0.1; - } - logger()->debug() << "Setting volume" << volume; - QDBusError err = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set") - << "org.mpris.MediaPlayer2.Player" << "Volume" << QVariant::fromValue(QDBusVariant(volume))); - if (err.isValid()) { - logger()->error() << err.message(); - } - } - else { - logger()->error() << VolumeReply.error().message(); - } - } - return; - case WatchConnector::musicGET_NOW_PLAYING: - onMprisMetadataChanged(parent()->property("mprisMetadata").toMap()); - return; - - case WatchConnector::musicSEND_NOW_PLAYING: - logger()->warn() << "Operation" << operation << "not supported"; - return; - } - - if (method.isEmpty()) { - logger()->error() << "Requested unsupported operation" << operation; - return; - } - - logger()->debug() << operation << "->" << method; - - QDBusError err = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method)); - if (err.isValid()) { - logger()->error() << err.message(); - } -} diff --git a/daemon/watchcommands.h b/daemon/watchcommands.h deleted file mode 100644 index 8626b7c..0000000 --- a/daemon/watchcommands.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef WATCHCOMMANDS_H -#define WATCHCOMMANDS_H - -#include "watchconnector.h" -#include - -#include - -class WatchCommands : public QObject -{ - Q_OBJECT - LOG4QT_DECLARE_QCLASS_LOGGER - - watch::WatchConnector *watch; - -public: - explicit WatchCommands(watch::WatchConnector *watch, QObject *parent = 0); - -signals: - void hangup(); - -public slots: - void processMessage(uint endpoint, QByteArray data); - -protected slots: - void onMprisMetadataChanged(QVariantMap metadata); - void musicControl(watch::WatchConnector::MusicControl operation); - -}; - -#endif // WATCHCOMMANDS_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a240b04..61eeb67 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,11 +1,12 @@ -#include "watchconnector.h" -#include #include #include -using namespace watch; +#include "watchconnector.h" +#include "unpacker.h" + +static const int RECONNECT_TIMEOUT = 500; //ms -static int RECONNECT_TIMEOUT = 500; //ms +using std::function; WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) @@ -83,49 +84,95 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) QString WatchConnector::decodeEndpoint(uint val) { - QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoints")); + QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoint")); const char *endpoint = Endpoints.valueToKey(val); return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::decodeMsg(QByteArray data) +void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) { - //Sometimes pebble sends a "00", we ignore it without future action - if (data.length() == 1 && data.at(0) == 0) { - return; - } - - if (data.length() < 4) { - logger()->error() << "Can not decode message data length invalid: " << data.toHex(); - return; + if (func) { + handlers.insert(endpoint, func); + } else { + handlers.remove(endpoint); } +} - unsigned int datalen = 0; - int index = 0; - datalen = (data.at(index) << 8) + data.at(index+1); - index += 2; +void WatchConnector::clearEndpointHandler(uint endpoint) +{ + handlers.remove(endpoint); +} - unsigned int endpoint = 0; - endpoint = (data.at(index) << 8) + data.at(index+1); - index += 2; +bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) +{ + auto tmp_it = tmpHandlers.find(endpoint); + if (tmp_it != tmpHandlers.end()) { + QList& funcs = tmp_it.value(); + bool ok = false; + if (!funcs.empty()) { + if (funcs.first()(data)) { + ok = true; + funcs.removeFirst(); + } + } + if (funcs.empty()) { + tmpHandlers.erase(tmp_it); + } + if (ok) { + return true; + } + } - logger()->debug() << "Length:" << datalen << "Endpoint:" << decodeEndpoint(endpoint); - logger()->debug() << "Data:" << data.mid(index).toHex(); + auto it = handlers.find(endpoint); + if (it != handlers.end()) { + if (it.value() && it.value()(data)) { + return true; + } + } - emit messageDecoded(endpoint, data.mid(index, datalen)); + logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched"; + emit messageReceived(endpoint, data); + return false; } void WatchConnector::onReadSocket() { - logger()->debug() << "read"; + static const int header_length = 4; + + logger()->debug() << "readyRead bytesAvailable =" << socket->bytesAvailable(); QBluetoothSocket *socket = qobject_cast(sender()); - if (!socket) return; + Q_ASSERT(socket && socket == this->socket); + + while (socket->bytesAvailable() >= header_length) { + // Do nothing if there is no message to read. + if (socket->bytesAvailable() < header_length) { + if (socket->bytesAvailable() > 0) { + logger()->debug() << "incomplete header in read buffer"; + } + return; + } + + uchar header[header_length]; + socket->peek(reinterpret_cast(header), header_length); + + quint16 message_length, endpoint; + message_length = qFromBigEndian(&header[0]); + endpoint = qFromBigEndian(&header[sizeof(quint16)]); + + // Now wait for the entire message + if (socket->bytesAvailable() < header_length + message_length) { + logger()->debug() << "incomplete msg body in read buffer"; + return; + } + + socket->read(header_length); // Skip the header - while (socket->bytesAvailable()) { - QByteArray line = socket->readAll(); - emit messageReceived(socket->peerName(), QString::fromUtf8(line.constData(), line.length())); - decodeMsg(line); + QByteArray data = socket->read(message_length); + + logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); + + dispatchMessage(endpoint, data); } } @@ -181,13 +228,11 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error) void WatchConnector::sendData(const QByteArray &data) { - writeData = data; + writeData.append(data); if (socket == nullptr) { logger()->debug() << "No socket - reconnecting"; reconnect(); - return; - } - if (is_connected) { + } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; socket->write(data); } @@ -195,13 +240,13 @@ void WatchConnector::sendData(const QByteArray &data) void WatchConnector::onBytesWritten(qint64 bytes) { - writeData = writeData.mid(bytes); + writeData.remove(0, bytes); logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, QByteArray data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) { - logger()->debug() << "Sending message"; + logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; // First send the length @@ -390,3 +435,95 @@ void WatchConnector::endPhoneCall(uint cookie) { phoneControl(callEND, cookie, QStringList()); } + +void WatchConnector::getAppbankStatus(const std::function& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); + + tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_STATUS) { + return false; + } + logger()->debug() << "getAppbankStatus response" << data.toHex(); + + if (data.size() < 9) { + logger()->warn() << "invalid getAppbankStatus response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int num_banks = u.read(); + unsigned int apps_installed = u.read(); + + logger()->debug() << num_banks << "/" << apps_installed; + + for (unsigned int i = 0; i < apps_installed; i++) { + unsigned int id = u.read(); + unsigned int index = u.read(); + QString name = u.readFixedString(32); + QString company = u.readFixedString(32); + unsigned int flags = u.read(); + unsigned short version = u.read(); + + logger()->debug() << id << index << name << company << flags << version; + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + } + + logger()->debug() << "finished"; + + return true; + }); +} + +void WatchConnector::getAppbankUuids(const function &)>& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); + + tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + if (data.at(0) != appmgrGET_APPBANK_UUIDS) { + return false; + } + logger()->debug() << "getAppbankUuids response" << data.toHex(); + + if (data.size() < 5) { + logger()->warn() << "invalid getAppbankUuids response"; + return true; + } + + Unpacker u(data); + + u.skip(sizeof(quint8)); + + unsigned int apps_installed = u.read(); + + logger()->debug() << apps_installed; + + QList uuids; + + for (unsigned int i = 0; i < apps_installed; i++) { + QUuid uuid = u.readUuid(); + + logger()->debug() << uuid.toString(); + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + uuids.push_back(uuid); + } + + logger()->debug() << "finished"; + + callback(uuids); + + return true; + }); +} diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index b64e31b..01c3dac 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -30,6 +30,7 @@ #ifndef WATCHCONNECTOR_H #define WATCHCONNECTOR_H +#include #include #include #include @@ -39,25 +40,18 @@ #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) -using namespace QtBluetooth; -#endif - -namespace watch -{ - class WatchConnector : public QObject { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER - Q_ENUMS(Endpoints) + Q_ENUMS(Endpoint) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString connected READ isConnected NOTIFY connectedChanged) public: - enum Endpoints { + enum Endpoint { watchTIME = 11, watchVERSION = 16, watchPHONE_VERSION = 17, @@ -111,6 +105,11 @@ public: systemBLUETOOTH_START_DISCOVERABLE = 6, systemBLUETOOTH_END_DISCOVERABLE = 7 }; + enum AppManager { + appmgrGET_APPBANK_STATUS = 1, + appmgrGET_APPBANK_UUIDS = 5 + }; + enum { leadEMAIL = 0, leadSMS = 1, @@ -139,27 +138,41 @@ public: osLINUX = 4, osWINDOWS = 5 }; + enum { + DEFAULT_TIMEOUT_MSECS = 100 + }; + typedef std::function EndpointHandlerFunc; explicit WatchConnector(QObject *parent = 0); virtual ~WatchConnector(); - bool isConnected() const { return is_connected; } - QString name() const { return socket != nullptr ? socket->peerName() : ""; } - QString timeStamp(); - QString decodeEndpoint(uint val); + inline bool isConnected() const { return is_connected; } + inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + + void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void clearEndpointHandler(uint endpoint); + + static QString timeStamp(); + static QString decodeEndpoint(uint val); + + void getAppbankStatus(const std::function& callback); + void getAppbankUuids(const std::function &uuids)>& callback); signals: - void messageReceived(QString peer, QString msg); - void messageDecoded(uint endpoint, QByteArray data); + void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); void connectedChanged(); public slots: - void sendData(const QByteArray &data); - void sendMessage(uint endpoint, QByteArray data); + void deviceConnect(const QString &name, const QString &address); + void disconnect(); + void reconnect(); + + void sendMessage(uint endpoint, const QByteArray &data); void ping(uint val); void time(); + void sendNotification(uint lead, QString sender, QString data, QString subject); void sendSMSNotification(QString sender, QString data); void sendEmailNotification(QString sender, QString data, QString subject); @@ -176,8 +189,7 @@ public slots: void startPhoneCall(uint cookie=0); void endPhoneCall(uint cookie=0); - void deviceConnect(const QString &name, const QString &address); - void disconnect(); +private slots: void deviceDiscovered(const QBluetoothDeviceInfo&); void handleWatch(const QString &name, const QString &address); void onReadSocket(); @@ -185,18 +197,19 @@ public slots: void onConnected(); void onDisconnected(); void onError(QBluetoothSocket::SocketError error); - void reconnect(); private: - void decodeMsg(QByteArray data); + void sendData(const QByteArray &data); + bool dispatchMessage(uint endpoint, const QByteArray &data); QPointer socket; + QHash> tmpHandlers; + QHash handlers; bool is_connected; QByteArray writeData; QTimer reconnectTimer; QString _last_name; QString _last_address; }; -} #endif // WATCHCONNECTOR_H diff --git a/log4qt-debug.conf b/log4qt-debug.conf index 974e593..cabd53a 100644 --- a/log4qt-debug.conf +++ b/log4qt-debug.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=DEBUG, consolelog, syslog, filelog +log4j.rootLogger=DEBUG, consolelog, syslog #, filelog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout @@ -11,5 +11,3 @@ log4j.appender.syslog.threshold=ERROR log4j.appender.filelog=org.apache.log4j.DailyRollingFileAppender log4j.appender.filelog.layout=org.apache.log4j.SimpleTimeLayout log4j.appender.filelog.file=$XDG_CACHE_HOME/pebble.log - -# log4j.logger.Qt=INFO diff --git a/log4qt-release.conf b/log4qt-release.conf index 393f493..996c311 100644 --- a/log4qt-release.conf +++ b/log4qt-release.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=WARN, consolelog, syslog, filelog +log4j.rootLogger=WARN, consolelog, syslog #, filelog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout @@ -11,5 +11,3 @@ log4j.appender.syslog.threshold=ERROR log4j.appender.filelog=org.apache.log4j.DailyRollingFileAppender log4j.appender.filelog.layout=org.apache.log4j.SimpleTimeLayout log4j.appender.filelog.file=$XDG_CACHE_HOME/pebble.log - -# log4j.logger.Qt=INFO -- cgit v1.2.3 From f7c8244641a4242f6a8c706bd918494b23e48075 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 20:58:57 +0100 Subject: introduce the AppMsgManager and the JsKitManager will be used to handle application messages (push, etc) while the JSKitManager will run PebbleKit JS scripts. also add the ability to unpack PebbleDicts --- daemon/appmanager.cpp | 15 +++++++++ daemon/appmanager.h | 3 +- daemon/appmsgmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++ daemon/appmsgmanager.h | 30 +++++++++++++++++ daemon/daemon.pro | 12 +++++-- daemon/jskitmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++ daemon/jskitmanager.h | 41 ++++++++++++++++++++++ daemon/jskitmanager_p.h | 15 +++++++++ daemon/manager.cpp | 15 ++++----- daemon/manager.h | 8 +++-- daemon/unpacker.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/unpacker.h | 29 +++++++++------- daemon/watchconnector.h | 12 +++++++ 13 files changed, 395 insertions(+), 27 deletions(-) create mode 100644 daemon/appmsgmanager.cpp create mode 100644 daemon/appmsgmanager.h create mode 100644 daemon/jskitmanager.cpp create mode 100644 daemon/jskitmanager.h create mode 100644 daemon/jskitmanager_p.h create mode 100644 daemon/unpacker.cpp (limited to 'daemon/appmanager.cpp') diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 34af3af..d06681e 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -29,6 +29,21 @@ QStringList AppManager::appPaths() const QStandardPaths::LocateDirectory); } +const AppManager::AppInfo & AppManager::info(const QUuid &uuid) const +{ + return _apps.value(uuid); +} + +const AppManager::AppInfo & AppManager::info(const QString &name) const +{ + QUuid uuid = _names.value(name); + if (!uuid.isNull()) { + return info(uuid); + } else { + return AppInfo(); + } +} + void AppManager::rescan() { QStringList watchedDirs = _watcher->directories(); diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 5e150ab..dc2a979 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -30,7 +30,8 @@ public: QStringList appPaths() const; - bool installPebbleApp(const QString &pbwFile); + const AppInfo & info(const QUuid &uuid) const; + const AppInfo & info(const QString &shortName) const; public slots: void rescan(); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp new file mode 100644 index 0000000..9eb948e --- /dev/null +++ b/daemon/appmsgmanager.cpp @@ -0,0 +1,77 @@ +#include "appmsgmanager.h" +#include "unpacker.h" + +// TODO D-Bus server for non JS kit apps!!!! + +AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) + : QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + if (data.at(0) == WatchConnector::appmsgPUSH) { + logger()->debug() << "LAUNCHER is PUSHing" << data.toHex(); + Unpacker u(data); + u.skip(1); // skip data.at(0) which we just already checked above. + uint transaction = u.read(); + QUuid uuid = u.readUuid(); + WatchConnector::Dict dict = u.readDict(); + if (u.bad() || !dict.contains(1)) { + logger()->warn() << "Failed to parse LAUNCHER message"; + return true; + } + + switch (dict.value(1).toInt()) { + case WatchConnector::launcherSTARTED: + logger()->debug() << "App starting in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStarted(uuid); + break; + case WatchConnector::launcherSTOPPED: + logger()->debug() << "App stopping in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStopped(uuid); + break; + default: + logger()->warn() << "LAUNCHER pushed unknown message:" << uuid << dict; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildNackMessage(transaction)); + break; + } + } + + return true; + }); + + watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, + [this](const QByteArray &data) { + switch (data.at(0)) { + case WatchConnector::appmsgPUSH: + break; + } + + return true; + }); +} + +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) +{ + // TODO +} + +QByteArray AppMsgManager::buildAckMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgACK; + ba[1] = transaction; + return ba; +} + +QByteArray AppMsgManager::buildNackMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgNACK; + ba[1] = transaction; + return ba; +} diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h new file mode 100644 index 0000000..651d84e --- /dev/null +++ b/daemon/appmsgmanager.h @@ -0,0 +1,30 @@ +#ifndef APPMSGMANAGER_H +#define APPMSGMANAGER_H + +#include "watchconnector.h" + +class AppMsgManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit AppMsgManager(WatchConnector *watch, QObject *parent); + +public slots: + void send(const QUuid &uuid, const QVariantMap &data); + +signals: + void appStarted(const QUuid &uuid); + void appStopped(const QUuid &uuid); + void dataReceived(const QUuid &uuid, const QVariantMap &data); + +private: + static QByteArray buildAckMessage(uint transaction); + static QByteArray buildNackMessage(uint transaction); + +private: + WatchConnector *watch; +}; + +#endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index d4d7dbf..21c15c8 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts +QT += bluetooth dbus contacts qml PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 @@ -23,7 +23,10 @@ SOURCES += \ dbusadaptor.cpp \ appmanager.cpp \ musicmanager.cpp \ - datalogmanager.cpp + datalogmanager.cpp \ + unpacker.cpp \ + appmsgmanager.cpp \ + jskitmanager.cpp HEADERS += \ manager.h \ @@ -37,7 +40,10 @@ HEADERS += \ appmanager.h \ musicmanager.h \ unpacker.h \ - datalogmanager.h + datalogmanager.h \ + appmsgmanager.h \ + jskitmanager.h \ + jskitmanager_p.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp new file mode 100644 index 0000000..698b22b --- /dev/null +++ b/daemon/jskitmanager.cpp @@ -0,0 +1,77 @@ +#include +#include +#include "jskitmanager.h" +#include "jskitmanager_p.h" + +JSKitPebble::JSKitPebble(JSKitManager *mgr) + : QObject(mgr) +{ +} + +JSKitPebble::~JSKitPebble() +{ +} + +JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : + QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +{ + connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); + connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); +} + +JSKitManager::~JSKitManager() +{ + if (_engine) { + stopJsApp(); + } +} + +void JSKitManager::handleAppStarted(const QUuid &uuid) +{ + const auto &info = _apps->info(uuid); + if (!info.uuid.isNull() && info.isJSKit) { + logger()->debug() << "Preparing to start JSKit app" << info.uuid << info.shortName; + _curApp = info; + startJsApp(); + } +} + +void JSKitManager::handleAppStopped(const QUuid &uuid) +{ + if (!_curApp.uuid.isNull()) { + if (_curApp.uuid != uuid) { + logger()->warn() << "Closed app with invalid UUID"; + } + + _curApp = AppManager::AppInfo(); + } +} + +void JSKitManager::startJsApp() +{ + if (_engine) stopJsApp(); + _engine = new QJSEngine(this); + _jspebble = new JSKitPebble(this); + + QJSValue globalObj = _engine->globalObject(); + + globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + + QJSValueIterator it(globalObj); + while (it.hasNext()) { + it.next(); + logger()->debug() << "JS property:" << it.name(); + } +} + +void JSKitManager::stopJsApp() +{ + if (!_engine) return; // Nothing to do! + + _engine->collectGarbage(); + + delete _engine; + _engine = 0; + delete _jspebble; + _jspebble = 0; +} diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h new file mode 100644 index 0000000..5e2880f --- /dev/null +++ b/daemon/jskitmanager.h @@ -0,0 +1,41 @@ +#ifndef JSKITMANAGER_H +#define JSKITMANAGER_H + +#include +#include "appmanager.h" +#include "appmsgmanager.h" + +class JSKitPebble; + +class JSKitManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + ~JSKitManager(); + +signals: + +public slots: + +private slots: + void handleAppStarted(const QUuid &uuid); + void handleAppStopped(const QUuid &uuid); + +private: + void startJsApp(); + void stopJsApp(); + +private: + friend class JSKitPebble; + + AppManager *_apps; + AppMsgManager *_appmsg; + AppManager::AppInfo _curApp; + QJSEngine *_engine; + QPointer _jspebble; +}; + +#endif // JSKITMANAGER_H diff --git a/daemon/jskitmanager_p.h b/daemon/jskitmanager_p.h new file mode 100644 index 0000000..690a0ec --- /dev/null +++ b/daemon/jskitmanager_p.h @@ -0,0 +1,15 @@ +#ifndef JSKITMANAGER_P_H +#define JSKITMANAGER_P_H + +#include "jskitmanager.h" + +class JSKitPebble : public QObject +{ + Q_OBJECT + +public: + explicit JSKitPebble(JSKitManager *mgr); + ~JSKitPebble(); +}; + +#endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 7a02c86..778fdc6 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -1,19 +1,21 @@ -#include "manager.h" -#include "dbusadaptor.h" - #include #include #include +#include "manager.h" +#include "dbusadaptor.h" + Manager::Manager(Settings *settings, QObject *parent) : QObject(parent), settings(settings), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), + apps(new AppManager(this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - apps(new AppManager(this)), + appmsg(new AppMsgManager(watch, this)), + js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); @@ -42,11 +44,6 @@ Manager::Manager(Settings *settings, QObject *parent) : } return true; }); - watch->setEndpointHandler(WatchConnector::watchLAUNCHER, - [this](const QByteArray &data) { - logger()->debug() << "LAUNCHER msg:" << data.toHex(); - return true; - }); connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); diff --git a/daemon/manager.h b/daemon/manager.h index 8b2fd96..f1dd53e 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -7,6 +7,8 @@ #include "notificationmanager.h" #include "musicmanager.h" #include "datalogmanager.h" +#include "appmsgmanager.h" +#include "jskitmanager.h" #include "appmanager.h" #include "settings.h" @@ -40,11 +42,13 @@ class Manager : WatchConnector *watch; DBusConnector *dbus; + AppManager *apps; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; DataLogManager *datalog; - AppManager *apps; + AppMsgManager *appmsg; + JSKitManager *js; MNotification notification; @@ -73,7 +77,7 @@ protected: void transliterateMessage(const QString &text); signals: - void mprisMetadataChanged(QVariantMap); + void mprisMetadataChanged(const QVariantMap &metadata); public slots: void applyProfile(); diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp new file mode 100644 index 0000000..fc38020 --- /dev/null +++ b/daemon/unpacker.cpp @@ -0,0 +1,88 @@ +#include "unpacker.h" +#include "watchconnector.h" + +QByteArray Unpacker::readBytes(int n) +{ + if (checkBad(n)) return QByteArray(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QByteArray(u, n); +} + +QString Unpacker::readFixedString(int n) +{ + if (checkBad(n)) return QString(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QString::fromUtf8(u, strnlen(u, n)); +} + +QUuid Unpacker::readUuid() +{ + if (checkBad(16)) return QString(); + _offset += 16; + return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); +} + +QMap Unpacker::readDict() +{ + QMap d; + if (checkBad(1)) return d; + + const int n = read(); + + for (int i = 0; i < n; i++) { + if (checkBad(4 + 1 + 2)) return d; + const int key = readLE(); // For some reason, this is little endian. + const int type = readLE(); + const int width = readLE(); + + switch (type) { + case WatchConnector::typeBYTES: + d.insert(key, QVariant::fromValue(readBytes(width))); + break; + case WatchConnector::typeSTRING: + d.insert(key, QVariant::fromValue(readFixedString(width))); + break; + case WatchConnector::typeUINT: + switch (width) { + case sizeof(quint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + case WatchConnector::typeINT: + switch (width) { + case sizeof(qint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + default: + _bad = true; + return d; + } + } + + return d; +} diff --git a/daemon/unpacker.h b/daemon/unpacker.h index 94908cb..000c3e8 100644 --- a/daemon/unpacker.h +++ b/daemon/unpacker.h @@ -5,19 +5,30 @@ #include #include #include +#include +#include class Unpacker { + LOG4QT_DECLARE_STATIC_LOGGER(logger, Unpacker) + public: Unpacker(const QByteArray &data); template T read(); + template + T readLE(); + + QByteArray readBytes(int n); + QString readFixedString(int n); QUuid readUuid(); + QMap readDict(); + void skip(int n); bool bad() const; @@ -45,19 +56,13 @@ inline T Unpacker::read() return qFromBigEndian(u); } -inline QString Unpacker::readFixedString(int n) -{ - if (checkBad(n)) return QString(); - const char *u = &_buf.constData()[_offset]; - _offset += n; - return QString::fromUtf8(u, strnlen(u, n)); -} - -inline QUuid Unpacker::readUuid() +template +inline T Unpacker::readLE() { - if (checkBad(16)) return QString(); - _offset += 16; - return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); + if (checkBad(sizeof(T))) return 0; + const uchar *u = p(); + _offset += sizeof(T); + return qFromLittleEndian(u); } inline void Unpacker::skip(int n) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index c5ec332..8a7d574 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -121,6 +121,10 @@ public: datalogCLOSE = 3, datalogTIMEOUT = 7 }; + enum { + launcherSTARTED = 1, + launcherSTOPPED = 0 + }; enum { leadEMAIL = 0, leadSMS = 1, @@ -153,6 +157,14 @@ public: DEFAULT_TIMEOUT_MSECS = 100 }; + typedef QMap Dict; + enum DictItemType { + typeBYTES, + typeSTRING, + typeUINT, + typeINT + }; + typedef std::function EndpointHandlerFunc; explicit WatchConnector(QObject *parent = 0); -- cgit v1.2.3 From dadca6f0b1e4660876cccb51702998d378a5dc03 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 21:32:13 +0100 Subject: convert appinfo into a Q_GADGET with properties --- daemon/appinfo.cpp | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/appinfo.h | 66 ++++++++++++++++++++++ daemon/appmanager.cpp | 34 ++++++------ daemon/appmanager.h | 18 +----- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 31 ++++++++--- daemon/jskitmanager.h | 2 +- 7 files changed, 258 insertions(+), 41 deletions(-) create mode 100644 daemon/appinfo.cpp create mode 100644 daemon/appinfo.h (limited to 'daemon/appmanager.cpp') diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp new file mode 100644 index 0000000..a4442a3 --- /dev/null +++ b/daemon/appinfo.cpp @@ -0,0 +1,142 @@ +#include "appinfo.h" +#include + +struct AppInfoData : public QSharedData { + QUuid uuid; + QString shortName; + QString longName; + QString companyName; + int versionCode; + QString versionLabel; + bool watchface; + bool jskit; + QHash appKeys; + QString path; +}; + +AppInfo::AppInfo() : d(new AppInfoData) +{ + d->versionCode = 0; + d->watchface = false; + d->jskit = false; +} + +AppInfo::AppInfo(const AppInfo &rhs) : d(rhs.d) +{ +} + +AppInfo &AppInfo::operator=(const AppInfo &rhs) +{ + if (this != &rhs) + d.operator=(rhs.d); + return *this; +} + +AppInfo::~AppInfo() +{ +} + +QUuid AppInfo::uuid() const +{ + return d->uuid; +} + +void AppInfo::setUuid(const QUuid &uuid) +{ + d->uuid = uuid; +} + +QString AppInfo::shortName() const +{ + return d->shortName; +} + +void AppInfo::setShortName(const QString &string) +{ + d->shortName = string; +} + +QString AppInfo::longName() const +{ + return d->longName; +} + +void AppInfo::setLongName(const QString &string) +{ + d->longName = string; +} + +QString AppInfo::companyName() const +{ + return d->companyName; +} + +void AppInfo::setCompanyName(const QString &string) +{ + d->companyName = string; +} + +int AppInfo::versionCode() const +{ + return d->versionCode; +} + +void AppInfo::setVersionCode(int code) +{ + d->versionCode = code; +} + +QString AppInfo::versionLabel() const +{ + return d->versionLabel; +} + +void AppInfo::setVersionLabel(const QString &string) +{ + d->versionLabel = string; +} + +bool AppInfo::isWatchface() const +{ + return d->watchface; +} + +void AppInfo::setWatchface(bool b) +{ + d->watchface = b; +} + +bool AppInfo::isJSKit() const +{ + return d->jskit; +} + +void AppInfo::setJSKit(bool b) +{ + d->jskit = b; +} + +QHash AppInfo::appKeys() const +{ + return d->appKeys; +} + +void AppInfo::setAppKeys(const QHash &appKeys) +{ + d->appKeys = appKeys; +} + +void AppInfo::addAppKey(const QString &key, int value) +{ + d->appKeys.insert(key, value); +} + +QString AppInfo::path() const +{ + return d->path; +} + +void AppInfo::setPath(const QString &string) +{ + d->path = string; +} diff --git a/daemon/appinfo.h b/daemon/appinfo.h new file mode 100644 index 0000000..da71dfc --- /dev/null +++ b/daemon/appinfo.h @@ -0,0 +1,66 @@ +#ifndef APPINFO_H +#define APPINFO_H + +#include +#include +#include +#include + +class AppInfoData; + +class AppInfo +{ + Q_GADGET + + Q_PROPERTY(QUuid uuid READ uuid WRITE setUuid) + Q_PROPERTY(QString shortName READ shortName WRITE setShortName) + Q_PROPERTY(QString longName READ longName WRITE setLongName) + Q_PROPERTY(QString companyName READ companyName WRITE setCompanyName) + Q_PROPERTY(int versionCode READ versionCode WRITE setVersionCode) + Q_PROPERTY(QString versionLabel READ versionLabel WRITE setVersionLabel) + Q_PROPERTY(bool watchface READ isWatchface WRITE setWatchface) + Q_PROPERTY(bool jskit READ isJSKit WRITE setJSKit) + Q_PROPERTY(QString path READ path WRITE setPath) + +public: + AppInfo(); + AppInfo(const AppInfo &); + AppInfo &operator=(const AppInfo &); + ~AppInfo(); + + QUuid uuid() const; + void setUuid(const QUuid &uuid); + + QString shortName() const; + void setShortName(const QString &string); + + QString longName() const; + void setLongName(const QString &string); + + QString companyName() const; + void setCompanyName(const QString &string); + + int versionCode() const; + void setVersionCode(int code); + + QString versionLabel() const; + void setVersionLabel(const QString &string); + + bool isWatchface() const; + void setWatchface(bool b); + + bool isJSKit() const; + void setJSKit(bool b); + + QHash appKeys() const; + void setAppKeys(const QHash &string); + void addAppKey(const QString &key, int value); + + QString path() const; + void setPath(const QString &string); + +private: + QSharedDataPointer d; +}; + +#endif // APPINFO_H diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index d06681e..867a15e 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -29,12 +29,12 @@ QStringList AppManager::appPaths() const QStandardPaths::LocateDirectory); } -const AppManager::AppInfo & AppManager::info(const QUuid &uuid) const +AppInfo AppManager::info(const QUuid &uuid) const { return _apps.value(uuid); } -const AppManager::AppInfo & AppManager::info(const QString &name) const +AppInfo AppManager::info(const QString &name) const { QUuid uuid = _names.value(name); if (!uuid.isNull()) { @@ -98,30 +98,32 @@ void AppManager::scanApp(const QString &path) const QJsonObject root = doc.object(); AppInfo info; - info.uuid = QUuid(root["uuid"].toString()); - info.shortName = root["shortName"].toString(); - info.longName = root["longName"].toString(); - info.company = root["companyName"].toString(); - info.versionCode = root["versionCode"].toInt(); - info.versionLabel = root["versionLabel"].toString(); + info.setUuid(QUuid(root["uuid"].toString())); + info.setShortName(root["shortName"].toString()); + info.setLongName(root["longName"].toString()); + info.setCompanyName(root["companyName"].toString()); + info.setVersionCode(root["versionCode"].toInt()); + info.setVersionLabel(root["versionLabel"].toString()); const QJsonObject watchapp = root["watchapp"].toObject(); - info.isWatchface = watchapp["watchface"].toBool(); - info.isJSKit = appDir.exists("pebble-js-app.js"); + info.setWatchface(watchapp["watchface"].toBool()); + info.setJSKit(appDir.exists("pebble-js-app.js")); const QJsonObject appkeys = root["appKeys"].toObject(); for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { - info.appKeys.insert(it.key(), it.value().toInt()); + info.addAppKey(it.key(), it.value().toInt()); } - if (info.uuid.isNull() || info.shortName.isEmpty()) { + info.setPath(path); + + if (info.uuid().isNull() || info.shortName().isEmpty()) { logger()->warn() << "invalid or empty uuid/name in" << appInfoFile.fileName(); return; } - _apps.insert(info.uuid, info); - _names.insert(info.shortName, info.uuid); + _apps.insert(info.uuid(), info); + _names.insert(info.shortName(), info.uuid()); - const char *type = info.isWatchface ? "watchface" : "app"; - logger()->debug() << "found installed" << type << info.shortName << info.versionLabel << "with uuid" << info.uuid.toString(); + const char *type = info.isWatchface() ? "watchface" : "app"; + logger()->debug() << "found installed" << type << info.shortName() << info.versionLabel() << "with uuid" << info.uuid().toString(); } diff --git a/daemon/appmanager.h b/daemon/appmanager.h index dc2a979..7458e19 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -6,6 +6,7 @@ #include #include #include +#include "appinfo.h" class AppManager : public QObject { @@ -15,23 +16,10 @@ class AppManager : public QObject public: explicit AppManager(QObject *parent = 0); - struct AppInfo { - QUuid uuid; - QString shortName; - QString longName; - QString company; - int versionCode; - QString versionLabel; - bool isWatchface; - bool isJSKit; - QHash appKeys; - QString path; - }; - QStringList appPaths() const; - const AppInfo & info(const QUuid &uuid) const; - const AppInfo & info(const QString &shortName) const; + AppInfo info(const QUuid &uuid) const; + AppInfo info(const QString &shortName) const; public slots: void rescan(); diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 21c15c8..5338bfd 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -26,7 +26,8 @@ SOURCES += \ datalogmanager.cpp \ unpacker.cpp \ appmsgmanager.cpp \ - jskitmanager.cpp + jskitmanager.cpp \ + appinfo.cpp HEADERS += \ manager.h \ @@ -43,7 +44,8 @@ HEADERS += \ datalogmanager.h \ appmsgmanager.h \ jskitmanager.h \ - jskitmanager_p.h + jskitmanager_p.h \ + appinfo.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 698b22b..41451ac 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "jskitmanager.h" #include "jskitmanager_p.h" @@ -28,9 +28,9 @@ JSKitManager::~JSKitManager() void JSKitManager::handleAppStarted(const QUuid &uuid) { - const auto &info = _apps->info(uuid); - if (!info.uuid.isNull() && info.isJSKit) { - logger()->debug() << "Preparing to start JSKit app" << info.uuid << info.shortName; + AppInfo info = _apps->info(uuid); + if (!info.uuid().isNull() && info.isJSKit()) { + logger()->debug() << "Preparing to start JSKit app" << info.uuid() << info.shortName(); _curApp = info; startJsApp(); } @@ -38,21 +38,29 @@ void JSKitManager::handleAppStarted(const QUuid &uuid) void JSKitManager::handleAppStopped(const QUuid &uuid) { - if (!_curApp.uuid.isNull()) { - if (_curApp.uuid != uuid) { + if (!_curApp.uuid().isNull()) { + if (_curApp.uuid() != uuid) { logger()->warn() << "Closed app with invalid UUID"; } - _curApp = AppManager::AppInfo(); + stopJsApp(); + _curApp.setUuid(QUuid()); // Clear the uuid to force invalid app } } void JSKitManager::startJsApp() { if (_engine) stopJsApp(); + if (_curApp.uuid().isNull()) { + logger()->warn() << "Attempting to start JS app with invalid UUID"; + return; + } + _engine = new QJSEngine(this); _jspebble = new JSKitPebble(this); + logger()->debug() << "starting JS app"; + QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); @@ -62,12 +70,21 @@ void JSKitManager::startJsApp() it.next(); logger()->debug() << "JS property:" << it.name(); } + + QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); + if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + logger()->warn() << "Failed to open JS file at:" << scriptFile.fileName(); + stopJsApp(); + return; + } } void JSKitManager::stopJsApp() { if (!_engine) return; // Nothing to do! + logger()->debug() << "stopping JS app"; + _engine->collectGarbage(); delete _engine; diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 5e2880f..2f5ae42 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -33,7 +33,7 @@ private: AppManager *_apps; AppMsgManager *_appmsg; - AppManager::AppInfo _curApp; + AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; }; -- cgit v1.2.3 From 1b920c3c0593f6810dd900c882e4760cbbbeeb56 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 4 Dec 2014 00:41:24 +0100 Subject: parse capabilities of installed apps --- daemon/appinfo.cpp | 11 +++++++++++ daemon/appinfo.h | 11 +++++++++++ daemon/appmanager.cpp | 10 ++++++++++ 3 files changed, 32 insertions(+) (limited to 'daemon/appmanager.cpp') diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index e2406b8..fd43248 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -10,6 +10,7 @@ struct AppInfoData : public QSharedData { QString versionLabel; bool watchface; bool jskit; + AppInfo::Capabilities capabilities; QHash keyInts; QHash keyNames; QString path; @@ -117,6 +118,16 @@ void AppInfo::setJSKit(bool b) d->jskit = b; } +AppInfo::Capabilities AppInfo::capabilities() const +{ + return d->capabilities; +} + +void AppInfo::setCapabilities(Capabilities caps) +{ + d->capabilities = caps; +} + void AppInfo::addAppKey(const QString &key, int value) { d->keyInts.insert(key, value); diff --git a/daemon/appinfo.h b/daemon/appinfo.h index 038a708..6f97639 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -12,6 +12,13 @@ class AppInfo { Q_GADGET +public: + enum Capability { + Location = 1 << 0, + Configurable = 1 << 2 + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + Q_PROPERTY(QUuid uuid READ uuid WRITE setUuid) Q_PROPERTY(QString shortName READ shortName WRITE setShortName) Q_PROPERTY(QString longName READ longName WRITE setLongName) @@ -20,6 +27,7 @@ class AppInfo Q_PROPERTY(QString versionLabel READ versionLabel WRITE setVersionLabel) Q_PROPERTY(bool watchface READ isWatchface WRITE setWatchface) Q_PROPERTY(bool jskit READ isJSKit WRITE setJSKit) + Q_PROPERTY(Capabilities capabilities READ capabilities WRITE setCapabilities) Q_PROPERTY(QString path READ path WRITE setPath) public: @@ -52,6 +60,9 @@ public: bool isJSKit() const; void setJSKit(bool b); + Capabilities capabilities() const; + void setCapabilities(Capabilities caps); + void addAppKey(const QString &key, int value); bool hasAppKeyValue(int value) const; diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 867a15e..2520ba6 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "appmanager.h" @@ -109,6 +110,15 @@ void AppManager::scanApp(const QString &path) info.setWatchface(watchapp["watchface"].toBool()); info.setJSKit(appDir.exists("pebble-js-app.js")); + const QJsonArray capabilities = root["capabilities"].toArray(); + AppInfo::Capabilities caps = 0; + for (QJsonArray::const_iterator it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { + QString cap = (*it).toString(); + if (cap == "location") caps |= AppInfo::Location; + if (cap == "configurable") caps |= AppInfo::Configurable; + } + info.setCapabilities(caps); + const QJsonObject appkeys = root["appKeys"].toObject(); for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { info.addAppKey(it.key(), it.value().toInt()); -- cgit v1.2.3 From 492a861a47c5165cf62a051303d4b45e5d5630cd Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 12 Dec 2014 23:32:46 +0100 Subject: query all apps from d-bus --- daemon/appmanager.cpp | 6 ++++++ daemon/appmanager.h | 4 ++++ daemon/manager.cpp | 20 ++++++++++++++++++++ daemon/manager.h | 4 ++++ org.pebbled.Watch.xml | 6 ++++++ 5 files changed, 40 insertions(+) (limited to 'daemon/appmanager.cpp') diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 2520ba6..10f2e3e 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -30,6 +30,11 @@ QStringList AppManager::appPaths() const QStandardPaths::LocateDirectory); } +QList AppManager::appUuids() const +{ + return _apps.keys(); +} + AppInfo AppManager::info(const QUuid &uuid) const { return _apps.value(uuid); @@ -71,6 +76,7 @@ void AppManager::rescan() } logger()->debug() << "now watching" << _watcher->directories() << _watcher->files(); + emit appsChanged(); } void AppManager::scanApp(const QString &path) diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 7458e19..1725c14 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -17,6 +17,7 @@ public: explicit AppManager(QObject *parent = 0); QStringList appPaths() const; + QList appUuids() const; AppInfo info(const QUuid &uuid) const; AppInfo info(const QString &shortName) const; @@ -24,6 +25,9 @@ public: public slots: void rescan(); +signals: + void appsChanged(); + private: void scanApp(const QString &path); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 73b80e5..25908e4 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -366,6 +366,26 @@ QStringList PebbledProxy::AppSlots() const return l; } +QVariantList PebbledProxy::AllApps() const +{ + QList uuids = manager()->apps->appUuids(); + QVariantList l; + + foreach (const QUuid &uuid, uuids) { + const AppInfo &info = manager()->apps->info(uuid); + QVariantMap m; + m.insert("uuid", QVariant::fromValue(uuid.toString())); + m.insert("short-name", QVariant::fromValue(info.shortName())); + m.insert("long-name", QVariant::fromValue(info.longName())); + m.insert("company-name", QVariant::fromValue(info.companyName())); + m.insert("version-label", QVariant::fromValue(info.versionLabel())); + m.insert("is-watchface", QVariant::fromValue(info.isWatchface())); + l.append(QVariant::fromValue(m)); + } + + return l; +} + bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { Q_ASSERT(calledFromDBus()); diff --git a/daemon/manager.h b/daemon/manager.h index c191b0c..efe9b82 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -110,6 +110,7 @@ class PebbledProxy : public QObject, protected QDBusContext Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) + Q_PROPERTY(QVariantList AllApps READ AllApps NOTIFY AllAppsChanged) inline Manager* manager() const { return static_cast(parent()); } inline QVariantMap pebble() const { return manager()->dbus->pebble(); } @@ -124,6 +125,8 @@ public: QStringList AppSlots() const; + QVariantList AllApps() const; + public slots: inline void Disconnect() { manager()->watch->disconnect(); } inline void Reconnect() { manager()->watch->reconnect(); } @@ -146,6 +149,7 @@ signals: void ConnectedChanged(); void AppUuidChanged(); void AppSlotsChanged(); + void AllAppsChanged(); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); }; diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index e076d6c..6336d04 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -60,5 +60,11 @@ + + + + + + -- cgit v1.2.3 From f40514fe681f5163deb5f579140ef4f7ac77f5a8 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 03:26:46 +0100 Subject: add icons to the slots managament UI --- app/app.pro | 8 ++- app/pebble.cpp | 12 +++- app/pebbleappiconprovider.cpp | 28 ++++++++ app/pebbleappiconprovider.h | 18 +++++ app/pebbledinterface.cpp | 18 ++++- app/pebbledinterface.h | 6 +- app/qml/pages/AppConfigPage.qml | 14 +++- app/qml/pages/InstallAppDialog.qml | 17 +++-- app/qml/pages/WatchPage.qml | 49 ++++++++++--- app/qml/pebble.qml | 4 -- daemon/appinfo.cpp | 25 ++++++- daemon/appinfo.h | 9 ++- daemon/appmanager.cpp | 142 ++++++++++++++++++++++++++++++++++--- daemon/appmanager.h | 2 + daemon/manager.cpp | 5 ++ 15 files changed, 315 insertions(+), 42 deletions(-) create mode 100644 app/pebbleappiconprovider.cpp create mode 100644 app/pebbleappiconprovider.h (limited to 'daemon/appmanager.cpp') diff --git a/app/app.pro b/app/app.pro index d375800..48fcf68 100644 --- a/app/app.pro +++ b/app/app.pro @@ -3,16 +3,18 @@ TARGET = pebble CONFIG += sailfishapp QT += dbus -QMAKE_CXXFLAGS += -std=c++0x +CONFIG += c++11 DEFINES += APP_VERSION=\\\"$$VERSION\\\" SOURCES += \ pebble.cpp \ - pebbledinterface.cpp + pebbledinterface.cpp \ + pebbleappiconprovider.cpp HEADERS += \ - pebbledinterface.h + pebbledinterface.h \ + pebbleappiconprovider.h DBUS_INTERFACES += ../org.pebbled.Watch.xml diff --git a/app/pebble.cpp b/app/pebble.cpp index 44f1aeb..41da080 100644 --- a/app/pebble.cpp +++ b/app/pebble.cpp @@ -33,16 +33,22 @@ #include #include "pebbledinterface.h" +#include "pebbleappiconprovider.h" int main(int argc, char *argv[]) { - // Register Pebble daemon interface object on QML side - qmlRegisterType("org.pebbled", 0, 1, "PebbledInterface"); - QScopedPointer app(SailfishApp::application(argc, argv)); + qmlRegisterUncreatableType("org.pebbled", 0, 1, "PebbledInterface", + "Please use pebbled context property"); + QScopedPointer view(SailfishApp::createView()); + QScopedPointer pebbled(new PebbledInterface); + QScopedPointer appicons(new PebbleAppIconProvider(pebbled.data())); + view->rootContext()->setContextProperty("APP_VERSION", APP_VERSION); + view->rootContext()->setContextProperty("pebbled", pebbled.data()); + view->engine()->addImageProvider("pebble-app-icon", appicons.data()); view->setSource(SailfishApp::pathTo("qml/pebble.qml")); view->show(); diff --git a/app/pebbleappiconprovider.cpp b/app/pebbleappiconprovider.cpp new file mode 100644 index 0000000..0e694ff --- /dev/null +++ b/app/pebbleappiconprovider.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "pebbleappiconprovider.h" + +PebbleAppIconProvider::PebbleAppIconProvider(PebbledInterface *interface) + : QQuickImageProvider(QQmlImageProviderBase::Image), pebbled(interface) +{ +} + +QImage PebbleAppIconProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + QUuid uuid(QUrl::fromPercentEncoding(id.toLatin1())); + QImage img = pebbled->menuIconForApp(uuid); + + if (requestedSize.width() > 0 && requestedSize.height() > 0) { + img = img.scaled(requestedSize, Qt::KeepAspectRatio); + } else if (requestedSize.width() > 0) { + img = img.scaledToWidth(requestedSize.width()); + } else if (requestedSize.height() > 0) { + img = img.scaledToHeight(requestedSize.height()); + } + + if (size) { + *size = img.size(); + } + + return img; +} diff --git a/app/pebbleappiconprovider.h b/app/pebbleappiconprovider.h new file mode 100644 index 0000000..c76641a --- /dev/null +++ b/app/pebbleappiconprovider.h @@ -0,0 +1,18 @@ +#ifndef PEBBLEAPPICONPROVIDER_H +#define PEBBLEAPPICONPROVIDER_H + +#include +#include "pebbledinterface.h" + +class PebbleAppIconProvider : public QQuickImageProvider +{ +public: + explicit PebbleAppIconProvider(PebbledInterface *interface); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); + +private: + PebbledInterface *pebbled; +}; + +#endif // PEBBLEAPPICONPROVIDER_H diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 588e24a..c978dd0 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -183,7 +183,7 @@ QUrl PebbledInterface::configureApp(const QString &uuid) } } -bool PebbledInterface::isAppInstalled(const QString &uuid) +bool PebbledInterface::isAppInstalled(const QString &uuid) const { QUuid u(uuid); @@ -196,6 +196,11 @@ bool PebbledInterface::isAppInstalled(const QString &uuid) return false; } +QImage PebbledInterface::menuIconForApp(const QUuid &uuid) const +{ + return _appMenuIcons.value(uuid); +} + void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &data) { qDebug() << Q_FUNC_INFO << uuid << data; @@ -210,8 +215,11 @@ void PebbledInterface::launchApp(const QString &uuid) // TODO Terrible hack; need to give time for the watch to open the app // A better solution would be to wait until AppUuidChanged is generated. + QUuid u(uuid); + if (u.isNull()) return; int sleep_count = 0; - while (watch->appUuid() != uuid && sleep_count < 5) { + while (QUuid(watch->appUuid()) != u && sleep_count < 5) { + qDebug() << "Waiting for" << u.toString() << "to launch"; QThread::sleep(1); sleep_count++; } @@ -271,6 +279,7 @@ void PebbledInterface::refreshAllApps() { _apps.clear(); _appsByUuid.clear(); + _appMenuIcons.clear(); qDebug() << "refreshing all apps list"; @@ -288,6 +297,11 @@ void PebbledInterface::refreshAllApps() m.insert("shortName", orig.value("short-name")); m.insert("longName", orig.value("long-name")); + QByteArray pngIcon = orig.value("menu-icon").toByteArray(); + if (!pngIcon.isEmpty()) { + _appMenuIcons.insert(uuid, QImage::fromData(pngIcon, "PNG")); + } + _apps.append(QVariant::fromValue(m)); } diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index e468505..51efa12 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -5,6 +5,7 @@ #include #include #include +#include #include class OrgPebbledWatchInterface; @@ -39,7 +40,9 @@ public: Q_INVOKABLE QUrl configureApp(const QString &uuid); - Q_INVOKABLE bool isAppInstalled(const QString &uuid); + Q_INVOKABLE bool isAppInstalled(const QString &uuid) const; + + QImage menuIconForApp(const QUuid &uuid) const; signals: void enabledChanged(); @@ -82,6 +85,7 @@ private: QStringList _appSlots; QVariantList _apps; QHash _appsByUuid; + QHash _appMenuIcons; }; #endif // PEBBLEDINTERFACE_H diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 10fbe05..00eb05c 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -11,6 +11,7 @@ Page { SilicaWebView { id: webview + visible: url != "" anchors.fill: parent header: PageHeader { @@ -32,8 +33,17 @@ Page { } } - ViewPlaceholder { - enabled: url == "" + Text { + anchors.centerIn: parent + visible: url == "" text: qsTr("No configuration settings available") + width: parent.width - 2*Theme.paddingLarge + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + font { + pixelSize: Theme.fontSizeLarge + family: Theme.fontFamilyHeading + } + color: Theme.highlightColor } } diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml index 3a3c0b1..79283a6 100644 --- a/app/qml/pages/InstallAppDialog.qml +++ b/app/qml/pages/InstallAppDialog.qml @@ -27,20 +27,29 @@ Dialog { property string uuid: modelData.uuid property bool alreadyInstalled: pebbled.isAppInstalled(uuid) - Image { - id: appImage + Item { + id: appIcon + width: Theme.itemSizeSmall + height: Theme.itemSizeSmall + anchors { top: parent.top left: parent.left leftMargin: Theme.paddingLarge } - width: Theme.itemSizeSmall + + Image { + id: appImage + anchors.centerIn: parent + source: "image://pebble-app-icon/" + uuid; + scale: 2 + } } Label { id: appName anchors { - left: appImage.right + left: appIcon.right leftMargin: Theme.paddingMedium right: parent.right rightMargin: Theme.paddiumLarge diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index ce9d636..3a712ab 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -77,7 +77,8 @@ Page { } Item { - height: Theme.paddingMedium + width: parent.width + height: Theme.paddingLarge } Label { @@ -139,26 +140,49 @@ Page { } - Image { - id: slotImage + Item { + id: slotIcon + width: Theme.itemSizeSmall + height: Theme.itemSizeSmall + anchors { top: parent.top left: parent.left leftMargin: Theme.paddingLarge } - width: Theme.itemSizeSmall - } - BusyIndicator { - id: slotBusy - anchors.centerIn: slotImage - running: slotDelegate.busy + Image { + id: slotImage + anchors.centerIn: parent + source: isKnownApp ? "image://pebble-app-icon/" + modelData : "" + scale: 2 + visible: !isEmptySlot && isKnownApp && !slotBusy.running + } + + Rectangle { + width: 30 + height: 30 + anchors.centerIn: parent + scale: 2 + border { + width: 2 + color: slotDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + color: "transparent" + visible: isEmptySlot && !slotBusy.running + } + + BusyIndicator { + id: slotBusy + anchors.centerIn: parent + running: slotDelegate.busy + } } Label { id: slotName anchors { - left: slotImage.right + left: slotIcon.right leftMargin: Theme.paddingMedium right: parent.right rightMargin: Theme.paddiumLarge @@ -172,6 +196,11 @@ Page { Component { id: slotMenu ContextMenu { + MenuItem { + text: qsTr("Install app...") + visible: isEmptySlot + onClicked: install(); + } MenuItem { text: qsTr("Configure...") visible: !isEmptySlot && isKnownApp diff --git a/app/qml/pebble.qml b/app/qml/pebble.qml index da3bfb5..2e26ebe 100644 --- a/app/qml/pebble.qml +++ b/app/qml/pebble.qml @@ -38,8 +38,4 @@ ApplicationWindow { initialPage: Component { ManagerPage { } } cover: Qt.resolvedUrl("cover/CoverPage.qml") - - PebbledInterface { - id: pebbled - } } diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index fd43248..4397abc 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -1,5 +1,6 @@ -#include "appinfo.h" #include +#include +#include "appinfo.h" struct AppInfoData : public QSharedData { QUuid uuid; @@ -13,6 +14,7 @@ struct AppInfoData : public QSharedData { AppInfo::Capabilities capabilities; QHash keyInts; QHash keyNames; + QImage menuIcon; QString path; }; @@ -21,6 +23,7 @@ AppInfo::AppInfo() : d(new AppInfoData) d->versionCode = 0; d->watchface = false; d->jskit = false; + d->capabilities = 0; } AppInfo::AppInfo(const AppInfo &rhs) : d(rhs.d) @@ -154,6 +157,26 @@ int AppInfo::valueForAppKey(const QString &key) const return d->keyInts.value(key, -1); } +QImage AppInfo::menuIcon() const +{ + return d->menuIcon; +} + +QByteArray AppInfo::menuIconAsPng() const +{ + QByteArray data; + QBuffer buf(&data); + buf.open(QIODevice::WriteOnly); + d->menuIcon.save(&buf, "PNG"); + buf.close(); + return data; +} + +void AppInfo::setMenuIcon(const QImage &img) +{ + d->menuIcon = img; +} + QString AppInfo::path() const { return d->path; diff --git a/daemon/appinfo.h b/daemon/appinfo.h index 6f97639..3d5c4b4 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -1,10 +1,10 @@ #ifndef APPINFO_H #define APPINFO_H -#include +#include #include #include -#include +#include class AppInfoData; @@ -28,6 +28,7 @@ public: Q_PROPERTY(bool watchface READ isWatchface WRITE setWatchface) Q_PROPERTY(bool jskit READ isJSKit WRITE setJSKit) Q_PROPERTY(Capabilities capabilities READ capabilities WRITE setCapabilities) + Q_PROPERTY(QImage menuIcon READ menuIcon WRITE setMenuIcon) Q_PROPERTY(QString path READ path WRITE setPath) public: @@ -71,6 +72,10 @@ public: bool hasAppKey(const QString &key) const; int valueForAppKey(const QString &key) const; + QImage menuIcon() const; + QByteArray menuIconAsPng() const; + void setMenuIcon(const QImage &img); + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 10f2e3e..8745160 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -4,6 +4,17 @@ #include #include #include "appmanager.h" +#include "unpacker.h" +#include "stm32crc.h" + +namespace { +struct ResourceEntry { + int index; + quint32 offset; + quint32 length; + quint32 crc; +}; +} AppManager::AppManager(QObject *parent) : QObject(parent), @@ -116,18 +127,55 @@ void AppManager::scanApp(const QString &path) info.setWatchface(watchapp["watchface"].toBool()); info.setJSKit(appDir.exists("pebble-js-app.js")); - const QJsonArray capabilities = root["capabilities"].toArray(); - AppInfo::Capabilities caps = 0; - for (QJsonArray::const_iterator it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { - QString cap = (*it).toString(); - if (cap == "location") caps |= AppInfo::Location; - if (cap == "configurable") caps |= AppInfo::Configurable; + if (root.contains("capabilities")) { + const QJsonArray capabilities = root["capabilities"].toArray(); + AppInfo::Capabilities caps = 0; + for (auto it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { + QString cap = (*it).toString(); + if (cap == "location") caps |= AppInfo::Location; + if (cap == "configurable") caps |= AppInfo::Configurable; + } + info.setCapabilities(caps); } - info.setCapabilities(caps); - const QJsonObject appkeys = root["appKeys"].toObject(); - for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { - info.addAppKey(it.key(), it.value().toInt()); + if (root.contains("appKeys")) { + const QJsonObject appkeys = root["appKeys"].toObject(); + for (auto it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { + info.addAppKey(it.key(), it.value().toInt()); + } + } + + if (root.contains("resources")) { + const QJsonObject resources = root["resources"].toObject(); + const QJsonArray media = resources["media"].toArray(); + int index = 0; + + for (auto it = media.constBegin(); it != media.constEnd(); ++it) { + const QJsonObject res = (*it).toObject(); + const QJsonValue menuIcon = res["menuIcon"]; + + bool is_menu_icon = false; + switch (menuIcon.type()) { + case QJsonValue::Bool: + is_menu_icon = menuIcon.toBool(); + break; + case QJsonValue::String: + is_menu_icon = !menuIcon.toString().isEmpty(); + break; + default: + break; + } + + if (is_menu_icon) { + QByteArray data = extractFromResourcePack(appDir.filePath("app_resources.pbpack"), index); + if (!data.isEmpty()) { + QImage icon = decodeResourceImage(data); + info.setMenuIcon(icon); + } + } + + index++; + } } info.setPath(path); @@ -143,3 +191,77 @@ void AppManager::scanApp(const QString &path) const char *type = info.isWatchface() ? "watchface" : "app"; logger()->debug() << "found installed" << type << info.shortName() << info.versionLabel() << "with uuid" << info.uuid().toString(); } + +QByteArray AppManager::extractFromResourcePack(const QString &file, int wanted_id) const +{ + QFile f(file); + if (!f.open(QIODevice::ReadOnly)) { + logger()->warn() << "cannot open resource file" << f.fileName(); + return QByteArray(); + } + + QByteArray data = f.readAll(); + Unpacker u(data); + + int num_files = u.readLE(); + u.readLE(); // crc for entire file + u.readLE(); // timestamp + + logger()->debug() << "reading" << num_files << "resources from" << file; + + QList table; + + for (int i = 0; i < num_files; i++) { + ResourceEntry e; + e.index = u.readLE(); + e.offset = u.readLE(); + e.length = u.readLE(); + e.crc = u.readLE(); + + if (u.bad()) { + logger()->warn() << "short read on resource file"; + return QByteArray(); + } + + table.append(e); + } + + if (wanted_id >= table.size()) { + logger()->warn() << "specified resource does not exist"; + return QByteArray(); + } + + const ResourceEntry &e = table[wanted_id]; + + int offset = 12 + 256 * 16 + e.offset; + + QByteArray res = data.mid(offset, e.length); + + Stm32Crc crc; + crc.addData(res); + + if (crc.result() != e.crc) { + logger()->warn() << "CRC failure in resource" << e.index << "on file" << file; + return QByteArray(); + } + + return res; +} + +QImage AppManager::decodeResourceImage(const QByteArray &data) const +{ + Unpacker u(data); + int scanline = u.readLE(); + u.skip(sizeof(quint16) + sizeof(quint32)); + int width = u.readLE(); + int height = u.readLE(); + + QImage img(width, height, QImage::Format_MonoLSB); + const uchar *src = reinterpret_cast(&data.constData()[12]); + for (int line = 0; line < height; ++line) { + memcpy(img.scanLine(line), src, qMin(scanline, img.bytesPerLine())); + src += scanline; + } + + return img; +} diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 1725c14..d5e5ba1 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -30,6 +30,8 @@ signals: private: void scanApp(const QString &path); + QByteArray extractFromResourcePack(const QString &file, int id) const; + QImage decodeResourceImage(const QByteArray &data) const; private: QFileSystemWatcher *_watcher; diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 25908e4..212a1d7 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -380,6 +380,11 @@ QVariantList PebbledProxy::AllApps() const m.insert("company-name", QVariant::fromValue(info.companyName())); m.insert("version-label", QVariant::fromValue(info.versionLabel())); m.insert("is-watchface", QVariant::fromValue(info.isWatchface())); + + if (!info.menuIcon().isNull()) { + m.insert("menu-icon", QVariant::fromValue(info.menuIconAsPng())); + } + l.append(QVariant::fromValue(m)); } -- cgit v1.2.3