| 1 | /** |
| 2 | * @description Remote Desktop |
| 3 | * @author Ylian Saint-Hilaire |
| 4 | * @version v0.0.2g |
| 5 | */ |
| 6 | |
| 7 | // Construct a MeshServer object |
| 8 | var CreateAmtRemoteDesktop = function (divid, scrolldiv) { |
| 9 | var obj = {}; |
| 10 | obj.canvasid = divid; |
| 11 | obj.CanvasId = Q(divid); |
| 12 | obj.scrolldiv = scrolldiv; |
| 13 | obj.canvas = Q(divid).getContext('2d'); |
| 14 | obj.protocol = 2; // KVM |
| 15 | obj.state = 0; |
| 16 | obj.acc = null; |
| 17 | obj.ScreenWidth = 960; |
| 18 | obj.ScreenHeight = 700; |
| 19 | obj.width = 0; |
| 20 | obj.height = 0; |
| 21 | obj.rwidth = 0; |
| 22 | obj.rheight = 0; |
| 23 | obj.bpp = 2; // Bytes per pixel (1 or 2 supported) |
| 24 | obj.useRLE = true; |
| 25 | obj.showmouse = true; |
| 26 | obj.buttonmask = 0; |
| 27 | obj.localKeyMap = true; |
| 28 | obj.spare = null; |
| 29 | obj.sparew = 0; |
| 30 | obj.spareh = 0; |
| 31 | obj.sparew2 = 0; |
| 32 | obj.spareh2 = 0; |
| 33 | obj.sparecache = {}; |
| 34 | obj.onScreenSizeChange = null; |
| 35 | obj.frameRateDelay = 0; |
| 36 | // ###BEGIN###{DesktopRotation} |
| 37 | obj.noMouseRotate = false; |
| 38 | obj.rotation = 0; |
| 39 | // ###END###{DesktopRotation} |
| 40 | // ###BEGIN###{DesktopInband} |
| 41 | obj.kvmDataSupported = false; |
| 42 | obj.onKvmData = null; |
| 43 | obj.onKvmDataPending = []; |
| 44 | obj.onKvmDataAck = -1; |
| 45 | obj.holding = false; |
| 46 | obj.lastKeepAlive = Date.now(); |
| 47 | obj.kvmExt = {}; |
| 48 | obj.kvmExtChanged = null; |
| 49 | obj.useZLib = false; |
| 50 | obj.decimationMode = 0; // 0 = Don't set, 1 = Disable, 2 = Automatic, 3 = Enabled |
| 51 | obj.graymode = false; |
| 52 | obj.lowcolor = false; |
| 53 | // ###END###{DesktopInband} |
| 54 | |
| 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 |
| 61 | obj.focusmode = 0; |
| 62 | // ###END###{DesktopFocus} |
| 63 | // ###BEGIN###{Inflate} |
| 64 | obj.inflate = ZLIB.inflateInit(-15); |
| 65 | // ###END###{Inflate} |
| 66 | |
| 67 | obj.xxStateChange = function (newstate) { |
| 68 | if (newstate == 0) { |
| 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'; |
| 74 | } else { |
| 75 | QS(obj.canvasid).cursor = obj.showmouse ? 'default' : 'none'; |
| 76 | } |
| 77 | } |
| 78 | |
| 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 } |
| 81 | |
| 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} |
| 87 | |
| 88 | // Append to accumulator |
| 89 | if (obj.acc == null) { |
| 90 | obj.acc = new Uint8Array(data); |
| 91 | } else { |
| 92 | var tmp = new Uint8Array(obj.acc.byteLength + data.byteLength); |
| 93 | tmp.set(obj.acc, 0); |
| 94 | tmp.set(new Uint8Array(data), obj.acc.byteLength); |
| 95 | obj.acc = tmp; |
| 96 | } |
| 97 | |
| 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 |
| 103 | cmdsize = 12; |
| 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); |
| 107 | obj.state = 1; |
| 108 | if (obj.parent) { delete obj.parent.connectTime; } |
| 109 | obj.send('RFB 003.008\n'); |
| 110 | } |
| 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. |
| 115 | obj.state = 2; |
| 116 | } |
| 117 | else if ((obj.state == 2) && (obj.acc.byteLength >= 4)) { |
| 118 | // Getting security response |
| 119 | cmdsize = 4; |
| 120 | if (accview.getUint32(0) != 0) { return obj.Stop(); } |
| 121 | obj.send(String.fromCharCode(1)); // Send share desktop flag |
| 122 | obj.state = 3; |
| 123 | if (obj.parent) { obj.parent.disconnectCode = 50000; } // If Intel AMT disconnects at exactly this moment, indicates we need RLE8 or unsupported GPU. |
| 124 | } |
| 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); |
| 136 | |
| 137 | // ###BEGIN###{DesktopRecorder} |
| 138 | obj.DeskRecordServerInit = String.fromCharCode.apply(null, new Uint8Array(obj.acc.buffer.slice(0, 24 + namelen))); |
| 139 | // ###END###{DesktopRecorder} |
| 140 | |
| 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. |
| 143 | /* |
| 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); |
| 163 | */ |
| 164 | |
| 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) |
| 167 | |
| 168 | var supportedEncodings = ''; |
| 169 | if (obj.useRLE) supportedEncodings += IntToStr(16); |
| 170 | supportedEncodings += IntToStr(0); |
| 171 | // ###BEGIN###{DesktopInband} |
| 172 | supportedEncodings += IntToStr(1092); |
| 173 | // ###END###{DesktopInband} |
| 174 | |
| 175 | obj.send(String.fromCharCode(2, 0) + ShortToStr((supportedEncodings.length / 4) + 1) + supportedEncodings + IntToStr(-223)); // Supported Encodings + Desktop Size |
| 176 | |
| 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 |
| 181 | } else { |
| 182 | // Gray scale modes |
| 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 |
| 186 | } else { |
| 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 |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | obj.state = 4; |
| 192 | if (obj.parent) { |
| 193 | obj.parent.connectTime = Date.now(); |
| 194 | obj.parent.disconnectCode = 0; |
| 195 | obj.parent.xxStateChange(3); |
| 196 | } |
| 197 | //obj.timer = setInterval(obj.xxOnTimer, 50); |
| 198 | |
| 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) |
| 206 | } |
| 207 | // ###END###{DesktopInband} |
| 208 | _SendRefresh(); |
| 209 | |
| 210 | if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); } |
| 211 | if (obj.parent) { |
| 212 | obj.parent.disconnectCode = 50001; // Everything looks good, a disconnection here would be Intel AMT initiated. |
| 213 | |
| 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. |
| 217 | } |
| 218 | } |
| 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. |
| 224 | cmdsize = 4; |
| 225 | |
| 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} |
| 230 | |
| 231 | break; |
| 232 | case 2: // This is the bell, do nothing. |
| 233 | cmdsize = 1; |
| 234 | break; |
| 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); |
| 240 | break; |
| 241 | } |
| 242 | } |
| 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), |
| 248 | s = width * height, |
| 249 | encoding = accview.getUint32(8); |
| 250 | |
| 251 | if (encoding < 17) { |
| 252 | if ((width < 1) || (width > 64) || (height < 1) || (height > 64)) { console.log('Invalid tile size (' + width + ',' + height + '), disconnecting.'); return obj.Stop(); } |
| 253 | |
| 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]; |
| 263 | if (!obj.spare) { |
| 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. |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | |
| 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 |
| 276 | cmdsize = 12; |
| 277 | if (obj.onScreenSizeChange != null) { obj.onScreenSizeChange(obj, obj.ScreenWidth, obj.ScreenHeight); } |
| 278 | //console.log('Desktop width: ' + obj.width + ', height: ' + obj.height); |
| 279 | |
| 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) { |
| 284 | // RAW encoding |
| 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. |
| 287 | cmdsize = cs; |
| 288 | |
| 289 | // CRITICAL LOOP, optimize this as much as possible |
| 290 | if (obj.bpp == 2) { |
| 291 | for (var i = 0; i < s; i++) { _setPixel16(accview.getUint16(ptr, true), i); ptr += 2; } |
| 292 | } else { |
| 293 | for (var i = 0; i < s; i++) { _setPixel8(obj.acc[ptr++], i); } |
| 294 | } |
| 295 | _putImage(obj.spare, x, y); |
| 296 | } else if (encoding == 16) { |
| 297 | // RLE encoding |
| 298 | if (obj.acc.byteLength < 16) return; |
| 299 | var datalen = accview.getUint32(12); |
| 300 | if (obj.acc.byteLength < (16 + datalen)) return; |
| 301 | |
| 302 | // Process the ZLib header if this is the first block |
| 303 | var ptr = 16, delta = 5, dx = 0; |
| 304 | |
| 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); |
| 308 | } |
| 309 | // ###BEGIN###{Inflate} |
| 310 | else { |
| 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'); } |
| 314 | } |
| 315 | // ###END###{Inflate} |
| 316 | |
| 317 | cmdsize = 16 + datalen; |
| 318 | } else { return obj.Stop(); } |
| 319 | if (--obj.state == 100) { |
| 320 | obj.state = 4; |
| 321 | if (obj.frameRateDelay == 0) { |
| 322 | _SendRefresh(); // Ask for new frame |
| 323 | } else { |
| 324 | setTimeout(_SendRefresh, obj.frameRateDelay); // Hold x miliseconds before asking for a new frame |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |
| 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; } |
| 332 | } |
| 333 | } |
| 334 | |
| 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) { |
| 338 | // RAW encoding |
| 339 | if (obj.bpp == 2) { |
| 340 | for (i = 0; i < s; i++) { _setPixel16(data[ptr++] + (data[ptr++] << 8), i); } |
| 341 | } else { |
| 342 | for (i = 0; i < s; i++) { _setPixel8(data[ptr++], i); } |
| 343 | } |
| 344 | _putImage(obj.spare, x, y); |
| 345 | } |
| 346 | else if (subencoding == 1) { |
| 347 | // Solid color tile |
| 348 | if (obj.graymode) { |
| 349 | v = data[ptr++]; |
| 350 | if (obj.lowcolor) { v = v << 4; } |
| 351 | obj.canvas.fillStyle = 'rgb(' + v + ',' + v + ',' + v + ')'; |
| 352 | } else { |
| 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))) + ')'; |
| 355 | } |
| 356 | |
| 357 | // ###BEGIN###{DesktopRotation} |
| 358 | var xx = _rotX(x, y); |
| 359 | y = _rotY(x, y); |
| 360 | x = xx; |
| 361 | // ###END###{DesktopRotation} |
| 362 | |
| 363 | obj.canvas.fillRect(x, y, width, height); |
| 364 | } |
| 365 | else if (subencoding > 1 && subencoding < 17) { // Packed palette encoded tile |
| 366 | // Read the palette |
| 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. |
| 368 | if (obj.bpp == 2) { |
| 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 |
| 372 | } else { |
| 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 |
| 376 | } |
| 377 | _putImage(obj.spare, x, y); |
| 378 | } |
| 379 | else if (subencoding == 128) { // RLE encoded tile |
| 380 | if (obj.bpp == 2) { |
| 381 | while (rlecount < s && ptr < data.byteLength) { |
| 382 | // Get the run color |
| 383 | v = data[ptr++] + (data[ptr++] << 8); |
| 384 | |
| 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); |
| 387 | |
| 388 | // Draw a run |
| 389 | if (obj.rotation == 0) { |
| 390 | _setPixel16run(v, rlecount, runlength); rlecount += runlength; |
| 391 | } else { |
| 392 | while (--runlength >= 0) { _setPixel16(v, rlecount++); } |
| 393 | } |
| 394 | } |
| 395 | } else { |
| 396 | while (rlecount < s && ptr < data.byteLength) { |
| 397 | // Get the run color |
| 398 | v = data[ptr++]; |
| 399 | |
| 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); |
| 402 | |
| 403 | // Draw a run |
| 404 | if (obj.rotation == 0) { |
| 405 | _setPixel8run(v, rlecount, runlength); rlecount += runlength; |
| 406 | } else { |
| 407 | while (--runlength >= 0) { _setPixel8(v, rlecount++); } |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 | _putImage(obj.spare, x, y); |
| 412 | } |
| 413 | else if (subencoding > 129) { // Palette RLE encoded tile |
| 414 | // Read the palette |
| 415 | if (obj.bpp == 2) { |
| 416 | for (i = 0; i < (subencoding - 128); i++) { palette[i] = data[ptr++] + (data[ptr++] << 8); } |
| 417 | } else { |
| 418 | for (i = 0; i < (subencoding - 128); i++) { palette[i] = data[ptr++]; } |
| 419 | } |
| 420 | |
| 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]; |
| 425 | |
| 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); } |
| 428 | |
| 429 | // Draw a run |
| 430 | if (obj.rotation == 0) { |
| 431 | if (obj.bpp == 2) { |
| 432 | _setPixel16run(v, rlecount, runlength); rlecount += runlength; |
| 433 | } else { |
| 434 | _setPixel8run(v, rlecount, runlength); rlecount += runlength; |
| 435 | } |
| 436 | } else { |
| 437 | if (obj.bpp == 2) { |
| 438 | while (--runlength >= 0) { _setPixel16(v, rlecount++); } |
| 439 | } else { |
| 440 | while (--runlength >= 0) { _setPixel8(v, rlecount++); } |
| 441 | } |
| 442 | } |
| 443 | } |
| 444 | _putImage(obj.spare, x, y); |
| 445 | } |
| 446 | } |
| 447 | |
| 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); } // ??? |
| 460 | } |
| 461 | obj.send(String.fromCharCode(3, 0, 0, 0, 0, 0) + ShortToStr(obj.width) + ShortToStr(obj.height)); // FramebufferUpdateRequest |
| 462 | } else { |
| 463 | obj.UnGrabMouseInput(); |
| 464 | obj.UnGrabKeyInput(); |
| 465 | } |
| 466 | } |
| 467 | // ###END###{DesktopInband} |
| 468 | |
| 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); |
| 475 | y = _arotY(x, y); |
| 476 | x = xx; |
| 477 | // ###END###{DesktopRotation} |
| 478 | obj.canvas.putImageData(i, x, y); |
| 479 | } |
| 480 | |
| 481 | // Set 8bit color RGB332 |
| 482 | function _setPixel8(v, p) { |
| 483 | var pp = p << 2; |
| 484 | |
| 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; } |
| 490 | } |
| 491 | // ###END###{DesktopRotation} |
| 492 | |
| 493 | if (obj.graymode) { |
| 494 | if (obj.lowcolor) { v = v << 4; } |
| 495 | obj.spare.data[pp] = obj.spare.data[pp + 1] = obj.spare.data[pp + 2] = v; |
| 496 | } else { |
| 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); |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | // Set 16bit color RGB565 |
| 504 | function _setPixel16(v, p) { |
| 505 | var pp = p << 2; |
| 506 | |
| 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; } |
| 512 | } |
| 513 | // ###END###{DesktopRotation} |
| 514 | |
| 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; |
| 518 | } |
| 519 | |
| 520 | // Set a run of 8bit color RGB332 |
| 521 | function _setPixel8run(v, p, run) { |
| 522 | if (obj.graymode) { |
| 523 | var pp = (p << 2); |
| 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; } |
| 526 | } else { |
| 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; } |
| 529 | } |
| 530 | } |
| 531 | |
| 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; } |
| 536 | } |
| 537 | |
| 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; |
| 544 | return 0; |
| 545 | } |
| 546 | |
| 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; |
| 552 | return 0; |
| 553 | } |
| 554 | |
| 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; |
| 560 | return 0; |
| 561 | } |
| 562 | |
| 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; |
| 568 | return 0; |
| 569 | } |
| 570 | |
| 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; |
| 576 | return 0; |
| 577 | } |
| 578 | |
| 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; |
| 584 | return 0; |
| 585 | } |
| 586 | |
| 587 | obj.tcanvas = null; |
| 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} |
| 595 | |
| 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; } |
| 600 | |
| 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); |
| 612 | |
| 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)); |
| 620 | |
| 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); |
| 624 | return true; |
| 625 | } |
| 626 | // ###END###{DesktopRotation} |
| 627 | |
| 628 | function _fixColor(c) { return (c > 127) ? (c + 32) : c; } |
| 629 | |
| 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 |
| 639 | obj.ox = obj.mx; |
| 640 | obj.oy = obj.my; |
| 641 | } else { |
| 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} |
| 646 | } |
| 647 | // ###END###{DesktopFocus} |
| 648 | } |
| 649 | |
| 650 | obj.Start = function () { |
| 651 | obj.state = 0; |
| 652 | obj.acc = null; |
| 653 | // ###BEGIN###{Inflate} |
| 654 | obj.inflate.inflateReset(); |
| 655 | // ###END###{Inflate} |
| 656 | // ###BEGIN###{DesktopInband} |
| 657 | obj.onKvmDataPending = []; |
| 658 | obj.onKvmDataAck = -1; |
| 659 | obj.kvmDataSupported = false; |
| 660 | obj.kvmExt = {}; |
| 661 | // ###END###{DesktopInband} |
| 662 | for (var i in obj.sparecache) { delete obj.sparecache[i]; } |
| 663 | } |
| 664 | |
| 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); } } |
| 667 | |
| 668 | var convertAmtKeyCodeTable = { |
| 669 | 'Pause': 19, |
| 670 | 'CapsLock': 20, |
| 671 | 'Space': 32, |
| 672 | 'Quote': 39, |
| 673 | 'Minus': 45, |
| 674 | 'NumpadMultiply': 42, |
| 675 | 'NumpadAdd': 43, |
| 676 | 'PrintScreen': 44, |
| 677 | 'Comma': 44, |
| 678 | 'NumpadSubtract': 45, |
| 679 | 'NumpadDecimal': 46, |
| 680 | 'Period': 46, |
| 681 | 'Slash': 47, |
| 682 | 'NumpadDivide': 47, |
| 683 | 'Semicolon': 59, |
| 684 | 'Equal': 61, |
| 685 | 'OSLeft': 91, |
| 686 | 'BracketLeft': 91, |
| 687 | 'OSRight': 91, |
| 688 | 'Backslash': 92, |
| 689 | 'BracketRight': 93, |
| 690 | 'ContextMenu': 93, |
| 691 | 'Backquote': 96, |
| 692 | 'NumLock': 144, |
| 693 | 'ScrollLock': 145, |
| 694 | 'Backspace': 0xff08, |
| 695 | 'Tab': 0xff09, |
| 696 | 'Enter': 0xff0d, |
| 697 | 'NumpadEnter': 0xff0d, |
| 698 | 'Escape': 0xff1b, |
| 699 | 'Delete': 0xffff, |
| 700 | 'Home': 0xff50, |
| 701 | 'PageUp': 0xff55, |
| 702 | 'PageDown': 0xff56, |
| 703 | 'ArrowLeft': 0xff51, |
| 704 | 'ArrowUp': 0xff52, |
| 705 | 'ArrowRight': 0xff53, |
| 706 | 'ArrowDown': 0xff54, |
| 707 | 'End': 0xff57, |
| 708 | 'Insert': 0xff63, |
| 709 | 'F1': 0xffbe, |
| 710 | 'F2': 0xffbf, |
| 711 | 'F3': 0xffc0, |
| 712 | 'F4': 0xffc1, |
| 713 | 'F5': 0xffc2, |
| 714 | 'F6': 0xffc3, |
| 715 | 'F7': 0xffc4, |
| 716 | 'F8': 0xffc5, |
| 717 | 'F9': 0xffc6, |
| 718 | 'F10': 0xffc7, |
| 719 | 'F11': 0xffc8, |
| 720 | 'F12': 0xffc9, |
| 721 | 'ShiftLeft': 0xffe1, |
| 722 | 'ShiftRight': 0xffe2, |
| 723 | 'ControlLeft': 0xffe3, |
| 724 | 'ControlRight': 0xffe4, |
| 725 | 'AltLeft': 0xffe9, |
| 726 | 'AltRight': 0xffea, |
| 727 | 'MetaLeft': 0xffe7, |
| 728 | 'MetaRight': 0xffe8 |
| 729 | } |
| 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]; |
| 735 | } |
| 736 | |
| 737 | /* |
| 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: |
| 742 | |
| 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 |
| 749 | */ |
| 750 | |
| 751 | function _keyevent(d, e) { |
| 752 | if (!e) { e = window.event; } |
| 753 | |
| 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); } |
| 758 | } else { |
| 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); |
| 798 | obj.sendkey(kk, d); |
| 799 | } |
| 800 | return obj.haltEvent(e); |
| 801 | } |
| 802 | |
| 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); |
| 806 | } else { |
| 807 | obj.send(String.fromCharCode(4, d, 0, 0) + IntToStr(k)); |
| 808 | } |
| 809 | } |
| 810 | |
| 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); |
| 827 | if (cmd == 1) { |
| 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); } |
| 831 | } |
| 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); } |
| 835 | } else { |
| 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) { } |
| 838 | } |
| 839 | } |
| 840 | // ###END###{DesktopInband} |
| 841 | return len; |
| 842 | } |
| 843 | |
| 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); |
| 849 | } |
| 850 | |
| 851 | obj.sendKvmData = function (x) { |
| 852 | if (obj.onKvmDataAck !== true) { |
| 853 | obj.onKvmDataPending.push(x); |
| 854 | } else { |
| 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; |
| 859 | } |
| 860 | } |
| 861 | |
| 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'); } |
| 865 | } |
| 866 | // ###END###{DesktopInband} |
| 867 | |
| 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); |
| 872 | } |
| 873 | // ###END###{DesktopClipboard} |
| 874 | |
| 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 |
| 877 | |
| 878 | var _MouseInputGrab = false; |
| 879 | var _KeyInputGrab = false; |
| 880 | |
| 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; |
| 891 | } |
| 892 | |
| 893 | obj.UnGrabMouseInput = function () { |
| 894 | if (_MouseInputGrab == false) return; |
| 895 | var c = obj.canvas.canvas; |
| 896 | c.onmousemove = null; |
| 897 | c.onmouseup = null; |
| 898 | c.onmousedown = null; |
| 899 | //c.onmousewheel = null; |
| 900 | c.onwheel = null; |
| 901 | //if (navigator.userAgent.match(/mozilla/i)) c.DOMMouseScroll = null; else c.onmousewheel = null; |
| 902 | _MouseInputGrab = false; |
| 903 | } |
| 904 | |
| 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; |
| 911 | } |
| 912 | |
| 913 | obj.UnGrabKeyInput = function () { |
| 914 | if (_KeyInputGrab == false) return; |
| 915 | document.onkeyup = null; |
| 916 | document.onkeydown = null; |
| 917 | document.onkeypress = null; |
| 918 | _KeyInputGrab = false; |
| 919 | } |
| 920 | |
| 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; } |
| 925 | |
| 926 | // RFB 'PointerEvent' and mouse handlers |
| 927 | obj.mousedblclick = function (e) { } |
| 928 | obj.mousewheel = function (e) { |
| 929 | var v = 0; |
| 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; } |
| 933 | if (v == 0) return; |
| 934 | |
| 935 | // Reverse mouse wheel if needed |
| 936 | if (obj.ReverseMouseWheel) { v = -1 * v; } |
| 937 | |
| 938 | var tmpmask = obj.buttonmask; |
| 939 | obj.buttonmask |= (1 << ((v > 0) ? 3 : 4)); |
| 940 | obj.mousemove(e, 1); |
| 941 | obj.buttonmask = tmpmask; |
| 942 | return obj.mousemove(e, 1); |
| 943 | } |
| 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; } |
| 955 | |
| 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); |
| 960 | } |
| 961 | if (obj.noMouseRotate != true) { |
| 962 | var mx2 = _crotX(obj.mx, obj.my); |
| 963 | obj.my = _crotY(obj.mx, obj.my); |
| 964 | obj.mx = mx2; |
| 965 | } |
| 966 | // ###END###{DesktopRotation} |
| 967 | |
| 968 | // This is the mouse motion nagle timer. Slow down the mouse motion event rate. |
| 969 | if (force == 1) { |
| 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; } |
| 972 | } else { |
| 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; |
| 977 | }, 50); |
| 978 | } |
| 979 | } |
| 980 | |
| 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, |
| 988 | c = Q(obj.canvasid), |
| 989 | qx = c.offsetHeight / obj.canvas.canvas.height, |
| 990 | qy = c.offsetWidth / obj.canvas.canvas.width, |
| 991 | q = QS('DeskFocus'), |
| 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'; |
| 997 | } |
| 998 | // ###END###{DesktopFocus} |
| 999 | |
| 1000 | return obj.haltEvent(e); |
| 1001 | } |
| 1002 | |
| 1003 | obj.getPositionOfControl = function (Control) { |
| 1004 | var Position = Array(2); |
| 1005 | Position[0] = Position[1] = 0; |
| 1006 | while (Control) { |
| 1007 | Position[0] += Control.offsetLeft; |
| 1008 | Position[1] += Control.offsetTop; |
| 1009 | Control = Control.offsetParent; |
| 1010 | } |
| 1011 | return Position; |
| 1012 | } |
| 1013 | |
| 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 |
| 1025 | return true; |
| 1026 | } |
| 1027 | |
| 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; |
| 1035 | return r; |
| 1036 | } |
| 1037 | |
| 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); |
| 1046 | } else { |
| 1047 | obj.recordedSize += data.length; |
| 1048 | return ShortToStr(type) + ShortToStr(flags) + IntToStr(data.length) + IntToStr(now >> 32) + IntToStr(now & 32) + data; |
| 1049 | } |
| 1050 | } |
| 1051 | // ###END###{DesktopRecorder} |
| 1052 | |
| 1053 | return obj; |
| 1054 | } |