VNC-114 Add support for keyboard layout mapping via navigator.keyboard (#150)
Co-authored-by: matt <matt@email.com>
This commit is contained in:
parent
df061dc3d5
commit
699daf8a81
|
|
@ -18,7 +18,7 @@ import * as browser from "../util/browser.js";
|
||||||
|
|
||||||
const thresholdTime = 16;
|
const thresholdTime = 16;
|
||||||
export default class Keyboard {
|
export default class Keyboard {
|
||||||
constructor(screenInput, touchInput) {
|
constructor(screenInput, touchInput, keyboardInput) {
|
||||||
this._screenInput = screenInput;
|
this._screenInput = screenInput;
|
||||||
this._touchInput = touchInput;
|
this._touchInput = touchInput;
|
||||||
|
|
||||||
|
|
@ -29,6 +29,17 @@ export default class Keyboard {
|
||||||
this._rfbKeyQueue = [];
|
this._rfbKeyQueue = [];
|
||||||
this._lastSendTime = 0;
|
this._lastSendTime = 0;
|
||||||
|
|
||||||
|
this._layoutMap = null;
|
||||||
|
|
||||||
|
if (keyboardInput?.getLayoutMap) {
|
||||||
|
keyboardInput.getLayoutMap().then((map) => {
|
||||||
|
this._layoutMap = map;
|
||||||
|
Log.Debug("Loaded keyboard layout map");
|
||||||
|
}).catch((err) => {
|
||||||
|
Log.Error("Failed to get layout map:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// keep these here so we can refer to them later
|
// keep these here so we can refer to them later
|
||||||
this._eventHandlers = {
|
this._eventHandlers = {
|
||||||
'keyup': this._handleKeyUp.bind(this),
|
'keyup': this._handleKeyUp.bind(this),
|
||||||
|
|
@ -46,7 +57,6 @@ export default class Keyboard {
|
||||||
this._enableIME = false;
|
this._enableIME = false;
|
||||||
this._imeStarted = false;
|
this._imeStarted = false;
|
||||||
this._lastKeyboardInput = null;
|
this._lastKeyboardInput = null;
|
||||||
this._defaultKeyboardInputLen = 100;
|
|
||||||
this._keyboardInputReset();
|
this._keyboardInputReset();
|
||||||
this._translateShortcuts = true;
|
this._translateShortcuts = true;
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +244,7 @@ export default class Keyboard {
|
||||||
Log.Debug("Non-IME input change, sending new characters");
|
Log.Debug("Non-IME input change, sending new characters");
|
||||||
const newValue = e.data;
|
const newValue = e.data;
|
||||||
|
|
||||||
for (let i = 0; i < newValue.length; i++) {
|
for (let i = 0; i < newValue?.length; i++) {
|
||||||
this._sendKeyStroke(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified');
|
this._sendKeyStroke(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,6 +270,18 @@ export default class Keyboard {
|
||||||
this.clearKeysDown(e);
|
this.clearKeysDown(e);
|
||||||
Log.Debug("Key Down: " + e.keyCode + " code: " + code + " keysym: " + keysym);
|
Log.Debug("Key Down: " + e.keyCode + " code: " + code + " keysym: " + keysym);
|
||||||
|
|
||||||
|
if (e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
|
const layoutChar = this._getLayoutChar(code);
|
||||||
|
if (layoutChar?.length === 1) {
|
||||||
|
const charCode = layoutChar.charCodeAt(0);
|
||||||
|
const layoutKeysym = keysyms.lookup(charCode);
|
||||||
|
if (layoutKeysym) {
|
||||||
|
keysym = layoutKeysym;
|
||||||
|
Log.Debug(`Remapped keysym for Ctrl+: ${layoutChar} (${layoutKeysym})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Windows doesn't have a proper AltGr, but handles it using
|
// Windows doesn't have a proper AltGr, but handles it using
|
||||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||||
// so we need to merge those in to a single AltGr event. We
|
// so we need to merge those in to a single AltGr event. We
|
||||||
|
|
@ -291,8 +313,7 @@ export default class Keyboard {
|
||||||
// If it's a virtual keyboard then it should be
|
// If it's a virtual keyboard then it should be
|
||||||
// sufficient to just send press and release right
|
// sufficient to just send press and release right
|
||||||
// after each other
|
// after each other
|
||||||
this._sendKeyEvent(keysym, code, true);
|
this._sendKeyStroke(keysym, code);
|
||||||
this._sendKeyEvent(keysym, code, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
|
|
@ -346,8 +367,7 @@ export default class Keyboard {
|
||||||
// which toggles on each press, but not on release. So pretend
|
// which toggles on each press, but not on release. So pretend
|
||||||
// it was a quick press and release of the button.
|
// it was a quick press and release of the button.
|
||||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
this._sendKeyStroke(KeyTable.XK_Caps_Lock, 'CapsLock');
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -360,8 +380,7 @@ export default class Keyboard {
|
||||||
KeyTable.XK_Hiragana,
|
KeyTable.XK_Hiragana,
|
||||||
KeyTable.XK_Romaji ];
|
KeyTable.XK_Romaji ];
|
||||||
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
|
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
|
||||||
this._sendKeyEvent(keysym, code, true);
|
this._sendKeyStroke(keysym, code);
|
||||||
this._sendKeyEvent(keysym, code, false);
|
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -400,8 +419,7 @@ export default class Keyboard {
|
||||||
|
|
||||||
// See comment in _handleKeyDown()
|
// See comment in _handleKeyDown()
|
||||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
this._sendKeyStroke(KeyTable.XK_Caps_Lock, 'CapsLock');
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,6 +472,13 @@ export default class Keyboard {
|
||||||
return e.keyCode in imekeys;
|
return e.keyCode in imekeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getLayoutChar(code) {
|
||||||
|
if (this._layoutMap?.get(code)) {
|
||||||
|
return this._layoutMap.get(code);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== PUBLIC METHODS =====
|
// ===== PUBLIC METHODS =====
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
||||||
this._decoders[encodings.encodingUDP] = new UDPDecoder();
|
this._decoders[encodings.encodingUDP] = new UDPDecoder();
|
||||||
|
|
||||||
this._keyboard = new Keyboard(this._canvas, touchInput);
|
this._keyboard = new Keyboard(this._canvas, touchInput, navigator.keyboard);
|
||||||
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
||||||
|
|
||||||
this._gestures = new GestureHandler();
|
this._gestures = new GestureHandler();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue