How can my javascript program determine which modifiers were pressed along with a keydown
event in Chrome on Linux?
I tried this code:
document.addEventListener('keydown', function(e)
{
console.log("KEY DOWN: key=" + e.key + ", code=" + e.code
+ ", meta=" + e.metaKey + ", alt=" + e.altKey);
// stop propagation, except don't kill F12.
if (e.code != 'F12')
{
e.preventDefault(); // Windows and Firefox
e.stopPropagation();
return false;
} // else: if propagating: Windows wants undefined result.
});
And, while it works in Firefox:
// output when I type M-q A-q in Firefox Quantum 67.0.4 (64-bit)
KEY DOWN: key=Meta, code=OSLeft, meta=true, alt=false
KEY DOWN: key=q, code=KeyQ, meta=true, alt=false
KEY DOWN: key=Alt, code=AltLeft, meta=false, alt=true
KEY DOWN: key=q, code=KeyQ, meta=false, alt=true
Linux Chrome seems to be modifier-challenged:
// output when I type M-q A-q in Linux Chrome Version 66.0.3359.181 (Official Build) (64-bit)
KEY DOWN: key=Meta, code=MetaLeft, meta=false, alt=true
KEY DOWN: key=q, code=KeyQ, meta=false, alt=true
KEY DOWN: key=Alt, code=AltLeft, meta=false, alt=true
KEY DOWN: key=q, code=KeyQ, meta=false, alt=false
Evidently, the metaKey
and altKey
fields of the KeyboardEvent
are not cross-browser. Or I didn't turn on Chrome's "modifier keys" feature (how do I do that?). Or something.
The above code works just fine in both Edge and Chrome in Windows. (Windows will catch the Windows key if it's the only modifier, but your application can perfectly well catch combinations like Ctrl-Meta-x). For example, the code above has the same output in both Edge and Chrome in Windows when I type C-S-M-Q A-Q:
KEY DOWN: key=Control, code=ControlLeft, meta=false, alt=false
KEY DOWN: key=Shift, code=ShiftLeft, meta=false, alt=false
KEY DOWN: key=Meta, code=MetaLeft, meta=true, alt=false
KEY DOWN: key=Q, code=KeyQ, meta=true, alt=false
KEY DOWN: key=Alt, code=AltLeft, meta=false, alt=true
KEY DOWN: key=q, code=KeyQ, meta=false, alt=true
(note one small difference between Chrome and Edge: Edge has code=undefined
. However there is still enough information in the KeyboardEvent
to determine which key was pressed, except possibly Windows has some issue with combining NumLock + Shift + Keypad number).
The last time I used a Mac, it supported both the Meta and Alt keys, so the question probably applies to Mac too.
In Linux, be sure your computer has working Meta and Alt keys, as some popular distros are broken out of the box. If it's set up correctly, xmodmap
should say:
shift Shift_L (0x32), Shift_R (0x3e)
lock Caps_Lock (0x42)
control Control_L (0x25), Control_R (0x69)
mod1 Meta_L (0x85), Meta_R (0x86)
mod2 Num_Lock (0x4d)
mod3 Alt_L (0x40), Alt_R (0x6c)
mod4
mod5 ISO_Level3_Shift (0x5c), Mode_switch (0xcb)
because programs interpret mod1
as Meta and mod3
as Alt. (Use the xev
utility to get the key codes for your keyboard.) With settings as shown above, Firefox, Emacs, and other programs can catch all the modifier keys correctly.
What is the correct method to determine the modifiers that were pressed along with a keydown
event in Linux Chrome?
I came up with something that works consistently across Chrome/Firefox/Edge:
let modifier_challenged = /Google/.test(navigator.vendor);
let last_mod = {Meta: false, Alt: false};
function keydown(e)
{
if (modifier_challenged)
{
last_mod[e.key] = true;
e = {key: e.key,
code: e.code,
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey};
e.metaKey = last_mod['Meta'];
e.altKey = last_mod['Alt'];
}
console.log("KEY DOWN: key=" + e.key + ", code=" + e.code
+ ", meta=" + e.metaKey + ", alt=" + e.altKey);
}
function keyup(e)
{
if (modifier_challenged)
last_mod[e.key] = false;
}
document.addEventListener('keydown', keydown);
document.addEventListener('keyup', keyup);
But I would prefer an answer that also works if the modifier was most recently pressed or released outside the window (before keyboard focus was moved back into the window). Maybe not possible?
Update
Well, the above code is fine if you only use one modifier at a time. But if you want to combine many modifiers, Chrome still has a problem... It changes the key
value from Alt to Meta if you are also holding the Shift key. So we have to use:
let modif_codes = /^Control|^Shift|^Meta|^Alt|^OS/;
function set_last_mod(e, value)
{
if (!modif_codes.test(e.key))
return;
let key_sym = e.code.replace(/([A-Z])/g, " $1").split(" ")[1];
if (key_sym != e.key)
{
console.log("Warning: code(" + e.code
+ ") does not match key(" + e.key
+ "). Using " + key_sym + ".");
}
last_mod[key_sym] = value;
}
in place of last_mod[e.key] = value
.