[PR #216] [MERGED] Map trackpads to joysticks #241

Closed
opened 2026-10-22 05:08:07 +00:00 by roman · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/safijari/openpuck/pull/216
Author: @Philip-Scott
Created: 8/4/2026
Status: ✅ Merged
Merged: 8/12/2026
Merged by: @safijari

Base: main ← Head: main


📝 Commits (9)

  • ff15c23 Add trackpad-to-joystick mapping configurable from the WebUI
  • 07328c3 Attach test build: OpenPuck .uf2 (trackpad-to-joystick)
  • 41d72fe Move trackpad->stick config fields off the per-type cfg range
  • ef66bab Allow manual dispatch of the Build workflow
  • eef0959 Revert ci and .uf2 file
  • 5e459d3 Touchpad does not override stick
  • e2b064e Code review
  • 71222cc Correct docs
  • 69c9b8b Merge pull request #1 from Philip-Scott/copilot/allow-mapping-trackpads-to-joysticks

📊 Changes

14 files changed (+233 additions, -45 deletions)

View changed files

📝 CODE_MAP.md (+1 -0)
📝 OpenPuck/config.cpp (+22 -2)
📝 OpenPuck/config.h (+12 -0)
📝 OpenPuck/gamepad_util.cpp (+51 -0)
📝 OpenPuck/gamepad_util.h (+14 -0)
📝 OpenPuck/mode_hidgyro.cpp (+16 -10)
📝 OpenPuck/mode_ps3.cpp (+6 -4)
📝 OpenPuck/mode_ps5.cpp (+16 -9)
📝 OpenPuck/mode_switch_hori.cpp (+6 -5)
📝 OpenPuck/mode_switch_pro.cpp (+4 -2)
📝 OpenPuck/mode_xinput.cpp (+11 -5)
📝 OpenPuck/webusb_config.cpp (+32 -3)
📝 docs/PROTOCOL.md (+9 -0)
📝 docs/index.html (+33 -5)

📄 Description

One thing I love about the touchpads is using them for movement by emulating the sticks. On this PR I've added the ability for us to map touchpads to sticks on any of the modes such as the Switch.

https://github.com/user-attachments/assets/350a161c-5e26-4856-bb6f-0434c27880c6

AI Generated PR Description:


Adds a per-emulated-controller-type trackpad → joystick mapping, configurable from the WebUSB panel. Each type (Xbox / Switch / DS4 / DS5) gets two selects — Left trackpad → stick and Right trackpad → stick — each Off (touchpad) / Left stick / Right stick.

Semantics — a mapped pad blends with its stick, it does not replace it. While the pad is touched, each axis reports whichever of the two sources is deflected further from center (sign preserved), so the pad and the physical stick stay usable at the same time. An untouched pad contributes nothing and the physical stick passes straight through — which is also why a release can never leave a stale pad value stuck on an axis (a capacitive pad can't express a flick back to center). A mapped pad additionally stops reporting as a DS4/DS5 touchpad contact and stops driving the Xbox-mode mouse, so the host never sees the same finger twice.

Config (config.h / config.cpp)

  • g_padStickCfg[ET_COUNT][2] — {left pad, right pad} → PS_OFF / PS_LEFT / PS_RIGHT, off by default.
  • Persisted in the Cfg tail, not inside TypeCfg, so an existing cfg.bin still loads and keeps every other setting (a short file leaves the new bytes at the 0xFF prefill → compiled default, per CFG_LEN_MIN).
  • g_padStick[2] live mirror, refreshed by applyActiveType() and forced to PS_OFF for ET_NONE, so puck/lizard modes are untouched.

Input path (gamepad_util)

  • padStickBlend() applies the mapping; slotSticks() wraps it as "the sticks a mode should send".
  • Wired into the Switch Hori, Switch Pro, DS3, DS4/HIDGYRO and DS5 report builders:
int16_t lx, ly, rx, ry;
slotSticks(slot, &lx, &ly, &rx, &ry);
out[0] = swStick(lx, false);
  • Xbox mode calls padStickBlend() directly against the raw 0x45 offsets (16/18 left pad, 22/24 right pad) because rfXboxGamepad decodes the report in place and never reads g_in.
  • PS modes gate their touch contacts on the mapping; rfXboxMouse treats a mapped right pad as untouched so its glide velocity decays instead of stopping dead.
  • strongerAxis() clamps the -32768 magnitude to 32767 before comparing — negating INT16_MIN overflows, which would make a full-deflection axis lose to anything.

WebUSB (webusb_config.cpp, protocol v20)

  • Status blob grows to p[187..194] (2 B per type); WB_PAYLEN 185 → 193, still inside the 256 B vendor TX FIFO.
  • Setter fields 80..87 = 80 + type*2 + pad, range-checked against PS_MAX. They must stay clear of the per-type cfg range 40..75, which is claimed before the field switch runs.
  • Writing a field for the active type re-runs applyActiveType() so the change takes effect without a reboot.

Panel (docs/index.html)

  • Two selects per type tab, capability-gated on blob version ≥ 20 (hidden entirely on older firmware rather than shown dead).
  • Included in backup export/import; the key is omitted for pre-v20 pucks so a restore can't replay defaults over a newer puck's settings.

docs/PROTOCOL.md documents the new blob tail and setter fields.

Cross-task note

g_padStick[] is written from the loop task (applyActiveType(), boot and the WebUSB setter) and read from the usbd task by jcInputPrefix (Switch Pro). It's a plain uint8_t[2] — byte writes, worst case one stale frame — matching how g_abSwap / g_back[] already behave.

Testing

Verified on a physical puck using: Steam's Test Device Inputs section, and on a Switch 2


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/safijari/openpuck/pull/216 **Author:** [@Philip-Scott](https://github.com/Philip-Scott) **Created:** 8/4/2026 **Status:** ✅ Merged **Merged:** 8/12/2026 **Merged by:** [@safijari](https://github.com/safijari) **Base:** `main` ← **Head:** `main` --- ### 📝 Commits (9) - [`ff15c23`](https://github.com/safijari/openpuck/commit/ff15c232fcb342e321bd1aa1f65a41e8725521ca) Add trackpad-to-joystick mapping configurable from the WebUI - [`07328c3`](https://github.com/safijari/openpuck/commit/07328c3988410f1e1573aa16195502c5a614ae14) Attach test build: OpenPuck .uf2 (trackpad-to-joystick) - [`41d72fe`](https://github.com/safijari/openpuck/commit/41d72fe4f8dab0a4aadad4a6c3ab5a045bdbab07) Move trackpad->stick config fields off the per-type cfg range - [`ef66bab`](https://github.com/safijari/openpuck/commit/ef66babd127bd9fb5d8ecd043e20072ec1599579) Allow manual dispatch of the Build workflow - [`eef0959`](https://github.com/safijari/openpuck/commit/eef09590cdccb817ce3eeea1b470e8469033c7d3) Revert ci and .uf2 file - [`5e459d3`](https://github.com/safijari/openpuck/commit/5e459d36a0f1ee711b3d04f103e7ece94972ed2c) Touchpad does not override stick - [`e2b064e`](https://github.com/safijari/openpuck/commit/e2b064ee3273020ca4d645aba49f52ce47e2446e) Code review - [`71222cc`](https://github.com/safijari/openpuck/commit/71222cc08dccdd9178e8f7f4d0b897abb22ca268) Correct docs - [`69c9b8b`](https://github.com/safijari/openpuck/commit/69c9b8b05816a41cddf05c3bd32d0b815ed8f613) Merge pull request #1 from Philip-Scott/copilot/allow-mapping-trackpads-to-joysticks ### 📊 Changes **14 files changed** (+233 additions, -45 deletions) <details> <summary>View changed files</summary> 📝 `CODE_MAP.md` (+1 -0) 📝 `OpenPuck/config.cpp` (+22 -2) 📝 `OpenPuck/config.h` (+12 -0) 📝 `OpenPuck/gamepad_util.cpp` (+51 -0) 📝 `OpenPuck/gamepad_util.h` (+14 -0) 📝 `OpenPuck/mode_hidgyro.cpp` (+16 -10) 📝 `OpenPuck/mode_ps3.cpp` (+6 -4) 📝 `OpenPuck/mode_ps5.cpp` (+16 -9) 📝 `OpenPuck/mode_switch_hori.cpp` (+6 -5) 📝 `OpenPuck/mode_switch_pro.cpp` (+4 -2) 📝 `OpenPuck/mode_xinput.cpp` (+11 -5) 📝 `OpenPuck/webusb_config.cpp` (+32 -3) 📝 `docs/PROTOCOL.md` (+9 -0) 📝 `docs/index.html` (+33 -5) </details> ### 📄 Description One thing I love about the touchpads is using them for movement by emulating the sticks. On this PR I've added the ability for us to map touchpads to sticks on any of the modes such as the Switch. https://github.com/user-attachments/assets/350a161c-5e26-4856-bb6f-0434c27880c6 # AI Generated PR Description: --------- Adds a per-emulated-controller-type **trackpad → joystick** mapping, configurable from the WebUSB panel. Each type (Xbox / Switch / DS4 / DS5) gets two selects — **Left trackpad → stick** and **Right trackpad → stick** — each `Off (touchpad)` / `Left stick` / `Right stick`. **Semantics — a mapped pad *blends* with its stick, it does not replace it.** While the pad is touched, each axis reports whichever of the two sources is deflected further from center (sign preserved), so the pad and the physical stick stay usable at the same time. An untouched pad contributes nothing and the physical stick passes straight through — which is also why a release can never leave a stale pad value stuck on an axis (a capacitive pad can't express a flick back to center). A mapped pad additionally stops reporting as a DS4/DS5 touchpad contact and stops driving the Xbox-mode mouse, so the host never sees the same finger twice. ### Config (`config.h` / `config.cpp`) - `g_padStickCfg[ET_COUNT][2]` — `{left pad, right pad}` → `PS_OFF` / `PS_LEFT` / `PS_RIGHT`, off by default. - Persisted in the **`Cfg` tail**, not inside `TypeCfg`, so an existing `cfg.bin` still loads and keeps every other setting (a short file leaves the new bytes at the `0xFF` prefill → compiled default, per `CFG_LEN_MIN`). - `g_padStick[2]` live mirror, refreshed by `applyActiveType()` and forced to `PS_OFF` for `ET_NONE`, so puck/lizard modes are untouched. ### Input path (`gamepad_util`) - `padStickBlend()` applies the mapping; `slotSticks()` wraps it as "the sticks a mode should send". - Wired into the Switch Hori, Switch Pro, DS3, DS4/HIDGYRO and DS5 report builders: ```c int16_t lx, ly, rx, ry; slotSticks(slot, &lx, &ly, &rx, &ry); out[0] = swStick(lx, false); ``` - Xbox mode calls `padStickBlend()` directly against the raw `0x45` offsets (16/18 left pad, 22/24 right pad) because `rfXboxGamepad` decodes the report in place and never reads `g_in`. - PS modes gate their touch contacts on the mapping; `rfXboxMouse` treats a mapped right pad as untouched so its glide velocity decays instead of stopping dead. - `strongerAxis()` clamps the `-32768` magnitude to `32767` before comparing — negating `INT16_MIN` overflows, which would make a full-deflection axis lose to anything. ### WebUSB (`webusb_config.cpp`, protocol **v20**) - Status blob grows to `p[187..194]` (2 B per type); `WB_PAYLEN` 185 → 193, still inside the 256 B vendor TX FIFO. - Setter fields **`80..87`** = `80 + type*2 + pad`, range-checked against `PS_MAX`. They must stay clear of the per-type cfg range `40..75`, which is claimed before the field switch runs. - Writing a field for the active type re-runs `applyActiveType()` so the change takes effect without a reboot. ### Panel (`docs/index.html`) - Two selects per type tab, capability-gated on blob version ≥ 20 (hidden entirely on older firmware rather than shown dead). - Included in backup export/import; the key is omitted for pre-v20 pucks so a restore can't replay defaults over a newer puck's settings. `docs/PROTOCOL.md` documents the new blob tail and setter fields. ### Cross-task note `g_padStick[]` is written from the loop task (`applyActiveType()`, boot and the WebUSB setter) and read from the usbd task by `jcInputPrefix` (Switch Pro). It's a plain `uint8_t[2]` — byte writes, worst case one stale frame — matching how `g_abSwap` / `g_back[]` already behave. ### Testing Verified on a physical puck using: Steam's Test Device Inputs section, and on a Switch 2 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
roman 2026-10-22 05:08:07 +00:00
Sign in to join this conversation.
No labels
pull-request
wontfix
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
safijari/openpuck#241
No description provided.