fix(input): keys are physical positions on every layout; Input.key_label names them
The Windows runtime keyed the held set by the character MapVirtualKeyA gave a virtual key, so a game's WASD belonged to whatever the active layout put there, and with an input method on every letter arrived as VK_PROCESSKEY. The typing block is now read from the scancode (the arrows, numpad and F-keys still by virtual key); macOS reads keyCode the same way. Input.key_label(key) names a key in the player's own layout (Windows) or as its US character elsewhere. Verified on Windows with SendInput into a live window: VK_Z carrying W's scancode holds 'w', VK_PROCESSKEY carrying A's holds 'a', Caps Lock changes nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2a2f463d5b
commit
619476ffed
14 changed files with 60552 additions and 60168 deletions
|
|
@ -333,11 +333,35 @@ ret:
|
|||
; keyDown switch: arrows -> WASD, return, escape -> 27, else the first character).
|
||||
; #88: escape delivers key 27 (left to the game), not 'q' — so the held-key set
|
||||
; stays consistent with @W_key and a shipped game owns Esc (pause).
|
||||
; A key in the typing block is its PHYSICAL position, named for what it types on a US
|
||||
; layout (win32.ll keys its held set the same way): keyCode is already a position, so the
|
||||
; kVK_ANSI_* codes map straight through and AZERTY's Z-labelled key is 'w' like everywhere
|
||||
; else. Anything outside the table falls back to the character it types.
|
||||
@C_kc2key = internal constant [51 x i8] [i8 97, i8 115, i8 100, i8 102, i8 104, i8 103, i8 122, i8 120, i8 99, i8 118, i8 0, i8 98, i8 113, i8 119, i8 101, i8 114, i8 121, i8 116, i8 49, i8 50, i8 51, i8 52, i8 54, i8 53, i8 61, i8 57, i8 55, i8 45, i8 56, i8 48, i8 93, i8 111, i8 117, i8 91, i8 105, i8 112, i8 0, i8 108, i8 106, i8 39, i8 107, i8 59, i8 92, i8 44, i8 47, i8 110, i8 109, i8 46, i8 0, i8 0, i8 96]
|
||||
|
||||
; what the active layout types on a position: not read on macOS yet, so the caller names
|
||||
; the key after its US position
|
||||
define i32 @win_key_char(i32 %k) {
|
||||
entry:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @ev_keyval(ptr %ev) {
|
||||
entry:
|
||||
%sel_kc = call ptr @sel_registerName(ptr @.s_keycd)
|
||||
%kc = call i16 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_kc)
|
||||
%kc32 = zext i16 %kc to i32
|
||||
%inpos = icmp ult i32 %kc32, 51
|
||||
br i1 %inpos, label %bypos, label %special
|
||||
bypos:
|
||||
%pp = getelementptr [51 x i8], ptr @C_kc2key, i64 0, i32 %kc32
|
||||
%p8 = load i8, ptr %pp
|
||||
%pos = zext i8 %p8 to i32
|
||||
%named = icmp ne i32 %pos, 0
|
||||
br i1 %named, label %haspos, label %special
|
||||
haspos:
|
||||
ret i32 %pos
|
||||
special:
|
||||
switch i32 %kc32, label %chars [
|
||||
i32 126, label %vw
|
||||
i32 125, label %vs
|
||||
|
|
|
|||
|
|
@ -372,6 +372,46 @@ function input_key_down(k: int) -> bool { in_init(); return in_bit_get(in_h
|
|||
function input_key_pressed(k: int) -> bool { in_init(); return in_bit_get(in_held, k) and (not in_bit_get(in_prev, k)) }
|
||||
function input_key_released(k: int) -> bool { in_init(); return (not in_bit_get(in_held, k)) and in_bit_get(in_prev, k) }
|
||||
|
||||
# The name to show a player for key code `k`. A letter, digit or punctuation code is a
|
||||
# PHYSICAL key - the one that types it on a US layout - so the name is what the player's
|
||||
# own layout types there: 'w' reads "W" on QWERTY and "Z" on AZERTY, and a binding
|
||||
# shown as "W" is never a key the player cannot find. Named keys and the arrows get words.
|
||||
# Headless, and on a platform that cannot ask the layout, it is the US character.
|
||||
function input_key_label(k: int) -> string {
|
||||
if k == 32 { return "Space" }
|
||||
if k == 10 { return "Enter" }
|
||||
if k == 27 { return "Esc" }
|
||||
if k == 9 { return "Tab" }
|
||||
if k == 8 { return "Backspace" }
|
||||
if k == 16 { return "Shift" }
|
||||
if k == 17 { return "Ctrl" }
|
||||
if k == 18 { return "Alt" }
|
||||
if k == 128 { return "Up" }
|
||||
if k == 129 { return "Down" }
|
||||
if k == 130 { return "Left" }
|
||||
if k == 131 { return "Right" }
|
||||
var c = 0
|
||||
if is_windowed() { c = win_key_char(k) }
|
||||
if c <= 0 and k > 32 and k < 127 {
|
||||
c = k
|
||||
if c >= 97 and c <= 122 { c = c - 32 }
|
||||
}
|
||||
if c <= 0 { return "" }
|
||||
return in_utf8(c)
|
||||
}
|
||||
# one code point as a UTF-8 string
|
||||
function in_utf8(c: int) -> string {
|
||||
let out = bytes(5)
|
||||
var n = 0
|
||||
if c < 128 { out[0] = c; n = 1 }
|
||||
else if c < 2048 { out[0] = 192 | (c >> 6); out[1] = 128 | (c & 63); n = 2 }
|
||||
else if c < 65536 { out[0] = 224 | (c >> 12); out[1] = 128 | ((c >> 6) & 63); out[2] = 128 | (c & 63); n = 3 }
|
||||
else { out[0] = 240 | (c >> 18); out[1] = 128 | ((c >> 12) & 63); out[2] = 128 | ((c >> 6) & 63); out[3] = 128 | (c & 63); n = 4 }
|
||||
out[n] = 0
|
||||
let s: string = out
|
||||
return s
|
||||
}
|
||||
|
||||
# inject a held key (AI, tutorial, testing, network) — persists until released.
|
||||
function input_press(k: int) -> void { in_init(); in_bit_set(in_sim, k, true) }
|
||||
function input_release(k: int) -> void { in_init(); in_bit_set(in_sim, k, false) }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,12 @@
|
|||
; held keys a 256-bit set of Ludic key codes: letters lowercase, digits and
|
||||
; punctuation as their unshifted character, Space 32, Enter 10,
|
||||
; Esc 27, Backspace 8, Tab 9, Shift 16, Ctrl 17, Alt 18, and the
|
||||
; arrows at 128 129 130 131 (up down left right)
|
||||
; arrows at 128 129 130 131 (up down left right). A key in the
|
||||
; typing block is its PHYSICAL position, named for what it types
|
||||
; on a US layout: 'w' is the key right of Tab's row start on every
|
||||
; layout, language, input method and Caps Lock state
|
||||
; key char win_key_char(code): what the active layout types on that key,
|
||||
; upper case, 0 when it types nothing or the code is not a position
|
||||
; frame key the last key pressed this frame, arrows reported as w s a d
|
||||
; mouse framebuffer pixels (client pixels / scale), top-left origin;
|
||||
; buttons bit0 left, bit1 right; wheel in notches, up positive
|
||||
|
|
@ -27,8 +32,13 @@
|
|||
; backing scale is 1, and a 1280x720 window is 1280x720 pixels on any display.
|
||||
;
|
||||
; Keys are read from the message queue before dispatch rather than from WM_CHAR:
|
||||
; TranslateMessage is never called, so no character messages are produced, and a
|
||||
; key's character comes from MapVirtualKeyA. The system keys (Alt, F10) are never
|
||||
; TranslateMessage is never called, so no character messages are produced. The held
|
||||
; set is keyed by the scancode in lParam, not by the virtual key: a virtual key is
|
||||
; the layout's idea of the key, so on AZERTY the key where W is printed on a US
|
||||
; board arrives as VK_Z, and an input method turns every letter into VK_PROCESSKEY
|
||||
; (229) - WASD simply stopped existing for those players. Keys outside the table
|
||||
; (arrows, the numpad, F-keys, the ISO key) still go by virtual key, and the frame
|
||||
; key keeps the layout's character, because that one is typing. The system keys (Alt, F10) are never
|
||||
; handed to DefWindowProc, whose menu loop would stall the game's frame loop -
|
||||
; Alt+F4 is honoured here instead.
|
||||
; ============================================================================
|
||||
|
|
@ -56,6 +66,8 @@ declare i32 @IsWindow(ptr)
|
|||
declare ptr @SetCapture(ptr)
|
||||
declare i32 @ReleaseCapture()
|
||||
declare i32 @MapVirtualKeyA(i32, i32)
|
||||
declare i32 @MapVirtualKeyW(i32, i32)
|
||||
declare ptr @CharUpperW(ptr)
|
||||
declare i16 @GetKeyState(i32)
|
||||
declare i32 @RegisterRawInputDevices(ptr, i32, i32)
|
||||
declare i32 @GetRawInputData(ptr, i32, ptr, ptr, i32)
|
||||
|
|
@ -306,9 +318,32 @@ entry:
|
|||
ret i32 %c7
|
||||
}
|
||||
|
||||
; the held-set code of a virtual key (cocoa.ll's ev_keyval)
|
||||
define internal i32 @w_keyval(i64 %vk64) {
|
||||
; The typing block by position: a set-1 scancode (non-extended) to the key code it is on a
|
||||
; US layout, and back. 0 is a key the table does not name.
|
||||
@W_sc2key = internal constant [128 x i8] [i8 0, i8 0, i8 49, i8 50, i8 51, i8 52, i8 53, i8 54, i8 55, i8 56, i8 57, i8 48, i8 45, i8 61, i8 0, i8 0, i8 113, i8 119, i8 101, i8 114, i8 116, i8 121, i8 117, i8 105, i8 111, i8 112, i8 91, i8 93, i8 0, i8 0, i8 97, i8 115, i8 100, i8 102, i8 103, i8 104, i8 106, i8 107, i8 108, i8 59, i8 39, i8 96, i8 0, i8 92, i8 122, i8 120, i8 99, i8 118, i8 98, i8 110, i8 109, i8 44, i8 46, i8 47, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0]
|
||||
@W_key2sc = internal constant [128 x i8] [i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 40, i8 0, i8 0, i8 0, i8 0, i8 51, i8 12, i8 52, i8 53, i8 11, i8 2, i8 3, i8 4, i8 5, i8 6, i8 7, i8 8, i8 9, i8 10, i8 0, i8 39, i8 0, i8 13, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 0, i8 26, i8 43, i8 27, i8 0, i8 0, i8 41, i8 30, i8 48, i8 46, i8 32, i8 18, i8 33, i8 34, i8 35, i8 23, i8 36, i8 37, i8 38, i8 50, i8 49, i8 24, i8 25, i8 16, i8 19, i8 31, i8 20, i8 22, i8 47, i8 17, i8 45, i8 21, i8 44, i8 0, i8 0, i8 0, i8 0, i8 0]
|
||||
|
||||
; the held-set code of a key message: the scancode's position when the table names it,
|
||||
; the virtual key otherwise (cocoa.ll's ev_keyval)
|
||||
define internal i32 @w_keyval(i64 %vk64, i64 %lp) {
|
||||
entry:
|
||||
%sc64 = lshr i64 %lp, 16
|
||||
%sc = and i64 %sc64, 255
|
||||
%ext64 = lshr i64 %lp, 24
|
||||
%ext = and i64 %ext64, 1
|
||||
%plain = icmp eq i64 %ext, 0
|
||||
%inrange = icmp ult i64 %sc, 128
|
||||
%lookup = and i1 %plain, %inrange
|
||||
br i1 %lookup, label %bypos, label %byvk
|
||||
bypos:
|
||||
%sp = getelementptr [128 x i8], ptr @W_sc2key, i64 0, i64 %sc
|
||||
%s8 = load i8, ptr %sp
|
||||
%pos = zext i8 %s8 to i32
|
||||
%named = icmp ne i32 %pos, 0
|
||||
br i1 %named, label %haspos, label %byvk
|
||||
haspos:
|
||||
ret i32 %pos
|
||||
byvk:
|
||||
%vk = trunc i64 %vk64 to i32
|
||||
switch i32 %vk, label %chars [
|
||||
i32 38, label %up
|
||||
|
|
@ -347,6 +382,39 @@ chars:
|
|||
ret i32 %f
|
||||
}
|
||||
|
||||
; What the active layout types on the key at held-set code %k, upper case: 'W' for 'w' on a
|
||||
; US layout, 'Z' on AZERTY, 0x131 for 'i' on Turkish F... 0 when %k is not a position in the
|
||||
; table or the key types nothing. A dead key reports its accent; the top bit that marks it is
|
||||
; dropped.
|
||||
define i32 @win_key_char(i32 %k) {
|
||||
entry:
|
||||
%inrange = icmp ult i32 %k, 128
|
||||
br i1 %inrange, label %look, label %none
|
||||
look:
|
||||
%kp = getelementptr [128 x i8], ptr @W_key2sc, i64 0, i32 %k
|
||||
%s8 = load i8, ptr %kp
|
||||
%sc = zext i8 %s8 to i32
|
||||
%has = icmp ne i32 %sc, 0
|
||||
br i1 %has, label %map, label %none
|
||||
map:
|
||||
%vk = call i32 @MapVirtualKeyW(i32 %sc, i32 1) ; MAPVK_VSC_TO_VK
|
||||
%c = call i32 @MapVirtualKeyW(i32 %vk, i32 2) ; MAPVK_VK_TO_CHAR
|
||||
%c16 = and i32 %c, 65535
|
||||
%types = icmp ne i32 %c16, 0
|
||||
br i1 %types, label %up, label %none
|
||||
up:
|
||||
; CharUpperW takes a single character in the low word of its pointer argument
|
||||
%cz = zext i32 %c16 to i64
|
||||
%cp = inttoptr i64 %cz to ptr
|
||||
%uq = call ptr @CharUpperW(ptr %cp)
|
||||
%ui = ptrtoint ptr %uq to i64
|
||||
%u16 = and i64 %ui, 65535
|
||||
%u = trunc i64 %u16 to i32
|
||||
ret i32 %u
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; the frame key of a virtual key (cocoa.ll's win_poll): arrows as wasd, a letter in the
|
||||
; case Shift gives it, and 0 for a key that types nothing
|
||||
define internal i32 @w_framekey(i64 %vk64) {
|
||||
|
|
@ -422,7 +490,7 @@ handle:
|
|||
i32 255, label %raw ; WM_INPUT
|
||||
]
|
||||
kdown:
|
||||
%hv = call i32 @w_keyval(i64 %wp)
|
||||
%hv = call i32 @w_keyval(i64 %wp, i64 %lp)
|
||||
call void @win_held_bit(i32 %hv, i32 1)
|
||||
%isf4 = icmp eq i64 %wp, 115 ; VK_F4
|
||||
%issys = icmp eq i32 %m, 260
|
||||
|
|
@ -439,7 +507,7 @@ setfk:
|
|||
store i32 %fk, ptr @W_key
|
||||
br label %pump
|
||||
kup:
|
||||
%uv = call i32 @w_keyval(i64 %wp)
|
||||
%uv = call i32 @w_keyval(i64 %wp, i64 %lp)
|
||||
call void @win_held_bit(i32 %uv, i32 0)
|
||||
br label %pump
|
||||
ldown:
|
||||
|
|
|
|||
|
|
@ -303,6 +303,9 @@ async function start({ wasm, canvas, manifest = null, files = {}, onExit = null,
|
|||
win_mouse: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 4; i++) host.u32[b + i] = 0; },
|
||||
win_pad: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 24; i++) host.u32[b + i] = 0; },
|
||||
win_touch: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 24; i++) host.u32[b + i] = 0; },
|
||||
// what the layout types on a key position: not read in the browser, so Input.key_label
|
||||
// names the key after its US position
|
||||
win_key_char: (k) => 0,
|
||||
};
|
||||
|
||||
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasm), { env });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue