Add DInput and Mac generic HID modes
This commit is contained in:
parent
ec9359bce8
commit
67801cc52a
22 changed files with 1926 additions and 75 deletions
|
|
@ -44,6 +44,11 @@ class FakeDevice:
|
|||
)
|
||||
self.configuration_generation = 3
|
||||
self.active_mode = config_manager.ACTIVE_MODE_SWITCH_PROBE
|
||||
self.capabilities = (
|
||||
config_manager.CAPABILITY_INPUT
|
||||
| config_manager.CAPABILITY_RUMBLE
|
||||
| config_manager.CAPABILITY_MOTION
|
||||
)
|
||||
self.transaction_id = 0
|
||||
self.transaction_payload = bytearray()
|
||||
self.transaction_expected_size = 0
|
||||
|
|
@ -215,7 +220,7 @@ class FakeDevice:
|
|||
0,
|
||||
2,
|
||||
self.active_mode,
|
||||
0,
|
||||
self.capabilities,
|
||||
0,
|
||||
2,
|
||||
]
|
||||
|
|
@ -366,6 +371,8 @@ class FakeDevice:
|
|||
config_manager.REQUESTED_MODE_AUTO,
|
||||
config_manager.REQUESTED_MODE_SWITCH,
|
||||
config_manager.REQUESTED_MODE_XINPUT,
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
)
|
||||
self.transaction_payload = bytearray()
|
||||
self.transaction_expected_size = 0
|
||||
|
|
@ -650,17 +657,20 @@ def test_mode_envelopes_and_host_side_validation(
|
|||
for transaction_id in (0, 0x80000000, True):
|
||||
with pytest.raises(config_manager.ConfigManagerError):
|
||||
config_manager.request_reboot(device, transaction_id)
|
||||
for mode in (
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
0xFF,
|
||||
True,
|
||||
):
|
||||
for mode in (0xFF, True):
|
||||
with pytest.raises(
|
||||
config_manager.ConfigManagerError, match="not available"
|
||||
):
|
||||
config_manager.set_mode(device, mode, 1.0)
|
||||
|
||||
for mode in (
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
):
|
||||
status = config_manager.set_mode(device, mode, 1.0)
|
||||
assert status.status == config_manager.STATUS_OK
|
||||
assert device.pending_requested_mode is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("failure_status", "message"),
|
||||
|
|
@ -728,6 +738,14 @@ def test_mode_transaction_must_correlate_before_reboot(
|
|||
config_manager.REQUESTED_MODE_XINPUT,
|
||||
config_manager.ACTIVE_MODE_XINPUT,
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_DINPUT,
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_mode_noop_accepts_only_mode_appropriate_active_state(
|
||||
|
|
@ -736,6 +754,16 @@ def test_mode_noop_accepts_only_mode_appropriate_active_state(
|
|||
device = FakeDevice()
|
||||
device.configuration = struct.pack("<HB5x", 60, requested_mode)
|
||||
device.active_mode = active_mode
|
||||
if active_mode in (
|
||||
config_manager.ACTIVE_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
):
|
||||
device.capabilities = config_manager.CAPABILITY_INPUT
|
||||
elif active_mode == config_manager.ACTIVE_MODE_XINPUT:
|
||||
device.capabilities = (
|
||||
config_manager.CAPABILITY_INPUT |
|
||||
config_manager.CAPABILITY_RUMBLE
|
||||
)
|
||||
|
||||
same_device, changed = config_manager.configure_mode(
|
||||
device, requested_mode, 1.0
|
||||
|
|
@ -765,6 +793,14 @@ def test_mode_noop_accepts_only_mode_appropriate_active_state(
|
|||
config_manager.REQUESTED_MODE_XINPUT,
|
||||
config_manager.ACTIVE_MODE_XINPUT,
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_DINPUT,
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_mode_change_waits_for_disappearance_and_reenumeration(
|
||||
|
|
@ -782,6 +818,16 @@ def test_mode_change_waits_for_disappearance_and_reenumeration(
|
|||
reenumerated.address = 8
|
||||
reenumerated.configuration = struct.pack("<HB5x", 60, requested_mode)
|
||||
reenumerated.active_mode = active_mode
|
||||
if active_mode in (
|
||||
config_manager.ACTIVE_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
):
|
||||
reenumerated.capabilities = config_manager.CAPABILITY_INPUT
|
||||
elif active_mode == config_manager.ACTIVE_MODE_XINPUT:
|
||||
reenumerated.capabilities = (
|
||||
config_manager.CAPABILITY_INPUT |
|
||||
config_manager.CAPABILITY_RUMBLE
|
||||
)
|
||||
scans = iter(((previous,), (), (reenumerated,)))
|
||||
monkeypatch.setattr(
|
||||
config_manager,
|
||||
|
|
@ -871,22 +917,37 @@ def test_mode_reboot_fails_when_missing_topology_is_ambiguous(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("stored_mode", "active_mode", "message"),
|
||||
("requested_mode", "stored_mode", "active_mode", "message"),
|
||||
(
|
||||
(
|
||||
config_manager.REQUESTED_MODE_SWITCH,
|
||||
config_manager.REQUESTED_MODE_SWITCH,
|
||||
config_manager.ACTIVE_MODE_SWITCH_PROBE,
|
||||
"activated",
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_SWITCH,
|
||||
config_manager.REQUESTED_MODE_AUTO,
|
||||
config_manager.ACTIVE_MODE_SWITCH,
|
||||
"was not stored",
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
"activated",
|
||||
),
|
||||
(
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
"was not stored",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_mode_verifies_requested_and_active_state_after_reenumeration(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
requested_mode: int,
|
||||
stored_mode: int,
|
||||
active_mode: int,
|
||||
message: str,
|
||||
|
|
@ -906,7 +967,7 @@ def test_mode_verifies_requested_and_active_state_after_reenumeration(
|
|||
|
||||
with pytest.raises(config_manager.ConfigManagerError, match=message):
|
||||
config_manager.configure_mode(
|
||||
previous, config_manager.REQUESTED_MODE_SWITCH, 1.0
|
||||
previous, requested_mode, 1.0
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -944,6 +1005,11 @@ def test_requested_and_active_mode_response_validation() -> None:
|
|||
config_manager.read_info(device).active_mode
|
||||
== config_manager.ACTIVE_MODE_SWITCH_PROBE
|
||||
)
|
||||
assert config_manager.read_info(device).capability_names() == (
|
||||
"input",
|
||||
"rumble",
|
||||
"motion",
|
||||
)
|
||||
|
||||
device.configuration = struct.pack("<HB5x", 60, 0xFF)
|
||||
with pytest.raises(
|
||||
|
|
@ -964,6 +1030,17 @@ def test_requested_and_active_mode_response_validation() -> None:
|
|||
config_manager.ConfigManagerError, match="unknown active USB mode"
|
||||
):
|
||||
config_manager.read_info(device)
|
||||
device.active_mode = config_manager.ACTIVE_MODE_DINPUT
|
||||
device.capabilities = 0x80
|
||||
with pytest.raises(
|
||||
config_manager.ConfigManagerError, match="unknown device capability"
|
||||
):
|
||||
config_manager.read_info(device)
|
||||
device.capabilities = config_manager.CAPABILITY_RUMBLE
|
||||
with pytest.raises(
|
||||
config_manager.ConfigManagerError, match="omit required input"
|
||||
):
|
||||
config_manager.read_info(device)
|
||||
|
||||
|
||||
def test_identity_and_profile_binary_json_round_trip() -> None:
|
||||
|
|
@ -1473,6 +1550,19 @@ def test_status_and_pairing_commands(
|
|||
assert "Pairing window: 60 seconds" in output
|
||||
assert "Requested USB mode: auto" in output
|
||||
assert "Active USB mode: Switch probe" in output
|
||||
assert "Mode capabilities: input, rumble, motion" in output
|
||||
|
||||
device.configuration = struct.pack(
|
||||
"<HB5x", 60, config_manager.REQUESTED_MODE_DINPUT
|
||||
)
|
||||
device.active_mode = config_manager.ACTIVE_MODE_DINPUT
|
||||
device.capabilities = config_manager.CAPABILITY_INPUT
|
||||
assert config_manager.main(["status"]) == 0
|
||||
generic_output = capsys.readouterr().out
|
||||
assert "Requested USB mode: dinput" in generic_output
|
||||
assert "Active USB mode: DInput" in generic_output
|
||||
assert "Mode capabilities: input only\n" in generic_output
|
||||
assert "rumble" not in generic_output.lower()
|
||||
|
||||
assert config_manager.main(["pairings", "list"]) == 0
|
||||
output = capsys.readouterr().out
|
||||
|
|
@ -1515,17 +1605,46 @@ def test_config_cli_preserves_requested_mode(
|
|||
assert "requested_mode=xinput" in output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mode_name", "requested_mode", "active_mode", "capabilities"),
|
||||
(
|
||||
(
|
||||
"xinput",
|
||||
config_manager.REQUESTED_MODE_XINPUT,
|
||||
config_manager.ACTIVE_MODE_XINPUT,
|
||||
config_manager.CAPABILITY_INPUT |
|
||||
config_manager.CAPABILITY_RUMBLE,
|
||||
),
|
||||
(
|
||||
"dinput",
|
||||
config_manager.REQUESTED_MODE_DINPUT,
|
||||
config_manager.ACTIVE_MODE_DINPUT,
|
||||
config_manager.CAPABILITY_INPUT,
|
||||
),
|
||||
(
|
||||
"mac",
|
||||
config_manager.REQUESTED_MODE_MAC,
|
||||
config_manager.ACTIVE_MODE_MAC,
|
||||
config_manager.CAPABILITY_INPUT,
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_mode_cli_changes_then_noops(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
mode_name: str,
|
||||
requested_mode: int,
|
||||
active_mode: int,
|
||||
capabilities: int,
|
||||
) -> None:
|
||||
previous = FakeDevice()
|
||||
reenumerated = FakeDevice()
|
||||
reenumerated.address = 8
|
||||
reenumerated.configuration = struct.pack(
|
||||
"<HB5x", 60, config_manager.REQUESTED_MODE_XINPUT
|
||||
"<HB5x", 60, requested_mode
|
||||
)
|
||||
reenumerated.active_mode = config_manager.ACTIVE_MODE_XINPUT
|
||||
reenumerated.active_mode = active_mode
|
||||
reenumerated.capabilities = capabilities
|
||||
scans = iter(
|
||||
((previous,), (previous,), (), (reenumerated,))
|
||||
)
|
||||
|
|
@ -1536,29 +1655,36 @@ def test_mode_cli_changes_then_noops(
|
|||
)
|
||||
monkeypatch.setattr(config_manager.time, "sleep", lambda _seconds: None)
|
||||
|
||||
assert config_manager.main(["mode", "xinput"]) == 0
|
||||
assert capsys.readouterr().out == "USB mode changed to xinput.\n"
|
||||
assert config_manager.main(["mode", mode_name]) == 0
|
||||
assert capsys.readouterr().out == f"USB mode changed to {mode_name}.\n"
|
||||
assert previous.reboot_transaction_ids == [previous.transaction_id]
|
||||
|
||||
monkeypatch.setattr(
|
||||
config_manager, "_candidate_devices", lambda: (reenumerated,)
|
||||
)
|
||||
assert config_manager.main(["mode", "xinput"]) == 0
|
||||
assert capsys.readouterr().out == "USB mode is already xinput.\n"
|
||||
assert config_manager.main(["mode", mode_name]) == 0
|
||||
assert capsys.readouterr().out == f"USB mode is already {mode_name}.\n"
|
||||
assert reenumerated.out_requests == []
|
||||
|
||||
|
||||
def test_mode_parser_rejects_unimplemented_modes() -> None:
|
||||
for mode in ("dinput", "mac"):
|
||||
with pytest.raises(SystemExit):
|
||||
config_manager.build_parser().parse_args(["mode", mode])
|
||||
def test_mode_parser_accepts_all_implemented_modes() -> None:
|
||||
for mode in config_manager.REQUESTED_MODE_NAMES:
|
||||
args = config_manager.build_parser().parse_args(["mode", mode])
|
||||
assert args.mode == mode
|
||||
|
||||
|
||||
def test_candidate_discovery_checks_switch_and_xinput_identities(
|
||||
def test_candidate_discovery_checks_all_usb_identities(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
lookups: list[tuple[int, int]] = []
|
||||
|
||||
assert config_manager.USB_IDENTITIES == (
|
||||
(0x057E, 0x2009),
|
||||
(0xCAFE, 0x4010),
|
||||
(0xCAFE, 0x4020),
|
||||
(0xCAFE, 0x4021),
|
||||
)
|
||||
|
||||
def find(**arguments: object) -> tuple[object, ...]:
|
||||
lookups.append(
|
||||
(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue