Bladeren bron

Add lizard_map binding system and rewrite mode_lizard to use it

copilot-swe-agent[bot] 4 maanden geleden
bovenliggende
commit
9fb65396d1
4 gewijzigde bestanden met toevoegingen van 391 en 144 verwijderingen
  1. 156 0
      OpenPuck/lizard_map.cpp
  2. 71 0
      OpenPuck/lizard_map.h
  3. 155 136
      OpenPuck/mode_lizard.cpp
  4. 9 8
      OpenPuck/mode_lizard.h

+ 156 - 0
OpenPuck/lizard_map.cpp

@@ -0,0 +1,156 @@
+#include "lizard_map.h"
+#include <Adafruit_LittleFS.h>
+#include <InternalFileSystem.h>
+#include <string.h>
+#include <class/hid/hid.h>
+using namespace Adafruit_LittleFS_Namespace;
+
+LizardMap g_lizardMap;
+
+#define LZ_FILE    "/lizard_map.bin"
+#define LZ_MAGIC   0xB1u
+#define LZ_VERSION 1u
+
+// KB modifier bits (from TinyUSB hid.h)
+#define KM_LCTRL  0x01u
+#define KM_LSHIFT 0x02u
+#define KM_LALT   0x04u
+#define KM_LGUI   0x08u
+
+// Helper: append a binding to g_lizardMap
+static void addBind(uint8_t type, const uint8_t *od7, uint32_t trig,
+		    uint32_t hold)
+{
+	if (g_lizardMap.count >= LZ_MAX_BINDINGS)
+		return;
+	LizardBinding &b = g_lizardMap.bindings[g_lizardMap.count++];
+	b.outType = type;
+	for (int i = 0; i < 7; i++)
+		b.outData[i] = od7[i];
+	b.trigMask = trig;
+	b.holdMask = hold;
+}
+
+static inline void addAxis(uint8_t src, uint8_t gyroAct)
+{
+	uint8_t d[7] = { src, gyroAct, 0, 0, 0, 0, 0 };
+	addBind(LZ_OUT_MOUSE_AXIS, d, 0, 0);
+}
+static inline void addScroll(uint8_t src)
+{
+	uint8_t d[7] = { src, 0, 0, 0, 0, 0, 0 };
+	addBind(LZ_OUT_SCROLL, d, 0, 0);
+}
+static inline void addMouseBtn(uint8_t btn, uint32_t trig, uint32_t hold)
+{
+	uint8_t d[7] = { btn, 0, 0, 0, 0, 0, 0 };
+	addBind(LZ_OUT_MOUSE_BTN, d, trig, hold);
+}
+static inline void addKey(uint8_t mod, uint8_t k0, uint32_t trig,
+			  uint32_t hold)
+{
+	uint8_t d[7] = { mod, k0, 0, 0, 0, 0, 0 };
+	addBind(LZ_OUT_KBD_CHORD, d, trig, hold);
+}
+static inline void addKey3(uint8_t mod, uint8_t k0, uint8_t k1, uint8_t k2,
+			   uint32_t trig, uint32_t hold)
+{
+	uint8_t d[7] = { mod, k0, k1, k2, 0, 0, 0 };
+	addBind(LZ_OUT_KBD_CHORD, d, trig, hold);
+}
+static inline void addConsumer(uint8_t bits, uint32_t trig, uint32_t hold)
+{
+	uint8_t d[7] = { bits, 0, 0, 0, 0, 0, 0 };
+	addBind(LZ_OUT_CONSUMER, d, trig, hold);
+}
+
+void defaultLizardMap()
+{
+	g_lizardMap.count = 0;
+
+	// Analog sources (always active; no trigger mask)
+	addAxis(LZ_MSRC_RPAD, LZ_GYRO_ALWAYS); // right pad → mouse
+	addScroll(LZ_MSRC_LPAD); // left pad → scroll
+
+	// Mouse buttons
+	// right pad click → left mouse
+	addMouseBtn(1, 0x400000u /*TB_RPADC*/, 0);
+	// right trigger (digital click) → left mouse
+	addMouseBtn(1, 0x800000u /*TB_R2*/, 0);
+	// left trigger → right mouse
+	addMouseBtn(2, 0x8000000u /*TB_L2*/, 0);
+	// left pad click → middle mouse
+	addMouseBtn(4, 0x4000000u /*TB_LPADC*/, 0);
+
+	// Hold-modifier shortcuts (Steam-button combos: place BEFORE generic X/L4 so they consume first)
+	// Steam+L4 → Ctrl+Alt+Delete
+	addKey3(KM_LCTRL | KM_LALT, HID_KEY_DELETE, 0, 0, 0x20000u /*TB_L4*/,
+		0x10000u /*TB_STEAM*/);
+	// Steam+X → Win+Ctrl+O (on-screen keyboard)
+	addKey3(KM_LGUI | KM_LCTRL, HID_KEY_O, 0, 0, 0x4u /*TB_X*/,
+		0x10000u /*TB_STEAM*/);
+	// Steam+L5 → volume down (consumer bit1=0x02)
+	addConsumer(0x02, 0x40000u /*TB_L5*/, 0x10000u /*TB_STEAM*/);
+	// Steam+R5 → volume up (consumer bit0=0x01)
+	addConsumer(0x01, 0x100u /*TB_R5*/, 0x10000u /*TB_STEAM*/);
+
+	// Keyboard keys
+	addKey(0, HID_KEY_ENTER, 0x1u /*TB_A*/, 0);
+	addKey(0, HID_KEY_ESCAPE, 0x2u /*TB_B*/, 0);
+	addKey(0, HID_KEY_PAGE_UP, 0x4u /*TB_X*/, 0);
+	addKey(0, HID_KEY_PAGE_DOWN, 0x8u /*TB_Y*/, 0);
+	addKey(0, HID_KEY_TAB, 0x40u /*TB_VIEW*/, 0);
+	addKey(0, HID_KEY_ESCAPE, 0x4000u /*TB_MENU*/, 0);
+
+	// D-pad → arrow keys
+	addKey(0, HID_KEY_ARROW_UP, 0x2000u /*TB_DUP*/, 0);
+	addKey(0, HID_KEY_ARROW_DOWN, 0x400u /*TB_DDN*/, 0);
+	addKey(0, HID_KEY_ARROW_LEFT, 0x1000u /*TB_DLF*/, 0);
+	addKey(0, HID_KEY_ARROW_RIGHT, 0x800u /*TB_DRT*/, 0);
+
+	// Left stick → arrow keys (virtual deflection bits)
+	addKey(0, HID_KEY_ARROW_UP, LZ_BTN_LSTICK_UP, 0);
+	addKey(0, HID_KEY_ARROW_DOWN, LZ_BTN_LSTICK_DN, 0);
+	addKey(0, HID_KEY_ARROW_LEFT, LZ_BTN_LSTICK_LF, 0);
+	addKey(0, HID_KEY_ARROW_RIGHT, LZ_BTN_LSTICK_RT, 0);
+
+	// Shoulder buttons → modifier keys only (no keycode)
+	addKey(KM_LCTRL, 0, 0x80000u /*TB_LB*/, 0);
+	addKey(KM_LALT, 0, 0x200u /*TB_RB*/, 0);
+}
+
+void saveLizardMap()
+{
+	InternalFS.remove(LZ_FILE);
+	File f(InternalFS);
+	if (!f.open(LZ_FILE, FILE_O_WRITE))
+		return;
+	uint8_t hdr[3] = { LZ_MAGIC, LZ_VERSION, g_lizardMap.count };
+	f.write(hdr, 3);
+	f.write((uint8_t *)g_lizardMap.bindings,
+		g_lizardMap.count * sizeof(LizardBinding));
+	f.close();
+}
+
+void loadLizardMap()
+{
+	File f(InternalFS);
+	if (f.open(LZ_FILE, FILE_O_READ)) {
+		uint8_t hdr[3] = { 0, 0, 0 };
+		if (f.read(hdr, 3) == 3 && hdr[0] == LZ_MAGIC &&
+		    hdr[1] == LZ_VERSION && hdr[2] <= LZ_MAX_BINDINGS) {
+			uint8_t cnt = hdr[2];
+			g_lizardMap.count = 0;
+			int got = f.read((uint8_t *)g_lizardMap.bindings,
+					 cnt * sizeof(LizardBinding));
+			if (got == (int)(cnt * sizeof(LizardBinding)))
+				g_lizardMap.count = cnt;
+		}
+		f.close();
+	}
+	// If nothing loaded, install + persist defaults
+	if (g_lizardMap.count == 0) {
+		defaultLizardMap();
+		saveLizardMap();
+	}
+}

