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/appmsgmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 daemon/appmsgmanager.cpp (limited to 'daemon/appmsgmanager.cpp') 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; +} -- cgit v1.2.3 From 2e0e33bd2d588a96fc471d024de583ec7d287f5e Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 21:01:53 +0100 Subject: remove now useless debug() msg --- daemon/appmsgmanager.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 9eb948e..23bf802 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -9,7 +9,6 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { if (data.at(0) == WatchConnector::appmsgPUSH) { - logger()->debug() << "LAUNCHER is PUSHing" << data.toHex(); Unpacker u(data); u.skip(1); // skip data.at(0) which we just already checked above. uint transaction = u.read(); -- cgit v1.2.3 From 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/appmsgmanager.cpp') 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 843e8c2550f69de3b9dfc3ec5f13d2c3a5710896 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 00:00:42 +0100 Subject: implement more parts of the new D-Bus API --- daemon/appmsgmanager.cpp | 10 ++++++ daemon/appmsgmanager.h | 2 ++ daemon/jskitmanager.cpp | 5 +++ daemon/jskitmanager.h | 3 +- daemon/jskitobjects.cpp | 7 +---- daemon/manager.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++----- daemon/manager.h | 37 +++++++--------------- org.pebbled.Watch.xml | 9 ++++++ 8 files changed, 113 insertions(+), 41 deletions(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index b620078..afaabee 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -130,6 +130,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) send(uuid, data, nullCallback, nullCallback); } +void AppMsgManager::launchApp(const QUuid &uuid) +{ + // TODO +} + +void AppMsgManager::closeApp(const QUuid &uuid) +{ + // TODO +} + WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) { AppInfo info = apps->info(uuid); diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index ca1d484..bc9c82f 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -18,6 +18,8 @@ public: public slots: void send(const QUuid &uuid, const QVariantMap &data); + void launchApp(const QUuid &uuid); + void closeApp(const QUuid &uuid); signals: void appStarted(const QUuid &uuid); diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index cfd860e..9efc5c8 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -24,6 +24,11 @@ QJSEngine * JSKitManager::engine() return _engine; } +bool JSKitManager::isJSKitAppRunning() const +{ + return _engine != 0; +} + void JSKitManager::showConfiguration() { if (_engine) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index fd6040d..07ecec1 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -19,10 +19,11 @@ public: ~JSKitManager(); QJSEngine * engine(); + bool isJSKitAppRunning() const; signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); - void appOpenUrl(const QString &url); + void appOpenUrl(const QUrl &url); public slots: void showConfiguration(); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 9e4cfde..a0bc0ba 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -57,12 +57,7 @@ void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QSt void JSKitPebble::openURL(const QUrl &url) { logger()->debug() << "opening url" << url.toString(); - emit _mgr->appOpenUrl(url.toString()); -#if 0 /* Until we figure out how to do this. Maybe signal the daemon? */ - if (!QDesktopServices::openUrl(url)) { - logger()->warn() << "Failed to open URL:" << url; - } -#endif + emit _mgr->appOpenUrl(url); } QJSValue JSKitPebble::createXMLHttpRequest() diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 38b3948..0666de0 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -56,6 +56,8 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); connect(appmsg, &AppMsgManager::messageReceived, this, &Manager::onAppMessage); + connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); + connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); QDBusConnection session = QDBusConnection::sessionBus(); new WatchAdaptor(proxy); @@ -389,19 +391,82 @@ void Manager::transliterateMessage(const QString &text) } } -void Manager::test() +void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) { - logger()->debug() << "Starting test"; - - js->showConfiguration(); + emit proxy->AppMessage(uuid.toString(), data); } -void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) +void Manager::onAppOpened(const QUuid &uuid) { - emit proxy->AppMessage(uuid.toString(), data); + currentAppUuid = uuid; + emit proxy->AppOpened(uuid.toString()); } -void Manager::onWebviewClosed(const QString &result) +void Manager::onAppClosed(const QUuid &uuid) { - js->handleWebviewClosed(result); + currentAppUuid = QUuid(); + emit proxy->AppClosed(uuid.toString()); +} + +bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + setDelayedReply(true); + manager()->appmsg->send(uuid, data, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); + this->connection().send(reply); + }, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); + this->connection().send(reply); + }); + return false; // D-Bus clients should never see this reply. +} + +QString PebbledProxy::StartAppConfiguration(const QString &uuid) { + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (manager()->currentAppUuid != uuid) { + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return QString(); + } + + if (!manager()->js->isJSKitAppRunning()) { + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return QString(); + } + + // After calling showConfiguration() on the script, + // it will (eventually!) return a URL to us via the appOpenUrl signal. + + // So we can't send the D-Bus reply right now. + setDelayedReply(true); + + // Set up a signal handler to catch the appOpenUrl signal. + QMetaObject::Connection c = connect(manager()->js, &JSKitManager::appOpenUrl, + [this,msg,c](const QUrl &url) { + // Workaround: due to a GCC bug we can't capture the uuid parameter, but we can extract + // it again from the original message arguments. + QString uuid = msg.arguments().at(0).toString(); + if (manager()->currentAppUuid != uuid) { + // App was changed while we were waiting for the script.. + QDBusMessage reply = msg.createErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + connection().send(reply); + } else { + QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); + connection().send(reply); + } + + disconnect(c); + }); + // TODO: JS script may fail, never call OpenURL, or something like that + // In those cases we may leak the above connection + // So we need to also set a timeout or similar. + + manager()->js->showConfiguration(); + + return QString(); // This return value should never be used. } diff --git a/daemon/manager.h b/daemon/manager.h index c12d3dc..f099aac 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -62,6 +62,8 @@ class Manager : public QObject, protected QDBusContext QString lastSeenMpris; QVariantMap mprisMetadata; + QUuid currentAppUuid; + QScopedPointer transliterator; public: @@ -84,8 +86,6 @@ public slots: void applyProfile(); private slots: - void test(); - void onWebviewClosed(const QString &result); void onSettingChanged(const QString &key); void onSettingsChanged(); void onPebbleChanged(); @@ -103,6 +103,8 @@ private slots: void setMprisMetadata(QVariantMap metadata); void onAppMessage(const QUuid &uuid, const QVariantMap &data); + void onAppOpened(const QUuid &uuid); + void onAppClosed(const QUuid &uuid); }; /** This class is what's actually exported over D-Bus, @@ -132,32 +134,13 @@ public slots: inline void Ping(uint val) { manager()->watch->ping(val); } inline void SyncTime() { manager()->watch->time(); } - inline void LaunchApp(const QString &uuid) { /* TODO */ } - inline void CloseApp(const QString &uuid) { /* TODO */ } - - bool SendAppMessage(const QString &uuid, const QVariantMap &data) { - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - setDelayedReply(true); - manager()->appmsg->send(uuid, data, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); - this->connection().send(reply); - }, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); - this->connection().send(reply); - }); - return false; // D-Bus clients should never see this reply. - } - - QString StartAppConfiguration(const QString &uuid) { - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - setDelayedReply(true); + inline void LaunchApp(const QString &uuid) { manager()->appmsg->launchApp(uuid); } + inline void CloseApp(const QString &uuid) { manager()->appmsg->closeApp(uuid); } - // TODO - } + bool SendAppMessage(const QString &uuid, const QVariantMap &data); + QString StartAppConfiguration(const QString &uuid); - inline void SendAppConfiguration(const QString &uuid, const QString &data) { + void SendAppConfiguration(const QString &uuid, const QString &data) { // TODO } @@ -166,6 +149,8 @@ signals: void AddressChanged(); void ConnectedChanged(); void AppMessage(const QString &uuid, const QVariantMap &data); + void AppOpened(const QString &uuid); + void AppClosed(const QString &uuid); }; #endif // MANAGER_H diff --git a/org.pebbled.Watch.xml b/org.pebbled.Watch.xml index 72aab6b..11f20b7 100644 --- a/org.pebbled.Watch.xml +++ b/org.pebbled.Watch.xml @@ -5,6 +5,7 @@ + @@ -15,6 +16,7 @@ + @@ -33,6 +35,13 @@ + + + + + + + -- cgit v1.2.3 From 8d98f990c9ed158d8c65befc154ab58a3c392646 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:47:48 +0100 Subject: fix some appmsg issues --- daemon/appmsgmanager.cpp | 15 ++++++++++----- daemon/watchconnector.cpp | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index afaabee..12971ce 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -71,8 +71,12 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p emit messageReceived(uuid, data); break; } + case WatchConnector::appmsgACK: + case WatchConnector::appmsgNACK: + logger()->info() << "appmsg endpoint handler received an unwanted ack/nack"; + break; default: - logger()->warn() << "Unknown application message type:" << data.at(0); + logger()->warn() << "Unknown application message type:" << int(data.at(0)); break; } @@ -88,15 +92,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; +#if 0 /* Try to unpack what we just packed. */ WatchConnector::Dict t_dict; QUuid t_uuid; uint t_trans; if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { logger()->debug() << t_trans << t_uuid << t_dict; } else { - logger()->warn() << "not unpack my own"; + logger()->error() << "not able to unpack my own dict"; } - +#endif watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { @@ -105,10 +110,10 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: quint8 type = reply[0]; quint8 recv_transaction = reply[1]; - logger()->debug() << "Got response to transaction" << transaction; - if (recv_transaction != transaction) return false; + logger()->debug() << "Got response to transaction" << transaction; + switch (type) { case WatchConnector::appmsgACK: logger()->debug() << "Got ACK to transaction" << transaction; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index dd95821..baec52c 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -109,13 +109,17 @@ bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data) if (tmp_it != tmpHandlers.end()) { QList& funcs = tmp_it.value(); bool ok = false; - if (!funcs.empty()) { - if (funcs.first()(data)) { + for (int i = 0; i < funcs.size(); i++) { + if (funcs[i](data)) { + // This handler accepted this message ok = true; - funcs.removeFirst(); + // Since it is a temporary handler, remove it. + funcs.removeAt(i); + break; } } if (funcs.empty()) { + // "Garbage collect" the tmpHandlers entry. tmpHandlers.erase(tmp_it); } if (ok) { -- cgit v1.2.3 From ddcc8ada42c186e980626ff617be038f45106145 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 4 Dec 2014 00:17:28 +0100 Subject: send appmessages one at a time --- daemon/appmsgmanager.cpp | 288 +++++++++++++++++++++++++++++++---------------- daemon/appmsgmanager.h | 33 +++++- 2 files changed, 218 insertions(+), 103 deletions(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 12971ce..d5f527e 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,3 +1,5 @@ +#include + #include "appmsgmanager.h" #include "unpacker.h" #include "packer.h" @@ -5,46 +7,22 @@ // TODO D-Bus server for non JS kit apps!!!! AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) - : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0), timeout(new QTimer(this)) { + connect(watch, &WatchConnector::connectedChanged, + this, &AppMsgManager::handleWatchConnectedChanged); + + timeout->setSingleShot(true); + timeout->setInterval(3000); + connect(timeout, &QTimer::timeout, + this, &AppMsgManager::handleTimeout); + watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { - if (data.at(0) == WatchConnector::appmsgPUSH) { - uint transaction; - QUuid uuid; - WatchConnector::Dict dict; - - if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { - // Failed to parse! - // Since we're the only one handling this endpoint, - // all messages must be accepted - logger()->warn() << "Failed to parser LAUNCHER PUSH message"; - return true; - } - if (!dict.contains(1)) { - logger()->warn() << "LAUNCHER message has no item in dict"; - return true; - } - - switch (dict.value(1).toInt()) { - case WatchConnector::launcherSTARTED: - logger()->debug() << "App starting in watch:" << uuid; - this->watch->sendMessage(WatchConnector::watchLAUNCHER, - buildAckMessage(transaction)); - emit appStarted(uuid); - break; - case WatchConnector::launcherSTOPPED: - logger()->debug() << "App stopping in watch:" << uuid; - this->watch->sendMessage(WatchConnector::watchLAUNCHER, - buildAckMessage(transaction)); - emit appStopped(uuid); - break; - default: - logger()->warn() << "LAUNCHER pushed unknown message:" << uuid << dict; - this->watch->sendMessage(WatchConnector::watchLAUNCHER, - buildNackMessage(transaction)); - break; - } + switch (data.at(0)) { + case WatchConnector::appmsgPUSH: + handleLauncherPushMessage(data); + break; } return true; @@ -53,27 +31,14 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: { - uint transaction; - QUuid uuid; - WatchConnector::Dict dict; - - if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { - logger()->warn() << "Failed to parse APP_MSG PUSH"; - return true; - } - - logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; - - QVariantMap data = mapAppKeys(uuid, dict); - logger()->debug() << "Mapped dict" << data; - - emit messageReceived(uuid, data); + case WatchConnector::appmsgPUSH: + handlePushMessage(data); break; - } case WatchConnector::appmsgACK: + handleAckMessage(data, true); + break; case WatchConnector::appmsgNACK: - logger()->info() << "appmsg endpoint handler received an unwanted ack/nack"; + handleAckMessage(data, false); break; default: logger()->warn() << "Unknown application message type:" << int(data.at(0)); @@ -86,47 +51,22 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) { - WatchConnector::Dict dict = mapAppKeys(uuid, data); - quint8 transaction = ++lastTransactionId; - QByteArray msg = buildPushMessage(transaction, uuid, dict); - - logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; - -#if 0 /* Try to unpack what we just packed. */ - WatchConnector::Dict t_dict; - QUuid t_uuid; - uint t_trans; - if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { - logger()->debug() << t_trans << t_uuid << t_dict; - } else { - logger()->error() << "not able to unpack my own dict"; + PendingTransaction trans; + trans.uuid = uuid; + trans.transactionId = ++lastTransactionId; + trans.dict = mapAppKeys(uuid, data); + trans.ackCallback = ackCallback; + trans.nackCallback = nackCallback; + + logger()->debug() << "Queueing appmsg" << trans.transactionId << "to" << trans.uuid + << "with dict" << trans.dict; + + pending.enqueue(trans); + if (pending.size() == 1) { + // This is the only transaction on the queue + // Therefore, we were idle before: we can submit this transaction right now. + transmitNextPendingTransaction(); } -#endif - - watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, - [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { - if (reply.size() < 2) return false; - - quint8 type = reply[0]; - quint8 recv_transaction = reply[1]; - - if (recv_transaction != transaction) return false; - - logger()->debug() << "Got response to transaction" << transaction; - - switch (type) { - case WatchConnector::appmsgACK: - logger()->debug() << "Got ACK to transaction" << transaction; - if (ackCallback) ackCallback(); - return true; - case WatchConnector::appmsgNACK: - logger()->info() << "Got NACK to transaction" << transaction; - if (nackCallback) nackCallback(); - return true; - default: - return false; - } - }); } void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) @@ -194,7 +134,7 @@ QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::D return data; } -bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, quint8 *transaction, QUuid *uuid, WatchConnector::Dict *dict) { Unpacker u(msg); quint8 code = u.read(); @@ -211,7 +151,7 @@ bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, return true; } -QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +QByteArray AppMsgManager::buildPushMessage(quint8 transaction, const QUuid &uuid, const WatchConnector::Dict &dict) { QByteArray ba; Packer p(&ba); @@ -223,7 +163,7 @@ QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, return ba; } -QByteArray AppMsgManager::buildAckMessage(uint transaction) +QByteArray AppMsgManager::buildAckMessage(quint8 transaction) { QByteArray ba(2, Qt::Uninitialized); ba[0] = WatchConnector::appmsgACK; @@ -231,10 +171,160 @@ QByteArray AppMsgManager::buildAckMessage(uint transaction) return ba; } -QByteArray AppMsgManager::buildNackMessage(uint transaction) +QByteArray AppMsgManager::buildNackMessage(quint8 transaction) { QByteArray ba(2, Qt::Uninitialized); ba[0] = WatchConnector::appmsgNACK; ba[1] = transaction; return ba; } + +void AppMsgManager::handleLauncherPushMessage(const QByteArray &data) +{ + quint8 transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; + return; + } + + switch (dict.value(1).toInt()) { + case WatchConnector::launcherSTARTED: + logger()->debug() << "App starting in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStarted(uuid); + break; + case WatchConnector::launcherSTOPPED: + logger()->debug() << "App stopping in watch:" << uuid; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildAckMessage(transaction)); + emit appStopped(uuid); + break; + default: + logger()->warn() << "LAUNCHER pushed unknown message:" << uuid << dict; + this->watch->sendMessage(WatchConnector::watchLAUNCHER, + buildNackMessage(transaction)); + break; + } +} + +void AppMsgManager::handlePushMessage(const QByteArray &data) +{ + quint8 transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap msg = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << msg; + + emit messageReceived(uuid, msg); +} + +void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) +{ + if (data.size() < 2) { + logger()->warn() << "invalid ack/nack message size"; + return; + } + + if (pending.empty()) { + logger()->warn() << "received an ack/nack but no active transaction"; + } + + const quint8 type = data[0]; + const quint8 recv_transaction = data[1]; + + Q_ASSERT(type == WatchConnector::appmsgACK || type == WatchConnector::appmsgNACK); + + PendingTransaction &trans = pending.head(); + if (trans.transactionId != recv_transaction) { + logger()->warn() << "received an ack/nack but for the wrong transaction"; + } + + logger()->debug() << "Got " << (ack ? "ACK" : "NACK") << " to transaction" << trans.transactionId; + + timeout->stop(); + + if (ack) { + if (trans.ackCallback) { + trans.ackCallback(); + } + } else { + if (trans.nackCallback) { + trans.nackCallback(); + } + } + + pending.dequeue(); + + if (!pending.empty()) { + transmitNextPendingTransaction(); + } +} + +void AppMsgManager::handleWatchConnectedChanged() +{ + // If the watch is disconnected, everything breaks loose + // TODO In the future we may want to avoid doing the following. + if (!watch->isConnected()) { + abortPendingTransactions(); + } +} + +void AppMsgManager::handleTimeout() +{ + // Abort the first transaction + Q_ASSERT(!pending.empty()); + PendingTransaction trans = pending.dequeue(); + + logger()->warn() << "timeout on appmsg transaction" << trans.transactionId; + + if (trans.nackCallback) { + trans.nackCallback(); + } + + if (!pending.empty()) { + transmitNextPendingTransaction(); + } +} + +void AppMsgManager::transmitNextPendingTransaction() +{ + Q_ASSERT(!pending.empty()); + PendingTransaction &trans = pending.head(); + + QByteArray msg = buildPushMessage(trans.transactionId, trans.uuid, trans.dict); + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg); + + timeout->start(); +} + +void AppMsgManager::abortPendingTransactions() +{ + // Invoke all the NACK callbacks in the pending queue, then drop them. + Q_FOREACH(const PendingTransaction &trans, pending) { + if (trans.nackCallback) { + trans.nackCallback(); + } + } + + pending.clear(); +} diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index bc9c82f..498d3fa 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -1,6 +1,10 @@ #ifndef APPMSGMANAGER_H #define APPMSGMANAGER_H +#include +#include +#include + #include "watchconnector.h" #include "appmanager.h" @@ -30,16 +34,37 @@ private: WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); - static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + static bool unpackPushMessage(const QByteArray &msg, quint8 *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(quint8 transaction, const QUuid &uuid, const WatchConnector::Dict &dict); + static QByteArray buildAckMessage(quint8 transaction); + static QByteArray buildNackMessage(quint8 transaction); + + void handleLauncherPushMessage(const QByteArray &data); + void handlePushMessage(const QByteArray &data); + void handleAckMessage(const QByteArray &data, bool ack); - static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); - static QByteArray buildAckMessage(uint transaction); - static QByteArray buildNackMessage(uint transaction); + void transmitNextPendingTransaction(); + void abortPendingTransactions(); + +private slots: + void handleWatchConnectedChanged(); + void handleTimeout(); private: AppManager *apps; WatchConnector *watch; quint8 lastTransactionId; + + struct PendingTransaction { + quint8 transactionId; + QUuid uuid; + WatchConnector::Dict dict; + std::function ackCallback; + std::function nackCallback; + }; + QQueue pending; + QTimer *timeout; }; #endif // APPMSGMANAGER_H -- cgit v1.2.3 From 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/appmsgmanager.cpp') 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 5499dc58d09f07081c41b8e4dead810a82137939 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 6 Dec 2014 23:31:57 +0100 Subject: properly send acks for incoming appmsgs --- daemon/appmsgmanager.cpp | 32 +++++++++++++++++++++++++++++++- daemon/appmsgmanager.h | 7 ++++++- daemon/jskitmanager.cpp | 22 +++++++++++++++++++--- daemon/jskitmanager.h | 2 +- daemon/jskitobjects.cpp | 3 ++- daemon/manager.cpp | 7 +------ daemon/manager.h | 1 - 7 files changed, 60 insertions(+), 14 deletions(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 312043a..f24b8d9 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -69,6 +69,16 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std:: } } +void AppMsgManager::setMessageHandler(const QUuid &uuid, MessageHandlerFunc func) +{ + _handlers.insert(uuid, func); +} + +void AppMsgManager::clearMessageHandler(const QUuid &uuid) +{ + _handlers.remove(uuid); +} + uint AppMsgManager::lastTransactionId() const { return _lastTransactionId; @@ -236,6 +246,8 @@ void AppMsgManager::handlePushMessage(const QByteArray &data) if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { logger()->warn() << "Failed to parse APP_MSG PUSH"; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildNackMessage(transaction)); return; } @@ -244,7 +256,25 @@ void AppMsgManager::handlePushMessage(const QByteArray &data) QVariantMap msg = mapAppKeys(uuid, dict); logger()->debug() << "Mapped dict" << msg; - emit messageReceived(uuid, msg); + bool result; + + MessageHandlerFunc handler = _handlers.value(uuid); + if (handler) { + result = handler(msg); + } else { + // No handler? Let's just send an ACK. + result = false; + } + + if (result) { + logger()->debug() << "ACKing transaction" << transaction; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildAckMessage(transaction)); + } else { + logger()->info() << "NACKing transaction" << transaction; + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, + buildNackMessage(transaction)); + } } void AppMsgManager::handleAckMessage(const QByteArray &data, bool ack) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 9aaabd4..e52c544 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -19,6 +19,11 @@ public: void send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback); + + typedef std::function MessageHandlerFunc; + void setMessageHandler(const QUuid &uuid, MessageHandlerFunc func); + void clearMessageHandler(const QUuid &uuid); + uint lastTransactionId() const; uint nextTransactionId() const; @@ -30,7 +35,6 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void messageReceived(const QUuid &uuid, const QVariantMap &data); private: WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); @@ -56,6 +60,7 @@ private slots: private: AppManager *apps; WatchConnector *watch; + QHash _handlers; quint8 _lastTransactionId; struct PendingTransaction { diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 6b291df..c73d32e 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,7 +9,6 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); - connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -83,7 +82,7 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } -void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg) { if (_curApp.uuid() == uuid) { logger()->debug() << "received a message for the current JSKit app"; @@ -94,7 +93,7 @@ void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) } QJSValue eventObj = _engine->newObject(); - eventObj.setProperty("payload", _engine->toScriptValue(data)); + eventObj.setProperty("payload", _engine->toScriptValue(msg)); _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); } @@ -164,6 +163,19 @@ void JSKitManager::startJsApp() // Now load the actual script loadJsFile(_curApp.path() + "/pebble-js-app.js"); + // Setup the message callback + QUuid uuid = _curApp.uuid(); + _appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) { + QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection, + Q_ARG(QUuid, uuid), + Q_ARG(QVariantMap, msg)); + + // Invoke the slot as a queued connection to give time for the ACK message + // to go through first. + + return true; + }); + // We try to invoke the callbacks even if script parsing resulted in error... _jspebble->invokeCallbacks("ready"); } @@ -174,6 +186,10 @@ void JSKitManager::stopJsApp() logger()->debug() << "stopping JS app"; + if (!_curApp.uuid().isNull()) { + _appmsg->clearMessageHandler(_curApp.uuid()); + } + _engine->collectGarbage(); _engine->deleteLater(); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 4239f91..b42e338 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -35,7 +35,7 @@ public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); - void handleAppMessage(const QUuid &uuid, const QVariantMap &data); + void handleAppMessage(const QUuid &uuid, const QVariantMap &msg); private: bool loadJsFile(const QString &filename); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index e55f2d5..3386f16 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -519,7 +519,8 @@ void JSKitGeolocation::handleTimeout() for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { if (it->timer.hasExpired(it->timeout)) { - logger()->info() << "positioning timeout for watch" << it->watchId; + logger()->info() << "positioning timeout for watch" << it->watchId + << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout; invokeCallback(it->errorCallback, obj); if (it->once) { diff --git a/daemon/manager.cpp b/daemon/manager.cpp index b488432..8a00373 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -55,7 +55,6 @@ Manager::Manager(Settings *settings, QObject *parent) : connect(notifications, SIGNAL(twitterNotify(const QString &,const QString &)), SLOT(onTwitterNotify(const QString &,const QString &))); connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); - connect(appmsg, &AppMsgManager::messageReceived, this, &Manager::onAppMessage); connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); @@ -395,14 +394,10 @@ void Manager::transliterateMessage(const QString &text) void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) { + Q_UNUSED(uuid); watch->sendSMSNotification(title, body); } -void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) -{ - emit proxy->AppMessage(uuid.toString(), data); -} - void Manager::onAppOpened(const QUuid &uuid) { currentAppUuid = uuid; diff --git a/daemon/manager.h b/daemon/manager.h index 18bd7bf..bf83f75 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -103,7 +103,6 @@ private slots: void setMprisMetadata(QVariantMap metadata); void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); - void onAppMessage(const QUuid &uuid, const QVariantMap &data); void onAppOpened(const QUuid &uuid); void onAppClosed(const QUuid &uuid); }; -- cgit v1.2.3 From d7474bbe49e2d14290ca1b0cddb746e0bf5fbe63 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Dec 2014 00:04:36 +0100 Subject: implement launcher d-bus commands --- daemon/appmsgmanager.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'daemon/appmsgmanager.cpp') diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index f24b8d9..eda1eb4 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -23,6 +23,10 @@ AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *p case WatchConnector::appmsgPUSH: handleLauncherPushMessage(data); break; + case WatchConnector::appmsgACK: + case WatchConnector::appmsgNACK: + // TODO we ignore those for now. + break; } return true; @@ -97,12 +101,24 @@ void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) void AppMsgManager::launchApp(const QUuid &uuid) { - // TODO + WatchConnector::Dict dict; + dict.insert(1, WatchConnector::launcherSTARTED); + + logger()->debug() << "Sending message to launcher" << uuid << dict; + + QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict); + watch->sendMessage(WatchConnector::watchLAUNCHER, msg); } void AppMsgManager::closeApp(const QUuid &uuid) { - // TODO + WatchConnector::Dict dict; + dict.insert(1, WatchConnector::launcherSTOPPED); + + logger()->debug() << "Sending message to launcher" << uuid << dict; + + QByteArray msg = buildPushMessage(++_lastTransactionId, uuid, dict); + watch->sendMessage(WatchConnector::watchLAUNCHER, msg); } WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) -- cgit v1.2.3