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
16
changes/physical-keys.md
Normal file
16
changes/physical-keys.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**Keys are physical positions on Windows and macOS, whatever the layout** - `Input.key_down('w')`
|
||||||
|
is the key above S on every keyboard.
|
||||||
|
|
||||||
|
- **Windows** keyed the held set by the character the active layout gave a virtual key, so
|
||||||
|
a game's WASD belonged to whatever the layout put there: on AZERTY W and A were other
|
||||||
|
keys, and with an input method on (Chinese, Japanese, Korean) every letter arrived as
|
||||||
|
VK_PROCESSKEY and no letter key worked at all. The typing block is now read from the
|
||||||
|
scancode; the arrows, the numpad and the F-keys still go by virtual key, and the frame
|
||||||
|
key (`Input.key`, typing) keeps the layout's character.
|
||||||
|
- **macOS** did the same through `charactersIgnoringModifiers`; it now reads `keyCode`.
|
||||||
|
- **`Input.key_label(key)`** names a key for the player in their own layout - `'w'` shows
|
||||||
|
as "W" on QWERTY and "Z" on AZERTY - so a game that shows its bindings never shows a
|
||||||
|
key the player cannot find. Windows reads the layout; headless, macOS and the browser
|
||||||
|
give the US character.
|
||||||
26
docs/language/input/input-key_label.md
Normal file
26
docs/language/input/input-key_label.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
id: input-key_label
|
||||||
|
name: Input.key_label
|
||||||
|
category: input
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Input.key_label
|
||||||
|
sig: Input.key_label(key) -> string
|
||||||
|
tip: The name to show a player for a key - in their own keyboard layout.
|
||||||
|
order: 11
|
||||||
|
ns: Input
|
||||||
|
member: key_label
|
||||||
|
---
|
||||||
|
|
||||||
|
Returns the name to show for <code>key</code>. The letter, digit and punctuation codes that <a href="input-key_down.html"><code>Input.key_down</code></a> reads are <em>physical</em> keys, named for what they type on a US layout: <code>'w'</code> is the key above <code>'s'</code> on every keyboard, whatever layout, language, input method or Caps Lock state the player has. So WASD is always under the left hand, and the label is what that key types in the player's layout - <code>"W"</code> on QWERTY, <code>"Z"</code> on AZERTY. Space, Enter, Esc, Tab, Backspace, Shift, Ctrl, Alt and the arrows get words, and a code with no name returns <code>""</code>.
|
||||||
|
|
||||||
|
On Windows the label is read from the active layout. Headless, on macOS and in the browser it is the US character, upper case.
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program Demo {
|
||||||
|
entry {
|
||||||
|
print(Input.key_label('w'))
|
||||||
|
print(Input.key_label(32))
|
||||||
|
print(Input.key_label(Key.Left))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
24
examples/library/input_key_label.ludic
Normal file
24
examples/library/input_key_label.ludic
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# input_key_label.ludic — the name a player sees for a key. Letters, digits and
|
||||||
|
# punctuation are physical positions named for the US layout; headless the label is that
|
||||||
|
# character upper case, and the named keys and arrows are words. An unnamed code is "".
|
||||||
|
#
|
||||||
|
# Deterministic; a full run prints: W A ; 1 Space Esc Shift Left Right 0 1
|
||||||
|
program InputKeyLabel {
|
||||||
|
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||||
|
|
||||||
|
entry {
|
||||||
|
print(Input.key_label('w'))
|
||||||
|
print(Input.key_label('a'))
|
||||||
|
print(Input.key_label(';'))
|
||||||
|
print(Input.key_label('1'))
|
||||||
|
print(Input.key_label(32))
|
||||||
|
print(Input.key_label(27))
|
||||||
|
print(Input.key_label(16))
|
||||||
|
print(Input.key_label(Key.Left))
|
||||||
|
print(Input.key_label(Key.Right))
|
||||||
|
print(len(Input.key_label(200)))
|
||||||
|
Input.press('a')
|
||||||
|
Input.poll()
|
||||||
|
print(bi(Input.key_down('a'))) # the held set is unchanged by naming
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -333,11 +333,35 @@ ret:
|
||||||
; keyDown switch: arrows -> WASD, return, escape -> 27, else the first character).
|
; 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
|
; #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).
|
; 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) {
|
define i32 @ev_keyval(ptr %ev) {
|
||||||
entry:
|
entry:
|
||||||
%sel_kc = call ptr @sel_registerName(ptr @.s_keycd)
|
%sel_kc = call ptr @sel_registerName(ptr @.s_keycd)
|
||||||
%kc = call i16 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_kc)
|
%kc = call i16 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_kc)
|
||||||
%kc32 = zext i16 %kc to i32
|
%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 [
|
switch i32 %kc32, label %chars [
|
||||||
i32 126, label %vw
|
i32 126, label %vw
|
||||||
i32 125, label %vs
|
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_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) }
|
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.
|
# 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_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) }
|
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
|
; held keys a 256-bit set of Ludic key codes: letters lowercase, digits and
|
||||||
; punctuation as their unshifted character, Space 32, Enter 10,
|
; punctuation as their unshifted character, Space 32, Enter 10,
|
||||||
; Esc 27, Backspace 8, Tab 9, Shift 16, Ctrl 17, Alt 18, and the
|
; 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
|
; frame key the last key pressed this frame, arrows reported as w s a d
|
||||||
; mouse framebuffer pixels (client pixels / scale), top-left origin;
|
; mouse framebuffer pixels (client pixels / scale), top-left origin;
|
||||||
; buttons bit0 left, bit1 right; wheel in notches, up positive
|
; 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.
|
; 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:
|
; 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
|
; TranslateMessage is never called, so no character messages are produced. The held
|
||||||
; key's character comes from MapVirtualKeyA. The system keys (Alt, F10) are never
|
; 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 -
|
; handed to DefWindowProc, whose menu loop would stall the game's frame loop -
|
||||||
; Alt+F4 is honoured here instead.
|
; Alt+F4 is honoured here instead.
|
||||||
; ============================================================================
|
; ============================================================================
|
||||||
|
|
@ -56,6 +66,8 @@ declare i32 @IsWindow(ptr)
|
||||||
declare ptr @SetCapture(ptr)
|
declare ptr @SetCapture(ptr)
|
||||||
declare i32 @ReleaseCapture()
|
declare i32 @ReleaseCapture()
|
||||||
declare i32 @MapVirtualKeyA(i32, i32)
|
declare i32 @MapVirtualKeyA(i32, i32)
|
||||||
|
declare i32 @MapVirtualKeyW(i32, i32)
|
||||||
|
declare ptr @CharUpperW(ptr)
|
||||||
declare i16 @GetKeyState(i32)
|
declare i16 @GetKeyState(i32)
|
||||||
declare i32 @RegisterRawInputDevices(ptr, i32, i32)
|
declare i32 @RegisterRawInputDevices(ptr, i32, i32)
|
||||||
declare i32 @GetRawInputData(ptr, i32, ptr, ptr, i32)
|
declare i32 @GetRawInputData(ptr, i32, ptr, ptr, i32)
|
||||||
|
|
@ -306,9 +318,32 @@ entry:
|
||||||
ret i32 %c7
|
ret i32 %c7
|
||||||
}
|
}
|
||||||
|
|
||||||
; the held-set code of a virtual key (cocoa.ll's ev_keyval)
|
; The typing block by position: a set-1 scancode (non-extended) to the key code it is on a
|
||||||
define internal i32 @w_keyval(i64 %vk64) {
|
; 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:
|
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
|
%vk = trunc i64 %vk64 to i32
|
||||||
switch i32 %vk, label %chars [
|
switch i32 %vk, label %chars [
|
||||||
i32 38, label %up
|
i32 38, label %up
|
||||||
|
|
@ -347,6 +382,39 @@ chars:
|
||||||
ret i32 %f
|
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
|
; 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
|
; case Shift gives it, and 0 for a key that types nothing
|
||||||
define internal i32 @w_framekey(i64 %vk64) {
|
define internal i32 @w_framekey(i64 %vk64) {
|
||||||
|
|
@ -422,7 +490,7 @@ handle:
|
||||||
i32 255, label %raw ; WM_INPUT
|
i32 255, label %raw ; WM_INPUT
|
||||||
]
|
]
|
||||||
kdown:
|
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)
|
call void @win_held_bit(i32 %hv, i32 1)
|
||||||
%isf4 = icmp eq i64 %wp, 115 ; VK_F4
|
%isf4 = icmp eq i64 %wp, 115 ; VK_F4
|
||||||
%issys = icmp eq i32 %m, 260
|
%issys = icmp eq i32 %m, 260
|
||||||
|
|
@ -439,7 +507,7 @@ setfk:
|
||||||
store i32 %fk, ptr @W_key
|
store i32 %fk, ptr @W_key
|
||||||
br label %pump
|
br label %pump
|
||||||
kup:
|
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)
|
call void @win_held_bit(i32 %uv, i32 0)
|
||||||
br label %pump
|
br label %pump
|
||||||
ldown:
|
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_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_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; },
|
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 });
|
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasm), { env });
|
||||||
|
|
|
||||||
|
|
@ -449,6 +449,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "key_down") { bare = "input_key_down"; push(labels, "key") }
|
if (meth == "key_down") { bare = "input_key_down"; push(labels, "key") }
|
||||||
if (meth == "key_pressed") { bare = "input_key_pressed"; push(labels, "key") }
|
if (meth == "key_pressed") { bare = "input_key_pressed"; push(labels, "key") }
|
||||||
if (meth == "key_released") { bare = "input_key_released"; push(labels, "key") }
|
if (meth == "key_released") { bare = "input_key_released"; push(labels, "key") }
|
||||||
|
if (meth == "key_label") { bare = "input_key_label"; push(labels, "key") }
|
||||||
if (meth == "press") { bare = "input_press"; push(labels, "key") }
|
if (meth == "press") { bare = "input_press"; push(labels, "key") }
|
||||||
if (meth == "release") { bare = "input_release"; push(labels, "key") }
|
if (meth == "release") { bare = "input_release"; push(labels, "key") }
|
||||||
if (meth == "axis") { bare = "input_axis"; push(labels, "neg"); push(labels, "pos") }
|
if (meth == "axis") { bare = "input_axis"; push(labels, "neg"); push(labels, "pos") }
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ function emit_header() -> void {
|
||||||
emith("declare i32 @win_running()\n")
|
emith("declare i32 @win_running()\n")
|
||||||
emith("declare void @win_close()\n")
|
emith("declare void @win_close()\n")
|
||||||
emith("declare void @win_held(ptr)\n")
|
emith("declare void @win_held(ptr)\n")
|
||||||
|
emith("declare i32 @win_key_char(i32)\n")
|
||||||
emith("declare void @win_mouse(ptr)\n")
|
emith("declare void @win_mouse(ptr)\n")
|
||||||
emith("declare void @win_pad(ptr)\n")
|
emith("declare void @win_pad(ptr)\n")
|
||||||
emith("declare void @win_touch(ptr)\n")
|
emith("declare void @win_touch(ptr)\n")
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ function is_intrinsic2(name: pointer) -> bool {
|
||||||
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
|
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
|
||||||
if (name == "win_running") or (name == "win_close") { return true }
|
if (name == "win_running") or (name == "win_close") { return true }
|
||||||
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
||||||
|
if (name == "win_key_char") { return true } # Input.key_label
|
||||||
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
||||||
if (name == "win_cursor_mode") { return true } # #89 cursor capture
|
if (name == "win_cursor_mode") { return true } # #89 cursor capture
|
||||||
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
||||||
|
|
@ -71,6 +72,9 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
||||||
# button-mask, wheel-delta].
|
# button-mask, wheel-delta].
|
||||||
if (name == "win_held") { let a = arg_code(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
if (name == "win_held") { let a = arg_code(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
if (name == "win_mouse") { let a = arg_code(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
if (name == "win_mouse") { let a = arg_code(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
|
# what the active keyboard layout types on a held-set key position (windowed only,
|
||||||
|
# DCE'd headless): a code point, 0 for none.
|
||||||
|
if (name == "win_key_char") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @win_key_char(i32 {a})`), "int") }
|
||||||
# #51 — win_pad: 6 words/pad [connected, mask, lx, ly, rx, ry]; win_touch: 3
|
# #51 — win_pad: 6 words/pad [connected, mask, lx, ly, rx, ry]; win_touch: 3
|
||||||
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
||||||
if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ function p_postfix() -> Node {
|
||||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "bind" or e.s == "rebind" or e.s == "poll" or e.s == "down" or e.s == "pressed" or e.s == "record" or e.s == "replay" or e.s == "action" or e.s == "bind_pad" or e.s == "active" or e.s == "just_pressed" or e.s == "just_released") { g_uses_input = true }
|
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "bind" or e.s == "rebind" or e.s == "poll" or e.s == "down" or e.s == "pressed" or e.s == "record" or e.s == "replay" or e.s == "action" or e.s == "bind_pad" or e.s == "active" or e.s == "just_pressed" or e.s == "just_released") { g_uses_input = true }
|
||||||
# #50 device layer — any of the multi-key / analog / mouse / gamepad / touch
|
# #50 device layer — any of the multi-key / analog / mouse / gamepad / touch
|
||||||
# methods also splices input.ludic (Input.key stays bare, no runtime).
|
# methods also splices input.ludic (Input.key stays bare, no runtime).
|
||||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "key_down" or e.s == "key_pressed" or e.s == "key_released" or e.s == "press" or e.s == "release" or e.s == "axis" or e.s == "axis_i" or e.s == "vector" or e.s == "strength" or e.s == "mouse_x" or e.s == "mouse_y" or e.s == "mouse_dx" or e.s == "mouse_dy" or e.s == "mouse_down" or e.s == "wheel" or e.s == "set_mouse" or e.s == "pad_connected" or e.s == "pad_button" or e.s == "pad_axis" or e.s == "move_i" or e.s == "set_pad" or e.s == "touch_count" or e.s == "touch_x" or e.s == "touch_y" or e.s == "set_touch" or e.s == "cursor_mode") { g_uses_input = true }
|
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "key_down" or e.s == "key_pressed" or e.s == "key_released" or e.s == "key_label" or e.s == "press" or e.s == "release" or e.s == "axis" or e.s == "axis_i" or e.s == "vector" or e.s == "strength" or e.s == "mouse_x" or e.s == "mouse_y" or e.s == "mouse_dx" or e.s == "mouse_dy" or e.s == "mouse_down" or e.s == "wheel" or e.s == "set_mouse" or e.s == "pad_connected" or e.s == "pad_button" or e.s == "pad_axis" or e.s == "move_i" or e.s == "set_pad" or e.s == "touch_count" or e.s == "touch_x" or e.s == "touch_y" or e.s == "set_touch" or e.s == "cursor_mode") { g_uses_input = true }
|
||||||
# Anim.play/clip/on_frame/fired + Motion.to (#48): the ergonomic writes over
|
# Anim.play/clip/on_frame/fired + Motion.to (#48): the ergonomic writes over
|
||||||
# the SpriteAnim/Motion components live in systems.ludic and use the world
|
# the SpriteAnim/Motion components live in systems.ludic and use the world
|
||||||
# table, so splice it and force the reflection ABI even if the game leaves
|
# table, so splice it and force the reflection ABI even if the game leaves
|
||||||
|
|
|
||||||
60248
selfhost/ludicc.seed.ll
60248
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -682,6 +682,7 @@ function cmd_dev_test() -> int {
|
||||||
feat_case("library/input_edge", "xkkxq", "0 11 1 100 0", "input_edge.ludic (#87 Input.key_pressed/key_released edges fire on the transition frame; no double-commit under the frame loop)")
|
feat_case("library/input_edge", "xkkxq", "0 11 1 100 0", "input_edge.ludic (#87 Input.key_pressed/key_released edges fire on the transition frame; no double-commit under the frame loop)")
|
||||||
feat_case("library/input_movement", "", "1 0 -1 -1 0", "input_movement.ludic (#79 Input.axis_i turns WASD into a -1/0/1 movement intent, no bool->int glue)")
|
feat_case("library/input_movement", "", "1 0 -1 -1 0", "input_movement.ludic (#79 Input.axis_i turns WASD into a -1/0/1 movement intent, no bool->int glue)")
|
||||||
feat_case("library/input_arrows", "", "128 129 130 131 1 0 -1 1 0", "input_arrows.ludic (Key.Up/Down/Left/Right are the held set's 128-131: key_down and move_i take the arrows)")
|
feat_case("library/input_arrows", "", "128 129 130 131 1 0 -1 1 0", "input_arrows.ludic (Key.Up/Down/Left/Right are the held set's 128-131: key_down and move_i take the arrows)")
|
||||||
|
feat_case("library/input_key_label", "", "W A ; 1 Space Esc Shift Left Right 0 1", "input_key_label.ludic (Input.key_label names a physical key: the US character headless, words for the named keys and arrows)")
|
||||||
feat_case("library/audio", "", "0 0 0 0 1", "audio.ludic (#22 Audio.* load/play/music/volume/pitch/stop/is_playing — headless no-op)")
|
feat_case("library/audio", "", "0 0 0 0 1", "audio.ludic (#22 Audio.* load/play/music/volume/pitch/stop/is_playing — headless no-op)")
|
||||||
# #6 Http.* — the client links Foundation (macOS-only), so build it through the
|
# #6 Http.* — the client links Foundation (macOS-only), so build it through the
|
||||||
# canonical `ludicc -o` path (which wires the framework) and gate on Darwin. The
|
# canonical `ludicc -o` path (which wires the framework) and gate on Darwin. The
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue