2 * Copyright (c) 2015 Sylvain Peyrefitte
4 * This file is part of mstsc.js.
6 * mstsc.js is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * added get clipboard from remote RDP - Simon Smith 2024
21 * added set clipboard to remote RDP - Simon Smith 2024
26 * Mouse button mapping
27 * @param button {integer} client button number
29 function mouseButtonMap(button) {
39 * Input client connection (mouse and keyboard)
41 * @param canvas {canvas} rendering element
43 function Client(canvas) {
46 this.render = new Mstsc.Canvas.create(this.canvas);
48 this.activeSession = false;
49 this.mouseNagleTimer = null;
50 this.mouseNagleData = null;
55 obj.mNagleTimer = setTimeout(function () {
56 obj.send(String.fromCharCode(5, obj.buttonmask) + ShortToStr(obj.mx) + ShortToStr(obj.my));
57 obj.mNagleTimer = null;
62 install : function () {
65 // Bind mouse move event
66 this.canvas.addEventListener('mousemove', function (e) {
67 if (!self.socket || !self.activeSession) return;
68 var rect = e.target.getBoundingClientRect();
69 self.mouseNagleData = ['mouse', e.clientX - rect.left, e.clientY - rect.top, 0, false];
70 if (self.mouseNagleTimer == null) {
71 //console.log('sending', self.mouseNagleData);
72 self.mouseNagleTimer = setTimeout(function () { self.socket.send(JSON.stringify(self.mouseNagleData)); self.mouseNagleTimer = null; }, 50);
74 //self.socket.send(JSON.stringify(this.mouseNagleData));
78 this.canvas.addEventListener('mousedown', function (e) {
79 if (!self.socket || !self.activeSession) return;
80 if (self.mouseNagleTimer != null) { clearTimeout(self.mouseNagleTimer); self.mouseNagleTimer = null; }
81 var rect = e.target.getBoundingClientRect();
82 self.socket.send(JSON.stringify(['mouse', e.clientX - rect.left, e.clientY - rect.top, mouseButtonMap(e.button), true]));
86 this.canvas.addEventListener('mouseup', function (e) {
87 if (!self.socket || !self.activeSession) return;
88 if (self.mouseNagleTimer != null) { clearTimeout(self.mouseNagleTimer); self.mouseNagleTimer = null; }
89 var rect = e.target.getBoundingClientRect();
90 self.socket.send(JSON.stringify(['mouse', e.clientX - rect.left, e.clientY - rect.top, mouseButtonMap(e.button), false]));
94 this.canvas.addEventListener('contextmenu', function (e) {
95 if (!self.socket || !self.activeSession) return;
96 if (self.mouseNagleTimer != null) { clearTimeout(self.mouseNagleTimer); self.mouseNagleTimer = null; }
97 var rect = e.target.getBoundingClientRect();
98 self.socket.send(JSON.stringify(['mouse', e.clientX - rect.left, e.clientY - rect.top, mouseButtonMap(e.button), false]));
102 this.canvas.addEventListener('DOMMouseScroll', function (e) {
103 if (!self.socket || !self.activeSession) return;
104 if (self.mouseNagleTimer != null) { clearTimeout(self.mouseNagleTimer); self.mouseNagleTimer = null; }
105 var isHorizontal = false;
106 var delta = e.detail;
107 //var step = Math.round(Math.abs(delta) * 15 / 8);
108 //var step = Math.abs(e.detail);
110 //console.log('DOMMouseScroll', delta, step, e.detail);
111 var rect = e.target.getBoundingClientRect();
112 self.socket.send(JSON.stringify(['wheel', e.clientX - rect.left, e.clientY - rect.top, step, delta > 0, isHorizontal]));
116 this.canvas.addEventListener('mousewheel', function (e) {
117 if (!self.socket || !self.activeSession) return;
118 if (self.mouseNagleTimer != null) { clearTimeout(self.mouseNagleTimer); self.mouseNagleTimer = null; }
119 var isHorizontal = Math.abs(e.deltaX) > Math.abs(e.deltaY);
120 var delta = isHorizontal?e.deltaX:e.deltaY;
121 //var step = Math.round(Math.abs(delta) * 15 / 8);
123 //console.log('mousewheel', delta, step, e);
124 var rect = e.target.getBoundingClientRect();
125 self.socket.send(JSON.stringify(['wheel', e.clientX - rect.left, e.clientY - rect.top, step, delta > 0, isHorizontal]));
130 // Bind keyboard event
131 window.addEventListener('keydown', function (e) {
132 if (!self.socket || !self.activeSession) return;
133 self.socket.send(JSON.stringify(['scancode', Mstsc.scancode(e), true]));
137 window.addEventListener('keyup', function (e) {
138 if (!self.socket || !self.activeSession) return;
139 self.socket.send(JSON.stringify(['scancode', Mstsc.scancode(e), false]));
149 disconnect: function () {
150 if (this.socket) { this.socket.close(); }
154 * @param ip {string} ip target for rdp
155 * @param domain {string} microsoft domain
156 * @param username {string} session username
157 * @param password {string} session password
158 * @param next {function} asynchrone end callback
160 connect : function (ip, domain, username, password, options, next) {
163 this.socket = new WebSocket('wss://' + window.location.host + '/mstscrelay.ashx');
164 this.socket.binaryType = 'arraybuffer';
165 this.socket.onopen = function () {
166 //console.log("WS-OPEN");
167 self.socket.send(JSON.stringify(['infos', {
171 width: self.canvas.width,
172 height: self.canvas.height
178 locale: Mstsc.locale()
180 self.prevClipboardText = null;
181 self.clipboardReadTimer = setInterval(function(){
182 if(navigator.clipboard.readText != null){
183 if (Mstsc.browser() == 'firefox') return; // this is needed because firefox pops up a PASTE option every second which is annoying
184 navigator.clipboard.readText()
185 .then(function(data){
186 if(data != self.prevClipboard){
187 self.prevClipboard = data;
188 if (self.socket) { self.socket.send(JSON.stringify(['clipboard', data])); }
191 .catch(function(){ });
195 this.socket.onmessage = function (evt) {
196 if (typeof evt.data == 'string') {
197 // This is a JSON text string, parse it.
198 var msg = JSON.parse(evt.data);
200 case 'rdp-connect': {
201 //console.log('[mstsc.js] connected');
202 self.activeSession = true;
206 if (self.bitmapData == null) break;
208 bitmap.data = self.bitmapData; // Use the binary data that was sent earlier.
209 delete self.bitmapData;
210 //console.log('[mstsc.js] bitmap update bpp : ' + bitmap.bitsPerPixel);
211 self.render.update(bitmap);
215 //console.log('[mstsc.js] close');
216 self.activeSession = false;
222 console.log('[mstsc.js] error : ' + err.code + '(' + err.message + ')');
223 self.activeSession = false;
227 case 'rdp-clipboard': {
228 if ((msg[1] != null) && (navigator.clipboard.writeText != null)) {
229 navigator.clipboard.writeText(msg[1]) // Put remote clipboard data into our clipboard
230 .then(function() { })
231 .catch(function(err) { console.log('clipboard.writeText Error', err); });
237 // This is binary bitmap data, store it.
238 self.bitmapData = evt.data;
241 this.socket.onclose = function () {
242 //console.log("WS-CLOSE");
243 self.activeSession = false;
244 clearInterval(self.clipboardReadTimer);
245 self.prevClipboardText = null;
251 MstscClient = { create : function (canvas) { return new Client(canvas); } }