From b01165c1f8fc15415ea30b5e1c00278fb230cf13 Mon Sep 17 00:00:00 2001 From: runarberg Date: Wed, 1 Jun 2016 17:06:57 +0000 Subject: [PATCH] Fix third level shifts for Mac OS and windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISO third level keys were not working. That prevented some inputting important characters (like pipe `|` and caret `^`) on some keyboard layouts. Instead of parsing the user-agent string to find the users os, we now look into the `platform` attribute of the `navigator` object. Removed the hijacking of the command key `⌘` on Mac OS as the `Alt` key on other systems. --- src/xterm.js | 54 ++++++++++++++++++++--- test/test.js | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 7 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 2ba64e26..7f01e2a4 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -659,12 +659,27 @@ * Parse User-Agent */ if (this.context.navigator && this.context.navigator.userAgent) { - this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac'); - this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad'); - this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone'); this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE'); } + /* + * Find the users platform. We use this to interpret the meta key + * and ISO third level shifts. + * http://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today + */ + if (this.context.navigator && this.context.navigator.platform) { + this.isMac = contains( + this.context.navigator.platform, + ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'] + ); + this.isIpad = this.context.navigator.platform === 'iPad'; + this.isIphone = this.context.navigator.platform === 'iPhone'; + this.isMSWindows = contains( + this.context.navigator.platform, + ['Windows', 'Win16', 'Win32', 'WinCE'] + ); + } + /* * Create main element container */ @@ -2337,12 +2352,16 @@ return this.cancel(ev); } - if (result.cancel) { + if (isThirdLevelShift(this, ev)) { + return true; + } + + if (result.cancel ) { // The event is canceled at the end already, is this necessary? this.cancel(ev, true); } - if (!result.key || (this.isMac && ev.metaKey)) { + if (!result.key) { return true; } @@ -2532,7 +2551,8 @@ // ^] - group sep result.key = String.fromCharCode(29); } - } else if ((!this.isMac && ev.altKey) || (this.isMac && ev.metaKey)) { + } else if (!this.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + // On Mac this is a third level shift. Use instead. if (ev.keyCode >= 65 && ev.keyCode <= 90) { result.key = '\x1b' + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode === 192) { @@ -2571,7 +2591,9 @@ return false; } - if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) { + if (!key || ( + (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) + )) { return false; } @@ -4398,6 +4420,15 @@ * Helpers */ + function contains(el, arr) { + for (var i = 0; i < arr.length; i += 1) { + if (el === arr[i]) { + return true; + } + } + return false; + } + function on(el, type, handler, capture) { if (!Array.isArray(el)) { el = [el]; @@ -4454,6 +4485,15 @@ return -1; } + function isThirdLevelShift(term, ev) { + var thirdLevelKey = + (term.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (term.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + + // Don't invoke for arrows, pageDown, home, backspace, etc. + return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); + } + function isWide(ch) { if (ch <= '\uff00') return false; return (ch >= '\uff01' && ch <= '\uffbe') diff --git a/test/test.js b/test/test.js index ffbc285f..6f7a0791 100644 --- a/test/test.js +++ b/test/test.js @@ -68,4 +68,126 @@ describe('xterm.js', function() { assert.equal(processedText.indexOf(nonBreakingSpace), -1); }); }); + + describe('Third level shift', function() { + var ev = { + preventDefault: function() {}, + stopPropagation: function() {} + }; + + beforeEach(function() { + xterm.handler = function() {}; + xterm.showCursor = function() {}; + xterm.clearSelection = function() {}; + }) + + describe('On Mac OS', function() { + beforeEach(function() { + xterm.isMac = true; + }); + + it('should not interfere with the alt key on keyDown', function() { + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 81 })), + true + ); + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 192 })), + true + ); + }); + + it('should interefere with the alt + arrow keys', function() { + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 37 })), + false + ); + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 39 })), + false + ); + }); + + it('should emit key with alt + key on keyPress', function(done) { + var keys = ['@', '@', '\\', '\\', '|', '|']; + + xterm.on('keypress', function(key) { + if (key) { + var index = keys.indexOf(key); + assert(index !== -1, "Emitted wrong key: " + key); + keys.splice(index, 1); + } + if (keys.length === 0) done(); + }); + + xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 64 })); // @ + // Firefox + xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 64, keyCode: 0 })); + xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 92 })); // \ + xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 92, keyCode: 0 })); + xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 124 })); // | + xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 124, keyCode: 0 })); + }); + }); + + describe('On MS Windows', function() { + beforeEach(function() { + xterm.isMSWindows = true; + }); + + it('should not interfere with the alt + ctrl key on keyDown', function() { + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 81 })), + true + ); + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 192 })), + true + ); + }); + + it('should interefere with the alt + ctrl + arrow keys', function() { + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 37 })), + false + ); + assert.equal( + xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 39 })), + false + ); + }); + + it('should emit key with alt + ctrl + key on keyPress', function(done) { + var keys = ['@', '@', '\\', '\\', '|', '|']; + + xterm.on('keypress', function(key) { + if (key) { + var index = keys.indexOf(key); + assert(index !== -1, "Emitted wrong key: " + key); + keys.splice(index, 1); + } + if (keys.length === 0) done(); + }); + + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 64 }) + ); // @ + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 64, keyCode: 0 }) + ); + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 92 }) + ); // \ + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 92, keyCode: 0 }) + ); + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 124 }) + ); // | + xterm.keyPress( + Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 124, keyCode: 0 }) + ); + }); + }); + }); });