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/daemon.pro | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'daemon/daemon.pro') 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 \ -- 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(-) (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 34af3af..d06681e 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -29,6 +29,21 @@ QStringList AppManager::appPaths() const QStandardPaths::LocateDirectory); } +const AppManager::AppInfo & AppManager::info(const QUuid &uuid) const +{ + return _apps.value(uuid); +} + +const AppManager::AppInfo & AppManager::info(const QString &name) const +{ + QUuid uuid = _names.value(name); + if (!uuid.isNull()) { + return info(uuid); + } else { + return AppInfo(); + } +} + void AppManager::rescan() { QStringList watchedDirs = _watcher->directories(); diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 5e150ab..dc2a979 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -30,7 +30,8 @@ public: QStringList appPaths() const; - bool installPebbleApp(const QString &pbwFile); + const AppInfo & info(const QUuid &uuid) const; + const AppInfo & info(const QString &shortName) const; public slots: void rescan(); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp new file mode 100644 index 0000000..9eb948e --- /dev/null +++ b/daemon/appmsgmanager.cpp @@ -0,0 +1,77 @@ +#include "appmsgmanager.h" +#include "unpacker.h" + +// TODO D-Bus server for non JS kit apps!!!! + +AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) + : QObject(parent), watch(watch) +{ + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, + [this](const QByteArray &data) { + if (data.at(0) == WatchConnector::appmsgPUSH) { + logger()->debug() << "LAUNCHER is PUSHing" << data.toHex(); + Unpacker u(data); + u.skip(1); // skip data.at(0) which we just already checked above. + uint transaction = u.read(); + QUuid uuid = u.readUuid(); + WatchConnector::Dict dict = u.readDict(); + if (u.bad() || !dict.contains(1)) { + logger()->warn() << "Failed to parse LAUNCHER message"; + return true; + } + + switch (dict.value(1).toInt()) { + case WatchConnector::launcherSTARTED: + logger()->debug() << "App starting in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStarted(uuid); + break; + case WatchConnector::launcherSTOPPED: + logger()->debug() << "App stopping in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStopped(uuid); + break; + default: + logger()->warn() << "LAUNCHER pushed unknown message:" << uuid << dict; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildNackMessage(transaction)); + break; + } + } + + return true; + }); + + watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, + [this](const QByteArray &data) { + switch (data.at(0)) { + case WatchConnector::appmsgPUSH: + break; + } + + return true; + }); +} + +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) +{ + // TODO +} + +QByteArray AppMsgManager::buildAckMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgACK; + ba[1] = transaction; + return ba; +} + +QByteArray AppMsgManager::buildNackMessage(uint transaction) +{ + QByteArray ba(2, Qt::Uninitialized); + ba[0] = WatchConnector::appmsgNACK; + ba[1] = transaction; + return ba; +} diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h new file mode 100644 index 0000000..651d84e --- /dev/null +++ b/daemon/appmsgmanager.h @@ -0,0 +1,30 @@ +#ifndef APPMSGMANAGER_H +#define APPMSGMANAGER_H + +#include "watchconnector.h" + +class AppMsgManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit AppMsgManager(WatchConnector *watch, QObject *parent); + +public slots: + void send(const QUuid &uuid, const QVariantMap &data); + +signals: + void appStarted(const QUuid &uuid); + void appStopped(const QUuid &uuid); + void dataReceived(const QUuid &uuid, const QVariantMap &data); + +private: + static QByteArray buildAckMessage(uint transaction); + static QByteArray buildNackMessage(uint transaction); + +private: + WatchConnector *watch; +}; + +#endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index d4d7dbf..21c15c8 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts +QT += bluetooth dbus contacts qml PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 @@ -23,7 +23,10 @@ SOURCES += \ dbusadaptor.cpp \ appmanager.cpp \ musicmanager.cpp \ - datalogmanager.cpp + datalogmanager.cpp \ + unpacker.cpp \ + appmsgmanager.cpp \ + jskitmanager.cpp HEADERS += \ manager.h \ @@ -37,7 +40,10 @@ HEADERS += \ appmanager.h \ musicmanager.h \ unpacker.h \ - datalogmanager.h + datalogmanager.h \ + appmsgmanager.h \ + jskitmanager.h \ + jskitmanager_p.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp new file mode 100644 index 0000000..698b22b --- /dev/null +++ b/daemon/jskitmanager.cpp @@ -0,0 +1,77 @@ +#include +#include +#include "jskitmanager.h" +#include "jskitmanager_p.h" + +JSKitPebble::JSKitPebble(JSKitManager *mgr) + : QObject(mgr) +{ +} + +JSKitPebble::~JSKitPebble() +{ +} + +JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : + QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +{ + connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); + connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); +} + +JSKitManager::~JSKitManager() +{ + if (_engine) { + stopJsApp(); + } +} + +void JSKitManager::handleAppStarted(const QUuid &uuid) +{ + const auto &info = _apps->info(uuid); + if (!info.uuid.isNull() && info.isJSKit) { + logger()->debug() << "Preparing to start JSKit app" << info.uuid << info.shortName; + _curApp = info; + startJsApp(); + } +} + +void JSKitManager::handleAppStopped(const QUuid &uuid) +{ + if (!_curApp.uuid.isNull()) { + if (_curApp.uuid != uuid) { + logger()->warn() << "Closed app with invalid UUID"; + } + + _curApp = AppManager::AppInfo(); + } +} + +void JSKitManager::startJsApp() +{ + if (_engine) stopJsApp(); + _engine = new QJSEngine(this); + _jspebble = new JSKitPebble(this); + + QJSValue globalObj = _engine->globalObject(); + + globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + + QJSValueIterator it(globalObj); + while (it.hasNext()) { + it.next(); + logger()->debug() << "JS property:" << it.name(); + } +} + +void JSKitManager::stopJsApp() +{ + if (!_engine) return; // Nothing to do! + + _engine->collectGarbage(); + + delete _engine; + _engine = 0; + delete _jspebble; + _jspebble = 0; +} diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h new file mode 100644 index 0000000..5e2880f --- /dev/null +++ b/daemon/jskitmanager.h @@ -0,0 +1,41 @@ +#ifndef JSKITMANAGER_H +#define JSKITMANAGER_H + +#include +#include "appmanager.h" +#include "appmsgmanager.h" + +class JSKitPebble; + +class JSKitManager : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + ~JSKitManager(); + +signals: + +public slots: + +private slots: + void handleAppStarted(const QUuid &uuid); + void handleAppStopped(const QUuid &uuid); + +private: + void startJsApp(); + void stopJsApp(); + +private: + friend class JSKitPebble; + + AppManager *_apps; + AppMsgManager *_appmsg; + AppManager::AppInfo _curApp; + QJSEngine *_engine; + QPointer _jspebble; +}; + +#endif // JSKITMANAGER_H diff --git a/daemon/jskitmanager_p.h b/daemon/jskitmanager_p.h new file mode 100644 index 0000000..690a0ec --- /dev/null +++ b/daemon/jskitmanager_p.h @@ -0,0 +1,15 @@ +#ifndef JSKITMANAGER_P_H +#define JSKITMANAGER_P_H + +#include "jskitmanager.h" + +class JSKitPebble : public QObject +{ + Q_OBJECT + +public: + explicit JSKitPebble(JSKitManager *mgr); + ~JSKitPebble(); +}; + +#endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 7a02c86..778fdc6 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -1,19 +1,21 @@ -#include "manager.h" -#include "dbusadaptor.h" - #include #include #include +#include "manager.h" +#include "dbusadaptor.h" + Manager::Manager(Settings *settings, QObject *parent) : QObject(parent), settings(settings), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), + apps(new AppManager(this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - apps(new AppManager(this)), + appmsg(new AppMsgManager(watch, this)), + js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); @@ -42,11 +44,6 @@ Manager::Manager(Settings *settings, QObject *parent) : } return true; }); - watch->setEndpointHandler(WatchConnector::watchLAUNCHER, - [this](const QByteArray &data) { - logger()->debug() << "LAUNCHER msg:" << data.toHex(); - return true; - }); connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); diff --git a/daemon/manager.h b/daemon/manager.h index 8b2fd96..f1dd53e 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -7,6 +7,8 @@ #include "notificationmanager.h" #include "musicmanager.h" #include "datalogmanager.h" +#include "appmsgmanager.h" +#include "jskitmanager.h" #include "appmanager.h" #include "settings.h" @@ -40,11 +42,13 @@ class Manager : WatchConnector *watch; DBusConnector *dbus; + AppManager *apps; VoiceCallManager *voice; NotificationManager *notifications; MusicManager *music; DataLogManager *datalog; - AppManager *apps; + AppMsgManager *appmsg; + JSKitManager *js; MNotification notification; @@ -73,7 +77,7 @@ protected: void transliterateMessage(const QString &text); signals: - void mprisMetadataChanged(QVariantMap); + void mprisMetadataChanged(const QVariantMap &metadata); public slots: void applyProfile(); diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp new file mode 100644 index 0000000..fc38020 --- /dev/null +++ b/daemon/unpacker.cpp @@ -0,0 +1,88 @@ +#include "unpacker.h" +#include "watchconnector.h" + +QByteArray Unpacker::readBytes(int n) +{ + if (checkBad(n)) return QByteArray(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QByteArray(u, n); +} + +QString Unpacker::readFixedString(int n) +{ + if (checkBad(n)) return QString(); + const char *u = &_buf.constData()[_offset]; + _offset += n; + return QString::fromUtf8(u, strnlen(u, n)); +} + +QUuid Unpacker::readUuid() +{ + if (checkBad(16)) return QString(); + _offset += 16; + return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); +} + +QMap Unpacker::readDict() +{ + QMap d; + if (checkBad(1)) return d; + + const int n = read(); + + for (int i = 0; i < n; i++) { + if (checkBad(4 + 1 + 2)) return d; + const int key = readLE(); // For some reason, this is little endian. + const int type = readLE(); + const int width = readLE(); + + switch (type) { + case WatchConnector::typeBYTES: + d.insert(key, QVariant::fromValue(readBytes(width))); + break; + case WatchConnector::typeSTRING: + d.insert(key, QVariant::fromValue(readFixedString(width))); + break; + case WatchConnector::typeUINT: + switch (width) { + case sizeof(quint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(quint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + case WatchConnector::typeINT: + switch (width) { + case sizeof(qint8): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint16): + d.insert(key, QVariant::fromValue(readLE())); + break; + case sizeof(qint32): + d.insert(key, QVariant::fromValue(readLE())); + break; + default: + _bad = true; + return d; + } + + break; + default: + _bad = true; + return d; + } + } + + return d; +} diff --git a/daemon/unpacker.h b/daemon/unpacker.h index 94908cb..000c3e8 100644 --- a/daemon/unpacker.h +++ b/daemon/unpacker.h @@ -5,19 +5,30 @@ #include #include #include +#include +#include class Unpacker { + LOG4QT_DECLARE_STATIC_LOGGER(logger, Unpacker) + public: Unpacker(const QByteArray &data); template T read(); + template + T readLE(); + + QByteArray readBytes(int n); + QString readFixedString(int n); QUuid readUuid(); + QMap readDict(); + void skip(int n); bool bad() const; @@ -45,19 +56,13 @@ inline T Unpacker::read() return qFromBigEndian(u); } -inline QString Unpacker::readFixedString(int n) -{ - if (checkBad(n)) return QString(); - const char *u = &_buf.constData()[_offset]; - _offset += n; - return QString::fromUtf8(u, strnlen(u, n)); -} - -inline QUuid Unpacker::readUuid() +template +inline T Unpacker::readLE() { - if (checkBad(16)) return QString(); - _offset += 16; - return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16)); + if (checkBad(sizeof(T))) return 0; + const uchar *u = p(); + _offset += sizeof(T); + return qFromLittleEndian(u); } inline void Unpacker::skip(int n) diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index c5ec332..8a7d574 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -121,6 +121,10 @@ public: datalogCLOSE = 3, datalogTIMEOUT = 7 }; + enum { + launcherSTARTED = 1, + launcherSTOPPED = 0 + }; enum { leadEMAIL = 0, leadSMS = 1, @@ -153,6 +157,14 @@ public: DEFAULT_TIMEOUT_MSECS = 100 }; + typedef QMap Dict; + enum DictItemType { + typeBYTES, + typeSTRING, + typeUINT, + typeINT + }; + typedef std::function EndpointHandlerFunc; explicit WatchConnector(QObject *parent = 0); -- cgit v1.2.3 From dadca6f0b1e4660876cccb51702998d378a5dc03 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 21:32:13 +0100 Subject: convert appinfo into a Q_GADGET with properties --- daemon/appinfo.cpp | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/appinfo.h | 66 ++++++++++++++++++++++ daemon/appmanager.cpp | 34 ++++++------ daemon/appmanager.h | 18 +----- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 31 ++++++++--- daemon/jskitmanager.h | 2 +- 7 files changed, 258 insertions(+), 41 deletions(-) create mode 100644 daemon/appinfo.cpp create mode 100644 daemon/appinfo.h (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') 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(-) (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') 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 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 (limited to 'daemon/daemon.pro') 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 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(-) (limited to 'daemon/daemon.pro') 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 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 (limited to 'daemon/daemon.pro') 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 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 (limited to 'daemon/daemon.pro') 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 (limited to 'daemon/daemon.pro') 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