This commit is contained in:
Hirokazu Takahashi 2013-01-31 07:10:59 -08:00
commit c939c0cd55
8 changed files with 6718 additions and 64 deletions

View File

@ -17,6 +17,7 @@ is not limited to):
include/vnc.js
include/websock.js
include/webutil.js
include/keymap.js
The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the

View File

@ -129,6 +129,7 @@ html {
background-color:#313131;
border-bottom-right-radius: 800px 600px;
/*border-top-left-radius: 800px 600px;*/
ime-mode: disabled;
}
#noVNC_container, #noVNC_canvas {

View File

@ -18,7 +18,7 @@ function Keyboard(defaults) {
var that = {}, // Public API methods
conf = {}, // Configuration attributes
keyDownList = []; // List of depressed keys
keyDownMap = {}; // List of depressed keys
// (even if they are happy)
// Configuration attributes
@ -29,6 +29,10 @@ Util.conf_defaults(conf, that, defaults, [
['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
]);
// CTRL/ALT/SHIFT/ALTGR key status.
var keyModifiers = {'altKey': false, 'ctrlKey': false, 'shiftKey': false,
'altgrKey': false, 'altgrKey_fake': false, 'altgrKey_native': false};
//
// Private functions
@ -92,6 +96,10 @@ function getKeysymSpecial(evt) {
}
if ((!keysym) && (evt.ctrlKey || evt.altKey)) {
if (evt.ctrlKey && evt.altKey) {
// This is a key event with ALTGR.
return keysym;
}
if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) {
keysym = evt.which;
} else {
@ -202,12 +210,15 @@ function getKeysym(evt) {
return keysym;
}
function show_keyDownList(kind) {
var c;
var msg = "keyDownList (" + kind + "):\n";
for (c = 0; c < keyDownList.length; c++) {
msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode +
" - which: " + keyDownList[c].which + "\n";
function show_keyDownMap(kind) {
var c = 0;
var keyCode, fevt;
var msg = "keyDownMap (" + kind + "):\n";
for (keyCode in keyDownMap) {
fevt = getKeyEvent(keyCode, false);
msg = msg + " " + c + " - keyCode: " + fevt.keyCode +
" - which: " + fevt.which + "\n";
c++;
}
Util.Debug(msg);
}
@ -225,20 +236,15 @@ function copyKeyEvent(evt) {
}
function pushKeyEvent(fevt) {
keyDownList.push(fevt);
keyDownMap[fevt.keyCode] = fevt;
}
function getKeyEvent(keyCode, pop) {
var i, fevt = null;
for (i = keyDownList.length-1; i >= 0; i--) {
if (keyDownList[i].keyCode === keyCode) {
if ((typeof(pop) !== "undefined") && (pop)) {
fevt = keyDownList.splice(i, 1)[0];
} else {
fevt = keyDownList[i];
}
break;
}
var fevt = keyDownMap[keyCode];
if (typeof(fevt) === 'undefined') {
fevt = null;
} else if ((typeof(pop) !== "undefined") && (pop)) {
delete keyDownMap[keyCode];
}
return fevt;
}
@ -254,6 +260,81 @@ function ignoreKeyEvent(evt) {
return false;
}
// 1. Save the current status of modifier keys to enable the keypress event
// handler to refer the status. Some browers may not fire keyup events
// pairing with some keydown events for modifier keys like CTRL, ALT and
// SHIFT keys. noVNC is expected to detect this situation to issue fake
// keyup events to fix it up.
// 2. Emulate ALTGR key events. Most browsers generate two key events
// when hitting an ALTGR key, events which are CTRL and ALT ones.
// But unfortunately there exist some vnc servers that can't handle
// them as an emulated ALTGR key event. noVNC has to convert them into
// one ALTGR key event on behalf of vnc servers.
function saveKeyModifiers(evt, keysym) {
var km = keyModifiers;
km.shiftKey = evt.shiftKey;
if (evt.type === 'keydown') {
switch (keysym) {
// If this event is ALT or CTRL down and both evt.ctrlKey and
// evt.altKey get true, pass the decision to the next key event
// that whether it should be turned into an ALTGR key event.
case 0xFFE3 :
if (evt.ctrlKey && evt.altKey) break;
km.ctrlKey = true;
break;
case 0xFFE9 :
if (evt.ctrlKey && evt.altKey) break;
km.altKey = true;
break;
default :
switch (keysym) {
case 0xFFE1 : km.shiftKey = true; break;
case 0xFE03 : km.altgrKey_native = true; break;
}
km.altKey = evt.altKey;
km.ctrlKey = evt.ctrlKey;
break;
}
if (!(keysym) && km.ctrlKey && km.altKey) {
// Turn the ALT and CTRL events into an ALTGR. The keycode will
// be translated by the ALTGR even if it is an emulated one.
km.altgrKey_fake = true;
km.ctrlKey = km.altKey = false;
} else {
km.altgrKey_fake = false;
}
} else { // keyup event
switch (keysym) {
// Notice: FireFox 15 for linux doesn't set evt.altKey and
// evt.ctrlKey false even when this is an ALT or CTRL keyup
// event. Don't use evt.altKey and evt.ctrlKey here.
case 0xFFE3 : km.ctrlKey = false; break;
case 0xFFE9 : km.altKey = false; break;
default :
switch (keysym) {
case 0xFFE1 : km.shiftKey = false; break;
case 0xFE03 : km.altgrKey_native = false; break;
}
km.altKey = evt.altKey;
km.ctrlKey = evt.ctrlKey;
break;
}
if (km.altgrKey_fake) {
if (keysym == 0xFFE3 || keysym == 0xFFE9) {
// This is a part of an emulated ALTGR keyup event.
// Don't let ALT or CTRL down yet.
km.altgrKey_fake = false;
km.ctrlKey = km.altKey = false;
} else if (km.altKey && km.ctrlKey) {
km.ctrlKey = km.altKey = false;
} else {
km.altgrKey_fake = false;
}
}
}
km.altgrKey = km.altgrKey_fake || km.altgrKey_native;
};
//
// Key Event Handling:
@ -313,6 +394,7 @@ function onKeyDown(e) {
keysym = getKeysymSpecial(evt);
// Save keysym decoding for use in keyUp
fevt.keysym = keysym;
saveKeyModifiers(evt, keysym);
if (keysym) {
// If it is a key or key combination that might trigger
// browser behaviors or it has no corresponding keyPress
@ -321,17 +403,12 @@ function onKeyDown(e) {
Util.Debug("onKeyPress down, keysym: " + keysym +
" (onKeyDown key: " + evt.keyCode +
", which: " + evt.which + ")");
conf.onKeyPress(keysym, 1, evt);
conf.onKeyPress(keysym, 1, keyModifiers);
pushKeyEvent(fevt);
}
suppress = true;
}
if (! ignoreKeyEvent(evt)) {
// Add it to the list of depressed keys
pushKeyEvent(fevt);
//show_keyDownList('down');
}
if (suppress) {
// Suppress bubbling/default actions
Util.stopEvent(e);
@ -348,7 +425,7 @@ function onKeyPress(e) {
return true;
}
var evt = (e ? e : window.event),
kdlen = keyDownList.length, keysym = null;
kdlen = keyDownMap.length, keysym = null;
//Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
if (((evt.which !== "undefined") && (evt.which === 0)) ||
@ -365,23 +442,22 @@ function onKeyPress(e) {
keysym = getKeysym(evt);
// Modify the the which attribute in the depressed keys list so
// that the keyUp event will be able to have the character code
// translation available.
if (kdlen > 0) {
keyDownList[kdlen-1].keysym = keysym;
} else {
Util.Warn("keyDownList empty when keyPress triggered");
}
//show_keyDownList('press');
//show_keyDownMap('press');
if (Util.Engine.presto && (evt.ctrlKey || evt.altKey)) {
// Opera may fire two keypress events againt one keydown when
// hitting a key with some modifiers. Just ignore the first one.
return false;
}
// Send the translated keysym
if (conf.onKeyPress && (keysym > 0)) {
Util.Debug("onKeyPress down, keysym: " + keysym +
" (onKeyPress key: " + evt.keyCode +
", which: " + evt.which + ")");
conf.onKeyPress(keysym, 1, evt);
Util.Debug("onKeyPress up, keysym: " + keysym +
" (onKeyPress key: " + evt.keyCode +
", which: " + evt.which + ")");
conf.onKeyPress(keysym, 2, keyModifiers);
}
// Stop keypress events just in case
@ -401,12 +477,13 @@ function onKeyUp(e) {
if (fevt) {
keysym = fevt.keysym;
} else {
Util.Warn("Key event (keyCode = " + evt.keyCode +
") not found on keyDownList");
//Util.Debug("Key event (keyCode = " + evt.keyCode +
// ") not found on keyDownMap");
keysym = 0;
}
saveKeyModifiers(evt, keysym);
//show_keyDownList('up');
//show_keyDownMap('up');
if (conf.onKeyPress && (keysym > 0)) {
//Util.Debug("keyPress up, keysym: " + keysym +
@ -414,7 +491,7 @@ function onKeyUp(e) {
Util.Debug("onKeyPress up, keysym: " + keysym +
" (onKeyPress key: " + evt.keyCode +
", which: " + evt.which + ")");
conf.onKeyPress(keysym, 0, evt);
conf.onKeyPress(keysym, 0, keyModifiers);
}
Util.stopEvent(e);
return false;
@ -422,18 +499,17 @@ function onKeyUp(e) {
function allKeysUp() {
Util.Debug(">> Keyboard.allKeysUp");
if (keyDownList.length > 0) {
Util.Info("Releasing pressed/down keys");
}
var i, keysym, fevt = null;
for (i = keyDownList.length-1; i >= 0; i--) {
fevt = keyDownList.splice(i, 1)[0];
var keyCode, keysym, fevt;
keyModifiers = {'altKey': false, 'ctrlKey': false, 'shiftKey': false,
'altgrKey': false, 'altgrKey_fake': false, 'altgrKey_native': false};
for (keyCode in keyDownMap) {
fevt = getKeyEvent(keyCode, true);
keysym = fevt.keysym;
if (conf.onKeyPress && (keysym > 0)) {
Util.Debug("allKeysUp, keysym: " + keysym +
" (keyCode: " + fevt.keyCode +
", which: " + fevt.which + ")");
conf.onKeyPress(keysym, 0, fevt);
conf.onKeyPress(keysym, 0, keyModifiers);
}
}
Util.Debug("<< Keyboard.allKeysUp");

6407
include/keymap.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -126,7 +126,9 @@ var that = {}, // Public API methods
mouse_buttonMask = 0,
mouse_arr = [],
viewportDragging = false,
viewportDragPos = {};
viewportDragPos = {},
keymap = null;
// Configuration attributes
Util.conf_defaults(conf, that, defaults, [
@ -596,16 +598,95 @@ checkEvents = function() {
setTimeout(checkEvents, conf.check_rate);
};
keyPress = function(keysym, down) {
var arr;
// The status of the VNC server side key modifers.
var remote_status = {shift: false, ctrl: false, alt: false, altgr: false};
var softKeyState = {ctrlKey: false, altKey: false};
keyPress = function(keysym, down, km) {
var arr = [];
if (conf.view_only) { return; } // View only, skip keyboard events
arr = keyEvent(keysym, down);
// Remap the modifiers with apppropriate ones depending on the
// keyboard type of the vnc server if this is a presskey event.
if (!!keymap && down === 2)
km = remapModifiers(km, keysym);
// Generate all modifier keys' events on demand.
// Send key events for modifiers to the vnc server when:
// 1. the status of modifers have been changed
// 2. this is a repeated keydown event for a modifer
var ctrlKey = km.ctrlKey || softKeyState.ctrlKey;
var altKey = km.altKey || softKeyState.altKey;
if (remote_status.ctrl !== ctrlKey || keysym === 0xFFE3) {
arr = arr.concat(keyEvent(0xFFE3, ctrlKey)); // CTRL
remote_status.ctrl = ctrlKey;
}
if (remote_status.alt !== altKey || keysym === 0xFFE9) {
arr = arr.concat(keyEvent(0xFFE9, altKey)); // ALT
remote_status.alt = altKey;
}
if (remote_status.altgr !== km.altgrKey || keysym === 0xFE03) {
arr = arr.concat(keyEvent(0xFE03, km.altgrKey)); // ALTGR
remote_status.altgr = km.altgrKey;
}
if (remote_status.shift !== km.shiftKey || keysym === 0xFFE1) {
arr = arr.concat(keyEvent(0xFFE1, km.shiftKey)); // SHIFT
remote_status.shift = km.shiftKey;
}
if (keysym === 0xFFE1 || keysym === 0xFFE3
|| keysym === 0xFFE9 || keysym === 0xFE03) {
// SHIFT, CTRL, ALT and ALTGR events are already set in arr[].
} else if (down === 2) {
// This is a keypress event.
// Send a keyup event here to avoid letting the server side generate
// repeated-key events. Otherwise, both sides will independently
// generate key down and press events against the same key.
arr = arr.concat(keyEvent(keysym, 1));
arr = arr.concat(keyEvent(keysym, 0));
} else {
// This is a keydown or keyup event.
arr = arr.concat(keyEvent(keysym, down));
}
if (!!keymap && down === 2) {
// Turn off SHIFT and ALTGR every time when emulating modifiers.
if (remote_status.shift) {
arr = arr.concat(keyEvent(0xFFE1, 0)); // SHIFT up
remote_status.shift = false;
}
if (remote_status.altgr) {
arr = arr.concat(keyEvent(0xFE03, 0)); // ALTGR up
remote_status.altgr = false;
}
}
arr = arr.concat(fbUpdateRequests());
ws.send(arr);
};
// Remap the modifiers depending on keyboard types. It depends on keyboards
// whether a certain keycode should be issued with modifiers. For example,
// the US keyboard issues '@' with SHIFT, the Japanese keyboard issues '@'
// without any modifers, and the German keyboard issues '@' with ALTGR.
// When the keyboard types are different between the viewer and the vnc
// server, the server is expected to generate fake modifer key events
// if needed. But unfortunately there are a lot of VNC servers that can't
// handle this. Then there is no choice but to make noVNC take care of it
// on behalf of them.
function remapModifiers(km, keysym)
{
var remap = {'altKey': km.altKey, 'ctrlKey': km.ctrlKey,
'shiftKey': km.shiftKey, 'altgrKey': km.altgrKey};
var key = keymap[keysym];
if (typeof key !== "undefined") {
remap.altgrKey = key.altgr;
remap.shiftKey = key.shift;
}
return remap;
}
mouseButton = function(x, y, down, bmask) {
if (down) {
mouse_buttonMask |= bmask;
@ -1838,6 +1919,10 @@ that.sendKey = function(code, down) {
ws.send(arr);
};
that.updateSoftKeyState = function(name, value) {
softKeyState[name] = value;
};
that.clipboardPasteFrom = function(text) {
if (rfb_state !== "normal") { return; }
//Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
@ -1860,6 +1945,14 @@ that.testMode = function(override_send, data_mode) {
};
};
that.setKeymap = function(kb) {
if (!kb || kb === 'default') {
keymap = null;
} else {
keymap = Kmap.getKeymap(kb);
}
};
return constructor(); // Return the public API interface

View File

@ -13,7 +13,8 @@
// Load supporting scripts
window.onscriptsload = function () { UI.load(); };
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
"input.js", "display.js", "jsunzip.js", "rfb.js",
"keymap.js"]);
var UI = {
@ -31,7 +32,7 @@ load: function (callback) {
// Render default UI and initialize settings menu
start: function(callback) {
var html = '', i, sheet, sheets, llevels;
var html = '', i, sheet, sheets, llevels, kbtypes;
// Stylesheet selection dropdown
sheet = WebUtil.selectStylesheet();
@ -46,6 +47,19 @@ start: function(callback) {
UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
}
// Keyboard type selection dropdown
kbtypes = ['default', 'ar', 'bepo', 'da', 'de', 'de-ch', 'en-gb',
'en-us', 'es', 'et', 'fi', 'fo', 'fr', 'fr-be', 'fr-ca',
'fr-ch', 'hr', 'hu', 'is', 'it', 'ja', 'lt', 'lv', 'mk',
'nl', 'nl-be', 'no', 'pl', 'pt', 'pt-br', 'ru', 'sl',
'sv', 'th', 'tr'];
for (i = 0; i < kbtypes.length; i += 1) {
UI.addOption($D('noVNC_keymap'), kbtypes[i], kbtypes[i]);
}
UI.initSetting('keymap', 'default');
// Settings with immediate effects
UI.initSetting('logging', 'warn');
WebUtil.init_logging(UI.getSetting('logging'));
@ -71,6 +85,7 @@ start: function(callback) {
UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState,
'onClipboard': UI.clipReceive});
UI.rfb.setKeymap(UI.getSetting('keymap'));
UI.updateVisualState();
// Unfocus clipboard when over the VNC area
@ -149,6 +164,8 @@ addMouseHandlers: function() {
$D("connectButton").onclick = UI.toggleConnectPanel;
$D("disconnectButton").onclick = UI.disconnect;
$D("descriptionButton").onclick = UI.toggleConnectPanel;
$D("ctrlLockCheckBox").onclick = UI.updateSoftKeyState;
$D("altLockCheckBox").onclick = UI.updateSoftKeyState;
$D("noVNC_clipboard_text").onfocus = UI.displayBlur;
$D("noVNC_clipboard_text").onblur = UI.displayFocus;
@ -326,6 +343,7 @@ toggleSettingsPanel: function() {
UI.updateSetting('repeaterID');
UI.updateSetting('stylesheet');
UI.updateSetting('logging');
UI.updateSetting('keymap');
UI.openSettingsMenu();
}
@ -370,8 +388,10 @@ settingsApply: function() {
UI.saveSetting('repeaterID');
UI.saveSetting('stylesheet');
UI.saveSetting('logging');
UI.saveSetting('keymap');
// Settings with immediate (non-connected related) effect
UI.rfb.setKeymap(UI.getSetting('keymap'));
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
WebUtil.init_logging(UI.getSetting('logging'));
UI.setViewClip();
@ -395,6 +415,10 @@ sendCtrlAltDel: function() {
UI.rfb.sendCtrlAltDel();
},
updateSoftKeyState: function() {
UI.rfb.updateSoftKeyState(this.value, this.checked);
},
setMouseButton: function(num) {
var b, blist = [0, 1,2,4], button;
@ -490,11 +514,19 @@ updateVisualState: function() {
$D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline";
$D('ctrlLockCheckBox').style.display = "inline";
$D('altLockCheckBox').style.display = "inline";
$D('ctrlLockLabel').style.display = "inline";
$D('altLockLabel').style.display = "inline";
} else {
UI.setMouseButton();
$D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
$D('ctrlLockCheckBox').style.display = "none";
$D('altLockCheckBox').style.display = "none";
$D('ctrlLockLabel').style.display = "none";
$D('altLockLabel').style.display = "none";
}
// State change disables viewport dragging.
// It is enabled (toggled) by direct click on the button

View File

@ -70,6 +70,12 @@
<!--noVNC Buttons-->
<div class="noVNC-buttons-right">
<input type=checkbox value="ctrlKey"
id="ctrlLockCheckBox">
<label for="ctrlLockCheckBox" id="ctrlLockLabel">CtrlLock</label>
<input type=checkbox value="altKey"
id="altLockCheckBox">
<label for="altLockCheckBox" id="altLockLabel" >AltLock</label>
<input type="image" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
title="Send Ctrl-Alt-Del" />
@ -138,6 +144,12 @@
<select id="noVNC_logging" name="vncLogging">
</select></label>
</li>
<!-- keyboard selection dropdown -->
<li><label><strong>Keyboard: </strong>
<select id="noVNC_keymap" name="vncKeymap">
</select></label>
</li>
<hr>
<li><input type="button" id="noVNC_apply" value="Apply"></li>
</ul>

View File

@ -25,10 +25,20 @@
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
<td><div id="noVNC_status">Loading</div></td>
<td width="1%"><div id="noVNC_buttons">
<td width="300pt"><div id="noVNC_buttons">
<span style="float: right">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
</div></td>
</span>
<span style="float: right">
<input type=checkbox value="ctrlKey"
id="ctrlLockCheckBox">
<label for="ctrlLockCheckBox">CtrlLock</label>
<input type=checkbox value="altKey"
id="altLockCheckBox">
<label for="altLockCheckBox">AltLock</label>
</span>
</div></td>
</tr></table>
</div>
<canvas id="noVNC_canvas" width="640px" height="20px">
@ -43,9 +53,15 @@
// Load supporting scripts
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
"input.js", "display.js", "jsunzip.js", "rfb.js",
"keymap.js"]);
var rfb;
var buttons = [
{name: 'ctrlLockCheckBox', onclick: updateSoftKeyState},
{name: 'altLockCheckBox', onclick: updateSoftKeyState},
{name: 'sendCtrlAltDelButton', onclick: sendCtrlAltDel}
];
function passwordRequired(rfb) {
var msg;
@ -65,11 +81,14 @@
rfb.sendCtrlAltDel();
return false;
}
function updateSoftKeyState() {
rfb.updateSoftKeyState(this.value, this.checked);
return true;
}
function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level;
var s, sb, level;
s = $D('noVNC_status');
sb = $D('noVNC_status_bar');
cad = $D('sendCtrlAltDelButton');
switch (state) {
case 'failed': level = "error"; break;
case 'fatal': level = "error"; break;
@ -79,8 +98,15 @@
default: level = "warn"; break;
}
if (state === "normal") { cad.disabled = false; }
else { cad.disabled = true; }
var i;
for (i = 0; i < buttons.length; i++) {
var elem = $D(buttons[i].name);
if (state === "normal") {
elem.disabled = false;
} else {
elem.disabled = true;
}
}
if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "noVNC_status_" + level);
@ -89,10 +115,15 @@
}
window.onscriptsload = function () {
var host, port, password, path, token;
var host, port, password, path, token, i;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
for (i = 0; i < buttons.length; i++) {
var button = buttons[i];
$D(button.name).style.display = "inline";
if (button.onclick) {
$D(button.name).onclick = button.onclick;
}
}
WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
@ -126,6 +157,7 @@
'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState,
'onPasswordRequired': passwordRequired});
rfb.setKeymap(WebUtil.getQueryVar('keymap', 'default'));
rfb.connect(host, port, password, path);
};
</script>