From bd1bb2ed75f022bfb91c0f63b042a3f7c07eb8ab Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 10 Dec 2020 09:42:19 +0100 Subject: [PATCH 1/5] Use toggle keysym for Eisu key This matches how the key behaves on a Linux system. --- core/input/domkeytable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/input/domkeytable.js b/core/input/domkeytable.js index b84ad45d..040b0898 100644 --- a/core/input/domkeytable.js +++ b/core/input/domkeytable.js @@ -119,7 +119,7 @@ addStandard("WakeUp", KeyTable.XF86XK_WakeUp); // 2.8. IME and Composition Keys addStandard("AllCandidates", KeyTable.XK_MultipleCandidate); -addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle +addStandard("Alphanumeric", KeyTable.XK_Eisu_toggle); addStandard("CodeInput", KeyTable.XK_Codeinput); addStandard("Compose", KeyTable.XK_Multi_key); addStandard("Convert", KeyTable.XK_Henkan); From 3e55d5d71af51eaa1d960bbd67b744ec61df9aba Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 10 Dec 2020 09:43:08 +0100 Subject: [PATCH 2/5] Fix typo for ZenkakuHankaku key --- core/input/domkeytable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/input/domkeytable.js b/core/input/domkeytable.js index 040b0898..1b6baa67 100644 --- a/core/input/domkeytable.js +++ b/core/input/domkeytable.js @@ -147,7 +147,7 @@ addStandard("KanjiMode", KeyTable.XK_Kanji); addStandard("Katakana", KeyTable.XK_Katakana); addStandard("Romaji", KeyTable.XK_Romaji); addStandard("Zenkaku", KeyTable.XK_Zenkaku); -addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku); +addStandard("ZenkakuHankaku", KeyTable.XK_Zenkaku_Hankaku); // 2.9. General-Purpose Function Keys From 146258291ad546a18b05a6c3704a26b8c5d819d4 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 10 Dec 2020 09:43:49 +0100 Subject: [PATCH 3/5] Send combination keysyms for some Japanese keys Windows doesn't give us stable symbols for a bunch of Japanese IM keys, instead alternating between two symbols. This state is not synchronised with the IM running on the remote server so to have stable behaviour we have to collapse these multiple symbols in to a single keysym. --- core/input/util.js | 15 +++++++++++++++ tests/test.helper.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/core/input/util.js b/core/input/util.js index 182be7f7..58f84e55 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -157,6 +157,21 @@ export function getKeysym(evt) { } } + // Windows sends alternating symbols for some keys when using a + // Japanese layout. We have no way of synchronising with the IM + // running on the remote system, so we send some combined keysym + // instead and hope for the best. + if (browser.isWindows()) { + switch (key) { + case 'Zenkaku': + case 'Hankaku': + return KeyTable.XK_Zenkaku_Hankaku; + case 'Romaji': + case 'KanaMode': + return KeyTable.XK_Romaji; + } + } + return DOMKeyTable[key][location]; } diff --git a/tests/test.helper.js b/tests/test.helper.js index 5552ec48..ed65770e 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -186,5 +186,38 @@ describe('Helpers', function () { expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); }); }); + + describe('Japanese IM keys on Windows', function () { + let origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "Windows"; + }); + + afterEach(function () { + if (origNavigator !== undefined) { + Object.defineProperty(window, "navigator", origNavigator); + } + }); + + const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a, + 'Romaji': 0xff24, 'KanaMode': 0xff24 }; + for (let [key, keysym] of Object.entries(keys)) { + it(`should fake combined key for ${key} on Windows`, function () { + expect(KeyboardUtil.getKeysym({code: 'FakeIM', key: key})).to.be.equal(keysym); + }); + } + }); }); }); From 0cdf2962c0600727dbc8bc7bc15eb0cef19f18c0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 10 Dec 2020 10:01:04 +0100 Subject: [PATCH 4/5] Fake key releases for some Japanese IM keys Windows behaves very oddly for some Japanese IM keys in that it won't send a key release event when the key is released. In some keys it never sends the event, and in some cases it sends the release as the key is pressed the subsequent time. --- core/input/keyboard.js | 14 ++++++++++++++ tests/test.keyboard.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/core/input/keyboard.js b/core/input/keyboard.js index ed3b1bf1..48f65cf6 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -164,6 +164,20 @@ export default class Keyboard { return; } + // Windows doesn't send proper key releases for a bunch of + // Japanese IM keys so we have to fake the release right away + const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku, + KeyTable.XK_Eisu_toggle, + KeyTable.XK_Katakana, + KeyTable.XK_Hiragana, + KeyTable.XK_Romaji ]; + if (browser.isWindows() && jpBadKeys.includes(keysym)) { + this._sendKeyEvent(keysym, code, true); + this._sendKeyEvent(keysym, code, false); + stopEvent(e); + return; + } + stopEvent(e); // Possible start of AltGr sequence? (see above) diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 940769e6..f460eb3b 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -268,6 +268,47 @@ describe('Key Event Handling', function () { }); }); + describe('Japanese IM keys on Windows', function () { + let origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "Windows"; + }); + + afterEach(function () { + if (origNavigator !== undefined) { + Object.defineProperty(window, "navigator", origNavigator); + } + }); + + const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a, + 'Alphanumeric': 0xff30, 'Katakana': 0xff26, + 'Hiragana': 0xff25, 'Romaji': 0xff24, + 'KanaMode': 0xff24 }; + for (let [key, keysym] of Object.entries(keys)) { + it(`should fake key release for ${key} on Windows`, function () { + let kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key})); + + expect(kbd.onkeyevent).to.have.been.calledTwice; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, "FakeIM", true); + expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, "FakeIM", false); + }); + } + }); + describe('Escape AltGraph on Windows', function () { let origNavigator; beforeEach(function () { From 4ae9d3e75a34403f44fda1bd061d9a20dd3ff76c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 10 Dec 2020 10:02:31 +0100 Subject: [PATCH 5/5] Remove some unnecessary use of done argument --- tests/test.keyboard.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index f460eb3b..381cd308 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -219,7 +219,7 @@ describe('Key Event Handling', function () { } }); - it('should toggle caps lock on key press on iOS', function (done) { + it('should toggle caps lock on key press on iOS', function () { window.navigator.platform = "iPad"; const kbd = new Keyboard(document); kbd.onkeyevent = sinon.spy(); @@ -228,10 +228,9 @@ describe('Key Event Handling', function () { expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); - done(); }); - it('should toggle caps lock on key press on mac', function (done) { + it('should toggle caps lock on key press on mac', function () { window.navigator.platform = "Mac"; const kbd = new Keyboard(document); kbd.onkeyevent = sinon.spy(); @@ -240,10 +239,9 @@ describe('Key Event Handling', function () { expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); - done(); }); - it('should toggle caps lock on key release on iOS', function (done) { + it('should toggle caps lock on key release on iOS', function () { window.navigator.platform = "iPad"; const kbd = new Keyboard(document); kbd.onkeyevent = sinon.spy(); @@ -252,10 +250,9 @@ describe('Key Event Handling', function () { expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); - done(); }); - it('should toggle caps lock on key release on mac', function (done) { + it('should toggle caps lock on key release on mac', function () { window.navigator.platform = "Mac"; const kbd = new Keyboard(document); kbd.onkeyevent = sinon.spy(); @@ -264,7 +261,6 @@ describe('Key Event Handling', function () { expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); - done(); }); });