UNPKG

angular-no-vnc

Version:
1,668 lines (1,428 loc) 231 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js angular.module('noVNC.util', []).factory('Base64', [function() { 'use strict'; return { /* Convert data (an array of integers) to a Base64 string. */ toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''), base64Pad : '=', encode: function (data) { var result = ''; var toBase64Table = this.toBase64Table; var length = data.length; var lengthpad = (length%3); var i = 0, j = 0; // Convert every three bytes to 4 ascii characters. /* BEGIN LOOP */ for (i = 0; i < (length - 2); i += 3) { result += toBase64Table[data[i] >> 2]; result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; result += toBase64Table[data[i+2] & 0x3f]; } /* END LOOP */ // Convert the remaining 1 or 2 bytes, pad out to 4 characters. if (lengthpad === 2) { j = length - lengthpad; result += toBase64Table[data[j] >> 2]; result += toBase64Table[((data[j] & 0x03) << 4) + (data[j+1] >> 4)]; result += toBase64Table[(data[j+1] & 0x0f) << 2]; result += toBase64Table[64]; } else if (lengthpad === 1) { j = length - lengthpad; result += toBase64Table[data[j] >> 2]; result += toBase64Table[(data[j] & 0x03) << 4]; result += toBase64Table[64]; result += toBase64Table[64]; } return result; }, /* Convert Base64 data to a string */ toBinaryTable : [ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 ], decode: function (data, offset) { offset = typeof(offset) !== 'undefined' ? offset : 0; var toBinaryTable = this.toBinaryTable; var base64Pad = this.base64Pad; var result, result_length, idx, i, c, padding; var leftbits = 0; // number of bits decoded, but yet to be appended var leftdata = 0; // bits decoded, but yet to be appended var data_length = data.indexOf('=') - offset; if (data_length < 0) { data_length = data.length - offset; } /* Every four characters is 3 resulting numbers */ result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5); result = new Array(result_length); // Convert one by one. /* BEGIN LOOP */ for (idx = 0, i = offset; i < data.length; i++) { c = toBinaryTable[data.charCodeAt(i) & 0x7f]; padding = (data.charAt(i) === base64Pad); // Skip illegal characters and whitespace if (c === -1) { console.error('Illegal character code ' + data.charCodeAt(i) + ' at position ' + i); continue; } // Collect data into leftdata, update bitcount leftdata = (leftdata << 6) | c; leftbits += 6; // If we have 8 or more bits, append 8 bits to the result if (leftbits >= 8) { leftbits -= 8; // Append if not padding. if (!padding) { result[idx++] = (leftdata >> leftbits) & 0xff; } leftdata &= (1 << leftbits) - 1; } } /* END LOOP */ // If there are any bits left, the base64 string was corrupted if (leftbits) { throw {name: 'Base64-Error', message: 'Corrupted base64 string'}; } return result; } }; }]); /* * Ported from Flashlight VNC ActionScript implementation: * http://www.wizhelp.com/flashlight-vnc/ * * Full attribution follows: * * ------------------------------------------------------------------------- * * This DES class has been extracted from package Acme.Crypto for use in VNC. * The unnecessary odd parity code has been removed. * * These changes are: * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * DesCipher - the DES encryption method * * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is: * * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. * * Permission to use, copy, modify, and distribute this software * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and * without fee is hereby granted, provided that this copyright notice is kept * intact. * * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. * * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR * HIGH RISK ACTIVITIES. * * * The rest is: * * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Visit the ACME Labs Java page for up-to-date versions of this and other * fine Java utilities: http://www.acme.com/java/ */ angular.module('noVNC.util').factory('DES', function() { 'use strict'; return function (passwd) { // Tables, permutations, S-boxes, etc. var PC2 = [ 13,16,10,23,0,4,2,27,14,5,20,9,22,18,11,3, 25,7,15,6,26,19,12,1,40,51,30,36,46,54,29,39, 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31], totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, keys = []; a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; // Set the key. function setKeys(keyBlock) { var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], raw0, raw1, rawi, KnLi; for (j = 0, l = 56; j < 56; ++j, l-=8) { l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1 m = l & 0x7; pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0; } for (i = 0; i < 16; ++i) { m = i << 1; n = m + 1; kn[m] = kn[n] = 0; for (o=28; o<59; o+=28) { for (j = o-28; j < o; ++j) { l = j + totrot[i]; if (l < o) { pcr[j] = pc1m[l]; } else { pcr[j] = pc1m[l - 28]; } } } for (j = 0; j < 24; ++j) { if (pcr[PC2[j]] !== 0) { kn[m] |= 1<<(23-j); } if (pcr[PC2[j + 24]] !== 0) { kn[n] |= 1<<(23-j); } } } // cookey for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) { raw0 = kn[rawi++]; raw1 = kn[rawi++]; keys[KnLi] = (raw0 & 0x00fc0000) << 6; keys[KnLi] |= (raw0 & 0x00000fc0) << 10; keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10; keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; ++KnLi; keys[KnLi] = (raw0 & 0x0003f000) << 12; keys[KnLi] |= (raw0 & 0x0000003f) << 16; keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; keys[KnLi] |= (raw1 & 0x0000003f); ++KnLi; } } // Encrypt 8 bytes of text function enc8(text) { var i = 0, b = text.slice(), fval, keysi = 0, l, r, x; // left, right, accumulator // Squash 8 bytes to 2 ints l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; x = ((l >>> 4) ^ r) & 0x0f0f0f0f; r ^= x; l ^= (x << 4); x = ((l >>> 16) ^ r) & 0x0000ffff; r ^= x; l ^= (x << 16); x = ((r >>> 2) ^ l) & 0x33333333; l ^= x; r ^= (x << 2); x = ((r >>> 8) ^ l) & 0x00ff00ff; l ^= x; r ^= (x << 8); r = (r << 1) | ((r >>> 31) & 1); x = (l ^ r) & 0xaaaaaaaa; l ^= x; r ^= x; l = (l << 1) | ((l >>> 31) & 1); for (i = 0; i < 8; ++i) { x = (r << 28) | (r >>> 4); x ^= keys[keysi++]; fval = SP7[x & 0x3f]; fval |= SP5[(x >>> 8) & 0x3f]; fval |= SP3[(x >>> 16) & 0x3f]; fval |= SP1[(x >>> 24) & 0x3f]; x = r ^ keys[keysi++]; fval |= SP8[x & 0x3f]; fval |= SP6[(x >>> 8) & 0x3f]; fval |= SP4[(x >>> 16) & 0x3f]; fval |= SP2[(x >>> 24) & 0x3f]; l ^= fval; x = (l << 28) | (l >>> 4); x ^= keys[keysi++]; fval = SP7[x & 0x3f]; fval |= SP5[(x >>> 8) & 0x3f]; fval |= SP3[(x >>> 16) & 0x3f]; fval |= SP1[(x >>> 24) & 0x3f]; x = l ^ keys[keysi++]; fval |= SP8[x & 0x0000003f]; fval |= SP6[(x >>> 8) & 0x3f]; fval |= SP4[(x >>> 16) & 0x3f]; fval |= SP2[(x >>> 24) & 0x3f]; r ^= fval; } r = (r << 31) | (r >>> 1); x = (l ^ r) & 0xaaaaaaaa; l ^= x; r ^= x; l = (l << 31) | (l >>> 1); x = ((l >>> 8) ^ r) & 0x00ff00ff; r ^= x; l ^= (x << 8); x = ((l >>> 2) ^ r) & 0x33333333; r ^= x; l ^= (x << 2); x = ((r >>> 16) ^ l) & 0x0000ffff; l ^= x; r ^= (x << 16); x = ((r >>> 4) ^ l) & 0x0f0f0f0f; l ^= x; r ^= (x << 4); // Spread ints to bytes x = [r, l]; for (i = 0; i < 8; i++) { b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256; if (b[i] < 0) { b[i] += 256; } // unsigned } return b; } // Encrypt 16 bytes of text using passwd as key function encrypt(t) { return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16))); } setKeys(passwd); // Setup keys return {'encrypt': encrypt}; // Public interface }; }); /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ var display = angular.module('noVNC.display', ['noVNC.util']); /* Set CSS cursor property using data URI encoded cursor file */ display.factory('changeCursor', ['Base64', function(Base64) { 'use strict'; return function (target, pixels, mask, hotx, hoty, w0, h0, cmap) { var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y; //Util.Debug('>> changeCursor, x: ' + hotx + ', y: ' + hoty + ', w0: ' + w0 + ', h0: ' + h0); var w = w0; var h = h0; if (h < w) { h = w; // increase h to make it square } else { w = h; // increace w to make it square } // Push multi-byte little-endian values cur.push16le = function (num) { this.push((num) & 0xFF, (num >> 8) & 0xFF ); }; cur.push32le = function (num) { this.push((num) & 0xFF, (num >> 8) & 0xFF, (num >> 16) & 0xFF, (num >> 24) & 0xFF ); }; IHDRsz = 40; RGBsz = w * h * 4; XORsz = Math.ceil( (w * h) / 8.0 ); ANDsz = Math.ceil( (w * h) / 8.0 ); // Main header cur.push16le(0); // 0: Reserved cur.push16le(2); // 2: .CUR type cur.push16le(1); // 4: Number of images, 1 for non-animated ico // Cursor #1 header (ICONDIRENTRY) cur.push(w); // 6: width cur.push(h); // 7: height cur.push(0); // 8: colors, 0 -> true-color cur.push(0); // 9: reserved cur.push16le(hotx); // 10: hotspot x coordinate cur.push16le(hoty); // 12: hotspot y coordinate cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); // 14: cursor data byte size cur.push32le(22); // 18: offset of cursor data in the file // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) cur.push32le(IHDRsz); // 22: Infoheader size cur.push32le(w); // 26: Cursor width cur.push32le(h*2); // 30: XOR+AND height cur.push16le(1); // 34: number of planes cur.push16le(32); // 36: bits per pixel cur.push32le(0); // 38: Type of compression cur.push32le(XORsz + ANDsz); // 43: Size of Image // Gimp leaves this as 0 cur.push32le(0); // 46: reserved cur.push32le(0); // 50: reserved cur.push32le(0); // 54: reserved cur.push32le(0); // 58: reserved // 62: color data (RGBQUAD icColors[]) for (y = h-1; y >= 0; y -= 1) { for (x = 0; x < w; x += 1) { if (x >= w0 || y >= h0) { cur.push(0); // blue cur.push(0); // green cur.push(0); // red cur.push(0); // alpha } else { idx = y * Math.ceil(w0 / 8) + Math.floor(x/8); alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; if (cmap) { idx = (w0 * y) + x; rgb = cmap[pixels[idx]]; cur.push(rgb[2]); // blue cur.push(rgb[1]); // green cur.push(rgb[0]); // red cur.push(alpha); // alpha } else { idx = ((w0 * y) + x) * 4; cur.push(pixels[idx + 2]); // blue cur.push(pixels[idx + 1]); // green cur.push(pixels[idx ]); // red cur.push(alpha); // alpha } } } } // XOR/bitmask data (BYTE icXOR[]) // (ignored, just needs to be right size) for (y = 0; y < h; y += 1) { for (x = 0; x < Math.ceil(w / 8); x += 1) { cur.push(0x00); } } // AND/bitmask data (BYTE icAND[]) // (ignored, just needs to be right size) for (y = 0; y < h; y += 1) { for (x = 0; x < Math.ceil(w / 8); x += 1) { cur.push(0x00); } } url = 'data:image/x-icon;base64,' + Base64.encode(cur); target.style.cursor = 'url(' + url + ') ' + hotx + ' ' + hoty + ', default'; //Util.Debug('<< changeCursor, cur.length: ' + cur.length); }; }]); display.factory('Display', ['Util', 'changeCursor', 'Base64', 'requestAnimFrame', function(Util, changeCursor, Base64, requestAnimFrame) { 'use strict'; return function(defaults) { var that = {}, // Public API methods conf = {}, // Configuration attributes // Private Display namespace variables c_ctx = null, // Queued drawing actions for in-order rendering renderQ = [], // Predefine function variables (jslint) rgbImageData, bgrxImageData, cmapImageData, setFillColor, rescale, scan_renderQ, // The full frame buffer (logical canvas) size fb_width = 0, fb_height = 0, // The visible 'physical canvas' viewport viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 }, cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1}, c_prevStyle = '', tile = null, tile16x16 = null, tile_x = 0, tile_y = 0; // Configuration attributes Util.conf_defaults(conf, that, defaults, [ ['target', 'wo', 'dom', null, 'Canvas element for rendering'], ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'], ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'], ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'], ['width', 'rw', 'int', null, 'Display area width'], ['height', 'rw', 'int', null, 'Display area height'], ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'], ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'], ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI'] ]); // Override some specific getters/setters that.get_context = function () { return c_ctx; }; that.set_scale = function(scale) { rescale(scale); }; that.set_width = function (val) { that.resize(val, fb_height); }; that.get_width = function() { return fb_width; }; that.set_height = function (val) { that.resize(fb_width, val); }; that.get_height = function() { return fb_height; }; // // Private functions // // Create the public API interface function constructor() { Util.Debug('>> Display.constructor'); var c, i, curDat, curSave, UE = Util.Engine; if (! conf.target) { throw('target must be set'); } if (typeof conf.target === 'string') { throw('target must be a DOM element'); } c = conf.target; if (! c.getContext) { throw('no getContext method'); } if (! c_ctx) { c_ctx = c.getContext('2d'); } Util.Debug('User Agent: ' + navigator.userAgent); if (UE.gecko) { Util.Debug('Browser: gecko ' + UE.gecko); } if (UE.webkit) { Util.Debug('Browser: webkit ' + UE.webkit); } if (UE.trident) { Util.Debug('Browser: trident ' + UE.trident); } if (UE.presto) { Util.Debug('Browser: presto ' + UE.presto); } that.clear(); // Check canvas features if ('createImageData' in c_ctx) { conf.render_mode = 'canvas rendering'; } else { throw('Canvas does not support createImageData'); } if (conf.prefer_js === null) { Util.Info('Prefering javascript operations'); conf.prefer_js = true; } // Initialize cached tile imageData tile16x16 = c_ctx.createImageData(16, 16); /* * Determine browser support for setting the cursor via data URI * scheme */ curDat = []; for (i=0; i < 8 * 8 * 4; i += 1) { curDat.push(255); } try { curSave = c.style.cursor; changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8); if (c.style.cursor) { if (conf.cursor_uri === null) { conf.cursor_uri = true; } Util.Info('Data URI scheme cursor supported'); } else { if (conf.cursor_uri === null) { conf.cursor_uri = false; } Util.Warn('Data URI scheme cursor not supported'); } c.style.cursor = curSave; } catch (exc2) { Util.Error('Data URI scheme cursor test exception: ' + exc2); conf.cursor_uri = false; } Util.Debug('<< Display.constructor'); return that ; } rescale = function(factor) { if (typeof(factor) === 'undefined') { factor = conf.scale; } else if (factor > 1.0) { factor = 1.0; } else if (factor < 0.1) { factor = 0.1; } conf.scale = factor; }; setFillColor = function(color) { var bgr, newStyle; if (conf.true_color) { bgr = color; } else { bgr = conf.colourMap[color[0]]; } newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')'; if (newStyle !== c_prevStyle) { c_ctx.fillStyle = newStyle; c_prevStyle = newStyle; } }; // // Public API interface functions // // Shift and/or resize the visible viewport that.viewportChange = function(deltaX, deltaY, width, height) { var c = conf.target, v = viewport, cr = cleanRect, saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h; if (!conf.viewport) { Util.Debug('Setting viewport to full display region'); deltaX = -v.w; // Clamped later if out of bounds deltaY = -v.h; // Clamped later if out of bounds width = fb_width; height = fb_height; } if (typeof(deltaX) === 'undefined') { deltaX = 0; } if (typeof(deltaY) === 'undefined') { deltaY = 0; } if (typeof(width) === 'undefined') { width = v.w; } if (typeof(height) === 'undefined') { height = v.h; } // Size change if (width > fb_width) { width = fb_width; } if (height > fb_height) { height = fb_height; } if ((v.w !== width) || (v.h !== height)) { // Change width if ((width < v.w) && (cr.x2 > v.x + width -1)) { cr.x2 = v.x + width - 1; } v.w = width; // Change height if ((height < v.h) && (cr.y2 > v.y + height -1)) { cr.y2 = v.y + height - 1; } v.h = height; if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) { saveImg = c_ctx.getImageData(0, 0, (c.width < v.w) ? c.width : v.w, (c.height < v.h) ? c.height : v.h); } // console.log(viewport) c.width = v.w; c.height = v.h; if (saveImg) { c_ctx.putImageData(saveImg, 0, 0); } } vx2 = v.x + v.w - 1; vy2 = v.y + v.h - 1; // Position change if ((deltaX < 0) && ((v.x + deltaX) < 0)) { deltaX = - v.x; } if ((vx2 + deltaX) >= fb_width) { deltaX -= ((vx2 + deltaX) - fb_width + 1); } if ((v.y + deltaY) < 0) { deltaY = - v.y; } if ((vy2 + deltaY) >= fb_height) { deltaY -= ((vy2 + deltaY) - fb_height + 1); } if ((deltaX === 0) && (deltaY === 0)) { //Util.Debug('skipping viewport change'); return; } Util.Debug('viewportChange deltaX: ' + deltaX + ', deltaY: ' + deltaY); v.x += deltaX; vx2 += deltaX; v.y += deltaY; vy2 += deltaY; // Update the clean rectangle if (v.x > cr.x1) { cr.x1 = v.x; } if (vx2 < cr.x2) { cr.x2 = vx2; } if (v.y > cr.y1) { cr.y1 = v.y; } if (vy2 < cr.y2) { cr.y2 = vy2; } if (deltaX < 0) { // Shift viewport left, redraw left section x1 = 0; w = - deltaX; } else { // Shift viewport right, redraw right section x1 = v.w - deltaX; w = deltaX; } if (deltaY < 0) { // Shift viewport up, redraw top section y1 = 0; h = - deltaY; } else { // Shift viewport down, redraw bottom section y1 = v.h - deltaY; h = deltaY; } // Copy the valid part of the viewport to the shifted location saveStyle = c_ctx.fillStyle; c_ctx.fillStyle = 'rgb(255,255,255)'; if (deltaX !== 0) { //that.copyImage(0, 0, -deltaX, 0, v.w, v.h); //that.fillRect(x1, 0, w, v.h, [255,255,255]); c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h); c_ctx.fillRect(x1, 0, w, v.h); } if (deltaY !== 0) { //that.copyImage(0, 0, 0, -deltaY, v.w, v.h); //that.fillRect(0, y1, v.w, h, [255,255,255]); c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h); c_ctx.fillRect(0, y1, v.w, h); } c_ctx.fillStyle = saveStyle; }; // Return a map of clean and dirty areas of the viewport and reset the // tracking of clean and dirty areas. // // Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h}, // 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]} that.getCleanDirtyReset = function() { var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [], vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1; // Copy the cleanRect cleanBox = {'x': c.x1, 'y': c.y1, 'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1}; if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) { // Whole viewport is dirty dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h}); } else { // Redraw dirty regions if (v.x < c.x1) { // left side dirty region dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': c.x1 - v.x + 1, 'h': v.h}); } if (vx2 > c.x2) { // right side dirty region dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y, 'w': vx2 - c.x2, 'h': v.h}); } if (v.y < c.y1) { // top/middle dirty region dirtyBoxes.push({'x': c.x1, 'y': v.y, 'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y}); } if (vy2 > c.y2) { // bottom/middle dirty region dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1, 'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2}); } } // Reset the cleanRect to the whole viewport cleanRect = {'x1': v.x, 'y1': v.y, 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1}; return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; }; // Translate viewport coordinates to absolute coordinates that.absX = function(x) { return x + viewport.x; }; that.absY = function(y) { return y + viewport.y; }; that.resize = function(width, height) { c_prevStyle = ''; fb_width = width; fb_height = height; rescale(conf.scale); that.viewportChange(); }; that.resizeAndScale = function(width, height, scale) { c_prevStyle = ''; rescale(scale); conf.target.style.width = width*conf.scale+'px'; conf.target.style.height = height*conf.scale+'px'; that.viewportChange(); }; that.clear = function() { if (conf.logo) { that.resize(conf.logo.width, conf.logo.height); that.blitStringImage(conf.logo.data, 0, 0); } else { that.resize(0, 0); c_ctx.clearRect(0, 0, viewport.w, viewport.h); } renderQ = []; // No benefit over default ('source-over') in Chrome and firefox //c_ctx.globalCompositeOperation = 'copy'; }; that.fillRect = function(x, y, width, height, color) { setFillColor(color); c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height); }; that.copyImage = function(old_x, old_y, new_x, new_y, w, h) { var x1 = old_x - viewport.x, y1 = old_y - viewport.y, x2 = new_x - viewport.x, y2 = new_y - viewport.y; c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h); }; // Start updating a tile that.startTile = function(x, y, width, height, color) { var data, bgr, red, green, blue, i; tile_x = x; tile_y = y; if ((width === 16) && (height === 16)) { tile = tile16x16; } else { tile = c_ctx.createImageData(width, height); } data = tile.data; if (conf.prefer_js) { if (conf.true_color) { bgr = color; } else { bgr = conf.colourMap[color[0]]; } red = bgr[2]; green = bgr[1]; blue = bgr[0]; for (i = 0; i < (width * height * 4); i+=4) { data[i ] = red; data[i + 1] = green; data[i + 2] = blue; data[i + 3] = 255; } } else { that.fillRect(x, y, width, height, color); } }; // Update sub-rectangle of the current tile that.subTile = function(x, y, w, h, color) { var data, p, bgr, red, green, blue, width, j, i, xend, yend; if (conf.prefer_js) { data = tile.data; width = tile.width; if (conf.true_color) { bgr = color; } else { bgr = conf.colourMap[color[0]]; } red = bgr[2]; green = bgr[1]; blue = bgr[0]; xend = x + w; yend = y + h; for (j = y; j < yend; j += 1) { for (i = x; i < xend; i += 1) { p = (i + (j * width) ) * 4; data[p ] = red; data[p + 1] = green; data[p + 2] = blue; data[p + 3] = 255; } } } else { that.fillRect(tile_x + x, tile_y + y, w, h, color); } }; // Draw the current tile to the screen that.finishTile = function() { if (conf.prefer_js) { c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y); } // else: No-op, if not prefer_js then already done by setSubTile }; rgbImageData = function(x, y, vx, vy, width, height, arr, offset) { var img, i, j, data; /* if ((x - v.x >= v.w) || (y - v.y >= v.h) || (x - v.x + width < 0) || (y - v.y + height < 0)) { // Skipping because outside of viewport return; } */ img = c_ctx.createImageData(width, height); data = img.data; for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) { data[i ] = arr[j ]; data[i + 1] = arr[j + 1]; data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } c_ctx.putImageData(img, x - vx, y - vy); }; bgrxImageData = function(x, y, vx, vy, width, height, arr, offset) { var img, i, j, data; /* if ((x - v.x >= v.w) || (y - v.y >= v.h) || (x - v.x + width < 0) || (y - v.y + height < 0)) { // Skipping because outside of viewport return; } */ img = c_ctx.createImageData(width, height); data = img.data; for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { data[i ] = arr[j + 2]; data[i + 1] = arr[j + 1]; data[i + 2] = arr[j ]; data[i + 3] = 255; // Set Alpha } c_ctx.putImageData(img, x - vx, y - vy); }; cmapImageData = function(x, y, vx, vy, width, height, arr, offset) { var img, i, j, data, bgr, cmap; img = c_ctx.createImageData(width, height); data = img.data; cmap = conf.colourMap; for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { bgr = cmap[arr[j]]; data[i ] = bgr[2]; data[i + 1] = bgr[1]; data[i + 2] = bgr[0]; data[i + 3] = 255; // Set Alpha } c_ctx.putImageData(img, x - vx, y - vy); }; that.blitImage = function(x, y, width, height, arr, offset) { if (conf.true_color) { bgrxImageData(x, y, viewport.x, viewport.y, width, height, arr, offset); } else { cmapImageData(x, y, viewport.x, viewport.y, width, height, arr, offset); } }; that.blitRgbImage = function(x, y, width, height, arr, offset) { if (conf.true_color) { rgbImageData(x, y, viewport.x, viewport.y, width, height, arr, offset); } else { // prolly wrong... cmapImageData(x, y, viewport.x, viewport.y, width, height, arr, offset); } }; that.blitStringImage = function(str, x, y) { var img = new Image(); img.onload = function () { c_ctx.drawImage(img, x - viewport.x, y - viewport.y); }; img.src = str; }; // Wrap ctx.drawImage but relative to viewport that.drawImage = function(img, x, y) { c_ctx.drawImage(img, x - viewport.x, y - viewport.y); }; that.renderQ_push = function(action) { renderQ.push(action); if (renderQ.length === 1) { // If this can be rendered immediately it will be, otherwise // the scanner will start polling the queue (every // requestAnimationFrame interval) scan_renderQ(); } }; scan_renderQ = function() { var a, ready = true; while (ready && renderQ.length > 0) { a = renderQ[0]; switch (a.type) { case 'copy': that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); break; case 'fill': that.fillRect(a.x, a.y, a.width, a.height, a.color); break; case 'blit': that.blitImage(a.x, a.y, a.width, a.height, a.data, 0); break; case 'blitRgb': that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); break; case 'img': if (a.img.complete) { that.drawImage(a.img, a.x, a.y); } else { // We need to wait for this image to 'load' // to keep things in-order ready = false; } break; } if (ready) { a = renderQ.shift(); } } if (renderQ.length > 0) { requestAnimFrame(scan_renderQ); } }; that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { if (conf.cursor_uri === false) { Util.Warn('changeCursor called but no cursor data URI support'); return; } if (conf.true_color) { changeCursor(conf.target, pixels, mask, hotx, hoty, w, h); } else { changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap); } }; that.defaultCursor = function() { conf.target.style.cursor = 'default'; }; return constructor(); // Return the public API interface }; }]); var input = angular.module('noVNC.input', ['noVNC.keyboard', 'noVNC.util']); /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin * Copyright (C) 2013 Samuel Mannehed for Cendio AB * Licensed under MPL 2.0 or any later version (see LICENSE.txt) */ // // Keyboard event handler // input.factory('Keyboard', ['Util', 'KeyEventDecoder', 'kbdUtil', 'VerifyCharModifier', 'TrackKeyState', 'EscapeModifiers', function (Util, KeyEventDecoder, kbdUtil, VerifyCharModifier, TrackKeyState, EscapeModifiers) { 'use strict'; return function(defaults) { var that = {}; // Public API methods var conf = {}; // Configuration attributes // (even if they are happy) // Configuration attributes Util.conf_defaults(conf, that, defaults, [ ['target', 'wo', 'dom', document, 'DOM element that captures keyboard input'], ['focused', 'rw', 'bool', false, 'Capture and send key events'], ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release'] ]); // // Private functions // /////// setup function onRfbEvent(evt) { if (conf.onKeyPress) { Util.Debug('onKeyPress ' + (evt.type === 'keydown' ? 'down' : 'up') + ', keysym: ' + evt.keysym.keysym + '(' + evt.keysym.keyname + ')'); conf.onKeyPress(evt.keysym.keysym, evt.type === 'keydown'); } } // create the keyboard handler var k = KeyEventDecoder( kbdUtil.ModifierSync(), VerifyCharModifier( TrackKeyState( EscapeModifiers(onRfbEvent) ) ) ); function onKeyDown(e) { if (!conf.focused) { return true; } if (k.keydown(e)) { // Suppress bubbling/default actions Util.stopEvent(e); return false; } else { // Allow the event to bubble and become a keyPress event which // will have the character code translated return true; } } function onKeyPress(e) { if (!conf.focused) { return true; } if (k.keypress(e)) { // Suppress bubbling/default actions Util.stopEvent(e); return false; } else { // Allow the event to bubble and become a keyPress event which // will have the character code translated return true; } } function onKeyUp(e) { if (!conf.focused) { return true; } if (k.keyup(e)) { // Suppress bubbling/default actions Util.stopEvent(e); return false; } else { // Allow the event to bubble and become a keyPress event which // will have the character code translated return true; } } function allKeysUp() { Util.Debug('>> Keyboard.allKeysUp'); k.releaseAll(); Util.Debug('<< Keyboard.allKeysUp'); } // // Public API interface functions // that.grab = function() { //Util.Debug('>> Keyboard.grab'); var c = conf.target; Util.addEvent(c, 'keydown', onKeyDown); Util.addEvent(c, 'keyup', onKeyUp); Util.addEvent(c, 'keypress', onKeyPress); // Release (key up) if window loses focus Util.addEvent(window, 'blur', allKeysUp); //Util.Debug('<< Keyboard.grab'); }; that.ungrab = function() { //Util.Debug('>> Keyboard.ungrab'); var c = conf.target; Util.removeEvent(c, 'keydown', onKeyDown); Util.removeEvent(c, 'keyup', onKeyUp); Util.removeEvent(c, 'keypress', onKeyPress); Util.removeEvent(window, 'blur', allKeysUp); // Release (key up) all keys that are in a down state allKeysUp(); //Util.Debug('>> Keyboard.ungrab'); }; that.sync = function(e) { k.syncModifiers(e); }; return that; // Return the public API interface }; }]); // // Mouse event handler // input.factory('Mouse', ['Util', function (Util) { 'use strict'; return function(defaults) { var that = {}; // Public API methods var conf = {}; // Configuration attributes var mouseCaptured = false; var doubleClickTimer = null; var lastTouchPos = null; // Configuration attributes Util.conf_defaults(conf, that, defaults, [ ['target', 'ro', 'dom', document, 'DOM element that captures mouse input'], ['keyboard', 'ro', 'dom', null, 'keyboard object'], ['notify', 'ro', 'func', null, 'Function to call to notify whenever a mouse event is received'], ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'], ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'], ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'], ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'], ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)'] ]); function captureMouse() { // capturing the mouse ensures we get the mouseup event if (conf.target.setCapture) { conf.target.setCapture(); } // some browsers give us mouseup events regardless, // so if we never captured the mouse, we can disregard the event mouseCaptured = true; } function releaseMouse() { if (conf.target.releaseCapture) { conf.target.releaseCapture(); } mouseCaptured = false; } // // Private functions // function resetDoubleClickTimer() { doubleClickTimer = null; } function onMouseButton(e, down) { var evt, pos, bmask; if (!conf.focused) { return true; } if (conf.notify) { conf.notify(e); } evt = (e ? e : window.event); pos = Util.getEventPosition(e, conf.target, conf.scale); if (e.touches || e.changedTouches) { // Touch device // When two touches occur within 500 ms of each other and are // closer than 20 pixels together a double click is triggered. if (down === 1) { if (doubleClickTimer == null) { lastTouchPos = pos; } else { clearTimeout(doubleClickTimer); // When the distance between the two touches is small enough // force the position of the latter touch to the position of // the first. var xs = lastTouchPos.x - pos.x; var ys = lastTouchPos.y - pos.y; var d = Math.sqrt((xs * xs) + (ys * ys)); // The goal is to trigger on a certain physical width, the // devicePixelRatio brings us a bit closer but is not optimal. if (d < 20 * window.devicePixelRatio) { pos = lastTouchPos; } } doubleClickTimer = setTimeout(resetDoubleClickTimer, 500); } bmask = conf.touchButton; // If bmask is set } else if (evt.which) { /* everything except IE */ bmask = 1 << evt.button; } else { /* IE including 9 */ bmask = (evt.button & 0x1) + // Left (evt.button & 0x2) * 2 + // Right (evt.button & 0x4) / 2; // Middle } //Util.Debug('mouse ' + pos.x + ',' + pos.y + ' down: ' + down + // ' bmask: ' + bmask + '(evt.button: ' + evt.button + ')'); if (conf.onMouseButton) { Util.Debug('onMouseButton ' + (down ? 'down' : 'up') + ', x: ' + pos.x + ', y: ' + pos.y + ', bmask: ' + bmask); conf.onMouseButton(pos.x, pos.y, down, bmask); } Util.stopEvent(e); return false; } function onMouseDown(e) { captureMouse(); onMouseButton(e, 1); } function onMouseUp(e) { if (!mouseCaptured) { return; } onMouseButton(e, 0); releaseMouse(); } function onMouseWheel(e) { var evt, pos, bmask, wheelData; if (!conf.focused) { return true; } if (conf.notify) { conf.notify(e); } evt = (e ? e : window.event); pos = Util.getEventPosition(e, conf.target, conf.scale); wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; if (wheelData > 0) { bmask = 1 << 3; } else { bmask = 1 << 4; } //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + ',' + pos.y); if (conf.onMouseButton) { conf.onMouseButton(pos.x, pos.y, 1, bmask); conf.onMouseButton(pos.x, pos.y, 0, bmask); } Util.stopEvent(e); return false; } function onMouseMove(e) { var evt, pos; if (!conf.focused) { return true; } if (conf.notify) { conf.notify(e); } evt = (e ? e : window.event); pos = Util.getEventPosition(e, conf.target, conf.scale); //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + ',' + pos.y); if (conf.onMouseMove) { conf.onMouseMove(pos.x, pos.y); } Util.stopEvent(e); return false; } function onMouseDisable(e) { var evt, pos; if (!conf.focused) { return true; } evt = (e ? e : window.event); pos = Util.getEventPosition(e, conf.target, conf.scale); /* Stop propagation if inside canvas area */ if ((pos.realx >= 0) && (pos.realy >= 0) && (pos.realx < conf.target.offsetWidth) && (pos.realy < conf.target.offsetHeight)) { //Util.Debug('mouse event disabled'); Util.stopEvent(e); return false; } //Util.Debug('mouse event not disabled'); return true; } function onMouseEnter () { conf.keyboard.set_focused(true); } function onMouseLeave () { conf.keyboard.set_focused(false); } // // Public API interface functions // that.grab = function() { //Util.Debug('>> Mouse.grab'); var c = conf.target; if ('ontouchstart' in document.documentElement) { Util.addEvent(c, 'touchstart', onMouseDown); Util.addEvent(window, 'touchend', onMouseUp); Util.addEvent(c, 'touchend', onMouseUp); Util.addEvent(c, 'touchmove', onMouseMove); } else { Util.addEvent(c, 'mousedown', onMouseDown); Util.addEvent(window, 'mouseup', onMouseUp); Util.addEvent(c, 'mouseup', onMouseUp); Util.addEvent(c, 'mousemove', onMouseMove); Util.addEvent(c, 'mouseenter', onMouseEnter); Util.addEvent(c, 'mouseleave', onMouseLeave); Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', onMouseWheel); } /* Work around right and middle click browser behaviors */ Util.addEvent(document, 'click', onMouseDisable); Util.addEvent(document.body, 'contextmenu', onMouseDisable); //Util.Debug('<< Mouse.grab'); }; that.ungrab = function() { //Util.Debug('>> Mouse.ungrab'); var c = conf.target; if ('ontouchstart' in document.documentElement) { Util.removeEvent(c, 'touchstart', onMouseDown); Util.removeEvent(window, 'touchend', onMouseUp); Util.removeEvent(c, 'touchend', onMouseUp); Util.removeEvent(c, 'touchmove', onMouseMove); } else { Util.removeEvent(c, 'mousedown', onMouseDown); Util.removeEvent(window, 'mouseup', onMouseUp); Util.removeEvent(c, 'mouseup', onMouseUp); Util.removeEvent(c, 'mousemove', onMouseMove); Util.removeEvent(c, 'mouseenter', onMouseEnter); Util.removeEvent(c, 'mouseleave', onMouseLeave); Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', onMouseWheel); } /* Work around right and middle click browser behaviors */ Util.removeEvent(document, 'click', onMouseDisable); Util.removeEvent(document.body, 'contextmenu', onMouseDisable); //Util.Debug('>> Mouse.ungrab'); }; return that; // Return the public API interface }; }]); var unzip = angular.module('noVNC.jsunzip', []); unzip.factory('JSUnzip', ['TINF', function(TINF) { 'use strict'; return function JSUnzip() { this.getInt = function(offset, size) { switch (size) { case 4: return (this.data.charCodeAt(offset + 3) & 0xff) << 24 | (this.data.charCodeAt(offset + 2) & 0xff) << 16 | (this.data.charCodeAt(offset + 1) & 0xff) << 8 | (this.data.charCodeAt(offset + 0) & 0xff); case 2: return (this.data.charCodeAt(offset + 1) & 0xff) << 8 | (this.data.charCodeAt(offset + 0) & 0xff); default: return this.data.charCodeAt(offset) & 0xff; } }; this.getDOSDate = function(dosdate, dostime) { var day = dosdate & 0x1f; var month = ((dosdate >> 5) & 0xf) - 1; var year = 1980 + ((dosdate >> 9) & 0x7f); var second = (dostime & 0x1f) * 2; var minute = (dostime >> 5) & 0x3f; var hour = (dostime >> 11) & 0x1f; return new Date(year, month, day, hour, minute, second); }; this.open = function(data) { this.data = data; this.files = []; if (this.data.length < 22) { return { 'status' : false, 'error' : 'Invalid data' }; } var endOfCentralDirectory = this.data.length - 22; while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) !== 0x06054b50) { --endOfCentralDirectory; } if (endOfCentralDirectory < 0) { return { 'status' : false, 'error' : 'Invalid data' }; } if (this.getInt(endOfCentralDirectory + 4, 2) !== 0 || this.getInt(endOfCentralDirectory + 6, 2) !== 0) { return { 'status' : false, 'error' : 'No multidisk support' }; } var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2); var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4); var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2); this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength); var fileOffset = centralDirectoryOffset; for (var i = 0; i < entriesInThisDisk; ++i) { if (this.getInt(fileOffset + 0, 4) !== 0x02014b50) { return { 'status' : false, 'error' : 'Invalid data' }; } if (this.getInt(fileOffset + 6, 2) > 20) { return { 'status' : false, 'error' : 'Unsupported version' }; } if (this.getInt(fileOffset + 8, 2) & 1) { return { 'status' : false, 'error' : 'Encryption not implemented' }; } var compressionMethod = this.getInt(fileOffset + 10, 2); if (compressionMethod !== 0 && compressionMethod !== 8) { return { 'status' : false, 'error' : 'Unsupported compression method' }; } var lastModFileTime = this.getInt(fileOffset + 12, 2); var lastModFileDate = this.getInt(fileOffset + 14, 2); var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime); // var crc = this.getInt(fileOffset + 16, 4); // TODO: crc var compressedSize = this.getInt(fileOffset + 20, 4); var uncompressedSize = this.getInt(fileOffset + 24, 4); var fileNameLength = this.getInt(fileOffset + 28, 2); var extraFieldLength = this.getInt(fileOffset + 30, 2); var fileCommentLength = this.getInt(fileOffset + 32, 2); var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4); var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength); var fileComment = this.data.slice( fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength ); if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) !== 0x04034b50) { return { 'status' : false, 'error' : 'Invalid data' }; } var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2); var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2); var localFileContent = rela