Support Bluepad32 gamepads as native Joy-Con pair sources

This commit is contained in:
Joey Yakimowich-Payne 2026-09-12 23:03:15 -06:00
commit a17ca603aa
33 changed files with 2529 additions and 851 deletions

View file

@ -13,8 +13,8 @@
#include <inttypes.h>
extern "C" int probe_debug_printf(const char* format, ...);
#endif
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#include "dualsense_input.h"
#if SWITCH2_BRIDGE_FULL_INPUT
#include "native_gamepad_input.h"
#endif
#if SWITCH2_BRIDGE_WII_INPUT
#include <math.h>
@ -26,9 +26,9 @@ extern "C" int probe_debug_printf(const char* format, ...);
#if !SWITCH_PICO_SWITCH2_USB_BRIDGE || !SWITCH_PICO_BLUEPAD32
#error "The controller bridge requires Bluepad32"
#elif SWITCH2_BRIDGE_DUALSENSE_INPUT
#if !SWITCH_PICO_ENABLE_CLASSIC
#error "The DualSense bridge requires Classic Bluetooth"
#elif SWITCH2_BRIDGE_FULL_INPUT
#if SWITCH2_BRIDGE_DUALSENSE_INPUT && !SWITCH_PICO_ENABLE_CLASSIC
#error "The DualSense source requires Classic Bluetooth"
#endif
#elif !SWITCH_PICO_ENABLE_BLE || !SWITCH_PICO_SWITCH2_MOUSE_CAPTURE || \
!SWITCH_PICO_SWITCH2_MOUSE_CAPTURE_NATIVE
@ -36,7 +36,7 @@ extern "C" int probe_debug_printf(const char* format, ...);
#endif
namespace {
#if !SWITCH2_BRIDGE_DUALSENSE_INPUT
#if !SWITCH2_BRIDGE_FULL_INPUT
constexpr uint8_t kSourceAddress[] = {SWITCH2_BRIDGE_SOURCE_ADDRESS_BYTES};
static_assert(sizeof(kSourceAddress) == 6, "Select one physical Bluetooth address");
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
@ -56,7 +56,7 @@ bool g_start_attempted;
bool g_flash_ready;
#if SWITCH2_BRIDGE_WII_INPUT
probe_controller_input g_input;
#elif !SWITCH2_BRIDGE_DUALSENSE_INPUT
#elif !SWITCH2_BRIDGE_FULL_INPUT
probe_controller_input g_inputs[PROBE_CONTROLLER_COUNT];
uint32_t g_received_times[PROBE_CONTROLLER_COUNT];
#endif
@ -382,9 +382,9 @@ extern "C" void probe_controller_input_clock_init(void) {
extern "C" void probe_controller_input_init(void) {
if (g_initialized) return;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_FULL_INPUT
bluepad32_input_backend_init();
probe_dualsense_input_init();
probe_native_gamepad_input_init();
#elif SWITCH2_BRIDGE_WII_INPUT
bluepad32_input_backend_init();
bluepad32_input_backend_select_wii_source(kSourceAddress);
@ -484,17 +484,17 @@ extern "C" void probe_controller_input_set_native_features(uint8_t features) {
}
#endif
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_FULL_INPUT
extern "C" void probe_controller_input_set_full_stick_calibration(
uint8_t instance, const uint8_t calibration[9]) {
probe_dualsense_input_set_stick_calibration(instance, calibration);
probe_native_gamepad_input_set_stick_calibration(instance, calibration);
}
#endif
extern "C" void probe_controller_input_set_native_stream(uint8_t instance, bool enabled) {
if (instance >= PROBE_CONTROLLER_COUNT) return;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
probe_dualsense_input_set_native_stream(instance, enabled && g_flash_ready);
#if SWITCH2_BRIDGE_FULL_INPUT
probe_native_gamepad_input_set_native_stream(instance, enabled && g_flash_ready);
#elif SWITCH2_BRIDGE_WII_INPUT
enabled = enabled && g_flash_ready;
if (g_native_stream != enabled || !enabled) discard_wii_output();
@ -508,8 +508,8 @@ extern "C" void probe_controller_input_set_native_stream(uint8_t instance, bool
extern "C" uint32_t probe_controller_input_peek_native_report(
uint8_t instance, uint32_t now_ms, uint8_t report[63]) {
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return 0;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
return probe_dualsense_input_peek_native_report(instance, now_ms, report);
#if SWITCH2_BRIDGE_FULL_INPUT
return probe_native_gamepad_input_peek_native_report(instance, now_ms, report);
#elif SWITCH2_BRIDGE_WII_INPUT
(void)now_ms;
return prepare_wii_report(report);
@ -520,8 +520,8 @@ extern "C" uint32_t probe_controller_input_peek_native_report(
extern "C" bool probe_controller_input_commit_native_report(uint8_t instance, uint32_t serial) {
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return false;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
return probe_dualsense_input_commit_native_report(instance, serial);
#if SWITCH2_BRIDGE_FULL_INPUT
return probe_native_gamepad_input_commit_native_report(instance, serial);
#elif SWITCH2_BRIDGE_WII_INPUT
if (!g_native_stream || !serial || serial != g_pending_serial ||
g_pending_generation != g_wii_generation || !g_wii_active) return false;
@ -543,8 +543,8 @@ extern "C" bool probe_controller_input_play_sample(uint8_t instance, uint8_t sam
if (token != nullptr) *token = 0;
return false;
}
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
return bluepad32_input_backend_dualsense_sample_request(instance, sample_id, token);
#if SWITCH2_BRIDGE_FULL_INPUT
return bluepad32_input_backend_native_sample_request(instance, sample_id, token);
#elif SWITCH2_BRIDGE_WII_INPUT
return bluepad32_input_backend_wii_sample_request(sample_id, token);
#else
@ -555,9 +555,9 @@ extern "C" bool probe_controller_input_play_sample(uint8_t instance, uint8_t sam
extern "C" int probe_controller_input_sample_result(uint8_t instance, uint64_t token, uint32_t now_ms) {
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return -1;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_FULL_INPUT
(void)now_ms;
return bluepad32_input_backend_dualsense_sample_result(instance, token);
return bluepad32_input_backend_native_sample_result(instance, token);
#elif SWITCH2_BRIDGE_WII_INPUT
(void)now_ms;
return bluepad32_input_backend_wii_sample_result(token);
@ -568,8 +568,8 @@ extern "C" int probe_controller_input_sample_result(uint8_t instance, uint64_t t
extern "C" void probe_controller_input_cancel_sample(uint8_t instance) {
if (instance >= PROBE_CONTROLLER_COUNT) return;
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
bluepad32_input_backend_dualsense_sample_cancel(instance);
#if SWITCH2_BRIDGE_FULL_INPUT
bluepad32_input_backend_native_sample_cancel(instance);
#elif SWITCH2_BRIDGE_WII_INPUT
bluepad32_input_backend_wii_sample_cancel();
#else
@ -584,8 +584,8 @@ extern "C" void probe_controller_input_poll(uint8_t instance, uint32_t now_ms,
*out = {};
return;
}
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
probe_dualsense_input_poll(instance, now_ms, out);
#if SWITCH2_BRIDGE_FULL_INPUT
probe_native_gamepad_input_poll(instance, now_ms, out);
return;
#elif SWITCH2_BRIDGE_WII_INPUT
poll_wii_source(now_ms);
@ -618,7 +618,7 @@ extern "C" void probe_controller_input_poll(uint8_t instance, uint32_t now_ms,
g_input.mouse_surface = 0;
}
#endif
#if !SWITCH2_BRIDGE_DUALSENSE_INPUT
#if !SWITCH2_BRIDGE_FULL_INPUT
*out = g_input;
#endif
}

View file

@ -47,7 +47,7 @@ void probe_controller_input_set_stick_calibration(const uint8_t calibration[9]);
// Native feature changes are output barriers, not Bluetooth/IMU resets.
void probe_controller_input_set_native_features(uint8_t features);
#endif
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_FULL_INPUT
// Supply each child's advertised, validated nine-byte stick record. Native
// output stays unavailable until that child's calibration has been supplied.
void probe_controller_input_set_full_stick_calibration(uint8_t instance, const uint8_t calibration[9]);
@ -55,7 +55,7 @@ void probe_controller_input_set_full_stick_calibration(uint8_t instance, const u
// Core0 native07/08 output. Disable discards queued/prepared data; repeated
// enable preserves it. Joy-Con mode relays its bounded FIFO; right-only Wii
// mode synthesizes fresh calibrated sensors and the selected IR pointer.
// DualSense mode splits one full controller into independent R/L output streams;
// Full-controller mode splits one supported gamepad into independent R/L output streams;
// controls remain live while motion is unavailable. Only Wii estimates stationary bias.
// No pairing changes.
void probe_controller_input_set_native_stream(uint8_t instance, bool enabled);

View file

@ -1,13 +0,0 @@
#pragma once
#include "controller_input.h"
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
// Core 0 only. One coherent profile/motion evaluation feeds both native children.
void probe_dualsense_input_init();
void probe_dualsense_input_set_stick_calibration(uint8_t instance, const uint8_t calibration[9]);
void probe_dualsense_input_set_native_stream(uint8_t instance, bool enabled);
void probe_dualsense_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out);
uint32_t probe_dualsense_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]);
bool probe_dualsense_input_commit_native_report(uint8_t instance, uint32_t token);
#endif

View file

@ -319,7 +319,7 @@ static void reset_controller_protocol(uint8_t instance) {
#if SWITCH2_BRIDGE_WII_INPUT
probe_controller_input_set_stick_calibration(stick_calibration);
probe_controller_input_set_native_features(0);
#elif SWITCH2_BRIDGE_DUALSENSE_INPUT
#elif SWITCH2_BRIDGE_FULL_INPUT
probe_controller_input_set_full_stick_calibration(instance, stick_calibration);
#endif
protocol->read_memory = read_memory;
@ -487,8 +487,8 @@ static void protocol_task(probe_usb_controller* controller, uint32_t now) {
#if SWITCH2_BRIDGE_WII_INPUT
probe_debug_printf("[PROBE] Wii cue dispatched itf=%u token=%" PRIu64 "\n",
instance, reply->deferred_token);
#elif SWITCH2_BRIDGE_DUALSENSE_INPUT
probe_debug_printf("[PROBE] DualSense cue dispatched itf=%u token=%" PRIu64 "\n",
#elif SWITCH2_BRIDGE_FULL_INPUT
probe_debug_printf("[PROBE] Native gamepad cue dispatched itf=%u token=%" PRIu64 "\n",
instance, reply->deferred_token);
#else
probe_debug_printf("[PROBE] Source sample ACK itf=%u token=%" PRIu64 "\n",
@ -528,7 +528,7 @@ static void protocol_task(probe_usb_controller* controller, uint32_t now) {
native_serial = probe_controller_input_peek_native_report(instance, now, input);
if (!native_serial) return;
length = sizeof(input);
#if SWITCH2_BRIDGE_WII_INPUT || SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_WII_INPUT || SWITCH2_BRIDGE_FULL_INPUT
input[8] = (uint8_t)(0x30 | ((protocol->enabled_features & 0x20) ? 8 : 0));
#endif
probe_protocol_gate_native_report(protocol, input);
@ -792,6 +792,8 @@ int main(void) {
probe_controller_input_init();
#if SWITCH2_BRIDGE_WII_INPUT
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; selected Wii IR/MotionPlus source enabled\n");
#elif SWITCH2_BRIDGE_FULL_INPUT
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; one supported gamepad feeds the native R/L pair\n");
#else
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; %u selected Joy-Con Bluetooth source(s)\n",
PROBE_CONTROLLER_COUNT);
@ -819,6 +821,10 @@ int main(void) {
#if SWITCH2_BRIDGE_WII_INPUT
probe_debug_printf("[PROBE] Wii IR drives native mouse movement; buttons retain profile mapping; keep Wii still for MotionPlus calibration\n");
probe_debug_printf("[PROBE] Hold BOOTSEL2s for pairing; Wii cue feedback uses bounded ERM patterns, not HD audio waveforms\n");
#elif SWITCH2_BRIDGE_FULL_INPUT
probe_debug_printf("[PROBE] Full gamepad controls on R/L; IMU mask=%u; only Wii requires settling\n",
(unsigned)SWITCH2_BRIDGE_IMU_TARGET_MASK);
probe_debug_printf("[PROBE] Hold BOOTSEL 2s for Bluetooth pairing (never clears pairings); cues use source capabilities\n");
#else
probe_debug_printf("[PROBE] Live Joy-Con buttons/stick/native mouse; hold BOOTSEL 2s for Bluetooth pairing (never clears pairings)\n");
#endif

View file

@ -1,6 +1,6 @@
#include "dualsense_input.h"
#include "native_gamepad_input.h"
#if SWITCH2_BRIDGE_DUALSENSE_INPUT
#if SWITCH2_BRIDGE_FULL_INPUT
#include <limits.h>
#include <string.h>
@ -11,7 +11,7 @@
#include "profile/controller_profile_runtime.h"
#if !SWITCH2_PROBE_HUB || SWITCH2_BRIDGE_WII_INPUT
#error "One DualSense requires the native R/L USB hub source mode"
#error "A full gamepad source requires the native R/L USB hub"
#endif
static_assert(PROBE_CONTROLLER_COUNT == 2);
extern "C" int probe_debug_printf(const char* format, ...);
@ -40,15 +40,17 @@ struct Child {
uint32_t pending_token = 0;
uint32_t pending_us = 0;
uint32_t pending_ticks = 0;
uint32_t pending_motion_sequence = 0;
uint32_t pending_accel_sequence = 0;
uint32_t pending_gyro_sequence = 0;
bool pending_motion = false;
uint8_t pending_report[63]{};
bool have_committed_motion = false;
uint32_t committed_motion_sequence = 0;
uint32_t committed_accel_sequence = 0;
uint32_t committed_gyro_sequence = 0;
uint32_t committed_ticks = 0;
};
Child g_children[PROBE_CONTROLLER_COUNT];
Bluepad32DualSenseBridgeSnapshot g_source;
Bluepad32NativeGamepadSnapshot g_source;
ControllerProfileTransformResult g_mapped;
ProbeNativeMotion g_motion;
bool g_active;
@ -78,8 +80,10 @@ void discard_output(Child& child) {
child.have_committed_motion = false;
}
bool sensors_fresh(uint32_t now_us) {
return g_source.motion_valid && now_us - g_source.motion_received_us < kSensorDeadlineUs;
bool sensors_fresh(const Bluepad32NativeGamepadSnapshot& source, uint32_t now_us) {
return source.accel_valid && source.gyro_valid &&
now_us - source.accel_received_us < kSensorDeadlineUs &&
now_us - source.gyro_received_us < kSensorDeadlineUs;
}
void unpack_stick_pair(const uint8_t* bytes, uint16_t pair[2]) {
@ -147,7 +151,7 @@ void lose_source(uint32_t now_ms) {
g_motion.reset();
for (uint8_t i = 0; i < PROBE_CONTROLLER_COUNT; ++i) {
discard_output(g_children[i]);
bluepad32_input_backend_dualsense_sample_cancel(i);
bluepad32_input_backend_native_sample_cancel(i);
}
g_sensor_status = -1;
}
@ -156,8 +160,8 @@ void lose_source(uint32_t now_ms) {
}
void refresh(uint32_t now_ms) {
Bluepad32DualSenseBridgeSnapshot source;
bluepad32_input_backend_dualsense_snapshot(&source);
Bluepad32NativeGamepadSnapshot source;
bluepad32_input_backend_native_snapshot(&source);
// Snapshot first: source receipt timestamps must not be ahead of this clock.
const uint32_t now_us = time_us_32();
advance_clock(now_us);
@ -174,13 +178,15 @@ void refresh(uint32_t now_ms) {
// motion evaluation. A real publication in the same millisecond still wins.
if (!changed_connection && g_evaluated && g_evaluated_ms == now_ms &&
source.state_generation == g_source.state_generation && source.received_us == g_source.received_us &&
source.motion_sequence == g_source.motion_sequence &&
source.motion_received_us == g_source.motion_received_us && source.motion_valid == g_source.motion_valid) return;
source.accel_sequence == g_source.accel_sequence && source.gyro_sequence == g_source.gyro_sequence &&
source.accel_received_us == g_source.accel_received_us && source.gyro_received_us == g_source.gyro_received_us &&
source.accel_valid == g_source.accel_valid && source.gyro_valid == g_source.gyro_valid &&
source.requires_stationary_bias == g_source.requires_stationary_bias) return;
if (changed_connection) {
lose_source(now_ms);
g_motion.reset();
for (Child& child : g_children) discard_output(child);
probe_debug_printf("[PROBE] DualSense source active in slot %u; using validated factory IMU calibration\n", source.slot);
probe_debug_printf("[PROBE] Native gamepad source active in slot %u\n", source.slot);
}
g_source = source;
g_active = true;
@ -194,9 +200,12 @@ void refresh(uint32_t now_ms) {
feedback.active_profile_number, feedback.policy);
}
ProbeNativeMotionSample sample{};
sample.accel_valid = sample.gyro_valid = source.motion_valid;
sample.accel_sequence = sample.gyro_sequence = source.motion_sequence;
sample.accel_us = sample.gyro_us = source.motion_received_us;
sample.accel_valid = source.accel_valid;
sample.gyro_valid = source.gyro_valid;
sample.accel_sequence = source.accel_sequence;
sample.gyro_sequence = source.gyro_sequence;
sample.accel_us = source.accel_received_us;
sample.gyro_us = source.gyro_received_us;
// SDL -> upright native body [X,-Z,Y], the same physical transform used by
// the Wii adapter before its mouse-mount rotation. Both halves represent
// one rigid, full controller: no solo-Joy-Con or mouse mounting rotation.
@ -206,12 +215,15 @@ void refresh(uint32_t now_ms) {
sample.gyro_dps[0] = static_cast<float>(source.gyro_q10[0]) / 1024.0f;
sample.gyro_dps[1] = -static_cast<float>(source.gyro_q10[2]) / 1024.0f;
sample.gyro_dps[2] = static_cast<float>(source.gyro_q10[1]) / 1024.0f;
g_motion.update(now_us, source.controller.connection_generation, sample);
const int status = !sensors_fresh(now_us) ? 0 : g_motion.ready() ? 2 : 1;
g_motion.update(now_us, source.controller.connection_generation, sample,
source.requires_stationary_bias ? ProbeNativeMotionBias::kEstimateStationary :
ProbeNativeMotionBias::kAlreadyCalibrated);
const int status = !sensors_fresh(g_source, now_us) ? 0 : g_motion.ready() ? 2 : 1;
if (status != g_sensor_status) {
g_sensor_status = status;
probe_debug_printf("[PROBE] DualSense native IMU %s\n", status == 2 ? "ready" :
status == 1 ? "waiting for a usable acceleration sample" : "waiting for fresh complete sensors");
probe_debug_printf("[PROBE] Native gamepad IMU %s\n", status == 2 ? "ready" :
status == 1 ? (source.requires_stationary_bias ? "Wii calibrating: keep still" :
"waiting for a usable acceleration sample") : "waiting for supported fresh sensors");
}
for (uint8_t i = 0; i < PROBE_CONTROLLER_COUNT; ++i) {
// Latest-only: a blocked endpoint never queues obsolete controls/IMU.
@ -221,15 +233,15 @@ void refresh(uint32_t now_ms) {
}
} // namespace
void probe_dualsense_input_init() {
void probe_native_gamepad_input_init() {
#if SWITCH2_BRIDGE_SOURCE_AUTO
bluepad32_input_backend_select_dualsense_source(nullptr);
bluepad32_input_backend_select_native_source(nullptr);
#else
bluepad32_input_backend_select_dualsense_source(kSourceAddress);
bluepad32_input_backend_select_native_source(kSourceAddress);
#endif
}
void probe_dualsense_input_set_stick_calibration(uint8_t instance, const uint8_t calibration[9]) {
void probe_native_gamepad_input_set_stick_calibration(uint8_t instance, const uint8_t calibration[9]) {
if (instance >= PROBE_CONTROLLER_COUNT) return;
Child& child = g_children[instance];
child.calibrated = false;
@ -248,30 +260,31 @@ void probe_dualsense_input_set_stick_calibration(uint8_t instance, const uint8_t
pack_controls(instance);
}
void probe_dualsense_input_set_native_stream(uint8_t instance, bool enabled) {
void probe_native_gamepad_input_set_native_stream(uint8_t instance, bool enabled) {
if (instance >= PROBE_CONTROLLER_COUNT) return;
Child& child = g_children[instance];
if (child.enabled != enabled || !enabled) discard_output(child);
child.enabled = enabled;
if (!enabled) bluepad32_input_backend_dualsense_sample_cancel(instance);
if (!enabled) bluepad32_input_backend_native_sample_cancel(instance);
}
void probe_dualsense_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out) {
void probe_native_gamepad_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out) {
if (!out) return;
if (instance >= PROBE_CONTROLLER_COUNT) { *out = {}; return; }
refresh(now_ms);
*out = g_children[instance].input;
}
uint32_t probe_dualsense_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]) {
uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]) {
if (instance >= PROBE_CONTROLLER_COUNT || !report) return 0;
refresh(now_ms);
Child& child = g_children[instance];
if (!child.enabled || !child.input.active) return 0;
const uint32_t now_us = time_us_32();
const bool motion_ready = (SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) != 0 &&
g_motion.ready() && sensors_fresh(now_us) &&
(!child.have_committed_motion || child.committed_motion_sequence != g_source.motion_sequence);
g_motion.ready() && sensors_fresh(g_source, now_us) &&
(!child.have_committed_motion || child.committed_accel_sequence != g_source.accel_sequence ||
child.committed_gyro_sequence != g_source.gyro_sequence);
if (child.pending_token && (now_us - child.pending_us >= kOutputDeadlineUs ||
child.pending_motion != motion_ready)) child.pending_token = 0;
if (!child.pending_token) {
@ -293,7 +306,8 @@ uint32_t probe_dualsense_input_peek_native_report(uint8_t instance, uint32_t now
g_motion.quaternion(), g_motion.acceleration(), static_cast<uint16_t>(child.pending_ticks & 0xfff),
wire_elapsed, 0, child.pending_report + probe_model_imu_data_offset(instance));
if (child.pending_motion) child.pending_report[probe_model_imu_length_offset(instance)] = 30;
child.pending_motion_sequence = g_source.motion_sequence;
child.pending_accel_sequence = g_source.accel_sequence;
child.pending_gyro_sequence = g_source.gyro_sequence;
child.pending_us = now_us;
child.pending_token = ++g_report_token;
}
@ -301,27 +315,28 @@ uint32_t probe_dualsense_input_peek_native_report(uint8_t instance, uint32_t now
return child.pending_token;
}
bool probe_dualsense_input_commit_native_report(uint8_t instance, uint32_t token) {
bool probe_native_gamepad_input_commit_native_report(uint8_t instance, uint32_t token) {
if (instance >= PROBE_CONTROLLER_COUNT || !token) return false;
Child& child = g_children[instance];
// Check the live source even when the caller did not poll after a disconnect.
Bluepad32DualSenseBridgeSnapshot source;
bluepad32_input_backend_dualsense_snapshot(&source);
Bluepad32NativeGamepadSnapshot source;
bluepad32_input_backend_native_snapshot(&source);
const uint32_t now_us = time_us_32();
if (!child.enabled || !g_active || child.pending_token != token ||
!source.controller.active || source.slot != g_source.slot ||
source.controller.connection_generation != g_source.controller.connection_generation ||
source.state_generation != g_source.state_generation ||
source.motion_sequence != g_source.motion_sequence ||
source.motion_received_us != g_source.motion_received_us ||
source.motion_valid != g_source.motion_valid ||
source.accel_sequence != g_source.accel_sequence || source.gyro_sequence != g_source.gyro_sequence ||
source.accel_received_us != g_source.accel_received_us || source.gyro_received_us != g_source.gyro_received_us ||
source.accel_valid != g_source.accel_valid || source.gyro_valid != g_source.gyro_valid ||
source.requires_stationary_bias != g_source.requires_stationary_bias ||
now_us - source.received_us >= kInputDeadlineUs || now_us - child.pending_us >= kOutputDeadlineUs ||
(child.pending_motion && (!source.motion_valid ||
now_us - g_source.motion_received_us >= kSensorDeadlineUs))) return false;
(child.pending_motion && !sensors_fresh(source, now_us))) return false;
child.pending_token = 0;
if (child.pending_motion) {
child.have_committed_motion = true;
child.committed_motion_sequence = child.pending_motion_sequence;
child.committed_accel_sequence = child.pending_accel_sequence;
child.committed_gyro_sequence = child.pending_gyro_sequence;
child.committed_ticks = child.pending_ticks;
}
++child.counter;

View file

@ -0,0 +1,13 @@
#pragma once
#include "controller_input.h"
#if SWITCH2_BRIDGE_FULL_INPUT
// Core 0 only. One coherent profile/motion evaluation feeds both native children.
void probe_native_gamepad_input_init();
void probe_native_gamepad_input_set_stick_calibration(uint8_t instance, const uint8_t calibration[9]);
void probe_native_gamepad_input_set_native_stream(uint8_t instance, bool enabled);
void probe_native_gamepad_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out);
uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]);
bool probe_native_gamepad_input_commit_native_report(uint8_t instance, uint32_t token);
#endif

View file

@ -25,8 +25,8 @@ else()
endif()
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
if(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_BRIDGE_WII_INPUT
OR (NOT SWITCH2_BRIDGE_INPUT STREQUAL "JOYCON2" AND NOT SWITCH2_BRIDGE_DUALSENSE_INPUT))
message(FATAL_ERROR "Native R/L output requires JOYCON2 input, or DUALSENSE with HUB")
OR (NOT SWITCH2_BRIDGE_INPUT STREQUAL "JOYCON2" AND NOT SWITCH2_BRIDGE_FULL_INPUT))
message(FATAL_ERROR "Native R/L output requires JOYCON2 input or a full-controller HUB source")
endif()
set(probe_composite 0)
if(SWITCH2_PROBE_COMPOSITE)
@ -54,10 +54,10 @@ set(SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS "" CACHE STRING
set(SWITCH2_BRIDGE_SOURCE_AUTO OFF)
if(SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_PROBE_COMPOSITE)
set(probe_source_fields SWITCH2_BRIDGE_SOURCE_ADDRESS)
if(SWITCH2_BRIDGE_DUALSENSE_INPUT AND SWITCH2_BRIDGE_SOURCE_ADDRESS STREQUAL "")
if(SWITCH2_BRIDGE_FULL_INPUT AND SWITCH2_BRIDGE_SOURCE_ADDRESS STREQUAL "")
set(SWITCH2_BRIDGE_SOURCE_AUTO ON)
set(probe_source_fields "")
elseif((SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB) AND NOT SWITCH2_BRIDGE_DUALSENSE_INPUT)
elseif((SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB) AND NOT SWITCH2_BRIDGE_FULL_INPUT)
list(APPEND probe_source_fields SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS)
endif()
set(probe_source_addresses "")
@ -367,11 +367,11 @@ function(switch2_usb_probe_configure target)
else()
pico_set_program_name(${target} "Switch 2 USB initialization capture")
endif()
if(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_DUALSENSE_INPUT)
if(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
pico_set_program_version(${target} "0.69-native-hub-imu-trace")
pico_set_program_version(${target} "0.70-native-gamepad-trace")
else()
pico_set_program_version(${target} "0.69-native-hub-imu")
pico_set_program_version(${target} "0.70-native-gamepad")
endif()
elseif(SWITCH2_PROBE_HUB)
pico_set_program_version(${target} "0.67-native-hub-latency")