Compare commits

..

No commits in common. "c2bfaa1d922e075085a24fcffcf732fcfd8f2822" and "4626b13bd8a58c563daa8afde86a47d2c1ca5771" have entirely different histories.

104 changed files with 640 additions and 549 deletions

5
.gitignore vendored
View file

@ -9,8 +9,3 @@ debug
hid-nintendo.c hid-nintendo.c
__pycache__ __pycache__
!.vscode/* !.vscode/*
.omo/*
.slim/*
pytest-*/*
build-*/*
.ignore

View file

@ -427,7 +427,7 @@ with the current identity.
#### 4A — Output-driver boundary and production XInput #### 4A — Output-driver boundary and production XInput
- replace `SWITCH_PICO_ADAPTER_FEASIBILITY` branches in `src/firmware/main.cpp` with - replace `SWITCH_PICO_ADAPTER_FEASIBILITY` branches in `switch-pico.cpp` with
one fixed static driver interface: descriptors, init/reset, input, task, one fixed static driver interface: descriptors, init/reset, input, task,
readiness/capabilities, and host output readiness/capabilities, and host output
- keep the Phase 3 profile/runtime transform exactly once before serialization - keep the Phase 3 profile/runtime transform exactly once before serialization

View file

@ -56,29 +56,26 @@ pico_sdk_init()
# Configure BLUEPAD32 input backend if selected # Configure BLUEPAD32 input backend if selected
if(SWITCH_PICO_INPUT_BACKEND STREQUAL "BLUEPAD32") if(SWITCH_PICO_INPUT_BACKEND STREQUAL "BLUEPAD32")
# Ensure Python3 is available and execute patch preparation
find_package(Python3 REQUIRED COMPONENTS Interpreter) find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(BLUEPAD32_PREP_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/tools/prepare_bluepad32.py) set(BLUEPAD32_PREP_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/tools/prepare_bluepad32.py)
set(BLUEPAD32_SOURCE ${CMAKE_CURRENT_LIST_DIR}/external/bluepad32)
set(BLUEPAD32_PATCH ${CMAKE_CURRENT_LIST_DIR}/patches/bluepad32-sdl3-imu.patch)
set(BLUEPAD32_ROOT ${CMAKE_CURRENT_BINARY_DIR}/_deps/bluepad32-src)
execute_process( execute_process(
COMMAND ${Python3_EXECUTABLE} ${BLUEPAD32_PREP_SCRIPT} COMMAND ${Python3_EXECUTABLE} ${BLUEPAD32_PREP_SCRIPT}
--bluepad32 ${BLUEPAD32_SOURCE}
--patch ${BLUEPAD32_PATCH}
--output ${BLUEPAD32_ROOT}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
RESULT_VARIABLE BLUEPAD32_PREP_RESULT RESULT_VARIABLE BLUEPAD32_PREP_RESULT
OUTPUT_VARIABLE BLUEPAD32_PREP_OUTPUT OUTPUT_VARIABLE BLUEPAD32_PREP_OUTPUT
ERROR_VARIABLE BLUEPAD32_PREP_ERROR ERROR_VARIABLE BLUEPAD32_PREP_ERROR
) )
if(NOT BLUEPAD32_PREP_RESULT EQUAL 0) if(NOT BLUEPAD32_PREP_RESULT EQUAL 0)
message(FATAL_ERROR message(FATAL_ERROR
"Failed to prepare isolated Bluepad32 source. " "Failed to prepare Bluepad32: Patch application or validation failed. "
"Details: ${BLUEPAD32_PREP_ERROR}") "Details: ${BLUEPAD32_PREP_ERROR}")
endif() endif()
# Configure Bluepad32 include paths and subdirectory
set(BLUEPAD32_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/bluepad32)
set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack) set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack)
include_directories( include_directories(
${CMAKE_CURRENT_LIST_DIR}/bluepad32_config ${CMAKE_CURRENT_LIST_DIR}/bluepad32_config
@ -92,37 +89,36 @@ if(SWITCH_PICO_INPUT_BACKEND STREQUAL "BLUEPAD32")
) )
endif() endif()
# Firmware sources live under one include root and are grouped by responsibility. # Add executable. Default name is the project name, version 0.1
set(SWITCH_PICO_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/src/firmware)
add_executable(switch-pico add_executable(switch-pico
${SWITCH_PICO_SOURCE_DIR}/main.cpp switch-pico.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/usb_output_driver.cpp switch_pro_driver.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/switch/switch_pro_driver.cpp usb_output_driver.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/switch/switch_haptics.cpp switch_haptics.cpp
) )
if(SWITCH_PICO_INPUT_BACKEND STREQUAL "BLUEPAD32") if(SWITCH_PICO_INPUT_BACKEND STREQUAL "BLUEPAD32")
target_sources(switch-pico PRIVATE target_sources(switch-pico PRIVATE
${SWITCH_PICO_SOURCE_DIR}/adapter/adapter_host_probe.cpp bluepad32_input_backend.cpp
${SWITCH_PICO_SOURCE_DIR}/adapter/adapter_mode_controller.cpp adapter_host_probe.cpp
${SWITCH_PICO_SOURCE_DIR}/configuration/adapter_configuration.cpp adapter_mode_controller.cpp
${SWITCH_PICO_SOURCE_DIR}/configuration/configuration_service.cpp controller_identity.cpp
${SWITCH_PICO_SOURCE_DIR}/configuration/configuration_storage.cpp controller_profile.cpp
${SWITCH_PICO_SOURCE_DIR}/configuration/configuration_transaction.cpp controller_profile_transform.cpp
${SWITCH_PICO_SOURCE_DIR}/core/controller_identity.cpp controller_synthetic_input.cpp
${SWITCH_PICO_SOURCE_DIR}/input/bluepad32_input_backend.cpp controller_profile_runtime.cpp
${SWITCH_PICO_SOURCE_DIR}/platform/pico/bootsel_pairing_button.cpp profile_storage.cpp
${SWITCH_PICO_SOURCE_DIR}/platform/pico/pico_configuration_storage.cpp profile_service.cpp
${SWITCH_PICO_SOURCE_DIR}/platform/pico/pico_profile_storage.cpp pico_profile_storage.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/controller_profile.cpp bootsel_pairing_button.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/controller_profile_runtime.cpp adapter_configuration.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/controller_profile_transform.cpp configuration_storage.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/controller_synthetic_input.cpp configuration_transaction.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/profile_service.cpp configuration_service.cpp
${SWITCH_PICO_SOURCE_DIR}/profile/profile_storage.cpp pico_configuration_storage.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/generic_hid/generic_hid_driver.cpp usb_configuration_management.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/usb_configuration_management.cpp xinput_driver.cpp
${SWITCH_PICO_SOURCE_DIR}/usb/xinput/xinput_driver.cpp generic_hid_driver.cpp
) )
target_compile_definitions(switch-pico PRIVATE target_compile_definitions(switch-pico PRIVATE
SWITCH_PICO_BLUEPAD32=1 SWITCH_PICO_BLUEPAD32=1
@ -170,11 +166,9 @@ if (SWITCH_PICO_LOG)
target_compile_definitions(switch-pico PRIVATE SWITCH_PICO_LOG=1) target_compile_definitions(switch-pico PRIVATE SWITCH_PICO_LOG=1)
endif() endif()
# Internal includes are rooted at src/firmware. TinyUSB discovers tusb_config.h # Add the standard include files to the build
# through the Pico platform directory.
target_include_directories(switch-pico PRIVATE target_include_directories(switch-pico PRIVATE
${SWITCH_PICO_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}
${SWITCH_PICO_SOURCE_DIR}/platform/pico
) )
pico_add_extra_outputs(switch-pico) pico_add_extra_outputs(switch-pico)

View file

@ -3,31 +3,11 @@
Raspberry Pi Pico firmware that emulates one or more Switch Pro controllers over USB. Input can come from the SDL3-to-UART computer bridge or, on Pico 2 W, directly from Bluetooth controllers through Bluepad32. Raspberry Pi Pico firmware that emulates one or more Switch Pro controllers over USB. Input can come from the SDL3-to-UART computer bridge or, on Pico 2 W, directly from Bluetooth controllers through Bluepad32.
## What you get ## What you get
- **Firmware** (`src/firmware/`): acts as a Switch Pro controller (one on standard Pico, four on Pico 2 W AIO), accepting either UART bridge reports or the optional Pico 2 W Bluepad32 backend. - **Firmware** (`switch-pico.cpp` + `switch_pro_driver.*`): acts as a Switch Pro controller (one on standard Pico, four on Pico 2 W AIO), accepting either UART bridge reports or the optional Pico 2 W Bluepad32 backend.
- **Python bridge** (`switch_pico_bridge.controller_uart_bridge` / CLI `controller-uart-bridge`): reads SDL3 controllers on the host, sends reports over UART, and applies rumble locally. Hot‑plug friendly and cross‑platform (macOS/Windows/Linux). - **Python bridge** (`switch_pico_bridge.controller_uart_bridge` / CLI `controller-uart-bridge`): reads SDL3 controllers on the host, sends reports over UART, and applies rumble locally. Hot‑plug friendly and cross‑platform (macOS/Windows/Linux).
- **Color configuration** (`src/firmware/platform/pico/controller_color_config.h`): compile-time RGB colors for emulated controller grips and supported Bluetooth controller LEDs. - **Color configuration** (`controller_color_config.h`): compile-time RGB colors for emulated controller grips and supported Bluetooth controller LEDs.
- **Pico 2 W AIO firmware** (`firmware/switch-pico-aio.uf2`): hosts four concurrent Bluetooth controllers and sends their controls, calibrated motion, rumble, and slot identity through four separate Switch Pro USB interfaces without a computer. - **Pico 2 W AIO firmware** (`firmware/switch-pico-aio.uf2`): hosts four concurrent Bluetooth controllers and sends their controls, calibrated motion, rumble, and slot identity through four separate Switch Pro USB interfaces without a computer.
## Source layout
Firmware code has one include root, `src/firmware`, with responsibility-based
modules:
| Path | Responsibility |
|---|---|
| `src/firmware/main.cpp` | Firmware entry point and backend orchestration |
| `src/firmware/adapter/` | USB mode selection, host probing, and managed reboot |
| `src/firmware/configuration/` | Persistent adapter configuration and transactions |
| `src/firmware/core/` | Shared controller identity, color, and input-state types |
| `src/firmware/input/` | Bluepad32 controller input backend and hotkeys |
| `src/firmware/platform/pico/` | Pico flash/BOOTSEL integrations and compile-time board configuration |
| `src/firmware/profile/` | Controller profiles, transforms, storage, and runtime |
| `src/firmware/usb/` | USB output boundary, management protocol, and per-protocol drivers |
Internal includes are rooted at `src/firmware`, for example
`#include "profile/controller_profile.h"`. Host-side Python remains in
`src/switch_pico_bridge/`; native firmware tests remain in `tests/`.
## Quick start ## Quick start
1. Flash the Pico with `firmware/switch-pico.uf2` (or build your own) using BOOTSEL drag-and-drop (see “Manual UF2 flashing” below). 1. Flash the Pico with `firmware/switch-pico.uf2` (or build your own) using BOOTSEL drag-and-drop (see “Manual UF2 flashing” below).
2. Wire Pico UART1 to a USB↔UART adapter (GPIO4 TX, GPIO5 RX, GND) and plug that adapter into your host PC. 2. Wire Pico UART1 to a USB↔UART adapter (GPIO4 TX, GPIO5 RX, GND) and plug that adapter into your host PC.
@ -64,7 +44,7 @@ This uses an isolated `build-aio/` CMake cache and publishes:
The default `python3 build.py` command and `firmware/switch-pico.*` artifacts remain the UART/Pico build. The AIO build requires `PICO_BOARD=pico2_w`; it is not interchangeable with the original non-wireless Pico firmware. The default `python3 build.py` command and `firmware/switch-pico.*` artifacts remain the UART/Pico build. The AIO build requires `PICO_BOARD=pico2_w`; it is not interchangeable with the original non-wireless Pico firmware.
Both `build.py --aio` and direct AIO CMake configuration copy the pinned Bluepad32 source into the active build directory and apply `patches/bluepad32-sdl3-imu.patch` there before compiling. The patch makes supported motion controllers use SDL3-equivalent axes and fixed-point units before conversion to Nintendo samples. The `external/bluepad32` submodule remains pristine; patch or source-revision drift fails configuration. Both `build.py --aio` and direct AIO CMake configuration apply `patches/bluepad32-sdl3-imu.patch` idempotently before compiling Bluepad32. The patch makes supported motion controllers use SDL3-equivalent axes and fixed-point units before conversion to Nintendo samples. It intentionally leaves the dependency worktree dirty; the committed submodule revision remains Bluepad32 4.2.0.
### Pairing up to four controllers ### Pairing up to four controllers
@ -182,7 +162,7 @@ Each AIO slot has one color shared by its emulated Switch Pro grips and its phys
3. Yellow `#F6C945` 3. Yellow `#F6C945`
4. Green `#2ECC71` 4. Green `#2ECC71`
When a controller becomes ready, RGB-capable devices such as DualSense and DualShock 4 receive a darker, more saturated RGB value derived automatically from the slot's Switch grip color. Controllers without an RGB light use player indicator 1, 2, 3, or 4 when Bluepad32 exposes player-LED control. Devices without either capability are left unchanged. Edit only the four grip colors in `src/firmware/platform/pico/controller_color_config.h`; rebuilding automatically recalibrates their lightbar colors. When a controller becomes ready, RGB-capable devices such as DualSense and DualShock 4 receive a darker, more saturated RGB value derived automatically from the slot's Switch grip color. Controllers without an RGB light use player indicator 1, 2, 3, or 4 when Bluepad32 exposes player-LED control. Devices without either capability are left unchanged. Edit only the four grip colors in `controller_color_config.h`; rebuilding automatically recalibrates their lightbar colors.
### Controller capabilities ### Controller capabilities
@ -372,7 +352,7 @@ python3 build.py --grip-color FF00AA
``` ```
Both options update all four slot definitions in Both options update all four slot definitions in
`src/firmware/platform/pico/controller_color_config.h` before building. With no color option, the `controller_color_config.h` before building. With no color option, the
per-slot blue/red/yellow/green palette is left unchanged. Run per-slot blue/red/yellow/green palette is left unchanged. Run
`python3 build.py --help` to see the available command-line options. `python3 build.py --help` to see the available command-line options.
@ -567,7 +547,7 @@ Keep `SwitchImuMode` as a three-state value. Never acknowledge mode 2 and then e
#### Mode-2 implementation #### Mode-2 implementation
`src/firmware/usb/switch/switch_pro_driver.cpp` implements this in `integrate_motion_sample()` and `fill_quaternion_imu_report_data()`: `switch_pro_driver.cpp` implements this in `integrate_motion_sample()` and `fill_quaternion_imu_report_data()`:
1. Reset quaternion state to `(0, 0, 0, 1)` when transitioning into mode 2. 1. Reset quaternion state to `(0, 0, 0, 1)` when transitioning into mode 2.
2. Integrate each report's three gyro samples at 5 ms per sample. The Nintendo quaternion axes use sensor `Y, X, Z`, not `X, Y, Z`. 2. Integrate each report's three gyro samples at 5 ms per sample. The Nintendo quaternion axes use sensor `Y, X, Z`, not `X, Y, Z`.

View file

@ -1,4 +1,4 @@
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
namespace { namespace {

View file

@ -3,7 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
constexpr uint16_t ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION = 1; constexpr uint16_t ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION = 1;
constexpr uint16_t ADAPTER_CONFIGURATION_SCHEMA_VERSION = 2; constexpr uint16_t ADAPTER_CONFIGURATION_SCHEMA_VERSION = 2;

View file

@ -1,4 +1,5 @@
#include "adapter/adapter_host_probe.h" #include "adapter_host_probe.h"
#include <stddef.h> #include <stddef.h>
#ifdef SWITCH_PICO_LOG #ifdef SWITCH_PICO_LOG
#include <stdio.h> #include <stdio.h>
@ -7,12 +8,12 @@
#define PROBE_LOG(...) ((void)0) #define PROBE_LOG(...) ((void)0)
#endif #endif
#include "adapter/adapter_host_probe_state.h" #include "adapter_host_probe_state.h"
#include "profile/controller_profile_runtime.h" #include "controller_profile_runtime.h"
#include "hardware/structs/watchdog.h" #include "hardware/structs/watchdog.h"
#include "hardware/watchdog.h" #include "hardware/watchdog.h"
#include "pico/time.h" #include "pico/time.h"
#include "usb/xinput/xinput_descriptors.h" #include "xinput_descriptors.h"
namespace { namespace {

View file

@ -2,7 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#include "tusb.h" #include "tusb.h"
// Consume watchdog scratch and freeze the active mode. Call exactly once // Consume watchdog scratch and freeze the active mode. Call exactly once

View file

@ -1,15 +1,16 @@
#include "adapter/adapter_mode_controller.h" #include "adapter_mode_controller.h"
#include "adapter/adapter_reboot.h" #include "adapter_reboot.h"
#include "configuration/adapter_configuration.h"
#include "adapter/adapter_host_probe.h" #include "adapter_configuration.h"
#include "input/bluepad32_input_backend.h" #include "adapter_host_probe.h"
#include "configuration/configuration_service.h" #include "bluepad32_input_backend.h"
#include "profile/controller_profile.h" #include "configuration_service.h"
#include "profile/controller_profile_runtime.h" #include "controller_profile.h"
#include "controller_profile_runtime.h"
#include "hardware/watchdog.h" #include "hardware/watchdog.h"
#include "pico/bootrom.h" #include "pico/bootrom.h"
#include "tusb.h" #include "tusb.h"
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
namespace { namespace {

View file

@ -2,8 +2,8 @@
#include <stdint.h> #include <stdint.h>
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#include "core/controller_state.h" #include "controller_state.h"
constexpr uint8_t ADAPTER_MODE_CONTROLLER_SLOT_COUNT = 4; constexpr uint8_t ADAPTER_MODE_CONTROLLER_SLOT_COUNT = 4;
constexpr uint32_t ADAPTER_MODE_CHORD_HOLD_MS = 3000; constexpr uint32_t ADAPTER_MODE_CHORD_HOLD_MS = 3000;

View file

@ -1,7 +1,8 @@
#include "input/bluepad32_input_backend.h" #include "bluepad32_input_backend.h"
#include "input/controller_hotkey_config.h" #include "controller_hotkey_config.h"
#include "configuration/configuration_service.h" #include "configuration_service.h"
#include "profile/profile_service.h" #include "profile_service.h"
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
@ -14,7 +15,7 @@
#include <pico/stdlib.h> #include <pico/stdlib.h>
#include <uni.h> #include <uni.h>
#ifdef SWITCH_PICO_USB_OUTPUT_MODES #ifdef SWITCH_PICO_USB_OUTPUT_MODES
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#endif #endif
namespace { namespace {

View file

@ -2,11 +2,11 @@
#include <stdint.h> #include <stdint.h>
#include "core/controller_color.h" #include "controller_color.h"
#include "core/controller_identity.h" #include "controller_identity.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "core/controller_state.h" #include "controller_state.h"
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
constexpr uint8_t BLUEPAD32_INPUT_BACKEND_SLOT_COUNT = 4; constexpr uint8_t BLUEPAD32_INPUT_BACKEND_SLOT_COUNT = 4;
constexpr uint8_t BLUEPAD32_PAIRING_RECORD_CAPACITY = 16; constexpr uint8_t BLUEPAD32_PAIRING_RECORD_CAPACITY = 16;

View file

@ -1,4 +1,5 @@
#include "platform/pico/bootsel_pairing_button.h" #include "bootsel_pairing_button.h"
#include "hardware/gpio.h" #include "hardware/gpio.h"
#include "hardware/structs/ioqspi.h" #include "hardware/structs/ioqspi.h"
#include "hardware/structs/sio.h" #include "hardware/structs/sio.h"

View file

@ -10,8 +10,7 @@ import sys
from pathlib import Path from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent SCRIPT_DIR = Path(__file__).resolve().parent
FIRMWARE_SOURCE_DIR = SCRIPT_DIR / "src" / "firmware" CONFIG_FILE = SCRIPT_DIR / "controller_color_config.h"
CONFIG_FILE = FIRMWARE_SOURCE_DIR / "platform" / "pico" / "controller_color_config.h"
BUILD_DIR = SCRIPT_DIR / "build" BUILD_DIR = SCRIPT_DIR / "build"
AIO_BUILD_DIR = SCRIPT_DIR / "build-aio" AIO_BUILD_DIR = SCRIPT_DIR / "build-aio"
FEASIBILITY_BUILD_DIR = SCRIPT_DIR / "build-feasibility" FEASIBILITY_BUILD_DIR = SCRIPT_DIR / "build-feasibility"
@ -266,7 +265,7 @@ def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Build and flash the project, optionally setting grip colors.", description="Build and flash the project, optionally setting grip colors.",
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"Default behavior leaves {CONFIG_FILE.relative_to(SCRIPT_DIR)} unchanged.", epilog="Default behavior leaves controller_color_config.h unchanged.",
) )
mode_group = parser.add_mutually_exclusive_group() mode_group = parser.add_mutually_exclusive_group()
mode_group.add_argument( mode_group.add_argument(
@ -366,6 +365,7 @@ def build(
firmware_uf2_path, firmware_uf2_path,
): ):
if aio or adapter_feasibility: if aio or adapter_feasibility:
run_cmd([sys.executable, str(SCRIPT_DIR / "tools" / "prepare_bluepad32.py")])
definitions = [ definitions = [
"-DSWITCH_PICO_LOG=OFF", "-DSWITCH_PICO_LOG=OFF",
"-DPICO_BOARD=pico2_w", "-DPICO_BOARD=pico2_w",

View file

@ -1,8 +1,9 @@
#include "configuration/configuration_service.h" #include "configuration_service.h"
#include <string.h> #include <string.h>
#include "pico/critical_section.h" #include "pico/critical_section.h"
#include "platform/pico/pico_configuration_storage.h" #include "pico_configuration_storage.h"
namespace { namespace {

View file

@ -3,8 +3,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
#include "configuration/configuration_transaction.h" #include "configuration_transaction.h"
constexpr uint32_t CONFIGURATION_SERVICE_INTERNAL_TRANSACTION_ID_MASK = constexpr uint32_t CONFIGURATION_SERVICE_INTERNAL_TRANSACTION_ID_MASK =
0x80000000u; 0x80000000u;

View file

@ -1,4 +1,5 @@
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
#include <string.h> #include <string.h>
namespace { namespace {

View file

@ -1,7 +1,9 @@
#include "configuration/configuration_transaction.h" #include "configuration_transaction.h"
#include <string.h> #include <string.h>
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
ConfigurationTransactionStatus ConfigurationTransaction::begin( ConfigurationTransactionStatus ConfigurationTransaction::begin(
uint32_t transaction_id, uint16_t schema_version, size_t payload_size, uint32_t transaction_id, uint16_t schema_version, size_t payload_size,
uint32_t payload_crc) { uint32_t payload_crc) {

View file

@ -3,7 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
enum class ConfigurationTransactionStatus : uint8_t { enum class ConfigurationTransactionStatus : uint8_t {
kIdle = 0, kIdle = 0,

View file

@ -1,4 +1,5 @@
#include "core/controller_identity.h" #include "controller_identity.h"
#include <string.h> #include <string.h>
namespace { namespace {

View file

@ -1,4 +1,5 @@
#include "profile/controller_profile.h" #include "controller_profile.h"
#include <string.h> #include <string.h>
namespace { namespace {

View file

@ -3,7 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "core/controller_identity.h" #include "controller_identity.h"
constexpr uint16_t CONTROLLER_PROFILE_LEGACY_SCHEMA_VERSION = 1; constexpr uint16_t CONTROLLER_PROFILE_LEGACY_SCHEMA_VERSION = 1;
constexpr uint16_t CONTROLLER_PROFILE_SCHEMA_VERSION = 2; constexpr uint16_t CONTROLLER_PROFILE_SCHEMA_VERSION = 2;

View file

@ -1,8 +1,9 @@
#include "profile/controller_profile_runtime.h" #include "controller_profile_runtime.h"
#include "configuration/configuration_service.h"
#include "core/controller_identity.h" #include "configuration_service.h"
#include "profile/controller_synthetic_input.h" #include "controller_identity.h"
#include "profile/profile_service.h" #include "controller_synthetic_input.h"
#include "profile_service.h"
namespace { namespace {

View file

@ -2,10 +2,10 @@
#include <stdint.h> #include <stdint.h>
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#include "input/bluepad32_input_backend.h" #include "bluepad32_input_backend.h"
#include "profile/controller_profile_transform.h" #include "controller_profile_transform.h"
constexpr uint8_t CONTROLLER_PROFILE_RUNTIME_SLOT_COUNT = 4; constexpr uint8_t CONTROLLER_PROFILE_RUNTIME_SLOT_COUNT = 4;
constexpr uint16_t CONTROLLER_PROFILE_DEFAULT_SWITCHING_CHORD = constexpr uint16_t CONTROLLER_PROFILE_DEFAULT_SWITCHING_CHORD =

View file

@ -1,4 +1,5 @@
#include "profile/controller_profile_transform.h" #include "controller_profile_transform.h"
#include <limits.h> #include <limits.h>
namespace { namespace {

View file

@ -2,9 +2,9 @@
#include <stdint.h> #include <stdint.h>
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "core/controller_state.h" #include "controller_state.h"
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
struct ControllerProfileTransformResult { struct ControllerProfileTransformResult {
ControllerState state{}; ControllerState state{};

View file

@ -1,4 +1,4 @@
#include "profile/controller_synthetic_input.h" #include "controller_synthetic_input.h"
namespace { namespace {

View file

@ -2,7 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include "profile/controller_profile_transform.h" #include "controller_profile_transform.h"
struct ControllerSyntheticBindingState { struct ControllerSyntheticBindingState {
bool active = false; bool active = false;

View file

@ -3,7 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "core/controller_state.h" #include "controller_state.h"
#ifndef SWITCH_PICO_HID_INSTANCE_COUNT #ifndef SWITCH_PICO_HID_INSTANCE_COUNT
#define SWITCH_PICO_HID_INSTANCE_COUNT 1 #define SWITCH_PICO_HID_INSTANCE_COUNT 1

View file

@ -1,7 +1,8 @@
#include "usb/generic_hid/generic_hid_driver.h" #include "generic_hid_driver.h"
#include <string.h> #include <string.h>
#include "usb/generic_hid/generic_hid_descriptors.h" #include "generic_hid_descriptors.h"
namespace { namespace {

View file

@ -2,9 +2,8 @@
#include <stdint.h> #include <stdint.h>
#include "core/controller_state.h" #include "controller_state.h"
#include "tusb.h" #include "tusb.h"
namespace GenericHid { namespace GenericHid {
enum class ReportDescriptorVariant : uint8_t { enum class ReportDescriptorVariant : uint8_t {

View file

@ -1,4 +1,5 @@
#include "platform/pico/pico_configuration_storage.h" #include "pico_configuration_storage.h"
#include <string.h> #include <string.h>
#include "hardware/flash.h" #include "hardware/flash.h"

View file

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
ConfigurationStorageIo pico_configuration_storage_io(); ConfigurationStorageIo pico_configuration_storage_io();

View file

@ -1,7 +1,8 @@
#include "platform/pico/pico_profile_storage.h" #include "pico_profile_storage.h"
#include <string.h> #include <string.h>
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
#include "hardware/flash.h" #include "hardware/flash.h"
#include "pico/btstack_flash_bank.h" #include "pico/btstack_flash_bank.h"
#include "pico/flash.h" #include "pico/flash.h"

View file

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "profile/profile_storage.h" #include "profile_storage.h"
ProfileStorageIo pico_profile_storage_io(); ProfileStorageIo pico_profile_storage_io();

View file

@ -1,9 +1,10 @@
#include "profile/profile_service.h" #include "profile_service.h"
#include <string.h> #include <string.h>
#include "pico/critical_section.h" #include "pico/critical_section.h"
#include "platform/pico/pico_profile_storage.h" #include "pico_profile_storage.h"
#include "profile/profile_storage.h" #include "profile_storage.h"
namespace { namespace {

View file

@ -3,8 +3,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "configuration/configuration_transaction.h" #include "configuration_transaction.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
constexpr uint8_t PROFILE_SERVICE_LIST_CAPACITY = constexpr uint8_t PROFILE_SERVICE_LIST_CAPACITY =
CONTROLLER_PROFILE_STABLE_IDENTITY_CAPACITY + 1; CONTROLLER_PROFILE_STABLE_IDENTITY_CAPACITY + 1;

View file

@ -1,4 +1,5 @@
#include "profile/profile_storage.h" #include "profile_storage.h"
#include <string.h> #include <string.h>
namespace { namespace {

View file

@ -3,7 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "profile/controller_profile.h" #include "controller_profile.h"
constexpr uint8_t PROFILE_STORAGE_BANK_COUNT = 2; constexpr uint8_t PROFILE_STORAGE_BANK_COUNT = 2;
constexpr size_t PROFILE_STORAGE_SECTOR_SIZE = 4096; constexpr size_t PROFILE_STORAGE_SECTOR_SIZE = 4096;

View file

@ -2,15 +2,15 @@
#include "bsp/board.h" #include "bsp/board.h"
#include "pico/stdlib.h" #include "pico/stdlib.h"
#include "tusb.h" #include "tusb.h"
#include "usb/switch/switch_pro_driver.h" #include "switch_pro_driver.h"
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
#ifndef SWITCH_PICO_BLUEPAD32 #ifndef SWITCH_PICO_BLUEPAD32
#include "hardware/uart.h" #include "hardware/uart.h"
#else #else
#include "adapter/adapter_mode_controller.h" #include "adapter_mode_controller.h"
#include "input/bluepad32_input_backend.h" #include "bluepad32_input_backend.h"
#include "platform/pico/bootsel_pairing_button.h" #include "bootsel_pairing_button.h"
#include "profile/controller_profile_runtime.h" #include "controller_profile_runtime.h"
#endif #endif
#ifdef SWITCH_PICO_LOG #ifdef SWITCH_PICO_LOG

View file

@ -1,4 +1,5 @@
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>

View file

@ -1,4 +1,4 @@
#include "usb/switch/switch_pro_driver.h" #include "switch_pro_driver.h"
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
@ -81,8 +81,8 @@ static SwitchProContext* context_for(uint8_t instance) {
} }
// Optional compile-time color palette shared with Bluetooth controller LEDs. // Optional compile-time color palette shared with Bluetooth controller LEDs.
#if __has_include("platform/pico/controller_color_config.h") #if __has_include("controller_color_config.h")
#include "platform/pico/controller_color_config.h" #include "controller_color_config.h"
#endif #endif
#ifndef SWITCH_COLOR_BODY_R #ifndef SWITCH_COLOR_BODY_R
#define SWITCH_COLOR_BODY_R 0x1B #define SWITCH_COLOR_BODY_R 0x1B

View file

@ -8,11 +8,11 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "core/controller_color.h" #include "controller_color.h"
#include "core/controller_state.h" #include "controller_state.h"
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
#include "tusb.h" #include "tusb.h"
#include "usb/switch/switch_pro_descriptors.h" #include "switch_pro_descriptors.h"
// Preserve the pre-neutral-state 35%-of-1023 digital trigger boundary. // Preserve the pre-neutral-state 35%-of-1023 digital trigger boundary.
constexpr uint32_t SWITCH_PRO_LEGACY_TRIGGER_RANGE_MAXIMUM = 1023; constexpr uint32_t SWITCH_PRO_LEGACY_TRIGGER_RANGE_MAXIMUM = 1023;
constexpr uint32_t SWITCH_PRO_LEGACY_TRIGGER_PRESS_THRESHOLD = 358; constexpr uint32_t SWITCH_PRO_LEGACY_TRIGGER_PRESS_THRESHOLD = 358;

View file

@ -1,4 +1,5 @@
#include "adapter/adapter_host_probe.h" #include "adapter_host_probe.h"
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -6,7 +7,7 @@
#include "hardware/structs/watchdog.h" #include "hardware/structs/watchdog.h"
#include "pico/time.h" #include "pico/time.h"
#include "usb/xinput/xinput_descriptors.h" #include "xinput_descriptors.h"
namespace { namespace {

View file

@ -1,15 +1,16 @@
#include "adapter/adapter_mode_controller.h" #include "adapter_mode_controller.h"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
#include "adapter/adapter_reboot.h" #include "adapter_reboot.h"
#include "input/bluepad32_input_backend.h" #include "bluepad32_input_backend.h"
#include "configuration/configuration_service.h" #include "configuration_service.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
namespace { namespace {

View file

@ -4,7 +4,7 @@
#include <string> #include <string>
#include <uni.h> #include <uni.h>
#include "platform/pico/controller_color_config.h" #include "../controller_color_config.h"
namespace { namespace {
@ -442,9 +442,8 @@ uint32_t btstack_run_loop_get_time_ms() {
} }
#include "core/controller_identity.cpp" #include "../controller_identity.cpp"
#include "input/bluepad32_input_backend.cpp" #include "../bluepad32_input_backend.cpp"
namespace { namespace {
void require_clear_completion_pending() { void require_clear_completion_pending() {
if (expected_pending_clear_token != 0) { if (expected_pending_clear_token != 0) {

View file

@ -1,4 +1,5 @@
#include "platform/pico/bootsel_pairing_button.h" #include "bootsel_pairing_button.h"
#include <cstdlib> #include <cstdlib>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>

View file

@ -1,7 +1,8 @@
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
#include "configuration/configuration_service.h" #include "configuration_service.h"
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
#include "platform/pico/pico_configuration_storage.h" #include "pico_configuration_storage.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>

View file

@ -1,6 +1,7 @@
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
#include "configuration/configuration_storage.h" #include "configuration_storage.h"
#include "configuration/configuration_transaction.h" #include "configuration_transaction.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>

View file

@ -2,7 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include "profile/controller_profile.h" #include "controller_profile.h"
constexpr uint8_t kLegacyDefaultProfile[CONTROLLER_PROFILE_ENCODED_SIZE] = { constexpr uint8_t kLegacyDefaultProfile[CONTROLLER_PROFILE_ENCODED_SIZE] = {
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,

View file

@ -1,7 +1,9 @@
#include "profile/controller_profile_runtime.h" #include "controller_profile_runtime.h"
#include "core/controller_identity.h"
#include "profile/controller_profile.h" #include "controller_identity.h"
#include "profile/profile_service.h" #include "controller_profile.h"
#include "profile_service.h"
#include <array> #include <array>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>

View file

@ -1,6 +1,6 @@
#include "core/controller_identity.h" #include "controller_identity.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "controller_profile_legacy_fixtures.h" #include "tests/controller_profile_legacy_fixtures.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>

View file

@ -1,6 +1,7 @@
#include "core/controller_identity.h" #include "controller_identity.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "profile/controller_profile_transform.h" #include "controller_profile_transform.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>

View file

@ -1,4 +1,5 @@
#include "profile/controller_synthetic_input.h" #include "controller_synthetic_input.h"
#include <array> #include <array>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>

View file

@ -1,4 +1,5 @@
#include "usb/generic_hid/generic_hid_descriptors.h" #include "generic_hid_descriptors.h"
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>

View file

@ -1,5 +1,7 @@
#include "usb/generic_hid/generic_hid_driver.h" #include "generic_hid_driver.h"
#include "usb/generic_hid/generic_hid_descriptors.h"
#include "generic_hid_descriptors.h"
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>

View file

@ -1,8 +1,9 @@
#include "core/controller_identity.h" #include "controller_identity.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "platform/pico/pico_profile_storage.h" #include "pico_profile_storage.h"
#include "profile/profile_service.h" #include "profile_service.h"
#include "profile/profile_storage.h" #include "profile_storage.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>

View file

@ -1,7 +1,7 @@
#include "core/controller_identity.h" #include "controller_identity.h"
#include "profile/controller_profile.h" #include "controller_profile.h"
#include "profile/profile_storage.h" #include "profile_storage.h"
#include "controller_profile_legacy_fixtures.h" #include "tests/controller_profile_legacy_fixtures.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>

View file

@ -1,4 +1,5 @@
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>

View file

@ -1,5 +1,6 @@
#include "usb/switch/switch_pro_descriptors.h" #include "switch_pro_descriptors.h"
#include "platform/pico/tusb_config.h" #include "tusb_config.h"
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>

View file

@ -1,6 +1,6 @@
#include "usb/switch/switch_pro_driver.h" #include "switch_pro_driver.h"
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
#include "platform/pico/controller_color_config.h" #include "controller_color_config.h"
#include "tusb.h" #include "tusb.h"
#include "pico/time.h" #include "pico/time.h"

View file

@ -20,8 +20,8 @@ def test_adapter_host_probe_native(tmp_path: Path) -> None:
"-pedantic", "-pedantic",
"-DSWITCH_PICO_HID_INSTANCE_COUNT=4", "-DSWITCH_PICO_HID_INSTANCE_COUNT=4",
f"-I{root / 'tests' / 'mode_native_stubs'}", f"-I{root / 'tests' / 'mode_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "src" / "firmware" / "adapter" / "adapter_host_probe.cpp"), str(root / "adapter_host_probe.cpp"),
str(root / "tests" / "adapter_host_probe_test.cpp"), str(root / "tests" / "adapter_host_probe_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -20,8 +20,8 @@ def test_adapter_mode_controller_native(tmp_path: Path) -> None:
"-pedantic", "-pedantic",
"-DSWITCH_PICO_HID_INSTANCE_COUNT=4", "-DSWITCH_PICO_HID_INSTANCE_COUNT=4",
f"-I{root / 'tests' / 'mode_native_stubs'}", f"-I{root / 'tests' / 'mode_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "src" / "firmware" / "adapter" / "adapter_mode_controller.cpp"), str(root / "adapter_mode_controller.cpp"),
str(root / "tests" / "adapter_mode_controller_test.cpp"), str(root / "tests" / "adapter_mode_controller_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -27,7 +27,7 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
command.extend( command.extend(
[ [
f"-I{root / 'tests' / 'bluepad32_native_stubs'}", f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "bluepad32_backend_lifecycle_test.cpp"), str(root / "tests" / "bluepad32_backend_lifecycle_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -22,8 +22,8 @@ def test_bootsel_pairing_button_native(tmp_path: Path) -> None:
"-pedantic", "-pedantic",
f"-DPICO_RP2350={rp2350}", f"-DPICO_RP2350={rp2350}",
f"-I{root / 'tests' / 'bootsel_native_stubs'}", f"-I{root / 'tests' / 'bootsel_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "src" / "firmware" / "platform" / "pico" / "bootsel_pairing_button.cpp"), str(root / "bootsel_pairing_button.cpp"),
str(root / "tests" / "bootsel_pairing_button_test.cpp"), str(root / "tests" / "bootsel_pairing_button_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -18,12 +18,12 @@ def test_configuration_service_native(tmp_path: Path) -> None:
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'tests' / 'bluepad32_native_stubs'}", f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "configuration_service_test.cpp"), str(root / "tests" / "configuration_service_test.cpp"),
str(root / "src" / "firmware" / "configuration" / "adapter_configuration.cpp"), str(root / "adapter_configuration.cpp"),
str(root / "src" / "firmware" / "configuration" / "configuration_service.cpp"), str(root / "configuration_service.cpp"),
str(root / "src" / "firmware" / "configuration" / "configuration_storage.cpp"), str(root / "configuration_storage.cpp"),
str(root / "src" / "firmware" / "configuration" / "configuration_transaction.cpp"), str(root / "configuration_transaction.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,11 +17,11 @@ def test_configuration_storage_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "configuration_storage_test.cpp"), str(root / "tests" / "configuration_storage_test.cpp"),
str(root / "src" / "firmware" / "configuration" / "adapter_configuration.cpp"), str(root / "adapter_configuration.cpp"),
str(root / "src" / "firmware" / "configuration" / "configuration_storage.cpp"), str(root / "configuration_storage.cpp"),
str(root / "src" / "firmware" / "configuration" / "configuration_transaction.cpp"), str(root / "configuration_transaction.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,10 +17,10 @@ def test_controller_profile_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "controller_profile_test.cpp"), str(root / "tests" / "controller_profile_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,13 +17,13 @@ def test_controller_profile_runtime_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "controller_profile_runtime_test.cpp"), str(root / "tests" / "controller_profile_runtime_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile_transform.cpp"), str(root / "controller_profile_transform.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_synthetic_input.cpp"), str(root / "controller_synthetic_input.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile_runtime.cpp"), str(root / "controller_profile_runtime.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,11 +17,11 @@ def test_controller_profile_transform_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "controller_profile_transform_test.cpp"), str(root / "tests" / "controller_profile_transform_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile_transform.cpp"), str(root / "controller_profile_transform.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,12 +17,12 @@ def test_controller_synthetic_input_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "controller_synthetic_input_test.cpp"), str(root / "tests" / "controller_synthetic_input_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile_transform.cpp"), str(root / "controller_profile_transform.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_synthetic_input.cpp"), str(root / "controller_synthetic_input.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -32,7 +32,7 @@ def compile_cpp(
command.append(f"-I{root / 'tests' / 'native_stubs'}") command.append(f"-I{root / 'tests' / 'native_stubs'}")
command.extend( command.extend(
[ [
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
*[str(source) for source in sources], *[str(source) for source in sources],
"-o", "-o",
str(output), str(output),
@ -98,7 +98,7 @@ def test_generic_hid_driver_contexts(tmp_path: Path) -> None:
host_compiler(), host_compiler(),
executable, executable,
[ [
root / "src" / "firmware" / "usb" / "generic_hid" / "generic_hid_driver.cpp", root / "generic_hid_driver.cpp",
root / "tests" / "generic_hid_driver_test.cpp", root / "tests" / "generic_hid_driver_test.cpp",
], ],
["SWITCH_PICO_HID_INSTANCE_COUNT=4"], ["SWITCH_PICO_HID_INSTANCE_COUNT=4"],

View file

@ -1,194 +1,339 @@
from __future__ import annotations """
Tests for prepare_bluepad32.py patch preparation tool.
Tests cover:
- Fresh patch application
- Idempotence (second invocation succeeds without changing content)
- Missing paths validation
- Diverged/ambiguous repository states
"""
import shutil
import subprocess import subprocess
import sys import tempfile
from pathlib import Path
import pytest import pytest
from pathlib import Path
from unittest.mock import patch as mock_patch
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "tools")) sys.path.insert(0, str(Path(__file__).parent.parent / "tools"))
from prepare_bluepad32 import ( # noqa: E402 from prepare_bluepad32 import (
PatchError, PatchError,
check_paths,
main,
prepare_bluepad32,
resolve_paths, resolve_paths,
check_paths,
is_patch_applied,
apply_patch,
prepare_bluepad32,
) )
def run_git(repository: Path, *args: str) -> subprocess.CompletedProcess[str]: @pytest.fixture
return subprocess.run( def temp_repo_structure():
["git", "-C", str(repository), *args], """Create a temporary directory structure with git repositories."""
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
# Create bluepad32 repo
bp_dir = root / "external" / "bluepad32"
bp_dir.mkdir(parents=True)
subprocess.run(["git", "init"], cwd=bp_dir, check=True, capture_output=True)
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=bp_dir, check=True, capture_output=True)
subprocess.run(["git", "config", "user.name", "Test User"], cwd=bp_dir, check=True, capture_output=True)
# Create a file to patch
test_file = bp_dir / "test.txt"
test_file.write_text("line 1\n")
subprocess.run(["git", "add", "test.txt"], cwd=bp_dir, check=True, capture_output=True)
subprocess.run(["git", "commit", "-m", "initial"], cwd=bp_dir, check=True, capture_output=True)
# Create patches dir
patches_dir = root / "patches"
patches_dir.mkdir()
yield root, bp_dir, patches_dir
def create_simple_patch(repo_path: Path, patch_path: Path, file_to_patch: str = "test.txt") -> str:
"""
Create a simple patch file that modifies a file in the repository.
Returns the patch content as a string.
"""
# Create the modification
test_file = repo_path / file_to_patch
original_content = test_file.read_text()
modified_content = original_content + "line 2\n"
# Generate patch using git diff
test_file.write_text(modified_content)
result = subprocess.run(
["git", "diff", file_to_patch],
cwd=repo_path,
capture_output=True, capture_output=True,
text=True, text=True,
check=True, check=True,
) )
patch_content = result.stdout
# Reset the file to original state
test_file.write_text(original_content)
# Write patch to file
patch_path.write_text(patch_content)
return patch_content
@pytest.fixture def test_resolve_paths_with_defaults():
def bluepad32_fixture(tmp_path: Path) -> tuple[Path, Path, Path, Path]: """Test that resolve_paths returns expected default paths."""
source = tmp_path / "external" / "bluepad32" with tempfile.TemporaryDirectory() as tmpdir:
source.mkdir(parents=True) bp_path, patch_path = resolve_paths(Path(tmpdir))
run_git(source, "init") assert bp_path == Path(tmpdir) / "external" / "bluepad32"
run_git(source, "config", "user.email", "test@example.com") assert patch_path == Path(tmpdir) / "patches" / "bluepad32-sdl3-imu.patch"
run_git(source, "config", "user.name", "Test User")
(source / "test.txt").write_text("line 1\n", encoding="utf-8")
run_git(source, "add", "test.txt")
run_git(source, "commit", "-m", "initial")
patches = tmp_path / "patches"
patches.mkdir()
patch = patches / "bluepad32-sdl3-imu.patch"
original = (source / "test.txt").read_text(encoding="utf-8")
(source / "test.txt").write_text(f"{original}line 2\n", encoding="utf-8")
patch.write_text(run_git(source, "diff", "--", "test.txt").stdout, encoding="utf-8")
run_git(source, "restore", "test.txt")
output = tmp_path / "build-aio" / "_deps" / "bluepad32-src"
return tmp_path, source, patch, output
def source_status(source: Path) -> str: def test_resolve_paths_no_root():
return run_git(source, "status", "--porcelain", "--untracked-files=all").stdout """Test resolve_paths with no root uses current directory."""
bp_path, patch_path = resolve_paths()
assert bp_path.is_absolute()
assert patch_path.is_absolute()
def test_resolve_paths_uses_project_build_tree(tmp_path: Path) -> None: def test_check_paths_missing_bluepad32(temp_repo_structure):
source, patch, output = resolve_paths(tmp_path) """Test that check_paths fails if bluepad32 dir is missing."""
root, bp_dir, patches_dir = temp_repo_structure
assert source == tmp_path / "external" / "bluepad32"
assert patch == tmp_path / "patches" / "bluepad32-sdl3-imu.patch" # Remove bluepad32
assert output == tmp_path / "build" / "_deps" / "bluepad32-src" import shutil
shutil.rmtree(bp_dir)
def test_check_paths_rejects_missing_source( patch_file = patches_dir / "test.patch"
bluepad32_fixture: tuple[Path, Path, Path, Path], patch_file.write_text("dummy")
) -> None:
_, source, patch, output = bluepad32_fixture
shutil.rmtree(source)
with pytest.raises(PatchError, match="bluepad32 directory does not exist"): with pytest.raises(PatchError, match="bluepad32 directory does not exist"):
check_paths(source, patch, output) check_paths(bp_dir, patch_file)
def test_check_paths_rejects_missing_patch( def test_check_paths_missing_patch(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test that check_paths fails if patch file is missing."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
patch.unlink() patch_file = patches_dir / "nonexistent.patch"
with pytest.raises(PatchError, match="patch file does not exist"): with pytest.raises(PatchError, match="patch file does not exist"):
check_paths(source, patch, output) check_paths(bp_dir, patch_file)
def test_check_paths_rejects_non_repository( def test_check_paths_bluepad32_not_git_repo(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test that check_paths fails if bluepad32 is not a git repo."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
shutil.rmtree(source / ".git") # Remove .git to make it not a git repo
import shutil
shutil.rmtree(bp_dir / ".git")
patch_file = patches_dir / "test.patch"
patch_file.write_text("dummy")
with pytest.raises(PatchError, match="not a git repository"): with pytest.raises(PatchError, match="not a git repository"):
check_paths(source, patch, output) check_paths(bp_dir, patch_file)
def test_check_paths_rejects_output_inside_source( def test_fresh_patch_application(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test applying a fresh patch to a clean repository."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, _ = bluepad32_fixture
patch_file = patches_dir / "test.patch"
with pytest.raises(PatchError, match="outside the Bluepad32 source tree"): create_simple_patch(bp_dir, patch_file, "test.txt")
check_paths(source, patch, source / "patched")
# Verify test.txt before patch
test_file = bp_dir / "test.txt"
original = test_file.read_text()
assert "line 2" not in original
# Apply patch
apply_patch(bp_dir, patch_file)
# Verify test.txt after patch
patched = test_file.read_text()
assert "line 2" in patched
def test_prepare_patches_copy_and_preserves_pristine_source( def test_idempotent_patch_application(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test that applying the same patch twice succeeds (idempotence)."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
patch_file = patches_dir / "test.patch"
result = prepare_bluepad32(source, patch, output) create_simple_patch(bp_dir, patch_file, "test.txt")
assert result == output # First application
assert (source / "test.txt").read_text(encoding="utf-8") == "line 1\n" apply_patch(bp_dir, patch_file)
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n" test_file = bp_dir / "test.txt"
assert not (output / ".git").exists() after_first = test_file.read_text()
assert source_status(source) == ""
# Second application should succeed without changing content
apply_patch(bp_dir, patch_file)
after_second = test_file.read_text()
assert after_first == after_second
def test_prepare_replaces_existing_output_idempotently( def test_is_patch_applied_not_applied(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test is_patch_applied returns False for unapplied patch."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
prepare_bluepad32(source, patch, output) patch_file = patches_dir / "test.patch"
(output / "stale.txt").write_text("stale", encoding="utf-8") create_simple_patch(bp_dir, patch_file, "test.txt")
prepare_bluepad32(source, patch, output) # Patch not applied yet
assert is_patch_applied(bp_dir, patch_file) is False
assert not (output / "stale.txt").exists()
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == ""
def test_prepare_rejects_dirty_source_without_replacing_output( def test_is_patch_applied_already_applied(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test is_patch_applied returns True for already applied patch."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
output.mkdir(parents=True) patch_file = patches_dir / "test.patch"
sentinel = output / "sentinel.txt" create_simple_patch(bp_dir, patch_file, "test.txt")
sentinel.write_text("keep", encoding="utf-8")
(source / "test.txt").write_text("local edit\n", encoding="utf-8") # Apply patch first
apply_patch(bp_dir, patch_file)
with pytest.raises(PatchError, match="must be pristine"):
prepare_bluepad32(source, patch, output) # Now check should detect it's applied
assert is_patch_applied(bp_dir, patch_file) is True
assert sentinel.read_text(encoding="utf-8") == "keep"
def test_prepare_rejects_diverged_source_revision( def test_diverged_repository_state(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], """Test that diverged repository (patch doesn't apply cleanly) is rejected."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
(source / "test.txt").write_text("different base\n", encoding="utf-8") patch_file = patches_dir / "test.patch"
run_git(source, "add", "test.txt") create_simple_patch(bp_dir, patch_file, "test.txt")
run_git(source, "commit", "-m", "diverge")
# Diverge the repository by modifying the file such that the patch conflicts
test_file = bp_dir / "test.txt"
test_file.write_text("completely different line 1\n")
subprocess.run(["git", "add", "test.txt"], cwd=bp_dir, check=True, capture_output=True)
subprocess.run(["git", "commit", "-m", "divergence"], cwd=bp_dir, check=True, capture_output=True)
# Try to apply patch - should fail because file content doesn't match
with pytest.raises(PatchError, match="Patch validation failed"): with pytest.raises(PatchError, match="Patch validation failed"):
prepare_bluepad32(source, patch, output) apply_patch(bp_dir, patch_file)
assert not output.exists()
def test_prepare_uses_default_paths( def test_missing_bluepad32_path(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], monkeypatch: pytest.MonkeyPatch """Test prepare_bluepad32 fails gracefully with missing bluepad32."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
root, source, _, _ = bluepad32_fixture
monkeypatch.chdir(root) import shutil
shutil.rmtree(bp_dir)
output = prepare_bluepad32()
patch_file = patches_dir / "test.patch"
assert output == root / "build" / "_deps" / "bluepad32-src" patch_file.write_text("dummy")
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n"
assert source_status(source) == "" with pytest.raises(PatchError, match="bluepad32 directory does not exist"):
prepare_bluepad32(bp_dir, patch_file)
def test_cli_accepts_explicit_output( def test_missing_patch_file(temp_repo_structure):
bluepad32_fixture: tuple[Path, Path, Path, Path], monkeypatch: pytest.MonkeyPatch """Test prepare_bluepad32 fails gracefully with missing patch file."""
) -> None: root, bp_dir, patches_dir = temp_repo_structure
_, source, patch, output = bluepad32_fixture
monkeypatch.setattr( patch_file = patches_dir / "nonexistent.patch"
sys,
"argv", with pytest.raises(PatchError, match="patch file does not exist"):
[ prepare_bluepad32(bp_dir, patch_file)
"prepare_bluepad32.py",
"--bluepad32",
str(source),
"--patch",
str(patch),
"--output",
str(output),
],
)
assert main() == 0
assert (output / "test.txt").read_text(encoding="utf-8") == "line 1\nline 2\n" def test_prepare_bluepad32_full_workflow(temp_repo_structure):
assert source_status(source) == "" """Test complete prepare_bluepad32 workflow: apply then idempotent re-apply."""
root, bp_dir, patches_dir = temp_repo_structure
patch_file = patches_dir / "test.patch"
create_simple_patch(bp_dir, patch_file, "test.txt")
test_file = bp_dir / "test.txt"
original = test_file.read_text()
# First prepare (should apply patch)
prepare_bluepad32(bp_dir, patch_file)
after_first = test_file.read_text()
assert after_first != original
assert "line 2" in after_first
# Second prepare (should be idempotent)
prepare_bluepad32(bp_dir, patch_file)
after_second = test_file.read_text()
assert after_first == after_second
def test_prepare_bluepad32_with_defaults(temp_repo_structure):
"""Test prepare_bluepad32 uses correct defaults when paths not provided."""
root, bp_dir, patches_dir = temp_repo_structure
patch_file = patches_dir / "bluepad32-sdl3-imu.patch"
create_simple_patch(bp_dir, patch_file, "test.txt")
# Change to root directory and call with defaults
import os
original_cwd = os.getcwd()
try:
os.chdir(root)
prepare_bluepad32() # Use defaults
finally:
os.chdir(original_cwd)
# Verify patch was applied
test_file = bp_dir / "test.txt"
assert "line 2" in test_file.read_text()
def test_patch_application_with_conflicting_content(temp_repo_structure):
"""Test that patch with conflicting content is rejected."""
root, bp_dir, patches_dir = temp_repo_structure
# Create a patch that adds a specific change
patch_content = """--- a/test.txt
+++ b/test.txt
@@ -1 +1,3 @@
line 1
+line 2
+line 3
"""
patch_file = patches_dir / "conflict.patch"
patch_file.write_text(patch_content)
# Modify the file to have different content that won't match the patch context
test_file = bp_dir / "test.txt"
test_file.write_text("modified line 1\n")
subprocess.run(["git", "add", "test.txt"], cwd=bp_dir, check=True, capture_output=True)
subprocess.run(["git", "commit", "-m", "modify"], cwd=bp_dir, check=True, capture_output=True)
# Try to apply patch - should fail due to context mismatch
with pytest.raises(PatchError):
apply_patch(bp_dir, patch_file)
def test_cli_with_explicit_paths(temp_repo_structure):
"""Test CLI argument parsing with explicit paths."""
root, bp_dir, patches_dir = temp_repo_structure
patch_file = patches_dir / "test.patch"
create_simple_patch(bp_dir, patch_file, "test.txt")
# Simulate CLI call
sys.argv = [
"prepare_bluepad32.py",
"--bluepad32", str(bp_dir),
"--patch", str(patch_file),
]
from prepare_bluepad32 import main
# Should not raise
try:
main()
except SystemExit as e:
# main() calls sys.exit on success, which we need to catch
if e.code != 0:
raise

View file

@ -18,12 +18,12 @@ def test_profile_service_native(tmp_path: Path) -> None:
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'tests' / 'bluepad32_native_stubs'}", f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "profile_service_test.cpp"), str(root / "tests" / "profile_service_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
str(root / "src" / "firmware" / "profile" / "profile_storage.cpp"), str(root / "profile_storage.cpp"),
str(root / "src" / "firmware" / "profile" / "profile_service.cpp"), str(root / "profile_service.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -17,11 +17,11 @@ def test_profile_storage_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "profile_storage_test.cpp"), str(root / "tests" / "profile_storage_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"), str(root / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"), str(root / "controller_profile.cpp"),
str(root / "src" / "firmware" / "profile" / "profile_storage.cpp"), str(root / "profile_storage.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -19,8 +19,8 @@ def test_switch_haptics_native(tmp_path: Path) -> None:
"-Wextra", "-Wextra",
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "src" / "firmware" / "usb" / "switch" / "switch_haptics.cpp"), str(root / "switch_haptics.cpp"),
str(root / "tests" / "switch_haptics_test.cpp"), str(root / "tests" / "switch_haptics_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -25,7 +25,7 @@ def compile_descriptor_test(
command.append(f"-DSWITCH_PICO_HID_INSTANCE_COUNT={configured_count}") command.append(f"-DSWITCH_PICO_HID_INSTANCE_COUNT={configured_count}")
command.extend( command.extend(
[ [
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "switch_pro_descriptors_test.cpp"), str(root / "tests" / "switch_pro_descriptors_test.cpp"),
"-o", "-o",
str(output), str(output),

View file

@ -21,10 +21,10 @@ def test_switch_pro_driver_four_contexts_native(tmp_path: Path) -> None:
"-pedantic", "-pedantic",
"-DSWITCH_PICO_HID_INSTANCE_COUNT=4", "-DSWITCH_PICO_HID_INSTANCE_COUNT=4",
f"-I{root / 'tests' / 'native_stubs'}", f"-I{root / 'tests' / 'native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "src" / "firmware" / "usb" / "switch" / "switch_pro_driver.cpp"), str(root / "switch_pro_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "usb_output_driver.cpp"), str(root / "usb_output_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "switch" / "switch_haptics.cpp"), str(root / "switch_haptics.cpp"),
str(root / "tests" / "switch_pro_driver_context_test.cpp"), str(root / "tests" / "switch_pro_driver_context_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -19,7 +19,7 @@ def test_usb_configuration_management_native(tmp_path: Path) -> None:
"-Werror", "-Werror",
"-pedantic", "-pedantic",
f"-I{root / 'tests' / 'usb_management_native_stubs'}", f"-I{root / 'tests' / 'usb_management_native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "usb_configuration_management_test.cpp"), str(root / "tests" / "usb_configuration_management_test.cpp"),
"-o", "-o",
str(executable), str(executable),

View file

@ -36,13 +36,13 @@ def test_usb_output_driver_contracts(tmp_path: Path) -> None:
"-DSWITCH_PICO_USB_OUTPUT_MODES=1", "-DSWITCH_PICO_USB_OUTPUT_MODES=1",
*backend_definitions, *backend_definitions,
f"-I{root / 'tests' / 'native_stubs'}", f"-I{root / 'tests' / 'native_stubs'}",
f"-I{root / 'src' / 'firmware'}", f"-I{root}",
str(root / "tests" / "usb_output_driver_test.cpp"), str(root / "tests" / "usb_output_driver_test.cpp"),
str(root / "src" / "firmware" / "usb" / "switch" / "switch_pro_driver.cpp"), str(root / "switch_pro_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "usb_output_driver.cpp"), str(root / "usb_output_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "generic_hid" / "generic_hid_driver.cpp"), str(root / "generic_hid_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "xinput" / "xinput_driver.cpp"), str(root / "xinput_driver.cpp"),
str(root / "src" / "firmware" / "usb" / "switch" / "switch_haptics.cpp"), str(root / "switch_haptics.cpp"),
"-o", "-o",
str(executable), str(executable),
], ],

View file

@ -1,11 +1,12 @@
#include "usb/usb_configuration_management.h" #include "usb_configuration_management.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <tusb.h> #include <tusb.h>
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
namespace { namespace {
@ -787,10 +788,10 @@ bool tud_control_status(uint8_t, const tusb_control_request_t*) {
return true; return true;
} }
#include "configuration/adapter_configuration.cpp" #include "../adapter_configuration.cpp"
#include "core/controller_identity.cpp" #include "../controller_identity.cpp"
#include "profile/controller_profile.cpp" #include "../controller_profile.cpp"
#include "usb/usb_configuration_management.cpp" #include "../usb_configuration_management.cpp"
int main() { int main() {
current_configuration.state = ConfigurationServiceState::kReady; current_configuration.state = ConfigurationServiceState::kReady;

View file

@ -4,17 +4,17 @@
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include "adapter/adapter_host_probe_state.h" #include "adapter_host_probe_state.h"
#include "usb/generic_hid/generic_hid_descriptors.h" #include "generic_hid_descriptors.h"
#include "usb/generic_hid/generic_hid_driver.h" #include "generic_hid_driver.h"
#include "usb/xinput/xinput_descriptors.h" #include "xinput_descriptors.h"
#include "usb/xinput/xinput_protocol.h" #include "xinput_protocol.h"
#include "device/usbd_pvt.h" #include "device/usbd_pvt.h"
#include "pico/time.h" #include "pico/time.h"
#include "usb/switch/switch_pro_driver.h" #include "switch_pro_driver.h"
#include "tusb.h" #include "tusb.h"
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
#include "usb/xinput/xinput_driver.h" #include "xinput_driver.h"
namespace { namespace {

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Create a patched build-local copy of the pinned Bluepad32 source.""" """Apply the project Bluepad32 patch exactly once."""
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import shutil
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@ -13,125 +12,69 @@ class PatchError(RuntimeError):
pass pass
def resolve_paths(repo_root: Path | None = None) -> tuple[Path, Path, Path]: def resolve_paths(repo_root: Path | None = None) -> tuple[Path, Path]:
root = Path.cwd() if repo_root is None else Path(repo_root) root = Path.cwd() if repo_root is None else Path(repo_root)
return ( return (
root / "external" / "bluepad32", root / "external" / "bluepad32",
root / "patches" / "bluepad32-sdl3-imu.patch", root / "patches" / "bluepad32-sdl3-imu.patch",
root / "build" / "_deps" / "bluepad32-src",
) )
def _run(command: list[str], *, cwd: Path | None = None) -> subprocess.CompletedProcess[str]: def check_paths(bluepad32_path: Path, patch_path: Path) -> None:
if not bluepad32_path.is_dir():
raise PatchError(f"bluepad32 directory does not exist: {bluepad32_path}")
if not (bluepad32_path / ".git").exists():
raise PatchError(f"Bluepad32 is not a git repository: {bluepad32_path}")
if not patch_path.is_file():
raise PatchError(f"patch file does not exist: {patch_path}")
def git_apply(bluepad32_path: Path, patch_path: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run( return subprocess.run(
command, ["git", "-C", str(bluepad32_path), "apply", *args, str(patch_path)],
cwd=cwd,
capture_output=True, capture_output=True,
text=True, text=True,
check=False, check=False,
) )
def check_paths(source_path: Path, patch_path: Path, output_path: Path) -> None: def is_patch_applied(bluepad32_path: Path, patch_path: Path) -> bool:
if not source_path.is_dir(): return git_apply(bluepad32_path, patch_path, "--reverse", "--check").returncode == 0
raise PatchError(f"bluepad32 directory does not exist: {source_path}")
if not patch_path.is_file():
raise PatchError(f"patch file does not exist: {patch_path}")
repository_check = _run(
["git", "-C", str(source_path), "rev-parse", "--is-inside-work-tree"]
)
if repository_check.returncode != 0:
raise PatchError(f"Bluepad32 is not a git repository: {source_path}")
source = source_path.resolve()
output = output_path.resolve()
if output == source or source in output.parents:
raise PatchError("patched output must be outside the Bluepad32 source tree")
def validate_pristine_source(source_path: Path, patch_path: Path) -> None: def apply_patch(bluepad32_path: Path, patch_path: Path) -> None:
status = _run( if is_patch_applied(bluepad32_path, patch_path):
[ return
"git",
"-C",
str(source_path),
"status",
"--porcelain",
"--untracked-files=all",
]
)
if status.returncode != 0:
detail = status.stderr.strip() or "git status failed"
raise PatchError(f"Could not inspect Bluepad32 source: {detail}")
if status.stdout.strip():
raise PatchError(
"Bluepad32 source must be pristine; patches are applied only to the build-local copy"
)
check = _run( check = git_apply(bluepad32_path, patch_path, "--check")
["git", "-C", str(source_path), "apply", "--check", str(patch_path)]
)
if check.returncode != 0: if check.returncode != 0:
detail = check.stderr.strip() or "patch does not apply" detail = check.stderr.strip() or "patch does not apply"
raise PatchError(f"Patch validation failed (source revision may have diverged):\n{detail}") raise PatchError(f"Patch validation failed (repository may be diverged):\n{detail}")
result = git_apply(bluepad32_path, patch_path)
def _copy_and_patch(source_path: Path, patch_path: Path, output_path: Path) -> None: if result.returncode != 0:
output_path.parent.mkdir(parents=True, exist_ok=True) detail = result.stderr.strip() or "git apply failed"
staging_path = output_path.with_name(f".{output_path.name}.tmp") raise PatchError(f"Could not patch Bluepad32: {detail}")
if staging_path.exists():
shutil.rmtree(staging_path)
try:
shutil.copytree(
source_path,
staging_path,
symlinks=True,
ignore=shutil.ignore_patterns(".git"),
)
result = _run(
["git", "apply", "--no-index", str(patch_path.resolve())],
cwd=staging_path,
)
if result.returncode != 0:
detail = result.stderr.strip() or "git apply failed"
raise PatchError(f"Could not patch build-local Bluepad32 copy: {detail}")
if output_path.exists():
shutil.rmtree(output_path)
staging_path.replace(output_path)
except Exception:
if staging_path.exists():
shutil.rmtree(staging_path)
raise
def prepare_bluepad32( def prepare_bluepad32(
source_path: Path | None = None, bluepad32_path: Path | None = None,
patch_path: Path | None = None, patch_path: Path | None = None,
output_path: Path | None = None, ) -> None:
) -> Path: default_bluepad32, default_patch = resolve_paths()
default_source, default_patch, default_output = resolve_paths() dependency = Path(bluepad32_path or default_bluepad32)
source = Path(source_path or default_source)
patch = Path(patch_path or default_patch) patch = Path(patch_path or default_patch)
output = Path(output_path or default_output) check_paths(dependency, patch)
apply_patch(dependency, patch)
check_paths(source, patch, output)
validate_pristine_source(source, patch)
_copy_and_patch(source, patch, output)
return output
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bluepad32", type=Path) parser.add_argument("--bluepad32", type=Path)
parser.add_argument("--patch", type=Path) parser.add_argument("--patch", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args() args = parser.parse_args()
try: try:
prepare_bluepad32(args.bluepad32, args.patch, args.output) prepare_bluepad32(args.bluepad32, args.patch)
except PatchError as exc: except PatchError as exc:
print(f"error: {exc}", file=sys.stderr) print(f"error: {exc}", file=sys.stderr)
return 1 return 1

View file

@ -1,12 +1,13 @@
#include "usb/usb_configuration_management.h" #include "usb_configuration_management.h"
#include <string.h> #include <string.h>
#include "configuration/adapter_configuration.h" #include "adapter_configuration.h"
#include "adapter/adapter_host_probe.h" #include "adapter_host_probe.h"
#include "adapter/adapter_reboot.h" #include "adapter_reboot.h"
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#include "tusb.h" #include "tusb.h"
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
namespace UsbConfigurationManagement { namespace UsbConfigurationManagement {
namespace { namespace {

View file

@ -5,9 +5,9 @@
#include "tusb.h" #include "tusb.h"
#include "input/bluepad32_input_backend.h" #include "bluepad32_input_backend.h"
#include "configuration/configuration_service.h" #include "configuration_service.h"
#include "profile/profile_service.h" #include "profile_service.h"
namespace UsbConfigurationManagement { namespace UsbConfigurationManagement {

View file

@ -1,23 +1,23 @@
#include "usb/usb_output_driver.h" #include "usb_output_driver.h"
#include <cstring> #include <cstring>
#include <stdio.h> #include <stdio.h>
#include "device/usbd_pvt.h" #include "device/usbd_pvt.h"
#include "usb/switch/switch_pro_driver.h" #include "switch_pro_driver.h"
#include "usb/switch/switch_pro_descriptors.h" #include "switch_pro_descriptors.h"
#include "tusb.h" #include "tusb.h"
#ifdef SWITCH_PICO_BLUEPAD32 #ifdef SWITCH_PICO_BLUEPAD32
#include "usb/usb_configuration_management.h" #include "usb_configuration_management.h"
#endif #endif
#ifdef SWITCH_PICO_USB_OUTPUT_MODES #ifdef SWITCH_PICO_USB_OUTPUT_MODES
#include "adapter/adapter_host_probe.h" #include "adapter_host_probe.h"
#include "usb/generic_hid/generic_hid_descriptors.h" #include "generic_hid_descriptors.h"
#include "usb/generic_hid/generic_hid_driver.h" #include "generic_hid_driver.h"
#include "usb/xinput/xinput_descriptors.h" #include "xinput_descriptors.h"
#include "usb/xinput/xinput_driver.h" #include "xinput_driver.h"
#endif #endif
#ifdef SWITCH_PICO_LOG #ifdef SWITCH_PICO_LOG

View file

@ -2,9 +2,9 @@
#include <stdint.h> #include <stdint.h>
#include "adapter/adapter_usb_mode.h" #include "adapter_usb_mode.h"
#include "core/controller_state.h" #include "controller_state.h"
#include "usb/switch/switch_haptics.h" #include "switch_haptics.h"
// Mode capability bits published in USB management info. Input is present in // Mode capability bits published in USB management info. Input is present in
// every current mode; rumble/motion describe host-visible USB capabilities. // every current mode; rumble/motion describe host-visible USB capabilities.

Some files were not shown because too many files have changed in this diff Show more