Input System
Implementation status: Input is one of CNA's broadest implemented namespaces. The Keys enum carries the full XNA key vocabulary (160 enumerators); GamePad provides rumble, trigger rumble, LED control, hot-plug handling and per-device capability probing; and TouchPanel detects all 10 XNA gesture types. This snapshot has 47 input test sources with 523 statically discoverable GoogleTest-family definitions (alpha.1: 43 and 490), and Microsoft.Xna.Framework.Input is the one XNA namespace whose public API is pinned by compile-time signature-freeze tests (PublicApiInputSignatureFreezeTests.cpp takes the address of, or static_asserts, every public Input member). One known contract bug is documented under TouchPanel.
Overview
All input classes live in the Microsoft::Xna::Framework::Input namespace and follow a polling model that matches XNA 4.0 exactly: you call GetState() once per frame, store the result, and compare it with the previous frame's state to detect transitions. The selected CNA platform (SDL3 by default) receives the underlying OS events and translates them into the state snapshots that CNA exposes.
C++ spelling of properties. C# properties such as MouseState.X or GamePadState.IsConnected are accessor functions in CNA: getXProperty(), getIsConnectedProperty(), and setXProperty(value) where XNA has a setter. Nested value types chain the same way (pad.getThumbSticksProperty().getLeftProperty()). Static methods such as Keyboard::GetState() and GamePad::SetVibration(...) are plain functions. The tables and examples below use the real names.
The typical per-frame pattern for any device is:
// Declare in your Game class
KeyboardState previousKeyboard;
// In Update()
KeyboardState currentKeyboard = Keyboard::GetState();
if (currentKeyboard.IsKeyDown(Keys::Space) && previousKeyboard.IsKeyUp(Keys::Space)) {
// Space was just pressed this frame
}
previousKeyboard = currentKeyboard; // save for next frame
The same pattern applies to MouseState and GamePadState. Because the state objects are plain value types, storing and copying them is cheap.
Keyboard
The Keyboard class provides a snapshot of every key on the physical keyboard (Keyboard::GetState(), or Keyboard::GetState(PlayerIndex)). There is no distinction between left and right modifier keys at the KeyboardState level — use Keys::LeftShift / Keys::RightShift etc. when you need that granularity.
| Member | Description |
|---|---|
Keyboard::GetState() |
Returns a KeyboardState snapshot for the current frame. Call once per frame. |
state.IsKeyDown(Keys k) |
Returns true if the specified key is held down. |
state.IsKeyUp(Keys k) |
Returns true if the specified key is not pressed. |
state.GetPressedKeys() |
Returns a std::vector<Keys> of all Keys values that are currently down. |
The Keys enum (160 enumerators) covers the full keyboard: alphanumeric keys, function keys (F1–F24), numpad keys (NumPad0–NumPad9, Multiply, Add, etc.), navigation keys (Home, End, PageUp, PageDown), and modifier keys (LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt).
Detecting a key press (held last frame, pressed this frame) versus a key release:
// Press: key was up last frame and is down this frame
bool justPressed = current.IsKeyDown(k) && previous.IsKeyUp(k);
// Release: key was down last frame and is up this frame
bool justReleased = current.IsKeyUp(k) && previous.IsKeyDown(k);
Example: WASD movement
#include <Microsoft/Xna/Framework/Input/Keyboard.hpp>
using namespace Microsoft::Xna::Framework::Input;
// In Game::Update(GameTime& gameTime)
KeyboardState kb = Keyboard::GetState();
Vector2 velocity = Vector2::Zero;
float speed = 200.0f * (float)gameTime.getElapsedGameTimeProperty().getTotalSecondsProperty();
if (kb.IsKeyDown(Keys::W) || kb.IsKeyDown(Keys::Up)) velocity.Y -= speed;
if (kb.IsKeyDown(Keys::S) || kb.IsKeyDown(Keys::Down)) velocity.Y += speed;
if (kb.IsKeyDown(Keys::A) || kb.IsKeyDown(Keys::Left)) velocity.X -= speed;
if (kb.IsKeyDown(Keys::D) || kb.IsKeyDown(Keys::Right)) velocity.X += speed;
playerPosition = playerPosition + velocity;
Mouse
The Mouse class returns a MouseState containing cursor coordinates in screen pixels, scroll wheel accumulation, and the state of all five buttons. Coordinates are relative to the top-left corner of the game window. On the SDL3 platform’s x11, windows and cocoa video drivers CNA reads the global cursor position minus the window position, as XNA and FNA do, so the value is current and is not clamped to the window edge; other drivers report the window-relative state. In a browser, Mouse::SetPosition cannot move the real cursor, so CNA emulates the warp: it anchors the requested position and applies later physical movement to it, which keeps re-centring mouse-look working.
| Member | Type | Description |
|---|---|---|
Mouse::GetState() |
MouseState |
Returns the current mouse state snapshot. |
state.getXProperty() |
int |
Cursor X position in logical game coordinates: the window-client position mapped through the window's renderer transform (letterboxing, virtual resolution). It equals window pixels only when no transform applies. |
state.getYProperty() |
int |
Cursor Y position in logical game coordinates, mapped the same way as X. |
state.getScrollWheelValueProperty() |
int |
Accumulated scroll wheel delta since game start. |
state.getLeftButtonProperty() |
ButtonState |
Pressed or Released. |
state.getRightButtonProperty() |
ButtonState |
Pressed or Released. |
state.getMiddleButtonProperty() |
ButtonState |
Pressed or Released. |
state.getXButton1Property() |
ButtonState |
First extra side button. |
state.getXButton2Property() |
ButtonState |
Second extra side button. |
Mouse::SetPosition(int x, int y) |
void |
Warps the cursor to the given logical (game) coordinates: the point is converted through the window's renderer transform to window coordinates before the warp, the exact inverse of what GetState() reports, so under a virtual resolution or letterbox it is not a window pixel. It does nothing in relative mouse mode. |
Example: mouse click detection
#include <Microsoft/Xna/Framework/Input/Mouse.hpp>
using namespace Microsoft::Xna::Framework::Input;
MouseState previousMouse;
// In Game::Update(GameTime& gameTime)
MouseState mouse = Mouse::GetState();
// Single click: button was released this frame
if (mouse.getLeftButtonProperty() == ButtonState::Released &&
previousMouse.getLeftButtonProperty() == ButtonState::Pressed)
{
// Left click at (mouse.getXProperty(), mouse.getYProperty())
OnLeftClick(mouse.getXProperty(), mouse.getYProperty());
}
// Track scroll
int scrollDelta = mouse.getScrollWheelValueProperty() - previousMouse.getScrollWheelValueProperty();
if (scrollDelta != 0) {
camera.Zoom(scrollDelta * 0.1f);
}
previousMouse = mouse;
GamePad
The GamePad class supports up to four simultaneously connected controllers via the PlayerIndex enum (One through Four). GamePadState exposes analog sticks, triggers, buttons, and the D-pad. Always check getIsConnectedProperty() before reading state to avoid acting on a zeroed-out snapshot for an absent controller.
Behind the XNA surface, on the default SDL3 platform, sits a substantial SDL3 bridge. It is not a thin passthrough: it handles hot-plug (controllers connected and disconnected mid-session), per-device capability probing (so GamePadCapabilities reports what the specific pad actually has rather than a fixed assumption), and the full haptics surface — standard rumble, trigger rumble on pads that support it, and LED control.
No pad? Keyboard GamePad emulation (CNAEXT, off by default). GamePad::setKeyboardEmulationEnabledEXT(true) makes PlayerIndex::One a connected pad driven by the keyboard: W/A/S/D left stick, arrow keys right stick, T/F/G/H D-pad, K/L/J/I for A/B/X/Y, Q/E shoulders, Z/C triggers, Left/Right Shift stick clicks, Enter/Escape for Start/Back. Buttons are combined with a real pad’s, a non-zero keyboard axis wins over the physical one, dead zones still apply, and players two to four are unchanged. The keys stay visible to Keyboard::GetState. BigButton, vibration, sensors and controller identity are not emulated. The switch is process-wide; getKeyboardEmulationEnabledEXT() reads it.
| Member | Type | Description |
|---|---|---|
GamePad::GetState(PlayerIndex) |
GamePadState |
Returns the state for the specified player slot. An overload takes a GamePadDeadZone (below); GamePad::GetCapabilities(PlayerIndex) returns the per-device GamePadCapabilities. |
state.getIsConnectedProperty() |
bool |
Whether a controller is plugged in for this slot. |
state.getThumbSticksProperty().getLeftProperty() |
Vector2 |
Left stick, X and Y in −1..1. |
state.getThumbSticksProperty().getRightProperty() |
Vector2 |
Right stick, X and Y in −1..1. |
state.getTriggersProperty().getLeftProperty() |
float |
Left trigger, 0..1. |
state.getTriggersProperty().getRightProperty() |
float |
Right trigger, 0..1. |
state.getButtonsProperty() |
GamePadButtons |
A, B, X, Y, Start, Back, LeftShoulder, RightShoulder, LeftStick, RightStick, BigButton — each read with getAProperty(), getBProperty() and so on, and each is ButtonState::Pressed or Released. |
state.getDPadProperty() |
GamePadDPad |
Up, Down, Left, Right — read with getUpProperty() and so on; each is ButtonState::Pressed or Released. |
GamePad::SetVibration(PlayerIndex, float left, float right) |
bool |
Sets rumble motor strength, 0.0–1.0 per motor. Returns false if the controller does not support vibration. |
Dead zone handling
Analog sticks rarely rest at exactly (0, 0) due to hardware tolerances. CNA applies a dead zone before returning stick values. The dead zone mode is passed as an optional second argument to GetState():
| Mode | Behaviour |
|---|---|
GamePadDeadZone::IndependentAxes |
Default. X and Y are clamped and rescaled independently. |
GamePadDeadZone::Circular |
The dead zone is a circle; values inside it are zeroed, the remainder is rescaled radially. |
GamePadDeadZone::None |
Raw values; no dead zone applied. Use when you implement your own dead zone logic. |
Example: analog stick movement
#include <Microsoft/Xna/Framework/Input/GamePad.hpp>
using namespace Microsoft::Xna::Framework::Input;
GamePadState previousPad;
// In Game::Update(GameTime& gameTime)
GamePadState pad = GamePad::GetState(PlayerIndex::One);
if (pad.getIsConnectedProperty()) {
float speed = 200.0f * (float)gameTime.getElapsedGameTimeProperty().getTotalSecondsProperty();
// Left stick movement
Vector2 move = pad.getThumbSticksProperty().getLeftProperty();
move.Y = -move.Y; // XNA Y axis: up is positive on stick, down on screen
playerPosition = playerPosition + move * speed;
// Right trigger as an accelerator
speed *= 1.0f + pad.getTriggersProperty().getRightProperty();
// A button just pressed
if (pad.getButtonsProperty().getAProperty() == ButtonState::Pressed &&
previousPad.getButtonsProperty().getAProperty() == ButtonState::Released)
{
Jump();
}
// Vibrate on damage
if (playerHit) {
GamePad::SetVibration(PlayerIndex::One, 0.8f, 0.4f);
}
}
previousPad = pad;
Beyond XNA: GamePad EXT methods
XNA 4.0 was designed around the Xbox 360 controller, so its GamePad API has no vocabulary for anything a modern pad does. CNA adds 21 CNAEXT-tagged static functions to GamePad to cover the gap: the keyboard-emulation switch above, device GUID, light bar colour, trigger vibration, gyroscope and accelerometer readings, battery/power info, touchpad finger positions, and the Steam input handle. The extra buttons modern pads expose — back paddles, Misc1, and the touchpad click — are available too, alongside six Keyboard scancode methods, MouseCursor, and TextInputEXT.
These are outside the XNA surface by construction. Compile with the CNA_STRICT_XNA_API definition on your target and the compiler flags every use as deprecated, which is what you want if the goal is a codebase that could also build against real XNA. (It is a compile definition, not a CMake option; CNA's own automated check of it covers the Devices namespace only.)
TouchPanel
The TouchPanel class exposes multi-touch and gesture recognition. All 10 XNA gesture types are genuinely detected — not stubbed, not partially wired — by a 474-line state machine that tracks contacts over time and computes flick velocity with exponential smoothing. Three points to know: GetState() is event-driven where FNA polls the platform every frame (a deliberate deviation from FNA; see Touch and gestures), while MaximumTouchCount reports 4 and GetState() caps at 8 touches, both matching FNA.
Known contract quirk: deliberate FNA parity, not XNA's contract. TouchCollection reports IsReadOnly == true, but its mutator methods and its indexer setter mutate the collection instead of throwing, as FNA's do (XNA 4.0's throw NotSupportedException); CNA's tests pin this as intended (TouchCollectionTest.IsReadOnlyIsAdvisoryAndMutationStillSucceedsLikeFna). Code that trusts IsReadOnly as a guarantee will be misled. Do not rely on the flag to protect a collection you hand out.
| Member | Type | Description |
|---|---|---|
TouchPanel::GetState() |
TouchCollection |
Returns all active touch points this frame; the collection is iterable with a range-based for. |
location.getIdProperty() |
int |
Unique identifier for this touch contact. |
location.getStateProperty() |
TouchLocationState |
Pressed, Moved, or Released. |
location.getPositionProperty() |
Vector2 |
Screen-space position of this touch point. |
TouchPanel::getIsGestureAvailableProperty() |
bool |
Returns true when at least one gesture is queued. |
TouchPanel::ReadGesture() |
GestureSample |
Dequeues and returns the next gesture sample. |
All 10 GestureType values are detected: Tap, DoubleTap, Hold, HorizontalDrag, VerticalDrag, FreeDrag, Flick, Pinch, PinchComplete, DragComplete.
Example: gesture reading
#include <Microsoft/Xna/Framework/Input/Touch/TouchPanel.hpp>
using namespace Microsoft::Xna::Framework::Input::Touch;
// Configure which gestures you want before your game loop
TouchPanel::setEnabledGesturesProperty(
GestureType::Tap | GestureType::FreeDrag | GestureType::Flick);
// In Game::Update(GameTime& gameTime)
while (TouchPanel::getIsGestureAvailableProperty()) {
GestureSample gesture = TouchPanel::ReadGesture();
switch (gesture.getGestureTypeProperty()) {
case GestureType::Tap:
OnTap(gesture.getPositionProperty());
break;
case GestureType::Flick:
// getDeltaProperty() holds the flick velocity
playerVelocity = playerVelocity + gesture.getDeltaProperty() * 0.01f;
break;
case GestureType::FreeDrag:
cameraOffset = cameraOffset - gesture.getDeltaProperty();
break;
default:
break;
}
}
New in this snapshot: mouse emulation and touch suppression
Two CNAEXT switches were added to TouchPanel. setMouseTouchEmulationEnabledEXT(true) makes the left mouse button synthesise a touch, so a touch UI can be tried on a desktop; it is off by default, matching XNA and FNA. getInputSuppressedEXT()/setInputSuppressedEXT() withhold touch input while a Guide message box or keyboard prompt is on screen; CNA raises it for you while such an overlay is pending. Mouse events that SDL synthesised from a touch do not feed the emulation back, so a real pinch stays two real fingers, and an emulated touch is clamped to the display. CNA also gained a MIME-typed CNA::Input::Clipboard API (including the X11 primary selection).
TextInputEXT
TextInputEXT is a CNA extension (not in the original XNA 4.0 API) that captures text entry events from the OS input method, including IME composition on platforms that support it. It is the correct way to implement chat boxes, in-game consoles, or any other free-text input — keyboard polling alone will not handle key repeat, modifier combinations, or international characters correctly. Draft IME text arrives separately through TextEditing (UTF-8), and TextEditingCandidatesEXT reports candidate lists.
| Member | Type | Description |
|---|---|---|
TextInputEXT::TextInput |
Event | Fires each time the user produces a printable character. The argument is one UTF-16 code unit (char16_t); a code point above U+FFFF, such as an emoji, arrives as two calls (high surrogate, then low surrogate), as in FNA. |
#include <Microsoft/Xna/Framework/Input/TextInputEXT.hpp>
using namespace Microsoft::Xna::Framework::Input;
// In Game::Initialize() or wherever you set up input
// inputBuffer is a std::u16string; TextInput passes one UTF-16 code unit per call
TextInputEXT::TextInput += [this](char16_t c) {
if (c == u'\b') { // backspace
if (!inputBuffer.empty()) inputBuffer.pop_back();
} else if (c >= u' ') { // printable
inputBuffer += c;
}
};
// To stop receiving events (e.g. when the text field is dismissed)
// TextInputEXT::StopTextInput();
Call TextInputEXT::StartTextInput() to activate text-entry mode (which may show a soft keyboard on mobile) and StopTextInput() when done. Text input is a mode, not a no-op on desktop: CNA's SDL3 platform turns SDL's text input on only from StartTextInput() (Sdl3TextInput::Start calls SDL_StartTextInput), and SDL3 sends text-input events only while that mode is on, so a game that never calls it receives no TextInput events on Windows, X11 or Wayland alike. Call StartTextInput() before you expect TextInput events.
Accelerometer (Sensors)
The Accelerometer class provides access to the device accelerometer. It is backed by the selected platform's real sensor service (the SDL3 sensor API on the default platform) on Android, iOS and desktop wherever a sensor exists, with correct m/s²-to-g conversion and substantial concurrency hardening around the sensor callbacks. Its sibling Gyroscope works the same way. See Sensors for the full Microsoft::Devices::Sensors surface, including which sensors are Android-only. Unlike the input classes above, the sensors are instances: construct one, Start() it and read it.
| Member | Type | Description |
|---|---|---|
Accelerometer::getIsSupportedProperty() |
bool (static) |
true if the selected platform reports an accelerometer on an Android, iOS or desktop target, or, with keyboard emulation on, on desktop and in the browser. |
accel.Start() / accel.Stop() |
void |
Begin and end data acquisition on an Accelerometer instance. |
accel.getCurrentValueProperty() |
AccelerometerReading |
The latest reading. |
reading.getAccelerationProperty() |
Vector3 |
Acceleration in G-force units along X, Y, Z device axes. |
accel.getStateProperty() |
SensorState |
An enum (NotSupported, Ready, Initializing, NoData, NoPermissions, Disabled); getIsDataValidProperty() says whether live data has arrived. |
Keyboard sensor and orientation emulation (CNAEXT, off by default). Accelerometer::setKeyboardEmulationEnabledEXT(true) replaces the primary accelerometer with a software one driven by the arrow keys (tilt of one g, no keys reads (0, 0, -1)), and getIsSupportedProperty() becomes true on desktop and in the browser without hardware; stop every running accelerometer before switching. Separately, getWindowProperty().setKeyboardOrientationEmulationEnabledEXT(true) lets Up, Left and Right request Portrait, LandscapeLeft and LandscapeRight within SupportedOrientations, through the normal device-reset path. Both are for trying phone-style games on a desktop; see Sensors.
Implementation status
| Class | Namespace | Status | Notes |
|---|---|---|---|
Keyboard |
Input |
Done | All 160 Keys enumerators. The scancode bridge maps 122 platform scancodes to keys and 119 keys back; IME, ChatPad, browser/media keys and OemBackslash have no physical position (see the input model). Public API pinned by compile-time signature-freeze tests. |
Mouse |
Input |
Done | All five buttons, scroll wheel, SetPosition. |
GamePad |
Input |
Done | SDL3 bridge: rumble, trigger rumble, LED, hot-plug, per-device capability probing. All three dead zone modes. 21 CNAEXT extension functions, including opt-in keyboard emulation of player one’s pad. |
TouchPanel |
Input.Touch |
Done | All 10 XNA gesture types detected by a 474-line state machine. Opt-in mouse-to-touch emulation and touch suppression (CNAEXT). See the TouchCollection::IsReadOnly FNA-parity note above. |
TextInputEXT |
Input |
Done | CNA extension (CNAEXT). UTF-16 char16_t code-unit events; IME composition and candidate events on desktop. |
Accelerometer |
Devices.Sensors |
Done | Instance-based (Start/getCurrentValueProperty); real platform sensor service on Android, iOS and desktop where a sensor exists; correct m/s²-to-g conversion. Built in every configuration: CNA_DEVICES gates only the CNA-specific device extensions (CNA::Devices), not the Microsoft::Devices sensors. |
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Framework services and ecosystem quick reference — A map of CNA's input, audio, media, device, network, gamer-services, storage and sharp-runtime types with their current boundaries and owner pages, plus the bindings and showcase applications around CNA.
- Gamepads, joysticks, haptics and host power — How CNA maps controllers to the four XNA slots, normalizes and dead-zones axes, what each GamePad extension does, and how the joystick, haptics, power and sensor classes of CNA::Input behave.
- Text input and IME composition — The exact TextInputEXT contract in CNA: committed text, IME composition and candidate events, the window-bound text mode, the input-type hint, composition placement and the clipboard classes.
- The input model: snapshots, keys, the mouse and logical coordinates — What Keyboard and Mouse GetState return and when, the Keys numbering and layout helpers, the mouse extensions and cursors, and how renderers map window pixels to logical coordinates.
- Touch panel and gesture semantics — When CNA's touch state advances, which connected flag to trust, TouchCollection and TouchLocation contracts, gesture filtering, timestamps and the deliberate differences from FNA.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-078: Game::PollEvents fires the renderer's test-only context-loss hooks on F9/F10 in every build — A non-repeated F9 or F10 press in any CNA game calls the renderer's DebugSimulateContextLoss() or DebugRestoreContext(), a channel CNA itself describes as a test seam, with no build, option or opt-out guard.
- CNA-BUG-132: FNA_KEYBOARD_USE_SCANCODES does not make Keyboard::GetState layout-independent; only the input bridge and GetKeyFromScancodeEXT honour it — 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 retu
- CNA-BUG-133: DIRECTX9, DIRECTX11 and METAL implement the window-to-logical transform but never register for their window, so mouse and touch input ignore it — Input looks renderers up by window id through IGraphicsRenderer::GetForWindow; these three renderers never call RegisterForWindow, so Mouse::GetState, Mouse::SetPosition and touch pass raw window coordinates through unde
- CNA-BUG-134: Inside a letterbox bar, Mouse::GetState reports the raw window coordinate on renderers whose transform declines the point — EasyGL (every GL-family identity), SDL_GPU and WEBGPU decline points outside the presented rectangle, and Mouse and the input bridge then report the untransformed window coordinate, which can look like a valid game posit
- CNA-BUG-135: Mouse::SetCaptureEXT, GetGlobalPositionEXT and WarpGlobalEXT let PlatformNotSupportedException escape on the terminal platform — The terminal mouse service throws PlatformNotSupportedException from SetCapture, TryGetGlobalPosition and SetGlobalPosition, and the Mouse wrappers do not catch it, although they document false (or (0, 0)) for an unsuppo
- CNA-BUG-176: Setting Game::IsMouseVisible throws PlatformException on the terminal platform when the terminal reports mouse input — Game::setIsMouseVisibleProperty forwards to IPlatformMouse::SetCursorVisible whenever a window and a mouse service exist; the terminal platform supplies a TerminalMouse whenever stdin and stdout are both terminals (Termi
- CNA-BUG-237: A comment in Sdl3Platform.cpp refers to a Game::UpdateInput() that does not exist — The SDL3 platform explains its lazy controller-subsystem start by saying Game::UpdateInput() pumps the input services only once the subsystem is initialised, but Game has no UpdateInput(); the per-frame controller pump l
- CNA-BUG-263: TerminalMouse::SetPosition throws PlatformException although IPlatformMouse::SetPosition documents no exception, so XNA's Mouse.SetPosition throws on the terminal platform — IPlatformMouse::SetPosition documents no exception, but TerminalMouse::SetPosition always throws PlatformException and Mouse::SetPosition calls it unguarded, so the XNA re-centre-the-cursor idiom throws once the terminal
- CNA-GAP-067: ScrollWheelValue drops sub-notch wheel motion: the SDL3 platform counts whole notches only, where XNA accumulates raw wheel units — The SDL3 platform truncates every wheel event to whole notches before scaling by 120 (a deliberate FNA-parity rule in docs/input-fna-fidelity.md), so high-resolution wheels and touchpads can scroll without changing Scrol
- CNA-PLAT-013: Wayland gives no desktop pointer position, and browsers hide gamepads until a button press and gate pointer lock on a user gesture — Under SDL3's Wayland video driver the desktop pointer position reads (0, 0) and the cursor cannot be warped reliably; on the web a gamepad appears only after the player presses a button on it, and relative mouse mode eng