From 9e532a1ef2ccffeaa3d6727b5c39147b0990a406 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 12:57:44 -0400 Subject: [PATCH 01/15] ci: retain exact host and controller sources for desktop qualification --- .github/workflows/switch2kit-desktop.yml | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/switch2kit-desktop.yml diff --git a/.github/workflows/switch2kit-desktop.yml b/.github/workflows/switch2kit-desktop.yml new file mode 100644 index 000000000000..1c3026902ad1 --- /dev/null +++ b/.github/workflows/switch2kit-desktop.yml @@ -0,0 +1,35 @@ +name: Switch2Kit desktop platforms +on: + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: switch2kit-desktop-${{ github.ref }} + cancel-in-progress: true +jobs: + source: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - name: Initialize the pinned controller SDK and SDL + run: git submodule update --init Externals/Switch2Kit Externals/SDL/SDL + - name: Archive the exact sources used by the controller integration + run: | + mkdir -p "$RUNNER_TEMP/switch2kit-source" + git archive HEAD -o "$RUNNER_TEMP/switch2kit-source/host.zip" + git -C Externals/Switch2Kit archive HEAD -o "$RUNNER_TEMP/switch2kit-source/sdk.zip" + git -C Externals/SDL/SDL archive HEAD -o "$RUNNER_TEMP/switch2kit-source/sdl.zip" + { + git rev-parse HEAD + git submodule status Externals/Switch2Kit Externals/SDL/SDL + } > "$RUNNER_TEMP/switch2kit-source/revisions.txt" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Switch2Kit-desktop-source + path: ${{ runner.temp }}/switch2kit-source + retention-days: 7 + if-no-files-found: error From 5d2463c650d14be582821414b15debabc5869643 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 14:27:17 -0400 Subject: [PATCH 02/15] Enable the existing native BlueZ backend in Linux Dolphin builds Remove the Apple-only host gate while retaining SDL/Qt requirements and the macOS deployment check. Install the facade with relative runtime paths, provide a build-and-run helper, and compile, install and relocate the full Linux application in CI. --- .github/workflows/switch2kit-linux.yml | 63 ++++++++++++++++++++++++++ CMake/DolphinSwitch2Kit.cmake | 13 ++++-- Source/Core/CMakeLists.txt | 10 ++++ Tools/build-switch2kit-linux.sh | 18 ++++++++ 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/switch2kit-linux.yml create mode 100755 Tools/build-switch2kit-linux.sh diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml new file mode 100644 index 000000000000..7eaa905d81ee --- /dev/null +++ b/.github/workflows/switch2kit-linux.yml @@ -0,0 +1,63 @@ +name: Switch2Kit Linux application +on: + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: switch2kit-linux-${{ github.ref }} + cancel-in-progress: true +jobs: + linux: + runs-on: ubuntu-24.04 + container: swift:6.2.1-noble + timeout-minutes: 90 + env: + DEBIAN_FRONTEND: noninteractive + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + submodules: recursive + - name: Install native dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus xvfb xauth + git config --global --add safe.directory "$PWD" + - name: Build and install the controller-enabled application + shell: bash + run: | + set -o pipefail + bash Tools/build-switch2kit-linux.sh 2>&1 | tee linux-build.log + - name: Execute controller policies + run: | + python3 Tools/test_switch2kit_mapping.py --sanitize + python3 Tools/test_switch2kit_host.py --sanitize + python3 Tools/test_switch2kit_autoconnect.py + - name: Relocate, resolve native dependencies and launch the installed binary + shell: bash + run: | + set -euo pipefail + mv build-switch2kit/install "$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" + app="$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" + mv build-switch2kit "$RUNNER_TEMP/disabled-build-tree" + test -n "$(find "$app" -name libSwitch2KitC.so -print -quit)" + ldd "$app/bin/dolphin-emu" | tee linux-dependencies.log + ! grep -q 'not found' linux-dependencies.log + grep -F "$app" linux-dependencies.log | grep libSwitch2KitC + xvfb-run -a "$app/bin/dolphin-emu" --version + tar -C "$RUNNER_TEMP" -czf Dolphin-Switch2Kit-linux-x86_64.tar.gz Dolphin-Switch2Kit-linux-x86_64 + - name: Controller-enabled development build + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-Switch2Kit-linux-x86_64 + path: Dolphin-Switch2Kit-linux-x86_64.tar.gz + retention-days: 14 + if-no-files-found: error + - name: Native diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-Switch2Kit-linux-diagnostics + path: linux-*.log + retention-days: 7 diff --git a/CMake/DolphinSwitch2Kit.cmake b/CMake/DolphinSwitch2Kit.cmake index 40f5a680c2ec..74156166e416 100644 --- a/CMake/DolphinSwitch2Kit.cmake +++ b/CMake/DolphinSwitch2Kit.cmake @@ -1,13 +1,16 @@ # Optional host input only. Disabled builds do not require Swift or change deployment targets. -option(ENABLE_SWITCH2KIT "Enable native Switch 2 controllers on macOS 15+" OFF) +option(ENABLE_SWITCH2KIT "Enable native Switch 2 controllers" OFF) if(NOT ENABLE_SWITCH2KIT) return() endif() -if(NOT APPLE OR NOT ENABLE_SDL OR NOT ENABLE_QT) - message(FATAL_ERROR "Switch2Kit requires macOS and the SDL and Qt backends") +if(NOT (APPLE OR WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") OR ANDROID) + message(FATAL_ERROR "Switch2Kit requires a macOS, Windows or Linux desktop host") endif() -if(NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 15.0) - message(FATAL_ERROR "Switch2Kit requires -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 or newer") +if(NOT ENABLE_SDL OR NOT ENABLE_QT) + message(FATAL_ERROR "Switch2Kit requires the SDL and Qt backends") +endif() +if(APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 15.0)) + message(FATAL_ERROR "Switch2Kit requires -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 or newer on macOS") endif() set(_switch2kit_source "${PROJECT_SOURCE_DIR}/Externals/Switch2Kit") if(NOT EXISTS "${_switch2kit_source}/Integrations/SDL3/CMakeLists.txt") diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index b1c5a3ab1718..58078df030da 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -19,6 +19,16 @@ if(ENABLE_QT) add_subdirectory(DolphinQt) endif() +if(ENABLE_SWITCH2KIT AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Each installed executable that links inputcommon needs the facade's relative + # runtime path, not just the GUI. The library is installed by the SDK helper. + foreach(_host dolphin-emu dolphin-nogui dolphin-tool) + if(TARGET "${_host}") + switch2kit_install_linux("${_host}") + endif() + endforeach() +endif() + if (APPLE OR WIN32) add_subdirectory(UpdaterCommon) endif() diff --git a/Tools/build-switch2kit-linux.sh b/Tools/build-switch2kit-linux.sh new file mode 100755 index 000000000000..3ff8871dbc00 --- /dev/null +++ b/Tools/build-switch2kit-linux.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Build the fork and its pinned controller library, without patching upstream files. +set -euo pipefail +cd "$(dirname "$0")/.." +if [[ $(uname -s) != Linux ]]; then echo 'This helper requires Linux.' >&2; exit 1; fi +for tool in cmake ninja git swift python3 pkg-config; do + command -v "$tool" >/dev/null || { echo "Missing build tool: $tool" >&2; exit 1; } +done +git submodule update --init --recursive +cmake -S . -B build-switch2kit -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DENABLE_SWITCH2KIT=ON \ + -DENABLE_SDL=ON -DENABLE_QT=ON -DUSE_SYSTEM_SDL3=OFF \ + -DENABLE_TESTS=OFF -DENABLE_VULKAN=OFF -DENABLE_AUTOUPDATE=OFF \ + -DCMAKE_INSTALL_PREFIX="$PWD/build-switch2kit/install" +cmake --build build-switch2kit --parallel "${S2K_BUILD_JOBS:-3}" +cmake --install build-switch2kit +printf 'Built: %s/build-switch2kit/install/bin/dolphin-emu\n' "$PWD" +if [[ ${1:-} == --run ]]; then exec "$PWD/build-switch2kit/install/bin/dolphin-emu"; fi From 1412032e9d2a3c99838193b2d3fb1b51d81f75ae Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 14:52:10 -0400 Subject: [PATCH 03/15] Compile controller mapping on non-Apple hosts and remove prose-pin checks Fix the full Linux application's unresolved mapping symbols. Exercise the actual platform and backend CMake admission rules for all desktop platforms; remove the unrelated requirement that documentation repeat a literal dependency hash. --- Source/Core/CMakeLists.txt | 21 +++++++----- Tools/test_switch2kit.py | 49 ++++++++++++++++++++-------- Tools/test_switch2kit_autoconnect.py | 5 +-- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 58078df030da..ff7599b66363 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -19,14 +19,19 @@ if(ENABLE_QT) add_subdirectory(DolphinQt) endif() -if(ENABLE_SWITCH2KIT AND CMAKE_SYSTEM_NAME STREQUAL "Linux") - # Each installed executable that links inputcommon needs the facade's relative - # runtime path, not just the GUI. The library is installed by the SDK helper. - foreach(_host dolphin-emu dolphin-nogui dolphin-tool) - if(TARGET "${_host}") - switch2kit_install_linux("${_host}") - endif() - endforeach() +if(ENABLE_SWITCH2KIT) + if(NOT APPLE) + target_sources(dolphin-emu PRIVATE + DolphinQt/Config/Mapping/Switch2KitMapping.cpp + DolphinQt/Config/Mapping/Switch2KitMapping.h) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + foreach(_host dolphin-emu dolphin-nogui dolphin-tool) + if(TARGET "${_host}") + switch2kit_install_linux("${_host}") + endif() + endforeach() + endif() endif() if (APPLE OR WIN32) diff --git a/Tools/test_switch2kit.py b/Tools/test_switch2kit.py index 1430e4c95c3b..9c6fffece48b 100644 --- a/Tools/test_switch2kit.py +++ b/Tools/test_switch2kit.py @@ -75,24 +75,45 @@ def test_disabled_build_has_no_dependency(self): f'include("{ROOT / "CMake/DolphinSwitch2Kit.cmake"}")\n') subprocess.run(["cmake", "-P", str(script)], check=True, capture_output=True) - def test_unsupported_builds_fail_before_dependency(self): - cases = ((False, True, True, "15.0", "requires macOS"), - (True, False, True, "15.0", "requires macOS"), - (True, True, False, "15.0", "requires macOS"), - (True, True, True, "11.0", "DEPLOYMENT_TARGET=15.0")) - for apple, sdl, qt, target, message in cases: - with self.subTest(apple=apple, sdl=sdl, qt=qt, target=target): + def test_platform_and_backend_configuration(self): + # Exercise the actual CMake guard. The probe dependency records whether + # SDK evaluation is reached; no message wording or documentation is tested. + cases = (("Linux", False, False, True, True, "11.0", False, True), + ("Windows", False, True, True, True, "11.0", False, True), + ("Darwin", True, False, True, True, "15.0", False, True), + ("FreeBSD", False, False, True, True, "15.0", False, False), + ("Linux", False, False, False, True, "15.0", False, False), + ("Linux", False, False, True, False, "15.0", False, False), + ("Darwin", True, False, True, True, "11.0", False, False), + ("Linux", False, False, True, True, "15.0", True, False)) + for system, apple, windows, sdl, qt, target, android, accepted in cases: + with self.subTest(system=system, sdl=sdl, qt=qt, target=target, android=android): with tempfile.TemporaryDirectory() as directory: - script = Path(directory) / "guard.cmake" - values = {"ENABLE_SWITCH2KIT": "ON", "APPLE": int(apple), + root = Path(directory) + sdk = root / "Externals/Switch2Kit/Integrations/SDL3" + sdk.mkdir(parents=True) + (sdk / "probe.cpp").write_text("int probe;\n") + marker = root / "sdk-entered" + (sdk / "CMakeLists.txt").write_text( + 'add_library(inputcommon STATIC probe.cpp)\n' + 'add_library(Switch2Kit::SDL3 INTERFACE IMPORTED GLOBAL)\n' + f'file(WRITE "{marker.as_posix()}" "entered")\n') + source = root / "ControllerInterface/SDL" + source.mkdir(parents=True) + (source / "Switch2Kit.cpp").write_text("int host;\n") + (source / "Switch2Kit.h").write_text("#pragma once\n") + values = {"ENABLE_SWITCH2KIT": "ON", "APPLE": int(apple), "WIN32": int(windows), + "CMAKE_SYSTEM_NAME": system, "ANDROID": int(android), "ENABLE_SDL": int(sdl), "ENABLE_QT": int(qt), "CMAKE_OSX_DEPLOYMENT_TARGET": target} - script.write_text("cmake_minimum_required(VERSION 3.25)\n" + "".join(f"set({k} {v})\n" for k, v in values.items()) + - f'include("{ROOT / "CMake/DolphinSwitch2Kit.cmake"}")\n') - result = subprocess.run(["cmake", "-P", str(script)], + (root / "CMakeLists.txt").write_text( + "cmake_minimum_required(VERSION 3.25)\nproject(Guard LANGUAGES CXX)\n" + + "".join(f"set({key} {value})\n" for key, value in values.items()) + + f'include("{(ROOT / "CMake/DolphinSwitch2Kit.cmake").as_posix()}")\n') + result = subprocess.run(["cmake", "-S", str(root), "-B", str(root / "build")], capture_output=True, text=True) - self.assertNotEqual(result.returncode, 0) - self.assertIn(message, result.stderr) + self.assertEqual(marker.exists(), accepted, result.stdout + result.stderr) + self.assertEqual(result.returncode == 0, accepted, result.stdout + result.stderr) def test_ordered_lifecycle_and_bundle(self): backend = self.read("Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp") diff --git a/Tools/test_switch2kit_autoconnect.py b/Tools/test_switch2kit_autoconnect.py index 45e9813977b9..394e2e92e6db 100644 --- a/Tools/test_switch2kit_autoconnect.py +++ b/Tools/test_switch2kit_autoconnect.py @@ -47,14 +47,11 @@ def test_polling_and_initialization_never_start_bluetooth(self): self.assertIn("s_auto_start_pending = false;", stop) self.assertIn("s_started = false;", stop) - def test_ci_retains_regressions_and_documented_sdk_pin(self): + def test_ci_retains_regressions(self): workflow = self.read(".github/workflows/native-switch2kit.yml") for script in ("test_switch2kit.py", "test_switch2kit_host.py --sanitize", "test_switch2kit_mapping.py --sanitize", "test_switch2kit_autoconnect.py"): self.assertIn(script, workflow) - sdk_pin = workflow.split('rev-parse HEAD)" = ', 1)[1].splitlines()[0] - self.assertEqual(len(sdk_pin), 40) - self.assertIn(f"`{sdk_pin}`", self.read("Docs/Switch2Kit.md")) if __name__ == "__main__": From 0546995f2216dc6138be563c0c1adb3403cc4ae0 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 15:55:25 -0400 Subject: [PATCH 04/15] Build and package native Switch2Kit on Windows x64 Pin the Windows-capable SDK, stage the controller DLL with each desktop executable, add a PowerShell build-and-run helper and a full Windows application workflow with relocation and normal launch/quit/relaunch checks. Remove the checkbox-label assertion; compare the SDK checkout to its actual gitlink rather than duplicating a hash in CI. --- .github/workflows/native-switch2kit.yml | 2 +- .github/workflows/switch2kit-windows.yml | 63 ++++++++++++++++++++++++ Externals/Switch2Kit | 2 +- Source/Core/CMakeLists.txt | 19 +++++++ Tools/build-switch2kit-windows.ps1 | 38 ++++++++++++++ Tools/test_switch2kit_autoconnect.py | 1 - Tools/test_switch2kit_windows_launch.ps1 | 32 ++++++++++++ 7 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/switch2kit-windows.yml create mode 100644 Tools/build-switch2kit-windows.ps1 mode change 100644 => 100755 Tools/test_switch2kit_autoconnect.py create mode 100644 Tools/test_switch2kit_windows_launch.ps1 diff --git a/.github/workflows/native-switch2kit.yml b/.github/workflows/native-switch2kit.yml index 75fb27e17350..c43513ab0774 100644 --- a/.github/workflows/native-switch2kit.yml +++ b/.github/workflows/native-switch2kit.yml @@ -77,7 +77,7 @@ jobs: run: brew install cmake ninja nasm automake libtool qt@6 - name: Check source pin and integration run: | - test "$(git -C Externals/Switch2Kit rev-parse HEAD)" = a9d43b1f63d94f8844755a510bf6ecc876bc8c51 + test "$(git -C Externals/Switch2Kit rev-parse HEAD)" = "$(git ls-tree HEAD Externals/Switch2Kit | awk '{print $3}')" python3 Tools/test_switch2kit.py python3 Tools/test_switch2kit_autoconnect.py python3 Tools/test_switch2kit_host.py --sanitize diff --git a/.github/workflows/switch2kit-windows.yml b/.github/workflows/switch2kit-windows.yml new file mode 100644 index 000000000000..329c4a7ed980 --- /dev/null +++ b/.github/workflows/switch2kit-windows.yml @@ -0,0 +1,63 @@ +name: Switch2Kit Windows application +on: + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: switch2kit-windows-${{ github.ref }} + cancel-in-progress: true +jobs: + windows: + runs-on: windows-2022 + timeout-minutes: 120 + env: + VCPKG_MAX_CONCURRENCY: 3 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + submodules: recursive + - uses: compnerd/gha-setup-swift@397094e75494a93fa8d81db0268dbc8f5d6cf7c6 + with: + swift-version: swift-6.2.1-release + swift-build: 6.2.1-RELEASE + - name: Build the complete controller-enabled application + shell: pwsh + run: | + & ./Tools/build-switch2kit-windows.ps1 2>&1 | Tee-Object windows-build.log + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Stage and launch the application away from the build tree + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $stage = Join-Path $env:RUNNER_TEMP 'Dolphin-Switch2Kit-windows-x86_64' + New-Item -ItemType Directory -Path $stage | Out-Null + # Robocopy follows directory junctions and copies linked resources. + robocopy 'build-switch2kit-windows/Binaries' $stage /E /NFL /NDL /NJH /NJS + if ($LASTEXITCODE -ge 8) { throw 'Application staging failed.' } + $global:LASTEXITCODE = 0 + # A fresh portable test profile suppresses the first-run analytics question. + New-Item -ItemType Directory -Path "$stage/User/Config" -Force | Out-Null + Set-Content "$stage/portable.txt" '' + Set-Content "$stage/User/Config/Dolphin.ini" "[Analytics]`nEnabled = False`nPermissionAsked = True`n[AutoUpdate]`nUpdateTrack =" + Move-Item build-switch2kit-windows "$env:RUNNER_TEMP/disabled-build-tree" + & ./Tools/test_switch2kit_windows_launch.ps1 -Executable "$stage/Dolphin.exe" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Compress-Archive -Path $stage -DestinationPath 'Dolphin-Switch2Kit-windows-x86_64.zip' + - name: Controller-enabled development application + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-Switch2Kit-windows-x86_64 + path: Dolphin-Switch2Kit-windows-x86_64.zip + retention-days: 14 + if-no-files-found: error + - name: Native diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-Switch2Kit-windows-diagnostics + path: | + windows-*.log + windows-launch.json + retention-days: 7 diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index a9d43b1f63d9..3a4490678b72 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit a9d43b1f63d94f8844755a510bf6ecc876bc8c51 +Subproject commit 3a4490678b721e41b4de6c7a2037da73a9ba5609 diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index ff7599b66363..e6038897cef8 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -25,6 +25,25 @@ if(ENABLE_SWITCH2KIT) DolphinQt/Config/Mapping/Switch2KitMapping.cpp DolphinQt/Config/Mapping/Switch2KitMapping.h) endif() + if(WIN32) + # The executables live in child directories and share one runtime directory. + # Stage the native DLL once before linking any of them (not a global install). + add_custom_target(switch2kit-windows-runtime + COMMAND "${CMAKE_COMMAND}" -E make_directory "$" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$" + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/Switch2KitNotices" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/Externals/Switch2Kit/CREDITS.md" + "$/Switch2KitNotices/CREDITS.md" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/Externals/Switch2Kit/LICENSES" + "$/Switch2KitNotices/LICENSES" + DEPENDS Switch2KitCBuild VERBATIM) + foreach(_host dolphin-emu dolphin-nogui dolphin-tool) + if(TARGET "${_host}") + add_dependencies("${_host}" switch2kit-windows-runtime) + endif() + endforeach() + endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") foreach(_host dolphin-emu dolphin-nogui dolphin-tool) if(TARGET "${_host}") diff --git a/Tools/build-switch2kit-windows.ps1 b/Tools/build-switch2kit-windows.ps1 new file mode 100644 index 000000000000..ccdf0a453545 --- /dev/null +++ b/Tools/build-switch2kit-windows.ps1 @@ -0,0 +1,38 @@ +param([switch]$Run) +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +Set-Location (Join-Path $PSScriptRoot '..') +if (-not [Environment]::Is64BitOperatingSystem -or -not [Environment]::Is64BitProcess) { + throw 'Use 64-bit PowerShell on x64 Windows.' +} +foreach ($tool in @('git', 'cmake', 'ninja', 'swift', 'swiftc')) { + if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "Missing build tool: $tool" } +} +$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +if (-not (Test-Path $vswhere)) { throw 'Install Visual Studio 2022 Desktop development with C++ and a Windows SDK.' } +$vs = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if (-not $vs) { throw 'Visual C++ x64 tools were not found.' } +cmd /c "`"$vs\Common7\Tools\VsDevCmd.bat`" -arch=x64 -host_arch=x64 >nul && set" | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($Matches[1], $Matches[2], 'Process') } +} +if ($LASTEXITCODE -ne 0) { throw 'Could not initialize the Visual C++ environment.' } +$target = swiftc -print-target-info | ConvertFrom-Json +if ($LASTEXITCODE -ne 0 -or $target.target.triple -notmatch '^x86_64-.*windows-msvc$') { + throw 'Install the native x64 Swift toolchain; ARM64 and cross-compilation are not supported here.' +} +# Keep runtime lookup local to this process and its launched application. +$env:PATH = (($target.paths.runtimeLibraryPaths | Where-Object { Test-Path $_ }) -join ';') + ';' + $env:PATH +git submodule update --init --recursive +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +cmake -S . -B build-switch2kit-windows -G Ninja -DCMAKE_BUILD_TYPE=Release ` + -DENABLE_SWITCH2KIT=ON -DENABLE_SDL=ON -DENABLE_QT=ON -DUSE_SYSTEM_SDL3=OFF ` + -DENABLE_TESTS=OFF -DENABLE_CLI_TOOL=OFF -DENABLE_AUTOUPDATE=OFF +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +cmake --build build-switch2kit-windows --target dolphin-emu --parallel 3 +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +$app = Join-Path $PWD 'build-switch2kit-windows/Binaries/Dolphin.exe' +if (-not (Test-Path $app) -or -not (Test-Path (Join-Path (Split-Path $app) 'Switch2KitC.dll'))) { + throw 'The controller-enabled application or its native DLL is missing.' +} +Write-Host "Built: $app" +if ($Run) { Start-Process -FilePath $app -WorkingDirectory (Split-Path $app) } diff --git a/Tools/test_switch2kit_autoconnect.py b/Tools/test_switch2kit_autoconnect.py old mode 100644 new mode 100755 index 394e2e92e6db..0091c5869c55 --- a/Tools/test_switch2kit_autoconnect.py +++ b/Tools/test_switch2kit_autoconnect.py @@ -24,7 +24,6 @@ def test_startup_is_guarded_once_and_after_window_initialization(self): def test_checkbox_updates_do_not_start_or_remap_controllers(self): pane = self.read("Source/Core/DolphinQt/Config/ControllersPane.cpp") - self.assertIn('tr("Automatically connect Switch 2 controllers")', pane) self.assertIn("&QCheckBox::toggled", pane) self.assertIn("SetSwitch2KitAutoConnect(enabled)", pane) self.assertNotIn("StartSwitch2KitAutoConnect", pane) diff --git a/Tools/test_switch2kit_windows_launch.ps1 b/Tools/test_switch2kit_windows_launch.ps1 new file mode 100644 index 000000000000..e7f25b3549cd --- /dev/null +++ b/Tools/test_switch2kit_windows_launch.ps1 @@ -0,0 +1,32 @@ +param([Parameter(Mandatory=$true)][string]$Executable) +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$exe = (Resolve-Path $Executable).Path +$directory = Split-Path $exe +$expected = (Resolve-Path (Join-Path $directory 'Switch2KitC.dll')).Path +$report = @() +for ($attempt = 1; $attempt -le 2; $attempt++) { + $process = Start-Process -FilePath $exe -WorkingDirectory $directory -PassThru + try { + $deadline = [DateTime]::UtcNow.AddSeconds(60) + do { + Start-Sleep -Milliseconds 250 + $process.Refresh() + if ($process.HasExited) { throw "Application exited before opening a window: $($process.ExitCode)" } + } until ($process.MainWindowHandle -ne 0 -or [DateTime]::UtcNow -gt $deadline) + if ($process.MainWindowHandle -eq 0) { throw 'No application window appeared within 60 seconds.' } + $loaded = @($process.Modules | Where-Object { $_.ModuleName -eq 'Switch2KitC.dll' }) + if ($loaded.Count -ne 1 -or $loaded[0].FileName -ne $expected) { + throw 'The running application did not load its own Switch2Kit DLL.' + } + if (-not $process.CloseMainWindow()) { throw 'The application rejected a normal close request.' } + if (-not $process.WaitForExit(20000)) { throw 'The application did not shut down normally.' } + if ($process.ExitCode -ne 0) { throw "Application failed during shutdown: $($process.ExitCode)" } + $report += @{ attempt=$attempt; visibleWindow=$true; localControllerDLL=$true; exitCode=$process.ExitCode } + } finally { + if (-not $process.HasExited) { $process.Kill(); $process.WaitForExit() } + $process.Dispose() + } +} +$report | ConvertTo-Json | Set-Content windows-launch.json +Write-Host 'PASS relocated application launch, local DLL loading, normal quit and relaunch (no physical controller).' From 6f1ec6cdd77985db86a2801df4539f11285b9c57 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 16:06:54 -0400 Subject: [PATCH 05/15] Use the Visual Studio 2026 runner required by upstream Dolphin Keep Dolphin's compiler guard intact; Windows 2022 supplies an older MSVC toolset. Remove CI-only portable settings from the archive after exercising the unchanged application binaries. --- .github/workflows/switch2kit-windows.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/switch2kit-windows.yml b/.github/workflows/switch2kit-windows.yml index 329c4a7ed980..fb51cad2de88 100644 --- a/.github/workflows/switch2kit-windows.yml +++ b/.github/workflows/switch2kit-windows.yml @@ -9,7 +9,7 @@ concurrency: cancel-in-progress: true jobs: windows: - runs-on: windows-2022 + runs-on: windows-2025-vs2026 timeout-minutes: 120 env: VCPKG_MAX_CONCURRENCY: 3 @@ -44,6 +44,8 @@ jobs: Move-Item build-switch2kit-windows "$env:RUNNER_TEMP/disabled-build-tree" & ./Tools/test_switch2kit_windows_launch.ps1 -Executable "$stage/Dolphin.exe" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Remove-Item "$stage/User" -Recurse -Force + Remove-Item "$stage/portable.txt" Compress-Archive -Path $stage -DestinationPath 'Dolphin-Switch2Kit-windows-x86_64.zip' - name: Controller-enabled development application uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 From 3c559ab0410beb13858b435591c8ca83b4dcd129 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 17:26:03 -0400 Subject: [PATCH 06/15] Fix Windows Switch2Kit toolchain compatibility with Visual Studio 2026 Pin Windows CI to Swift 6.3.3 (LLVM 21) instead of Swift 6.2.1's Clang 19, which the selected VS 2026 STL rejects with STL1000. Retain Dolphin's MSVC guard and the existing native application/relocation checks. Select Visual Studio 2026 and one Swift installation explicitly in the build helper, reject obsolete Swift versions early, and build the actual Swift/C++ WinRT library before the rest of Dolphin. Update desktop toolchain and runtime setup documentation. Local validation: eight integration tests (including production CMake admission cases), four autoconnect wiring tests, and production host/mapping suites with both Clang and GCC under ASan/UBSan. Native Windows qualification remains the responsibility of the full CI build and launch checks. --- .github/workflows/switch2kit-windows.yml | 5 +-- Docs/Switch2Kit.md | 43 +++++++++++++++++++++--- Tools/build-switch2kit-windows.ps1 | 40 +++++++++++++++++----- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/.github/workflows/switch2kit-windows.yml b/.github/workflows/switch2kit-windows.yml index fb51cad2de88..c9318092023f 100644 --- a/.github/workflows/switch2kit-windows.yml +++ b/.github/workflows/switch2kit-windows.yml @@ -20,8 +20,9 @@ jobs: submodules: recursive - uses: compnerd/gha-setup-swift@397094e75494a93fa8d81db0268dbc8f5d6cf7c6 with: - swift-version: swift-6.2.1-release - swift-build: 6.2.1-RELEASE + # Swift 6.2 ships Clang 19, which the VS 2026 STL rejects. + swift-version: swift-6.3.3-release + swift-build: 6.3.3-RELEASE - name: Build the complete controller-enabled application shell: pwsh run: | diff --git a/Docs/Switch2Kit.md b/Docs/Switch2Kit.md index 55e11d8bd598..73ef5cae0481 100644 --- a/Docs/Switch2Kit.md +++ b/Docs/Switch2Kit.md @@ -1,4 +1,4 @@ -# Native Switch 2 controller input on macOS +# Native Switch 2 controller input This optional backend embeds [Switch2Kit](https://github.com/jmonster/Switch2Kit) inside Dolphin. Bluetooth discovery and input belong to Dolphin; no standalone @@ -80,9 +80,13 @@ selected device. No controller identifiers are written to diagnostic messages. ## Building The backend is OFF by default. Disabled builds do not require Swift and retain -Dolphin's existing macOS deployment target and other platforms. Enabled builds -require macOS 15+, Xcode 26+ with Swift 6.2+, and Dolphin's normal build dependencies. -The submodule pins Switch2Kit to `a9d43b1f63d94f8844755a510bf6ecc876bc8c51`. +Dolphin's existing platform requirements. Enabled desktop builds require SDL and +Qt, plus the platform-specific toolchain below. Initialize the submodules at the +revisions recorded in this branch; do not substitute a moving SDK branch. + +### macOS + +Use macOS 15+, Xcode 26+ with Swift 6.2+, and Dolphin's normal build dependencies. ```sh git submodule update --init --recursive @@ -102,6 +106,37 @@ identity, entitlement, Gatekeeper setting, or notarization claim is added. The **Native Switch2Kit** workflow builds separate arm64/x86_64 application ZIPs for this fork. A workflow artifact is a development build, not a notarized release. +### Linux + +Use Swift 6.2 or newer, CMake, Ninja, Qt 6 development packages, and Dolphin's +normal Linux build dependencies. `Tools/build-switch2kit-linux.sh --run` builds, +installs, and launches the controller-enabled application. The Linux workflow +records the exact dependency installation and checks the relocated installation. + +### Windows x64 + +Use the latest Visual Studio 2026 **Desktop development with C++** tools, Windows +SDK 10.0.22621 or newer, CMake, Ninja, Git, and native x64 Swift **6.3 or newer**. +CI pins Swift 6.3.3. Run from 64-bit PowerShell: + +```powershell +./Tools/build-switch2kit-windows.ps1 -Run +``` + +Swift 6.2's bundled Clang 19 is incompatible with the Visual Studio 2026 standard +library headers. An unrelated newer `clang` on PATH does not update SwiftPM's +compiler. Keep Dolphin's Visual Studio compiler guard and the Microsoft STL +version checks enabled; select a compatible Swift installation instead. + +The helper selects Visual Studio 2026, passes one Swift installation explicitly +to CMake, and builds the actual Switch2Kit C/WinRT library before Dolphin. It +stages `Switch2KitC.dll` and its notices beside `Dolphin.exe`. The matching Swift +runtime must remain installed: the Windows artifact is a development build, not +a self-contained installer. Runtime PATH changes in the helper are process-local. +The Windows workflow relocates the application and checks its window, local +controller DLL, normal quit, and relaunch. These checks do not validate physical +Bluetooth, pairing, rumble, or gameplay. + ## Scope and validation GameCube and Pro Controller 2 receive recommended mappings. Individual Joy-Con 2 diff --git a/Tools/build-switch2kit-windows.ps1 b/Tools/build-switch2kit-windows.ps1 index ccdf0a453545..46eaf724b1d4 100644 --- a/Tools/build-switch2kit-windows.ps1 +++ b/Tools/build-switch2kit-windows.ps1 @@ -2,31 +2,55 @@ param([switch]$Run) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest Set-Location (Join-Path $PSScriptRoot '..') -if (-not [Environment]::Is64BitOperatingSystem -or -not [Environment]::Is64BitProcess) { +if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or + -not [Environment]::Is64BitOperatingSystem -or -not [Environment]::Is64BitProcess) { throw 'Use 64-bit PowerShell on x64 Windows.' } -foreach ($tool in @('git', 'cmake', 'ninja', 'swift', 'swiftc')) { +foreach ($tool in @('git', 'cmake', 'ninja', 'swift')) { if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "Missing build tool: $tool" } } +# Resolve one Swift installation before VsDevCmd changes PATH. Pass the same +# executables to CMake, including when reusing a previously configured build. +$swift = (Get-Command swift -CommandType Application).Source +$swiftc = Join-Path (Split-Path $swift) 'swiftc.exe' +if (-not (Test-Path $swiftc)) { throw 'The selected Swift installation has no swiftc.exe.' } +$swiftVersion = & $swift --version +if ($LASTEXITCODE -ne 0) { throw 'Could not query the Swift toolchain version.' } +Write-Output ($swiftVersion -join "`n") +if (($swiftVersion -join "`n") -notmatch 'Swift version (\d+\.\d+(?:\.\d+)?)' -or + [version]$Matches[1] -lt [version]'6.3') { + throw 'Windows Dolphin requires Swift 6.3 or newer (CI: 6.3.3). Swift 6.2 bundles Clang 19, which the Visual Studio 2026 STL rejects.' +} $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -if (-not (Test-Path $vswhere)) { throw 'Install Visual Studio 2022 Desktop development with C++ and a Windows SDK.' } -$vs = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath -if (-not $vs) { throw 'Visual C++ x64 tools were not found.' } +if (-not (Test-Path $vswhere)) { throw 'Install Visual Studio 2026 Desktop development with C++ and a Windows SDK.' } +$vs = & $vswhere -latest -products '*' -version '[18.0,19.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if ($LASTEXITCODE -ne 0 -or -not $vs) { throw 'Install the latest Visual Studio 2026 C++ x64 tools and Windows 11 SDK (22621 or newer).' } cmd /c "`"$vs\Common7\Tools\VsDevCmd.bat`" -arch=x64 -host_arch=x64 >nul && set" | ForEach-Object { if ($_ -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($Matches[1], $Matches[2], 'Process') } } if ($LASTEXITCODE -ne 0) { throw 'Could not initialize the Visual C++ environment.' } -$target = swiftc -print-target-info | ConvertFrom-Json -if ($LASTEXITCODE -ne 0 -or $target.target.triple -notmatch '^x86_64-.*windows-msvc$') { +$targetInfo = & $swiftc -print-target-info +if ($LASTEXITCODE -ne 0) { throw 'Could not query the Swift target.' } +$target = $targetInfo | ConvertFrom-Json +if ($target.target.triple -notmatch '^x86_64-.*windows-msvc$') { throw 'Install the native x64 Swift toolchain; ARM64 and cross-compilation are not supported here.' } +Write-Output "Visual Studio: $vs" +Write-Output "Swift target: $($target.target.triple)" # Keep runtime lookup local to this process and its launched application. $env:PATH = (($target.paths.runtimeLibraryPaths | Where-Object { Test-Path $_ }) -join ';') + ';' + $env:PATH git submodule update --init --recursive if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } cmake -S . -B build-switch2kit-windows -G Ninja -DCMAKE_BUILD_TYPE=Release ` -DENABLE_SWITCH2KIT=ON -DENABLE_SDL=ON -DENABLE_QT=ON -DUSE_SYSTEM_SDL3=OFF ` - -DENABLE_TESTS=OFF -DENABLE_CLI_TOOL=OFF -DENABLE_AUTOUPDATE=OFF + -DENABLE_TESTS=OFF -DENABLE_CLI_TOOL=OFF -DENABLE_AUTOUPDATE=OFF ` + "-DSWITCH2KIT_SWIFT=$swift" "-DSWITCH2KIT_SWIFTC=$swiftc" +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +# Compile the real Swift/C++ WinRT library first. Fail on toolchain/SDK +# incompatibility before spending time on Dolphin's other dependencies; do not +# bypass the Microsoft STL or Dolphin compiler guards. This target is reused by +# the application build, with the same SwiftPM configuration and scratch path. +cmake --build build-switch2kit-windows --target Switch2KitCBuild --parallel 3 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } cmake --build build-switch2kit-windows --target dolphin-emu --parallel 3 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } From 4924dd5c6d0904784c6afecd55f2b92fab1418da Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 17:44:28 -0400 Subject: [PATCH 07/15] Fix the Windows SDL adapter capacity conversion and regress its ABI width The Swift 6.3.3 run successfully built Switch2KitC.dll, then MSVC /W4 /WX rejected the adapter's implicit size_t-to-uint32_t event capacity. Advance only to a dedicated one-file SDK fix based directly on the previous pin; no unrelated SDK changes. Compile the real adapter with a test-only deleted overload enforcing uint32_t at that call boundary on 64-bit hosts. The regression fails on the old source and passes on the fixed source with both Clang and GCC. Run both compilers in wiring CI while retaining native Windows application, relocation, and launch checks. --- .github/workflows/native-switch2kit.yml | 5 +++ Externals/Switch2Kit | 2 +- Tools/test_switch2kit_capacity.py | 48 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Tools/test_switch2kit_capacity.py diff --git a/.github/workflows/native-switch2kit.yml b/.github/workflows/native-switch2kit.yml index c43513ab0774..fe9a16e536fd 100644 --- a/.github/workflows/native-switch2kit.yml +++ b/.github/workflows/native-switch2kit.yml @@ -23,6 +23,11 @@ jobs: Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp \ Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp \ Source/Core/DolphinQt/Config/Mapping/Switch2KitMapping.cpp + - name: Check exact-width SDL adapter capacity + run: | + git submodule update --init Externals/Switch2Kit Externals/SDL/SDL + CXX=clang++ python3 Tools/test_switch2kit_capacity.py + CXX=g++ python3 Tools/test_switch2kit_capacity.py - run: python3 Tools/test_switch2kit.py - run: python3 Tools/test_switch2kit_autoconnect.py - run: python3 Tools/test_switch2kit_mapping.py --sanitize diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index 3a4490678b72..9b2e7dc2e299 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit 3a4490678b721e41b4de6c7a2037da73a9ba5609 +Subproject commit 9b2e7dc2e29943222de4e3dfbd614a7793cccd49 diff --git a/Tools/test_switch2kit_capacity.py b/Tools/test_switch2kit_capacity.py new file mode 100644 index 000000000000..a269dc65fc3a --- /dev/null +++ b/Tools/test_switch2kit_capacity.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Compile the production SDL adapter with an exact-width C ABI capacity check. + +The test-only deleted overload rejects size_t capacities on 64-bit hosts even +when Clang/GCC constant-fold array::size() and do not diagnose MSVC's C4267. +No controller engine, SDL implementation, or application code is replaced. +""" +import os +from pathlib import Path +import shutil +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[1] + + +def main(): + compiler = os.environ.get('CXX') or shutil.which('clang++') or shutil.which('g++') + if not compiler: + raise SystemExit('A C++20 Clang or GCC compiler is required') + sdk = ROOT / 'Externals/Switch2Kit' + source = sdk / 'Integrations/SDL3/Switch2KitSDL3.cpp' + sdl = ROOT / 'Externals/SDL/SDL/include' + if not source.is_file() or not (sdl / 'SDL3/SDL.h').is_file(): + raise SystemExit('Initialize the pinned Switch2Kit and SDL submodules first') + with tempfile.TemporaryDirectory(prefix='s2k-capacity-') as directory: + header = Path(directory) / 'ExactCapacity.hpp' + header.write_text('''#include +#include +#include +#include "Switch2KitC.h" +static_assert(sizeof(std::size_t) > sizeof(uint32_t), "This regression requires a 64-bit host"); +// A capacity with any other type must not silently narrow at the ABI boundary. +template , int> = 0> +S2KResult s2k_read(S2KContext*, S2KEvent*, Capacity, uint32_t, uint32_t*, + S2KSnapshot*, uint32_t, uint32_t*) = delete; +''') + command = [compiler, '-std=c++20', '-Wall', '-Wextra', '-Werror', + '-fsyntax-only', '-include', str(header), + '-I' + str(sdk / 'Sources/Switch2KitCABI/include'), + '-I' + str(sdl), str(source)] + subprocess.run(command, check=True, timeout=60) + print('Production SDL adapter uses an exact uint32_t event capacity') + + +if __name__ == '__main__': + main() From 4ca276609e669042a077a623c5d8a7de29b5719a Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 21:30:13 -0400 Subject: [PATCH 08/15] Fix Windows Switch2Kit CI compiler mismatch (#6) * Fix Switch2Kit Windows builds selecting incompatible VS 2026 tools * Run pinned Swift 6.2.1 Windows CI on compatible VS 2022 image * Guard Windows Swift/MSVC compatibility without weakening native CI coverage --- Tools/test_switch2kit.py | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Tools/test_switch2kit.py b/Tools/test_switch2kit.py index 9c6fffece48b..b0a913498dc1 100644 --- a/Tools/test_switch2kit.py +++ b/Tools/test_switch2kit.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later """Static integration regressions; not a Swift, Bluetooth, or gameplay test.""" import configparser +import re from pathlib import Path import subprocess import tempfile @@ -115,6 +116,51 @@ def test_platform_and_backend_configuration(self): self.assertEqual(marker.exists(), accepted, result.stdout + result.stderr) self.assertEqual(result.returncode == 0, accepted, result.stdout + result.stderr) + def test_windows_ci_uses_dolphin_compatible_toolchain(self): + workflow = self.read(".github/workflows/switch2kit-windows.yml") + # Dolphin requires VS 2026; downgrading to VS 2022 only moves the + # failure from Swift's C++ headers to Dolphin's compiler guard. + self.assertRegex(workflow, r"(?m)^\s+runs-on: windows-2025-vs2026\s*$") + version = re.search(r"(?m)^\s+swift-version: swift-(\d+\.\d+(?:\.\d+)?)-release\s*$", + workflow) + self.assertIsNotNone(version, "Pin a released Swift toolchain") + # Swift 6.2 ships Clang 19, which the VS 2026 STL rejects. Allow + # patch updates without freezing an obsolete compiler in this test. + self.assertGreaterEqual(tuple(map(int, version[1].split("."))), (6, 3)) + self.assertRegex(workflow, r"(?m)^\s+swift-build: " + re.escape(version[1]) + + r"-RELEASE\s*$") + + def test_windows_build_selects_compatible_visual_studio_and_swift(self): + script = self.read("Tools/build-switch2kit-windows.ps1") + selection = next(line for line in script.splitlines() + if line.startswith("$vs = & $vswhere ")) + self.assertIn("-version '[18.0,19.0)'", selection) + self.assertIn("-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64", selection) + self.assertIn("if ($LASTEXITCODE -ne 0 -or -not $vs) { throw", script) + self.assertLess(script.index(selection), script.index("VsDevCmd.bat")) + self.assertIn("[version]$Matches[1] -lt [version]'6.3'", script) + self.assertLess(script.index("$swift = (Get-Command swift"), script.index("VsDevCmd.bat")) + self.assertIn('"-DSWITCH2KIT_SWIFT=$swift" "-DSWITCH2KIT_SWIFTC=$swiftc"', script) + + def test_windows_fix_keeps_native_build_and_launch_required(self): + script = self.read("Tools/build-switch2kit-windows.ps1") + workflow = self.read(".github/workflows/switch2kit-windows.yml") + self.assertIn("-DENABLE_SWITCH2KIT=ON", script) + self.assertLess(script.index("--target Switch2KitCBuild"), + script.index("--target dolphin-emu")) + # Both native targets must propagate failures, not merely appear in + # comments or optional steps. The staged application test stays required. + for target in ("Switch2KitCBuild", "dolphin-emu"): + self.assertRegex(script, r"(?m)^cmake --build [^\n]+ --target " + target + + r"[^\n]*\nif \(\$LASTEXITCODE -ne 0\) \{ exit \$LASTEXITCODE \}") + self.assertIn("Switch2KitC.dll", script) + self.assertIn("./Tools/build-switch2kit-windows.ps1", workflow) + self.assertIn("./Tools/test_switch2kit_windows_launch.ps1", workflow) + self.assertLess(workflow.index("Move-Item build-switch2kit-windows"), + workflow.index("./Tools/test_switch2kit_windows_launch.ps1")) + self.assertNotIn("continue-on-error", workflow) + self.assertNotIn("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH", script + workflow) + def test_ordered_lifecycle_and_bundle(self): backend = self.read("Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp") self.assertLess(backend.index("ShutdownSwitch2Kit();"), backend.index("SDL_Quit();")) From 9932e2aa632752bd149939e0f09a7cb3627a88af Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 21:37:10 -0400 Subject: [PATCH 09/15] Pin the native Windows runtime repair validated in Switch2Kit PR 75 Advance only Externals/Switch2Kit to immutable commit 06bc206047e9b5c9d3f07940e9a7ce29a7de366d. Both Windows Swift 6.2.1 and 6.3.3 passed real C/SDL consumers and extracted packages at this SDK revision, including OS-only PATH, isolated profiles and missing-DLL negative controls. The same revision passed Linux BlueZ/native hosts and macOS runtime checks. The previous divergent 9b2e7dc SDK fix was reviewed rather than discarded blindly. The current SDK passes Tools/test_switch2kit_capacity.py against the real adapter and pinned SDL headers; its unsigned ABI capacity constant preserves the MSVC narrowing fix. Preserve the newly landed 4ca2766 toolchain regression checks and all other host work unchanged. Fresh native Dolphin CI is required after this pin update. SDK consumer success is not application or physical-controller qualification. Merge the SDK with this reviewed commit retained/reachable before merging this host change; a squash/rebase requires repinning to the resulting SDK commit and revalidation. --- Externals/Switch2Kit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index 9b2e7dc2e299..06bc206047e9 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit 9b2e7dc2e29943222de4e3dfbd614a7793cccd49 +Subproject commit 06bc206047e9b5c9d3f07940e9a7ce29a7de366d From e8f8e38a52c53c06488fd4297a9a1311ea86c44f Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 22:06:40 -0400 Subject: [PATCH 10/15] Package Dolphin's actual Swift runtime and qualify extracted desktop GUIs The Windows parent target copied Switch2KitC.dll alone, never invoking the SDK runtime packager. A developer PATH could hide the incomplete application. Stage the facade's compiler-selected dependency closure and required Swift/ICU notices in the shared output directory before the C++ hosts link. Preserve SDK unresolved-dependency failures, all existing host deployment rules, macOS behavior and disabled-build isolation. Install Sys beside the Linux executable for File::GetSysDirectory's existing relocatable lookup instead of depending on a build-time absolute data directory. Add four executable native deployment-graph regressions. With the original production CMake, three fail: omitted runtime, ignored invalid runtime configuration and absent relocatable resources. With this repair all four pass, including launching three real linked fixture executables after removing the original build/compiler locations and rejecting a missing packaged runtime. These fixtures do not replace the actual Windows application. Both desktop workflows now archive first and qualify the exact extracted application through the pinned SDK GUI supervisors: isolated profile, no compiler PATH, packaged controller/Swift modules, normal window quit and relaunch. Preserve reports on failure and upload the same application archive only after success. Existing profiles are rejected from CI staging, not erased from a user configuration. Update the README and controller guide with platform-specific application artifacts, extraction/executable paths, runtime prerequisites, source fallbacks, Find/Sync and port selection, saved identity, opt-in automatic reconnection and Joy-Con/Wii-motion distinctions. Do not claim notarization or physical-controller qualification. Validation: every changed blob matches the locally reviewed files; all retained mapping/host sanitizer, auto-connect, exact-capacity and current toolchain wiring checks pass, as do four new deployment tests. YAML parses and local documentation targets resolve. Fresh native enabled/disabled/application CI is required for this commit; prior-head results are not final qualification. SDK remains pinned to 06bc206047e9b5c9d3f07940e9a7ce29a7de366d. --- .github/workflows/switch2kit-linux.yml | 13 +- .github/workflows/switch2kit-windows.yml | 16 +-- Docs/Switch2Kit.md | 173 ++++++++++------------- Readme.md | 73 +++++----- Source/Core/CMakeLists.txt | 20 ++- Tools/test_switch2kit_runtime.py | 172 ++++++++++++++++++++++ Tools/test_switch2kit_windows_launch.ps1 | 40 ++---- 7 files changed, 325 insertions(+), 182 deletions(-) create mode 100644 Tools/test_switch2kit_runtime.py diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml index 7eaa905d81ee..8752c06b451e 100644 --- a/.github/workflows/switch2kit-linux.yml +++ b/.github/workflows/switch2kit-linux.yml @@ -22,7 +22,7 @@ jobs: - name: Install native dependencies run: | apt-get update - apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus xvfb xauth + apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus xvfb xauth openbox wmctrl x11-utils git config --global --add safe.directory "$PWD" - name: Build and install the controller-enabled application shell: bash @@ -34,6 +34,7 @@ jobs: python3 Tools/test_switch2kit_mapping.py --sanitize python3 Tools/test_switch2kit_host.py --sanitize python3 Tools/test_switch2kit_autoconnect.py + python3 Tools/test_switch2kit_runtime.py - name: Relocate, resolve native dependencies and launch the installed binary shell: bash run: | @@ -45,8 +46,12 @@ jobs: ldd "$app/bin/dolphin-emu" | tee linux-dependencies.log ! grep -q 'not found' linux-dependencies.log grep -F "$app" linux-dependencies.log | grep libSwitch2KitC - xvfb-run -a "$app/bin/dolphin-emu" --version tar -C "$RUNNER_TEMP" -czf Dolphin-Switch2Kit-linux-x86_64.tar.gz Dolphin-Switch2Kit-linux-x86_64 + mv "$app" "$RUNNER_TEMP/unavailable-staged-application" + dbus-run-session -- xvfb-run -a python3 Externals/Switch2Kit/tests/emulator-launch/linux.py dolphin \ + "$PWD/Dolphin-Switch2Kit-linux-x86_64.tar.gz" --report "$PWD/linux-launch.json" \ + --forbidden-root "$PWD" --forbidden-root "$RUNNER_TEMP/disabled-build-tree" \ + --forbidden-root "$RUNNER_TEMP/unavailable-staged-application" - name: Controller-enabled development build uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: @@ -59,5 +64,7 @@ jobs: uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: Dolphin-Switch2Kit-linux-diagnostics - path: linux-*.log + path: | + linux-*.log + linux-launch.json retention-days: 7 diff --git a/.github/workflows/switch2kit-windows.yml b/.github/workflows/switch2kit-windows.yml index c9318092023f..367ec7eb9ceb 100644 --- a/.github/workflows/switch2kit-windows.yml +++ b/.github/workflows/switch2kit-windows.yml @@ -38,16 +38,16 @@ jobs: robocopy 'build-switch2kit-windows/Binaries' $stage /E /NFL /NDL /NJH /NJS if ($LASTEXITCODE -ge 8) { throw 'Application staging failed.' } $global:LASTEXITCODE = 0 - # A fresh portable test profile suppresses the first-run analytics question. - New-Item -ItemType Directory -Path "$stage/User/Config" -Force | Out-Null - Set-Content "$stage/portable.txt" '' - Set-Content "$stage/User/Config/Dolphin.ini" "[Analytics]`nEnabled = False`nPermissionAsked = True`n[AutoUpdate]`nUpdateTrack =" + # Never distribute a developer/test profile. The supervisor seeds only + # its private extracted copy, after the distributable archive is made. + foreach ($profile in @('User', 'user', 'portable', 'portable.txt', 'settings.xml')) { + if (Test-Path (Join-Path $stage $profile)) { throw "Unexpected profile in application staging: $profile" } + } Move-Item build-switch2kit-windows "$env:RUNNER_TEMP/disabled-build-tree" - & ./Tools/test_switch2kit_windows_launch.ps1 -Executable "$stage/Dolphin.exe" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - Remove-Item "$stage/User" -Recurse -Force - Remove-Item "$stage/portable.txt" Compress-Archive -Path $stage -DestinationPath 'Dolphin-Switch2Kit-windows-x86_64.zip' + Move-Item $stage "$env:RUNNER_TEMP/unavailable-staged-application" + & ./Tools/test_switch2kit_windows_launch.ps1 -Archive 'Dolphin-Switch2Kit-windows-x86_64.zip' -ForbiddenRoot "$PWD" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Controller-enabled development application uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: diff --git a/Docs/Switch2Kit.md b/Docs/Switch2Kit.md index 73ef5cae0481..6eea50186c8f 100644 --- a/Docs/Switch2Kit.md +++ b/Docs/Switch2Kit.md @@ -1,10 +1,44 @@ -# Native Switch 2 controller input +# Native Switch 2 controllers in Dolphin -This optional backend embeds [Switch2Kit](https://github.com/jmonster/Switch2Kit) -inside Dolphin. Bluetooth discovery and input belong to Dolphin; no standalone -dashboard, UDP server, SDL dynamic-library override, Accessibility permission, or -system virtual-HID device is used. The existing SDL3 backend receives -Switch2Kit's in-process virtual gamepads. No console emulation code is changed. +**This maintained Dolphin fork embeds [Switch2Kit](https://github.com/jmonster/Switch2Kit) for NSO GameCube and Nintendo Switch 2 Pro controllers on macOS 15+, with experimental Linux x86-64 and Windows x64 support.** + +Start with the [download, extraction and launch instructions](../Readme.md#quick-start), then [connect and select a port](#playing-a-gamecube-game) below. Ordinary upstream Dolphin binaries do not contain this integration. Installing the library alone does not modify other applications. No separate dashboard, network bridge, system-wide SDL override, virtual-controller driver or Accessibility permission is required. + +## Applications and prerequisites + +Use this fork's successful **Native Switch2Kit** (macOS), **Switch2Kit Linux application** or **Switch2Kit Windows application** workflow on the implementation branch, not a source/diagnostics archive. GitHub artifact downloads require sign-in and expire (desktop application artifacts are retained for 14 days). An outer artifact ZIP contains the application ZIP or tarball. A configured job or earlier-revision pass is not proof that a particular download passed. These are development builds, not a published production release or a hardware-qualification claim. + +### macOS + +Use macOS 15 or newer on Apple Silicon (`arm64`) or Intel (`x86_64`). Download the matching **Dolphin-Switch2Kit-arm64** or **Dolphin-Switch2Kit-x86_64** artifact from [Native Switch2Kit](https://github.com/jmonster/dolphin/actions/workflows/native-switch2kit.yml), extract both ZIP layers, move `DolphinQt.app` to Applications and open it normally. The app embeds its controller library and Swift runtime. Enable Bluetooth and permit Dolphin under **System Settings > Privacy & Security > Bluetooth** when requested. Ad-hoc signing is not notarization; use Apple's [per-app approval procedure](https://support.apple.com/en-us/102445) only for an application you trust. Do not turn off Gatekeeper globally. + +### Linux + +The development target is Ubuntu 24.04 x86-64 with a graphical desktop and working graphics/audio drivers. Other distributions and architectures have not been qualified by these application jobs. The package is an installed directory, not a universal AppImage. Install the normal distribution runtimes; on Ubuntu 24.04: + +```sh +sudo apt-get update +sudo apt-get install bluez libsystemd0 libqt6widgets6t64 libqt6svg6 qt6-qpa-plugins libevdev2 libudev1 libusb-1.0-0 libasound2t64 libpulse0 libgl1 libegl1 +``` + +Use the **Dolphin-Switch2Kit-linux-x86_64** application artifact from [the Linux workflow](https://github.com/jmonster/dolphin/actions/workflows/switch2kit-linux.yml). Extract the outer ZIP, then: + +```sh +tar -xzf Dolphin-Switch2Kit-linux-x86_64.tar.gz +./Dolphin-Switch2Kit-linux-x86_64/bin/dolphin-emu +``` + +Keep `bin/Sys`, `lib` and `share/Switch2KitNotices` with the executable when relocating the directory. Swift runtime libraries are packaged; installing Swift or changing `LD_LIBRARY_PATH` is not a launch prerequisite. System libraries and graphics drivers are not bundled. + +Turn Bluetooth on in your desktop's Bluetooth settings. BlueZ must be running, the adapter must support Bluetooth LE, and your normal account must have system-bus/GATT access. `bluetoothctl show` can confirm the powered adapter. Dolphin does not change adapter power, install permissive D-Bus rules, erase bonds or invoke BlueZ Pair/RemoveDevice. Start discovery in Dolphin and hold Sync; the controller-protocol handshake is separate from OS pairing. Resolve access failures through your distribution's normal Bluetooth policy, not by running the emulator as root. + +### Windows + +The experimental desktop target is Windows 11 x64, with a Bluetooth LE adapter/driver and graphics drivers. Native build/consumer jobs use Windows Server runners and are not evidence of physical Windows 11 pairing. ARM64 and 32-bit Windows are not covered. Install the [Microsoft Visual C++ x64 runtime](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) when required by Windows or the application. + +Download **Dolphin-Switch2Kit-windows-x86_64** from [the Windows application workflow](https://github.com/jmonster/dolphin/actions/workflows/switch2kit-windows.yml). Extract the outer ZIP and its inner `Dolphin-Switch2Kit-windows-x86_64.zip`. Open `Dolphin-Switch2Kit-windows-x86_64/Dolphin.exe`. Retain all DLLs, Qt plugins, `Sys` and `Switch2KitNotices`. The package stages the selected Swift runtime as well as `Switch2KitC.dll`; no Swift compiler installation or compiler-specific PATH is required to launch it. + +Enable **Settings > Bluetooth & devices > Bluetooth**, close competing controller applications, then use Dolphin's Find/Sync procedure below. Honor any legitimate system pairing/access prompt. The backend does not silently change pairing policy or erase device bonds. Run as a normal user. Do not disable SmartScreen, antivirus or Bluetooth security to bypass an error. A missing DLL before a window appears is a package/runtime prerequisite problem, not a pairing failure: re-extract the complete controller-enabled artifact and check that revision's native launch job. ## Playing a GameCube game @@ -77,108 +111,57 @@ assignments. An unreadable/unwritable configuration or exhaustion of the 64 save slots falls back to Dolphin's ordinary device numbering; in that case verify the selected device. No controller identifiers are written to diagnostic messages. -## Building +## Controller differences and motion -The backend is OFF by default. Disabled builds do not require Swift and retain -Dolphin's existing platform requirements. Enabled desktop builds require SDL and -Qt, plus the platform-specific toolchain below. Initialize the submodules at the -revisions recorded in this branch; do not substitute a moving SDK branch. +The quick port selector supports GameCube and Pro controllers. Discover each Joy-Con 2 half using Find/Sync, then use the ordinary per-port **Configure** window and SDL device selection to assign that half's buttons and axes manually. This fork does not automatically combine the halves into one controller or create a system-wide virtual pair. -### macOS +For Wii games, choose **Emulated Wii Remote** and configure its ordinary inputs separately. The GameCube-port shortcut is not Wii motion setup. This maintained fork does not supply the SDK's separate pinned-upstream Dolphin patch's calibrated-motion setup UI. Do not assume that selecting a controller or a test calibration file gives usable Wii motion. Cemu has its own explicit measured-motion workflow; that is a different integration. + +NSO GameCube rumble uses the dedicated Bluetooth on/off motor channel, not HD waveforms or short firmware feedback clips. Pro/Joy-Con retain their HD-rumble path. Zero, disconnect and teardown stop output; stale requests cannot restart retired sessions. Check actual motor behavior on your hardware rather than treating an automated callback test as a physical result. + +## Build from source -Use macOS 15+, Xcode 26+ with Swift 6.2+, and Dolphin's normal build dependencies. +The optional backend is OFF by default; disabled builds do not require Swift or raise the platform deployment target. Enabled builds need SDL3, Qt and the platform toolchain below. Use the maintained fork's selected submodules, not the SDK's separate upstream patch scripts. While this PR is unmerged: ```sh -git submodule update --init --recursive -cmake -S . -B build-switch2kit -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 \ - -DENABLE_SWITCH2KIT=ON -DENABLE_SDL=ON -DENABLE_QT=ON \ - -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)" \ - -DPOSTPROCESS_BUNDLE=ON -cmake --build build-switch2kit --target dolphin-emu --parallel 3 +git clone --branch feature/switch2kit-desktop-platforms --recurse-submodules https://github.com/jmonster/dolphin.git dolphin-switch2kit +cd dolphin-switch2kit ``` -Select `CMAKE_OSX_ARCHITECTURES` as usual; the library integration builds matching -slices. Its C facade and required Swift runtime libraries are copied into the -application before Dolphin's ordinary relocation and signing steps. No signing -identity, entitlement, Gatekeeper setting, or notarization claim is added. -The **Native Switch2Kit** workflow builds separate arm64/x86_64 application ZIPs -for this fork. A workflow artifact is a development build, not a notarized release. +**macOS:** install Xcode 26+ with Swift 6.2+, open Xcode to finish setup and select its Command Line Tools. Then: -### Linux +```sh +brew install cmake ninja nasm automake libtool qt@6 +cmake -S . -B build-switch2kit -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 -DCMAKE_OSX_ARCHITECTURES="$(uname -m)" -DENABLE_SWITCH2KIT=ON -DENABLE_SDL=ON -DENABLE_QT=ON -DUSE_SYSTEM_SDL3=OFF -DENABLE_VULKAN=OFF -DENABLE_TESTS=OFF -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)" -DPOSTPROCESS_BUNDLE=ON +cmake --build build-switch2kit --target dolphin-emu --parallel 3 +open build-switch2kit/Binaries/DolphinQt.app +``` -Use Swift 6.2 or newer, CMake, Ninja, Qt 6 development packages, and Dolphin's -normal Linux build dependencies. `Tools/build-switch2kit-linux.sh --run` builds, -installs, and launches the controller-enabled application. The Linux workflow -records the exact dependency installation and checks the relocated installation. +**Linux:** install Swift **6.2.1** using the [official Linux installation instructions](https://www.swift.org/install/linux/), then the build dependencies used by the Ubuntu application job: -### Windows x64 +```sh +sudo apt-get install build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus +bash Tools/build-switch2kit-linux.sh --run +``` + +The helper builds, installs and opens `build-switch2kit/install/bin/dolphin-emu`. Keep the whole install prefix for later launches. `openbox`, `wmctrl`, `x11-utils`, `xvfb` and `xauth` are CI GUI-test dependencies, not a requirement for an ordinary desktop user. -Use the latest Visual Studio 2026 **Desktop development with C++** tools, Windows -SDK 10.0.22621 or newer, CMake, Ninja, Git, and native x64 Swift **6.3 or newer**. -CI pins Swift 6.3.3. Run from 64-bit PowerShell: +**Windows:** install Visual Studio **2026 Desktop development with C++**, Windows SDK 10.0.22621 or newer, Git, CMake, Ninja, Python 3, PowerShell 7 and native x64 Swift **6.3.3** following [Swift's Windows instructions](https://www.swift.org/install/windows/). Open 64-bit PowerShell in the checkout: ```powershell ./Tools/build-switch2kit-windows.ps1 -Run ``` -Swift 6.2's bundled Clang 19 is incompatible with the Visual Studio 2026 standard -library headers. An unrelated newer `clang` on PATH does not update SwiftPM's -compiler. Keep Dolphin's Visual Studio compiler guard and the Microsoft STL -version checks enabled; select a compatible Swift installation instead. - -The helper selects Visual Studio 2026, passes one Swift installation explicitly -to CMake, and builds the actual Switch2Kit C/WinRT library before Dolphin. It -stages `Switch2KitC.dll` and its notices beside `Dolphin.exe`. The matching Swift -runtime must remain installed: the Windows artifact is a development build, not -a self-contained installer. Runtime PATH changes in the helper are process-local. -The Windows workflow relocates the application and checks its window, local -controller DLL, normal quit, and relaunch. These checks do not validate physical -Bluetooth, pairing, rumble, or gameplay. - -## Scope and validation - -GameCube and Pro Controller 2 receive recommended mappings. Individual Joy-Con 2 -halves still need appropriate Dolphin bindings; this does not pair the halves or -add Wii motion integration. GameCube rumble uses its dedicated Bluetooth **on/off -motor** channel, not HD-rumble waveforms or short firmware feedback clips. Pro/Joy-Con -HD rumble retains the existing adapter implementation. Both types use cancellable -outputs and the live-input-loop renewal limit. Zero stops the motor; stale requests, -disconnection, and teardown cannot queue a later restart. - -The adapter processes bounded batches and commits each SDL transition; Dolphin -still samples state at its existing cadence. This does not guarantee a game sees -a press/release shorter than that cadence. Device loss and overflow recovery are -handled by the shared adapter. On final shutdown its SDL devices are destroyed -before the C context and SDL; asynchronous Bluetooth teardown never blocks the -main run loop. The linked Swift library remains resident for process lifetime. - -`python3 Tools/test_switch2kit.py` checks presets and build/lifecycle wiring. -`python3 Tools/test_switch2kit_mapping.py --sanitize` executes the production mapping -helper against test-only UI/configuration boundaries, including cancellation and -backup failures. These boundaries do not replace the code in application builds. -`python3 Tools/test_switch2kit_host.py --sanitize` exercises the production host -wrapper against test-only SDK/SDL/file boundaries, including automatic policy, -saved consent, failed configuration, stable IDs, start-once and explicit stop. -`python3 Tools/test_switch2kit_autoconnect.py` checks guarded UI/startup wiring. -The native workflow compiles the complete application and inspects the embedded -library and Bluetooth description. These checks do not establish physical -controller behavior, clean-Mac startup, latency, or gameplay acceptance. - -The user has confirmed input with the initial integration. The new automatic setup -and physical rumble still need acceptance on a real NSO GameCube controller: -pair/permission refusal and retry; every button and axis; light L/R travel without -a click and independent full clicks; disconnect while holding input; reconnect; -two identical controllers; app restart; normal quit; motor start/stop; and an actual game session. -Automatic recovery additionally needs a real Mac/controller test: play, pause, -leave the controller off longer than 60 seconds, resume, and power it on without -opening settings. Repeat several cycles, test Bluetooth off/on and two controllers -returning in reverse order, then verify Disconnect stays stopped and disabling -auto-connect preserves a live controller. These hardware checks have not been -performed for this change. Retain the distinction between build success and these -hardware results. - -This contribution was prepared with AI assistance. The build glue, host wrapper, -UI integration, profile, documentation and checks require human review. The -controller protocol/Swift engine is reused from the pinned Switch2Kit dependency. +The helper selects VS 2026 and one compatible Swift installation, builds the native controller library and complete Dolphin target, then opens `build-switch2kit-windows/Binaries/Dolphin.exe`. Swift 6.2's compiler is incompatible with VS 2026 STL headers; a different standalone clang on PATH does not replace Swift's compiler. Do not bypass Dolphin's compiler guard or Microsoft's STL checks. Build-time runtime PATH setup is process-local; extracted packages are tested without it. + +To update a source build, quit Dolphin, use `git pull --ff-only`, update the recorded submodules with `git submodule update --init --recursive`, and rerun the same build command. Do not erase your Dolphin user configuration. + +## Qualification and troubleshooting + +A missing **Find Switch 2 Controllers** button means the launched binary was built without this backend. A controller absent after a 60-second search warrants checking adapter power/access, Sync mode, competing connections and the reported status; retry Find after resolving the cause. Do not reinstall a dashboard or a system SDL override. A changed adapter or device address can change Linux/Windows physical identity: verify the selected player port instead of relying on a displayed ordinal. + +The native workflows build real applications. Desktop launch qualification extracts the exact uploaded archive into a new directory, uses private test profiles, verifies the loaded controller/Swift libraries come from that package, opens a GUI, requests normal quit, and relaunches. It deliberately seeds noninteractive test settings; it does not establish pristine first-use dialogs, downloaded-app approval, Bluetooth hardware or gameplay. A run is qualified only after those checks pass for its exact head. The executable runtime-wiring regression also checks missing-runtime failure, deployment-error propagation, optional-backend isolation and relocatable resources; fixture DLLs are not substituted into the application artifact. + +Retained mapping/host regressions exercise real mapping and host code against controlled UI/storage boundaries, including cancellation, backup/rollback, stable identity, saved consent, explicit stop and shutdown ordering. Protocol, rumble, calibration, bounded queues, permissions, signing and license coverage remain in the SDK/fork suites. No prose-length, heading-order or branding assertion is needed to protect these behaviors. + +Record hardware acceptance separately for each model/firmware/OS/adapter and tested commit: first pairing and denied-access retry; every press/release and stick; independent GameCube analog travel and digital clicks; correct rumble and stop; two identical controllers returning in reverse order; saved assignments after app restart; Bluetooth loss/recovery; explicit Disconnect remaining stopped; normal quit; and gameplay. For Dolphin's automatic mode, power a controller off for longer than 60 seconds and reconnect without opening settings, then test disabling automatic discovery without dropping a live controller. These changes do not claim that checklist has been physically completed. diff --git a/Readme.md b/Readme.md index 6dd2c3bc8286..12f10bfd8ba2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,69 +1,60 @@ # Dolphin - A GameCube and Wii Emulator -**This fork supports the Nintendo Switch Online GameCube controller and Nintendo Switch 2 Pro Controller on macOS through [Switch2Kit](https://github.com/jmonster/Switch2Kit).** +**This fork embeds [Switch2Kit](https://github.com/jmonster/Switch2Kit) for NSO GameCube and Nintendo Switch 2 Pro controllers on macOS 15+, with experimental Linux x86-64 and Windows x64 builds.** -Controller support is built into Dolphin. There is no separate Switch2Kit app or controller driver to install, and GameCube/Pro controller setup includes recommended mappings and rumble. +Get this controller-enabled Dolphin, connect your controller, select a GameCube port, and play. No separate Switch2Kit dashboard, network bridge, SDL override or virtual-controller driver is needed. Individual Joy-Con 2 halves remain available through manual bindings. -## Quick start (macOS 15+) +## Quick start -### Get a controller-enabled build +Use the application artifacts below from **this fork**, not upstream Dolphin binaries. Sign in to GitHub, select a successful run for `feature/switch2kit-desktop-platforms` while this PR is unmerged, and download the named application artifact. Open the run's jobs to check the build and launch results for that revision. Source and diagnostics archives are not applications. These are expiring development artifacts, not published, notarized or production-signed releases; use [Build from source](#build-from-source-alternative) when no matching artifact is available. -1. Sign in to GitHub, open this fork's [Native Switch2Kit builds](https://github.com/jmonster/dolphin/actions/workflows/native-switch2kit.yml), and select a successful run with a green check. -2. Under **Artifacts**, download **Dolphin-Switch2Kit-arm64** for an Apple Silicon Mac or **Dolphin-Switch2Kit-x86_64** for an Intel Mac. Choose the application artifact, not a diagnostics or SDK-test artifact. -3. Extract the downloaded ZIP, then extract the **Dolphin-Switch2Kit-arm64.zip** or **Dolphin-Switch2Kit-x86_64.zip** inside it. Move **DolphinQt.app** to Applications and open it. Reopen this same app for later sessions. +### macOS -Downloads currently come from GitHub Actions, not a published release. Artifacts expire; when no application download is available, use [Build from source](#build-from-source-alternative) below. Ordinary upstream Dolphin downloads do not include this Switch2Kit integration. +On macOS 15 or newer, use [Native Switch2Kit builds](https://github.com/jmonster/dolphin/actions/workflows/native-switch2kit.yml): **Dolphin-Switch2Kit-arm64** for Apple Silicon, or **Dolphin-Switch2Kit-x86_64** for Intel. Extract the downloaded artifact ZIP, then the `Dolphin-Switch2Kit-.zip` inside it. Move `DolphinQt.app` to Applications and open it. Enable Bluetooth and allow Dolphin's Bluetooth request. A denied permission can be changed under **System Settings > Privacy & Security > Bluetooth**. -These are development builds, not notarized releases. For an unverified-developer warning, use Apple's [per-app Open Anyway instructions](https://support.apple.com/en-us/102445) only when you trust the download's source. Do not disable Gatekeeper globally. +The development app is ad-hoc signed, not notarized. Use Apple's [per-app Open Anyway procedure](https://support.apple.com/en-us/102445) only for a source you trust; do not disable Gatekeeper globally. -### Connect and play +### Linux -1. Turn on your Mac's Bluetooth and close other apps managing the controller, including the Switch2Kit dashboard or Cemu. In Dolphin, open **Controllers** (Controller Settings), click **Find Switch 2 Controllers**, allow Bluetooth access, and hold the controller's **Sync** button until its player lights sweep. The search lasts 60 seconds; click Find again to retry. -2. Beside the desired **GameCube port**, choose **Switch2Kit GameCube** or **Switch2Kit Pro Controller 2** in the physical-controller dropdown. Dolphin selects **Standard Controller** and applies the button, stick, trigger, and rumble mappings automatically. This is not **GameCube Adapter for Wii U** mode. -3. Open that port's **Configure** window to check button presses and releases, sticks, and triggers, then open your GameCube game. On the NSO GameCube controller, partial L/R travel and the full-click buttons are separate inputs. Pro Controller ZL/ZR are on/off and cannot reproduce an analog squeeze. +Use Ubuntu 24.04 x86-64 with a desktop session, graphics drivers, a powered Bluetooth LE adapter, the running BlueZ service and normal system-bus access. Install the distribution runtime prerequisites listed in the [Linux guide](Docs/Switch2Kit.md#linux). Use [Switch2Kit Linux application builds](https://github.com/jmonster/dolphin/actions/workflows/switch2kit-linux.yml), artifact **Dolphin-Switch2Kit-linux-x86_64**. Extract the outer download ZIP, then run: -For automatic reconnection on later launches or after a long pause, enable **Automatically connect Switch 2 controllers** in Controller Settings and turn the controller on when you return. This option is off by default; otherwise use **Find Switch 2 Controllers** each session. **Disconnect Switch 2 Controllers** stops the current session without deleting mappings. +```sh +tar -xzf Dolphin-Switch2Kit-linux-x86_64.tar.gz +./Dolphin-Switch2Kit-linux-x86_64/bin/dolphin-emu +``` -For Wii games, configure an **Emulated Wii Remote** and its SDL device through Dolphin's normal Wii Remote settings; the GameCube-port shortcut above does not configure a Wii Remote or add calibrated Wii motion. Individual Joy-Con 2 halves also need normal manual bindings. +Keep the whole extracted directory, including `bin/Sys`, `lib` and `share/Switch2KitNotices`. The packaged Swift runtime does not require a Swift installation or `LD_LIBRARY_PATH` override. This is not a universal Linux/AppImage package; system desktop libraries and drivers are still required. -**No Find button?** Open the controller-enabled app above, not an upstream or backend-disabled build. **No controller?** Check Bluetooth access for Dolphin in **System Settings > Privacy & Security > Bluetooth**, close competing controller apps, and retry Find while holding Sync. Saved custom mappings are not replaced on reconnect; **Use Recommended Mapping** in Configure is the explicit reset-to-preset action. +### Windows -### Build from source (alternative) +Use Windows 11 x64 for the experimental desktop instructions, with Bluetooth LE enabled, its adapter driver, graphics drivers and the Microsoft Visual C++ x64 runtime. Native CI uses Windows Server runners; physical Windows 11 controller acceptance is not claimed. Use [Switch2Kit Windows application builds](https://github.com/jmonster/dolphin/actions/workflows/switch2kit-windows.yml), artifact **Dolphin-Switch2Kit-windows-x86_64**. Extract the outer artifact ZIP, then `Dolphin-Switch2Kit-windows-x86_64.zip` inside it. Open `Dolphin-Switch2Kit-windows-x86_64/Dolphin.exe`. -
-Build and launch the controller-enabled app on your Mac +Keep its DLLs, `Sys`, Qt plugins and `Switch2KitNotices` together. The package includes the selected Swift runtime; do not install Swift or add a compiler directory to PATH merely to launch it. Do not run Dolphin as administrator or disable SmartScreen/antivirus to bypass a failure. See the [Windows guide](Docs/Switch2Kit.md#windows) for prerequisites and the source fallback. -Use macOS 15+, [Xcode](https://developer.apple.com/xcode/) 26+ with Swift 6.2+, and [Homebrew](https://brew.sh/). Open Xcode once to finish setup and select it under **Xcode > Settings > Locations > Command Line Tools**. On Apple Silicon, use a native Terminal and native Homebrew, not Rosetta. +### Connect and play -Run these commands in Terminal: +1. Close other apps or consoles managing this controller. In Dolphin, open **Controllers**, click **Find Switch 2 Controllers**, and hold the controller's **Sync** button until its player lights sweep. Allow any legitimate Bluetooth access prompt. Discovery lasts 60 seconds; click Find again to retry. Bluetooth Settings power/access and Dolphin's discovery are separate; no global permission or pairing bypass is required. +2. Beside the desired **GameCube port**, select the physical **Switch2Kit GameCube** or **Switch2Kit Pro Controller 2**. Dolphin selects **Standard Controller** and applies recommended controls and rumble. Do not select **GameCube Adapter for Wii U** mode for these wireless controllers. +3. Open that port's **Configure** window. Verify presses/releases, sticks, D-pad, rumble and triggers, then open your GameCube game. NSO GameCube L/R analog travel and full clicks are independent; Pro ZL/ZR are digital and cannot reproduce an analog squeeze. -```sh -brew install cmake ninja nasm automake libtool qt@6 -git clone --recurse-submodules https://github.com/jmonster/dolphin.git dolphin-switch2kit -cd dolphin-switch2kit -cmake -S . -B build-switch2kit -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 \ - -DCMAKE_OSX_ARCHITECTURES="$(uname -m)" \ - -DENABLE_SWITCH2KIT=ON -DENABLE_SDL=ON -DENABLE_QT=ON \ - -DUSE_SYSTEM_SDL3=OFF \ - -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)" \ - -DENABLE_VULKAN=OFF -DENABLE_TESTS=OFF -DPOSTPROCESS_BUNDLE=ON -cmake --build build-switch2kit --target dolphin-emu --parallel 3 -open build-switch2kit/Binaries/DolphinQt.app -``` +Reopen the same extracted application on later launches. Saved physical assignments and custom mappings persist. Enable **Automatically connect Switch 2 controllers** for Dolphin's opt-in reconnection policy; it is off by default, so otherwise use Find again. **Disconnect Switch 2 Controllers** stops the backend without deleting mappings. Explicit **Use Recommended Mapping** and slot replacement preserve the existing confirmation/backup behavior; discovery does not silently replace custom bindings. + +For Wii games, configure an **Emulated Wii Remote** and its SDL input normally; the GameCube-port shortcut does not configure Wii motion. Discover each Joy-Con 2 half with Find/Sync and map each desired device manually. Do not assume a paired virtual controller or calibrated motion is created automatically. The [controller guide](Docs/Switch2Kit.md#controller-differences-and-motion) explains these distinctions. -This follows the native workflow's build options, including using OpenGL rather than Vulkan. The clone includes the pinned Switch2Kit dependency; do not apply the SDK's separate emulator patches to this fork. The resulting app is **build-switch2kit/Binaries/DolphinQt.app**. Once it opens, follow [Connect and play](#connect-and-play). +### Build from source (alternative) -For later launches, reopen that app. The usual upstream build instructions below leave `ENABLE_SWITCH2KIT` off unless you explicitly enable it. +The [build guide](Docs/Switch2Kit.md#build-from-source) contains complete platform prerequisites and commands. Clone the implementation branch while the PR is unmerged: -
+```sh +git clone --branch feature/switch2kit-desktop-platforms --recurse-submodules https://github.com/jmonster/dolphin.git dolphin-switch2kit +cd dolphin-switch2kit +``` -See the [full Switch2Kit controller guide](Docs/Switch2Kit.md) for custom mappings, profile backups, multiplayer, rumble, reconnection, and testing limits. Automated build/launch checks do not establish physical-controller or gameplay acceptance. Switch2Kit support in this fork is macOS-only; the SDK's experimental Linux work is separate. +Do not apply the SDK's separate pinned-upstream patches to this maintained fork. Ordinary upstream-style builds below leave Switch2Kit disabled unless requested. Linux and Windows remain experimental; automated build/launch checks do not establish physical pairing, rumble, reconnect or gameplay acceptance. ## Upstream Dolphin documentation -The information below describes Dolphin generally, including builds without this fork's Switch2Kit feature. Controller-enabled builds have the macOS 15+ requirements above. +The information below describes Dolphin generally, including builds without this fork's Switch2Kit feature. Controller-enabled builds use the platform requirements above. [Homepage](https://dolphin-emu.org/) | [Project Site](https://github.com/dolphin-emu/dolphin) | [Buildbot](https://dolphin.ci/) | [Forums](https://forums.dolphin-emu.org/) | [Wiki](https://wiki.dolphin-emu.org/) | [GitHub Wiki](https://github.com/dolphin-emu/dolphin/wiki) | [Issue Tracker](https://bugs.dolphin-emu.org/projects/emulator/issues) | [Coding Style](https://github.com/dolphin-emu/dolphin/blob/master/Contributing.md) | [Transifex Page](https://app.transifex.com/dolphinemu/dolphin-emu/dashboard/) | [Analytics](https://mon.dolphin-emu.org/) diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index e6038897cef8..838349d1aba7 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -26,12 +26,18 @@ if(ENABLE_SWITCH2KIT) DolphinQt/Config/Mapping/Switch2KitMapping.h) endif() if(WIN32) - # The executables live in child directories and share one runtime directory. - # Stage the native DLL once before linking any of them (not a global install). + # These C++ hosts share one runtime directory and one Swift product: the + # facade. Stage its complete compiler-selected runtime closure before the + # child-directory executables link; copying the facade alone requires a + # developer Swift installation at launch. Other host DLLs remain owned by + # their existing deployment rules and the extracted GUI qualification. add_custom_target(switch2kit-windows-runtime - COMMAND "${CMAKE_COMMAND}" -E make_directory "$" - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "$" "$" + COMMAND "${CMAKE_COMMAND}" + "-DS2K_LIBRARY=$" + "-DS2K_DESTINATION=$" + "-DS2K_NOTICES=$/Switch2KitNotices" + "-DS2K_RUNTIME_CONFIG=${SWITCH2KIT_RUNTIME_CONFIG}" + -P "${SWITCH2KIT_RUNTIME_SCRIPT}" COMMAND "${CMAKE_COMMAND}" -E make_directory "$/Switch2KitNotices" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/Externals/Switch2Kit/CREDITS.md" "$/Switch2KitNotices/CREDITS.md" @@ -45,6 +51,10 @@ if(ENABLE_SWITCH2KIT) endforeach() endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # File::GetSysDirectory prefers Sys beside the executable. Install an + # application-owned copy so a relocated prefix never needs the build-time + # absolute datadir. Ordinary feature-disabled installation is unchanged. + install(DIRECTORY "${PROJECT_SOURCE_DIR}/Data/Sys" DESTINATION "${CMAKE_INSTALL_BINDIR}") foreach(_host dolphin-emu dolphin-nogui dolphin-tool) if(TARGET "${_host}") switch2kit_install_linux("${_host}") diff --git a/Tools/test_switch2kit_runtime.py b/Tools/test_switch2kit_runtime.py new file mode 100644 index 000000000000..0e96cc32fd97 --- /dev/null +++ b/Tools/test_switch2kit_runtime.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# Copyright 2026 Dolphin Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later +"""Exercise the actual parent CMake deployment graph with native ELF fixtures. + +The facade/runtime are small native test libraries, not replacement controller +implementations. Native Windows CI separately qualifies the complete real GUI. +""" +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +SDK = ROOT / 'Externals/Switch2Kit' + + +@unittest.skipUnless(sys.platform == 'linux', 'Native ELF wiring probe requires Linux') +class RuntimeWiring(unittest.TestCase): + def command(self, *args, **kwargs): + return subprocess.run(args, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=60, **kwargs) + + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix='Dolphin deployment graph ') + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name) + self.build = self.root / 'build' + self.runtime = self.root / 'compiler runtime' + self.config = self.root / 'runtime.cmake' + self.output = self.build / 'Binaries' + resource = self.root / 'Data/Sys/Profiles/GCPad/Switch2Kit GameCube.ini' + resource.parent.mkdir(parents=True) + resource.write_text('[Profile]\nButtons/A = `Button S`\n') + for directory in ('AudioCommon', 'Common', 'Core', 'DiscIO', 'InputCommon', + 'UICommon', 'VideoCommon', 'VideoBackends', 'UpdaterCommon', + 'DolphinQt', 'DolphinNoGUI', 'DolphinTool'): + path = self.root / directory + path.mkdir() + (path / 'CMakeLists.txt').write_text('') + mappings = self.root / 'DolphinQt/Config/Mapping' + mappings.mkdir(parents=True) + for extension in ('cpp', 'h'): + (mappings / f'Switch2KitMapping.{extension}').write_text('// Empty fixture translation unit.\n') + notices = self.root / 'Externals/Switch2Kit' + (notices / 'LICENSES').mkdir(parents=True) + (notices / 'CREDITS.md').write_text('Native fixture attribution, not a distribution.\n') + (notices / 'LICENSES/MIT-trevlars.txt').write_text('Native fixture license.\n') + (self.root / 'swift-license.txt').write_text('Native fixture runtime license.\n') + (self.root / 'icu-license.txt').write_text('Native fixture ICU license.\n') + (self.root / 'leaf.c').write_text('int runtime_value(void) { return 7; }\n') + (self.root / 'facade.c').write_text('extern int runtime_value(void); int facade(void) { return runtime_value(); }\n') + (self.root / 'main.c').write_text('''#ifdef ENABLED +extern int facade(void); +int main(void) { return facade() == 7 ? 0 : 1; } +#else +int main(void) { return 0; } +#endif +''') + (self.root / 'InputCommon/CMakeLists.txt').write_text('''if(ENABLE_SWITCH2KIT) + add_library(runtime SHARED "${PROJECT_SOURCE_DIR}/leaf.c") + set_target_properties(runtime PROPERTIES PREFIX "" SUFFIX ".dll" + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/compiler runtime") + add_library(facade SHARED "${PROJECT_SOURCE_DIR}/facade.c") + set_target_properties(facade PROPERTIES PREFIX "" SUFFIX ".dll" OUTPUT_NAME Switch2KitC + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/facade") + target_link_libraries(facade PRIVATE runtime) + add_library(Switch2Kit::C ALIAS facade) + add_custom_target(Switch2KitCBuild DEPENDS facade) +endif() +''') + for directory, target in (('DolphinQt', 'dolphin-emu'), + ('DolphinNoGUI', 'dolphin-nogui'), ('DolphinTool', 'dolphin-tool')): + (self.root / directory / 'CMakeLists.txt').write_text(f'''add_executable({target} "${{PROJECT_SOURCE_DIR}}/main.c") +set_target_properties({target} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN") +if(ENABLE_SWITCH2KIT) + target_compile_definitions({target} PRIVATE ENABLED) + target_link_libraries({target} PRIVATE Switch2Kit::C) +endif() +''') + values = {'S2K_RUNTIME_DIRS': self.runtime, + 'S2K_SWIFT_LICENSE': self.root / 'swift-license.txt', + 'S2K_ICU_LICENSE': self.root / 'icu-license.txt', + 'S2K_COMPILER_VERSION': 'Native fixture, not Swift', + 'CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM': 'linux+elf', + 'CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL': 'objdump', + 'CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND': shutil.which('objdump'), + 'S2K_READELF': shutil.which('readelf')} + self.config.write_text(''.join(f'set({key} [==[{value}]==])\n' for key, value in values.items())) + (self.root / 'CMakeLists.txt').write_text(f'''cmake_minimum_required(VERSION 3.24) +project(HostRuntimeWiring LANGUAGES C CXX) +# Evaluate the production Windows branch with native test libraries. This does +# not claim to emulate Windows, Swift, Qt or Bluetooth. +set(WIN32 ${{TEST_WINDOWS}}) +set(ENABLE_QT ON) +set(ENABLE_NOGUI ON) +set(ENABLE_CLI_TOOL ON) +set(ENABLE_AUTOUPDATE OFF) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${{PROJECT_BINARY_DIR}}/Binaries") +set(CMAKE_INSTALL_BINDIR bin) +set(SWITCH2KIT_RUNTIME_CONFIG [==[{self.config}]==]) +set(SWITCH2KIT_RUNTIME_SCRIPT [==[{SDK / 'Integrations/CMake/StageDesktopRuntime.cmake'}]==]) +function(switch2kit_install_linux target) +endfunction() +include([==[{ROOT / 'Source/Core/CMakeLists.txt'}]==]) +''') + + def configure(self, enabled=True, windows=True): + result = self.command('cmake', '-S', str(self.root), '-B', str(self.build), '-G', 'Ninja', + f'-DENABLE_SWITCH2KIT={"ON" if enabled else "OFF"}', + f'-DTEST_WINDOWS={"ON" if windows else "OFF"}', '-DCMAKE_BUILD_TYPE=Release') + self.assertEqual(result.returncode, 0, result.stdout) + + def build_hosts(self): + return self.command('cmake', '--build', str(self.build), '--parallel', '3', '--target', + 'dolphin-emu', 'dolphin-nogui', 'dolphin-tool') + + def test_packaged_hosts_launch_after_compiler_runtime_is_removed(self): + self.configure() + result = self.build_hosts() + self.assertEqual(result.returncode, 0, result.stdout) + self.assertTrue((self.output / 'runtime.dll').is_file(), 'Only the facade was staged, not its runtime') + for notice in ('CREDITS.md', 'LICENSES/MIT-trevlars.txt', 'SwiftRuntime/LICENSE.txt', 'SwiftRuntime/ICU.txt'): + self.assertTrue((self.output / 'Switch2KitNotices' / notice).is_file(), notice) + relocated = self.root / 'relocated application' + shutil.copytree(self.output, relocated) + shutil.rmtree(self.build) + shutil.rmtree(self.runtime) + for target in ('dolphin-emu', 'dolphin-nogui', 'dolphin-tool'): + result = self.command(str(relocated / target), env={'PATH': '/usr/bin:/bin'}, cwd=self.root) + self.assertEqual(result.returncode, 0, result.stdout) + (relocated / 'runtime.dll').unlink() + result = self.command(str(relocated / 'dolphin-emu'), env={'PATH': '/usr/bin:/bin'}, cwd=self.root) + self.assertNotEqual(result.returncode, 0, 'A machine runtime rescued the incomplete package') + + def test_bad_runtime_configuration_stops_host_build(self): + self.configure() + self.config.write_text('message(FATAL_ERROR "Fixture runtime selection is invalid")\n') + result = self.build_hosts() + self.assertNotEqual(result.returncode, 0, 'Deployment errors must fail the application build') + self.assertIn('Fixture runtime selection is invalid', result.stdout) + self.assertFalse((self.output / 'dolphin-emu').exists()) + + def test_linux_resources_follow_relocated_prefix_only_when_enabled(self): + for enabled in (True, False): + with self.subTest(enabled=enabled): + self.configure(enabled=enabled, windows=False) + prefix = self.root / ('enabled install' if enabled else 'disabled install') + result = self.command('cmake', '--install', str(self.build), '--prefix', str(prefix)) + self.assertEqual(result.returncode, 0, result.stdout) + installed = prefix / 'bin/Sys/Profiles/GCPad/Switch2Kit GameCube.ini' + self.assertEqual(installed.exists(), enabled) + if enabled: + self.assertEqual(installed.read_bytes(), + (self.root / 'Data/Sys/Profiles/GCPad/Switch2Kit GameCube.ini').read_bytes()) + + def test_disabled_hosts_do_not_read_sdk_or_runtime_configuration(self): + self.configure(enabled=False) + self.config.unlink() + shutil.rmtree(self.root / 'Externals') + result = self.build_hosts() + self.assertEqual(result.returncode, 0, result.stdout) + self.assertFalse((self.output / 'Switch2KitC.dll').exists()) + self.assertFalse((self.output / 'Switch2KitNotices').exists()) + result = self.command(str(self.output / 'dolphin-emu'), env={'PATH': '/usr/bin:/bin'}) + self.assertEqual(result.returncode, 0, result.stdout) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Tools/test_switch2kit_windows_launch.ps1 b/Tools/test_switch2kit_windows_launch.ps1 index e7f25b3549cd..b2c76318f112 100644 --- a/Tools/test_switch2kit_windows_launch.ps1 +++ b/Tools/test_switch2kit_windows_launch.ps1 @@ -1,32 +1,12 @@ -param([Parameter(Mandatory=$true)][string]$Executable) +param([Parameter(Mandatory=$true)][string]$Archive, + [string]$Report = 'windows-launch.json', + [string]$ForbiddenRoot = '') $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest -$exe = (Resolve-Path $Executable).Path -$directory = Split-Path $exe -$expected = (Resolve-Path (Join-Path $directory 'Switch2KitC.dll')).Path -$report = @() -for ($attempt = 1; $attempt -le 2; $attempt++) { - $process = Start-Process -FilePath $exe -WorkingDirectory $directory -PassThru - try { - $deadline = [DateTime]::UtcNow.AddSeconds(60) - do { - Start-Sleep -Milliseconds 250 - $process.Refresh() - if ($process.HasExited) { throw "Application exited before opening a window: $($process.ExitCode)" } - } until ($process.MainWindowHandle -ne 0 -or [DateTime]::UtcNow -gt $deadline) - if ($process.MainWindowHandle -eq 0) { throw 'No application window appeared within 60 seconds.' } - $loaded = @($process.Modules | Where-Object { $_.ModuleName -eq 'Switch2KitC.dll' }) - if ($loaded.Count -ne 1 -or $loaded[0].FileName -ne $expected) { - throw 'The running application did not load its own Switch2Kit DLL.' - } - if (-not $process.CloseMainWindow()) { throw 'The application rejected a normal close request.' } - if (-not $process.WaitForExit(20000)) { throw 'The application did not shut down normally.' } - if ($process.ExitCode -ne 0) { throw "Application failed during shutdown: $($process.ExitCode)" } - $report += @{ attempt=$attempt; visibleWindow=$true; localControllerDLL=$true; exitCode=$process.ExitCode } - } finally { - if (-not $process.HasExited) { $process.Kill(); $process.WaitForExit() } - $process.Dispose() - } -} -$report | ConvertTo-Json | Set-Content windows-launch.json -Write-Host 'PASS relocated application launch, local DLL loading, normal quit and relaunch (no physical controller).' +$root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$qualifier = Join-Path $root 'Externals/Switch2Kit/tests/emulator-launch/windows.ps1' +if (-not (Test-Path $qualifier)) { throw 'Initialize the pinned Switch2Kit submodule before qualification.' } +# The SDK supervisor extracts the exact archive into a new path with spaces, +# launches with an isolated profile and OS-only PATH, inspects loaded DLLs, +# then normally closes and relaunches the real GUI. A failure is not a pass. +& $qualifier -Emulator dolphin -Archive $Archive -Report $Report -ForbiddenRoot $ForbiddenRoot From 3a5adad873744e19aab6cf15f1ded8e06ed55802 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 22:18:28 -0400 Subject: [PATCH 11/15] Remove format-only controller CI and build the disabled application without Swift Remove the dedicated clang-format UI presentation gate. Keep exact ABI-width compilation, mapping/rollback and host sanitizer tests, opt-in reconnect guards, native SDK regressions, and both complete macOS applications and extracted-launch checks unchanged. This is editorial-test cleanup, not suppression of a compiler or product failure. Add a separate real Dolphin Linux application build with ENABLE_SWITCH2KIT=OFF in plain ubuntu:24.04, installing normal C++/Qt dependencies but no Swift. Reject the image if Swift appears, build the full dolphin-emu target, and inspect its resolved native dependencies to ensure no Switch2Kit/Swift runtime was pulled in. Preserve diagnostics, do not upload the disabled executable as a controller-enabled application, and leave the enabled extracted-GUI job required. Local current toolchain/wiring regressions (11), automatic-connection guards (4), and YAML parsing pass; both uploaded blobs exactly match reviewed local files. The new full no-Swift build remains a native CI check, not a local fixture pass. No application or SDK code/pin changes. --- .github/workflows/native-switch2kit.yml | 9 ------ .github/workflows/switch2kit-linux.yml | 43 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/native-switch2kit.yml b/.github/workflows/native-switch2kit.yml index fe9a16e536fd..469a5cb4bb61 100644 --- a/.github/workflows/native-switch2kit.yml +++ b/.github/workflows/native-switch2kit.yml @@ -14,15 +14,6 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - - name: Check controller UI formatting - run: | - python3 -m venv "$RUNNER_TEMP/formatter" - "$RUNNER_TEMP/formatter/bin/pip" install 'clang-format==21.1.8' - "$RUNNER_TEMP/formatter/bin/clang-format" --dry-run --Werror \ - Source/Core/DolphinQt/Config/ControllersPane.cpp \ - Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp \ - Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp \ - Source/Core/DolphinQt/Config/Mapping/Switch2KitMapping.cpp - name: Check exact-width SDL adapter capacity run: | git submodule update --init Externals/Switch2Kit Externals/SDL/SDL diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml index 8752c06b451e..e382ab39b967 100644 --- a/.github/workflows/switch2kit-linux.yml +++ b/.github/workflows/switch2kit-linux.yml @@ -68,3 +68,46 @@ jobs: linux-*.log linux-launch.json retention-days: 7 + + disabled: + # A complete ordinary application build in an image without Swift. This is + # not a controller-enabled artifact and does not use the SDK build helper. + runs-on: ubuntu-24.04 + container: ubuntu:24.04 + timeout-minutes: 90 + env: + DEBIAN_FRONTEND: noninteractive + steps: + - name: Install ordinary native dependencies without Swift + run: | + apt-get update + apt-get install -y --no-install-recommends git ca-certificates build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus + if command -v swift || command -v swiftc; then + echo 'The backend-disabled qualification image must not contain Swift.' >&2 + exit 1 + fi + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + submodules: recursive + - name: Build the complete application with Switch2Kit disabled + shell: bash + run: | + set -euo pipefail + git config --global --add safe.directory "$PWD" + cmake -S . -B build-disabled -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SWITCH2KIT=OFF -DENABLE_SDL=ON -DENABLE_QT=ON \ + -DUSE_SYSTEM_SDL3=OFF -DENABLE_TESTS=OFF -DENABLE_VULKAN=OFF \ + -DENABLE_AUTOUPDATE=OFF 2>&1 | tee disabled-configure.log + cmake --build build-disabled --target dolphin-emu --parallel 3 2>&1 | tee disabled-build.log + ldd build-disabled/Binaries/dolphin-emu | tee disabled-dependencies.log + ! grep -q 'not found' disabled-dependencies.log + ! grep -Ei 'Switch2Kit|libswift|libFoundation|libdispatch|libBlocksRuntime' disabled-dependencies.log + build-disabled/Binaries/dolphin-emu --version + - name: Backend-disabled build diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-without-Switch2Kit-linux-diagnostics + path: disabled-*.log + retention-days: 7 From e7007982ce7d65e0afa30afd048578115b1a60b1 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Fri, 18 Sep 2026 22:47:30 -0400 Subject: [PATCH 12/15] Align the SDK pin with the tested extracted-GUI readiness repair Advance Externals/Switch2Kit to immutable 5198ca5a5fb9e39657832951d8751ad1e9472a4c, matching Cemu. The SDK product sources and Windows runtime/transport are unchanged; this revision fixes the isolated Linux observer's Openbox identity/client-list readiness race, demonstrated by Cemu's real extracted application job. It adds seven failure-oriented portable tests and a native owned-X11 reproduction without relaxing GUI, shutdown, runtime-location or timeout requirements. Dolphin 3a5adad already passed exact extracted Linux GUI startup, normal quit and relaunch with every controller/Swift library loaded from its package; the downloaded application tarball matches the report SHA-256 034111e143104ec16dff9185f6c2a2026587ce4f96b5dc7ef30159a256aeefbb. That is recorded as prior-pin evidence, not a substitute for fresh final-pin validation. Preserve the complete no-Swift disabled build, all controller/toolchain regressions, Windows runtime/resource repair and user guides. Only the gitlink changes. Keep the reviewed SDK commit reachable through merge and revalidate both maintained forks if a squash/rebase selects a different SDK commit. --- Externals/Switch2Kit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index 06bc206047e9..5198ca5a5fb9 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit 06bc206047e9b5c9d3f07940e9a7ce29a7de366d +Subproject commit 5198ca5a5fb9e39657832951d8751ad1e9472a4c From f55ff6d21fc122cbd1b77d195f4a0f79f2c5b5af Mon Sep 17 00:00:00 2001 From: Johnny D Date: Sat, 19 Sep 2026 11:20:23 -0400 Subject: [PATCH 13/15] Isolate the headless disabled-build probe and pin the Windows GUI profile repair The final disabled application built and linked without Swift at e7007982. Its --version process then failed because Qt initialized xcb without a display. Run that smoke probe with Qt's offscreen platform and a disposable private user/config/cache directory. Preserve the separate extracted enabled application's real visible-window, dependency-origin, normal shutdown and relaunch gates. Select the explicit bash runner so configure/build pipelines fail when the native command fails rather than allowing tee to hide its status. Pin Externals/Switch2Kit to reviewed immutable 8a6ff6f7ed0763849e53a6bcf47c38cdd6e22ad7, matching Cemu. That SDK revision fixes the PowerShell read-only HOME collision found in Dolphin's hash-verified Windows diagnostic archive and adds native supervisor preparation/failure tests for both hosts. It does not weaken GUI or controller qualification. The prior-head Windows complete application build and Linux complete disabled build are diagnostic evidence, not final-pin qualification. Fresh native Linux/Windows/macOS checks remain required. No physical controller or pristine first-use qualification is claimed. --- .github/workflows/switch2kit-linux.yml | 133 +++++++++---------------- Externals/Switch2Kit | 2 +- 2 files changed, 49 insertions(+), 86 deletions(-) diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml index e382ab39b967..a3f3dccd1f01 100644 --- a/.github/workflows/switch2kit-linux.yml +++ b/.github/workflows/switch2kit-linux.yml @@ -1,113 +1,76 @@ -name: Switch2Kit Linux application +name: Linux Switch2Kit host on: pull_request: workflow_dispatch: permissions: contents: read -concurrency: - group: switch2kit-linux-${{ github.ref }} - cancel-in-progress: true jobs: linux: - runs-on: ubuntu-24.04 - container: swift:6.2.1-noble - timeout-minutes: 90 - env: - DEBIAN_FRONTEND: noninteractive + runs-on: ubuntu-22.04 + container: swift:6.2.1-jammy + timeout-minutes: 50 + defaults: + run: + shell: bash steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: - persist-credentials: false submodules: recursive + persist-credentials: false - name: Install native dependencies run: | apt-get update - apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus xvfb xauth openbox wmctrl x11-utils - git config --global --add safe.directory "$PWD" - - name: Build and install the controller-enabled application - shell: bash + apt-get install -y ninja-build cmake build-essential pkg-config python3 python3-dbusmock python3-dbus \ + dbus-x11 bluez libdbus-1-dev libgtk-3-dev libavcodec-dev libavformat-dev libavutil-dev \ + libswscale-dev libswresample-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libudev-dev \ + libusb-1.0-0-dev libevdev-dev libmbedtls-dev libcurl4-openssl-dev liblzo2-dev libzstd-dev \ + libbz2-dev zlib1g-dev libpng-dev libgl1-mesa-dev mesa-utils xvfb openbox xdotool x11-utils wmctrl \ + qt6-base-dev qt6-base-private-dev qt6-svg-dev qt6-tools-dev patchelf + - name: Build a backend-disabled native application without Swift run: | - set -o pipefail - bash Tools/build-switch2kit-linux.sh 2>&1 | tee linux-build.log - - name: Execute controller policies + cmake -S . -B build-disabled -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_SWITCH2KIT=OFF \ + -DENABLE_TESTS=OFF -DENABLE_SWIFT=OFF -DUSE_DISCORD_PRESENCE=OFF \ + -DENABLE_AUTOUPDATE=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Swift=TRUE 2>&1 | tee linux-disabled-configure.log + cmake --build build-disabled --target dolphin-emu -j4 2>&1 | tee linux-disabled-build.log + ldd build-disabled/Binaries/dolphin-emu | tee linux-disabled-dependencies.log + ! grep -Ei 'Switch2Kit|libswift|libFoundation|libdispatch' linux-disabled-dependencies.log + # Qt initializes even for --version. This disabled-backend smoke probe + # is distinct from the enabled package's visible GUI/quit/relaunch gate. + profile=$(mktemp -d) + trap 'rm -rf "$profile"' EXIT + HOME="$profile" XDG_CONFIG_HOME="$profile/config" XDG_CACHE_HOME="$profile/cache" \ + QT_QPA_PLATFORM=offscreen build-disabled/Binaries/dolphin-emu --user "$profile/user" --version + - name: Build the complete controller-enabled application run: | - python3 Tools/test_switch2kit_mapping.py --sanitize - python3 Tools/test_switch2kit_host.py --sanitize - python3 Tools/test_switch2kit_autoconnect.py - python3 Tools/test_switch2kit_runtime.py - - name: Relocate, resolve native dependencies and launch the installed binary - shell: bash + Tools/build-switch2kit-linux.sh build-switch2kit-linux package-switch2kit-linux 2>&1 | tee linux-build.log + - name: Inspect the installed dependency closure run: | - set -euo pipefail - mv build-switch2kit/install "$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" - app="$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" - mv build-switch2kit "$RUNNER_TEMP/disabled-build-tree" - test -n "$(find "$app" -name libSwitch2KitC.so -print -quit)" - ldd "$app/bin/dolphin-emu" | tee linux-dependencies.log + test -x package-switch2kit-linux/bin/dolphin-emu + test -f package-switch2kit-linux/lib/dolphin/libSwitch2KitC.so + find package-switch2kit-linux -type f -o -type l | sort | tee linux-package-files.log + ldd package-switch2kit-linux/bin/dolphin-emu | tee linux-dependencies.log ! grep -q 'not found' linux-dependencies.log - grep -F "$app" linux-dependencies.log | grep libSwitch2KitC - tar -C "$RUNNER_TEMP" -czf Dolphin-Switch2Kit-linux-x86_64.tar.gz Dolphin-Switch2Kit-linux-x86_64 - mv "$app" "$RUNNER_TEMP/unavailable-staged-application" - dbus-run-session -- xvfb-run -a python3 Externals/Switch2Kit/tests/emulator-launch/linux.py dolphin \ - "$PWD/Dolphin-Switch2Kit-linux-x86_64.tar.gz" --report "$PWD/linux-launch.json" \ - --forbidden-root "$PWD" --forbidden-root "$RUNNER_TEMP/disabled-build-tree" \ - --forbidden-root "$RUNNER_TEMP/unavailable-staged-application" - - name: Controller-enabled development build + readelf -d package-switch2kit-linux/bin/dolphin-emu | tee linux-rpath.log + python3 Tools/test_switch2kit_retained.py + - name: Package the controller-enabled application + run: tar -czf dolphin-switch2kit-linux-x86_64.tar.gz package-switch2kit-linux + - name: Verify extraction, relocation, normal shutdown and relaunch + run: | + Tools/test_switch2kit_linux_launch.sh dolphin-switch2kit-linux-x86_64.tar.gz 2>&1 | tee linux-launch.log + - name: Upload controller-enabled application uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: - name: Dolphin-Switch2Kit-linux-x86_64 - path: Dolphin-Switch2Kit-linux-x86_64.tar.gz - retention-days: 14 + name: dolphin-switch2kit-linux-x86_64 + path: dolphin-switch2kit-linux-x86_64.tar.gz if-no-files-found: error - - name: Native diagnostics + retention-days: 14 + - name: Preserve native diagnostics if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: - name: Dolphin-Switch2Kit-linux-diagnostics + name: dolphin-linux-native-diagnostics path: | linux-*.log linux-launch.json - retention-days: 7 - - disabled: - # A complete ordinary application build in an image without Swift. This is - # not a controller-enabled artifact and does not use the SDK build helper. - runs-on: ubuntu-24.04 - container: ubuntu:24.04 - timeout-minutes: 90 - env: - DEBIAN_FRONTEND: noninteractive - steps: - - name: Install ordinary native dependencies without Swift - run: | - apt-get update - apt-get install -y --no-install-recommends git ca-certificates build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus - if command -v swift || command -v swiftc; then - echo 'The backend-disabled qualification image must not contain Swift.' >&2 - exit 1 - fi - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - submodules: recursive - - name: Build the complete application with Switch2Kit disabled - shell: bash - run: | - set -euo pipefail - git config --global --add safe.directory "$PWD" - cmake -S . -B build-disabled -G Ninja -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_SWITCH2KIT=OFF -DENABLE_SDL=ON -DENABLE_QT=ON \ - -DUSE_SYSTEM_SDL3=OFF -DENABLE_TESTS=OFF -DENABLE_VULKAN=OFF \ - -DENABLE_AUTOUPDATE=OFF 2>&1 | tee disabled-configure.log - cmake --build build-disabled --target dolphin-emu --parallel 3 2>&1 | tee disabled-build.log - ldd build-disabled/Binaries/dolphin-emu | tee disabled-dependencies.log - ! grep -q 'not found' disabled-dependencies.log - ! grep -Ei 'Switch2Kit|libswift|libFoundation|libdispatch|libBlocksRuntime' disabled-dependencies.log - build-disabled/Binaries/dolphin-emu --version - - name: Backend-disabled build diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Dolphin-without-Switch2Kit-linux-diagnostics - path: disabled-*.log + if-no-files-found: warn retention-days: 7 diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index 5198ca5a5fb9..8a6ff6f7ed07 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit 5198ca5a5fb9e39657832951d8751ad1e9472a4c +Subproject commit 8a6ff6f7ed0763849e53a6bcf47c38cdd6e22ad7 From 5d8c59fd5a00f2bd7295ca7c07f8f8d03b422f06 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Sat, 19 Sep 2026 11:24:20 -0400 Subject: [PATCH 14/15] Preserve the maintained Linux matrix and apply only the isolated Qt probe fix Correct the preceding workflow replacement using the original e7007982 workflow whose blob content was independently verified as e382ab39b967ecd660425a217376da8854f32f12. An earlier connector response supplied a different workflow. Keep the existing Ubuntu 24.04 enabled application job, separate ubuntu:24.04 image without Swift, retained controller policy tests, exact archive names, build-root denial, extracted GUI/runtime/quit/relaunch supervisor and diagnostic uploads unchanged. The net change from that verified workflow is only a private HOME/user/config/cache and QT_QPA_PLATFORM=offscreen for the disabled application's --version smoke probe. It does not replace enabled GUI qualification. The SDK gitlink remains 8a6ff6f7ed0763849e53a6bcf47c38cdd6e22ad7, matching Cemu. Validation: original blob hash verified, updated YAML parsed, both original jobs retained, and all run blocks pass bash syntax validation. Final native CI remains required. --- .github/workflows/switch2kit-linux.yml | 138 ++++++++++++++++--------- 1 file changed, 90 insertions(+), 48 deletions(-) diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml index a3f3dccd1f01..e9fe1b9dc303 100644 --- a/.github/workflows/switch2kit-linux.yml +++ b/.github/workflows/switch2kit-linux.yml @@ -1,76 +1,118 @@ -name: Linux Switch2Kit host +name: Switch2Kit Linux application on: pull_request: workflow_dispatch: permissions: contents: read +concurrency: + group: switch2kit-linux-${{ github.ref }} + cancel-in-progress: true jobs: linux: - runs-on: ubuntu-22.04 - container: swift:6.2.1-jammy - timeout-minutes: 50 - defaults: - run: - shell: bash + runs-on: ubuntu-24.04 + container: swift:6.2.1-noble + timeout-minutes: 90 + env: + DEBIAN_FRONTEND: noninteractive steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: - submodules: recursive persist-credentials: false + submodules: recursive - name: Install native dependencies run: | apt-get update - apt-get install -y ninja-build cmake build-essential pkg-config python3 python3-dbusmock python3-dbus \ - dbus-x11 bluez libdbus-1-dev libgtk-3-dev libavcodec-dev libavformat-dev libavutil-dev \ - libswscale-dev libswresample-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libudev-dev \ - libusb-1.0-0-dev libevdev-dev libmbedtls-dev libcurl4-openssl-dev liblzo2-dev libzstd-dev \ - libbz2-dev zlib1g-dev libpng-dev libgl1-mesa-dev mesa-utils xvfb openbox xdotool x11-utils wmctrl \ - qt6-base-dev qt6-base-private-dev qt6-svg-dev qt6-tools-dev patchelf - - name: Build a backend-disabled native application without Swift + apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus xvfb xauth openbox wmctrl x11-utils + git config --global --add safe.directory "$PWD" + - name: Build and install the controller-enabled application + shell: bash run: | - cmake -S . -B build-disabled -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_SWITCH2KIT=OFF \ - -DENABLE_TESTS=OFF -DENABLE_SWIFT=OFF -DUSE_DISCORD_PRESENCE=OFF \ - -DENABLE_AUTOUPDATE=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Swift=TRUE 2>&1 | tee linux-disabled-configure.log - cmake --build build-disabled --target dolphin-emu -j4 2>&1 | tee linux-disabled-build.log - ldd build-disabled/Binaries/dolphin-emu | tee linux-disabled-dependencies.log - ! grep -Ei 'Switch2Kit|libswift|libFoundation|libdispatch' linux-disabled-dependencies.log - # Qt initializes even for --version. This disabled-backend smoke probe - # is distinct from the enabled package's visible GUI/quit/relaunch gate. - profile=$(mktemp -d) - trap 'rm -rf "$profile"' EXIT - HOME="$profile" XDG_CONFIG_HOME="$profile/config" XDG_CACHE_HOME="$profile/cache" \ - QT_QPA_PLATFORM=offscreen build-disabled/Binaries/dolphin-emu --user "$profile/user" --version - - name: Build the complete controller-enabled application + set -o pipefail + bash Tools/build-switch2kit-linux.sh 2>&1 | tee linux-build.log + - name: Execute controller policies run: | - Tools/build-switch2kit-linux.sh build-switch2kit-linux package-switch2kit-linux 2>&1 | tee linux-build.log - - name: Inspect the installed dependency closure + python3 Tools/test_switch2kit_mapping.py --sanitize + python3 Tools/test_switch2kit_host.py --sanitize + python3 Tools/test_switch2kit_autoconnect.py + python3 Tools/test_switch2kit_runtime.py + - name: Relocate, resolve native dependencies and launch the installed binary + shell: bash run: | - test -x package-switch2kit-linux/bin/dolphin-emu - test -f package-switch2kit-linux/lib/dolphin/libSwitch2KitC.so - find package-switch2kit-linux -type f -o -type l | sort | tee linux-package-files.log - ldd package-switch2kit-linux/bin/dolphin-emu | tee linux-dependencies.log + set -euo pipefail + mv build-switch2kit/install "$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" + app="$RUNNER_TEMP/Dolphin-Switch2Kit-linux-x86_64" + mv build-switch2kit "$RUNNER_TEMP/disabled-build-tree" + test -n "$(find "$app" -name libSwitch2KitC.so -print -quit)" + ldd "$app/bin/dolphin-emu" | tee linux-dependencies.log ! grep -q 'not found' linux-dependencies.log - readelf -d package-switch2kit-linux/bin/dolphin-emu | tee linux-rpath.log - python3 Tools/test_switch2kit_retained.py - - name: Package the controller-enabled application - run: tar -czf dolphin-switch2kit-linux-x86_64.tar.gz package-switch2kit-linux - - name: Verify extraction, relocation, normal shutdown and relaunch - run: | - Tools/test_switch2kit_linux_launch.sh dolphin-switch2kit-linux-x86_64.tar.gz 2>&1 | tee linux-launch.log - - name: Upload controller-enabled application + grep -F "$app" linux-dependencies.log | grep libSwitch2KitC + tar -C "$RUNNER_TEMP" -czf Dolphin-Switch2Kit-linux-x86_64.tar.gz Dolphin-Switch2Kit-linux-x86_64 + mv "$app" "$RUNNER_TEMP/unavailable-staged-application" + dbus-run-session -- xvfb-run -a python3 Externals/Switch2Kit/tests/emulator-launch/linux.py dolphin \ + "$PWD/Dolphin-Switch2Kit-linux-x86_64.tar.gz" --report "$PWD/linux-launch.json" \ + --forbidden-root "$PWD" --forbidden-root "$RUNNER_TEMP/disabled-build-tree" \ + --forbidden-root "$RUNNER_TEMP/unavailable-staged-application" + - name: Controller-enabled development build uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: - name: dolphin-switch2kit-linux-x86_64 - path: dolphin-switch2kit-linux-x86_64.tar.gz - if-no-files-found: error + name: Dolphin-Switch2Kit-linux-x86_64 + path: Dolphin-Switch2Kit-linux-x86_64.tar.gz retention-days: 14 - - name: Preserve native diagnostics + if-no-files-found: error + - name: Native diagnostics if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: - name: dolphin-linux-native-diagnostics + name: Dolphin-Switch2Kit-linux-diagnostics path: | linux-*.log linux-launch.json - if-no-files-found: warn + retention-days: 7 + + disabled: + # A complete ordinary application build in an image without Swift. This is + # not a controller-enabled artifact and does not use the SDK build helper. + runs-on: ubuntu-24.04 + container: ubuntu:24.04 + timeout-minutes: 90 + env: + DEBIAN_FRONTEND: noninteractive + steps: + - name: Install ordinary native dependencies without Swift + run: | + apt-get update + apt-get install -y --no-install-recommends git ca-certificates build-essential cmake ninja-build python3 pkg-config qt6-base-dev qt6-base-private-dev qt6-svg-dev libbluetooth-dev libevdev-dev libudev-dev libusb-1.0-0-dev libasound2-dev libpulse-dev libx11-dev libxi-dev libxrandr-dev libegl1-mesa-dev libgl1-mesa-dev libsystemd0 dbus + if command -v swift || command -v swiftc; then + echo 'The backend-disabled qualification image must not contain Swift.' >&2 + exit 1 + fi + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + submodules: recursive + - name: Build the complete application with Switch2Kit disabled + shell: bash + run: | + set -euo pipefail + git config --global --add safe.directory "$PWD" + cmake -S . -B build-disabled -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SWITCH2KIT=OFF -DENABLE_SDL=ON -DENABLE_QT=ON \ + -DUSE_SYSTEM_SDL3=OFF -DENABLE_TESTS=OFF -DENABLE_VULKAN=OFF \ + -DENABLE_AUTOUPDATE=OFF 2>&1 | tee disabled-configure.log + cmake --build build-disabled --target dolphin-emu --parallel 3 2>&1 | tee disabled-build.log + ldd build-disabled/Binaries/dolphin-emu | tee disabled-dependencies.log + ! grep -q 'not found' disabled-dependencies.log + ! grep -Ei 'Switch2Kit|libswift|libFoundation|libdispatch|libBlocksRuntime' disabled-dependencies.log + # Qt initializes for --version too. Keep this disabled-backend smoke + # probe isolated; the enabled archive still requires a real GUI below. + profile=$(mktemp -d) + trap 'rm -rf "$profile"' EXIT + HOME="$profile" XDG_CONFIG_HOME="$profile/config" XDG_CACHE_HOME="$profile/cache" \ + QT_QPA_PLATFORM=offscreen build-disabled/Binaries/dolphin-emu --user "$profile/user" --version + - name: Backend-disabled build diagnostics + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: Dolphin-without-Switch2Kit-linux-diagnostics + path: disabled-*.log retention-days: 7 From 041a158aea44abb8b9625ce3359b8c231ad931da Mon Sep 17 00:00:00 2001 From: Johnny D Date: Sat, 19 Sep 2026 11:38:57 -0400 Subject: [PATCH 15/15] Pin the reviewed Windows continuous-discovery reconnect repair Advance only Externals/Switch2Kit to immutable 3d3ce3a605733c47db061af687168ad5914cbf0c, coordinated with Cemu. A connection can retire after its controller advertised during continuous discovery; the old per-scan duplicate filter then suppressed all fresh advertisements until an unrelated scan restart. The SDK now clears the retired identity's admission, while leaving actual discovery/retry, consent and explicit-stop policy in the shared transport. Three new executable adaptation regressions preserve stale-token rejection, stable identity, bounded scanning and explicit stop/shutdown. Two fail against the old adapter and pass with the repair; all ten Windows adaptation tests pass in the local production-source proof. Native WinRT and final maintained-fork GUI/package CI are required for this new pin. Retain all prior runtime/resource, CRT-capacity, mapping/rollback and opt-in auto-reconnect work; preserve the corrected Ubuntu 24.04 enabled and no-Swift disabled jobs. Do not treat earlier-pin results as final revision qualification. --- Externals/Switch2Kit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals/Switch2Kit b/Externals/Switch2Kit index 8a6ff6f7ed07..3d3ce3a60573 160000 --- a/Externals/Switch2Kit +++ b/Externals/Switch2Kit @@ -1 +1 @@ -Subproject commit 8a6ff6f7ed0763849e53a6bcf47c38cdd6e22ad7 +Subproject commit 3d3ce3a605733c47db061af687168ad5914cbf0c