Skip to content

Input: remotes and gamepads

Every platform’s input arrives as DOM keyboard events, because SDL has already normalised the hardware:

Hardware Arrives as
tvOS Siri Remote arrows, Enter (select), Escape (Menu)
Android TV / Fire TV remote arrows, Enter (D-pad centre), AC Back, media keys
macOS / Linux keyboard a keyboard
Game controllers keys, until you claim them (below)

Each becomes a keydown / keyup with key, code and the legacy keyCode that TV frameworks still key on — Blits and Lightning read 37–40 for the arrows, 13 for enter and 8 for back.

window.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowRight': focusNext(); break
case 'Enter': activate(); break
case 'Escape': goBack(); break // tvOS Menu, Android Back
}
})

Gamepads are the W3C Gamepad API in the standard mapping. gamepadconnected and gamepaddisconnected are trusted GamepadEvents at window, fired as tasks that a pause holds rather than drops — a controller connecting is a fact the page needs after it resumes.

window.addEventListener('gamepadconnected', (e) => {
console.log(e.gamepad.index, e.gamepad.id)
})
function frame () {
const [pad] = navigator.getGamepads()
if (pad?.buttons[0].pressed) jump()
requestAnimationFrame(frame)
}

Keys go to the focused browsing context and to nothing else. When a launcher calls focus() on an <iframe>, the launcher stops receiving input and the instance starts. A focus switch releases keys that were held, the way a controller disconnecting mid-press does, so no context is left with a key down it will never see go up.

window.close() calls the host’s exit path where the host defines one — the Android host finishes the activity. On tvOS, the Menu button at the app’s root does not yet exit to the Home screen; the JavaScript-visible way out is the app’s own window.close() call rather than a guess about Back.