Phase 1 of controller-support epic: labelle-toolkit/labelle-engine#609 — centerpiece (Android TV is sokol-only).
Goal
A sokol+Android gamepad detection source: detect controllers connected/removed and supply a stable reconnection key, via JNI to Android's InputManager — independent of sokol's render/input pipeline.
Why this is the priority
Android TV is sokol-only (verified — no other backend has an Android template). sokol has zero gamepad support today and owns the AInputQueue with no raw-event hook. But hotplug detection does not need the event stream — InputManager is reachable purely via JNI.
Available hook
sapp_android_get_native_activity() is exposed by the sokol fork (app.zig:2470, currently unused) → gives the ANativeActivity → JNIEnv. sokol here is a labelle fork (commit 887b30f), so adding a tiny JNI shim/companion is feasible.
Scope
Build a gamepad_source sub-package (in-tree, per the sub-package convention) with an Android impl:
- Via JNI:
InputManager.getInputDeviceIds() to enumerate at startup; register an InputDeviceListener (onInputDeviceAdded/Removed) → push GamepadEvents into a ring buffer.
- Identity:
InputDevice.getDescriptor() → derive a stable guid; getName() → name; getSources() → set source_class (gamepad vs dpad_remote — TV remotes must be distinguishable).
- Marshal listener callbacks (Looper thread) safely into the ring buffer the engine drains in tick.
- Wire
pollGamepadEvents for the sokol backend on Android to this source (engine fallback path from Phase 0).
- Manifest: ensure
<uses-feature android.hardware.gamepad required="false"> in the Android template; keep D-pad navigability; intercept controller-B → KEYCODE_BACK so it doesn't exit the app.
Acceptance criteria
Non-goals (→ Phase 3, full-gameplay state)
- Button/axis state is NOT in this issue. Reading analog state on sokol+Android requires forwarding
AInputEvents (sokol-fork patch) or Paddleboat/AGDK + semantic mapping — tracked as the Phase 3 epic. This issue delivers detection/removal/identity only.
Phase 1 of controller-support epic: labelle-toolkit/labelle-engine#609 — centerpiece (Android TV is sokol-only).
Goal
A sokol+Android gamepad detection source: detect controllers connected/removed and supply a stable reconnection key, via JNI to Android's
InputManager— independent of sokol's render/input pipeline.Why this is the priority
Android TV is sokol-only (verified — no other backend has an Android template). sokol has zero gamepad support today and owns the AInputQueue with no raw-event hook. But hotplug detection does not need the event stream —
InputManageris reachable purely via JNI.Available hook
sapp_android_get_native_activity()is exposed by the sokol fork (app.zig:2470, currently unused) → gives theANativeActivity→ JNIEnv. sokol here is a labelle fork (commit887b30f), so adding a tiny JNI shim/companion is feasible.Scope
Build a
gamepad_sourcesub-package (in-tree, per the sub-package convention) with an Android impl:InputManager.getInputDeviceIds()to enumerate at startup; register anInputDeviceListener(onInputDeviceAdded/Removed) → pushGamepadEvents into a ring buffer.InputDevice.getDescriptor()→ derive a stableguid;getName()→name;getSources()→ setsource_class(gamepadvsdpad_remote— TV remotes must be distinguishable).pollGamepadEventsfor the sokol backend on Android to this source (engine fallback path from Phase 0).<uses-feature android.hardware.gamepad required="false">in the Android template; keep D-pad navigability; intercept controller-B →KEYCODE_BACKso it doesn't exit the app.Acceptance criteria
guid.source_class = dpad_remote, real pad reportsgamepad.Non-goals (→ Phase 3, full-gameplay state)
AInputEvents (sokol-fork patch) or Paddleboat/AGDK + semantic mapping — tracked as the Phase 3 epic. This issue delivers detection/removal/identity only.