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
|
@ -25,7 +25,6 @@ module.exports = syrup.serial()
|
|||
.dependency(require('./options'))
|
||||
.define(function(options, adb, minicap, display, screenOptions) {
|
||||
var log = logger.createLogger('device:plugins:screen:stream')
|
||||
var plugin = Object.create(null)
|
||||
|
||||
function FrameProducer(config) {
|
||||
EventEmitter.call(this)
|
||||
|
@ -443,9 +442,9 @@ module.exports = syrup.serial()
|
|||
|
||||
return createServer()
|
||||
.then(function(wss) {
|
||||
var broadcastSet = new BroadcastSet()
|
||||
var frameProducer = new FrameProducer(
|
||||
new FrameConfig(display.properties, display.properties))
|
||||
var broadcastSet = frameProducer.broadcastSet = new BroadcastSet()
|
||||
|
||||
broadcastSet.on('nonempty', function() {
|
||||
frameProducer.start()
|
||||
|
@ -455,37 +454,26 @@ module.exports = syrup.serial()
|
|||
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) {
|
||||
frameProducer.updateRotation(newRotation)
|
||||
})
|
||||
|
||||
frameProducer.on('start', function() {
|
||||
var message = util.format(
|
||||
'start %s'
|
||||
, JSON.stringify(frameProducer.banner)
|
||||
)
|
||||
|
||||
broadcastSet.keys().forEach(function(id) {
|
||||
var ws = broadcastSet.get(id)
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.OPENING:
|
||||
// This should never happen.
|
||||
log.warn('Unable to send banner to OPENING client "%s"', id)
|
||||
break
|
||||
case WebSocket.OPEN:
|
||||
// This is what SHOULD happen.
|
||||
ws.send(message)
|
||||
break
|
||||
case WebSocket.CLOSING:
|
||||
// Ok, a 'close' event should remove the client from the set
|
||||
// soon.
|
||||
break
|
||||
case WebSocket.CLOSED:
|
||||
// This should never happen.
|
||||
log.warn('Unable to send banner to CLOSED client "%s"', id)
|
||||
broadcastSet.remove(id)
|
||||
break
|
||||
}
|
||||
broadcastSet.keys().map(function(id) {
|
||||
return broadcastSet.get(id).onStart(frameProducer)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -493,32 +481,7 @@ module.exports = syrup.serial()
|
|||
var frame
|
||||
if ((frame = frameProducer.nextFrame())) {
|
||||
Promise.settle([broadcastSet.keys().map(function(id) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var ws = broadcastSet.get(id)
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.OPENING:
|
||||
// This should never happen.
|
||||
return reject(new Error(util.format(
|
||||
'Unable to send frame to OPENING client "%s"', id)))
|
||||
case WebSocket.OPEN:
|
||||
// This is what SHOULD happen.
|
||||
ws.send(frame, {
|
||||
binary: true
|
||||
}, function(err) {
|
||||
return err ? reject(err) : resolve()
|
||||
})
|
||||
return
|
||||
case WebSocket.CLOSING:
|
||||
// Ok, a 'close' event should remove the client from the set
|
||||
// soon.
|
||||
return
|
||||
case WebSocket.CLOSED:
|
||||
// This should never happen.
|
||||
broadcastSet.remove(id)
|
||||
return reject(new Error(util.format(
|
||||
'Unable to send frame to CLOSED client "%s"', id)))
|
||||
}
|
||||
})
|
||||
return broadcastSet.get(id).onFrame(frame)
|
||||
})]).then(next)
|
||||
}
|
||||
else {
|
||||
|
@ -534,12 +497,74 @@ module.exports = syrup.serial()
|
|||
wss.on('connection', function(ws) {
|
||||
var id = uuid.v4()
|
||||
|
||||
function wsStartNotifier() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var message = util.format(
|
||||
'start %s'
|
||||
, JSON.stringify(frameProducer.banner)
|
||||
)
|
||||
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.OPENING:
|
||||
// This should never happen.
|
||||
log.warn('Unable to send banner to OPENING client "%s"', id)
|
||||
break
|
||||
case WebSocket.OPEN:
|
||||
// This is what SHOULD happen.
|
||||
ws.send(message, function(err) {
|
||||
return err ? reject(err) : resolve()
|
||||
})
|
||||
break
|
||||
case WebSocket.CLOSING:
|
||||
// Ok, a 'close' event should remove the client from the set
|
||||
// soon.
|
||||
break
|
||||
case WebSocket.CLOSED:
|
||||
// This should never happen.
|
||||
log.warn('Unable to send banner to CLOSED client "%s"', id)
|
||||
broadcastSet.remove(id)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function wsFrameNotifier(frame) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.OPENING:
|
||||
// This should never happen.
|
||||
return reject(new Error(util.format(
|
||||
'Unable to send frame to OPENING client "%s"', id)))
|
||||
case WebSocket.OPEN:
|
||||
// This is what SHOULD happen.
|
||||
ws.send(frame, {
|
||||
binary: true
|
||||
}, function(err) {
|
||||
return err ? reject(err) : resolve()
|
||||
})
|
||||
return
|
||||
case WebSocket.CLOSING:
|
||||
// Ok, a 'close' event should remove the client from the set
|
||||
// soon.
|
||||
return
|
||||
case WebSocket.CLOSED:
|
||||
// This should never happen.
|
||||
broadcastSet.remove(id)
|
||||
return reject(new Error(util.format(
|
||||
'Unable to send frame to CLOSED client "%s"', id)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ws.on('message', function(data) {
|
||||
var match
|
||||
if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) {
|
||||
switch (match[2] || match[1]) {
|
||||
case 'on':
|
||||
broadcastSet.insert(id, ws)
|
||||
broadcastSet.insert(id, {
|
||||
onStart: wsStartNotifier
|
||||
, onFrame: wsFrameNotifier
|
||||
})
|
||||
break
|
||||
case 'off':
|
||||
broadcastSet.remove(id)
|
||||
|
@ -563,6 +588,7 @@ module.exports = syrup.serial()
|
|||
lifecycle.observe(function() {
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue