Follow-up to #50: native gamepad + touch hardware bindings (GameController / NSTouch) #51

Closed
opened 2026-08-31 16:15:07 +02:00 by orkun · 1 comment
Owner

Follow-up to #50, which shipped the full input device-state layer (53bb441): multi-key held state, analog axes/vectors, mouse, and a Godot-style injection API (Input.press / Input.set_mouse / Input.set_pad / Input.set_touch) that already drives gamepads and touch deterministically for replays, AI, and network-fed input. The read APIs (Input.pad_button/pad_axis/pad_connected, Input.touch_count/touch_x/touch_y) are complete and tested; this tracks the remaining native hardware bindings that feed the same injected state on a real device.

  • Gamepad — GameController.framework: enumerate connected controllers, poll buttons/sticks/triggers each frame into in_pad_* (via Input.set_pad), map to SDL standard names, and emit hotplug PadConnected / PadDisconnected events. A new native binding alongside cocoa.ll (windowed only; DCE'd headless, like win_held/win_mouse).
  • Touch — NSTouch / trackpad + touchscreen: feed in_touch_* (via Input.set_touch) from platform touch events, and add tap / hold / basic gesture recognition over the point stream.
  • Native mouse position: cocoa.ll currently feeds mouse buttons + wheel; wire locationInWindow into win_mouse (framebuffer-pixel space, y-flipped) so windowed games get the cursor position without Input.set_mouse.

Each binding only populates the state buffers the device layer already reads, so no API changes — purely the OS glue. Related: #50 (the shipped device layer), #7 (action maps + replay).

Follow-up to #50, which shipped the full input device-state layer (53bb441): multi-key held state, analog axes/vectors, mouse, and a Godot-style injection API (`Input.press` / `Input.set_mouse` / `Input.set_pad` / `Input.set_touch`) that already drives gamepads and touch deterministically for replays, AI, and network-fed input. The read APIs (`Input.pad_button`/`pad_axis`/`pad_connected`, `Input.touch_count`/`touch_x`/`touch_y`) are complete and tested; this tracks the remaining **native hardware bindings** that feed the same injected state on a real device. - [ ] **Gamepad — GameController.framework**: enumerate connected controllers, poll buttons/sticks/triggers each frame into `in_pad_*` (via `Input.set_pad`), map to SDL standard names, and emit hotplug `PadConnected` / `PadDisconnected` events. A new native binding alongside cocoa.ll (windowed only; DCE'd headless, like `win_held`/`win_mouse`). - [ ] **Touch — NSTouch / trackpad + touchscreen**: feed `in_touch_*` (via `Input.set_touch`) from platform touch events, and add tap / hold / basic gesture recognition over the point stream. - [ ] **Native mouse position**: cocoa.ll currently feeds mouse buttons + wheel; wire `locationInWindow` into `win_mouse` (framebuffer-pixel space, y-flipped) so windowed games get the cursor position without `Input.set_mouse`. Each binding only populates the state buffers the device layer already reads, so no API changes — purely the OS glue. Related: #50 (the shipped device layer), #7 (action maps + replay).
Author
Owner

Shipped in d6ca320 — the native hardware side of the #50 device layer, feeding the same state buffers the read APIs already consume (no API changes).

Native mouse position — win_mouse in cocoa.ll now reads the live cursor via -mouseLocationOutsideOfEventStream, converts window points to framebuffer pixels and y-flips it, so windowed games get Input.mouse_x/y without Input.set_mouse (the W_mx/W_my slots were declared but never written before).

Gamepad — GameController.framework — a new win_pad binding walks [GCController controllers] each frame and packs every connected extended gamepad into in_pad_* (via Input.set_pad): buttons in SDL_GameControllerButton order (A/B/X/Y, shoulders, menu/options, dpad, stick clicks, triggers), and left/right thumbsticks as 16.16 fixed with the Y axis negated to match SDL's "down is positive" convention. objc_getClass("GCController") reaches the classes by name, so a plain -framework link dead-strips it — the windowed link now uses -needed_framework,GameController to force the load command (asserted by a new regression test). Hotplug is implicit: a disconnected pad simply stops appearing in controllers, so its slot reports pad_connected == false the next frame.

Touch — NSTouch — the view now accepts indirect (trackpad) touches (setAllowedTouchTypes:), and its touchesBegan/Moved/Ended/Cancelled handlers snapshot the currently-touching set into in_touch_* (via Input.set_touch), mapping normalizedPosition (0..1, bottom-left) to framebuffer pixels.

Native mouse buttons/wheel were already wired in #50; this completes the position piece noted in the third checkbox.

The whole binding is DCE'd out of a headless build (all three are only reached on the is_windowed() branch), and Input.set_pad / set_touch injection keeps working on every target, so replays, AI, and network-fed input are unchanged. platform.js gains zero-fill stubs for the device imports so a windowed wasm build still links. Full suite + self-host suite green (79 + 29).

Shipped in d6ca320 — the native hardware side of the #50 device layer, feeding the same state buffers the read APIs already consume (no API changes). **Native mouse position** — `win_mouse` in `cocoa.ll` now reads the live cursor via `-mouseLocationOutsideOfEventStream`, converts window points to framebuffer pixels and y-flips it, so windowed games get `Input.mouse_x/y` without `Input.set_mouse` (the `W_mx`/`W_my` slots were declared but never written before). **Gamepad — GameController.framework** — a new `win_pad` binding walks `[GCController controllers]` each frame and packs every connected extended gamepad into `in_pad_*` (via `Input.set_pad`): buttons in `SDL_GameControllerButton` order (A/B/X/Y, shoulders, menu/options, dpad, stick clicks, triggers), and left/right thumbsticks as 16.16 fixed with the Y axis negated to match SDL's "down is positive" convention. `objc_getClass("GCController")` reaches the classes by name, so a plain `-framework` link dead-strips it — the windowed link now uses `-needed_framework,GameController` to force the load command (asserted by a new regression test). Hotplug is implicit: a disconnected pad simply stops appearing in `controllers`, so its slot reports `pad_connected == false` the next frame. **Touch — NSTouch** — the view now accepts indirect (trackpad) touches (`setAllowedTouchTypes:`), and its `touchesBegan/Moved/Ended/Cancelled` handlers snapshot the currently-touching set into `in_touch_*` (via `Input.set_touch`), mapping `normalizedPosition` (0..1, bottom-left) to framebuffer pixels. **Native mouse buttons/wheel** were already wired in #50; this completes the position piece noted in the third checkbox. The whole binding is DCE'd out of a headless build (all three are only reached on the `is_windowed()` branch), and `Input.set_pad` / `set_touch` injection keeps working on every target, so replays, AI, and network-fed input are unchanged. `platform.js` gains zero-fill stubs for the device imports so a windowed wasm build still links. Full suite + self-host suite green (79 + 29).
orkun closed this issue 2026-08-31 16:38:00 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#51
No description provided.