+ 71 - 0
OpenPuck/lizard_map.h

@@ -0,0 +1,71 @@
+// lizard_map.h -- configurable input→keyboard/mouse/consumer binding table for lizard (desktop)
+// mode.
+//
+// g_lizardMap replaces the hardcoded button→key map in mode_lizard.cpp with a runtime-configurable
+// list of LizardBinding entries. Each binding maps a trigger condition (button bitmask + optional
+// hold-modifier bitmask) to a single output action. rfLizard() walks the table every input frame.
+//
+// Designed for forward compatibility: the output union can gain a GAMEPAD variant to remap emulated-
+// controller buttons without changing the trigger model, storage format, or WebUSB protocol.
+#pragma once
+#include <stdint.h>
+
+// ---- output type (LizardBinding::outType) ----
+// 0 = disabled; skip this entry.
+#define LZ_OUT_NONE       0u
+// Keyboard chord: outData[0]=modifier bits, outData[1..6]=HID keycodes (up to 6, zero=unused).
+#define LZ_OUT_KBD_CHORD  1u
+// Mouse button: outData[0]=button bitmask (bit0=left, bit1=right, bit2=middle).
+#define LZ_OUT_MOUSE_BTN  2u
+// Mouse axis: outData[0]=source (LZ_MSRC_*), outData[1]=gyro activation (LZ_GYRO_*).
+#define LZ_OUT_MOUSE_AXIS 3u
+// Scroll wheel from an analog source: outData[0]=source (LZ_MSRC_*).
+#define LZ_OUT_SCROLL     4u
+// Consumer control (edge-triggered, MODE_LIZARD only): outData[0]=report bitmask (bit0=vol+, bit1=vol-).
+#define LZ_OUT_CONSUMER   5u
+
+// ---- analog source (outData[0] for MOUSE_AXIS and SCROLL) ----
+#define LZ_MSRC_RPAD      0u // right trackpad (MOUSE_AXIS; touch-gated by TB_RPADT)
+#define LZ_MSRC_LSTICK    1u // left analog stick (MOUSE_AXIS)
+#define LZ_MSRC_GYRO      2u // gyroscope (MOUSE_AXIS; activation-gated, see LZ_GYRO_*)
+#define LZ_MSRC_LPAD      0u // left trackpad (SCROLL; touch-gated by TB_LPADT)
+
+// ---- gyro activation condition (outData[1] when src=LZ_MSRC_GYRO) ----
+#define LZ_GYRO_ALWAYS    0u // gyro always drives mouse
+#define LZ_GYRO_RPAD      1u // only when right trackpad is touched
+#define LZ_GYRO_LSTICK    2u // only when left stick is deflected past threshold
+// only when all bits in LizardBinding::holdMask are held
+#define LZ_GYRO_BTN       3u
+
+// ---- virtual stick-deflection trigger bits (usable in trigMask / holdMask) ----
+// The SC2 controller never sets bits 28..31 in its 0x45 report; rfLizard ORs these in after
+// reading the physical button word so bindings can fire on stick deflection.
+#define LZ_BTN_LSTICK_RT  0x10000000u // lx >  12000
+#define LZ_BTN_LSTICK_LF  0x20000000u // lx < -12000
+#define LZ_BTN_LSTICK_DN  0x40000000u // ly < -12000
+#define LZ_BTN_LSTICK_UP  0x80000000u // ly >  12000
+
+// ---- binding (16 bytes, naturally 4-byte aligned) ----
+// trigMask: (buttons & trigMask) != 0 activates the binding.
+// holdMask: ALL bits must also be set at the same time (AND-guard).
+// MOUSE_AXIS and SCROLL bindings ignore trigMask/holdMask; the analog source drives them.
+struct LizardBinding {
+	uint8_t outType;    // LZ_OUT_*
+	uint8_t outData[7]; // type-specific payload (see constants above)
+	uint32_t trigMask;  // physical/virtual button bits: any-of (OR)
+	uint32_t holdMask;  // physical/virtual button bits: all-of (AND guard)
+};
+
+#define LZ_MAX_BINDINGS 32u
+
+struct LizardMap {
+	uint8_t count;
+	LizardBinding bindings[LZ_MAX_BINDINGS];
+};
+
+extern LizardMap g_lizardMap;
+
+// fill g_lizardMap with defaults (does NOT save to flash)
+void defaultLizardMap();
+void loadLizardMap();
+void saveLizardMap();

