diff --git a/docs/TODO b/docs/TODO index 95b60cc..ec76ad6 100644 --- a/docs/TODO +++ b/docs/TODO @@ -14,6 +14,9 @@ Short Term: http://excanvas.sourceforge.net/ http://code.google.com/p/fxcanvas/ +- Fix cursor URI detection in Arora: + - allows data URI, but doesn't actually work + Medium Term: diff --git a/include/black.css b/include/black.css index 2d9d0ad..4cc195e 100644 --- a/include/black.css +++ b/include/black.css @@ -1,5 +1,4 @@ body { - background: #ddd; margin: 0; font-size: 13px; color: #111; @@ -56,7 +55,7 @@ body { margin: 0px; padding: 1em; } -#VNC_status_bar input { +.VNC_status_button { font-size: 10px; margin: 0px; padding: 0px; @@ -64,24 +63,46 @@ body { #VNC_status { text-align: center; } -#VNC_buttons { - text-align: right; +#VNC_settings_menu { + display: none; + position: absolute; + width: 12em; + border: 1px solid #888; + background-color: #f0f2f6; + padding: 5px; margin: 3px; + z-index: 100; opacity: 1; + text-align: left; white-space: normal; +} +#VNC_settings_menu ul { + list-style: none; + margin: 0; + padding: 0; } +.VNC_buttons_right { + text-align: right; +} +.VNC_buttons_left { + text-align: left; +} .VNC_status_normal { + background: #111; color: #fff; } .VNC_status_error { + background: #111; color: #f44; } .VNC_status_warn { + background: #111; color: #ff4; } + #VNC_screen { -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; - background: #000; + background: #111; padding: 20px; margin: 0 auto; color: #FFF; @@ -93,6 +114,7 @@ body { table-layout: auto; } #VNC_canvas { + background: #111; margin: 0 auto; } #VNC_clipboard { diff --git a/include/canvas.js b/include/canvas.js index 8e822c4..be9e7fe 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -29,7 +29,7 @@ Canvas = { prefer_js : false, // make private force_canvas : false, // make private -cursor_uri : true, // make private, create getter +cursor_uri : true, // make private true_color : false, colourMap : [], @@ -47,6 +47,9 @@ mouseMove : null, onMouseButton: function(e, down) { var evt, pos, bmask; + if (! Canvas.focused) { + return true; + } evt = (e ? e : window.event); pos = Util.getEventPosition(e, $(Canvas.id)); bmask = 1 << evt.button; @@ -122,6 +125,9 @@ onKeyUp : function (e) { onMouseDisable: function (e) { var evt, pos; + if (! Canvas.focused) { + return true; + } evt = (e ? e : window.event); pos = Util.getPosition($(Canvas.id)); /* Stop propagation if inside canvas area */ @@ -208,7 +214,7 @@ init: function (id) { curDat.push(255); } curSave = c.style.cursor; - Canvas.setCursor(curDat, curDat, 2, 2, 8, 8); + Canvas.changeCursor(curDat, curDat, 2, 2, 8, 8); if (c.style.cursor) { Util.Info("Data URI scheme cursor supported"); } else { @@ -561,13 +567,12 @@ getKeysym: function(e) { isCursor: function() { return Canvas.cursor_uri; }, - -setCursor: function(pixels, mask, hotx, hoty, w, h) { +changeCursor: function(pixels, mask, hotx, hoty, w, h) { var cur = [], cmap, IHDRsz, ANDsz, XORsz, url, idx, x, y; - //Util.Debug(">> setCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); + //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); if (!Canvas.cursor_uri) { - Util.Warn("setCursor called but no cursor data URI support"); + Util.Warn("changeCursor called but no cursor data URI support"); return; } @@ -636,7 +641,7 @@ setCursor: function(pixels, mask, hotx, hoty, w, h) { url = "data:image/x-icon;base64," + Base64.encode(cur); $(Canvas.id).style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; - //Util.Debug("<< setCursor, cur.length: " + cur.length); + //Util.Debug("<< changeCursor, cur.length: " + cur.length); } }; diff --git a/include/default_controls.js b/include/default_controls.js index a6c7ab0..aa42203 100644 --- a/include/default_controls.js +++ b/include/default_controls.js @@ -10,12 +10,16 @@ var DefaultControls = { +settingsOpen : false, + +// Render default controls and initialize settings menu load: function(target) { - var url, html; + var url, html, encrypt, cursor, base64, i, sheet, sheets, + DC = DefaultControls; /* Handle state updates */ - RFB.setUpdateState(DefaultControls.updateState); - RFB.setClipboardReceive(DefaultControls.clipReceive); + RFB.setUpdateState(DC.updateState); + RFB.setClipboardReceive(DC.clipReceive); /* Populate the 'target' DOM element with default controls */ if (!target) { target = 'vnc'; } @@ -27,10 +31,6 @@ load: function(target) { html += '
  • Port:
  • '; html += '
  • Password: ' + sheets[i].title + ''; + } + html += ' Style
  • '; + + // Logging selection dropdown + html += '
  • Logging
  • '; + + html += '
    '; + html += '
  • > settingsApply"); + var curSS, newSS, DC = DefaultControls; + DC.saveSetting('encrypt'); + DC.saveSetting('base64'); + DC.saveSetting('true_color'); + if (Canvas.isCursor()) { + DC.saveSetting('cursor'); + } + DC.saveSetting('stylesheet'); + DC.saveSetting('logging'); + + // Settings with immediate (non-connected related) effect + Util.selectStylesheet(DC.getSetting('stylesheet')); + Util.init_logging(DC.getSetting('logging')); + + Util.Debug("<< settingsApply"); +}, + + + setPassword: function() { console.log("setPassword"); RFB.sendPassword($('VNC_password').value); @@ -103,6 +296,7 @@ updateState: function(state, msg) { case 'fatal': c.disabled = true; cad.disabled = true; + DefaultControls.settingsDisabled(true); klass = "VNC_status_error"; break; case 'normal': @@ -110,6 +304,7 @@ updateState: function(state, msg) { c.onclick = DefaultControls.disconnect; c.disabled = false; cad.disabled = false; + DefaultControls.settingsDisabled(true); klass = "VNC_status_normal"; break; case 'disconnected': @@ -119,6 +314,7 @@ updateState: function(state, msg) { c.disabled = false; cad.disabled = true; + DefaultControls.settingsDisabled(false); klass = "VNC_status_normal"; break; case 'password': @@ -127,11 +323,13 @@ updateState: function(state, msg) { c.disabled = false; cad.disabled = true; + DefaultControls.settingsDisabled(true); klass = "VNC_status_warn"; break; default: c.disabled = true; cad.disabled = true; + DefaultControls.settingsDisabled(true); klass = "VNC_status_warn"; break; } @@ -145,28 +343,36 @@ updateState: function(state, msg) { }, connect: function() { - var host, port, password, encrypt, true_color; + var host, port, password, DC = DefaultControls; + + DC.closeSettingsMenu(); + host = $('VNC_host').value; port = $('VNC_port').value; password = $('VNC_password').value; - encrypt = $('VNC_encrypt').checked; - true_color = $('VNC_true_color').checked; if ((!host) || (!port)) { throw("Must set host and port"); } - RFB.connect(host, port, password, encrypt, true_color); + RFB.setEncrypt(DC.getSetting('encrypt')); + RFB.setBase64(DC.getSetting('base64')); + RFB.setTrueColor(DC.getSetting('true_color')); + RFB.setCursor(DC.getSetting('cursor')); + + RFB.connect(host, port, password); }, disconnect: function() { + DefaultControls.closeSettingsMenu(); + RFB.disconnect(); }, -clipFocus: function() { +canvasBlur: function() { Canvas.focused = false; }, -clipBlur: function() { +canvasFocus: function() { Canvas.focused = true; }, diff --git a/include/plain.css b/include/plain.css index dff781c..c8d853b 100644 --- a/include/plain.css +++ b/include/plain.css @@ -36,7 +36,7 @@ margin: 0px; padding: 0px; } -#VNC_status_bar input { +.VNC_status_button { font-size: 10px; margin: 0px; padding: 0px; @@ -44,10 +44,28 @@ #VNC_status { text-align: center; } -#VNC_buttons { - text-align: right; +#VNC_settings_menu { + display: none; + position: absolute; + width: 12em; + border: 1px solid #888; + background-color: #f0f2f6; + padding: 5px; margin: 3px; + z-index: 100; opacity: 1; + text-align: left; white-space: normal; +} +#VNC_settings_menu ul { + list-style: none; + margin: 0; + padding: 0; } +.VNC_buttons_right { + text-align: right; +} +.VNC_buttons_left { + text-align: left; +} .VNC_status_normal { background: #eee; } diff --git a/include/util.js b/include/util.js index 64e3f93..f7a83b2 100644 --- a/include/util.js +++ b/include/util.js @@ -14,37 +14,44 @@ var Util = {}, $; -// Logging/debug routines -if (typeof window.console === "undefined") { - if (typeof window.opera !== "undefined") { - window.console = { - 'log' : window.opera.postError, - 'warn' : window.opera.postError, - 'error': window.opera.postError }; - } else { - window.console = { - 'log' : function(m) {}, - 'warn' : function(m) {}, - 'error': function(m) {}}; +/* + * Logging/debug routines + */ + +Util.init_logging = function (level) { + if (typeof window.console === "undefined") { + if (typeof window.opera !== "undefined") { + window.console = { + 'log' : window.opera.postError, + 'warn' : window.opera.postError, + 'error': window.opera.postError }; + } else { + window.console = { + 'log' : function(m) {}, + 'warn' : function(m) {}, + 'error': function(m) {}}; + } + } + + Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; + switch (level) { + case 'debug': Util.Debug = function (msg) { console.log(msg); }; + case 'info': Util.Info = function (msg) { console.log(msg); }; + case 'warn': Util.Warn = function (msg) { console.warn(msg); }; + case 'error': Util.Error = function (msg) { console.error(msg); }; + break; + default: + throw("invalid logging type '" + level + "'"); } } +// Initialize logging level +Util.init_logging( (document.location.href.match( + /logging=([A-Za-z0-9\._\-]*)/) || + ['', 'warn'])[1] ); -Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; - -Util.logging = (document.location.href.match( - /logging=([A-Za-z0-9\._\-]*)/) || ['', 'warn'])[1]; -switch (Util.logging) { - case 'debug': Util.Debug = function (msg) { console.log(msg); }; - case 'info': Util.Info = function (msg) { console.log(msg); }; - case 'warn': Util.Warn = function (msg) { console.warn(msg); }; - case 'error': Util.Error = function (msg) { console.error(msg); }; - break; - default: - throw("invalid logging type '" + Util.logging + "'"); -} - - -// Simple DOM selector by ID +/* + * Simple DOM selector by ID + */ if (!window.$) { $ = function (id) { if (document.getElementById) { @@ -254,3 +261,69 @@ Util.Flash = (function(){ return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; }()); +/* + * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html + */ +// No days means only for this browser session +Util.createCookie = function(name,value,days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else var expires = ""; + document.cookie = name+"="+value+expires+"; path=/"; +}; + +Util.readCookie = function(name, defaultValue) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return (typeof defaultValue !== 'undefined') ? defaultValue : null; +}; + +Util.eraseCookie = function(name) { + createCookie(name,"",-1); +}; + +/* + * Alternate stylesheet selection + */ +Util.getStylesheets = function() { var i, links, sheets = []; + links = document.getElementsByTagName("link") + for (i = 0; i < links.length; i++) { + if (links[i].title && + links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) { + sheets.push(links[i]); + } + } + return sheets; +}; + +// No sheet means try and use value from cookie, null sheet used to +// clear all alternates. +Util.selectStylesheet = function(sheet) { + var i, link, sheets = Util.getStylesheets(); + if (typeof sheet === 'undefined') { + sheet = 'default'; + } + for (i=0; i < sheets.length; i++) { + link = sheets[i]; + if (link.title === sheet) { + Util.Debug("Using stylesheet " + sheet); + link.disabled = false; + } else { + Util.Debug("Skipping stylesheet " + link.title); + link.disabled = true; + } + } + return sheet; +}; + +// call once to disable alternates and get around webkit bug +Util.selectStylesheet(null); + diff --git a/include/vnc.js b/include/vnc.js index 4b77bc6..9341395 100644 --- a/include/vnc.js +++ b/include/vnc.js @@ -61,11 +61,11 @@ RFB = { host : '', port : 5900, password : '', + encrypt : true, true_color : false, - b64encode : true, // false means UTF-8 on the wire -//b64encode : false, // false means UTF-8 on the wire +local_cursor : true, connectTimeout : 2000, // time to wait for connection @@ -77,6 +77,7 @@ encodings : [ ['RRE', 0x02, 'display_rre'], ['RAW', 0x00, 'display_raw'], ['DesktopSize', -223, 'set_desktopsize'], + ['Cursor', -239, 'set_cursor'], // Psuedo-encoding settings ['JPEG_quality_lo', -32, 'set_jpeg_quality'], @@ -85,9 +86,6 @@ encodings : [ // ['compress_hi', -247, 'set_compress_level'] ], -encodingCursor : - ['Cursor', -239, 'set_cursor'], - setUpdateState: function(externalUpdateState) { RFB.externalUpdateState = externalUpdateState; @@ -101,6 +99,43 @@ setCanvasID: function(canvasID) { RFB.canvasID = canvasID; }, +setEncrypt: function(encrypt) { + if ((!encrypt) || (encrypt in {'0':1, 'no':1, 'false':1})) { + RFB.encrypt = false; + } else { + RFB.encrypt = true; + } +}, + +setBase64: function(b64) { + if ((!b64) || (b64 in {'0':1, 'no':1, 'false':1})) { + RFB.b64encode = false; + } else { + RFB.b64encode = true; + } + Util.Debug("Set b64encode to: " + RFB.b64encode); +}, + +setTrueColor: function(trueColor) { + if ((!trueColor) || (trueColor in {'0':1, 'no':1, 'false':1})) { + RFB.true_color = false; + } else { + RFB.true_color = true; + } +}, + +setCursor: function(cursor) { + if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { + RFB.local_cursor = false; + } else { + if (Canvas.isCursor()) { + RFB.local_cursor = true; + } else { + Util.Warn("Browser does not support local cursor"); + } + } +}, + sendPassword: function(passwd) { RFB.password = passwd; RFB.state = "Authentication"; @@ -149,14 +184,6 @@ load: function () { RFB.updateState('fatal', "No working Canvas"); } - // Add Cursor pseudo-encoding if supported -/* - if (Canvas.isCursor()) { - Util.Debug("Adding Cursor pseudo-encoding to encoding list"); - RFB.encodings.push(RFB.encodingCursor); - } -*/ - // Populate encoding lookup tables RFB.encHandlers = {}; RFB.encNames = {}; @@ -167,24 +194,12 @@ load: function () { //Util.Debug("<< load"); }, -connect: function (host, port, password, encrypt, true_color) { +connect: function (host, port, password) { //Util.Debug(">> connect"); RFB.host = host; RFB.port = port; RFB.password = (password !== undefined) ? password : ""; - RFB.encrypt = (encrypt !== undefined) ? encrypt : true; - if ((RFB.encrypt === "0") || - (RFB.encrypt === "no") || - (RFB.encrypt === "false")) { - RFB.encrypt = false; - } - RFB.true_color = (true_color !== undefined) ? true_color: true; - if ((RFB.true_color === "0") || - (RFB.true_color === "no") || - (RFB.true_color === "false")) { - RFB.true_color = false; - } if ((!RFB.host) || (!RFB.port)) { RFB.updateState('failed', "Must set host and port"); @@ -501,7 +516,11 @@ init_msg: function () { RFB.timing.history_start = (new Date()).getTime(); setTimeout(RFB.update_timings, 1000); - RFB.updateState('normal', "Connected to: " + RFB.fb_name); + if (RFB.encrypt) { + RFB.updateState('normal', "Connected (encrypted) to: " + RFB.fb_name); + } else { + RFB.updateState('normal', "Connected (unencrypted) to: " + RFB.fb_name); + } break; } //Util.Debug("<< init_msg"); @@ -1051,9 +1070,9 @@ set_cursor: function () { //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - Canvas.setCursor(RFB.RQ.shiftBytes(pixelslength), - RFB.RQ.shiftBytes(masklength), - x, y, w, h); + Canvas.changeCursor(RFB.RQ.shiftBytes(pixelslength), + RFB.RQ.shiftBytes(masklength), + x, y, w, h); RFB.FBU.bytes = 0; RFB.FBU.rects -= 1; @@ -1104,14 +1123,24 @@ fixColourMapEntries: function () { clientEncodings: function () { //Util.Debug(">> clientEncodings"); - var arr, i; + var arr, i, encList = []; + + for (i=0; i - Canvas Performance Test + + Canvas Performance Test + + + + + + + Iterations:   @@ -22,13 +31,6 @@ - - - - + + + + +

    Roll over the buttons to test cursors

    +
    + + + +
    +
    +
    + Debug:
    + +
    +
    + + Canvas not supported. + + + + + diff --git a/tests/face.png b/tests/face.png new file mode 100644 index 0000000..74c30d8 Binary files /dev/null and b/tests/face.png differ diff --git a/vnc.html b/vnc.html index 480264a..9a4596a 100644 --- a/vnc.html +++ b/vnc.html @@ -4,8 +4,8 @@ noVNC example: simple example using default controls VNC Client - - + +