Merge branches 'ffalt' and 'altgr' of https://github.com/CendioOssman/noVNC
This commit is contained in:
commit
35dd3c2299
|
@ -21,6 +21,7 @@ export default function Keyboard(target) {
|
||||||
this._keyDownList = {}; // List of depressed keys
|
this._keyDownList = {}; // List of depressed keys
|
||||||
// (even if they are happy)
|
// (even if they are happy)
|
||||||
this._pendingKey = null; // Key waiting for keypress
|
this._pendingKey = null; // Key waiting for keypress
|
||||||
|
this._altGrArmed = false; // Windows AltGr detection
|
||||||
|
|
||||||
// keep these here so we can refer to them later
|
// keep these here so we can refer to them later
|
||||||
this._eventHandlers = {
|
this._eventHandlers = {
|
||||||
|
@ -40,35 +41,19 @@ Keyboard.prototype = {
|
||||||
// ===== PRIVATE METHODS =====
|
// ===== PRIVATE METHODS =====
|
||||||
|
|
||||||
_sendKeyEvent: function (keysym, code, down) {
|
_sendKeyEvent: function (keysym, code, down) {
|
||||||
|
if (down) {
|
||||||
|
this._keyDownList[code] = keysym;
|
||||||
|
} else {
|
||||||
|
// Do we really think this key is down?
|
||||||
|
if (!(code in this._keyDownList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete this._keyDownList[code];
|
||||||
|
}
|
||||||
|
|
||||||
Log.Debug("onkeyevent " + (down ? "down" : "up") +
|
Log.Debug("onkeyevent " + (down ? "down" : "up") +
|
||||||
", keysym: " + keysym, ", code: " + code);
|
", keysym: " + keysym, ", code: " + code);
|
||||||
|
|
||||||
// Windows sends CtrlLeft+AltRight when you press
|
|
||||||
// AltGraph, which tends to confuse the hell out of
|
|
||||||
// remote systems. Fake a release of these keys until
|
|
||||||
// there is a way to detect AltGraph properly.
|
|
||||||
var fakeAltGraph = false;
|
|
||||||
if (down && browser.isWindows()) {
|
|
||||||
if ((code !== 'ControlLeft') &&
|
|
||||||
(code !== 'AltRight') &&
|
|
||||||
('ControlLeft' in this._keyDownList) &&
|
|
||||||
('AltRight' in this._keyDownList)) {
|
|
||||||
fakeAltGraph = true;
|
|
||||||
this.onkeyevent(this._keyDownList['AltRight'],
|
|
||||||
'AltRight', false);
|
|
||||||
this.onkeyevent(this._keyDownList['ControlLeft'],
|
|
||||||
'ControlLeft', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onkeyevent(keysym, code, down);
|
this.onkeyevent(keysym, code, down);
|
||||||
|
|
||||||
if (fakeAltGraph) {
|
|
||||||
this.onkeyevent(this._keyDownList['ControlLeft'],
|
|
||||||
'ControlLeft', true);
|
|
||||||
this.onkeyevent(this._keyDownList['AltRight'],
|
|
||||||
'AltRight', true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getKeyCode: function (e) {
|
_getKeyCode: function (e) {
|
||||||
|
@ -110,6 +95,30 @@ Keyboard.prototype = {
|
||||||
var code = this._getKeyCode(e);
|
var code = this._getKeyCode(e);
|
||||||
var keysym = KeyboardUtil.getKeysym(e);
|
var keysym = KeyboardUtil.getKeysym(e);
|
||||||
|
|
||||||
|
// Windows doesn't have a proper AltGr, but handles it using
|
||||||
|
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||||
|
// so we need to merge those in to a single AltGr event. We
|
||||||
|
// detect this case by seeing the two key events directly after
|
||||||
|
// each other with a very short time between them (<50ms).
|
||||||
|
if (this._altGrArmed) {
|
||||||
|
this._altGrArmed = false;
|
||||||
|
clearTimeout(this._altGrTimeout);
|
||||||
|
|
||||||
|
if ((code === "AltRight") &&
|
||||||
|
((e.timeStamp - this._altGrCtrlTime) < 50)) {
|
||||||
|
// FIXME: We fail to detect this if either Ctrl key is
|
||||||
|
// first manually pressed as Windows then no
|
||||||
|
// longer sends the fake Ctrl down event. It
|
||||||
|
// does however happily send real Ctrl events
|
||||||
|
// even when AltGr is already down. Some
|
||||||
|
// browsers detect this for us though and set the
|
||||||
|
// key to "AltGraph".
|
||||||
|
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||||
|
} else {
|
||||||
|
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We cannot handle keys we cannot track, but we also need
|
// We cannot handle keys we cannot track, but we also need
|
||||||
// to deal with virtual keyboards which omit key info
|
// to deal with virtual keyboards which omit key info
|
||||||
// (iOS omits tracking info on keyup events, which forces us to
|
// (iOS omits tracking info on keyup events, which forces us to
|
||||||
|
@ -181,7 +190,14 @@ Keyboard.prototype = {
|
||||||
this._pendingKey = null;
|
this._pendingKey = null;
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
|
|
||||||
this._keyDownList[code] = keysym;
|
// Possible start of AltGr sequence? (see above)
|
||||||
|
if ((code === "ControlLeft") && browser.isWindows() &&
|
||||||
|
!("ControlLeft" in this._keyDownList)) {
|
||||||
|
this._altGrArmed = true;
|
||||||
|
this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100);
|
||||||
|
this._altGrCtrlTime = e.timeStamp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._sendKeyEvent(keysym, code, true);
|
this._sendKeyEvent(keysym, code, true);
|
||||||
},
|
},
|
||||||
|
@ -211,8 +227,6 @@ Keyboard.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._keyDownList[code] = keysym;
|
|
||||||
|
|
||||||
this._sendKeyEvent(keysym, code, true);
|
this._sendKeyEvent(keysym, code, true);
|
||||||
},
|
},
|
||||||
_handleKeyPressTimeout: function (e) {
|
_handleKeyPressTimeout: function (e) {
|
||||||
|
@ -246,8 +260,6 @@ Keyboard.prototype = {
|
||||||
keysym = 0;
|
keysym = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._keyDownList[code] = keysym;
|
|
||||||
|
|
||||||
this._sendKeyEvent(keysym, code, true);
|
this._sendKeyEvent(keysym, code, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -256,6 +268,14 @@ Keyboard.prototype = {
|
||||||
|
|
||||||
var code = this._getKeyCode(e);
|
var code = this._getKeyCode(e);
|
||||||
|
|
||||||
|
// We can't get a release in the middle of an AltGr sequence, so
|
||||||
|
// abort that detection
|
||||||
|
if (this._altGrArmed) {
|
||||||
|
this._altGrArmed = false;
|
||||||
|
clearTimeout(this._altGrTimeout);
|
||||||
|
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
}
|
||||||
|
|
||||||
// See comment in _handleKeyDown()
|
// See comment in _handleKeyDown()
|
||||||
if (browser.isMac() && (code === 'CapsLock')) {
|
if (browser.isMac() && (code === 'CapsLock')) {
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||||
|
@ -263,14 +283,13 @@ Keyboard.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we really think this key is down?
|
|
||||||
if (!(code in this._keyDownList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||||
|
},
|
||||||
|
|
||||||
delete this._keyDownList[code];
|
_handleAltGrTimeout: function () {
|
||||||
|
this._altGrArmed = false;
|
||||||
|
clearTimeout(this._altGrTimeout);
|
||||||
|
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
},
|
},
|
||||||
|
|
||||||
_allKeysUp: function () {
|
_allKeysUp: function () {
|
||||||
|
@ -278,7 +297,6 @@ Keyboard.prototype = {
|
||||||
for (var code in this._keyDownList) {
|
for (var code in this._keyDownList) {
|
||||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||||
};
|
};
|
||||||
this._keyDownList = {};
|
|
||||||
Log.Debug("<< Keyboard.allKeysUp");
|
Log.Debug("<< Keyboard.allKeysUp");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -386,108 +386,128 @@ describe('Key Event Handling', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.navigator.platform = "Windows x86_64";
|
window.navigator.platform = "Windows x86_64";
|
||||||
|
|
||||||
|
this.clock = sinon.useFakeTimers();
|
||||||
});
|
});
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
Object.defineProperty(window, "navigator", origNavigator);
|
Object.defineProperty(window, "navigator", origNavigator);
|
||||||
|
this.clock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate fake undo/redo events on press when AltGraph is down', function() {
|
it('should supress ControlLeft until it knows if it is AltGr', function () {
|
||||||
var times_called = 0;
|
|
||||||
var kbd = new Keyboard(document);
|
var kbd = new Keyboard(document);
|
||||||
kbd.onkeyevent = function(keysym, code, down) {
|
kbd.onkeyevent = sinon.spy();
|
||||||
switch(times_called++) {
|
|
||||||
case 0:
|
|
||||||
expect(keysym).to.be.equal(0xFFE3);
|
|
||||||
expect(code).to.be.equal('ControlLeft');
|
|
||||||
expect(down).to.be.equal(true);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
expect(keysym).to.be.equal(0xFFEA);
|
|
||||||
expect(code).to.be.equal('AltRight');
|
|
||||||
expect(down).to.be.equal(true);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
expect(keysym).to.be.equal(0xFFEA);
|
|
||||||
expect(code).to.be.equal('AltRight');
|
|
||||||
expect(down).to.be.equal(false);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
expect(keysym).to.be.equal(0xFFE3);
|
|
||||||
expect(code).to.be.equal('ControlLeft');
|
|
||||||
expect(down).to.be.equal(false);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
expect(keysym).to.be.equal(0x61);
|
|
||||||
expect(code).to.be.equal('KeyA');
|
|
||||||
expect(down).to.be.equal(true);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
expect(keysym).to.be.equal(0xFFE3);
|
|
||||||
expect(code).to.be.equal('ControlLeft');
|
|
||||||
expect(down).to.be.equal(true);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
expect(keysym).to.be.equal(0xFFEA);
|
|
||||||
expect(code).to.be.equal('AltRight');
|
|
||||||
expect(down).to.be.equal(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// First the modifier combo
|
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
// Next a normal character
|
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
|
||||||
expect(times_called).to.be.equal(7);
|
|
||||||
});
|
});
|
||||||
it('should no do anything on key release', function() {
|
|
||||||
var times_called = 0;
|
it('should not trigger on repeating ControlLeft', function () {
|
||||||
var kbd = new Keyboard(document);
|
var kbd = new Keyboard(document);
|
||||||
kbd.onkeyevent = function(keysym, code, down) {
|
kbd.onkeyevent = sinon.spy();
|
||||||
switch(times_called++) {
|
|
||||||
case 7:
|
|
||||||
expect(keysym).to.be.equal(0x61);
|
|
||||||
expect(code).to.be.equal('KeyA');
|
|
||||||
expect(down).to.be.equal(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// First the modifier combo
|
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
// Next a normal character
|
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||||
|
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
|
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not supress ControlRight', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should release ControlLeft after 100 ms', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
|
this.clock.tick(100);
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should release ControlLeft on other key press', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||||
|
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
|
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
|
||||||
|
|
||||||
|
// Check that the timer is properly dead
|
||||||
|
kbd.onkeyevent.reset();
|
||||||
|
this.clock.tick(100);
|
||||||
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should release ControlLeft on other key release', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
|
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true);
|
||||||
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
||||||
expect(times_called).to.be.equal(8);
|
expect(kbd.onkeyevent).to.have.been.calledThrice;
|
||||||
|
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
|
expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
|
||||||
|
|
||||||
|
// Check that the timer is properly dead
|
||||||
|
kbd.onkeyevent.reset();
|
||||||
|
this.clock.tick(100);
|
||||||
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
it('should not consider a char modifier to be down on the modifier key itself', function() {
|
|
||||||
var times_called = 0;
|
it('should generate AltGraph for quick Ctrl+Alt sequence', function () {
|
||||||
var kbd = new Keyboard(document);
|
var kbd = new Keyboard(document);
|
||||||
kbd.onkeyevent = function(keysym, code, down) {
|
kbd.onkeyevent = sinon.spy();
|
||||||
switch(times_called++) {
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
|
||||||
case 0:
|
this.clock.tick(20);
|
||||||
expect(keysym).to.be.equal(0xFFE3);
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
|
||||||
expect(code).to.be.equal('ControlLeft');
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
expect(down).to.be.equal(true);
|
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
|
||||||
break;
|
|
||||||
case 1:
|
// Check that the timer is properly dead
|
||||||
expect(keysym).to.be.equal(0xFFE9);
|
kbd.onkeyevent.reset();
|
||||||
expect(code).to.be.equal('AltLeft');
|
this.clock.tick(100);
|
||||||
expect(down).to.be.equal(true);
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
break;
|
});
|
||||||
case 2:
|
|
||||||
expect(keysym).to.be.equal(0xFFE3);
|
it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {
|
||||||
expect(code).to.be.equal('ControlLeft');
|
var kbd = new Keyboard(document);
|
||||||
expect(down).to.be.equal(true);
|
kbd.onkeyevent = sinon.spy();
|
||||||
break;
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
|
||||||
}
|
this.clock.tick(60);
|
||||||
};
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
|
||||||
// First the modifier combo
|
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
|
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
|
||||||
// Then one of the keys again
|
|
||||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
|
// Check that the timer is properly dead
|
||||||
expect(times_called).to.be.equal(3);
|
kbd.onkeyevent.reset();
|
||||||
|
this.clock.tick(100);
|
||||||
|
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass through single Alt', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass through single AltGr', function () {
|
||||||
|
var kbd = new Keyboard(document);
|
||||||
|
kbd.onkeyevent = sinon.spy();
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||||
|
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue