From 6456b840eb660fdafe21d376e07e0b67a24cd0b4 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 23:25:33 +0100 Subject: more JSKit objects --- daemon/jskitobjects.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 daemon/jskitobjects.h (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h new file mode 100644 index 0000000..8acd76f --- /dev/null +++ b/daemon/jskitobjects.h @@ -0,0 +1,48 @@ +#ifndef JSKITMANAGER_P_H +#define JSKITMANAGER_P_H + +#include +#include "jskitmanager.h" + +class JSKitPebble : public QObject +{ + Q_OBJECT + +public: + explicit JSKitPebble(JSKitManager *mgr); + + Q_INVOKABLE void addEventListener(const QString &event, QJSValue callback); +private: + QHash> _callbacks; +}; + +class JSKitLocalStorage : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int length READ length NOTIFY lengthChanged) + +public: + explicit JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr); + + int length() const; + + Q_INVOKABLE QJSValue getItem(const QString &key) const; + Q_INVOKABLE void setItem(const QString &key, const QString &value); + Q_INVOKABLE void removeItem(const QString &key); + + Q_INVOKABLE void clear(); + +signals: + void lengthChanged(); + +private: + void checkLengthChanged(); + static QString getStorageFileFor(const QUuid &uuid); + +private: + QSettings *_storage; + int _len; +}; + +#endif // JSKITMANAGER_P_H -- cgit v1.2.3 From cf405034b49e5e8ba7a8d22522878c8834b8d4ae Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Nov 2014 23:47:38 +0100 Subject: stub all functions of Pebble JS object --- daemon/daemon.pro | 2 +- daemon/jskitmanager.cpp | 2 ++ daemon/jskitobjects.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++--- daemon/jskitobjects.h | 14 +++++++++++- 4 files changed, 71 insertions(+), 5 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 48410c4..a6631df 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts qml +QT += bluetooth dbus contacts gui qml PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 8329e74..2da6c09 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -75,6 +75,8 @@ void JSKitManager::startJsApp() } logger()->debug() << "JS script evaluated"; + + _jspebble->invokeCallbacks("ready"); } void JSKitManager::stopJsApp() diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index fc9506d..f40138a 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,15 +1,67 @@ #include +#include +#include #include #include "jskitobjects.h" JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr) + : QObject(mgr), _mgr(mgr) { } -void JSKitPebble::addEventListener(const QString &value, QJSValue callback) +void JSKitPebble::addEventListener(const QString &type, QJSValue function) { - _callbacks[value].append(callback); + _callbacks[type].append(function); +} + +void JSKitPebble::removeEventListener(const QString &type, QJSValue function) +{ + if (!_callbacks.contains(type)) return; + QList &callbacks = _callbacks[type]; + + for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ) { + if (it->strictlyEquals(function)) { + it = callbacks.erase(it); + } else { + ++it; + } + } + + if (callbacks.empty()) { + _callbacks.remove(type); + } +} + +void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) +{ + // TODO contact _mgr->appmsg->... + logger()->debug() << "sendAppMessage" << message.toString(); +} + +void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) +{ + logger()->debug() << "showSimpleNotificationOnPebble" << title << body; +} + +void JSKitPebble::openUrl(const QUrl &url) +{ + if (!QDesktopServices::openUrl(url)) { + logger()->warn() << "Failed to open URL:" << url; + } +} + +void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) +{ + if (!_callbacks.contains(type)) return; + QList &callbacks = _callbacks[type]; + + for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + QJSValue result = it->call(args); + if (result.isError()) { + logger()->warn() << "error while invoking callback" << type << it->toString() << ":" + << result.toString(); + } + } } JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 8acd76f..2375084 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -7,12 +7,24 @@ class JSKitPebble : public QObject { Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER public: explicit JSKitPebble(JSKitManager *mgr); - Q_INVOKABLE void addEventListener(const QString &event, QJSValue callback); + Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); + Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); + + Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack); + + Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); + + Q_INVOKABLE void openUrl(const QUrl &url); + + void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); + private: + JSKitManager *_mgr; QHash> _callbacks; }; -- cgit v1.2.3 From 1e3794c476caf5c41360c36cc13c8425ec0dd26c Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 02:21:30 +0100 Subject: implement message passing around jskit apps and watch --- daemon/appinfo.cpp | 26 +++++-- daemon/appinfo.h | 8 ++- daemon/appmsgmanager.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++--- daemon/appmsgmanager.h | 17 ++++- daemon/daemon.pro | 6 +- daemon/jskitmanager.cpp | 25 ++++++- daemon/jskitmanager.h | 5 +- daemon/jskitobjects.cpp | 35 ++++++++-- daemon/jskitobjects.h | 14 +++- daemon/manager.cpp | 2 +- daemon/packer.cpp | 91 ++++++++++++++++++++++++ daemon/packer.h | 67 ++++++++++++++++++ daemon/unpacker.cpp | 2 +- daemon/watchconnector.cpp | 19 +++--- daemon/watchconnector.h | 4 +- 15 files changed, 448 insertions(+), 44 deletions(-) create mode 100644 daemon/packer.cpp create mode 100644 daemon/packer.h (limited to 'daemon/jskitobjects.h') diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index a4442a3..e2406b8 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -10,7 +10,8 @@ struct AppInfoData : public QSharedData { QString versionLabel; bool watchface; bool jskit; - QHash appKeys; + QHash keyInts; + QHash keyNames; QString path; }; @@ -116,19 +117,30 @@ void AppInfo::setJSKit(bool b) d->jskit = b; } -QHash AppInfo::appKeys() const +void AppInfo::addAppKey(const QString &key, int value) { - return d->appKeys; + d->keyInts.insert(key, value); + d->keyNames.insert(value, key); } -void AppInfo::setAppKeys(const QHash &appKeys) +bool AppInfo::hasAppKeyValue(int value) const { - d->appKeys = appKeys; + return d->keyNames.contains(value); } -void AppInfo::addAppKey(const QString &key, int value) +QString AppInfo::appKeyForValue(int value) const +{ + return d->keyNames.value(value); +} + +bool AppInfo::hasAppKey(const QString &key) const +{ + return d->keyInts.contains(key); +} + +int AppInfo::valueForAppKey(const QString &key) const { - d->appKeys.insert(key, value); + return d->keyInts.value(key, -1); } QString AppInfo::path() const diff --git a/daemon/appinfo.h b/daemon/appinfo.h index da71dfc..038a708 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -52,10 +52,14 @@ public: bool isJSKit() const; void setJSKit(bool b); - QHash appKeys() const; - void setAppKeys(const QHash &string); void addAppKey(const QString &key, int value); + bool hasAppKeyValue(int value) const; + QString appKeyForValue(int value) const; + + bool hasAppKey(const QString &key) const; + int valueForAppKey(const QString &key) const; + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmsgmanager.cpp b/daemon/appmsgmanager.cpp index 23bf802..b620078 100644 --- a/daemon/appmsgmanager.cpp +++ b/daemon/appmsgmanager.cpp @@ -1,21 +1,28 @@ #include "appmsgmanager.h" #include "unpacker.h" +#include "packer.h" // TODO D-Bus server for non JS kit apps!!!! -AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) - : QObject(parent), watch(watch) +AppMsgManager::AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent) + : QObject(parent), apps(apps), watch(watch), lastTransactionId(0) { watch->setEndpointHandler(WatchConnector::watchLAUNCHER, [this](const QByteArray &data) { if (data.at(0) == WatchConnector::appmsgPUSH) { - Unpacker u(data); - u.skip(1); // skip data.at(0) which we just already checked above. - uint transaction = u.read(); - QUuid uuid = u.readUuid(); - WatchConnector::Dict dict = u.readDict(); - if (u.bad() || !dict.contains(1)) { - logger()->warn() << "Failed to parse LAUNCHER message"; + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + // Failed to parse! + // Since we're the only one handling this endpoint, + // all messages must be accepted + logger()->warn() << "Failed to parser LAUNCHER PUSH message"; + return true; + } + if (!dict.contains(1)) { + logger()->warn() << "LAUNCHER message has no item in dict"; return true; } @@ -46,7 +53,26 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) watch->setEndpointHandler(WatchConnector::watchAPPLICATION_MESSAGE, [this](const QByteArray &data) { switch (data.at(0)) { - case WatchConnector::appmsgPUSH: + case WatchConnector::appmsgPUSH: { + uint transaction; + QUuid uuid; + WatchConnector::Dict dict; + + if (!unpackPushMessage(data, &transaction, &uuid, &dict)) { + logger()->warn() << "Failed to parse APP_MSG PUSH"; + return true; + } + + logger()->debug() << "Received appmsg PUSH from" << uuid << "with" << dict; + + QVariantMap data = mapAppKeys(uuid, dict); + logger()->debug() << "Mapped dict" << data; + + emit messageReceived(uuid, data); + break; + } + default: + logger()->warn() << "Unknown application message type:" << data.at(0); break; } @@ -54,9 +80,132 @@ AppMsgManager::AppMsgManager(WatchConnector *watch, QObject *parent) }); } +void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data, const std::function &ackCallback, const std::function &nackCallback) +{ + WatchConnector::Dict dict = mapAppKeys(uuid, data); + quint8 transaction = ++lastTransactionId; + QByteArray msg = buildPushMessage(transaction, uuid, dict); + + logger()->debug() << "Sending appmsg" << transaction << "to" << uuid << "with" << dict; + + WatchConnector::Dict t_dict; + QUuid t_uuid; + uint t_trans; + if (unpackPushMessage(msg, &t_trans, &t_uuid, &t_dict)) { + logger()->debug() << t_trans << t_uuid << t_dict; + } else { + logger()->warn() << "not unpack my own"; + } + + + watch->sendMessage(WatchConnector::watchAPPLICATION_MESSAGE, msg, + [this, ackCallback, nackCallback, transaction](const QByteArray &reply) { + if (reply.size() < 2) return false; + + quint8 type = reply[0]; + quint8 recv_transaction = reply[1]; + + logger()->debug() << "Got response to transaction" << transaction; + + if (recv_transaction != transaction) return false; + + switch (type) { + case WatchConnector::appmsgACK: + logger()->debug() << "Got ACK to transaction" << transaction; + if (ackCallback) ackCallback(); + return true; + case WatchConnector::appmsgNACK: + logger()->info() << "Got NACK to transaction" << transaction; + if (nackCallback) nackCallback(); + return true; + default: + return false; + } + }); +} + void AppMsgManager::send(const QUuid &uuid, const QVariantMap &data) { - // TODO + std::function nullCallback; + send(uuid, data, nullCallback, nullCallback); +} + +WatchConnector::Dict AppMsgManager::mapAppKeys(const QUuid &uuid, const QVariantMap &data) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + WatchConnector::Dict d; + + for (QVariantMap::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) { + if (info.hasAppKey(it.key())) { + d.insert(info.valueForAppKey(it.key()), it.value()); + } else { + // Even if we do not know about this appkey, try to see if it's already a numeric key we + // can send to the watch. + bool ok = false; + int num = it.key().toInt(&ok); + if (ok) { + d.insert(num, it.value()); + } else { + logger()->warn() << "Unknown appKey" << it.key() << "for app with GUID" << uuid; + } + } + } + + return d; +} + +QVariantMap AppMsgManager::mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict) +{ + AppInfo info = apps->info(uuid); + if (info.uuid() != uuid) { + logger()->warn() << "Unknown app GUID while sending message:" << uuid; + } + + QVariantMap data; + + for (WatchConnector::Dict::const_iterator it = dict.constBegin(); it != dict.constEnd(); ++it) { + if (info.hasAppKeyValue(it.key())) { + data.insert(info.appKeyForValue(it.key()), it.value()); + } else { + logger()->warn() << "Unknown appKey value" << it.key() << "for app with GUID" << uuid; + data.insert(QString::number(it.key()), it.value()); + } + } + + return data; +} + +bool AppMsgManager::unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict) +{ + Unpacker u(msg); + quint8 code = u.read(); + Q_ASSERT(code == WatchConnector::appmsgPUSH); + + *transaction = u.read(); + *uuid = u.readUuid(); + *dict = u.readDict(); + + if (u.bad()) { + return false; + } + + return true; +} + +QByteArray AppMsgManager::buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict) +{ + QByteArray ba; + Packer p(&ba); + p.write(WatchConnector::appmsgPUSH); + p.write(transaction); + p.writeUuid(uuid); + p.writeDict(dict); + + return ba; } QByteArray AppMsgManager::buildAckMessage(uint transaction) diff --git a/daemon/appmsgmanager.h b/daemon/appmsgmanager.h index 651d84e..ca1d484 100644 --- a/daemon/appmsgmanager.h +++ b/daemon/appmsgmanager.h @@ -2,6 +2,7 @@ #define APPMSGMANAGER_H #include "watchconnector.h" +#include "appmanager.h" class AppMsgManager : public QObject { @@ -9,7 +10,11 @@ class AppMsgManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit AppMsgManager(WatchConnector *watch, QObject *parent); + explicit AppMsgManager(AppManager *apps, WatchConnector *watch, QObject *parent); + + void send(const QUuid &uuid, const QVariantMap &data, + const std::function &ackCallback, + const std::function &nackCallback); public slots: void send(const QUuid &uuid, const QVariantMap &data); @@ -17,14 +22,22 @@ public slots: signals: void appStarted(const QUuid &uuid); void appStopped(const QUuid &uuid); - void dataReceived(const QUuid &uuid, const QVariantMap &data); + void messageReceived(const QUuid &uuid, const QVariantMap &data); private: + WatchConnector::Dict mapAppKeys(const QUuid &uuid, const QVariantMap &data); + QVariantMap mapAppKeys(const QUuid &uuid, const WatchConnector::Dict &dict); + + static bool unpackPushMessage(const QByteArray &msg, uint *transaction, QUuid *uuid, WatchConnector::Dict *dict); + + static QByteArray buildPushMessage(uint transaction, const QUuid &uuid, const WatchConnector::Dict &dict); static QByteArray buildAckMessage(uint transaction); static QByteArray buildNackMessage(uint transaction); private: + AppManager *apps; WatchConnector *watch; + quint8 lastTransactionId; }; #endif // APPMSGMANAGER_H diff --git a/daemon/daemon.pro b/daemon/daemon.pro index a6631df..c59d408 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -28,7 +28,8 @@ SOURCES += \ appmsgmanager.cpp \ jskitmanager.cpp \ appinfo.cpp \ - jskitobjects.cpp + jskitobjects.cpp \ + packer.cpp HEADERS += \ manager.h \ @@ -46,7 +47,8 @@ HEADERS += \ appmsgmanager.h \ jskitmanager.h \ appinfo.h \ - jskitobjects.h + jskitobjects.h \ + packer.h OTHER_FILES += \ org.pebbled.xml \ diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 2da6c09..f8ec34a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -9,6 +9,7 @@ JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *par { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); + connect(_appmsg, &AppMsgManager::messageReceived, this, &JSKitManager::handleAppMessage); } JSKitManager::~JSKitManager() @@ -40,6 +41,23 @@ void JSKitManager::handleAppStopped(const QUuid &uuid) } } +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &data) +{ + if (_curApp.uuid() == uuid) { + logger()->debug() << "received a message for the current JSKit app"; + + if (!_engine) { + logger()->debug() << "but engine is stopped"; + return; + } + + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("payload", _engine->toScriptValue(data)); + + _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); + } +} + void JSKitManager::startJsApp() { if (_engine) stopJsApp(); @@ -49,7 +67,8 @@ void JSKitManager::startJsApp() } _engine = new QJSEngine(this); - _jspebble = new JSKitPebble(this); + _jspebble = new JSKitPebble(_curApp, this); + _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); logger()->debug() << "starting JS app"; @@ -57,8 +76,12 @@ void JSKitManager::startJsApp() QJSValue globalObj = _engine->globalObject(); globalObj.setProperty("Pebble", _engine->newQObject(_jspebble)); + globalObj.setProperty("console", _engine->newQObject(_jsconsole)); globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage)); + QJSValue windowObj = _engine->newObject(); + windowObj.setProperty("localStorage", globalObj.property("localStorage")); + globalObj.setProperty("window", windowObj); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index f25a96f..d09bf54 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -6,6 +6,7 @@ #include "appmsgmanager.h" class JSKitPebble; +class JSKitConsole; class JSKitLocalStorage; class JSKitManager : public QObject @@ -18,12 +19,14 @@ public: ~JSKitManager(); signals: + void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: private slots: void handleAppStarted(const QUuid &uuid); void handleAppStopped(const QUuid &uuid); + void handleAppMessage(const QUuid &uuid, const QVariantMap &data); private: void startJsApp(); @@ -37,8 +40,8 @@ private: AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; + QPointer _jsconsole; QPointer _jsstorage; - }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f40138a..aecc55d 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,8 +4,8 @@ #include #include "jskitobjects.h" -JSKitPebble::JSKitPebble(JSKitManager *mgr) - : QObject(mgr), _mgr(mgr) +JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) + : QObject(mgr), _appInfo(info), _mgr(mgr) { } @@ -34,17 +34,28 @@ void JSKitPebble::removeEventListener(const QString &type, QJSValue function) void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack) { - // TODO contact _mgr->appmsg->... - logger()->debug() << "sendAppMessage" << message.toString(); + QVariantMap data = message.toVariant().toMap(); + + logger()->debug() << "sendAppMessage" << data; + + _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { + logger()->debug() << "Invoking ack callback"; + callbackForAck.call(); + }, [this, callbackForNack]() mutable { + logger()->debug() << "Invoking nack callback"; + callbackForNack.call(); + }); } void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body) { logger()->debug() << "showSimpleNotificationOnPebble" << title << body; + emit _mgr->appNotification(_appInfo.uuid(), title, body); } void JSKitPebble::openUrl(const QUrl &url) { + logger()->debug() << "opening url" << url.toString(); if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } @@ -56,6 +67,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QList &callbacks = _callbacks[type]; for (QList::iterator it = callbacks.begin(); it != callbacks.end(); ++it) { + logger()->debug() << "invoking callback" << type << it->toString(); QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" @@ -64,6 +76,16 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) } } +JSKitConsole::JSKitConsole(JSKitManager *mgr) + : QObject(mgr) +{ +} + +void JSKitConsole::log(const QString &msg) +{ + logger()->info() << msg; +} + JSKitLocalStorage::JSKitLocalStorage(const QUuid &uuid, JSKitManager *mgr) : QObject(mgr), _storage(new QSettings(getStorageFileFor(uuid), QSettings::IniFormat, this)) { @@ -117,5 +139,8 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) { QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); dataDir.mkdir("js-storage"); - return dataDir.absoluteFilePath("js-storage/" + uuid.toString() + ".ini"); + QString fileName = uuid.toString(); + fileName.remove('{'); + fileName.remove('}'); + return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 2375084..c59bfac 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -10,7 +10,7 @@ class JSKitPebble : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitPebble(JSKitManager *mgr); + explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr); Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); @@ -24,10 +24,22 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: + AppInfo _appInfo; JSKitManager *_mgr; QHash> _callbacks; }; +class JSKitConsole : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + +public: + explicit JSKitConsole(JSKitManager *mgr); + + Q_INVOKABLE void log(const QString &msg); +}; + class JSKitLocalStorage : public QObject { Q_OBJECT diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 778fdc6..8d41c89 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -14,7 +14,7 @@ Manager::Manager(Settings *settings, QObject *parent) : notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), - appmsg(new AppMsgManager(watch, this)), + appmsg(new AppMsgManager(apps, watch, this)), js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { diff --git a/daemon/packer.cpp b/daemon/packer.cpp new file mode 100644 index 0000000..569f7a8 --- /dev/null +++ b/daemon/packer.cpp @@ -0,0 +1,91 @@ +#include "packer.h" +#include "watchconnector.h" + +void Packer::writeBytes(int n, const QByteArray &b) +{ + if (b.size() > n) { + _buf->append(b.constData(), n); + } else { + int diff = n - b.size(); + _buf->append(b); + if (diff > 0) { + _buf->append(QByteArray(diff, '\0')); + } + } +} + +void Packer::writeUuid(const QUuid &uuid) +{ + writeBytes(16, uuid.toRfc4122()); +} + +void Packer::writeDict(const QMap &d) +{ + int size = d.size(); + if (size > 0xFF) { + logger()->warn() << "Dictionary is too large to encode"; + writeLE(0); + return; + } + + writeLE(size); + + for (QMap::const_iterator it = d.constBegin(); it != d.constEnd(); ++it) { + writeLE(it.key()); + + switch (int(it.value().type())) { + case QMetaType::Char: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::Short: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::Int: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::UChar: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(char)); + writeLE(it.value().value()); + break; + case QMetaType::UShort: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(short)); + writeLE(it.value().value()); + break; + case QMetaType::UInt: + writeLE(WatchConnector::typeINT); + writeLE(sizeof(int)); + writeLE(it.value().value()); + break; + + case QMetaType::QByteArray: { + QByteArray ba = it.value().toByteArray(); + writeLE(WatchConnector::typeBYTES); + writeLE(ba.size()); + _buf->append(ba); + break; + } + + default: + logger()->warn() << "Unknown dict item type:" << it.value().typeName(); + /* Fallthrough */ + case QMetaType::QString: + case QMetaType::QUrl: + { + QByteArray s = it.value().toString().toUtf8(); + writeLE(WatchConnector::typeSTRING); + writeLE(s.size()); + _buf->append(s); + break; + } + } + } +} diff --git a/daemon/packer.h b/daemon/packer.h new file mode 100644 index 0000000..d22072c --- /dev/null +++ b/daemon/packer.h @@ -0,0 +1,67 @@ +#ifndef PACKER_H +#define PACKER_H + +#include +#include +#include +#include +#include +#include + +class Packer +{ + LOG4QT_DECLARE_STATIC_LOGGER(logger, Packer) + +public: + Packer(QByteArray *buf); + + template + void write(T v); + + template + void writeLE(T v); + + void writeBytes(int n, const QByteArray &b); + + void writeFixedString(int n, const QString &s); + + void writeUuid(const QUuid &uuid); + + void writeDict(const QMap &d); + +private: + char *p(int n); + uchar *up(int n); + QByteArray *_buf; +}; + +inline Packer::Packer(QByteArray *buf) + : _buf(buf) +{ +} + +template +void Packer::write(T v) +{ + qToBigEndian(v, up(sizeof(T))); +} + +template +void Packer::writeLE(T v) +{ + qToLittleEndian(v, up(sizeof(T))); +} + +inline char * Packer::p(int n) +{ + int size = _buf->size(); + _buf->resize(size + n); + return &_buf->data()[size]; +} + +inline uchar * Packer::up(int n) +{ + return reinterpret_cast(p(n)); +} + +#endif // PACKER_H diff --git a/daemon/unpacker.cpp b/daemon/unpacker.cpp index fc38020..e904db8 100644 --- a/daemon/unpacker.cpp +++ b/daemon/unpacker.cpp @@ -29,7 +29,7 @@ QMap Unpacker::readDict() QMap d; if (checkBad(1)) return d; - const int n = read(); + const int n = readLE(); for (int i = 0; i < n; i++) { if (checkBad(4 + 1 + 2)) return d; diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index a3ea181..dd95821 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -89,7 +89,7 @@ QString WatchConnector::decodeEndpoint(uint val) return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val); } -void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func) +void WatchConnector::setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func) { if (func) { handlers.insert(endpoint, func); @@ -241,6 +241,7 @@ void WatchConnector::sendData(const QByteArray &data) reconnect(); } else if (is_connected) { logger()->debug() << "Writing" << data.length() << "bytes to socket"; + logger()->debug() << data.toHex(); socket->write(data); } } @@ -251,7 +252,7 @@ void WatchConnector::onBytesWritten(qint64 bytes) logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left"; } -void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) +void WatchConnector::sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback) { logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint); QByteArray msg; @@ -268,6 +269,10 @@ void WatchConnector::sendMessage(uint endpoint, const QByteArray &data) msg.append(data); sendData(msg); + + if (callback) { + tmpHandlers[endpoint].append(callback); + } } void WatchConnector::buildData(QByteArray &res, QStringList data) @@ -445,9 +450,8 @@ void WatchConnector::endPhoneCall(uint cookie) void WatchConnector::getAppbankStatus(const std::function& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_STATUS) { return false; } @@ -491,9 +495,8 @@ void WatchConnector::getAppbankStatus(const std::function &)>& callback) { - sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS)); - - tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) { + sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS), + [this, callback](const QByteArray &data) { if (data.at(0) != appmgrGET_APPBANK_UUIDS) { return false; } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 8a7d574..6c7fdd4 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -173,7 +173,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } - void setEndpointHandler(uint endpoint, EndpointHandlerFunc func); + void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); static QString timeStamp(); @@ -192,7 +192,7 @@ public slots: void disconnect(); void reconnect(); - void sendMessage(uint endpoint, const QByteArray &data); + void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); void ping(uint val); void time(); -- cgit v1.2.3 From be139d8ff95160782b424134a025b30c82083e28 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 03:12:00 +0100 Subject: add stub xmlhttprequest and allow showConfig event --- daemon/jskitmanager.cpp | 14 ++++++ daemon/jskitmanager.h | 3 ++ daemon/jskitobjects.cpp | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitobjects.h | 56 ++++++++++++++++++++++++ daemon/manager.cpp | 8 +--- 5 files changed, 188 insertions(+), 7 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index f8ec34a..6023a9a 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -19,6 +19,18 @@ JSKitManager::~JSKitManager() } } +QJSEngine * JSKitManager::engine() +{ + return _engine; +} + +void JSKitManager::showConfiguration() +{ + if (_engine) { + _jspebble->invokeCallbacks("showConfiguration"); + } +} + void JSKitManager::handleAppStarted(const QUuid &uuid) { AppInfo info = _apps->info(uuid); @@ -83,6 +95,8 @@ void JSKitManager::startJsApp() windowObj.setProperty("localStorage", globalObj.property("localStorage")); globalObj.setProperty("window", windowObj); + _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); + QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { logger()->warn() << "Failed to open JS file at:" << scriptFile.fileName(); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index d09bf54..73d7853 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -18,10 +18,13 @@ public: explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); ~JSKitManager(); + QJSEngine * engine(); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); public slots: + void showConfiguration(); private slots: void handleAppStarted(const QUuid &uuid); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index aecc55d..f745233 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "jskitobjects.h" @@ -61,6 +62,13 @@ void JSKitPebble::openUrl(const QUrl &url) } } +QJSValue JSKitPebble::createXMLHttpRequest() +{ + JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); + // Should be deleted by JS engine. + return _mgr->engine()->newQObject(xhr); +} + void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) { if (!_callbacks.contains(type)) return; @@ -144,3 +152,109 @@ QString JSKitLocalStorage::getStorageFileFor(const QUuid &uuid) fileName.remove('}'); return dataDir.absoluteFilePath("js-storage/" + fileName + ".ini"); } + +JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) + : QObject(parent), _mgr(mgr), + _net(new QNetworkAccessManager(this)), _reply(0) +{ + logger()->debug() << "constructed"; +} + +JSKitXMLHttpRequest::~JSKitXMLHttpRequest() +{ + logger()->debug() << "destructed"; +} + +void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async) +{ + if (_reply) { + _reply->deleteLater(); + _reply = 0; + } + + _request = QNetworkRequest(QUrl(url)); + _verb = method; + Q_UNUSED(async); +} + +void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) +{ + logger()->debug() << "setRequestHeader" << header << value; + _request.setRawHeader(header.toLatin1(), value.toLatin1()); +} + +void JSKitXMLHttpRequest::send(const QString &body) +{ + QBuffer *buffer = new QBuffer; + buffer->setData(body.toUtf8()); + logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << body; + _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); + connect(_reply, &QNetworkReply::finished, this, &JSKitXMLHttpRequest::handleReplyFinished); + buffer->setParent(_reply); // So that it gets deleted alongside the reply object. +} + +void JSKitXMLHttpRequest::abort() +{ + if (_reply) { + _reply->deleteLater(); + _reply = 0; + } +} + +QJSValue JSKitXMLHttpRequest::onload() const +{ + return _onload; +} + +void JSKitXMLHttpRequest::setOnload(const QJSValue &value) +{ + _onload = value; +} + +unsigned short JSKitXMLHttpRequest::readyState() const +{ + if (!_reply) { + return UNSENT; + } else if (_reply->isFinished()) { + return DONE; + } else { + return LOADING; + } +} + +unsigned short JSKitXMLHttpRequest::status() const +{ + if (!_reply || !_reply->isFinished()) { + return 0; + } else { + return _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); + } +} + +QString JSKitXMLHttpRequest::responseText() const +{ + return QString::fromUtf8(_response); +} + +void JSKitXMLHttpRequest::handleReplyFinished() +{ + if (!_reply) { + logger()->info() << "reply finished too late"; + return; + } + + _response = _reply->readAll(); + logger()->debug() << "reply finished, reply text:" << QString::fromUtf8(_response); + + emit readyStateChanged(); + emit statusChanged(); + emit responseTextChanged(); + + + if (_onload.isCallable()) { + logger()->debug() << "going to call onload handler:" << _onload.toString(); + _onload.callWithInstance(_mgr->engine()->toScriptValue(this)); + } else { + logger()->debug() << "No onload set"; + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index c59bfac..1962e01 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -2,6 +2,8 @@ #define JSKITMANAGER_P_H #include +#include +#include #include "jskitmanager.h" class JSKitPebble : public QObject @@ -21,6 +23,8 @@ public: Q_INVOKABLE void openUrl(const QUrl &url); + Q_INVOKABLE QJSValue createXMLHttpRequest(); + void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); private: @@ -69,4 +73,56 @@ private: int _len; }; +class JSKitXMLHttpRequest : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + + Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) + Q_PROPERTY(unsigned short readyState READ readyState NOTIFY readyStateChanged) + Q_PROPERTY(unsigned short status READ status NOTIFY statusChanged) + Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) + +public: + explicit JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent = 0); + ~JSKitXMLHttpRequest(); + + enum ReadyStates { + UNSENT, + OPENED, + HEADERS_RECEIVED, + LOADING, + DONE + }; + + Q_INVOKABLE void open(const QString &method, const QString &url, bool async); + Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); + Q_INVOKABLE void send(const QString &body); + Q_INVOKABLE void abort(); + + QJSValue onload() const; + void setOnload(const QJSValue &value); + + unsigned short readyState() const; + unsigned short status() const; + QString responseText() const; + +signals: + void readyStateChanged(); + void statusChanged(); + void responseTextChanged(); + +private slots: + void handleReplyFinished(); + +private: + JSKitManager *_mgr; + QNetworkAccessManager *_net; + QString _verb; + QNetworkRequest _request; + QNetworkReply *_reply; + QByteArray _response; + QJSValue _onload; +}; + #endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 8d41c89..9db4c70 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -395,11 +395,5 @@ void Manager::test() { logger()->debug() << "Starting test"; - watch->getAppbankStatus([this](const QString &s) { - logger()->debug() << "Callback invoked" << s; - }); - - watch->getAppbankUuids([this](const QList &uuids) { - logger()->debug() << "Callback invoked. UUIDs:" << uuids.size(); - }); + js->showConfiguration(); } -- cgit v1.2.3 From 81f91639969de0f3852a3fe73db13b4cb0ecf3b4 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 1 Dec 2014 04:13:06 +0100 Subject: hackily implement openURL by signalling the URLs via D-Bus to the settings app, which pops a webview --- app/app.pro | 3 ++- app/pebbledinterface.cpp | 16 ++++++++++++++++ app/pebbledinterface.h | 4 ++++ app/qml/pages/WebViewPage.qml | 29 +++++++++++++++++++++++++++++ app/qml/pebble.qml | 5 +++++ daemon/dbusadaptor.cpp | 5 +++++ daemon/dbusadaptor.h | 8 ++++++++ daemon/jskitmanager.cpp | 10 ++++++++++ daemon/jskitmanager.h | 2 ++ daemon/jskitobjects.cpp | 10 ++++++++-- daemon/jskitobjects.h | 12 ++++++------ daemon/manager.cpp | 6 ++++++ daemon/manager.h | 3 ++- 13 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 app/qml/pages/WebViewPage.qml (limited to 'daemon/jskitobjects.h') diff --git a/app/app.pro b/app/app.pro index cb4c33b..e08fa87 100644 --- a/app/app.pro +++ b/app/app.pro @@ -22,4 +22,5 @@ OTHER_FILES += \ qml/pebble.qml \ qml/images/* \ pebble.desktop \ - pebble.png + pebble.png \ + qml/pages/WebViewPage.qml diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index c6f5674..8759863 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -19,6 +19,10 @@ PebbledInterface::PebbledInterface(QObject *parent) : PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, "pebbleChanged", this, SLOT(onPebbleChanged())); + QDBusConnection::sessionBus().connect( + PEBBLED_DBUS_SERVICE, PEBBLED_DBUS_PATH, PEBBLED_DBUS_IFACE, + "openUrl", this, SIGNAL(openUrl(QString))); + // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' // when going down @@ -163,3 +167,15 @@ void PebbledInterface::reconnect() qDebug() << __FUNCTION__; PebbledDbusInterface.call("reconnect"); } + +void PebbledInterface::test() +{ + qDebug() << __FUNCTION__; + PebbledDbusInterface.call("test"); +} + +void PebbledInterface::webviewClosed(const QString &result) +{ + qDebug() << __FUNCTION__; + PebbledDbusInterface.call("webviewClosed", QVariant::fromValue(result)); +} diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index 0a6f15d..7d00110 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -45,6 +45,8 @@ signals: void nameChanged(); void addressChanged(); + void openUrl(const QString &url); + public slots: void setEnabled(bool); void setActive(bool); @@ -52,6 +54,8 @@ public slots: void time(); void disconnect(); void reconnect(); + void test(); + void webviewClosed(const QString &result); private slots: void getUnitProperties(); diff --git a/app/qml/pages/WebViewPage.qml b/app/qml/pages/WebViewPage.qml new file mode 100644 index 0000000..2c6fcf0 --- /dev/null +++ b/app/qml/pages/WebViewPage.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0 +import QtQml 2.1 +import QtWebKit 3.0 +import Sailfish.Silica 1.0 + +Page { + id: webviewPage + + property alias url: webview.url + + SilicaWebView { + id: webview + anchors.fill: parent + + onNavigationRequested: { + console.log("navigation requested to " + request.url); + var url = request.url.toString() + if (/^pebblejs:\/\/close/.exec(url)) { + var data = decodeURI(url.substring(17)); + console.log("match with pebble close regexp. data: " + data); + pebbled.webviewClosed(data); + pageStack.pop(); + request.action = WebView.IgnoreRequest; + } else { + request.action = WebView.AcceptRequest; + } + } + } +} diff --git a/app/qml/pebble.qml b/app/qml/pebble.qml index da3bfb5..2ff0839 100644 --- a/app/qml/pebble.qml +++ b/app/qml/pebble.qml @@ -41,5 +41,10 @@ ApplicationWindow PebbledInterface { id: pebbled + + onOpenUrl: { + console.log("got open url: " + url); + pageStack.push(Qt.resolvedUrl("pages/WebViewPage.qml"), {url: url}); + } } } diff --git a/daemon/dbusadaptor.cpp b/daemon/dbusadaptor.cpp index 7bbf623..25e2508 100644 --- a/daemon/dbusadaptor.cpp +++ b/daemon/dbusadaptor.cpp @@ -85,3 +85,8 @@ void PebbledAdaptor::test() { QMetaObject::invokeMethod(parent(), "test"); } + +void PebbledAdaptor::webviewClosed(const QString &result) +{ + QMetaObject::invokeMethod(parent(), "webviewClosed", Q_ARG(QString, result)); +} diff --git a/daemon/dbusadaptor.h b/daemon/dbusadaptor.h index 54a0963..f347f92 100644 --- a/daemon/dbusadaptor.h +++ b/daemon/dbusadaptor.h @@ -47,6 +47,12 @@ class PebbledAdaptor: public QDBusAbstractAdaptor " \n" " \n" " \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" " \n" "") public: @@ -72,9 +78,11 @@ public Q_SLOTS: // METHODS void time(); void reconnect(); void test(); + void webviewClosed(const QString &result); Q_SIGNALS: // SIGNALS void connectedChanged(); void pebbleChanged(); + void openUrl(const QString &s); }; #endif diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 6023a9a..cfd860e 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -31,6 +31,16 @@ void JSKitManager::showConfiguration() } } +void JSKitManager::handleWebviewClosed(const QString &result) +{ + if (_engine) { + QJSValue eventObj = _engine->newObject(); + eventObj.setProperty("response", _engine->toScriptValue(result)); + + _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); + } +} + void JSKitManager::handleAppStarted(const QUuid &uuid) { AppInfo info = _apps->info(uuid); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 73d7853..fd6040d 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -22,9 +22,11 @@ public: signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); + void appOpenUrl(const QString &url); public slots: void showConfiguration(); + void handleWebviewClosed(const QString &result); private slots: void handleAppStarted(const QUuid &uuid); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index f745233..9e4cfde 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -54,12 +54,15 @@ void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QSt emit _mgr->appNotification(_appInfo.uuid(), title, body); } -void JSKitPebble::openUrl(const QUrl &url) +void JSKitPebble::openURL(const QUrl &url) { logger()->debug() << "opening url" << url.toString(); + emit _mgr->appOpenUrl(url.toString()); +#if 0 /* Until we figure out how to do this. Maybe signal the daemon? */ if (!QDesktopServices::openUrl(url)) { logger()->warn() << "Failed to open URL:" << url; } +#endif } QJSValue JSKitPebble::createXMLHttpRequest() @@ -253,7 +256,10 @@ void JSKitXMLHttpRequest::handleReplyFinished() if (_onload.isCallable()) { logger()->debug() << "going to call onload handler:" << _onload.toString(); - _onload.callWithInstance(_mgr->engine()->toScriptValue(this)); + QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); + if (result.isError()) { + logger()->warn() << "JS error on onload handler:" << result.toString(); + } } else { logger()->debug() << "No onload set"; } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 1962e01..4f000f1 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -21,7 +21,7 @@ public: Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); - Q_INVOKABLE void openUrl(const QUrl &url); + Q_INVOKABLE void openURL(const QUrl &url); Q_INVOKABLE QJSValue createXMLHttpRequest(); @@ -88,11 +88,11 @@ public: ~JSKitXMLHttpRequest(); enum ReadyStates { - UNSENT, - OPENED, - HEADERS_RECEIVED, - LOADING, - DONE + UNSENT = 0, + OPENED = 1, + HEADERS_RECEIVED = 2, + LOADING = 3, + DONE = 4 }; Q_INVOKABLE void open(const QString &method, const QString &url, bool async); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 9db4c70..e732d38 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -61,6 +61,7 @@ Manager::Manager(Settings *settings, QObject *parent) : session.registerService("org.pebbled"); connect(dbus, SIGNAL(pebbleChanged()), adaptor, SIGNAL(pebbleChanged())); connect(watch, SIGNAL(connectedChanged()), adaptor, SIGNAL(connectedChanged())); + connect(js, SIGNAL(appOpenUrl(QString)), adaptor, SIGNAL(openUrl(QString))); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; @@ -397,3 +398,8 @@ void Manager::test() js->showConfiguration(); } + +void Manager::onWebviewClosed(const QString &result) +{ + js->handleWebviewClosed(result); +} diff --git a/daemon/manager.h b/daemon/manager.h index f1dd53e..b0e15fb 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -84,6 +84,7 @@ public slots: private slots: void test(); + void onWebviewClosed(const QString &result); void onSettingChanged(const QString &key); void onSettingsChanged(); void onPebbleChanged(); @@ -123,7 +124,7 @@ public slots: void disconnect() { static_cast(parent())->watch->disconnect(); } void reconnect() { static_cast(parent())->watch->reconnect(); } void test() { static_cast(parent())->test(); } - + void webviewClosed(const QString &result) { static_cast(parent())->onWebviewClosed(result); } }; #endif // MANAGER_H -- cgit v1.2.3 From 87de94ea74c68b5ea0a4b125f1745c7449fbe4f1 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 01:19:57 +0100 Subject: fix actually setting the configuration, improve xhr support --- app/qml/pages/AppConfigPage.qml | 2 +- daemon/jskitmanager.cpp | 2 ++ daemon/jskitobjects.cpp | 67 ++++++++++++++++++++++++++++++++++++++--- daemon/jskitobjects.h | 11 ++++++- daemon/manager.cpp | 26 ++++++++++++++-- daemon/manager.h | 5 +-- 6 files changed, 100 insertions(+), 13 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 8fb31ca..7b969a3 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -21,7 +21,7 @@ Page { console.log("appconfig navigation requested to " + request.url); var url = request.url.toString(); if (/^pebblejs:\/\/close/.exec(url)) { - var data = decodeURI(url.substring(17)); + var data = decodeURIComponent(url.substring(17)); console.log("appconfig requesting close; data: " + data); pebbled.setAppConfiguration(uuid, data); pageStack.pop(); diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index e1e4073..7bf8cdc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -44,6 +44,8 @@ void JSKitManager::handleWebviewClosed(const QString &result) QJSValue eventObj = _engine->newObject(); eventObj.setProperty("response", _engine->toScriptValue(result)); + logger()->debug() << "webview closed with the following result: " << result; + _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); } else { logger()->warn() << "webview closed event, but JS engine is not running"; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index a0bc0ba..728daf7 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -40,11 +40,27 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV logger()->debug() << "sendAppMessage" << data; _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { - logger()->debug() << "Invoking ack callback"; - callbackForAck.call(); + if (callbackForAck.isCallable()) { + logger()->debug() << "Invoking ack callback"; + QJSValue result = callbackForAck.call(); + if (result.isError()) { + logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" + << result.toString(); + } + } else { + logger()->debug() << "Ack callback not callable"; + } }, [this, callbackForNack]() mutable { - logger()->debug() << "Invoking nack callback"; - callbackForNack.call(); + if (callbackForNack.isCallable()) { + logger()->debug() << "Invoking nack callback"; + QJSValue result = callbackForNack.call(); + if (result.isError()) { + logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" + << result.toString(); + } + } else { + logger()->debug() << "Nack callback not callable"; + } }); } @@ -187,7 +203,10 @@ void JSKitXMLHttpRequest::send(const QString &body) buffer->setData(body.toUtf8()); logger()->debug() << "sending" << _verb << "to" << _request.url() << "with" << body; _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer); - connect(_reply, &QNetworkReply::finished, this, &JSKitXMLHttpRequest::handleReplyFinished); + connect(_reply, &QNetworkReply::finished, + this, &JSKitXMLHttpRequest::handleReplyFinished); + connect(_reply, static_cast(&QNetworkReply::error), + this, &JSKitXMLHttpRequest::handleReplyError); buffer->setParent(_reply); // So that it gets deleted alongside the reply object. } @@ -209,6 +228,26 @@ void JSKitXMLHttpRequest::setOnload(const QJSValue &value) _onload = value; } +QJSValue JSKitXMLHttpRequest::ontimeout() const +{ + return _ontimeout; +} + +void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value) +{ + _ontimeout = value; +} + +QJSValue JSKitXMLHttpRequest::onerror() const +{ + return _onerror; +} + +void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) +{ + _onerror = value; +} + unsigned short JSKitXMLHttpRequest::readyState() const { if (!_reply) { @@ -259,3 +298,21 @@ void JSKitXMLHttpRequest::handleReplyFinished() logger()->debug() << "No onload set"; } } + +void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) +{ + if (!_reply) { + logger()->info() << "reply error too late"; + return; + } + + logger()->info() << "reply error" << code; + + if (_onerror.isCallable()) { + logger()->debug() << "going to call onerror handler:" << _onload.toString(); + QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); + if (result.isError()) { + logger()->warn() << "JS error on onerror handler:" << result.toString(); + } + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 4f000f1..849529f 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -17,7 +17,7 @@ public: Q_INVOKABLE void addEventListener(const QString &type, QJSValue function); Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function); - Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack); + Q_INVOKABLE void sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue()); Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body); @@ -79,6 +79,8 @@ class JSKitXMLHttpRequest : public QObject LOG4QT_DECLARE_QCLASS_LOGGER Q_PROPERTY(QJSValue onload READ onload WRITE setOnload) + Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout) + Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror) Q_PROPERTY(unsigned short readyState READ readyState NOTIFY readyStateChanged) Q_PROPERTY(unsigned short status READ status NOTIFY statusChanged) Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged) @@ -102,6 +104,10 @@ public: QJSValue onload() const; void setOnload(const QJSValue &value); + QJSValue ontimeout() const; + void setOntimeout(const QJSValue &value); + QJSValue onerror() const; + void setOnerror(const QJSValue &value); unsigned short readyState() const; unsigned short status() const; @@ -114,6 +120,7 @@ signals: private slots: void handleReplyFinished(); + void handleReplyError(QNetworkReply::NetworkError code); private: JSKitManager *_mgr; @@ -123,6 +130,8 @@ private: QNetworkReply *_reply; QByteArray _response; QJSValue _onload; + QJSValue _ontimeout; + QJSValue _onerror; }; #endif // JSKITMANAGER_P_H diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 1fc8a20..6fd47a4 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -410,7 +410,8 @@ void Manager::onAppClosed(const QUuid &uuid) emit proxy->AppUuidChanged(); } -bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) { +bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) +{ Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); setDelayedReply(true); @@ -424,7 +425,8 @@ bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) return false; // D-Bus clients should never see this reply. } -QString PebbledProxy::StartAppConfiguration(const QString &uuid) { +QString PebbledProxy::StartAppConfiguration(const QString &uuid) +{ Q_ASSERT(calledFromDBus()); const QDBusMessage msg = message(); @@ -474,3 +476,23 @@ QString PebbledProxy::StartAppConfiguration(const QString &uuid) { return QString(); // This return value should never be used. } + +void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString &data) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (manager()->currentAppUuid != uuid) { + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return; + } + + if (!manager()->js->isJSKitAppRunning()) { + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return; + } + + manager()->js->handleWebviewClosed(data); +} diff --git a/daemon/manager.h b/daemon/manager.h index 22382b8..239c212 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -141,10 +141,7 @@ public slots: bool SendAppMessage(const QString &uuid, const QVariantMap &data); QString StartAppConfiguration(const QString &uuid); - - void SendAppConfiguration(const QString &uuid, const QString &data) { - // TODO - } + void SendAppConfigurationData(const QString &uuid, const QString &data); signals: void NameChanged(); -- cgit v1.2.3 From 8722bee52922f8c6707103795fdf69f9ed7d0240 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 4 Dec 2014 01:21:32 +0100 Subject: add stub geolocation API --- daemon/daemon.pro | 2 +- daemon/jskitmanager.cpp | 7 ++++ daemon/jskitmanager.h | 2 ++ daemon/jskitobjects.cpp | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ daemon/jskitobjects.h | 43 +++++++++++++++++++++++ 5 files changed, 146 insertions(+), 1 deletion(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 3306541..0c4154b 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -4,7 +4,7 @@ CONFIG += console CONFIG += link_pkgconfig QT -= gui -QT += bluetooth dbus contacts gui qml +QT += bluetooth dbus contacts gui qml positioning PKGCONFIG += mlite5 icu-i18n CONFIG += c++11 diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 7bf8cdc..70ea4bd 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -103,6 +103,7 @@ void JSKitManager::startJsApp() _jspebble = new JSKitPebble(_curApp, this); _jsconsole = new JSKitConsole(this); _jsstorage = new JSKitLocalStorage(_curApp.uuid(), this); + _jsgeo = new JSKitGeolocation(this); logger()->debug() << "starting JS app"; @@ -116,6 +117,10 @@ void JSKitManager::startJsApp() windowObj.setProperty("localStorage", globalObj.property("localStorage")); globalObj.setProperty("window", windowObj); + QJSValue navigatorObj = _engine->newObject(); + navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); + globalObj.setProperty("navigator", navigatorObj); + _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); @@ -151,4 +156,6 @@ void JSKitManager::stopJsApp() _jsstorage = 0; delete _jspebble; _jspebble = 0; + delete _jsgeo; + _jsgeo = 0; } diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 07ecec1..1f842b7 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -8,6 +8,7 @@ class JSKitPebble; class JSKitConsole; class JSKitLocalStorage; +class JSKitGeolocation; class JSKitManager : public QObject { @@ -48,6 +49,7 @@ private: QPointer _jspebble; QPointer _jsconsole; QPointer _jsstorage; + QPointer _jsgeo; }; #endif // JSKITMANAGER_H diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 728daf7..3b4584b 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -316,3 +316,96 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) } } } + +JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) + : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) +{ + +} + +void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) +{ + logger()->debug() << Q_FUNC_INFO; + setupWatcher(successCallback, errorCallback, options, true); +} + +int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) +{ + logger()->debug() << Q_FUNC_INFO; + return setupWatcher(successCallback, errorCallback, options, false); +} + +void JSKitGeolocation::clearWatch(int watchId) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) +{ + logger()->debug() << Q_FUNC_INFO; +} + +void JSKitGeolocation::handleTimeout() +{ + logger()->debug() << Q_FUNC_INFO; +} + +int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) +{ + Watcher watcher; + watcher.successCallback = successCallback; + watcher.errorCallback = errorCallback; + watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); + watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); + watcher.maximumAge = options.value("maximumAge", 0).toUInt(); + watcher.once = once; + watcher.watchId = ++_lastWatchId; + + if (!_source) { + _source = QGeoPositionInfoSource::createDefaultSource(this); + connect(_source, static_cast(&QGeoPositionInfoSource::error), + this, &JSKitGeolocation::handleError); + connect(_source, &QGeoPositionInfoSource::positionUpdated, + this, &JSKitGeolocation::handlePosition); + connect(_source, &QGeoPositionInfoSource::updateTimeout, + this, &JSKitGeolocation::handleTimeout); + } + + if (once && watcher.maximumAge > 0) { + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-watcher.maximumAge); + QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); + if (pos.isValid() && pos.timestamp() >= threshold) { + invokeSuccessCallback(watcher, pos); + return -1; + } else if (watcher.timeout == 0) { + invokeErrorCallback(watcher); + return -1; + } + } + + if (once) { + _source->requestUpdate(watcher.timeout); + } else { + // TODO _source->setInterval to the minimum of all watches + _source->startUpdates(); + } + + return watcher.watchId; +} + +void JSKitGeolocation::invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos) +{ + // TODO +} + +void JSKitGeolocation::invokeErrorCallback(Watcher &watcher) +{ + if (watcher.errorCallback.isCallable()) { + watcher.errorCallback.call(); // TODO this, eventArgs + } +} diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 849529f..9c9b84e 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "jskitmanager.h" class JSKitPebble : public QObject @@ -134,4 +135,46 @@ private: QJSValue _onerror; }; +class JSKitGeolocation : public QObject +{ + Q_OBJECT + LOG4QT_DECLARE_QCLASS_LOGGER + + struct Watcher; + +public: + explicit JSKitGeolocation(JSKitManager *mgr); + + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); + Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); + Q_INVOKABLE void clearWatch(int watchId); + +private slots: + void handleError(const QGeoPositionInfoSource::Error error); + void handlePosition(const QGeoPositionInfo &pos); + void handleTimeout(); + +private: + int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); + void invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos); + void invokeErrorCallback(Watcher &watcher); + +private: + JSKitManager *_mgr; + QGeoPositionInfoSource *_source; + + struct Watcher { + QJSValue successCallback; + QJSValue errorCallback; + int watchId; + bool once; + bool highAccuracy; + uint timeout; + uint maximumAge; + }; + + QList _watches; + int _lastWatchId; +}; + #endif // JSKITMANAGER_P_H -- cgit v1.2.3 From e96c2ee30342b5a198025c3dd0c51bf43688d9ee Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 22:56:27 +0100 Subject: partial implementation of geolocation --- daemon/jskitmanager.cpp | 10 ++- daemon/jskitmanager.h | 2 + daemon/jskitobjects.cpp | 172 +++++++++++++++++++++++++++++++++++++++++------- daemon/jskitobjects.h | 20 ++++-- 4 files changed, 175 insertions(+), 29 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 70ea4bd..9c739fc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -29,6 +29,14 @@ bool JSKitManager::isJSKitAppRunning() const return _engine != 0; } +QString JSKitManager::describeError(QJSValue error) +{ + return QString("%1:%2: %3") + .arg(error.property("fileName").toString()) + .arg(error.property("lineNumber").toInt()) + .arg(error.toString()); +} + void JSKitManager::showConfiguration() { if (_engine) { @@ -134,7 +142,7 @@ void JSKitManager::startJsApp() QJSValue result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { - logger()->warn() << "error while evaluating JSKit script:" << result.toString(); + logger()->warn() << "error while evaluating JSKit script:" << describeError(result); } logger()->debug() << "JS script evaluated"; diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 1f842b7..873489b 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -22,6 +22,8 @@ public: QJSEngine * engine(); bool isJSKitAppRunning() const; + static QString describeError(QJSValue error); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); void appOpenUrl(const QUrl &url); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 3b4584b..b3820b5 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "jskitobjects.h" JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) @@ -45,7 +46,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForAck.call(); if (result.isError()) { logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Ack callback not callable"; @@ -56,7 +57,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForNack.call(); if (result.isError()) { logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Nack callback not callable"; @@ -93,7 +94,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } } @@ -287,12 +288,11 @@ void JSKitXMLHttpRequest::handleReplyFinished() emit statusChanged(); emit responseTextChanged(); - if (_onload.isCallable()) { logger()->debug() << "going to call onload handler:" << _onload.toString(); QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onload handler:" << result.toString(); + logger()->warn() << "JS error on onload handler:" << JSKitManager::describeError(result); } } else { logger()->debug() << "No onload set"; @@ -312,7 +312,7 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) logger()->debug() << "going to call onerror handler:" << _onload.toString(); QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onerror handler:" << result.toString(); + logger()->warn() << "JS error on onerror handler:" << JSKitManager::describeError(result); } } } @@ -320,7 +320,6 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) { - } void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) @@ -331,28 +330,59 @@ void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) { - logger()->debug() << Q_FUNC_INFO; return setupWatcher(successCallback, errorCallback, options, false); } void JSKitGeolocation::clearWatch(int watchId) { - logger()->debug() << Q_FUNC_INFO; + removeWatcher(watchId); } void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) { - logger()->debug() << Q_FUNC_INFO; + logger()->warn() << "positioning error: " << error; + // TODO } void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { logger()->debug() << Q_FUNC_INFO; + if (_watches.empty()) { + logger()->warn() << "got position update but no one is watching"; + } + + QJSValue obj = buildPositionObject(pos); + + for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { + invokeCallback(it->successCallback, obj); + + if (it->once) { + it = _watches.erase(it); + } else { + ++it; + } + } + + if (_watches.empty()) { + _source->stopUpdates(); + } } void JSKitGeolocation::handleTimeout() { logger()->debug() << Q_FUNC_INFO; + // TODO +} + +uint JSKitGeolocation::minimumTimeout() const +{ + uint minimum = std::numeric_limits::max(); + Q_FOREACH(const Watcher &watcher, _watches) { + if (!watcher.once) { + minimum = qMin(watcher.timeout, minimum); + } + } + return minimum; } int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) @@ -362,10 +392,13 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal watcher.errorCallback = errorCallback; watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); - watcher.maximumAge = options.value("maximumAge", 0).toUInt(); watcher.once = once; watcher.watchId = ++_lastWatchId; + uint maximumAge = options.value("maximumAge", 0).toUInt(); + + logger()->debug() << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once; + if (!_source) { _source = QGeoPositionInfoSource::createDefaultSource(this); connect(_source, static_cast(&QGeoPositionInfoSource::error), @@ -376,14 +409,17 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal this, &JSKitGeolocation::handleTimeout); } - if (once && watcher.maximumAge > 0) { - QDateTime threshold = QDateTime::currentDateTime().addMSecs(-watcher.maximumAge); + if (maximumAge > 0) { + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(maximumAge)); QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); + logger()->debug() << "got pos timestamp" << pos.timestamp() << " but we want" << threshold; if (pos.isValid() && pos.timestamp() >= threshold) { - invokeSuccessCallback(watcher, pos); - return -1; - } else if (watcher.timeout == 0) { - invokeErrorCallback(watcher); + invokeCallback(watcher.successCallback, buildPositionObject(pos)); + if (once) { + return -1; + } + } else if (watcher.timeout == 0 && once) { + invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); return -1; } } @@ -391,21 +427,111 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal if (once) { _source->requestUpdate(watcher.timeout); } else { - // TODO _source->setInterval to the minimum of all watches + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + logger()->debug() << "starting location updates"; _source->startUpdates(); } + _watches.append(watcher); + + logger()->debug() << "added new watch" << watcher.watchId; + return watcher.watchId; } -void JSKitGeolocation::invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos) +void JSKitGeolocation::removeWatcher(int watchId) { - // TODO + Watcher watcher; + + logger()->debug() << "removing watchId" << watcher.watchId; + + for (int i = 0; i < _watches.size(); i++) { + if (_watches[i].watchId == watchId) { + watcher = _watches.takeAt(i); + break; + } + } + + if (watcher.watchId != watchId) { + logger()->warn() << "watchId not found"; + return; + } + + if (_watches.empty()) { + logger()->debug() << "stopping updates"; + _source->stopUpdates(); + } else { + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + } } -void JSKitGeolocation::invokeErrorCallback(Watcher &watcher) +QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) { - if (watcher.errorCallback.isCallable()) { - watcher.errorCallback.call(); // TODO this, eventArgs + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + QJSValue coords = engine->newObject(); + QJSValue timestamp = engine->toScriptValue(pos.timestamp().toMSecsSinceEpoch()); + + coords.setProperty("latitude", engine->toScriptValue(pos.coordinate().latitude())); + coords.setProperty("longitude", engine->toScriptValue(pos.coordinate().longitude())); + if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) { + coords.setProperty("altitude", engine->toScriptValue(pos.coordinate().altitude())); + } else { + coords.setProperty("altitude", engine->toScriptValue(0)); + } + + coords.setProperty("accuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy))); + + if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy))); + } else { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::Direction)) { + coords.setProperty("heading", engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction))); + } else { + coords.setProperty("heading", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) { + coords.setProperty("speed", engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed))); + } else { + coords.setProperty("speed", engine->toScriptValue(0)); + } + + obj.setProperty("coords", coords); + obj.setProperty("timestamp", timestamp); + + logger()->debug() << obj.toString(); + + return obj; +} + +QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message) +{ + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + + obj.setProperty("code", engine->toScriptValue(error)); + obj.setProperty("message", engine->toScriptValue(message)); + + return obj; +} + +void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event) +{ + if (callback.isCallable()) { + logger()->debug() << "invoking callback" << callback.toString(); + QJSValue result = callback.call(QJSValueList({event})); + if (result.isError()) { + logger()->warn() << "while invoking callback: " << JSKitManager::describeError(result); + } + } else { + logger()->warn() << "callback is not callable"; } } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 9c9b84e..0dccc05 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -98,9 +98,9 @@ public: DONE = 4 }; - Q_INVOKABLE void open(const QString &method, const QString &url, bool async); + Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false); Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); - Q_INVOKABLE void send(const QString &body); + Q_INVOKABLE void send(const QString &body = QString()); Q_INVOKABLE void abort(); QJSValue onload() const; @@ -138,6 +138,7 @@ private: class JSKitGeolocation : public QObject { Q_OBJECT + Q_ENUMS(PositionError) LOG4QT_DECLARE_QCLASS_LOGGER struct Watcher; @@ -145,6 +146,12 @@ class JSKitGeolocation : public QObject public: explicit JSKitGeolocation(JSKitManager *mgr); + enum PositionError { + PERMISSION_DENIED = 1, + POSITION_UNAVAILABLE = 2, + TIMEOUT = 3 + }; + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE void clearWatch(int watchId); @@ -155,9 +162,13 @@ private slots: void handleTimeout(); private: + uint minimumTimeout() const; int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); - void invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos); - void invokeErrorCallback(Watcher &watcher); + void removeWatcher(int watchId); + QJSValue buildPositionObject(const QGeoPositionInfo &pos); + QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); + QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); + void invokeCallback(QJSValue callback, QJSValue event); private: JSKitManager *_mgr; @@ -170,7 +181,6 @@ private: bool once; bool highAccuracy; uint timeout; - uint maximumAge; }; QList _watches; -- cgit v1.2.3 From deb44cc068f03c7c6bdeb1b8803e58c2ad62a47d Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 23:57:06 +0100 Subject: minor changes --- daemon/jskitmanager.cpp | 20 +++++++++++++------- daemon/jskitobjects.cpp | 23 ++++++++++++++++++----- daemon/jskitobjects.h | 3 +++ 3 files changed, 34 insertions(+), 12 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 9c739fc..d632f7b 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -40,6 +40,7 @@ QString JSKitManager::describeError(QJSValue error) void JSKitManager::showConfiguration() { if (_engine) { + logger()->debug() << "requesting configuration"; _jspebble->invokeCallbacks("showConfiguration"); } else { logger()->warn() << "requested to show configuration, but JS engine is not running"; @@ -129,7 +130,11 @@ void JSKitManager::startJsApp() navigatorObj.setProperty("geolocation", _engine->newQObject(_jsgeo)); globalObj.setProperty("navigator", navigatorObj); - _engine->evaluate("function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }"); + // Shims for compatibility... + QJSValue result = _engine->evaluate( + "function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }\n" + ); + Q_ASSERT(!result.isError()); QFile scriptFile(_curApp.path() + "/pebble-js-app.js"); if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -140,7 +145,7 @@ void JSKitManager::startJsApp() QString script = QString::fromUtf8(scriptFile.readAll()); - QJSValue result = _engine->evaluate(script, scriptFile.fileName()); + result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { logger()->warn() << "error while evaluating JSKit script:" << describeError(result); } @@ -158,12 +163,13 @@ void JSKitManager::stopJsApp() _engine->collectGarbage(); - delete _engine; + _engine->deleteLater(); _engine = 0; - delete _jsstorage; + _jsstorage->deleteLater(); _jsstorage = 0; - delete _jspebble; - _jspebble = 0; - delete _jsgeo; + _jsgeo->deleteLater(); _jsgeo = 0; + _jspebble->deleteLater(); + _jspebble = 0; + } diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index b3820b5..1fc0062 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -43,7 +43,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV _mgr->_appmsg->send(_appInfo.uuid(), data, [this, callbackForAck]() mutable { if (callbackForAck.isCallable()) { logger()->debug() << "Invoking ack callback"; - QJSValue result = callbackForAck.call(); + QJSValue result = callbackForAck.call(QJSValueList({buildAckEventObject()})); if (result.isError()) { logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" << JSKitManager::describeError(result); @@ -54,7 +54,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV }, [this, callbackForNack]() mutable { if (callbackForNack.isCallable()) { logger()->debug() << "Invoking nack callback"; - QJSValue result = callbackForNack.call(); + QJSValue result = callbackForNack.call(QJSValueList({buildAckEventObject()})); if (result.isError()) { logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" << JSKitManager::describeError(result); @@ -84,6 +84,20 @@ QJSValue JSKitPebble::createXMLHttpRequest() return _mgr->engine()->newQObject(xhr); } +QJSValue JSKitPebble::buildAckEventObject() const +{ + QJSEngine *engine = _mgr->engine(); + QJSValue eventObj = engine->newObject(); + QJSValue dataObj = engine->newObject(); + + // Why do scripts need the real transactionId? + // No idea. Just fake it. + dataObj.setProperty("transactionId", engine->toScriptValue(0)); + eventObj.setProperty("data", dataObj); + + return eventObj; +} + void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) { if (!_callbacks.contains(type)) return; @@ -346,11 +360,12 @@ void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { - logger()->debug() << Q_FUNC_INFO; if (_watches.empty()) { logger()->warn() << "got position update but no one is watching"; } + logger()->debug() << "got position at" << pos.timestamp() << "type" << pos.coordinate().type(); + QJSValue obj = buildPositionObject(pos); for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { @@ -507,8 +522,6 @@ QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) obj.setProperty("coords", coords); obj.setProperty("timestamp", timestamp); - logger()->debug() << obj.toString(); - return obj; } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 0dccc05..5f039c1 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -28,6 +28,9 @@ public: void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); +private: + QJSValue buildAckEventObject() const; + private: AppInfo _appInfo; JSKitManager *_mgr; -- cgit v1.2.3 From 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/jskitobjects.h') 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 9c20ff45555cbe74c9afa24fafe5fc0c4ff9e7b8 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 8 Dec 2014 02:09:07 +0100 Subject: implement xhr authentication --- daemon/jskitobjects.cpp | 23 +++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ 2 files changed, 26 insertions(+) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 3386f16..5f5acaf 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,8 @@ JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent) _net(new QNetworkAccessManager(this)), _timeout(0), _reply(0) { logger()->debug() << "constructed"; + connect(_net, &QNetworkAccessManager::authenticationRequired, + this, &JSKitXMLHttpRequest::handleAuthenticationRequired); } JSKitXMLHttpRequest::~JSKitXMLHttpRequest() @@ -215,9 +218,13 @@ void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool a _reply = 0; } + _username = username; + _password = password; _request = QNetworkRequest(QUrl(url)); _verb = method; Q_UNUSED(async); + + logger()->debug() << "opened to URL" << _request.url().toString(); } void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) @@ -455,6 +462,22 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) } } +void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth) +{ + if (_reply == reply) { + logger()->debug() << "authentication required"; + + if (!_username.isEmpty() || !_password.isEmpty()) { + logger()->debug() << "using provided authorization:" << _username; + + auth->setUser(_username); + auth->setPassword(_password); + } else { + logger()->debug() << "no username or password provided"; + } + } +} + JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) { diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index b1954c0..23dfabe 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -143,12 +143,15 @@ signals: private slots: void handleReplyFinished(); void handleReplyError(QNetworkReply::NetworkError code); + void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth); private: JSKitManager *_mgr; QNetworkAccessManager *_net; QString _verb; uint _timeout; + QString _username; + QString _password; QNetworkRequest _request; QNetworkReply *_reply; QString _responseType; -- cgit v1.2.3 From ab362bf43227552bab209fea792d60d370ce0260 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:03:21 +0100 Subject: implement account and device tokens --- daemon/jskitmanager.cpp | 4 +-- daemon/jskitmanager.h | 5 +++- daemon/jskitobjects.cpp | 39 +++++++++++++++++++++++++++++ daemon/jskitobjects.h | 3 +++ daemon/manager.cpp | 2 +- daemon/settings.h | 29 ++++++++++----------- daemon/watchconnector.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------ daemon/watchconnector.h | 2 ++ 8 files changed, 123 insertions(+), 25 deletions(-) (limited to 'daemon/jskitobjects.h') diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index c73d32e..6d40970 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -4,8 +4,8 @@ #include "jskitmanager.h" #include "jskitobjects.h" -JSKitManager::JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent) : - QObject(parent), _apps(apps), _appmsg(appmsg), _engine(0) +JSKitManager::JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent) : + QObject(parent), _watch(watch), _apps(apps), _appmsg(appmsg), _settings(settings), _engine(0) { connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 7ffc0ad..871ab8e 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -4,6 +4,7 @@ #include #include "appmanager.h" #include "appmsgmanager.h" +#include "settings.h" class JSKitPebble; class JSKitConsole; @@ -16,7 +17,7 @@ class JSKitManager : public QObject LOG4QT_DECLARE_QCLASS_LOGGER public: - explicit JSKitManager(AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0); + explicit JSKitManager(WatchConnector *watch, AppManager *apps, AppMsgManager *appmsg, Settings *settings, QObject *parent = 0); ~JSKitManager(); QJSEngine * engine(); @@ -44,8 +45,10 @@ private: private: friend class JSKitPebble; + WatchConnector *_watch; AppManager *_apps; AppMsgManager *_appmsg; + Settings *_settings; AppInfo _curApp; QJSEngine *_engine; QPointer _jspebble; diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 5f5acaf..fe924e8 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -4,9 +4,12 @@ #include #include #include +#include #include #include "jskitobjects.h" +static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1"; + JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) : QObject(mgr), _appInfo(info), _mgr(mgr) { @@ -88,6 +91,42 @@ void JSKitPebble::openURL(const QUrl &url) emit _mgr->appOpenUrl(url); } +QString JSKitPebble::getAccountToken() const +{ + // We do not have any account system, so we just fake something up. + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + + QString token = _mgr->_settings->property("accountToken").toString(); + if (token.isEmpty()) { + token = QUuid::createUuid().toString(); + logger()->debug() << "created new account token" << token; + _mgr->_settings->setProperty("accountToken", token); + } + hasher.addData(token.toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning account token" << hash; + + return hash; +} + +QString JSKitPebble::getWatchToken() const +{ + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(token_salt, strlen(token_salt)); + hasher.addData(_appInfo.uuid().toByteArray()); + hasher.addData(_mgr->_watch->serialNumber().toLatin1()); + + QString hash = hasher.result().toHex(); + logger()->debug() << "returning watch token" << hash; + + return hash; +} + QJSValue JSKitPebble::createXMLHttpRequest() { JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0); diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 23dfabe..532ce1b 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -25,6 +25,9 @@ public: Q_INVOKABLE void openURL(const QUrl &url); + Q_INVOKABLE QString getAccountToken() const; + Q_INVOKABLE QString getWatchToken() const; + Q_INVOKABLE QJSValue createXMLHttpRequest(); void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList()); diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 212a1d7..32b313f 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -18,7 +18,7 @@ Manager::Manager(Settings *settings, QObject *parent) : music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), appmsg(new AppMsgManager(apps, watch, this)), - js(new JSKitManager(apps, appmsg, this)), + js(new JSKitManager(watch, apps, appmsg, settings, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); diff --git a/daemon/settings.h b/daemon/settings.h index d6db9b6..90e25e2 100644 --- a/daemon/settings.h +++ b/daemon/settings.h @@ -18,6 +18,8 @@ class Settings : public MDConfGroup Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) + Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) + bool silentWhenConnected; bool transliterateMessage; bool incomingCallNotification; @@ -29,6 +31,7 @@ class Settings : public MDConfGroup bool notificationsFacebook; bool notificationsOther; bool notificationsAll; + QString accountToken; public: explicit Settings(QObject *parent = 0) : @@ -36,20 +39,18 @@ public: { resolveMetaObject(); } signals: - void silentWhenConnectedChanged(bool); - void transliterateMessageChanged(bool); - void incomingCallNotificationChanged(bool); - void notificationsCommhistorydChanged(bool); - void notificationsMissedCallChanged(bool); - void notificationsEmailsChanged(bool); - void notificationsMitakuuluuChanged(bool); - void notificationsTwitterChanged(bool); - void notificationsFacebookChanged(bool); - void notificationsOtherChanged(bool); - void notificationsAllChanged(bool); - -public slots: - + void silentWhenConnectedChanged(); + void transliterateMessageChanged(); + void incomingCallNotificationChanged(); + void notificationsCommhistorydChanged(); + void notificationsMissedCallChanged(); + void notificationsEmailsChanged(); + void notificationsMitakuuluuChanged(); + void notificationsTwitterChanged(); + void notificationsFacebookChanged(); + void notificationsOtherChanged(); + void notificationsAllChanged(); + void accountTokenChanged(); }; #endif // SETTINGS_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 22f4a8a..27a5511 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -1,19 +1,60 @@ #include #include -#include "watchconnector.h" #include "unpacker.h" +#include "watchconnector.h" static const int RECONNECT_TIMEOUT = 500; //ms static const bool PROTOCOL_DEBUG = false; -using std::function; - WatchConnector::WatchConnector(QObject *parent) : QObject(parent), socket(nullptr), is_connected(false) { reconnectTimer.setSingleShot(true); connect(&reconnectTimer, SIGNAL(timeout()), SLOT(reconnect())); + + setEndpointHandler(watchVERSION, [this](const QByteArray &data) { + Unpacker u(data); + + u.skip(1); + + quint32 version = u.read(); + QString version_string = u.readFixedString(32); + QString commit = u.readFixedString(8); + bool is_recovery = u.read(); + quint8 hw_platform = u.read(); + quint8 metadata_version = u.read(); + + quint32 safe_version = u.read(); + QString safe_version_string = u.readFixedString(32); + QString safe_commit = u.readFixedString(8); + bool safe_is_recovery = u.read(); + quint8 safe_hw_platform = u.read(); + quint8 safe_metadata_version = u.read(); + + quint32 bootLoaderTimestamp = u.read(); + QString hardwareRevision = u.readFixedString(9); + QString serialNumber = u.readFixedString(12); + QByteArray address = u.readBytes(6); + + if (u.bad()) { + logger()->warn() << "short read while reading firmware version"; + } + + logger()->debug() << "got version information" + << version << version_string << commit + << is_recovery << hw_platform << metadata_version; + logger()->debug() << "recovery version information" + << safe_version << safe_version_string << safe_commit + << safe_is_recovery << safe_hw_platform << safe_metadata_version; + logger()->debug() << "hardware information" << bootLoaderTimestamp << hardwareRevision; + logger()->debug() << "serial number" << serialNumber.left(3) << "..."; + logger()->debug() << "bt address" << address.toHex(); + + this->_serialNumber = serialNumber; + + return true; + }); } WatchConnector::~WatchConnector() @@ -46,11 +87,11 @@ void WatchConnector::reconnect() void WatchConnector::disconnect() { - logger()->debug() << __FUNCTION__; + logger()->debug() << "disconnecting"; socket->close(); socket->deleteLater(); reconnectTimer.stop(); - logger()->debug() << "Stopped reconnect timer"; + logger()->debug() << "stopped reconnect timer"; } void WatchConnector::handleWatch(const QString &name, const QString &address) @@ -67,6 +108,11 @@ void WatchConnector::handleWatch(const QString &name, const QString &address) _last_address = address; if (emit_name) emit nameChanged(); + if (emit_name) { + // If we've changed names, don't reuse cached serial number! + _serialNumber.clear(); + } + logger()->debug() << "Creating socket"; #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket); @@ -198,11 +244,15 @@ void WatchConnector::onConnected() is_connected = true; reconnectTimer.stop(); reconnectTimer.setInterval(0); - if (not was_connected) { - if (not writeData.isEmpty()) { + if (!was_connected) { + if (!writeData.isEmpty()) { logger()->info() << "Found" << writeData.length() << "bytes in write buffer - resending"; sendData(writeData); } + if (_serialNumber.isEmpty()) { + // Ask for version information from the watch + sendMessage(watchVERSION, QByteArray(1, 0)); + } emit connectedChanged(); } } diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 6546302..45fd3c7 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -188,6 +188,7 @@ public: inline bool isConnected() const { return is_connected; } inline QString name() const { return socket != nullptr ? socket->peerName() : ""; } + inline QString serialNumber() const { return _serialNumber; } void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); void clearEndpointHandler(uint endpoint); @@ -245,6 +246,7 @@ private: QTimer reconnectTimer; QString _last_name; QString _last_address; + QString _serialNumber; }; #endif // WATCHCONNECTOR_H -- cgit v1.2.3