CNA-BUG-132: FNA_KEYBOARD_USE_SCANCODES does not make Keyboard::GetState layout-independent; only the input bridge and GetKeyFromScancodeEXT honour it
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
CNA documents scancode mode as FNA's layout-independent key polling, but Keyboard::GetState reads the platform snapshot, which always resolves keys through the current layout, while GetKeyFromScancodeEXT switches to returning keys unchanged.
- Identifier
CNA-BUG-132- Category
- Bug
- Subsystem
- Input
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Medium (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Keyboard::GetState() and Keyboard::GetKeyFromScancodeEXT(Keys) under the FNA_KEYBOARD_USE_SCANCODES=1 environment switch
Expected behaviour
In FNA, UseScancodes is read once at start-up and ToXNAKey maps every key event through the scancode table when it is set, so Keyboard.GetState() reports the key at the US position whatever the layout, and GetKeyFromScancodeEXT returns its argument unchanged (FNA SDL3_FNAPlatform.cs, ToXNAKey and GetKeyFromScancode). CNA's own platform-input-notes.md promises the same: “Set FNA_KEYBOARD_USE_SCANCODES=1 (read once at startup) for layout-independent physical-position key bindings” and “The physical position maps to the US-equivalent XNA Keys regardless of layout”.
Actual behaviour at TARGET
use_scancode_mode() in SdlInputBridge.cpp is consulted in two places only: SdlInputBridge::GetKeyFromScancode, which then returns the key unchanged, and the KeyEvent branch of PlatformInputBridge::ProcessEvent, which maps the event through the scancode table for the legacy InputManager held-key set and the TextInputEXT control characters. Keyboard::GetState in Keyboard.cpp never reads that set: it copies the IPlatformKeyboard snapshot, and Sdl3Keyboard::Update in Sdl3InputServices.cpp resolves each held scancode with SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false), that is, through the current layout. No platform keyboard service reads the variable.
The two halves therefore disagree once the mode is on. On an AZERTY layout, holding the key at the US W position makes GetState() report Keys::Z, while GetKeyFromScancodeEXT(Keys::W) now returns Keys::W. The FNA idiom for positional bindings, IsKeyDown(GetKeyFromScancodeEXT(Keys::W)), works with the mode off and stops working when it is switched on.
Source locations
modules/input/src/Internal/SdlInputBridge.cpp— use_scancode_mode; SdlInputBridge::GetKeyFromScancode; PlatformInputBridge::ProcessEvent KeyEvent branchmodules/input/src/Xna/Keyboard.cpp— Keyboard::GetState and Keyboard::GetKeyFromScancodeEXTmodules/platform/src/Sdl3/Sdl3InputServices.cpp— Sdl3Keyboard::Update: SDL_GetKeyFromScancode with the current layoutdocs/platform-input-notes.md— the scancode-mode promise (keyboard layouts, INPUT-KBD-014)docs/input-fna-fidelity.md— scancode mode listed as layout-independent, matching FNAmodules/input/tests/CNA/Internal/Input/SdlInputBridgeKeyboardTests.cpp— ScancodeMapUsedWhenScancodeModeForced: a local Keyboard shim returns InputManager's statemodules/input/tests/Microsoft/Xna/Framework/Input/KeyboardInputTests.cpp— header comment: scancode mode not covered
Evidence
Checked by reading at 009d40f5; not executed. The FNA reference was read from the FNA-XNA/FNA sources at b3551247 (SDL3_FNAPlatform.cs). A search of modules/platform/src finds no reader of the variable; only the SDL3 keyboard service's translation was read in detail, the X11, Wayland and Win32 services were not examined for their layout behaviour.
Focused reproduction
Illustrative; not compiled or run.
// FNA_KEYBOARD_USE_SCANCODES=1, AZERTY layout, the key at the US "W" position held
KeyboardState state = Keyboard::GetState();
state.IsKeyDown(Keys::Z); // true: the layout key
state.IsKeyDown(Keys::W); // false (FNA: true)
state.IsKeyDown(Keyboard::GetKeyFromScancodeEXT(Keys::W)); // false: the helper returns W unchanged
Current tests
SdlInputBridgeKeyboardTests.cpp forces the mode with SetScancodeModeForTests, but its Keyboard::GetState is a local shim returning InputManager::GetKeyboardState(), so ScancodeMapUsedWhenScancodeModeForced asserts the legacy accumulator, not the public query. KeyboardInputTests.cpp states that scancode mode is not covered because the flag is cached per process.
Regression test
With the test override on, a canned IPlatformKeyboard (or the SDL3 service fed a scancode whose layout key differs) must make the real Microsoft::Xna::Framework::Input::Keyboard::GetState() report the US-position key, and IsKeyDown(GetKeyFromScancodeEXT(k)) must agree in both modes.
Blast radius
Games that set FNA_KEYBOARD_USE_SCANCODES=1 and are played on a non-US layout. With the variable unset (the default) behaviour is consistent: layout keys from GetState and a layout translation from GetKeyFromScancodeEXT.
Workaround
Leave the variable unset and bind by position with IsKeyDown(Keyboard::GetKeyFromScancodeEXT(key)).
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Input: keyboard
- Internals
- Input internals: keyboard
- Known issues
- Bug index