Implement per-controller ABXY toggle
This commit is contained in:
parent
fa8ccf0b12
commit
94ccfc7220
8 changed files with 247 additions and 15 deletions
12
README.md
12
README.md
|
|
@ -79,6 +79,18 @@ The Pico 2 W onboard LED reports the overall Bluetooth state:
|
||||||
- **Pair a new controller**: hold BOOTSEL until the LED double-blinks, then put the controller into its explicit Bluetooth pairing mode.
|
- **Pair a new controller**: hold BOOTSEL until the LED double-blinks, then put the controller into its explicit Bluetooth pairing mode.
|
||||||
- **Pairing window expires**: scanning and incoming connections stop; already connected controllers remain connected.
|
- **Pairing window expires**: scanning and incoming connections stop; already connected controllers remain connected.
|
||||||
|
|
||||||
|
### Per-controller ABXY layout
|
||||||
|
|
||||||
|
Each connected AIO controller can toggle its own ABXY layout by pressing **L + R + Select + Start** together. On DualSense, use **L1 + R1 + Create + Options**. The controller gives one short rumble when the toggle is accepted; release the chord before toggling again.
|
||||||
|
|
||||||
|
- **Standard**: south→B, east→A, west→Y, north→X.
|
||||||
|
- **Swapped**: south→A, east→B, west→X, north→Y.
|
||||||
|
- The chord is consumed locally and is not forwarded to the Switch.
|
||||||
|
- Other controller slots are unaffected.
|
||||||
|
- Layout returns to the configured default after disconnect or reboot.
|
||||||
|
|
||||||
|
Edit `controller_hotkey_config.h` to change the chord, default layout, or confirmation pulse.
|
||||||
|
|
||||||
### Per-slot controller colors
|
### Per-slot controller colors
|
||||||
|
|
||||||
Each AIO slot has one color shared by its emulated Switch Pro grips and its physical Bluetooth controller:
|
Each AIO slot has one color shared by its emulated Switch Pro grips and its physical Bluetooth controller:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "bluepad32_input_backend.h"
|
#include "bluepad32_input_backend.h"
|
||||||
|
#include "controller_hotkey_config.h"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
@ -23,6 +24,20 @@ constexpr uint16_t kRumbleDurationMs = 50;
|
||||||
constexpr uint32_t kRumblePollIntervalMs = 5;
|
constexpr uint32_t kRumblePollIntervalMs = 5;
|
||||||
constexpr uint8_t kSlotCount = BLUEPAD32_INPUT_BACKEND_SLOT_COUNT;
|
constexpr uint8_t kSlotCount = BLUEPAD32_INPUT_BACKEND_SLOT_COUNT;
|
||||||
constexpr uint32_t kPairingWindowDurationMs = 60000;
|
constexpr uint32_t kPairingWindowDurationMs = 60000;
|
||||||
|
constexpr uint32_t kAbxyHotkeyButtonMask =
|
||||||
|
SWITCH_ABXY_HOTKEY_BUTTON_MASK;
|
||||||
|
constexpr uint32_t kAbxyHotkeyMiscMask = SWITCH_ABXY_HOTKEY_MISC_MASK;
|
||||||
|
constexpr bool kDefaultSwapAbxy = SWITCH_ABXY_DEFAULT_SWAPPED != 0;
|
||||||
|
constexpr uint32_t kAbxyFeedbackDurationMs =
|
||||||
|
SWITCH_ABXY_FEEDBACK_DURATION_MS;
|
||||||
|
constexpr uint8_t kAbxyFeedbackWeakMagnitude =
|
||||||
|
SWITCH_ABXY_FEEDBACK_WEAK_MAGNITUDE;
|
||||||
|
constexpr uint8_t kAbxyFeedbackStrongMagnitude =
|
||||||
|
SWITCH_ABXY_FEEDBACK_STRONG_MAGNITUDE;
|
||||||
|
|
||||||
|
static_assert(kAbxyHotkeyButtonMask != 0);
|
||||||
|
static_assert(kAbxyHotkeyMiscMask != 0);
|
||||||
|
static_assert(kAbxyFeedbackDurationMs > 0);
|
||||||
|
|
||||||
static_assert(kSlotCount == 4);
|
static_assert(kSlotCount == 4);
|
||||||
static_assert(SWITCH_PICO_HID_INSTANCE_COUNT == kSlotCount);
|
static_assert(SWITCH_PICO_HID_INSTANCE_COUNT == kSlotCount);
|
||||||
|
|
@ -56,6 +71,11 @@ struct BackendSlot {
|
||||||
uint32_t connection_generation;
|
uint32_t connection_generation;
|
||||||
bool active;
|
bool active;
|
||||||
bool rumble_pending;
|
bool rumble_pending;
|
||||||
|
bool swap_abxy;
|
||||||
|
bool hotkey_latched;
|
||||||
|
bool feedback_pending;
|
||||||
|
uint32_t feedback_generation;
|
||||||
|
uint32_t feedback_until_ms;
|
||||||
RumbleEnvelope pending_rumble;
|
RumbleEnvelope pending_rumble;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -243,7 +263,8 @@ bool has_motion(const uni_gamepad_t& gamepad) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchInputState map_gamepad(const uni_gamepad_t& gamepad) {
|
SwitchInputState map_gamepad(const uni_gamepad_t& gamepad,
|
||||||
|
bool swap_abxy) {
|
||||||
SwitchInputState state = make_neutral_state();
|
SwitchInputState state = make_neutral_state();
|
||||||
|
|
||||||
state.dpad_up = (gamepad.dpad & DPAD_UP) != 0;
|
state.dpad_up = (gamepad.dpad & DPAD_UP) != 0;
|
||||||
|
|
@ -256,6 +277,14 @@ SwitchInputState map_gamepad(const uni_gamepad_t& gamepad) {
|
||||||
state.button_a = (gamepad.buttons & BUTTON_B) != 0;
|
state.button_a = (gamepad.buttons & BUTTON_B) != 0;
|
||||||
state.button_y = (gamepad.buttons & BUTTON_X) != 0;
|
state.button_y = (gamepad.buttons & BUTTON_X) != 0;
|
||||||
state.button_x = (gamepad.buttons & BUTTON_Y) != 0;
|
state.button_x = (gamepad.buttons & BUTTON_Y) != 0;
|
||||||
|
if (swap_abxy) {
|
||||||
|
bool temporary = state.button_a;
|
||||||
|
state.button_a = state.button_b;
|
||||||
|
state.button_b = temporary;
|
||||||
|
temporary = state.button_x;
|
||||||
|
state.button_x = state.button_y;
|
||||||
|
state.button_y = temporary;
|
||||||
|
}
|
||||||
state.button_l = (gamepad.buttons & BUTTON_SHOULDER_L) != 0;
|
state.button_l = (gamepad.buttons & BUTTON_SHOULDER_L) != 0;
|
||||||
state.button_r = (gamepad.buttons & BUTTON_SHOULDER_R) != 0;
|
state.button_r = (gamepad.buttons & BUTTON_SHOULDER_R) != 0;
|
||||||
state.button_zl = (gamepad.buttons & BUTTON_TRIGGER_L) != 0 || gamepad.brake >= kTriggerThreshold;
|
state.button_zl = (gamepad.buttons & BUTTON_TRIGGER_L) != 0 || gamepad.brake >= kTriggerThreshold;
|
||||||
|
|
@ -290,6 +319,36 @@ SwitchInputState map_gamepad(const uni_gamepad_t& gamepad) {
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
struct LayoutDecision {
|
||||||
|
bool swap_abxy;
|
||||||
|
bool suppress_hotkey;
|
||||||
|
};
|
||||||
|
|
||||||
|
LayoutDecision update_layout_hotkey(uint8_t slot_index,
|
||||||
|
uni_hid_device_t* device,
|
||||||
|
const uni_gamepad_t& gamepad) {
|
||||||
|
const bool chord_pressed =
|
||||||
|
(gamepad.buttons & kAbxyHotkeyButtonMask) ==
|
||||||
|
kAbxyHotkeyButtonMask &&
|
||||||
|
(gamepad.misc_buttons & kAbxyHotkeyMiscMask) ==
|
||||||
|
kAbxyHotkeyMiscMask;
|
||||||
|
LayoutDecision decision{kDefaultSwapAbxy, false};
|
||||||
|
|
||||||
|
critical_section_enter_blocking(&g_state_lock);
|
||||||
|
BackendSlot& slot = g_slots[slot_index];
|
||||||
|
if (slot.active && slot.device == device) {
|
||||||
|
if (chord_pressed && !slot.hotkey_latched) {
|
||||||
|
slot.swap_abxy = !slot.swap_abxy;
|
||||||
|
slot.feedback_pending = true;
|
||||||
|
slot.feedback_generation = slot.connection_generation;
|
||||||
|
}
|
||||||
|
slot.hotkey_latched = chord_pressed;
|
||||||
|
decision = {slot.swap_abxy, chord_pressed};
|
||||||
|
}
|
||||||
|
critical_section_exit(&g_state_lock);
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool pairing_window_active_at(uint32_t now_ms) {
|
bool pairing_window_active_at(uint32_t now_ms) {
|
||||||
return g_pairing_window_open &&
|
return g_pairing_window_open &&
|
||||||
|
|
@ -379,25 +438,48 @@ void process_rumble_timer(btstack_timer_source_t* timer) {
|
||||||
for (uint8_t slot_index = 0; slot_index < kSlotCount; ++slot_index) {
|
for (uint8_t slot_index = 0; slot_index < kSlotCount; ++slot_index) {
|
||||||
RumbleEnvelope envelope{};
|
RumbleEnvelope envelope{};
|
||||||
uni_hid_device_t* device = nullptr;
|
uni_hid_device_t* device = nullptr;
|
||||||
bool dispatch = false;
|
bool feedback_dispatch = false;
|
||||||
|
bool host_dispatch = false;
|
||||||
|
|
||||||
critical_section_enter_blocking(&g_state_lock);
|
critical_section_enter_blocking(&g_state_lock);
|
||||||
BackendSlot& slot = g_slots[slot_index];
|
BackendSlot& slot = g_slots[slot_index];
|
||||||
if (slot.rumble_pending) {
|
if (slot.feedback_pending) {
|
||||||
|
feedback_dispatch =
|
||||||
|
slot.active && slot.device != nullptr &&
|
||||||
|
slot.feedback_generation == slot.connection_generation &&
|
||||||
|
slot.device->report_parser.play_dual_rumble != nullptr;
|
||||||
|
slot.feedback_pending = false;
|
||||||
|
if (feedback_dispatch) {
|
||||||
|
device = slot.device;
|
||||||
|
slot.feedback_until_ms =
|
||||||
|
now_ms + kAbxyFeedbackDurationMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool feedback_active =
|
||||||
|
static_cast<int32_t>(now_ms - slot.feedback_until_ms) < 0;
|
||||||
|
if (!feedback_dispatch && !feedback_active &&
|
||||||
|
slot.rumble_pending) {
|
||||||
envelope = slot.pending_rumble;
|
envelope = slot.pending_rumble;
|
||||||
slot.rumble_pending = false;
|
slot.rumble_pending = false;
|
||||||
dispatch = envelope.slot == slot_index && slot.active &&
|
host_dispatch =
|
||||||
slot.device != nullptr &&
|
envelope.slot == slot_index && slot.active &&
|
||||||
envelope.connection_generation ==
|
slot.device != nullptr &&
|
||||||
slot.connection_generation;
|
envelope.connection_generation ==
|
||||||
if (dispatch) {
|
slot.connection_generation;
|
||||||
|
if (host_dispatch) {
|
||||||
device = slot.device;
|
device = slot.device;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
critical_section_exit(&g_state_lock);
|
critical_section_exit(&g_state_lock);
|
||||||
|
|
||||||
if (dispatch &&
|
if (feedback_dispatch) {
|
||||||
device->report_parser.play_dual_rumble != nullptr) {
|
device->report_parser.play_dual_rumble(
|
||||||
|
device, 0, kAbxyFeedbackDurationMs,
|
||||||
|
kAbxyFeedbackWeakMagnitude,
|
||||||
|
kAbxyFeedbackStrongMagnitude);
|
||||||
|
} else if (host_dispatch &&
|
||||||
|
device->report_parser.play_dual_rumble != nullptr) {
|
||||||
device->report_parser.play_dual_rumble(
|
device->report_parser.play_dual_rumble(
|
||||||
device, 0, kRumbleDurationMs,
|
device, 0, kRumbleDurationMs,
|
||||||
envelope.rumble.high_frequency_magnitude,
|
envelope.rumble.high_frequency_magnitude,
|
||||||
|
|
@ -464,6 +546,10 @@ void platform_on_device_connected(uni_hid_device_t* device) {
|
||||||
if (!slot.active && slot.device == nullptr) {
|
if (!slot.active && slot.device == nullptr) {
|
||||||
slot.device = device;
|
slot.device = device;
|
||||||
slot.rumble_pending = false;
|
slot.rumble_pending = false;
|
||||||
|
slot.swap_abxy = kDefaultSwapAbxy;
|
||||||
|
slot.hotkey_latched = false;
|
||||||
|
slot.feedback_pending = false;
|
||||||
|
slot.feedback_until_ms = 0;
|
||||||
tracked_connection = true;
|
tracked_connection = true;
|
||||||
} else {
|
} else {
|
||||||
tracked_connection = slot.device == device;
|
tracked_connection = slot.device == device;
|
||||||
|
|
@ -492,6 +578,10 @@ void platform_on_device_disconnected(uni_hid_device_t* device) {
|
||||||
slot.device = nullptr;
|
slot.device = nullptr;
|
||||||
slot.active = false;
|
slot.active = false;
|
||||||
slot.rumble_pending = false;
|
slot.rumble_pending = false;
|
||||||
|
slot.swap_abxy = kDefaultSwapAbxy;
|
||||||
|
slot.hotkey_latched = false;
|
||||||
|
slot.feedback_pending = false;
|
||||||
|
slot.feedback_until_ms = 0;
|
||||||
++slot.connection_generation;
|
++slot.connection_generation;
|
||||||
disconnected_tracked_device = true;
|
disconnected_tracked_device = true;
|
||||||
}
|
}
|
||||||
|
|
@ -523,6 +613,10 @@ uni_error_t platform_on_device_ready(uni_hid_device_t* device) {
|
||||||
slot.state = make_neutral_state();
|
slot.state = make_neutral_state();
|
||||||
slot.active = true;
|
slot.active = true;
|
||||||
slot.rumble_pending = false;
|
slot.rumble_pending = false;
|
||||||
|
slot.swap_abxy = kDefaultSwapAbxy;
|
||||||
|
slot.hotkey_latched = false;
|
||||||
|
slot.feedback_pending = false;
|
||||||
|
slot.feedback_until_ms = 0;
|
||||||
++slot.state_generation;
|
++slot.state_generation;
|
||||||
became_active = true;
|
became_active = true;
|
||||||
}
|
}
|
||||||
|
|
@ -541,14 +635,23 @@ uni_error_t platform_on_device_ready(uni_hid_device_t* device) {
|
||||||
return UNI_ERROR_SUCCESS;
|
return UNI_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void platform_on_controller_data(uni_hid_device_t* device, uni_controller_t* controller) {
|
void platform_on_controller_data(uni_hid_device_t* device,
|
||||||
|
uni_controller_t* controller) {
|
||||||
const int slot_index = slot_for_device(device);
|
const int slot_index = slot_for_device(device);
|
||||||
if (slot_index < 0 || controller == nullptr ||
|
if (slot_index < 0 || controller == nullptr ||
|
||||||
controller->klass != UNI_CONTROLLER_CLASS_GAMEPAD) {
|
controller->klass != UNI_CONTROLLER_CLASS_GAMEPAD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uni_gamepad_t gamepad = controller->gamepad;
|
||||||
|
const LayoutDecision layout = update_layout_hotkey(
|
||||||
|
static_cast<uint8_t>(slot_index), device, gamepad);
|
||||||
|
if (layout.suppress_hotkey) {
|
||||||
|
gamepad.buttons &= ~kAbxyHotkeyButtonMask;
|
||||||
|
gamepad.misc_buttons &= ~kAbxyHotkeyMiscMask;
|
||||||
|
}
|
||||||
publish_device_state(static_cast<uint8_t>(slot_index), device,
|
publish_device_state(static_cast<uint8_t>(slot_index), device,
|
||||||
map_gamepad(controller->gamepad));
|
map_gamepad(gamepad, layout.swap_abxy));
|
||||||
}
|
}
|
||||||
|
|
||||||
const uni_property_t* platform_get_property(uni_property_idx_t index) {
|
const uni_property_t* platform_get_property(uni_property_idx_t index) {
|
||||||
|
|
@ -621,6 +724,7 @@ void bluepad32_input_backend_init() {
|
||||||
slot = {};
|
slot = {};
|
||||||
slot.state = make_neutral_state();
|
slot.state = make_neutral_state();
|
||||||
slot.pending_rumble.slot = slot_index;
|
slot.pending_rumble.slot = slot_index;
|
||||||
|
slot.swap_abxy = kDefaultSwapAbxy;
|
||||||
g_consumed_generation[slot_index] = 0;
|
g_consumed_generation[slot_index] = 0;
|
||||||
g_last_snapshot_generation[slot_index] = 0;
|
g_last_snapshot_generation[slot_index] = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
controller_hotkey_config.h
Normal file
17
controller_hotkey_config.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Compile-time AIO controller hotkey configuration.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Bluepad32 button masks. Default chord: L + R + SELECT + START.
|
||||||
|
#define SWITCH_ABXY_HOTKEY_BUTTON_MASK \
|
||||||
|
(BUTTON_SHOULDER_L | BUTTON_SHOULDER_R)
|
||||||
|
#define SWITCH_ABXY_HOTKEY_MISC_MASK \
|
||||||
|
(MISC_BUTTON_SELECT | MISC_BUTTON_START)
|
||||||
|
|
||||||
|
// 0 starts each new connection in Nintendo positional layout; 1 starts swapped.
|
||||||
|
#define SWITCH_ABXY_DEFAULT_SWAPPED 0
|
||||||
|
|
||||||
|
// Local confirmation pulse sent only to the controller that toggled.
|
||||||
|
#define SWITCH_ABXY_FEEDBACK_DURATION_MS 120
|
||||||
|
#define SWITCH_ABXY_FEEDBACK_WEAK_MAGNITUDE 0x80
|
||||||
|
#define SWITCH_ABXY_FEEDBACK_STRONG_MAGNITUDE 0x80
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -702,6 +702,102 @@ void test_slot_lighting() {
|
||||||
"controller without RGB support did not receive its slot LED");
|
"controller without RGB support did not receive its slot LED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void require_south_button_mapping(const SwitchInputState& state,
|
||||||
|
bool swapped,
|
||||||
|
const char* message) {
|
||||||
|
require(state.button_a == swapped && state.button_b == !swapped &&
|
||||||
|
!state.button_x && !state.button_y,
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_abxy_hotkey() {
|
||||||
|
start_pairing_backend();
|
||||||
|
uni_hid_device_t slot_zero = device(0);
|
||||||
|
uni_hid_device_t slot_one = device(1);
|
||||||
|
require(platform_on_device_ready(&slot_zero) == UNI_ERROR_SUCCESS &&
|
||||||
|
platform_on_device_ready(&slot_one) == UNI_ERROR_SUCCESS,
|
||||||
|
"ABXY test controllers did not become ready");
|
||||||
|
|
||||||
|
uni_controller_t input{};
|
||||||
|
input.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||||
|
input.gamepad.buttons = BUTTON_A;
|
||||||
|
platform_on_controller_data(&slot_zero, &input);
|
||||||
|
SwitchInputState snapshot{};
|
||||||
|
require(bluepad32_input_backend_snapshot(0, &snapshot),
|
||||||
|
"slot 0 ABXY state was not published");
|
||||||
|
require_south_button_mapping(
|
||||||
|
snapshot, kDefaultSwapAbxy,
|
||||||
|
"slot 0 did not start in the configured ABXY layout");
|
||||||
|
|
||||||
|
input.gamepad.buttons =
|
||||||
|
kAbxyHotkeyButtonMask | BUTTON_A;
|
||||||
|
input.gamepad.misc_buttons = kAbxyHotkeyMiscMask;
|
||||||
|
platform_on_controller_data(&slot_zero, &input);
|
||||||
|
require(bluepad32_input_backend_snapshot(0, &snapshot),
|
||||||
|
"toggled slot 0 state was not published");
|
||||||
|
require_south_button_mapping(
|
||||||
|
snapshot, !kDefaultSwapAbxy,
|
||||||
|
"hotkey did not toggle slot 0 ABXY mapping");
|
||||||
|
require(!snapshot.button_l && !snapshot.button_r &&
|
||||||
|
!snapshot.button_minus && !snapshot.button_plus,
|
||||||
|
"hotkey chord leaked into the Switch report");
|
||||||
|
|
||||||
|
bluepad32_input_backend_queue_rumble(
|
||||||
|
0, SwitchRumbleOutput{0x11, 0x22});
|
||||||
|
process_rumble_timer(&g_rumble_timer);
|
||||||
|
require(slot_zero.rumble_calls == 1 &&
|
||||||
|
slot_zero.last_high == kAbxyFeedbackWeakMagnitude &&
|
||||||
|
slot_zero.last_low == kAbxyFeedbackStrongMagnitude &&
|
||||||
|
g_slots[0].rumble_pending,
|
||||||
|
"ABXY confirmation did not take priority over host rumble");
|
||||||
|
|
||||||
|
platform_on_controller_data(&slot_zero, &input);
|
||||||
|
process_rumble_timer(&g_rumble_timer);
|
||||||
|
require(g_slots[0].swap_abxy == !kDefaultSwapAbxy &&
|
||||||
|
slot_zero.rumble_calls == 1,
|
||||||
|
"held hotkey toggled or rumbled more than once");
|
||||||
|
|
||||||
|
input.gamepad = {};
|
||||||
|
platform_on_controller_data(&slot_zero, &input);
|
||||||
|
input.gamepad.buttons = kAbxyHotkeyButtonMask | BUTTON_A;
|
||||||
|
input.gamepad.misc_buttons = kAbxyHotkeyMiscMask;
|
||||||
|
platform_on_controller_data(&slot_zero, &input);
|
||||||
|
process_rumble_timer(&g_rumble_timer);
|
||||||
|
require(g_slots[0].swap_abxy == kDefaultSwapAbxy &&
|
||||||
|
slot_zero.rumble_calls == 2,
|
||||||
|
"released hotkey did not re-arm for a second toggle");
|
||||||
|
|
||||||
|
now_ms = kAbxyFeedbackDurationMs - 1;
|
||||||
|
process_rumble_timer(&g_rumble_timer);
|
||||||
|
require(slot_zero.rumble_calls == 2 && g_slots[0].rumble_pending,
|
||||||
|
"host rumble interrupted ABXY confirmation");
|
||||||
|
now_ms = kAbxyFeedbackDurationMs;
|
||||||
|
process_rumble_timer(&g_rumble_timer);
|
||||||
|
require(slot_zero.rumble_calls == 3 &&
|
||||||
|
slot_zero.last_high == 0x22 &&
|
||||||
|
slot_zero.last_low == 0x11 &&
|
||||||
|
!g_slots[0].rumble_pending,
|
||||||
|
"deferred host rumble did not resume after confirmation");
|
||||||
|
|
||||||
|
uni_controller_t peer_input{};
|
||||||
|
peer_input.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
|
||||||
|
peer_input.gamepad.buttons = BUTTON_A;
|
||||||
|
platform_on_controller_data(&slot_one, &peer_input);
|
||||||
|
require(bluepad32_input_backend_snapshot(1, &snapshot),
|
||||||
|
"slot 1 ABXY state was not published");
|
||||||
|
require_south_button_mapping(
|
||||||
|
snapshot, kDefaultSwapAbxy,
|
||||||
|
"slot 0 hotkey changed slot 1 layout");
|
||||||
|
|
||||||
|
platform_on_device_disconnected(&slot_zero);
|
||||||
|
uni_hid_device_t replacement = device(0);
|
||||||
|
require(platform_on_device_ready(&replacement) == UNI_ERROR_SUCCESS &&
|
||||||
|
g_slots[0].swap_abxy == kDefaultSwapAbxy &&
|
||||||
|
!g_slots[0].hotkey_latched &&
|
||||||
|
!g_slots[0].feedback_pending,
|
||||||
|
"disconnect did not reset slot 0 hotkey state");
|
||||||
|
}
|
||||||
|
|
||||||
void test_flash_core_start_contract() {
|
void test_flash_core_start_contract() {
|
||||||
bluepad32_input_backend_init();
|
bluepad32_input_backend_init();
|
||||||
flash_core_init_result = false;
|
flash_core_init_result = false;
|
||||||
|
|
@ -753,6 +849,8 @@ int main(int argc, char** argv) {
|
||||||
test_pairing_window_policy();
|
test_pairing_window_policy();
|
||||||
} else if (scenario == "slot-lighting") {
|
} else if (scenario == "slot-lighting") {
|
||||||
test_slot_lighting();
|
test_slot_lighting();
|
||||||
|
} else if (scenario == "abxy-hotkey") {
|
||||||
|
test_abxy_hotkey();
|
||||||
} else if (scenario == "flash-core-start") {
|
} else if (scenario == "flash-core-start") {
|
||||||
test_flash_core_start_contract();
|
test_flash_core_start_contract();
|
||||||
} else if (scenario == "flash-core-failure") {
|
} else if (scenario == "flash-core-failure") {
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,9 @@ enum {
|
||||||
BUTTON_TRIGGER_R = 1 << 7,
|
BUTTON_TRIGGER_R = 1 << 7,
|
||||||
BUTTON_THUMB_L = 1 << 8,
|
BUTTON_THUMB_L = 1 << 8,
|
||||||
BUTTON_THUMB_R = 1 << 9,
|
BUTTON_THUMB_R = 1 << 9,
|
||||||
MISC_BUTTON_SELECT = 1 << 0,
|
MISC_BUTTON_SYSTEM = 1 << 0,
|
||||||
MISC_BUTTON_START = 1 << 1,
|
MISC_BUTTON_SELECT = 1 << 1,
|
||||||
MISC_BUTTON_SYSTEM = 1 << 2,
|
MISC_BUTTON_START = 1 << 2,
|
||||||
MISC_BUTTON_CAPTURE = 1 << 3,
|
MISC_BUTTON_CAPTURE = 1 << 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
||||||
"lifecycle",
|
"lifecycle",
|
||||||
"pairing-policy",
|
"pairing-policy",
|
||||||
"slot-lighting",
|
"slot-lighting",
|
||||||
|
"abxy-hotkey",
|
||||||
"flash-core-start",
|
"flash-core-start",
|
||||||
"flash-core-failure",
|
"flash-core-failure",
|
||||||
):
|
):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue