Added Web-RDP mouse cursor support.

Ylian Saint-Hilaire committed Jun 23, 2022 at 22:10 UTC 6dbae08c4031461838e927cd1f4dd7d32be71799
6 files changed +109 -17
apprelays.js
+9 -3
@@ -202,10 +202,16 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
202 delete bitmap.data;
203 send(['rdp-bitmap', bitmap]); // Send the bitmap metadata seperately, without bitmap data.
204 }).on('clipboard', function (content) {
205 - // Clipboard data changed
206 - send(['rdp-clipboard', content]);
205 + send(['rdp-clipboard', content]); // The clipboard data has changed
206 + }).on('pointer', function (cursorId, cursorStr) {
207 + if (cursorStr == null) { cursorStr = 'default'; }
208 + if (obj.lastCursorStrSent != cursorStr) {
209 + obj.lastCursorStrSent = cursorStr;
210 + //console.log('pointer', cursorStr);
211 + send(['rdp-pointer', cursorStr]); // The mouse pointer has changed
212 + }
213 }).on('close', function () {
208 - send(['rdp-close']);
214 + send(['rdp-close']); // This RDP session has closed
215 }).on('error', function (err) {
216 if (typeof err == 'string') { send(['rdp-error', err]); }
217 if ((typeof err == 'object') && (err.err) && (err.code)) { send(['rdp-error', err.err, err.code]); }
public/scripts/agent-rdp-0.0.1.js
+10
@@ -18,6 +18,10 @@ var CreateRDPDesktop = function (canvasid) {
18 obj.m.onClipboardChanged = null;
19 obj.onConsoleMessageChange = null;
20
21 + var xMouseCursorActive = true;
22 + var xMouseCursorCurrent = 'default';
23 + obj.mouseCursorActive = function (x) { if (xMouseCursorActive == x) return; xMouseCursorActive = x; obj.CanvasId.style.cursor = ((x == true) ? xMouseCursorCurrent : 'default'); }
24 +
25 function mouseButtonMap(button) {
26 // Swap mouse buttons if needed
27 if (obj.m.SwapMouse === true) return [2, 0, 1, 0, 0][button];
@@ -75,6 +79,12 @@ var CreateRDPDesktop = function (canvasid) {
79 obj.render.update(bitmap);
80 break;
81 }
82 + case 'rdp-pointer': {
83 + var pointer = msg[1];
84 + xMouseCursorCurrent = pointer;
85 + if (xMouseCursorActive) { obj.CanvasId.style.cursor = pointer; }
86 + break;
87 + }
88 case 'rdp-close': {
89 obj.Stop();
90 break;
rdp/core/layer.js
+1 -1
@@ -22,7 +22,7 @@ var fs = require('fs');
22 var type = require('./type');
23 var log = require('./log');
24 var tls = require('tls');
25 -var crypto = require('crypto');
25 +//var crypto = require('crypto');
26 var events = require('events');
27
28 /**
rdp/protocol/pdu/data.js
+78 -9
@@ -186,7 +186,7 @@ var FastPathUpdateType = {
186 FASTPATH_UPDATETYPE_PTR_NULL : 0x5,
187 FASTPATH_UPDATETYPE_PTR_DEFAULT : 0x6,
188 FASTPATH_UPDATETYPE_PTR_POSITION : 0x8,
189 - FASTPATH_UPDATETYPE_COLOR : 0x9,
189 + FASTPATH_UPDATETYPE_COLOR : 0x9, // Mouse cursor
190 FASTPATH_UPDATETYPE_CACHED : 0xA,
191 FASTPATH_UPDATETYPE_POINTER : 0xB
192 };
@@ -1075,7 +1075,7 @@ function clipPDU() {
1075 * @param opt {object} type option
1076 * @returns {type.Component}
1077 */
1078 -function fastPathBitmapUpdateDataPDU (opt) {
1078 +function fastPathBitmapUpdateDataPDU(opt) {
1079 var self = {
1080 __FASTPATH_UPDATE_TYPE__ : FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP,
1081 header : new type.UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, { constant : true }),
@@ -1093,13 +1093,67 @@ function fastPathBitmapUpdateDataPDU (opt) {
1093 return new type.Component(self, opt);
1094 }
1095
1096 +// This is a table of cursorid to cursor name.
1097 +// Created by movering the mouse over this page: https://www.w3schools.com/csSref/tryit.asp?filename=trycss_cursor
1098 +const cursorIdTable = {
1099 + // Normal style mouse cursor
1100 + 903013897: 'alias',
1101 + 370524792: 'all-scroll',
1102 + 853046751: 'cell',
1103 + 2101250798: 'col-resize',
1104 + 703681364: 'copy',
1105 + 992638936: 'crosshair',
1106 + 1539083673: 'ew-resize',
1107 + 1919796298: 'grab',
1108 + 1010243511: 'grabbing',
1109 + 1247283057: 'help',
1110 + 1390892051: 'none',
1111 + 885751489: 'not-allowed',
1112 + 1732952247: 'row-resize',
1113 + 747144997: 'url',
1114 + 2018345610: 'zoom-in',
1115 + 347367048: 'zoom-out',
1116 + 1872942890: 'default',
1117 + 1737852989: 'text',
1118 + 1932827019: 'ns-resize',
1119 + 1884471290: 'nesw-resize',
1120 + 1204065391: 'nwse-resize',
1121 + 2030531519: 'progress',
1122 + 1050842114: 'pointer',
1123 +
1124 + // Black style cursors
1125 + 1258195498: 'default',
1126 + 219484254: 'all-scroll',
1127 + 399295089: 'text',
1128 + 1912613597: 'wait',
1129 + 864127801: 'ew-resize',
1130 + 23245044: 'nesw-resize',
1131 + 1966995494: 'not-allowed',
1132 + 1873216615: 'help',
1133 + 255126408: 'nesw-resize',
1134 + 157191894: 'ns-resize',
1135 + 1768446509: 'pointer',
1136 + 1032011501: 'crosshair'
1137 +}
1138 +
1139 +function fastPathPointerUpdateDataPDU(opt, cursorId, cursorStr) {
1140 + var self = {
1141 + __FASTPATH_UPDATE_TYPE__: FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR,
1142 + header: new type.UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR, { constant: true }),
1143 + cursorId: cursorId,
1144 + cursorStr: cursorStr
1145 + };
1146 +
1147 + return new type.Component(self, opt);
1148 +}
1149 +
1150 /**
1151 * @see http://msdn.microsoft.com/en-us/library/cc240622.aspx
1152 * @param updateData {type.Component}
1153 * @param opt {object} type option
1154 * @returns {type.Component}
1155 */
1102 -function fastPathUpdatePDU (updateData, opt) {
1156 +function fastPathUpdatePDU(updateData, opt) {
1157 var self = {
1158 updateHeader : new type.UInt8( function () {
1159 return self.updateData.obj.__FASTPATH_UPDATE_TYPE__;
@@ -1116,12 +1170,27 @@ function fastPathUpdatePDU (updateData, opt) {
1170 }) };
1171
1172 switch (self.updateHeader.value & 0xf) {
1119 - case FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
1120 - self.updateData = fastPathBitmapUpdateDataPDU(options).read(s);
1121 - break;
1122 - default:
1123 - self.updateData = new type.BinaryString(null, options).read(s);
1124 - log.debug('unknown fast path pdu type ' + (self.updateHeader.value & 0xf));
1173 + case FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: {
1174 + self.updateData = fastPathBitmapUpdateDataPDU(options).read(s);
1175 + break;
1176 + }
1177 + case FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR: {
1178 + var data = new type.BinaryString(null, options).read(s);
1179 +
1180 + // Hash the data to get a cursor id.
1181 + // 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
1182 + const hasher = require('crypto').createHash('sha384');
1183 + hasher.update(data.value);
1184 + const cursorid = Math.abs(hasher.digest().readInt32BE(0));
1185 + const cursorStr = cursorIdTable[cursorid];
1186 + //if (cursorStr == null) { console.log('Unknown cursorId: ' + cursorid); }
1187 + self.updateData = fastPathPointerUpdateDataPDU(options, cursorid, cursorStr);
1188 + break;
1189 + }
1190 + default: {
1191 + self.updateData = new type.BinaryString(null, options).read(s);
1192 + log.debug('unknown fast path pdu type ' + (self.updateHeader.value & 0xf));
1193 + }
1194 }
1195 })
1196 };
rdp/protocol/pdu/global.js
+9 -4
@@ -268,11 +268,16 @@ Client.prototype.recvServerFontMapPDU = function(s) {
268 */
269 Client.prototype.recvFastPath = function (secFlag, s) {
270 while (s.availableLength() > 0) {
271 - var pdu = data.fastPathUpdatePDU().read(s);
271 + var pdu = data.fastPathUpdatePDU().read(s);
272 switch (pdu.obj.updateHeader.value & 0xf) {
273 - case data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
274 - this.emit('bitmap', pdu.obj.updateData.obj.rectangles.obj);
275 - break;
273 + case data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: {
274 + this.emit('bitmap', pdu.obj.updateData.obj.rectangles.obj);
275 + break;
276 + }
277 + case data.FastPathUpdateType.FASTPATH_UPDATETYPE_COLOR: {
278 + this.emit('pointer', pdu.obj.updateData.obj.cursorId, pdu.obj.updateData.obj.cursorStr);
279 + break;
280 + }
281 default:
282 }
283 }
rdp/protocol/rdp.js
+2
@@ -160,6 +160,8 @@ function RdpClient(config) {
160 }).on('close', function () {
161 self.connected = false;
162 self.emit('close');
163 + }).on('pointer', function (cursorId, cursorStr) {
164 + self.emit('pointer', cursorId, cursorStr);
165 }).on('bitmap', function (bitmaps) {
166 for (var bitmap in bitmaps) {
167 var bitmapData = bitmaps[bitmap].obj.bitmapDataStream.value;