CNA-BUG-132: FNA_KEYBOARD_USE_SCANCODES does not make Keyboard::GetState layout-independent; only the input bridge and GetKeyFromScancodeEXT honour it

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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

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)).

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

User guide
Input: keyboard
Known issues
Bug index