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 +++++++++++++++ daemon/appmanager.h | 20 ++++++++++++++++++++ daemon/daemon.cpp | 3 ++- daemon/daemon.pro | 6 ++++-- daemon/manager.cpp | 6 +++--- daemon/manager.h | 4 +++- 6 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 daemon/appmanager.cpp create mode 100644 daemon/appmanager.h 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); +} diff --git a/daemon/appmanager.h b/daemon/appmanager.h new file mode 100644 index 0000000..59d0bd7 --- /dev/null +++ b/daemon/appmanager.h @@ -0,0 +1,20 @@ +#ifndef APPMANAGER_H +#define APPMANAGER_H + +#include + +class AppManager : public QObject +{ + Q_OBJECT + +public: + explicit AppManager(QObject *parent = 0); + + bool installPebbleApp(const QString &pbwFile); + + QList allInstalledApps() const; + + QString getAppDir(const QUuid &uuid) const; +}; + +#endif // APPMANAGER_H diff --git a/daemon/daemon.cpp b/daemon/daemon.cpp index 9d89980..80ae667 100644 --- a/daemon/daemon.cpp +++ b/daemon/daemon.cpp @@ -82,7 +82,8 @@ int main(int argc, char *argv[]) DBusConnector dbus; VoiceCallManager voice(&settings); NotificationManager notifications(&settings); - Manager manager(&watch, &dbus, &voice, ¬ifications, &settings); + AppManager apps(&settings); + Manager manager(&watch, &dbus, &voice, ¬ifications, &apps, &settings); signal(SIGINT, signalhandler); signal(SIGTERM, signalhandler); diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 0528ec9..78091e0 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -22,7 +22,8 @@ SOURCES += \ watchconnector.cpp \ dbusconnector.cpp \ dbusadaptor.cpp \ - watchcommands.cpp + watchcommands.cpp \ + appmanager.cpp HEADERS += \ manager.h \ @@ -33,7 +34,8 @@ HEADERS += \ dbusconnector.h \ dbusadaptor.h \ watchcommands.h \ - settings.h + settings.h \ + appmanager.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index b01006c..7761864 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -5,9 +5,9 @@ #include #include -Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, Settings *settings) : - QObject(0), watch(watch), dbus(dbus), voice(voice), notifications(notifications), commands(new WatchCommands(watch, this)), - settings(settings), notification(MNotification::DeviceEvent) +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) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged())); diff --git a/daemon/manager.h b/daemon/manager.h index 9de5667..5946bf0 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -5,6 +5,7 @@ #include "dbusconnector.h" #include "voicecallmanager.h" #include "notificationmanager.h" +#include "appmanager.h" #include "watchcommands.h" #include "settings.h" @@ -38,6 +39,7 @@ class Manager : DBusConnector *dbus; VoiceCallManager *voice; NotificationManager *notifications; + AppManager *apps; WatchCommands *commands; @@ -55,7 +57,7 @@ class Manager : QScopedPointer transliterator; public: - explicit Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, Settings *settings); + explicit Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, AppManager *apps, Settings *settings); Q_INVOKABLE QString findPersonByNumber(QString number); Q_INVOKABLE QString getCurrentProfile(); -- cgit v1.2.3 From d55d1d472d5876f90dd95301d9f3b6bef6f4c494 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Nov 2014 21:23:59 +0100 Subject: detect icu via pkgconfig --- daemon/daemon.pro | 5 ++--- rpm/pebble.spec | 2 ++ rpm/pebble.yaml | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 78091e0..1799d18 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -5,11 +5,10 @@ CONFIG += link_pkgconfig QT -= gui QT += bluetooth dbus contacts -PKGCONFIG += mlite5 -QMAKE_CXXFLAGS += -std=c++0x +PKGCONFIG += mlite5 icu-i18n +CONFIG += c++11 LIBS += -llog4qt -LIBS += -licuuc -licui18n DEFINES += APP_VERSION=\\\"$$VERSION\\\" diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 9b9df50..ba5b4b3 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -30,6 +30,8 @@ BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(mlite5) BuildRequires: pkgconfig(sailfishapp) >= 0.0.10 +BuildRequires: pkgconfig(icu-i18n) +BuildRequires: log4qt-devel BuildRequires: desktop-file-utils %description diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index e5edbaa..45fb409 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -22,9 +22,9 @@ PkgConfigBR: - Qt5Core - mlite5 - sailfishapp >= 0.0.10 +- icu-i18n PkgBR: - log4qt-devel -- libicu-devel Requires: - sailfishsilica-qt5 >= 0.10.9 - systemd-user-session-targets @@ -36,4 +36,3 @@ Files: - '%{_libdir}/systemd/user/%{name}d.service' - '%{_libdir}/systemd/user/user-session.target.wants/%{name}d.service' - '%{_datadir}/%{name}/log4qt.conf' -PkgBR: [] -- 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 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 4527ab9a4147a8f15bf8ca5613341df9d0029d0c Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 18:49:07 +0100 Subject: add stub datalogmanager --- daemon/daemon.pro | 6 ++++-- daemon/datalogmanager.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ daemon/datalogmanager.h | 25 +++++++++++++++++++++++++ daemon/manager.cpp | 9 +++++---- daemon/manager.h | 2 ++ daemon/watchconnector.cpp | 7 +++++++ daemon/watchconnector.h | 13 ++++++++++++- 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 daemon/datalogmanager.cpp create mode 100644 daemon/datalogmanager.h diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 768b50e..d4d7dbf 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -22,7 +22,8 @@ SOURCES += \ dbusconnector.cpp \ dbusadaptor.cpp \ appmanager.cpp \ - musicmanager.cpp + musicmanager.cpp \ + datalogmanager.cpp HEADERS += \ manager.h \ @@ -35,7 +36,8 @@ HEADERS += \ settings.h \ appmanager.h \ musicmanager.h \ - unpacker.h + unpacker.h \ + datalogmanager.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/datalogmanager.cpp b/daemon/datalogmanager.cpp new file mode 100644 index 0000000..8026e15 --- /dev/null +++ b/daemon/datalogmanager.cpp @@ -0,0 +1,42 @@ +#include "datalogmanager.h" +#include "unpacker.h" + +DataLogManager::DataLogManager(WatchConnector *watch, QObject *parent) : + QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING, [this](const QByteArray& data) { + if (data.size() < 2) { + logger()->warn() << "small data_logging packet"; + return false; + } + + const char command = data[0]; + const int session = data[1]; + + switch (command) { + case WatchConnector::datalogOPEN: + logger()->debug() << "open datalog session" << session; + return true; + case WatchConnector::datalogCLOSE: + logger()->debug() << "close datalog session" << session; + return true; + case WatchConnector::datalogTIMEOUT: + logger()->debug() << "timeout datalog session" << session; + return true; + case WatchConnector::datalogDATA: + handleDataCommand(session, data.mid(2)); + return true; + default: + return false; + } + }); +} + +void DataLogManager::handleDataCommand(int session, const QByteArray &data) +{ + Unpacker u(data); + + // TODO Seemingly related to analytics, so not important. + + logger()->debug() << "got datalog data" << session << data.size(); +} diff --git a/daemon/datalogmanager.h b/daemon/datalogmanager.h new file mode 100644 index 0000000..47fc948 --- /dev/null +++ b/daemon/datalogmanager.h @@ -0,0 +1,25 @@ +#ifndef DATALOGMANAGER_H +#define DATALOGMANAGER_H + +#include "watchconnector.h" + +class DataLogManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit DataLogManager(WatchConnector *watch, QObject *parent = 0); + +signals: + +public slots: + +private: + void handleDataCommand(int session, const QByteArray &data); + +private: + WatchConnector *watch; +}; + +#endif // DATALOGMANAGER_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a8acf4..7a02c86 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -12,6 +12,7 @@ Manager::Manager(Settings *settings, QObject *parent) : voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), + datalog(new DataLogManager(watch, this)), apps(new AppManager(this)), notification(MNotification::DeviceEvent) { @@ -35,15 +36,15 @@ Manager::Manager(Settings *settings, QObject *parent) : return true; }); watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, - [this](const QByteArray& data) { + [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(); + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + logger()->debug() << "LAUNCHER msg:" << data.toHex(); return true; }); diff --git a/daemon/manager.h b/daemon/manager.h index b210700..8b2fd96 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -6,6 +6,7 @@ #include "voicecallmanager.h" #include "notificationmanager.h" #include "musicmanager.h" +#include "datalogmanager.h" #include "appmanager.h" #include "settings.h" @@ -42,6 +43,7 @@ class Manager : VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; + DataLogManager *datalog; AppManager *apps; MNotification notification; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 61eeb67..a3ea181 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -160,6 +160,13 @@ void WatchConnector::onReadSocket() message_length = qFromBigEndian(&header[0]); endpoint = qFromBigEndian(&header[sizeof(quint16)]); + if (message_length > 8 * 1024) { + // Protocol does not allow messages more than 8K long, seemingly. + logger()->warn() << "received message size too long: " << message_length; + socket->readAll(); // drop input buffer + return; + } + // Now wait for the entire message if (socket->bytesAvailable() < header_length + message_length) { logger()->debug() << "incomplete msg body in read buffer"; diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 01c3dac..c5ec332 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -109,7 +109,18 @@ public: appmgrGET_APPBANK_STATUS = 1, appmgrGET_APPBANK_UUIDS = 5 }; - + enum AppMessage { + appmsgPUSH = 1, + appmsgREQUEST = 2, + appmsgACK = 0xFF, + appmsgNACK = 0x7F + }; + enum DataLogMessage { + datalogOPEN = 1, + datalogDATA = 2, + datalogCLOSE = 3, + datalogTIMEOUT = 7 + }; enum { leadEMAIL = 0, leadSMS = 1, -- 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 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 2e0e33bd2d588a96fc471d024de583ec7d287f5e Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 21:01:53 +0100 Subject: remove now useless debug() msg --- daemon/appmsgmanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 9eb948e..23bf802 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -9,7 +9,6 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) 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(); -- 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 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 6456b840eb660fdafe21d376e07e0b67a24cd0b4 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 23:25:33 +0100 Subject: more JSKit objects --- daemon/daemon.pro | 7 ++--- daemon/jskitmanager.cpp | 32 +++++++++++------------ daemon/jskitmanager.h | 3 +++ daemon/jskitmanager_p.h | 15 ----------- daemon/jskitobjects.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitobjects.h | 48 ++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 34 deletions(-) delete mode 100644 daemon/jskitmanager_p.h create mode 100644 daemon/jskitobjects.cpp create mode 100644 daemon/jskitobjects.h diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 5338bfd..48410c4 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -27,7 +27,8 @@ SOURCES += \ unpacker.cpp \ appmsgmanager.cpp \ jskitmanager.cpp \ - appinfo.cpp + appinfo.cpp \ + jskitobjects.cpp HEADERS += \ manager.h \ @@ -44,8 +45,8 @@ HEADERS += \ datalogmanager.h \ appmsgmanager.h \ jskitmanager.h \ - jskitmanager_p.h \ - appinfo.h + appinfo.h \ + jskitobjects.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 41451ac..8329e74 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -1,16 +1,8 @@ #include -#include -#include "jskitmanager.h" -#include "jskitmanager_p.h" - -JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr) -{ -} +#include -JSKitPebble::~JSKitPebble() -{ -} +#include "jskitmanager.h" +#include "jskitobjects.h" JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) @@ -58,18 +50,15 @@ void JSKitManager::startJsApp() _engine = new QJSEngine(this); _jspebble = new JSKitPebble(this); + _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); logger()->debug() << "starting JS app"; QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); - QJSValueIterator it(globalObj); - while (it.hasNext()) { - it.next(); - logger()->debug() << "JS property:" << it.name(); - } QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -77,6 +66,15 @@ void JSKitManager::startJsApp() stopJsApp(); return; } + + QString script = QString::fromUtf8(scriptFile.readAll()); + + QJSValue result = _engine->evaluate(script, scriptFile.fileName()); + if (result.isError()) { + logger()->warn() << "error while evaluating JSKit script:" << result.toString(); + } + + logger()->debug() << "JS script evaluated"; } void JSKitManager::stopJsApp() @@ -89,6 +87,8 @@ void JSKitManager::stopJsApp() delete _engine; _engine = 0; + delete _jsstorage; + _jsstorage = 0; delete _jspebble; _jspebble = 0; } diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 2f5ae42..f25a96f 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -6,6 +6,7 @@ #include "appmsgmanager.h" class JSKitPebble; +class JSKitLocalStorage; class JSKitManager : public QObject { @@ -36,6 +37,8 @@ private: AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; + QPointer _jsstorage; + }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitmanager_p.h b/daemon/jskitmanager_p.h deleted file mode 100644 index 690a0ec..0000000 --- a/daemon/jskitmanager_p.h +++ /dev/null @@ -1,15 +0,0 @@ -#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/jskitobjects.cpp b/daemon/jskitobjects.cpp new file mode 100644 index 0000000..fc9506d --- /dev/null +++ b/daemon/jskitobjects.cpp @@ -0,0 +1,69 @@ +#include +#include +#include "jskitobjects.h" + +JSKitPebble::JSKitPebble(JSKitManager *mgr) + : QObject(mgr) +{ +} + +void JSKitPebble::addEventListener(const QString &value, QJSValue callback) +{ + _callbacks[value].append(callback); +} + +JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) + : QObject(mgr), _storage(new QSettings(getStorageFileFor(uuid), QSettings::IniFormat, this)) +{ + _len = _storage->allKeys().size(); +} + +int JSKitLocalStorage::length() const +{ + return _len; +} + +QJSValue JSKitLocalStorage::getItem(const QString &key) const +{ + QVariant value = _storage->value(key); + if (value.isValid()) { + return QJSValue(value.toString()); + } else { + return QJSValue(QJSValue::NullValue); + } +} + +void JSKitLocalStorage::setItem(const QString &key, const QString &value) +{ + _storage->setValue(key, QVariant::fromValue(value)); + checkLengthChanged(); +} + +void JSKitLocalStorage::removeItem(const QString &key) +{ + _storage->remove(key); + checkLengthChanged(); +} + +void JSKitLocalStorage::clear() +{ + _storage->clear(); + _len = 0; + emit lengthChanged(); +} + +void JSKitLocalStorage::checkLengthChanged() +{ + int curLen = _storage->allKeys().size(); + if (_len != curLen) { + _len = curLen; + emit lengthChanged(); + } +} + +QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) +{ + QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + dataDir.mkdir("js-storage"); + return dataDir.absoluteFilePath("js-storage/" + uuid.toString() + ".ini"); +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h new file mode 100644 index 0000000..8acd76f --- /dev/null +++ b/daemon/jskitobjects.h @@ -0,0 +1,48 @@ +#ifndef JSKITMANAGER_P_H +#define JSKITMANAGER_P_H + +#include +#include "jskitmanager.h" + +class JSKitPebble : public QObject +{ + Q_OBJECT + +public: + explicit JSKitPebble(JSKitManager *mgr); + + Q_INVOKABLE void addEventListener(const QString &event, QJSValue callback); +private: + QHash> _callbacks; +}; + +class JSKitLocalStorage : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int length READ length NOTIFY lengthChanged) + +public: + explicit JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr); + + int length() const; + + Q_INVOKABLE QJSValue getItem(const QString &key) const; + Q_INVOKABLE void setItem(const QString &key, const QString &value); + Q_INVOKABLE void removeItem(const QString &key); + + Q_INVOKABLE void clear(); + +signals: + void lengthChanged(); + +private: + void checkLengthChanged(); + static QString getStorageFileFor(const QUuid &uuid); + +private: + QSettings *_storage; + int _len; +}; + +#endif // JSKITMANAGER_P_H -- cgit v1.2.3 From cf405034b49e5e8ba7a8d22522878c8834b8d4ae Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 23:47:38 +0100 Subject: stub all functions of Pebble JS object --- daemon/daemon.pro | 2 +- daemon/jskitmanager.cpp | 2 ++ daemon/jskitobjects.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++--- daemon/jskitobjects.h | 14 +++++++++++- 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 48410c4..a6631df 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts qml +QT += bluetooth dbus contacts gui qml PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 8329e74..2da6c09 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -75,6 +75,8 @@ void JSKitManager::startJsApp() } logger()->debug() << "JS script evaluated"; + + _jspebble->invokeCallbacks("ready"); } void JSKitManager::stopJsApp() diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index fc9506d..f40138a 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,15 +1,67 @@ #include +#include +#include #include #include "jskitobjects.h" JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr) + : QObject(mgr), _mgr(mgr) { } -void JSKitPebble::addEventListener(const QString &value, QJSValue callback) +void JSKitPebble::addEventListener(const QString &type, QJSValue function) { - _callbacks[value].append(callback); + _callbacks[type].append(function); +} + +void JSKitPebble::removeEventListener(const QString &type, QJSValue function) +{ + if (!_callbacks.contains(type)) return; + QList &callbacks = _callbacks[type]; + + for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ) { + if (it->strictlyEquals(function)) { + it = callbacks.erase(it); + } else { + ++it; + } + } + + if (callbacks.empty()) { + _callbacks.remove(type); + } +} + +void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) +{ + // TODO contact _mgr->appmsg->... + logger()->debug() << "sendAppMessage" << message.toString(); +} + +void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) +{ + logger()->debug() << "showSimpleNotificationOnPebble" << title << body; +} + +void JSKitPebble::openUrl(const QUrl &url) +{ + if (!QDesktopServices::openUrl(url)) { + logger()->warn() << "Failed to open URL:" << url; + } +} + +void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) +{ + if (!_callbacks.contains(type)) return; + QList &callbacks = _callbacks[type]; + + for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + QJSValue result = it->call(args); + if (result.isError()) { + logger()->warn() << "error while invoking callback" << type << it->toString() << ":" + << result.toString(); + } + } } JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 8acd76f..2375084 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -7,12 +7,24 @@ class JSKitPebble : public QObject { Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER public: explicit JSKitPebble(JSKitManager *mgr); - Q_INVOKABLE void addEventListener(const QString &event, QJSValue callback); + Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); + Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); + + Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack); + + Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); + + Q_INVOKABLE void openUrl(const QUrl &url); + + void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); + private: + JSKitManager *_mgr; QHash> _callbacks; }; -- cgit v1.2.3 From 1e3794c476caf5c41360c36cc13c8425ec0dd26c Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 02:21:30 +0100 Subject: implement message passing around jskit apps and watch --- daemon/appinfo.cpp | 26 +++++-- daemon/appinfo.h | 8 ++- daemon/appmsgmanager.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++--- daemon/appmsgmanager.h | 17 ++++- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 25 ++++++- daemon/jskitmanager.h | 5 +- daemon/jskitobjects.cpp | 35 ++++++++-- daemon/jskitobjects.h | 14 +++- daemon/manager.cpp | 2 +- daemon/packer.cpp | 91 ++++++++++++++++++++++++ daemon/packer.h | 67 ++++++++++++++++++ daemon/unpacker.cpp | 2 +- daemon/watchconnector.cpp | 19 +++--- daemon/watchconnector.h | 4 +- 15 files changed, 448 insertions(+), 44 deletions(-) create mode 100644 daemon/packer.cpp create mode 100644 daemon/packer.h diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index a4442a3..e2406b8 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -10,7 +10,8 @@ struct AppInfoData : public QSharedData { QString versionLabel; bool watchface; bool jskit; - QHash appKeys; + QHash keyInts; + QHash keyNames; QString path; }; @@ -116,19 +117,30 @@ void AppInfo::setJSKit(bool b) d->jskit = b; } -QHash AppInfo::appKeys() const +void AppInfo::addAppKey(const QString &key, int value) { - return d->appKeys; + d->keyInts.insert(key, value); + d->keyNames.insert(value, key); } -void AppInfo::setAppKeys(const QHash &appKeys) +bool AppInfo::hasAppKeyValue(int value) const { - d->appKeys = appKeys; + return d->keyNames.contains(value); } -void AppInfo::addAppKey(const QString &key, int value) +QString AppInfo::appKeyForValue(int value) const +{ + return d->keyNames.value(value); +} + +bool AppInfo::hasAppKey(const QString &key) const +{ + return d->keyInts.contains(key); +} + +int AppInfo::valueForAppKey(const QString &key) const { - d->appKeys.insert(key, value); + return d->keyInts.value(key, -1); } QString AppInfo::path() const diff --git a/daemon/appinfo.h b/daemon/appinfo.h index da71dfc..038a708 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -52,10 +52,14 @@ public: bool isJSKit() const; void setJSKit(bool b); - QHash appKeys() const; - void setAppKeys(const QHash &string); void addAppKey(const QString &key, int value); + bool hasAppKeyValue(int value) const; + QString appKeyForValue(int value) const; + + bool hasAppKey(const QString &key) const; + int valueForAppKey(const QString &key) const; + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 23bf802..b620078 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,21 +1,28 @@ #include "appmsgmanager.h" #include "unpacker.h" +#include "packer.h" // TODO D-Bus server for non JS kit apps!!!! -AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) +AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) { watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { if (data.at(0) == WatchConnector::appmsgPUSH) { - 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"; + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return true; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; return true; } @@ -46,7 +53,26 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: + case WatchConnector::appmsgPUSH: { + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return true; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap data = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << data; + + emit messageReceived(uuid, data); + break; + } + default: + logger()->warn() << "Unknown application message type:" << data.at(0); break; } @@ -54,9 +80,132 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) }); } +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) +{ + WatchConnector::Dict dict = mapAppKeys(uuid, data); + quint8 transaction = ++lastTransactionId; + QByteArray msg = buildPushMessage(transaction, uuid, dict); + + logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; + + WatchConnector::Dict t_dict; + QUuid t_uuid; + uint t_trans; + if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { + logger()->debug() << t_trans << t_uuid << t_dict; + } else { + logger()->warn() << "not unpack my own"; + } + + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, + [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { + if (reply.size() < 2) return false; + + quint8 type = reply[0]; + quint8 recv_transaction = reply[1]; + + logger()->debug() << "Got response to transaction" << transaction; + + if (recv_transaction != transaction) return false; + + switch (type) { + case WatchConnector::appmsgACK: + logger()->debug() << "Got ACK to transaction" << transaction; + if (ackCallback) ackCallback(); + return true; + case WatchConnector::appmsgNACK: + logger()->info() << "Got NACK to transaction" << transaction; + if (nackCallback) nackCallback(); + return true; + default: + return false; + } + }); +} + void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) { - // TODO + std::function nullCallback; + send(uuid, data, nullCallback, nullCallback); +} + +WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + WatchConnector::Dict d; + + for (QVariantMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) { + if (info.hasAppKey(it.key())) { + d.insert(info.valueForAppKey(it.key()), it.value()); + } else { + // Even if we do not know about this appkey, try to see if it's already a numeric key we + // can send to the watch. + bool ok = false; + int num = it.key().toInt(&ok); + if (ok) { + d.insert(num, it.value()); + } else { + logger()->warn() << "Unknown appKey" << it.key() << "for app with GUID" << uuid; + } + } + } + + return d; +} + +QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + QVariantMap data; + + for (WatchConnector::Dict::const_iterator it = dict.constBegin(); it != dict.constEnd(); ++it) { + if (info.hasAppKeyValue(it.key())) { + data.insert(info.appKeyForValue(it.key()), it.value()); + } else { + logger()->warn() << "Unknown appKey value" << it.key() << "for app with GUID" << uuid; + data.insert(QString::number(it.key()), it.value()); + } + } + + return data; +} + +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +{ + Unpacker u(msg); + quint8 code = u.read(); + Q_ASSERT(code == WatchConnector::appmsgPUSH); + + *transaction = u.read(); + *uuid = u.readUuid(); + *dict = u.readDict(); + + if (u.bad()) { + return false; + } + + return true; +} + +QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +{ + QByteArray ba; + Packer p(&ba); + p.write(WatchConnector::appmsgPUSH); + p.write(transaction); + p.writeUuid(uuid); + p.writeDict(dict); + + return ba; } QByteArray AppMsgManager::buildAckMessage(uint transaction) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 651d84e..ca1d484 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -2,6 +2,7 @@ #define APPMSGMANAGER_H #include "watchconnector.h" +#include "appmanager.h" class AppMsgManager : public QObject { @@ -9,7 +10,11 @@ class AppMsgManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit AppMsgManager(WatchConnector *watch, QObject *parent); + explicit AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent); + + void send(const QUuid &uuid, const QVariantMap &data, + const std::function &ackCallback, + const std::function &nackCallback); public slots: void send(const QUuid &uuid, const QVariantMap &data); @@ -17,14 +22,22 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void dataReceived(const QUuid &uuid, const QVariantMap &data); + void messageReceived(const QUuid &uuid, const QVariantMap &data); private: + WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); + QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); + + static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); static QByteArray buildAckMessage(uint transaction); static QByteArray buildNackMessage(uint transaction); private: + AppManager *apps; WatchConnector *watch; + quint8 lastTransactionId; }; #endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index a6631df..c59d408 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ appmsgmanager.cpp \ jskitmanager.cpp \ appinfo.cpp \ - jskitobjects.cpp + jskitobjects.cpp \ + packer.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ appmsgmanager.h \ jskitmanager.h \ appinfo.h \ - jskitobjects.h + jskitobjects.h \ + packer.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 2da6c09..f8ec34a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,6 +9,7 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); + connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -40,6 +41,23 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +{ + if (_curApp.uuid() == uuid) { + logger()->debug() << "received a message for the current JSKit app"; + + if (!_engine) { + logger()->debug() << "but engine is stopped"; + return; + } + + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("payload", _engine->toScriptValue(data)); + + _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); + } +} + void JSKitManager::startJsApp() { if (_engine) stopJsApp(); @@ -49,7 +67,8 @@ void JSKitManager::startJsApp() } _engine = new QJSEngine(this); - _jspebble = new JSKitPebble(this); + _jspebble = new JSKitPebble(_curApp, this); + _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); logger()->debug() << "starting JS app"; @@ -57,8 +76,12 @@ void JSKitManager::startJsApp() QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + globalObj.setProperty("console", _engine->newQObject(_jsconsole)); globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); + QJSValue windowObj = _engine->newObject(); + windowObj.setProperty("localStorage", globalObj.property("localStorage")); + globalObj.setProperty("window", windowObj); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index f25a96f..d09bf54 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -6,6 +6,7 @@ #include "appmsgmanager.h" class JSKitPebble; +class JSKitConsole; class JSKitLocalStorage; class JSKitManager : public QObject @@ -18,12 +19,14 @@ public: ~JSKitManager(); signals: + void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); + void handleAppMessage(const QUuid &uuid, const QVariantMap &data); private: void startJsApp(); @@ -37,8 +40,8 @@ private: AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; + QPointer _jsconsole; QPointer _jsstorage; - }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f40138a..aecc55d 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,8 +4,8 @@ #include #include "jskitobjects.h" -JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr), _mgr(mgr) +JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) + : QObject(mgr), _appInfo(info), _mgr(mgr) { } @@ -34,17 +34,28 @@ void JSKitPebble::removeEventListener(const QString &type, QJSValue function) void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) { - // TODO contact _mgr->appmsg->... - logger()->debug() << "sendAppMessage" << message.toString(); + QVariantMap data = message.toVariant().toMap(); + + logger()->debug() << "sendAppMessage" << data; + + _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { + logger()->debug() << "Invoking ack callback"; + callbackForAck.call(); + }, [this, callbackForNack]() mutable { + logger()->debug() << "Invoking nack callback"; + callbackForNack.call(); + }); } void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) { logger()->debug() << "showSimpleNotificationOnPebble" << title << body; + emit _mgr->appNotification(_appInfo.uuid(), title, body); } void JSKitPebble::openUrl(const QUrl &url) { + logger()->debug() << "opening url" << url.toString(); if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } @@ -56,6 +67,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QList &callbacks = _callbacks[type]; for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + logger()->debug() << "invoking callback" << type << it->toString(); QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" @@ -64,6 +76,16 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) } } +JSKitConsole::JSKitConsole(JSKitManager *mgr) + : QObject(mgr) +{ +} + +void JSKitConsole::log(const QString &msg) +{ + logger()->info() << msg; +} + JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) : QObject(mgr), _storage(new QSettings(getStorageFileFor(uuid), QSettings::IniFormat, this)) { @@ -117,5 +139,8 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) { QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); dataDir.mkdir("js-storage"); - return dataDir.absoluteFilePath("js-storage/" + uuid.toString() + ".ini"); + QString fileName = uuid.toString(); + fileName.remove('{'); + fileName.remove('}'); + return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 2375084..c59bfac 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -10,7 +10,7 @@ class JSKitPebble : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitPebble(JSKitManager *mgr); + explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr); Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); @@ -24,10 +24,22 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: + AppInfo _appInfo; JSKitManager *_mgr; QHash> _callbacks; }; +class JSKitConsole : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitConsole(JSKitManager *mgr); + + Q_INVOKABLE void log(const QString &msg); +}; + class JSKitLocalStorage : public QObject { Q_OBJECT diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 778fdc6..8d41c89 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -14,7 +14,7 @@ Manager::Manager(Settings *settings, QObject *parent) : notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - appmsg(new AppMsgManager(watch, this)), + appmsg(new AppMsgManager(apps, watch, this)), js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { diff --git a/daemon/packer.cpp b/daemon/packer.cpp new file mode 100644 index 0000000..569f7a8 --- /dev/null +++ b/daemon/packer.cpp @@ -0,0 +1,91 @@ +#include "packer.h" +#include "watchconnector.h" + +void Packer::writeBytes(int n, const QByteArray &b) +{ + if (b.size() > n) { + _buf->append(b.constData(), n); + } else { + int diff = n - b.size(); + _buf->append(b); + if (diff > 0) { + _buf->append(QByteArray(diff, '\0')); + } + } +} + +void Packer::writeUuid(const QUuid &uuid) +{ + writeBytes(16, uuid.toRfc4122()); +} + +void Packer::writeDict(const QMap &d) +{ + int size = d.size(); + if (size > 0xFF) { + logger()->warn() << "Dictionary is too large to encode"; + writeLE(0); + return; + } + + writeLE(size); + + for (QMap::const_iterator it = d.constBegin(); it != d.constEnd(); ++it) { + writeLE(it.key()); + + switch (int(it.value().type())) { + case QMetaType::Char: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::Short: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::Int: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::UChar: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::UShort: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::UInt: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::QByteArray: { + QByteArray ba = it.value().toByteArray(); + writeLE(WatchConnector::typeBYTES); + writeLE(ba.size()); + _buf->append(ba); + break; + } + + default: + logger()->warn() << "Unknown dict item type:" << it.value().typeName(); + /* Fallthrough */ + case QMetaType::QString: + case QMetaType::QUrl: + { + QByteArray s = it.value().toString().toUtf8(); + writeLE(WatchConnector::typeSTRING); + writeLE(s.size()); + _buf->append(s); + break; + } + } + } +} diff --git a/daemon/packer.h b/daemon/packer.h new file mode 100644 index 0000000..d22072c --- /dev/null +++ b/daemon/packer.h @@ -0,0 +1,67 @@ +#ifndef PACKER_H +#define PACKER_H + +#include +#include +#include +#include +#include +#include + +class Packer +{ + LOG4QT_DECLARE_STATIC_LOGGER(logger, Packer) + +public: + Packer(QByteArray *buf); + + template + void write(T v); + + template + void writeLE(T v); + + void writeBytes(int n, const QByteArray &b); + + void writeFixedString(int n, const QString &s); + + void writeUuid(const QUuid &uuid); + + void writeDict(const QMap &d); + +private: + char *p(int n); + uchar *up(int n); + QByteArray *_buf; +}; + +inline Packer::Packer(QByteArray *buf) + : _buf(buf) +{ +} + +template +void Packer::write(T v) +{ + qToBigEndian(v, up(sizeof(T))); +} + +template +void Packer::writeLE(T v) +{ + qToLittleEndian(v, up(sizeof(T))); +} + +inline char * Packer::p(int n) +{ + int size = _buf->size(); + _buf->resize(size + n); + return &_buf->data()[size]; +} + +inline uchar * Packer::up(int n) +{ + return reinterpret_cast(p(n)); +} + +#endif // PACKER_H diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp index fc38020..e904db8 100644 --- a/daemon/unpacker.cpp +++ b/daemon/unpacker.cpp @@ -29,7 +29,7 @@ QMap Unpacker::readDict() QMap d; if (checkBad(1)) return d; - const int n = read(); + const int n = readLE(); for (int i = 0; i < n; i++) { if (checkBad(4 + 1 + 2)) return d; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a3ea181..dd95821 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -89,7 +89,7 @@ QString WatchConnector::decodeEndpoint(uint val) return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) +void WatchConnector::setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func) { if (func) { handlers.insert(endpoint, func); @@ -241,6 +241,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; + logger()->debug() << data.toHex(); socket->write(data); } } @@ -251,7 +252,7 @@ void WatchConnector::onBytesWritten(qint64 bytes) logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) { logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; @@ -268,6 +269,10 @@ void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) msg.append(data); sendData(msg); + + if (callback) { + tmpHandlers[endpoint].append(callback); + } } void WatchConnector::buildData(QByteArray &res, QStringList data) @@ -445,9 +450,8 @@ void WatchConnector::endPhoneCall(uint cookie) void WatchConnector::getAppbankStatus(const std::function& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_STATUS) { return false; } @@ -491,9 +495,8 @@ void WatchConnector::getAppbankStatus(const std::function &)>& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_UUIDS) { return false; } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 8a7d574..6c7fdd4 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -173,7 +173,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } - void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); static QString timeStamp(); @@ -192,7 +192,7 @@ public slots: void disconnect(); void reconnect(); - void sendMessage(uint endpoint, const QByteArray &data); + void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); void ping(uint val); void time(); -- cgit v1.2.3 From be139d8ff95160782b424134a025b30c82083e28 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 03:12:00 +0100 Subject: add stub xmlhttprequest and allow showConfig event --- daemon/jskitmanager.cpp | 14 ++++++ daemon/jskitmanager.h | 3 ++ daemon/jskitobjects.cpp | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitobjects.h | 56 ++++++++++++++++++++++++ daemon/manager.cpp | 8 +--- 5 files changed, 188 insertions(+), 7 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index f8ec34a..6023a9a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -19,6 +19,18 @@ JSKitManager::~JSKitManager() } } +QJSEngine * JSKitManager::engine() +{ + return _engine; +} + +void JSKitManager::showConfiguration() +{ + if (_engine) { + _jspebble->invokeCallbacks("showConfiguration"); + } +} + void JSKitManager::handleAppStarted(const QUuid &uuid) { AppInfo info = _apps->info(uuid); @@ -83,6 +95,8 @@ void JSKitManager::startJsApp() windowObj.setProperty("localStorage", globalObj.property("localStorage")); globalObj.setProperty("window", windowObj); + _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); + 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(); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index d09bf54..73d7853 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -18,10 +18,13 @@ public: explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); ~JSKitManager(); + QJSEngine * engine(); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: + void showConfiguration(); private slots: void handleAppStarted(const QUuid &uuid); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index aecc55d..f745233 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "jskitobjects.h" @@ -61,6 +62,13 @@ void JSKitPebble::openUrl(const QUrl &url) } } +QJSValue JSKitPebble::createXMLHttpRequest() +{ + JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); + // Should be deleted by JS engine. + return _mgr->engine()->newQObject(xhr); +} + void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) { if (!_callbacks.contains(type)) return; @@ -144,3 +152,109 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) fileName.remove('}'); return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } + +JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) + : QObject(parent), _mgr(mgr), + _net(new QNetworkAccessManager(this)), _reply(0) +{ + logger()->debug() << "constructed"; +} + +JSKitXMLHttpRequest::~JSKitXMLHttpRequest() +{ + logger()->debug() << "destructed"; +} + +void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async) +{ + if (_reply) { + _reply->deleteLater(); + _reply = 0; + } + + _request = QNetworkRequest(QUrl(url)); + _verb = method; + Q_UNUSED(async); +} + +void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) +{ + logger()->debug() << "setRequestHeader" << header << value; + _request.setRawHeader(header.toLatin1(), value.toLatin1()); +} + +void JSKitXMLHttpRequest::send(const QString &body) +{ + QBuffer *buffer = new QBuffer; + buffer->setData(body.toUtf8()); + logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << body; + _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); + connect(_reply, &QNetworkReply::finished, this, &JSKitXMLHttpRequest::handleReplyFinished); + buffer->setParent(_reply); // So that it gets deleted alongside the reply object. +} + +void JSKitXMLHttpRequest::abort() +{ + if (_reply) { + _reply->deleteLater(); + _reply = 0; + } +} + +QJSValue JSKitXMLHttpRequest::onload() const +{ + return _onload; +} + +void JSKitXMLHttpRequest::setOnload(const QJSValue &value) +{ + _onload = value; +} + +unsigned short JSKitXMLHttpRequest::readyState() const +{ + if (!_reply) { + return UNSENT; + } else if (_reply->isFinished()) { + return DONE; + } else { + return LOADING; + } +} + +unsigned short JSKitXMLHttpRequest::status() const +{ + if (!_reply || !_reply->isFinished()) { + return 0; + } else { + return _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); + } +} + +QString JSKitXMLHttpRequest::responseText() const +{ + return QString::fromUtf8(_response); +} + +void JSKitXMLHttpRequest::handleReplyFinished() +{ + if (!_reply) { + logger()->info() << "reply finished too late"; + return; + } + + _response = _reply->readAll(); + logger()->debug() << "reply finished, reply text:" << QString::fromUtf8(_response); + + emit readyStateChanged(); + emit statusChanged(); + emit responseTextChanged(); + + + if (_onload.isCallable()) { + logger()->debug() << "going to call onload handler:" << _onload.toString(); + _onload.callWithInstance(_mgr->engine()->toScriptValue(this)); + } else { + logger()->debug() << "No onload set"; + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index c59bfac..1962e01 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -2,6 +2,8 @@ #define JSKITMANAGER_P_H #include +#include +#include #include "jskitmanager.h" class JSKitPebble : public QObject @@ -21,6 +23,8 @@ public: Q_INVOKABLE void openUrl(const QUrl &url); + Q_INVOKABLE QJSValue createXMLHttpRequest(); + void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: @@ -69,4 +73,56 @@ private: int _len; }; +class JSKitXMLHttpRequest : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + + Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) + Q_PROPERTY(unsigned short readyState READ readyState NOTIFY readyStateChanged) + Q_PROPERTY(unsigned short status READ status NOTIFY statusChanged) + Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) + +public: + explicit JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent = 0); + ~JSKitXMLHttpRequest(); + + enum ReadyStates { + UNSENT, + OPENED, + HEADERS_RECEIVED, + LOADING, + DONE + }; + + Q_INVOKABLE void open(const QString &method, const QString &url, bool async); + Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); + Q_INVOKABLE void send(const QString &body); + Q_INVOKABLE void abort(); + + QJSValue onload() const; + void setOnload(const QJSValue &value); + + unsigned short readyState() const; + unsigned short status() const; + QString responseText() const; + +signals: + void readyStateChanged(); + void statusChanged(); + void responseTextChanged(); + +private slots: + void handleReplyFinished(); + +private: + JSKitManager *_mgr; + QNetworkAccessManager *_net; + QString _verb; + QNetworkRequest _request; + QNetworkReply *_reply; + QByteArray _response; + QJSValue _onload; +}; + #endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8d41c89..9db4c70 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -395,11 +395,5 @@ 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(); - }); + js->showConfiguration(); } -- cgit v1.2.3 From 81f91639969de0f3852a3fe73db13b4cb0ecf3b4 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 04:13:06 +0100 Subject: hackily implement openURL by signalling the URLs via D-Bus to the settings app, which pops a webview --- app/app.pro | 3 ++- app/pebbledinterface.cpp | 16 ++++++++++++++++ app/pebbledinterface.h | 4 ++++ app/qml/pages/WebViewPage.qml | 29 +++++++++++++++++++++++++++++ app/qml/pebble.qml | 5 +++++ daemon/dbusadaptor.cpp | 5 +++++ daemon/dbusadaptor.h | 8 ++++++++ daemon/jskitmanager.cpp | 10 ++++++++++ daemon/jskitmanager.h | 2 ++ daemon/jskitobjects.cpp | 10 ++++++++-- daemon/jskitobjects.h | 12 ++++++------ daemon/manager.cpp | 6 ++++++ daemon/manager.h | 3 ++- 13 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 app/qml/pages/WebViewPage.qml diff --git a/app/app.pro b/app/app.pro index cb4c33b..e08fa87 100644 --- a/app/app.pro +++ b/app/app.pro @@ -22,4 +22,5 @@ OTHER_FILES += \ qml/pebble.qml \ qml/images/* \ pebble.desktop \ - pebble.png + pebble.png \ + qml/pages/WebViewPage.qml diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index c6f5674..8759863 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -19,6 +19,10 @@ PebbledInterface::PebbledInterface(QObject *parent) : PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, "pebbleChanged", this, SLOT(onPebbleChanged())); + QDBusConnection::sessionBus().connect( + PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, + "openUrl", this, SIGNAL(openUrl(QString))); + // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' // when going down @@ -163,3 +167,15 @@ void PebbledInterface::reconnect() qDebug() << __FUNCTION__; PebbledDbusInterface.call("reconnect"); } + +void PebbledInterface::test() +{ + qDebug() << __FUNCTION__; + PebbledDbusInterface.call("test"); +} + +void PebbledInterface::webviewClosed(const QString &result) +{ + qDebug() << __FUNCTION__; + PebbledDbusInterface.call("webviewClosed", QVariant::fromValue(result)); +} diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index 0a6f15d..7d00110 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -45,6 +45,8 @@ signals: void nameChanged(); void addressChanged(); + void openUrl(const QString &url); + public slots: void setEnabled(bool); void setActive(bool); @@ -52,6 +54,8 @@ public slots: void time(); void disconnect(); void reconnect(); + void test(); + void webviewClosed(const QString &result); private slots: void getUnitProperties(); diff --git a/app/qml/pages/WebViewPage.qml b/app/qml/pages/WebViewPage.qml new file mode 100644 index 0000000..2c6fcf0 --- /dev/null +++ b/app/qml/pages/WebViewPage.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0 +import QtQml 2.1 +import QtWebKit 3.0 +import Sailfish.Silica 1.0 + +Page { + id: webviewPage + + property alias url: webview.url + + SilicaWebView { + id: webview + anchors.fill: parent + + onNavigationRequested: { + console.log("navigation requested to " + request.url); + var url = request.url.toString() + if (/^pebblejs:\/\/close/.exec(url)) { + var data = decodeURI(url.substring(17)); + console.log("match with pebble close regexp. data: " + data); + pebbled.webviewClosed(data); + pageStack.pop(); + request.action = WebView.IgnoreRequest; + } else { + request.action = WebView.AcceptRequest; + } + } + } +} diff --git a/app/qml/pebble.qml b/app/qml/pebble.qml index da3bfb5..2ff0839 100644 --- a/app/qml/pebble.qml +++ b/app/qml/pebble.qml @@ -41,5 +41,10 @@ ApplicationWindow PebbledInterface { id: pebbled + + onOpenUrl: { + console.log("got open url: " + url); + pageStack.push(Qt.resolvedUrl("pages/WebViewPage.qml"), {url: url}); + } } } diff --git a/daemon/dbusadaptor.cpp b/daemon/dbusadaptor.cpp index 7bbf623..25e2508 100644 --- a/daemon/dbusadaptor.cpp +++ b/daemon/dbusadaptor.cpp @@ -85,3 +85,8 @@ void PebbledAdaptor::test() { QMetaObject::invokeMethod(parent(), "test"); } + +void PebbledAdaptor::webviewClosed(const QString &result) +{ + QMetaObject::invokeMethod(parent(), "webviewClosed", Q_ARG(QString, result)); +} diff --git a/daemon/dbusadaptor.h b/daemon/dbusadaptor.h index 54a0963..f347f92 100644 --- a/daemon/dbusadaptor.h +++ b/daemon/dbusadaptor.h @@ -47,6 +47,12 @@ class PebbledAdaptor: public QDBusAbstractAdaptor " \n" " \n" " \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" " \n" "") public: @@ -72,9 +78,11 @@ public Q_SLOTS: // METHODS void time(); void reconnect(); void test(); + void webviewClosed(const QString &result); Q_SIGNALS: // SIGNALS void connectedChanged(); void pebbleChanged(); + void openUrl(const QString &s); }; #endif diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 6023a9a..cfd860e 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -31,6 +31,16 @@ void JSKitManager::showConfiguration() } } +void JSKitManager::handleWebviewClosed(const QString &result) +{ + if (_engine) { + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("response", _engine->toScriptValue(result)); + + _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); + } +} + void JSKitManager::handleAppStarted(const QUuid &uuid) { AppInfo info = _apps->info(uuid); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 73d7853..fd6040d 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -22,9 +22,11 @@ public: signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); + void appOpenUrl(const QString &url); public slots: void showConfiguration(); + void handleWebviewClosed(const QString &result); private slots: void handleAppStarted(const QUuid &uuid); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f745233..9e4cfde 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -54,12 +54,15 @@ void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QSt emit _mgr->appNotification(_appInfo.uuid(), title, body); } -void JSKitPebble::openUrl(const QUrl &url) +void JSKitPebble::openURL(const QUrl &url) { logger()->debug() << "opening url" << url.toString(); + emit _mgr->appOpenUrl(url.toString()); +#if 0 /* Until we figure out how to do this. Maybe signal the daemon? */ if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } +#endif } QJSValue JSKitPebble::createXMLHttpRequest() @@ -253,7 +256,10 @@ void JSKitXMLHttpRequest::handleReplyFinished() if (_onload.isCallable()) { logger()->debug() << "going to call onload handler:" << _onload.toString(); - _onload.callWithInstance(_mgr->engine()->toScriptValue(this)); + QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); + if (result.isError()) { + logger()->warn() << "JS error on onload handler:" << result.toString(); + } } else { logger()->debug() << "No onload set"; } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 1962e01..4f000f1 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -21,7 +21,7 @@ public: Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); - Q_INVOKABLE void openUrl(const QUrl &url); + Q_INVOKABLE void openURL(const QUrl &url); Q_INVOKABLE QJSValue createXMLHttpRequest(); @@ -88,11 +88,11 @@ public: ~JSKitXMLHttpRequest(); enum ReadyStates { - UNSENT, - OPENED, - HEADERS_RECEIVED, - LOADING, - DONE + UNSENT = 0, + OPENED = 1, + HEADERS_RECEIVED = 2, + LOADING = 3, + DONE = 4 }; Q_INVOKABLE void open(const QString &method, const QString &url, bool async); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 9db4c70..e732d38 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -61,6 +61,7 @@ Manager::Manager(Settings *settings, QObject *parent) : session.registerService("org.pebbled"); connect(dbus, SIGNAL(pebbleChanged()), adaptor, SIGNAL(pebbleChanged())); connect(watch, SIGNAL(connectedChanged()), adaptor, SIGNAL(connectedChanged())); + connect(js, SIGNAL(appOpenUrl(QString)), adaptor, SIGNAL(openUrl(QString))); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -397,3 +398,8 @@ void Manager::test() js->showConfiguration(); } + +void Manager::onWebviewClosed(const QString &result) +{ + js->handleWebviewClosed(result); +} diff --git a/daemon/manager.h b/daemon/manager.h index f1dd53e..b0e15fb 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -84,6 +84,7 @@ public slots: private slots: void test(); + void onWebviewClosed(const QString &result); void onSettingChanged(const QString &key); void onSettingsChanged(); void onPebbleChanged(); @@ -123,7 +124,7 @@ public slots: void disconnect() { static_cast(parent())->watch->disconnect(); } void reconnect() { static_cast(parent())->watch->reconnect(); } void test() { static_cast(parent())->test(); } - + void webviewClosed(const QString &result) { static_cast(parent())->onWebviewClosed(result); } }; #endif // MANAGER_H -- cgit v1.2.3 From c35a3a9bea759cadf1e975a2a62e50789cad096c Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 2 Dec 2014 23:33:19 +0100 Subject: define new D-Bus interface, use qmake to generate adaptors/interfaces also slightly clean up the way d-bus is handled both in daemon and UI --- app/app.pro | 2 + app/pebbledinterface.cpp | 100 +++++++++++++++++++---------------------------- app/pebbledinterface.h | 45 +++++++-------------- daemon/daemon.pro | 5 +-- daemon/dbusadaptor.cpp | 92 ------------------------------------------- daemon/dbusadaptor.h | 88 ----------------------------------------- daemon/dbusconnector.h | 4 +- daemon/manager.cpp | 32 ++++++++------- daemon/manager.h | 91 ++++++++++++++++++++++++++++++------------ daemon/org.pebbled.xml | 20 ---------- org.pebbled.Watch.xml | 46 ++++++++++++++++++++++ 11 files changed, 191 insertions(+), 334 deletions(-) delete mode 100644 daemon/dbusadaptor.cpp delete mode 100644 daemon/dbusadaptor.h delete mode 100644 daemon/org.pebbled.xml create mode 100644 org.pebbled.Watch.xml diff --git a/app/app.pro b/app/app.pro index e08fa87..e0ff449 100644 --- a/app/app.pro +++ b/app/app.pro @@ -14,6 +14,8 @@ SOURCES += \ HEADERS += \ pebbledinterface.h +DBUS_INTERFACES += ../org.pebbled.Watch.xml + OTHER_FILES += \ qml/cover/CoverPage.qml \ qml/pages/ManagerPage.qml \ diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 8759863..fb937f2 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -1,37 +1,33 @@ #include "pebbledinterface.h" +#include "watch_interface.h" -QString PebbledInterface::PEBBLED_SYSTEMD_UNIT("pebbled.service"); -QString PebbledInterface::PEBBLED_DBUS_SERVICE("org.pebbled"); -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) - +static const QString PEBBLED_SYSTEMD_UNIT("pebbled.service"); +static const QString PEBBLED_DBUS_SERVICE("org.pebbled"); +static const QString PEBBLED_DBUS_PATH("/org/pebbled/watch"); +static const QString PEBBLED_DBUS_IFACE("org.pebbled.Watch"); PebbledInterface::PebbledInterface(QObject *parent) : - QObject(parent), systemd(0) -{ - QDBusConnection::sessionBus().connect( - PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, - "connectedChanged", this, SIGNAL(connectedChanged())); - - QDBusConnection::sessionBus().connect( - PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, - "pebbleChanged", this, SLOT(onPebbleChanged())); - - QDBusConnection::sessionBus().connect( - PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, - "openUrl", this, SIGNAL(openUrl(QString))); + QObject(parent), + systemd(new QDBusInterface("org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + QDBusConnection::sessionBus(), this)), + watch(new OrgPebbledWatchInterface(PEBBLED_DBUS_SERVICE, + PEBBLED_DBUS_PATH, + QDBusConnection::sessionBus(), this)) +{ + connect(watch, &OrgPebbledWatchInterface::NameChanged, + this, &PebbledInterface::nameChanged); + connect(watch, &OrgPebbledWatchInterface::AddressChanged, + this, &PebbledInterface::addressChanged); + connect(watch, &OrgPebbledWatchInterface::ConnectedChanged, + this, &PebbledInterface::connectedChanged); // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' // when going down - connect(this, SIGNAL(activeChanged()), SIGNAL(connectedChanged())); - - systemd = new QDBusInterface("org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - QDBusConnection::sessionBus(), this); + connect(this, &PebbledInterface::activeChanged, + this, &PebbledInterface::connectedChanged); systemd->call("Subscribe"); @@ -59,9 +55,9 @@ void PebbledInterface::getUnitProperties() QDBusReply reply = QDBusConnection::sessionBus().call(request); if (reply.isValid()) { QVariantMap newProperties = reply.value(); - bool emitEnabledChanged = (properties["UnitFileState"] != newProperties["UnitFileState"]); - bool emitActiveChanged = (properties["ActiveState"] != newProperties["ActiveState"]); - properties = newProperties; + bool emitEnabledChanged = (unitProperties["UnitFileState"] != newProperties["UnitFileState"]); + bool emitActiveChanged = (unitProperties["ActiveState"] != newProperties["ActiveState"]); + unitProperties = newProperties; if (emitEnabledChanged) emit enabledChanged(); if (emitActiveChanged) emit activeChanged(); } else { @@ -73,22 +69,14 @@ void PebbledInterface::onPropertiesChanged(QString interface, QMapconnected(); } QString PebbledInterface::name() const { qDebug() << __FUNCTION__; - return PebbledDbusInterface.property(__FUNCTION__).toString(); + return watch->name(); } QString PebbledInterface::address() const { qDebug() << __FUNCTION__; - return PebbledDbusInterface.property(__FUNCTION__).toString(); + return watch->address(); } void PebbledInterface::ping() { qDebug() << __FUNCTION__; - PebbledDbusInterface.call("ping", 66); + watch->Ping(66); } void PebbledInterface::time() { qDebug() << __FUNCTION__; - PebbledDbusInterface.call("time"); + watch->SyncTime(); } void PebbledInterface::disconnect() { qDebug() << __FUNCTION__; - PebbledDbusInterface.call("disconnect"); + watch->Disconnect(); } void PebbledInterface::reconnect() { qDebug() << __FUNCTION__; - PebbledDbusInterface.call("reconnect"); + watch->Reconnect(); } -void PebbledInterface::test() +QUrl PebbledInterface::configureApp(const QUuid &uuid) { - qDebug() << __FUNCTION__; - PebbledDbusInterface.call("test"); + qDebug() << __FUNCTION__ << uuid; + QString url = watch->StartAppConfiguration(uuid.toString()); + return QUrl(url); } -void PebbledInterface::webviewClosed(const QString &result) +void PebbledInterface::setAppConfiguration(const QUuid &uuid, const QString &data) { - qDebug() << __FUNCTION__; - PebbledDbusInterface.call("webviewClosed", QVariant::fromValue(result)); + watch->SendAppConfigurationData(uuid.toString(), data); } diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index 7d00110..78724ad 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -2,51 +2,36 @@ #define PEBBLEDINTERFACE_H #include -#include -#include +#include +#include + +class OrgPebbledWatchInterface; class PebbledInterface : public QObject { Q_OBJECT - - static QString PEBBLED_SYSTEMD_UNIT; - static QString PEBBLED_DBUS_SERVICE; - static QString PEBBLED_DBUS_PATH; - static QString PEBBLED_DBUS_IFACE; - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) - bool enabled() const; - Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) - bool active() const; - Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) - bool connected() const; - - Q_PROPERTY(QVariantMap pebble READ pebble NOTIFY pebbleChanged) - QVariantMap pebble() const; - Q_PROPERTY(QString name READ name NOTIFY nameChanged) - QString name() const; - Q_PROPERTY(QString address READ address NOTIFY addressChanged) - QString address() const; - public: explicit PebbledInterface(QObject *parent = 0); + bool enabled() const; + bool active() const; + bool connected() const; + QString name() const; + QString address() const; + signals: void enabledChanged(); void activeChanged(); - void connectedChanged(); - void pebbleChanged(); void nameChanged(); void addressChanged(); - void openUrl(const QString &url); - public slots: void setEnabled(bool); void setActive(bool); @@ -54,19 +39,19 @@ public slots: void time(); void disconnect(); void reconnect(); - void test(); - void webviewClosed(const QString &result); + + QUrl configureApp(const QUuid &uuid); + void setAppConfiguration(const QUuid &uuid, const QString &data); private slots: void getUnitProperties(); void onPropertiesChanged(QString interface, QMap changed, QStringList invalidated); - void onPebbleChanged(); private: QDBusInterface *systemd; + OrgPebbledWatchInterface *watch; QDBusObjectPath unitPath; - - QVariantMap properties; + QVariantMap unitProperties; }; #endif // PEBBLEDINTERFACE_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index c59d408..3306541 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -20,7 +20,6 @@ SOURCES += \ notificationmanager.cpp \ watchconnector.cpp \ dbusconnector.cpp \ - dbusadaptor.cpp \ appmanager.cpp \ musicmanager.cpp \ datalogmanager.cpp \ @@ -38,7 +37,6 @@ HEADERS += \ notificationmanager.h \ watchconnector.h \ dbusconnector.h \ - dbusadaptor.h \ settings.h \ appmanager.h \ musicmanager.h \ @@ -51,10 +49,11 @@ HEADERS += \ packer.h OTHER_FILES += \ - org.pebbled.xml \ ../log4qt-debug.conf \ ../log4qt-release.conf +DBUS_ADAPTORS += ../org.pebbled.Watch.xml + INSTALLS += target pebbled confile target.path = /usr/bin diff --git a/daemon/dbusadaptor.cpp b/daemon/dbusadaptor.cpp deleted file mode 100644 index 25e2508..0000000 --- a/daemon/dbusadaptor.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -a dbusadaptor org.pebbled.xml - * - * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). - * - * This is an auto-generated file. - * Do not edit! All changes made to it will be lost. - */ - -#include "dbusadaptor.h" -#include -#include -#include -#include -#include -#include -#include - -/* - * Implementation of adaptor class PebbledAdaptor - */ - -PebbledAdaptor::PebbledAdaptor(QObject *parent) - : QDBusAbstractAdaptor(parent) -{ - // constructor - setAutoRelaySignals(true); -} - -PebbledAdaptor::~PebbledAdaptor() -{ - // destructor -} - -QString PebbledAdaptor::address() const -{ - // get the value of property address - return qvariant_cast< QString >(parent()->property("address")); -} - -bool PebbledAdaptor::connected() const -{ - // get the value of property connected - return qvariant_cast< bool >(parent()->property("connected")); -} - -QString PebbledAdaptor::name() const -{ - // get the value of property name - return qvariant_cast< QString >(parent()->property("name")); -} - -QVariantMap PebbledAdaptor::pebble() const -{ - // get the value of property pebble - return qvariant_cast< QVariantMap >(parent()->property("pebble")); -} - -void PebbledAdaptor::disconnect() -{ - // handle method call org.pebbled.disconnect - QMetaObject::invokeMethod(parent(), "disconnect"); -} - -void PebbledAdaptor::ping(int val) -{ - // handle method call org.pebbled.ping - QMetaObject::invokeMethod(parent(), "ping", Q_ARG(int, val)); -} - -void PebbledAdaptor::time() -{ - // handle method call org.pebbled.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"); -} - -void PebbledAdaptor::webviewClosed(const QString &result) -{ - QMetaObject::invokeMethod(parent(), "webviewClosed", Q_ARG(QString, result)); -} diff --git a/daemon/dbusadaptor.h b/daemon/dbusadaptor.h deleted file mode 100644 index f347f92..0000000 --- a/daemon/dbusadaptor.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -a dbusadaptor org.pebbled.xml - * - * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). - * - * This is an auto-generated file. - * This file may have been hand-edited. Look for HAND-EDIT comments - * before re-generating it. - */ - -#ifndef DBUSADAPTOR_H_1404986135 -#define DBUSADAPTOR_H_1404986135 - -#include -#include -QT_BEGIN_NAMESPACE -class QByteArray; -template class QList; -template class QMap; -class QString; -class QStringList; -class QVariant; -QT_END_NAMESPACE - -/* - * Adaptor class for interface org.pebbled - */ -class PebbledAdaptor: public QDBusAbstractAdaptor -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.pebbled") - Q_CLASSINFO("D-Bus Introspection", "" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" - "") -public: - PebbledAdaptor(QObject *parent); - virtual ~PebbledAdaptor(); - -public: // PROPERTIES - Q_PROPERTY(QString address READ address) - QString address() const; - - Q_PROPERTY(bool connected READ connected) - bool connected() const; - - Q_PROPERTY(QString name READ name) - QString name() const; - - Q_PROPERTY(QVariantMap pebble READ pebble) - QVariantMap pebble() const; - -public Q_SLOTS: // METHODS - void disconnect(); - void ping(int val); - void time(); - void reconnect(); - void test(); - void webviewClosed(const QString &result); -Q_SIGNALS: // SIGNALS - void connectedChanged(); - void pebbleChanged(); - void openUrl(const QString &s); -}; - -#endif diff --git a/daemon/dbusconnector.h b/daemon/dbusconnector.h index e6dd793..d4c1bcb 100644 --- a/daemon/dbusconnector.h +++ b/daemon/dbusconnector.h @@ -20,8 +20,8 @@ class DBusConnector : public QObject public: explicit DBusConnector(QObject *parent = 0); - QVariantMap pebble() { return pebbleProps; } - QStringList services() { return dbusServices; } + QVariantMap pebble() const { return pebbleProps; } + QStringList services() const { return dbusServices; } signals: void pebbleChanged(); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index e732d38..38b3948 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -3,10 +3,11 @@ #include #include "manager.h" -#include "dbusadaptor.h" +#include "watch_adaptor.h" Manager::Manager(Settings *settings, QObject *parent) : QObject(parent), settings(settings), + proxy(new PebbledProxy(this)), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), apps(new AppManager(this)), @@ -54,14 +55,16 @@ Manager::Manager(Settings *settings, QObject *parent) : 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 &))); - PebbledProxy *proxy = new PebbledProxy(this); - PebbledAdaptor *adaptor = new PebbledAdaptor(proxy); + connect(appmsg, &AppMsgManager::messageReceived, this, &Manager::onAppMessage); + QDBusConnection session = QDBusConnection::sessionBus(); - session.registerObject("/org/pebbled", proxy); + new WatchAdaptor(proxy); + session.registerObject("/org/pebbled/watch", proxy); session.registerService("org.pebbled"); - connect(dbus, SIGNAL(pebbleChanged()), adaptor, SIGNAL(pebbleChanged())); - connect(watch, SIGNAL(connectedChanged()), adaptor, SIGNAL(connectedChanged())); - connect(js, SIGNAL(appOpenUrl(QString)), adaptor, SIGNAL(openUrl(QString))); + + connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); + connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); + connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -289,7 +292,7 @@ void Manager::onMprisPropertiesChanged(QString interface, QMap logger()->debug() << "lastSeenMpris:" << lastSeenMpris; } -QString Manager::mpris() +QString Manager::mpris() const { const QStringList &services = dbus->services(); if (not lastSeenMpris.isEmpty() && services.contains(lastSeenMpris)) @@ -316,7 +319,7 @@ void Manager::setMprisMetadata(QVariantMap metadata) emit mprisMetadataChanged(mprisMetadata); } -QString Manager::getCurrentProfile() +QString Manager::getCurrentProfile() const { QDBusReply profile = QDBusConnection::sessionBus().call( QDBusMessage::createMethodCall("com.nokia.profiled", "/com/nokia/profiled", "com.nokia.profiled", "get_profile")); @@ -386,12 +389,6 @@ void Manager::transliterateMessage(const QString &text) } } -bool Manager::uploadApp(const QUuid &uuid, int slot) -{ - // TODO - return false; -} - void Manager::test() { logger()->debug() << "Starting test"; @@ -399,6 +396,11 @@ void Manager::test() js->showConfiguration(); } +void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) +{ + emit proxy->AppMessage(uuid.toString(), data); +} + void Manager::onWebviewClosed(const QString &result) { js->handleWebviewClosed(result); diff --git a/daemon/manager.h b/daemon/manager.h index b0e15fb..c12d3dc 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -24,9 +24,9 @@ using namespace QtContacts; -class Manager : - public QObject, - protected QDBusContext +class PebbledProxy; + +class Manager : public QObject, protected QDBusContext { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER @@ -40,6 +40,8 @@ class Manager : Settings *settings; + PebbledProxy *proxy; + WatchConnector *watch; DBusConnector *dbus; AppManager *apps; @@ -58,6 +60,7 @@ class Manager : QString defaultProfile; QString lastSeenMpris; + QVariantMap mprisMetadata; QScopedPointer transliterator; @@ -65,13 +68,11 @@ public: explicit Manager(Settings *settings, QObject *parent = 0); ~Manager(); - Q_INVOKABLE QString findPersonByNumber(QString number); - Q_INVOKABLE QString getCurrentProfile(); - Q_INVOKABLE QString mpris(); - QVariantMap mprisMetadata; - QVariantMap getMprisMetadata() { return mprisMetadata; } + QString findPersonByNumber(QString number); + QString getCurrentProfile() const; + QString mpris() const; - Q_INVOKABLE bool uploadApp(const QUuid &uuid, int slot = -1); + inline QVariantMap getMprisMetadata() const { return mprisMetadata; } protected: void transliterateMessage(const QString &text); @@ -100,31 +101,71 @@ private slots: void onMprisPropertiesChanged(QString,QMap,QStringList); void setMprisMetadata(QDBusArgument metadata); void setMprisMetadata(QVariantMap metadata); + + void onAppMessage(const QUuid &uuid, const QVariantMap &data); }; -class PebbledProxy : public QObject +/** This class is what's actually exported over D-Bus, + * so the names of the slots and properties must match with org.pebbled.Watch D-Bus interface. + * Case sensitive. Otherwise, _runtime_ failures will occur. */ +// The methods are marked inline so that they may be inlined inside qt_metacall +class PebbledProxy : public QObject, protected QDBusContext { Q_OBJECT - Q_PROPERTY(QVariantMap pebble READ pebble) - Q_PROPERTY(QString name READ pebbleName) - Q_PROPERTY(QString address READ pebbleAddress) - Q_PROPERTY(bool connected READ pebbleConnected) + Q_PROPERTY(QString Name READ Name NOTIFY NameChanged) + Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) + Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) - QVariantMap pebble() { return static_cast(parent())->dbus->pebble(); } - QString pebbleName() { return static_cast(parent())->dbus->pebble()["Name"].toString(); } - QString pebbleAddress() { return static_cast(parent())->dbus->pebble()["Address"].toString(); } - bool pebbleConnected() { return static_cast(parent())->watch->isConnected(); } + inline Manager* manager() const { return static_cast(parent()); } + inline QVariantMap pebble() const { return manager()->dbus->pebble(); } public: - explicit PebbledProxy(QObject *parent) : QObject(parent) {} + inline explicit PebbledProxy(QObject *parent) : QObject(parent) {} + + inline QString Name() const { return pebble()["Name"].toString(); } + inline QString Address() const { return pebble()["Address"].toString(); } + inline bool Connected() const { return manager()->watch->isConnected(); } public slots: - void ping(int val) { static_cast(parent())->watch->ping((unsigned int)val); } - 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(); } - void webviewClosed(const QString &result) { static_cast(parent())->onWebviewClosed(result); } + inline void Disconnected() { manager()->watch->disconnect(); } + inline void Reconnect() { manager()->watch->reconnect(); } + inline void Ping(uint val) { manager()->watch->ping(val); } + inline void SyncTime() { manager()->watch->time(); } + + inline void LaunchApp(const QString &uuid) { /* TODO */ } + inline void CloseApp(const QString &uuid) { /* TODO */ } + + bool SendAppMessage(const QString &uuid, const QVariantMap &data) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + setDelayedReply(true); + manager()->appmsg->send(uuid, data, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); + this->connection().send(reply); + }, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); + this->connection().send(reply); + }); + return false; // D-Bus clients should never see this reply. + } + + QString StartAppConfiguration(const QString &uuid) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + setDelayedReply(true); + + // TODO + } + + inline void SendAppConfiguration(const QString &uuid, const QString &data) { + // TODO + } + +signals: + void NameChanged(); + void AddressChanged(); + void ConnectedChanged(); + void AppMessage(const QString &uuid, const QVariantMap &data); }; #endif // MANAGER_H diff --git a/daemon/org.pebbled.xml b/daemon/org.pebbled.xml deleted file mode 100644 index e255782..0000000 --- a/daemon/org.pebbled.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml new file mode 100644 index 0000000..72aab6b --- /dev/null +++ b/org.pebbled.Watch.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 843e8c2550f69de3b9dfc3ec5f13d2c3a5710896 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 00:00:42 +0100 Subject: implement more parts of the new D-Bus API --- daemon/appmsgmanager.cpp | 10 ++++++ daemon/appmsgmanager.h | 2 ++ daemon/jskitmanager.cpp | 5 +++ daemon/jskitmanager.h | 3 +- daemon/jskitobjects.cpp | 7 +---- daemon/manager.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++----- daemon/manager.h | 37 +++++++--------------- org.pebbled.Watch.xml | 9 ++++++ 8 files changed, 113 insertions(+), 41 deletions(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index b620078..afaabee 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -130,6 +130,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) send(uuid, data, nullCallback, nullCallback); } +void AppMsgManager::launchApp(const QUuid &uuid) +{ + // TODO +} + +void AppMsgManager::closeApp(const QUuid &uuid) +{ + // TODO +} + WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) { AppInfo info = apps->info(uuid); diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index ca1d484..bc9c82f 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -18,6 +18,8 @@ public: public slots: void send(const QUuid &uuid, const QVariantMap &data); + void launchApp(const QUuid &uuid); + void closeApp(const QUuid &uuid); signals: void appStarted(const QUuid &uuid); diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index cfd860e..9efc5c8 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -24,6 +24,11 @@ QJSEngine * JSKitManager::engine() return _engine; } +bool JSKitManager::isJSKitAppRunning() const +{ + return _engine != 0; +} + void JSKitManager::showConfiguration() { if (_engine) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index fd6040d..07ecec1 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -19,10 +19,11 @@ public: ~JSKitManager(); QJSEngine * engine(); + bool isJSKitAppRunning() const; signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); - void appOpenUrl(const QString &url); + void appOpenUrl(const QUrl &url); public slots: void showConfiguration(); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 9e4cfde..a0bc0ba 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -57,12 +57,7 @@ void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QSt void JSKitPebble::openURL(const QUrl &url) { logger()->debug() << "opening url" << url.toString(); - emit _mgr->appOpenUrl(url.toString()); -#if 0 /* Until we figure out how to do this. Maybe signal the daemon? */ - if (!QDesktopServices::openUrl(url)) { - logger()->warn() << "Failed to open URL:" << url; - } -#endif + emit _mgr->appOpenUrl(url); } QJSValue JSKitPebble::createXMLHttpRequest() diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 38b3948..0666de0 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -56,6 +56,8 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); connect(appmsg, &AppMsgManager::messageReceived, this, &Manager::onAppMessage); + connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); + connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); QDBusConnection session = QDBusConnection::sessionBus(); new WatchAdaptor(proxy); @@ -389,19 +391,82 @@ void Manager::transliterateMessage(const QString &text) } } -void Manager::test() +void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) { - logger()->debug() << "Starting test"; - - js->showConfiguration(); + emit proxy->AppMessage(uuid.toString(), data); } -void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) +void Manager::onAppOpened(const QUuid &uuid) { - emit proxy->AppMessage(uuid.toString(), data); + currentAppUuid = uuid; + emit proxy->AppOpened(uuid.toString()); } -void Manager::onWebviewClosed(const QString &result) +void Manager::onAppClosed(const QUuid &uuid) { - js->handleWebviewClosed(result); + currentAppUuid = QUuid(); + emit proxy->AppClosed(uuid.toString()); +} + +bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + setDelayedReply(true); + manager()->appmsg->send(uuid, data, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); + this->connection().send(reply); + }, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); + this->connection().send(reply); + }); + return false; // D-Bus clients should never see this reply. +} + +QString PebbledProxy::StartAppConfiguration(const QString &uuid) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (manager()->currentAppUuid != uuid) { + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return QString(); + } + + if (!manager()->js->isJSKitAppRunning()) { + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return QString(); + } + + // After calling showConfiguration() on the script, + // it will (eventually!) return a URL to us via the appOpenUrl signal. + + // So we can't send the D-Bus reply right now. + setDelayedReply(true); + + // Set up a signal handler to catch the appOpenUrl signal. + QMetaObject::Connection c = connect(manager()->js, &JSKitManager::appOpenUrl, + [this,msg,c](const QUrl &url) { + // Workaround: due to a GCC bug we can't capture the uuid parameter, but we can extract + // it again from the original message arguments. + QString uuid = msg.arguments().at(0).toString(); + if (manager()->currentAppUuid != uuid) { + // App was changed while we were waiting for the script.. + QDBusMessage reply = msg.createErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + connection().send(reply); + } else { + QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); + connection().send(reply); + } + + disconnect(c); + }); + // TODO: JS script may fail, never call OpenURL, or something like that + // In those cases we may leak the above connection + // So we need to also set a timeout or similar. + + manager()->js->showConfiguration(); + + return QString(); // This return value should never be used. } diff --git a/daemon/manager.h b/daemon/manager.h index c12d3dc..f099aac 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -62,6 +62,8 @@ class Manager : public QObject, protected QDBusContext QString lastSeenMpris; QVariantMap mprisMetadata; + QUuid currentAppUuid; + QScopedPointer transliterator; public: @@ -84,8 +86,6 @@ public slots: void applyProfile(); private slots: - void test(); - void onWebviewClosed(const QString &result); void onSettingChanged(const QString &key); void onSettingsChanged(); void onPebbleChanged(); @@ -103,6 +103,8 @@ private slots: void setMprisMetadata(QVariantMap metadata); void onAppMessage(const QUuid &uuid, const QVariantMap &data); + void onAppOpened(const QUuid &uuid); + void onAppClosed(const QUuid &uuid); }; /** This class is what's actually exported over D-Bus, @@ -132,32 +134,13 @@ public slots: inline void Ping(uint val) { manager()->watch->ping(val); } inline void SyncTime() { manager()->watch->time(); } - inline void LaunchApp(const QString &uuid) { /* TODO */ } - inline void CloseApp(const QString &uuid) { /* TODO */ } - - bool SendAppMessage(const QString &uuid, const QVariantMap &data) { - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - setDelayedReply(true); - manager()->appmsg->send(uuid, data, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); - this->connection().send(reply); - }, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); - this->connection().send(reply); - }); - return false; // D-Bus clients should never see this reply. - } - - QString StartAppConfiguration(const QString &uuid) { - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - setDelayedReply(true); + inline void LaunchApp(const QString &uuid) { manager()->appmsg->launchApp(uuid); } + inline void CloseApp(const QString &uuid) { manager()->appmsg->closeApp(uuid); } - // TODO - } + bool SendAppMessage(const QString &uuid, const QVariantMap &data); + QString StartAppConfiguration(const QString &uuid); - inline void SendAppConfiguration(const QString &uuid, const QString &data) { + void SendAppConfiguration(const QString &uuid, const QString &data) { // TODO } @@ -166,6 +149,8 @@ signals: void AddressChanged(); void ConnectedChanged(); void AppMessage(const QString &uuid, const QVariantMap &data); + void AppOpened(const QString &uuid); + void AppClosed(const QString &uuid); }; #endif // MANAGER_H diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 72aab6b..11f20b7 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -5,6 +5,7 @@ + @@ -15,6 +16,7 @@ + @@ -33,6 +35,13 @@ + + + + + + + -- cgit v1.2.3 From 69822e4dcf541a52e4202d5ff566364fb90e6ec0 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 00:48:19 +0100 Subject: implement UI for JS app configuration --- app/app.pro | 2 +- app/pebbledinterface.cpp | 41 +++++++++++++++++++++++++---------------- app/pebbledinterface.h | 7 +++++-- app/qml/pages/AboutPage.qml | 2 +- app/qml/pages/AppConfigPage.qml | 34 ++++++++++++++++++++++++++++++++++ app/qml/pages/WatchPage.qml | 30 ++++++++++++++++++++++++++++++ app/qml/pages/WebViewPage.qml | 29 ----------------------------- app/qml/pebble.qml | 5 ----- daemon/jskitmanager.cpp | 4 ++++ daemon/manager.cpp | 6 +++++- daemon/manager.h | 3 +++ org.pebbled.Watch.xml | 5 ++++- 12 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 app/qml/pages/AppConfigPage.qml delete mode 100644 app/qml/pages/WebViewPage.qml diff --git a/app/app.pro b/app/app.pro index e0ff449..ddf2dba 100644 --- a/app/app.pro +++ b/app/app.pro @@ -25,4 +25,4 @@ OTHER_FILES += \ qml/images/* \ pebble.desktop \ pebble.png \ - qml/pages/WebViewPage.qml + qml/pages/AppConfigPage.qml diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index fb937f2..0aceaa0 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -3,7 +3,7 @@ static const QString PEBBLED_SYSTEMD_UNIT("pebbled.service"); static const QString PEBBLED_DBUS_SERVICE("org.pebbled"); -static const QString PEBBLED_DBUS_PATH("/org/pebbled/watch"); +static const QString PEBBLED_DBUS_PATH("/org/pebbled/Watch"); static const QString PEBBLED_DBUS_IFACE("org.pebbled.Watch"); PebbledInterface::PebbledInterface(QObject *parent) : @@ -22,6 +22,8 @@ PebbledInterface::PebbledInterface(QObject *parent) : this, &PebbledInterface::addressChanged); connect(watch, &OrgPebbledWatchInterface::ConnectedChanged, this, &PebbledInterface::connectedChanged); + connect(watch, &OrgPebbledWatchInterface::AppUuidChanged, + this, &PebbledInterface::appUuidChanged); // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' @@ -67,7 +69,7 @@ void PebbledInterface::getUnitProperties() void PebbledInterface::onPropertiesChanged(QString interface, QMap changed, QStringList invalidated) { - qDebug() << __FUNCTION__ << interface << changed << invalidated; + qDebug() << Q_FUNC_INFO << interface << changed << invalidated; if (interface != "org.freedesktop.systemd1.Unit") return; if (invalidated.contains("UnitFileState") || invalidated.contains("ActiveState")) getUnitProperties(); @@ -75,7 +77,7 @@ void PebbledInterface::onPropertiesChanged(QString interface, QMapconnected(); } QString PebbledInterface::name() const { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; return watch->name(); } QString PebbledInterface::address() const { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; return watch->address(); } +QString PebbledInterface::appUuid() const +{ + qDebug() << Q_FUNC_INFO; + return watch->appUuid(); +} + void PebbledInterface::ping() { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; watch->Ping(66); } void PebbledInterface::time() { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; watch->SyncTime(); } void PebbledInterface::disconnect() { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; watch->Disconnect(); } void PebbledInterface::reconnect() { - qDebug() << __FUNCTION__; + qDebug() << Q_FUNC_INFO; watch->Reconnect(); } -QUrl PebbledInterface::configureApp(const QUuid &uuid) +QUrl PebbledInterface::configureApp(const QString &uuid) { - qDebug() << __FUNCTION__ << uuid; - QString url = watch->StartAppConfiguration(uuid.toString()); + qDebug() << Q_FUNC_INFO << uuid; + QString url = watch->StartAppConfiguration(uuid); return QUrl(url); } -void PebbledInterface::setAppConfiguration(const QUuid &uuid, const QString &data) +void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &data) { - watch->SendAppConfigurationData(uuid.toString(), data); + qDebug() << Q_FUNC_INFO << uuid << data; + watch->SendAppConfigurationData(uuid, data); } diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index 78724ad..f506e67 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -15,6 +15,7 @@ class PebbledInterface : public QObject Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString address READ address NOTIFY addressChanged) + Q_PROPERTY(QString appUuid READ appUuid NOTIFY appUuidChanged) public: explicit PebbledInterface(QObject *parent = 0); @@ -24,6 +25,7 @@ public: bool connected() const; QString name() const; QString address() const; + QString appUuid() const; signals: void enabledChanged(); @@ -31,6 +33,7 @@ signals: void connectedChanged(); void nameChanged(); void addressChanged(); + void appUuidChanged(); public slots: void setEnabled(bool); @@ -40,8 +43,8 @@ public slots: void disconnect(); void reconnect(); - QUrl configureApp(const QUuid &uuid); - void setAppConfiguration(const QUuid &uuid, const QString &data); + QUrl configureApp(const QString &uuid); + void setAppConfiguration(const QString &uuid, const QString &data); private slots: void getUnitProperties(); diff --git a/app/qml/pages/AboutPage.qml b/app/qml/pages/AboutPage.qml index bec1031..8fd009e 100644 --- a/app/qml/pages/AboutPage.qml +++ b/app/qml/pages/AboutPage.qml @@ -40,7 +40,7 @@ Page { anchors { left: parent.left right: parent.right - margins: Theme.paddingSmall + margins: Theme.paddingMedium } font.pixelSize: Theme.fontSizeTiny horizontalAlignment: Text.AlignJustify diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml new file mode 100644 index 0000000..8fb31ca --- /dev/null +++ b/app/qml/pages/AppConfigPage.qml @@ -0,0 +1,34 @@ +import QtQuick 2.0 +import QtQml 2.1 +import QtWebKit 3.0 +import Sailfish.Silica 1.0 + +Page { + id: appConfigPage + + property alias url: webview.url + property string uuid + + SilicaWebView { + id: webview + anchors.fill: parent + + header: PageHeader { + title: "Configuring " + uuid + } + + onNavigationRequested: { + console.log("appconfig navigation requested to " + request.url); + var url = request.url.toString(); + if (/^pebblejs:\/\/close/.exec(url)) { + var data = decodeURI(url.substring(17)); + console.log("appconfig requesting close; data: " + data); + pebbled.setAppConfiguration(uuid, data); + pageStack.pop(); + request.action = WebView.IgnoreRequest; + } else { + request.action = WebView.AcceptRequest; + } + } + } +} diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 90e5ec9..8169507 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -77,6 +77,36 @@ Page { } } + + Label { + text: qsTr("App configuration") + font.family: Theme.fontFamilyHeading + color: Theme.highlightColor + anchors.right: parent.right + anchors.rightMargin: Theme.paddingMedium + } + + Button { + text: "Configure current app" + anchors { + left: parent.left + right: parent.right + margins: Theme.paddingLarge + } + onClicked: { + var uuid = pebbled.appUuid; + console.log("going to configureApp " + uuid); + var url = pebbled.configureApp(uuid); + console.log("obtained configure URL " + url); + if (url) { + pageStack.push(Qt.resolvedUrl("AppConfigPage.qml"), { + url: url, + uuid: uuid + }); + } + } + } + } } } diff --git a/app/qml/pages/WebViewPage.qml b/app/qml/pages/WebViewPage.qml deleted file mode 100644 index 2c6fcf0..0000000 --- a/app/qml/pages/WebViewPage.qml +++ /dev/null @@ -1,29 +0,0 @@ -import QtQuick 2.0 -import QtQml 2.1 -import QtWebKit 3.0 -import Sailfish.Silica 1.0 - -Page { - id: webviewPage - - property alias url: webview.url - - SilicaWebView { - id: webview - anchors.fill: parent - - onNavigationRequested: { - console.log("navigation requested to " + request.url); - var url = request.url.toString() - if (/^pebblejs:\/\/close/.exec(url)) { - var data = decodeURI(url.substring(17)); - console.log("match with pebble close regexp. data: " + data); - pebbled.webviewClosed(data); - pageStack.pop(); - request.action = WebView.IgnoreRequest; - } else { - request.action = WebView.AcceptRequest; - } - } - } -} diff --git a/app/qml/pebble.qml b/app/qml/pebble.qml index 2ff0839..da3bfb5 100644 --- a/app/qml/pebble.qml +++ b/app/qml/pebble.qml @@ -41,10 +41,5 @@ ApplicationWindow PebbledInterface { id: pebbled - - onOpenUrl: { - console.log("got open url: " + url); - pageStack.push(Qt.resolvedUrl("pages/WebViewPage.qml"), {url: url}); - } } } diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 9efc5c8..e1e4073 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -33,6 +33,8 @@ void JSKitManager::showConfiguration() { if (_engine) { _jspebble->invokeCallbacks("showConfiguration"); + } else { + logger()->warn() << "requested to show configuration, but JS engine is not running"; } } @@ -43,6 +45,8 @@ void JSKitManager::handleWebviewClosed(const QString &result) eventObj.setProperty("response", _engine->toScriptValue(result)); _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); + } else { + logger()->warn() << "webview closed event, but JS engine is not running"; } } diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 0666de0..1fc8a20 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -61,7 +61,7 @@ Manager::Manager(Settings *settings, QObject *parent) : QDBusConnection session = QDBusConnection::sessionBus(); new WatchAdaptor(proxy); - session.registerObject("/org/pebbled/watch", proxy); + session.registerObject("/org/pebbled/Watch", proxy); session.registerService("org.pebbled"); connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); @@ -399,6 +399,7 @@ void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) void Manager::onAppOpened(const QUuid &uuid) { currentAppUuid = uuid; + emit proxy->AppUuidChanged(); emit proxy->AppOpened(uuid.toString()); } @@ -406,6 +407,7 @@ void Manager::onAppClosed(const QUuid &uuid) { currentAppUuid = QUuid(); emit proxy->AppClosed(uuid.toString()); + emit proxy->AppUuidChanged(); } bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { @@ -468,5 +470,7 @@ QString PebbledProxy::StartAppConfiguration(const QString &uuid) { manager()->js->showConfiguration(); + // Note that the above signal handler _might_ have been already called by this point. + return QString(); // This return value should never be used. } diff --git a/daemon/manager.h b/daemon/manager.h index f099aac..22382b8 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -117,6 +117,7 @@ class PebbledProxy : public QObject, protected QDBusContext Q_PROPERTY(QString Name READ Name NOTIFY NameChanged) Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) + Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) inline Manager* manager() const { return static_cast(parent()); } inline QVariantMap pebble() const { return manager()->dbus->pebble(); } @@ -127,6 +128,7 @@ public: inline QString Name() const { return pebble()["Name"].toString(); } inline QString Address() const { return pebble()["Address"].toString(); } inline bool Connected() const { return manager()->watch->isConnected(); } + inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } public slots: inline void Disconnected() { manager()->watch->disconnect(); } @@ -148,6 +150,7 @@ signals: void NameChanged(); void AddressChanged(); void ConnectedChanged(); + void AppUuidChanged(); void AppMessage(const QString &uuid, const QVariantMap &data); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 11f20b7..759a6db 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -1,15 +1,18 @@ - + + + + -- cgit v1.2.3 From 87de94ea74c68b5ea0a4b125f1745c7449fbe4f1 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:19:57 +0100 Subject: fix actually setting the configuration, improve xhr support --- app/qml/pages/AppConfigPage.qml | 2 +- daemon/jskitmanager.cpp | 2 ++ daemon/jskitobjects.cpp | 67 ++++++++++++++++++++++++++++++++++++++--- daemon/jskitobjects.h | 11 ++++++- daemon/manager.cpp | 26 ++++++++++++++-- daemon/manager.h | 5 +-- 6 files changed, 100 insertions(+), 13 deletions(-) diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 8fb31ca..7b969a3 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -21,7 +21,7 @@ Page { console.log("appconfig navigation requested to " + request.url); var url = request.url.toString(); if (/^pebblejs:\/\/close/.exec(url)) { - var data = decodeURI(url.substring(17)); + var data = decodeURIComponent(url.substring(17)); console.log("appconfig requesting close; data: " + data); pebbled.setAppConfiguration(uuid, data); pageStack.pop(); diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index e1e4073..7bf8cdc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -44,6 +44,8 @@ void JSKitManager::handleWebviewClosed(const QString &result) QJSValue eventObj = _engine->newObject(); eventObj.setProperty("response", _engine->toScriptValue(result)); + logger()->debug() << "webview closed with the following result: " << result; + _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); } else { logger()->warn() << "webview closed event, but JS engine is not running"; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index a0bc0ba..728daf7 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -40,11 +40,27 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV logger()->debug() << "sendAppMessage" << data; _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { - logger()->debug() << "Invoking ack callback"; - callbackForAck.call(); + if (callbackForAck.isCallable()) { + logger()->debug() << "Invoking ack callback"; + QJSValue result = callbackForAck.call(); + if (result.isError()) { + logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" + << result.toString(); + } + } else { + logger()->debug() << "Ack callback not callable"; + } }, [this, callbackForNack]() mutable { - logger()->debug() << "Invoking nack callback"; - callbackForNack.call(); + if (callbackForNack.isCallable()) { + logger()->debug() << "Invoking nack callback"; + QJSValue result = callbackForNack.call(); + if (result.isError()) { + logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" + << result.toString(); + } + } else { + logger()->debug() << "Nack callback not callable"; + } }); } @@ -187,7 +203,10 @@ void JSKitXMLHttpRequest::send(const QString &body) buffer->setData(body.toUtf8()); logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << body; _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); - connect(_reply, &QNetworkReply::finished, this, &JSKitXMLHttpRequest::handleReplyFinished); + connect(_reply, &QNetworkReply::finished, + this, &JSKitXMLHttpRequest::handleReplyFinished); + connect(_reply, static_cast(&QNetworkReply::error), + this, &JSKitXMLHttpRequest::handleReplyError); buffer->setParent(_reply); // So that it gets deleted alongside the reply object. } @@ -209,6 +228,26 @@ void JSKitXMLHttpRequest::setOnload(const QJSValue &value) _onload = value; } +QJSValue JSKitXMLHttpRequest::ontimeout() const +{ + return _ontimeout; +} + +void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value) +{ + _ontimeout = value; +} + +QJSValue JSKitXMLHttpRequest::onerror() const +{ + return _onerror; +} + +void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) +{ + _onerror = value; +} + unsigned short JSKitXMLHttpRequest::readyState() const { if (!_reply) { @@ -259,3 +298,21 @@ void JSKitXMLHttpRequest::handleReplyFinished() logger()->debug() << "No onload set"; } } + +void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) +{ + if (!_reply) { + logger()->info() << "reply error too late"; + return; + } + + logger()->info() << "reply error" << code; + + if (_onerror.isCallable()) { + logger()->debug() << "going to call onerror handler:" << _onload.toString(); + QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); + if (result.isError()) { + logger()->warn() << "JS error on onerror handler:" << result.toString(); + } + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 4f000f1..849529f 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -17,7 +17,7 @@ public: Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); - Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack); + Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); @@ -79,6 +79,8 @@ class JSKitXMLHttpRequest : public QObject LOG4QT_DECLARE_QCLASS_LOGGER Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) + Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout) + Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror) Q_PROPERTY(unsigned short readyState READ readyState NOTIFY readyStateChanged) Q_PROPERTY(unsigned short status READ status NOTIFY statusChanged) Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) @@ -102,6 +104,10 @@ public: QJSValue onload() const; void setOnload(const QJSValue &value); + QJSValue ontimeout() const; + void setOntimeout(const QJSValue &value); + QJSValue onerror() const; + void setOnerror(const QJSValue &value); unsigned short readyState() const; unsigned short status() const; @@ -114,6 +120,7 @@ signals: private slots: void handleReplyFinished(); + void handleReplyError(QNetworkReply::NetworkError code); private: JSKitManager *_mgr; @@ -123,6 +130,8 @@ private: QNetworkReply *_reply; QByteArray _response; QJSValue _onload; + QJSValue _ontimeout; + QJSValue _onerror; }; #endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 1fc8a20..6fd47a4 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -410,7 +410,8 @@ void Manager::onAppClosed(const QUuid &uuid) emit proxy->AppUuidChanged(); } -bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { +bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) +{ Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); setDelayedReply(true); @@ -424,7 +425,8 @@ bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) return false; // D-Bus clients should never see this reply. } -QString PebbledProxy::StartAppConfiguration(const QString &uuid) { +QString PebbledProxy::StartAppConfiguration(const QString &uuid) +{ Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); @@ -474,3 +476,23 @@ QString PebbledProxy::StartAppConfiguration(const QString &uuid) { return QString(); // This return value should never be used. } + +void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString &data) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (manager()->currentAppUuid != uuid) { + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return; + } + + if (!manager()->js->isJSKitAppRunning()) { + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return; + } + + manager()->js->handleWebviewClosed(data); +} diff --git a/daemon/manager.h b/daemon/manager.h index 22382b8..239c212 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -141,10 +141,7 @@ public slots: bool SendAppMessage(const QString &uuid, const QVariantMap &data); QString StartAppConfiguration(const QString &uuid); - - void SendAppConfiguration(const QString &uuid, const QString &data) { - // TODO - } + void SendAppConfigurationData(const QString &uuid, const QString &data); signals: void NameChanged(); -- cgit v1.2.3 From 132562620764a8c0cc01dcc1574003beaa70272f Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:24:25 +0100 Subject: fix small typo --- daemon/manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/manager.h b/daemon/manager.h index 239c212..e84e982 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -131,7 +131,7 @@ public: inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } public slots: - inline void Disconnected() { manager()->watch->disconnect(); } + inline void Disconnect() { manager()->watch->disconnect(); } inline void Reconnect() { manager()->watch->reconnect(); } inline void Ping(uint val) { manager()->watch->ping(val); } inline void SyncTime() { manager()->watch->time(); } -- cgit v1.2.3 From 8d98f990c9ed158d8c65befc154ab58a3c392646 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:47:48 +0100 Subject: fix some appmsg issues --- daemon/appmsgmanager.cpp | 15 ++++++++++----- daemon/watchconnector.cpp | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index afaabee..12971ce 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -71,8 +71,12 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p emit messageReceived(uuid, data); break; } + case WatchConnector::appmsgACK: + case WatchConnector::appmsgNACK: + logger()->info() << "appmsg endpoint handler received an unwanted ack/nack"; + break; default: - logger()->warn() << "Unknown application message type:" << data.at(0); + logger()->warn() << "Unknown application message type:" << int(data.at(0)); break; } @@ -88,15 +92,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; +#if 0 /* Try to unpack what we just packed. */ WatchConnector::Dict t_dict; QUuid t_uuid; uint t_trans; if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { logger()->debug() << t_trans << t_uuid << t_dict; } else { - logger()->warn() << "not unpack my own"; + logger()->error() << "not able to unpack my own dict"; } - +#endif watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { @@ -105,10 +110,10 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: quint8 type = reply[0]; quint8 recv_transaction = reply[1]; - logger()->debug() << "Got response to transaction" << transaction; - if (recv_transaction != transaction) return false; + logger()->debug() << "Got response to transaction" << transaction; + switch (type) { case WatchConnector::appmsgACK: logger()->debug() << "Got ACK to transaction" << transaction; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index dd95821..baec52c 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -109,13 +109,17 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) if (tmp_it != tmpHandlers.end()) { QList& funcs = tmp_it.value(); bool ok = false; - if (!funcs.empty()) { - if (funcs.first()(data)) { + for (int i = 0; i < funcs.size(); i++) { + if (funcs[i](data)) { + // This handler accepted this message ok = true; - funcs.removeFirst(); + // Since it is a temporary handler, remove it. + funcs.removeAt(i); + break; } } if (funcs.empty()) { + // "Garbage collect" the tmpHandlers entry. tmpHandlers.erase(tmp_it); } if (ok) { -- cgit v1.2.3 From ddcc8ada42c186e980626ff617be038f45106145 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 4 Dec 2014 00:17:28 +0100 Subject: send appmessages one at a time --- daemon/appmsgmanager.cpp | 288 +++++++++++++++++++++++++++++++---------------- daemon/appmsgmanager.h | 33 +++++- 2 files changed, 218 insertions(+), 103 deletions(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 12971ce..d5f527e 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,3 +1,5 @@ +#include + #include "appmsgmanager.h" #include "unpacker.h" #include "packer.h" @@ -5,46 +7,22 @@ // TODO D-Bus server for non JS kit apps!!!! AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) - : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0), timeout(new QTimer(this)) { + connect(watch, &WatchConnector::connectedChanged, + this, &AppMsgManager::handleWatchConnectedChanged); + + timeout->setSingleShot(true); + timeout->setInterval(3000); + connect(timeout, &QTimer::timeout, + this, &AppMsgManager::handleTimeout); + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { - if (data.at(0) == WatchConnector::appmsgPUSH) { - uint transaction; - QUuid uuid; - WatchConnector::Dict dict; - - if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { - // Failed to parse! - // Since we're the only one handling this endpoint, - // all messages must be accepted - logger()->warn() << "Failed to parser LAUNCHER PUSH message"; - return true; - } - if (!dict.contains(1)) { - logger()->warn() << "LAUNCHER message has no item in dict"; - 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; - } + switch (data.at(0)) { + case WatchConnector::appmsgPUSH: + handleLauncherPushMessage(data); + break; } return true; @@ -53,27 +31,14 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: { - uint transaction; - QUuid uuid; - WatchConnector::Dict dict; - - if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { - logger()->warn() << "Failed to parse APP_MSG PUSH"; - return true; - } - - logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; - - QVariantMap data = mapAppKeys(uuid, dict); - logger()->debug() << "Mapped dict" << data; - - emit messageReceived(uuid, data); + case WatchConnector::appmsgPUSH: + handlePushMessage(data); break; - } case WatchConnector::appmsgACK: + handleAckMessage(data, true); + break; case WatchConnector::appmsgNACK: - logger()->info() << "appmsg endpoint handler received an unwanted ack/nack"; + handleAckMessage(data, false); break; default: logger()->warn() << "Unknown application message type:" << int(data.at(0)); @@ -86,47 +51,22 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) { - WatchConnector::Dict dict = mapAppKeys(uuid, data); - quint8 transaction = ++lastTransactionId; - QByteArray msg = buildPushMessage(transaction, uuid, dict); - - logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; - -#if 0 /* Try to unpack what we just packed. */ - WatchConnector::Dict t_dict; - QUuid t_uuid; - uint t_trans; - if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { - logger()->debug() << t_trans << t_uuid << t_dict; - } else { - logger()->error() << "not able to unpack my own dict"; + PendingTransaction trans; + trans.uuid = uuid; + trans.transactionId = ++lastTransactionId; + trans.dict = mapAppKeys(uuid, data); + trans.ackCallback = ackCallback; + trans.nackCallback = nackCallback; + + logger()->debug() << "Queueing appmsg" << trans.transactionId << "to" << trans.uuid + << "with dict" << trans.dict; + + pending.enqueue(trans); + if (pending.size() == 1) { + // This is the only transaction on the queue + // Therefore, we were idle before: we can submit this transaction right now. + transmitNextPendingTransaction(); } -#endif - - watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, - [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { - if (reply.size() < 2) return false; - - quint8 type = reply[0]; - quint8 recv_transaction = reply[1]; - - if (recv_transaction != transaction) return false; - - logger()->debug() << "Got response to transaction" << transaction; - - switch (type) { - case WatchConnector::appmsgACK: - logger()->debug() << "Got ACK to transaction" << transaction; - if (ackCallback) ackCallback(); - return true; - case WatchConnector::appmsgNACK: - logger()->info() << "Got NACK to transaction" << transaction; - if (nackCallback) nackCallback(); - return true; - default: - return false; - } - }); } void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) @@ -194,7 +134,7 @@ QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::D return data; } -bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, quint8 *transaction, QUuid *uuid, WatchConnector::Dict *dict) { Unpacker u(msg); quint8 code = u.read(); @@ -211,7 +151,7 @@ bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, return true; } -QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +QByteArray AppMsgManager::buildPushMessage(quint8 transaction, const QUuid &uuid, const WatchConnector::Dict &dict) { QByteArray ba; Packer p(&ba); @@ -223,7 +163,7 @@ QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, return ba; } -QByteArray AppMsgManager::buildAckMessage(uint transaction) +QByteArray AppMsgManager::buildAckMessage(quint8 transaction) { QByteArray ba(2, Qt::Uninitialized); ba[0] = WatchConnector::appmsgACK; @@ -231,10 +171,160 @@ QByteArray AppMsgManager::buildAckMessage(uint transaction) return ba; } -QByteArray AppMsgManager::buildNackMessage(uint transaction) +QByteArray AppMsgManager::buildNackMessage(quint8 transaction) { QByteArray ba(2, Qt::Uninitialized); ba[0] = WatchConnector::appmsgNACK; ba[1] = transaction; return ba; } + +void AppMsgManager::handleLauncherPushMessage(const QByteArray &data) +{ + quint8 transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; + return; + } + + 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; + } +} + +void AppMsgManager::handlePushMessage(const QByteArray &data) +{ + quint8 transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap msg = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << msg; + + emit messageReceived(uuid, msg); +} + +void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) +{ + if (data.size() < 2) { + logger()->warn() << "invalid ack/nack message size"; + return; + } + + if (pending.empty()) { + logger()->warn() << "received an ack/nack but no active transaction"; + } + + const quint8 type = data[0]; + const quint8 recv_transaction = data[1]; + + Q_ASSERT(type == WatchConnector::appmsgACK || type == WatchConnector::appmsgNACK); + + PendingTransaction &trans = pending.head(); + if (trans.transactionId != recv_transaction) { + logger()->warn() << "received an ack/nack but for the wrong transaction"; + } + + logger()->debug() << "Got " << (ack ? "ACK" : "NACK") << " to transaction" << trans.transactionId; + + timeout->stop(); + + if (ack) { + if (trans.ackCallback) { + trans.ackCallback(); + } + } else { + if (trans.nackCallback) { + trans.nackCallback(); + } + } + + pending.dequeue(); + + if (!pending.empty()) { + transmitNextPendingTransaction(); + } +} + +void AppMsgManager::handleWatchConnectedChanged() +{ + // If the watch is disconnected, everything breaks loose + // TODO In the future we may want to avoid doing the following. + if (!watch->isConnected()) { + abortPendingTransactions(); + } +} + +void AppMsgManager::handleTimeout() +{ + // Abort the first transaction + Q_ASSERT(!pending.empty()); + PendingTransaction trans = pending.dequeue(); + + logger()->warn() << "timeout on appmsg transaction" << trans.transactionId; + + if (trans.nackCallback) { + trans.nackCallback(); + } + + if (!pending.empty()) { + transmitNextPendingTransaction(); + } +} + +void AppMsgManager::transmitNextPendingTransaction() +{ + Q_ASSERT(!pending.empty()); + PendingTransaction &trans = pending.head(); + + QByteArray msg = buildPushMessage(trans.transactionId, trans.uuid, trans.dict); + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg); + + timeout->start(); +} + +void AppMsgManager::abortPendingTransactions() +{ + // Invoke all the NACK callbacks in the pending queue, then drop them. + Q_FOREACH(const PendingTransaction &trans, pending) { + if (trans.nackCallback) { + trans.nackCallback(); + } + } + + pending.clear(); +} diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index bc9c82f..498d3fa 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -1,6 +1,10 @@ #ifndef APPMSGMANAGER_H #define APPMSGMANAGER_H +#include +#include +#include + #include "watchconnector.h" #include "appmanager.h" @@ -30,16 +34,37 @@ private: WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); - static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + static bool unpackPushMessage(const QByteArray &msg, quint8 *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(quint8 transaction, const QUuid &uuid, const WatchConnector::Dict &dict); + static QByteArray buildAckMessage(quint8 transaction); + static QByteArray buildNackMessage(quint8 transaction); + + void handleLauncherPushMessage(const QByteArray &data); + void handlePushMessage(const QByteArray &data); + void handleAckMessage(const QByteArray &data, bool ack); - static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); - static QByteArray buildAckMessage(uint transaction); - static QByteArray buildNackMessage(uint transaction); + void transmitNextPendingTransaction(); + void abortPendingTransactions(); + +private slots: + void handleWatchConnectedChanged(); + void handleTimeout(); private: AppManager *apps; WatchConnector *watch; quint8 lastTransactionId; + + struct PendingTransaction { + quint8 transactionId; + QUuid uuid; + WatchConnector::Dict dict; + std::function ackCallback; + std::function nackCallback; + }; + QQueue pending; + QTimer *timeout; }; #endif // APPMSGMANAGER_H -- 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(+) 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 8722bee52922f8c6707103795fdf69f9ed7d0240 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 4 Dec 2014 01:21:32 +0100 Subject: add stub geolocation API --- daemon/daemon.pro | 2 +- daemon/jskitmanager.cpp | 7 ++++ daemon/jskitmanager.h | 2 ++ daemon/jskitobjects.cpp | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitobjects.h | 43 +++++++++++++++++++++++ 5 files changed, 146 insertions(+), 1 deletion(-) diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 3306541..0c4154b 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts gui qml +QT += bluetooth dbus contacts gui qml positioning PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 7bf8cdc..70ea4bd 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -103,6 +103,7 @@ void JSKitManager::startJsApp() _jspebble = new JSKitPebble(_curApp, this); _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); + _jsgeo = new JSKitGeolocation(this); logger()->debug() << "starting JS app"; @@ -116,6 +117,10 @@ void JSKitManager::startJsApp() windowObj.setProperty("localStorage", globalObj.property("localStorage")); globalObj.setProperty("window", windowObj); + QJSValue navigatorObj = _engine->newObject(); + navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); + globalObj.setProperty("navigator", navigatorObj); + _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); @@ -151,4 +156,6 @@ void JSKitManager::stopJsApp() _jsstorage = 0; delete _jspebble; _jspebble = 0; + delete _jsgeo; + _jsgeo = 0; } diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 07ecec1..1f842b7 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -8,6 +8,7 @@ class JSKitPebble; class JSKitConsole; class JSKitLocalStorage; +class JSKitGeolocation; class JSKitManager : public QObject { @@ -48,6 +49,7 @@ private: QPointer _jspebble; QPointer _jsconsole; QPointer _jsstorage; + QPointer _jsgeo; }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 728daf7..3b4584b 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -316,3 +316,96 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) } } } + +JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) + : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) +{ + +} + +void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) +{ + logger()->debug() << Q_FUNC_INFO; + setupWatcher(successCallback, errorCallback, options, true); +} + +int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) +{ + logger()->debug() << Q_FUNC_INFO; + return setupWatcher(successCallback, errorCallback, options, false); +} + +void JSKitGeolocation::clearWatch(int watchId) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handleTimeout() +{ + logger()->debug() << Q_FUNC_INFO; +} + +int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) +{ + Watcher watcher; + watcher.successCallback = successCallback; + watcher.errorCallback = errorCallback; + watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); + watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); + watcher.maximumAge = options.value("maximumAge", 0).toUInt(); + watcher.once = once; + watcher.watchId = ++_lastWatchId; + + if (!_source) { + _source = QGeoPositionInfoSource::createDefaultSource(this); + connect(_source, static_cast(&QGeoPositionInfoSource::error), + this, &JSKitGeolocation::handleError); + connect(_source, &QGeoPositionInfoSource::positionUpdated, + this, &JSKitGeolocation::handlePosition); + connect(_source, &QGeoPositionInfoSource::updateTimeout, + this, &JSKitGeolocation::handleTimeout); + } + + if (once && watcher.maximumAge > 0) { + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-watcher.maximumAge); + QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); + if (pos.isValid() && pos.timestamp() >= threshold) { + invokeSuccessCallback(watcher, pos); + return -1; + } else if (watcher.timeout == 0) { + invokeErrorCallback(watcher); + return -1; + } + } + + if (once) { + _source->requestUpdate(watcher.timeout); + } else { + // TODO _source->setInterval to the minimum of all watches + _source->startUpdates(); + } + + return watcher.watchId; +} + +void JSKitGeolocation::invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos) +{ + // TODO +} + +void JSKitGeolocation::invokeErrorCallback(Watcher &watcher) +{ + if (watcher.errorCallback.isCallable()) { + watcher.errorCallback.call(); // TODO this, eventArgs + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 849529f..9c9b84e 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "jskitmanager.h" class JSKitPebble : public QObject @@ -134,4 +135,46 @@ private: QJSValue _onerror; }; +class JSKitGeolocation : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + + struct Watcher; + +public: + explicit JSKitGeolocation(JSKitManager *mgr); + + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); + Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); + Q_INVOKABLE void clearWatch(int watchId); + +private slots: + void handleError(const QGeoPositionInfoSource::Error error); + void handlePosition(const QGeoPositionInfo &pos); + void handleTimeout(); + +private: + int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); + void invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos); + void invokeErrorCallback(Watcher &watcher); + +private: + JSKitManager *_mgr; + QGeoPositionInfoSource *_source; + + struct Watcher { + QJSValue successCallback; + QJSValue errorCallback; + int watchId; + bool once; + bool highAccuracy; + uint timeout; + uint maximumAge; + }; + + QList _watches; + int _lastWatchId; +}; + #endif // JSKITMANAGER_P_H -- cgit v1.2.3 From e96c2ee30342b5a198025c3dd0c51bf43688d9ee Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 22:56:27 +0100 Subject: partial implementation of geolocation --- daemon/jskitmanager.cpp | 10 ++- daemon/jskitmanager.h | 2 + daemon/jskitobjects.cpp | 172 +++++++++++++++++++++++++++++++++++++++++------- daemon/jskitobjects.h | 20 ++++-- 4 files changed, 175 insertions(+), 29 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 70ea4bd..9c739fc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -29,6 +29,14 @@ bool JSKitManager::isJSKitAppRunning() const return _engine != 0; } +QString JSKitManager::describeError(QJSValue error) +{ + return QString("%1:%2: %3") + .arg(error.property("fileName").toString()) + .arg(error.property("lineNumber").toInt()) + .arg(error.toString()); +} + void JSKitManager::showConfiguration() { if (_engine) { @@ -134,7 +142,7 @@ void JSKitManager::startJsApp() QJSValue result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { - logger()->warn() << "error while evaluating JSKit script:" << result.toString(); + logger()->warn() << "error while evaluating JSKit script:" << describeError(result); } logger()->debug() << "JS script evaluated"; diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 1f842b7..873489b 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -22,6 +22,8 @@ public: QJSEngine * engine(); bool isJSKitAppRunning() const; + static QString describeError(QJSValue error); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); void appOpenUrl(const QUrl &url); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 3b4584b..b3820b5 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "jskitobjects.h" JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) @@ -45,7 +46,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForAck.call(); if (result.isError()) { logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Ack callback not callable"; @@ -56,7 +57,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForNack.call(); if (result.isError()) { logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Nack callback not callable"; @@ -93,7 +94,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } } @@ -287,12 +288,11 @@ void JSKitXMLHttpRequest::handleReplyFinished() emit statusChanged(); emit responseTextChanged(); - if (_onload.isCallable()) { logger()->debug() << "going to call onload handler:" << _onload.toString(); QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onload handler:" << result.toString(); + logger()->warn() << "JS error on onload handler:" << JSKitManager::describeError(result); } } else { logger()->debug() << "No onload set"; @@ -312,7 +312,7 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) logger()->debug() << "going to call onerror handler:" << _onload.toString(); QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onerror handler:" << result.toString(); + logger()->warn() << "JS error on onerror handler:" << JSKitManager::describeError(result); } } } @@ -320,7 +320,6 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) { - } void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) @@ -331,28 +330,59 @@ void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) { - logger()->debug() << Q_FUNC_INFO; return setupWatcher(successCallback, errorCallback, options, false); } void JSKitGeolocation::clearWatch(int watchId) { - logger()->debug() << Q_FUNC_INFO; + removeWatcher(watchId); } void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) { - logger()->debug() << Q_FUNC_INFO; + logger()->warn() << "positioning error: " << error; + // TODO } void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { logger()->debug() << Q_FUNC_INFO; + if (_watches.empty()) { + logger()->warn() << "got position update but no one is watching"; + } + + QJSValue obj = buildPositionObject(pos); + + for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { + invokeCallback(it->successCallback, obj); + + if (it->once) { + it = _watches.erase(it); + } else { + ++it; + } + } + + if (_watches.empty()) { + _source->stopUpdates(); + } } void JSKitGeolocation::handleTimeout() { logger()->debug() << Q_FUNC_INFO; + // TODO +} + +uint JSKitGeolocation::minimumTimeout() const +{ + uint minimum = std::numeric_limits::max(); + Q_FOREACH(const Watcher &watcher, _watches) { + if (!watcher.once) { + minimum = qMin(watcher.timeout, minimum); + } + } + return minimum; } int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) @@ -362,10 +392,13 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal watcher.errorCallback = errorCallback; watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); - watcher.maximumAge = options.value("maximumAge", 0).toUInt(); watcher.once = once; watcher.watchId = ++_lastWatchId; + uint maximumAge = options.value("maximumAge", 0).toUInt(); + + logger()->debug() << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once; + if (!_source) { _source = QGeoPositionInfoSource::createDefaultSource(this); connect(_source, static_cast(&QGeoPositionInfoSource::error), @@ -376,14 +409,17 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal this, &JSKitGeolocation::handleTimeout); } - if (once && watcher.maximumAge > 0) { - QDateTime threshold = QDateTime::currentDateTime().addMSecs(-watcher.maximumAge); + if (maximumAge > 0) { + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(maximumAge)); QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); + logger()->debug() << "got pos timestamp" << pos.timestamp() << " but we want" << threshold; if (pos.isValid() && pos.timestamp() >= threshold) { - invokeSuccessCallback(watcher, pos); - return -1; - } else if (watcher.timeout == 0) { - invokeErrorCallback(watcher); + invokeCallback(watcher.successCallback, buildPositionObject(pos)); + if (once) { + return -1; + } + } else if (watcher.timeout == 0 && once) { + invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); return -1; } } @@ -391,21 +427,111 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal if (once) { _source->requestUpdate(watcher.timeout); } else { - // TODO _source->setInterval to the minimum of all watches + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + logger()->debug() << "starting location updates"; _source->startUpdates(); } + _watches.append(watcher); + + logger()->debug() << "added new watch" << watcher.watchId; + return watcher.watchId; } -void JSKitGeolocation::invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos) +void JSKitGeolocation::removeWatcher(int watchId) { - // TODO + Watcher watcher; + + logger()->debug() << "removing watchId" << watcher.watchId; + + for (int i = 0; i < _watches.size(); i++) { + if (_watches[i].watchId == watchId) { + watcher = _watches.takeAt(i); + break; + } + } + + if (watcher.watchId != watchId) { + logger()->warn() << "watchId not found"; + return; + } + + if (_watches.empty()) { + logger()->debug() << "stopping updates"; + _source->stopUpdates(); + } else { + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + } } -void JSKitGeolocation::invokeErrorCallback(Watcher &watcher) +QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) { - if (watcher.errorCallback.isCallable()) { - watcher.errorCallback.call(); // TODO this, eventArgs + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + QJSValue coords = engine->newObject(); + QJSValue timestamp = engine->toScriptValue(pos.timestamp().toMSecsSinceEpoch()); + + coords.setProperty("latitude", engine->toScriptValue(pos.coordinate().latitude())); + coords.setProperty("longitude", engine->toScriptValue(pos.coordinate().longitude())); + if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) { + coords.setProperty("altitude", engine->toScriptValue(pos.coordinate().altitude())); + } else { + coords.setProperty("altitude", engine->toScriptValue(0)); + } + + coords.setProperty("accuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy))); + + if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy))); + } else { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::Direction)) { + coords.setProperty("heading", engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction))); + } else { + coords.setProperty("heading", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) { + coords.setProperty("speed", engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed))); + } else { + coords.setProperty("speed", engine->toScriptValue(0)); + } + + obj.setProperty("coords", coords); + obj.setProperty("timestamp", timestamp); + + logger()->debug() << obj.toString(); + + return obj; +} + +QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message) +{ + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + + obj.setProperty("code", engine->toScriptValue(error)); + obj.setProperty("message", engine->toScriptValue(message)); + + return obj; +} + +void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event) +{ + if (callback.isCallable()) { + logger()->debug() << "invoking callback" << callback.toString(); + QJSValue result = callback.call(QJSValueList({event})); + if (result.isError()) { + logger()->warn() << "while invoking callback: " << JSKitManager::describeError(result); + } + } else { + logger()->warn() << "callback is not callable"; } } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 9c9b84e..0dccc05 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -98,9 +98,9 @@ public: DONE = 4 }; - Q_INVOKABLE void open(const QString &method, const QString &url, bool async); + Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false); Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); - Q_INVOKABLE void send(const QString &body); + Q_INVOKABLE void send(const QString &body = QString()); Q_INVOKABLE void abort(); QJSValue onload() const; @@ -138,6 +138,7 @@ private: class JSKitGeolocation : public QObject { Q_OBJECT + Q_ENUMS(PositionError) LOG4QT_DECLARE_QCLASS_LOGGER struct Watcher; @@ -145,6 +146,12 @@ class JSKitGeolocation : public QObject public: explicit JSKitGeolocation(JSKitManager *mgr); + enum PositionError { + PERMISSION_DENIED = 1, + POSITION_UNAVAILABLE = 2, + TIMEOUT = 3 + }; + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE void clearWatch(int watchId); @@ -155,9 +162,13 @@ private slots: void handleTimeout(); private: + uint minimumTimeout() const; int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); - void invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos); - void invokeErrorCallback(Watcher &watcher); + void removeWatcher(int watchId); + QJSValue buildPositionObject(const QGeoPositionInfo &pos); + QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); + QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); + void invokeCallback(QJSValue callback, QJSValue event); private: JSKitManager *_mgr; @@ -170,7 +181,6 @@ private: bool once; bool highAccuracy; uint timeout; - uint maximumAge; }; QList _watches; -- cgit v1.2.3 From b1acefc4ef93a0023df62b9ec8b7b9d7aa216535 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 22:58:01 +0100 Subject: add some minimal information to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8ee4a77..5892986 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Features * Transliterate strings to plain ASCII * daemon management app * "org.pebbled" DBus interface +* PebbleKit JS application partial support + (including Pebble object, XMLHTTPRequest, localStorage, geolocation) -- cgit v1.2.3 From deb44cc068f03c7c6bdeb1b8803e58c2ad62a47d Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 23:57:06 +0100 Subject: minor changes --- daemon/jskitmanager.cpp | 20 +++++++++++++------- daemon/jskitobjects.cpp | 23 ++++++++++++++++++----- daemon/jskitobjects.h | 3 +++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 9c739fc..d632f7b 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -40,6 +40,7 @@ QString JSKitManager::describeError(QJSValue error) void JSKitManager::showConfiguration() { if (_engine) { + logger()->debug() << "requesting configuration"; _jspebble->invokeCallbacks("showConfiguration"); } else { logger()->warn() << "requested to show configuration, but JS engine is not running"; @@ -129,7 +130,11 @@ void JSKitManager::startJsApp() navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); globalObj.setProperty("navigator", navigatorObj); - _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); + // Shims for compatibility... + QJSValue result = _engine->evaluate( + "function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }\n" + ); + Q_ASSERT(!result.isError()); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -140,7 +145,7 @@ void JSKitManager::startJsApp() QString script = QString::fromUtf8(scriptFile.readAll()); - QJSValue result = _engine->evaluate(script, scriptFile.fileName()); + result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { logger()->warn() << "error while evaluating JSKit script:" << describeError(result); } @@ -158,12 +163,13 @@ void JSKitManager::stopJsApp() _engine->collectGarbage(); - delete _engine; + _engine->deleteLater(); _engine = 0; - delete _jsstorage; + _jsstorage->deleteLater(); _jsstorage = 0; - delete _jspebble; - _jspebble = 0; - delete _jsgeo; + _jsgeo->deleteLater(); _jsgeo = 0; + _jspebble->deleteLater(); + _jspebble = 0; + } diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index b3820b5..1fc0062 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -43,7 +43,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { if (callbackForAck.isCallable()) { logger()->debug() << "Invoking ack callback"; - QJSValue result = callbackForAck.call(); + QJSValue result = callbackForAck.call(QJSValueList({buildAckEventObject()})); if (result.isError()) { logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" << JSKitManager::describeError(result); @@ -54,7 +54,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV }, [this, callbackForNack]() mutable { if (callbackForNack.isCallable()) { logger()->debug() << "Invoking nack callback"; - QJSValue result = callbackForNack.call(); + QJSValue result = callbackForNack.call(QJSValueList({buildAckEventObject()})); if (result.isError()) { logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" << JSKitManager::describeError(result); @@ -84,6 +84,20 @@ QJSValue JSKitPebble::createXMLHttpRequest() return _mgr->engine()->newQObject(xhr); } +QJSValue JSKitPebble::buildAckEventObject() const +{ + QJSEngine *engine = _mgr->engine(); + QJSValue eventObj = engine->newObject(); + QJSValue dataObj = engine->newObject(); + + // Why do scripts need the real transactionId? + // No idea. Just fake it. + dataObj.setProperty("transactionId", engine->toScriptValue(0)); + eventObj.setProperty("data", dataObj); + + return eventObj; +} + void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) { if (!_callbacks.contains(type)) return; @@ -346,11 +360,12 @@ void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { - logger()->debug() << Q_FUNC_INFO; if (_watches.empty()) { logger()->warn() << "got position update but no one is watching"; } + logger()->debug() << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); + QJSValue obj = buildPositionObject(pos); for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { @@ -507,8 +522,6 @@ QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) obj.setProperty("coords", coords); obj.setProperty("timestamp", timestamp); - logger()->debug() << obj.toString(); - return obj; } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 0dccc05..5f039c1 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -28,6 +28,9 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); +private: + QJSValue buildAckEventObject() const; + private: AppInfo _appInfo; JSKitManager *_mgr; -- cgit v1.2.3 From faa06a9cf59ca0044a928330a63190a98e9b6bcc Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 00:24:17 +0100 Subject: add small progress indicator --- daemon/jskitmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index d632f7b..894d6f5 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -145,6 +145,8 @@ void JSKitManager::startJsApp() QString script = QString::fromUtf8(scriptFile.readAll()); + logger()->debug() << "now parsing" << scriptFile.fileName(); + result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { logger()->warn() << "error while evaluating JSKit script:" << describeError(result); -- cgit v1.2.3 From 0a92face6b035a26aad3d4d7ffa5a72b463e4c2a Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 02:11:46 +0100 Subject: forward jskit notifications --- daemon/manager.cpp | 7 +++++++ daemon/manager.h | 1 + 2 files changed, 8 insertions(+) diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 6fd47a4..b488432 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -59,6 +59,8 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); + connect(js, &JSKitManager::appNotification, this, &Manager::onAppNotification); + QDBusConnection session = QDBusConnection::sessionBus(); new WatchAdaptor(proxy); session.registerObject("/org/pebbled/Watch", proxy); @@ -391,6 +393,11 @@ void Manager::transliterateMessage(const QString &text) } } +void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) +{ + watch->sendSMSNotification(title, body); +} + void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) { emit proxy->AppMessage(uuid.toString(), data); diff --git a/daemon/manager.h b/daemon/manager.h index e84e982..18bd7bf 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -102,6 +102,7 @@ private slots: void setMprisMetadata(QDBusArgument metadata); void setMprisMetadata(QVariantMap metadata); + void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); void onAppMessage(const QUuid &uuid, const QVariantMap &data); void onAppOpened(const QUuid &uuid); void onAppClosed(const QUuid &uuid); -- cgit v1.2.3 From 3785de21ec2e466535a45183b6f9082b5dfba976 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 21:14:24 +0100 Subject: add a polyfill for typed arrays, and many other compat changes --- daemon/appmsgmanager.cpp | 57 +-- daemon/appmsgmanager.h | 8 +- daemon/daemon.pro | 17 +- daemon/js/typedarray.js | 1030 ++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitmanager.cpp | 45 +- daemon/jskitmanager.h | 1 + daemon/jskitobjects.cpp | 275 ++++++++++--- daemon/jskitobjects.h | 42 +- daemon/packer.cpp | 16 + rpm/pebble.spec | 1 + rpm/pebble.yaml | 1 + 11 files changed, 1374 insertions(+), 119 deletions(-) create mode 100644 daemon/js/typedarray.js diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index d5f527e..312043a 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -7,14 +7,14 @@ // TODO D-Bus server for non JS kit apps!!!! AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) - : QObject(parent), apps(apps), watch(watch), lastTransactionId(0), timeout(new QTimer(this)) + : QObject(parent), apps(apps), watch(watch), _lastTransactionId(0), _timeout(new QTimer(this)) { connect(watch, &WatchConnector::connectedChanged, this, &AppMsgManager::handleWatchConnectedChanged); - timeout->setSingleShot(true); - timeout->setInterval(3000); - connect(timeout, &QTimer::timeout, + _timeout->setSingleShot(true); + _timeout->setInterval(3000); + connect(_timeout, &QTimer::timeout, this, &AppMsgManager::handleTimeout); watch->setEndpointHandler(WatchConnector::watchLAUNCHER, @@ -53,7 +53,7 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: { PendingTransaction trans; trans.uuid = uuid; - trans.transactionId = ++lastTransactionId; + trans.transactionId = ++_lastTransactionId; trans.dict = mapAppKeys(uuid, data); trans.ackCallback = ackCallback; trans.nackCallback = nackCallback; @@ -61,14 +61,24 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: logger()->debug() << "Queueing appmsg" << trans.transactionId << "to" << trans.uuid << "with dict" << trans.dict; - pending.enqueue(trans); - if (pending.size() == 1) { + _pending.enqueue(trans); + if (_pending.size() == 1) { // This is the only transaction on the queue // Therefore, we were idle before: we can submit this transaction right now. transmitNextPendingTransaction(); } } +uint AppMsgManager::lastTransactionId() const +{ + return _lastTransactionId; +} + +uint AppMsgManager::nextTransactionId() const +{ + return _lastTransactionId + 1; +} + void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) { std::function nullCallback; @@ -244,23 +254,24 @@ void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) return; } - if (pending.empty()) { - logger()->warn() << "received an ack/nack but no active transaction"; - } - const quint8 type = data[0]; const quint8 recv_transaction = data[1]; Q_ASSERT(type == WatchConnector::appmsgACK || type == WatchConnector::appmsgNACK); - PendingTransaction &trans = pending.head(); + if (_pending.empty()) { + logger()->warn() << "received an ack/nack for transaction" << recv_transaction << "but no transaction is pending"; + return; + } + + PendingTransaction &trans = _pending.head(); if (trans.transactionId != recv_transaction) { logger()->warn() << "received an ack/nack but for the wrong transaction"; } logger()->debug() << "Got " << (ack ? "ACK" : "NACK") << " to transaction" << trans.transactionId; - timeout->stop(); + _timeout->stop(); if (ack) { if (trans.ackCallback) { @@ -272,9 +283,9 @@ void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) } } - pending.dequeue(); + _pending.dequeue(); - if (!pending.empty()) { + if (!_pending.empty()) { transmitNextPendingTransaction(); } } @@ -291,8 +302,8 @@ void AppMsgManager::handleWatchConnectedChanged() void AppMsgManager::handleTimeout() { // Abort the first transaction - Q_ASSERT(!pending.empty()); - PendingTransaction trans = pending.dequeue(); + Q_ASSERT(!_pending.empty()); + PendingTransaction trans = _pending.dequeue(); logger()->warn() << "timeout on appmsg transaction" << trans.transactionId; @@ -300,31 +311,31 @@ void AppMsgManager::handleTimeout() trans.nackCallback(); } - if (!pending.empty()) { + if (!_pending.empty()) { transmitNextPendingTransaction(); } } void AppMsgManager::transmitNextPendingTransaction() { - Q_ASSERT(!pending.empty()); - PendingTransaction &trans = pending.head(); + Q_ASSERT(!_pending.empty()); + PendingTransaction &trans = _pending.head(); QByteArray msg = buildPushMessage(trans.transactionId, trans.uuid, trans.dict); watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg); - timeout->start(); + _timeout->start(); } void AppMsgManager::abortPendingTransactions() { // Invoke all the NACK callbacks in the pending queue, then drop them. - Q_FOREACH(const PendingTransaction &trans, pending) { + Q_FOREACH(const PendingTransaction &trans, _pending) { if (trans.nackCallback) { trans.nackCallback(); } } - pending.clear(); + _pending.clear(); } diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 498d3fa..9aaabd4 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -19,6 +19,8 @@ public: void send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback); + uint lastTransactionId() const; + uint nextTransactionId() const; public slots: void send(const QUuid &uuid, const QVariantMap &data); @@ -54,7 +56,7 @@ private slots: private: AppManager *apps; WatchConnector *watch; - quint8 lastTransactionId; + quint8 _lastTransactionId; struct PendingTransaction { quint8 transactionId; @@ -63,8 +65,8 @@ private: std::function ackCallback; std::function nackCallback; }; - QQueue pending; - QTimer *timeout; + QQueue _pending; + QTimer *_timeout; }; #endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 0c4154b..81570c7 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -50,23 +50,24 @@ HEADERS += \ OTHER_FILES += \ ../log4qt-debug.conf \ - ../log4qt-release.conf + ../log4qt-release.conf \ + js/typedarray.js DBUS_ADAPTORS += ../org.pebbled.Watch.xml -INSTALLS += target pebbled confile +INSTALLS += target systemd confile js target.path = /usr/bin -pebbled.files = $${TARGET}.service -pebbled.path = /usr/lib/systemd/user +systemd.files = $${TARGET}.service +systemd.path = /usr/lib/systemd/user + +js.files = js/* +js.path = /usr/share/pebble/js CONFIG(debug, debug|release) { - message(Debug build) confile.extra = cp $$PWD/../log4qt-debug.conf $$OUT_PWD/../log4qt.conf -} -else { - message(Release build) +} else { confile.extra = cp $$PWD/../log4qt-release.conf $$OUT_PWD/../log4qt.conf } diff --git a/daemon/js/typedarray.js b/daemon/js/typedarray.js new file mode 100644 index 0000000..eec78a2 --- /dev/null +++ b/daemon/js/typedarray.js @@ -0,0 +1,1030 @@ +/* + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2014, Joshua Bell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + $/LicenseInfo$ + */ + +// Original can be found at: +// https://bitbucket.org/lindenlab/llsd +// Modifications by Joshua Bell inexorabletash@gmail.com +// https://github.com/inexorabletash/polyfill + +// ES3/ES5 implementation of the Krhonos Typed Array Specification +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ +// Date: 2011-02-01 +// +// Variations: +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) +// * Gradually migrating structure from Khronos spec to ES6 spec +(function(global) { + 'use strict'; + var undefined = (void 0); // Paranoia + + // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to + // create, and consume so much memory, that the browser appears frozen. + var MAX_ARRAY_LENGTH = 1e5; + + // Approximations of internal ECMAScript conversion functions + function Type(v) { + switch(typeof v) { + case 'undefined': return 'undefined'; + case 'boolean': return 'boolean'; + case 'number': return 'number'; + case 'string': return 'string'; + default: return v === null ? 'null' : 'object'; + } + } + + // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: + function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } + function IsCallable(o) { return typeof o === 'function'; } + function ToObject(v) { + if (v === null || v === undefined) throw TypeError(); + return Object(v); + } + function ToInt32(v) { return v >> 0; } + function ToUint32(v) { return v >>> 0; } + + // Snapshot intrinsics + var LN2 = Math.LN2, + abs = Math.abs, + floor = Math.floor, + log = Math.log, + max = Math.max, + min = Math.min, + pow = Math.pow, + round = Math.round; + + // emulate ES5 getter/setter API using legacy APIs + // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx + // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but + // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) + + (function() { + var orig = Object.defineProperty; + var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); + + if (!orig || dom_only) { + Object.defineProperty = function (o, prop, desc) { + // In IE8 try built-in implementation for defining properties on DOM prototypes. + if (orig) + try { return orig(o, prop, desc); } catch (_) {} + if (o !== Object(o)) + throw TypeError('Object.defineProperty called on non-object'); + if (Object.prototype.__defineGetter__ && ('get' in desc)) + Object.prototype.__defineGetter__.call(o, prop, desc.get); + if (Object.prototype.__defineSetter__ && ('set' in desc)) + Object.prototype.__defineSetter__.call(o, prop, desc.set); + if ('value' in desc) + o[prop] = desc.value; + return o; + }; + } + }()); + + // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) + // for index in 0 ... obj.length + function makeArrayAccessors(obj) { + if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); + + function makeArrayAccessor(index) { + Object.defineProperty(obj, index, { + 'get': function() { return obj._getter(index); }, + 'set': function(v) { obj._setter(index, v); }, + enumerable: true, + configurable: false + }); + } + + var i; + for (i = 0; i < obj.length; i += 1) { + makeArrayAccessor(i); + } + } + + // Internal conversion functions: + // pack() - take a number (interpreted as Type), output a byte array + // unpack() - take a byte array, output a Type-like number + + function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } + function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } + + function packI8(n) { return [n & 0xff]; } + function unpackI8(bytes) { return as_signed(bytes[0], 8); } + + function packU8(n) { return [n & 0xff]; } + function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } + + function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } + + function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; } + function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } + + function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; } + function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } + + function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } + function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + + function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } + function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + + function packIEEE754(v, ebits, fbits) { + + var bias = (1 << (ebits - 1)) - 1, + s, e, f, ln, + i, bits, str, bytes; + + function roundToEven(n) { + var w = floor(n), f = n - w; + if (f < 0.5) + return w; + if (f > 0.5) + return w + 1; + return w % 2 ? w + 1 : w; + } + + // Compute sign, exponent, fraction + if (v !== v) { + // NaN + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; + } else if (v === Infinity || v === -Infinity) { + e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; + } else if (v === 0) { + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; + } else { + s = v < 0; + v = abs(v); + + if (v >= pow(2, 1 - bias)) { + e = min(floor(log(v) / LN2), 1023); + f = roundToEven(v / pow(2, e) * pow(2, fbits)); + if (f / pow(2, fbits) >= 2) { + e = e + 1; + f = 1; + } + if (e > bias) { + // Overflow + e = (1 << ebits) - 1; + f = 0; + } else { + // Normalized + e = e + bias; + f = f - pow(2, fbits); + } + } else { + // Denormalized + e = 0; + f = roundToEven(v / pow(2, 1 - bias - fbits)); + } + } + + // Pack sign, exponent, fraction + bits = []; + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } + bits.push(s ? 1 : 0); + bits.reverse(); + str = bits.join(''); + + // Bits to bytes + bytes = []; + while (str.length) { + bytes.push(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; + } + + function unpackIEEE754(bytes, ebits, fbits) { + // Bytes to bits + var bits = [], i, j, b, str, + bias, s, e, f; + + for (i = bytes.length; i; i -= 1) { + b = bytes[i - 1]; + for (j = 8; j; j -= 1) { + bits.push(b % 2 ? 1 : 0); b = b >> 1; + } + } + bits.reverse(); + str = bits.join(''); + + // Unpack sign, exponent, fraction + bias = (1 << (ebits - 1)) - 1; + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + e = parseInt(str.substring(1, 1 + ebits), 2); + f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } else if (e > 0) { + // Normalized + return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); + } else if (f !== 0) { + // Denormalized + return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); + } else { + return s < 0 ? -0 : 0; + } + } + + function unpackF64(b) { return unpackIEEE754(b, 11, 52); } + function packF64(v) { return packIEEE754(v, 11, 52); } + function unpackF32(b) { return unpackIEEE754(b, 8, 23); } + function packF32(v) { return packIEEE754(v, 8, 23); } + + // + // 3 The ArrayBuffer Type + // + + (function() { + + function ArrayBuffer(length) { + length = ToInt32(length); + if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.'); + Object.defineProperty(this, 'byteLength', {value: length}); + Object.defineProperty(this, '_bytes', {value: Array(length)}); + + for (var i = 0; i < length; i += 1) + this._bytes[i] = 0; + } + + global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer; + + // + // 5 The Typed Array View Types + // + + function $TypedArray$() { + + // %TypedArray% ( length ) + if (!arguments.length || typeof arguments[0] !== 'object') { + return (function(length) { + length = ToInt32(length); + if (length < 0) throw RangeError('length is not a small enough positive integer.'); + Object.defineProperty(this, 'length', {value: length}); + Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT}); + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)}); + Object.defineProperty(this, 'byteOffset', {value: 0}); + + }).apply(this, arguments); + } + + // %TypedArray% ( typedArray ) + if (arguments.length >= 1 && + Type(arguments[0]) === 'object' && + arguments[0] instanceof $TypedArray$) { + return (function(typedArray){ + if (this.constructor !== typedArray.constructor) throw TypeError(); + + var byteLength = typedArray.length * this.BYTES_PER_ELEMENT; + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); + Object.defineProperty(this, 'byteLength', {value: byteLength}); + Object.defineProperty(this, 'byteOffset', {value: 0}); + Object.defineProperty(this, 'length', {value: typedArray.length}); + + for (var i = 0; i < this.length; i += 1) + this._setter(i, typedArray._getter(i)); + + }).apply(this, arguments); + } + + // %TypedArray% ( array ) + if (arguments.length >= 1 && + Type(arguments[0]) === 'object' && + !(arguments[0] instanceof $TypedArray$) && + !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { + return (function(array) { + + var byteLength = array.length * this.BYTES_PER_ELEMENT; + Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); + Object.defineProperty(this, 'byteLength', {value: byteLength}); + Object.defineProperty(this, 'byteOffset', {value: 0}); + Object.defineProperty(this, 'length', {value: array.length}); + + for (var i = 0; i < this.length; i += 1) { + var s = array[i]; + this._setter(i, Number(s)); + } + }).apply(this, arguments); + } + + // %TypedArray% ( buffer, byteOffset=0, length=undefined ) + if (arguments.length >= 1 && + Type(arguments[0]) === 'object' && + (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { + return (function(buffer, byteOffset, length) { + + byteOffset = ToUint32(byteOffset); + if (byteOffset > buffer.byteLength) + throw RangeError('byteOffset out of range'); + + // The given byteOffset must be a multiple of the element + // size of the specific type, otherwise an exception is raised. + if (byteOffset % this.BYTES_PER_ELEMENT) + throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.'); + + if (length === undefined) { + var byteLength = buffer.byteLength - byteOffset; + if (byteLength % this.BYTES_PER_ELEMENT) + throw RangeError('length of buffer minus byteOffset not a multiple of the element size'); + length = byteLength / this.BYTES_PER_ELEMENT; + + } else { + length = ToUint32(length); + byteLength = length * this.BYTES_PER_ELEMENT; + } + + if ((byteOffset + byteLength) > buffer.byteLength) + throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); + + Object.defineProperty(this, 'buffer', {value: buffer}); + Object.defineProperty(this, 'byteLength', {value: byteLength}); + Object.defineProperty(this, 'byteOffset', {value: byteOffset}); + Object.defineProperty(this, 'length', {value: length}); + + }).apply(this, arguments); + } + + // %TypedArray% ( all other argument combinations ) + throw TypeError(); + } + + // Properties of the %TypedArray Instrinsic Object + + // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined ) + Object.defineProperty($TypedArray$, 'from', {value: function(iterable) { + return new this(iterable); + }}); + + // %TypedArray%.of ( ...items ) + Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) { + return new this(arguments); + }}); + + // %TypedArray%.prototype + var $TypedArrayPrototype$ = {}; + $TypedArray$.prototype = $TypedArrayPrototype$; + + // WebIDL: getter type (unsigned long index); + Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) { + if (arguments.length < 1) throw SyntaxError('Not enough arguments'); + + index = ToUint32(index); + if (index >= this.length) + return undefined; + + var bytes = [], i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + bytes.push(this.buffer._bytes[o]); + } + return this._unpack(bytes); + }}); + + // NONSTANDARD: convenience alias for getter: type get(unsigned long index); + Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter}); + + // WebIDL: setter void (unsigned long index, type value); + Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) { + if (arguments.length < 2) throw SyntaxError('Not enough arguments'); + + index = ToUint32(index); + if (index >= this.length) + return; + + var bytes = this._pack(value), i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + this.buffer._bytes[o] = bytes[i]; + } + }}); + + // get %TypedArray%.prototype.buffer + // get %TypedArray%.prototype.byteLength + // get %TypedArray%.prototype.byteOffset + // -- applied directly to the object in the constructor + + // %TypedArray%.prototype.constructor + Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$}); + + // %TypedArray%.prototype.copyWithin (target, start, end = this.length ) + Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) { + var end = arguments[2]; + + var o = ToObject(this); + var lenVal = o.length; + var len = ToUint32(lenVal); + len = max(len, 0); + var relativeTarget = ToInt32(target); + var to; + if (relativeTarget < 0) + to = max(len + relativeTarget, 0); + else + to = min(relativeTarget, len); + var relativeStart = ToInt32(start); + var from; + if (relativeStart < 0) + from = max(len + relativeStart, 0); + else + from = min(relativeStart, len); + var relativeEnd; + if (end === undefined) + relativeEnd = len; + else + relativeEnd = ToInt32(end); + var final; + if (relativeEnd < 0) + final = max(len + relativeEnd, 0); + else + final = min(relativeEnd, len); + var count = min(final - from, len - to); + var direction; + if (from < to && to < from + count) { + direction = -1; + from = from + count - 1; + to = to + count - 1; + } else { + direction = 1; + } + while (count > 0) { + o._setter(to, o._getter(from)); + from = from + direction; + to = to + direction; + count = count - 1; + } + return o; + }}); + + // %TypedArray%.prototype.entries ( ) + // -- defined in es6.js to shim browsers w/ native TypedArrays + + // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + var thisArg = arguments[1]; + for (var i = 0; i < len; i++) { + if (!callbackfn.call(thisArg, t._getter(i), i, t)) + return false; + } + return true; + }}); + + // %TypedArray%.prototype.fill (value, start = 0, end = this.length ) + Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) { + var start = arguments[1], + end = arguments[2]; + + var o = ToObject(this); + var lenVal = o.length; + var len = ToUint32(lenVal); + len = max(len, 0); + var relativeStart = ToInt32(start); + var k; + if (relativeStart < 0) + k = max((len + relativeStart), 0); + else + k = min(relativeStart, len); + var relativeEnd; + if (end === undefined) + relativeEnd = len; + else + relativeEnd = ToInt32(end); + var final; + if (relativeEnd < 0) + final = max((len + relativeEnd), 0); + else + final = min(relativeEnd, len); + while (k < final) { + o._setter(k, value); + k += 1; + } + return o; + }}); + + // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + var res = []; + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + var val = t._getter(i); // in case fun mutates this + if (callbackfn.call(thisp, val, i, t)) + res.push(val); + } + return new this.constructor(res); + }}); + + // %TypedArray%.prototype.find (predicate, thisArg = undefined) + Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) { + var o = ToObject(this); + var lenValue = o.length; + var len = ToUint32(lenValue); + if (!IsCallable(predicate)) throw TypeError(); + var t = arguments.length > 1 ? arguments[1] : undefined; + var k = 0; + while (k < len) { + var kValue = o._getter(k); + var testResult = predicate.call(t, kValue, k, o); + if (Boolean(testResult)) + return kValue; + ++k; + } + return undefined; + }}); + + // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) { + var o = ToObject(this); + var lenValue = o.length; + var len = ToUint32(lenValue); + if (!IsCallable(predicate)) throw TypeError(); + var t = arguments.length > 1 ? arguments[1] : undefined; + var k = 0; + while (k < len) { + var kValue = o._getter(k); + var testResult = predicate.call(t, kValue, k, o); + if (Boolean(testResult)) + return k; + ++k; + } + return -1; + }}); + + // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) + callbackfn.call(thisp, t._getter(i), i, t); + }}); + + // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 ) + Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (len === 0) return -1; + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { + n = 0; + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { + n = (n > 0 || -1) * floor(abs(n)); + } + } + if (n >= len) return -1; + var k = n >= 0 ? n : max(len - abs(n), 0); + for (; k < len; k++) { + if (t._getter(k) === searchElement) { + return k; + } + } + return -1; + }}); + + // %TypedArray%.prototype.join ( separator ) + Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + var tmp = Array(len); + for (var i = 0; i < len; ++i) + tmp[i] = t._getter(i); + return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7 + }}); + + // %TypedArray%.prototype.keys ( ) + // -- defined in es6.js to shim browsers w/ native TypedArrays + + // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 ) + Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (len === 0) return -1; + var n = len; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n !== n) { + n = 0; + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { + n = (n > 0 || -1) * floor(abs(n)); + } + } + var k = n >= 0 ? min(n, len - 1) : len - abs(n); + for (; k >= 0; k--) { + if (t._getter(k) === searchElement) + return k; + } + return -1; + }}); + + // get %TypedArray%.prototype.length + // -- applied directly to the object in the constructor + + // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + var res = []; res.length = len; + var thisp = arguments[1]; + for (var i = 0; i < len; i++) + res[i] = callbackfn.call(thisp, t._getter(i), i, t); + return new this.constructor(res); + }}); + + // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] ) + Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + // no value to return if no initial value and an empty array + if (len === 0 && arguments.length === 1) throw TypeError(); + var k = 0; + var accumulator; + if (arguments.length >= 2) { + accumulator = arguments[1]; + } else { + accumulator = t._getter(k++); + } + while (k < len) { + accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); + k++; + } + return accumulator; + }}); + + // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] ) + Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + // no value to return if no initial value, empty array + if (len === 0 && arguments.length === 1) throw TypeError(); + var k = len - 1; + var accumulator; + if (arguments.length >= 2) { + accumulator = arguments[1]; + } else { + accumulator = t._getter(k--); + } + while (k >= 0) { + accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); + k--; + } + return accumulator; + }}); + + // %TypedArray%.prototype.reverse ( ) + Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + var half = floor(len / 2); + for (var i = 0, j = len - 1; i < half; ++i, --j) { + var tmp = t._getter(i); + t._setter(i, t._getter(j)); + t._setter(j, tmp); + } + return t; + }}); + + // %TypedArray%.prototype.set(array, offset = 0 ) + // %TypedArray%.prototype.set(typedArray, offset = 0 ) + // WebIDL: void set(TypedArray array, optional unsigned long offset); + // WebIDL: void set(sequence array, optional unsigned long offset); + Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) { + if (arguments.length < 1) throw SyntaxError('Not enough arguments'); + var array, sequence, offset, len, + i, s, d, + byteOffset, byteLength, tmp; + + if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { + // void set(TypedArray array, optional unsigned long offset); + array = arguments[0]; + offset = ToUint32(arguments[1]); + + if (offset + array.length > this.length) { + throw RangeError('Offset plus length of array is out of range'); + } + + byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; + byteLength = array.length * this.BYTES_PER_ELEMENT; + + if (array.buffer === this.buffer) { + tmp = []; + for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { + tmp[i] = array.buffer._bytes[s]; + } + for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { + this.buffer._bytes[d] = tmp[i]; + } + } else { + for (i = 0, s = array.byteOffset, d = byteOffset; + i < byteLength; i += 1, s += 1, d += 1) { + this.buffer._bytes[d] = array.buffer._bytes[s]; + } + } + } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { + // void set(sequence array, optional unsigned long offset); + sequence = arguments[0]; + len = ToUint32(sequence.length); + offset = ToUint32(arguments[1]); + + if (offset + len > this.length) { + throw RangeError('Offset plus length of array is out of range'); + } + + for (i = 0; i < len; i += 1) { + s = sequence[i]; + this._setter(offset + i, Number(s)); + } + } else { + throw TypeError('Unexpected argument type(s)'); + } + }}); + + // %TypedArray%.prototype.slice ( start, end ) + Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) { + var o = ToObject(this); + var lenVal = o.length; + var len = ToUint32(lenVal); + var relativeStart = ToInt32(start); + var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len); + var relativeEnd = (end === undefined) ? len : ToInt32(end); + var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len); + var count = final - k; + var c = o.constructor; + var a = new c(count); + var n = 0; + while (k < final) { + var kValue = o._getter(k); + a._setter(n, kValue); + ++k; + ++n; + } + return a; + }}); + + // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined ) + Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + if (!IsCallable(callbackfn)) throw TypeError(); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (callbackfn.call(thisp, t._getter(i), i, t)) { + return true; + } + } + return false; + }}); + + // %TypedArray%.prototype.sort ( comparefn ) + Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) { + if (this === undefined || this === null) throw TypeError(); + var t = Object(this); + var len = ToUint32(t.length); + var tmp = Array(len); + for (var i = 0; i < len; ++i) + tmp[i] = t._getter(i); + if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9 + for (i = 0; i < len; ++i) + t._setter(i, tmp[i]); + return t; + }}); + + // %TypedArray%.prototype.subarray(begin = 0, end = this.length ) + // WebIDL: TypedArray subarray(long begin, optional long end); + Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) { + function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } + + start = ToInt32(start); + end = ToInt32(end); + + if (arguments.length < 1) { start = 0; } + if (arguments.length < 2) { end = this.length; } + + if (start < 0) { start = this.length + start; } + if (end < 0) { end = this.length + end; } + + start = clamp(start, 0, this.length); + end = clamp(end, 0, this.length); + + var len = end - start; + if (len < 0) { + len = 0; + } + + return new this.constructor( + this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); + }}); + + // %TypedArray%.prototype.toLocaleString ( ) + // %TypedArray%.prototype.toString ( ) + // %TypedArray%.prototype.values ( ) + // %TypedArray%.prototype [ @@iterator ] ( ) + // get %TypedArray%.prototype [ @@toStringTag ] + // -- defined in es6.js to shim browsers w/ native TypedArrays + + function makeTypedArray(elementSize, pack, unpack) { + // Each TypedArray type requires a distinct constructor instance with + // identical logic, which this produces. + var TypedArray = function() { + Object.defineProperty(this, 'constructor', {value: TypedArray}); + $TypedArray$.apply(this, arguments); + makeArrayAccessors(this); + }; + if ('__proto__' in TypedArray) { + TypedArray.__proto__ = $TypedArray$; + } else { + TypedArray.from = $TypedArray$.from; + TypedArray.of = $TypedArray$.of; + } + + TypedArray.BYTES_PER_ELEMENT = elementSize; + + var TypedArrayPrototype = function() {}; + TypedArrayPrototype.prototype = $TypedArrayPrototype$; + + TypedArray.prototype = new TypedArrayPrototype(); + + Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize}); + Object.defineProperty(TypedArray.prototype, '_pack', {value: pack}); + Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack}); + + return TypedArray; + } + + var Int8Array = makeTypedArray(1, packI8, unpackI8); + var Uint8Array = makeTypedArray(1, packU8, unpackU8); + var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8); + var Int16Array = makeTypedArray(2, packI16, unpackI16); + var Uint16Array = makeTypedArray(2, packU16, unpackU16); + var Int32Array = makeTypedArray(4, packI32, unpackI32); + var Uint32Array = makeTypedArray(4, packU32, unpackU32); + var Float32Array = makeTypedArray(4, packF32, unpackF32); + var Float64Array = makeTypedArray(8, packF64, unpackF64); + + global.Int8Array = global.Int8Array || Int8Array; + global.Uint8Array = global.Uint8Array || Uint8Array; + global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray; + global.Int16Array = global.Int16Array || Int16Array; + global.Uint16Array = global.Uint16Array || Uint16Array; + global.Int32Array = global.Int32Array || Int32Array; + global.Uint32Array = global.Uint32Array || Uint32Array; + global.Float32Array = global.Float32Array || Float32Array; + global.Float64Array = global.Float64Array || Float64Array; + }()); + + // + // 6 The DataView View Type + // + + (function() { + function r(array, index) { + return IsCallable(array.get) ? array.get(index) : array[index]; + } + + var IS_BIG_ENDIAN = (function() { + var u16array = new Uint16Array([0x1234]), + u8array = new Uint8Array(u16array.buffer); + return r(u8array, 0) === 0x12; + }()); + + // DataView(buffer, byteOffset=0, byteLength=undefined) + // WebIDL: Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, + // optional unsigned long byteLength) + function DataView(buffer, byteOffset, byteLength) { + if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError(); + + byteOffset = ToUint32(byteOffset); + if (byteOffset > buffer.byteLength) + throw RangeError('byteOffset out of range'); + + if (byteLength === undefined) + byteLength = buffer.byteLength - byteOffset; + else + byteLength = ToUint32(byteLength); + + if ((byteOffset + byteLength) > buffer.byteLength) + throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); + + Object.defineProperty(this, 'buffer', {value: buffer}); + Object.defineProperty(this, 'byteLength', {value: byteLength}); + Object.defineProperty(this, 'byteOffset', {value: byteOffset}); + }; + + // get DataView.prototype.buffer + // get DataView.prototype.byteLength + // get DataView.prototype.byteOffset + // -- applied directly to instances by the constructor + + function makeGetter(arrayType) { + return function GetViewValue(byteOffset, littleEndian) { + byteOffset = ToUint32(byteOffset); + + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) + throw RangeError('Array index out of range'); + + byteOffset += this.byteOffset; + + var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), + bytes = []; + for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) + bytes.push(r(uint8Array, i)); + + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) + bytes.reverse(); + + return r(new arrayType(new Uint8Array(bytes).buffer), 0); + }; + } + + Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)}); + Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)}); + Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)}); + Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)}); + Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)}); + Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)}); + Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)}); + Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)}); + + function makeSetter(arrayType) { + return function SetViewValue(byteOffset, value, littleEndian) { + byteOffset = ToUint32(byteOffset); + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) + throw RangeError('Array index out of range'); + + // Get bytes + var typeArray = new arrayType([value]), + byteArray = new Uint8Array(typeArray.buffer), + bytes = [], i, byteView; + + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) + bytes.push(r(byteArray, i)); + + // Flip if necessary + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) + bytes.reverse(); + + // Write them + byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); + byteView.set(bytes); + }; + } + + Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)}); + Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)}); + Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)}); + Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)}); + Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)}); + Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)}); + Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)}); + Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)}); + + global.DataView = global.DataView || DataView; + + }()); + +}(this)); diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 894d6f5..c5a80e9 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -100,6 +100,29 @@ void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) } } +bool JSKitManager::loadJsFile(const QString &filename) +{ + Q_ASSERT(_engine); + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + logger()->warn() << "Failed to load JS file:" << file.fileName(); + return false; + } + + logger()->debug() << "now parsing" << file.fileName(); + + QJSValue result = _engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName()); + if (result.isError()) { + logger()->warn() << "error while evaluating JS script:" << describeError(result); + return false; + } + + logger()->debug() << "JS script evaluated"; + + return true; +} + void JSKitManager::startJsApp() { if (_engine) stopJsApp(); @@ -136,24 +159,13 @@ void JSKitManager::startJsApp() ); Q_ASSERT(!result.isError()); - 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; - } - - QString script = QString::fromUtf8(scriptFile.readAll()); + // Polyfills... + loadJsFile("/usr/share/pebble/js/typedarray.js"); - logger()->debug() << "now parsing" << scriptFile.fileName(); - - result = _engine->evaluate(script, scriptFile.fileName()); - if (result.isError()) { - logger()->warn() << "error while evaluating JSKit script:" << describeError(result); - } - - logger()->debug() << "JS script evaluated"; + // Now load the actual script + loadJsFile(_curApp.path() + "/pebble-js-app.js"); + // We try to invoke the callbacks even if script parsing resulted in error... _jspebble->invokeCallbacks("ready"); } @@ -173,5 +185,4 @@ void JSKitManager::stopJsApp() _jsgeo = 0; _jspebble->deleteLater(); _jspebble = 0; - } diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 873489b..4239f91 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -38,6 +38,7 @@ private slots: void handleAppMessage(const QUuid &uuid, const QVariantMap &data); private: + bool loadJsFile(const QString &filename); void startJsApp(); void stopJsApp(); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 1fc0062..e55f2d5 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -34,35 +34,45 @@ void JSKitPebble::removeEventListener(const QString &type, QJSValue function) } } -void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) +uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) { QVariantMap data = message.toVariant().toMap(); + QPointer pebbObj = this; + uint transactionId = _mgr->_appmsg->nextTransactionId(); logger()->debug() << "sendAppMessage" << data; - _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { + _mgr->_appmsg->send(_appInfo.uuid(), data, + [pebbObj, transactionId, callbackForAck]() mutable { + if (pebbObj.isNull()) return; if (callbackForAck.isCallable()) { - logger()->debug() << "Invoking ack callback"; - QJSValue result = callbackForAck.call(QJSValueList({buildAckEventObject()})); + pebbObj->logger()->debug() << "Invoking ack callback"; + QJSValue event = pebbObj->buildAckEventObject(transactionId); + QJSValue result = callbackForAck.call(QJSValueList({event})); if (result.isError()) { - logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" - << JSKitManager::describeError(result); + pebbObj->logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" + << JSKitManager::describeError(result); } } else { - logger()->debug() << "Ack callback not callable"; + pebbObj->logger()->debug() << "Ack callback not callable"; } - }, [this, callbackForNack]() mutable { + }, + [pebbObj, transactionId, callbackForNack]() mutable { + if (pebbObj.isNull()) return; if (callbackForNack.isCallable()) { - logger()->debug() << "Invoking nack callback"; - QJSValue result = callbackForNack.call(QJSValueList({buildAckEventObject()})); + pebbObj->logger()->debug() << "Invoking nack callback"; + QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch"); + QJSValue result = callbackForNack.call(QJSValueList({event})); if (result.isError()) { - logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" - << JSKitManager::describeError(result); + pebbObj->logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" + << JSKitManager::describeError(result); } } else { - logger()->debug() << "Nack callback not callable"; + pebbObj->logger()->debug() << "Nack callback not callable"; } }); + + return transactionId; } void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) @@ -84,17 +94,21 @@ QJSValue JSKitPebble::createXMLHttpRequest() return _mgr->engine()->newQObject(xhr); } -QJSValue JSKitPebble::buildAckEventObject() const +QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const { QJSEngine *engine = _mgr->engine(); QJSValue eventObj = engine->newObject(); QJSValue dataObj = engine->newObject(); - // Why do scripts need the real transactionId? - // No idea. Just fake it. - dataObj.setProperty("transactionId", engine->toScriptValue(0)); + dataObj.setProperty("transactionId", engine->toScriptValue(transaction)); eventObj.setProperty("data", dataObj); + if (!message.isEmpty()) { + QJSValue errorObj = engine->newObject(); + errorObj.setProperty("message", engine->toScriptValue(message)); + eventObj.setProperty("error", errorObj); + } + return eventObj; } @@ -184,7 +198,7 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) : QObject(parent), _mgr(mgr), - _net(new QNetworkAccessManager(this)), _reply(0) + _net(new QNetworkAccessManager(this)), _timeout(0), _reply(0) { logger()->debug() << "constructed"; } @@ -194,7 +208,7 @@ JSKitXMLHttpRequest::~JSKitXMLHttpRequest() logger()->debug() << "destructed"; } -void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async) +void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password) { if (_reply) { _reply->deleteLater(); @@ -212,17 +226,63 @@ void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString _request.setRawHeader(header.toLatin1(), value.toLatin1()); } -void JSKitXMLHttpRequest::send(const QString &body) +void JSKitXMLHttpRequest::send(const QJSValue &data) { - QBuffer *buffer = new QBuffer; - buffer->setData(body.toUtf8()); - logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << body; + QByteArray byteData; + + if (data.isUndefined() || data.isNull()) { + // Do nothing, byteData is empty. + } else if (data.isString()) { + byteData == data.toString().toUtf8(); + } else if (data.isObject()) { + if (data.hasProperty("byteLength")) { + // Looks like an ArrayView or an ArrayBufferView! + QJSValue buffer = data.property("buffer"); + if (buffer.isUndefined()) { + // We must assume we've been passed an ArrayBuffer directly + buffer = data; + } + + QJSValue array = data.property("_bytes"); + int byteLength = data.property("byteLength").toInt(); + + if (array.isArray()) { + byteData.reserve(byteLength); + + for (int i = 0; i < byteLength; i++) { + byteData.append(array.property(i).toInt()); + } + + logger()->debug() << "passed an ArrayBufferView of" << byteData.length() << "bytes"; + } else { + logger()->warn() << "passed an unknown/invalid ArrayBuffer" << data.toString(); + } + } else { + logger()->warn() << "passed an unknown object" << data.toString(); + } + + } + + QBuffer *buffer; + if (!byteData.isEmpty()) { + buffer = new QBuffer; + buffer->setData(byteData); + } else { + buffer = 0; + } + + logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << QString::fromUtf8(byteData); _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); + connect(_reply, &QNetworkReply::finished, this, &JSKitXMLHttpRequest::handleReplyFinished); connect(_reply, static_cast(&QNetworkReply::error), this, &JSKitXMLHttpRequest::handleReplyError); - buffer->setParent(_reply); // So that it gets deleted alongside the reply object. + + if (buffer) { + // So that it gets deleted alongside the reply object. + buffer->setParent(_reply); + } } void JSKitXMLHttpRequest::abort() @@ -263,7 +323,7 @@ void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) _onerror = value; } -unsigned short JSKitXMLHttpRequest::readyState() const +uint JSKitXMLHttpRequest::readyState() const { if (!_reply) { return UNSENT; @@ -274,7 +334,18 @@ unsigned short JSKitXMLHttpRequest::readyState() const } } -unsigned short JSKitXMLHttpRequest::status() const +uint JSKitXMLHttpRequest::timeout() const +{ + return _timeout; +} + +void JSKitXMLHttpRequest::setTimeout(uint value) +{ + _timeout = value; + // TODO Handle fetch in-progress. +} + +uint JSKitXMLHttpRequest::status() const { if (!_reply || !_reply->isFinished()) { return 0; @@ -283,6 +354,53 @@ unsigned short JSKitXMLHttpRequest::status() const } } +QString JSKitXMLHttpRequest::statusText() const +{ + if (!_reply || !_reply->isFinished()) { + return QString(); + } else { + return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + } +} + +QString JSKitXMLHttpRequest::responseType() const +{ + return _responseType; +} + +void JSKitXMLHttpRequest::setResponseType(const QString &type) +{ + logger()->debug() << "response type set to" << type; + _responseType = type; +} + +QJSValue JSKitXMLHttpRequest::response() const +{ + QJSEngine *engine = _mgr->engine(); + if (_responseType.isEmpty() || _responseType == "text") { + return engine->toScriptValue(QString::fromUtf8(_response)); + } else if (_responseType == "arraybuffer") { + QJSValue arrayBufferProto = engine->globalObject().property("ArrayBuffer").property("prototype"); + QJSValue arrayBuf = engine->newObject(); + if (!arrayBufferProto.isUndefined()) { + arrayBuf.setPrototype(arrayBufferProto); + arrayBuf.setProperty("byteLength", engine->toScriptValue(_response.size())); + QJSValue array = engine->newArray(_response.size()); + for (int i = 0; i < _response.size(); i++) { + array.setProperty(i, engine->toScriptValue(_response[i])); + } + arrayBuf.setProperty("_bytes", array); + logger()->debug() << "returning ArrayBuffer of" << _response.size() << "bytes"; + } else { + logger()->warn() << "Cannot find proto of ArrayBuffer"; + } + return arrayBuf; + } else { + logger()->warn() << "unsupported responseType:" << _responseType; + return engine->toScriptValue(0); + } +} + QString JSKitXMLHttpRequest::responseText() const { return QString::fromUtf8(_response); @@ -300,6 +418,8 @@ void JSKitXMLHttpRequest::handleReplyFinished() emit readyStateChanged(); emit statusChanged(); + emit statusTextChanged(); + emit responseChanged(); emit responseTextChanged(); if (_onload.isCallable()) { @@ -322,6 +442,10 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) logger()->info() << "reply error" << code; + emit readyStateChanged(); + emit statusChanged(); + emit statusTextChanged(); + if (_onerror.isCallable()) { logger()->debug() << "going to call onerror handler:" << _onload.toString(); QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); @@ -338,7 +462,6 @@ JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) { - logger()->debug() << Q_FUNC_INFO; setupWatcher(successCallback, errorCallback, options, true); } @@ -360,12 +483,14 @@ void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { + logger()->debug() << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); + if (_watches.empty()) { logger()->warn() << "got position update but no one is watching"; + _source->stopUpdates(); // Just in case. + return; } - logger()->debug() << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); - QJSValue obj = buildPositionObject(pos); for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { @@ -374,30 +499,76 @@ void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) if (it->once) { it = _watches.erase(it); } else { + it->timer.restart(); ++it; } } +} + +void JSKitGeolocation::handleTimeout() +{ + logger()->info() << "positioning timeout"; if (_watches.empty()) { + logger()->warn() << "got position timeout but no one is watching"; _source->stopUpdates(); + return; + } + + QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout"); + + for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { + if (it->timer.hasExpired(it->timeout)) { + logger()->info() << "positioning timeout for watch" << it->watchId; + invokeCallback(it->errorCallback, obj); + + if (it->once) { + it = _watches.erase(it); + } else { + it->timer.restart(); + ++it; + } + } else { + ++it; + } } + + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); } -void JSKitGeolocation::handleTimeout() +void JSKitGeolocation::updateTimeouts() { + int once_timeout = -1, updates_timeout = -1; + logger()->debug() << Q_FUNC_INFO; - // TODO -} -uint JSKitGeolocation::minimumTimeout() const -{ - uint minimum = std::numeric_limits::max(); Q_FOREACH(const Watcher &watcher, _watches) { - if (!watcher.once) { - minimum = qMin(watcher.timeout, minimum); + qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed(); + logger()->debug() << "watch" << watcher.watchId << "rem timeout" << rem_timeout; + if (rem_timeout >= 0) { + // In case it is too large... + rem_timeout = qMin(rem_timeout, std::numeric_limits::max()); + if (watcher.once) { + once_timeout = once_timeout >= 0 ? qMin(once_timeout, rem_timeout) : rem_timeout; + } else { + updates_timeout = updates_timeout >= 0 ? qMin(updates_timeout, rem_timeout) : rem_timeout; + } } } - return minimum; + + if (updates_timeout >= 0) { + logger()->debug() << "setting location update interval to" << updates_timeout; + _source->setUpdateInterval(updates_timeout); + _source->startUpdates(); + } else { + logger()->debug() << "stopping updates"; + _source->stopUpdates(); + } + + if (once_timeout >= 0) { + logger()->debug() << "requesting single location update with timeout" << once_timeout; + _source->requestUpdate(once_timeout); + } } int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) @@ -406,11 +577,11 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal watcher.successCallback = successCallback; watcher.errorCallback = errorCallback; watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); - watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); + watcher.timeout = options.value("timeout", std::numeric_limits::max() - 1).toInt(); watcher.once = once; watcher.watchId = ++_lastWatchId; - uint maximumAge = options.value("maximumAge", 0).toUInt(); + qlonglong maximumAge = options.value("maximumAge", 0).toLongLong(); logger()->debug() << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once; @@ -434,25 +605,20 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal return -1; } } else if (watcher.timeout == 0 && once) { + // If the timeout has already expired, and we have no cached data + // Do not even bother to turn on the GPS; return error object now. invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); return -1; } } - if (once) { - _source->requestUpdate(watcher.timeout); - } else { - uint timeout = minimumTimeout(); - logger()->debug() << "setting location update interval to" << timeout; - _source->setUpdateInterval(timeout); - logger()->debug() << "starting location updates"; - _source->startUpdates(); - } - + watcher.timer.start(); _watches.append(watcher); logger()->debug() << "added new watch" << watcher.watchId; + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); + return watcher.watchId; } @@ -474,14 +640,7 @@ void JSKitGeolocation::removeWatcher(int watchId) return; } - if (_watches.empty()) { - logger()->debug() << "stopping updates"; - _source->stopUpdates(); - } else { - uint timeout = minimumTimeout(); - logger()->debug() << "setting location update interval to" << timeout; - _source->setUpdateInterval(timeout); - } + QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection); } QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 5f039c1..b1954c0 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -1,6 +1,7 @@ #ifndef JSKITMANAGER_P_H #define JSKITMANAGER_P_H +#include #include #include #include @@ -18,7 +19,7 @@ public: Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); - Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); + Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); @@ -29,7 +30,7 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: - QJSValue buildAckEventObject() const; + QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const; private: AppInfo _appInfo; @@ -81,12 +82,17 @@ class JSKitXMLHttpRequest : public QObject { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER + Q_ENUMS(ReadyStates) Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout) Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror) - Q_PROPERTY(unsigned short readyState READ readyState NOTIFY readyStateChanged) - Q_PROPERTY(unsigned short status READ status NOTIFY statusChanged) + Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged) + Q_PROPERTY(uint timeout READ timeout WRITE setTimeout) + Q_PROPERTY(uint status READ status NOTIFY statusChanged) + Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) + Q_PROPERTY(QString responseType READ responseType WRITE setResponseType) + Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged) Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) public: @@ -101,9 +107,9 @@ public: DONE = 4 }; - Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false); + Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false, const QString &username = QString(), const QString &password = QString()); Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); - Q_INVOKABLE void send(const QString &body = QString()); + Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue)); Q_INVOKABLE void abort(); QJSValue onload() const; @@ -113,13 +119,25 @@ public: QJSValue onerror() const; void setOnerror(const QJSValue &value); - unsigned short readyState() const; - unsigned short status() const; + uint readyState() const; + + uint timeout() const; + void setTimeout(uint value); + + uint status() const; + QString statusText() const; + + QString responseType() const; + void setResponseType(const QString& type); + + QJSValue response() const; QString responseText() const; signals: void readyStateChanged(); void statusChanged(); + void statusTextChanged(); + void responseChanged(); void responseTextChanged(); private slots: @@ -130,8 +148,10 @@ private: JSKitManager *_mgr; QNetworkAccessManager *_net; QString _verb; + uint _timeout; QNetworkRequest _request; QNetworkReply *_reply; + QString _responseType; QByteArray _response; QJSValue _onload; QJSValue _ontimeout; @@ -163,11 +183,12 @@ private slots: void handleError(const QGeoPositionInfoSource::Error error); void handlePosition(const QGeoPositionInfo &pos); void handleTimeout(); + void updateTimeouts(); private: - uint minimumTimeout() const; int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); void removeWatcher(int watchId); + QJSValue buildPositionObject(const QGeoPositionInfo &pos); QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); @@ -183,7 +204,8 @@ private: int watchId; bool once; bool highAccuracy; - uint timeout; + int timeout; + QElapsedTimer timer; }; QList _watches; diff --git a/daemon/packer.cpp b/daemon/packer.cpp index 569f7a8..4dabf96 100644 --- a/daemon/packer.cpp +++ b/daemon/packer.cpp @@ -74,6 +74,22 @@ void Packer::writeDict(const QMap &d) break; } + case QMetaType::QVariantList: { + // Generally a JS array, which we marshal as a byte array. + QVariantList list = it.value().toList(); + QByteArray ba; + ba.reserve(list.size()); + + Q_FOREACH (const QVariant &v, list) { + ba.append(v.toInt()); + } + + writeLE(WatchConnector::typeBYTES); + writeLE(ba.size()); + _buf->append(ba); + break; + } + default: logger()->warn() << "Unknown dict item type:" << it.value().typeName(); /* Fallthrough */ diff --git a/rpm/pebble.spec b/rpm/pebble.spec index ba5b4b3..f2e3611 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -80,6 +80,7 @@ systemctl --user daemon-reload %defattr(-,root,root,-) %{_bindir} %{_datadir}/%{name}/qml +%{_datadir}/%{name}/js %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/86x86/apps/%{name}.png %{_libdir}/systemd/user/%{name}d.service diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index 45fb409..ca0b068 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -31,6 +31,7 @@ Requires: Files: - '%{_bindir}' - '%{_datadir}/%{name}/qml' +- '%{_datadir}/%{name}/js' - '%{_datadir}/applications/%{name}.desktop' - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' - '%{_libdir}/systemd/user/%{name}d.service' -- cgit v1.2.3 From b03ee6521f61d02dcebb5d140f8d308479a89e35 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 21:24:06 +0100 Subject: do "this.window = this" --- daemon/jskitmanager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index c5a80e9..6b291df 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -145,14 +145,13 @@ void JSKitManager::startJsApp() globalObj.setProperty("console", _engine->newQObject(_jsconsole)); globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); - QJSValue windowObj = _engine->newObject(); - windowObj.setProperty("localStorage", globalObj.property("localStorage")); - globalObj.setProperty("window", windowObj); - QJSValue navigatorObj = _engine->newObject(); navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); globalObj.setProperty("navigator", navigatorObj); + // Set this.window = this + globalObj.setProperty("window", globalObj); + // Shims for compatibility... QJSValue result = _engine->evaluate( "function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }\n" -- cgit v1.2.3 From 5499dc58d09f07081c41b8e4dead810a82137939 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 23:31:57 +0100 Subject: properly send acks for incoming appmsgs --- daemon/appmsgmanager.cpp | 32 +++++++++++++++++++++++++++++++- daemon/appmsgmanager.h | 7 ++++++- daemon/jskitmanager.cpp | 22 +++++++++++++++++++--- daemon/jskitmanager.h | 2 +- daemon/jskitobjects.cpp | 3 ++- daemon/manager.cpp | 7 +------ daemon/manager.h | 1 - 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 312043a..f24b8d9 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -69,6 +69,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: } } +void AppMsgManager::setMessageHandler(const QUuid &uuid, MessageHandlerFunc func) +{ + _handlers.insert(uuid, func); +} + +void AppMsgManager::clearMessageHandler(const QUuid &uuid) +{ + _handlers.remove(uuid); +} + uint AppMsgManager::lastTransactionId() const { return _lastTransactionId; @@ -236,6 +246,8 @@ void AppMsgManager::handlePushMessage(const QByteArray &data) if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { logger()->warn() << "Failed to parse APP_MSG PUSH"; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildNackMessage(transaction)); return; } @@ -244,7 +256,25 @@ void AppMsgManager::handlePushMessage(const QByteArray &data) QVariantMap msg = mapAppKeys(uuid, dict); logger()->debug() << "Mapped dict" << msg; - emit messageReceived(uuid, msg); + bool result; + + MessageHandlerFunc handler = _handlers.value(uuid); + if (handler) { + result = handler(msg); + } else { + // No handler? Let's just send an ACK. + result = false; + } + + if (result) { + logger()->debug() << "ACKing transaction" << transaction; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildAckMessage(transaction)); + } else { + logger()->info() << "NACKing transaction" << transaction; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildNackMessage(transaction)); + } } void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 9aaabd4..e52c544 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -19,6 +19,11 @@ public: void send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback); + + typedef std::function MessageHandlerFunc; + void setMessageHandler(const QUuid &uuid, MessageHandlerFunc func); + void clearMessageHandler(const QUuid &uuid); + uint lastTransactionId() const; uint nextTransactionId() const; @@ -30,7 +35,6 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void messageReceived(const QUuid &uuid, const QVariantMap &data); private: WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); @@ -56,6 +60,7 @@ private slots: private: AppManager *apps; WatchConnector *watch; + QHash _handlers; quint8 _lastTransactionId; struct PendingTransaction { diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 6b291df..c73d32e 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,7 +9,6 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); - connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -83,7 +82,7 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } -void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg) { if (_curApp.uuid() == uuid) { logger()->debug() << "received a message for the current JSKit app"; @@ -94,7 +93,7 @@ void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) } QJSValue eventObj = _engine->newObject(); - eventObj.setProperty("payload", _engine->toScriptValue(data)); + eventObj.setProperty("payload", _engine->toScriptValue(msg)); _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); } @@ -164,6 +163,19 @@ void JSKitManager::startJsApp() // Now load the actual script loadJsFile(_curApp.path() + "/pebble-js-app.js"); + // Setup the message callback + QUuid uuid = _curApp.uuid(); + _appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) { + QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection, + Q_ARG(QUuid, uuid), + Q_ARG(QVariantMap, msg)); + + // Invoke the slot as a queued connection to give time for the ACK message + // to go through first. + + return true; + }); + // We try to invoke the callbacks even if script parsing resulted in error... _jspebble->invokeCallbacks("ready"); } @@ -174,6 +186,10 @@ void JSKitManager::stopJsApp() logger()->debug() << "stopping JS app"; + if (!_curApp.uuid().isNull()) { + _appmsg->clearMessageHandler(_curApp.uuid()); + } + _engine->collectGarbage(); _engine->deleteLater(); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 4239f91..b42e338 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -35,7 +35,7 @@ public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); - void handleAppMessage(const QUuid &uuid, const QVariantMap &data); + void handleAppMessage(const QUuid &uuid, const QVariantMap &msg); private: bool loadJsFile(const QString &filename); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index e55f2d5..3386f16 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -519,7 +519,8 @@ void JSKitGeolocation::handleTimeout() for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { if (it->timer.hasExpired(it->timeout)) { - logger()->info() << "positioning timeout for watch" << it->watchId; + logger()->info() << "positioning timeout for watch" << it->watchId + << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout; invokeCallback(it->errorCallback, obj); if (it->once) { diff --git a/daemon/manager.cpp b/daemon/manager.cpp index b488432..8a00373 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -55,7 +55,6 @@ Manager::Manager(Settings *settings, QObject *parent) : 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(appmsg, &AppMsgManager::messageReceived, this, &Manager::onAppMessage); connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); @@ -395,14 +394,10 @@ void Manager::transliterateMessage(const QString &text) void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) { + Q_UNUSED(uuid); watch->sendSMSNotification(title, body); } -void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) -{ - emit proxy->AppMessage(uuid.toString(), data); -} - void Manager::onAppOpened(const QUuid &uuid) { currentAppUuid = uuid; diff --git a/daemon/manager.h b/daemon/manager.h index 18bd7bf..bf83f75 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -103,7 +103,6 @@ private slots: void setMprisMetadata(QVariantMap metadata); void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); - void onAppMessage(const QUuid &uuid, const QVariantMap &data); void onAppOpened(const QUuid &uuid); void onAppClosed(const QUuid &uuid); }; -- cgit v1.2.3 From 9aeb1bf31ad9ff5979f598f5925cfd25f544ba34 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 23:43:37 +0100 Subject: ensure packed strings are always null-terminated --- daemon/packer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/packer.cpp b/daemon/packer.cpp index 4dabf96..0cc71f6 100644 --- a/daemon/packer.cpp +++ b/daemon/packer.cpp @@ -97,6 +97,10 @@ void Packer::writeDict(const QMap &d) case QMetaType::QUrl: { QByteArray s = it.value().toString().toUtf8(); + if (s.isEmpty() || s[s.size() - 1] != '\0') { + // Add null terminator if it doesn't have one + s.append('\0'); + } writeLE(WatchConnector::typeSTRING); writeLE(s.size()); _buf->append(s); -- cgit v1.2.3 From d7474bbe49e2d14290ca1b0cddb746e0bf5fbe63 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 00:04:36 +0100 Subject: implement launcher d-bus commands --- daemon/appmsgmanager.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index f24b8d9..eda1eb4 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -23,6 +23,10 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p case WatchConnector::appmsgPUSH: handleLauncherPushMessage(data); break; + case WatchConnector::appmsgACK: + case WatchConnector::appmsgNACK: + // TODO we ignore those for now. + break; } return true; @@ -97,12 +101,24 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) void AppMsgManager::launchApp(const QUuid &uuid) { - // TODO + WatchConnector::Dict dict; + dict.insert(1, WatchConnector::launcherSTARTED); + + logger()->debug() << "Sending message to launcher" << uuid << dict; + + QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict); + watch->sendMessage(WatchConnector::watchLAUNCHER, msg); } void AppMsgManager::closeApp(const QUuid &uuid) { - // TODO + WatchConnector::Dict dict; + dict.insert(1, WatchConnector::launcherSTOPPED); + + logger()->debug() << "Sending message to launcher" << uuid << dict; + + QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict); + watch->sendMessage(WatchConnector::watchLAUNCHER, msg); } WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) -- cgit v1.2.3 From c7804f23412c14d6252bee6deb904d59ced835e2 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 00:11:54 +0100 Subject: delete unused d-bus signal --- daemon/manager.h | 1 - org.pebbled.Watch.xml | 5 ----- 2 files changed, 6 deletions(-) diff --git a/daemon/manager.h b/daemon/manager.h index bf83f75..9347289 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -148,7 +148,6 @@ signals: void AddressChanged(); void ConnectedChanged(); void AppUuidChanged(); - void AppMessage(const QString &uuid, const QVariantMap &data); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); }; diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 759a6db..87a22b9 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -33,11 +33,6 @@ - - - - - -- cgit v1.2.3 From 49c20eb7e2933ae6e9e4337fc0fe9b49a39efde8 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 01:31:51 +0100 Subject: add bankmanager and ability to unload apps --- daemon/bankmanager.cpp | 233 ++++++++++++++++++++++++++++++++++++++++++++++ daemon/bankmanager.h | 51 ++++++++++ daemon/daemon.pro | 6 +- daemon/manager.cpp | 12 +++ daemon/manager.h | 4 + daemon/watchconnector.cpp | 90 ------------------ daemon/watchconnector.h | 4 +- org.pebbled.Watch.xml | 5 + 8 files changed, 310 insertions(+), 95 deletions(-) create mode 100644 daemon/bankmanager.cpp create mode 100644 daemon/bankmanager.h diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp new file mode 100644 index 0000000..194ec77 --- /dev/null +++ b/daemon/bankmanager.cpp @@ -0,0 +1,233 @@ +#include "unpacker.h" +#include "packer.h" +#include "bankmanager.h" + +BankManager::BankManager(WatchConnector *watch, AppManager *apps, QObject *parent) : + QObject(parent), watch(watch), apps(apps) +{ + connect(watch, &WatchConnector::connectedChanged, this, &BankManager::handleWatchConnected); +} + +int BankManager::numSlots() const +{ + return _slots.size(); +} + +bool BankManager::uploadApp(const QUuid &uuid, int slot) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "uuid" << uuid << "is not installed"; + return false; + } + if (slot == -1) { + slot = findUnusedSlot(); + if (slot == -1) { + logger()->warn() << "no free slots!"; + return false; + } + } + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + + // TODO + + return false; +} + +bool BankManager::unloadApp(int slot) +{ + if (slot < 0 || slot > _slots.size()) { + logger()->warn() << "invalid slot index"; + return false; + } + if (!_slots[slot].used) { + logger()->warn() << "slot is empty"; + return false; + } + + logger()->debug() << "going to unload app" << _slots[slot].name << "in slot" << slot; + + int installId = _slots[slot].id; + + QByteArray msg; + msg.reserve(2 * sizeof(quint32)); + Packer p(&msg); + p.write(WatchConnector::appmgrREMOVE_APP); + p.write(installId); + p.write(slot); + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, + [this](const QByteArray &data) { + Unpacker u(data); + if (u.read() != WatchConnector::appmgrREMOVE_APP) { + return false; + } + + uint result = u.read(); + switch (result) { + case 1: /* Success */ + logger()->debug() << "sucessfully unloaded app"; + break; + default: + logger()->warn() << "could not unload app. result code:" << result; + break; + } + + QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + + return true; + }); + + return true; // Operation in progress +} + +void BankManager::refresh() +{ + logger()->debug() << "refreshing bank status"; + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, + QByteArray(1, WatchConnector::appmgrGET_APPBANK_STATUS), + [this](const QByteArray &data) { + if (data.at(0) != WatchConnector::appmgrGET_APPBANK_STATUS) { + return false; + } + + 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() << "Bank status:" << apps_installed << "/" << num_banks; + + _slots.resize(num_banks); + for (unsigned int i = 0; i < num_banks; i++) { + _slots[i].used = false; + _slots[i].id = 0; + _slots[i].name.clear(); + _slots[i].company.clear(); + _slots[i].flags = 0; + _slots[i].version = 0; + _slots[i].uuid = QUuid(); + } + + for (unsigned int i = 0; i < apps_installed; i++) { + unsigned int id = u.read(); + int index = u.read(); + QString name = u.readFixedString(32); + QString company = u.readFixedString(32); + unsigned int flags = u.read(); + unsigned short version = u.read(); + + if (index < 0 || index >= _slots.size()) { + logger()->warn() << "Invalid slot index" << index; + continue; + } + + if (u.bad()) { + logger()->warn() << "short read"; + return true; + } + + _slots[index].used = true; + _slots[index].id = id; + _slots[index].name = name; + _slots[index].company = company; + _slots[index].flags = flags; + _slots[index].version = version; + + AppInfo info = apps->info(name); + QUuid uuid = info.uuid(); + _slots[index].uuid = uuid; + + logger()->debug() << index << id << name << company << flags << version << uuid; + } + + emit this->slotsChanged(); + + return true; + }); +} + +int BankManager::findUnusedSlot() const +{ + for (int i = 0; i < _slots.size(); ++i) { + if (!_slots[i].used) { + return i; + } + } + + return -1; +} + +void BankManager::handleWatchConnected() +{ + if (watch->isConnected()) { + refresh(); + } +} + +#if 0 + +void WatchConnector::getAppbankStatus(const std::function& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { + + }); +} + +void WatchConnector::getAppbankUuids(const function &)>& callback) +{ + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [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; + }); +} +#endif diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h new file mode 100644 index 0000000..28729b9 --- /dev/null +++ b/daemon/bankmanager.h @@ -0,0 +1,51 @@ +#ifndef BANKMANAGER_H +#define BANKMANAGER_H + +#include "watchconnector.h" +#include "appmanager.h" + +class BankManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit BankManager(WatchConnector *watch, AppManager *apps, QObject *parent = 0); + + int numSlots() const; + + +signals: + void slotsChanged(); + +public slots: + bool uploadApp(const QUuid &uuid, int slot = -1); + bool unloadApp(int slot); + + void refresh(); + +private: + int findUnusedSlot() const; + + +private slots: + void handleWatchConnected(); + +private: + WatchConnector *watch; + AppManager *apps; + + struct SlotInfo { + bool used; + quint32 id; + QString name; + QString company; + quint32 flags; + quint16 version; + QUuid uuid; + }; + + QVector _slots; +}; + +#endif // BANKMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 81570c7..21ebffa 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ jskitmanager.cpp \ appinfo.cpp \ jskitobjects.cpp \ - packer.cpp + packer.cpp \ + bankmanager.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ jskitmanager.h \ appinfo.h \ jskitobjects.h \ - packer.h + packer.h \ + bankmanager.h OTHER_FILES += \ ../log4qt-debug.conf \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8a00373..27bb870 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -11,6 +11,7 @@ Manager::Manager(Settings *settings, QObject *parent) : watch(new WatchConnector(this)), dbus(new DBusConnector(this)), apps(new AppManager(this)), + bank(new BankManager(watch, apps, this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), @@ -498,3 +499,14 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } + +void PebbledProxy::UnloadApp(uint slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->unloadApp(slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUnload", + "Cannot unload application"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index 9347289..0e55190 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -10,6 +10,7 @@ #include "appmsgmanager.h" #include "jskitmanager.h" #include "appmanager.h" +#include "bankmanager.h" #include "settings.h" #include @@ -45,6 +46,7 @@ class Manager : public QObject, protected QDBusContext WatchConnector *watch; DBusConnector *dbus; AppManager *apps; + BankManager *bank; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; @@ -143,6 +145,8 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); + void UnloadApp(uint slot); + signals: void NameChanged(); void AddressChanged(); diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index baec52c..21f5ad5 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -451,93 +451,3 @@ void WatchConnector::endPhoneCall(uint cookie) { phoneControl(callEND, cookie, QStringList()); } - -void WatchConnector::getAppbankStatus(const std::function& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), - [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), - [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 6c7fdd4..a5fe1ea 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -107,6 +107,7 @@ public: }; enum AppManager { appmgrGET_APPBANK_STATUS = 1, + appmgrREMOVE_APP = 2, appmgrGET_APPBANK_UUIDS = 5 }; enum AppMessage { @@ -179,9 +180,6 @@ public: static QString timeStamp(); static QString decodeEndpoint(uint val); - void getAppbankStatus(const std::function& callback); - void getAppbankUuids(const std::function &uuids)>& callback); - signals: void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 87a22b9..ce71248 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -49,5 +49,10 @@ + + + + + -- cgit v1.2.3 From a60c1cb3c4afd6dfd305115ec4c52e993172fa7d Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 23:39:29 +0100 Subject: ability to upload apps --- daemon/bankmanager.cpp | 134 +++++++++++++++++++++--- daemon/bankmanager.h | 14 ++- daemon/daemon.pro | 8 +- daemon/manager.cpp | 14 ++- daemon/manager.h | 3 + daemon/stm32crc.cpp | 119 +++++++++++++++++++++ daemon/stm32crc.h | 24 +++++ daemon/uploadmanager.cpp | 266 +++++++++++++++++++++++++++++++++++++++++++++++ daemon/uploadmanager.h | 65 ++++++++++++ daemon/watchconnector.h | 16 ++- org.pebbled.Watch.xml | 4 + 11 files changed, 643 insertions(+), 24 deletions(-) create mode 100644 daemon/stm32crc.cpp create mode 100644 daemon/stm32crc.h create mode 100644 daemon/uploadmanager.cpp create mode 100644 daemon/uploadmanager.h diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index 194ec77..fe5dc21 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -1,11 +1,19 @@ +#include +#include #include "unpacker.h" #include "packer.h" #include "bankmanager.h" -BankManager::BankManager(WatchConnector *watch, AppManager *apps, QObject *parent) : - QObject(parent), watch(watch), apps(apps) +BankManager::BankManager(WatchConnector *watch, UploadManager *upload, AppManager *apps, QObject *parent) : + QObject(parent), watch(watch), upload(upload), apps(apps), _refresh(new QTimer(this)) { - connect(watch, &WatchConnector::connectedChanged, this, &BankManager::handleWatchConnected); + connect(watch, &WatchConnector::connectedChanged, + this, &BankManager::handleWatchConnected); + + _refresh->setInterval(0); + _refresh->setSingleShot(true); + connect(_refresh, &QTimer::timeout, + this, &BankManager::refresh); } int BankManager::numSlots() const @@ -32,9 +40,82 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) return false; } - // TODO + QDir appDir(info.path()); + + logger()->debug() << "about to install app from" << appDir.absolutePath() << "into slot" << slot; + + QFile *binaryFile = new QFile(appDir.absoluteFilePath("pebble-app.bin"), this); + if (!binaryFile->open(QIODevice::ReadOnly)) { + logger()->warn() << "failed to open" << binaryFile->fileName() << ":" << binaryFile->errorString(); + delete binaryFile; + return false; + } + + logger()->debug() << "binary file size is" << binaryFile->size(); + + QFile *resourceFile = 0; + if (appDir.exists("app_resources.pbpack")) { + resourceFile = new QFile(appDir.absoluteFilePath("app_resources.pbpack"), this); + if (!resourceFile->open(QIODevice::ReadOnly)) { + logger()->warn() << "failed to open" << resourceFile->fileName() << ":" << resourceFile->errorString(); + delete resourceFile; + return false; + } + } + + // Mark the slot as used, but without any app, just in case. + _slots[slot].used = true; + _slots[slot].name.clear(); + _slots[slot].uuid = QUuid(); + + upload->upload(WatchConnector::uploadBINARY, slot, binaryFile, -1, + [this, binaryFile, resourceFile, slot]() { + logger()->debug() << "app binary upload succesful"; + delete binaryFile; + + // Proceed to upload the resource file + if (resourceFile) { + upload->upload(WatchConnector::uploadRESOURCES, slot, resourceFile, -1, + [this, resourceFile, slot]() { + logger()->debug() << "app resources upload succesful"; + delete resourceFile; + + // Upload succesful + // Tell the watch to reload the slot + refreshWatchApp(slot, [this]() { + logger()->debug() << "app refresh succesful"; + _refresh->start(); + }, [this](int code) { + logger()->warn() << "app refresh failed" << code; + _refresh->start(); + }); + }, [this, resourceFile](int code) { + logger()->warn() << "app resources upload failed" << code; + delete resourceFile; + + _refresh->start(); + }); + + } else { + // No resource file + // Tell the watch to reload the slot + refreshWatchApp(slot, [this]() { + logger()->debug() << "app refresh succesful"; + _refresh->start(); + }, [this](int code) { + logger()->warn() << "app refresh failed" << code; + _refresh->start(); + }); + } + }, [this, binaryFile, resourceFile](int code) { + logger()->warn() << "app binary upload failed" << code; + delete binaryFile; + delete resourceFile; + + _refresh->start(); + }); - return false; + return true; } bool BankManager::unloadApp(int slot) @@ -76,7 +157,7 @@ bool BankManager::unloadApp(int slot) break; } - QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + _refresh->start(); return true; }); @@ -169,24 +250,43 @@ int BankManager::findUnusedSlot() const return -1; } +void BankManager::refreshWatchApp(int slot, std::function successCallback, std::function errorCallback) +{ + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::appmgrREFRESH_APP); + p.write(slot); + + watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, + [this, successCallback, errorCallback](const QByteArray &data) { + Unpacker u(data); + if (u.read() != WatchConnector::appmgrREFRESH_APP) { + return false; + } + int code = u.read(); + if (code == Success) { + if (successCallback) { + successCallback(); + } + } else { + if (errorCallback) { + errorCallback(code); + } + } + + return true; + }); +} + void BankManager::handleWatchConnected() { if (watch->isConnected()) { - refresh(); + _refresh->start(); } } #if 0 - -void WatchConnector::getAppbankStatus(const std::function& callback) -{ - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), - [this, callback](const QByteArray &data) { - - }); -} - -void WatchConnector::getAppbankUuids(const function &)>& callback) +void BankManager::getAppbankUuids(const function &)>& callback) { sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), [this, callback](const QByteArray &data) { diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h index 28729b9..6abedc8 100644 --- a/daemon/bankmanager.h +++ b/daemon/bankmanager.h @@ -2,6 +2,7 @@ #define BANKMANAGER_H #include "watchconnector.h" +#include "uploadmanager.h" #include "appmanager.h" class BankManager : public QObject @@ -10,11 +11,10 @@ class BankManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit BankManager(WatchConnector *watch, AppManager *apps, QObject *parent = 0); + explicit BankManager(WatchConnector *watch, UploadManager *upload, AppManager *apps, QObject *parent = 0); int numSlots() const; - signals: void slotsChanged(); @@ -26,6 +26,7 @@ public slots: private: int findUnusedSlot() const; + void refreshWatchApp(int slot, std::function successCallback, std::function errorCallback); private slots: @@ -33,8 +34,16 @@ private slots: private: WatchConnector *watch; + UploadManager *upload; AppManager *apps; + enum ResultCodes { + Success = 1, + BankInUse = 2, + InvalidCommand = 3, + GeneralFailure = 4 + }; + struct SlotInfo { bool used; quint32 id; @@ -46,6 +55,7 @@ private: }; QVector _slots; + QTimer *_refresh; }; #endif // BANKMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 21ebffa..6eea288 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -29,7 +29,9 @@ SOURCES += \ appinfo.cpp \ jskitobjects.cpp \ packer.cpp \ - bankmanager.cpp + bankmanager.cpp \ + uploadmanager.cpp \ + stm32crc.cpp HEADERS += \ manager.h \ @@ -48,7 +50,9 @@ HEADERS += \ appinfo.h \ jskitobjects.h \ packer.h \ - bankmanager.h + bankmanager.h \ + uploadmanager.h \ + stm32crc.h OTHER_FILES += \ ../log4qt-debug.conf \ diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 27bb870..136d7f3 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -10,8 +10,9 @@ Manager::Manager(Settings *settings, QObject *parent) : proxy(new PebbledProxy(this)), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), + upload(new UploadManager(watch, this)), apps(new AppManager(this)), - bank(new BankManager(watch, apps, this)), + bank(new BankManager(watch, upload, apps, this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), @@ -510,3 +511,14 @@ void PebbledProxy::UnloadApp(uint slot) "Cannot unload application"); } } + +void PebbledProxy::UploadApp(const QString &uuid, uint slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->uploadApp(QUuid(uuid), slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUpload", + "Cannot upload application"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index 0e55190..f27da98 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -3,6 +3,7 @@ #include "watchconnector.h" #include "dbusconnector.h" +#include "uploadmanager.h" #include "voicecallmanager.h" #include "notificationmanager.h" #include "musicmanager.h" @@ -45,6 +46,7 @@ class Manager : public QObject, protected QDBusContext WatchConnector *watch; DBusConnector *dbus; + UploadManager *upload; AppManager *apps; BankManager *bank; VoiceCallManager *voice; @@ -146,6 +148,7 @@ public slots: void SendAppConfigurationData(const QString &uuid, const QString &data); void UnloadApp(uint slot); + void UploadApp(const QString &uuid, uint slot); signals: void NameChanged(); diff --git a/daemon/stm32crc.cpp b/daemon/stm32crc.cpp new file mode 100644 index 0000000..dd09f38 --- /dev/null +++ b/daemon/stm32crc.cpp @@ -0,0 +1,119 @@ +#include "stm32crc.h" + +/** Precomputed CRC polynomial + * Generated by pycrc v0.8.2, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = False + * XorOut = 0xffffffff + * ReflectOut = False + * Algorithm = table-driven + * Modified to use STM32-like word size + *****************************************************************************/ +static const quint32 crc_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +Stm32Crc::Stm32Crc() +{ + reset(); +} + +void Stm32Crc::reset() +{ + crc = 0xFFFFFFFFU; +} + +void Stm32Crc::addData(const char *data, int length) +{ + const int word_len = sizeof(quint32); + int i = 0; + + Q_ASSERT(length % word_len == 0); + Q_ASSERT(quintptr(data) % word_len == 0); + + for (; i < (length/word_len)*word_len; i+=word_len) { + const quint32 *word = reinterpret_cast(&data[i]); + + crc ^= *word; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + } +} + +void Stm32Crc::addData(const QByteArray &data) +{ + addData(data.constData(), data.length()); +} + +quint32 Stm32Crc::result() const +{ + return crc; +} diff --git a/daemon/stm32crc.h b/daemon/stm32crc.h new file mode 100644 index 0000000..1361de3 --- /dev/null +++ b/daemon/stm32crc.h @@ -0,0 +1,24 @@ +#ifndef STM32CRC_H +#define STM32CRC_H + +#include + +class Stm32Crc +{ +public: + Stm32Crc(); + + void reset(); + + // Data size must be multiple of 4, data must be aligned to 4. + + void addData(const char *data, int length); + void addData(const QByteArray &data); + + quint32 result() const; + +private: + quint32 crc; +}; + +#endif // STM32CRC_H diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp new file mode 100644 index 0000000..89d70f7 --- /dev/null +++ b/daemon/uploadmanager.cpp @@ -0,0 +1,266 @@ +#include "uploadmanager.h" +#include "unpacker.h" +#include "packer.h" +#include "stm32crc.h" + +static const int CHUNK_SIZE = 2000; +using std::function; + +UploadManager::UploadManager(WatchConnector *watch, QObject *parent) : + QObject(parent), watch(watch), _lastUploadId(0), _state(StateNotStarted) +{ + watch->setEndpointHandler(WatchConnector::watchPUTBYTES, + [this](const QByteArray &msg) { + if (_pending.empty()) { + logger()->warn() << "putbytes message, but queue is empty!"; + return false; + } + handleMessage(msg); + return true; + }); +} + +uint UploadManager::upload(WatchConnector::UploadType type, int index, QIODevice *device, int size, + function successCallback, function errorCallback) +{ + PendingUpload upload; + upload.id = ++_lastUploadId; + upload.type = type; + upload.index = index; + upload.device = device; + if (size < 0) { + upload.remaining = device->size(); + } else { + upload.remaining = size; + } + upload.successCallback = successCallback; + upload.errorCallback = errorCallback; + + if (upload.remaining <= 0) { + logger()->warn() << "upload is empty"; + if (errorCallback) { + errorCallback(-1); + return -1; + } + } + + _pending.enqueue(upload); + + if (_pending.size() == 1) { + startNextUpload(); + } + + return upload.id; +} + +void UploadManager::cancel(uint id, int code) +{ + if (_pending.empty()) { + logger()->warn() << "cannot cancel, empty queue"; + return; + } + + if (id == _pending.head().id) { + PendingUpload upload = _pending.dequeue(); + logger()->debug() << "aborting current upload" << id << "(code:" << code << ")"; + + if (_state != StateNotStarted && _state != StateWaitForToken && _state != StateComplete) { + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesABORT); + p.write(_token); + + logger()->debug() << "sending abort for upload" << id; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + } + + _state = StateNotStarted; + _token = 0; + + if (upload.errorCallback) { + upload.errorCallback(code); + } + + if (!_pending.empty()) { + startNextUpload(); + } + } else { + for (int i = 1; i < _pending.size(); ++i) { + if (_pending[i].id == id) { + logger()->debug() << "cancelling upload" << id << "(code:" << code << ")"; + if (_pending[i].errorCallback) { + _pending[i].errorCallback(code); + } + _pending.removeAt(i); + return; + } + } + logger()->warn() << "cannot cancel, id" << id << "not found"; + } +} + +void UploadManager::startNextUpload() +{ + Q_ASSERT(!_pending.empty()); + Q_ASSERT(_state == StateNotStarted); + + PendingUpload &upload = _pending.head(); + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesINIT); + p.write(upload.remaining); + p.write(upload.type); + p.write(upload.index); + + _state = StateWaitForToken; + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); +} + +void UploadManager::handleMessage(const QByteArray &msg) +{ + Q_ASSERT(!_pending.empty()); + PendingUpload &upload = _pending.head(); + + logger()->debug() << "get message" << msg.toHex(); + + Unpacker u(msg); + int status = u.read(); + + if (u.bad() || status != 1) { + logger()->warn() << "upload" << upload.id << "got error code=" << status; + cancel(upload.id, status); + return; + } + + quint32 recv_token = u.read(); + + if (u.bad()) { + logger()->warn() << "upload" << upload.id << ": could not read the token"; + cancel(upload.id, -1); + return; + } + + if (_state != StateNotStarted && _state != StateWaitForToken && _state != StateComplete) { + if (recv_token != _token) { + logger()->warn() << "upload" << upload.id << ": invalid token"; + cancel(upload.id, -1); + return; + } + } + + switch (_state) { + case StateNotStarted: + logger()->warn() << "got packet when upload is not started"; + break; + case StateWaitForToken: + logger()->debug() << "token received"; + _token = recv_token; + _state = StateInProgress; + + /* fallthrough */ + case StateInProgress: + logger()->debug() << "moving to the next chunk"; + if (upload.remaining > 0) { + if (!uploadNextChunk(upload)) { + cancel(upload.id, -1); + return; + } + } else { + logger()->debug() << "no additional chunks, commit"; + _state = StateCommit; + if (!commit(upload)) { + cancel(upload.id, -1); + return; + } + } + break; + case StateCommit: + logger()->debug() << "commited succesfully"; + _state = StateComplete; + if (!complete(upload)) { + cancel(upload.id, -1); + return; + } + break; + case StateComplete: + logger()->debug() << "upload" << upload.id << "succesful, invoking callback"; + if (upload.successCallback) { + upload.successCallback(); + } + _pending.dequeue(); + _token = 0; + _state = StateNotStarted; + if (!_pending.empty()) { + startNextUpload(); + } + break; + } +} + +bool UploadManager::uploadNextChunk(PendingUpload &upload) +{ + QByteArray chunk = upload.device->read(qMin(upload.remaining, CHUNK_SIZE)); + + if (upload.remaining < CHUNK_SIZE && chunk.size() < upload.remaining) { + // Short read! + logger()->warn() << "short read during upload" << upload.id; + return false; + } + + Q_ASSERT(!chunk.isEmpty()); + Q_ASSERT(_state = StateInProgress); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesSEND); + p.write(_token); + p.write(chunk.size()); + msg.append(chunk); + + logger()->debug() << "sending a chunk of" << chunk.size() << "bytes"; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + upload.remaining -= chunk.size(); + upload.crc.addData(chunk); + + logger()->debug() << "remaining" << upload.remaining << "bytes"; + + return true; +} + +bool UploadManager::commit(PendingUpload &upload) +{ + Q_ASSERT(_state == StateCommit); + Q_ASSERT(upload.remaining == 0); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesCOMMIT); + p.write(_token); + p.write(upload.crc.result()); + + logger()->debug() << "commiting upload" << upload.id + << "with crc" << qPrintable(QString("0x%1").arg(upload.crc.result(), 0, 16)); + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + return true; +} + +bool UploadManager::complete(PendingUpload &upload) +{ + Q_ASSERT(_state == StateComplete); + + QByteArray msg; + Packer p(&msg); + p.write(WatchConnector::putbytesCOMPLETE); + p.write(_token); + + logger()->debug() << "completing upload" << upload.id; + + watch->sendMessage(WatchConnector::watchPUTBYTES, msg); + + return true; +} diff --git a/daemon/uploadmanager.h b/daemon/uploadmanager.h new file mode 100644 index 0000000..1d42237 --- /dev/null +++ b/daemon/uploadmanager.h @@ -0,0 +1,65 @@ +#ifndef UPLOADMANAGER_H +#define UPLOADMANAGER_H + +#include +#include +#include "watchconnector.h" +#include "stm32crc.h" + +class UploadManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit UploadManager(WatchConnector *watch, QObject *parent = 0); + + typedef std::function Callback; + + uint upload(WatchConnector::UploadType type, int index, QIODevice *device, int size = -1, + std::function successCallback = std::function(), + std::function errorCallback = std::function()); + void cancel(uint id, int code = 0); + +signals: + +public slots: + + +private: + enum State { + StateNotStarted, + StateWaitForToken, + StateInProgress, + StateCommit, + StateComplete + }; + + struct PendingUpload { + uint id; + + WatchConnector::UploadType type; + int index; + QIODevice *device; + int remaining; + Stm32Crc crc; + + std::function successCallback; + std::function errorCallback; + }; + + void startNextUpload(); + void handleMessage(const QByteArray &msg); + bool uploadNextChunk(PendingUpload &upload); + bool commit(PendingUpload &upload); + bool complete(PendingUpload &upload); + +private: + WatchConnector *watch; + QQueue _pending; + uint _lastUploadId; + State _state; + quint32 _token; +}; + +#endif // UPLOADMANAGER_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index a5fe1ea..1aaf39d 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -108,6 +108,7 @@ public: enum AppManager { appmgrGET_APPBANK_STATUS = 1, appmgrREMOVE_APP = 2, + appmgrREFRESH_APP = 3, appmgrGET_APPBANK_UUIDS = 5 }; enum AppMessage { @@ -154,8 +155,19 @@ public: osLINUX = 4, osWINDOWS = 5 }; - enum { - DEFAULT_TIMEOUT_MSECS = 100 + enum UploadType { + uploadFIRMWARE = 1, + uploadRECOVERY = 2, + uploadSYS_RESOURCES = 3, + uploadRESOURCES = 4, + uploadBINARY = 5 + }; + enum PutBytesCommand { + putbytesINIT = 1, + putbytesSEND = 2, + putbytesCOMMIT = 3, + putbytesABORT = 4, + putbytesCOMPLETE = 5 }; typedef QMap Dict; diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index ce71248..2c5202f 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -54,5 +54,9 @@ + + + + -- cgit v1.2.3 From 4280a9bda38046f702a4151d7b831a3bf46ef169 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 00:16:06 +0100 Subject: add d-bus API to list slot contents and fix other API issues --- daemon/bankmanager.cpp | 14 ++++++++++++++ daemon/bankmanager.h | 4 +++- daemon/manager.cpp | 25 +++++++++++++++++++++++-- daemon/manager.h | 8 ++++++-- daemon/uploadmanager.cpp | 5 +++-- daemon/watchconnector.cpp | 16 +++++++++------- daemon/watchconnector.h | 1 - org.pebbled.Watch.xml | 6 ++++-- 8 files changed, 62 insertions(+), 17 deletions(-) diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index fe5dc21..8636e95 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -21,6 +21,16 @@ int BankManager::numSlots() const return _slots.size(); } +bool BankManager::isUsed(int slot) const +{ + return _slots.at(slot).used; +} + +QUuid BankManager::appAt(int slot) const +{ + return _slots.at(slot).uuid; +} + bool BankManager::uploadApp(const QUuid &uuid, int slot) { AppInfo info = apps->info(uuid); @@ -39,6 +49,10 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) logger()->warn() << "invalid slot index"; return false; } + if (_slots[slot].used) { + logger()->warn() << "slot in use"; + return false; + } QDir appDir(info.path()); diff --git a/daemon/bankmanager.h b/daemon/bankmanager.h index 6abedc8..871db6b 100644 --- a/daemon/bankmanager.h +++ b/daemon/bankmanager.h @@ -15,6 +15,9 @@ public: int numSlots() const; + bool isUsed(int slot) const; + QUuid appAt(int slot) const; + signals: void slotsChanged(); @@ -28,7 +31,6 @@ private: int findUnusedSlot() const; void refreshWatchApp(int slot, std::function successCallback, std::function errorCallback); - private slots: void handleWatchConnected(); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 136d7f3..469e92b 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -70,6 +70,7 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); + connect(bank, &BankManager::slotsChanged, proxy, &PebbledProxy::AppSlotsChanged); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -414,6 +415,26 @@ void Manager::onAppClosed(const QUuid &uuid) emit proxy->AppUuidChanged(); } +QStringList PebbledProxy::AppSlots() const +{ + const int num_slots = manager()->bank->numSlots(); + QStringList l; + l.reserve(num_slots); + + for (int i = 0; i < num_slots; ++i) { + if (manager()->bank->isUsed(i)) { + QUuid uuid = manager()->bank->appAt(i); + l.append(uuid.toString()); + } else { + l.append(QString()); + } + } + + Q_ASSERT(l.size() == num_slots); + + return l; +} + bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { Q_ASSERT(calledFromDBus()); @@ -501,7 +522,7 @@ void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString & manager()->js->handleWebviewClosed(data); } -void PebbledProxy::UnloadApp(uint slot) +void PebbledProxy::UnloadApp(int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); @@ -512,7 +533,7 @@ void PebbledProxy::UnloadApp(uint slot) } } -void PebbledProxy::UploadApp(const QString &uuid, uint slot) +void PebbledProxy::UploadApp(const QString &uuid, int slot) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); diff --git a/daemon/manager.h b/daemon/manager.h index f27da98..0588705 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -122,6 +122,7 @@ class PebbledProxy : public QObject, protected QDBusContext Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) + Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) inline Manager* manager() const { return static_cast(parent()); } inline QVariantMap pebble() const { return manager()->dbus->pebble(); } @@ -134,6 +135,8 @@ public: inline bool Connected() const { return manager()->watch->isConnected(); } inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } + QStringList AppSlots() const; + public slots: inline void Disconnect() { manager()->watch->disconnect(); } inline void Reconnect() { manager()->watch->reconnect(); } @@ -147,14 +150,15 @@ public slots: QString StartAppConfiguration(const QString &uuid); void SendAppConfigurationData(const QString &uuid, const QString &data); - void UnloadApp(uint slot); - void UploadApp(const QString &uuid, uint slot); + void UnloadApp(int slot); + void UploadApp(const QString &uuid, int slot); signals: void NameChanged(); void AddressChanged(); void ConnectedChanged(); void AppUuidChanged(); + void AppSlotsChanged(); void AppOpened(const QString &uuid); void AppClosed(const QString &uuid); }; diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp index 89d70f7..ccbf12a 100644 --- a/daemon/uploadmanager.cpp +++ b/daemon/uploadmanager.cpp @@ -122,8 +122,6 @@ void UploadManager::handleMessage(const QByteArray &msg) Q_ASSERT(!_pending.empty()); PendingUpload &upload = _pending.head(); - logger()->debug() << "get message" << msg.toHex(); - Unpacker u(msg); int status = u.read(); @@ -195,6 +193,9 @@ void UploadManager::handleMessage(const QByteArray &msg) startNextUpload(); } break; + default: + logger()->warn() << "received message in wrong state"; + break; } } diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 21f5ad5..e66ec0f 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -5,6 +5,7 @@ #include "unpacker.h" static const int RECONNECT_TIMEOUT = 500; //ms +static const bool PROTOCOL_DEBUG = false; using std::function; @@ -135,7 +136,7 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) } logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched"; - emit messageReceived(endpoint, data); + logger()->debug() << data.toHex(); return false; } @@ -182,6 +183,7 @@ void WatchConnector::onReadSocket() QByteArray data = socket->read(message_length); logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); dispatchMessage(endpoint, data); } @@ -225,7 +227,7 @@ void WatchConnector::onDisconnected() reconnectTimer.setInterval(reconnectTimer.interval() + RECONNECT_TIMEOUT); } reconnectTimer.start(); - logger()->debug() << "Will reconnect in" << reconnectTimer.interval() << "ms"; + logger()->debug() << "will reconnect in" << reconnectTimer.interval() << "ms"; } void WatchConnector::onError(QBluetoothSocket::SocketError error) @@ -233,7 +235,7 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error) if (error == QBluetoothSocket::UnknownSocketError) { logger()->info() << error << socket->errorString(); } else { - logger()->error() << "Error connecting Pebble:" << error << socket->errorString(); + logger()->error() << "error connecting Pebble:" << error << socket->errorString(); } } @@ -241,11 +243,11 @@ void WatchConnector::sendData(const QByteArray &data) { writeData.append(data); if (socket == nullptr) { - logger()->debug() << "No socket - reconnecting"; + logger()->debug() << "no socket - reconnecting"; reconnect(); } else if (is_connected) { - logger()->debug() << "Writing" << data.length() << "bytes to socket"; - logger()->debug() << data.toHex(); + logger()->debug() << "writing" << data.length() << "bytes to socket"; + if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); socket->write(data); } } @@ -253,7 +255,7 @@ void WatchConnector::sendData(const QByteArray &data) void WatchConnector::onBytesWritten(qint64 bytes) { writeData.remove(0, bytes); - logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; + logger()->debug() << "socket written" << bytes << "bytes," << writeData.length() << "left"; } void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 1aaf39d..2e89b8c 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -193,7 +193,6 @@ public: static QString decodeEndpoint(uint val); signals: - void messageReceived(uint endpoint, const QByteArray &data); void nameChanged(); void connectedChanged(); diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 2c5202f..e076d6c 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -51,12 +51,14 @@ + + - + - + -- cgit v1.2.3 From 38b2c79758a2ef619b887e0e226a71ab39b10e80 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 00:28:48 +0100 Subject: use same config path as UI and fix logfiles --- daemon/daemon.cpp | 1 + log4qt-debug.conf | 4 ++-- log4qt-release.conf | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/daemon/daemon.cpp b/daemon/daemon.cpp index c9456c6..b745e5c 100644 --- a/daemon/daemon.cpp +++ b/daemon/daemon.cpp @@ -70,6 +70,7 @@ void initLogging() int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); + app.setApplicationName("pebble"); // Use the same appname as the UI. // Init logging should be called after app object creation as initLogging() will examine // QCoreApplication for determining the .conf files locations diff --git a/log4qt-debug.conf b/log4qt-debug.conf index cabd53a..2d55cdc 100644 --- a/log4qt-debug.conf +++ b/log4qt-debug.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=DEBUG, consolelog, syslog #, filelog +log4j.rootLogger=DEBUG, consolelog, syslog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout @@ -10,4 +10,4 @@ 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.appender.filelog.file=/tmp/pebble.log diff --git a/log4qt-release.conf b/log4qt-release.conf index 996c311..de7c321 100644 --- a/log4qt-release.conf +++ b/log4qt-release.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=WARN, consolelog, syslog #, filelog +log4j.rootLogger=WARN, consolelog, syslog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout @@ -10,4 +10,4 @@ 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.appender.filelog.file=/tmp/pebble.log -- cgit v1.2.3 From 35581f3c0e345ecd256b15618aa5fafe23465bef Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 01:56:40 +0100 Subject: fix a issue where the watch will sometimes send back a "app deleted" msg --- daemon/bankmanager.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index 8636e95..331dd6a 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -274,7 +274,11 @@ void BankManager::refreshWatchApp(int slot, std::function successCallba watch->sendMessage(WatchConnector::watchAPP_MANAGER, msg, [this, successCallback, errorCallback](const QByteArray &data) { Unpacker u(data); - if (u.read() != WatchConnector::appmgrREFRESH_APP) { + int type = u.read(); + // For some reason, the watch might sometimes reply an "app installed" message + // with a "app removed" confirmation message + // Every other implementation seems to ignore this fact, so I guess it's not important. + if (type != WatchConnector::appmgrREFRESH_APP && type != WatchConnector::appmgrREMOVE_APP) { return false; } int code = u.read(); @@ -302,9 +306,10 @@ void BankManager::handleWatchConnected() #if 0 void BankManager::getAppbankUuids(const function &)>& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + watch->sendMessage(WatchConnector::watchAPP_MANAGER, + QByteArray(1, WatchConnector::appmgrGET_APPBANK_UUIDS), [this, callback](const QByteArray &data) { - if (data.at(0) != appmgrGET_APPBANK_UUIDS) { + if (data.at(0) != WatchConnector::appmgrGET_APPBANK_UUIDS) { return false; } logger()->debug() << "getAppbankUuids response" << data.toHex(); -- cgit v1.2.3 From 9c20ff45555cbe74c9afa24fafe5fc0c4ff9e7b8 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 02:09:07 +0100 Subject: implement xhr authentication --- daemon/jskitobjects.cpp | 23 +++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 3386f16..5f5acaf 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,8 @@ JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) _net(new QNetworkAccessManager(this)), _timeout(0), _reply(0) { logger()->debug() << "constructed"; + connect(_net, &QNetworkAccessManager::authenticationRequired, + this, &JSKitXMLHttpRequest::handleAuthenticationRequired); } JSKitXMLHttpRequest::~JSKitXMLHttpRequest() @@ -215,9 +218,13 @@ void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool a _reply = 0; } + _username = username; + _password = password; _request = QNetworkRequest(QUrl(url)); _verb = method; Q_UNUSED(async); + + logger()->debug() << "opened to URL" << _request.url().toString(); } void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) @@ -455,6 +462,22 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) } } +void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth) +{ + if (_reply == reply) { + logger()->debug() << "authentication required"; + + if (!_username.isEmpty() || !_password.isEmpty()) { + logger()->debug() << "using provided authorization:" << _username; + + auth->setUser(_username); + auth->setPassword(_password); + } else { + logger()->debug() << "no username or password provided"; + } + } +} + JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) { diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index b1954c0..23dfabe 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -143,12 +143,15 @@ signals: private slots: void handleReplyFinished(); void handleReplyError(QNetworkReply::NetworkError code); + void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth); private: JSKitManager *_mgr; QNetworkAccessManager *_net; QString _verb; uint _timeout; + QString _username; + QString _password; QNetworkRequest _request; QNetworkReply *_reply; QString _responseType; -- cgit v1.2.3 From fe08705333690db42ac758e55776f94c667d0194 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 15:46:16 +0100 Subject: cleanup BT message decoding --- daemon/watchconnector.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index e66ec0f..22f4a8a 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -149,41 +149,43 @@ void WatchConnector::onReadSocket() QBluetoothSocket *socket = qobject_cast(sender()); Q_ASSERT(socket && socket == this->socket); + // Keep attempting to read messages as long as at least a header is present 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; - } - + // 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, endpoint; - message_length = qFromBigEndian(&header[0]); - endpoint = qFromBigEndian(&header[sizeof(quint16)]); + quint16 message_length = qFromBigEndian(&header[0]); + quint16 endpoint = qFromBigEndian(&header[2]); - if (message_length > 8 * 1024) { + // Sanity checks on the message_length + if (message_length == 0) { + logger()->warn() << "received empty message"; + socket->read(header_length); // skip this header + continue; // check if there are additional headers. + } else if (message_length > 8 * 1024) { // Protocol does not allow messages more than 8K long, seemingly. logger()->warn() << "received message size too long: " << message_length; - socket->readAll(); // drop input buffer + socket->readAll(); // drop entire input buffer return; } // Now wait for the entire message if (socket->bytesAvailable() < header_length + message_length) { logger()->debug() << "incomplete msg body in read buffer"; - return; + return; // try again once more data comes in } - socket->read(header_length); // Skip the header + // We can now safely remove the header from the input buffer, + // as we know the entire message is in the input buffer. + socket->read(header_length); + // Now read the rest of the message QByteArray data = socket->read(message_length); logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint); - if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); + if (PROTOCOL_DEBUG) logger()->trace() << data.toHex(); dispatchMessage(endpoint, data); } @@ -247,7 +249,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "writing" << data.length() << "bytes to socket"; - if (PROTOCOL_DEBUG) logger()->debug() << data.toHex(); + if (PROTOCOL_DEBUG) logger()->trace() << data.toHex(); socket->write(data); } } -- cgit v1.2.3 From 132c349727bf8c0a0ce6dd5725e48e5a060d69bb Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 19:51:53 +0100 Subject: fix a crash within the lambda that captures appOpenUrl --- daemon/jskitmanager.h | 7 +++---- daemon/manager.cpp | 26 ++++++++++++++++++-------- daemon/manager.h | 4 +++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index b42e338..7ffc0ad 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -24,14 +24,13 @@ public: static QString describeError(QJSValue error); + void showConfiguration(); + void handleWebviewClosed(const QString &result); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); void appOpenUrl(const QUrl &url); -public slots: - void showConfiguration(); - void handleWebviewClosed(const QString &result); - private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 469e92b..dcf9c16 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -454,14 +454,17 @@ QString PebbledProxy::StartAppConfiguration(const QString &uuid) { Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); + QDBusConnection conn = connection(); if (manager()->currentAppUuid != uuid) { + logger()->warn() << "Called StartAppConfiguration but the uuid" << uuid << "is not running"; sendErrorReply(msg.interface() + ".Error.AppNotRunning", "The requested app is not currently opened in the watch"); return QString(); } if (!manager()->js->isJSKitAppRunning()) { + logger()->warn() << "Called StartAppConfiguration but the uuid" << uuid << "is not a JS app"; sendErrorReply(msg.interface() + ".Error.JSNotActive", "The requested app is not a PebbleKit JS application"); return QString(); @@ -474,25 +477,32 @@ QString PebbledProxy::StartAppConfiguration(const QString &uuid) setDelayedReply(true); // Set up a signal handler to catch the appOpenUrl signal. - QMetaObject::Connection c = connect(manager()->js, &JSKitManager::appOpenUrl, - [this,msg,c](const QUrl &url) { - // Workaround: due to a GCC bug we can't capture the uuid parameter, but we can extract + QMetaObject::Connection *c = new QMetaObject::Connection; + *c = connect(manager()->js, &JSKitManager::appOpenUrl, + this, [this,conn,msg,c](const QUrl &url) { + // Workaround: due to a GCC crash we can't capture the uuid parameter, but we can extract // it again from the original message arguments. - QString uuid = msg.arguments().at(0).toString(); + // Suspect GCC bug# is 59195, 61233, or 61321. + // TODO Possibly fixed in 4.9.0 + const QString uuid = msg.arguments().at(0).toString(); + if (manager()->currentAppUuid != uuid) { // App was changed while we were waiting for the script.. QDBusMessage reply = msg.createErrorReply(msg.interface() + ".Error.AppNotRunning", "The requested app is not currently opened in the watch"); - connection().send(reply); + conn.send(reply); } else { QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); - connection().send(reply); + conn.send(reply); } - disconnect(c); + disconnect(*c); + delete c; }); + // TODO: JS script may fail, never call OpenURL, or something like that - // In those cases we may leak the above connection + // In those cases we WILL leak the above connection. + // (at least until the next appOpenURL event comes in) // So we need to also set a timeout or similar. manager()->js->showConfiguration(); diff --git a/daemon/manager.h b/daemon/manager.h index 0588705..ad0428b 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -114,10 +114,12 @@ private slots: /** This class is what's actually exported over D-Bus, * so the names of the slots and properties must match with org.pebbled.Watch D-Bus interface. * Case sensitive. Otherwise, _runtime_ failures will occur. */ -// The methods are marked inline so that they may be inlined inside qt_metacall +// Some of the methods are marked inline so that they may be inlined inside qt_metacall class PebbledProxy : public QObject, protected QDBusContext { Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + Q_PROPERTY(QString Name READ Name NOTIFY NameChanged) Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) -- cgit v1.2.3 From 8cf0a301a6fba635a3e1bf66b23548fb23cbcad6 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Dec 2014 00:27:10 +0100 Subject: minor changes (some new protocol constants (2.8?)) --- daemon/dbusconnector.h | 1 - daemon/watchconnector.h | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/daemon/dbusconnector.h b/daemon/dbusconnector.h index d4c1bcb..c24bb9b 100644 --- a/daemon/dbusconnector.h +++ b/daemon/dbusconnector.h @@ -33,7 +33,6 @@ public slots: protected slots: void onServiceRegistered(const QString &); void onServiceUnregistered(const QString &); - }; #endif // DBUSCONNECTOR_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 2e89b8c..4194e9a 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -71,6 +71,8 @@ public: watchAPP_MANAGER = 6000, watchDATA_LOGGING = 6778, watchSCREENSHOT = 8000, + watchFILE_MANAGER = 8181, + watchCORE_DUMP = 9000, watchPUTBYTES = 48879 }; enum { @@ -160,7 +162,9 @@ public: uploadRECOVERY = 2, uploadSYS_RESOURCES = 3, uploadRESOURCES = 4, - uploadBINARY = 5 + uploadBINARY = 5, + uploadFILE = 6, + uploadWORKER = 7 }; enum PutBytesCommand { putbytesINIT = 1, -- cgit v1.2.3 From 8c86d80504bec6524d9c5006d168438500130ca5 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 12 Dec 2014 01:03:56 +0100 Subject: add support for uploading files, requires fw 2.8 --- daemon/bankmanager.cpp | 24 ++++++++++++++++++++++-- daemon/packer.cpp | 6 ++++++ daemon/packer.h | 2 ++ daemon/uploadmanager.cpp | 26 ++++++++++++++++++++++++-- daemon/uploadmanager.h | 14 ++++++++++---- log4qt-debug.conf | 2 +- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index 331dd6a..dd278af 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -4,6 +4,26 @@ #include "packer.h" #include "bankmanager.h" +#if 0 +// TODO -- This is how language files seems to be installed. +if (slot == -4) { + logger()->debug() << "starting lang install"; + QFile *pbl = new QFile(QDir::home().absoluteFilePath("es.pbl")); + if (!pbl->open(QIODevice::ReadOnly)) { + logger()->warn() << "Failed to open pbl"; + return false; + } + + upload->uploadFile("lang", pbl, [this]() { + logger()->debug() << "success"; + }, [this](int code) { + logger()->warn() << "Some error" << code; + }); + + return true; +} +#endif + BankManager::BankManager(WatchConnector *watch, UploadManager *upload, AppManager *apps, QObject *parent) : QObject(parent), watch(watch), upload(upload), apps(apps), _refresh(new QTimer(this)) { @@ -82,14 +102,14 @@ bool BankManager::uploadApp(const QUuid &uuid, int slot) _slots[slot].name.clear(); _slots[slot].uuid = QUuid(); - upload->upload(WatchConnector::uploadBINARY, slot, binaryFile, -1, + upload->uploadAppBinary(slot, binaryFile, [this, binaryFile, resourceFile, slot]() { logger()->debug() << "app binary upload succesful"; delete binaryFile; // Proceed to upload the resource file if (resourceFile) { - upload->upload(WatchConnector::uploadRESOURCES, slot, resourceFile, -1, + upload->uploadAppResources(slot, resourceFile, [this, resourceFile, slot]() { logger()->debug() << "app resources upload succesful"; delete resourceFile; diff --git a/daemon/packer.cpp b/daemon/packer.cpp index 0cc71f6..00d5383 100644 --- a/daemon/packer.cpp +++ b/daemon/packer.cpp @@ -14,6 +14,12 @@ void Packer::writeBytes(int n, const QByteArray &b) } } +void Packer::writeCString(const QString &s) +{ + _buf->append(s.toUtf8()); + _buf->append('\0'); +} + void Packer::writeUuid(const QUuid &uuid) { writeBytes(16, uuid.toRfc4122()); diff --git a/daemon/packer.h b/daemon/packer.h index d22072c..ceb6593 100644 --- a/daemon/packer.h +++ b/daemon/packer.h @@ -25,6 +25,8 @@ public: void writeFixedString(int n, const QString &s); + void writeCString(const QString &s); + void writeUuid(const QUuid &uuid); void writeDict(const QMap &d); diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp index ccbf12a..29c436e 100644 --- a/daemon/uploadmanager.cpp +++ b/daemon/uploadmanager.cpp @@ -20,13 +20,14 @@ UploadManager::UploadManager(WatchConnector *watch, QObject *parent) : }); } -uint UploadManager::upload(WatchConnector::UploadType type, int index, QIODevice *device, int size, - function successCallback, function errorCallback) +uint UploadManager::upload(WatchConnector::UploadType type, int index, const QString &filename, QIODevice *device, int size, + SuccessCallback successCallback, ErrorCallback errorCallback) { PendingUpload upload; upload.id = ++_lastUploadId; upload.type = type; upload.index = index; + upload.filename = filename; upload.device = device; if (size < 0) { upload.remaining = device->size(); @@ -53,6 +54,22 @@ uint UploadManager::upload(WatchConnector::UploadType type, int index, QIODevice return upload.id; } +uint UploadManager::uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +{ + return upload(WatchConnector::uploadBINARY, slot, QString(), device, -1, successCallback, errorCallback); +} + +uint UploadManager::uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +{ + return upload(WatchConnector::uploadRESOURCES, slot, QString(), device, -1, successCallback, errorCallback); +} + +uint UploadManager::uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +{ + Q_ASSERT(!filename.isEmpty()); + return upload(WatchConnector::uploadFILE, 0, filename, device, -1, successCallback, errorCallback); +} + void UploadManager::cancel(uint id, int code) { if (_pending.empty()) { @@ -112,6 +129,11 @@ void UploadManager::startNextUpload() p.write(upload.remaining); p.write(upload.type); p.write(upload.index); + if (!upload.filename.isEmpty()) { + p.writeCString(upload.filename); + } + + logger()->debug() << "starting new upload, size:" << upload.remaining << ", type:" << upload.type << ", slot:" << upload.index; _state = StateWaitForToken; watch->sendMessage(WatchConnector::watchPUTBYTES, msg); diff --git a/daemon/uploadmanager.h b/daemon/uploadmanager.h index 1d42237..b4e951a 100644 --- a/daemon/uploadmanager.h +++ b/daemon/uploadmanager.h @@ -14,11 +14,16 @@ class UploadManager : public QObject public: explicit UploadManager(WatchConnector *watch, QObject *parent = 0); - typedef std::function Callback; + typedef std::function SuccessCallback; + typedef std::function ErrorCallback; + + uint upload(WatchConnector::UploadType type, int index, const QString &filename, QIODevice *device, int size = -1, + SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); + + uint uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); + uint uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); + uint uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); - uint upload(WatchConnector::UploadType type, int index, QIODevice *device, int size = -1, - std::function successCallback = std::function(), - std::function errorCallback = std::function()); void cancel(uint id, int code = 0); signals: @@ -40,6 +45,7 @@ private: WatchConnector::UploadType type; int index; + QString filename; QIODevice *device; int remaining; Stm32Crc crc; diff --git a/log4qt-debug.conf b/log4qt-debug.conf index 2d55cdc..1baed5d 100644 --- a/log4qt-debug.conf +++ b/log4qt-debug.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=DEBUG, consolelog, syslog +log4j.rootLogger=TRACE, consolelog, syslog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout -- cgit v1.2.3 From 617c632f245c44151f0e17917f9e158403c444c6 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 12 Dec 2014 02:33:03 +0100 Subject: move the mpris tracking into musicmanager --- daemon/dbusconnector.cpp | 24 ------- daemon/dbusconnector.h | 11 +--- daemon/manager.cpp | 69 -------------------- daemon/manager.h | 15 ----- daemon/musicmanager.cpp | 159 +++++++++++++++++++++++++++++++++++++++-------- daemon/musicmanager.h | 14 ++++- 6 files changed, 148 insertions(+), 144 deletions(-) diff --git a/daemon/dbusconnector.cpp b/daemon/dbusconnector.cpp index 8bde322..1f3ffc2 100644 --- a/daemon/dbusconnector.cpp +++ b/daemon/dbusconnector.cpp @@ -6,7 +6,6 @@ #include #include #include -#include //dbus-send --system --dest=org.bluez --print-reply / org.bluez.Manager.ListAdapters //dbus-send --system --dest=org.bluez --print-reply $path org.bluez.Adapter.GetProperties @@ -16,17 +15,6 @@ DBusConnector::DBusConnector(QObject *parent) : QObject(parent) { - QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); - - QDBusReply serviceNames = interface->registeredServiceNames(); - if (serviceNames.isValid()) { - dbusServices = serviceNames.value(); - } - else { - logger()->error() << serviceNames.error().message(); - } - connect(interface, SIGNAL(serviceRegistered(const QString &)), SLOT(onServiceRegistered(const QString &))); - connect(interface, SIGNAL(serviceUnregistered(const QString &)), SLOT(onServiceUnregistered(const QString &))); } bool DBusConnector::findPebble() @@ -82,15 +70,3 @@ bool DBusConnector::findPebble() return false; } - -void DBusConnector::onServiceRegistered(const QString &name) -{ - logger()->debug() << "DBus service online:" << name; - if (!dbusServices.contains(name)) dbusServices.append(name); -} - -void DBusConnector::onServiceUnregistered(const QString &name) -{ - logger()->debug() << "DBus service offline:" << name; - if (dbusServices.contains(name)) dbusServices.removeAll(name); -} diff --git a/daemon/dbusconnector.h b/daemon/dbusconnector.h index c24bb9b..7ed3d56 100644 --- a/daemon/dbusconnector.h +++ b/daemon/dbusconnector.h @@ -6,33 +6,26 @@ #include #include +// TODO Remove this. + class DBusConnector : public QObject { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER Q_PROPERTY(QVariantMap pebble READ pebble NOTIFY pebbleChanged) - Q_PROPERTY(QStringList services READ services NOTIFY servicesChanged) - QVariantMap pebbleProps; - QStringList dbusServices; public: explicit DBusConnector(QObject *parent = 0); QVariantMap pebble() const { return pebbleProps; } - QStringList services() const { return dbusServices; } signals: void pebbleChanged(); - void servicesChanged(); public slots: bool findPebble(); - -protected slots: - void onServiceRegistered(const QString &); - void onServiceUnregistered(const QString &); }; #endif // DBUSCONNECTOR_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index dcf9c16..73b80e5 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -76,13 +76,6 @@ Manager::Manager(Settings *settings, QObject *parent) : defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; connect(watch, SIGNAL(connectedChanged()), SLOT(applyProfile())); - // Music Control interface - session.connect("", "/org/mpris/MediaPlayer2", - "org.freedesktop.DBus.Properties", "PropertiesChanged", - this, SLOT(onMprisPropertiesChanged(QString,QMap,QStringList))); - - connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), music, SLOT(onMprisMetadataChanged(QVariantMap))); - // Set BT icon for notification notification.setImage("icon-system-bluetooth-device"); @@ -131,22 +124,6 @@ void Manager::onConnectedChanged() if (!notification.publish()) { logger()->debug() << "Failed publishing notification"; } - - if (watch->isConnected()) { - QString mpris = this->mpris(); - if (not mpris.isEmpty()) { - QDBusReply Metadata = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get") - << "org.mpris.MediaPlayer2.Player" << "Metadata"); - if (Metadata.isValid()) { - setMprisMetadata(Metadata.value().variant().value()); - } - else { - logger()->error() << Metadata.error().message(); - setMprisMetadata(QVariantMap()); - } - } - } } void Manager::onActiveVoiceCallChanged() @@ -279,52 +256,6 @@ void Manager::onEmailNotify(const QString &sender, const QString &data,const QSt watch->sendEmailNotification(sender, data, subject); } -void Manager::onMprisPropertiesChanged(QString interface, QMap changed, QStringList invalidated) -{ - logger()->debug() << interface << changed << invalidated; - - if (changed.contains("Metadata")) { - setMprisMetadata(changed.value("Metadata").value()); - } - - if (changed.contains("PlaybackStatus")) { - QString PlaybackStatus = changed.value("PlaybackStatus").toString(); - if (PlaybackStatus == "Stopped") { - setMprisMetadata(QVariantMap()); - } - } - - lastSeenMpris = message().service(); - logger()->debug() << "lastSeenMpris:" << lastSeenMpris; -} - -QString Manager::mpris() const -{ - const QStringList &services = dbus->services(); - if (not lastSeenMpris.isEmpty() && services.contains(lastSeenMpris)) - return lastSeenMpris; - - foreach (QString service, services) - if (service.startsWith("org.mpris.MediaPlayer2.")) - return service; - - return QString(); -} - -void Manager::setMprisMetadata(QDBusArgument metadata) -{ - if (metadata.currentType() == QDBusArgument::MapType) { - metadata >> mprisMetadata; - emit mprisMetadataChanged(mprisMetadata); - } -} - -void Manager::setMprisMetadata(QVariantMap metadata) -{ - mprisMetadata = metadata; - emit mprisMetadataChanged(mprisMetadata); -} - QString Manager::getCurrentProfile() const { QDBusReply profile = QDBusConnection::sessionBus().call( diff --git a/daemon/manager.h b/daemon/manager.h index ad0428b..c191b0c 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -35,9 +35,6 @@ class Manager : public QObject, protected QDBusContext friend class PebbledProxy; - Q_PROPERTY(QString mpris READ mpris) - Q_PROPERTY(QVariantMap mprisMetadata READ getMprisMetadata WRITE setMprisMetadata NOTIFY mprisMetadataChanged) - QBluetoothLocalDevice btDevice; Settings *settings; @@ -63,9 +60,6 @@ class Manager : public QObject, protected QDBusContext QString defaultProfile; - QString lastSeenMpris; - QVariantMap mprisMetadata; - QUuid currentAppUuid; QScopedPointer transliterator; @@ -76,16 +70,10 @@ public: QString findPersonByNumber(QString number); QString getCurrentProfile() const; - QString mpris() const; - - inline QVariantMap getMprisMetadata() const { return mprisMetadata; } protected: void transliterateMessage(const QString &text); -signals: - void mprisMetadataChanged(const QVariantMap &metadata); - public slots: void applyProfile(); @@ -102,9 +90,6 @@ private slots: void onTwitterNotify(const QString &sender, const QString &data); void onFacebookNotify(const QString &sender, const QString &data); void onEmailNotify(const QString &sender, const QString &data,const QString &subject); - void onMprisPropertiesChanged(QString,QMap,QStringList); - void setMprisMetadata(QDBusArgument metadata); - void setMprisMetadata(QVariantMap metadata); void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); void onAppOpened(const QUuid &uuid); diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp index abea715..e018e4c 100644 --- a/daemon/musicmanager.cpp +++ b/daemon/musicmanager.cpp @@ -4,28 +4,45 @@ MusicManager::MusicManager(WatchConnector *watch, QObject *parent) : QObject(parent), watch(watch) { + QDBusConnection bus = QDBusConnection::sessionBus(); + QDBusConnectionInterface *bus_iface = bus.interface(); + + // Listen for MPRIS signals from every player + bus.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", + this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); + + // Listen for D-Bus name registered signals to see if a MPRIS service comes up + connect(bus_iface, &QDBusConnectionInterface::serviceRegistered, + this, &MusicManager::handleServiceRegistered); + connect(bus_iface, &QDBusConnectionInterface::serviceUnregistered, + this, &MusicManager::handleServiceUnregistered); + connect(bus_iface, &QDBusConnectionInterface::serviceOwnerChanged, + this, &MusicManager::handleServiceOwnerChanged); + + // But also try to find an already active MPRIS service + const QStringList &services = bus_iface->registeredServiceNames(); + foreach (QString service, services) { + if (service.startsWith("org.mpris.MediaPlayer2.")) { + switchToService(service); + break; + } + } + + // Set up watch endpoint handler 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); + connect(watch, &WatchConnector::connectedChanged, + this, &MusicManager::handleWatchConnected); } void MusicManager::musicControl(WatchConnector::MusicControl operation) { - logger()->debug() << "Operation:" << operation; + logger()->debug() << "operation from watch:" << operation; - QString mpris = parent()->property("mpris").toString(); - if (mpris.isEmpty()) { - logger()->debug() << "No mpris interface active"; + if (_curService.isEmpty()) { + logger()->info() << "No mpris interface active"; return; } @@ -50,11 +67,11 @@ void MusicManager::musicControl(WatchConnector::MusicControl operation) 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(); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + call << "org.mpris.MediaPlayer2.Player" << "Volume"; + QDBusReply volumeReply = bus.call(call); + if (volumeReply.isValid()) { + double volume = volumeReply.value().variant().toDouble(); if (operation == WatchConnector::musicVOLUME_UP) { volume += 0.1; } @@ -62,22 +79,25 @@ void MusicManager::musicControl(WatchConnector::MusicControl operation) 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))); + + call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set"); + call << "org.mpris.MediaPlayer2.Player" << "Volume" << QVariant::fromValue(QDBusVariant(volume)); + + QDBusError err = QDBusConnection::sessionBus().call(call); if (err.isValid()) { logger()->error() << err.message(); } } else { - logger()->error() << VolumeReply.error().message(); + logger()->error() << volumeReply.error().message(); } } return; case WatchConnector::musicGET_NOW_PLAYING: - onMprisMetadataChanged(parent()->property("mprisMetadata").toMap()); + setMprisMetadata(_curMetadata); return; case WatchConnector::musicSEND_NOW_PLAYING: + default: logger()->warn() << "Operation" << operation << "not supported"; return; } @@ -89,9 +109,98 @@ void MusicManager::musicControl(WatchConnector::MusicControl operation) logger()->debug() << operation << "->" << method; - QDBusError err = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method)); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method); + QDBusError err = QDBusConnection::sessionBus().call(call); if (err.isValid()) { logger()->error() << err.message(); } } + +void MusicManager::switchToService(const QString &service) +{ + if (_curService != service) { + logger()->debug() << "switching to mpris service" << service; + _curService = service; + } +} + +void MusicManager::setMprisMetadata(const QVariantMap &metadata) +{ + _curMetadata = metadata; + QString track = metadata.value("xesam:title").toString(); + QString album = metadata.value("xesam:album").toString(); + QString artist = metadata.value("xesam:artist").toString(); + logger()->debug() << "new mpris metadata:" << track << album << artist; + + if (watch->isConnected()) { + watch->sendMusicNowPlaying(track, album, artist); + } +} + +void MusicManager::handleServiceRegistered(const QString &service) +{ + if (service.startsWith("org.mpris.MediaPlayer2.")) { + if (_curService.isEmpty()) { + switchToService(service); + } + } +} + +void MusicManager::handleServiceUnregistered(const QString &service) +{ + if (service == _curService) { + // Oops! + setMprisMetadata(QVariantMap()); + switchToService(QString()); + } +} + +void MusicManager::handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) +{ + Q_UNUSED(oldOwner); + if (newOwner.isEmpty()) { + handleServiceUnregistered(name); + } else { + handleServiceRegistered(name); + } +} + +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()); + logger()->debug() << "received new metadata" << metadata; + setMprisMetadata(metadata); + } + + if (changed.contains("PlaybackStatus")) { + QString status = changed.value("PlaybackStatus").toString(); + if (status == "Stopped") { + setMprisMetadata(QVariantMap()); + } + } + + switchToService(message().service()); +} + +void MusicManager::handleWatchConnected() +{ + if (watch->isConnected()) { + if (!_curService.isEmpty()) { + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + call << "org.mpris.MediaPlayer2.Player" << "Metadata"; + QDBusReply metadata = QDBusConnection::sessionBus().call(call); + if (metadata.isValid()) { + setMprisMetadata(qdbus_cast(metadata.value().variant().value())); + // + } else { + logger()->error() << metadata.error().message(); + setMprisMetadata(QVariantMap()); + } + } + } +} diff --git a/daemon/musicmanager.h b/daemon/musicmanager.h index ca86ce3..88c46c3 100644 --- a/daemon/musicmanager.h +++ b/daemon/musicmanager.h @@ -2,9 +2,10 @@ #define MUSICMANAGER_H #include +#include #include "watchconnector.h" -class MusicManager : public QObject +class MusicManager : public QObject, protected QDBusContext { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER @@ -14,12 +15,21 @@ public: private: void musicControl(WatchConnector::MusicControl operation); + void switchToService(const QString &service); + void setMprisMetadata(const QVariantMap &data); private slots: - void onMprisMetadataChanged(QVariantMap metadata); + void handleServiceRegistered(const QString &service); + void handleServiceUnregistered(const QString &service); + void handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + void handleMprisPropertiesChanged(const QString &interface, const QMap &changed, const QStringList &invalidated); + void handleWatchConnected(); private: WatchConnector *watch; + + QVariantMap _curMetadata; + QString _curService; }; #endif // MUSICMANAGER_H -- cgit v1.2.3 From 75352f8cf5a60cfd291a26fe2c93d06281055f31 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 12 Dec 2014 22:46:56 +0100 Subject: minor cleanup --- daemon/musicmanager.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp index e018e4c..05e3727 100644 --- a/daemon/musicmanager.cpp +++ b/daemon/musicmanager.cpp @@ -12,10 +12,6 @@ MusicManager::MusicManager(WatchConnector *watch, QObject *parent) this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); // Listen for D-Bus name registered signals to see if a MPRIS service comes up - connect(bus_iface, &QDBusConnectionInterface::serviceRegistered, - this, &MusicManager::handleServiceRegistered); - connect(bus_iface, &QDBusConnectionInterface::serviceUnregistered, - this, &MusicManager::handleServiceUnregistered); connect(bus_iface, &QDBusConnectionInterface::serviceOwnerChanged, this, &MusicManager::handleServiceOwnerChanged); @@ -28,7 +24,7 @@ MusicManager::MusicManager(WatchConnector *watch, QObject *parent) } } - // Set up watch endpoint handler + // Set up watch endpoint handler for music control watch->setEndpointHandler(WatchConnector::watchMUSIC_CONTROL, [this](const QByteArray& data) { musicControl(WatchConnector::MusicControl(data.at(0))); return true; @@ -130,6 +126,7 @@ void MusicManager::setMprisMetadata(const 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() << "new mpris metadata:" << track << album << artist; if (watch->isConnected()) { @@ -149,7 +146,8 @@ void MusicManager::handleServiceRegistered(const QString &service) void MusicManager::handleServiceUnregistered(const QString &service) { if (service == _curService) { - // Oops! + // Oops! Losing the current MPRIS service + // We must assume it's been closed and thus remove current metadata setMprisMetadata(QVariantMap()); switchToService(QString()); } @@ -196,7 +194,6 @@ void MusicManager::handleWatchConnected() QDBusReply metadata = QDBusConnection::sessionBus().call(call); if (metadata.isValid()) { setMprisMetadata(qdbus_cast(metadata.value().variant().value())); - // } else { logger()->error() << metadata.error().message(); setMprisMetadata(QVariantMap()); -- 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(+) 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 e0fdb8c0f36fc78d3f445c3a218902bc132a33ec Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Dec 2014 00:24:07 +0100 Subject: progress reporting callbacks in uploadmanager --- daemon/uploadmanager.cpp | 30 ++++++++++++++++++++---------- daemon/uploadmanager.h | 15 +++++++++------ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/daemon/uploadmanager.cpp b/daemon/uploadmanager.cpp index 29c436e..5976fe6 100644 --- a/daemon/uploadmanager.cpp +++ b/daemon/uploadmanager.cpp @@ -21,7 +21,7 @@ UploadManager::UploadManager(WatchConnector *watch, QObject *parent) : } uint UploadManager::upload(WatchConnector::UploadType type, int index, const QString &filename, QIODevice *device, int size, - SuccessCallback successCallback, ErrorCallback errorCallback) + SuccessCallback successCallback, ErrorCallback errorCallback, ProgressCallback progressCallback) { PendingUpload upload; upload.id = ++_lastUploadId; @@ -30,12 +30,14 @@ uint UploadManager::upload(WatchConnector::UploadType type, int index, const QSt upload.filename = filename; upload.device = device; if (size < 0) { - upload.remaining = device->size(); + upload.size = device->size(); } else { - upload.remaining = size; + upload.size = size; } + upload.remaining = upload.size; upload.successCallback = successCallback; upload.errorCallback = errorCallback; + upload.progressCallback = progressCallback; if (upload.remaining <= 0) { logger()->warn() << "upload is empty"; @@ -54,20 +56,20 @@ uint UploadManager::upload(WatchConnector::UploadType type, int index, const QSt return upload.id; } -uint UploadManager::uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +uint UploadManager::uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback, ProgressCallback progressCallback) { - return upload(WatchConnector::uploadBINARY, slot, QString(), device, -1, successCallback, errorCallback); + return upload(WatchConnector::uploadBINARY, slot, QString(), device, -1, successCallback, errorCallback, progressCallback); } -uint UploadManager::uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +uint UploadManager::uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback, ProgressCallback progressCallback) { - return upload(WatchConnector::uploadRESOURCES, slot, QString(), device, -1, successCallback, errorCallback); + return upload(WatchConnector::uploadRESOURCES, slot, QString(), device, -1, successCallback, errorCallback, progressCallback); } -uint UploadManager::uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback) +uint UploadManager::uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback, ErrorCallback errorCallback, ProgressCallback progressCallback) { Q_ASSERT(!filename.isEmpty()); - return upload(WatchConnector::uploadFILE, 0, filename, device, -1, successCallback, errorCallback); + return upload(WatchConnector::uploadFILE, 0, filename, device, -1, successCallback, errorCallback, progressCallback); } void UploadManager::cancel(uint id, int code) @@ -181,6 +183,10 @@ void UploadManager::handleMessage(const QByteArray &msg) /* fallthrough */ case StateInProgress: logger()->debug() << "moving to the next chunk"; + if (upload.progressCallback) { + // Report that the previous chunk has been succesfully uploaded + upload.progressCallback(1.0 - (qreal(upload.remaining) / upload.size)); + } if (upload.remaining > 0) { if (!uploadNextChunk(upload)) { cancel(upload.id, -1); @@ -197,6 +203,10 @@ void UploadManager::handleMessage(const QByteArray &msg) break; case StateCommit: logger()->debug() << "commited succesfully"; + if (upload.progressCallback) { + // Report that all chunks have been succesfully uploaded + upload.progressCallback(1.0); + } _state = StateComplete; if (!complete(upload)) { cancel(upload.id, -1); @@ -248,7 +258,7 @@ bool UploadManager::uploadNextChunk(PendingUpload &upload) upload.remaining -= chunk.size(); upload.crc.addData(chunk); - logger()->debug() << "remaining" << upload.remaining << "bytes"; + logger()->debug() << "remaining" << upload.remaining << "/" << upload.size << "bytes"; return true; } diff --git a/daemon/uploadmanager.h b/daemon/uploadmanager.h index b4e951a..45453b6 100644 --- a/daemon/uploadmanager.h +++ b/daemon/uploadmanager.h @@ -16,13 +16,14 @@ public: typedef std::function SuccessCallback; typedef std::function ErrorCallback; + typedef std::function ProgressCallback; uint upload(WatchConnector::UploadType type, int index, const QString &filename, QIODevice *device, int size = -1, - SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); + SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback(), ProgressCallback progressCallback = ProgressCallback()); - uint uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); - uint uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); - uint uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback()); + uint uploadAppBinary(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback(), ProgressCallback progressCallback = ProgressCallback()); + uint uploadAppResources(int slot, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback(), ProgressCallback progressCallback = ProgressCallback()); + uint uploadFile(const QString &filename, QIODevice *device, SuccessCallback successCallback = SuccessCallback(), ErrorCallback errorCallback = ErrorCallback(), ProgressCallback progressCallback = ProgressCallback()); void cancel(uint id, int code = 0); @@ -47,11 +48,13 @@ private: int index; QString filename; QIODevice *device; + int size; int remaining; Stm32Crc crc; - std::function successCallback; - std::function errorCallback; + SuccessCallback successCallback; + ErrorCallback errorCallback; + ProgressCallback progressCallback; }; void startNextUpload(); -- cgit v1.2.3 From d0b0090e951668f9160632c5c30b9f1e0d0aa5a0 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Dec 2014 19:27:07 +0100 Subject: do not listen for every d-bus owner change rather, just update the current mpris service when a signal comes in; seems much more efficient. --- daemon/musicmanager.cpp | 217 ++++++++++++++++++++++++++---------------------- daemon/musicmanager.h | 15 ++-- daemon/watchconnector.h | 3 +- 3 files changed, 126 insertions(+), 109 deletions(-) diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp index 05e3727..d34ae5c 100644 --- a/daemon/musicmanager.cpp +++ b/daemon/musicmanager.cpp @@ -1,69 +1,143 @@ -#include +#include +#include #include "musicmanager.h" MusicManager::MusicManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) + : QObject(parent), watch(watch), _watcher(new QDBusServiceWatcher(this)) { QDBusConnection bus = QDBusConnection::sessionBus(); QDBusConnectionInterface *bus_iface = bus.interface(); - // Listen for MPRIS signals from every player - bus.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", - this, SLOT(handleMprisPropertiesChanged(QString,QMap,QStringList))); - - // Listen for D-Bus name registered signals to see if a MPRIS service comes up - connect(bus_iface, &QDBusConnectionInterface::serviceOwnerChanged, - this, &MusicManager::handleServiceOwnerChanged); + // 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); - // But also try to find an already active MPRIS service + // 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; } } - // Set up watch endpoint handler for music control + // 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))); + + // Now set up the Pebble endpoint handler for music control commands watch->setEndpointHandler(WatchConnector::watchMUSIC_CONTROL, [this](const QByteArray& data) { - musicControl(WatchConnector::MusicControl(data.at(0))); + handleMusicControl(WatchConnector::MusicControl(data.at(0))); return true; }); + + // If the watch disconnects, we will send the current metadata when it comes back. connect(watch, &WatchConnector::connectedChanged, this, &MusicManager::handleWatchConnected); } -void MusicManager::musicControl(WatchConnector::MusicControl operation) +void MusicManager::switchToService(const QString &service) +{ + if (_curService != service) { + logger()->debug() << "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()) { + logger()->debug() << "got mpris metadata from service" << _curService; + _curMetadata = qdbus_cast(reply.value().variant().value()); + } else { + logger()->error() << reply.error().message(); + } + } +} + +void MusicManager::sendCurrentMprisMetadata() +{ + Q_ASSERT(watch->isConnected()); + + QString track = _curMetadata.value("xesam:title").toString().left(30); + QString album = _curMetadata.value("xesam:album").toString().left(30); + QString artist = _curMetadata.value("xesam:artist").toString().left(30); + + logger()->debug() << "sending mpris metadata:" << track << album << artist; + + watch->sendMusicNowPlaying(track, album, artist); +} + +void MusicManager::callMprisMethod(const QString &method) +{ + Q_ASSERT(!method.isEmpty()); + Q_ASSERT(!_curService.isEmpty()); + + logger()->debug() << _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()) { + logger()->error() << "while calling mpris method on" << _curService << ":" << err.message(); + } +} + +void MusicManager::handleMusicControl(WatchConnector::MusicControl operation) { logger()->debug() << "operation from watch:" << operation; if (_curService.isEmpty()) { - logger()->info() << "No mpris interface active"; + logger()->info() << "can't do any music operation, no mpris interface active"; return; } - QString method; - - switch(operation) { + switch (operation) { case WatchConnector::musicPLAY_PAUSE: - method = "PlayPause"; + callMprisMethod("PlayPause"); break; case WatchConnector::musicPAUSE: - method = "Pause"; + callMprisMethod("Pause"); break; case WatchConnector::musicPLAY: - method = "Play"; + callMprisMethod("Play"); break; case WatchConnector::musicNEXT: - method = "Next"; + callMprisMethod("Next"); break; case WatchConnector::musicPREVIOUS: - method = "Previous"; + callMprisMethod("Previous"); break; + case WatchConnector::musicVOLUME_UP: case WatchConnector::musicVOLUME_DOWN: { QDBusConnection bus = QDBusConnection::sessionBus(); - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); + QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", + "org.freedesktop.DBus.Properties", "Get"); call << "org.mpris.MediaPlayer2.Player" << "Volume"; QDBusReply volumeReply = bus.call(call); if (volumeReply.isValid()) { @@ -87,79 +161,28 @@ void MusicManager::musicControl(WatchConnector::MusicControl operation) logger()->error() << volumeReply.error().message(); } } - return; + break; + case WatchConnector::musicGET_NOW_PLAYING: - setMprisMetadata(_curMetadata); - return; + sendCurrentMprisMetadata(); + break; - case WatchConnector::musicSEND_NOW_PLAYING: default: logger()->warn() << "Operation" << operation << "not supported"; - return; - } - - if (method.isEmpty()) { - logger()->error() << "Requested unsupported operation" << operation; - return; - } - - logger()->debug() << operation << "->" << method; - - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method); - QDBusError err = QDBusConnection::sessionBus().call(call); - if (err.isValid()) { - logger()->error() << err.message(); - } -} - -void MusicManager::switchToService(const QString &service) -{ - if (_curService != service) { - logger()->debug() << "switching to mpris service" << service; - _curService = service; - } -} - -void MusicManager::setMprisMetadata(const QVariantMap &metadata) -{ - _curMetadata = metadata; - QString track = metadata.value("xesam:title").toString(); - QString album = metadata.value("xesam:album").toString(); - QString artist = metadata.value("xesam:artist").toString(); - - logger()->debug() << "new mpris metadata:" << track << album << artist; - - if (watch->isConnected()) { - watch->sendMusicNowPlaying(track, album, artist); - } -} - -void MusicManager::handleServiceRegistered(const QString &service) -{ - if (service.startsWith("org.mpris.MediaPlayer2.")) { - if (_curService.isEmpty()) { - switchToService(service); - } - } -} - -void MusicManager::handleServiceUnregistered(const QString &service) -{ - if (service == _curService) { - // Oops! Losing the current MPRIS service - // We must assume it's been closed and thus remove current metadata - setMprisMetadata(QVariantMap()); - switchToService(QString()); + break; } } -void MusicManager::handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) +void MusicManager::handleMprisServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); - if (newOwner.isEmpty()) { - handleServiceUnregistered(name); - } else { - handleServiceRegistered(name); + if (name == _curService && newOwner.isEmpty()) { + // Oops, current service is going away + switchToService(QString()); + _curMetadata.clear(); + if (watch->isConnected()) { + sendCurrentMprisMetadata(); + } } } @@ -172,32 +195,26 @@ void MusicManager::handleMprisPropertiesChanged(const QString &interface, const if (changed.contains("Metadata")) { QVariantMap metadata = qdbus_cast(changed.value("Metadata").value()); logger()->debug() << "received new metadata" << metadata; - setMprisMetadata(metadata); + _curMetadata = metadata; } if (changed.contains("PlaybackStatus")) { QString status = changed.value("PlaybackStatus").toString(); if (status == "Stopped") { - setMprisMetadata(QVariantMap()); + _curMetadata.clear(); } } + if (watch->isConnected()) { + sendCurrentMprisMetadata(); + } + switchToService(message().service()); } void MusicManager::handleWatchConnected() { if (watch->isConnected()) { - if (!_curService.isEmpty()) { - QDBusMessage call = QDBusMessage::createMethodCall(_curService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); - call << "org.mpris.MediaPlayer2.Player" << "Metadata"; - QDBusReply metadata = QDBusConnection::sessionBus().call(call); - if (metadata.isValid()) { - setMprisMetadata(qdbus_cast(metadata.value().variant().value())); - } else { - logger()->error() << metadata.error().message(); - setMprisMetadata(QVariantMap()); - } - } + sendCurrentMprisMetadata(); } } diff --git a/daemon/musicmanager.h b/daemon/musicmanager.h index 88c46c3..89e5fd7 100644 --- a/daemon/musicmanager.h +++ b/daemon/musicmanager.h @@ -3,6 +3,7 @@ #include #include +#include #include "watchconnector.h" class MusicManager : public QObject, protected QDBusContext @@ -14,22 +15,22 @@ public: explicit MusicManager(WatchConnector *watch, QObject *parent = 0); private: - void musicControl(WatchConnector::MusicControl operation); void switchToService(const QString &service); - void setMprisMetadata(const QVariantMap &data); + void fetchMetadataFromService(); + void sendCurrentMprisMetadata(); + void callMprisMethod(const QString &method); private slots: - void handleServiceRegistered(const QString &service); - void handleServiceUnregistered(const QString &service); - void handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + void handleMusicControl(WatchConnector::MusicControl operation); + void handleMprisServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); void handleMprisPropertiesChanged(const QString &interface, const QMap &changed, const QStringList &invalidated); void handleWatchConnected(); private: WatchConnector *watch; - - QVariantMap _curMetadata; + QDBusServiceWatcher *_watcher; QString _curService; + QVariantMap _curMetadata; }; #endif // MUSICMANAGER_H diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 4194e9a..6546302 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -94,8 +94,7 @@ public: musicPREVIOUS = 5, musicVOLUME_UP = 6, musicVOLUME_DOWN = 7, - musicGET_NOW_PLAYING = 8, - musicSEND_NOW_PLAYING = 9 + musicGET_NOW_PLAYING = 8 }; enum SystemMessage { systemFIRMWARE_AVAILABLE = 0, -- cgit v1.2.3 From 78d9610591174ee02db02249c4ce9c25d93fc550 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Dec 2014 19:30:55 +0100 Subject: set loglevel to INFO on release version --- log4qt-release.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4qt-release.conf b/log4qt-release.conf index de7c321..9934a60 100644 --- a/log4qt-release.conf +++ b/log4qt-release.conf @@ -1,4 +1,4 @@ -log4j.rootLogger=WARN, consolelog, syslog +log4j.rootLogger=INFO, consolelog, syslog log4j.appender.consolelog=org.apache.log4j.ColorConsoleAppender log4j.appender.consolelog.layout=org.apache.log4j.SimpleTimeLayout -- cgit v1.2.3 From 93ec9b745032b4e9c02756dd0361de3a364b6742 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Dec 2014 19:36:05 +0100 Subject: minor cleanup --- daemon/bankmanager.cpp | 4 ++-- daemon/stm32crc.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/bankmanager.cpp b/daemon/bankmanager.cpp index dd278af..7bc7f91 100644 --- a/daemon/bankmanager.cpp +++ b/daemon/bankmanager.cpp @@ -168,7 +168,7 @@ bool BankManager::unloadApp(int slot) int installId = _slots[slot].id; QByteArray msg; - msg.reserve(2 * sizeof(quint32)); + msg.reserve(1 + 2 * sizeof(quint32)); Packer p(&msg); p.write(WatchConnector::appmgrREMOVE_APP); p.write(installId); @@ -183,7 +183,7 @@ bool BankManager::unloadApp(int slot) uint result = u.read(); switch (result) { - case 1: /* Success */ + case Success: /* Success */ logger()->debug() << "sucessfully unloaded app"; break; default: diff --git a/daemon/stm32crc.cpp b/daemon/stm32crc.cpp index dd09f38..2bb1def 100644 --- a/daemon/stm32crc.cpp +++ b/daemon/stm32crc.cpp @@ -10,7 +10,7 @@ * XorOut = 0xffffffff * ReflectOut = False * Algorithm = table-driven - * Modified to use STM32-like word size + * The algorithm has been modified to use 32bit word size like STM32 *****************************************************************************/ static const quint32 crc_table[256] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, -- cgit v1.2.3 From df30ca18eebd2dfec03c589b607d45a5891cf2b2 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 00:45:55 +0100 Subject: add UI to install/remove apps from watch --- app/app.pro | 3 +- app/pebbledinterface.cpp | 140 ++++++++++++++++++++++++++++++++++++- app/pebbledinterface.h | 29 +++++++- app/qml/pages/AppConfigPage.qml | 9 ++- app/qml/pages/InstallAppDialog.qml | 66 +++++++++++++++++ app/qml/pages/WatchPage.qml | 125 ++++++++++++++++++++++++++++----- 6 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 app/qml/pages/InstallAppDialog.qml diff --git a/app/app.pro b/app/app.pro index ddf2dba..d375800 100644 --- a/app/app.pro +++ b/app/app.pro @@ -25,4 +25,5 @@ OTHER_FILES += \ qml/images/* \ pebble.desktop \ pebble.png \ - qml/pages/AppConfigPage.qml + qml/pages/AppConfigPage.qml \ + qml/pages/InstallAppDialog.qml diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 0aceaa0..588e24a 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -24,6 +24,13 @@ PebbledInterface::PebbledInterface(QObject *parent) : this, &PebbledInterface::connectedChanged); connect(watch, &OrgPebbledWatchInterface::AppUuidChanged, this, &PebbledInterface::appUuidChanged); + connect(watch, &OrgPebbledWatchInterface::AppSlotsChanged, + this, &PebbledInterface::refreshAppSlots); + connect(watch, &OrgPebbledWatchInterface::AllAppsChanged, + this, &PebbledInterface::refreshAllApps); + + connect(watch, &OrgPebbledWatchInterface::ConnectedChanged, + this, &PebbledInterface::onWatchConnectedChanged); // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' @@ -46,6 +53,11 @@ PebbledInterface::PebbledInterface(QObject *parent) : } else { qWarning() << unit.error().message(); } + + if (watch->isValid()) { + refreshAllApps(); + refreshAppSlots(); + } } void PebbledInterface::getUnitProperties() @@ -161,8 +173,27 @@ void PebbledInterface::reconnect() QUrl PebbledInterface::configureApp(const QString &uuid) { qDebug() << Q_FUNC_INFO << uuid; - QString url = watch->StartAppConfiguration(uuid); - return QUrl(url); + QDBusPendingReply reply = watch->StartAppConfiguration(uuid); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Received error:" << reply.error().message(); + return QUrl(); + } else { + return QUrl(reply.value()); + } +} + +bool PebbledInterface::isAppInstalled(const QString &uuid) +{ + QUuid u(uuid); + + foreach (const QString &s, _appSlots) { + if (QUuid(s) == u) { + return true; + } + } + + return false; } void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &data) @@ -170,3 +201,108 @@ void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &d qDebug() << Q_FUNC_INFO << uuid << data; watch->SendAppConfigurationData(uuid, data); } + +void PebbledInterface::launchApp(const QString &uuid) +{ + qDebug() << Q_FUNC_INFO << uuid; + QDBusPendingReply<> reply = watch->LaunchApp(uuid); + reply.waitForFinished(); + + // 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. + int sleep_count = 0; + while (watch->appUuid() != uuid && sleep_count < 5) { + QThread::sleep(1); + sleep_count++; + } +} + +void PebbledInterface::uploadApp(const QString &uuid, int slot) +{ + qDebug() << Q_FUNC_INFO << uuid << slot; + QDBusPendingReply<> reply = watch->UploadApp(uuid, slot); + reply.waitForFinished(); +} + +void PebbledInterface::unloadApp(int slot) +{ + qDebug() << Q_FUNC_INFO << slot; + QDBusPendingReply<> reply = watch->UnloadApp(slot); + reply.waitForFinished(); +} + +QStringList PebbledInterface::appSlots() const +{ + return _appSlots; +} + +QVariantList PebbledInterface::allApps() const +{ + return _apps; +} + +QVariantMap PebbledInterface::appInfoByUuid(const QString &uuid) const +{ + int index = _appsByUuid.value(QUuid(uuid), -1); + if (index >= 0) { + return _apps[index].toMap(); + } else { + return QVariantMap(); + } +} + +void PebbledInterface::onWatchConnectedChanged() +{ + qDebug() << Q_FUNC_INFO; + if (watch->connected()) { + refreshAllApps(); + refreshAppSlots(); + } +} + +void PebbledInterface::refreshAppSlots() +{ + qDebug() << "refreshing app slots list"; + _appSlots = watch->appSlots(); + emit appSlotsChanged(); +} + +void PebbledInterface::refreshAllApps() +{ + _apps.clear(); + _appsByUuid.clear(); + + qDebug() << "refreshing all apps list"; + + const QVariantList l = watch->allApps(); + foreach (const QVariant &v, l) { + QVariantMap orig = qdbus_cast(v.value()); + QUuid uuid = orig.value("uuid").toUuid(); + if (uuid.isNull()) { + qWarning() << "Invalid app uuid received" << orig; + continue; + } + + QVariantMap m; + m.insert("uuid", uuid.toString()); + m.insert("shortName", orig.value("short-name")); + m.insert("longName", orig.value("long-name")); + + _apps.append(QVariant::fromValue(m)); + } + + std::sort(_apps.begin(), _apps.end(), [](const QVariant &v1, const QVariant &v2) { + const QVariantMap &a = v1.toMap(); + const QVariantMap &b = v2.toMap(); + return a.value("shortName").toString() < b.value("shortName").toString(); + }); + + for (int i = 0; i < _apps.size(); ++i) { + QUuid uuid = _apps[i].toMap().value("uuid").toUuid(); + _appsByUuid.insert(uuid, i); + } + + qDebug() << _appsByUuid.size() << "different app uuids known"; + + emit allAppsChanged(); +} diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index f506e67..e468505 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include class OrgPebbledWatchInterface; @@ -17,6 +19,9 @@ class PebbledInterface : public QObject Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString appUuid READ appUuid NOTIFY appUuidChanged) + Q_PROPERTY(QStringList appSlots READ appSlots NOTIFY appSlotsChanged) + Q_PROPERTY(QVariantList allApps READ allApps NOTIFY allAppsChanged) + public: explicit PebbledInterface(QObject *parent = 0); @@ -27,6 +32,15 @@ public: QString address() const; QString appUuid() const; + QStringList appSlots() const; + QVariantList allApps() const; + + Q_INVOKABLE QVariantMap appInfoByUuid(const QString& uuid) const; + + Q_INVOKABLE QUrl configureApp(const QString &uuid); + + Q_INVOKABLE bool isAppInstalled(const QString &uuid); + signals: void enabledChanged(); void activeChanged(); @@ -34,6 +48,8 @@ signals: void nameChanged(); void addressChanged(); void appUuidChanged(); + void appSlotsChanged(); + void allAppsChanged(); public slots: void setEnabled(bool); @@ -43,18 +59,29 @@ public slots: void disconnect(); void reconnect(); - QUrl configureApp(const QString &uuid); void setAppConfiguration(const QString &uuid, const QString &data); + void launchApp(const QString &uuid); + void uploadApp(const QString &uuid, int slot); + void unloadApp(int slot); + private slots: + void onWatchConnectedChanged(); void getUnitProperties(); void onPropertiesChanged(QString interface, QMap changed, QStringList invalidated); + void refreshAppSlots(); + void refreshAllApps(); private: QDBusInterface *systemd; OrgPebbledWatchInterface *watch; QDBusObjectPath unitPath; QVariantMap unitProperties; + + // Cached properties + QStringList _appSlots; + QVariantList _apps; + QHash _appsByUuid; }; #endif // PEBBLEDINTERFACE_H diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 7b969a3..10fbe05 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -7,14 +7,14 @@ Page { id: appConfigPage property alias url: webview.url - property string uuid + property string name SilicaWebView { id: webview anchors.fill: parent header: PageHeader { - title: "Configuring " + uuid + title: "Configuring " + name } onNavigationRequested: { @@ -31,4 +31,9 @@ Page { } } } + + ViewPlaceholder { + enabled: url == "" + text: qsTr("No configuration settings available") + } } diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml new file mode 100644 index 0000000..3a3c0b1 --- /dev/null +++ b/app/qml/pages/InstallAppDialog.qml @@ -0,0 +1,66 @@ +import QtQuick 2.0 +import QtQml 2.1 +import Sailfish.Silica 1.0 + +Dialog { + id: installAppPage + + property string selectedUuid; + + SilicaListView { + id: appList + anchors.fill: parent + + header: DialogHeader { + title: qsTr("Install app") + defaultAcceptText: qsTr("Install") + } + + VerticalScrollDecorator { flickable: flickable } + + currentIndex: -1 + + delegate: ListItem { + id: appDelegate + contentHeight: Theme.itemSizeSmall + + property string uuid: modelData.uuid + property bool alreadyInstalled: pebbled.isAppInstalled(uuid) + + Image { + id: appImage + anchors { + top: parent.top + left: parent.left + leftMargin: Theme.paddingLarge + } + width: Theme.itemSizeSmall + } + + Label { + id: appName + anchors { + left: appImage.right + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.paddiumLarge + verticalCenter: parent.verticalCenter + } + text: modelData.longName + color: appDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + + onClicked: { + appList.currentIndex = index + if (!alreadyInstalled) { + selectedUuid = uuid + accept(); + } + } + } + + model: pebbled.allApps + } + + canAccept: appList.currentIndex >= 0 && !appList.currentItem.alreadyInstalled +} diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 8169507..ce9d636 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -34,7 +34,7 @@ import QtQml 2.1 import Sailfish.Silica 1.0 Page { - id: page + id: watchPage SilicaFlickable { id: flickable @@ -45,9 +45,8 @@ Page { Column { id: column + width: watchPage.width - width: page.width - spacing: Theme.paddingLarge PageHeader { title: pebbled.name } @@ -77,36 +76,124 @@ Page { } } + Item { + height: Theme.paddingMedium + } Label { - text: qsTr("App configuration") + text: qsTr("Installed applications") font.family: Theme.fontFamilyHeading color: Theme.highlightColor anchors.right: parent.right anchors.rightMargin: Theme.paddingMedium } - Button { - text: "Configure current app" - anchors { - left: parent.left - right: parent.right - margins: Theme.paddingLarge - } - onClicked: { - var uuid = pebbled.appUuid; - console.log("going to configureApp " + uuid); - var url = pebbled.configureApp(uuid); - console.log("obtained configure URL " + url); - if (url) { + Repeater { + id: slotsRepeater + model: pebbled.appSlots + + ListItem { + id: slotDelegate + menu: slotMenu + contentHeight: Theme.itemSizeSmall + + property bool isEmptySlot: modelData === "" + property var appInfo: pebbled.appInfoByUuid(modelData) + property bool isKnownApp: appInfo.hasOwnProperty("uuid") + property bool busy: false + + function configure() { + var uuid = modelData; + pebbled.launchApp(uuid); + console.log("going to call configure on app with uuid " + uuid); + var url = pebbled.configureApp(uuid); + console.log("received url: " + url); pageStack.push(Qt.resolvedUrl("AppConfigPage.qml"), { url: url, - uuid: uuid + name: appInfo.longName }); } + + function remove() { + remorseAction(qsTr("Uninstalling"), function() { + busy = true; + pebbled.unloadApp(index); + }); + } + + function install() { + var dialog = pageStack.push(Qt.resolvedUrl("InstallAppDialog.qml")); + dialog.accepted.connect(function() { + var uuid = dialog.selectedUuid; + + if (pebbled.isAppInstalled(uuid)) { + console.warn("uuid already installed"); + return; + } + + var slot = index; + console.log("installing " + uuid + " into " + slot); + busy = true; + pebbled.uploadApp(uuid, slot); + }); + + } + + Image { + id: slotImage + anchors { + top: parent.top + left: parent.left + leftMargin: Theme.paddingLarge + } + width: Theme.itemSizeSmall + } + + BusyIndicator { + id: slotBusy + anchors.centerIn: slotImage + running: slotDelegate.busy + } + + Label { + id: slotName + anchors { + left: slotImage.right + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.paddiumLarge + verticalCenter: parent.verticalCenter + } + text: isEmptySlot ? qsTr("(empty slot)") : (isKnownApp ? appInfo.longName : qsTr("(slot in use by unknown app)")) + color: slotDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + onTextChanged: slotDelegate.busy = false; + } + + Component { + id: slotMenu + ContextMenu { + MenuItem { + text: qsTr("Configure...") + visible: !isEmptySlot && isKnownApp + onClicked: configure(); + } + MenuItem { + text: qsTr("Uninstall") + visible: !isEmptySlot + onClicked: remove(); + } + } + } + + onClicked: { + if (isEmptySlot) { + install(); + } else { + showMenu(); + } + } } } - } } } -- 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 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 From 6ad0d9aee73808c95a7af5cac932b63ff2ce15dd Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 03:29:51 +0100 Subject: bumping version to 0.12.1b --- rpm/pebble.spec | 2 +- rpm/pebble.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpm/pebble.spec b/rpm/pebble.spec index f2e3611..8779710 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -13,7 +13,7 @@ Name: pebble %{!?qtc_make:%define qtc_make make} %{?qtc_builddir:%define _builddir %qtc_builddir} Summary: Support for Pebble watch in SailfishOS -Version: 0.12 +Version: 0.12.1b Release: 1 Group: Qt/Qt License: GPL3 diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index ca0b068..80a5d20 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -1,6 +1,6 @@ Name: pebble Summary: Support for Pebble watch in SailfishOS -Version: 0.12 +Version: 0.12.1b Release: 1 Group: Qt/Qt URL: http://getpebble.com/ -- cgit v1.2.3 From ab362bf43227552bab209fea792d60d370ce0260 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:03:21 +0100 Subject: implement account and device tokens --- daemon/jskitmanager.cpp | 4 +-- daemon/jskitmanager.h | 5 +++- daemon/jskitobjects.cpp | 39 +++++++++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ daemon/manager.cpp | 2 +- daemon/settings.h | 29 ++++++++++----------- daemon/watchconnector.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------ daemon/watchconnector.h | 2 ++ 8 files changed, 123 insertions(+), 25 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index c73d32e..6d40970 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -4,8 +4,8 @@ #include "jskitmanager.h" #include "jskitobjects.h" -JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : - QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +JSKitManager::JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent) : + QObject(parent), _watch(watch), _apps(apps), _appmsg(appmsg), _settings(settings), _engine(0) { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 7ffc0ad..871ab8e 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -4,6 +4,7 @@ #include #include "appmanager.h" #include "appmsgmanager.h" +#include "settings.h" class JSKitPebble; class JSKitConsole; @@ -16,7 +17,7 @@ class JSKitManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + explicit JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent = 0); ~JSKitManager(); QJSEngine * engine(); @@ -44,8 +45,10 @@ private: private: friend class JSKitPebble; + WatchConnector *_watch; AppManager *_apps; AppMsgManager *_appmsg; + Settings *_settings; AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 5f5acaf..fe924e8 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,9 +4,12 @@ #include #include #include +#include #include #include "jskitobjects.h" +static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; + JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) : QObject(mgr), _appInfo(info), _mgr(mgr) { @@ -88,6 +91,42 @@ void JSKitPebble::openURL(const QUrl &url) emit _mgr->appOpenUrl(url); } +QString JSKitPebble::getAccountToken() const +{ + // We do not have any account system, so we just fake something up. + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + + QString token = _mgr->_settings->property("accountToken").toString(); + if (token.isEmpty()) { + token = QUuid::createUuid().toString(); + logger()->debug() << "created new account token" << token; + _mgr->_settings->setProperty("accountToken", token); + } + hasher.addData(token.toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning account token" << hash; + + return hash; +} + +QString JSKitPebble::getWatchToken() const +{ + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + hasher.addData(_mgr->_watch->serialNumber().toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning watch token" << hash; + + return hash; +} + QJSValue JSKitPebble::createXMLHttpRequest() { JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 23dfabe..532ce1b 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -25,6 +25,9 @@ public: Q_INVOKABLE void openURL(const QUrl &url); + Q_INVOKABLE QString getAccountToken() const; + Q_INVOKABLE QString getWatchToken() const; + Q_INVOKABLE QJSValue createXMLHttpRequest(); void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 212a1d7..32b313f 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -18,7 +18,7 @@ Manager::Manager(Settings *settings, QObject *parent) : music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), appmsg(new AppMsgManager(apps, watch, this)), - js(new JSKitManager(apps, appmsg, this)), + js(new JSKitManager(watch, apps, appmsg, settings, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); diff --git a/daemon/settings.h b/daemon/settings.h index d6db9b6..90e25e2 100644 --- a/daemon/settings.h +++ b/daemon/settings.h @@ -18,6 +18,8 @@ class Settings : public MDConfGroup Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) + Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) + bool silentWhenConnected; bool transliterateMessage; bool incomingCallNotification; @@ -29,6 +31,7 @@ class Settings : public MDConfGroup bool notificationsFacebook; bool notificationsOther; bool notificationsAll; + QString accountToken; public: explicit Settings(QObject *parent = 0) : @@ -36,20 +39,18 @@ public: { resolveMetaObject(); } signals: - void silentWhenConnectedChanged(bool); - void transliterateMessageChanged(bool); - void incomingCallNotificationChanged(bool); - void notificationsCommhistorydChanged(bool); - void notificationsMissedCallChanged(bool); - void notificationsEmailsChanged(bool); - void notificationsMitakuuluuChanged(bool); - void notificationsTwitterChanged(bool); - void notificationsFacebookChanged(bool); - void notificationsOtherChanged(bool); - void notificationsAllChanged(bool); - -public slots: - + void silentWhenConnectedChanged(); + void transliterateMessageChanged(); + void incomingCallNotificationChanged(); + void notificationsCommhistorydChanged(); + void notificationsMissedCallChanged(); + void notificationsEmailsChanged(); + void notificationsMitakuuluuChanged(); + void notificationsTwitterChanged(); + void notificationsFacebookChanged(); + void notificationsOtherChanged(); + void notificationsAllChanged(); + void accountTokenChanged(); }; #endif // SETTINGS_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 22f4a8a..27a5511 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,19 +1,60 @@ #include #include -#include "watchconnector.h" #include "unpacker.h" +#include "watchconnector.h" static const int RECONNECT_TIMEOUT = 500; //ms static const bool PROTOCOL_DEBUG = false; -using std::function; - WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) { reconnectTimer.setSingleShot(true); connect(&reconnectTimer, SIGNAL(timeout()), SLOT(reconnect())); + + setEndpointHandler(watchVERSION, [this](const QByteArray &data) { + Unpacker u(data); + + u.skip(1); + + quint32 version = u.read(); + QString version_string = u.readFixedString(32); + QString commit = u.readFixedString(8); + bool is_recovery = u.read(); + quint8 hw_platform = u.read(); + quint8 metadata_version = u.read(); + + quint32 safe_version = u.read(); + QString safe_version_string = u.readFixedString(32); + QString safe_commit = u.readFixedString(8); + bool safe_is_recovery = u.read(); + quint8 safe_hw_platform = u.read(); + quint8 safe_metadata_version = u.read(); + + quint32 bootLoaderTimestamp = u.read(); + QString hardwareRevision = u.readFixedString(9); + QString serialNumber = u.readFixedString(12); + QByteArray address = u.readBytes(6); + + if (u.bad()) { + logger()->warn() << "short read while reading firmware version"; + } + + logger()->debug() << "got version information" + << version << version_string << commit + << is_recovery << hw_platform << metadata_version; + logger()->debug() << "recovery version information" + << safe_version << safe_version_string << safe_commit + << safe_is_recovery << safe_hw_platform << safe_metadata_version; + logger()->debug() << "hardware information" << bootLoaderTimestamp << hardwareRevision; + logger()->debug() << "serial number" << serialNumber.left(3) << "..."; + logger()->debug() << "bt address" << address.toHex(); + + this->_serialNumber = serialNumber; + + return true; + }); } WatchConnector::~WatchConnector() @@ -46,11 +87,11 @@ void WatchConnector::reconnect() void WatchConnector::disconnect() { - logger()->debug() << __FUNCTION__; + logger()->debug() << "disconnecting"; socket->close(); socket->deleteLater(); reconnectTimer.stop(); - logger()->debug() << "Stopped reconnect timer"; + logger()->debug() << "stopped reconnect timer"; } void WatchConnector::handleWatch(const QString &name, const QString &address) @@ -67,6 +108,11 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) _last_address = address; if (emit_name) emit nameChanged(); + if (emit_name) { + // If we've changed names, don't reuse cached serial number! + _serialNumber.clear(); + } + logger()->debug() << "Creating socket"; #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket); @@ -198,11 +244,15 @@ void WatchConnector::onConnected() is_connected = true; reconnectTimer.stop(); reconnectTimer.setInterval(0); - if (not was_connected) { - if (not writeData.isEmpty()) { + if (!was_connected) { + if (!writeData.isEmpty()) { logger()->info() << "Found" << writeData.length() << "bytes in write buffer - resending"; sendData(writeData); } + if (_serialNumber.isEmpty()) { + // Ask for version information from the watch + sendMessage(watchVERSION, QByteArray(1, 0)); + } emit connectedChanged(); } } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6546302..45fd3c7 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -188,6 +188,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + inline QString serialNumber() const { return _serialNumber; } void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); @@ -245,6 +246,7 @@ private: QTimer reconnectTimer; QString _last_name; QString _last_address; + QString _serialNumber; }; #endif // WATCHCONNECTOR_H -- cgit v1.2.3 From b4e7744db78b5df2b9a693ea13157b73caaef6c2 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:10:41 +0100 Subject: treat JS reals as ints before sending to pebble --- daemon/packer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/daemon/packer.cpp b/daemon/packer.cpp index 00d5383..60cbfc1 100644 --- a/daemon/packer.cpp +++ b/daemon/packer.cpp @@ -72,6 +72,13 @@ void Packer::writeDict(const QMap &d) writeLE(it.value().value()); break; + case QMetaType::Float: // Treat qreals as ints + case QMetaType::Double: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + case QMetaType::QByteArray: { QByteArray ba = it.value().toByteArray(); writeLE(WatchConnector::typeBYTES); -- cgit v1.2.3 From 5a49b8f6f356a0dd7c7d012048b0d3da6774d2bc Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:25:02 +0100 Subject: treat JS bools as ints --- daemon/packer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daemon/packer.cpp b/daemon/packer.cpp index 60cbfc1..df32a4a 100644 --- a/daemon/packer.cpp +++ b/daemon/packer.cpp @@ -72,6 +72,12 @@ void Packer::writeDict(const QMap &d) writeLE(it.value().value()); break; + case QMetaType::Bool: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::Float: // Treat qreals as ints case QMetaType::Double: writeLE(WatchConnector::typeINT); -- cgit v1.2.3 From 24a27dcfdd6ce8f3e5a635404e6650081ebd63ca Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:40:01 +0100 Subject: convert appconfig into a dialog --- app/app.pro | 4 +-- app/qml/pages/AppConfigDialog.qml | 53 +++++++++++++++++++++++++++++++++++++++ app/qml/pages/AppConfigPage.qml | 49 ------------------------------------ app/qml/pages/WatchPage.qml | 3 ++- 4 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 app/qml/pages/AppConfigDialog.qml delete mode 100644 app/qml/pages/AppConfigPage.qml diff --git a/app/app.pro b/app/app.pro index 48fcf68..9cc2d09 100644 --- a/app/app.pro +++ b/app/app.pro @@ -27,5 +27,5 @@ OTHER_FILES += \ qml/images/* \ pebble.desktop \ pebble.png \ - qml/pages/AppConfigPage.qml \ - qml/pages/InstallAppDialog.qml + qml/pages/InstallAppDialog.qml \ + qml/pages/AppConfigDialog.qml diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml new file mode 100644 index 0000000..65a1f5b --- /dev/null +++ b/app/qml/pages/AppConfigDialog.qml @@ -0,0 +1,53 @@ +import QtQuick 2.0 +import QtQml 2.1 +import QtWebKit 3.0 +import Sailfish.Silica 1.0 + +Dialog { + id: appConfigPage + + property alias url: webview.url + property string uuid + property string name + + SilicaWebView { + id: webview + visible: url != "" + anchors.fill: parent + + header: DialogHeader { + title: "Configuring " + name + } + + onNavigationRequested: { + console.log("appconfig navigation requested to " + request.url); + var url = request.url.toString(); + if (/^pebblejs:\/\/close/.exec(url)) { + var data = decodeURIComponent(url.substring(17)); + console.log("appconfig requesting close; data: " + data); + pebbled.setAppConfiguration(uuid, data); + appConfigPage.canAccept = true; + appConfigPage.accept(); + request.action = WebView.IgnoreRequest; + } else { + request.action = WebView.AcceptRequest; + } + } + } + + 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 + } + + canAccept: false +} diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml deleted file mode 100644 index 00eb05c..0000000 --- a/app/qml/pages/AppConfigPage.qml +++ /dev/null @@ -1,49 +0,0 @@ -import QtQuick 2.0 -import QtQml 2.1 -import QtWebKit 3.0 -import Sailfish.Silica 1.0 - -Page { - id: appConfigPage - - property alias url: webview.url - property string name - - SilicaWebView { - id: webview - visible: url != "" - anchors.fill: parent - - header: PageHeader { - title: "Configuring " + name - } - - onNavigationRequested: { - console.log("appconfig navigation requested to " + request.url); - var url = request.url.toString(); - if (/^pebblejs:\/\/close/.exec(url)) { - var data = decodeURIComponent(url.substring(17)); - console.log("appconfig requesting close; data: " + data); - pebbled.setAppConfiguration(uuid, data); - pageStack.pop(); - request.action = WebView.IgnoreRequest; - } else { - request.action = WebView.AcceptRequest; - } - } - } - - 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/WatchPage.qml b/app/qml/pages/WatchPage.qml index 3a712ab..9096df6 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -109,8 +109,9 @@ Page { console.log("going to call configure on app with uuid " + uuid); var url = pebbled.configureApp(uuid); console.log("received url: " + url); - pageStack.push(Qt.resolvedUrl("AppConfigPage.qml"), { + pageStack.push(Qt.resolvedUrl("AppConfigDialog.qml"), { url: url, + uuid: uuid, name: appInfo.longName }); } -- cgit v1.2.3 From 7c48bbe17251fef1d3045ac8b5b6fe8058b4fb10 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 17:19:46 +0100 Subject: add i18n support to UI app also 'es' translation --- app/app.pro | 10 +- app/qml/pages/WatchPage.qml | 4 +- app/translations/pebble-es.ts | 254 ++++++++++++++++++++++++++++++++++++++++++ app/translations/pebble.ts | 253 +++++++++++++++++++++++++++++++++++++++++ rpm/pebble.spec | 1 + rpm/pebble.yaml | 1 + 6 files changed, 518 insertions(+), 5 deletions(-) create mode 100644 app/translations/pebble-es.ts create mode 100644 app/translations/pebble.ts diff --git a/app/app.pro b/app/app.pro index 9cc2d09..ca03ab1 100644 --- a/app/app.pro +++ b/app/app.pro @@ -23,9 +23,13 @@ OTHER_FILES += \ qml/pages/ManagerPage.qml \ qml/pages/WatchPage.qml \ qml/pages/AboutPage.qml \ + qml/pages/InstallAppDialog.qml \ + qml/pages/AppConfigDialog.qml \ qml/pebble.qml \ qml/images/* \ + translations/*.ts \ pebble.desktop \ - pebble.png \ - qml/pages/InstallAppDialog.qml \ - qml/pages/AppConfigDialog.qml + pebble.png + +CONFIG += sailfishapp_i18n +TRANSLATIONS += translations/pebble-es.ts diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 9096df6..2d69306 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -60,7 +60,7 @@ Page { Button { - text: "Ping" + text: qsTr("Ping") width: parent.width / 2 onClicked: { pebbled.ping(66) @@ -68,7 +68,7 @@ Page { } Button { - text: "Sync Time" + text: qsTr("Sync Time") width: parent.width / 2 onClicked: { pebbled.time() diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts new file mode 100644 index 0000000..7332e4c --- /dev/null +++ b/app/translations/pebble-es.ts @@ -0,0 +1,254 @@ + + + + + AboutPage + + + Version + Versión + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + ¿Errores? + + + + AppConfigDialog + + + No configuration settings available + No hay opciones disponibles para configurar + + + + CoverPage + + + connected + conectado + + + + disconnected + desconectado + + + + InstallAppDialog + + + Install app + Instalar app + + + + Install + Instalar + + + + ManagerPage + + + About + Acerca de + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + Buscando el reloj +Si esto tarda mucho, comprueba que el reloj esté emparejado correctamente. + + + + Service + Servicio + + + + Enabled + Habilitado + + + + Automatic startup + Inicio automático + + + + Manual startup + Inicio manual + + + + Active + Activo + + + + Running + Ejecutándose + + + + Dead + Detenido + + + + Connection + Conexión + + + + Connected + Conectado + + + + Disconnected + Desconectado + + + + Settings + Configuración + + + + Forward phone calls + Transferir llamadas + + + + Silent when connected + Modo silencio automático + + + + Sets phone profile to "silent" when Pebble is connected + Activa el modo silencio cuando se conecte un Pebble + + + + Transliterate messages + Transliterar mensajes + + + + Messages are transliterated to ASCII before sending to Pebble + Codifica los mensajes entrates a ASCII antes de enviarlos a Pebble + + + + Notifications + Notificaciones + + + + Messaging + Mensajería + + + + SMS and IM + SMS y chat + + + + Missed call + Llamadas perdidas + + + + Emails + Correos electrónicos + + + + Mitakuuluu + + + + + Twitter + Twitter + + + + Facebook + Facebook + + + + Other notifications + Resto de notificaciones + + + + All notifications + Todas las notificaciones + + + + WatchPage + + + Ping + Ping + + + + Sync Time + Ajustar hora + + + + Installed applications + Aplicaciones instaladas + + + + Uninstalling + Desinstalando + + + + (empty slot) + (hueco libre) + + + + (slot in use by unknown app) + (hueco en uso) + + + + Install app... + Instalar app... + + + + Configure... + Configurar... + + + + Uninstall + Desinstalar + + + diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts new file mode 100644 index 0000000..3cc8b6d --- /dev/null +++ b/app/translations/pebble.ts @@ -0,0 +1,253 @@ + + + + + AboutPage + + + Version + + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + + + + + AppConfigDialog + + + No configuration settings available + + + + + CoverPage + + + connected + + + + + disconnected + + + + + InstallAppDialog + + + Install app + + + + + Install + + + + + ManagerPage + + + About + + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + + + + + Service + + + + + Enabled + + + + + Automatic startup + + + + + Manual startup + + + + + Active + + + + + Running + + + + + Dead + + + + + Connection + + + + + Connected + + + + + Disconnected + + + + + Settings + + + + + Forward phone calls + + + + + Silent when connected + + + + + Sets phone profile to "silent" when Pebble is connected + + + + + Transliterate messages + + + + + Messages are transliterated to ASCII before sending to Pebble + + + + + Notifications + + + + + Messaging + + + + + SMS and IM + + + + + Missed call + + + + + Emails + + + + + Mitakuuluu + + + + + Twitter + + + + + Facebook + + + + + Other notifications + + + + + All notifications + + + + + WatchPage + + + Ping + + + + + Sync Time + + + + + Installed applications + + + + + Uninstalling + + + + + (empty slot) + + + + + (slot in use by unknown app) + + + + + Install app... + + + + + Configure... + + + + + Uninstall + + + + diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 8779710..9fb9575 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -81,6 +81,7 @@ systemctl --user daemon-reload %{_bindir} %{_datadir}/%{name}/qml %{_datadir}/%{name}/js +%{_datadir}/%{name}/translations %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/86x86/apps/%{name}.png %{_libdir}/systemd/user/%{name}d.service diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index 80a5d20..c788c27 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -32,6 +32,7 @@ Files: - '%{_bindir}' - '%{_datadir}/%{name}/qml' - '%{_datadir}/%{name}/js' +- '%{_datadir}/%{name}/translations' - '%{_datadir}/applications/%{name}.desktop' - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' - '%{_libdir}/systemd/user/%{name}d.service' -- cgit v1.2.3 From 6b1ffd680201314171d3e23c7abbec83f32f1dad Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 17:48:24 +0100 Subject: add progress bar and scroll indicator to appconfig dialog --- app/qml/pages/AppConfigDialog.qml | 16 ++++++++++++++++ app/translations/pebble-es.ts | 2 +- app/translations/pebble.ts | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml index 65a1f5b..92f188f 100644 --- a/app/qml/pages/AppConfigDialog.qml +++ b/app/qml/pages/AppConfigDialog.qml @@ -33,6 +33,22 @@ Dialog { request.action = WebView.AcceptRequest; } } + + VerticalScrollDecorator { flickable: webview } + } + + ProgressBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + visible: webview.visible && webview.loading + minimumValue: 0 + maximumValue: 100 + indeterminate: webview.loadProgress === 0 + value: webview.loadProgress } Text { diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts index 7332e4c..3a8da7d 100644 --- a/app/translations/pebble-es.ts +++ b/app/translations/pebble-es.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available No hay opciones disponibles para configurar diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts index 3cc8b6d..0c61f38 100644 --- a/app/translations/pebble.ts +++ b/app/translations/pebble.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available -- cgit v1.2.3 From 16544135d466a5ef64c2c86c8ca22d006d801987 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 20 Dec 2014 16:10:56 +0100 Subject: add navigator.language property (but it returns invalid value currently) --- daemon/jskitmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 6d40970..5c72acc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -146,6 +146,7 @@ void JSKitManager::startJsApp() QJSValue navigatorObj = _engine->newObject(); navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); + navigatorObj.setProperty("language", _engine->toScriptValue(QLocale().name())); globalObj.setProperty("navigator", navigatorObj); // Set this.window = this -- cgit v1.2.3 From 8f0905197f7cead299c00006ada482095fe9d1a4 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 18:25:06 +0100 Subject: allow buffer sizes that are not multiples of 4 in crc calculation --- daemon/stm32crc.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++----------- daemon/stm32crc.h | 4 ++-- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/daemon/stm32crc.cpp b/daemon/stm32crc.cpp index 2bb1def..d4e5bd9 100644 --- a/daemon/stm32crc.cpp +++ b/daemon/stm32crc.cpp @@ -1,5 +1,9 @@ +#include #include "stm32crc.h" +namespace +{ + /** Precomputed CRC polynomial * Generated by pycrc v0.8.2, http://www.tty1.net/pycrc/ * using the configuration: @@ -12,7 +16,7 @@ * Algorithm = table-driven * The algorithm has been modified to use 32bit word size like STM32 *****************************************************************************/ -static const quint32 crc_table[256] = { +const quint32 crc_table[256] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, @@ -79,6 +83,20 @@ static const quint32 crc_table[256] = { 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 }; +const int word_len = sizeof(quint32); + +quint32 calc_crc(quint32 crc, quint32 word) +{ + crc ^= word; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + return crc; +} + +} + Stm32Crc::Stm32Crc() { reset(); @@ -87,25 +105,40 @@ Stm32Crc::Stm32Crc() void Stm32Crc::reset() { crc = 0xFFFFFFFFU; + rem = 0; + memset(buffer, 0, word_len); } void Stm32Crc::addData(const char *data, int length) { - const int word_len = sizeof(quint32); - int i = 0; + int off = 0; - Q_ASSERT(length % word_len == 0); - Q_ASSERT(quintptr(data) % word_len == 0); + if (rem > 0) { + for (; rem < word_len && off < length; ++rem, ++off) { + buffer[rem] = data[off]; + } - for (; i < (length/word_len)*word_len; i+=word_len) { - const quint32 *word = reinterpret_cast(&data[i]); + Q_ASSERT(rem <= word_len); - crc ^= *word; - crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; - crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; - crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; - crc = (crc << 8) ^ crc_table[(crc >> 24) & 0xFF]; + if (rem == word_len) { + crc = calc_crc(crc, qFromLittleEndian(buffer)); + memset(buffer, 0, word_len); + rem = 0; + } } + + Q_ASSERT(rem == 0 || off == length); + + for (; off < (length/word_len)*word_len; off+=word_len) { + quint32 word = qFromLittleEndian(reinterpret_cast(&data[off])); + crc = calc_crc(crc, word); + } + + for (; off < length; ++off, ++rem) { + buffer[rem] = data[off]; + } + + Q_ASSERT(rem <= word_len); } void Stm32Crc::addData(const QByteArray &data) @@ -115,5 +148,9 @@ void Stm32Crc::addData(const QByteArray &data) quint32 Stm32Crc::result() const { - return crc; + if (rem > 0) { + return calc_crc(crc, qFromLittleEndian(buffer)); + } else { + return crc; + } } diff --git a/daemon/stm32crc.h b/daemon/stm32crc.h index 1361de3..b21f5ed 100644 --- a/daemon/stm32crc.h +++ b/daemon/stm32crc.h @@ -10,8 +10,6 @@ public: void reset(); - // Data size must be multiple of 4, data must be aligned to 4. - void addData(const char *data, int length); void addData(const QByteArray &data); @@ -19,6 +17,8 @@ public: private: quint32 crc; + quint8 buffer[4]; + quint8 rem; }; #endif // STM32CRC_H -- cgit v1.2.3 From e1d94fc21ecbce16815810c4f274f349b49e82a3 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 21:03:18 +0100 Subject: add itemselector to the appconfig webview --- app/app.pro | 1 + app/qml/pages/AppConfigDialog.qml | 18 ++++++++++++++- app/qml/pages/WebItemSelDialog.qml | 45 ++++++++++++++++++++++++++++++++++++++ app/translations/pebble-es.ts | 2 +- app/translations/pebble.ts | 2 +- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 app/qml/pages/WebItemSelDialog.qml diff --git a/app/app.pro b/app/app.pro index ca03ab1..97c6232 100644 --- a/app/app.pro +++ b/app/app.pro @@ -25,6 +25,7 @@ OTHER_FILES += \ qml/pages/AboutPage.qml \ qml/pages/InstallAppDialog.qml \ qml/pages/AppConfigDialog.qml \ + qml/pages/WebItemSelDialog.qml \ qml/pebble.qml \ qml/images/* \ translations/*.ts \ diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml index 92f188f..304bced 100644 --- a/app/qml/pages/AppConfigDialog.qml +++ b/app/qml/pages/AppConfigDialog.qml @@ -19,6 +19,8 @@ Dialog { title: "Configuring " + name } + VerticalScrollDecorator { flickable: webview } + onNavigationRequested: { console.log("appconfig navigation requested to " + request.url); var url = request.url.toString(); @@ -34,7 +36,21 @@ Dialog { } } - VerticalScrollDecorator { flickable: webview } + experimental.itemSelector: Component { + Item { + Component.onCompleted: { + var dialog = pageStack.push(Qt.resolvedUrl("WebItemSelDialog.qml"), { + model: model.items + }); + dialog.onRejected.connect(function() { + model.reject(); + }); + dialog.onAccepted.connect(function() { + model.accept(dialog.selectedIndex); + }); + } + } + } } ProgressBar { diff --git a/app/qml/pages/WebItemSelDialog.qml b/app/qml/pages/WebItemSelDialog.qml new file mode 100644 index 0000000..f8c49f2 --- /dev/null +++ b/app/qml/pages/WebItemSelDialog.qml @@ -0,0 +1,45 @@ +import QtQuick 2.0 +import QtQml 2.1 +import Sailfish.Silica 1.0 + +Dialog { + id: itemSelDialog + property alias model: listView.model + property int selectedIndex: -1 + + SilicaListView { + id: listView + anchors.fill: parent + + VerticalScrollDecorator { flickable: webview } + + header: PageHeader { + } + + delegate: ListItem { + id: itemDelegate + contentHeight: Theme.itemSizeSmall + + Label { + anchors { + left: parent.left + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.paddingMedium + verticalCenter: parent.verticalCenter + } + text: model.text + color: model.enabled ? + (itemDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor) + : Theme.secondaryColor + truncationMode: TruncationMode.Fade + } + + enabled: model.enabled + onClicked: { + selectedIndex = model.index; + accept(); + } + } + } +} diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts index 3a8da7d..18b1ec2 100644 --- a/app/translations/pebble-es.ts +++ b/app/translations/pebble-es.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available No hay opciones disponibles para configurar diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts index 0c61f38..222645c 100644 --- a/app/translations/pebble.ts +++ b/app/translations/pebble.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available -- cgit v1.2.3 From ed43269c2adebed7baf4e2452f998d7e60c797e6 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 21:04:35 +0100 Subject: fix typos --- app/qml/pages/InstallAppDialog.qml | 2 +- app/qml/pages/WatchPage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml index 79283a6..fa96c28 100644 --- a/app/qml/pages/InstallAppDialog.qml +++ b/app/qml/pages/InstallAppDialog.qml @@ -52,7 +52,7 @@ Dialog { left: appIcon.right leftMargin: Theme.paddingMedium right: parent.right - rightMargin: Theme.paddiumLarge + rightMargin: Theme.paddingLarge verticalCenter: parent.verticalCenter } text: modelData.longName diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 2d69306..43c2b99 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -186,7 +186,7 @@ Page { left: slotIcon.right leftMargin: Theme.paddingMedium right: parent.right - rightMargin: Theme.paddiumLarge + rightMargin: Theme.paddingLarge verticalCenter: parent.verticalCenter } text: isEmptySlot ? qsTr("(empty slot)") : (isKnownApp ? appInfo.longName : qsTr("(slot in use by unknown app)")) -- cgit v1.2.3 From e6ec758b364fcaf9fda35e56740c3fcd7e8fe25e Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 22:36:14 +0100 Subject: use overridePageStackNavigation to ensure accept() works --- app/qml/pages/AppConfigDialog.qml | 2 ++ app/translations/pebble-es.ts | 2 +- app/translations/pebble.ts | 2 +- rpm/pebble.spec | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml index 304bced..1562985 100644 --- a/app/qml/pages/AppConfigDialog.qml +++ b/app/qml/pages/AppConfigDialog.qml @@ -21,6 +21,8 @@ Dialog { VerticalScrollDecorator { flickable: webview } + overridePageStackNavigation: true + onNavigationRequested: { console.log("appconfig navigation requested to " + request.url); var url = request.url.toString(); diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts index 18b1ec2..0316ecb 100644 --- a/app/translations/pebble-es.ts +++ b/app/translations/pebble-es.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available No hay opciones disponibles para configurar diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts index 222645c..7a78d9d 100644 --- a/app/translations/pebble.ts +++ b/app/translations/pebble.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 9fb9575..02cbe6c 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -13,7 +13,7 @@ Name: pebble %{!?qtc_make:%define qtc_make make} %{?qtc_builddir:%define _builddir %qtc_builddir} Summary: Support for Pebble watch in SailfishOS -Version: 0.12.1b +Version: 0.12.1c Release: 1 Group: Qt/Qt License: GPL3 -- cgit v1.2.3