From 6c09a53d554687dbaa3bb1059ff2ec5ce023c954 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Fri, 12 Sep 2014 19:22:16 +0900 Subject: [PATCH] New multitouch-compatible touch system. --- lib/units/device/plugins/touch.js | 200 ++++---- lib/units/device/resources/minitouch.js | 93 ++++ lib/units/websocket/index.js | 82 ++- lib/wire/seqqueue.js | 66 ++- lib/wire/wire.proto | 37 +- .../components/stf/control/control-service.js | 64 ++- .../components/stf/screen/screen-directive.js | 468 +++++++++++++----- res/app/components/stf/screen/screen.jade | 1 - .../device-control/device-control.css | 10 +- .../device-control/device-control.jade | 4 +- test/wire/seqqueue.js | 116 +++++ vendor/minitouch/armeabi-v7a/minitouch | Bin 0 -> 30016 bytes vendor/minitouch/armeabi/minitouch | Bin 0 -> 30004 bytes vendor/minitouch/mips/minitouch | Bin 0 -> 71172 bytes vendor/minitouch/x86/minitouch | Bin 0 -> 29956 bytes 15 files changed, 861 insertions(+), 280 deletions(-) create mode 100644 lib/units/device/resources/minitouch.js create mode 100644 test/wire/seqqueue.js create mode 100755 vendor/minitouch/armeabi-v7a/minitouch create mode 100755 vendor/minitouch/armeabi/minitouch create mode 100755 vendor/minitouch/mips/minitouch create mode 100755 vendor/minitouch/x86/minitouch diff --git a/lib/units/device/plugins/touch.js b/lib/units/device/plugins/touch.js index ba87242a..f068a622 100644 --- a/lib/units/device/plugins/touch.js +++ b/lib/units/device/plugins/touch.js @@ -1,9 +1,10 @@ +var util = require('util') + var Promise = require('bluebird') var syrup = require('syrup') -var monkey = require('adbkit-monkey') +var split = require('split') var wire = require('../../../wire') -var devutil = require('../../../util/devutil') var logger = require('../../../util/logger') var lifecycle = require('../../../util/lifecycle') var streamutil = require('../../../util/streamutil') @@ -12,111 +13,138 @@ var SeqQueue = require('../../../wire/seqqueue') module.exports = syrup.serial() .dependency(require('../support/adb')) .dependency(require('../support/router')) - .dependency(require('../resources/remote')) - .dependency(require('./display')) - .dependency(require('./data')) - .define(function(options, adb, router, remote, display, data) { + .dependency(require('../resources/minitouch')) + .define(function(options, adb, router, minitouch) { var log = logger.createLogger('device:plugins:touch') var plugin = Object.create(null) - var service = { - port: 2820 - } - - function openService() { + function startService() { log.info('Launching touch service') - return devutil.ensureUnusedPort(adb, options.serial, service.port) + return adb.shell(options.serial, [ + 'exec' + , minitouch.bin + ]) .timeout(10000) - .then(function() { - return adb.shell(options.serial, [ - 'exec' - , remote.bin - , '--lib', remote.lib - , '--listen-input', service.port - ]) - .timeout(10000) - }) .then(function(out) { lifecycle.share('Touch shell', out) streamutil.talk(log, 'Touch shell says: "%s"', out) }) - .then(function() { - return devutil.waitForPort(adb, options.serial, service.port) - .timeout(15000) - }) - .then(function(conn) { - return Promise.promisifyAll(monkey.connectStream(conn)) - }) - .then(function(monkey) { - return lifecycle.share('Touch monkey', monkey) - }) } - function modifyCoords(message) { - message.x = Math.floor(message.x * display.width) - message.y = Math.floor(message.y * display.height) + function connectService() { + function tryConnect(times, delay) { + return adb.openLocal(options.serial, '/data/local/tmp/minitouch.sock') + .timeout(10000) + .then(function(out) { + lifecycle.share('Touch socket', out) + return out + }) + .then(function(out) { + return new Promise(function(resolve, reject) { + out.pipe(split()).on('data', function(line) { + var args = line.toString().split(/ /g) + switch (args[0]) { + case 'v': + out.version = +args[1] + log.info('Touch protocol is version %d', out.version) + break + case '^': + out.maxContacts = args[1] + out.maxX = args[2] + out.maxY = args[3] + out.maxPressure = args[4] + log.info( + 'Touch protocol reports %d contacts in a %dx%d grid ' + + 'with a max pressure of %d' + , out.maxContacts + , out.maxX + , out.maxY + , out.maxPressure + ) + return resolve(out) + default: + return reject(new Error(util.format( + 'Unknown metadata "%s"' + , line + ))) + } + }) + }) + }) + .catch(function(err) { + if (/closed/.test(err.message) && times > 1) { + return Promise.delay(delay) + .then(function() { + return tryConnect(--times, delay * 2) + }) + } + return Promise.reject(err) + }) + } + log.info('Connecting to touch service') + return tryConnect(5, 100) } - return openService() - .then(function(monkey) { - var queue = new SeqQueue() - , pressure = (data && data.touch && data.touch.defaultPressure) || 50 + return startService() + .then(connectService) + .then(function(socket) { + var queue = new SeqQueue(100, 4) - log.info('Setting default pressure to %d', pressure) + function send(command) { + socket.write(command) + } plugin.touchDown = function(point) { - modifyCoords(point) - monkey.sendAsync([ - 'touch down' - , point.x - , point.y - , pressure - ].join(' ')) - .catch(function(err) { - log.error('touchDown failed', err.stack) - }) + send(util.format( + 'd %s %s %s %s\n' + , point.contact + , Math.floor(point.x * socket.maxX) + , Math.floor(point.y * socket.maxY) + , Math.floor((point.pressure || 0.5) * socket.maxPressure) + )) } plugin.touchMove = function(point) { - modifyCoords(point) - monkey.sendAsync([ - 'touch move' - , point.x - , point.y - , pressure - ].join(' ')) - .catch(function(err) { - log.error('touchMove failed', err.stack) - }) + send(util.format( + 'm %s %s %s %s\n' + , point.contact + , Math.floor(point.x * socket.maxX) + , Math.floor(point.y * socket.maxX) + , Math.floor((point.pressure || 0.5) * socket.maxPressure) + )) } plugin.touchUp = function(point) { - modifyCoords(point) - monkey.sendAsync([ - 'touch up' - , point.x - , point.y - , pressure - ].join(' ')) - .catch(function(err) { - log.error('touchUp failed', err.stack) - }) + send(util.format( + 'u %s\n' + , point.contact + )) + } + + plugin.touchCommit = function() { + send('c\n') + } + + plugin.touchReset = function() { + send('r\n') } plugin.tap = function(point) { - modifyCoords(point) - monkey.sendAsync([ - 'tap' - , point.x - , point.y - , pressure - ].join(' ')) - .catch(function(err) { - log.error('tap failed', err.stack) - }) + plugin.touchDown(point) + plugin.touchCommit() + plugin.touchUp(point) + plugin.touchCommit() } router + .on(wire.GestureStartMessage, function(channel, message) { + queue.start(message.seq) + }) + .on(wire.GestureStopMessage, function(channel, message) { + queue.push(message.seq, function() { + queue.stop() + }) + }) .on(wire.TouchDownMessage, function(channel, message) { queue.push(message.seq, function() { plugin.touchDown(message) @@ -131,12 +159,16 @@ module.exports = syrup.serial() queue.push(message.seq, function() { plugin.touchUp(message) }) - - // Reset queue - queue = new SeqQueue() }) - .on(wire.TapMessage, function(channel, message) { - plugin.tap(message) + .on(wire.TouchCommitMessage, function(channel, message) { + queue.push(message.seq, function() { + plugin.touchCommit() + }) + }) + .on(wire.TouchResetMessage, function(channel, message) { + queue.push(message.seq, function() { + plugin.touchReset() + }) }) }) .return(plugin) diff --git a/lib/units/device/resources/minitouch.js b/lib/units/device/resources/minitouch.js new file mode 100644 index 00000000..f847af47 --- /dev/null +++ b/lib/units/device/resources/minitouch.js @@ -0,0 +1,93 @@ +var util = require('util') + +var Promise = require('bluebird') +var syrup = require('syrup') + +var logger = require('../../../util/logger') +var pathutil = require('../../../util/pathutil') +var devutil = require('../../../util/devutil') +var streamutil = require('../../../util/streamutil') + +module.exports = syrup.serial() + .dependency(require('../support/adb')) + .dependency(require('../support/properties')) + .define(function(options, adb, properties) { + var log = logger.createLogger('device:resources:minitouch') + + var resources = { + bin: { + src: pathutil.vendor(util.format( + 'minitouch/%s/minitouch' + , properties['ro.product.cpu.abi'] + )) + , dest: '/data/local/tmp/minitouch' + , comm: 'minitouch' + , mode: 0755 + } + } + + function removeResource(res) { + return adb.shell(options.serial, ['rm', res.dest]) + .timeout(10000) + .then(function(out) { + return streamutil.readAll(out) + }) + .return(res) + } + + function installResource(res) { + return adb.push(options.serial, res.src, res.dest, res.mode) + .timeout(10000) + .then(function(transfer) { + return new Promise(function(resolve, reject) { + transfer.on('error', reject) + transfer.on('end', resolve) + }) + }) + .return(res) + } + + function ensureNotBusy(res) { + return adb.shell(options.serial, [res.dest, '--help']) + .timeout(10000) + .then(function(out) { + // Can be "Text is busy", "text busy" + return streamutil.findLine(out, (/busy/i)) + .timeout(10000) + .then(function() { + log.info('Binary is busy, will retry') + return Promise.delay(1000) + }) + .then(function() { + return ensureNotBusy(res) + }) + .catch(streamutil.NoSuchLineError, function() { + return res + }) + }) + } + + function installAll() { + return Promise.all([ + removeResource(resources.bin).then(installResource).then(ensureNotBusy) + ]) + } + + function stop() { + return devutil.killProcsByComm( + adb + , options.serial + , resources.bin.comm + , resources.bin.dest + ) + .timeout(15000) + } + + return stop() + .then(installAll) + .then(function() { + return { + bin: resources.bin.dest + } + }) + }) diff --git a/lib/units/websocket/index.js b/lib/units/websocket/index.js index dfe5426d..5bd19546 100644 --- a/lib/units/websocket/index.js +++ b/lib/units/websocket/index.js @@ -82,19 +82,6 @@ module.exports = function(options) { sub.unsubscribe(channel) } - function createTouchHandler(Klass) { - return function(channel, data) { - push.send([ - channel - , wireutil.envelope(new Klass( - data.seq - , data.x - , data.y - )) - ]) - } - } - function createKeyHandler(Klass) { return function(channel, data) { push.send([ @@ -268,10 +255,71 @@ module.exports = function(options) { dbapi.resetUserSettings(user.email) }) // Touch events - .on('input.touchDown', createTouchHandler(wire.TouchDownMessage)) - .on('input.touchMove', createTouchHandler(wire.TouchMoveMessage)) - .on('input.touchUp', createTouchHandler(wire.TouchUpMessage)) - .on('input.tap', createTouchHandler(wire.TapMessage)) + .on('input.touchDown', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.TouchDownMessage( + data.seq + , data.contact + , data.x + , data.y + , data.pressure + )) + ]) + }) + .on('input.touchMove', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.TouchMoveMessage( + data.seq + , data.contact + , data.x + , data.y + , data.pressure + )) + ]) + }) + .on('input.touchUp', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.TouchUpMessage( + data.seq + , data.contact + )) + ]) + }) + .on('input.touchCommit', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.TouchCommitMessage( + data.seq + )) + ]) + }) + .on('input.touchReset', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.TouchResetMessage( + data.seq + )) + ]) + }) + .on('input.gestureStart', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.GestureStartMessage( + data.seq + )) + ]) + }) + .on('input.gestureStop', function(channel, data) { + push.send([ + channel + , wireutil.envelope(new wire.GestureStopMessage( + data.seq + )) + ]) + }) // Key events .on('input.keyDown', createKeyHandler(wire.KeyDownMessage)) .on('input.keyUp', createKeyHandler(wire.KeyUpMessage)) diff --git a/lib/wire/seqqueue.js b/lib/wire/seqqueue.js index 4e5fc499..812854d1 100644 --- a/lib/wire/seqqueue.js +++ b/lib/wire/seqqueue.js @@ -1,25 +1,61 @@ -function SeqQueue() { - this.queue = [] - this.seq = 0 +function SeqQueue(size, maxWaiting) { + this.lo = 0 + this.size = size + this.maxWaiting = maxWaiting + this.waiting = 0 + this.list = new Array(size) + this.locked = true +} + +SeqQueue.prototype.start = function(seq) { + this.locked = false + this.lo = seq + this.maybeConsume() +} + +SeqQueue.prototype.stop = function() { + this.locked = true + this.maybeConsume() } SeqQueue.prototype.push = function(seq, handler) { - this.queue[seq] = handler - this.maybeDequeue() + if (seq >= this.size) { + return + } + + this.list[seq] = handler + this.waiting += 1 + this.maybeConsume() } -SeqQueue.prototype.done = function(seq, handler) { - this.queue[seq] = handler - this.maybeDequeue() -} +SeqQueue.prototype.maybeConsume = function() { + if (this.locked) { + return + } -SeqQueue.prototype.maybeDequeue = function() { - var handler + while (this.waiting) { + // Did we reach the end of the loop? If so, start from the beginning. + if (this.lo === this.size) { + this.lo = 0 + } - while ((handler = this.queue[this.seq])) { - this.queue[this.seq] = void 0 - handler() - this.seq += 1 + var handler = this.list[this.lo] + // Have we received it yet? + if (handler) { + this.list[this.lo] = void 0 + handler() + this.lo += 1 + this.waiting -= 1 + } + // Are we too much behind? If so, just move on. + else if (this.waiting >= this.maxWaiting) { + this.lo += 1 + this.waiting -= 1 + } + // We don't have it yet, stop. + else { + break + } } } diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 6f74e6d2..718aba70 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -25,10 +25,13 @@ enum MessageType { ProbeMessage = 17; ShellCommandMessage = 18; ShellKeepAliveMessage = 19; - TapMessage = 20; TouchDownMessage = 21; TouchMoveMessage = 22; TouchUpMessage = 23; + TouchCommitMessage = 65; + TouchResetMessage = 66; + GestureStartMessage = 67; + GestureStopMessage = 68; TransactionDoneMessage = 24; TransactionProgressMessage = 25; TypeMessage = 26; @@ -249,25 +252,39 @@ message PhysicalIdentifyMessage { message TouchDownMessage { required uint32 seq = 1; - required float x = 2; - required float y = 3; + required uint32 contact = 2; + required float x = 3; + required float y = 4; + optional float pressure = 5; } message TouchMoveMessage { required uint32 seq = 1; - required float x = 2; - required float y = 3; + required uint32 contact = 2; + required float x = 3; + required float y = 4; + optional float pressure = 5; } message TouchUpMessage { required uint32 seq = 1; - required float x = 2; - required float y = 3; + required uint32 contact = 2; } -message TapMessage { - required float x = 1; - required float y = 2; +message TouchCommitMessage { + required uint32 seq = 1; +} + +message TouchResetMessage { + required uint32 seq = 1; +} + +message GestureStartMessage { + required uint32 seq = 1; +} + +message GestureStopMessage { + required uint32 seq = 1; } message TypeMessage { diff --git a/res/app/components/stf/control/control-service.js b/res/app/components/stf/control/control-service.js index 7740bc66..27a2322b 100644 --- a/res/app/components/stf/control/control-service.js +++ b/res/app/components/stf/control/control-service.js @@ -21,16 +21,6 @@ module.exports = function ControlServiceFactory( return tx.promise } - function touchSender(type) { - return function(seq, x, y) { - sendOneWay(type, { - seq: seq - , x: x - , y: y - }) - } - } - function keySender(type, fixedKey) { return function(key) { if (typeof key === 'string') { @@ -49,10 +39,56 @@ module.exports = function ControlServiceFactory( } } - this.touchDown = touchSender('input.touchDown') - this.touchMove = touchSender('input.touchMove') - this.touchUp = touchSender('input.touchUp') - this.tap = touchSender('input.tap') + this.gestureStart = function(seq) { + sendOneWay('input.gestureStart', { + seq: seq + }) + } + + this.gestureStop = function(seq) { + sendOneWay('input.gestureStop', { + seq: seq + }) + } + + this.touchDown = function(seq, contact, x, y, pressure) { + sendOneWay('input.touchDown', { + seq: seq + , contact: contact + , x: x + , y: y + , pressure: pressure + }) + } + + this.touchMove = function(seq, contact, x, y, pressure) { + sendOneWay('input.touchMove', { + seq: seq + , contact: contact + , x: x + , y: y + , pressure: pressure + }) + } + + this.touchUp = function(seq, contact) { + sendOneWay('input.touchUp', { + seq: seq + , contact: contact + }) + } + + this.touchCommit = function(seq) { + sendOneWay('input.touchCommit', { + seq: seq + }) + } + + this.touchReset = function(seq) { + sendOneWay('input.touchReset', { + seq: seq + }) + } this.keyDown = keySender('input.keyDown') this.keyUp = keySender('input.keyUp') diff --git a/res/app/components/stf/screen/screen-directive.js b/res/app/components/stf/screen/screen-directive.js index 734cccd7..a1ec566f 100644 --- a/res/app/components/stf/screen/screen-directive.js +++ b/res/app/components/stf/screen/screen-directive.js @@ -4,31 +4,52 @@ var _ = require('lodash') module.exports = function DeviceScreenDirective($document, ScalingService, VendorUtil, PageVisibilityService, BrowserInfo, $timeout) { return { - restrict: 'E', - template: require('./screen.jade'), - link: function (scope, element) { + restrict: 'E' + , template: require('./screen.jade') + , scope: { + control: '&' + , device: '&' + } + , link: function (scope, element) { + var device = scope.device() + , control = scope.control() + var canvas = element.find('canvas')[0] + , input = element.find('input') + var imageRender = new FastImageRender(canvas, { render: 'canvas', timeout: 3000 }) var guestDisplayDensity = setDisplayDensity(1.5) //var guestDisplayRotation = 0 - var finger = element.find('span') - var input = element.find('input') - var boundingWidth = 0 // TODO: cache inside FastImageRender? - var boundingHeight = 0 - var cachedBoundingWidth = 0 - var cachedBoundingHeight = 0 - var cachedImageWidth = 0 - var cachedImageHeight = 0 - var cachedRotation = 0 - var rotation = 0 + var loading = false - var scaler - var seq = 0 var cssTransform = VendorUtil.style(['transform', 'webkitTransform']) + var screen = scope.screen = { + scaler: ScalingService.coordinator( + device.display.width, device.display.height + ) + , rotation: 0 + , bounds: { + x: 0 + , y: 0 + , w: 0 + , h: 0 + } + } + + var cachedScreen = { + rotation: 0 + , bounds: { + x: 0 + , y: 0 + , w: 0 + , h: 0 + } + } + // NOTE: instead of fa-pane-resize, a fa-child-pane-resize could be better var onPanelResizeThrottled = _.throttle(updateBounds, 16) scope.$on('fa-pane-resize', onPanelResizeThrottled) @@ -42,88 +63,20 @@ module.exports = function DeviceScreenDirective($document, ScalingService, return guestDisplayDensity } - function sendTouch(type, e) { - var x = e.offsetX || e.layerX || 0 - var y = e.offsetY || e.layerY || 0 - var r = scope.device.display.rotation - - if (BrowserInfo.touch) { - if (e.touches && e.touches.length) { - x = e.touches[0].pageX - y = e.touches[0].pageY - } else if (e.changedTouches && e.changedTouches.length) { - x = e.changedTouches[0].pageX - y = e.changedTouches[0].pageY - } - } - - var scaled = scaler.coords(boundingWidth, boundingHeight, x, y, r) - - finger[0].style[cssTransform] = - 'translate3d(' + x + 'px,' + y + 'px,0)' - - scope.control[type]( - seq++, scaled.xP, scaled.yP - ) - } - - function stopTouch() { - element.removeClass('fingering') - if (BrowserInfo.touch) { - element.unbind('touchmove', moveListener) - $document.unbind('touchend', upListener) - $document.unbind('touchleave', upListener) - } else { - element.unbind('mousemove', moveListener) - $document.unbind('mouseup', upListener) - $document.unbind('mouseleave', upListener) - } - seq = 0 - } - function updateBounds() { - boundingWidth = element[0].offsetWidth - boundingHeight = element[0].offsetHeight + screen.bounds.w = element[0].offsetWidth + screen.bounds.h = element[0].offsetHeight // TODO: element is an object HTMLUnknownElement in IE9 // Developer error, let's try to reduce debug time - if (!boundingWidth || !boundingHeight) { + if (!screen.bounds.w || !screen.bounds.h) { throw new Error( 'Unable to update display size; container must have dimensions' ) } } - function downListener(e) { - e.preventDefault() - if (!BrowserInfo.touch) { - input[0].focus() - element.addClass('fingering') - } - - sendTouch('touchDown', e) - - if (BrowserInfo.touch) { - element.bind('touchmove', moveListener) - $document.bind('touchend', upListener) - $document.bind('touchleave', upListener) - } else { - element.bind('mousemove', moveListener) - $document.bind('mouseup', upListener) - $document.bind('mouseleave', upListener) - } - } - - function moveListener(e) { - sendTouch('touchMove', e) - } - - function upListener(e) { - sendTouch('touchUp', e) - stopTouch() - } - function isChangeCharsetKey(e) { // Add any special key here for changing charset //console.log('e', e) @@ -162,7 +115,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService, if (isChangeCharsetKey(e)) { specialKey = true - scope.control.keyPress('switch_charset') + control.keyPress('switch_charset') } if (specialKey) { @@ -173,58 +126,58 @@ module.exports = function DeviceScreenDirective($document, ScalingService, } function keydownListener(e) { - scope.control.keyDown(e.keyCode) + control.keyDown(e.keyCode) } function keyupListener(e) { if (!keyupSpecialKeys(e)) { - scope.control.keyUp(e.keyCode) + control.keyUp(e.keyCode) } } function keypressListener(e) { e.preventDefault() // no need to change value - scope.control.type(String.fromCharCode(e.charCode)) + control.type(String.fromCharCode(e.charCode)) } function pasteListener(e) { e.preventDefault() // no need to change value - scope.control.paste(e.clipboardData.getData('text/plain')) + control.paste(e.clipboardData.getData('text/plain')) } function copyListener(e) { - scope.control.getClipboardContent() + control.getClipboardContent() // @TODO: OK, this basically copies last clipboard content - if (scope.control.clipboardContent) { - e.clipboardData.setData("text/plain", scope.control.clipboardContent) + if (control.clipboardContent) { + e.clipboardData.setData("text/plain", control.clipboardContent) } e.preventDefault() } scope.retryLoadingScreen = function () { if (scope.displayError === 'secure') { - scope.control.home() + control.home() } $timeout(maybeLoadScreen, 3000) } function maybeLoadScreen() { - if (!loading && scope.$parent.showScreen && scope.device) { + if (!loading && scope.$parent.showScreen && device) { var w, h - switch (rotation) { + switch (screen.rotation) { case 0: case 180: - w = boundingWidth - h = boundingHeight + w = screen.bounds.w + h = screen.bounds.h break case 90: case 270: - w = boundingHeight - h = boundingWidth + w = screen.bounds.h + h = screen.bounds.w break } loading = true - imageRender.load(scope.device.display.url + + imageRender.load(device.display.url + '?width=' + Math.ceil(w * guestDisplayDensity) + '&height=' + Math.ceil(h * guestDisplayDensity) + '&time=' + Date.now() @@ -233,35 +186,37 @@ module.exports = function DeviceScreenDirective($document, ScalingService, } function on() { - scaler = ScalingService.coordinator( - scope.device.display.width, scope.device.display.height - ) - imageRender.onLoad = function (image) { + var cachedImageWidth = 0 + , cachedImageHeight = 0 + loading = false if (scope.$parent.showScreen) { + screen.rotation = device.display.rotation // Check to set the size only if updated - if (cachedBoundingWidth !== boundingWidth || - cachedBoundingHeight !== boundingHeight || + if (cachedScreen.bounds.w !== screen.bounds.w || + cachedScreen.bounds.h !== screen.bounds.h || cachedImageWidth !== image.width || cachedImageHeight !== image.height || - cachedRotation !== rotation) { + cachedScreen.rotation !== screen.rotation) { - cachedBoundingWidth = boundingWidth - cachedBoundingHeight = boundingHeight + cachedScreen.bounds.w = screen.bounds.w + cachedScreen.bounds.h = screen.bounds.h cachedImageWidth = image.width cachedImageHeight = image.height - cachedRotation = rotation + cachedScreen.rotation = screen.rotation imageRender.canvasWidth = cachedImageWidth imageRender.canvasHeight = cachedImageHeight - var projectedSize = scaler.projectedSize( - boundingWidth, boundingHeight, rotation + var projectedSize = screen.scaler.projectedSize( + screen.bounds.w + , screen.bounds.h + , screen.rotation ) imageRender.canvasStyleWidth = projectedSize.width @@ -270,7 +225,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService, // @todo Make sure that each position is able to rotate smoothly // to the next one. This current setup doesn't work if rotation // changes from 180 to 270 (it will do a reverse rotation). - switch (rotation) { + switch (screen.rotation) { case 0: canvas.style[cssTransform] = 'translate(-50%, -50%) rotate(0deg)' break @@ -321,30 +276,17 @@ module.exports = function DeviceScreenDirective($document, ScalingService, input.bind('keypress', keypressListener) input.bind('paste', pasteListener) input.bind('copy', copyListener) - - if (BrowserInfo.touch) { - element.bind('touchstart', downListener) - } else { - element.bind('mousedown', downListener) - } - } function off() { imageRender.onLoad = imageRender.onError = null loading = false - stopTouch() + input.unbind('keydown', keydownListener) input.unbind('keyup', keyupListener) input.unbind('keypress', keypressListener) input.unbind('paste', pasteListener) input.unbind('copy', copyListener) - - if (BrowserInfo.touch) { - element.unbind('touchstart', downListener) - } else { - element.unbind('mousedown', downListener) - } } scope.$watch('$parent.showScreen', function (val) { @@ -357,7 +299,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService, }) function checkEnabled() { - var using = scope.device && scope.device.using + var using = device && device.using if (using && !PageVisibilityService.hidden) { on() } @@ -369,22 +311,276 @@ module.exports = function DeviceScreenDirective($document, ScalingService, scope.$watch('device.using', checkEnabled) scope.$on('visibilitychange', checkEnabled) - scope.$watch('device.display.rotation', function (r) { - rotation = r || 0 - }) - scope.$on('guest-portrait', function () { - scope.control.rotate(0) + control.rotate(0) updateBounds() }) scope.$on('guest-landscape', function () { - scope.control.rotate(90) + control.rotate(90) setDisplayDensity(2) updateBounds() }) scope.$on('$destroy', off) + + // @todo Move everything below this line elsewhere. + var slots = [] + , slotted = Object.create(null) + , fingers = [] + , seq = -1 + , cycle = 100 + + function nextSeq() { + return ++seq >= cycle ? (seq = 0) : seq + } + + function createSlots() { + for (var i = 9; i >= 0; --i) { + slots.push(i) + fingers.unshift(createFinger(i)) + } + } + + function activateFinger(index, x, y, pressure) { + var scale = 0.5 + pressure + fingers[index].classList.add('active') + fingers[index].style[cssTransform] = + 'translate3d(' + x + 'px,' + y + 'px,0) ' + + 'scale(' + scale + ',' + scale + ')' + } + + function deactivateFinger(index) { + fingers[index].classList.remove('active') + } + + function deactivateFingers() { + for (var i = 0, l = fingers.length; i < l; ++i) { + fingers[i].classList.remove('active') + } + } + + function createFinger(index) { + var el = document.createElement('span') + el.className = 'finger finger-' + index + return el + } + + function calculateBounds() { + var el = element[0] + + screen.bounds.w = el.offsetWidth + screen.bounds.h = el.offsetHeight + screen.bounds.x = 0 + screen.bounds.y = 0 + + while (el.offsetParent) { + screen.bounds.x += el.offsetLeft + screen.bounds.y += el.offsetTop + el = el.offsetParent + } + } + + function mouseDownListener(e) { + e.preventDefault() + + calculateBounds() + startMousing() + + var x = e.pageX - screen.bounds.x + , y = e.pageY - screen.bounds.y + , pressure = 0.5 + , scaled = screen.scaler.coords( + screen.bounds.w + , screen.bounds.h + , x + , y + , screen.rotation + ) + + control.touchDown(nextSeq(), 0, scaled.xP, scaled.yP, pressure) + control.touchCommit(nextSeq()) + + activateFinger(0, x, y, pressure) + + element.bind('mousemove', mouseMoveListener) + $document.bind('mouseup', mouseUpListener) + $document.bind('mouseleave', mouseUpListener) + } + + function mouseMoveListener(e) { + e.preventDefault() + + var x = e.pageX - screen.bounds.x + , y = e.pageY - screen.bounds.y + , pressure = 0.5 + , scaled = screen.scaler.coords( + screen.bounds.w + , screen.bounds.h + , x + , y + , screen.rotation + ) + + control.touchMove(nextSeq(), 0, scaled.xP, scaled.yP, pressure) + control.touchCommit(nextSeq()) + + activateFinger(0, x, y, pressure) + } + + function mouseUpListener(e) { + e.preventDefault() + + control.touchUp(nextSeq(), 0) + control.touchCommit(nextSeq()) + + deactivateFinger(0) + + stopMousing() + } + + function startMousing() { + control.gestureStart(nextSeq()) + } + + function stopMousing() { + element.unbind('mousemove', mouseMoveListener) + $document.unbind('mouseup', mouseUpListener) + $document.unbind('mouseleave', mouseUpListener) + deactivateFingers() + control.gestureStop(nextSeq()) + } + + function touchStartListener(e) { + e.preventDefault() + + calculateBounds() + + if (e.touches.length === e.changedTouches.length) { + startTouching() + } + + var currentTouches = Object.create(null) + var i, l + + for (i = 0, l = e.touches.length; i < l; ++i) { + currentTouches[e.touches[i].identifier] = 1; + } + + function maybeLostTouchEnd(id) { + return !(id in currentTouches) + } + + // We might have lost a touchend event due to various edge cases + // (literally) such as dragging from the bottom of the screen so that + // the control center appears. If so, let's ask for a reset. + if (Object.keys(slotted).some(maybeLostTouchEnd)) { + Object.keys(slotted).forEach(function(id) { + slots.push(slotted[id]) + delete slotted[id] + }) + slots.sort().reverse() + control.touchReset(nextSeq()) + deactivateFingers() + } + + if (!slots.length) { + // This should never happen but who knows... + throw new Error('Ran out of multitouch slots') + } + + for (i = 0, l = e.changedTouches.length; i < l; ++i) { + var touch = e.changedTouches[i] + , slot = slots.pop() + , x = touch.pageX - screen.bounds.x + , y = touch.pageY - screen.bounds.y + , pressure = touch.force || 0.5 + , scaled = screen.scaler.coords( + screen.bounds.w + , screen.bounds.h + , x + , y + , screen.rotation + ) + + slotted[touch.identifier] = slot + control.touchDown(nextSeq(), slot, scaled.xP, scaled.yP, pressure) + activateFinger(slot, x, y, pressure) + } + + element.bind('touchmove', touchMoveListener) + $document.bind('touchend', touchEndListener) + $document.bind('touchleave', touchEndListener) + + control.touchCommit(nextSeq()) + } + + function touchMoveListener(e) { + e.preventDefault() + + for (var i = 0, l = e.changedTouches.length; i < l; ++i) { + var touch = e.changedTouches[i] + , slot = slotted[touch.identifier] + , x = touch.pageX - screen.bounds.x + , y = touch.pageY - screen.bounds.y + , pressure = touch.force || 0.5 + , scaled = screen.scaler.coords( + screen.bounds.w + , screen.bounds.h + , x + , y + , screen.rotation + ) + + control.touchMove(nextSeq(), slot, scaled.xP, scaled.yP, pressure) + activateFinger(slot, x, y, pressure) + } + + control.touchCommit(nextSeq()) + } + + function touchEndListener(e) { + var foundAny = false + + for (var i = 0, l = e.changedTouches.length; i < l; ++i) { + var touch = e.changedTouches[i] + , slot = slotted[touch.identifier] + if (slot === void 0) { + // We've already disposed of the contact. We may have gotten a + // touchend event for the same contact twice. + continue + } + delete slotted[touch.identifier] + slots.push(slot) + control.touchUp(nextSeq(), slot) + deactivateFinger(slot) + foundAny = true + } + + if (foundAny) { + control.touchCommit(nextSeq()) + if (!e.touches.length) { + stopTouching() + } + } + } + + function startTouching() { + control.gestureStart(nextSeq()) + } + + function stopTouching() { + element.unbind('touchmove', touchMoveListener) + $document.unbind('touchend', touchEndListener) + $document.unbind('touchleave', touchEndListener) + deactivateFingers() + control.gestureStop(nextSeq()) + } + + element.on('touchstart', touchStartListener) + element.on('mousedown', mouseDownListener) + + createSlots() } } } diff --git a/res/app/components/stf/screen/screen.jade b/res/app/components/stf/screen/screen.jade index db65acfd..dceb2586 100644 --- a/res/app/components/stf/screen/screen.jade +++ b/res/app/components/stf/screen/screen.jade @@ -13,4 +13,3 @@ div(ng-if='displayError').screen-error i.fa.fa-refresh span(translate) Retry input(type='password', tabindex='40', accesskey='C', autocorrect='off', autocapitalize='off', focus-element='$root.screenFocus') -span.finger diff --git a/res/app/control-panes/device-control/device-control.css b/res/app/control-panes/device-control/device-control.css index 4a0e71e7..b045ecb7 100644 --- a/res/app/control-panes/device-control/device-control.css +++ b/res/app/control-panes/device-control/device-control.css @@ -11,6 +11,9 @@ } device-screen { + width: 100%; + height: 100%; + background: gray; position: relative; display: block; overflow: hidden; @@ -24,6 +27,11 @@ device-screen { user-select: none; } +device-screen .screen-touch { + width: 100%; + height: 100%; +} + device-screen canvas { position: absolute; top: 50%; @@ -49,7 +57,7 @@ device-screen .finger { display: none; } -device-screen.fingering .finger { +device-screen .finger.active { display: block; } diff --git a/res/app/control-panes/device-control/device-control.jade b/res/app/control-panes/device-control/device-control.jade index 4d49d435..b7b2efc5 100644 --- a/res/app/control-panes/device-control/device-control.jade +++ b/res/app/control-panes/device-control/device-control.jade @@ -36,8 +36,8 @@ i.fa.fa-times .as-row.fill-height - div(ng-controller='DeviceScreenCtrl', ng-file-drop='installFile($files)', ng-file-drag-over-class='dragover').fill-height - device-screen(style='width: 100%; height: 100%; background: gray') + div(ng-controller='DeviceScreenCtrl', ng-if='device', ng-file-drop='installFile($files)', ng-file-drag-over-class='dragover').fill-height + device-screen(device='device', control='control') .stf-vnc-bottom.as-row .controls diff --git a/test/wire/seqqueue.js b/test/wire/seqqueue.js new file mode 100644 index 00000000..193e8ab8 --- /dev/null +++ b/test/wire/seqqueue.js @@ -0,0 +1,116 @@ +var sinon = require('sinon') +var chai = require('chai') +chai.use(require('sinon-chai')) +var expect = chai.expect + +var SeqQueue = require('../../lib/wire/seqqueue') + +describe('SeqQueue', function() { + it('should wait until started', function() { + var spy = sinon.spy() + var q = new SeqQueue(10, Infinity) + q.push(0, spy) + expect(spy).to.not.have.been.called + q.start(0) + expect(spy).to.have.been.calledOnce + }) + + it('should call first item immediately if started', function() { + var spy = sinon.spy() + var q = new SeqQueue(10, Infinity) + q.start(0) + q.push(0, spy) + expect(spy).to.have.been.calledOnce + }) + + it('should call items in seq order', function() { + var spy1 = sinon.spy() + var spy2 = sinon.spy() + var spy3 = sinon.spy() + var spy4 = sinon.spy() + var q = new SeqQueue(10, Infinity) + q.start(0) + q.push(0, spy1) + q.push(1, spy2) + q.push(2, spy3) + q.push(3, spy4) + expect(spy1).to.have.been.calledOnce + expect(spy2).to.have.been.calledOnce + expect(spy3).to.have.been.calledOnce + expect(spy4).to.have.been.calledOnce + }) + + it('should not call item until seq reaches it', function() { + var spy1 = sinon.spy() + var spy2 = sinon.spy() + var spy3 = sinon.spy() + var spy4 = sinon.spy() + var q = new SeqQueue(10, Infinity) + q.start(0) + q.push(0, spy1) + q.push(3, spy4) + expect(spy1).to.have.been.calledOnce + expect(spy4).to.not.have.been.called + q.push(2, spy3) + expect(spy3).to.not.have.been.called + expect(spy4).to.not.have.been.called + q.push(1, spy2) + expect(spy2).to.have.been.calledOnce + expect(spy3).to.have.been.calledOnce + expect(spy4).to.have.been.calledOnce + }) + + it('should should start skipping items if too far behind', function() { + var spy1 = sinon.spy() + var spy2 = sinon.spy() + var spy3 = sinon.spy() + var spy4 = sinon.spy() + var q = new SeqQueue(10, 2) + q.start(0) + q.push(0, spy1) + q.push(2, spy3) + q.push(3, spy4) + q.push(1, spy2) + expect(spy1).to.have.been.calledOnce + expect(spy2).to.not.have.been.called + expect(spy3).to.have.been.calledOnce + expect(spy4).to.have.been.calledOnce + }) + + it('should should start a new queue', function() { + var spy1 = sinon.spy() + var spy2 = sinon.spy() + var spy3 = sinon.spy() + var spy4 = sinon.spy() + var q = new SeqQueue(2, Infinity) + q.start(0) + q.push(0, spy1) + q.push(1, spy2) + q.stop(2) + q.start(0) + q.push(0, spy3) + q.push(1, spy4) + expect(spy1).to.have.been.calledOnce + expect(spy2).to.have.been.calledOnce + expect(spy3).to.have.been.calledOnce + expect(spy4).to.have.been.calledOnce + }) + + it('should should start a new queue on even on 1 length', function() { + var spy1 = sinon.spy() + var spy2 = sinon.spy() + var spy3 = sinon.spy() + var q = new SeqQueue(1, Infinity) + q.start(0) + q.push(0, spy1) + q.stop(1) + q.start(0) + q.push(0, spy2) + q.stop(1) + q.start(0) + q.push(0, spy3) + expect(spy1).to.have.been.calledOnce + expect(spy2).to.have.been.calledOnce + expect(spy3).to.have.been.calledOnce + }) +}) diff --git a/vendor/minitouch/armeabi-v7a/minitouch b/vendor/minitouch/armeabi-v7a/minitouch new file mode 100755 index 0000000000000000000000000000000000000000..6b01401c1e6c09f6c80e95e427616733eee2ecab GIT binary patch literal 30016 zcmeHwe|%KcweLPNlVtJ(7$7L(4^9%0AdpOc0Fk0*l9`hXnPi5{gn+d-3CTdBA&E&q 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 N2)e#-19h10e*y3Gbin`s literal 0 HcmV?d00001 diff --git a/vendor/minitouch/armeabi/minitouch b/vendor/minitouch/armeabi/minitouch new file mode 100755 index 0000000000000000000000000000000000000000..5e4a0a47b8e087c5bc2a48b6182bb729ed764d98 GIT binary patch literal 30004 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 HjPHK|4{%Q> literal 0 HcmV?d00001 diff --git a/vendor/minitouch/mips/minitouch b/vendor/minitouch/mips/minitouch new file mode 100755 index 0000000000000000000000000000000000000000..a1c0a3485383cd7c89ded3a8df3a881134555c63 GIT binary patch literal 71172 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 Hxx)M3p+_ru literal 0 HcmV?d00001 diff --git a/vendor/minitouch/x86/minitouch b/vendor/minitouch/x86/minitouch new file mode 100755 index 0000000000000000000000000000000000000000..332de36efc0841074c3b3825b771cd7adda512c2 GIT binary patch literal 29956 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�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 literal 0 HcmV?d00001