From 6dbae08c4031461838e927cd1f4dd7d32be71799 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 23 Jun 2022 22:10:07 -0700 Subject: [PATCH] Added Web-RDP mouse cursor support. --- apprelays.js | 12 +++-- public/scripts/agent-rdp-0.0.1.js | 10 ++++ rdp/core/layer.js | 2 +- rdp/protocol/pdu/data.js | 87 +++++++++++++++++++++++++++---- rdp/protocol/pdu/global.js | 13 +++-- rdp/protocol/rdp.js | 2 + 6 files changed, 109 insertions(+), 17 deletions(-) diff --git a/apprelays.js b/apprelays.js index ac742a2d..d9ba6cbe 100644 --- a/apprelays.js +++ b/apprelays.js @@ -202,10 +202,16 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { delete bitmap.data; send(['rdp-bitmap', bitmap]); // Send the bitmap metadata seperately, without bitmap data. }).on('clipboard', function (content) { - // Clipboard data changed - send(['rdp-clipboard', content]); + send(['rdp-clipboard', content]); // The clipboard data has changed + }).on('pointer', function (cursorId, cursorStr) { + if (cursorStr == null) { cursorStr = 'default'; } + if (obj.lastCursorStrSent != cursorStr) { + obj.lastCursorStrSent = cursorStr; + //console.log('pointer', cursorStr); + send(['rdp-pointer', cursorStr]); // The mouse pointer has changed + } }).on('close', function () { - send(['rdp-close']); + send(['rdp-close']); // This RDP session has closed }).on('error', function (err) { if (typeof err == 'string') { send(['rdp-error', err]); } if ((typeof err == 'object') && (err.err) && (err.code)) { send(['rdp-error', err.err, err.code]); } diff --git a/public/scripts/agent-rdp-0.0.1.js b/public/scripts/agent-rdp-0.0.1.js index 36740295..9c298870 100644 --- a/public/scripts/agent-rdp-0.0.1.js +++ b/public/scripts/agent-rdp-0.0.1.js @@ -18,6 +18,10 @@ var CreateRDPDesktop = function (canvasid) { obj.m.onClipboardChanged = null; obj.onConsoleMessageChange = null; + var xMouseCursorActive = true; + var xMouseCursorCurrent = 'default'; + obj.mouseCursorActive = function (x) { if (xMouseCursorActive == x) return; xMouseCursorActive = x; obj.CanvasId.style.cursor = ((x == true) ? xMouseCursorCurrent : 'default'); } + function mouseButtonMap(button) { // Swap mouse buttons if needed if (obj.m.SwapMouse === true) return [2, 0, 1, 0, 0][button]; @@ -75,6 +79,12 @@ var CreateRDPDesktop = function (canvasid) { obj.render.update(bitmap); break; } + case 'rdp-pointer': { + var pointer = msg[1]; + xMouseCursorCurrent = pointer; + if (xMouseCursorActive) { obj.CanvasId.style.cursor = pointer; } + break; + } case 'rdp-close': { obj.Stop(); break; diff --git a/rdp/core/layer.js b/rdp/core/layer.js index 2f7e9a66..7aa92d3b 100644 --- a/rdp/core/layer.js +++ b/rdp/core/layer.js @@ -22,7 +22,7 @@ var fs = require('fs'); var type = require('./type'); var log = require('./log'); var tls = require('tls'); -var crypto = require('crypto'); +//var crypto = require('crypto'); var events = require('events'); /** diff --git a/rdp/protocol/pdu/data.js b/rdp/protocol/pdu/data.js index 7c79012d..dec3ffd8 100644 --- a/rdp/protocol/pdu/data.js +++ b/rdp/protocol/pdu/data.js @@ -186,7 +186,7 @@ var FastPathUpdateType = { FASTPATH_UPDATETYPE_PTR_NULL : 0x5, FASTPATH_UPDATETYPE_PTR_DEFAULT : 0x6, FASTPATH_UPDATETYPE_PTR_POSITION : 0x8, - FASTPATH_UPDATETYPE_COLOR : 0x9, + FASTPATH_UPDATETYPE_COLOR : 0x9, // Mouse cursor FASTPATH_UPDATETYPE_CACHED : 0xA, FASTPATH_UPDATETYPE_POINTER : 0xB }; @@ -1075,7 +1075,7 @@ function clipPDU() { * @param opt {object} type option * @returns {type.Component} */ -function fastPathBitmapUpdateDataPDU (opt) { +function fastPathBitmapUpdateDataPDU(opt) { var self = { __FASTPATH_UPDATE_TYPE__ : FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, header : new type.UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, { constant : true }), @@ -1093,13 +1093,67 @@ function fastPathBitmapUpdateDataPDU (opt) { return new type.Component(self, opt); } +// This is a table of cursorid to cursor name. +// Created by movering the mouse over this page: https://www.w3schools.com/csSref/tryit.asp?filename=trycss_cursor +const cursorIdTable = { + // Normal style mouse cursor + 903013897: 'alias', + 370524792: 'all-scroll', + 853046751: 'cell', + 2101250798: 'col-resize', + 703681364: 'copy', + 992638936: 'crosshair', + 1539083673: 'ew-resize', + 1919796298: 'grab', + 1010243511: 'grabbing', + 1247283057: 'help', + 1390892051: 'none', + 885751489: 'not-allowed', + 1732952247: 'row-resize', + 747144997: 'url', + 2018345610: 'zoom-in', + 347367048: 'zoom-out', + 1872942890: 'default', + 1737852989: 'text', + 1932827019: 'ns-resize', + 1884471290: 'nesw-resize', + 1204065391: 'nwse-resize', + 2030531519: 'progress', + 1050842114: 'pointer', + + // Black style cursors + 1258195498: 'default', + 219484254: 'all-scroll', + 399295089: 'text', + 1912613597: 'wait', + 864127801: 'ew-resize', + 23245044: 'nesw-resize', + 1966995494: 'not-allowed', + 1873216615: 'help', + 255126408: 'nesw-resize', + 157191894: 'ns-resize', + 1768446509: 'pointer', + 1032011501: 'crosshair' +} + +function fastPathPointerUpdateDataPDU(opt, cursorId, cursorStr) { + var self = { + __FASTPATH_UPDATE_TYPE__: FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR, + header: new type.UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR, { constant: true }), + cursorId: cursorId, + cursorStr: cursorStr + }; + + return new type.Component(self, opt); +} + /** * @see http://msdn.microsoft.com/en-us/library/cc240622.aspx * @param updateData {type.Component} * @param opt {object} type option * @returns {type.Component} */ -function fastPathUpdatePDU (updateData, opt) { +function fastPathUpdatePDU(updateData, opt) { var self = { updateHeader : new type.UInt8( function () { return self.updateData.obj.__FASTPATH_UPDATE_TYPE__; @@ -1116,12 +1170,27 @@ function fastPathUpdatePDU (updateData, opt) { }) }; switch (self.updateHeader.value & 0xf) { - case FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: - self.updateData = fastPathBitmapUpdateDataPDU(options).read(s); - break; - default: - self.updateData = new type.BinaryString(null, options).read(s); - log.debug('unknown fast path pdu type ' + (self.updateHeader.value & 0xf)); + case FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: { + self.updateData = fastPathBitmapUpdateDataPDU(options).read(s); + break; + } + case FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR: { + var data = new type.BinaryString(null, options).read(s); + + // Hash the data to get a cursor id. + // This is a hack since the cursor bitmap is sent but we can't use that, we has hash the bitmap and use that as a hint as to what cursor we need to display + const hasher = require('crypto').createHash('sha384'); + hasher.update(data.value); + const cursorid = Math.abs(hasher.digest().readInt32BE(0)); + const cursorStr = cursorIdTable[cursorid]; + //if (cursorStr == null) { console.log('Unknown cursorId: ' + cursorid); } + self.updateData = fastPathPointerUpdateDataPDU(options, cursorid, cursorStr); + break; + } + default: { + self.updateData = new type.BinaryString(null, options).read(s); + log.debug('unknown fast path pdu type ' + (self.updateHeader.value & 0xf)); + } } }) }; diff --git a/rdp/protocol/pdu/global.js b/rdp/protocol/pdu/global.js index a3f5766d..5fc3ddd7 100644 --- a/rdp/protocol/pdu/global.js +++ b/rdp/protocol/pdu/global.js @@ -268,11 +268,16 @@ Client.prototype.recvServerFontMapPDU = function(s) { */ Client.prototype.recvFastPath = function (secFlag, s) { while (s.availableLength() > 0) { - var pdu = data.fastPathUpdatePDU().read(s); + var pdu = data.fastPathUpdatePDU().read(s); switch (pdu.obj.updateHeader.value & 0xf) { - case data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: - this.emit('bitmap', pdu.obj.updateData.obj.rectangles.obj); - break; + case data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: { + this.emit('bitmap', pdu.obj.updateData.obj.rectangles.obj); + break; + } + case data.FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR: { + this.emit('pointer', pdu.obj.updateData.obj.cursorId, pdu.obj.updateData.obj.cursorStr); + break; + } default: } } diff --git a/rdp/protocol/rdp.js b/rdp/protocol/rdp.js index c58e2e1c..41d71c9a 100644 --- a/rdp/protocol/rdp.js +++ b/rdp/protocol/rdp.js @@ -160,6 +160,8 @@ function RdpClient(config) { }).on('close', function () { self.connected = false; self.emit('close'); + }).on('pointer', function (cursorId, cursorStr) { + self.emit('pointer', cursorId, cursorStr); }).on('bitmap', function (bitmaps) { for (var bitmap in bitmaps) { var bitmapData = bitmaps[bitmap].obj.bitmapDataStream.value;