+ 155 - 136
OpenPuck/mode_lizard.cpp

@@ -1,84 +1,161 @@
 #include "mode_lizard.h"
+#include "lizard_map.h"
 #include "triton.h"
 #include "config.h"
+#include <string.h>
 
-void rfLizard(const uint8_t *r, Adafruit_USBD_HID *mdev,
-	      Adafruit_USBD_HID *kdev, uint8_t mrid, uint8_t krid)
+void rfLizard(Adafruit_USBD_HID *mdev, Adafruit_USBD_HID *kdev, uint8_t mrid,
+	      uint8_t krid)
 {
-	uint32_t b = btnsOf(r);
-	bool qamHeld = (b & TB_QAM) != 0;
-	// --- right pad -> mouse motion with glide (mirrors mode_xinput's rfXboxMouse) ---
+	const PuckInput &in = g_in[0];
+	uint32_t buttons = in.buttons;
+
+	// Append virtual stick-deflection bits so bindings can trigger on stick direction
+	if (in.lx > 12000)
+		buttons |= LZ_BTN_LSTICK_RT;
+	if (in.lx < -12000)
+		buttons |= LZ_BTN_LSTICK_LF;
+	if (in.ly < -12000)
+		buttons |= LZ_BTN_LSTICK_DN;
+	if (in.ly > 12000)
+		buttons |= LZ_BTN_LSTICK_UP;
+
+	// ---- accumulate outputs from the binding table ----
+	uint8_t outMod = 0;
+	uint8_t outKeys[6] = { 0, 0, 0, 0, 0, 0 };
+	uint8_t nKeys = 0;
+	uint8_t outMBtn = 0;
+	bool doRpadMouse = false, doLpadScroll = false;
+	uint8_t consumerBits = 0;
+
+	// kbdConsumed: any trig bits claimed by a hold-modifier binding so that simpler
+	// single-button bindings sharing those bits are suppressed.
+	uint32_t kbdConsumed = 0;
+
+	for (uint8_t i = 0; i < g_lizardMap.count; i++) {
+		const LizardBinding &b = g_lizardMap.bindings[i];
+		if (b.outType == LZ_OUT_NONE)
+			continue;
+
+		if (b.outType == LZ_OUT_MOUSE_AXIS) {
+			if (b.outData[0] == LZ_MSRC_RPAD)
+				doRpadMouse = true;
+			// LZ_MSRC_LSTICK and LZ_MSRC_GYRO could be added later
+			continue;
+		}
+		if (b.outType == LZ_OUT_SCROLL) {
+			if (b.outData[0] == LZ_MSRC_LPAD)
+				doLpadScroll = true;
+			continue;
+		}
+
+		// Check hold modifier (AND) then trigger (any-of OR)
+		if (b.holdMask && (buttons & b.holdMask) != b.holdMask)
+			continue;
+		if (b.trigMask && (buttons & b.trigMask) == 0)
+			continue;
+
+		switch (b.outType) {
+		case LZ_OUT_MOUSE_BTN:
+			if (!b.trigMask || (buttons & b.trigMask))
+				outMBtn |= b.outData[0];
+			break;
+
+		case LZ_OUT_KBD_CHORD:
+			// suppress if trig bits already claimed by a hold-modifier binding
+			if (b.holdMask == 0 && b.trigMask != 0 &&
+			    (b.trigMask & kbdConsumed))
+				break;
+			outMod |= b.outData[0];
+			for (int j = 1; j < 7 && b.outData[j]; j++) {
+				if (nKeys < 6)
+					outKeys[nKeys++] = b.outData[j];
+			}
+			// claim trig bits so simpler bindings don't double-fire
+			if (b.holdMask != 0)
+				kbdConsumed |= b.trigMask;
+			break;
+
+		case LZ_OUT_CONSUMER:
+			if (g_usbMode == MODE_LIZARD)
+				consumerBits |= b.outData[0];
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	// ---- right pad -> mouse motion with glide ----
 	static int prx = 0, pry = 0;
 	static bool prt = false;
 	static float vx = 0, vy = 0, rmx = 0, rmy = 0;
-	bool rtouch = b & TB_RPADT;
-	int rx = s16off(r, 22), ry = s16off(r, 24);
-	if (rtouch) {
-		if (prt) {
+	int dx = 0, dy = 0;
+	if (doRpadMouse) {
+		bool rtouch = (buttons & TB_RPADT) != 0;
+		int rx = in.rpx, ry = in.rpy;
+		if (rtouch && prt) {
 			vx += (rx - prx);
 			vy += (ry - pry);
 		}
-		prx = rx;
-		pry = ry;
+		if (rtouch) {
+			prx = rx;
+			pry = ry;
+		}
+		prt = rtouch;
+		// Y inverted; *10 = desktop cursor sensitivity (g_mDiv 64 -> eff 640)
+		float mxf = vx / (float)(g_mDiv * 10) + rmx,
+		      myf = -(vy / (float)(g_mDiv * 10)) + rmy;
+		dx = (int)mxf;
+		dy = (int)myf;
+		rmx = mxf - dx;
+		rmy = myf - dy; // sub-pixel carry
+		if (dx > 127)
+			dx = 127;
+		if (dx < -127)
+			dx = -127;
+		if (dy > 127)
+			dy = 127;
+		if (dy < -127)
+			dy = -127;
+		float f = g_mFric / 100.0f;
+		vx *= f;
+		vy *= f;
+		if (vx > -1 && vx < 1)
+			vx = 0;
+		if (vy > -1 && vy < 1)
+			vy = 0;
 	}
-	prt = rtouch;
-
-	// Y inverted; *10 = desktop-cursor sensitivity (g_mDiv 64 -> eff 640). Lower g_mDiv via WebUI slider = faster.
-	float mxf = vx / (float)(g_mDiv * 10) + rmx,
-	      myf = -(vy / (float)(g_mDiv * 10)) + rmy;
-	int dx = (int)mxf, dy = (int)myf;
-	rmx = mxf - dx;
-	rmy = myf - dy; // sub-pixel carry
-	if (dx > 127)
-		dx = 127;
-	if (dx < -127)
-		dx = -127;
-	if (dy > 127)
-		dy = 127;
-	if (dy < -127)
-		dy = -127;
-	float f = g_mFric / 100.0f;
-	vx *= f;
-	vy *= f;
-	if (vx > -1 && vx < 1)
-		vx = 0;
-	if (vy > -1 && vy < 1)
-		vy = 0; // friction = glide/decay
-	// --- left pad -> vertical scroll wheel (no momentum; only while touching, coarse) ---
+
+	// ---- left pad -> scroll wheel ----
 	static int ply = 0;
 	static bool plt = false;
 	static float sacc = 0;
-	bool ltouch = b & TB_LPADT;
-	int ly = s16off(r, 18);
-	if (ltouch) {
-		if (plt) {
+	int dw = 0;
+	if (doLpadScroll) {
+		bool ltouch = (buttons & TB_LPADT) != 0;
+		int ly = in.lpy;
+		if (ltouch && plt)
 			sacc += (ly - ply) / (float)(g_mDiv * 24);
-		}
-		ply = ly;
-	} else
-		sacc = 0;
-	plt = ltouch;
-	int dw = (int)sacc;
-	sacc -= dw;
-	if (dw > 15)
-		dw = 15;
-	if (dw < -15)
-		dw = -15; // finger up = wheel up (positive)
-	// --- mouse buttons: left=R-pad-click|R-trigger, right=L-trigger, middle=L-pad-click ---
-	// trigU8 scales full-range so a full pull reaches ~0xFF; a raw >>8 tops out ~0x80, never crossing threshold.
-	uint8_t rtrig = trigU8(u16off(r, 6)), ltrig = trigU8(u16off(r, 4));
-	uint8_t mbtn = 0;
-	if ((b & TB_RPADC) || rtrig > 180)
-		mbtn |= 1; // right trigger -> left (primary) click
-	if (ltrig > 180)
-		mbtn |= 2; // left trigger  -> right click
-	if (b & TB_LPADC)
-		mbtn |= 4;
+		if (ltouch)
+			ply = ly;
+		else
+			sacc = 0;
+		plt = ltouch;
+		dw = (int)sacc;
+		sacc -= dw;
+		if (dw > 15)
+			dw = 15;
+		if (dw < -15)
+			dw = -15;
+	}
+
+	// ---- mouse report ----
 	static uint8_t pmbtn = 0;
-	if (dx || dy || dw || mbtn != pmbtn) {
-		pmbtn = mbtn;
+	if (dx || dy || dw || outMBtn != pmbtn) {
+		pmbtn = outMBtn;
 		hid_mouse_report_t m;
-		m.buttons = mbtn;
+		m.buttons = outMBtn;
 		m.x = (int8_t)dx;
 		m.y = (int8_t)dy;
 		m.wheel = (int8_t)dw;
@@ -86,88 +163,30 @@ void rfLizard(const uint8_t *r, Adafruit_USBD_HID *mdev,
 		if (mdev->ready())
 			mdev->sendReport(mrid, &m, sizeof m);
 	}
-	// --- keyboard: modifiers + up to 6 keycodes ---
-	uint8_t mod = 0, kc[6] = { 0, 0, 0, 0, 0, 0 }, nk = 0;
-	if (b & TB_LB)
-		mod |= KEYBOARD_MODIFIER_LEFTCTRL;
-	if (b & TB_RB)
-		mod |= KEYBOARD_MODIFIER_LEFTALT;
-#define LZK(cond, code)                    \
-	do {                               \
-		if ((cond) && nk < 6)      \
-			kc[nk++] = (code); \
-	} while (0)
-	LZK(b & TB_A, HID_KEY_ENTER);
-	LZK(b & TB_B, HID_KEY_ESCAPE);
-	LZK(b & TB_X, HID_KEY_PAGE_UP);
-	LZK(b & TB_Y, HID_KEY_PAGE_DOWN);
-	LZK(b & TB_VIEW,
-	    (g_usbMode == MODE_LIZARD) ? HID_KEY_ESCAPE : HID_KEY_TAB);
-	LZK(b & TB_MENU,
-	    (g_usbMode == MODE_LIZARD) ? HID_KEY_TAB : HID_KEY_ESCAPE);
-
-	// left stick (XInput sign: +Y = up); deflect ~37% acts as a d-pad
-	int sx = s16off(r, 8), sy = s16off(r, 10);
-	LZK((b & TB_DUP) || sy > 12000, HID_KEY_ARROW_UP);
-	LZK((b & TB_DDN) || sy < -12000, HID_KEY_ARROW_DOWN);
-	LZK((b & TB_DLF) || sx < -12000, HID_KEY_ARROW_LEFT);
-	LZK((b & TB_DRT) || sx > 12000, HID_KEY_ARROW_RIGHT);
-#undef LZK
+
+	// ---- consumer control (edge-triggered) ----
+	static uint8_t prevCC = 0;
 	if (g_usbMode == MODE_LIZARD) {
-		static bool prevL5 = false, prevR5 = false;
-		bool mh = (b & TB_STEAM) || qamHeld;
-		bool nL5 = mh && (b & TB_L5), nR5 = mh && (b & TB_R5);
-		if (nL5 && !prevL5) {
-			uint8_t cc = 0x02;
+		if (consumerBits != prevCC) {
 			if (mdev->ready())
-				mdev->sendReport(0x03, &cc, 1);
-		}
-		if (nR5 && !prevR5) {
-			uint8_t cc = 0x01;
-			if (mdev->ready())
-				mdev->sendReport(0x03, &cc, 1);
-		}
-		if ((!nL5 && prevL5) || (!nR5 && prevR5)) {
-			uint8_t cc = 0x00;
-			if (mdev->ready())
-				mdev->sendReport(0x03, &cc, 1);
-		}
-		prevL5 = nL5;
-		prevR5 = nR5;
-		if (mh && (b & TB_X)) {
-			mod = KEYBOARD_MODIFIER_LEFTGUI |
-			      KEYBOARD_MODIFIER_LEFTCTRL;
-			kc[0] = HID_KEY_O;
-			kc[1] = 0;
-			kc[2] = 0;
-			kc[3] = 0;
-			kc[4] = 0;
-			kc[5] = 0;
-			nk = 1;
-		}
-		if (mh && (b & TB_L4)) {
-			mod = KEYBOARD_MODIFIER_LEFTCTRL |
-			      KEYBOARD_MODIFIER_LEFTALT;
-			kc[0] = HID_KEY_DELETE;
-			kc[1] = 0;
-			kc[2] = 0;
-			kc[3] = 0;
-			kc[4] = 0;
-			kc[5] = 0;
-			nk = 1;
+				mdev->sendReport(0x03, &consumerBits, 1);
+			prevCC = consumerBits;
 		}
 	}
+
+	// ---- keyboard report ----
 	static uint8_t pmod = 0, pkc[6] = { 0, 0, 0, 0, 0, 0 };
-	bool chg = (mod != pmod);
+	bool chg = (outMod != pmod);
 	for (int i = 0; i < 6; i++)
-		if (kc[i] != pkc[i])
+		if (outKeys[i] != pkc[i])
 			chg = true;
 	if (chg) {
-		pmod = mod;
+		pmod = outMod;
 		for (int i = 0; i < 6; i++)
-			pkc[i] = kc[i];
-		uint8_t krep[8] = { mod,   0,	  kc[0], kc[1],
-				    kc[2], kc[3], kc[4], kc[5] };
+			pkc[i] = outKeys[i];
+		uint8_t krep[8] = { outMod,    0,	    outKeys[0],
+				    outKeys[1], outKeys[2], outKeys[3],
+				    outKeys[4], outKeys[5] };
 		if (kdev->ready())
 			kdev->sendReport(krid, krep, 8);
 	}

+ 9 - 8
OpenPuck/mode_lizard.h

@@ -1,13 +1,14 @@
-// mode_lizard.h -- the controller's native "lizard" desktop behavior (driverless keyboard + mouse).
+// mode_lizard.h -- configurable lizard (desktop) keyboard+mouse mode.
 //
-// Not a standalone IController: lizard rides ON the puck interface (puck_hid.cpp calls rfLizard when Steam is
-// closed), driving mouse(0x40)+keyboard(0x41) on the same puck HID slot. Canonical Valve SC1 map: right pad ->
-// mouse (trackball glide), left pad -> scroll wheel + middle-click, R-trigger/R-pad-click -> left mouse,
-// L-trigger -> right mouse; A=Enter B=Esc X=PgUp Y=PgDn, d-pad/left-stick -> arrows, LB=LeftCtrl RB=LeftAlt,
-// View=Tab, Menu=Esc. Mouse reuses the Xbox-mode velocity+friction+sub-pixel glide model (g_mDiv / g_mFric).
+// Not a standalone IController: lizard rides ON the puck interface (puck_hid.cpp calls rfLizard
+// when Steam is closed or in MODE_LIZARD), driving mouse(0x40)+keyboard(0x41)+consumer(0x03) on
+// the same puck HID slot. The mapping table g_lizardMap (lizard_map.h) drives all outputs; the
+// default map mirrors the original hardcoded Valve SC1 behavior. Mouse reuses the Xbox-mode
+// velocity+friction+sub-pixel glide model (g_mDiv / g_mFric).
 #pragma once
 #include <Adafruit_TinyUSB.h>
 #include <stdint.h>
 
-void rfLizard(const uint8_t *r, Adafruit_USBD_HID *mdev,
-	      Adafruit_USBD_HID *kdev, uint8_t mrid, uint8_t krid);
+// mdev / kdev may be the same object (puck mode sends to the same HID slot for both reports).
+void rfLizard(Adafruit_USBD_HID *mdev, Adafruit_USBD_HID *kdev, uint8_t mrid,
+	      uint8_t krid);