2* @description Remote Desktop
3* @author Ylian Saint-Hilaire
7// Construct a MeshServer object
8var CreateAmtRemoteDesktop = function (divid, scrolldiv) {
11 obj.CanvasId = Q(divid);
12 obj.scrolldiv = scrolldiv;
13 obj.canvas = Q(divid).getContext('2d');
14 obj.protocol = 2; // KVM
17 obj.ScreenWidth = 960;
18 obj.ScreenHeight = 700;
23 obj.bpp = 2; // Bytes per pixel (1 or 2 supported)
27 obj.localKeyMap = true;
34 obj.onScreenSizeChange = null;
35 obj.frameRateDelay = 0;
36 // ###BEGIN###{DesktopRotation}
37 obj.noMouseRotate = false;
39 // ###END###{DesktopRotation}
40 // ###BEGIN###{DesktopInband}
41 obj.kvmDataSupported = false;
43 obj.onKvmDataPending = [];
44 obj.onKvmDataAck = -1;
46 obj.lastKeepAlive = Date.now();
48 obj.kvmExtChanged = null;
50 obj.decimationMode = 0; // 0 = Don't set, 1 = Disable, 2 = Automatic, 3 = Enabled
53 // ###END###{DesktopInband}
55 obj.mNagleTimer = null; // Mouse motion slowdown timer
56 obj.mx = 0; // Last mouse x position
57 obj.my = 0; // Last mouse y position
58 // ###BEGIN###{DesktopFocus}
59 obj.ox = -1; // Old mouse x position
60 obj.oy = -1; // Old mouse y position
62 // ###END###{DesktopFocus}
63 // ###BEGIN###{Inflate}
64 obj.inflate = ZLIB.inflateInit(-15);
67 obj.xxStateChange = function (newstate) {
69 obj.canvas.fillStyle = '#000000';
70 obj.canvas.fillRect(0, 0, obj.width, obj.height);
71 obj.canvas.canvas.width = obj.rwidth = obj.width = 640;
72 obj.canvas.canvas.height = obj.rheight = obj.height = 400;
73 QS(obj.canvasid).cursor = 'default';
75 QS(obj.canvasid).cursor = obj.showmouse ? 'default' : 'none';
79 function arrToStr(arr) { return String.fromCharCode.apply(null, arr); }
80 function strToArr(str) { var arr = new Uint8Array(str.length); for (var i = 0, j = str.length; i < j; ++i) { arr[i] = str.charCodeAt(i); } return arr }
82 obj.ProcessBinaryData = function (data) {
83 // ###BEGIN###{DesktopRecorder}
84 // Record the data if needed
85 if ((obj.recordedData != null) && (obj.recordedHolding !== true)) { obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, new Uint8Array(data)))); }
86 // ###END###{DesktopRecorder}
88 // Append to accumulator
89 if (obj.acc == null) {
90 obj.acc = new Uint8Array(data);
92 var tmp = new Uint8Array(obj.acc.byteLength + data.byteLength);
94 tmp.set(new Uint8Array(data), obj.acc.byteLength);
98 while ((obj.acc != null) && (obj.acc.byteLength > 0)) {
99 //console.log('KAcc', obj.state, obj.acc);
100 var cmdsize = 0, accview = new DataView(obj.acc.buffer);
101 if ((obj.state == 0) && (obj.acc.byteLength >= 12)) {
102 // Getting handshake & version
104 //if (obj.acc.substring(0, 4) != 'RFB ') { return obj.Stop(); }
105 //var version = parseFloat(obj.acc.substring(4, 11));
106 //console.log('KVersion: ' + version);
108 if (obj.parent) { delete obj.parent.connectTime; }
109 obj.send('RFB 003.008\n');
111 else if ((obj.state == 1) && (obj.acc.byteLength >= 1)) {
112 // Getting security options
113 cmdsize = obj.acc[0] + 1;
114 obj.send(String.fromCharCode(1)); // Send the 'None' security type. Since we already authenticated using redirection digest auth, we don't need to do this again.
117 else if ((obj.state == 2) && (obj.acc.byteLength >= 4)) {
118 // Getting security response
120 if (accview.getUint32(0) != 0) { return obj.Stop(); }
121 obj.send(String.fromCharCode(1)); // Send share desktop flag
123 if (obj.parent) { obj.parent.disconnectCode = 50000; } // If Intel AMT disconnects at exactly this moment, indicates we need RLE8 or unsupported GPU.
125 else if ((obj.state == 3) && (obj.acc.byteLength >= 24)) {
126 // Getting server init
127 // ###BEGIN###{DesktopRotation}
128 obj.rotation = 0; // We don't currently support screen init while rotated.
129 // ###END###{DesktopRotation}
130 var namelen = accview.getUint32(20);
131 if (obj.acc.byteLength < 24 + namelen) return;
132 cmdsize = 24 + namelen;
133 obj.canvas.canvas.width = obj.rwidth = obj.width = obj.ScreenWidth = accview.getUint16(0);
134 obj.canvas.canvas.height = obj.rheight = obj.height = obj.ScreenHeight = accview.getUint16(2);
135 //console.log('Initial Desktop width: ' + obj.width + ', height: ' + obj.height);
137 // ###BEGIN###{DesktopRecorder}
138 obj.DeskRecordServerInit = String.fromCharCode.apply(null, new Uint8Array(obj.acc.buffer.slice(0, 24 + namelen)));
139 // ###END###{DesktopRecorder}
141 // These are all values we don't really need, we are going to only run in RGB565 or RGB332 and not use the flexibility provided by these settings.
142 // Makes the javascript code smaller and maybe a bit faster.
144 obj.xbpp = obj.acc[4];
145 obj.depth = obj.acc[5];
146 obj.bigend = obj.acc[6];
147 obj.truecolor = obj.acc[7];
148 obj.rmax = ReadShort(obj.acc, 8);
149 obj.gmax = ReadShort(obj.acc, 10);
150 obj.bmax = ReadShort(obj.acc, 12);
151 obj.rsh = obj.acc[14];
152 obj.gsh = obj.acc[15];
153 obj.bsh = obj.acc[16];
154 var name = obj.acc.substring(24, 24 + namelen);
155 console.log('name: ' + name);
156 console.log('width: ' + obj.width + ', height: ' + obj.height);
157 console.log('bits-per-pixel: ' + obj.xbpp);
158 console.log('depth: ' + obj.depth);
159 console.log('big-endian-flag: ' + obj.bigend);
160 console.log('true-colour-flag: ' + obj.truecolor);
161 console.log('rgb max: ' + obj.rmax + ',' + obj.gmax + ',' + obj.bmax);
162 console.log('rgb shift: ' + obj.rsh + ',' + obj.gsh + ',' + obj.bsh);
165 // SetEncodings, with AMT we can't omit RAW, must be specified.
166 // Intel AMT supports encodings: RAW (0), RLE (16), Desktop Size (0xFFFFFF21, -223)
168 var supportedEncodings = '';
169 if (obj.useRLE) supportedEncodings += IntToStr(16);
170 supportedEncodings += IntToStr(0);
171 // ###BEGIN###{DesktopInband}
172 supportedEncodings += IntToStr(1092);
173 // ###END###{DesktopInband}
175 obj.send(String.fromCharCode(2, 0) + ShortToStr((supportedEncodings.length / 4) + 1) + supportedEncodings + IntToStr(-223)); // Supported Encodings + Desktop Size
177 if (obj.graymode == false) {
178 // Set the pixel encoding to something much smaller
179 // obj.send(String.fromCharCode(0, 0, 0, 0, 16, 16, 0, 1) + ShortToStr(31) + ShortToStr(63) + ShortToStr(31) + String.fromCharCode(11, 5, 0, 0, 0, 0)); // Setup 16 bit color RGB565 (This is the default, so we don't need to set it)
180 if (obj.bpp == 1) obj.send(String.fromCharCode(0, 0, 0, 0, 8, 8, 0, 1) + ShortToStr(7) + ShortToStr(7) + ShortToStr(3) + String.fromCharCode(5, 2, 0, 0, 0, 0)); // Setup 8 bit color RGB332
183 if (obj.bpp == 2) { obj.bpp = 1; }
184 if (obj.lowcolor == false) {
185 obj.send(String.fromCharCode(0, 0, 0, 0, 8, 8, 0, 1) + ShortToStr(255) + ShortToStr(0) + ShortToStr(0) + String.fromCharCode(0, 0, 0, 0, 0, 0)); // Setup 8 bit black and white RGB800
187 obj.send(String.fromCharCode(0, 0, 0, 0, 8, 4, 0, 1) + ShortToStr(15) + ShortToStr(0) + ShortToStr(0) + String.fromCharCode(0, 0, 0, 0, 0, 0)); // Setup 4 bit black and white RGB400
193 obj.parent.connectTime = Date.now();
194 obj.parent.disconnectCode = 0;
195 obj.parent.xxStateChange(3);
197 //obj.timer = setInterval(obj.xxOnTimer, 50);
199 // ###BEGIN###{DesktopFocus}
200 obj.ox = -1; // Old mouse x position
201 // ###END###{DesktopFocus}
202 // ###BEGIN###{DesktopInband}
203 if (obj.kvmExtChanged != null) {
204 if (obj.decimationMode > 0) { obj.sendKvmExtCmd(2, obj.decimationMode); } // Set Decimation Mode (0 = Do not set, 1 = Disable, 2 = Auto, 3 = Enable)
205 obj.sendKvmExtCmd(4, (obj.useZLib === true) ? 1 : 0); // Set ZLib state (0 = Disabled, 1 = Enabled)
207 // ###END###{DesktopInband}
210 if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); }
212 obj.parent.disconnectCode = 50001; // Everything looks good, a disconnection here would be Intel AMT initiated.
214 // Check if the screen size is larger than Intel AMT should be able to handle
215 //console.log('KVM Buffer Size: ' + (obj.bpp * obj.width * obj.height));
216 if ((obj.bpp * obj.width * obj.height) > 9216000) { obj.parent.disconnectCode = 50002; } // Display buffer too large.
219 else if (obj.state == 4) {
220 switch (obj.acc[0]) {
221 case 0: // FramebufferUpdate
222 if (obj.acc.byteLength < 4) return;
223 obj.state = 100 + accview.getUint16(2); // Read the number of tiles that are going to be sent, add 100 and use that as our protocol state.
226 // ###BEGIN###{DesktopRecorder}
227 // This is the start of a new frame, start recording now if needed.
228 if (obj.recordedHolding === true) { delete obj.recordedHolding; obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, obj.acc))); }
229 // ###END###{DesktopRecorder}
232 case 2: // This is the bell, do nothing.
235 case 3: // This is ServerCutText
236 if (obj.acc.byteLength < 8) return;
237 var len = accview.getUint32(4) + 8;
238 if (obj.acc.byteLength < len) return;
239 cmdsize = handleServerCutText(obj.acc, accview);
243 else if ((obj.state > 100) && (obj.acc.byteLength >= 12)) {
244 var x = accview.getUint16(0),
245 y = accview.getUint16(2),
246 width = accview.getUint16(4),
247 height = accview.getUint16(6),
249 encoding = accview.getUint32(8);
252 if ((width < 1) || (width > 64) || (height < 1) || (height > 64)) { console.log('Invalid tile size (' + width + ',' + height + '), disconnecting.'); return obj.Stop(); }
254 // Set the spare bitmap to the right size if it's not already. This allows us to recycle the spare most if not all the time.
255 if ((obj.sparew != width) || (obj.spareh != height)) {
256 obj.sparew = obj.sparew2 = width;
257 obj.spareh = obj.spareh2 = height;
258 // ###BEGIN###{DesktopRotation}
259 if (obj.rotation == 1 || obj.rotation == 3) { obj.sparew2 = height, obj.spareh2 = width; }
260 // ###END###{DesktopRotation}
261 var xspacecachename = obj.sparew2 + 'x' + obj.spareh2;
262 obj.spare = obj.sparecache[xspacecachename];
264 obj.sparecache[xspacecachename] = obj.spare = obj.canvas.createImageData(obj.sparew2, obj.spareh2);
265 var j = (obj.sparew2 * obj.spareh2) << 2;
266 for (var i = 3; i < j; i += 4) { obj.spare.data[i] = 0xFF; } // Set alpha channel to opaque.
271 if (encoding == 0xFFFFFF21) {
272 // Desktop Size (0xFFFFFF21, -223)
273 obj.canvas.canvas.width = obj.rwidth = obj.width = width;
274 obj.canvas.canvas.height = obj.rheight = obj.height = height;
275 obj.send(String.fromCharCode(3, 0, 0, 0, 0, 0) + ShortToStr(obj.width) + ShortToStr(obj.height)); // FramebufferUpdateRequest
277 if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); }
278 //console.log('Desktop width: ' + obj.width + ', height: ' + obj.height);
280 // Check if the screen size is larger than Intel AMT should be able to handle
281 //console.log('KVM Buffer Size: ' + (obj.bpp * obj.width * obj.height));
282 if ((obj.parent) && ((obj.bpp * obj.width * obj.height) > 9216000)) { obj.parent.disconnectCode = 50002; } // Display buffer too large.
283 } else if (encoding == 0) {
285 var ptr = 12, cs = 12 + (s * obj.bpp);
286 if (obj.acc.byteLength < cs) return; // Check we have all the data needed and we can only draw 64x64 tiles.
289 // CRITICAL LOOP, optimize this as much as possible
291 for (var i = 0; i < s; i++) { _setPixel16(accview.getUint16(ptr, true), i); ptr += 2; }
293 for (var i = 0; i < s; i++) { _setPixel8(obj.acc[ptr++], i); }
295 _putImage(obj.spare, x, y);
296 } else if (encoding == 16) {
298 if (obj.acc.byteLength < 16) return;
299 var datalen = accview.getUint32(12);
300 if (obj.acc.byteLength < (16 + datalen)) return;
302 // Process the ZLib header if this is the first block
303 var ptr = 16, delta = 5, dx = 0;
305 if ((datalen > 5) && (obj.acc[ptr] == 0) && (accview.getUint16(ptr + 1, true) == (datalen - delta))) {
306 // This is an uncompressed ZLib data block
307 _decodeLRE(obj.acc, ptr + 5, x, y, width, height, s, datalen);
309 // ###BEGIN###{Inflate}
311 // This is compressed ZLib data, decompress and process it. (TODO: This need to be optimized, remove str/arr conversions)
312 var str = obj.inflate.inflate(arrToStr(new Uint8Array(obj.acc.buffer.slice(ptr, ptr + datalen - dx))));
313 if (str.length > 0) { _decodeLRE(strToArr(str), 0, x, y, width, height, s, str.length); } else { console.log('Invalid deflate data'); }
315 // ###END###{Inflate}
317 cmdsize = 16 + datalen;
318 } else { return obj.Stop(); }
319 if (--obj.state == 100) {
321 if (obj.frameRateDelay == 0) {
322 _SendRefresh(); // Ask for new frame
324 setTimeout(_SendRefresh, obj.frameRateDelay); // Hold x miliseconds before asking for a new frame
329 //console.log('cmdsize', cmdsize);
330 if (cmdsize == 0) return;
331 if (cmdsize != obj.acc.byteLength) { obj.acc = new Uint8Array(obj.acc.buffer.slice(cmdsize)); } else { obj.acc = null; }
335 function _decodeLRE(data, ptr, x, y, width, height, s, datalen) {
336 var subencoding = data[ptr++], index, v, runlengthdecode, palette = {}, rlecount = 0, runlength = 0, i;
337 if (subencoding == 0) {
340 for (i = 0; i < s; i++) { _setPixel16(data[ptr++] + (data[ptr++] << 8), i); }
342 for (i = 0; i < s; i++) { _setPixel8(data[ptr++], i); }
344 _putImage(obj.spare, x, y);
346 else if (subencoding == 1) {
350 if (obj.lowcolor) { v = v << 4; }
351 obj.canvas.fillStyle = 'rgb(' + v + ',' + v + ',' + v + ')';
353 v = data[ptr++] + ((obj.bpp == 2) ? (data[ptr++] << 8) : 0);
354 obj.canvas.fillStyle = 'rgb(' + ((obj.bpp == 1) ? ((v & 224) + ',' + ((v & 28) << 3) + ',' + _fixColor((v & 3) << 6)) : (((v >> 8) & 248) + ',' + ((v >> 3) & 252) + ',' + ((v & 31) << 3))) + ')';
357 // ###BEGIN###{DesktopRotation}
358 var xx = _rotX(x, y);
361 // ###END###{DesktopRotation}
363 obj.canvas.fillRect(x, y, width, height);
365 else if (subencoding > 1 && subencoding < 17) { // Packed palette encoded tile
367 var br = 4, bm = 15; // br is BitRead and bm is BitMask. By adjusting these two we can support all the variations in this encoding.
369 for (i = 0; i < subencoding; i++) { palette[i] = data[ptr++] + (data[ptr++] << 8); }
370 if (subencoding == 2) { br = 1; bm = 1; } else if (subencoding <= 4) { br = 2; bm = 3; } // Compute bits to read & bit mark
371 while (rlecount < s && ptr < data.byteLength) { v = data[ptr++]; for (i = (8 - br); i >= 0; i -= br) { _setPixel16(palette[(v >> i) & bm], rlecount++); } } // Display all the bits
373 for (i = 0; i < subencoding; i++) { palette[i] = data[ptr++]; }
374 if (subencoding == 2) { br = 1; bm = 1; } else if (subencoding <= 4) { br = 2; bm = 3; } // Compute bits to read & bit mark
375 while (rlecount < s && ptr < data.byteLength) { v = data[ptr++]; for (i = (8 - br); i >= 0; i -= br) { _setPixel8(palette[(v >> i) & bm], rlecount++); } } // Display all the bits
377 _putImage(obj.spare, x, y);
379 else if (subencoding == 128) { // RLE encoded tile
381 while (rlecount < s && ptr < data.byteLength) {
383 v = data[ptr++] + (data[ptr++] << 8);
385 // Decode the run length. This is the fastest and most compact way I found to do this.
386 runlength = 1; do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255);
389 if (obj.rotation == 0) {
390 _setPixel16run(v, rlecount, runlength); rlecount += runlength;
392 while (--runlength >= 0) { _setPixel16(v, rlecount++); }
396 while (rlecount < s && ptr < data.byteLength) {
400 // Decode the run length. This is the fastest and most compact way I found to do this.
401 runlength = 1; do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255);
404 if (obj.rotation == 0) {
405 _setPixel8run(v, rlecount, runlength); rlecount += runlength;
407 while (--runlength >= 0) { _setPixel8(v, rlecount++); }
411 _putImage(obj.spare, x, y);
413 else if (subencoding > 129) { // Palette RLE encoded tile
416 for (i = 0; i < (subencoding - 128); i++) { palette[i] = data[ptr++] + (data[ptr++] << 8); }
418 for (i = 0; i < (subencoding - 128); i++) { palette[i] = data[ptr++]; }
421 // Decode RLE on palette
422 while (rlecount < s && ptr < data.byteLength) {
423 // Setup the run, get the color index and get the color from the palette.
424 runlength = 1; index = data[ptr++]; v = palette[index % 128];
426 // If the index starts with high order bit 1, this is a run and decode the run length.
427 if (index > 127) { do { runlength += (runlengthdecode = data[ptr++]); } while (runlengthdecode == 255); }
430 if (obj.rotation == 0) {
432 _setPixel16run(v, rlecount, runlength); rlecount += runlength;
434 _setPixel8run(v, rlecount, runlength); rlecount += runlength;
438 while (--runlength >= 0) { _setPixel16(v, rlecount++); }
440 while (--runlength >= 0) { _setPixel8(v, rlecount++); }
444 _putImage(obj.spare, x, y);
448 // ###BEGIN###{DesktopInband}
449 obj.hold = function (holding) {
450 if (obj.holding == holding) return;
451 obj.holding = holding;
452 obj.canvas.fillStyle = '#000000';
453 obj.canvas.fillRect(0, 0, obj.width, obj.height); // Paint black
454 if (obj.holding == false) {
455 // Go back to normal operations
456 // Set canvas size and ask for full screen refresh
457 if ((obj.canvas.canvas.width != obj.width) || (obj.canvas.canvas.height != obj.height)) {
458 obj.canvas.canvas.width = obj.width; obj.canvas.canvas.height = obj.height;
459 if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); } // ???
461 obj.send(String.fromCharCode(3, 0, 0, 0, 0, 0) + ShortToStr(obj.width) + ShortToStr(obj.height)); // FramebufferUpdateRequest
463 obj.UnGrabMouseInput();
464 obj.UnGrabKeyInput();
467 // ###END###{DesktopInband}
469 function _putImage(i, x, y) {
470 // ###BEGIN###{DesktopInband}
471 if (obj.holding == true) return;
472 // ###END###{DesktopInband}
473 // ###BEGIN###{DesktopRotation}
474 var xx = _arotX(x, y);
477 // ###END###{DesktopRotation}
478 obj.canvas.putImageData(i, x, y);
481 // Set 8bit color RGB332
482 function _setPixel8(v, p) {
485 // ###BEGIN###{DesktopRotation}
486 if (obj.rotation > 0) {
487 if (obj.rotation == 1) { var x = p % obj.sparew, y = Math.floor(p / obj.sparew); p = (x * obj.sparew2) + (obj.sparew2 - 1 - y); pp = p << 2; }
488 else if (obj.rotation == 2) { pp = (obj.sparew * obj.spareh * 4) - 4 - pp; }
489 else if (obj.rotation == 3) { var x = p % obj.sparew, y = Math.floor(p / obj.sparew); p = ((obj.sparew2 - 1 - x) * obj.sparew2) + (y); pp = p << 2; }
491 // ###END###{DesktopRotation}
494 if (obj.lowcolor) { v = v << 4; }
495 obj.spare.data[pp] = obj.spare.data[pp + 1] = obj.spare.data[pp + 2] = v;
497 obj.spare.data[pp] = v & 224;
498 obj.spare.data[pp + 1] = (v & 28) << 3;
499 obj.spare.data[pp + 2] = _fixColor((v & 3) << 6);
503 // Set 16bit color RGB565
504 function _setPixel16(v, p) {
507 // ###BEGIN###{DesktopRotation}
508 if (obj.rotation > 0) {
509 if (obj.rotation == 1) { var x = p % obj.sparew, y = Math.floor(p / obj.sparew); p = (x * obj.sparew2) + (obj.sparew2 - 1 - y); pp = p << 2; }
510 else if (obj.rotation == 2) { pp = (obj.sparew * obj.spareh * 4) - 4 - pp; }
511 else if (obj.rotation == 3) { var x = p % obj.sparew, y = Math.floor(p / obj.sparew); p = ((obj.sparew2 - 1 - x) * obj.sparew2) + (y); pp = p << 2; }
513 // ###END###{DesktopRotation}
515 obj.spare.data[pp] = (v >> 8) & 248;
516 obj.spare.data[pp + 1] = (v >> 3) & 252;
517 obj.spare.data[pp + 2] = (v & 31) << 3;
520 // Set a run of 8bit color RGB332
521 function _setPixel8run(v, p, run) {
524 if (obj.lowcolor) { v = v << 4; }
525 while (--run >= 0) { obj.spare.data[pp] = obj.spare.data[pp + 1] = obj.spare.data[pp + 2] = v; pp += 4; }
527 var pp = (p << 2), r = (v & 224), g = ((v & 28) << 3), b = (_fixColor((v & 3) << 6));
528 while (--run >= 0) { obj.spare.data[pp] = r; obj.spare.data[pp + 1] = g; obj.spare.data[pp + 2] = b; pp += 4; }
532 // Set a run of 16bit color RGB565
533 function _setPixel16run(v, p, run) {
534 var pp = (p << 2), r = ((v >> 8) & 248), g = ((v >> 3) & 252), b = ((v & 31) << 3);
535 while (--run >= 0) { obj.spare.data[pp] = r; obj.spare.data[pp + 1] = g; obj.spare.data[pp + 2] = b; pp += 4; }
538 // ###BEGIN###{DesktopRotation}
539 function _arotX(x, y) {
540 if (obj.rotation == 0) return x;
541 if (obj.rotation == 1) return obj.canvas.canvas.width - obj.sparew2 - y;
542 if (obj.rotation == 2) return obj.canvas.canvas.width - obj.sparew2 - x;
543 if (obj.rotation == 3) return y;
547 function _arotY(x, y) {
548 if (obj.rotation == 0) return y;
549 if (obj.rotation == 1) return x;
550 if (obj.rotation == 2) return obj.canvas.canvas.height - obj.spareh2 - y;
551 if (obj.rotation == 3) return obj.canvas.canvas.height - obj.spareh - x;
555 function _crotX(x, y) {
556 if (obj.rotation == 0) return x;
557 if (obj.rotation == 1) return y;
558 if (obj.rotation == 2) return obj.canvas.canvas.width - x;
559 if (obj.rotation == 3) return obj.canvas.canvas.height - y;
563 function _crotY(x, y) {
564 if (obj.rotation == 0) return y;
565 if (obj.rotation == 1) return obj.canvas.canvas.width - x;
566 if (obj.rotation == 2) return obj.canvas.canvas.height - y;
567 if (obj.rotation == 3) return x;
571 function _rotX(x, y) {
572 if (obj.rotation == 0) return x;
573 if (obj.rotation == 1) return x;
574 if (obj.rotation == 2) return x - obj.canvas.canvas.width;
575 if (obj.rotation == 3) return x - obj.canvas.canvas.height;
579 function _rotY(x, y) {
580 if (obj.rotation == 0) return y;
581 if (obj.rotation == 1) return y - obj.canvas.canvas.width;
582 if (obj.rotation == 2) return y - obj.canvas.canvas.height;
583 if (obj.rotation == 3) return y;
588 obj.setRotation = function (x) {
589 while (x < 0) { x += 4; }
590 var newrotation = x % 4;
591 //console.log('hard-rot: ' + newrotation);
592 // ###BEGIN###{DesktopInband}
593 if (obj.holding == true) { obj.rotation = newrotation; return; }
594 // ###END###{DesktopInband}
596 if (newrotation == obj.rotation) return true;
597 var rw = obj.canvas.canvas.width;
598 var rh = obj.canvas.canvas.height;
599 if (obj.rotation == 1 || obj.rotation == 3) { rw = obj.canvas.canvas.height; rh = obj.canvas.canvas.width; }
601 // Copy the canvas, put it back in the correct direction
602 if (obj.tcanvas == null) obj.tcanvas = document.createElement('canvas');
603 var tcanvasctx = obj.tcanvas.getContext('2d');
604 tcanvasctx.setTransform(1, 0, 0, 1, 0, 0);
605 tcanvasctx.canvas.width = rw;
606 tcanvasctx.canvas.height = rh;
607 tcanvasctx.rotate((obj.rotation * -90) * Math.PI / 180);
608 if (obj.rotation == 0) tcanvasctx.drawImage(obj.canvas.canvas, 0, 0);
609 if (obj.rotation == 1) tcanvasctx.drawImage(obj.canvas.canvas, -obj.canvas.canvas.width, 0);
610 if (obj.rotation == 2) tcanvasctx.drawImage(obj.canvas.canvas, -obj.canvas.canvas.width, -obj.canvas.canvas.height);
611 if (obj.rotation == 3) tcanvasctx.drawImage(obj.canvas.canvas, 0, -obj.canvas.canvas.height);
613 // Change the size and orientation and copy the canvas back into the rotation
614 if (obj.rotation == 0 || obj.rotation == 2) { obj.canvas.canvas.height = rw; obj.canvas.canvas.width = rh; }
615 if (obj.rotation == 1 || obj.rotation == 3) { obj.canvas.canvas.height = rh; obj.canvas.canvas.width = rw; }
616 obj.canvas.setTransform(1, 0, 0, 1, 0, 0);
617 obj.canvas.rotate((newrotation * 90) * Math.PI / 180);
618 obj.rotation = newrotation;
619 obj.canvas.drawImage(obj.tcanvas, _rotX(0, 0), _rotY(0, 0));
621 obj.width = obj.canvas.canvas.width;
622 obj.height = obj.canvas.canvas.height;
623 if (obj.onScreenResize != null) obj.onScreenResize(obj, obj.width, obj.height, obj.CanvasId);
626 // ###END###{DesktopRotation}
628 function _fixColor(c) { return (c > 127) ? (c + 32) : c; }
630 function _SendRefresh() {
631 // ###BEGIN###{DesktopInband}
632 if (obj.holding == true) return;
633 // ###END###{DesktopInband}
634 // ###BEGIN###{DesktopFocus}
635 if (obj.focusmode > 0) {
636 // Request only pixels around the last mouse position
637 var df = obj.focusmode * 2;
638 obj.send(String.fromCharCode(3, 1) + ShortToStr(Math.max(Math.min(obj.ox, obj.mx) - obj.focusmode, 0)) + ShortToStr(Math.max(Math.min(obj.oy, obj.my) - obj.focusmode, 0)) + ShortToStr(df + Math.abs(obj.ox - obj.mx)) + ShortToStr(df + Math.abs(obj.oy - obj.my))); // FramebufferUpdateRequest
642 // ###END###{DesktopFocus}
643 // Request the entire screen
644 obj.send(String.fromCharCode(3, 1, 0, 0, 0, 0) + ShortToStr(obj.rwidth) + ShortToStr(obj.rheight)); // FramebufferUpdateRequest
645 // ###BEGIN###{DesktopFocus}
647 // ###END###{DesktopFocus}
650 obj.Start = function () {
653 // ###BEGIN###{Inflate}
654 obj.inflate.inflateReset();
655 // ###END###{Inflate}
656 // ###BEGIN###{DesktopInband}
657 obj.onKvmDataPending = [];
658 obj.onKvmDataAck = -1;
659 obj.kvmDataSupported = false;
661 // ###END###{DesktopInband}
662 for (var i in obj.sparecache) { delete obj.sparecache[i]; }
665 obj.Stop = function () { obj.UnGrabMouseInput(); obj.UnGrabKeyInput(); if (obj.parent) { obj.parent.Stop(); } }
666 obj.send = function (x) { if (obj.parent) { obj.parent.send(x); } }
668 var convertAmtKeyCodeTable = {
674 'NumpadMultiply': 42,
678 'NumpadSubtract': 45,
697 'NumpadEnter': 0xff0d,
705 'ArrowRight': 0xff53,
722 'ShiftRight': 0xffe2,
723 'ControlLeft': 0xffe3,
724 'ControlRight': 0xffe4,
730 function convertAmtKeyCode(e) {
731 if (e.code.startsWith('Key') && e.code.length == 4) { return e.code.charCodeAt(3) + ((e.shiftKey == false) ? 32 : 0); }
732 if (e.code.startsWith('Digit') && e.code.length == 6) { return e.code.charCodeAt(5); }
733 if (e.code.startsWith('Numpad') && e.code.length == 7) { return e.code.charCodeAt(6); }
734 return convertAmtKeyCodeTable[e.code];
738 Intel AMT only recognizes a small subset of keysym characters defined in the keysymdef.h so you dont need to
739 implement all the languages (this is taken care by the USB Scancode Extension in RFB4.0 protocol).
740 The only subset recognized by the FW is the defined by the following sets : XK_LATIN1 , XK_MISCELLANY, XK_3270, XK_XKB_KEYS, XK_KATAKANA.
741 In addition to keysymdef.h symbols there are 6 japanese extra keys that we do support:
743 #define XK_Intel_EU_102kbd_backslash_pipe_45 0x17170056 // European 102-key: 45 (backslash/pipe), usb Usage: 0x64
744 #define XK_Intel_JP_106kbd_yen_pipe 0x1717007d // Japanese 106-key: 14 (Yen/pipe), usb Usage: 0x89
745 #define XK_Intel_JP_106kbd_backslash_underbar 0x17170073 // Japanese 106-key: 56 (backslash/underbar), usb Usage: 0x87
746 #define XK_Intel_JP_106kbd_NoConvert 0x1717007b // Japanese 106-key: 131 (NoConvert), usb Usage: 0x8b
747 #define XK_Intel_JP_106kbd_Convert 0x17170079 // Japanese 106-key: 132 (Convert), usb Usage: 0x8a
748 #define XK_Intel_JP_106kbd_Hirigana_Katakana 0x17170070 // Japanese 106-key: 133 (Hirigana/Katakana), usb Usage: 0x88
751 function _keyevent(d, e) {
752 if (!e) { e = window.event; }
754 if (e.code && (obj.localKeyMap == false)) {
755 // For new browsers, this mapping is keyboard language independent
756 var k = convertAmtKeyCode(e);
757 if (k != null) { obj.sendkey(k, d); }
759 // For older browsers, this mapping works best for EN-US keyboard
760 var k = e.keyCode, kk = k;
761 if (e.shiftKey == false && k >= 65 && k <= 90) kk = k + 32;
762 if (k >= 112 && k <= 124) kk = k + 0xFF4E;
763 if (k == 8) kk = 0xff08; // Backspace
764 if (k == 9) kk = 0xff09; // Tab
765 if (k == 13) kk = 0xff0d; // Return
766 if (k == 16) kk = 0xffe1; // Shift (Left)
767 if (k == 17) kk = 0xffe3; // Ctrl (Left)
768 if (k == 18) kk = 0xffe9; // Alt (Left)
769 if (k == 27) kk = 0xff1b; // ESC
770 if (k == 33) kk = 0xff55; // PageUp
771 if (k == 34) kk = 0xff56; // PageDown
772 if (k == 35) kk = 0xff57; // End
773 if (k == 36) kk = 0xff50; // Home
774 if (k == 37) kk = 0xff51; // Left
775 if (k == 38) kk = 0xff52; // Up
776 if (k == 39) kk = 0xff53; // Right
777 if (k == 40) kk = 0xff54; // Down
778 if (k == 45) kk = 0xff63; // Insert
779 if (k == 46) kk = 0xffff; // Delete
780 if (k >= 96 && k <= 105) kk = k - 48; // Key pad numbers
781 if (k == 106) kk = 42; // Pad *
782 if (k == 107) kk = 43; // Pad +
783 if (k == 109) kk = 45; // Pad -
784 if (k == 110) kk = 46; // Pad .
785 if (k == 111) kk = 47; // Pad /
786 if (k == 186) kk = 59; // ;
787 if (k == 187) kk = 61; // =
788 if (k == 188) kk = 44; // ,
789 if (k == 189) kk = 45; // -
790 if (k == 190) kk = 46; // .
791 if (k == 191) kk = 47; // /
792 if (k == 192) kk = 96; // `
793 if (k == 219) kk = 91; // [
794 if (k == 220) kk = 92; // \
795 if (k == 221) kk = 93; // ]
796 if (k == 222) kk = 39; // '
797 //console.log('Key' + d + ': ' + k + ' = ' + kk);
800 return obj.haltEvent(e);
803 obj.sendkey = function (k, d) {
804 if (typeof k == 'object') {
805 var buf = ''; for (var i in k) { buf += (String.fromCharCode(4, k[i][1], 0, 0) + IntToStr(k[i][0])); } obj.send(buf);
807 obj.send(String.fromCharCode(4, d, 0, 0) + IntToStr(k));
811 function handleServerCutText(acc, accview) {
812 if (acc.byteLength < 8) return 0;
813 var len = accview.getUint32(4) + 8;
814 if (acc.byteLength < len) return 0;
815 // ###BEGIN###{DesktopInband}
816 if (obj.onKvmData != null) {
817 var d = arrToStr(new Uint8Array(acc.buffer.slice(8, len)));
818 if ((d.length >= 16) && (d.substring(0, 15) == '\0KvmDataChannel')) {
819 if (obj.kvmDataSupported == false) { obj.kvmDataSupported = true; /*console.log('KVM Data Channel Supported.');*/ }
820 if (((obj.onKvmDataAck == -1) && (d.length == 16)) || (d.charCodeAt(15) != 0)) { obj.onKvmDataAck = true; }
821 try { if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-DataChannel-Recv(' + (d.length - 16) + '): ' + d.substring(16)); } } catch (ex) { }
822 if (d.length >= 16) { obj.onKvmData(d.substring(16)); } // Event the data and ack
823 if ((obj.onKvmDataAck == true) && (obj.onKvmDataPending.length > 0)) { obj.sendKvmData(obj.onKvmDataPending.shift()); } // Send pending data
824 } else if ((d.length >= 13) && (d.substring(0, 11) == '\0KvmExtCmd\0')) {
825 var cmd = d.charCodeAt(11), val = d.charCodeAt(12);
826 //console.log('Received KvmExtCmd', cmd, val, d.length);
828 obj.kvmExt.decimationMode = val;
829 if (d.length > 13) { obj.kvmExt.decimationState = d.charCodeAt(13); }
830 if (obj.kvmExtChanged != null) { obj.kvmExtChanged(1, obj.kvmExt, obj.kvmExt); }
832 if (cmd == 2) { obj.sendKvmExtCmd(1); }
833 if (cmd == 3) { obj.kvmExt.compression = val; if (obj.kvmExtChanged != null) { obj.kvmExtChanged(3, obj.kvmExt); } }
834 if (cmd == 4) { obj.sendKvmExtCmd(3); }
836 console.log('Got KVM clipboard data:', d);
837 try { if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-ClipBoard-Recv(' + d.length + '): ' + rstr2hex(d) + ', ' + d); } } catch (ex) { }
840 // ###END###{DesktopInband}
844 // ###BEGIN###{DesktopInband}
845 obj.sendKvmExtCmd = function (cmd, val) {
846 //console.log('Sending KvmExtCmd', cmd, val);
847 var x = '\0KvmExtCmd\0' + String.fromCharCode(cmd) + (val != null ? String.fromCharCode(val) : '');
848 obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(x.length) + x);
851 obj.sendKvmData = function (x) {
852 if (obj.onKvmDataAck !== true) {
853 obj.onKvmDataPending.push(x);
855 try { if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-DataChannel-Send(' + x.length + '): ' + x); } } catch (ex) { }
856 x = '\0KvmDataChannel\0' + x;
857 obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(x.length) + x);
858 obj.onKvmDataAck = false;
862 // Send a HWKVM keep alive if it's not been sent in the last 5 seconds.
863 obj.sendKeepAlive = function () {
864 if (obj.lastKeepAlive < Date.now() - 5000) { obj.lastKeepAlive = Date.now(); obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(16) + '\0KvmDataChannel\0'); }
866 // ###END###{DesktopInband}
868 // ###BEGIN###{DesktopClipboard}
869 obj.sendClipboardData = function (x) {
870 try { if (urlvars && urlvars['kvmdatatrace']) { console.log('KVM-ClipBoard-Send(' + x.length + '): ' + rstr2hex(x) + ', ' + x); } } catch (ex) { }
871 obj.send(String.fromCharCode(6, 0, 0, 0) + IntToStr(x.length) + x);
873 // ###END###{DesktopClipboard}
875 obj.SendCtrlAltDelMsg = function () { obj.sendcad(); }
876 obj.sendcad = function () { obj.sendkey([[0xFFE3, 1], [0xFFE9, 1], [0xFFFF, 1], [0xFFFF, 0], [0xFFE9, 0], [0xFFE3, 0]]); } // Control down, Alt down, Delete down, Delete up , Alt up , Control up
878 var _MouseInputGrab = false;
879 var _KeyInputGrab = false;
881 obj.GrabMouseInput = function () {
882 if (_MouseInputGrab == true) return;
883 var c = obj.canvas.canvas;
884 c.onmouseup = obj.mouseup;
885 c.onmousedown = obj.mousedown;
886 c.onmousemove = obj.mousemove;
887 //c.onmousewheel = obj.mousewheel;
888 c.onwheel = obj.mousewheel;
889 //if (navigator.userAgent.match(/mozilla/i)) c.DOMMouseScroll = obj.xxDOMMouseScroll; else c.onmousewheel = obj.xxMouseWheel;
890 _MouseInputGrab = true;
893 obj.UnGrabMouseInput = function () {
894 if (_MouseInputGrab == false) return;
895 var c = obj.canvas.canvas;
896 c.onmousemove = null;
898 c.onmousedown = null;
899 //c.onmousewheel = null;
901 //if (navigator.userAgent.match(/mozilla/i)) c.DOMMouseScroll = null; else c.onmousewheel = null;
902 _MouseInputGrab = false;
905 obj.GrabKeyInput = function () {
906 if (_KeyInputGrab == true) return;
907 document.onkeyup = obj.handleKeyUp;
908 document.onkeydown = obj.handleKeyDown;
909 document.onkeypress = obj.handleKeys;
910 _KeyInputGrab = true;
913 obj.UnGrabKeyInput = function () {
914 if (_KeyInputGrab == false) return;
915 document.onkeyup = null;
916 document.onkeydown = null;
917 document.onkeypress = null;
918 _KeyInputGrab = false;
921 obj.handleKeys = function (e) { return obj.haltEvent(e); }
922 obj.handleKeyUp = function (e) { return _keyevent(0, e); }
923 obj.handleKeyDown = function (e) { return _keyevent(1, e); }
924 obj.haltEvent = function (e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
926 // RFB 'PointerEvent' and mouse handlers
927 obj.mousedblclick = function (e) { }
928 obj.mousewheel = function (e) {
930 if (typeof e.deltaY == 'number') { v = -1 * e.deltaY; }
931 else if (typeof e.detail == 'number') { v = -1 * e.detail; }
932 else if (typeof e.wheelDelta == 'number') { v = e.wheelDelta; }
935 // Reverse mouse wheel if needed
936 if (obj.ReverseMouseWheel) { v = -1 * v; }
938 var tmpmask = obj.buttonmask;
939 obj.buttonmask |= (1 << ((v > 0) ? 3 : 4));
941 obj.buttonmask = tmpmask;
942 return obj.mousemove(e, 1);
944 obj.mousedown = function (e) { obj.buttonmask |= (1 << e.button); return obj.mousemove(e, 1); }
945 obj.mouseup = function (e) { obj.buttonmask &= (0xFFFF - (1 << e.button)); return obj.mousemove(e, 1); }
946 obj.mousemove = function (e, force) {
947 if (obj.state < 4) return true;
948 var ScaleFactorHeight = (obj.canvas.canvas.height / Q(obj.canvasid).offsetHeight);
949 var ScaleFactorWidth = (obj.canvas.canvas.width / Q(obj.canvasid).offsetWidth);
950 var Offsets = obj.getPositionOfControl(Q(obj.canvasid));
951 obj.mx = ((event.pageX - Offsets[0]) * ScaleFactorWidth);
952 obj.my = ((event.pageY - Offsets[1]) * ScaleFactorHeight);
953 if (event.addx) { obj.mx += event.addx; }
954 if (event.addy) { obj.my += event.addy; }
956 // ###BEGIN###{DesktopRotation}
957 if ((obj.rotation == 1) || (obj.rotation == 3)) {
958 obj.mx = ((obj.mx * obj.rwidth) / obj.width);
959 obj.my = ((obj.my * obj.rheight) / obj.height);
961 if (obj.noMouseRotate != true) {
962 var mx2 = _crotX(obj.mx, obj.my);
963 obj.my = _crotY(obj.mx, obj.my);
966 // ###END###{DesktopRotation}
968 // This is the mouse motion nagle timer. Slow down the mouse motion event rate.
970 obj.send(String.fromCharCode(5, obj.buttonmask) + ShortToStr(obj.mx) + ShortToStr(obj.my));
971 if (obj.mNagleTimer != null) { clearTimeout(obj.mNagleTimer); obj.mNagleTimer = null; }
973 if (obj.mNagleTimer == null) {
974 obj.mNagleTimer = setTimeout(function () {
975 obj.send(String.fromCharCode(5, obj.buttonmask) + ShortToStr(obj.mx) + ShortToStr(obj.my));
976 obj.mNagleTimer = null;
981 // ###BEGIN###{DesktopFocus}
982 // Update focus area if we are in focus mode
983 QV('DeskFocus', obj.focusmode);
984 if (obj.focusmode != 0) {
985 var x = Math.min(obj.mx, obj.canvas.canvas.width - obj.focusmode),
986 y = Math.min(obj.my, obj.canvas.canvas.height - obj.focusmode),
987 df = obj.focusmode * 2,
989 qx = c.offsetHeight / obj.canvas.canvas.height,
990 qy = c.offsetWidth / obj.canvas.canvas.width,
992 ppos = obj.getPositionOfControl(Q(obj.canvasid).parentElement);
993 q.left = (Math.max(((x - obj.focusmode) * qx), 0) + (pos[0] - ppos[0])) + 'px';
994 q.top = (Math.max(((y - obj.focusmode) * qy), 0) + (pos[1] - ppos[1])) + 'px';
995 q.width = ((df * qx) - 6) + 'px';
996 q.height = ((df * qx) - 6) + 'px';
998 // ###END###{DesktopFocus}
1000 return obj.haltEvent(e);
1003 obj.getPositionOfControl = function (Control) {
1004 var Position = Array(2);
1005 Position[0] = Position[1] = 0;
1007 Position[0] += Control.offsetLeft;
1008 Position[1] += Control.offsetTop;
1009 Control = Control.offsetParent;
1014 // ###BEGIN###{DesktopRecorder}
1015 obj.StartRecording = function () {
1016 if ((obj.recordedData != null) && (obj.DeskRecordServerInit != null)) return false;
1017 obj.recordedHolding = true;
1018 obj.recordedData = [];
1019 obj.recordedStart = Date.now();
1020 obj.recordedSize = 0;
1021 obj.recordedData.push(recordingEntry(1, 0, JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, time: new Date().toLocaleString(), protocol: 102, bpp: obj.bpp, graymode: obj.graymode, lowcolor: obj.lowcolor, screenSize: [obj.width, obj.height] }))); // Metadata, 102 = Midstream Intel AMT KVM
1022 obj.DeskRecordServerInit = String.fromCharCode((obj.width >> 8), (obj.width & 0xFF), (obj.height >> 8), (obj.height & 0xFF)) + obj.DeskRecordServerInit.substring(4);
1023 obj.recordedData.push(recordingEntry(2, 1, obj.DeskRecordServerInit)); // This is the server init command
1024 obj.recordedData.push(recordingEntry(3, 0, atob(obj.CanvasId.toDataURL('image/png').split(',')[1]))); // Take a screen shot
1028 obj.StopRecording = function () {
1029 if (obj.recordedData == null) return;
1030 var r = obj.recordedData;
1031 r.push(recordingEntry(3, 0, 'MeshCentralMCREC'));
1032 delete obj.recordedData;
1033 delete obj.recordedStart;
1034 delete obj.recordedSize;
1038 function recordingEntry(type, flags, data) {
1039 //console.log('recordingEntry', type, flags, (typeof data == 'number')?data:data.length);
1040 // Header: Type (2) + Flags (2) + Size(4) + Time(8)
1041 // Type (1 = Header, 2 = Network Data), Flags (1 = Binary, 2 = User), Size (4 bytes), Time (8 bytes)
1042 var now = Date.now();
1043 if (typeof data == 'number') {
1044 obj.recordedSize += data;
1045 return ShortToStr(type) + ShortToStr(flags) + IntToStr(data) + IntToStr(now >> 32) + IntToStr(now & 32);
1047 obj.recordedSize += data.length;
1048 return ShortToStr(type) + ShortToStr(flags) + IntToStr(data.length) + IntToStr(now >> 32) + IntToStr(now & 32) + data;
1051 // ###END###{DesktopRecorder}