From d2f534b17acfb671df1af471159d783a527ca1ae Mon Sep 17 00:00:00 2001 From: RblSb Date: Sun, 20 Sep 2026 01:56:30 +0300 Subject: [PATCH 1/2] Implement keyboard.show for html5 --- Backends/HTML5/kha/SystemImpl.hx | 42 +++++------ Backends/HTML5/kha/input/KeyboardImpl.hx | 91 ++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 Backends/HTML5/kha/input/KeyboardImpl.hx diff --git a/Backends/HTML5/kha/SystemImpl.hx b/Backends/HTML5/kha/SystemImpl.hx index eec951ee6..847ef2043 100644 --- a/Backends/HTML5/kha/SystemImpl.hx +++ b/Backends/HTML5/kha/SystemImpl.hx @@ -7,6 +7,7 @@ import js.html.ClipboardEvent; import js.html.DeviceMotionEvent; import js.html.DeviceOrientationEvent; import js.html.DragEvent; +import js.html.FocusEvent; import js.html.KeyboardEvent; import js.html.MouseEvent; import js.html.PointerEvent; @@ -20,7 +21,9 @@ import kha.graphics4.TextureFormat; import kha.input.Gamepad; import kha.input.KeyCode; import kha.input.Keyboard; +import kha.input.KeyboardImpl; import kha.input.Mouse; +import kha.input.MouseImpl; import kha.input.Sensor; import kha.input.Surface; import kha.js.AudioElementAudio; @@ -247,9 +250,9 @@ class SystemImpl { static function init2(defaultWidth: Int, defaultHeight: Int, ?backbufferFormat: TextureFormat) { #if !kha_no_keyboard - keyboard = new Keyboard(); + keyboard = new KeyboardImpl(); #end - mouse = new kha.input.MouseImpl(); + mouse = new MouseImpl(); surface = new Surface(); gamepads = new Array(); gamepadStates = new Array(); @@ -498,7 +501,6 @@ class SystemImpl { if (keyboard != null) { canvas.onkeydown = keyDown; canvas.onkeyup = keyUp; - canvas.onkeypress = keyPress; } canvas.onblur = onBlur; canvas.onfocus = onFocus; @@ -991,12 +993,18 @@ class SystemImpl { insideInputEvent = false; } - static function onBlur() { + static function onBlur(event: FocusEvent) { + final input = KeyboardImpl.input; + if (input != null && event.relatedTarget == input) + return; // System.pause(); System.background(); } - static function onFocus() { + static function onFocus(event: FocusEvent) { + final input = KeyboardImpl.input; + if (input != null && event.relatedTarget == input) + return; // System.resume(); System.foreground(); } @@ -1139,10 +1147,17 @@ class SystemImpl { // prevent key repeat if (event.repeat) { event.preventDefault(); + activeKeyEvent = null; + insideInputEvent = false; return; } - var keyCode = fixedKeyCode(event); + final keyCode = fixedKeyCode(event); keyboard.sendDownEvent(keyCode); + + if (event.key.length == 1) { + keyboard.sendPressEvent(event.key); + } + activeKeyEvent = null; insideInputEvent = false; } @@ -1209,21 +1224,6 @@ class SystemImpl { insideInputEvent = false; } - static function keyPress(event: KeyboardEvent): Void { - insideInputEvent = true; - activeKeyEvent = event; - unlockSound(); - - if (event.which == 0) - return; // for Firefox and Safari - preventDefaultKeyBehavior(event); - event.stopPropagation(); - keyboard.sendPressEvent(String.fromCharCode(event.which)); - - activeKeyEvent = null; - insideInputEvent = false; - } - public static function canSwitchFullscreen(): Bool { return Syntax.code("'fullscreenElement ' in document || 'mozFullScreenElement' in document || diff --git a/Backends/HTML5/kha/input/KeyboardImpl.hx b/Backends/HTML5/kha/input/KeyboardImpl.hx new file mode 100644 index 000000000..a577bef61 --- /dev/null +++ b/Backends/HTML5/kha/input/KeyboardImpl.hx @@ -0,0 +1,91 @@ +package kha.input; + +import js.Browser.document; +import js.html.InputElement; +import js.html.InputEvent; +import js.html.KeyboardEvent; + +using StringTools; + +class KeyboardImpl extends Keyboard { + static var virtualKeyboardId = "kha-virtual-keyboard"; + public static var input: InputElement; + + public function new() { + super(); + } + + override function show(): Void { + input = cast document.getElementById(virtualKeyboardId); + input ??= createKeyboardInputElement(); + document.body.appendChild(input); + input.focus(); + } + + static function createKeyboardInputElement(): InputElement { + final input = document.createInputElement(); + input.id = virtualKeyboardId; + input.style.position = "fixed"; + input.style.left = "0px"; + input.style.top = "0px"; + input.style.opacity = "0.0"; + input.style.pointerEvents = "none"; + input.type = "text"; + input.autocomplete = "off"; + input.spellcheck = false; + input.setAttribute("autocorrect", "off"); + input.setAttribute("autocapitalize", "off"); + + input.onkeydown = (e: KeyboardEvent) -> { + if (e.repeat) { + e.preventDefault(); + return; + } + // virtual keyboards do not report pressed chars in most cases + // so we only redirect non-printable keys to canvas there + if (e.key == null || e.key.length == 1) + return; + final keyEvent = copyKeyboardEventFrom(e); + SystemImpl.khanvas.dispatchEvent(keyEvent); + } + input.onkeyup = (e: KeyboardEvent) -> { + if (e.key == null || e.key.length == 1) + return; + final keyEvent = copyKeyboardEventFrom(e); + SystemImpl.khanvas.dispatchEvent(keyEvent); + } + input.oninput = (e: InputEvent) -> { + // IME-composition, key is not final yet + if (e.isComposing) + return; + final str: String = (e : Dynamic).data ?? return; + final kb = Keyboard.get() ?? return; + for (code in str) { + final char = String.fromCharCode(code); + kb.sendPressEvent(char); + } + input.value = ""; + } + return input; + } + + static function copyKeyboardEventFrom(e: KeyboardEvent): KeyboardEvent { + return new KeyboardEvent(e.type, { + key: e.key, + code: e.code, + keyCode: e.keyCode, + location: e.location, + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + bubbles: true, + cancelable: true + }); + } + + override function hide(): Void { + SystemImpl.khanvas.focus(); + input?.remove(); + } +} From 304656e0ad2721c07899ae3e17587f8a4469831f Mon Sep 17 00:00:00 2001 From: RblSb Date: Sun, 20 Sep 2026 05:00:29 +0300 Subject: [PATCH 2/2] Do not send chars on shortcut activations --- Backends/HTML5/kha/SystemImpl.hx | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Backends/HTML5/kha/SystemImpl.hx b/Backends/HTML5/kha/SystemImpl.hx index 847ef2043..667fd6509 100644 --- a/Backends/HTML5/kha/SystemImpl.hx +++ b/Backends/HTML5/kha/SystemImpl.hx @@ -62,6 +62,7 @@ class SystemImpl { static var firefox: Bool = false; public static var safari: Bool = false; public static var ie: Bool = false; + public static var macos: Bool = false; public static var insideInputEvent: Bool = false; static public var activeMouseEvent: Null; static public var activeWheelEvent: Null; @@ -107,6 +108,7 @@ class SystemImpl { firefox = isFirefox(); safari = isSafari(); ie = isIE(); + macos = isMacOS(); mobileAudioPlaying = !mobile && !chrome && !firefox; @@ -200,6 +202,11 @@ class SystemImpl { return false; } + static function isMacOS(): Bool { + var agent = js.Browser.navigator.userAgent; + return agent.contains("Mac") || isIOS(); + } + public static function setCanvas(canvas: CanvasElement): Void { khanvas = canvas; } @@ -1154,7 +1161,7 @@ class SystemImpl { final keyCode = fixedKeyCode(event); keyboard.sendDownEvent(keyCode); - if (event.key.length == 1) { + if (isPrintableText(event)) { keyboard.sendPressEvent(event.key); } @@ -1173,6 +1180,26 @@ class SystemImpl { } } + static function isPrintableText(event: KeyboardEvent): Bool { + if (event.key == null || event.key.length != 1) + return false; + + if (event.metaKey) + return false; + if (macos) { + if (event.ctrlKey) + return false; + } + else { + // detect ctrl+alt chars + if (event.getModifierState("AltGraph")) + return true; + if (event.ctrlKey || event.altKey) + return false; + } + return true; + } + static function preventDefaultKeyBehavior(event: KeyboardEvent): Void { switch (Keyboard.keyBehavior) { case Default: