mode_lizard.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. #include "mode_lizard.h"
  2. #include "lizard_map.h"
  3. #include "triton.h"
  4. #include "config.h"
  5. #include "usb_tx.h"
  6. #include <string.h>
  7. // Evaluate the binding table for ONE controller's button word, merging its keyboard/mouse-button/
  8. // consumer outputs into the shared accumulators. Pulled out of rfLizard so every bonded controller can
  9. // feed the same outputs. doRpadMouse/doLpadScroll are map-level (set if such a binding exists at all).
  10. static void lizardEvalSlot(uint32_t buttons, uint8_t &outMod,
  11. uint8_t outKeys[6], uint8_t &nKeys, uint8_t &outMBtn,
  12. uint8_t &consumerBits, bool &doRpadMouse,
  13. bool &doLpadScroll)
  14. {
  15. // kbdConsumed (per controller): trig bits claimed by a hold-modifier binding so simpler
  16. // single-button bindings sharing those bits are suppressed for THIS controller.
  17. uint32_t kbdConsumed = 0;
  18. for (uint8_t i = 0; i < g_lizardMap.count; i++) {
  19. const LizardBinding &b = g_lizardMap.bindings[i];
  20. if (b.outType == LZ_OUT_NONE)
  21. continue;
  22. if (b.outType == LZ_OUT_MOUSE_AXIS) {
  23. if (b.outData[0] == LZ_MSRC_RPAD)
  24. doRpadMouse = true;
  25. // LZ_MSRC_LSTICK and LZ_MSRC_GYRO could be added later
  26. continue;
  27. }
  28. if (b.outType == LZ_OUT_SCROLL) {
  29. if (b.outData[0] == LZ_MSRC_LPAD)
  30. doLpadScroll = true;
  31. continue;
  32. }
  33. // Check hold modifier (AND) then trigger (any-of OR)
  34. if (b.holdMask && (buttons & b.holdMask) != b.holdMask)
  35. continue;
  36. if (b.trigMask && (buttons & b.trigMask) == 0)
  37. continue;
  38. switch (b.outType) {
  39. case LZ_OUT_MOUSE_BTN:
  40. if (!b.trigMask || (buttons & b.trigMask))
  41. outMBtn |= b.outData[0];
  42. break;
  43. case LZ_OUT_KBD_CHORD:
  44. // suppress if trig bits already claimed by a hold-modifier binding
  45. if (b.holdMask == 0 && b.trigMask != 0 &&
  46. (b.trigMask & kbdConsumed))
  47. break;
  48. outMod |= b.outData[0];
  49. for (int j = 1; j < 7 && b.outData[j]; j++) {
  50. if (nKeys < 6)
  51. outKeys[nKeys++] = b.outData[j];
  52. }
  53. // claim trig bits so simpler bindings don't double-fire
  54. if (b.holdMask != 0)
  55. kbdConsumed |= b.trigMask;
  56. break;
  57. case LZ_OUT_CONSUMER:
  58. if (g_usbMode == MODE_LIZARD)
  59. consumerBits |= b.outData[0];
  60. break;
  61. default:
  62. break;
  63. }
  64. }
  65. }
  66. // Add the virtual stick-deflection bits so bindings can trigger on left-stick direction.
  67. // The LZ_BTN_LSTICK_* flags live on bits 28..31, which OVERLAP real controller inputs: bit 28 = right
  68. // grip touch, bit 29 = left grip touch (PROTOCOL.md §8). Merely holding the controller sets the grip
  69. // bits, which would masquerade as an L-stick deflection and fire the bound key. Clear the top 4 bits of
  70. // the physical word (grips are not a bindable lizard input) so these flags reflect ONLY stick deflection.
  71. static uint32_t lizardButtons(const PuckInput &in)
  72. {
  73. uint32_t buttons = in.buttons & 0x0FFFFFFFu;
  74. if (in.lx > 12000)
  75. buttons |= LZ_BTN_LSTICK_RT;
  76. if (in.lx < -12000)
  77. buttons |= LZ_BTN_LSTICK_LF;
  78. if (in.ly < -12000)
  79. buttons |= LZ_BTN_LSTICK_DN;
  80. if (in.ly > 12000)
  81. buttons |= LZ_BTN_LSTICK_UP;
  82. return buttons;
  83. }
  84. // Per-slot glide/scroll integrators + last-sent report state for rfLizard. These are FILE-SCOPE (not
  85. // function-local statics) so rfLizardRelease() can reset them on a lizard->Steam handoff: without that reset
  86. // a key/mouse-button that was DOWN the instant Steam takes over is never released (the change-dedup below
  87. // thinks it is still "down") and sticks on the desktop until a reconnect/power-cycle.
  88. static int prx[NSLOT] = { 0 }, pry[NSLOT] = { 0 };
  89. static bool prt[NSLOT] = { false };
  90. static float vx[NSLOT] = { 0 }, vy[NSLOT] = { 0 }, rmx = 0, rmy = 0;
  91. static int ply[NSLOT] = { 0 };
  92. static bool plt[NSLOT] = { false };
  93. static float sacc = 0;
  94. static uint8_t pmbtn = 0; // last-sent mouse buttons
  95. static uint8_t prevCC = 0; // last-sent consumer bits
  96. static uint8_t pmod = 0, pkc[6] = {
  97. 0, 0, 0, 0, 0, 0
  98. }; // last-sent kbd modifier + keycodes
  99. void rfLizard(Adafruit_USBD_HID *mdev, Adafruit_USBD_HID *kdev, uint8_t mrid,
  100. uint8_t krid)
  101. {
  102. // ---- merge binding outputs across every bonded controller ----
  103. uint8_t outMod = 0;
  104. uint8_t outKeys[6] = { 0, 0, 0, 0, 0, 0 };
  105. uint8_t nKeys = 0;
  106. uint8_t outMBtn = 0;
  107. bool doRpadMouse = false, doLpadScroll = false;
  108. uint8_t consumerBits = 0;
  109. for (int s = 0; s < NSLOT; s++) {
  110. if (!g_slot[s].used)
  111. continue;
  112. lizardEvalSlot(lizardButtons(g_in[s]), outMod, outKeys, nKeys,
  113. outMBtn, consumerBits, doRpadMouse,
  114. doLpadScroll);
  115. }
  116. // ---- right pad -> mouse motion with glide ----
  117. // Per-slot velocity/last-position so each controller's pad integrates independently; the resulting
  118. // deltas are SUMMED into one cursor (sub-pixel carry rmx/rmy is shared -- one mouse). State is
  119. // file-scope (see the block above rfLizard) so rfLizardRelease() can zero it on a handoff.
  120. int dx = 0, dy = 0;
  121. if (doRpadMouse) {
  122. float sumx = 0, sumy = 0;
  123. float f = g_mFric / 100.0f;
  124. for (int s = 0; s < NSLOT; s++) {
  125. if (!g_slot[s].used) {
  126. vx[s] = vy[s] = 0;
  127. prt[s] = false;
  128. continue;
  129. }
  130. const PuckInput &in = g_in[s];
  131. bool rtouch = (in.buttons & TB_RPADT) != 0;
  132. int rx = in.rpx, ry = in.rpy;
  133. if (rtouch && prt[s]) {
  134. vx[s] += (rx - prx[s]);
  135. vy[s] += (ry - pry[s]);
  136. }
  137. if (rtouch) {
  138. prx[s] = rx;
  139. pry[s] = ry;
  140. }
  141. prt[s] = rtouch;
  142. sumx += vx[s];
  143. sumy += vy[s];
  144. // friction = glide/decay (per slot)
  145. vx[s] *= f;
  146. vy[s] *= f;
  147. if (vx[s] > -1 && vx[s] < 1)
  148. vx[s] = 0;
  149. if (vy[s] > -1 && vy[s] < 1)
  150. vy[s] = 0;
  151. }
  152. // Y inverted; *10 = desktop cursor sensitivity (g_mDiv 64 -> eff 640)
  153. float mxf = sumx / (float)(g_mDiv * 10) + rmx,
  154. myf = -(sumy / (float)(g_mDiv * 10)) + rmy;
  155. dx = (int)mxf;
  156. dy = (int)myf;
  157. rmx = mxf - dx;
  158. rmy = myf - dy; // sub-pixel carry
  159. if (dx > 127)
  160. dx = 127;
  161. if (dx < -127)
  162. dx = -127;
  163. if (dy > 127)
  164. dy = 127;
  165. if (dy < -127)
  166. dy = -127;
  167. }
  168. // ---- left pad -> scroll wheel ----
  169. // Per-slot last-position; accumulator shared (one wheel). Reset only when NO controller is scrolling.
  170. // State is file-scope (see the block above rfLizard) so rfLizardRelease() can zero it on a handoff.
  171. int dw = 0;
  172. if (doLpadScroll) {
  173. bool anyTouch = false;
  174. for (int s = 0; s < NSLOT; s++) {
  175. if (!g_slot[s].used) {
  176. plt[s] = false;
  177. continue;
  178. }
  179. const PuckInput &in = g_in[s];
  180. bool ltouch = (in.buttons & TB_LPADT) != 0;
  181. int ly = in.lpy;
  182. if (ltouch && plt[s])
  183. sacc += (ly - ply[s]) / (float)(g_mDiv * 24);
  184. if (ltouch) {
  185. ply[s] = ly;
  186. anyTouch = true;
  187. }
  188. plt[s] = ltouch;
  189. }
  190. if (!anyTouch)
  191. sacc = 0;
  192. dw = (int)sacc;
  193. sacc -= dw;
  194. if (dw > 15)
  195. dw = 15;
  196. if (dw < -15)
  197. dw = -15;
  198. }
  199. // ---- mouse report ----
  200. // Mouse, consumer and keyboard share ONE endpoint (hid[0]) that stays busy
  201. // until the host next polls. Sending directly under ready() dropped every
  202. // report after the first in a cycle while the shadow state still advanced,
  203. // losing key edges whenever the cursor moved; usbTxHid queues them in order.
  204. if (dx || dy || dw || outMBtn != pmbtn) {
  205. pmbtn = outMBtn;
  206. hid_mouse_report_t m;
  207. m.buttons = outMBtn;
  208. m.x = (int8_t)dx;
  209. m.y = (int8_t)dy;
  210. m.wheel = (int8_t)dw;
  211. m.pan = 0;
  212. usbTxHid(mdev, mrid, &m, sizeof m);
  213. }
  214. // ---- consumer control (edge-triggered) ----
  215. if (g_usbMode == MODE_LIZARD) {
  216. if (consumerBits != prevCC) {
  217. usbTxHid(mdev, 0x03, &consumerBits, 1);
  218. prevCC = consumerBits;
  219. }
  220. }
  221. // ---- keyboard report ----
  222. bool chg = (outMod != pmod);
  223. for (int i = 0; i < 6; i++)
  224. if (outKeys[i] != pkc[i])
  225. chg = true;
  226. if (chg) {
  227. pmod = outMod;
  228. for (int i = 0; i < 6; i++)
  229. pkc[i] = outKeys[i];
  230. uint8_t krep[8] = { outMod, 0, outKeys[0],
  231. outKeys[1], outKeys[2], outKeys[3],
  232. outKeys[4], outKeys[5] };
  233. usbTxHid(kdev, krid, krep, 8);
  234. }
  235. }
  236. // Release everything lizard may currently be holding on the host, then clear the last-sent dedup + glide
  237. // state so a later re-entry re-asserts from scratch. Called by puck_hid when Steam TAKES OVER (the
  238. // lizard->gamepad handoff): that transition is a pure runtime flip with NO USB re-enumeration, so a
  239. // keyboard key / mouse button / consumer key that was DOWN at that instant would otherwise stay latched on
  240. // the desktop until a reconnect or power-cycle (the reported "stuck input after switching modes"). Sends the
  241. // neutral reports unconditionally (not change-gated) because the whole point is to override a stale held
  242. // state that the dedup would suppress. Same usbTxHid path rfLizard uses.
  243. void rfLizardRelease(Adafruit_USBD_HID *mdev, Adafruit_USBD_HID *kdev,
  244. uint8_t mrid, uint8_t krid)
  245. {
  246. uint8_t krep[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // all keys + modifiers up
  247. usbTxHid(kdev, krid, krep, 8);
  248. hid_mouse_report_t m = { 0, 0, 0, 0, 0 }; // no buttons, no motion/wheel
  249. usbTxHid(mdev, mrid, &m, sizeof m);
  250. if (g_usbMode == MODE_LIZARD) {
  251. uint8_t cc = 0; // release any held consumer-control key
  252. usbTxHid(mdev, 0x03, &cc, 1);
  253. }
  254. // Zero the integrators + last-sent shadows so re-entry starts clean (no cursor fling from stale
  255. // velocity, and held keys/buttons re-send correctly on the next rfLizard pass).
  256. for (int s = 0; s < NSLOT; s++) {
  257. prx[s] = pry[s] = 0;
  258. prt[s] = false;
  259. vx[s] = vy[s] = 0;
  260. ply[s] = 0;
  261. plt[s] = false;
  262. }
  263. rmx = rmy = sacc = 0;
  264. pmbtn = 0;
  265. prevCC = 0;
  266. pmod = 0;
  267. for (int i = 0; i < 6; i++)
  268. pkc[i] = 0;
  269. }