From d55d1d472d5876f90dd95301d9f3b6bef6f4c494 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Nov 2014 21:23:59 +0100 Subject: detect icu via pkgconfig --- rpm/pebble.spec | 2 ++ rpm/pebble.yaml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'rpm') diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 9b9df50..ba5b4b3 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -30,6 +30,8 @@ BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(mlite5) BuildRequires: pkgconfig(sailfishapp) >= 0.0.10 +BuildRequires: pkgconfig(icu-i18n) +BuildRequires: log4qt-devel BuildRequires: desktop-file-utils %description diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index e5edbaa..45fb409 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -22,9 +22,9 @@ PkgConfigBR: - Qt5Core - mlite5 - sailfishapp >= 0.0.10 +- icu-i18n PkgBR: - log4qt-devel -- libicu-devel Requires: - sailfishsilica-qt5 >= 0.10.9 - systemd-user-session-targets @@ -36,4 +36,3 @@ Files: - '%{_libdir}/systemd/user/%{name}d.service' - '%{_libdir}/systemd/user/user-session.target.wants/%{name}d.service' - '%{_datadir}/%{name}/log4qt.conf' -PkgBR: [] -- cgit v1.2.3 From 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 'rpm') 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 6ad0d9aee73808c95a7af5cac932b63ff2ce15dd Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 03:29:51 +0100 Subject: bumping version to 0.12.1b --- rpm/pebble.spec | 2 +- rpm/pebble.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'rpm') diff --git a/rpm/pebble.spec b/rpm/pebble.spec index f2e3611..8779710 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -13,7 +13,7 @@ Name: pebble %{!?qtc_make:%define qtc_make make} %{?qtc_builddir:%define _builddir %qtc_builddir} Summary: Support for Pebble watch in SailfishOS -Version: 0.12 +Version: 0.12.1b Release: 1 Group: Qt/Qt License: GPL3 diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index ca0b068..80a5d20 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -1,6 +1,6 @@ Name: pebble Summary: Support for Pebble watch in SailfishOS -Version: 0.12 +Version: 0.12.1b Release: 1 Group: Qt/Qt URL: http://getpebble.com/ -- cgit v1.2.3 From 7c48bbe17251fef1d3045ac8b5b6fe8058b4fb10 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 17:19:46 +0100 Subject: add i18n support to UI app also 'es' translation --- app/app.pro | 10 +- app/qml/pages/WatchPage.qml | 4 +- app/translations/pebble-es.ts | 254 ++++++++++++++++++++++++++++++++++++++++++ app/translations/pebble.ts | 253 +++++++++++++++++++++++++++++++++++++++++ rpm/pebble.spec | 1 + rpm/pebble.yaml | 1 + 6 files changed, 518 insertions(+), 5 deletions(-) create mode 100644 app/translations/pebble-es.ts create mode 100644 app/translations/pebble.ts (limited to 'rpm') diff --git a/app/app.pro b/app/app.pro index 9cc2d09..ca03ab1 100644 --- a/app/app.pro +++ b/app/app.pro @@ -23,9 +23,13 @@ OTHER_FILES += \ qml/pages/ManagerPage.qml \ qml/pages/WatchPage.qml \ qml/pages/AboutPage.qml \ + qml/pages/InstallAppDialog.qml \ + qml/pages/AppConfigDialog.qml \ qml/pebble.qml \ qml/images/* \ + translations/*.ts \ pebble.desktop \ - pebble.png \ - qml/pages/InstallAppDialog.qml \ - qml/pages/AppConfigDialog.qml + pebble.png + +CONFIG += sailfishapp_i18n +TRANSLATIONS += translations/pebble-es.ts diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 9096df6..2d69306 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -60,7 +60,7 @@ Page { Button { - text: "Ping" + text: qsTr("Ping") width: parent.width / 2 onClicked: { pebbled.ping(66) @@ -68,7 +68,7 @@ Page { } Button { - text: "Sync Time" + text: qsTr("Sync Time") width: parent.width / 2 onClicked: { pebbled.time() diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts new file mode 100644 index 0000000..7332e4c --- /dev/null +++ b/app/translations/pebble-es.ts @@ -0,0 +1,254 @@ + + + + + AboutPage + + + Version + Versión + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + ¿Errores? + + + + AppConfigDialog + + + No configuration settings available + No hay opciones disponibles para configurar + + + + CoverPage + + + connected + conectado + + + + disconnected + desconectado + + + + InstallAppDialog + + + Install app + Instalar app + + + + Install + Instalar + + + + ManagerPage + + + About + Acerca de + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + Buscando el reloj +Si esto tarda mucho, comprueba que el reloj esté emparejado correctamente. + + + + Service + Servicio + + + + Enabled + Habilitado + + + + Automatic startup + Inicio automático + + + + Manual startup + Inicio manual + + + + Active + Activo + + + + Running + Ejecutándose + + + + Dead + Detenido + + + + Connection + Conexión + + + + Connected + Conectado + + + + Disconnected + Desconectado + + + + Settings + Configuración + + + + Forward phone calls + Transferir llamadas + + + + Silent when connected + Modo silencio automático + + + + Sets phone profile to "silent" when Pebble is connected + Activa el modo silencio cuando se conecte un Pebble + + + + Transliterate messages + Transliterar mensajes + + + + Messages are transliterated to ASCII before sending to Pebble + Codifica los mensajes entrates a ASCII antes de enviarlos a Pebble + + + + Notifications + Notificaciones + + + + Messaging + Mensajería + + + + SMS and IM + SMS y chat + + + + Missed call + Llamadas perdidas + + + + Emails + Correos electrónicos + + + + Mitakuuluu + + + + + Twitter + Twitter + + + + Facebook + Facebook + + + + Other notifications + Resto de notificaciones + + + + All notifications + Todas las notificaciones + + + + WatchPage + + + Ping + Ping + + + + Sync Time + Ajustar hora + + + + Installed applications + Aplicaciones instaladas + + + + Uninstalling + Desinstalando + + + + (empty slot) + (hueco libre) + + + + (slot in use by unknown app) + (hueco en uso) + + + + Install app... + Instalar app... + + + + Configure... + Configurar... + + + + Uninstall + Desinstalar + + + diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts new file mode 100644 index 0000000..3cc8b6d --- /dev/null +++ b/app/translations/pebble.ts @@ -0,0 +1,253 @@ + + + + + AboutPage + + + Version + + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + + + + + AppConfigDialog + + + No configuration settings available + + + + + CoverPage + + + connected + + + + + disconnected + + + + + InstallAppDialog + + + Install app + + + + + Install + + + + + ManagerPage + + + About + + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + + + + + Service + + + + + Enabled + + + + + Automatic startup + + + + + Manual startup + + + + + Active + + + + + Running + + + + + Dead + + + + + Connection + + + + + Connected + + + + + Disconnected + + + + + Settings + + + + + Forward phone calls + + + + + Silent when connected + + + + + Sets phone profile to "silent" when Pebble is connected + + + + + Transliterate messages + + + + + Messages are transliterated to ASCII before sending to Pebble + + + + + Notifications + + + + + Messaging + + + + + SMS and IM + + + + + Missed call + + + + + Emails + + + + + Mitakuuluu + + + + + Twitter + + + + + Facebook + + + + + Other notifications + + + + + All notifications + + + + + WatchPage + + + Ping + + + + + Sync Time + + + + + Installed applications + + + + + Uninstalling + + + + + (empty slot) + + + + + (slot in use by unknown app) + + + + + Install app... + + + + + Configure... + + + + + Uninstall + + + + diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 8779710..9fb9575 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -81,6 +81,7 @@ systemctl --user daemon-reload %{_bindir} %{_datadir}/%{name}/qml %{_datadir}/%{name}/js +%{_datadir}/%{name}/translations %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/86x86/apps/%{name}.png %{_libdir}/systemd/user/%{name}d.service diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index 80a5d20..c788c27 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -32,6 +32,7 @@ Files: - '%{_bindir}' - '%{_datadir}/%{name}/qml' - '%{_datadir}/%{name}/js' +- '%{_datadir}/%{name}/translations' - '%{_datadir}/applications/%{name}.desktop' - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' - '%{_libdir}/systemd/user/%{name}d.service' -- cgit v1.2.3 From e6ec758b364fcaf9fda35e56740c3fcd7e8fe25e Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 22:36:14 +0100 Subject: use overridePageStackNavigation to ensure accept() works --- app/qml/pages/AppConfigDialog.qml | 2 ++ app/translations/pebble-es.ts | 2 +- app/translations/pebble.ts | 2 +- rpm/pebble.spec | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) (limited to 'rpm') diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml index 304bced..1562985 100644 --- a/app/qml/pages/AppConfigDialog.qml +++ b/app/qml/pages/AppConfigDialog.qml @@ -21,6 +21,8 @@ Dialog { VerticalScrollDecorator { flickable: webview } + overridePageStackNavigation: true + onNavigationRequested: { console.log("appconfig navigation requested to " + request.url); var url = request.url.toString(); diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts index 18b1ec2..0316ecb 100644 --- a/app/translations/pebble-es.ts +++ b/app/translations/pebble-es.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available No hay opciones disponibles para configurar diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts index 222645c..7a78d9d 100644 --- a/app/translations/pebble.ts +++ b/app/translations/pebble.ts @@ -22,7 +22,7 @@ AppConfigDialog - + No configuration settings available diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 9fb9575..02cbe6c 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -13,7 +13,7 @@ Name: pebble %{!?qtc_make:%define qtc_make make} %{?qtc_builddir:%define _builddir %qtc_builddir} Summary: Support for Pebble watch in SailfishOS -Version: 0.12.1b +Version: 0.12.1c Release: 1 Group: Qt/Qt License: GPL3 -- cgit v1.2.3