From bd40a662777f5d6e4fcee812baebdd170f00bbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benoi=CC=82t=20Rouleau?= Date: Fri, 30 Oct 2026 13:08:44 -0400 Subject: [PATCH] js: report Cmd, and release keys the browser never says came up Browsers name the Cmd and Windows keys `MetaLeft` and `MetaRight`, and gl.js only knew their old names, `OSLeft` and `OSRight`, which Firefox dropped in version 118, so `KeyCode::LeftSuper` never went down and key events never held `KeyMods::logo`: Cmd+Z reached an app as Z. Both names now map to Super, and `metaKey` sets the logo modifier. macOS browsers send no keyup for a key released while Cmd is down, so after Cmd+Z, Z stayed down until it was pressed again. Every key but the modifiers now comes up with Cmd. A canvas that loses focus gets none of its keys' keyups either, as when Cmd+Tab switches apps, so they all come up with its focus. Co-Authored-By: Claude Opus 5.5 --- js/gl.js | 74 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/js/gl.js b/js/gl.js index df0116ac9..ac1f40683 100644 --- a/js/gl.js +++ b/js/gl.js @@ -576,17 +576,50 @@ function into_sapp_keycode(key_code) { case "ShiftLeft": return 340; case "ControlLeft": return 341; case "AltLeft": return 342; - case "OSLeft": return 343; + // Cmd and the Windows key: "Meta" in every browser, and "OS" in + // Firefox before version 118. + case "MetaLeft": case "OSLeft": return 343; case "ShiftRight": return 344; case "ControlRight": return 345; case "AltRight": return 346; - case "OSRight": return 347; + case "MetaRight": case "OSRight": return 347; case "ContextMenu": return 348; } console.log("Unsupported keyboard key: ", key_code) } +function into_sapp_modifiers(event) { + var modifiers = 0; + if (event.ctrlKey) { + modifiers |= SAPP_MODIFIER_CTRL; + } + if (event.shiftKey) { + modifiers |= SAPP_MODIFIER_SHIFT; + } + if (event.altKey) { + modifiers |= SAPP_MODIFIER_ALT; + } + if (event.metaKey) { + modifiers |= SAPP_MODIFIER_SUPER; + } + return modifiers; +} + +// The keys the app was told went down, and not yet that they came up. +var keys_down = new Set(); + +// Tell the app the keys `released` picks came up, as browsers don't +// always say. +function release_keys(released, modifiers) { + keys_down.forEach(function (key) { + if (released(key)) { + keys_down.delete(key); + wasm_exports.key_up(key, modifiers); + } + }); +} + function dpi_scale() { if (high_dpi) { return window.devicePixelRatio || 1.0; @@ -1228,17 +1261,8 @@ var importObject = { break; } - var modifiers = 0; - if (event.ctrlKey) { - modifiers |= SAPP_MODIFIER_CTRL; - } - if (event.shiftKey) { - modifiers |= SAPP_MODIFIER_SHIFT; - } - if (event.altKey) { - modifiers |= SAPP_MODIFIER_ALT; - } - wasm_exports.key_down(sapp_key_code, modifiers, event.repeat); + keys_down.add(sapp_key_code); + wasm_exports.key_down(sapp_key_code, into_sapp_modifiers(event), event.repeat); // for "space", "quote", and "slash" preventDefault will prevent // key_press event, so send it here instead if (sapp_key_code == 32 || sapp_key_code == 39 || sapp_key_code == 47) { @@ -1247,20 +1271,22 @@ var importObject = { }; canvas.onkeyup = function (event) { var sapp_key_code = into_sapp_keycode(event.code); + var modifiers = into_sapp_modifiers(event); - var modifiers = 0; - if (event.ctrlKey) { - modifiers |= SAPP_MODIFIER_CTRL; - } - if (event.shiftKey) { - modifiers |= SAPP_MODIFIER_SHIFT; - } - if (event.altKey) { - modifiers |= SAPP_MODIFIER_ALT; - } - + keys_down.delete(sapp_key_code); wasm_exports.key_up(sapp_key_code, modifiers); + // macOS browsers send no keyup for a key released while Cmd + // is down, so every key but the modifiers (340 to 347), + // which do send one, comes up with Cmd. + if (sapp_key_code == 343 || sapp_key_code == 347) { + release_keys(function (key) { return key < 340 || key > 347; }, modifiers); + } }; + // Once the canvas loses focus, as when Cmd+Tab switches apps or + // Ctrl+Tab switches tabs, its keys come up elsewhere. + canvas.addEventListener("blur", function () { + release_keys(function () { return true; }, 0); + }); canvas.onkeypress = function (event) { var sapp_key_code = into_sapp_keycode(event.code);