mode_lizard.cpp 9.0 KB

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