diff --git a/proto/config.proto b/proto/config.proto index 73d9b34d14..a27769e1be 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -38,6 +38,22 @@ message GamepadOptions optional uint32 usbVendorID = 31; optional uint32 miniMenuGamepadInput = 32; optional InputModeDeviceType inputDeviceType = 33; + // Separate XInput USB descriptor overrides + optional bool hidDescOverride = 34; + optional string hidDescProduct = 35 [(nanopb).max_length = 32]; + optional string hidDescManufacturer = 36 [(nanopb).max_length = 32]; + optional string hidDescVersion = 37 [(nanopb).max_length = 8]; + optional bool hidOverrideID = 38; + optional uint32 hidProductID = 39; + optional uint32 hidVendorID = 40; + // Separate USB-HID (non-XInput) USB descriptor overrides + optional bool xinputDescOverride = 41; + optional string xinputDescProduct = 42 [(nanopb).max_length = 32]; + optional string xinputDescManufacturer = 43 [(nanopb).max_length = 32]; + optional string xinputDescVersion = 44 [(nanopb).max_length = 8]; + optional bool xinputOverrideID = 45; + optional uint32 xinputProductID = 46; + optional uint32 xinputVendorID = 47; } message KeyboardMapping diff --git a/src/config_utils.cpp b/src/config_utils.cpp index 95ddb0839b..92fa2063c6 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -319,6 +319,22 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.gamepadOptions, usbOverrideID, DEFAULT_USB_ID_OVERRIDE); INIT_UNSET_PROPERTY(config.gamepadOptions, usbVendorID, DEFAULT_USB_VENDOR_ID); INIT_UNSET_PROPERTY(config.gamepadOptions, usbProductID, DEFAULT_USB_PRODUCT_ID); + // HID specific USB descriptor overrides + INIT_UNSET_PROPERTY(config.gamepadOptions, hidDescOverride, DEFAULT_USB_DESC_OVERRIDE); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, hidDescProduct, DEFAULT_USB_DESC_PRODUCT); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, hidDescManufacturer, DEFAULT_USB_DESC_MANUFACTURER); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, hidDescVersion, DEFAULT_USB_DESC_VERSION); + INIT_UNSET_PROPERTY(config.gamepadOptions, hidOverrideID, DEFAULT_USB_ID_OVERRIDE); + INIT_UNSET_PROPERTY(config.gamepadOptions, hidVendorID, DEFAULT_USB_VENDOR_ID); + INIT_UNSET_PROPERTY(config.gamepadOptions, hidProductID, DEFAULT_USB_PRODUCT_ID); + // XInput specific USB descriptor overrides + INIT_UNSET_PROPERTY(config.gamepadOptions, xinputDescOverride, DEFAULT_USB_DESC_OVERRIDE); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, xinputDescProduct, DEFAULT_USB_DESC_PRODUCT); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, xinputDescManufacturer, DEFAULT_USB_DESC_MANUFACTURER); + INIT_UNSET_PROPERTY_STR(config.gamepadOptions, xinputDescVersion, DEFAULT_USB_DESC_VERSION); + INIT_UNSET_PROPERTY(config.gamepadOptions, xinputOverrideID, DEFAULT_USB_ID_OVERRIDE); + INIT_UNSET_PROPERTY(config.gamepadOptions, xinputVendorID, DEFAULT_USB_VENDOR_ID); + INIT_UNSET_PROPERTY(config.gamepadOptions, xinputProductID, DEFAULT_USB_PRODUCT_ID); INIT_UNSET_PROPERTY(config.gamepadOptions, miniMenuGamepadInput, MINI_MENU_GAMEPAD_INPUT); // hotkeyOptions diff --git a/src/drivers/hid/HIDDriver.cpp b/src/drivers/hid/HIDDriver.cpp index 05ab530de3..b3f83a7dd3 100644 --- a/src/drivers/hid/HIDDriver.cpp +++ b/src/drivers/hid/HIDDriver.cpp @@ -135,18 +135,20 @@ bool HIDDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_contr const uint16_t * HIDDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) { char *value; - // Check for override settings + // Check for override settings - use HID-specific overrides if available GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions(); - if ( gamepadOptions.usbDescOverride == true ) { + // Check for HID-specific overrides first, fall back to legacy shared settings + bool useOverride = gamepadOptions.hidDescOverride; + if ( useOverride == true ) { switch(index) { case 1: - value = gamepadOptions.usbDescManufacturer; + value = gamepadOptions.hidDescManufacturer; break; case 2: - value = gamepadOptions.usbDescProduct; + value = gamepadOptions.hidDescProduct; break; case 3: - value = gamepadOptions.usbDescVersion; + value = gamepadOptions.hidDescVersion; default: value = (char *)hid_string_descriptors[index]; break; @@ -159,13 +161,15 @@ const uint16_t * HIDDriver::get_descriptor_string_cb(uint8_t index, uint16_t lan } const uint8_t * HIDDriver::get_descriptor_device_cb() { - // Check for override settings + // Check for override settings - use HID-specific overrides if available GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions(); - if ( gamepadOptions.usbOverrideID == true ) { + // Check for HID-specific overrides first, fall back to legacy shared settings + bool useOverride = gamepadOptions.hidOverrideID; + if ( useOverride == true ) { static uint8_t modified_device_descriptor[18]; memcpy(modified_device_descriptor, hid_device_descriptor, sizeof(hid_device_descriptor)); - memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.usbVendorID, sizeof(uint16_t)); // Vendor ID - memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.usbProductID, sizeof(uint16_t)); // Product ID + memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.hidVendorID, sizeof(uint16_t)); // Vendor ID + memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.hidProductID, sizeof(uint16_t)); // Product ID return (const uint8_t*)modified_device_descriptor; } diff --git a/src/drivers/xinput/XInputDriver.cpp b/src/drivers/xinput/XInputDriver.cpp index 40bc2ca089..227f79fba1 100644 --- a/src/drivers/xinput/XInputDriver.cpp +++ b/src/drivers/xinput/XInputDriver.cpp @@ -488,18 +488,20 @@ bool XInputDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_co const uint16_t * XInputDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) { char *value; - // Check for override settings + // Check for override settings - use XInput-specific overrides if available GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions(); - if ( gamepadOptions.usbDescOverride == true ) { + // Check for XInput-specific overrides first, fall back to legacy shared settings + bool useOverride = gamepadOptions.xinputDescOverride; + if ( useOverride == true ) { switch(index) { case 1: - value = gamepadOptions.usbDescManufacturer; + value = gamepadOptions.xinputDescManufacturer; break; case 2: - value = gamepadOptions.usbDescProduct; + value = gamepadOptions.xinputDescProduct; break; case 3: - value = gamepadOptions.usbDescVersion; + value = gamepadOptions.xinputDescVersion; default: value = (char *)xinput_get_string_descriptor(index); break; @@ -511,13 +513,15 @@ const uint16_t * XInputDriver::get_descriptor_string_cb(uint8_t index, uint16_t } const uint8_t * XInputDriver::get_descriptor_device_cb() { - // Check for override settings + // Check for override settings - use XInput-specific overrides if available GamepadOptions & gamepadOptions = Storage::getInstance().getGamepadOptions(); - if ( gamepadOptions.usbOverrideID == true ) { + // Check for XInput-specific overrides first, fall back to legacy shared settings + bool useOverride = gamepadOptions.xinputOverrideID; + if ( useOverride == true ) { static uint8_t modified_device_descriptor[18]; memcpy(modified_device_descriptor, xinput_device_descriptor, sizeof(xinput_device_descriptor)); - memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.usbVendorID, sizeof(uint16_t)); // Vendor ID - memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.usbProductID, sizeof(uint16_t)); // Product ID + memcpy(&modified_device_descriptor[8], (uint8_t*)&gamepadOptions.xinputVendorID, sizeof(uint16_t)); // Vendor ID + memcpy(&modified_device_descriptor[10], (uint8_t*)&gamepadOptions.xinputProductID, sizeof(uint16_t)); // Product ID return (const uint8_t*)modified_device_descriptor; } return xinput_device_descriptor; diff --git a/src/webconfig.cpp b/src/webconfig.cpp index c8a317552b..a8c8417061 100644 --- a/src/webconfig.cpp +++ b/src/webconfig.cpp @@ -679,7 +679,7 @@ std::string setGamepadOptions() readDoc(gamepadOptions.ps4ControllerIDMode, doc, "ps4ControllerIDMode"); readDoc(gamepadOptions.usbDescOverride, doc, "usbDescOverride"); readDoc(gamepadOptions.miniMenuGamepadInput, doc, "miniMenuGamepadInput"); - // Copy USB descriptor strings + // Copy USB descriptor strings (legacy shared settings for backward compatibility) size_t strSize = sizeof(gamepadOptions.usbDescManufacturer); strncpy(gamepadOptions.usbDescManufacturer, doc["usbDescManufacturer"], strSize - 1); gamepadOptions.usbDescManufacturer[strSize - 1] = '\0'; @@ -690,8 +690,53 @@ std::string setGamepadOptions() strncpy(gamepadOptions.usbDescVersion, doc["usbDescVersion"], strSize - 1); gamepadOptions.usbDescVersion[strSize - 1] = '\0'; readDoc(gamepadOptions.usbOverrideID, doc, "usbOverrideID"); - readDoc(gamepadOptions.usbVendorID, doc, "usbVendorID"); - readDoc(gamepadOptions.usbProductID, doc, "usbProductID"); + // Parse USB VID/PID as hex strings + if (doc.containsKey("usbVendorID")) { + gamepadOptions.usbVendorID = strtoul(doc["usbVendorID"], nullptr, 16); + } + if (doc.containsKey("usbProductID")) { + gamepadOptions.usbProductID = strtoul(doc["usbProductID"], nullptr, 16); + } + + // HID (USB-HID) specific overrides + readDoc(gamepadOptions.hidDescOverride, doc, "hidDescOverride"); + strSize = sizeof(gamepadOptions.hidDescManufacturer); + strncpy(gamepadOptions.hidDescManufacturer, doc["hidDescManufacturer"], strSize - 1); + gamepadOptions.hidDescManufacturer[strSize - 1] = '\0'; + strSize = sizeof(gamepadOptions.hidDescProduct); + strncpy(gamepadOptions.hidDescProduct, doc["hidDescProduct"], strSize - 1); + gamepadOptions.hidDescProduct[strSize - 1] = '\0'; + strSize = sizeof(gamepadOptions.hidDescVersion); + strncpy(gamepadOptions.hidDescVersion, doc["hidDescVersion"], strSize - 1); + gamepadOptions.hidDescVersion[strSize - 1] = '\0'; + readDoc(gamepadOptions.hidOverrideID, doc, "hidOverrideID"); + // Parse HID VID/PID as hex strings + if (doc.containsKey("hidVendorID")) { + gamepadOptions.hidVendorID = strtoul(doc["hidVendorID"], nullptr, 16); + } + if (doc.containsKey("hidProductID")) { + gamepadOptions.hidProductID = strtoul(doc["hidProductID"], nullptr, 16); + } + + // XInput specific overrides + readDoc(gamepadOptions.xinputDescOverride, doc, "xinputDescOverride"); + strSize = sizeof(gamepadOptions.xinputDescManufacturer); + strncpy(gamepadOptions.xinputDescManufacturer, doc["xinputDescManufacturer"], strSize - 1); + gamepadOptions.xinputDescManufacturer[strSize - 1] = '\0'; + strSize = sizeof(gamepadOptions.xinputDescProduct); + strncpy(gamepadOptions.xinputDescProduct, doc["xinputDescProduct"], strSize - 1); + gamepadOptions.xinputDescProduct[strSize - 1] = '\0'; + strSize = sizeof(gamepadOptions.xinputDescVersion); + strncpy(gamepadOptions.xinputDescVersion, doc["xinputDescVersion"], strSize - 1); + gamepadOptions.xinputDescVersion[strSize - 1] = '\0'; + readDoc(gamepadOptions.xinputOverrideID, doc, "xinputOverrideID"); + // Parse XInput VID/PID as hex strings + if (doc.containsKey("xinputVendorID")) { + gamepadOptions.xinputVendorID = strtoul(doc["xinputVendorID"], nullptr, 16); + } + if (doc.containsKey("xinputProductID")) { + gamepadOptions.xinputProductID = strtoul(doc["xinputProductID"], nullptr, 16); + } HotkeyOptions& hotkeyOptions = Storage::getInstance().getHotkeyOptions(); @@ -753,13 +798,40 @@ std::string getGamepadOptions() writeDoc(doc, "usbDescVersion", gamepadOptions.usbDescVersion); writeDoc(doc, "usbOverrideID", gamepadOptions.usbOverrideID); writeDoc(doc, "miniMenuGamepadInput", gamepadOptions.miniMenuGamepadInput); - // Write USB Vendor ID and Product ID as 4 character hex strings with 0 padding + // Write USB Vendor ID and Product ID as 4 character hex strings with 0 padding (legacy) char usbVendorStr[5]; snprintf(usbVendorStr, 5, "%04X", gamepadOptions.usbVendorID); writeDoc(doc, "usbVendorID", usbVendorStr); char usbProductStr[5]; snprintf(usbProductStr, 5, "%04X", gamepadOptions.usbProductID); writeDoc(doc, "usbProductID", usbProductStr); + + // HID (USB-HID) specific overrides + writeDoc(doc, "hidDescOverride", gamepadOptions.hidDescOverride); + writeDoc(doc, "hidDescManufacturer", gamepadOptions.hidDescManufacturer); + writeDoc(doc, "hidDescProduct", gamepadOptions.hidDescProduct); + writeDoc(doc, "hidDescVersion", gamepadOptions.hidDescVersion); + writeDoc(doc, "hidOverrideID", gamepadOptions.hidOverrideID); + char hidVendorStr[5]; + snprintf(hidVendorStr, 5, "%04X", gamepadOptions.hidVendorID); + writeDoc(doc, "hidVendorID", hidVendorStr); + char hidProductStr[5]; + snprintf(hidProductStr, 5, "%04X", gamepadOptions.hidProductID); + writeDoc(doc, "hidProductID", hidProductStr); + + // XInput specific overrides + writeDoc(doc, "xinputDescOverride", gamepadOptions.xinputDescOverride); + writeDoc(doc, "xinputDescManufacturer", gamepadOptions.xinputDescManufacturer); + writeDoc(doc, "xinputDescProduct", gamepadOptions.xinputDescProduct); + writeDoc(doc, "xinputDescVersion", gamepadOptions.xinputDescVersion); + writeDoc(doc, "xinputOverrideID", gamepadOptions.xinputOverrideID); + char xinputVendorStr[5]; + snprintf(xinputVendorStr, 5, "%04X", gamepadOptions.xinputVendorID); + writeDoc(doc, "xinputVendorID", xinputVendorStr); + char xinputProductStr[5]; + snprintf(xinputProductStr, 5, "%04X", gamepadOptions.xinputProductID); + writeDoc(doc, "xinputProductID", xinputProductStr); + writeDoc(doc, "fnButtonPin", -1); GpioMappingInfo* gpioMappings = Storage::getInstance().getGpioMappings().pins; for (unsigned int pin = 0; pin < NUM_BANK0_GPIOS; pin++) { diff --git a/www/src/Pages/SettingsPage.jsx b/www/src/Pages/SettingsPage.jsx index a96f2668ca..e3c4560421 100644 --- a/www/src/Pages/SettingsPage.jsx +++ b/www/src/Pages/SettingsPage.jsx @@ -1107,7 +1107,31 @@ export default function SettingsPage() { ); }; - const usbOverride = (values, errors, setFieldValue, handleChange) => { + const usbOverride = (values, errors, setFieldValue, handleChange, mode) => { + // Determine which set of USB override fields to use based on mode + // mode can be 'xinput', 'hid', or undefined (for legacy backward compatibility) + const overrideToggle = mode === 'xinput' ? 'xinputDescOverride' : + mode === 'hid' ? 'hidDescOverride' : + 'usbDescOverride'; + const manuField = mode === 'xinput' ? 'xinputDescManufacturer' : + mode === 'hid' ? 'hidDescManufacturer' : + 'usbDescManufacturer'; + const prodField = mode === 'xinput' ? 'xinputDescProduct' : + mode === 'hid' ? 'hidDescProduct' : + 'usbDescProduct'; + const verField = mode === 'xinput' ? 'xinputDescVersion' : + mode === 'hid' ? 'hidDescVersion' : + 'usbDescVersion'; + const idOverrideToggle = mode === 'xinput' ? 'xinputOverrideID' : + mode === 'hid' ? 'hidOverrideID' : + 'usbOverrideID'; + const vendorField = mode === 'xinput' ? 'xinputVendorID' : + mode === 'hid' ? 'hidVendorID' : + 'usbVendorID'; + const productField = mode === 'xinput' ? 'xinputProductID' : + mode === 'hid' ? 'hidProductID' : + 'usbProductID'; + return (
@@ -1115,18 +1139,18 @@ export default function SettingsPage() { { - setFieldValue('usbDescOverride', e.target.checked ? 1 : 0); - setFieldValue('usbOverrideID', e.target.checked ? values.usbOverrideID : 0); + setFieldValue(overrideToggle, e.target.checked ? 1 : 0); + setFieldValue(idOverrideToggle, e.target.checked ? values[idOverrideToggle] : 0); } } /> - {values.usbDescOverride === 1 && ( + {values[overrideToggle] === 1 && ( <> @@ -1144,10 +1168,10 @@ export default function SettingsPage() { size="sm" type="text" placeholder={'test'} - name="usbDescProduct" - value={values.usbDescProduct} - error={errors?.usbDescProduct} - isInvalid={errors?.usbDescProduct} + name={prodField} + value={values[prodField]} + error={errors?.[prodField]} + isInvalid={errors?.[prodField]} onChange={handleChange} maxLength={32} /> @@ -1159,10 +1183,10 @@ export default function SettingsPage() { @@ -1174,10 +1198,10 @@ export default function SettingsPage() { @@ -1188,16 +1212,28 @@ export default function SettingsPage() { { - setFieldValue('usbOverrideID', e.target.checked ? 1 : 0); + setFieldValue(idOverrideToggle, e.target.checked ? 1 : 0); + // Populate default VID/PID based on mode when enabling override + if (e.target.checked) { + if (mode === 'xinput') { + // XInput defaults: Microsoft Xbox 360 + setFieldValue(vendorField, '045E'); + setFieldValue(productField, '028E'); + } else if (mode === 'hid') { + // USB-HID defaults: Silicon Labs + setFieldValue(vendorField, '10C4'); + setFieldValue(productField, '82C0'); + } + } }} /> -
); }; @@ -1317,7 +1353,7 @@ export default function SettingsPage() { ) => { return (
- {usbOverride(values, errors, setFieldValue, handleChange)} + {usbOverride(values, errors, setFieldValue, handleChange, 'hid')}
); };