mirror of
https://github.com/openstf/stf
synced 2025-10-05 02:29:26 +02:00
Merge branch 'vnc'
This commit is contained in:
commit
ae449a631a
14 changed files with 927 additions and 60 deletions
|
@ -10,6 +10,7 @@ addons:
|
||||||
- libprotobuf-dev
|
- libprotobuf-dev
|
||||||
- graphicsmagick
|
- graphicsmagick
|
||||||
- rethinkdb
|
- rethinkdb
|
||||||
|
- yasm
|
||||||
script:
|
script:
|
||||||
- gulp build
|
- gulp build
|
||||||
before_script:
|
before_script:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM openstf/base:v1.0.2
|
FROM openstf/base:v1.0.4
|
||||||
|
|
||||||
# Sneak the stf executable into $PATH.
|
# Sneak the stf executable into $PATH.
|
||||||
ENV PATH /app/bin:$PATH
|
ENV PATH /app/bin:$PATH
|
||||||
|
|
|
@ -43,6 +43,7 @@ It is currently being used at [CyberAgent](https://www.cyberagent.co.jp/en/) to
|
||||||
* Run any `adb` command locally, including shell access
|
* Run any `adb` command locally, including shell access
|
||||||
* [Android Studio](http://developer.android.com/tools/studio/index.html) and other IDE support, debug your app while watching the device screen on your browser
|
* [Android Studio](http://developer.android.com/tools/studio/index.html) and other IDE support, debug your app while watching the device screen on your browser
|
||||||
* Supports [Chrome remote debug tools](https://developer.chrome.com/devtools/docs/remote-debugging)
|
* Supports [Chrome remote debug tools](https://developer.chrome.com/devtools/docs/remote-debugging)
|
||||||
|
- Experimental VNC support (work in progress)
|
||||||
* Manage your device inventory
|
* Manage your device inventory
|
||||||
- See which devices are connected, offline/unavailable (indicating a weak USB connection), unauthorized or unplugged
|
- See which devices are connected, offline/unavailable (indicating a weak USB connection), unauthorized or unplugged
|
||||||
- See who's using a device
|
- See who's using a device
|
||||||
|
@ -65,14 +66,15 @@ As the product has evolved from an internal tool running in our internal network
|
||||||
* [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots)
|
* [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots)
|
||||||
* [ZeroMQ](http://zeromq.org/) libraries installed
|
* [ZeroMQ](http://zeromq.org/) libraries installed
|
||||||
* [Protocol Buffers](https://github.com/google/protobuf) libraries installed
|
* [Protocol Buffers](https://github.com/google/protobuf) libraries installed
|
||||||
|
* [yasm](http://yasm.tortall.net/) installed (for compiling embedded [libjpeg-turbo](https://github.com/sorccu/node-jpeg-turbo))
|
||||||
* [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) so that Node.js can find the libraries
|
* [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) so that Node.js can find the libraries
|
||||||
|
|
||||||
Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included.
|
Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included in the package.
|
||||||
|
|
||||||
On OS X, you can use [homebrew](http://brew.sh/) to install most of the dependencies:
|
On OS X, you can use [homebrew](http://brew.sh/) to install most of the dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install rethinkdb graphicsmagick zeromq protobuf pkg-config
|
brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows you're on your own. In theory you might be able to get STF installed via [Cygwin](https://www.cygwin.com/) or similar, but we've never tried. In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do.
|
On Windows you're on your own. In theory you might be able to get STF installed via [Cygwin](https://www.cygwin.com/) or similar, but we've never tried. In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do.
|
||||||
|
|
23
lib/cli.js
23
lib/cli.js
|
@ -68,6 +68,10 @@ program
|
||||||
, 'adb connect URL pattern'
|
, 'adb connect URL pattern'
|
||||||
, String
|
, String
|
||||||
, '${publicIp}:${publicPort}')
|
, '${publicIp}:${publicPort}')
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--mute-master'
|
.option('--mute-master'
|
||||||
, 'whether to mute master volume when devices are being used')
|
, 'whether to mute master volume when devices are being used')
|
||||||
.option('--lock-rotation'
|
.option('--lock-rotation'
|
||||||
|
@ -103,6 +107,7 @@ program
|
||||||
, '--connect-push', options.connectPush.join(',')
|
, '--connect-push', options.connectPush.join(',')
|
||||||
, '--screen-port', ports.shift()
|
, '--screen-port', ports.shift()
|
||||||
, '--connect-port', ports.shift()
|
, '--connect-port', ports.shift()
|
||||||
|
, '--vnc-port', ports.shift()
|
||||||
, '--public-ip', options.publicIp
|
, '--public-ip', options.publicIp
|
||||||
, '--group-timeout', options.groupTimeout
|
, '--group-timeout', options.groupTimeout
|
||||||
, '--storage-url', options.storageUrl
|
, '--storage-url', options.storageUrl
|
||||||
|
@ -111,6 +116,7 @@ program
|
||||||
, '--screen-ws-url-pattern', options.screenWsUrlPattern
|
, '--screen-ws-url-pattern', options.screenWsUrlPattern
|
||||||
, '--connect-url-pattern', options.connectUrlPattern
|
, '--connect-url-pattern', options.connectUrlPattern
|
||||||
, '--heartbeat-interval', options.heartbeatInterval
|
, '--heartbeat-interval', options.heartbeatInterval
|
||||||
|
, '--vnc-initial-size', options.vncInitialSize.join('x')
|
||||||
]
|
]
|
||||||
.concat(options.muteMaster ? ['--mute-master'] : [])
|
.concat(options.muteMaster ? ['--mute-master'] : [])
|
||||||
.concat(options.lockRotation ? ['--lock-rotation'] : []))
|
.concat(options.lockRotation ? ['--lock-rotation'] : []))
|
||||||
|
@ -142,6 +148,13 @@ program
|
||||||
.option('--connect-port <port>'
|
.option('--connect-port <port>'
|
||||||
, 'port allocated to adb connect'
|
, 'port allocated to adb connect'
|
||||||
, Number)
|
, Number)
|
||||||
|
.option('--vnc-port <port>'
|
||||||
|
, 'port allocated to vnc'
|
||||||
|
, Number)
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--connect-url-pattern <pattern>'
|
.option('--connect-url-pattern <pattern>'
|
||||||
, 'adb connect URL pattern'
|
, 'adb connect URL pattern'
|
||||||
, String
|
, String
|
||||||
|
@ -193,6 +206,9 @@ program
|
||||||
if (!options.connectPort) {
|
if (!options.connectPort) {
|
||||||
this.missingArgument('--connect-port')
|
this.missingArgument('--connect-port')
|
||||||
}
|
}
|
||||||
|
if (!options.vncPort) {
|
||||||
|
this.missingArgument('--vnc-port')
|
||||||
|
}
|
||||||
if (!options.storageUrl) {
|
if (!options.storageUrl) {
|
||||||
this.missingArgument('--storage-url')
|
this.missingArgument('--storage-url')
|
||||||
}
|
}
|
||||||
|
@ -213,6 +229,8 @@ program
|
||||||
, screenPort: options.screenPort
|
, screenPort: options.screenPort
|
||||||
, connectUrlPattern: options.connectUrlPattern
|
, connectUrlPattern: options.connectUrlPattern
|
||||||
, connectPort: options.connectPort
|
, connectPort: options.connectPort
|
||||||
|
, vncPort: options.vncPort
|
||||||
|
, vncInitialSize: options.vncInitialSize
|
||||||
, heartbeatInterval: options.heartbeatInterval
|
, heartbeatInterval: options.heartbeatInterval
|
||||||
, muteMaster: options.muteMaster
|
, muteMaster: options.muteMaster
|
||||||
, lockRotation: options.lockRotation
|
, lockRotation: options.lockRotation
|
||||||
|
@ -946,6 +964,10 @@ program
|
||||||
.option('--user-profile-url <url>'
|
.option('--user-profile-url <url>'
|
||||||
, 'URL to external user profile page'
|
, 'URL to external user profile page'
|
||||||
, String)
|
, String)
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--mute-master'
|
.option('--mute-master'
|
||||||
, 'whether to mute master volume when devices are being used')
|
, 'whether to mute master volume when devices are being used')
|
||||||
.option('--lock-rotation'
|
.option('--lock-rotation'
|
||||||
|
@ -1013,6 +1035,7 @@ program
|
||||||
, util.format('http://localhost:%d/', options.poorxyPort)
|
, util.format('http://localhost:%d/', options.poorxyPort)
|
||||||
, '--adb-host', options.adbHost
|
, '--adb-host', options.adbHost
|
||||||
, '--adb-port', options.adbPort
|
, '--adb-port', options.adbPort
|
||||||
|
, '--vnc-initial-size', options.vncInitialSize.join('x')
|
||||||
]
|
]
|
||||||
.concat(options.allowRemote ? ['--allow-remote'] : [])
|
.concat(options.allowRemote ? ['--allow-remote'] : [])
|
||||||
.concat(options.muteMaster ? ['--mute-master'] : [])
|
.concat(options.muteMaster ? ['--mute-master'] : [])
|
||||||
|
|
|
@ -20,6 +20,7 @@ module.exports = function(options) {
|
||||||
.dependency(require('./plugins/solo'))
|
.dependency(require('./plugins/solo'))
|
||||||
.dependency(require('./plugins/screen/stream'))
|
.dependency(require('./plugins/screen/stream'))
|
||||||
.dependency(require('./plugins/screen/capture'))
|
.dependency(require('./plugins/screen/capture'))
|
||||||
|
.dependency(require('./plugins/vnc'))
|
||||||
.dependency(require('./plugins/service'))
|
.dependency(require('./plugins/service'))
|
||||||
.dependency(require('./plugins/browser'))
|
.dependency(require('./plugins/browser'))
|
||||||
.dependency(require('./plugins/store'))
|
.dependency(require('./plugins/store'))
|
||||||
|
|
|
@ -25,7 +25,6 @@ module.exports = syrup.serial()
|
||||||
.dependency(require('./options'))
|
.dependency(require('./options'))
|
||||||
.define(function(options, adb, minicap, display, screenOptions) {
|
.define(function(options, adb, minicap, display, screenOptions) {
|
||||||
var log = logger.createLogger('device:plugins:screen:stream')
|
var log = logger.createLogger('device:plugins:screen:stream')
|
||||||
var plugin = Object.create(null)
|
|
||||||
|
|
||||||
function FrameProducer(config) {
|
function FrameProducer(config) {
|
||||||
EventEmitter.call(this)
|
EventEmitter.call(this)
|
||||||
|
@ -443,9 +442,9 @@ module.exports = syrup.serial()
|
||||||
|
|
||||||
return createServer()
|
return createServer()
|
||||||
.then(function(wss) {
|
.then(function(wss) {
|
||||||
var broadcastSet = new BroadcastSet()
|
|
||||||
var frameProducer = new FrameProducer(
|
var frameProducer = new FrameProducer(
|
||||||
new FrameConfig(display.properties, display.properties))
|
new FrameConfig(display.properties, display.properties))
|
||||||
|
var broadcastSet = frameProducer.broadcastSet = new BroadcastSet()
|
||||||
|
|
||||||
broadcastSet.on('nonempty', function() {
|
broadcastSet.on('nonempty', function() {
|
||||||
frameProducer.start()
|
frameProducer.start()
|
||||||
|
@ -455,18 +454,56 @@ module.exports = syrup.serial()
|
||||||
frameProducer.stop()
|
frameProducer.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
broadcastSet.on('insert', function(id) {
|
||||||
|
// If two clients join a session in the middle, one of them
|
||||||
|
// may not release the initial size because the projection
|
||||||
|
// doesn't necessarily change, and the producer doesn't Getting
|
||||||
|
// restarted. Therefore we have to call onStart() manually
|
||||||
|
// if the producer is already up and running.
|
||||||
|
switch (frameProducer.runningState) {
|
||||||
|
case FrameProducer.STATE_STARTED:
|
||||||
|
broadcastSet.get(id).onStart(frameProducer)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
display.on('rotationChange', function(newRotation) {
|
display.on('rotationChange', function(newRotation) {
|
||||||
frameProducer.updateRotation(newRotation)
|
frameProducer.updateRotation(newRotation)
|
||||||
})
|
})
|
||||||
|
|
||||||
frameProducer.on('start', function() {
|
frameProducer.on('start', function() {
|
||||||
|
broadcastSet.keys().map(function(id) {
|
||||||
|
return broadcastSet.get(id).onStart(frameProducer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
frameProducer.on('readable', function next() {
|
||||||
|
var frame
|
||||||
|
if ((frame = frameProducer.nextFrame())) {
|
||||||
|
Promise.settle([broadcastSet.keys().map(function(id) {
|
||||||
|
return broadcastSet.get(id).onFrame(frame)
|
||||||
|
})]).then(next)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frameProducer.needFrame()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
frameProducer.on('error', function(err) {
|
||||||
|
log.fatal('Frame producer had an error', err.stack)
|
||||||
|
lifecycle.fatal()
|
||||||
|
})
|
||||||
|
|
||||||
|
wss.on('connection', function(ws) {
|
||||||
|
var id = uuid.v4()
|
||||||
|
|
||||||
|
function wsStartNotifier() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
var message = util.format(
|
var message = util.format(
|
||||||
'start %s'
|
'start %s'
|
||||||
, JSON.stringify(frameProducer.banner)
|
, JSON.stringify(frameProducer.banner)
|
||||||
)
|
)
|
||||||
|
|
||||||
broadcastSet.keys().forEach(function(id) {
|
|
||||||
var ws = broadcastSet.get(id)
|
|
||||||
switch (ws.readyState) {
|
switch (ws.readyState) {
|
||||||
case WebSocket.OPENING:
|
case WebSocket.OPENING:
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
|
@ -474,7 +511,9 @@ module.exports = syrup.serial()
|
||||||
break
|
break
|
||||||
case WebSocket.OPEN:
|
case WebSocket.OPEN:
|
||||||
// This is what SHOULD happen.
|
// This is what SHOULD happen.
|
||||||
ws.send(message)
|
ws.send(message, function(err) {
|
||||||
|
return err ? reject(err) : resolve()
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case WebSocket.CLOSING:
|
case WebSocket.CLOSING:
|
||||||
// Ok, a 'close' event should remove the client from the set
|
// Ok, a 'close' event should remove the client from the set
|
||||||
|
@ -487,14 +526,10 @@ module.exports = syrup.serial()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
frameProducer.on('readable', function next() {
|
function wsFrameNotifier(frame) {
|
||||||
var frame
|
|
||||||
if ((frame = frameProducer.nextFrame())) {
|
|
||||||
Promise.settle([broadcastSet.keys().map(function(id) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var ws = broadcastSet.get(id)
|
|
||||||
switch (ws.readyState) {
|
switch (ws.readyState) {
|
||||||
case WebSocket.OPENING:
|
case WebSocket.OPENING:
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
|
@ -519,27 +554,17 @@ module.exports = syrup.serial()
|
||||||
'Unable to send frame to CLOSED client "%s"', id)))
|
'Unable to send frame to CLOSED client "%s"', id)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})]).then(next)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
frameProducer.needFrame()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frameProducer.on('error', function(err) {
|
|
||||||
log.fatal('Frame producer had an error', err.stack)
|
|
||||||
lifecycle.fatal()
|
|
||||||
})
|
|
||||||
|
|
||||||
wss.on('connection', function(ws) {
|
|
||||||
var id = uuid.v4()
|
|
||||||
|
|
||||||
ws.on('message', function(data) {
|
ws.on('message', function(data) {
|
||||||
var match
|
var match
|
||||||
if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) {
|
if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) {
|
||||||
switch (match[2] || match[1]) {
|
switch (match[2] || match[1]) {
|
||||||
case 'on':
|
case 'on':
|
||||||
broadcastSet.insert(id, ws)
|
broadcastSet.insert(id, {
|
||||||
|
onStart: wsStartNotifier
|
||||||
|
, onFrame: wsFrameNotifier
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'off':
|
case 'off':
|
||||||
broadcastSet.remove(id)
|
broadcastSet.remove(id)
|
||||||
|
@ -563,6 +588,7 @@ module.exports = syrup.serial()
|
||||||
lifecycle.observe(function() {
|
lifecycle.observe(function() {
|
||||||
frameProducer.stop()
|
frameProducer.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return frameProducer
|
||||||
})
|
})
|
||||||
.return(plugin)
|
|
||||||
})
|
})
|
||||||
|
|
201
lib/units/device/plugins/vnc/index.js
Normal file
201
lib/units/device/plugins/vnc/index.js
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
var net = require('net')
|
||||||
|
var util = require('util')
|
||||||
|
var os = require('os')
|
||||||
|
|
||||||
|
var syrup = require('stf-syrup')
|
||||||
|
var Promise = require('bluebird')
|
||||||
|
var uuid = require('node-uuid')
|
||||||
|
var jpeg = require('jpeg-turbo')
|
||||||
|
|
||||||
|
var logger = require('../../../../util/logger')
|
||||||
|
var lifecycle = require('../../../../util/lifecycle')
|
||||||
|
|
||||||
|
var VncServer = require('./util/server')
|
||||||
|
var VncConnection = require('./util/connection')
|
||||||
|
var PointerTranslator = require('./util/pointertranslator')
|
||||||
|
|
||||||
|
module.exports = syrup.serial()
|
||||||
|
.dependency(require('../screen/stream'))
|
||||||
|
.dependency(require('../touch'))
|
||||||
|
.define(function(options, screenStream, touch) {
|
||||||
|
var log = logger.createLogger('device:plugins:vnc')
|
||||||
|
|
||||||
|
function createServer() {
|
||||||
|
log.info('Starting VNC server on port %d', options.vncPort)
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: options.serial
|
||||||
|
, width: options.vncInitialSize[0]
|
||||||
|
, height: options.vncInitialSize[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var vnc = new VncServer(net.createServer({
|
||||||
|
allowHalfOpen: true
|
||||||
|
}), opts)
|
||||||
|
|
||||||
|
var listeningListener, errorListener
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
listeningListener = function() {
|
||||||
|
return resolve(vnc)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorListener = function(err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vnc.on('listening', listeningListener)
|
||||||
|
vnc.on('error', errorListener)
|
||||||
|
|
||||||
|
vnc.listen(options.vncPort)
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
vnc.removeListener('listening', listeningListener)
|
||||||
|
vnc.removeListener('error', errorListener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return createServer()
|
||||||
|
.then(function(vnc) {
|
||||||
|
vnc.on('connection', function(conn) {
|
||||||
|
var id = util.format('vnc-%s', uuid.v4())
|
||||||
|
|
||||||
|
var connState = {
|
||||||
|
lastFrame: null
|
||||||
|
, lastFrameTime: null
|
||||||
|
, frameWidth: 0
|
||||||
|
, frameHeight: 0
|
||||||
|
, sentFrameTime: null
|
||||||
|
, updateRequests: 0
|
||||||
|
, frameConfig: {
|
||||||
|
format: jpeg.FORMAT_RGB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerTranslator = new PointerTranslator()
|
||||||
|
|
||||||
|
pointerTranslator.on('touchdown', function(event) {
|
||||||
|
touch.touchDown(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchmove', function(event) {
|
||||||
|
touch.touchMove(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchup', function(event) {
|
||||||
|
touch.touchUp(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchcommit', function() {
|
||||||
|
touch.touchCommit()
|
||||||
|
})
|
||||||
|
|
||||||
|
function vncStartListener(frameProducer) {
|
||||||
|
return new Promise(function(resolve/*, reject*/) {
|
||||||
|
connState.frameWidth = frameProducer.banner.virtualWidth
|
||||||
|
connState.frameHeight = frameProducer.banner.virtualHeight
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function vncFrameListener(frame) {
|
||||||
|
return new Promise(function(resolve/*, reject*/) {
|
||||||
|
connState.lastFrame = frame
|
||||||
|
connState.lastFrameTime = Date.now()
|
||||||
|
maybeSendFrame()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeSendFrame() {
|
||||||
|
if (!connState.updateRequests) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connState.lastFrame) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connState.lastFrameTime === connState.sentFrameTime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded = jpeg.decompressSync(
|
||||||
|
connState.lastFrame, connState.frameConfig)
|
||||||
|
|
||||||
|
conn.writeFramebufferUpdate([
|
||||||
|
{ xPosition: 0
|
||||||
|
, yPosition: 0
|
||||||
|
, width: decoded.width
|
||||||
|
, height: decoded.height
|
||||||
|
, encodingType: VncConnection.ENCODING_RAW
|
||||||
|
, data: decoded.data
|
||||||
|
}
|
||||||
|
, { xPosition: 0
|
||||||
|
, yPosition: 0
|
||||||
|
, width: decoded.width
|
||||||
|
, height: decoded.height
|
||||||
|
, encodingType: VncConnection.ENCODING_DESKTOPSIZE
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
connState.updateRequests = 0
|
||||||
|
connState.sentFrameTime = connState.lastFrameTime
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.on('authenticated', function() {
|
||||||
|
screenStream.updateProjection(
|
||||||
|
options.vncInitialSize[0], options.vncInitialSize[1])
|
||||||
|
screenStream.broadcastSet.insert(id, {
|
||||||
|
onStart: vncStartListener
|
||||||
|
, onFrame: vncFrameListener
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('fbupdaterequest', function() {
|
||||||
|
connState.updateRequests += 1
|
||||||
|
maybeSendFrame()
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('formatchange', function(format) {
|
||||||
|
var same = os.endianness() == 'BE' == format.bigEndianFlag
|
||||||
|
switch (format.bitsPerPixel) {
|
||||||
|
case 8:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: jpeg.FORMAT_GRAY
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 24:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: ((format.redShift > format.blueShift) === same)
|
||||||
|
? jpeg.FORMAT_BGR
|
||||||
|
: jpeg.FORMAT_RGB
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 32:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: ((format.redShift > format.blueShift) === same)
|
||||||
|
? (format.blueShift === 0
|
||||||
|
? jpeg.FORMAT_BGRX
|
||||||
|
: jpeg.FORMAT_XBGR)
|
||||||
|
: (format.redShift === 0
|
||||||
|
? jpeg.FORMAT_RGBX
|
||||||
|
: jpeg.FORMAT_XRGB)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('pointer', function(event) {
|
||||||
|
pointerTranslator.push(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('close', function() {
|
||||||
|
screenStream.broadcastSet.remove(id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
lifecycle.observe(function() {
|
||||||
|
vnc.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
474
lib/units/device/plugins/vnc/util/connection.js
Normal file
474
lib/units/device/plugins/vnc/util/connection.js
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
var util = require('util')
|
||||||
|
var os = require('os')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
var debug = require('debug')('vnc:connection')
|
||||||
|
|
||||||
|
var PixelFormat = require('./pixelformat')
|
||||||
|
|
||||||
|
function VncConnection(conn, options) {
|
||||||
|
this.options = options
|
||||||
|
|
||||||
|
this._bound = {
|
||||||
|
_errorListener: this._errorListener.bind(this)
|
||||||
|
, _readableListener: this._readableListener.bind(this)
|
||||||
|
, _endListener: this._endListener.bind(this)
|
||||||
|
, _closeListener: this._closeListener.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._buffer = null
|
||||||
|
this._state = 0
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_VERSION)
|
||||||
|
|
||||||
|
this._serverVersion = VncConnection.V3_008
|
||||||
|
this._serverSupportedSecurity = [VncConnection.SECURITY_NONE]
|
||||||
|
this._serverWidth = this.options.width
|
||||||
|
this._serverHeight = this.options.height
|
||||||
|
this._serverPixelFormat = new PixelFormat({
|
||||||
|
bitsPerPixel: 32
|
||||||
|
, depth: 24
|
||||||
|
, bigEndianFlag: os.endianness() == 'BE' ? 1 : 0
|
||||||
|
, trueColorFlag: 1
|
||||||
|
, redMax: 255
|
||||||
|
, greenMax: 255
|
||||||
|
, blueMax: 255
|
||||||
|
, redShift: 16
|
||||||
|
, greenShift: 8
|
||||||
|
, blueShift: 0
|
||||||
|
})
|
||||||
|
this._serverName = this.options.name
|
||||||
|
|
||||||
|
this._clientVersion = null
|
||||||
|
this._clientShare = false
|
||||||
|
this._clientPixelFormat = this._serverPixelFormat
|
||||||
|
this._clientEncodingCount = 0
|
||||||
|
this._clientEncodings = []
|
||||||
|
this._clientCutTextLength = 0
|
||||||
|
|
||||||
|
this.conn = conn
|
||||||
|
.on('error', this._bound._errorListener)
|
||||||
|
.on('readable', this._bound._readableListener)
|
||||||
|
.on('end', this._bound._endListener)
|
||||||
|
.on('close', this._bound._closeListener)
|
||||||
|
|
||||||
|
this._writeServerVersion()
|
||||||
|
this._read()
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(VncConnection, EventEmitter)
|
||||||
|
|
||||||
|
VncConnection.V3_003 = 3003
|
||||||
|
VncConnection.V3_007 = 3007
|
||||||
|
VncConnection.V3_008 = 3008
|
||||||
|
|
||||||
|
VncConnection.SECURITY_NONE = 1
|
||||||
|
VncConnection.SECURITY_VNC = 2
|
||||||
|
|
||||||
|
VncConnection.SECURITYRESULT_OK = 0
|
||||||
|
VncConnection.SECURITYRESULT_FAIL = 1
|
||||||
|
|
||||||
|
VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT = 0
|
||||||
|
VncConnection.CLIENT_MESSAGE_SETENCODINGS = 2
|
||||||
|
VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST = 3
|
||||||
|
VncConnection.CLIENT_MESSAGE_KEYEVENT = 4
|
||||||
|
VncConnection.CLIENT_MESSAGE_POINTEREVENT = 5
|
||||||
|
VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT = 6
|
||||||
|
|
||||||
|
VncConnection.SERVER_MESSAGE_FBUPDATE = 0
|
||||||
|
|
||||||
|
var StateReverse = Object.create(null), State = {
|
||||||
|
STATE_NEED_CLIENT_VERSION: 10
|
||||||
|
, STATE_NEED_CLIENT_SECURITY: 20
|
||||||
|
, STATE_NEED_CLIENT_INIT: 30
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE: 40
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: 50
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: 60
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: 61
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: 70
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_KEYEVENT: 80
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: 90
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: 100
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: 101
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.ENCODING_RAW = 0
|
||||||
|
VncConnection.ENCODING_DESKTOPSIZE = -223
|
||||||
|
|
||||||
|
Object.keys(State).map(function(name) {
|
||||||
|
VncConnection[name] = State[name]
|
||||||
|
StateReverse[State[name]] = name
|
||||||
|
})
|
||||||
|
|
||||||
|
VncConnection.prototype.end = function() {
|
||||||
|
this.conn.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype.writeFramebufferUpdate = function(rectangles) {
|
||||||
|
var chunk = new Buffer(4)
|
||||||
|
chunk[0] = VncConnection.SERVER_MESSAGE_FBUPDATE
|
||||||
|
chunk[1] = 0
|
||||||
|
chunk.writeUInt16BE(rectangles.length, 2)
|
||||||
|
this._write(chunk)
|
||||||
|
|
||||||
|
rectangles.forEach(function(rect) {
|
||||||
|
var chunk = new Buffer(12)
|
||||||
|
chunk.writeUInt16BE(rect.xPosition, 0)
|
||||||
|
chunk.writeUInt16BE(rect.yPosition, 2)
|
||||||
|
chunk.writeUInt16BE(rect.width, 4)
|
||||||
|
chunk.writeUInt16BE(rect.height, 6)
|
||||||
|
chunk.writeInt32BE(rect.encodingType, 8)
|
||||||
|
this._write(chunk)
|
||||||
|
|
||||||
|
switch (rect.encodingType) {
|
||||||
|
case VncConnection.ENCODING_RAW:
|
||||||
|
this._write(rect.data)
|
||||||
|
break
|
||||||
|
case VncConnection.ENCODING_DESKTOPSIZE:
|
||||||
|
this._serverWidth = rect.width
|
||||||
|
this._serverHeight = rect.height
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(util.format(
|
||||||
|
'Unsupported encoding type', rect.encodingType))
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._error = function(err) {
|
||||||
|
this.emit('error', err)
|
||||||
|
this.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._errorListener = function(err) {
|
||||||
|
this._error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._endListener = function() {
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._closeListener = function() {
|
||||||
|
this.emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeServerVersion = function() {
|
||||||
|
// Yes, we could just format the string instead. Didn't feel like it.
|
||||||
|
switch (this._serverVersion) {
|
||||||
|
case VncConnection.V3_003:
|
||||||
|
this._write(new Buffer('RFB 003.003\n'))
|
||||||
|
break
|
||||||
|
case VncConnection.V3_007:
|
||||||
|
this._write(new Buffer('RFB 003.007\n'))
|
||||||
|
break
|
||||||
|
case VncConnection.V3_008:
|
||||||
|
this._write(new Buffer('RFB 003.008\n'))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeSupportedSecurity = function() {
|
||||||
|
var chunk = new Buffer(1 + this._serverSupportedSecurity.length)
|
||||||
|
|
||||||
|
chunk[0] = this._serverSupportedSecurity.length
|
||||||
|
this._serverSupportedSecurity.forEach(function(security, i) {
|
||||||
|
chunk[1 + i] = security
|
||||||
|
})
|
||||||
|
|
||||||
|
this._write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeSelectedSecurity = function() {
|
||||||
|
var chunk = new Buffer(4)
|
||||||
|
chunk.writeUInt32BE(VncConnection.SECURITY_NONE, 0)
|
||||||
|
this._write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeSecurityResult = function(result, reason) {
|
||||||
|
var chunk
|
||||||
|
switch (result) {
|
||||||
|
case VncConnection.SECURITYRESULT_OK:
|
||||||
|
chunk = new Buffer(4)
|
||||||
|
chunk.writeUInt32BE(result, 0)
|
||||||
|
this._write(chunk)
|
||||||
|
break
|
||||||
|
case VncConnection.SECURITYRESULT_FAIL:
|
||||||
|
chunk = new Buffer(4 + 4 + reason.length)
|
||||||
|
chunk.writeUInt32BE(result, 0)
|
||||||
|
chunk.writeUInt32BE(reason.length, 4)
|
||||||
|
chunk.write(reason, 8, reason.length)
|
||||||
|
this._write(chunk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeServerInit = function() {
|
||||||
|
debug('server pixel format', this._serverPixelFormat)
|
||||||
|
var chunk = new Buffer(2 + 2 + 16 + 4 + this._serverName.length)
|
||||||
|
chunk.writeUInt16BE(this._serverWidth, 0)
|
||||||
|
chunk.writeUInt16BE(this._serverHeight, 2)
|
||||||
|
chunk[4] = this._serverPixelFormat.bitsPerPixel
|
||||||
|
chunk[5] = this._serverPixelFormat.depth
|
||||||
|
chunk[6] = this._serverPixelFormat.bigEndianFlag
|
||||||
|
chunk[7] = this._serverPixelFormat.trueColorFlag
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.redMax, 8)
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.greenMax, 10)
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.blueMax, 12)
|
||||||
|
chunk[14] = this._serverPixelFormat.redShift
|
||||||
|
chunk[15] = this._serverPixelFormat.greenShift
|
||||||
|
chunk[16] = this._serverPixelFormat.blueShift
|
||||||
|
chunk[17] = 0 // padding
|
||||||
|
chunk[18] = 0 // padding
|
||||||
|
chunk[19] = 0 // padding
|
||||||
|
chunk.writeUInt32BE(this._serverName.length, 20)
|
||||||
|
chunk.write(this._serverName, 24, this._serverName.length)
|
||||||
|
this._write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._readableListener = function() {
|
||||||
|
this._read()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._read = function() {
|
||||||
|
var chunk, lo, hi
|
||||||
|
while (this._append(this.conn.read())) {
|
||||||
|
do {
|
||||||
|
debug('state', StateReverse[this._state])
|
||||||
|
chunk = null
|
||||||
|
switch (this._state) {
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_VERSION:
|
||||||
|
if ((chunk = this._consume(12))) {
|
||||||
|
if ((this._clientVersion = this._parseVersion(chunk)) === null) {
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug('client version', this._clientVersion)
|
||||||
|
this._writeSupportedSecurity()
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_SECURITY:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
if ((this._clientSecurity = this._parseSecurity(chunk)) === null) {
|
||||||
|
this._writeSecurityResult(
|
||||||
|
VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type')
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug('client security', this._clientSecurity)
|
||||||
|
this._writeSecurityResult(VncConnection.SECURITYRESULT_OK)
|
||||||
|
this.emit('authenticated')
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_INIT)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_INIT:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
this._clientShare = chunk[0]
|
||||||
|
debug('client shareFlag', this._clientShare)
|
||||||
|
this._writeServerInit()
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
switch (chunk[0]) {
|
||||||
|
case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_SETENCODINGS:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_KEYEVENT:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_POINTEREVENT:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this._error(new Error(util.format(
|
||||||
|
'Unsupported message type %d', chunk[0])))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT:
|
||||||
|
if ((chunk = this._consume(19))) {
|
||||||
|
// [0b, 3b) padding
|
||||||
|
this._clientPixelFormat = new PixelFormat({
|
||||||
|
bitsPerPixel: chunk[3]
|
||||||
|
, depth: chunk[4]
|
||||||
|
, bigEndianFlag: chunk[5]
|
||||||
|
, trueColorFlag: chunk[6]
|
||||||
|
, redMax: chunk.readUInt16BE(7, true)
|
||||||
|
, greenMax: chunk.readUInt16BE(9, true)
|
||||||
|
, blueMax: chunk.readUInt16BE(11, true)
|
||||||
|
, redShift: chunk[13]
|
||||||
|
, greenShift: chunk[14]
|
||||||
|
, blueShift: chunk[15]
|
||||||
|
})
|
||||||
|
// [16b, 19b) padding
|
||||||
|
debug('client pixel format', this._clientPixelFormat)
|
||||||
|
this.emit('formatchange', this._clientPixelFormat)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS:
|
||||||
|
if ((chunk = this._consume(3))) {
|
||||||
|
// [0b, 1b) padding
|
||||||
|
this._clientEncodingCount = chunk.readUInt16BE(1, true)
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE:
|
||||||
|
lo = 0
|
||||||
|
hi = 4 * this._clientEncodingCount
|
||||||
|
if ((chunk = this._consume(hi))) {
|
||||||
|
this._clientEncodings = []
|
||||||
|
while (lo < hi) {
|
||||||
|
this._clientEncodings.push(chunk.readInt32BE(lo, true))
|
||||||
|
lo += 4
|
||||||
|
}
|
||||||
|
debug('client encodings', this._clientEncodings)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST:
|
||||||
|
if ((chunk = this._consume(9))) {
|
||||||
|
this.emit('fbupdaterequest', {
|
||||||
|
incremental: chunk[0]
|
||||||
|
, xPosition: chunk.readUInt16BE(1, true)
|
||||||
|
, yPosition: chunk.readUInt16BE(3, true)
|
||||||
|
, width: chunk.readUInt16BE(5, true)
|
||||||
|
, height: chunk.readUInt16BE(7, true)
|
||||||
|
})
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT:
|
||||||
|
if ((chunk = this._consume(7))) {
|
||||||
|
// downFlag = chunk[0]
|
||||||
|
// [1b, 3b) padding
|
||||||
|
// key = chunk.readUInt32BE(3, true)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT:
|
||||||
|
if ((chunk = this._consume(5))) {
|
||||||
|
this.emit('pointer', {
|
||||||
|
buttonMask: chunk[0]
|
||||||
|
, xPosition: chunk.readUInt16BE(1, true) / this._serverWidth
|
||||||
|
, yPosition: chunk.readUInt16BE(3, true) / this._serverHeight
|
||||||
|
})
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT:
|
||||||
|
if ((chunk = this._consume(7))) {
|
||||||
|
// [0b, 3b) padding
|
||||||
|
this._clientCutTextLength = chunk.readUInt32BE(3)
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE:
|
||||||
|
if ((chunk = this._consume(this._clientCutTextLength))) {
|
||||||
|
// value = chunk
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(util.format('Impossible state %d', this._state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._parseVersion = function(chunk) {
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.008\n'))) {
|
||||||
|
return VncConnection.V3_008
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.007\n'))) {
|
||||||
|
return VncConnection.V3_007
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.003\n'))) {
|
||||||
|
return VncConnection.V3_003
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._parseSecurity = function(chunk) {
|
||||||
|
switch (chunk[0]) {
|
||||||
|
case VncConnection.SECURITY_NONE:
|
||||||
|
case VncConnection.SECURITY_VNC:
|
||||||
|
return chunk[0]
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._changeState = function(state) {
|
||||||
|
this._state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._append = function(chunk) {
|
||||||
|
if (!chunk) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('in', chunk)
|
||||||
|
|
||||||
|
if (this._buffer) {
|
||||||
|
this._buffer = Buffer.concat(
|
||||||
|
[this._buffer, chunk], this._buffer.length + chunk.length)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._buffer = chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._consume = function(n) {
|
||||||
|
var chunk
|
||||||
|
|
||||||
|
if (!this._buffer) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < this._buffer.length) {
|
||||||
|
chunk = this._buffer.slice(0, n)
|
||||||
|
this._buffer = this._buffer.slice(n)
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n === this._buffer.length) {
|
||||||
|
chunk = this._buffer
|
||||||
|
this._buffer = null
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._write = function(chunk) {
|
||||||
|
debug('out', chunk)
|
||||||
|
this.conn.write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = VncConnection
|
14
lib/units/device/plugins/vnc/util/pixelformat.js
Normal file
14
lib/units/device/plugins/vnc/util/pixelformat.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
function PixelFormat(values) {
|
||||||
|
this.bitsPerPixel = values.bitsPerPixel
|
||||||
|
this.depth = values.depth
|
||||||
|
this.bigEndianFlag = values.bigEndianFlag
|
||||||
|
this.trueColorFlag = values.trueColorFlag
|
||||||
|
this.redMax = values.redMax
|
||||||
|
this.greenMax = values.greenMax
|
||||||
|
this.blueMax = values.blueMax
|
||||||
|
this.redShift = values.redShift
|
||||||
|
this.greenShift = values.greenShift
|
||||||
|
this.blueShift = values.blueShift
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PixelFormat
|
66
lib/units/device/plugins/vnc/util/pointertranslator.js
Normal file
66
lib/units/device/plugins/vnc/util/pointertranslator.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
|
||||||
|
function PointerTranslator() {
|
||||||
|
this.previousEvent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(PointerTranslator, EventEmitter)
|
||||||
|
|
||||||
|
PointerTranslator.prototype.push = function(event) {
|
||||||
|
if (event.buttonMask & 0xFE) {
|
||||||
|
// Non-primary buttons included, ignore.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previousEvent) {
|
||||||
|
var buttonChanges = event.buttonMask ^ this.previousEvent.buttonMask
|
||||||
|
|
||||||
|
// If the primary button changed, we have an up/down event.
|
||||||
|
if (buttonChanges & 1) {
|
||||||
|
// If it's pressed now, that's a down event.
|
||||||
|
if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchdown', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
// It's not pressed, so we have an up event.
|
||||||
|
else {
|
||||||
|
this.emit('touchup', {
|
||||||
|
contact: 1
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, if we're still holding the primary button down,
|
||||||
|
// that's a move event.
|
||||||
|
else if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchmove', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If it's the first event we get and the primary button's pressed,
|
||||||
|
// it's a down event.
|
||||||
|
if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchdown', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousEvent = event
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PointerTranslator
|
52
lib/units/device/plugins/vnc/util/server.js
Normal file
52
lib/units/device/plugins/vnc/util/server.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
var debug = require('debug')('vnc:server')
|
||||||
|
|
||||||
|
var VncConnection = require('./connection')
|
||||||
|
|
||||||
|
function VncServer(server, options) {
|
||||||
|
this.options = options
|
||||||
|
|
||||||
|
this._bound = {
|
||||||
|
_listeningListener: this._listeningListener.bind(this)
|
||||||
|
, _connectionListener: this._connectionListener.bind(this)
|
||||||
|
, _closeListener: this._closeListener.bind(this)
|
||||||
|
, _errorListener: this._errorListener.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server = server
|
||||||
|
.on('listening', this._bound._listeningListener)
|
||||||
|
.on('connection', this._bound._connectionListener)
|
||||||
|
.on('close', this._bound._closeListener)
|
||||||
|
.on('error', this._bound._errorListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(VncServer, EventEmitter)
|
||||||
|
|
||||||
|
VncServer.prototype.close = function() {
|
||||||
|
this.server.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype.listen = function() {
|
||||||
|
this.server.listen.apply(this.server, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._listeningListener = function() {
|
||||||
|
this.emit('listening')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._connectionListener = function(conn) {
|
||||||
|
debug('connection', conn.remoteAddress, conn.remotePort)
|
||||||
|
this.emit('connection', new VncConnection(conn, this.options))
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._closeListener = function() {
|
||||||
|
this.emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._errorListener = function(err) {
|
||||||
|
this.emit('error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = VncServer
|
|
@ -316,7 +316,7 @@ module.exports = function(options) {
|
||||||
|
|
||||||
// Spawn a device worker
|
// Spawn a device worker
|
||||||
function spawn() {
|
function spawn() {
|
||||||
var allocatedPorts = ports.splice(0, 2)
|
var allocatedPorts = ports.splice(0, 4)
|
||||||
, proc = options.fork(device, allocatedPorts)
|
, proc = options.fork(device, allocatedPorts)
|
||||||
, resolver = Promise.defer()
|
, resolver = Promise.defer()
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,11 @@ module.exports.list = function(val) {
|
||||||
return val.split(/\s*,\s*/g).filter(Boolean)
|
return val.split(/\s*,\s*/g).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.size = function(val) {
|
||||||
|
var match = /^(\d+)x(\d+)$/.exec(val)
|
||||||
|
return match ? [+match[1], +match[2]] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.allUnknownArgs = function(args) {
|
module.exports.allUnknownArgs = function(args) {
|
||||||
return [].slice.call(args, 0, -1).filter(Boolean)
|
return [].slice.call(args, 0, -1).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"compression": "^1.5.2",
|
"compression": "^1.5.2",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"csurf": "^1.7.0",
|
"csurf": "^1.7.0",
|
||||||
|
"debug": "^2.2.0",
|
||||||
"eventemitter3": "^0.1.6",
|
"eventemitter3": "^0.1.6",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"express-validator": "^2.17.1",
|
"express-validator": "^2.17.1",
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
"http-proxy": "^1.11.2",
|
"http-proxy": "^1.11.2",
|
||||||
"in-publish": "^2.0.0",
|
"in-publish": "^2.0.0",
|
||||||
"jade": "^1.9.2",
|
"jade": "^1.9.2",
|
||||||
|
"jpeg-turbo": "^0.3.0",
|
||||||
"jws": "^3.1.0",
|
"jws": "^3.1.0",
|
||||||
"ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e",
|
"ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue