Preserve analog triggers in generic HID modes
This commit is contained in:
parent
c5649b06a0
commit
ecd8ca2b40
9 changed files with 252 additions and 130 deletions
|
|
@ -24,7 +24,7 @@ Official feature references:
|
|||
- Keep settings and Bluetooth bond storage separate.
|
||||
- DualShock 3 support is explicitly out of scope.
|
||||
- Proprietary 2.4 GHz controllers are out of scope; Bluetooth Classic and BLE controllers are in scope.
|
||||
- Do not impersonate 8BitDo or Microsoft USB identities in a release build.
|
||||
- Do not impersonate vendor USB identities; compatibility modes use project-owned or clearly development-only identities.
|
||||
- PlayStation Classic and Mega Drive output modes are explicitly out of scope.
|
||||
|
||||
The official adapter also does not support the following, so they are not parity requirements:
|
||||
|
|
@ -458,9 +458,11 @@ no-phantom-controller tests on Switch and Windows.
|
|||
- implement four-interface DInput generic HID first
|
||||
- capture DInput rumble requirements before promising force feedback; Windows
|
||||
HID PID force feedback is not a free consequence of a gamepad descriptor
|
||||
- research whether the same standards-compliant HID report is sufficient on
|
||||
macOS; share the driver when hardware proves it, otherwise add only the
|
||||
descriptor/report differences required by macOS
|
||||
- Mac mode remains generic HID under the project development identity
|
||||
- use X/Y/Z/Rx for left/right sticks so generic macOS axis indices 0..3 are
|
||||
stable
|
||||
- expose independent analog triggers through Simulation Controls Brake and
|
||||
Accelerator usages; verify raw IOHID values and GameController behavior
|
||||
- do not duplicate profile transforms inside either output driver
|
||||
|
||||
Gate: descriptor/report/output tests, then real Windows DInput and macOS
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ On macOS, inspect the firmware's raw Game Pad values before GameController or br
|
|||
swift tools/Test-SwitchPicoMac.swift
|
||||
```
|
||||
|
||||
The diagnostic matches only `CAFE:4021`, identifies each of the four interfaces by interface and location, and prints changed axes, hats, and buttons with their HID usage and logical range. Move each analog trigger slowly and confirm that `Ry`/`Rz` report intermediate values across `0...65535`, rather than only the endpoints. It continues through hot-plug events until Ctrl-C. If opening a device fails, allow the terminal (or the app launching Swift) under **System Settings → Privacy & Security → Input Monitoring**, then rerun it.
|
||||
The diagnostic matches only `CAFE:4021`, identifies each of the four interfaces by interface and location, and prints changed axes, hats, and buttons with their HID usage and logical range. The four signed stick axes remain `X`/`Y`/`Z`/`Rx`. Move each analog trigger slowly and confirm output such as `LeftBrake page=0x02 usage=0xC5 logical=0...65535 value=32768` and `RightAccelerator page=0x02 usage=0xC4 logical=0...65535 value=32768`; each trigger should traverse intermediate values across `0...65535`, not only the endpoints. The diagnostic continues through hot-plug events until Ctrl-C. If opening a device fails, allow the terminal (or the app launching Swift) under **System Settings → Privacy & Security → Input Monitoring**, then rerun it.
|
||||
|
||||
On the tested Linux host, all four HID interfaces enumerated, but `hid-nintendo` timed out (`-110`) while requesting controller information from the composite device and removed the transient hidraw nodes. This is an observed, undiagnosed composite interoperability limitation; its root cause has not been established. The timeout was not observed on the Switch, so successful `hid-nintendo` binding is not the release criterion for the four-interface AIO firmware. The pairing CLI uses vendor control transfers on endpoint 0 and does not depend on those hidraw nodes.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace {
|
|||
constexpr int32_t kAxisMinimum = -512;
|
||||
constexpr int32_t kAxisMaximum = 511;
|
||||
constexpr int32_t kTriggerMaximum = 1023;
|
||||
constexpr int32_t kTriggerFullScaleMinimum = 1020;
|
||||
constexpr uint16_t kSwitchHostRumbleDurationMs = 50;
|
||||
// XInput vibration is stateful and remains active until XInputSetState sends
|
||||
// a new magnitude.
|
||||
|
|
@ -570,7 +571,7 @@ constexpr uint16_t scale_trigger(int32_t value) {
|
|||
if (value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (value >= kTriggerMaximum) {
|
||||
if (value >= kTriggerFullScaleMinimum) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return static_cast<uint16_t>(
|
||||
|
|
@ -610,6 +611,8 @@ static_assert(scale_axis(-512) == INT16_MIN);
|
|||
static_assert(scale_axis(0) == 0);
|
||||
static_assert(scale_axis(511) == INT16_MAX);
|
||||
static_assert(scale_trigger(0) == 0);
|
||||
static_assert(scale_trigger(1016) == 65086);
|
||||
static_assert(scale_trigger(1020) == UINT16_MAX);
|
||||
static_assert(scale_trigger(1023) == UINT16_MAX);
|
||||
static_assert(convert_accel(8192) == 4096);
|
||||
static_assert(convert_accel(-8192) == -4096);
|
||||
|
|
@ -747,14 +750,16 @@ ControllerState map_gamepad(const uni_gamepad_t& gamepad,
|
|||
state.button_right_shoulder =
|
||||
(button_mask & logical_button_bit(
|
||||
ControllerProfileLogicalButton::kRightShoulder)) != 0;
|
||||
state.left_trigger =
|
||||
(gamepad.buttons & BUTTON_TRIGGER_L) != 0
|
||||
? UINT16_MAX
|
||||
: scale_trigger(gamepad.brake);
|
||||
state.right_trigger =
|
||||
(gamepad.buttons & BUTTON_TRIGGER_R) != 0
|
||||
? UINT16_MAX
|
||||
: scale_trigger(gamepad.throttle);
|
||||
state.left_trigger = scale_trigger(gamepad.brake);
|
||||
if (gamepad.brake == 0 &&
|
||||
(gamepad.buttons & BUTTON_TRIGGER_L) != 0) {
|
||||
state.left_trigger = UINT16_MAX;
|
||||
}
|
||||
state.right_trigger = scale_trigger(gamepad.throttle);
|
||||
if (gamepad.throttle == 0 &&
|
||||
(gamepad.buttons & BUTTON_TRIGGER_R) != 0) {
|
||||
state.right_trigger = UINT16_MAX;
|
||||
}
|
||||
state.button_left_stick =
|
||||
(button_mask & logical_button_bit(
|
||||
ControllerProfileLogicalButton::kLeftStick)) != 0;
|
||||
|
|
|
|||
|
|
@ -226,10 +226,12 @@ inline constexpr uint8_t kMacReportDescriptor[] = {
|
|||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x05, 0x02, // Usage Page (Simulation Controls)
|
||||
0x09, 0xc5, // Usage (Brake)
|
||||
0x09, 0xc4, // Usage (Accelerator)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x07, // Logical Maximum (7)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
|
|
@ -257,32 +259,48 @@ inline constexpr uint8_t kMacReportDescriptor[] = {
|
|||
0xc0, // End Collection
|
||||
};
|
||||
|
||||
static_assert(sizeof(kDInputReportDescriptor) ==
|
||||
sizeof(kMacReportDescriptor));
|
||||
|
||||
#define GENERIC_HID_INTERFACE(number, endpoint) \
|
||||
#define GENERIC_HID_INTERFACE(number, endpoint, report_descriptor) \
|
||||
0x09, 0x04, number, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, \
|
||||
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, \
|
||||
static_cast<uint8_t>(sizeof(kDInputReportDescriptor)), \
|
||||
static_cast<uint8_t>(sizeof(kDInputReportDescriptor) >> 8u), \
|
||||
static_cast<uint8_t>(sizeof(report_descriptor)), \
|
||||
static_cast<uint8_t>(sizeof(report_descriptor) >> 8u), \
|
||||
0x07, 0x05, static_cast<uint8_t>(0x80u | endpoint), 0x03, \
|
||||
kEndpointSize, 0x00, kEndpointIntervalMs
|
||||
|
||||
inline constexpr uint8_t kConfigurationDescriptor[] = {
|
||||
inline constexpr uint8_t kDInputConfigurationDescriptor[] = {
|
||||
0x09, 0x02,
|
||||
static_cast<uint8_t>(kConfigurationDescriptorSize),
|
||||
static_cast<uint8_t>(kConfigurationDescriptorSize >> 8u),
|
||||
SWITCH_PICO_HID_INSTANCE_COUNT,
|
||||
0x01, 0x00, 0x80, 0xfa,
|
||||
GENERIC_HID_INTERFACE(0x00, 0x01),
|
||||
GENERIC_HID_INTERFACE(0x00, 0x01, kDInputReportDescriptor),
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 2
|
||||
GENERIC_HID_INTERFACE(0x01, 0x02),
|
||||
GENERIC_HID_INTERFACE(0x01, 0x02, kDInputReportDescriptor),
|
||||
#endif
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 3
|
||||
GENERIC_HID_INTERFACE(0x02, 0x03),
|
||||
GENERIC_HID_INTERFACE(0x02, 0x03, kDInputReportDescriptor),
|
||||
#endif
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 4
|
||||
GENERIC_HID_INTERFACE(0x03, 0x04),
|
||||
GENERIC_HID_INTERFACE(0x03, 0x04, kDInputReportDescriptor),
|
||||
#endif
|
||||
};
|
||||
|
||||
inline constexpr uint8_t kMacConfigurationDescriptor[] = {
|
||||
0x09, 0x02,
|
||||
static_cast<uint8_t>(kConfigurationDescriptorSize),
|
||||
static_cast<uint8_t>(kConfigurationDescriptorSize >> 8u),
|
||||
SWITCH_PICO_HID_INSTANCE_COUNT,
|
||||
0x01, 0x00, 0x80, 0xfa,
|
||||
GENERIC_HID_INTERFACE(0x00, 0x01, kMacReportDescriptor),
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 2
|
||||
GENERIC_HID_INTERFACE(0x01, 0x02, kMacReportDescriptor),
|
||||
#endif
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 3
|
||||
GENERIC_HID_INTERFACE(0x02, 0x03, kMacReportDescriptor),
|
||||
#endif
|
||||
#if SWITCH_PICO_HID_INSTANCE_COUNT >= 4
|
||||
GENERIC_HID_INTERFACE(0x03, 0x04, kMacReportDescriptor),
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -290,6 +308,9 @@ inline constexpr uint8_t kConfigurationDescriptor[] = {
|
|||
|
||||
static_assert(sizeof(kDInputDeviceDescriptor) == 18);
|
||||
static_assert(sizeof(kMacDeviceDescriptor) == 18);
|
||||
static_assert(sizeof(kConfigurationDescriptor) == kConfigurationDescriptorSize);
|
||||
static_assert(sizeof(kDInputConfigurationDescriptor) ==
|
||||
kConfigurationDescriptorSize);
|
||||
static_assert(sizeof(kMacConfigurationDescriptor) ==
|
||||
kConfigurationDescriptorSize);
|
||||
|
||||
} // namespace GenericHid
|
||||
|
|
|
|||
|
|
@ -2063,9 +2063,26 @@ void test_motion_hotkey() {
|
|||
void test_protocol_neutral_analog_state() {
|
||||
start_pairing_backend();
|
||||
uni_hid_device_t controller = device(0);
|
||||
uni_hid_device_t peer = device(1);
|
||||
platform_on_device_connected(&controller);
|
||||
require(platform_on_device_ready(&controller) == UNI_ERROR_SUCCESS,
|
||||
"analog-state controller did not become ready");
|
||||
platform_on_device_connected(&peer);
|
||||
require(platform_on_device_ready(&controller) == UNI_ERROR_SUCCESS &&
|
||||
platform_on_device_ready(&peer) == UNI_ERROR_SUCCESS,
|
||||
"analog-state controllers did not become ready");
|
||||
|
||||
uni_controller_t peer_input{};
|
||||
peer_input.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||
peer_input.gamepad.brake = 256;
|
||||
peer_input.gamepad.throttle = 768;
|
||||
peer_input.gamepad.buttons =
|
||||
BUTTON_TRIGGER_L | BUTTON_TRIGGER_R;
|
||||
platform_on_controller_data(&peer, &peer_input);
|
||||
|
||||
ControllerState peer_state{};
|
||||
require(read_controller_state(1, &peer_state) &&
|
||||
peer_state.left_trigger == 16399 &&
|
||||
peer_state.right_trigger == 49199,
|
||||
"peer analog trigger state was not published exactly");
|
||||
|
||||
uni_controller_t input{};
|
||||
input.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||
|
|
@ -2073,33 +2090,69 @@ void test_protocol_neutral_analog_state() {
|
|||
input.gamepad.axis_y = 0;
|
||||
input.gamepad.axis_rx = 511;
|
||||
input.gamepad.axis_ry = -256;
|
||||
input.gamepad.brake = 1;
|
||||
input.gamepad.throttle = 512;
|
||||
platform_on_controller_data(&controller, &input);
|
||||
input.gamepad.buttons =
|
||||
BUTTON_TRIGGER_L | BUTTON_TRIGGER_R;
|
||||
|
||||
struct TriggerCase {
|
||||
int32_t brake;
|
||||
int32_t throttle;
|
||||
uint16_t expected_left;
|
||||
uint16_t expected_right;
|
||||
};
|
||||
constexpr TriggerCase trigger_cases[] = {
|
||||
{4, 512, 256, 32799},
|
||||
{512, 1020, 32799, UINT16_MAX},
|
||||
{1020, 1016, UINT16_MAX, 65086},
|
||||
{1016, 1023, 65086, UINT16_MAX},
|
||||
{1023, 4, UINT16_MAX, 256},
|
||||
};
|
||||
ControllerState state{};
|
||||
require(read_controller_state(0, &state),
|
||||
"analog state was not published");
|
||||
for (const TriggerCase& trigger_case : trigger_cases) {
|
||||
input.gamepad.brake = trigger_case.brake;
|
||||
input.gamepad.throttle = trigger_case.throttle;
|
||||
platform_on_controller_data(&controller, &input);
|
||||
|
||||
require(read_controller_state(0, &state) &&
|
||||
state.left_trigger == trigger_case.expected_left &&
|
||||
state.right_trigger == trigger_case.expected_right,
|
||||
"digital trigger bits flattened analog trigger precision");
|
||||
require(read_controller_state(1, &peer_state) &&
|
||||
peer_state.left_trigger == 16399 &&
|
||||
peer_state.right_trigger == 49199,
|
||||
"slot 0 trigger update changed slot 1");
|
||||
}
|
||||
require(state.left_stick_x == INT16_MIN &&
|
||||
state.left_stick_y == 0 &&
|
||||
state.right_stick_x == INT16_MAX &&
|
||||
state.right_stick_y == -16384,
|
||||
"stick axes were not normalized to signed full range");
|
||||
require(state.left_trigger == scale_trigger(1) &&
|
||||
state.left_trigger > 0 &&
|
||||
state.right_trigger == scale_trigger(512) &&
|
||||
state.right_trigger < UINT16_MAX,
|
||||
"analog trigger precision was discarded");
|
||||
|
||||
input.gamepad.brake = 0;
|
||||
input.gamepad.throttle = 0;
|
||||
input.gamepad.buttons =
|
||||
BUTTON_TRIGGER_L | BUTTON_TRIGGER_R;
|
||||
input.gamepad.buttons = BUTTON_TRIGGER_L;
|
||||
platform_on_controller_data(&controller, &input);
|
||||
require(read_controller_state(0, &state) &&
|
||||
state.left_trigger == UINT16_MAX &&
|
||||
state.right_trigger == 0,
|
||||
"left digital-only trigger did not map independently");
|
||||
|
||||
input.gamepad.buttons = BUTTON_TRIGGER_R;
|
||||
platform_on_controller_data(&controller, &input);
|
||||
require(read_controller_state(0, &state) &&
|
||||
state.left_trigger == 0 &&
|
||||
state.right_trigger == UINT16_MAX,
|
||||
"digital trigger buttons did not map to full analog range");
|
||||
"right digital-only trigger did not map independently");
|
||||
|
||||
input.gamepad.buttons = 0;
|
||||
platform_on_controller_data(&controller, &input);
|
||||
require(read_controller_state(0, &state) &&
|
||||
state.left_trigger == 0 &&
|
||||
state.right_trigger == 0,
|
||||
"released triggers did not return to zero");
|
||||
require(read_controller_state(1, &peer_state) &&
|
||||
peer_state.left_trigger == 16399 &&
|
||||
peer_state.right_trigger == 49199,
|
||||
"slot 0 digital trigger fallback changed slot 1");
|
||||
}
|
||||
|
||||
void test_host_rumble_mode_duration() {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@
|
|||
#endif
|
||||
|
||||
static_assert(SWITCH_PICO_HID_INSTANCE_COUNT == EXPECTED_HID_INSTANCE_COUNT);
|
||||
static_assert(sizeof(GenericHid::kConfigurationDescriptor) ==
|
||||
static_assert(sizeof(GenericHid::kDInputConfigurationDescriptor) ==
|
||||
9u + 25u * EXPECTED_HID_INSTANCE_COUNT);
|
||||
static_assert(sizeof(GenericHid::kMacConfigurationDescriptor) ==
|
||||
9u + 25u * EXPECTED_HID_INSTANCE_COUNT);
|
||||
static_assert(sizeof(GenericHid::InputReport) == 15);
|
||||
static_assert(sizeof(GenericHid::kDInputReportDescriptor) ==
|
||||
sizeof(GenericHid::kMacReportDescriptor));
|
||||
static_assert(sizeof(GenericHid::kMacReportDescriptor) ==
|
||||
sizeof(GenericHid::kDInputReportDescriptor) + 4u);
|
||||
|
||||
|
||||
namespace {
|
||||
|
|
@ -64,6 +66,55 @@ constexpr std::array<ItemGolden, 43> kDInputReportItemGolden{{
|
|||
{0, 12, 0, 0x00},
|
||||
}};
|
||||
|
||||
bool expected_report_item(size_t decoded, bool mac_variant,
|
||||
ItemGolden* expected) {
|
||||
if (!mac_variant) {
|
||||
if (decoded >= kDInputReportItemGolden.size()) {
|
||||
return false;
|
||||
}
|
||||
*expected = kDInputReportItemGolden[decoded];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (decoded < 16) {
|
||||
if (decoded >= kDInputReportItemGolden.size()) {
|
||||
return false;
|
||||
}
|
||||
*expected = kDInputReportItemGolden[decoded];
|
||||
if (decoded == 10) {
|
||||
expected->value = 0x32;
|
||||
} else if (decoded == 11) {
|
||||
expected->value = 0x33;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (decoded == 16) {
|
||||
*expected = {1, 0, 1, 0x02};
|
||||
return true;
|
||||
}
|
||||
if (decoded == 17) {
|
||||
*expected = {2, 0, 1, 0xc5};
|
||||
return true;
|
||||
}
|
||||
if (decoded == 18) {
|
||||
*expected = {2, 0, 1, 0xc4};
|
||||
return true;
|
||||
}
|
||||
if (decoded == 19) {
|
||||
*expected = kDInputReportItemGolden[18];
|
||||
return true;
|
||||
}
|
||||
if (decoded == 20) {
|
||||
*expected = {1, 0, 1, 0x01};
|
||||
return true;
|
||||
}
|
||||
if (decoded - 2u >= kDInputReportItemGolden.size()) {
|
||||
return false;
|
||||
}
|
||||
*expected = kDInputReportItemGolden[decoded - 2u];
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t sign_extend(uint32_t value, uint8_t size) {
|
||||
if (size == 4 || size == 0) {
|
||||
return static_cast<int32_t>(value);
|
||||
|
|
@ -111,7 +162,7 @@ void inspect_report_descriptor(const uint8_t* descriptor,
|
|||
mac_variant ? std::array<uint32_t, 4>{{0x30, 0x31, 0x32, 0x33}}
|
||||
: std::array<uint32_t, 4>{{0x30, 0x31, 0x33, 0x34}};
|
||||
const std::array<uint32_t, 2> expected_trigger_usages =
|
||||
mac_variant ? std::array<uint32_t, 2>{{0x34, 0x35}}
|
||||
mac_variant ? std::array<uint32_t, 2>{{0xc5, 0xc4}}
|
||||
: std::array<uint32_t, 2>{{0x32, 0x35}};
|
||||
size_t offset = 0;
|
||||
size_t decoded = 0;
|
||||
|
|
@ -149,24 +200,13 @@ void inspect_report_descriptor(const uint8_t* descriptor,
|
|||
const uint8_t type = static_cast<uint8_t>((prefix >> 2u) & 0x03u);
|
||||
const uint8_t tag = static_cast<uint8_t>(prefix >> 4u);
|
||||
|
||||
expect(decoded < kDInputReportItemGolden.size(),
|
||||
"report descriptor contains an extra HID item");
|
||||
if (decoded < kDInputReportItemGolden.size()) {
|
||||
const ItemGolden& golden = kDInputReportItemGolden[decoded];
|
||||
uint32_t expected_value = golden.value;
|
||||
if (mac_variant) {
|
||||
if (decoded == 10) {
|
||||
expected_value = 0x32;
|
||||
} else if (decoded == 11) {
|
||||
expected_value = 0x33;
|
||||
} else if (decoded == 16) {
|
||||
expected_value = 0x34;
|
||||
} else if (decoded == 17) {
|
||||
expected_value = 0x35;
|
||||
}
|
||||
}
|
||||
ItemGolden golden{};
|
||||
const bool has_golden =
|
||||
expected_report_item(decoded, mac_variant, &golden);
|
||||
expect(has_golden, "report descriptor contains an extra HID item");
|
||||
if (has_golden) {
|
||||
expect(type == golden.type && tag == golden.tag &&
|
||||
size == golden.size && value == expected_value,
|
||||
size == golden.size && value == golden.value,
|
||||
"decoded HID item differs from its mode golden");
|
||||
}
|
||||
++decoded;
|
||||
|
|
@ -265,8 +305,9 @@ void inspect_report_descriptor(const uint8_t* descriptor,
|
|||
locals.clear();
|
||||
}
|
||||
|
||||
expect(offset == descriptor_size, "report descriptor was not fully decoded");
|
||||
expect(decoded == kDInputReportItemGolden.size(),
|
||||
const size_t expected_item_count =
|
||||
kDInputReportItemGolden.size() + (mac_variant ? 2u : 0u);
|
||||
expect(decoded == expected_item_count,
|
||||
"report descriptor is missing a golden HID item");
|
||||
expect(gamepad_application && collection_depth == 0,
|
||||
"report is not one balanced Game Pad application collection");
|
||||
|
|
@ -296,35 +337,22 @@ void inspect_report_descriptor(const uint8_t* descriptor,
|
|||
const InputField& triggers = fields[1];
|
||||
expect(triggers.bit_offset == 64 && triggers.size == 16 &&
|
||||
triggers.count == 2 && triggers.flags == 0x02 &&
|
||||
triggers.globals.usage_page == 1 &&
|
||||
triggers.globals.usage_page == (mac_variant ? 2u : 1u) &&
|
||||
triggers.globals.logical_minimum == 0 &&
|
||||
triggers.globals.logical_maximum == 65535 &&
|
||||
triggers.locals.usage_count ==
|
||||
expected_trigger_usages.size() &&
|
||||
triggers.locals.usages[0] == expected_trigger_usages[0] &&
|
||||
triggers.locals.usages[1] == expected_trigger_usages[1],
|
||||
mac_variant ? "unsigned Mac Ry/Rz trigger field layout is wrong"
|
||||
: "unsigned DInput Z/Rz trigger field layout is wrong");
|
||||
mac_variant
|
||||
? "unsigned Mac Brake/Accelerator trigger field layout is wrong"
|
||||
: "unsigned DInput Z/Rz trigger field layout is wrong");
|
||||
if (mac_variant) {
|
||||
const std::array<uint32_t, 6> usage_indices{{
|
||||
sticks.locals.usages[0] - 0x30u,
|
||||
sticks.locals.usages[1] - 0x30u,
|
||||
sticks.locals.usages[2] - 0x30u,
|
||||
sticks.locals.usages[3] - 0x30u,
|
||||
triggers.locals.usages[0] - 0x30u,
|
||||
triggers.locals.usages[1] - 0x30u,
|
||||
}};
|
||||
constexpr std::array<uint32_t, 6> kSemanticAxisGolden{{
|
||||
0, 1, 2, 3, 4, 5,
|
||||
}};
|
||||
expect(usage_indices == kSemanticAxisGolden,
|
||||
"Mac physical fields do not map to Chromium axes 0..5");
|
||||
expect((sticks.bit_offset + 3u * sticks.size) / 8u == 6u &&
|
||||
usage_indices[3] == 3u &&
|
||||
sticks.locals.usages[3] == 0x33u &&
|
||||
triggers.bit_offset / 8u == 8u &&
|
||||
usage_indices[4] == 4u,
|
||||
"Mac field offset 6 must be Rx/axis3 and offset 8 must be "
|
||||
"Ry/axis4; reversing them reproduces the hardware failure");
|
||||
(triggers.bit_offset + triggers.size) / 8u == 10u,
|
||||
"Mac X/Y/Z/Rx sticks and following trigger offsets changed");
|
||||
}
|
||||
|
||||
const InputField& hat = fields[2];
|
||||
|
|
@ -356,34 +384,6 @@ void inspect_report_descriptor(const uint8_t* descriptor,
|
|||
buttons.locals.usage_maximum == 16,
|
||||
"sequential Button 1..16 field layout is wrong");
|
||||
}
|
||||
void inspect_report_descriptor_parity() {
|
||||
constexpr std::array<uint8_t, 3> kDInputDifferentBytes{{
|
||||
0x33, 0x34, 0x32,
|
||||
}};
|
||||
constexpr std::array<uint8_t, 3> kMacDifferentBytes{{
|
||||
0x32, 0x33, 0x34,
|
||||
}};
|
||||
size_t difference_count = 0;
|
||||
for (size_t offset = 0;
|
||||
offset < sizeof(GenericHid::kDInputReportDescriptor); ++offset) {
|
||||
const uint8_t dinput =
|
||||
GenericHid::kDInputReportDescriptor[offset];
|
||||
const uint8_t mac = GenericHid::kMacReportDescriptor[offset];
|
||||
if (dinput == mac) {
|
||||
continue;
|
||||
}
|
||||
expect(difference_count < kDInputDifferentBytes.size(),
|
||||
"report descriptors differ outside the three Mac axis usages");
|
||||
if (difference_count < kDInputDifferentBytes.size()) {
|
||||
expect(dinput == kDInputDifferentBytes[difference_count] &&
|
||||
mac == kMacDifferentBytes[difference_count],
|
||||
"report descriptor axis usage difference is wrong");
|
||||
}
|
||||
++difference_count;
|
||||
}
|
||||
expect(difference_count == kDInputDifferentBytes.size(),
|
||||
"report descriptors do not differ at exactly three axis usages");
|
||||
}
|
||||
|
||||
|
||||
void inspect_device_descriptors_and_strings() {
|
||||
|
|
@ -423,13 +423,13 @@ void inspect_device_descriptors_and_strings() {
|
|||
"DInput and Mac identities do not have distinct strings");
|
||||
}
|
||||
|
||||
void inspect_configuration_descriptor() {
|
||||
void inspect_configuration_descriptor(
|
||||
const uint8_t* descriptor, size_t descriptor_size,
|
||||
size_t expected_report_descriptor_size) {
|
||||
constexpr uint8_t kConfiguration = 0x02;
|
||||
constexpr uint8_t kInterface = 0x04;
|
||||
constexpr uint8_t kEndpoint = 0x05;
|
||||
constexpr uint8_t kHid = 0x21;
|
||||
const uint8_t* descriptor = GenericHid::kConfigurationDescriptor;
|
||||
const size_t descriptor_size = sizeof(GenericHid::kConfigurationDescriptor);
|
||||
|
||||
expect(descriptor[0] == 9 && descriptor[1] == kConfiguration &&
|
||||
read_u16(descriptor + 2) == descriptor_size &&
|
||||
|
|
@ -474,9 +474,7 @@ void inspect_configuration_descriptor() {
|
|||
const uint16_t report_descriptor_length =
|
||||
read_u16(descriptor + offset + 7);
|
||||
expect(report_descriptor_length ==
|
||||
sizeof(GenericHid::kDInputReportDescriptor) &&
|
||||
report_descriptor_length ==
|
||||
sizeof(GenericHid::kMacReportDescriptor),
|
||||
expected_report_descriptor_size,
|
||||
"HID descriptor advertises the wrong report length");
|
||||
if (current_interface >= 0) {
|
||||
++hid_counts[static_cast<size_t>(current_interface)];
|
||||
|
|
@ -523,6 +521,28 @@ void inspect_configuration_descriptor() {
|
|||
}
|
||||
}
|
||||
|
||||
void inspect_configuration_descriptor_parity() {
|
||||
size_t difference_count = 0;
|
||||
for (size_t offset = 0;
|
||||
offset < sizeof(GenericHid::kDInputConfigurationDescriptor);
|
||||
++offset) {
|
||||
const uint8_t dinput =
|
||||
GenericHid::kDInputConfigurationDescriptor[offset];
|
||||
const uint8_t mac = GenericHid::kMacConfigurationDescriptor[offset];
|
||||
if (dinput == mac) {
|
||||
continue;
|
||||
}
|
||||
const size_t expected_offset = 25u + 25u * difference_count;
|
||||
expect(offset == expected_offset &&
|
||||
dinput == sizeof(GenericHid::kDInputReportDescriptor) &&
|
||||
mac == sizeof(GenericHid::kMacReportDescriptor),
|
||||
"configuration descriptors differ outside report lengths");
|
||||
++difference_count;
|
||||
}
|
||||
expect(difference_count == EXPECTED_HID_INSTANCE_COUNT,
|
||||
"configuration descriptors do not differ once per interface");
|
||||
}
|
||||
|
||||
void inspect_report_encoding() {
|
||||
ControllerState state{};
|
||||
state.left_stick_x = INT16_MIN;
|
||||
|
|
@ -622,9 +642,16 @@ int main() {
|
|||
false);
|
||||
inspect_report_descriptor(GenericHid::kMacReportDescriptor,
|
||||
sizeof(GenericHid::kMacReportDescriptor), true);
|
||||
inspect_report_descriptor_parity();
|
||||
inspect_device_descriptors_and_strings();
|
||||
inspect_configuration_descriptor();
|
||||
inspect_configuration_descriptor(
|
||||
GenericHid::kDInputConfigurationDescriptor,
|
||||
sizeof(GenericHid::kDInputConfigurationDescriptor),
|
||||
sizeof(GenericHid::kDInputReportDescriptor));
|
||||
inspect_configuration_descriptor(
|
||||
GenericHid::kMacConfigurationDescriptor,
|
||||
sizeof(GenericHid::kMacConfigurationDescriptor),
|
||||
sizeof(GenericHid::kMacReportDescriptor));
|
||||
inspect_configuration_descriptor_parity();
|
||||
inspect_report_encoding();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -560,9 +560,11 @@ void test_xinput_boundary_dispatch() {
|
|||
|
||||
void test_generic_boundary_dispatch(
|
||||
AdapterUsbMode mode, const uint8_t* expected_device_descriptor,
|
||||
const uint8_t* expected_configuration_descriptor,
|
||||
const uint8_t* expected_report_descriptor,
|
||||
const char* expected_driver_name, const char* expected_mode_name,
|
||||
const char* expected_product, const char* expected_serial) {
|
||||
size_t expected_report_descriptor_size, const char* expected_driver_name,
|
||||
const char* expected_mode_name, const char* expected_product,
|
||||
const char* expected_serial) {
|
||||
reset_usb_harness();
|
||||
usb_output_driver_init(mode);
|
||||
|
||||
|
|
@ -588,14 +590,14 @@ void test_generic_boundary_dispatch(
|
|||
tud_descriptor_configuration_cb(0);
|
||||
expect(configuration_descriptor != nullptr &&
|
||||
std::memcmp(configuration_descriptor,
|
||||
GenericHid::kConfigurationDescriptor,
|
||||
expected_configuration_descriptor,
|
||||
GenericHid::kConfigurationDescriptorSize) == 0,
|
||||
"generic configuration descriptor was not selected");
|
||||
const uint8_t* report_descriptor =
|
||||
tud_hid_descriptor_report_cb(0);
|
||||
expect(report_descriptor != nullptr &&
|
||||
std::memcmp(report_descriptor, expected_report_descriptor,
|
||||
sizeof(GenericHid::kDInputReportDescriptor)) == 0 &&
|
||||
expected_report_descriptor_size) == 0 &&
|
||||
tud_hid_descriptor_report_cb(kInvalidInstance) == nullptr,
|
||||
"generic report descriptor routing was incorrect");
|
||||
|
||||
|
|
@ -679,11 +681,15 @@ void test_generic_boundary_dispatch(
|
|||
void test_generic_modes_boundary_dispatch() {
|
||||
test_generic_boundary_dispatch(
|
||||
AdapterUsbMode::kDInput, GenericHid::kDInputDeviceDescriptor,
|
||||
GenericHid::kDInputReportDescriptor, "DINPUT", "DInput",
|
||||
GenericHid::kDInputConfigurationDescriptor,
|
||||
GenericHid::kDInputReportDescriptor,
|
||||
sizeof(GenericHid::kDInputReportDescriptor), "DINPUT", "DInput",
|
||||
GenericHid::kDInputProductString, GenericHid::kDInputSerialString);
|
||||
test_generic_boundary_dispatch(
|
||||
AdapterUsbMode::kMac, GenericHid::kMacDeviceDescriptor,
|
||||
GenericHid::kMacReportDescriptor, "MAC", "Mac",
|
||||
GenericHid::kMacConfigurationDescriptor,
|
||||
GenericHid::kMacReportDescriptor,
|
||||
sizeof(GenericHid::kMacReportDescriptor), "MAC", "Mac",
|
||||
GenericHid::kMacProductString, GenericHid::kMacSerialString);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import IOKit.hid
|
|||
private let vendorID = 0xCAFE
|
||||
private let productID = 0x4021
|
||||
private let genericDesktopPage = 0x01
|
||||
private let simulationControlsPage = 0x02
|
||||
private let gamePadUsage = 0x05
|
||||
private let buttonPage = 0x09
|
||||
private let expectedInterfaceCount = 4
|
||||
|
|
@ -229,12 +230,17 @@ private func usageName(page: UInt32, usage: UInt32) -> String? {
|
|||
case 0x31: return "Y"
|
||||
case 0x32: return "Z"
|
||||
case 0x33: return "Rx"
|
||||
case 0x34: return "Ry"
|
||||
case 0x35: return "Rz"
|
||||
case 0x39: return "Hat"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
if page == UInt32(simulationControlsPage) {
|
||||
switch usage {
|
||||
case 0xc5: return "LeftBrake"
|
||||
case 0xc4: return "RightAccelerator"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
if page == UInt32(buttonPage) {
|
||||
return "Button \(usage)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,9 @@ extern "C" uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
|
|||
return XInput::kConfigurationDescriptor;
|
||||
}
|
||||
if (generic_selected()) {
|
||||
return GenericHid::kConfigurationDescriptor;
|
||||
return g_mode == AdapterUsbMode::kMac
|
||||
? GenericHid::kMacConfigurationDescriptor
|
||||
: GenericHid::kDInputConfigurationDescriptor;
|
||||
}
|
||||
#endif
|
||||
return switch_pro_configuration_descriptor;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue