Tracks the autonomous implementation of
input_noxna.md. One task = one commit, never batched. Each task: implement (SDL3-only, NOXNA/EXT-tagged, no SDL leak in public headers), build, test (ctest -L inputgreen + ASan-clean where behavior changes), record here + commit + push. If a task needs a decision only the owner can make, SKIP it and note why (owner is away).
[ ]not started ·[~]in progress ·[x]done ·[!]skipped (needs owner input) / hardware-gated
- N-001
CNA::Input::Clipboard— text Get/Set/Has (SDL_GetClipboardText/SetClipboardText/HasClipboardText). - N-002
Keyboardscancode-name helpers EXT —GetScancodeNameEXT/GetScancodeFromNameEXT(physical, layout-independent). - N-002b
Keyboardkeycode-name helpers EXT —GetKeyNameEXT/GetKeyFromNameEXT(layout-dependent). - N-003
Keyboard::GetModStateEXT+KeyModifiersEXT— modifier flags (Shift/Ctrl/Alt/Gui + Caps/Num/Scroll/Mode) viaSDL_GetModStateseam. - [!] N-004
Mousecursor visibility EXT — SKIPPED (superseded, would conflict). CNAGame::IsMouseVisible(Game.hpp:128) already owns cursor visibility and callsSDL_ShowCursor()/SDL_HideCursor()with its own cachedIsMouseVisible_. AMouse::SetCursorVisibleEXTwould be a second path to the same global SDL state and desync Game's cache.Game.IsMouseVisibleis the XNA-idiomatic API — do not duplicate. (Engineering decision, not an owner question.) - N-005 Mouse horizontal scroll wheel EXT — surface SDL
wheel.x(currently dropped, DEC-18). - N-006
TouchLocation::getPressureEXT— expose SDL finger pressure (XNA dropped Pressure).
- N-007
CNA::Input::Joysticks— raw joystick (axes/buttons/hats/balls); tested via injectableISdlJoystickBackendfake. - N-008
GamePadtouchpad fingers EXT —GetTouchpadCountEXT/GetTouchpadFingerCountEXT/GetTouchpadFingerEXT(poll-based). - N-009
GamePadplayer-index EXT —Get/SetPlayerIndexEXT(SDL device player-number LED). - N-009b
GamePadbattery/power EXT —GetPowerInfoEXT(SDL_GetGamepadPowerInfo) + sharedCNA::Input::PowerStateEXT. - N-010
GamePadmetadata EXT —Get{Name,Path,Serial,FirmwareVersion,SteamHandle}EXT. - N-010b
GamePadconnection-state EXT —GetConnectionStateEXT -> {Wired,Wireless,Unknown}. - N-011
GamePadbutton labels EXT —GetButtonLabelEXT(ABXY vs cross/circle/square/triangle). - [!] N-012
CNA::Input::Pen— CANCELLED (2026-07-07, owner request). Stylus support (pressure/ tilt/rotation/eraser/buttons) will not be implemented in this pass. Not an engineering conflict like N-004 — an explicit scope cut so the pass can move straight to N-007/N-013.
- N-013
CNA::Input::Haptics— SDL_haptic force-feedback (constant/periodic/ramp/condition/custom + gain/autocenter). Real actuation manual[!]. - N-014
TextInputEXT::TextEditingCandidatesEXT— IME candidate lists (SDL_EVENT_TEXT_EDITING_CANDIDATES). Input-type hints -> N-014b. - N-014b
TextInputEXTinput-type hints —StartTextInputWithTypeEXT(TextInputTypeEXT)(text/URL/email/number/password) viaSDL_StartTextInputWithProperties(split off from N-014). - N-015
CNA::Input::Sensors— device-level accelerometer/gyro + enumeration (SDL_sensor) via seam. - N-016
Mousecapture / global-position EXT —SetCaptureEXT,GetGlobalPositionEXT,WarpGlobalEXTvia seam. - N-017
CNA::Input::InputDevices— enumeration (mice/keyboards/touch id+name) via seam. Hot-plug events -> N-017b. - N-017b
InputDeviceshot-plug events —Mouse/Keyboard Connected/DisconnectedEXTmulticast events. - N-018
CNA::Input::Power— system battery (SDL_GetPowerInfo) via injectable seam; reusesPowerStateEXT.
- New standalone types live in the public
CNA::Inputnamespace (include/CNA/Input/,src/CNA/Input/),NOXNAat the class. NOXNA members on existing XNA types keep theEXTsuffix + update the signature-freeze test +docs/input-public-api-frozen.mdin the SAME commit. - No SDL type may appear in a public header (opaque
uintptr_t/ internal seam only). - Device-query capabilities get an injectable backend + fake (mirror
ISdlGamepadBackend); real actuation is[!]. - CMake auto-globs
src/**/*.cpp+tests/**/*.cpp(CONFIGURE_DEPENDS) — reconfigure if a fresh file isn't picked up. - Not every device-scoped seam needs
SdlInputBridgehot-plug tracking: N-013 Haptics is caller-managed RAII (Haptics::OpenEXT->HapticDevice, closed onDispose()/destruction), no bridge event-switch involvement at all (unlike N-007 Joystick's always-open registry). Pick whichever lifecycle model matches how the real device is actually used.
All planned tasks are resolved: 21 done, N-004 skipped (engineering conflict), N-012 cancelled (owner
request). Nothing remains to pick up from this backlog — see NEXT.md before starting new NOXNA work.
(most recent first — filled as tasks complete)
- N-013 done (2026-07-07):
CNA::Input::Haptics+CNA::Input::HapticDevice— SDL3 force-feedback (SDL_haptic), the last task in this pass (N-004 skipped, N-012 cancelled are the only non-done rows now). New enum/descriptor headers:HapticFeatureEXT(bit flags, mirrorsSDL_HapticFeatures),HapticDirectionTypeEXT/HapticDirectionEXT(Polar/Cartesian/Spherical/SteeringAxis),HapticEffectTypeEXT(13 effect families),HapticInfoEXT(enumeration id+name),HapticCapabilitiesEXT(features/axes/max-effects/rumble), andHapticEffectEXT— one flattened descriptor covering every SDL effect family (Constant/Sine/Square/Triangle/SawtoothUp/SawtoothDown/ Ramp/Spring/Damper/Inertia/Friction/LeftRight/Custom) instead of six separate tagged-union-mirroring types, since this is NOXNA-only (no FNA fidelity constraint) and atype-discriminated flat struct is far less combinatorial than 6 near-identical types + 6 test suites + 6x the Doxygen.CNA::Input::HapticDevice(whole-class NOXNA, RAII, move-only,System::IDisposable, mirrorsMouseCursor's SDL-forward-declared-pointer pattern) wraps an openedSDL_Haptic*: capabilities, simple rumble (Init/Play/Stop), the full effect lifecycle (Create/Update/Run/Stop/Destroy/Status/ StopAll), and Gain/Autocenter/Pause/Resume.CNA::Input::Haptics(static factory/enumerator, likeJoysticks/Sensors) opens a device standalone (OpenEXT(id)), from an already-connected raw joystick (OpenFromJoystickEXT, reusing N-007'sJoystickssubsystem), or from the mouse (OpenFromMouseEXT) — a newSdlInputBridge::GetOpenedJoystickHandle(id)internal-only accessor (not part of the publicCNA::Inputsurface) bridges N-007's opened-joystick registry to N-013'sOpenFromJoystickEXTwithout leakingSDL_Joystick*into any public header. New, independent seamISdlHapticBackend(include|src/CNA/Internal/Input/SdlHapticBackend.hpp/.cpp) — unlike the gamepad/joystick seams, haptic devices have no hot-plug lifecycle owned by SdlInputBridge: aHapticDeviceis opened/closed explicitly by the caller (RAII), mirroringMouseCursorrather than the always-open gamepad/joystick registries, so no bridge event-switch changes were needed beyond the one accessor. TheHapticEffectEXT -> SDL_HapticEffectunion conversion lives inHapticDevice.cpp's anonymous namespace (explicit switch-case mapping for both the direction-type and effect-type enums — never relying on numeric ordinal coincidence with SDL's constants, even where they currently match). Whole types NOXNA/additive (no freeze pin, matches Sensors/Power/InputDevices/Joysticks precedent). Teststests/CNA/Internal/Input/ SdlHapticBackendTests.cpp(newFakeSdlHapticBackend.hpp) — 37 tests: enumeration, open/close (standalone/from-joystick/from-mouse, using N-007'sFakeSdlJoystickBackend+ a syntheticSDL_EVENT_JOYSTICK_ADDEDto get a real opened-joystick handle to open a haptic from), move semantics, capabilities, rumble, the full effect lifecycle, the union conversion for every effect family (including per-axis condition arrays and the Custom raw-sample-buffer path), gain/ autocenter/pause/resume, closed-device safe-defaults for every method, and descriptor-equality + bit-op tests. Caught and fixed one test-double-only bug during this task: the fake'sCreate/ UpdateHapticEffectinitially copied theSDL_HapticEffectunion by value (capturing thecustom. dataraw pointer) without snapshotting the pointee, so introspectinglastCreatedEffect.custom. dataafter the call returned read freed memory (the caller's localstd::vectorhad already been destroyed) — real SDL copies Custom sample data synchronously during the call so productionHapticDevice::CreateEffectEXT/UpdateEffectEXTwere never at risk; fixed by having the fake snapshot the buffer into an owned member and repoint.custom.dataat that snapshot. Added*Haptic*toCNA_INPUT_TEST_FILTER(CMakeLists.txt).ctest -L inputgreen; ASan-clean (detect_leaks=0, documentedlibGLX_mesafalse positive). Files: HapticFeature.hpp, HapticDirection.hpp, HapticEffectType.hpp, HapticEffect.hpp, HapticInfo.hpp, HapticCapabilities.hpp, HapticDevice.hpp/.cpp, Haptics.hpp/.cpp, SdlHapticBackend.hpp/.cpp, SdlInputBridge.hpp/.cpp (GetOpenedJoystickHandle only), FakeSdlHapticBackend.hpp (new), SdlHapticBackendTests.cpp (new), CMakeLists.txt. - N-012 CANCELLED (2026-07-07, owner request): stylus/pen support removed from scope for this pass. Not implemented; no code changes.
- N-007 done (2026-07-07):
CNA::Input::Joysticks— raw joystick access (axes/buttons/hats/ trackballs), independent ofGamePad's mapped view of the same hardware. New descriptor/state headersinclude/CNA/Input/{JoystickType,JoystickHatPosition,JoystickInfo,JoystickCapabilities, JoystickState}.hpp(JoystickTypeEXTmirrorsSDL_JoystickType;JoystickHatPositionEXTenumerates the 9 reachableSDL_HAT_*combinations rather than exposing them as flags;JoystickCapabilitiesEXTreuses the sharedPowerStateEXTfrom N-009b/N-018). New, deliberately-separate seamISdlJoystickBackend(include|src/CNA/Internal/Input/ SdlJoystickBackend.hpp/.cpp) — kept apart fromISdlGamepadBackendper NEXT.md guidance since a gamepad is a mapped joystick and this is the raw device; the same physical device is opened independently through both seams.SdlInputBridgeopens every joystick onSDL_EVENT_JOYSTICK_ ADDEDinto a newstd::unordered_map<SDL_JoystickID, SDL_Joystick*>registry (closed on_REMOVED), firesJoysticks::Connected/DisconnectedEXT(mirrors N-017b's direct-invoke style), and does NOT handle AXIS_MOTION/BUTTON_DOWN/UP/HAT_MOTION/BALL_MOTION events at all — SDL's own event pump already updates its internal per-joystick state cache independent of what CNA does with each dequeued event, soGetJoystickStatepollsSDL_GetJoystickAxis/Button/Hat/Balllive via the seam on every call (same poll-on-demand principle as the GamePad EXT metadata getters). PublicCNA::Input::Joysticks(whole-class NOXNA, additive — no freeze pin, matches Sensors/Power/ InputDevices precedent):GetJoysticksEXT(),GetCapabilitiesEXT(id),GetStateEXT(id),Connected/DisconnectedEXT. Teststests/CNA/Internal/Input/SdlJoystickBackendTests.cpp(newFakeSdlJoystickBackend.hpp) drive the realSdlInputBridge::ProcessEventpath with syntheticSDL_EVENT_JOYSTICK_ADDED/REMOVED: hot-plug open/close/duplicate-add/unknown-remove/open-failure, enumeration, hot-plug events, capabilities (counts/type/name/guid/power) and disconnected-default, state (axes/buttons/hats/balls) and disconnected-default, exhaustive hat-position and joystick-type mapping, plus descriptor-equality tests. Added*Joystick*toCNA_INPUT_TEST_FILTER(CMakeLists.txt) soctest -L inputpicks up the new suite.ctest -L inputgreen; ASan-clean (detect_leaks=0for the documentedlibGLX_mesafalse positive). Files: JoystickType.hpp, JoystickHatPosition.hpp, JoystickInfo.hpp, JoystickCapabilities.hpp, JoystickState.hpp, Joysticks.hpp/.cpp, SdlJoystickBackend.hpp/.cpp, SdlInputBridge.hpp/.cpp, FakeSdlJoystickBackend.hpp (new), SdlJoystickBackendTests.cpp (new), CMakeLists.txt. - N-014b done (2026-07-07):
TextInputEXT::StartTextInputWithTypeEXT(CNA::Input::TextInputTypeEXT)— input-type hint (text/name/email/username/password-hidden/password-visible/number/number-password- hidden/number-password-visible) for the on-screen keyboard / IME, completing the N-014 split. New enum headerinclude/CNA/Input/TextInputType.hpp(TextInputTypeEXT, mirrorsSDL_TextInputType1:1). No new seam: keptTextInputEXT's existing direct-SDL style (matchesStartTextInput) — builds anSDL_PropertiesIDviaSDL_CreateProperties/SDL_SetNumberProperty(..., SDL_PROP_TEXTINPUT_TYPE_NUMBER, ...), callsSDL_StartTextInputWithProperties, thenSDL_DestroyProperties; same null-window guard asStartTextInput(no window handle -> no-op). Enum-to-SDL mapping is a private switch in the .cpp anonymous namespace. Pinned the method in the freeze test + documented indocs/input-public-api-frozen.md(TextInputEXT's whole-class-NOXNA status still means member-level EXT tagging, matching N-014's precedent). Tests: no-window safe- no-op across all 9 hint values, and a real-hidden-window round-trip (Xvfb-gated,GTEST_SKIPfallback like the existingStartStopAndIsActiveRoundTripThroughRealWindow) exercisingStartTextInputWithTypeEXT/IsTextInputActive/StopTextInputfor all 9 values.ctest -L inputgreen; ASan-clean. Files: TextInputType.hpp (new), TextInputEXT.hpp/.cpp, PublicApiInputSignatureFreezeTests.cpp, input-public-api-frozen.md, TextInputEXTTests.cpp. - N-014 done (2026-07-07):
TextInputEXT::TextEditingCandidatesEXT— aMulticastAction<const vector<string>&, int, bool>(candidates, selected index, horizontal) for SDL3's IME candidate list (CJK). The bridge decodesSDL_EVENT_TEXT_EDITING_CANDIDATES(event.edit_candidates.*) into UTF-8 std::strings before the SDL event is recycled, thenINTERNAL_OnTextEditingCandidatesinvokes the event (mirrors the existing TextInput/TextEditing dispatch); ResetForTests clears it. Kept it on the existingTextInputEXTclass (not a new TextComposition type). Pinned the event in the freeze test (TextInputEXT is pinned) + documented (incl. the INTERNAL dispatcher in the internal list). TestsSdlInputBridgeCandidatesTestfeed a synthetic candidates event (incl. a CJK UTF-8 string) + a null-list case.ctest -L inputgreen; ASan-clean. Deferred theStartTextInputWithTypeEXTinput-type hint to N-014b. - N-017b done (2026-07-06):
InputDevices::{Mouse,Keyboard}{Connected,Disconnected}EXT— fourSystem::MulticastAction<uint32_t>hot-plug events carrying the SDL device id (use+=). The bridge's ProcessEvent decodesSDL_EVENT_{MOUSE,KEYBOARD}_{ADDED,REMOVED}(event.mdevice.which/event.kdevice.which) and Invokes the matching event;InputDevices::ResetForTestsclears all four. Additive NOXNA (no freeze pin). TestsCnaInputDevicesHotplugTestfeed synthetic device events through ProcessEvent and assert the right event fires with the right id + no cross-firing.ctest -L inputgreen; ASan-clean. Completes the InputDevices feature (enumeration + hot-plug). - N-008 done (2026-07-06):
GamePad::GetTouchpadCountEXT/GetTouchpadFingerCountEXT(touchpad)/GetTouchpadFingerEXT(touchpad, finger, out down,x,y,pressure)— PS4/PS5-style touchpad finger data, poll-based (like GetGyro/AccelEXT), NOT event-driven, so no InputManager changes. Gamepad-seam pattern #1: addedGetNumGamepadTouchpadFingers+GetGamepadTouchpadFingertoISdlGamepadBackend(real = the matching SDL fns; reused existingGetNumGamepadTouchpads); fake gainedFakeTouchpadFinger+FakeGamepadConfig.touchpadFingers[[touchpad]][finger]. Bridge resolves the slot and returns 0/false (out-params reset) when disconnected/out of range. Pinned all three in the freeze test + documented. Tests: counts + finger contact/pos read, out-of-range touchpad/finger, disconnected slot.ctest -L inputgreen; ASan-clean. - N-015 done (2026-07-06):
CNA::Input::Sensors— host-device motion sensors (distinct from the gamepad gyro/accel EXT).GetSensorsEXT() -> vector<SensorInfoEXT{id,name,SensorTypeEXT}>+GetAccelerometerEXT(out Vector3)/GetGyroscopeEXT(out Vector3)(m/s² / rad/s), returning false when absent. New public headerinclude/CNA/Input/Sensors.hpp(enumSensorTypeEXT+ structSensorInfoEXTw/ ==/!= + class). New injectableISystemSensorBackendseam (real enumeratesSDL_GetSensors, maps SDL_SensorType<->EXT, and ReadSensor does open->SDL_GetSensorData->close on the first matching sensor) withSetSystemSensorBackendForTests. Whole types NOXNA/additive (no freeze pin). NamedSensors(plural, matches the plan'sSensors::shape + InputDevices style). TestsCnaInputSensorsTest+CnaInputSensorInfoEXTTestdrive a fake: enumeration forwarding, accel/gyro sample reads, false-when-absent (out-param untouched), descriptor equality.ctest -L inputgreen; ASan-clean. - N-016 done (2026-07-06):
Mouse::SetCaptureEXT(bool),GetGlobalPositionEXT(out x,y),WarpGlobalEXT(x,y)— mouse capture + desktop-global cursor position/warp. New injectableISystemMouseBackendseam (real =SDL_CaptureMouse/SDL_GetGlobalMouseState/SDL_WarpMouse Global) withSetSystemMouseBackendForTests— chosen over the Mouse class's usual direct-SDL + real-window tests so the global ops are deterministically testable headless (no real desktop cursor on CI). GetGlobalPositionEXT truncates SDL's float coords to int (XNA MouseState is int). Pinned all three in the freeze test + documented. TestsMouseGlobalEXTTest(fake seam): capture flag+result forwarding, global position read+truncation, warp coord forwarding+result.ctest -L inputgreen; ASan-clean. - N-017 done (2026-07-06):
CNA::Input::InputDevices::Get{Mice,Keyboards,TouchDevices}EXT() -> vector<InputDeviceInfoEXT{id,name}>— device enumeration (metadata only; XNA state stays merged). New descriptorinclude/CNA/Input/InputDeviceInfo.hpp(id+name, with ==/!=) and public classinclude/CNA/Input/InputDevices.hpp. New injectableISystemDeviceBackendseam (real =SDL_GetMice/SDL_GetKeyboards/SDL_GetTouchDevices+ the *NameForID getters, NULL->"", arrays SDL_free'd) withSetSystemDeviceBackendForTests. Whole types NOXNA/additive (like Clipboard/ Power — no freeze pin). TestsCnaInputDevicesTest+CnaInputDeviceInfoEXTTestdrive a fake: per-category forwarding, empty lists, and descriptor equality.ctest -L inputgreen; ASan-clean. Scoped to enumeration; hot-plug ADDED/REMOVED events deferred to N-017b. - N-002b done (2026-07-06):
Keyboard::GetKeyNameEXT(Keys) -> std::string+GetKeyFromNameEXT(std::string) -> Keys— the layout-dependent (virtual-key) name and its inverse, completing the N-002 name helpers. Bridge resolves Keys -> SDL_Scancode -> (keymap) SDL_GetKeyFromScancode -> SDL_GetKeyName; the inverse is SDL_GetKeyFromName -> try_convert_sdl_key. Unmapped key -> "", unrecognized name -> Keys::None. Pinned in the freeze test + documented. TestsKeyboardKeyNameEXTTestgate on SDL_INIT_VIDEO (the keymap needs it; Xvfb in CI) with GTEST_SKIP, then assert US-layout names (A/Z/Space), empty for None, name<->key round-trip, unrecognized -> None.ctest -L inputgreen; ASan-clean. - N-002 done (2026-07-06):
Keyboard::GetScancodeNameEXT(Keys) -> std::string+GetScancodeFromNameEXT(std::string) -> Keys— the physical, layout-independent key name and its inverse (for rebind UIs). Reuses the bridge's existingtry_convert_keys_to_sdl_scancode/try_convert_sdl_scancodetables aroundSDL_GetScancodeName/SDL_GetScancodeFromName; unmapped key -> "", unrecognized name -> Keys::None. Keyboard delegates to the bridge (same pattern as the other keyboard EXT). Fully deterministic headless (static SDL tables, no video/ layout). Pinned both in the freeze test + documented. TestsKeyboardScancodeNameEXTTest: stable names (A/Z/Space), empty for None, name<->key round-trip over a key set, unrecognized -> None.ctest -L inputgreen; ASan-clean. Split the plan's N-002 into scancode-names (this commit, deterministic) + N-002b keycode-names (layout-dependent, deferred). - N-003 done (2026-07-06):
Keyboard::GetModStateEXT() -> CNA::Input::KeyModifiersEXT{None,Shift,Ctrl,Alt,Gui,Caps,Num,Scroll,Mode} — active modifier + lock state. New flags enum headerinclude/CNA/Input/KeyModifiers.hpp(constexpr |,&,~,|=,&= like Buttons/GestureType). New injectableISystemKeyboardBackendseam (SDL_GetModState) withSetSystemKeyboardBackend ForTests, so tests inject mod state deterministically (SDL's real mod state only updates on real key events). BridgeGetModStatecollapses SDL's L/R variants (KMOD_SHIFT/CTRL/ALT/GUI) into one flag each + the 4 lock/mode bits; Keyboard delegates (same pattern as GetKeyFromScancodeEXT). Pinned in the freeze test + documented. TestsKeyboardModStateEXTTest(fake seam): None, per-bit mapping incl. L/R collapse, and a combined state.ctest -L inputgreen; ASan-clean. - N-006 done (2026-07-06):
TouchLocation::getPressureEXT()restores SDL finger pressure (0..1) that XNA 4.0 dropped. Added a NOXNApressure_field (default 0) + two NOXNA pressure-carrying ctors (4-arg and 6-arg); the XNA ctors leave it 0. Excluded from Equals/GetHashCode/ToString (kept FNA-frozen —has_frozen_equalitystill holds). Event path:InternalTouchLocationStategained aPressurefield;InputManager::SetTouchStatetakes a defaultedfloat pressureandGetTouchStatebuilds the snapshot via the pressure ctors; the bridge's 3 FINGER_DOWN/MOTION/UP handlers forwardevent.tfinger.pressure. Pinned the getter + both ctors in the freeze test + documented indocs/input-public-api-frozen.md. Tests: TouchLocation ctor/default + Equals/hash/ ToString-ignore-pressure (TouchInputTests), and end-to-end pressure through GetState with a MOTION update (SdlInputBridgeTouchGestureTests, synthetic finger events).ctest -L inputgreen; ASan-clean. Files: TouchLocation.hpp/.cpp, InputManager.hpp/.cpp, SdlInputBridge.cpp, freeze test, frozen-API doc, TouchInputTests.cpp, SdlInputBridgeTouchGestureTests.cpp. - N-018 done (2026-07-06):
CNA::Input::Power::GetInfoEXT(out secondsLeft, out percent) -> PowerStateEXT— host system battery/charge, reusing the sharedPowerStateEXTfrom N-009b. Established the "standalone CNA::Input type + injectable system seam" pattern (reusable for N-016/N-017): newISystemPowerBackendseam (include/CNA/Internal/Input/SystemPowerBackend.hpp.cpp, real =SDL_GetPowerInfo) withSetSystemPowerBackendForTests.Powerresets the out-params to -1 then maps SDL->EXT. New public typeinclude/CNA/Input/Power.hpp+src/CNA/ Input/Power.cpp(whole class NOXNA, like Clipboard — additive, so no freeze pin / frozen-doc entry). TestsCnaInputPowerTestdrive a fake backend: exhaustive 6-state mapping with seconds/percent forwarding + the out-param-reset default.ctest -L inputgreen; ASan-clean.
- N-010b done (2026-07-06):
GamePad::GetConnectionStateEXT -> CNA::Input:: GamePadConnectionStateEXT {Unknown,Wired,Wireless}— how the pad is attached. New enum headerinclude/CNA/Input/GamePadConnectionState.hpp. SeamGetGamepadConnectionState(real =SDL_GetGamepadConnectionState; fake = config, returns INVALID for an unknown device). Bridge maps SDL {Invalid,Unknown}->Unknown, Wired->Wired, Wireless->Wireless; disconnected slot -> Unknown. Pinned in the freeze test + documented. Tests: 4-state SDL->EXT mapping + disconnected path.ctest -L inputgreen; ASan-clean. Files: GamePadConnectionState.hpp (new), SdlGamepadBackend.hpp/.cpp, FakeSdlGamepadBackend.hpp, SdlInputBridge.hpp/.cpp, GamePad.hpp/.cpp, freeze test, frozen-API doc, SdlGamepadBackendTests.cpp. - N-010 done (2026-07-06):
GamePad::Get{Name,Path,Serial}EXT -> std::string+GetFirmwareVersionEXT -> uint16+GetSteamHandleEXT -> uint64— device metadata via the gamepad seam. Seam gainsGetGamepad{Name,Path,Serial,FirmwareVersion,SteamHandle}(real = the matching SDL getters, with NULL->"" for the string ones; fake = canned config). Bridge getters return ""/0 for a disconnected slot. Pinned all five in the freeze test + documented indocs/input-public-api-frozen.md. Tests: canned-value forwarding + disconnected empties.ctest -L inputgreen; ASan-clean. Split the tracker's original N-010 into metadata (this commit) + N-010b connection-state (new enum). Files: SdlGamepadBackend.hpp/.cpp, FakeSdlGamepadBackend.hpp, SdlInputBridge.hpp/.cpp, GamePad.hpp/.cpp, freeze test, frozen-API doc, SdlGamepadBackendTests.cpp. - N-011 done (2026-07-06):
GamePad::GetButtonLabelEXT(player, Buttons) -> CNA::Input:: GamePadButtonLabelEXT {Unknown,A,B,X,Y,Cross,Circle,Square,Triangle}— the printed glyph for a face button, so UI prompts show the right symbol per controller family. New enum headerinclude/CNA/Input/GamePadButtonLabel.hpp. Seam:ISdlGamepadBackend::GetGamepadButtonLabel(real =SDL_GetGamepadButtonLabel; fake = per-buttonbuttonLabelsconfig map). Bridge addstry_convert_xna_button_to_sdl(publicButtons->SDL_GamepadButton, inverse of the existing SDL->internal map) +sdl_button_label_to_ext, andGetButtonLabelreturns Unknown for a disconnected pad or a non-physicalButtonsvalue (stick dirs/triggers) without touching the device. Pinned in the freeze test + documented indocs/input-public-api-frozen.md. Tests: Xbox glyphs, PlayStation glyphs (full label mapping), and the Unknown paths (non-physical / unlabeled / disconnected).ctest -L inputgreen; ASan-clean. Files: GamePadButtonLabel.hpp (new), SdlGamepadBackend.hpp/.cpp, FakeSdlGamepadBackend.hpp, SdlInputBridge.hpp/.cpp, GamePad.hpp/.cpp, freeze test, frozen-API doc, SdlGamepadBackendTests.cpp. - N-009b done (2026-07-06):
GamePad::GetPowerInfoEXT(player, out percent)— reads the pad's battery/charge state via the gamepad seam. Introduced the shared public enumCNA::Input::PowerStateEXT {Error,Unknown,OnBattery,NoBattery,Charging,Charged}(include/CNA/Input/PowerState.hpp), mirroringSDL_PowerState; N-018 (system Power) will reuse it. Seam:ISdlGamepadBackend::GetGamepadPowerInfo(real =SDL_GetGamepadPowerInfo; fake =FakeGamepadConfig.powerState/powerPercent). BridgeGetPowerInforesolves the slot, maps SDL→EXT, and returnsError+percent=-1 when disconnected. Pinned in the freeze test + documented indocs/input-public-api-frozen.md. Tests: exhaustive SDL_PowerState→PowerStateEXT mapping (6 states) with percent round-trip + disconnected→Error.ctest -L inputgreen; ASan-clean. Files: PowerState.hpp (new), SdlGamepadBackend.hpp/.cpp, FakeSdlGamepadBackend.hpp, SdlInputBridge.hpp/.cpp, GamePad.hpp/.cpp, freeze test, frozen-API doc, SdlGamepadBackendTests.cpp. - N-009 done (2026-07-06):
GamePad::Get/SetPlayerIndexEXT— reads/sets the SDL device player index (the 0-based player-number LED) through the injectable gamepad seam. Established the "gamepad-seam-extension" flow: addedGetGamepadPlayerIndex/SetGamepadPlayerIndextoISdlGamepadBackend(real =SDL_Get/SetGamepadPlayerIndex) + the fake (FakeGamepadConfig.playerIndex+setPlayerIndexCalls/lastSetPlayerIndexintrospection); bridgeGet/SetPlayerIndexresolve the slot and return -1/false when disconnected; publicGamePad::Get/SetPlayerIndexEXT(NOXNA) delegate. Pinned both inPublicApiInputSignatureFreeze Tests+ documented indocs/input-public-api-frozen.md. Tests:FakeGamepadTest.PlayerIndex RoundTripsThroughBackend(Get reads device index, Set forwards + Get reads back) +...IsSafe ForDisconnectedSlot(Get→-1, Set→false, backend untouched).ctest -L input100% green; ASan-clean. Split the tracker's old N-009 into player-index (this commit) + N-009b battery/power. Files: SdlGamepadBackend.hpp/.cpp, FakeSdlGamepadBackend.hpp, SdlInputBridge.hpp/.cpp, GamePad.hpp/.cpp, freeze test, frozen-API doc, SdlGamepadBackendTests.cpp. - N-005 done (2026-07-06): Mouse horizontal scroll wheel EXT (reverses DEC-18's drop of
wheel.x).MouseState::getHorizontalScrollWheelValueEXTProperty+ a NOXNA 9-arg ctor (8-arg XNA ctor unchanged, leaves it 0);InputManagergained aHorizontalScrollWheelValueaccumulator +AddHorizontalScroll WheelDelta; the bridge MOUSE_WHEEL handler now accumulates(int)wheel.x * 120(same cast-then-scale notch truncation as vertical). Excluded from Equals/GetHashCode so those stay FNA-frozen. Established the "NOXNA-member-on-frozen-type" flow: pinned the getter + 9-arg ctor inPublicApiInputSignatureFreeze Tests, documented both indocs/input-public-api-frozen.md. Tests: rewrote the oldHorizontalWheelIs Ignoredinto independence + 120-notch accumulation + truncation tests, + 3 MouseState ctor/equality tests.ctest -L input100% green; ASan-clean. Files: MouseState.hpp/.cpp, InputManager.hpp/.cpp, SdlInputBridge.cpp, freeze test, frozen-API doc, 2 test files. - N-001 done (2026-07-06):
CNA::Input::Clipboard— new publicCNA::Inputnamespace established (include/CNA/Input/Clipboard.hpp,src/CNA/Input/Clipboard.cpp).GetTextEXT/SetTextEXT/HasTextEXTwrap SDL3 clipboard (SDL_free'd read). TestsCnaInputClipboardTest(UTF-8 round-trip + empty) — 2 tests, Xvfb-gated with GTEST_SKIP fallback. Added*CnaInput*toCNA_INPUT_TEST_FILTER(one-time, covers all future CNA::Input suites namedCnaInput*).ctest -L input100% green; ASan-clean. Convention set: newCNA::Inputtypes areNOXNAat the class, methods keep theEXTsuffix, test suites areCnaInput<Type>Test.