new branch

This commit is contained in:
madwind 2025-01-27 16:26:03 +08:00
parent 31de0bf8c6
commit 2f8fbb21c9
84 changed files with 11851 additions and 1303 deletions

215
.github/workflows/create_sdl3_pr.yml vendored Normal file
View File

@ -0,0 +1,215 @@
name: Create SDL3 pr
on:
workflow_dispatch:
# schedule:
# - cron: '0 0 * * *'
env:
BUILD_TYPE: Release
jobs:
build:
name: ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
platform:
- { name: win-x64, os: windows-latest, flags: -A x64 }
# - { name: win-x86, os: windows-latest, flags: -A Win32 }
# - { name: win-arm64, os: windows-latest, flags: -A ARM64 }
- { name: linux-x64, os: ubuntu-20.04, flags: -GNinja, target_apt_arch: ":amd64" }
# - { name: linux-x86, os: ubuntu-20.04, flags: -GNinja -DCMAKE_C_FLAGS=-m32 -DCMAKE_CXX_FLAGS=-m32", target_apt_arch: ":i386" }
- { name: linux-arm64, os: ubuntu-22.04-arm, flags: -GNinja, target_apt_arch: ":arm64", container: "arm64v8/ubuntu:20.04" }
# - { name: linux-arm, os: ubuntu-22.04-arm, flags: -GNinja, target_apt_arch: ":armhf", container: "arm32v7/ubuntu:20.04" }
# - { name: osx-x64, os: macos-latest, flags: -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 }
# NOTE: macOS 11.0 is the first released supported by Apple Silicon.
- { name: osx-arm64, os: macos-latest, flags: -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 }
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build (Linux ARM)
if: contains(matrix.platform.container, 'arm')
uses: addnab/docker-run-action@v3
with:
image: ${{ matrix.platform.container }}
options: >
-v ${{ github.workspace }}:/workspace
-e NAME=${{ matrix.platform.name }}
-e TARGET_APT_ARCH=${{ matrix.platform.target_apt_arch }}
-e RUNNER_OS=${{ runner.os }}
-e FLAGS=${{ matrix.platform.flags }}
-e BUILD_TYPE=${{ env.BUILD_TYPE }}
run: |
cd /workspace
./src/Ryujinx.SDL3-CS/external/build.sh
- name: Build
if: ${{ !contains(matrix.platform.container, 'arm') }}
shell: bash
env:
NAME: ${{ matrix.platform.name }}
TARGET_APT_ARCH: ${{ matrix.platform.target_apt_arch }}
RUNNER_OS: ${{ runner.os }}
FLAGS: ${{ matrix.platform.flags }}
run: ./src/Ryujinx.SDL3-CS/external/build.sh
- name: Get Actions user id
if: runner.os == 'Linux'
id: get_uid
run: echo "uid=$(id -u $USER)" >> $GITHUB_OUTPUT
- name: Correct Ownership in GITHUB_WORKSPACE directory
if: runner.os == 'Linux'
uses: peter-murray/reset-workspace-ownership-action@v1
with:
user_id: ${{ steps.get_uid.outputs.uid }}
- name: Compress native directory
run: tar -cf native-${{ matrix.platform.name }}.tar src/Ryujinx.SDL3-CS/runtimes/${{ matrix.platform.name }}
- name: Upload native artifact
uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.platform.name }}
path: native-${{ matrix.platform.name }}.tar
if-no-files-found: error
build-ios:
name: ios
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build (iOS)
run: xcodebuild -project src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/SDL.xcodeproj -target SDL3.xcframework -configuration Release
- name: Prepare release directory (iOS)
run: mkdir -p src/Ryujinx.SDL3-CS/runtimes/ios
- name: Prepare release (iOS)
run: |
mkdir -p src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64/SDL3.framework;
mkdir -p src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64_x86_64-simulator/SDL3.framework;
cp src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/build/SDL3.xcframework/Info.plist src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/Info.plist;
cp src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/build/SDL3.xcframework/ios-arm64/SDL3.framework/SDL3 src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64/SDL3.framework/SDL3;
cp src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/build/SDL3.xcframework/ios-arm64/SDL3.framework/Info.plist src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64/SDL3.framework/Info.plist;
cp src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/build/SDL3.xcframework/ios-arm64_x86_64-simulator/SDL3.framework/SDL3 src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64_x86_64-simulator/SDL3.framework/SDL3;
cp src/Ryujinx.SDL3-CS/external/SDL/Xcode/SDL/build/SDL3.xcframework/ios-arm64_x86_64-simulator/SDL3.framework/Info.plist src/Ryujinx.SDL3-CS/runtimes/ios/native/SDL3.xcframework/ios-arm64_x86_64-simulator/SDL3.framework/Info.plist;
- name: Compress native directory
run: tar -cf native-ios.tar src/Ryujinx.SDL3-CS/runtimes/ios
- name: Upload native artifact
uses: actions/upload-artifact@v4
with:
name: native-ios
path: native-ios.tar
if-no-files-found: error
build-android:
name: android
runs-on: ubuntu-20.04
env:
NDK_VER: 23.1.7779620
PLATFORM_VER: android-34
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: microsoft
java-version: |
11
17
- name: Install Android SDK Manager
uses: android-actions/setup-android@v3
with:
packages: ''
- name: Install Android SDK
run: |
sdkmanager --install "platform-tools" "platforms;$PLATFORM_VER"
sdkmanager --install "ndk;$NDK_VER" --channel=3
- name: Build (Android)
run: |
export PATH=$ANDROID_HOME/ndk/$NDK_VER:$PATH
export OUTPUT=$PWD/src/Ryujinx.SDL3-CS/runtimes/android
rm -rf $OUTPUT && mkdir -p $OUTPUT
# Build SDL3
./src/Ryujinx.SDL3-CS/external/SDL/build-scripts/androidbuildlibs.sh APP_ABI="armeabi-v7a arm64-v8a x86 x86_64" NDK_LIBS_OUT="$OUTPUT"
- name: Build SDL3 Android Java
run: |
export JAVA_HOME=$JAVA_HOME_11_X64
export PATH=$JAVA_HOME_11_X64/bin:$PATH
export OUTPUT=$PWD/src/Ryujinx.SDL3-CS/runtimes/android/Jars/
rm -rf $OUTPUT && mkdir -p $OUTPUT
# Build SDL3 Android Java part
cd ./src/Ryujinx.SDL3-CS/external/SDL/android-project/app/src/main/java
javac -cp $ANDROID_HOME/platforms/$PLATFORM_VER/android.jar -encoding utf8 org/libsdl/app/*.java
jar cvf $OUTPUT/SDL3AndroidBridge.jar org/libsdl/app/*.class
- name: Compress native directory
run: tar -cf native-android.tar src/Ryujinx.SDL3-CS/runtimes/android
- name: Upload native artifact
uses: actions/upload-artifact@v4
with:
name: native-android
path: native-android.tar
if-no-files-found: error
- name: Upload JAR artifact
uses: actions/upload-artifact@v4
with:
name: android-jar
path: src/Ryujinx.SDL3-CS/runtimes/android/Jars/SDL3AndroidBridge.jar
if-no-files-found: error
make-pr:
name: Submit pull request
runs-on: ubuntu-latest
needs: [ build, build-ios, build-android ]
steps:
- uses: actions/checkout@v4
- name: Download native artifacts
uses: actions/download-artifact@v4
with:
pattern: native-*
merge-multiple: true
- name: Decompress native artifacts
run: |
for file in native-*.tar
do
tar -xf "$file"
done
rm native-*.tar
- name: Download JAR artifact
uses: actions/download-artifact@v4
with:
name: android-jar
path: src/Ryujinx.SDL3-CS/runtimes/android/Jars/
- name: Create pull request
uses: peter-evans/create-pull-request@v6
with:
commit-message: Update native binaries
title: Update native binaries
body: This PR has been auto-generated to update the native SDL binaries
branch: update-native-binaries
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'

245
.github/workflows/sdl3_release.yml vendored Normal file
View File

@ -0,0 +1,245 @@
name: SDL3 Release job
on:
workflow_dispatch:
inputs: {}
concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "madwind"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
RELEASE: 1
jobs:
tag:
name: Create tag
runs-on: ubuntu-20.04
steps:
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
shell: bash
- name: Create tag
uses: actions/github-script@v7
with:
script: |
github.rest.git.createRef({
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}",
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}-SDL3',
sha: context.sha
})
- name: Create release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}-SDL3
tag: ${{ steps.version_info.outputs.build_version }}-SDL3
body: |
# Stable builds:
| Platform | Artifact |
|--|--|
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
| macOS | [Stable macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release:
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
run: "mkdir release_output"
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
shell: bash
- name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
shell: bash
- name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest'
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt install -y zsync desktop-file-utils appstream
mkdir -p tools
export PATH="$PATH:$(readlink -f tools)"
# Setup appimagetool
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x tools/appimagetool
chmod +x distribution/linux/appimage/build-appimage.sh
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
ARCH_NAME=x64
export ARCH=x86_64
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
ARCH_NAME=arm64
export ARCH=aarch64
else
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
exit 1
fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
popd
shell: bash
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}-SDL3
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}-SDL3
body: |
# Stable builds:
| Platform | Artifact |
|--|--|
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
| macOS | [Stable macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
macos_release:
name: Release MacOS universal
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install rcodesign
run: |
mkdir -p $HOME/.bin
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
rm apple-codesign.tar.gz
mv rcodesign $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}-SDL3
artifacts: "publish/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}-SDL3
body: ""
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/Ryujinx.SDL3-CS/external/SDL"]
path = src/Ryujinx.SDL3-CS/external/SDL
url = https://github.com/libsdl-org/SDL.git

View File

@ -41,7 +41,6 @@
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" />

View File

@ -51,12 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Soun
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "src\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
@ -95,6 +89,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.SDL3.Common", "src\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj", "{7C70B441-F3D1-41FE-A648-19014BFB88D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{7420A718-7E3C-42B5-82EA-74F6BEE0F1FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.SDL3-CS", "src\Ryujinx.SDL3-CS\Ryujinx.SDL3-CS.csproj", "{ED2A7EA4-4098-47ED-BA87-EDB3537CFC10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{027A38DC-774D-4CF7-A1C0-C510BFC4BD29}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -197,18 +199,6 @@ Global
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -259,6 +249,22 @@ Global
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
{7C70B441-F3D1-41FE-A648-19014BFB88D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C70B441-F3D1-41FE-A648-19014BFB88D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C70B441-F3D1-41FE-A648-19014BFB88D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C70B441-F3D1-41FE-A648-19014BFB88D9}.Release|Any CPU.Build.0 = Release|Any CPU
{7420A718-7E3C-42B5-82EA-74F6BEE0F1FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7420A718-7E3C-42B5-82EA-74F6BEE0F1FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7420A718-7E3C-42B5-82EA-74F6BEE0F1FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7420A718-7E3C-42B5-82EA-74F6BEE0F1FB}.Release|Any CPU.Build.0 = Release|Any CPU
{ED2A7EA4-4098-47ED-BA87-EDB3537CFC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED2A7EA4-4098-47ED-BA87-EDB3537CFC10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED2A7EA4-4098-47ED-BA87-EDB3537CFC10}.Release|Any CPU.Build.0 = Release|Any CPU
{ED2A7EA4-4098-47ED-BA87-EDB3537CFC10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{027A38DC-774D-4CF7-A1C0-C510BFC4BD29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{027A38DC-774D-4CF7-A1C0-C510BFC4BD29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{027A38DC-774D-4CF7-A1C0-C510BFC4BD29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{027A38DC-774D-4CF7-A1C0-C510BFC4BD29}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,13 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" />
<ProjectReference Include="..\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,12 +1,12 @@
namespace Ryujinx.Audio.Backends.SDL2
namespace Ryujinx.Audio.Backends.SDL3
{
class SDL2AudioBuffer
class SDL3AudioBuffer
{
public readonly ulong DriverIdentifier;
public readonly ulong SampleCount;
public ulong SamplePlayed;
public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount)
public SDL3AudioBuffer(ulong driverIdentifier, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
SampleCount = sampleCount;

View File

@ -2,44 +2,34 @@ using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Audio.Backends.SDL2
namespace Ryujinx.Audio.Backends.SDL3
{
public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
private readonly ConcurrentDictionary<SDL3HardwareDeviceSession, byte> _sessions;
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054
[DllImport("SDL2")]
private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture);
#pragma warning restore SYSLIB1054
public SDL2HardwareDeviceDriver()
public SDL3HardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
_sessions = new ConcurrentDictionary<SDL3HardwareDeviceSession, byte>();
SDL2Driver.Instance.Initialize();
SDL3Driver.Instance.Initialize();
int res = SDL_GetDefaultAudioInfo(nint.Zero, out SDL_AudioSpec spec, 0);
if (res != 0)
if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, out var spec, out int _))
{
Logger.Error?.Print(LogClass.Application,
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
@ -58,11 +48,11 @@ namespace Ryujinx.Audio.Backends.SDL2
private static bool IsSupportedInternal()
{
uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
nint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, null);
if (device != 0)
{
SDL_CloseAudioDevice(device);
SDL_DestroyAudioStream(device);
}
return device != 0;
@ -78,7 +68,8 @@ namespace Ryujinx.Audio.Backends.SDL2
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager,
SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -92,70 +83,61 @@ namespace Ryujinx.Audio.Backends.SDL2
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
throw new NotImplementedException("Input direction is currently not implemented on SDL3 backend!");
}
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);
return session;
}
internal bool Unregister(SDL2HardwareDeviceSession session)
internal bool Unregister(SDL3HardwareDeviceSession session)
{
return _sessions.TryRemove(session, out _);
}
private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate,
uint requestedChannelCount)
{
return new SDL_AudioSpec
{
channels = (byte)requestedChannelCount,
format = GetSDL2Format(requestedSampleFormat),
format = GetSDL3Format(requestedSampleFormat),
freq = (int)requestedSampleRate,
samples = (ushort)sampleCount,
};
}
internal static ushort GetSDL2Format(SampleFormat format)
internal static SDL_AudioFormat GetSDL3Format(SampleFormat format)
{
return format switch
{
SampleFormat.PcmInt8 => AUDIO_S8,
SampleFormat.PcmInt16 => AUDIO_S16,
SampleFormat.PcmInt32 => AUDIO_S32,
SampleFormat.PcmFloat => AUDIO_F32,
SampleFormat.PcmInt8 => SDL_AudioFormat.SDL_AUDIO_S8,
SampleFormat.PcmInt16 => SDL_AudioFormat.SDL_AUDIO_S16,
SampleFormat.PcmInt32 => SDL_AudioFormat.SDL_AUDIO_S32,
SampleFormat.PcmFloat => SDL_AudioFormat.SDL_AUDIO_F32,
_ => throw new ArgumentException($"Unsupported sample format {format}"),
};
}
internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
internal static nint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate,
uint requestedChannelCount, SDL_AudioStreamCallback callback)
{
SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount);
desired.callback = callback;
nint stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, ref desired, callback, nint.Zero);
uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
if (device == 0)
if (stream == 0)
{
Logger.Error?.Print(LogClass.Application, $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
Logger.Error?.Print(LogClass.Application,
$"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\"");
return 0;
}
bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
if (!isValid)
{
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
SDL_CloseAudioDevice(device);
return 0;
}
return device;
return stream;
}
public void Dispose()
@ -168,12 +150,12 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (disposing)
{
foreach (SDL2HardwareDeviceSession session in _sessions.Keys)
foreach (SDL3HardwareDeviceSession session in _sessions.Keys)
{
session.Dispose();
}
SDL2Driver.Instance.Dispose();
SDL3Driver.Instance.Dispose();
_pauseEvent.Dispose();
}

View File

@ -4,40 +4,38 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Threading;
using static SDL3.SDL;
using static SDL2.SDL;
namespace Ryujinx.Audio.Backends.SDL2
namespace Ryujinx.Audio.Backends.SDL3
{
class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase
class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly SDL2HardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SDL2AudioBuffer> _queuedBuffers;
private readonly SDL3HardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent;
private uint _outputStream;
private nint _outputStream;
private bool _hasSetupError;
private readonly SDL_AudioCallback _callbackDelegate;
private readonly SDL_AudioStreamCallback _callbackDelegate;
private readonly int _bytesPerFrame;
private uint _sampleCount;
private bool _started;
private float _volume;
private readonly ushort _nativeSampleFormat;
private readonly SDL_AudioFormat _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager,
SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(
memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SDL2AudioBuffer>();
_queuedBuffers = new ConcurrentQueue<SDL3AudioBuffer>();
_ringBuffer = new DynamicRingBuffer();
_callbackDelegate = Update;
_bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue;
_nativeSampleFormat = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat);
_started = false;
_volume = 1f;
}
@ -46,13 +44,12 @@ namespace Ryujinx.Audio.Backends.SDL2
{
uint bufferSampleCount = (uint)GetSampleCount(buffer);
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
(bufferSampleCount >= Constants.TargetSampleCount);
if (needAudioSetup)
{
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
nint newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate,
RequestedChannelCount, _callbackDelegate);
_hasSetupError = newOutputStream == 0;
@ -60,64 +57,64 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (_outputStream != 0)
{
SDL_CloseAudioDevice(_outputStream);
SDL_DestroyAudioStream(_outputStream);
}
_outputStream = newOutputStream;
SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1);
Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}");
if (_started)
{
SDL_ResumeAudioStreamDevice(_outputStream);
}
else
{
SDL_PauseAudioStreamDevice(_outputStream);
}
}
}
}
private unsafe void Update(nint userdata, nint stream, int streamLength)
private unsafe void Update(nint userdata, nint stream, int additionalAmount, int totalAmount)
{
Span<byte> streamSpan = new((void*)stream, streamLength);
int maxFrameCount = (int)GetSampleCount(streamLength);
int maxFrameCount = (int)GetSampleCount(additionalAmount);
int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
if (frameCount == 0)
{
// SDL2 left the responsibility to the user to clear the buffer.
streamSpan.Clear();
return;
}
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
int samplesLength = samples.Length;
_ringBuffer.Read(samples, 0, samplesLength);
fixed (byte* p = samples)
{
nint pStreamSrc = (nint)p;
// Zero the dest buffer
streamSpan.Clear();
nint pStreamDst = SDL_calloc(1,samplesLength);
// Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
SDL_MixAudio(pStreamDst, pStreamSrc, _nativeSampleFormat, (uint)samplesLength, _driver.Volume);
SDL_PutAudioStreamData(stream, pStreamDst, samplesLength);
SDL_free(pStreamDst);
}
ulong sampleCount = GetSampleCount(samples.Length);
ulong sampleCount = GetSampleCount(samplesLength);
ulong availaibleSampleCount = sampleCount;
bool needUpdate = false;
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{
ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
ulong currentSamplePlayed =
Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
availaibleSampleCount -= playedAudioBufferSampleCount;
if (currentSamplePlayed == driverBuffer.SampleCount)
@ -155,7 +152,7 @@ namespace Ryujinx.Audio.Backends.SDL2
if (_outputStream != 0)
{
SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
@ -180,7 +177,7 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (_outputStream != 0)
{
SDL_PauseAudioDevice(_outputStream, 0);
SDL_ResumeAudioStreamDevice(_outputStream);
}
_started = true;
@ -193,7 +190,7 @@ namespace Ryujinx.Audio.Backends.SDL2
{
if (_outputStream != 0)
{
SDL_PauseAudioDevice(_outputStream, 1);
SDL_PauseAudioStreamDevice(_outputStream);
}
_started = false;
@ -204,7 +201,7 @@ namespace Ryujinx.Audio.Backends.SDL2
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
if (!_queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{
return true;
}
@ -221,7 +218,7 @@ namespace Ryujinx.Audio.Backends.SDL2
if (_outputStream != 0)
{
SDL_CloseAudioDevice(_outputStream);
SDL_DestroyAudioStream(_outputStream);
}
}
}

View File

@ -78,10 +78,5 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Controller Rumble Settings
/// </summary>
public RumbleConfigController Rumble { get; set; }
/// <summary>
/// Controller LED Settings
/// </summary>
public LedConfigController Led { get; set; }
}
}

View File

@ -1,25 +0,0 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class LedConfigController
{
/// <summary>
/// Enable LED color changing by the emulator
/// </summary>
public bool EnableLed { get; set; }
/// <summary>
/// Ignores the color and disables the LED entirely.
/// </summary>
public bool TurnOffLed { get; set; }
/// <summary>
/// Ignores the color and uses the rainbow color functionality for the LED.
/// </summary>
public bool UseRainbow { get; set; }
/// <summary>
/// Packed RGB int of the color
/// </summary>
public uint LedColor { get; set; }
}
}

View File

@ -8,6 +8,6 @@ namespace Ryujinx.Common.Configuration.Hid
{
Invalid,
WindowKeyboard,
GamepadSDL2,
GamepadSDL3
}
}

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch
{
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig),
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
InputBackendType.GamepadSDL3 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
};
}
@ -70,7 +70,7 @@ namespace Ryujinx.Common.Configuration.Hid
case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig);
break;
case InputBackendType.GamepadSDL2:
case InputBackendType.GamepadSDL3:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig);
break;
default:

View File

@ -1,129 +0,0 @@
using Gommon;
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities
{
public static class Rainbow
{
public static bool CyclingEnabled { get; set; }
public static void Enable()
{
if (!CyclingEnabled)
{
CyclingEnabled = true;
Executor.ExecuteBackgroundAsync(async () =>
{
while (CyclingEnabled)
{
await Task.Delay(20);
Tick();
}
});
}
}
public static void Disable()
{
CyclingEnabled = false;
}
public static float Speed { get; set; } = 1;
private static readonly Lock _lock = new();
private static Color _color = Color.Blue;
public static ref Color Color
{
get
{
lock (_lock)
{
return ref _color;
}
}
}
public static void Tick()
{
lock (_lock)
{
_color = HsbToRgb((_color.GetHue() + Speed) / 360);
_updatedHandler.Call(_color.ToArgb());
}
}
public static void Reset()
{
_updatedHandler.Clear();
lock (_lock)
_color = Color.Blue;
}
public static event Action<int> Updated
{
add => _updatedHandler.Add(value);
remove => _updatedHandler.Remove(value);
}
private static readonly Event<int> _updatedHandler = new();
private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1)
{
int r = 0, g = 0, b = 0;
if (saturation == 0)
{
r = g = b = (int)(brightness * 255.0f + 0.5f);
}
else
{
float h = (hue - (float)Math.Floor(hue)) * 6.0f;
float f = h - (float)Math.Floor(h);
float p = brightness * (1.0f - saturation);
float q = brightness * (1.0f - saturation * f);
float t = brightness * (1.0f - (saturation * (1.0f - f)));
switch ((int)h)
{
case 0:
r = (int)(brightness * 255.0f + 0.5f);
g = (int)(t * 255.0f + 0.5f);
b = (int)(p * 255.0f + 0.5f);
break;
case 1:
r = (int)(q * 255.0f + 0.5f);
g = (int)(brightness * 255.0f + 0.5f);
b = (int)(p * 255.0f + 0.5f);
break;
case 2:
r = (int)(p * 255.0f + 0.5f);
g = (int)(brightness * 255.0f + 0.5f);
b = (int)(t * 255.0f + 0.5f);
break;
case 3:
r = (int)(p * 255.0f + 0.5f);
g = (int)(q * 255.0f + 0.5f);
b = (int)(brightness * 255.0f + 0.5f);
break;
case 4:
r = (int)(t * 255.0f + 0.5f);
g = (int)(p * 255.0f + 0.5f);
b = (int)(brightness * 255.0f + 0.5f);
break;
case 5:
r = (int)(brightness * 255.0f + 0.5f);
g = (int)(p * 255.0f + 0.5f);
b = (int)(q * 255.0f + 0.5f);
break;
}
}
return Color.FromArgb(Convert.ToByte(255), Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b));
}
}
}

View File

@ -1,188 +0,0 @@
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Generic;
using System.Threading;
using static SDL2.SDL;
namespace Ryujinx.Input.SDL2
{
public class SDL2GamepadDriver : IGamepadDriver
{
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
private readonly List<string> _gamepadsIds;
private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds
{
get
{
lock (_lock)
{
return _gamepadsIds.ToArray();
}
}
}
public string DriverName => "SDL2";
public event Action<string> OnGamepadConnected;
public event Action<string> OnGamepadDisconnected;
public SDL2GamepadDriver()
{
_gamepadsInstanceIdsMapping = new Dictionary<int, string>();
_gamepadsIds = [];
SDL2Driver.Instance.Initialize();
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
// Add already connected gamepads
int numJoysticks = SDL_NumJoysticks();
for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++)
{
HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex));
}
}
private string GenerateGamepadId(int joystickIndex)
{
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
// Add a unique identifier to the start of the GUID in case of duplicates.
if (guid == Guid.Empty)
{
return null;
}
string id;
lock (_lock)
{
int guidIndex = 0;
id = guidIndex + "-" + guid;
while (_gamepadsIds.Contains(id))
{
id = (++guidIndex) + "-" + guid;
}
}
return id;
}
private int GetJoystickIndexByGamepadId(string id)
{
lock (_lock)
{
return _gamepadsIds.IndexOf(id);
}
}
private void HandleJoyStickDisconnected(int joystickInstanceId)
{
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
return;
lock (_lock)
{
_gamepadsIds.Remove(id);
}
OnGamepadDisconnected?.Invoke(id);
}
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
{
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
{
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
{
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
// so it is rejected to avoid doubling the entries.
return;
}
string id = GenerateGamepadId(joystickDeviceId);
if (id == null)
{
return;
}
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
{
lock (_lock)
{
if (joystickDeviceId <= _gamepadsIds.FindLastIndex(_ => true))
_gamepadsIds.Insert(joystickDeviceId, id);
else
_gamepadsIds.Add(id);
}
OnGamepadConnected?.Invoke(id);
}
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
// Simulate a full disconnect when disposing
foreach (string id in _gamepadsIds)
{
OnGamepadDisconnected?.Invoke(id);
}
lock (_lock)
{
_gamepadsIds.Clear();
}
SDL2Driver.Instance.Dispose();
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
int joystickIndex = GetJoystickIndexByGamepadId(id);
if (joystickIndex == -1)
{
return null;
}
nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
if (gamepadHandle == nint.Zero)
{
return null;
}
return new SDL2Gamepad(gamepadHandle, id);
}
public IEnumerable<IGamepad> GetGamepads()
{
lock (_gamepadsIds)
{
foreach (string gamepadId in _gamepadsIds)
{
yield return GetGamepad(gamepadId);
}
}
}
}
}

View File

@ -7,7 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" />
<ProjectReference Include="..\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,18 +1,17 @@
using Humanizer;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Hid;
using SDL2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Input.SDL2
namespace Ryujinx.Input.SDL3
{
class SDL2Gamepad : IGamepad
class SDL3Gamepad : IGamepad
{
private bool HasConfiguration => _configuration != null;
@ -20,58 +19,38 @@ namespace Ryujinx.Input.SDL2
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private static readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _buttonsDriverDict = new()
{
{ GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK },
{ GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_UP },
{ GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_DOWN },
{ GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_LEFT },
{ GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_RIGHT },
{ GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_BACK },
{ GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER },
{ GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 },
{ GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_STICK },
{ GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST },
{ GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH },
{ GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH },
{ GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST },
{ GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START },
{ GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER },
{ GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 },
};
private StandardControllerInputConfig _configuration;
private static readonly SDL_GameControllerButton[] _buttonsDriverMapping =
[
// Unbound, ignored.
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
// NOTE: The left and right trigger are axis, we handle those differently
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD,
// Virtual buttons are invalid, ignored.
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID
];
private static readonly SDL_GamepadButton[] _buttonsDriverMapping = ToSDLButtonMapping(_buttonsDriverDict);
private readonly Lock _userMappingLock = new();
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly StickInputId[] _stickUserMapping =
[
StickInputId.Unbound,
StickInputId.Left,
StickInputId.Right
];
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
{
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
};
public GamepadFeaturesFlag Features { get; }
@ -79,76 +58,69 @@ namespace Ryujinx.Input.SDL2
private float _triggerThreshold;
public SDL2Gamepad(nint gamepadHandle, string driverId)
public SDL3Gamepad(uint joystickId, string driverId)
{
_gamepadHandle = gamepadHandle;
_gamepadHandle = SDL_OpenGamepad(joystickId);
_buttonsUserMapping = new List<ButtonMappingEntry>(20);
Name = SDL_GameControllerName(_gamepadHandle);
Name = SDL_GetGamepadName(_gamepadHandle);
Id = driverId;
Features = GetFeaturesFlag();
_triggerThreshold = 0.0f;
// Enable motion tracking
if (Features.HasFlag(GamepadFeaturesFlag.Motion))
{
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0)
if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, true))
{
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
}
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE) != 0)
if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, true))
{
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
}
}
}
public void SetLed(uint packedRgb)
private static SDL_GamepadButton[] ToSDLButtonMapping(
Dictionary<GamepadButtonInputId, SDL_GamepadButton> buttonsDriverMapping)
{
if (!Features.HasFlag(GamepadFeaturesFlag.Led)) return;
byte red = packedRgb > 0 ? (byte)(packedRgb >> 16) : (byte)0;
byte green = packedRgb > 0 ? (byte)(packedRgb >> 8) : (byte)0;
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
Logger.Error?.Print(LogClass.Hid, "LED is not supported on this game controller.");
return Enumerable.Range(0, (int)GamepadButtonInputId.Count)
.Select(i =>
buttonsDriverMapping.GetValueOrDefault((GamepadButtonInputId)i,
SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID))
.ToArray();
}
private GamepadFeaturesFlag GetFeaturesFlag()
{
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
if (SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) &&
SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO))
{
result |= GamepadFeaturesFlag.Motion;
}
if (SDL_GameControllerHasRumble(_gamepadHandle) == SDL_bool.SDL_TRUE)
if (SDL_RumbleGamepad(_gamepadHandle, 0, 0, 100))
{
result |= GamepadFeaturesFlag.Rumble;
}
if (SDL_GameControllerHasLED(_gamepadHandle) == SDL_bool.SDL_TRUE)
{
result |= GamepadFeaturesFlag.Led;
}
return result;
}
public string Id { get; }
public string Name { get; }
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
protected virtual void Dispose(bool disposing)
private void Dispose(bool disposing)
{
if (disposing && _gamepadHandle != nint.Zero)
{
SDL_GameControllerClose(_gamepadHandle);
SDL_CloseGamepad(_gamepadHandle);
_gamepadHandle = nint.Zero;
}
@ -172,19 +144,10 @@ namespace Ryujinx.Input.SDL2
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (durationMs == uint.MaxValue)
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
else if (durationMs > SDL_HAPTIC_INFINITY)
{
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
}
else
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
@ -202,14 +165,14 @@ namespace Ryujinx.Input.SDL2
const int ElementCount = 3;
unsafe
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount);
if (result != 0)
if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, values, ElementCount))
{
return Vector3.Zero;
}
Vector3 value = new(values[0], values[1], values[2]);
@ -225,24 +188,13 @@ namespace Ryujinx.Input.SDL2
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
public void SetConfiguration(InputConfig configuration)
{
lock (_userMappingLock)
{
_configuration = (StandardControllerInputConfig)configuration;
if (Features.HasFlag(GamepadFeaturesFlag.Led) && _configuration.Led.EnableLed)
{
if (_configuration.Led.TurnOffLed)
(this as IGamepad).ClearLed();
else if (_configuration.Led.UseRainbow)
SetLed((uint)Rainbow.Color.ToArgb());
if (!_configuration.Led.TurnOffLed && !_configuration.Led.UseRainbow)
SetLed(_configuration.Led.LedColor);
}
_buttonsUserMapping.Clear();
// First update sticks
@ -250,28 +202,48 @@ namespace Ryujinx.Input.SDL2
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
// Then left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick,
(GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick,
(GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
SetTriggerThreshold(_configuration.TriggerThreshold);
}
@ -323,21 +295,25 @@ namespace Ryujinx.Input.SDL2
return value * ConvertRate;
}
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId> GetLogicalJoyStickConfig(StickInputId inputId)
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId>
GetLogicalJoyStickConfig(StickInputId inputId)
{
switch (inputId)
{
case StickInputId.Left:
if (_configuration.RightJoyconStick.Joystick == Common.Configuration.Hid.Controller.StickInputId.Left)
if (_configuration.RightJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Left)
return _configuration.RightJoyconStick;
else
return _configuration.LeftJoyconStick;
case StickInputId.Right:
if (_configuration.LeftJoyconStick.Joystick == Common.Configuration.Hid.Controller.StickInputId.Right)
if (_configuration.LeftJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Right)
return _configuration.LeftJoyconStick;
else
return _configuration.RightJoyconStick;
}
return null;
}
@ -353,7 +329,7 @@ namespace Ryujinx.Input.SDL2
if (HasConfiguration)
{
JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId> joyconStickConfig = GetLogicalJoyStickConfig(inputId);
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
if (joyconStickConfig != null)
{
@ -380,11 +356,11 @@ namespace Ryujinx.Input.SDL2
inputId switch
{
StickInputId.Left => (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY)),
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX),
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY)),
StickInputId.Right => (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY)),
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTX),
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTY)),
_ => throw new NotSupportedException($"Unsupported stick {inputId}")
};
@ -393,17 +369,20 @@ namespace Ryujinx.Input.SDL2
switch (inputId)
{
case GamepadButtonInputId.LeftTrigger:
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle,
SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) > _triggerThreshold;
case GamepadButtonInputId.RightTrigger:
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle,
SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) > _triggerThreshold;
}
if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID)
var button = _buttonsDriverMapping[(int)inputId];
if (button == SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID)
{
return false;
}
return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
return SDL_GetGamepadButton(_gamepadHandle, button);
}
}
}

View File

@ -0,0 +1,192 @@
using Ryujinx.Common.Logging;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using static SDL3.SDL;
namespace Ryujinx.Input.SDL3
{
public class SDL3GamepadDriver : IGamepadDriver
{
private readonly Dictionary<uint, string> _gamepadsInstanceIdsMapping;
private readonly List<string> _gamepadsIds;
private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds
{
get
{
lock (_lock)
{
return _gamepadsIds.ToArray();
}
}
}
public string DriverName => "SDL3";
public event Action<string> OnGamepadConnected;
public event Action<string> OnGamepadDisconnected;
public SDL3GamepadDriver()
{
_gamepadsInstanceIdsMapping = new Dictionary<uint, string>();
_gamepadsIds = new List<string>();
SDL3Driver.Instance.Initialize();
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
SDL3Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
SDL3Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
}
private string GenerateGamepadId(uint joystickId)
{
int bufferSize = 33;
Span<byte> pszGuid = stackalloc byte[bufferSize];
SDL_GUIDToString(SDL_GetJoystickGUIDForID(joystickId), pszGuid, bufferSize);
var guid = Encoding.UTF8.GetString(pszGuid);
string id;
lock (_lock)
{
int guidIndex = 0;
id = guidIndex + guid;
while (_gamepadsIds.Contains(id))
{
id = (++guidIndex) + "-" + guid;
}
}
return id;
}
private KeyValuePair<uint,string> GetGamepadInfoByGamepadId(string id)
{
lock (_lock)
{
return _gamepadsInstanceIdsMapping.FirstOrDefault(gamepadId => gamepadId.Value == id);
}
}
private void HandleJoyStickDisconnected(uint joystickId)
{
bool joyConPairDisconnected = false;
if (!_gamepadsInstanceIdsMapping.Remove(joystickId, out string id))
return;
lock (_lock)
{
_gamepadsIds.Remove(id);
if (!SDL3JoyConPair.IsCombinable(_gamepadsInstanceIdsMapping))
{
_gamepadsIds.Remove(SDL3JoyConPair.Id);
joyConPairDisconnected = true;
}
}
OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected)
{
OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
}
}
private void HandleJoyStickConnected(uint joystickId)
{
bool joyConPairConnected = false;
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickId))
{
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
// so it is rejected to avoid doubling the entries.
return;
}
string id = GenerateGamepadId(joystickId);
if (id == null)
{
return;
}
if (_gamepadsInstanceIdsMapping.TryAdd(joystickId, id))
{
lock (_lock)
{
_gamepadsIds.Add(id);
if (SDL3JoyConPair.IsCombinable(_gamepadsInstanceIdsMapping))
{
_gamepadsIds.Remove(SDL3JoyConPair.Id);
_gamepadsIds.Add(SDL3JoyConPair.Id);
joyConPairConnected = true;
}
}
OnGamepadConnected?.Invoke(id);
if (joyConPairConnected)
{
OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
}
}
}
private void HandleJoyBatteryUpdated(uint joystickId, SDL_JoyBatteryEvent joyBatteryEvent)
{
Logger.Info?.Print(LogClass.Hid,
$"{SDL_GetGamepadNameForID(joystickId)}, Battery percent: {joyBatteryEvent.percent}");
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
SDL3Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
SDL3Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
// Simulate a full disconnect when disposing
foreach (string id in _gamepadsIds)
{
OnGamepadDisconnected?.Invoke(id);
}
lock (_lock)
{
_gamepadsIds.Clear();
}
SDL3Driver.Instance.Dispose();
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (id == SDL3JoyConPair.Id)
{
lock (_lock)
{
return SDL3JoyConPair.GetGamepad(_gamepadsInstanceIdsMapping);
}
}
var gamepadInfo = GetGamepadInfoByGamepadId(id);
if (SDL3JoyCon.IsJoyCon(gamepadInfo.Key))
{
return new SDL3JoyCon(gamepadInfo.Key, gamepadInfo.Value);
}
return new SDL3Gamepad(gamepadInfo.Key, gamepadInfo.Value);
}
}
}

View File

@ -0,0 +1,418 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using static SDL3.SDL;
namespace Ryujinx.Input.SDL3
{
class SDL3JoyCon : IGamepad
{
private bool HasConfiguration => _configuration != null;
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private StandardControllerInputConfig _configuration;
private static readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _leftButtonsDriverDict = new()
{
{ GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK },
{ GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST },
{ GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST },
{ GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH },
{ GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH },
{ GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START },
{ GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 },
{ GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 },
{ GamepadButtonInputId.SingleRightTrigger0, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER },
{ GamepadButtonInputId.SingleLeftTrigger0, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER },
};
private static readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _rightButtonsDriverDict = new()
{
{ GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK },
{ GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH },
{ GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST },
{ GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST },
{ GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH },
{ GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START },
{ GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 },
{ GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 },
{ GamepadButtonInputId.SingleRightTrigger1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER },
{ GamepadButtonInputId.SingleLeftTrigger1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER }
};
private readonly SDL_GamepadButton[] _buttonsDriverMapping;
private readonly Lock _userMappingLock = new();
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
{
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
};
public GamepadFeaturesFlag Features { get; }
private nint _gamepadHandle;
private readonly SDL_GamepadType _gamepadType;
public SDL3JoyCon(uint joystickId, string driverId)
{
_gamepadHandle = SDL_OpenGamepad(joystickId);
_buttonsUserMapping = new List<ButtonMappingEntry>(10);
Name = SDL_GetGamepadName(_gamepadHandle);
Id = driverId;
Features = GetFeaturesFlag();
// Enable motion tracking
if (Features.HasFlag(GamepadFeaturesFlag.Motion))
{
if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, true))
{
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
}
if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, true))
{
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
}
}
_gamepadType = SDL_GetGamepadType(_gamepadHandle);
_buttonsDriverMapping = _gamepadType switch
{
SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => ToSDLButtonMapping(
_leftButtonsDriverDict),
SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => ToSDLButtonMapping(
_rightButtonsDriverDict),
_ => throw new InvalidOperationException($"Unexpected JoyConType value: {_gamepadType}")
};
}
private static SDL_GamepadButton[] ToSDLButtonMapping(
Dictionary<GamepadButtonInputId, SDL_GamepadButton> buttonsDriverDict)
{
return Enumerable.Range(0, (int)GamepadButtonInputId.Count)
.Select(i =>
buttonsDriverDict.GetValueOrDefault((GamepadButtonInputId)i,
SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID))
.ToArray();
}
private GamepadFeaturesFlag GetFeaturesFlag()
{
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
if (SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) &&
SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO))
{
result |= GamepadFeaturesFlag.Motion;
}
if (SDL_RumbleGamepad(_gamepadHandle, 0, 0, 100))
{
result |= GamepadFeaturesFlag.Rumble;
}
return result;
}
public string Id { get; }
public string Name { get; }
public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
private void Dispose(bool disposing)
{
if (disposing && _gamepadHandle != nint.Zero)
{
SDL_CloseGamepad(_gamepadHandle);
_gamepadHandle = nint.Zero;
}
}
public void Dispose()
{
Dispose(true);
}
public void SetTriggerThreshold(float triggerThreshold)
{
}
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble))
return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
{
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
public Vector3 GetMotionData(MotionInputId inputId)
{
SDL_SensorType sensorType = inputId switch
{
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
_ => SDL_SensorType.SDL_SENSOR_INVALID
};
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
return Vector3.Zero;
const int ElementCount = 3;
unsafe
{
float* values = stackalloc float[ElementCount];
if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, values, ElementCount))
{
return Vector3.Zero;
}
Vector3 value = _gamepadType switch
{
SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => new Vector3(-values[2], values[1], values[0]),
SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => new Vector3(values[2], values[1], -values[0]),
_ => throw new ArgumentOutOfRangeException($"Unexpected JoyConType value: {_gamepadType}")
};
return inputId switch
{
MotionInputId.Gyroscope => RadToDegree(value),
MotionInputId.Accelerometer => GsToMs2(value),
_ => value
};
}
}
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
public void SetConfiguration(InputConfig configuration)
{
lock (_userMappingLock)
{
_configuration = (StandardControllerInputConfig)configuration;
_buttonsUserMapping.Clear();
// First update sticks
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
switch (_gamepadType)
{
case SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick,
(GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
break;
case SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick,
(GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
break;
default:
throw new ArgumentOutOfRangeException();
}
SetTriggerThreshold(_configuration.TriggerThreshold);
}
}
public GamepadStateSnapshot GetStateSnapshot()
{
return IGamepad.GetStateSnapshot(this);
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
GamepadStateSnapshot rawState = GetStateSnapshot();
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (_buttonsUserMapping.Count == 0)
return rawState;
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{
if (!entry.IsValid)
continue;
// Do not touch state of button already pressed
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
}
return result;
}
private static float ConvertRawStickValue(short value)
{
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
return value * ConvertRate;
}
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId>
GetLogicalJoyStickConfig(StickInputId inputId)
{
switch (inputId)
{
case StickInputId.Left:
if (_configuration.RightJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Left)
return _configuration.RightJoyconStick;
else
return _configuration.LeftJoyconStick;
case StickInputId.Right:
if (_configuration.LeftJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Right)
return _configuration.LeftJoyconStick;
else
return _configuration.RightJoyconStick;
}
return null;
}
public (float, float) GetStick(StickInputId inputId)
{
if (inputId == StickInputId.Unbound)
return (0.0f, 0.0f);
if (inputId == StickInputId.Left && _gamepadType == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT ||
inputId == StickInputId.Right && _gamepadType == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT)
{
return (0.0f, 0.0f);
}
(short stickX, short stickY) = GetStickXY();
float resultX = ConvertRawStickValue(stickX);
float resultY = -ConvertRawStickValue(stickY);
if (HasConfiguration)
{
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
if (joyconStickConfig != null)
{
if (joyconStickConfig.InvertStickX)
resultX = -resultX;
if (joyconStickConfig.InvertStickY)
resultY = -resultY;
if (joyconStickConfig.Rotate90CW)
{
float temp = resultX;
resultX = resultY;
resultY = -temp;
}
}
}
return inputId switch
{
StickInputId.Left when _gamepadType == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => (resultY, -resultX),
StickInputId.Right when _gamepadType == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => (-resultY, resultX),
_ => (0.0f, 0.0f)
};
}
private (short, short) GetStickXY()
{
return (
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX),
SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY));
}
public bool IsPressed(GamepadButtonInputId inputId)
{
var button = _buttonsDriverMapping[(int)inputId];
if (button == SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID)
{
return false;
}
return SDL_GetGamepadButton(_gamepadHandle, button);
}
public static bool IsJoyCon(uint joystickId)
{
var gamepadName = SDL_GetGamepadTypeForID(joystickId);
return gamepadName is SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
or SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
}
}
}

View File

@ -0,0 +1,215 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using static SDL3.SDL;
namespace Ryujinx.Input.SDL3
{
class SDL3JoyConPair(SDL3JoyCon left, SDL3JoyCon right) : IGamepad
{
private StandardControllerInputConfig _configuration;
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
{
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
};
public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) |
(right?.Features ?? GamepadFeaturesFlag.None);
public const string Id = "JoyConPair";
private readonly Lock _userMappingLock = new();
private readonly List<ButtonMappingEntry> _buttonsUserMapping = new List<ButtonMappingEntry>(20);
string IGamepad.Id => Id;
public string Name => "* Nintendo Switch Joy-Con (L/R)";
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
public void Dispose()
{
left?.Dispose();
right?.Dispose();
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
GamepadStateSnapshot rawState = GetStateSnapshot();
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (_buttonsUserMapping.Count == 0)
return rawState;
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{
if (!entry.IsValid)
continue;
// Do not touch state of button already pressed
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
}
return result;
}
public Vector3 GetMotionData(MotionInputId inputId)
{
return inputId switch
{
MotionInputId.Accelerometer or
MotionInputId.Gyroscope => left.GetMotionData(inputId),
MotionInputId.RightAccelerometer => right.GetMotionData(MotionInputId.Accelerometer),
MotionInputId.RightGyroscope => right.GetMotionData(MotionInputId.Gyroscope),
_ => Vector3.Zero
};
}
public GamepadStateSnapshot GetStateSnapshot()
{
return IGamepad.GetStateSnapshot(this);
}
public (float, float) GetStick(StickInputId inputId)
{
return inputId switch
{
StickInputId.Left => left.GetStick(StickInputId.Left),
StickInputId.Right => right.GetStick(StickInputId.Right),
_ => (0, 0)
};
}
public bool IsPressed(GamepadButtonInputId inputId)
{
return left.IsPressed(inputId) || right.IsPressed(inputId);
}
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (lowFrequency != 0)
{
right.Rumble(lowFrequency, lowFrequency, durationMs);
}
if (highFrequency != 0)
{
left.Rumble(highFrequency, highFrequency, durationMs);
}
if (lowFrequency == 0 && highFrequency == 0)
{
left.Rumble(0, 0, durationMs);
right.Rumble(0, 0, durationMs);
}
}
public void SetConfiguration(InputConfig configuration)
{
lock (_userMappingLock)
{
_configuration = (StandardControllerInputConfig)configuration;
_buttonsUserMapping.Clear();
// First update sticks
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
// Then left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick,
(GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight,
(GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0,
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick,
(GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1,
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
left.SetConfiguration(configuration);
right.SetConfiguration(configuration);
}
}
public void SetTriggerThreshold(float triggerThreshold)
{
}
public static bool IsCombinable(Dictionary<uint, string> gamepadsInstanceIdsMapping)
{
var gamepadTypes = gamepadsInstanceIdsMapping.Keys.Select(SDL_GetGamepadTypeForID).ToArray();
return gamepadTypes.Contains(SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) &&
gamepadTypes.Contains(SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT);
}
public static IGamepad GetGamepad(Dictionary<uint, string> gamepadsInstanceIdsMapping)
{
var leftPair =
gamepadsInstanceIdsMapping.FirstOrDefault(pair =>
SDL_GetGamepadTypeForID(pair.Key) == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT);
var rightPair =
gamepadsInstanceIdsMapping.FirstOrDefault(pair =>
SDL_GetGamepadTypeForID(pair.Key) == SDL_GamepadType.SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT);
if (leftPair.Key == 0 || rightPair.Key == 0)
{
return null;
}
return new SDL3JoyConPair(new SDL3JoyCon(leftPair.Key, leftPair.Value),
new SDL3JoyCon(rightPair.Key, rightPair.Value));
}
}
}

View File

@ -1,18 +1,18 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using static SDL2.SDL;
using static SDL3.SDL;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Input.SDL2
namespace Ryujinx.Input.SDL3
{
class SDL2Keyboard : IKeyboard
class SDL3Keyboard : IKeyboard
{
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
{
@ -22,7 +22,7 @@ namespace Ryujinx.Input.SDL2
private readonly Lock _userMappingLock = new();
#pragma warning disable IDE0052 // Remove unread private member
private readonly SDL2KeyboardDriver _driver;
private readonly SDL3KeyboardDriver _driver;
#pragma warning restore IDE0052
private StandardKeyboardInputConfig _configuration;
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
@ -116,32 +116,32 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_KP_PLUS,
SDL_Keycode.SDLK_KP_DECIMAL,
SDL_Keycode.SDLK_KP_ENTER,
SDL_Keycode.SDLK_a,
SDL_Keycode.SDLK_b,
SDL_Keycode.SDLK_c,
SDL_Keycode.SDLK_d,
SDL_Keycode.SDLK_e,
SDL_Keycode.SDLK_f,
SDL_Keycode.SDLK_g,
SDL_Keycode.SDLK_h,
SDL_Keycode.SDLK_i,
SDL_Keycode.SDLK_j,
SDL_Keycode.SDLK_k,
SDL_Keycode.SDLK_l,
SDL_Keycode.SDLK_m,
SDL_Keycode.SDLK_n,
SDL_Keycode.SDLK_o,
SDL_Keycode.SDLK_p,
SDL_Keycode.SDLK_q,
SDL_Keycode.SDLK_r,
SDL_Keycode.SDLK_s,
SDL_Keycode.SDLK_t,
SDL_Keycode.SDLK_u,
SDL_Keycode.SDLK_v,
SDL_Keycode.SDLK_w,
SDL_Keycode.SDLK_x,
SDL_Keycode.SDLK_y,
SDL_Keycode.SDLK_z,
SDL_Keycode.SDLK_A,
SDL_Keycode.SDLK_B,
SDL_Keycode.SDLK_C,
SDL_Keycode.SDLK_D,
SDL_Keycode.SDLK_E,
SDL_Keycode.SDLK_F,
SDL_Keycode.SDLK_G,
SDL_Keycode.SDLK_H,
SDL_Keycode.SDLK_I,
SDL_Keycode.SDLK_J,
SDL_Keycode.SDLK_K,
SDL_Keycode.SDLK_L,
SDL_Keycode.SDLK_M,
SDL_Keycode.SDLK_N,
SDL_Keycode.SDLK_O,
SDL_Keycode.SDLK_P,
SDL_Keycode.SDLK_Q,
SDL_Keycode.SDLK_R,
SDL_Keycode.SDLK_S,
SDL_Keycode.SDLK_T,
SDL_Keycode.SDLK_U,
SDL_Keycode.SDLK_V,
SDL_Keycode.SDLK_W,
SDL_Keycode.SDLK_X,
SDL_Keycode.SDLK_Y,
SDL_Keycode.SDLK_Z,
SDL_Keycode.SDLK_0,
SDL_Keycode.SDLK_1,
SDL_Keycode.SDLK_2,
@ -152,14 +152,14 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_7,
SDL_Keycode.SDLK_8,
SDL_Keycode.SDLK_9,
SDL_Keycode.SDLK_BACKQUOTE,
SDL_Keycode.SDLK_BACKQUOTE,
SDL_Keycode.SDLK_GRAVE,
SDL_Keycode.SDLK_GRAVE,
SDL_Keycode.SDLK_MINUS,
SDL_Keycode.SDLK_PLUS,
SDL_Keycode.SDLK_LEFTBRACKET,
SDL_Keycode.SDLK_RIGHTBRACKET,
SDL_Keycode.SDLK_SEMICOLON,
SDL_Keycode.SDLK_QUOTE,
SDL_Keycode.SDLK_APOSTROPHE,
SDL_Keycode.SDLK_COMMA,
SDL_Keycode.SDLK_PERIOD,
SDL_Keycode.SDLK_SLASH,
@ -169,7 +169,7 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_0
];
public SDL2Keyboard(SDL2KeyboardDriver driver, string id, string name)
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
{
_driver = driver;
Id = id;
@ -193,55 +193,54 @@ namespace Ryujinx.Input.SDL2
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ToSDL2Scancode(Key key)
private static int ToSDL3Scancode(Key key)
{
if (key >= Key.Unknown && key <= Key.Menu)
{
return -1;
}
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key]);
IntPtr modstate = (int)SDL_Keymod.SDL_KMOD_NONE;
return (int)SDL_GetScancodeFromKey((uint)_keysDriverMapping[(int)key], modstate);
}
private static SDL_Keymod GetKeyboardModifierMask(Key key)
{
return key switch
{
Key.ShiftLeft => SDL_Keymod.KMOD_LSHIFT,
Key.ShiftRight => SDL_Keymod.KMOD_RSHIFT,
Key.ControlLeft => SDL_Keymod.KMOD_LCTRL,
Key.ControlRight => SDL_Keymod.KMOD_RCTRL,
Key.AltLeft => SDL_Keymod.KMOD_LALT,
Key.AltRight => SDL_Keymod.KMOD_RALT,
Key.WinLeft => SDL_Keymod.KMOD_LGUI,
Key.WinRight => SDL_Keymod.KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL2.
_ => SDL_Keymod.KMOD_NONE,
Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL3.
_ => SDL_Keymod.SDL_KMOD_NONE,
};
}
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
{
ReadOnlySpan<byte> rawKeyboardState;
Span<SDLBool> rawKeyboardState;
SDL_Keymod rawKeyboardModifierState = SDL_GetModState();
unsafe
{
nint statePtr = SDL_GetKeyboardState(out int numKeys);
rawKeyboardState = new ReadOnlySpan<byte>((byte*)statePtr, numKeys);
rawKeyboardState = SDL_GetKeyboardState(out int numKeys);
}
bool[] keysState = new bool[(int)Key.Count];
for (Key key = 0; key < Key.Count; key++)
{
int index = ToSDL2Scancode(key);
int index = ToSDL3Scancode(key);
if (index == -1)
{
SDL_Keymod modifierMask = GetKeyboardModifierMask(key);
if (modifierMask == SDL_Keymod.KMOD_NONE)
if (modifierMask == SDL_Keymod.SDL_KMOD_NONE)
{
continue;
}
@ -250,7 +249,7 @@ namespace Ryujinx.Input.SDL2
}
else
{
keysState[(int)key] = rawKeyboardState[index] == 1;
keysState[(int)key] = rawKeyboardState[index];
}
}
@ -386,11 +385,6 @@ namespace Ryujinx.Input.SDL2
}
}
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Keyboard");
}
public void SetTriggerThreshold(float triggerThreshold)
{
// No operations

View File

@ -1,19 +1,18 @@
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Input.SDL2
namespace Ryujinx.Input.SDL3
{
public class SDL2KeyboardDriver : IGamepadDriver
public class SDL3KeyboardDriver : IGamepadDriver
{
public SDL2KeyboardDriver()
public SDL3KeyboardDriver()
{
SDL2Driver.Instance.Initialize();
SDL3Driver.Instance.Initialize();
}
public string DriverName => "SDL2";
public string DriverName => "SDL3";
private static readonly string[] _keyboardIdentifers = ["0"];
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
@ -33,7 +32,7 @@ namespace Ryujinx.Input.SDL2
{
if (disposing)
{
SDL2Driver.Instance.Dispose();
SDL3Driver.Instance.Dispose();
}
}
@ -50,15 +49,7 @@ namespace Ryujinx.Input.SDL2
return null;
}
return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards");
}
public IEnumerable<IGamepad> GetGamepads()
{
foreach (string keyboardId in _keyboardIdentifers)
{
yield return GetGamepad(keyboardId);
}
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
}
}
}

View File

@ -1,20 +1,19 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using System;
using System.Drawing;
using System.Numerics;
namespace Ryujinx.Input.SDL2
namespace Ryujinx.Input.SDL3
{
public class SDL2Mouse : IMouse
public class SDL3Mouse : IMouse
{
private SDL2MouseDriver _driver;
private SDL3MouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0";
public string Name => "SDL2Mouse";
public string Name => "SDL3Mouse";
public bool IsConnected => true;
@ -22,7 +21,7 @@ namespace Ryujinx.Input.SDL2
Size IMouse.ClientSize => _driver.GetClientSize();
public SDL2Mouse(SDL2MouseDriver driver)
public SDL3Mouse(SDL3MouseDriver driver)
{
_driver = driver;
}
@ -77,11 +76,6 @@ namespace Ryujinx.Input.SDL2
throw new NotImplementedException();
}
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Mouse");
}
public void SetTriggerThreshold(float triggerThreshold)
{
throw new NotImplementedException();

View File

@ -1,16 +1,15 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using System.Runtime.CompilerServices;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Input.SDL2
namespace Ryujinx.Input.SDL3
{
public class SDL2MouseDriver : IGamepadDriver
public class SDL3MouseDriver : IGamepadDriver
{
private const int CursorHideIdleTime = 5; // seconds
@ -25,14 +24,14 @@ namespace Ryujinx.Input.SDL2
public Vector2 Scroll { get; private set; }
public Size ClientSize;
public SDL2MouseDriver(HideCursorMode hideCursorMode)
public SDL3MouseDriver(HideCursorMode hideCursorMode)
{
PressedButtons = new bool[(int)MouseButton.Count];
_hideCursorMode = hideCursorMode;
if (_hideCursorMode == HideCursorMode.Always)
{
if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE)
if (!SDL_HideCursor())
{
Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor.");
}
@ -51,7 +50,7 @@ namespace Ryujinx.Input.SDL2
public void UpdatePosition()
{
_ = SDL_GetMouseState(out int posX, out int posY);
_ = SDL_GetMouseState(out float posX, out float posY);
Vector2 position = new(posX, posY);
if (CurrentPosition != position)
@ -76,7 +75,7 @@ namespace Ryujinx.Input.SDL2
{
if (!_isHidden)
{
if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE)
if (!SDL_HideCursor())
{
Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor.");
}
@ -88,7 +87,7 @@ namespace Ryujinx.Input.SDL2
{
if (_isHidden)
{
if (SDL_ShowCursor(SDL_ENABLE) != SDL_ENABLE)
if (!SDL_HideCursor())
{
Logger.Error?.PrintMsg(LogClass.Application, "Failed to enable the cursor.");
}
@ -100,15 +99,16 @@ namespace Ryujinx.Input.SDL2
public void Update(SDL_Event evnt)
{
switch (evnt.type)
var type = (SDL_EventType)evnt.type;
switch (type)
{
case SDL_EventType.SDL_MOUSEBUTTONDOWN:
case SDL_EventType.SDL_MOUSEBUTTONUP:
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP:
uint rawButton = evnt.button.button;
if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
{
PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN;
PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = type == SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN;
CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
}
@ -116,13 +116,13 @@ namespace Ryujinx.Input.SDL2
break;
// NOTE: On Linux using Wayland mouse motion events won't be received at all.
case SDL_EventType.SDL_MOUSEMOTION:
case SDL_EventType.SDL_EVENT_MOUSE_MOTION:
CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
_lastCursorMoveTime = Stopwatch.GetTimestamp();
break;
case SDL_EventType.SDL_MOUSEWHEEL:
case SDL_EventType.SDL_EVENT_MOUSE_WHEEL:
Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
break;
@ -144,7 +144,7 @@ namespace Ryujinx.Input.SDL2
return ClientSize;
}
public string DriverName => "SDL2";
public string DriverName => "SDL3";
public event Action<string> OnGamepadConnected
{
@ -162,11 +162,9 @@ namespace Ryujinx.Input.SDL2
public IGamepad GetGamepad(string id)
{
return new SDL2Mouse(this);
return new SDL3Mouse(this);
}
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
public void Dispose()
{
if (_isDisposed)

View File

@ -24,10 +24,5 @@ namespace Ryujinx.Input
/// <remarks>Also named sixaxis</remarks>
/// </summary>
Motion,
/// <summary>
/// The LED on the back of modern PlayStation controllers (DualSense &amp; DualShock 4).
/// </summary>
Led,
}
}

View File

@ -269,6 +269,7 @@ namespace Ryujinx.Input.HLE
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
{
_leftMotionInput = new MotionInput();
_rightMotionInput = new MotionInput();
}
else
{
@ -301,7 +302,20 @@ namespace Ryujinx.Input.HLE
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
_rightMotionInput = _leftMotionInput;
if (gamepad.Id== "JoyConPair")
{
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.RightAccelerometer);
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.RightGyroscope);
rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y);
rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y);
_rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
}
else
{
_rightMotionInput = _leftMotionInput;
}
}
}
}
@ -336,6 +350,7 @@ namespace Ryujinx.Input.HLE
// Reset states
State = default;
_leftMotionInput = null;
_rightMotionInput = null;
}
}
@ -380,6 +395,11 @@ namespace Ryujinx.Input.HLE
return state;
}
public static JoystickPosition GetJoystickPosition(float x, float y, float deadzone, float range)
{
return ClampToCircle(ApplyDeadzone(x, y,deadzone), range);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static JoystickPosition ApplyDeadzone(float x, float y, float deadzone)

View File

@ -65,15 +65,6 @@ namespace Ryujinx.Input
/// <param name="configuration">The configuration of the gamepad</param>
void SetConfiguration(InputConfig configuration);
/// <summary>
/// Set the LED on the gamepad to a given color.
/// </summary>
/// <remarks>Does nothing on a controller without LED functionality.</remarks>
/// <param name="packedRgb">The packed RGB integer.</param>
void SetLed(uint packedRgb);
public void ClearLed() => SetLed(0);
/// <summary>
/// Starts a rumble effect on the gamepad.
/// </summary>

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Input
{
@ -34,11 +33,6 @@ namespace Ryujinx.Input
/// <param name="id">The unique id of the gamepad</param>
/// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns>
IGamepad GetGamepad(string id);
/// <summary>
/// Returns an <see cref="IEnumerable{T}"/> of the connected gamepads.
/// </summary>
IEnumerable<IGamepad> GetGamepads();
/// <summary>
/// Clear the internal state of the driver.

View File

@ -21,5 +21,17 @@ namespace Ryujinx.Input
/// </summary>
/// <remarks>Values are in degrees</remarks>
Gyroscope,
/// <summary>
/// Right accelerometer.
/// </summary>
/// <remarks>Values are in m/s^2</remarks>
RightAccelerometer,
/// <summary>
/// Right gyroscope.
/// </summary>
/// <remarks>Values are in degrees</remarks>
RightGyroscope
}
}

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Ryujinx.SDL3_CS</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<UseCurrentRuntimeIdentifier>true</UseCurrentRuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<None Include="runtimes/win-x64/native/libSDL3.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>libSDL3.dll</Link>
</None>
<None Include="runtimes/linux-x64/native/libSDL3.so" Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>libSDL3.so</Link>
</None>
<None Include="runtimes/linux-arm64/native/libSDL3.so" Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>libSDL3.so</Link>
</None>
<None Include="runtimes/osx-arm64/native/libSDL3.dylib" Condition="'$(RuntimeIdentifier)' == 'osx-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>libSDL3.dylib</Link>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="runtimes\win-x64\native\" />
</ItemGroup>
</Project>

1
src/Ryujinx.SDL3-CS/external/SDL vendored Submodule

@ -0,0 +1 @@
Subproject commit fa8c0f0552cc232ab454b4b5ce787e63617ef7ab

118
src/Ryujinx.SDL3-CS/external/build.sh vendored Executable file
View File

@ -0,0 +1,118 @@
#!/bin/bash
set -e
pushd "$(dirname "$0")"
# Check if environment variables are defined
if [[ -z $NAME || -z $RUNNER_OS || -z $FLAGS ]]; then
echo "One or more required environment variables are not defined."
exit 1
fi
SUDO=$(which sudo || exit 0)
export DEBIAN_FRONTEND=noninteractive
if [[ $RUNNER_OS == 'Linux' ]]; then
# Setup Linux dependencies
if [[ $TARGET_APT_ARCH == :i386 ]]; then
$SUDO dpkg --add-architecture i386
fi
$SUDO apt-get update -y -qq
if [[ $TARGET_APT_ARCH == :i386 ]]; then
# Workaround GitHub's ubuntu-20.04 image issue <https://github.com/actions/virtual-environments/issues/4589>
$SUDO apt-get install -y --allow-downgrades libpcre2-8-0=10.34-7
fi
if [[ $NAME != 'linux-x86' && $NAME != 'linux-x64' ]]; then
GCC="gcc"
GPP="g++"
else
GCC="gcc-multilib"
GPP="g++-multilib"
fi
$SUDO apt-get install -y \
$GCC \
$GPP \
git \
cmake \
ninja-build \
wayland-scanner++ \
wayland-protocols \
meson \
pkg-config$TARGET_APT_ARCH \
libasound2-dev$TARGET_APT_ARCH \
libdbus-1-dev$TARGET_APT_ARCH \
libegl1-mesa-dev$TARGET_APT_ARCH \
libgl1-mesa-dev$TARGET_APT_ARCH \
libgles2-mesa-dev$TARGET_APT_ARCH \
libglu1-mesa-dev$TARGET_APT_ARCH \
libgtk-3-dev$TARGET_APT_ARCH \
libibus-1.0-dev$TARGET_APT_ARCH \
libpango1.0-dev$TARGET_APT_ARCH \
libpulse-dev$TARGET_APT_ARCH \
libsndio-dev$TARGET_APT_ARCH \
libudev-dev$TARGET_APT_ARCH \
libwayland-dev$TARGET_APT_ARCH \
libx11-dev$TARGET_APT_ARCH \
libxcursor-dev$TARGET_APT_ARCH \
libxext-dev$TARGET_APT_ARCH \
libxi-dev$TARGET_APT_ARCH \
libxinerama-dev$TARGET_APT_ARCH \
libxkbcommon-dev$TARGET_APT_ARCH \
libxrandr-dev$TARGET_APT_ARCH \
libxss-dev$TARGET_APT_ARCH \
libxt-dev$TARGET_APT_ARCH \
libxv-dev$TARGET_APT_ARCH \
libxxf86vm-dev$TARGET_APT_ARCH \
libdrm-dev$TARGET_APT_ARCH \
libgbm-dev$TARGET_APT_ARCH \
libpulse-dev$TARGET_APT_ARCH
if [[ $TARGET_APT_ARCH != :i386 ]]; then
# Build libdecor.
# This is required so that window decorations can work on wayland.
# The support will only be enabled in SDL, but we're not shipping the libdecor binaries
# because making them work from a c# app as everything else does (via runtimes) is too difficult.
# Also skip i386 because attempting to support this for i386 is a pain.
# Special shoutouts to gnome for refusing to support server-side decorations.
git clone https://gitlab.freedesktop.org/libdecor/libdecor.git
cd libdecor
git checkout 0.2.2
meson build --buildtype release
$SUDO meson install -C build
cd ..
fi
fi
# Build SDL
pushd SDL
git reset --hard HEAD || echo "Failed to clean up the repository"
if [[ $RUNNER_OS == 'Windows' ]]; then
echo "Patching SDL to not include gameinput.h"
sed -i 's/#include <gameinput.h>/#_include <gameinput.h>/g' CMakeLists.txt
fi
cmake -B build $FLAGS -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSDL_SHARED_ENABLED_BY_DEFAULT=ON -DSDL_STATIC_ENABLED_BY_DEFAULT=ON
cmake --build build/ --config Release
$SUDO cmake --install build/ --prefix install_output --config Release
popd
# Ensure the directory exists
mkdir -p ../runtimes/$NAME/native
# Move build lib into correct folders
if [[ $RUNNER_OS == 'Windows' ]]; then
cp SDL/install_output/bin/SDL3.dll ../runtimes/$NAME/native/libSDL3.dll
elif [[ $RUNNER_OS == 'Linux' ]]; then
cp SDL/install_output/lib/libSDL3.so ../runtimes/$NAME/native/libSDL3.so
elif [[ $RUNNER_OS == 'macOS' ]]; then
cp SDL/install_output/lib/libSDL3.dylib ../runtimes/$NAME/native/libSDL3.dylib
fi
popd

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>SDL3.framework/SDL3</string>
<key>LibraryIdentifier</key>
<string>tvos-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>SDL3.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3.framework/Versions/A/SDL3</string>
<key>LibraryIdentifier</key>
<string>macos-arm64_x86_64</string>
<key>LibraryPath</key>
<string>SDL3.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>macos</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3.framework/SDL3</string>
<key>LibraryIdentifier</key>
<string>tvos-arm64</string>
<key>LibraryPath</key>
<string>SDL3.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3.framework/SDL3</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>SDL3.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3.framework/SDL3</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>SDL3.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

Binary file not shown.

View File

@ -1,15 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.SDL2-CS" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.SDL3-CS\Ryujinx.SDL3-CS.csproj" />
</ItemGroup>
</Project>

View File

@ -1,24 +1,24 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.SDL2.Common
namespace Ryujinx.SDL3.Common
{
public class SDL2Driver : IDisposable
public class SDL3Driver : IDisposable
{
private static SDL2Driver _instance;
private static SDL3Driver _instance;
public static SDL2Driver Instance
public static SDL3Driver Instance
{
get
{
_instance ??= new SDL2Driver();
_instance ??= new SDL3Driver();
return _instance;
}
@ -26,22 +26,22 @@ namespace Ryujinx.SDL2.Common
public static Action<Action> MainThreadDispatcher { get; set; }
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_GAMEPAD | SDL_InitFlags.SDL_INIT_AUDIO |
SDL_InitFlags.SDL_INIT_VIDEO;
private bool _isRunning;
private uint _refereceCount;
private Thread _worker;
public event Action<int, int> OnJoyStickConnected;
public event Action<int> OnJoystickDisconnected;
public event Action<uint> OnJoyStickConnected;
public event Action<uint> OnJoystickDisconnected;
public event Action<uint, SDL_JoyBatteryEvent> OnJoyBatteryUpdated;
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers;
private readonly Lock _lock = new();
private SDL2Driver() { }
private const string SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS";
private SDL3Driver() { }
public void Initialize()
{
@ -55,21 +55,20 @@ namespace Ryujinx.SDL2.Common
}
SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
// NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them.
// We disable this behavior for now.
//
//
// // NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them.
// // We disable this behavior for now.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0");
if (SDL_Init(SdlInitFlags) != 0)
if (!SDL_Init(SdlInitFlags))
{
string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\"";
string errorMessage = $"SDL3 initialization failed with error \"{SDL_GetError()}\"";
Logger.Error?.Print(LogClass.Application, errorMessage);
@ -77,30 +76,32 @@ namespace Ryujinx.SDL2.Common
}
// First ensure that we only enable joystick events (for connected/disconnected).
if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE)
if (!SDL_GamepadEventsEnabled())
{
Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events.");
Logger.Error?.PrintMsg(LogClass.Application,
"Couldn't change the state of game controller events.");
}
if (SDL_JoystickEventState(SDL_ENABLE) < 0)
if (!SDL_JoystickEventsEnabled())
{
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}");
Logger.Error?.PrintMsg(LogClass.Application,
$"Failed to enable joystick event polling: {SDL_GetError()}");
}
// Disable all joysticks information, we don't need them no need to flood the event queue for that.
SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_AXIS_MOTION, false);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BALL_MOTION, false);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_HAT_MOTION, false);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_DOWN, false);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_UP, false);
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_GAMEPAD_SENSOR_UPDATE, false);
string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt");
if (File.Exists(gamepadDbPath))
{
SDL_GameControllerAddMappingsFromFile(gamepadDbPath);
SDL_AddGamepadMappingsFromFile(gamepadDbPath);
}
_registeredWindowHandlers = new ConcurrentDictionary<uint, Action<SDL_Event>>();
@ -122,29 +123,29 @@ namespace Ryujinx.SDL2.Common
private void HandleSDLEvent(ref SDL_Event evnt)
{
if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED)
if (evnt.type == (uint)SDL_EventType.SDL_EVENT_GAMEPAD_ADDED)
{
int deviceId = evnt.cbutton.which;
// SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system.
int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId);
if (instanceId == -1)
{
return;
}
var instanceId = evnt.jdevice.which;
Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}");
OnJoyStickConnected?.Invoke(deviceId, instanceId);
OnJoyStickConnected?.Invoke(instanceId);
}
else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED)
else if (evnt.type == (uint)SDL_EventType.SDL_EVENT_GAMEPAD_REMOVED)
{
Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}");
var instanceId = evnt.jdevice.which;
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {instanceId}");
OnJoystickDisconnected?.Invoke(instanceId);
}
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
else if (evnt.type == (uint)SDL_EventType.SDL_EVENT_JOYSTICK_BATTERY_UPDATED)
{
OnJoyBatteryUpdated?.Invoke(evnt.jbattery.which, evnt.jbattery);
}
else if (evnt.type is >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST and <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST
or (uint)SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN
or (uint)SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP)
{
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
{
@ -158,12 +159,11 @@ namespace Ryujinx.SDL2.Common
const int WaitTimeMs = 10;
using ManualResetEventSlim waitHandle = new(false);
while (_isRunning)
{
MainThreadDispatcher?.Invoke(() =>
{
while (SDL_PollEvent(out SDL_Event evnt) != 0)
while (SDL_PollEvent(out SDL_Event evnt))
{
HandleSDLEvent(ref evnt);
}

View File

@ -9,7 +9,7 @@ using LibHac.Ns;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Audio.Integration;
using Ryujinx.Ava.Common;
@ -502,8 +502,6 @@ namespace Ryujinx.Ava
_renderingThread.Start();
_viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
Rainbow.Enable();
MainLoop();
@ -590,17 +588,6 @@ namespace Ryujinx.Ava
return;
}
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
gamepad?.Dispose();
}
DiscordIntegrationModule.GuestAppStartedAt = null;
Rainbow.Disable();
Rainbow.Reset();
_isStopped = true;
Stop();
}
@ -977,7 +964,7 @@ namespace Ryujinx.Ava
{
List<AudioBackend> availableBackends =
[
AudioBackend.SDL2,
AudioBackend.SDL3,
AudioBackend.SoundIo,
AudioBackend.OpenAl,
AudioBackend.Dummy
@ -1016,7 +1003,7 @@ namespace Ryujinx.Ava
deviceDriver = currentBackend switch
{
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
AudioBackend.SDL3 => InitializeAudioBackend<SDL3HardwareDeviceDriver>(AudioBackend.SDL3, nextBackend),
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
_ => new DummyHardwareDeviceDriver(),

View File

@ -0,0 +1,346 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
width="1000.8"
height="1300"
x="0"
y="0"
version="1.1"
viewBox="0 0 1000.8 1300"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1"><filter
id="filter3994"
color-interpolation-filters="sRGB"
y="-0.059999999"
height="1.12"
x="-inf"
width="inf"><feGaussianBlur
stdDeviation="0.95"
id="feGaussianBlur3996" /></filter><filter
id="filter3994-1"
color-interpolation-filters="sRGB"
y="-0.059999999"
height="1.12"
x="-inf"
width="inf"><feGaussianBlur
stdDeviation="0.95"
id="feGaussianBlur3996-5" /></filter></defs><metadata
id="metadata85" /><g
id="left"
transform="matrix(8.7009658,0,0,8.7009658,119.872,-3.6543895e-8)"><path
id="path4182"
d="m 33.122745,22.256318 v 1.601973 c 0,0.13798 -0.164307,0.292312 -0.292312,0.292312 h -5.133234 c -0.161131,0 -0.356659,-0.160814 -0.356659,-0.356632 V 22.24465 Z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4186"
d="m 23.902465,22.949553 c 0,0.04675 0,1.145911 0,1.145911 0,0.478393 0.291942,0.666512 0.666512,0.666512 2.017951,0.177932 4.469606,0.167507 6.536399,0 0.347319,0 0.687917,-0.3302 0.684054,-0.684054 v -1.397318 h -7.886965 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4190"
d="m 7.3451308,22.949553 c 0,0.04675 0,1.145911 0,1.145911 0,0.478393 0.291941,0.666512 0.666485,0.666512 2.0179782,0.177932 4.4696072,0.167507 6.5364262,0 0.347319,0 0.687917,-0.3302 0.684027,-0.684054 V 22.680604 H 7.3451308 Z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path3828"
d="m 39.503431,63.613187 h -1.110837 v 9.132265 h 1.08745 c 0.379812,0 0.572961,-0.264917 0.572961,-0.572958 v -8.079893 c 0,-0.292343 -0.213908,-0.479414 -0.549574,-0.479414 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 39.503431,117.52619 h -1.110837 v -9.13226 h 1.08745 c 0.379812,0 0.572961,0.26491 0.572961,0.57295 v 8.0799 c 0,0.29234 -0.213908,0.47941 -0.549574,0.47941 z"
id="path3830" /><path
id="path3826"
d="m 39.643747,44.647073 h -3.706693 v 88.793887 h 1.734386 v 3.68763 c 0,0.41206 0.346123,1.10727 0.639284,1.10727 h 1.291346 c 0.23689,0 0.450287,-0.18294 0.450287,-0.45028 v -17.26235 c 0,-2.28046 -1.425903,-2.30845 -1.425903,-3.0285 v -9.15565 c 0,-0.64968 1.391472,-0.80953 1.391472,-2.66601 V 75.364695 c 0,-1.840243 -1.391472,-1.964013 -1.391472,-2.666016 l 0.03307,-9.003641 c 0,-0.620152 1.391473,-1.097836 1.391473,-2.747865 l -7.93e-4,-15.823609 c 0.0024,-0.259744 -0.125479,-0.476491 -0.406334,-0.476491 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path3832"
d="m 30.154591,38.984701 v -1.107942 c 0,-0.332553 -0.339662,-0.7028 -0.7028,-0.7028 h -6.945312 c -10.713919,0 -20.3040802,8.641797 -21.9475795,14.775418 -0.07498,0.279813 -0.101862,0.841899 0.385869,0.841899 h 1.2336173 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path3925"
d="m 36.251186,39.39185 v 109.19024 c 0,0.2708 -0.133853,0.47543 -0.475422,0.47543 H 22.641681 c -13.6735372,0 -22.5093895,-12.09084 -22.5093895,-22.50939 V 60.947123 c 0,-15.315427 15.0189105,-22.415219 22.4152215,-22.415219 h 12.909171 c 0.657781,0 0.794502,0.361942 0.794502,0.859946 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><circle
id="path3871"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.236444"
cy="90.101624"
r="4.0085444" /><circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3873"
cx="19.236444"
cy="106.67232"
r="4.0085444" /><circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3875"
transform="rotate(90)"
cx="98.386986"
cy="-10.951083"
r="4.0085444" /><circle
transform="rotate(90)"
id="path3877"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="98.386986"
cy="-27.521791"
r="4.0085444" /><rect
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="Back"
width="5.8704429"
height="1.6867187"
x="27.454165"
y="48.675072"
class="button" /><circle
id="path3907-1"
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.268621"
cy="67.195892"
r="8.4832029" /><g
id="LeftStick"
class="button"
style="display:inline"><circle
id="path3907"
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.268621"
cy="67.195892"
r="8.4832029" /><circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3909"
cx="19.268621"
cy="67.254738"
r="5.9489326" /><path
id="path3911"
d="m 18.961234,60.286636 c 5.3e-5,-0.567529 0.0016,-0.902846 0.0042,-0.920872 0.0079,-0.0547 0.03481,-0.106976 0.07652,-0.148682 0.12573,-0.12573 0.339551,-0.08842 0.417084,0.07277 0.02906,0.06043 0.02704,-0.01283 0.02711,0.983676 l 6.1e-5,0.900091 -0.185587,-2.17e-4 c -0.102071,-1.19e-4 -0.220205,0.0011 -0.262517,0.0028 l -0.07693,0.003 7.9e-5,-0.892521 z"
style="fill:#000000;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path3913"
d="m 19.194179,75.214639 c -0.103042,-0.01154 -0.191744,-0.08817 -0.224182,-0.193662 -0.0065,-0.02098 -0.0071,-0.08735 -0.0082,-0.857049 l -0.0013,-0.83431 0.06106,0.0034 c 0.03359,0.0019 0.151787,0.0034 0.262666,0.0034 h 0.201596 l -0.0011,0.835091 -0.0011,0.835091 -0.0092,0.02687 c -0.02764,0.08078 -0.09145,0.144222 -0.172333,0.171315 -0.02641,0.0088 -0.07656,0.01347 -0.107987,0.0099 v 0 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path3915"
d="m 25.349998,67.27271 c 0,-0.09385 -0.0016,-0.212042 -0.0034,-0.262662 l -0.0034,-0.09203 0.854977,0.0011 0.854977,0.0011 0.03032,0.01109 c 0.114072,0.04167 0.18497,0.146846 0.177263,0.262977 -0.0043,0.06436 -0.02954,0.121094 -0.07432,0.166859 -0.03514,0.03591 -0.06816,0.05496 -0.129127,0.07451 -0.01318,0.0042 -0.188249,0.0056 -0.861963,0.0065 l -0.845428,0.0013 v -0.170629 0 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path3917"
d="m 11.413899,67.438823 c -0.0569,-0.0075 -0.112456,-0.03622 -0.152974,-0.07906 -0.140957,-0.149016 -0.06985,-0.386953 0.130236,-0.435806 0.01865,-0.0046 0.182329,-0.0056 0.912762,-0.0057 l 0.890024,-1.49e-4 -0.0033,0.08785 c -0.0019,0.04832 -0.0033,0.166452 -0.0033,0.26252 v 0.174667 l -0.874363,-5.29e-4 c -0.480901,-2.65e-4 -0.885529,-0.0021 -0.899171,-0.0038 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g><rect
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3919"
width="6.8130207"
height="6.647656"
x="21.930986"
y="116.11076"
ry="0.89296871" /><circle
style="fill:#000000;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3921"
cx="25.312696"
cy="119.49245"
r="2.3399088" /><g
id="dpad"><path
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="DpadRight"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
transform="matrix(0.29245602,0,0,0.29245602,-13.019911,-152.18812)" /><path
transform="matrix(-0.29245602,0,0,0.29245602,51.534858,-152.18812)"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
id="DpadLeft"
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
transform="matrix(0,-0.29245602,0.29245602,0,-231.35192,130.71823)"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
id="DpadUp"
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="DpadDown"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
transform="matrix(0,0.29245602,0.29245602,0,-231.35192,66.16345)" /></g><path
id="path4046"
d="m 63.771731,94.018495 c -0.04677,0 -1.145916,0 -1.145916,0 -0.478388,0 -0.666504,-0.29194 -0.666504,-0.66651 -0.177953,-2.01795 -0.167505,-4.469602 0,-6.536416 0,-0.34731 0.330195,-0.687918 0.684043,-0.684039 h 1.397318 v 7.886965 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 63.771731,102.29717 c -0.04677,0 -1.145916,0 -1.145916,0 -0.478388,0 -0.666504,-0.29194 -0.666504,-0.6665 -0.177953,-2.017963 -0.167505,-4.469615 0,-6.536415 0,-0.34732 0.330195,-0.68793 0.684043,-0.68405 h 1.397318 v 7.886965 z"
id="path4048" /><path
id="path4000"
d="m 75.761211,40.180332 c 2.66361,0 6.929524,2.444663 10.289879,2.759557 0.71167,0 1.19269,0.71711 1.19269,1.262848 v 2.38538 c 0,0.791848 -1.14592,1.389081 -1.14592,2.081363 h -8.956871 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4002"
d="m 78.916187,62.68351 c 0,-5.087943 6.223703,-8.710414 8.301303,-13.411065 0.12093,-0.451335 -0.23601,-0.975651 -0.678,-0.975651 h -2.629293 c -4.062013,0 -3.225233,-4.668785 -5.936588,-6.746875 v 21.139517 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4004"
d="m 80.619442,54.481429 h -1.852083 v -4.960938 h 1.885156 c 0.243705,0 0.353693,0.213125 0.405143,0.405144 0.371227,1.385437 0.352412,2.876767 0,4.191992 -0.05556,0.207356 -0.194738,0.363802 -0.438216,0.363802 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4006"
d="m 87.20095,46.246273 c -4.955503,0 -5.858031,-1.554427 -7.17682,-1.554427"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4008"
d="m 72.381923,39.022719 c 0,0 -5.575442,-0.03565 -5.565888,0 0.0096,0.03565 0,-1.110837 0,-1.110837 0,-0.29836 0.221101,-0.491109 0.491106,-0.491109 h 4.548595 c 0.321982,0 0.526187,0.287943 0.526187,0.526187 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4036"
d="m 63.776512,62.176195 c -0.740701,0.206184 -2.124943,1.629344 -2.447311,3.050632 -0.421434,0 -1.005602,-0.247509 -1.005602,-0.760047 v -4.887693 c 0,-1.029377 -3.348204,-0.770353 -3.348204,0.897149 v 1.687013 c -1.440344,3.504932 -1.469731,6.635117 0,10.056016 v 1.777344 c 0,1.5485 3.317637,1.867318 3.317637,0.888957 v -4.794435 c 0,-0.695261 0.486277,-1.020768 0.979958,-0.979958 0.516763,1.247886 1.193901,2.362125 2.453283,2.991162 h 0.467722 V 62.17202 Z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><rect
ry="0.26458332"
y="116.11076"
x="62.155056"
height="6.647656"
width="6.8130207"
id="rect4044"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
rx="0.26458332" /><path
id="path4050"
d="m 63.771731,110.57584 c -0.04677,0 -1.145916,0 -1.145916,0 -0.478388,0 -0.666504,-0.29194 -0.666504,-0.6665 -0.177953,-2.01796 -0.167505,-4.46961 0,-6.53641 0,-0.34731 0.330195,-0.68792 0.684043,-0.68404 h 1.397318 v 7.88695 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><rect
y="48.675072"
x="62.34621"
height="1.6867187"
width="5.8704429"
id="rect4052"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
rx="0.25724691"
ry="0.25724691" /><path
id="path4010"
d="m 73.258901,38.730394 h -5.713257 c -2.190945,0 -3.68795,2.151269 -3.68795,4.115951 V 145.23063 c 0,2.30569 1.843881,4.04578 4.045794,4.04578 h 5.168323 c 3.238691,0 6.06869,-3.32049 6.06869,-6.06868 V 44.623686 c 0,-2.970212 -3.405764,-5.893292 -5.8816,-5.893292 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><rect
ry="1.1287988"
rx="1.1287988"
y="44.664803"
x="-76.537956"
height="93.491112"
width="10.22559"
id="rect4012"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" /><rect
ry="1.1287988"
rx="1.1287988"
y="120.92493"
x="-75.426994"
height="16.172655"
width="7.9375"
id="rect4014"
style="fill:#2e2f31;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" /><path
transform="matrix(-0.29141287,0,0,0.29141287,93.622165,249.09704)"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
id="path4016"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4018"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
transform="matrix(-0.29141287,0,0,0.29141287,93.622165,244.15264)" /><path
transform="matrix(-0.29141287,0,0,0.29141287,93.622165,239.20824)"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
id="path4020"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><rect
ry="0.53348637"
rx="0.53348637"
y="79.881424"
x="-72.450432"
height="1.9513021"
width="1.9513021"
id="rect4022"
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" /><rect
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4024"
width="1.9513021"
height="1.9513021"
x="-72.450432"
y="84.213974"
rx="0.53348637"
ry="0.53348637"
transform="scale(-1,1)" /><rect
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4026"
width="1.9513021"
height="1.9513021"
x="-72.450432"
y="88.645744"
rx="0.53348637"
ry="0.53348637"
transform="scale(-1,1)" /><rect
ry="0.53348637"
rx="0.53348637"
y="92.978294"
x="-72.450432"
height="1.9513021"
width="1.9513021"
id="rect4028"
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" /><circle
transform="scale(-1,1)"
id="path4030"
style="fill:#2e2f31;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="-71.498466"
cy="100.48306"
r="1.5743958" /><rect
ry="1.3933822"
rx="1.4264551"
y="108.258"
x="-73.442619"
height="9.2934895"
width="4.0018229"
id="rect4032"
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(-1,1)" /><rect
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4034"
width="4.0018229"
height="9.2934895"
x="-73.442619"
y="63.58707"
rx="1.4264551"
ry="1.3933822"
transform="scale(-1,1)" /><path
id="path4038"
d="m 60.336929,64.524372 v 5.126302"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4040"
d="m 61.350497,65.186661 v 4.070318"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4042"
d="m 381.46875,790.93248 v 38"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994-1)"
transform="matrix(-0.26458333,0,0,0.26458333,157.90199,-147.07481)" /><g
id="SingleRightTrigger0"><path
id="path4064"
d="m 71.505099,112.83374 h 1.144559 l -7.9e-5,0.49975 c -4e-5,0.27487 -0.0019,0.54181 -0.0035,0.59321 -0.01228,0.33592 -0.05805,0.48248 -0.190294,0.60941 -0.147082,0.14117 -0.373115,0.19975 -0.597612,0.15487 -0.125074,-0.025 -0.235143,-0.0877 -0.319156,-0.18196 -0.07718,-0.0865 -0.137531,-0.22569 -0.161803,-0.37297 -0.0054,-0.0327 -0.0066,-0.0361 -0.01082,-0.0301 -0.0026,0.004 -0.0205,0.0307 -0.03981,0.0598 -0.081,0.12257 -0.150067,0.19243 -0.287899,0.2912 -0.07999,0.0573 -0.119782,0.083 -0.421375,0.27174 l -0.255003,0.1596 -7.94e-4,-0.27267 c -5.29e-4,-0.21526 2.7e-5,-0.27353 0.0035,-0.27675 0.0024,-0.002 0.137388,-0.0932 0.299871,-0.20215 0.469347,-0.31467 0.522494,-0.35391 0.578141,-0.42691 0.0552,-0.0724 0.06888,-0.13057 0.07187,-0.30563 l 0.0019,-0.11056 H 70.838662 70.36057 v -0.22998 -0.22997 h 1.144559 z m 0.178769,1.03666 c 0.0042,0.0751 0.01164,0.1415 0.01913,0.17021 0.02265,0.0867 0.09008,0.15452 0.179001,0.18013 0.03318,0.01 0.117309,0.0135 0.154126,0.007 0.08929,-0.0151 0.15145,-0.06 0.189063,-0.13655 0.03607,-0.0734 0.0386,-0.10715 0.03873,-0.51921 l 7.7e-5,-0.27862 H 71.97211 71.680214 l 8e-6,0.25563 c 0,0.14059 0.0019,0.28507 0.0036,0.32108 z"
style="stroke:#000000;stroke-width:0.00353806;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path4066"
d="m 70.31892,111.61238 c 0.02758,-0.34989 0.150275,-0.57472 0.391093,-0.71667 0.09661,-0.0569 0.252651,-0.10728 0.374687,-0.12089 l 0.02191,-0.002 0.0021,0.012 c 0.0038,0.0221 0.04287,0.43626 0.04133,0.4378 -7.93e-4,8e-4 -0.01757,0.005 -0.03718,0.009 -0.01963,0.004 -0.055,0.0141 -0.07862,0.0219 -0.182986,0.0608 -0.284086,0.17537 -0.317138,0.35939 -0.01011,0.0563 -0.01098,0.1795 -0.0016,0.23891 0.01243,0.0793 0.0329,0.13956 0.06354,0.18693 0.04204,0.065 0.100952,0.11379 0.164135,0.13598 0.03574,0.0125 0.107465,0.0162 0.143017,0.007 0.06979,-0.0176 0.11543,-0.0597 0.157126,-0.14499 0.02925,-0.0598 0.04967,-0.12802 0.114951,-0.38399 0.05945,-0.23312 0.09148,-0.32753 0.15009,-0.4425 0.05596,-0.10976 0.132363,-0.19601 0.23001,-0.25965 0.103841,-0.0677 0.22174,-0.0992 0.351624,-0.0939 0.111916,0.005 0.215693,0.0387 0.310094,0.10225 0.06944,0.0467 0.118321,0.0959 0.165862,0.16675 0.09689,0.14446 0.144976,0.33554 0.144928,0.57598 -5.6e-5,0.26803 -0.05399,0.47124 -0.164872,0.62123 -0.02867,0.0388 -0.08962,0.0997 -0.128405,0.12841 -0.104757,0.0774 -0.22251,0.12035 -0.363342,0.13239 -0.02162,0.002 -0.03976,0.003 -0.04031,0.002 -0.0016,-0.002 -0.02178,-0.46169 -0.02048,-0.46312 7.94e-4,-7.9e-4 0.01529,-0.004 0.0327,-0.008 0.09819,-0.0214 0.177176,-0.0644 0.221345,-0.12058 0.03419,-0.0434 0.05729,-0.0998 0.07029,-0.1715 0.01037,-0.0573 0.01138,-0.18658 0.0019,-0.24714 -0.01484,-0.0949 -0.04761,-0.1723 -0.09421,-0.22263 -0.04939,-0.0534 -0.134353,-0.0688 -0.202705,-0.0368 -0.03108,0.0145 -0.06774,0.0497 -0.08965,0.0861 -0.04056,0.0673 -0.07932,0.18338 -0.131625,0.39436 -0.06314,0.25467 -0.111067,0.39943 -0.168209,0.50814 -0.09247,0.17592 -0.245912,0.29388 -0.430609,0.33102 -0.06693,0.0135 -0.116637,0.0173 -0.193069,0.0151 -0.0537,-0.002 -0.0776,-0.004 -0.111324,-0.0111 -0.186269,-0.0393 -0.3468,-0.14926 -0.442304,-0.30291 -0.06723,-0.10815 -0.110238,-0.246 -0.130572,-0.41855 -0.0054,-0.0461 -0.0099,-0.26111 -0.0064,-0.30598 v 0 z"
style="stroke:#000000;stroke-width:0.00357862;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g><g
id="SingleLeftTrigger0"><path
id="path4062"
d="m 70.291107,67.031148 c 0.01625,-0.267039 0.09544,-0.468148 0.240077,-0.60956 0.107686,-0.105286 0.255148,-0.17938 0.427839,-0.214958 0.03948,-0.0081 0.111088,-0.01982 0.112144,-0.01826 0,5.3e-4 0.0095,0.09546 0.02061,0.211246 0.01106,0.115779 0.02098,0.216483 0.02204,0.223785 0.0019,0.01167 0.0011,0.01326 -0.0053,0.01326 -0.01241,0 -0.06767,0.01376 -0.108361,0.02695 -0.193627,0.06286 -0.297113,0.190103 -0.320421,0.393944 -0.0054,0.04684 -0.0024,0.167018 0.0052,0.21064 0.02,0.115199 0.06517,0.200318 0.136514,0.257399 0.05573,0.04458 0.101222,0.06073 0.171641,0.06091 0.05019,0 0.07817,-0.0063 0.111447,-0.02553 0.02289,-0.01326 0.05459,-0.04465 0.07293,-0.07224 0.04377,-0.06585 0.06522,-0.131302 0.142975,-0.43634 0.04856,-0.190569 0.07764,-0.281419 0.12169,-0.380288 0.05968,-0.133898 0.138597,-0.231024 0.246674,-0.303562 0.05956,-0.03997 0.136289,-0.07169 0.209214,-0.08647 0.03466,-0.0071 0.05009,-0.008 0.121102,-0.0079 0.07039,8.2e-5 0.08604,0.0011 0.115898,0.0079 0.174774,0.03905 0.32139,0.148201 0.407297,0.303199 0.03861,0.06965 0.07079,0.159824 0.08892,0.249145 0.03979,0.196067 0.0318,0.45579 -0.01966,0.639056 -0.02373,0.08456 -0.06444,0.172673 -0.108688,0.235291 -0.03041,0.04306 -0.106032,0.118632 -0.149458,0.149384 -0.06951,0.04921 -0.160809,0.0887 -0.24679,0.106752 -0.04347,0.0091 -0.099,0.01627 -0.126307,0.01627 h -0.01606 l -2.1e-5,-0.02035 c -1.6e-5,-0.01117 -0.004,-0.108704 -0.0088,-0.216704 -0.0049,-0.108 -0.0088,-0.20197 -0.0088,-0.208833 l -5e-6,-0.01246 0.03095,-0.0063 c 0.0694,-0.01413 0.138766,-0.04496 0.180888,-0.08032 0.08203,-0.06887 0.117224,-0.170627 0.117173,-0.338828 -2.3e-5,-0.110773 -0.01492,-0.18678 -0.05113,-0.260747 -0.04201,-0.08582 -0.08739,-0.120843 -0.161595,-0.124727 -0.05522,-0.0029 -0.09344,0.01029 -0.131992,0.04553 -0.06168,0.05637 -0.102473,0.159848 -0.17213,0.436541 -0.05422,0.21541 -0.07865,0.297317 -0.120269,0.403336 -0.07394,0.188336 -0.176062,0.308941 -0.323448,0.381971 -0.08816,0.04368 -0.16936,0.06288 -0.286578,0.06778 -0.139166,0.0058 -0.266994,-0.02745 -0.389152,-0.10123 -0.136559,-0.08248 -0.234262,-0.210222 -0.290007,-0.37915 -0.0486,-0.147275 -0.06991,-0.343268 -0.05823,-0.535456 v 0 z"
style="stroke:#000000;stroke-width:0.00353806;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path4068"
d="m 71.473953,68.654557 h 1.148736 v 0.232609 0.232608 h -0.955495 -0.955493 v 0.581531 0.581522 h -0.193246 -0.193246 v -0.814131 -0.814139 z"
style="stroke:#000000;stroke-width:0.00357862;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 35.904558,11.76891 h 1.501193 l 0.0042,-0.958829 h 1.605518 c 0.420423,0 0.553932,0.382418 0.553932,0.959443 v 8.792554 c 0,0.41027 -0.387932,0.732996 -0.733002,0.732996 h -1.083786 c -0.295275,0 -0.312341,-0.319455 -0.342768,-0.593693 V 20.117742 H 35.92972 Z"
id="path4172" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 37.406703,20.100131 V 11.831902"
id="path4174" /><path
id="path4176"
d="m 29.267115,6.5371897 v 1.852083 h 4.960937 v -1.885156 c 0,-0.2437079 -0.213122,-0.3536949 -0.40513,-0.4051559 -1.385438,-0.371211 -2.876788,-0.352399 -4.192005,0 -0.207354,0.05556 -0.363802,0.1947589 -0.363802,0.4382289 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4180"
d="m 24.419382,23.304492 c -0.20619,0.740701 -1.629331,2.124948 -3.05062,2.447316 0,0.421429 0.247492,1.005602 0.760043,1.005602 h 4.8877 c 1.029362,0 0.770335,3.348196 -0.897149,3.348196 h -1.68701 c -3.504935,1.440339 -6.635141,1.469734 -10.056019,0 h -1.777365 c -1.5485,0 -1.867296,-3.317637 -0.888947,-3.317637 h 4.794436 c 0.695272,0 1.020762,-0.486277 0.979963,-0.979963 -1.247881,-0.516758 -2.36212,-1.193906 -2.991167,-2.45327 v -0.46773 h 9.930315 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 3.1349058,23.187797 c -0.970783,0 -2.8442713,-1.929288 -2.8442713,-2.968281 v -6.81302 c 0.520621,-1.975089 1.5200053,-4.1207013 4.7625003,-4.7625003 0.566261,0 0.859896,-0.533533 0.859896,-0.859896 3.06951,-4.0709057 6.4024682,-6.1659029 9.8226572,-7.6067708 h 7.242969 c 3.115389,1.3968147 4.852326,3.4828691 6.217708,5.7546869 v 1.2567709 c 0,0.518822 0.42971,1.058334 0.992187,1.058334 h 6.019271 V 23.196079 Z"
id="path4178" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 1.9949328,14.65448 H 30.215662 v 3.251237 c 0,1.098243 -1.401771,2.731037 -2.73104,2.731037 H 3.8156258 c -1.444993,0 -2.5142903,-1.396424 -2.5142903,-2.514288 0,-1.34669 -0.1161,-3.467986 0.6935973,-3.467986 z"
id="path4164" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 4.1190738,11.099795 H 29.998912 C 29.985313,5.7111738 27.335498,0.1322915 22.239297,0.1322915 l -7.239421,0.086701 C 7.9123758,1.5120668 6.4557758,6.6026927 4.1190738,11.099795 Z"
id="path4162" /><path
id="path4184"
d="M 22.071205,26.744075 H 16.944902"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4188"
d="M 21.408926,25.730509 H 17.338602"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4192"
d="m 1036.7923,1045.6825 v 38"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994-1)"
transform="matrix(0,0.26458333,-0.26458333,0,301.07298,-244.20874)" /><g
id="LeftTrigger"><path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 17.550756,7.0784747 v -0.211688 l 0.613645,-0.7579259 c 0.337506,-0.416859 0.613646,-0.7587585 0.613646,-0.7597745 0,-0.00106 -0.245293,-0.00185 -0.545092,-0.00185 H 17.687866 V 5.1516039 4.9559737 h 0.854421 0.854419 v 0.1817132 0.1817105 l -0.64054,0.7889164 -0.640541,0.7889159 0.665621,7.93e-4 0.665623,7.94e-4 v 0.195623 0.195622 h -0.948055 -0.948058 v -0.211688 z"
id="path4216" /><path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 19.761215,6.1330998 V 4.976037 h 0.234087 0.234088 v 0.9614328 0.9614319 h 0.586891 0.586893 v 0.19563 0.19563 h -0.820981 -0.820978 z"
id="path4218" /></g><path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 18.76467,17.759127 v -1.157063 h 0.234087 0.234088 v 0.961433 0.961429 h 0.586893 0.586891 v 0.195633 0.19563 H 19.585648 18.76467 Z"
id="LeftShoulder" /></g><style
id="style1">.button{fill:#44484c;}</style></svg>

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,442 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1000"
height="1300"
viewBox="0 0 264.58333 343.95833"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1">
<filter
id="filter3994"
color-interpolation-filters="sRGB"
y="-0.059999999"
height="1.12"
x="-inf"
width="inf">
<feGaussianBlur
stdDeviation="0.95"
id="feGaussianBlur3996" />
</filter>
</defs>
<style
id="style2">#buttons{fill:#ffffff;}
.button{fill:#44484c;}
</style>
<g
id="joycon"
transform="scale(2.3029882)">
<g
id="right"
transform="translate(27.045207)">
<path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 48.375673,63.613147 h 1.11085 v 9.13226 h -1.08746 c -0.37981,0 -0.57295,-0.264917 -0.57295,-0.572958 v -8.079888 c 0,-0.292343 0.21391,-0.479414 0.54956,-0.479414 z"
id="path3822" />
<path
id="path3824"
d="m 48.375673,117.52611 h 1.11085 v -9.13226 h -1.08746 c -0.37981,0 -0.57295,0.26491 -0.57295,0.57295 v 8.0799 c 0,0.29234 0.21391,0.47941 0.54956,0.47941 z"
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 48.235363,44.647045 h 3.70671 v 88.793825 h -1.7344 v 3.68762 c 0,0.41206 -0.34613,1.10727 -0.63928,1.10727 h -1.29136 c -0.23688,0 -0.45026,-0.18294 -0.45026,-0.45028 v -17.26234 c 0,-2.28045 1.42589,-2.30844 1.42589,-3.02849 V 108.339 c 0,-0.64968 -1.39147,-0.80953 -1.39147,-2.66601 V 75.364648 c 0,-1.840242 1.39147,-1.964011 1.39147,-2.666014 l -0.0331,-9.003636 c 0,-0.620151 -1.39147,-1.097835 -1.39147,-2.747863 V 45.123536 c -0.003,-0.259744 0.12546,-0.476491 0.40632,-0.476491 z"
id="path3820" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 57.624263,38.984676 v -1.107941 c 0,-0.332553 0.33967,-0.7028 0.70281,-0.7028 h 6.94532 c 10.7139,0 20.30407,8.641792 21.94758,14.775409 0.075,0.279813 0.10186,0.841899 -0.38587,0.841899 h -1.23362 z"
id="path3818" />
<path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 51.580203,39.555184 V 148.74535 c 0,0.2708 0.13385,0.47542 0.47543,0.47542 h 13.13407 c 13.67354,0 22.5094,-12.09083 22.5094,-22.50937 V 61.110444 c 0,-15.315418 -15.01891,-22.415206 -22.41523,-22.415206 h -12.90918 c -0.65778,0 -0.79449,0.361942 -0.79449,0.859946 z"
id="path3923" />
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 54.564653,48.674461 h 2.01287 v -1.987816 h 1.67042 v 1.996166 h 1.97948 v 1.670435 h -1.97112 v 2.062984 h -1.69548 v -2.062984 h -1.98784 z"
id="Start"
class="button" />
<ellipse
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3095"
cx="68.447418"
cy="58.209846"
rx="4.0085444"
ry="4.0085421" />
<ellipse
id="path3865"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="68.447418"
cy="74.78054"
rx="4.0085444"
ry="4.0085421" />
<ellipse
transform="rotate(90)"
id="path3867"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="66.495186"
cy="-60.162022"
rx="4.0085421"
ry="4.0085444" />
<ellipse
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3869"
transform="rotate(90)"
cx="66.495186"
cy="-76.732758"
rx="4.0085421"
ry="4.0085444" />
<ellipse
id="path3879"
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="62.425785"
cy="117.94157"
rx="4.4528017"
ry="4.4527988" />
<ellipse
style="fill:#3a3d40;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3881"
cx="62.425755"
cy="117.98209"
rx="3.3256173"
ry="3.3256154" />
<path
style="fill:#000000;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 60.086503,117.80491 h 0.6572 v 2.06837 c 1.10172,0 2.33288,0 3.3343,0 v -2.06837 h 0.65718 c -0.72404,-0.67331 -1.69847,-1.59279 -2.32434,-2.16524 -0.75748,0.69632 -1.55583,1.44766 -2.32434,2.16524 z m 1.54956,0.0346 c 0.51263,0 1.08492,0 1.54956,0 v 1.32128 c -0.5121,0 -1.08427,0 -1.54956,0 z"
id="path3883" />
<circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rightStickBackground"
cx="68.49704"
cy="97.259186"
r="8.4832029" />
<g
id="RightStick"
class="button"
style="display:inline">
<circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3888"
cx="68.49704"
cy="97.259186"
r="8.4832029" />
<circle
id="path3892"
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="68.49704"
cy="97.318031"
r="5.9489326" />
<path
style="fill:#000000;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 68.1896,90.349919 c 6e-5,-0.56753 0.003,-0.90285 0.004,-0.92087 0.008,-0.0547 0.0348,-0.10698 0.0765,-0.14869 0.12573,-0.12573 0.33957,-0.0884 0.41709,0.0728 0.029,0.0604 0.027,-0.0128 0.0271,0.98367 l 6e-5,0.900086 -0.18558,-2.1e-4 c -0.10208,-1.2e-4 -0.22022,0.001 -0.26252,0.003 l -0.0769,0.003 8e-5,-0.892526 z"
id="path3899" />
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 68.42254,105.27792 c -0.10303,-0.0115 -0.19174,-0.0882 -0.22418,-0.19366 -0.006,-0.021 -0.007,-0.0874 -0.008,-0.85705 l -0.003,-0.83431 0.0611,0.003 c 0.0336,0.002 0.15179,0.003 0.26268,0.003 h 0.20158 v 0.83509 0.83509 l -0.009,0.0269 c -0.0276,0.0808 -0.0914,0.14422 -0.17232,0.17131 -0.0265,0.009 -0.0766,0.0135 -0.10798,0.01 v 0 z"
id="path3901" />
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 74.57836,97.335985 c 0,-0.0938 -0.003,-0.21204 -0.003,-0.26266 l -0.003,-0.092 0.85498,0.001 0.855,10e-4 0.0303,0.0111 c 0.11406,0.0417 0.18497,0.14685 0.17725,0.26298 -0.004,0.0644 -0.0295,0.121092 -0.0743,0.166862 -0.0351,0.0359 -0.0682,0.055 -0.12911,0.0745 -0.0132,0.004 -0.18825,0.006 -0.86196,0.007 l -0.84543,10e-4 v -0.170632 0 z"
id="path3903" />
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 60.64228,97.502107 c -0.0569,-0.008 -0.11247,-0.0362 -0.15298,-0.0791 -0.14097,-0.149022 -0.0698,-0.386962 0.13023,-0.435812 0.0188,-0.005 0.18232,-0.006 0.91276,-0.006 l 0.89003,-1.4e-4 -0.003,0.0878 c -0.003,0.0483 -0.003,0.16646 -0.003,0.26252 v 0.174672 l -0.87437,-5.3e-4 c -0.4809,-2.6e-4 -0.88553,-0.002 -0.89916,-0.004 z"
id="path3905" />
</g>
<g
id="buttons">
<path
style="stroke:none;stroke-width:0.264583"
d="m 66.51122,60.355075 c 0,-0.0029 0.32536,-0.501412 0.72302,-1.108578 l 0.72303,-1.104053 -0.64079,-0.989807 c -0.35243,-0.54438 -0.64887,-1.001289 -0.65874,-1.015312 l -0.018,-0.0254 h 0.49586 0.49585 l 0.42516,0.681726 c 0.23384,0.374967 0.42924,0.680376 0.43421,0.678709 0.005,-0.0029 0.19529,-0.307393 0.42291,-0.679397 l 0.41381,-0.676407 0.4885,-0.0029 c 0.26866,-0.0029 0.48847,0 0.48847,0.0029 0,0.0029 -0.2945,0.464634 -0.6545,1.026662 -0.37467,0.58502 -0.65283,1.028224 -0.65061,1.036744 0.003,0.0082 0.32589,0.499559 0.71943,1.091988 0.39354,0.592402 0.71554,1.0795 0.71554,1.082463 0,0.0029 -0.22849,0.005 -0.50779,0.005 H 69.41879 L 68.9468,59.621781 c -0.38118,-0.595762 -0.47418,-0.73533 -0.48339,-0.725408 -0.006,0.0066 -0.22109,0.338852 -0.47734,0.738029 l -0.46593,0.725752 h -0.50448 c -0.27747,0 -0.5045,-0.0029 -0.5045,-0.0045 z"
id="X" />
<path
style="stroke:none;stroke-width:0.264583"
d="m 74.70499,68.578086 c 0.0159,-0.04217 1.60489,-4.121546 1.62507,-4.172241 l 0.023,-0.05794 h 0.44749 0.44749 l 0.84585,2.111613 c 0.46522,1.161388 0.84796,2.116852 0.85055,2.123281 0.004,0.0093 -0.0887,0.01111 -0.45704,0.009 l -0.4618,-0.0026 -0.18592,-0.484399 -0.18593,-0.484399 -0.85288,0.0026 -0.85288,0.0026 -0.17585,0.484452 -0.17584,0.484426 h -0.44876 c -0.42357,0 -0.44844,-0.0026 -0.44265,-0.01614 z m 2.6729,-1.673542 c -0.003,-0.0064 -0.13496,-0.363246 -0.29456,-0.793062 -0.15957,-0.429816 -0.29292,-0.778193 -0.2963,-0.774197 -0.005,0.0053 -0.3456,0.938741 -0.57272,1.567259 -0.003,0.009 0.11697,0.01138 0.5819,0.01138 0.46506,0 0.5852,-0.0026 0.58168,-0.01138 z"
id="A" />
<path
style="stroke:none;stroke-width:0.264583"
d="m 66.87468,74.871836 v -2.119339 l 1.04767,0.0045 c 1.04455,0.0045 1.15731,0.0079 1.36678,0.03969 0.31785,0.04826 0.57875,0.210662 0.75094,0.467466 0.26178,0.390393 0.21953,0.899689 -0.103,1.241584 -0.0752,0.07972 -0.14721,0.13552 -0.24757,0.19177 l -0.0779,0.04366 0.0631,0.0209 c 0.15386,0.05075 0.31663,0.143987 0.42588,0.243999 0.12821,0.117316 0.23862,0.303504 0.28662,0.483314 0.022,0.08186 0.0243,0.108189 0.0251,0.26215 0,0.133508 -0.003,0.187933 -0.0161,0.247623 -0.0534,0.243126 -0.17126,0.469159 -0.32763,0.628544 -0.20013,0.203968 -0.44561,0.30742 -0.79624,0.335571 -0.17465,0.01402 -1.02536,0.02733 -1.76633,0.02762 h -0.63137 v -2.119339 z m 2.11238,1.397661 c 0.19571,-0.01614 0.30006,-0.05593 0.40346,-0.153458 0.10692,-0.100859 0.15121,-0.217382 0.15087,-0.396848 0,-0.276649 -0.15034,-0.470483 -0.41069,-0.531337 -0.15079,-0.03524 -0.23929,-0.03998 -0.82907,-0.04434 l -0.57587,-0.0042 v 0.56978 0.569807 h 0.57587 c 0.3167,-8e-5 0.62515,-0.0042 0.68543,-0.0093 v 0 z m -0.34084,-1.831075 c 0.30885,-0.0087 0.37068,-0.01773 0.48202,-0.07046 0.11647,-0.05524 0.19852,-0.147002 0.23728,-0.265377 0.0265,-0.08091 0.0265,-0.246115 -2.7e-4,-0.328348 -0.0499,-0.154278 -0.16449,-0.253629 -0.3406,-0.295354 -0.0549,-0.01296 -0.15099,-0.01561 -0.68143,-0.01905 l -0.61749,-0.0037 v 0.495591 0.495617 h 0.32145 c 0.17682,0 0.44635,-0.004 0.59899,-0.0082 v 0 z"
id="B" />
<path
style="display:inline;stroke:none;stroke-width:0.264583"
d="m 59.75794,67.784945 v -0.891461 l -0.77073,-1.218485 c -0.42392,-0.670137 -0.77076,-1.220179 -0.77076,-1.222269 0,-0.0026 0.22119,-0.0037 0.49152,-0.0037 l 0.49154,1.06e-4 0.49842,0.839153 c 0.37071,0.624072 0.50107,0.836374 0.50864,0.828331 0.006,-0.0058 0.2272,-0.38362 0.49241,-0.839232 l 0.48218,-0.828437 h 0.4848 0.48477 l -0.0132,0.0209 c -0.007,0.01138 -0.35669,0.563192 -0.77642,1.226185 L 60.598,66.901451 v 0.887572 0.887571 h -0.42 -0.41998 v -0.891487 z"
id="Y" />
</g>
<path
id="path4136"
d="m 52.095503,11.768902 h -1.50119 l -0.004,-0.958829 h -1.60551 c -0.42043,0 -0.55393,0.382418 -0.55393,0.959443 v 8.792548 c 0,0.41027 0.38793,0.732996 0.733,0.732996 h 1.08378 c 0.29528,0 0.31234,-0.319455 0.34277,-0.593693 v -0.583638 h 1.48016 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4138"
d="M 50.593363,20.100118 V 11.831894"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 58.732943,6.5371856 v 1.8520819 h -4.96093 V 6.5041126 c 0,-0.2437079 0.21312,-0.3536948 0.40513,-0.4051558 1.38543,-0.3712107 2.87678,-0.3523988 4.192,0 0.20736,0.05556 0.3638,0.1947589 0.3638,0.4382288 z"
id="path4158" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 63.516853,23.377163 c 0.20619,0.7407 1.62933,2.124946 3.05062,2.447315 0,0.421428 -0.24749,1.005601 -0.76004,1.005601 h -4.8877 c -1.02937,0 -0.77034,3.348195 0.89714,3.348195 h 1.68701 c 3.50494,1.440337 6.63515,1.469733 10.05602,0 h 1.77737 c 1.5485,0 1.86729,-3.317635 0.88895,-3.317635 h -4.79444 c -0.69527,0 -1.02076,-0.486278 -0.97996,-0.979964 1.24788,-0.516758 2.36212,-1.193905 2.99116,-2.453268 v -0.46773 h -9.93031 z"
id="path4146" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 64.033773,23.022225 c 0,0.04675 0,1.145909 0,1.145909 0,0.478393 -0.29195,0.666512 -0.66652,0.666512 -2.01795,0.177932 -4.4696,0.167508 -6.5364,0 -0.34731,0 -0.68791,-0.3302 -0.68405,-0.684054 v -1.397316 h 7.88697 z"
id="path4154" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 65.865033,26.816744 h 5.1263"
id="path4148" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 54.813493,22.32899 v 1.601971 c 0,0.137981 0.1643,0.292312 0.29231,0.292312 h 5.13323 c 0.16113,0 0.35666,-0.160814 0.35666,-0.356632 v -1.549319 z"
id="path4142" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 66.527303,25.803179 h 4.07033"
id="path4150" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 80.591103,23.022225 c 0,0.04675 0,1.145909 0,1.145909 0,0.478393 -0.29194,0.666512 -0.66649,0.666512 -2.01797,0.177932 -4.4696,0.167508 -6.53642,0 -0.34732,0 -0.68792,-0.3302 -0.68403,-0.684054 v -1.397316 h 7.88694 z"
id="path4144" />
<path
transform="matrix(0,0.26458317,0.26458333,0,-213.13674,-244.1359)"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994)"
d="m 1036.7923,1045.6825 v 38"
id="path4152" />
<path
id="path4160"
d="m 72.311283,23.022225 c 0,0.04675 0,1.145909 0,1.145909 0,0.478393 -0.29194,0.666512 -0.66648,0.666512 -2.01798,0.177932 -4.46961,0.167508 -6.53643,0 -0.34732,0 -0.68792,-0.3302 -0.68403,-0.684054 v -1.397316 h 7.88694 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4140"
d="m 84.865153,23.187782 c 0.97078,0 2.84427,-1.929287 2.84427,-2.968279 v -6.813016 c -0.52062,-1.975088 -1.52,-4.1206981 -4.7625,-4.7624967 -0.56626,0 -0.85989,-0.5335327 -0.85989,-0.8598955 -3.06951,-4.070903 -6.40247,-6.165899 -9.82266,-7.60676597 h -7.24297 c -3.11539,1.39681387 -4.85232,3.48286697 -6.21771,5.75468307 v 1.2567703 c 0,0.5188217 -0.42971,1.0583333 -0.99218,1.0583333 h -6.01927 V 23.196064 Z"
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
<path
id="path4166"
d="m 83.838993,11.099787 h -25.87985 c 0.0135,-5.3886169 2.66343,-10.96749554 7.75962,-10.96749554 l 7.23943,0.0867009 c 7.0875,1.29307354 8.54411,6.38369614 10.8808,10.88079464 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4168"
d="m 85.963123,14.65447 h -28.22072 v 3.251235 c 0,1.098242 1.40179,2.731035 2.73105,2.731035 h 23.66899 c 1.445,0 2.51429,-1.396423 2.51429,-2.514286 0,-1.346689 0.11609,-3.467984 -0.69361,-3.467984 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 67.399983,17.660894 v -1.210391 l 0.55007,6.6e-5 c 0.30252,3.7e-5 0.59417,0.0024 0.64807,0.0049 0.23492,0.01161 0.38338,0.0444 0.48519,0.107175 0.15637,0.09641 0.26062,0.266599 0.28702,0.468541 0.008,0.06163 0.003,0.197848 -0.009,0.254818 -0.0416,0.191362 -0.15044,0.334386 -0.32104,0.421952 -0.0654,0.03355 -0.16529,0.06695 -0.24334,0.08133 -0.0324,0.006 -0.0588,0.01204 -0.0587,0.01349 8e-5,0.0016 0.0175,0.01312 0.0385,0.0259 0.0679,0.04108 0.1387,0.09714 0.19921,0.157761 0.0982,0.09839 0.16031,0.1876 0.42134,0.605501 0.0884,0.141498 0.16391,0.26223 0.16783,0.268295 l 0.007,0.01103 h -0.28882 -0.28884 l -0.19288,-0.287895 c -0.36298,-0.541718 -0.41074,-0.605218 -0.49914,-0.663437 -0.0681,-0.04484 -0.12798,-0.05709 -0.3008,-0.06151 l -0.11637,-0.003 v 0.50791 0.507904 h -0.24257 -0.24257 v -1.210391 z m 1.19824,-0.197742 c 0.10322,-0.01349 0.15547,-0.03687 0.20572,-0.09207 0.0507,-0.0557 0.0711,-0.115819 0.0711,-0.210029 0,-0.130434 -0.0482,-0.216285 -0.14822,-0.264016 -0.0777,-0.03708 -0.0652,-0.03607 -0.47538,-0.03847 l -0.36629,-0.0021 v 0.309612 0.309613 l 0.3271,-0.0024 c 0.25813,-0.0019 0.33952,-0.004 0.386,-0.01011 v 0 z"
id="RightShoulder" />
<g
id="RightTrigger">
<path
style="fill-opacity:1;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 68.43397,6.1446855 v -1.208246 l 0.62601,0.00212 c 0.66757,0.00212 0.68236,0.00273 0.82087,0.027509 0.0828,0.014843 0.13729,0.031819 0.19719,0.061523 0.0639,0.03167 0.10575,0.06235 0.15301,0.112101 0.0781,0.08226 0.1333,0.187278 0.16198,0.308377 0.0119,0.0507 0.013,0.06345 0.0132,0.162814 8e-5,0.08425 -0.003,0.117107 -0.008,0.150355 -0.0561,0.275579 -0.24551,0.44872 -0.56002,0.512053 -0.0592,0.01193 -0.066,0.01426 -0.0579,0.02011 0.005,0.0036 0.0317,0.02135 0.0591,0.03937 0.14401,0.09456 0.23318,0.189458 0.36915,0.392957 0.0495,0.07413 0.38581,0.60892 0.3906,0.621162 0.003,0.0049 -0.0559,0.0059 -0.2858,0.0049 l -0.28824,-0.0013 -0.21582,-0.321297 c -0.30739,-0.457573 -0.34192,-0.505973 -0.40719,-0.570571 -0.09,-0.08909 -0.15473,-0.109487 -0.36116,-0.113864 l -0.12173,-0.0027 v 0.505378 0.505375 h -0.24257 -0.24257 v -1.208244 z m 1.17118,-0.193157 c 0.0797,-0.0088 0.11668,-0.01786 0.15986,-0.03914 0.0694,-0.03421 0.11798,-0.09792 0.13692,-0.179525 0.0103,-0.04405 0.0101,-0.136223 -2.6e-4,-0.175921 -0.0313,-0.12047 -0.12489,-0.194789 -0.26226,-0.208325 -0.0257,-0.0026 -0.19817,-0.0046 -0.38346,-0.0047 l -0.33689,-1.7e-4 v 0.309306 0.309304 l 0.31729,-0.0026 c 0.18902,-0.0016 0.33811,-0.0049 0.36875,-0.0083 v 0 z"
id="path4234" />
<path
style="fill-opacity:1;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 66.15527,7.1346265 v -0.218302 l 0.63461,-0.783873 c 0.34904,-0.431131 0.6346,-0.784953 0.6346,-0.786276 0,-0.0013 -0.2536,-0.0024 -0.56354,-0.0024 H 66.29741 V 5.1404305 4.9370639 h 0.8845 0.88453 v 0.1874836 0.187481 l -0.66442,0.818322 -0.6644,0.818319 0.6889,0.0013 0.68892,0.0013 v 0.200898 0.200898 h -0.98007 -0.9801 v -0.2183 z"
id="path4236" />
</g>
</g>
<g
id="left">
<path
id="path4182"
d="m 33.122745,22.256318 v 1.601973 c 0,0.13798 -0.164307,0.292312 -0.292312,0.292312 h -5.133234 c -0.161131,0 -0.356659,-0.160814 -0.356659,-0.356632 V 22.24465 Z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4186"
d="m 23.902465,22.949553 c 0,0.04675 0,1.145911 0,1.145911 0,0.478393 0.291942,0.666512 0.666512,0.666512 2.017951,0.177932 4.469606,0.167507 6.536399,0 0.347319,0 0.687917,-0.3302 0.684054,-0.684054 v -1.397318 h -7.886965 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4190"
d="m 7.3451308,22.949553 c 0,0.04675 0,1.145911 0,1.145911 0,0.478393 0.291941,0.666512 0.666485,0.666512 2.0179782,0.177932 4.4696072,0.167507 6.5364262,0 0.347319,0 0.687917,-0.3302 0.684027,-0.684054 V 22.680604 H 7.3451308 Z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3828"
d="m 39.503431,63.613187 h -1.110837 v 9.132265 h 1.08745 c 0.379812,0 0.572961,-0.264917 0.572961,-0.572958 v -8.079893 c 0,-0.292343 -0.213908,-0.479414 -0.549574,-0.479414 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 39.503431,117.52619 h -1.110837 v -9.13226 h 1.08745 c 0.379812,0 0.572961,0.26491 0.572961,0.57295 v 8.0799 c 0,0.29234 -0.213908,0.47941 -0.549574,0.47941 z"
id="path3830" />
<path
id="path3826"
d="m 39.643747,44.647073 h -3.706693 v 88.793887 h 1.734386 v 3.68763 c 0,0.41206 0.346123,1.10727 0.639284,1.10727 h 1.291346 c 0.23689,0 0.450287,-0.18294 0.450287,-0.45028 v -17.26235 c 0,-2.28046 -1.425903,-2.30845 -1.425903,-3.0285 v -9.15565 c 0,-0.64968 1.391472,-0.80953 1.391472,-2.66601 V 75.364695 c 0,-1.840243 -1.391472,-1.964013 -1.391472,-2.666016 l 0.03307,-9.003641 c 0,-0.620152 1.391473,-1.097836 1.391473,-2.747865 l -7.93e-4,-15.823609 c 0.0024,-0.259744 -0.125479,-0.476491 -0.406334,-0.476491 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3832"
d="m 30.154591,38.984701 v -1.107942 c 0,-0.332553 -0.339662,-0.7028 -0.7028,-0.7028 h -6.945312 c -10.713919,0 -20.3040802,8.641797 -21.9475795,14.775418 -0.07498,0.279813 -0.101862,0.841899 0.385869,0.841899 h 1.2336173 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3925"
d="m 36.251186,39.39185 v 109.19024 c 0,0.2708 -0.133853,0.47543 -0.475422,0.47543 H 22.641681 c -13.6735372,0 -22.5093895,-12.09084 -22.5093895,-22.50939 V 60.947123 c 0,-15.315427 15.0189105,-22.415219 22.4152215,-22.415219 h 12.909171 c 0.657781,0 0.794502,0.361942 0.794502,0.859946 z"
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
id="path3871"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.236444"
cy="90.101624"
r="4.0085444" />
<circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3873"
cx="19.236444"
cy="106.67232"
r="4.0085444" />
<circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3875"
transform="rotate(90)"
cx="98.386986"
cy="-10.951083"
r="4.0085444" />
<circle
transform="rotate(90)"
id="path3877"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="98.386986"
cy="-27.521791"
r="4.0085444" />
<rect
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="Back"
width="5.8704429"
height="1.6867187"
x="27.454165"
y="48.675072"
class="button" />
<circle
id="leftStickBackground"
style="display:inline;fill-opacity:1;stroke:#000000;stroke-width:0.264439;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.268621"
cy="67.195892"
r="8.4832029" />
<g
id="LeftStick"
class="button"
style="display:inline">
<circle
id="path3907"
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="19.268621"
cy="67.195892"
r="8.4832029" />
<circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3909"
cx="19.268621"
cy="67.254738"
r="5.9489326" />
<path
id="path3911"
d="m 18.961234,60.286636 c 5.3e-5,-0.567529 0.0016,-0.902846 0.0042,-0.920872 0.0079,-0.0547 0.03481,-0.106976 0.07652,-0.148682 0.12573,-0.12573 0.339551,-0.08842 0.417084,0.07277 0.02906,0.06043 0.02704,-0.01283 0.02711,0.983676 l 6.1e-5,0.900091 -0.185587,-2.17e-4 c -0.102071,-1.19e-4 -0.220205,0.0011 -0.262517,0.0028 l -0.07693,0.003 7.9e-5,-0.892521 z"
style="fill:#000000;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path3913"
d="m 19.194179,75.214639 c -0.103042,-0.01154 -0.191744,-0.08817 -0.224182,-0.193662 -0.0065,-0.02098 -0.0071,-0.08735 -0.0082,-0.857049 l -0.0013,-0.83431 0.06106,0.0034 c 0.03359,0.0019 0.151787,0.0034 0.262666,0.0034 h 0.201596 l -0.0011,0.835091 -0.0011,0.835091 -0.0092,0.02687 c -0.02764,0.08078 -0.09145,0.144222 -0.172333,0.171315 -0.02641,0.0088 -0.07656,0.01347 -0.107987,0.0099 v 0 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path3915"
d="m 25.349998,67.27271 c 0,-0.09385 -0.0016,-0.212042 -0.0034,-0.262662 l -0.0034,-0.09203 0.854977,0.0011 0.854977,0.0011 0.03032,0.01109 c 0.114072,0.04167 0.18497,0.146846 0.177263,0.262977 -0.0043,0.06436 -0.02954,0.121094 -0.07432,0.166859 -0.03514,0.03591 -0.06816,0.05496 -0.129127,0.07451 -0.01318,0.0042 -0.188249,0.0056 -0.861963,0.0065 l -0.845428,0.0013 v -0.170629 0 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path3917"
d="m 11.413899,67.438823 c -0.0569,-0.0075 -0.112456,-0.03622 -0.152974,-0.07906 -0.140957,-0.149016 -0.06985,-0.386953 0.130236,-0.435806 0.01865,-0.0046 0.182329,-0.0056 0.912762,-0.0057 l 0.890024,-1.49e-4 -0.0033,0.08785 c -0.0019,0.04832 -0.0033,0.166452 -0.0033,0.26252 v 0.174667 l -0.874363,-5.29e-4 c -0.480901,-2.65e-4 -0.885529,-0.0021 -0.899171,-0.0038 z"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<rect
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3919"
width="6.8130207"
height="6.647656"
x="21.930986"
y="116.11076"
ry="0.89296871" />
<circle
style="fill:#000000;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3921"
cx="25.312696"
cy="119.49245"
r="2.3399088" />
<g
id="dpad">
<path
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="DpadRight"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
transform="matrix(0.29245602,0,0,0.29245602,-13.019911,-152.18812)" />
<path
transform="matrix(-0.29245602,0,0,0.29245602,51.534858,-152.18812)"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
id="DpadLeft"
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
transform="matrix(0,-0.29245602,0.29245602,0,-231.35192,130.71823)"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
id="DpadUp"
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="stroke:#000000;stroke-width:0.904694;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="DpadDown"
d="m 134.97306,862.5577 0,-11.46794 9.93153,5.73397 z"
transform="matrix(0,0.29245602,0.29245602,0,-231.35192,66.16345)" />
</g>
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 35.904558,11.76891 h 1.501193 l 0.0042,-0.958829 h 1.605518 c 0.420423,0 0.553932,0.382418 0.553932,0.959443 v 8.792554 c 0,0.41027 -0.387932,0.732996 -0.733002,0.732996 h -1.083786 c -0.295275,0 -0.312341,-0.319455 -0.342768,-0.593693 V 20.117742 H 35.92972 Z"
id="path4172" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 37.406703,20.100131 V 11.831902"
id="path4174" />
<path
id="path4176"
d="m 29.267115,6.5371897 v 1.852083 h 4.960937 v -1.885156 c 0,-0.2437079 -0.213122,-0.3536949 -0.40513,-0.4051559 -1.385438,-0.371211 -2.876788,-0.352399 -4.192005,0 -0.207354,0.05556 -0.363802,0.1947589 -0.363802,0.4382289 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4180"
d="m 24.419382,23.304492 c -0.20619,0.740701 -1.629331,2.124948 -3.05062,2.447316 0,0.421429 0.247492,1.005602 0.760043,1.005602 h 4.8877 c 1.029362,0 0.770335,3.348196 -0.897149,3.348196 h -1.68701 c -3.504935,1.440339 -6.635141,1.469734 -10.056019,0 h -1.777365 c -1.5485,0 -1.867296,-3.317637 -0.888947,-3.317637 h 4.794436 c 0.695272,0 1.020762,-0.486277 0.979963,-0.979963 -1.247881,-0.516758 -2.36212,-1.193906 -2.991167,-2.45327 v -0.46773 h 9.930315 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#00bbdb;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 3.1349058,23.187797 c -0.970783,0 -2.8442713,-1.929288 -2.8442713,-2.968281 v -6.81302 c 0.520621,-1.975089 1.5200053,-4.1207013 4.7625003,-4.7625003 0.566261,0 0.859896,-0.533533 0.859896,-0.859896 3.06951,-4.0709057 6.4024682,-6.1659029 9.8226572,-7.6067708 h 7.242969 c 3.115389,1.3968147 4.852326,3.4828691 6.217708,5.7546869 v 1.2567709 c 0,0.518822 0.42971,1.058334 0.992187,1.058334 h 6.019271 V 23.196079 Z"
id="path4178" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 1.9949328,14.65448 H 30.215662 v 3.251237 c 0,1.098243 -1.401771,2.731037 -2.73104,2.731037 H 3.8156258 c -1.444993,0 -2.5142903,-1.396424 -2.5142903,-2.514288 0,-1.34669 -0.1161,-3.467986 0.6935973,-3.467986 z"
id="path4164" />
<path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 4.1190738,11.099795 H 29.998912 C 29.985313,5.7111738 27.335498,0.1322915 22.239297,0.1322915 l -7.239421,0.086701 C 7.9123758,1.5120668 6.4557758,6.6026927 4.1190738,11.099795 Z"
id="path4162" />
<path
id="path4184"
d="M 22.071205,26.744075 H 16.944902"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4188"
d="M 21.408926,25.730509 H 17.338602"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4192"
d="m 1036.7923,1045.6825 v 38"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994)"
transform="matrix(0,0.26458333,-0.26458333,0,301.07298,-244.20874)" />
<g
id="LeftTrigger">
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 17.550756,7.0784747 v -0.211688 l 0.613645,-0.7579259 c 0.337506,-0.416859 0.613646,-0.7587585 0.613646,-0.7597745 0,-0.00106 -0.245293,-0.00185 -0.545092,-0.00185 H 17.687866 V 5.1516039 4.9559737 h 0.854421 0.854419 v 0.1817132 0.1817105 l -0.64054,0.7889164 -0.640541,0.7889159 0.665621,7.93e-4 0.665623,7.94e-4 v 0.195623 0.195622 h -0.948055 -0.948058 v -0.211688 z"
id="path4216" />
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 19.761215,6.1330998 V 4.976037 h 0.234087 0.234088 v 0.9614328 0.9614319 h 0.586891 0.586893 v 0.19563 0.19563 h -0.820981 -0.820978 z"
id="path4218" />
</g>
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00334412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 18.76467,17.759127 v -1.157063 h 0.234087 0.234088 v 0.961433 0.961429 h 0.586893 0.586891 v 0.195633 0.19563 H 19.585648 18.76467 Z"
id="LeftShoulder" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
width="1000.8"
height="1300"
x="0"
y="0"
version="1.1"
viewBox="0 0 1000.8 1300"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1"><filter
id="filter3994"
color-interpolation-filters="sRGB"
y="-0.059999999"
height="1.12"
x="-inf"
width="inf"><feGaussianBlur
stdDeviation="0.95"
id="feGaussianBlur3996" /></filter><filter
id="filter3994-6"
color-interpolation-filters="sRGB"
y="-0.059999999"
height="1.12"
x="-inf"
width="inf"><feGaussianBlur
stdDeviation="0.95"
id="feGaussianBlur3996-1" /></filter></defs><metadata
id="metadata85" /><style
type="text/css"
id="style1">#buttons{fill:#ffffff;}
.button{fill:#44484c;}</style><g
id="right"
transform="matrix(8.7009664,0,0,8.7009664,-1454.1279,-3.6543895e-8)"><path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 225.11734,63.613186 h 1.11085 v 9.132265 h -1.08746 c -0.37981,0 -0.57295,-0.264917 -0.57295,-0.572958 V 64.0926 c 0,-0.292343 0.21391,-0.479414 0.54956,-0.479414 z"
id="path3822" /><path
id="path3824"
d="m 225.11734,117.52618 h 1.11085 v -9.13226 h -1.08746 c -0.37981,0 -0.57295,0.26491 -0.57295,0.57295 v 8.0799 c 0,0.29234 0.21391,0.47941 0.54956,0.47941 z"
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 224.97703,44.647072 h 3.70671 v 88.793878 h -1.7344 v 3.68763 c 0,0.41206 -0.34613,1.10727 -0.63928,1.10727 h -1.29136 c -0.23688,0 -0.45026,-0.18294 -0.45026,-0.45028 v -17.26235 c 0,-2.28046 1.42589,-2.30845 1.42589,-3.0285 v -9.15565 c 0,-0.64968 -1.39147,-0.80953 -1.39147,-2.66601 V 75.364694 c 0,-1.840243 1.39147,-1.964013 1.39147,-2.666016 l -0.0331,-9.003641 c 0,-0.620152 -1.39147,-1.097836 -1.39147,-2.747865 V 45.123563 c -0.003,-0.259744 0.12546,-0.476491 0.40632,-0.476491 z"
id="path3820" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 234.36593,38.9847 v -1.107942 c 0,-0.332553 0.33967,-0.7028 0.70281,-0.7028 h 6.94532 c 10.7139,0 20.30407,8.641797 21.94758,14.775418 0.075,0.279813 0.10186,0.841899 -0.38587,0.841899 h -1.23362 z"
id="path3818" /><path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 228.32187,39.555208 V 148.74544 c 0,0.2708 0.13385,0.47542 0.47543,0.47542 h 13.13407 c 13.67354,0 22.5094,-12.09083 22.5094,-22.50938 V 61.110481 c 0,-15.315427 -15.01891,-22.415219 -22.41523,-22.415219 h -12.90918 c -0.65778,0 -0.79449,0.361942 -0.79449,0.859946 z"
id="path3923" /><path
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 231.30632,48.674491 h 2.01287 v -1.987818 h 1.67042 v 1.996168 h 1.97948 v 1.670436 h -1.97112 v 2.062985 h -1.69548 v -2.062985 h -1.98784 z"
id="Start"
class="button" /><circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3095"
cx="245.18909"
cy="58.209881"
r="4.0085444" /><circle
id="path3865"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="245.18909"
cy="74.780586"
r="4.0085444" /><circle
transform="rotate(90)"
id="path3867"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="66.495232"
cy="-236.90369"
r="4.0085444" /><circle
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3869"
transform="rotate(90)"
cx="66.495232"
cy="-253.47443"
r="4.0085444" /><circle
id="path3879"
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="239.16745"
cy="117.94165"
r="4.4528017" /><circle
style="fill:#3a3d40;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3881"
cx="239.16742"
cy="117.98216"
r="3.3256173" /><path
style="fill:#000000;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 236.82817,117.80498 h 0.6572 v 2.06838 c 1.10172,0 2.33288,0 3.3343,0 v -2.06838 h 0.65718 c -0.72404,-0.67331 -1.69847,-1.59279 -2.32434,-2.16524 -0.75748,0.69632 -1.55583,1.44766 -2.32434,2.16524 z m 1.54956,0.0346 c 0.51263,0 1.08492,0 1.54956,0 v 1.32129 c -0.5121,0 -1.08427,0 -1.54956,0 z"
id="path3883" /><circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3888-2"
cx="245.23871"
cy="97.259186"
r="8.4832029" /><g
id="RightStick"
class="button"><circle
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3888"
cx="245.23871"
cy="97.259186"
r="8.4832029" /><circle
id="path3892"
style="fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="245.23871"
cy="97.318031"
r="5.9489326" /><path
style="fill:#000000;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 244.93127,90.349919 c 6e-5,-0.56753 0.003,-0.90285 0.004,-0.92087 0.008,-0.0547 0.0348,-0.10698 0.0765,-0.14869 0.12573,-0.12573 0.33957,-0.0884 0.41709,0.0728 0.029,0.0604 0.027,-0.0128 0.0271,0.98367 l 6e-5,0.900086 -0.18558,-2.1e-4 c -0.10208,-1.2e-4 -0.22022,0.001 -0.26252,0.003 l -0.0769,0.003 8e-5,-0.892526 z"
id="path3899" /><path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 245.16421,105.27792 c -0.10303,-0.0115 -0.19174,-0.0882 -0.22418,-0.19366 -0.006,-0.021 -0.007,-0.0874 -0.008,-0.85705 l -0.003,-0.83431 0.0611,0.003 c 0.0336,0.002 0.15179,0.003 0.26268,0.003 h 0.20158 v 0.83509 0.83509 l -0.009,0.0269 c -0.0276,0.0808 -0.0914,0.14422 -0.17232,0.17131 -0.0265,0.009 -0.0766,0.0135 -0.10798,0.01 v 0 z"
id="path3901" /><path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 251.32003,97.335985 c 0,-0.0938 -0.003,-0.21204 -0.003,-0.26266 l -0.003,-0.092 0.85498,0.001 0.855,10e-4 0.0303,0.0111 c 0.11406,0.0417 0.18497,0.14685 0.17725,0.26298 -0.004,0.0644 -0.0295,0.121092 -0.0743,0.166862 -0.0351,0.0359 -0.0682,0.055 -0.12911,0.0745 -0.0132,0.004 -0.18825,0.006 -0.86196,0.007 l -0.84543,10e-4 v -0.170632 0 z"
id="path3903" /><path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.00826823;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 237.38395,97.502107 c -0.0569,-0.008 -0.11247,-0.0362 -0.15298,-0.0791 -0.14097,-0.149022 -0.0698,-0.386962 0.13023,-0.435812 0.0188,-0.005 0.18232,-0.006 0.91276,-0.006 l 0.89003,-1.4e-4 -0.003,0.0878 c -0.003,0.0483 -0.003,0.16646 -0.003,0.26252 v 0.174672 l -0.87437,-5.3e-4 c -0.4809,-2.6e-4 -0.88553,-0.002 -0.89916,-0.004 z"
id="path3905" /></g><g
id="buttons"><path
style="stroke:none;stroke-width:0.264583"
d="m 243.25289,60.355075 c 0,-0.0029 0.32536,-0.501412 0.72302,-1.108578 l 0.72303,-1.104053 -0.64079,-0.989807 c -0.35243,-0.54438 -0.64887,-1.001289 -0.65874,-1.015312 l -0.018,-0.0254 h 0.49586 0.49585 l 0.42516,0.681726 c 0.23384,0.374967 0.42924,0.680376 0.43421,0.678709 0.005,-0.0029 0.19529,-0.307393 0.42291,-0.679397 l 0.41381,-0.676407 0.4885,-0.0029 c 0.26866,-0.0029 0.48847,0 0.48847,0.0029 0,0.0029 -0.2945,0.464634 -0.6545,1.026662 -0.37467,0.58502 -0.65283,1.028224 -0.65061,1.036744 0.003,0.0082 0.32589,0.499559 0.71943,1.091988 0.39354,0.592402 0.71554,1.0795 0.71554,1.082463 0,0.0029 -0.22849,0.005 -0.50779,0.005 h -0.50779 l -0.47199,-0.737632 c -0.38118,-0.595762 -0.47418,-0.73533 -0.48339,-0.725408 -0.006,0.0066 -0.22109,0.338852 -0.47734,0.738029 l -0.46593,0.725752 h -0.50448 c -0.27747,0 -0.5045,-0.0029 -0.5045,-0.0045 z"
id="X" /><path
style="stroke:none;stroke-width:0.264583"
d="m 251.44666,68.578086 c 0.0159,-0.04217 1.60489,-4.121546 1.62507,-4.172241 l 0.023,-0.05794 h 0.44749 0.44749 l 0.84585,2.111613 c 0.46522,1.161388 0.84796,2.116852 0.85055,2.123281 0.004,0.0093 -0.0887,0.01111 -0.45704,0.009 l -0.4618,-0.0026 -0.18592,-0.484399 -0.18593,-0.484399 -0.85288,0.0026 -0.85288,0.0026 -0.17585,0.484452 -0.17584,0.484426 h -0.44876 c -0.42357,0 -0.44844,-0.0026 -0.44265,-0.01614 z m 2.6729,-1.673542 c -0.003,-0.0064 -0.13496,-0.363246 -0.29456,-0.793062 -0.15957,-0.429816 -0.29292,-0.778193 -0.2963,-0.774197 -0.005,0.0053 -0.3456,0.938741 -0.57272,1.567259 -0.003,0.009 0.11697,0.01138 0.5819,0.01138 0.46506,0 0.5852,-0.0026 0.58168,-0.01138 z"
id="A" /><path
style="stroke:none;stroke-width:0.264583"
d="m 243.61635,74.871836 v -2.119339 l 1.04767,0.0045 c 1.04455,0.0045 1.15731,0.0079 1.36678,0.03969 0.31785,0.04826 0.57875,0.210662 0.75094,0.467466 0.26178,0.390393 0.21953,0.899689 -0.103,1.241584 -0.0752,0.07972 -0.14721,0.13552 -0.24757,0.19177 l -0.0779,0.04366 0.0631,0.0209 c 0.15386,0.05075 0.31663,0.143987 0.42588,0.243999 0.12821,0.117316 0.23862,0.303504 0.28662,0.483314 0.022,0.08186 0.0243,0.108189 0.0251,0.26215 0,0.133508 -0.003,0.187933 -0.0161,0.247623 -0.0534,0.243126 -0.17126,0.469159 -0.32763,0.628544 -0.20013,0.203968 -0.44561,0.30742 -0.79624,0.335571 -0.17465,0.01402 -1.02536,0.02733 -1.76633,0.02762 h -0.63137 v -2.119339 z m 2.11238,1.397661 c 0.19571,-0.01614 0.30006,-0.05593 0.40346,-0.153458 0.10692,-0.100859 0.15121,-0.217382 0.15087,-0.396848 0,-0.276649 -0.15034,-0.470483 -0.41069,-0.531337 -0.15079,-0.03524 -0.23929,-0.03998 -0.82907,-0.04434 l -0.57587,-0.0042 v 0.56978 0.569807 h 0.57587 c 0.3167,-8e-5 0.62515,-0.0042 0.68543,-0.0093 v 0 z m -0.34084,-1.831075 c 0.30885,-0.0087 0.37068,-0.01773 0.48202,-0.07046 0.11647,-0.05524 0.19852,-0.147002 0.23728,-0.265377 0.0265,-0.08091 0.0265,-0.246115 -2.7e-4,-0.328348 -0.0499,-0.154278 -0.16449,-0.253629 -0.3406,-0.295354 -0.0549,-0.01296 -0.15099,-0.01561 -0.68143,-0.01905 l -0.61749,-0.0037 v 0.495591 0.495617 h 0.32145 c 0.17682,0 0.44635,-0.004 0.59899,-0.0082 v 0 z"
id="B" /><path
style="display:inline;stroke:none;stroke-width:0.264583"
d="m 236.49961,67.784945 v -0.891461 l -0.77073,-1.218485 c -0.42392,-0.670137 -0.77076,-1.220179 -0.77076,-1.222269 0,-0.0026 0.22119,-0.0037 0.49152,-0.0037 l 0.49154,1.06e-4 0.49842,0.839153 c 0.37071,0.624072 0.50107,0.836374 0.50864,0.828331 0.006,-0.0058 0.2272,-0.38362 0.49241,-0.839232 l 0.48218,-0.828437 h 0.4848 0.48477 l -0.0132,0.0209 c -0.007,0.01138 -0.35669,0.563192 -0.77642,1.226185 l -0.76311,1.205415 v 0.887572 0.887571 h -0.42 -0.41998 v -0.891487 z"
id="Y" /></g><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 191.83897,40.180331 c -2.66361,0 -6.92952,2.444663 -10.28988,2.759557 -0.71165,0 -1.19269,0.71711 -1.19269,1.262848 v 2.38538 c 0,0.791848 1.14593,1.389081 1.14593,2.081363 h 8.95687 z"
id="path3930" /><path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 188.684,62.683509 c 0,-5.087943 -6.22371,-8.710414 -8.3013,-13.411065 -0.12094,-0.451335 0.23601,-0.975651 0.67799,-0.975651 h 2.6293 c 4.06201,0 3.22524,-4.668785 5.93659,-6.746875 v 21.139517 z"
id="path3928" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 186.98074,54.481428 h 1.85209 V 49.52049 h -1.88516 c -0.24371,0 -0.35369,0.213125 -0.40515,0.405144 -0.37122,1.385437 -0.3524,2.876767 0,4.191992 0.0556,0.207356 0.19475,0.363802 0.43822,0.363802 z"
id="path3924" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 180.39923,46.246272 c 4.95552,0 5.85804,-1.554427 7.17683,-1.554427"
id="path3932" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 195.21826,39.022718 c 0,0 5.57545,-0.03565 5.5659,0 -0.01,0.03565 0,-1.110837 0,-1.110837 0,-0.29836 -0.22111,-0.491109 -0.49112,-0.491109 h -4.54858 c -0.322,0 -0.5262,0.287943 -0.5262,0.526187 z"
id="path3926" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.40338,46.681662 h 1.60197 c 0.13798,0 0.29231,0.164314 0.29231,0.292325 v 5.133245 c 0,0.161131 -0.16081,0.35664 -0.35663,0.35664 h -1.54932 z"
id="path3942" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.89818,62.268486 c 0.0467,0 1.14591,0 1.14591,0 0.47839,0 0.66651,-0.291941 0.66651,-0.666504 0.17793,-2.017953 0.16751,-4.469606 0,-6.53641 0,-0.347313 -0.3302,-0.687922 -0.68406,-0.684043 h -1.39731 v 7.886957 z"
id="path3948" /><path
id="path3950"
d="m 203.89818,70.547158 c 0.0467,0 1.14591,0 1.14591,0 0.47839,0 0.66651,-0.291941 0.66651,-0.666504 0.17793,-2.017953 0.16751,-4.469606 0,-6.536409 0,-0.347314 -0.3302,-0.687922 -0.68406,-0.684044 h -1.39731 v 7.886957 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.89818,78.82583 c 0.0467,0 1.14591,0 1.14591,0 0.47839,0 0.66651,-0.291941 0.66651,-0.666504 0.17793,-2.017953 0.16751,-4.469606 0,-6.536409 0,-0.347314 -0.3302,-0.687922 -0.68406,-0.684043 h -1.39731 v 7.886956 z"
id="path3952" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.71564,92.250695 c 0.7407,0.20618 2.12495,1.62934 2.44732,3.05063 0.42142,0 1.0056,-0.24751 1.0056,-0.76005 v -4.887686 c 0,-1.02938 3.3482,-0.77035 3.3482,0.89715 v 1.687006 c 1.44034,3.50493 1.46973,6.635122 0,10.056025 v 1.77734 c 0,1.5485 -3.31764,1.86732 -3.31764,0.88896 v -4.79444 c 0,-0.695263 -0.48628,-1.020763 -0.97997,-0.979953 -0.51675,1.247883 -1.1939,2.362123 -2.45327,2.991163 h -0.46773 v -9.930325 z"
id="path3986" /><path
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.40145,122.33051 h 1.5875 c 0.5131,0 0.44132,-8.71471 0,-8.71471 h -1.5875 z"
id="path3998" /><path
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 194.34129,38.730393 h 5.71326 c 2.19093,0 3.68795,2.151269 3.68795,4.115951 V 145.23062 c 0,2.30569 -1.84389,4.04578 -4.0458,4.04578 h -5.16832 c -3.23868,0 -6.06869,-3.32049 -6.06869,-6.06868 V 44.623685 c 0,-2.970212 3.40577,-5.893292 5.8816,-5.893292 z"
id="path3918" /><rect
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3922"
width="10.22559"
height="93.491112"
x="191.06221"
y="44.664806"
rx="1.1287988"
ry="1.1287988" /><rect
style="fill:#2e2f31;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3940"
width="7.9375"
height="16.172655"
x="192.17319"
y="120.92493"
rx="1.1287988"
ry="1.1287988" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3934"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
transform="matrix(0.29141287,0,0,0.29141287,173.97803,249.09703)" /><path
transform="matrix(0.29141287,0,0,0.29141287,173.97803,244.15263)"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
id="path3936"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3938"
d="m 76,-387.61176 -8.184578,-14.17611 16.369155,0 z"
transform="matrix(0.29141287,0,0,0.29141287,173.97803,239.20823)" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.75697,48.759813 h 1.45521"
id="path3944" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 203.74871,50.355582 h 1.49654"
id="path3946" /><rect
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3954"
width="1.9513021"
height="1.9513021"
x="195.14975"
y="79.881424"
rx="0.53348637"
ry="0.53348637" /><rect
ry="0.53348637"
rx="0.53348637"
y="84.213974"
x="195.14975"
height="1.9513021"
width="1.9513021"
id="rect3956"
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><rect
ry="0.53348637"
rx="0.53348637"
y="88.645752"
x="195.14975"
height="1.9513021"
width="1.9513021"
id="rect3958"
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><rect
style="fill:#aaeeff;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3960"
width="1.9513021"
height="1.9513021"
x="195.14975"
y="92.978302"
rx="0.53348637"
ry="0.53348637" /><circle
style="fill:#2e2f31;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3962"
cx="196.10172"
cy="100.48306"
r="1.5743958" /><rect
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3964"
width="4.0018229"
height="9.2934895"
x="194.15756"
y="108.258"
rx="1.4264551"
ry="1.3933822" /><rect
ry="1.3933822"
rx="1.4264551"
y="63.58707"
x="194.15756"
height="9.2934895"
width="4.0018229"
id="rect3966"
style="fill:#999595;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><g
id="SingleRightTrigger1"><path
style="stroke:#000000;stroke-width:0.00353806;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 196.12845,68.242846 h -1.14456 l 8e-5,-0.499748 c 5e-5,-0.27487 0.003,-0.541808 0.003,-0.593214 0.0122,-0.335912 0.0581,-0.482473 0.19028,-0.609404 0.14709,-0.141174 0.37312,-0.199747 0.59762,-0.154871 0.12509,0.025 0.23516,0.08775 0.31917,0.181964 0.0772,0.08655 0.13753,0.225682 0.16179,0.372968 0.005,0.03268 0.007,0.03609 0.0109,0.03008 0.003,-0.0038 0.0204,-0.03066 0.0398,-0.05985 0.081,-0.122563 0.15007,-0.192424 0.28789,-0.2912 0.08,-0.05732 0.1198,-0.08298 0.42137,-0.271735 l 0.25501,-0.1596 v 0.272667 c 0,0.215259 -3e-5,0.273531 -0.003,0.276746 -0.003,0.0024 -0.13737,0.09322 -0.29985,0.202152 -0.46937,0.314672 -0.5225,0.353915 -0.57814,0.426911 -0.0552,0.0724 -0.0689,0.130566 -0.0719,0.305628 l -0.003,0.110558 h 0.47808 0.4781 v 0.229982 0.22997 h -1.14456 z m -0.17875,-1.036653 c -0.004,-0.07511 -0.0116,-0.141507 -0.019,-0.170215 -0.0228,-0.08667 -0.0901,-0.154516 -0.17902,-0.180131 -0.0332,-0.0096 -0.11732,-0.01355 -0.15412,-0.0073 -0.0893,0.01511 -0.15145,0.06 -0.18907,0.136544 -0.0361,0.07341 -0.0386,0.107151 -0.0387,0.51921 l -8e-5,0.278622 h 0.29189 0.29192 l -3e-5,-0.255629 c 0,-0.140587 -0.003,-0.285073 -0.004,-0.321078 z"
id="path3980" /><path
style="stroke:#000000;stroke-width:0.00357862;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 197.31463,69.464205 c -0.0276,0.349896 -0.15028,0.57472 -0.39108,0.716672 -0.0966,0.05693 -0.25265,0.10728 -0.37471,0.120896 l -0.022,0.0024 -0.003,-0.01204 c -0.004,-0.02209 -0.0429,-0.436258 -0.0413,-0.437798 0,-7.93e-4 0.0175,-0.0049 0.0372,-0.0092 0.0196,-0.0042 0.055,-0.0141 0.0786,-0.02193 0.18298,-0.0608 0.28408,-0.175368 0.31713,-0.359391 0.0101,-0.05627 0.0111,-0.179504 0.003,-0.238916 -0.0124,-0.0793 -0.0329,-0.13956 -0.0635,-0.186923 -0.042,-0.065 -0.10097,-0.113795 -0.16415,-0.135983 -0.0357,-0.01254 -0.10745,-0.01617 -0.14301,-0.0072 -0.0698,0.01759 -0.11543,0.0597 -0.15713,0.144986 -0.0292,0.05981 -0.0497,0.128021 -0.11494,0.38399 -0.0594,0.233122 -0.0915,0.327528 -0.1501,0.442497 -0.056,0.109762 -0.13237,0.196009 -0.23,0.259654 -0.10385,0.06769 -0.22175,0.09919 -0.35163,0.09395 -0.11192,-0.0045 -0.21569,-0.03873 -0.31009,-0.102254 -0.0694,-0.04674 -0.11832,-0.09587 -0.16587,-0.166751 -0.0969,-0.14446 -0.14496,-0.335544 -0.14491,-0.575985 5e-5,-0.268025 0.054,-0.471233 0.16486,-0.621223 0.0287,-0.03879 0.0896,-0.09974 0.1284,-0.12841 0.10475,-0.07745 0.22252,-0.120354 0.36336,-0.132395 0.0217,-0.0019 0.0397,-0.0029 0.0403,-0.0024 0.003,0.0016 0.0217,0.461698 0.0204,0.463121 0,7.94e-4 -0.0153,0.0043 -0.0327,0.0081 -0.0982,0.0214 -0.17716,0.06442 -0.22135,0.120573 -0.0342,0.04344 -0.0573,0.09982 -0.0703,0.171505 -0.0103,0.0573 -0.0114,0.186582 -0.003,0.247142 0.0148,0.09486 0.0476,0.172292 0.0942,0.222626 0.0494,0.05334 0.13436,0.06876 0.20273,0.03678 0.0311,-0.01455 0.0677,-0.04974 0.0896,-0.08607 0.0406,-0.06726 0.0793,-0.183383 0.13163,-0.394364 0.0631,-0.254667 0.11104,-0.399426 0.16819,-0.508135 0.0925,-0.175927 0.24591,-0.293883 0.43061,-0.331026 0.0669,-0.01347 0.11663,-0.01733 0.19307,-0.01508 0.0537,0.0016 0.0776,0.004 0.11134,0.01109 0.18626,0.03933 0.34679,0.149262 0.4423,0.302906 0.0672,0.108156 0.11023,0.245999 0.13057,0.418557 0.006,0.04614 0.0101,0.261104 0.006,0.305972 v 0 z"
id="path3982" /></g><g
id="SingleLeftTrigger1"><path
style="stroke:#000000;stroke-width:0.00353806;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 197.34244,114.04543 c -0.0161,0.26704 -0.0954,0.46815 -0.24006,0.60956 -0.10769,0.10529 -0.25516,0.17938 -0.42786,0.21496 -0.0395,0.008 -0.11107,0.0198 -0.11213,0.0183 0,-5.3e-4 -0.01,-0.0955 -0.0206,-0.21125 -0.0111,-0.11578 -0.0209,-0.21648 -0.022,-0.22378 -0.003,-0.0117 0,-0.0133 0.005,-0.0133 0.0124,0 0.0677,-0.0138 0.10834,-0.0269 0.19362,-0.0629 0.29713,-0.1901 0.32044,-0.39394 0.005,-0.0468 0.003,-0.16702 -0.005,-0.21064 -0.0201,-0.1152 -0.0652,-0.20032 -0.13653,-0.2574 -0.0557,-0.0446 -0.10123,-0.0607 -0.17163,-0.0609 -0.0502,0 -0.0782,0.006 -0.11145,0.0255 -0.023,0.0133 -0.0546,0.0446 -0.0729,0.0722 -0.0438,0.0659 -0.0652,0.1313 -0.14298,0.43634 -0.0486,0.19057 -0.0776,0.28142 -0.12168,0.38029 -0.0597,0.1339 -0.13859,0.23102 -0.24667,0.30356 -0.0596,0.04 -0.13629,0.0717 -0.20921,0.0865 -0.0347,0.007 -0.0501,0.008 -0.1211,0.008 -0.0704,-8e-5 -0.086,-0.001 -0.11591,-0.008 -0.17476,-0.0391 -0.32139,-0.1482 -0.4073,-0.3032 -0.0386,-0.0696 -0.0708,-0.15982 -0.0889,-0.24915 -0.0398,-0.19606 -0.0318,-0.45578 0.0196,-0.63905 0.0238,-0.0846 0.0644,-0.17267 0.10869,-0.23529 0.0304,-0.0431 0.10604,-0.11863 0.14946,-0.14939 0.0695,-0.0492 0.16082,-0.0887 0.24681,-0.10675 0.0435,-0.009 0.099,-0.0163 0.12628,-0.0163 h 0.0161 l 3e-5,0.0204 c 2e-5,0.0112 0.004,0.1087 0.009,0.2167 0.005,0.108 0.009,0.20197 0.009,0.20883 v 0.0125 l -0.0309,0.006 c -0.0694,0.0141 -0.13877,0.045 -0.1809,0.0803 -0.082,0.0689 -0.11723,0.17063 -0.11718,0.33883 3e-5,0.11077 0.0148,0.18678 0.0511,0.26075 0.042,0.0858 0.0874,0.12084 0.16159,0.12472 0.0552,0.003 0.0935,-0.0103 0.132,-0.0455 0.0617,-0.0564 0.10247,-0.15984 0.17213,-0.43654 0.0542,-0.21541 0.0786,-0.29731 0.12026,-0.40333 0.0739,-0.18834 0.17608,-0.30894 0.32345,-0.38197 0.0882,-0.0437 0.16936,-0.0629 0.28657,-0.0678 0.13917,-0.006 0.26699,0.0274 0.38915,0.10122 0.13658,0.0825 0.23426,0.21023 0.29001,0.37916 0.0486,0.14727 0.0699,0.34326 0.0582,0.53545 v 0 z"
id="path3978" /><path
style="stroke:#000000;stroke-width:0.00357862;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 196.15959,112.42202 h -1.14872 v -0.23261 -0.2326 h 0.95549 0.95549 v -0.58153 -0.58153 h 0.19326 0.19322 v 0.81413 0.81414 z"
id="path3984" /></g><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 207.15522,94.598865 v 5.126312"
id="path3988" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 206.14166,95.261155 v 4.070322"
id="path3990" /><path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994-6)"
d="m 966.57659,655.39154 v 38"
id="path3992"
transform="matrix(0.26458333,0,0,0.26458333,-45.219666,-81.138444)" /><path
id="path4136"
d="m 228.83717,11.768909 h -1.50119 l -0.004,-0.958829 h -1.60551 c -0.42043,0 -0.55393,0.382418 -0.55393,0.959443 v 8.792554 c 0,0.41027 0.38793,0.732996 0.733,0.732996 h 1.08378 c 0.29528,0 0.31234,-0.319455 0.34277,-0.593693 v -0.583639 h 1.48016 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4138"
d="M 227.33503,20.10013 V 11.831901"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 235.47461,6.5371895 v 1.852083 h -4.96093 v -1.885156 c 0,-0.243708 0.21312,-0.353695 0.40513,-0.405156 1.38543,-0.371211 2.87678,-0.352399 4.192,0 0.20736,0.05556 0.3638,0.194759 0.3638,0.438229 z"
id="path4158" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 240.25852,23.377177 c 0.20619,0.740701 1.62933,2.124948 3.05062,2.447317 0,0.421428 -0.24749,1.005601 -0.76004,1.005601 h -4.8877 c -1.02937,0 -0.77034,3.348197 0.89714,3.348197 h 1.68701 c 3.50494,1.440338 6.63515,1.469734 10.05602,0 h 1.77737 c 1.5485,0 1.86729,-3.317637 0.88895,-3.317637 h -4.79444 c -0.69527,0 -1.02076,-0.486278 -0.97996,-0.979964 1.24788,-0.516758 2.36212,-1.193906 2.99116,-2.45327 v -0.46773 h -9.93031 z"
id="path4146" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 240.77544,23.022239 c 0,0.04675 0,1.14591 0,1.14591 0,0.478393 -0.29195,0.666512 -0.66652,0.666512 -2.01795,0.177932 -4.4696,0.167508 -6.5364,0 -0.34731,0 -0.68791,-0.3302 -0.68405,-0.684054 V 22.75329 h 7.88697 z"
id="path4154" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 242.6067,26.81676 h 5.1263"
id="path4148" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 231.55516,22.329004 v 1.601972 c 0,0.137981 0.1643,0.292312 0.29231,0.292312 h 5.13323 c 0.16113,0 0.35666,-0.160814 0.35666,-0.356632 v -1.54932 z"
id="path4142" /><path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 243.26897,25.803195 h 4.07033"
id="path4150" /><path
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 257.33277,23.022239 c 0,0.04675 0,1.14591 0,1.14591 0,0.478393 -0.29194,0.666512 -0.66649,0.666512 -2.01797,0.177932 -4.4696,0.167508 -6.53642,0 -0.34732,0 -0.68792,-0.3302 -0.68403,-0.684054 V 22.75329 h 7.88694 z"
id="path4144" /><path
transform="matrix(0,0.26458333,0.26458333,0,-36.395078,-244.13604)"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3994-6)"
d="m 1036.7923,1045.6825 v 38"
id="path4152" /><path
id="path4160"
d="m 249.05295,23.022239 c 0,0.04675 0,1.14591 0,1.14591 0,0.478393 -0.29194,0.666512 -0.66648,0.666512 -2.01798,0.177932 -4.46961,0.167508 -6.53643,0 -0.34732,0 -0.68792,-0.3302 -0.68403,-0.684054 V 22.75329 h 7.88694 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4140"
d="m 261.60682,23.187796 c 0.97078,0 2.84427,-1.929288 2.84427,-2.968281 v -6.81302 c -0.52062,-1.975089 -1.52,-4.1207005 -4.7625,-4.7624995 -0.56626,0 -0.85989,-0.533533 -0.85989,-0.859896 -3.06951,-4.0709055 -6.40247,-6.1659027 -9.82266,-7.6067706 h -7.24297 c -3.11539,1.3968147 -4.85232,3.4828691 -6.21771,5.7546866 v 1.256771 c 0,0.518822 -0.42971,1.058334 -0.99218,1.058334 h -6.01927 V 23.196078 Z"
style="fill:#ff5f53;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
id="path4166"
d="m 260.58066,11.099794 h -25.87985 c 0.0135,-5.3886205 2.66343,-10.9675025 7.75962,-10.9675025 l 7.23943,0.086701 c 7.0875,1.2930743 8.54411,6.3837 10.8808,10.8808015 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
id="path4168"
d="m 262.70479,14.654479 h -28.22072 v 3.251237 c 0,1.098243 1.40179,2.731037 2.73105,2.731037 h 23.66899 c 1.445,0 2.51429,-1.396424 2.51429,-2.514288 0,-1.34669 0.11609,-3.467986 -0.69361,-3.467986 z"
style="fill:#44484c;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
style="fill-opacity:1;stroke:#000000;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 244.14165,17.660905 v -1.210392 l 0.55007,6.6e-5 c 0.30252,3.7e-5 0.59417,0.0024 0.64807,0.0049 0.23492,0.01161 0.38338,0.0444 0.48519,0.107175 0.15637,0.09641 0.26062,0.266599 0.28702,0.468542 0.008,0.06163 0.003,0.197848 -0.009,0.254818 -0.0416,0.191362 -0.15044,0.334386 -0.32104,0.421952 -0.0654,0.03355 -0.16529,0.06695 -0.24334,0.08133 -0.0324,0.006 -0.0588,0.01204 -0.0587,0.01349 8e-5,0.0016 0.0175,0.01312 0.0385,0.0259 0.0679,0.04108 0.1387,0.09714 0.19921,0.157761 0.0982,0.09839 0.16031,0.1876 0.42134,0.605501 0.0884,0.141499 0.16391,0.262231 0.16783,0.268296 l 0.007,0.01103 h -0.28882 -0.28884 l -0.19288,-0.287896 c -0.36298,-0.541718 -0.41074,-0.605218 -0.49914,-0.663437 -0.0681,-0.04484 -0.12798,-0.05709 -0.3008,-0.06151 l -0.11637,-0.003 v 0.50791 0.507905 h -0.24257 -0.24257 v -1.210392 z m 1.19824,-0.197742 c 0.10322,-0.01349 0.15547,-0.03687 0.20572,-0.09207 0.0507,-0.0557 0.0711,-0.115819 0.0711,-0.210029 0,-0.130434 -0.0482,-0.216286 -0.14822,-0.264017 -0.0777,-0.03708 -0.0652,-0.03607 -0.47538,-0.03847 l -0.36629,-0.0021 v 0.309613 0.309613 l 0.3271,-0.0024 c 0.25813,-0.0019 0.33952,-0.004 0.386,-0.01011 v 0 z"
id="RightShoulder" /><g
id="RightTrigger"><path
style="fill-opacity:1;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 245.17564,6.1446855 v -1.208246 l 0.62601,0.00212 c 0.66757,0.00212 0.68236,0.00273 0.82087,0.027509 0.0828,0.014843 0.13729,0.031819 0.19719,0.061523 0.0639,0.03167 0.10575,0.06235 0.15301,0.112101 0.0781,0.08226 0.1333,0.187278 0.16198,0.308377 0.0119,0.0507 0.013,0.06345 0.0132,0.162814 8e-5,0.08425 -0.003,0.117107 -0.008,0.150355 -0.0561,0.275579 -0.24551,0.44872 -0.56002,0.512053 -0.0592,0.01193 -0.066,0.01426 -0.0579,0.02011 0.005,0.0036 0.0317,0.02135 0.0591,0.03937 0.14401,0.09456 0.23318,0.189458 0.36915,0.392957 0.0495,0.07413 0.38581,0.60892 0.3906,0.621162 0.003,0.0049 -0.0559,0.0059 -0.2858,0.0049 l -0.28824,-0.0013 -0.21582,-0.321297 c -0.30739,-0.457573 -0.34192,-0.505973 -0.40719,-0.570571 -0.09,-0.08909 -0.15473,-0.109487 -0.36116,-0.113864 l -0.12173,-0.0027 v 0.505378 0.505375 h -0.24257 -0.24257 v -1.208244 z m 1.17118,-0.193157 c 0.0797,-0.0088 0.11668,-0.01786 0.15986,-0.03914 0.0694,-0.03421 0.11798,-0.09792 0.13692,-0.179525 0.0103,-0.04405 0.0101,-0.136223 -2.6e-4,-0.175921 -0.0313,-0.12047 -0.12489,-0.194789 -0.26226,-0.208325 -0.0257,-0.0026 -0.19817,-0.0046 -0.38346,-0.0047 l -0.33689,-1.7e-4 v 0.309306 0.309304 l 0.31729,-0.0026 c 0.18902,-0.0016 0.33811,-0.0049 0.36875,-0.0083 v 0 z"
id="path4234" /><path
style="fill-opacity:1;stroke-width:0.00490037;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 242.89694,7.1346265 v -0.218302 l 0.63461,-0.783873 c 0.34904,-0.431131 0.6346,-0.784953 0.6346,-0.786276 0,-0.0013 -0.2536,-0.0024 -0.56354,-0.0024 h -0.56353 V 5.1404305 4.9370639 h 0.8845 0.88453 v 0.1874836 0.187481 l -0.66442,0.818322 -0.6644,0.818319 0.6889,0.0013 0.68892,0.0013 v 0.200898 0.200898 h -0.98007 -0.9801 v -0.2183 z"
id="path4236" /></g></g></svg>

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1050 1050.5"
style="enable-background:new 0 0 1050 1050.5;"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs7" />
<style
type="text/css"
id="style1">
.st0{fill:#20221F;}
.st1{fill:#3B3B3B;}
.st2{fill:#121212;}
.st3{fill:#444542;}
.st4{fill:#FFFFFF;}
.st5{fill:#444542;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
.st6{fill:#454644;}
.st7{fill:#454644;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
.st8{fill:#3B3C3A;}
.st9{font-family:'Helvetica-Bold';}
.st10{font-size:40px;}
.st11{fill:#0D0D0A;}
</style>
<g
id="Front">
<path
id="Right_Grip_00000028282943321285403220000008369785803052272051_"
class="st0"
d="M766,850.5 c34,28.2,27.6,35.9,68.5,108.5c36.7,74.7,64.4,104.4,125.1,84.1v0c95.3-57.9,59.3-145.3,43.6-275.2c-10-60.6-35.6-190.3-35.6-190.3 L766,850.5z" />
<path
id="Left_Grip"
class="st0"
d="M82.3,577.6c0,0-25.6,129.7-35.6,190.3C31,897.8-5,985.1,90.3,1043v0 c60.8,20.3,88.4-9.4,125.1-84.1c40.9-72.7,34.5-80.3,68.5-108.5L82.3,577.6z" />
<path
id="Right_Bumper_00000006710349871522532470000011078040965381267594_"
class="st1"
d="M676.3,378.4 c10.1-4.3,39.7-22.5,58.7-19.7c59.5,0.9,166.7,17.7,172.6,81.2" />
<path
id="Left_Bumper_00000024680414077879639570000011759596763560342154_"
class="st1"
d="M142.4,439.9 c5.9-63.4,113-80.2,172.6-81.2c19-2.8,48.6,15.4,58.7,19.7" />
<path
id="Background_00000141418846164053065470000016150094984198570163_"
class="st2"
d="M766,850.5 c35.5-30.8,68.5-74.7,96-113.5c26.9-36.3,94.7-136.7,105.6-159.3c0-2.4-6.3-30.1-12.8-56.2C919.1,361.9,702.2,378.1,525,378.1 c-177.4,0-394.1-16.2-429.9,143.3c-6.5,26-12.8,53.8-12.8,56.2c10.9,22.6,78.8,123,105.6,159.3c27.5,38.8,60.5,82.8,96,113.5" />
<g
id="Directional_Pad">
<path
id="Background_00000032628022449190479560000015279211462520783249_"
class="st3"
d="M466.2,683.5h-40c-2.8,0-5-2.2-5-5v-40 c0-2.8-2.2-5-5-5h-30c-2.8,0-5,2.2-5,5v40c0,2.8-2.2,5-5,5h-40c-2.8,0-5,2.2-5,5v30c0,2.8,2.2,5,5,5h40c2.8,0,5,2.2,5,5v40 c0,2.8,2.2,5,5,5h30c2.8,0,5-2.2,5-5v-40c0-2.8,2.2-5,5-5h40c2.8,0,5-2.2,5-5v-30C471.2,685.8,469,683.5,466.2,683.5z" />
<g
id="Arrows">
<g
id="g1">
<polygon
class="st4"
points="393.7,746 408.7,746 401.2,761 "
id="DpadDown" />
</g>
<g
id="g2">
<polygon
class="st4"
points="358.7,696 358.7,711 343.7,703.5 "
id="DpadLeft" />
</g>
<g
id="g3">
<polygon
class="st4"
points="408.7,661 393.7,661 401.2,646 "
id="DpadUp" />
</g>
<g
id="g4">
<polygon
class="st4"
points="443.7,711 443.7,696 458.7,703.5 "
id="DpadRight" />
</g>
</g>
</g>
<g
id="R_Thumbstick_00000152226188525111835500000011838297421350334865_">
<circle
id="Background_00000035532849542660068350000006517224202948159422_"
class="st0"
cx="650.59998"
cy="703.5"
r="55"
style="display:inline" />
<circle
id="RightStick"
class="st5"
cx="650.6"
cy="703.5"
r="45" />
</g>
<g
id="L_Thumbstick_00000047032468231999382210000005512347386782594484_">
<circle
id="Background_00000182502673988292164000000007125719133096369561_"
class="st0"
cx="240.2"
cy="564.8"
r="55" />
<circle
id="LeftStick"
class="st5"
cx="240.2"
cy="564.8"
r="45" />
</g>
<g
id="Minus_Button">
<circle
id="_Background_00000120554951013892796430000015877571645746699662_"
class="st6"
cx="401"
cy="489.3"
r="22.5" />
<polyline
id="Back"
class="st4"
points="386.2,491.8 386.2,486.8 416.2,486.8 416.2,491.8 " />
</g>
<g
id="Plus_Button">
<circle
id="_Background"
class="st6"
cx="650.4"
cy="489.6"
r="22.5" />
<polygon
id="Start"
class="st4"
points="665.6,487.1 653.1,487.1 653.1,474.4 648.1,474.4 648.1,487.1 635.6,487.1 635.6,492.1 648.1,492.1 648.1,504.4 653.1,504.4 653.1,492.1 665.6,492.1 " />
</g>
<g
id="Home_Button_00000029758737660217614780000001403165237001195407_">
<circle
id="_Background_00000132788487854287834010000009548421243227981499_"
class="st6"
cx="605.4"
cy="564.8"
r="22.5" />
<path
id="Home"
class="st4"
d="M605.4,549.8l-15,15h5v15h20v-15h5L605.4,549.8z M610.4,574.8h-10v-10h10V574.8z" />
</g>
<g
id="Capture_Button_00000105394663133565750060000017455731898661404072_">
<path
class="st6"
d="M468.6,586.5h-30c-2.8,0-5-2.2-5-5v-29.5c0-2.8,2.2-5,5-5h30c2.8,0,5,2.2,5,5v29.5 C473.6,584.2,471.4,586.5,468.6,586.5z"
id="path4" />
<circle
class="st7"
cx="453.6"
cy="566.7"
r="15"
id="circle4" />
</g>
<g
id="Buttons_00000023239109225132251950000005218343074279628213_">
<g
id="A_Button">
<circle
id="Background_00000006699118933065716380000004636085088820886913_"
class="st8"
cx="863.9"
cy="564.8"
r="35" />
<text
transform="matrix(1 0 0 1 849.4224 578.6607)"
class="st4 st9 st10"
id="A">A</text>
</g>
<g
id="X_Button">
<circle
id="Background_00000083074713085756701790000016893839312974798515_"
class="st8"
cx="793.9"
cy="494.8"
r="35" />
<text
transform="matrix(1 0 0 1 780.5266 508.6604)"
class="st4 st9 st10"
id="X">X</text>
</g>
<g
id="Y_Button_00000100344340438574137780000014238704828967683973_">
<circle
id="Background_00000137100455694543496620000011124722786613194377_"
class="st8"
cx="723.9"
cy="564.8"
r="35" />
<text
transform="matrix(1 0 0 1 710.5263 578.661)"
class="st4 st9 st10"
id="Y">Y</text>
</g>
<g
id="B_Button_00000041994261956088037220000013468634544777304733_">
<circle
id="Background_00000096038108578846046800000001873940014252420514_"
class="st8"
cx="793.9"
cy="634.8"
r="35" />
<text
transform="matrix(1 0 0 1 780.9706 648.6605)"
class="st4 st9 st10"
id="B">B</text>
</g>
</g>
</g>
<g
id="Top_Down">
<path
id="Left_Grip_00000026131988385328425370000016677941743356253314_"
class="st0"
d="M219.2,78.5 c-12.5-17.6-25.9-42.3-45.6-58.6C153.5,3.3,112.1-4.7,87.1,5.8c-13.9,5.8-33.4,33.1-42.7,52.8C33.9,80.9,30.4,109.9,32,141.4 c1.2,25.1,5.3,51.7,14.2,78.6c0,0,14.3,53.8,42.8,80.8c11.2,10.6,35,26.6,35,26.6l116-217.5C240,109.9,224.6,86.2,219.2,78.5z" />
<path
id="Right_Grip_00000016782759094708820330000002450847065936193693_"
class="st0"
d="M828.6,78.5 c12.5-17.6,25.9-42.3,45.6-58.6c20.1-16.6,61.4-24.5,86.5-14.1c13.9,5.8,33.4,33.1,42.7,52.8c10.5,22.3,13.9,51.3,12.4,82.8 c-1.2,25.1-5.3,51.7-14.2,78.6c0,0-14.3,53.8-42.8,80.8c-11.2,10.6-35,26.6-35,26.6l-116-217.5C807.8,109.9,823.2,86.2,828.6,78.5z " />
<path
id="Background_00000169534857628063347190000007586592143875928969_"
class="st11"
d="M866,122.2 c66.3,18.7,85.1,128.8,69,186c-2.5,54.2-148.9,15.3-265.1,31.2c-41.1,1.7-91.8,2.4-145.9,2.3c-54.1,0-104.8-0.6-145.9-2.3 c-116.2-15.9-262.6,23.1-265.1-31.2c-16.1-57.1,2.6-167.3,69-186l60.5-18.8l38.9-1.9c40.2,0.1,142.8,0,242.7,0 c99.9,0,202.4,0.1,242.7,0l38.9,1.9L866,122.2z" />
<g
id="ZL_Trigger_00000005254517714433203260000014117442438696169895_">
<path
id="Background_00000111870097528015387240000017384507710402295183_"
class="st1"
d="M145.9,239.2 c15.2-97.4,38.1-147.2,141.7-137c8.2,16.4,43.3,83,50.6,105.7C280.6,227.2,204.7,225.6,145.9,239.2z" />
<text
id="LeftTrigger"
transform="matrix(1.0139 0 0 1 218.3906 179.3992)"
class="st4 st9 st10">ZL</text>
</g>
<g
id="ZR_Trigger">
<path
id="Background_00000133526766189752063450000016781240006605114763_"
class="st1"
d="M716.2,207.9 c7.4-22.7,42.5-89.3,50.6-105.7c103.7-10.2,126.5,39.6,141.7,137C849.8,225.6,773.8,227.2,716.2,207.9z" />
<text
id="RightTrigger"
transform="matrix(1.0139 0 0 1 784.2356 179.3992)"
class="st4 st9 st10">ZR</text>
</g>
<g
id="R_Trigger_00000085939413106284991650000014018840000393673094_">
<path
id="Background"
class="st1"
d="M664,318.5c7-10.1,27.8-78.4,45.4-78.7C1040.8,243.7,897.1,334,664,318.5z" />
<text
id="RightShoulder"
transform="matrix(1 0 0 1 769.6461 292.8947)"
class="st4 st9 st10">R</text>
</g>
<g
id="L_Trigger">
<path
id="Background_00000043427985111927735300000011910735497762731703_"
class="st1"
d="M340.6,238.6 c17.6,0.3,38.4,68.6,45.4,78.7C152.9,332.8,9.2,242.6,340.6,238.6z" />
<text
id="LeftShoulder"
transform="matrix(1 0 0 1 253.7327 291.7279)"
class="st4 st9 st10">L</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -4298,12 +4298,12 @@
}
},
{
"ID": "SettingsTabSystemAudioBackendSDL2",
"ID": "SettingsTabSystemAudioBackendSDL3",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "SDL2",
"en_US": "SDL3",
"es_ES": "",
"fr_FR": "",
"he_IL": "",

View File

@ -1,6 +1,6 @@
using DiscordRPC;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Ava;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration;
@ -157,7 +157,7 @@ namespace Ryujinx.Headless
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Backend = InputBackendType.GamepadSDL3,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
@ -335,7 +335,7 @@ namespace Ryujinx.Headless
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
new SDL3HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,

View File

@ -21,8 +21,8 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Generic;
using System.IO;
@ -59,7 +59,7 @@ namespace Ryujinx.Headless
AutoResetEvent invoked = new(false);
// MacOS must perform SDL polls from the main thread.
SDL2Driver.MainThreadDispatcher = action =>
SDL3Driver.MainThreadDispatcher = action =>
{
invoked.Reset();
@ -180,7 +180,7 @@ namespace Ryujinx.Headless
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
_inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver());
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;

View File

@ -1,9 +1,9 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using SharpMetal.QuartzCore;
using System.Runtime.Versioning;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Headless
{
@ -35,7 +35,7 @@ namespace Ryujinx.Headless
_caMetalLayer = new CAMetalLayer(SDL_Metal_GetLayer(SDL_Metal_CreateView(WindowHandle)));
}
SDL2Driver.MainThreadDispatcher?.Invoke(CreateLayer);
SDL3Driver.MainThreadDispatcher?.Invoke(CreateLayer);
}
protected override void InitializeRenderer() { }

View File

@ -5,15 +5,15 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Headless
{
class OpenGLWindow : WindowBase
{
private static void CheckResult(int result)
private static void CheckResult(bool result)
{
if (result < 0)
if (!result)
{
throw new InvalidOperationException($"SDL_GL function returned an error: {SDL_GetError()}");
}
@ -21,21 +21,21 @@ namespace Ryujinx.Headless
private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel)
{
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 3));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 3));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLProfile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STEREO, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ACCELERATED_VISUAL, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_RED_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_GREEN_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_BLUE_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ALPHA_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DEPTH_SIZE, 16));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STENCIL_SIZE, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DOUBLEBUFFER, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STEREO, 0));
}
private class OpenToolkitBindingsContext : IBindingsContext
@ -46,35 +46,35 @@ namespace Ryujinx.Headless
}
}
private class SDL2OpenGLContext : IOpenGLContext
private class SDL3OpenGLContext : IOpenGLContext
{
private readonly nint _context;
private readonly nint _window;
private readonly bool _shouldDisposeWindow;
public SDL2OpenGLContext(nint context, nint window, bool shouldDisposeWindow = true)
public SDL3OpenGLContext(nint context, nint window, bool shouldDisposeWindow = true)
{
_context = context;
_window = window;
_shouldDisposeWindow = shouldDisposeWindow;
}
public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext)
public static SDL3OpenGLContext CreateBackgroundContext(SDL3OpenGLContext sharedContext)
{
sharedContext.MakeCurrent();
// Ensure we share our contexts.
SetupOpenGLAttributes(true, GraphicsDebugLevel.None);
nint windowHandle = SDL_CreateWindow("Ryujinx background context window", 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
nint windowHandle = SDL_CreateWindow("Ryujinx background context window", 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
nint context = SDL_GL_CreateContext(windowHandle);
GL.LoadBindings(new OpenToolkitBindingsContext());
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0));
CheckResult(SDL_GL_MakeCurrent(windowHandle, nint.Zero));
return new SDL2OpenGLContext(context, windowHandle);
return new SDL3OpenGLContext(context, windowHandle);
}
public void MakeCurrent()
@ -84,9 +84,7 @@ namespace Ryujinx.Headless
return;
}
int res = SDL_GL_MakeCurrent(_window, _context);
if (res != 0)
if (!SDL_GL_MakeCurrent(_window, _context))
{
string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\"";
@ -100,7 +98,7 @@ namespace Ryujinx.Headless
public void Dispose()
{
SDL_GL_DeleteContext(_context);
SDL_GL_DestroyContext(_context);
if (_shouldDisposeWindow)
{
@ -108,8 +106,8 @@ namespace Ryujinx.Headless
}
}
}
private SDL2OpenGLContext _openGLContext;
private SDL3OpenGLContext _openGLContext;
public OpenGLWindow(
InputManager inputManager,
@ -141,10 +139,10 @@ namespace Ryujinx.Headless
}
// NOTE: The window handle needs to be disposed by the thread that created it and is handled separately.
_openGLContext = new SDL2OpenGLContext(context, WindowHandle, false);
_openGLContext = new SDL3OpenGLContext(context, WindowHandle, false);
// First take exclusivity on the OpenGL context.
((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext));
((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL3OpenGLContext.CreateBackgroundContext(_openGLContext));
_openGLContext.MakeCurrent();
@ -160,7 +158,7 @@ namespace Ryujinx.Headless
else if (IsFullscreen)
{
// NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow.
if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0)
if (!SDL_GetDisplayBounds((uint)DisplayId, out SDL_Rect displayBounds))
{
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");

View File

@ -1,10 +1,10 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
using static SDL3.SDL;
namespace Ryujinx.Headless
{
@ -50,7 +50,7 @@ namespace Ryujinx.Headless
void CreateSurface()
{
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE)
if (!SDL_Vulkan_CreateSurface(WindowHandle, instance, IntPtr.Zero, out surfaceHandle))
{
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
@ -60,9 +60,9 @@ namespace Ryujinx.Headless
}
}
if (SDL2Driver.MainThreadDispatcher != null)
if (SDL3Driver.MainThreadDispatcher != null)
{
SDL2Driver.MainThreadDispatcher(CreateSurface);
SDL3Driver.MainThreadDispatcher(CreateSurface);
}
else
{
@ -74,23 +74,19 @@ namespace Ryujinx.Headless
public unsafe string[] GetRequiredInstanceExtensions()
{
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, nint.Zero) == SDL_bool.SDL_TRUE)
nint rawExtensions = SDL_Vulkan_GetInstanceExtensions(out uint count);
IntPtr[] extensionPointers = new IntPtr[count];
Marshal.Copy(rawExtensions, extensionPointers, 0, (int)count);
if (rawExtensions != nint.Zero)
{
nint[] rawExtensions = new nint[(int)extensionsCount];
string[] extensions = new string[(int)extensionsCount];
fixed (nint* rawExtensionsPtr = rawExtensions)
string[] extensions = new string[(int)count];
for (int i = 0; i < extensions.Length; i++)
{
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (nint)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
{
for (int i = 0; i < extensions.Length; i++)
{
extensions[i] = Marshal.PtrToStringUTF8(rawExtensions[i]);
}
return extensions;
}
extensions[i] = Marshal.PtrToStringUTF8(extensionPointers[i]);
}
return extensions;
}
string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\"";

View File

@ -1,7 +1,7 @@
using Humanizer;
using LibHac.Ns;
using LibHac.Util;
using Ryujinx.Ava;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@ -16,8 +16,8 @@ using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.HLE.UI;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -25,7 +25,7 @@ using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using static SDL3.SDL;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
@ -38,7 +38,7 @@ namespace Ryujinx.Headless
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
private const int TargetFps = 60;
private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS;
private SDL_WindowFlags FullscreenFlag = 0;
private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
@ -71,7 +71,7 @@ namespace Ryujinx.Headless
public ScalingFilter ScalingFilter { get; set; }
public int ScalingFilterLevel { get; set; }
protected SDL2MouseDriver MouseDriver;
protected SDL3MouseDriver MouseDriver;
private readonly InputManager _inputManager;
private readonly IKeyboard _keyboardInterface;
protected readonly GraphicsDebugLevel GlLogLevel;
@ -100,7 +100,7 @@ namespace Ryujinx.Headless
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
{
MouseDriver = new SDL2MouseDriver(hideCursorMode);
MouseDriver = new SDL3MouseDriver(hideCursorMode);
_inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager();
@ -117,7 +117,7 @@ namespace Ryujinx.Headless
_ignoreControllerApplet = ignoreControllerApplet;
HostUITheme = new HeadlessHostUiTheme();
SDL2Driver.Instance.Initialize();
SDL3Driver.Instance.Initialize();
}
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
@ -156,11 +156,11 @@ namespace Ryujinx.Headless
{
fixed (byte* iconPtr = iconBytes)
{
nint rwOpsStruct = SDL_RWFromConstMem((nint)iconPtr, iconBytes.Length);
nint iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
nint rwOpsStruct = SDL_IOFromConstMem((nint)iconPtr, (nuint)iconBytes.Length);
SDL_Surface* iconHandle = SDL_LoadBMP_IO(rwOpsStruct, true);
SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_FreeSurface(iconHandle);
SDL_SetWindowIcon(WindowHandle, (nint)iconHandle);
SDL_DestroySurface((nint)iconHandle);
}
}
}
@ -168,7 +168,7 @@ namespace Ryujinx.Headless
private void InitializeWindow()
{
ProcessResult activeProcess = Device.Processes.ActiveApplication;
ApplicationControlProperty nacp = activeProcess.ApplicationControlProperties;
ApplicationControlProperty nacp = activeProcess.ApplicationControlProperties;
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
@ -184,16 +184,16 @@ namespace Ryujinx.Headless
Width = ExclusiveFullscreenWidth;
Height = ExclusiveFullscreenHeight;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (IsFullscreen)
{
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | WindowFlags);
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", Width, Height, DefaultFlags | FullscreenFlag | WindowFlags);
if (WindowHandle == nint.Zero)
{
@ -207,16 +207,18 @@ namespace Ryujinx.Headless
SetWindowIcon();
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
SDL3Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
}
private void HandleWindowEvent(SDL_Event evnt)
{
if (evnt.type == SDL_EventType.SDL_WINDOWEVENT)
if (evnt.type >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST &&
evnt.type <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST)
{
switch (evnt.window.windowEvent)
switch (evnt.window.type)
{
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_EventType.SDL_EVENT_WINDOW_RESIZED:
case SDL_EventType.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
// As we don't need this to fire in either case we can test for fullscreen.
@ -229,7 +231,7 @@ namespace Ryujinx.Headless
}
break;
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
Exit();
break;
}
@ -409,7 +411,7 @@ namespace Ryujinx.Headless
// Get screen touch position
if (!_enableMouse)
{
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL2MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat());
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL3MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat());
}
if (!hasTouch)
@ -516,25 +518,33 @@ namespace Ryujinx.Headless
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
{
SDL_MessageBoxData data = new()
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
unsafe
{
title = title,
message = message,
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
numbuttons = buttonsText.Length,
window = WindowHandle,
};
for (int i = 0; i < buttonsText.Length; i++)
{
data.buttons[i] = new SDL_MessageBoxButtonData
for (int i = 0; i < buttonsText.Length; i++)
{
buttonid = i,
text = buttonsText[i],
};
}
fixed (byte* button = buttonsText[i].ToBytes())
{
buttons[i] = new SDL_MessageBoxButtonData { buttonID = i, text = button, };
}
}
SDL_ShowMessageBox(ref data, out int _);
fixed (byte* t = title.ToBytes())
fixed (byte* m = message.ToBytes())
fixed (SDL_MessageBoxButtonData* b = buttons)
{
SDL_MessageBoxData data = new()
{
title = t,
message = m,
buttons = b,
numbuttons = buttonsText.Length,
window = WindowHandle,
};
SDL_ShowMessageBox(ref data, out int _);
}
}
return true;
}
@ -552,11 +562,11 @@ namespace Ryujinx.Headless
TouchScreenManager?.Dispose();
NpadManager.Dispose();
SDL2Driver.Instance.UnregisterWindow(_windowId);
SDL3Driver.Instance.UnregisterWindow(_windowId);
SDL_DestroyWindow(WindowHandle);
SDL2Driver.Instance.Dispose();
SDL3Driver.Instance.Dispose();
}
}

View File

@ -1,6 +1,5 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
@ -144,11 +143,6 @@ namespace Ryujinx.Ava.Input
}
}
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
}
public void SetTriggerThreshold(float triggerThreshold) { }
public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }

View File

@ -59,8 +59,6 @@ namespace Ryujinx.Ava.Input
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
}
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
protected virtual void Dispose(bool disposing)
{
if (disposing)

View File

@ -1,5 +1,4 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using System;
using System.Drawing;
@ -75,11 +74,6 @@ namespace Ryujinx.Ava.Input
throw new NotImplementedException();
}
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaMouse");
}
public void SetTriggerThreshold(float triggerThreshold)
{
throw new NotImplementedException();

View File

@ -3,7 +3,6 @@ using Avalonia.Controls;
using Avalonia.Input;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Numerics;
using MouseButton = Ryujinx.Input.MouseButton;
using Size = System.Drawing.Size;
@ -135,8 +134,6 @@ namespace Ryujinx.Ava.Input
return new AvaloniaMouse(this);
}
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
public void Dispose()
{
if (_isDisposed)

View File

@ -5,11 +5,9 @@ using Gommon;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using Projektanker.Icons.Avalonia.MaterialDesign;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Ava.Utilities.SystemInfo;
using Ryujinx.Common;
@ -19,7 +17,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Generic;
using System.IO;
@ -126,9 +124,9 @@ namespace Ryujinx.Ava
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
// Initialize SDL3 driver
SDL3Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();

View File

@ -11,6 +11,7 @@
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationManifest>app.manifest</ApplicationManifest>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
<CETCompat>false</CETCompat>
</PropertyGroup>
<Target Name="BuildValidationProj" BeforeTargets="BeforeBuild">
@ -70,12 +71,12 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
@ -134,6 +135,10 @@
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
<None Remove="Assets\Icons\Controller_ProCon.svg" />
<None Remove="Assets\Icons\Controller_JoyConLeft_Settings.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair_Settings.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight_Settings.svg" />
<None Remove="Assets\Icons\Controller_ProCon_Settings.svg" />
</ItemGroup>
<ItemGroup>
@ -156,6 +161,10 @@
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft_Settings.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair_Settings.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight_Settings.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon_Settings.svg" />
<EmbeddedResource Include="Assets\UIImages\Icon_NCA.png" />
<EmbeddedResource Include="Assets\UIImages\Icon_NRO.png" />
<EmbeddedResource Include="Assets\UIImages\Icon_NSO.png" />
@ -174,6 +183,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\Fonts\Mono\" />
<Folder Include="bin\Debug\net9.0\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,146 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using System;
using System.Linq;
using System.Numerics;
namespace Ryujinx.Ava.UI.Controls
{
public class Motion : Control
{
private double _XRotation = 0;
private double _YRotation = 0;
private double _ZRotation = 0;
private bool _isRight;
double length = 11;
double width = 4;
double height = 27;
public void UpdateRotationFromMotionData(Vector3 accelerometerData, Vector3 gyroData, bool isRight = false)
{
_XRotation = Math.Atan2(-accelerometerData.Y, -accelerometerData.Z) * 180 / Math.PI;
//TODO: issue
_YRotation = Math.Atan2(-accelerometerData.X, -accelerometerData.Y) * 180 / Math.PI;
_ZRotation = Math.Atan2(accelerometerData.X, -accelerometerData.Z) * 180 / Math.PI;
_isRight = isRight;
}
public override void Render(DrawingContext context)
{
base.Render(context);
double size = new[] { length, width, height }.Max();
var centerX = _isRight ? -size : size;
DrawCube(context, centerX, size, _XRotation, 0, 0);
if (_isRight)
{
DrawCube(context, centerX, 4 * size, -_YRotation - 90, 180, 90);
}
else
{
DrawCube(context, centerX, 4 * size, _YRotation + 90, 0, 90);
}
DrawCube(context, centerX, 7 * size, 0, 0, _ZRotation);
}
private void DrawCube(DrawingContext context, double centerX, double centerY, double xRotation,
double yRotation, double zRotation)
{
Point3D[] cubeVertices =
[
new(-length, -height, -width), new(length, -height, -width), new(length, height, -width),
new(-length, height, -width), new(-length, -height, width), new(length, -height, width),
new(length, height, width), new(-length, height, width)
];
Point[] projectedPoints = new Point[cubeVertices.Length];
Point3D[] rotatedVertices = new Point3D[cubeVertices.Length];
for (int i = 0; i < cubeVertices.Length; i++)
{
Point3D rotatedPoint = RotatePoint(cubeVertices[i], xRotation, yRotation, zRotation);
rotatedVertices[i] = rotatedPoint;
double projectedX = centerX + rotatedPoint.X / (1 - rotatedPoint.Z / 200);
double projectedY = centerY + rotatedPoint.Y / (1 - rotatedPoint.Z / 200);
projectedPoints[i] = new Point(projectedX, projectedY);
}
int[][] cubeFaces =
[
[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 5, 4], [2, 3, 7, 6],
[0, 3, 7, 4], [1, 2, 6, 5]
];
IImmutableSolidColorBrush[] faceColors = _isRight
?
[
Brushes.DimGray, Brushes.IndianRed, Brushes.DimGray, Brushes.IndianRed, Brushes.DimGray,
Brushes.IndianRed
]
:
[
Brushes.DimGray, Brushes.SkyBlue, Brushes.DimGray, Brushes.SkyBlue, Brushes.SkyBlue, Brushes.DimGray
];
var sortedFaces = cubeFaces
.Select((face, index) => new
{
Face = face,
Color = faceColors[index],
MinZ = face.Min(vertexIndex => rotatedVertices[vertexIndex].Z)
})
.OrderBy(faceData => faceData.MinZ)
.ToArray();
foreach (var faceData in sortedFaces)
{
int[] face = faceData.Face;
IImmutableSolidColorBrush color = faceData.Color;
PathGeometry faceGeometry = new();
PathFigure faceFigure = new()
{
StartPoint = projectedPoints[face[0]], IsClosed = true, IsFilled = true
};
for (int j = 1; j < face.Length; j++)
{
faceFigure.Segments.Add(new LineSegment { Point = projectedPoints[face[j]] });
}
faceGeometry.Figures.Add(faceFigure);
context.DrawGeometry(color, new Pen(Brushes.White), faceGeometry);
}
}
private Point3D RotatePoint(Point3D point, double xRotation, double yRotation, double zRotation)
{
double radX = xRotation * Math.PI / 180;
double radY = yRotation * Math.PI / 180;
double radZ = zRotation * Math.PI / 180;
double cosX = Math.Cos(radX), sinX = Math.Sin(radX);
double cosY = Math.Cos(radY), sinY = Math.Sin(radY);
double cosZ = Math.Cos(radZ), sinZ = Math.Sin(radZ);
double newX = cosY * cosZ * point.X + (cosY * sinZ * point.Y) - (sinY * point.Z);
double newY = (sinX * sinY * cosZ - cosX * sinZ) * point.X + (sinX * sinY * sinZ + cosX * cosZ) * point.Y +
sinX * cosY * point.Z;
double newZ = (cosX * sinY * cosZ + sinX * sinZ) * point.X + (cosX * sinY * sinZ - sinX * cosZ) * point.Y +
cosX * cosY * point.Z;
return new Point3D(newX, newY, newZ);
}
private record Point3D(double X, double Y, double Z);
}
}

View File

@ -1,4 +1,3 @@
using Avalonia.Media;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
@ -409,58 +408,6 @@ namespace Ryujinx.Ava.UI.Models.Input
OnPropertyChanged();
}
}
private bool _enableLedChanging;
public bool EnableLedChanging
{
get => _enableLedChanging;
set
{
_enableLedChanging = value;
OnPropertyChanged();
}
}
public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;
private bool _turnOffLed;
public bool TurnOffLed
{
get => _turnOffLed;
set
{
_turnOffLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private bool _useRainbowLed;
public bool UseRainbowLed
{
get => _useRainbowLed;
set
{
_useRainbowLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private Color _ledColor;
public Color LedColor
{
get => _ledColor;
set
{
_ledColor = value;
OnPropertyChanged();
}
}
public GamepadInputConfig(InputConfig config)
{
@ -536,28 +483,15 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = controllerInput.Rumble.WeakRumble;
StrongRumble = controllerInput.Rumble.StrongRumble;
}
if (controllerInput.Led != null)
{
EnableLedChanging = controllerInput.Led.EnableLed;
TurnOffLed = controllerInput.Led.TurnOffLed;
UseRainbowLed = controllerInput.Led.UseRainbow;
uint rawColor = controllerInput.Led.LedColor;
byte alpha = (byte)(rawColor >> 24);
byte red = (byte)(rawColor >> 16);
byte green = (byte)(rawColor >> 8);
byte blue = (byte)(rawColor % 256);
LedColor = new Color(alpha, red, green, blue);
}
}
}
public InputConfig GetConfig()
{
StandardControllerInputConfig config = new()
var config = new StandardControllerInputConfig
{
Id = Id,
Backend = InputBackendType.GamepadSDL2,
Backend = InputBackendType.GamepadSDL3,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
@ -606,13 +540,6 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = WeakRumble,
StrongRumble = StrongRumble,
},
Led = new LedConfigController
{
EnableLed = EnableLedChanging,
TurnOffLed = this.TurnOffLed,
UseRainbow = UseRainbowLed,
LedColor = LedColor.ToUInt32()
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,

View File

@ -5,7 +5,6 @@ using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.UI.Views.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
@ -41,7 +40,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private SvgImage _image;
public InputViewModel ParentModel { get; }
public readonly InputViewModel ParentModel;
[ObservableProperty] private string _leftStickPosition;
[ObservableProperty] private string _rightStickPosition;
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{
@ -60,11 +63,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{
await RumbleInputView.Show(this);
}
public async void ShowLedConfig()
{
await LedInputView.Show(this);
}
public void OnParentModelChanged()
{
@ -72,5 +70,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
IsRight = ParentModel.IsRight;
Image = ParentModel.Image;
}
public void UpdateImageCss(string css)
{
Image = new SvgImage { Source = ParentModel.Image.Source, Css = css };
}
}
}

View File

@ -3,7 +3,6 @@ using Avalonia.Controls;
using Avalonia.Svg.Skia;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
@ -35,10 +34,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public partial class InputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg";
private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair.svg";
private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg";
private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg";
private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon_Settings.svg";
private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair_Settings.svg";
private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft_Settings.svg";
private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight_Settings.svg";
private const string KeyboardString = "keyboard";
private const string ControllerString = "controller";
private readonly MainWindow _mainWindow;
@ -55,18 +54,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; }
private IGamepad _selectedGamepad;
public IGamepad SelectedGamepad
{
get => _selectedGamepad;
private set
{
_selectedGamepad = value;
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
}
}
public IGamepad SelectedGamepad { get; private set; }
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@ -81,9 +69,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsRight { get; set; }
public bool IsLeft { get; set; }
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
@ -198,7 +183,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
image.Source = source;
}
return image;
}
}
@ -595,7 +579,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Backend = InputBackendType.GamepadSDL3,
Id = id,
ControllerType = ControllerType.ProController,
DeadzoneLeft = 0.1f,
@ -612,8 +596,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger0,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger0,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
@ -631,8 +615,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger1,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger1,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{

View File

@ -1,53 +0,0 @@
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Ryujinx.Ava.UI.Helpers;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public partial class LedInputViewModel : BaseModel
{
public required InputViewModel ParentModel { get; init; }
public RelayCommand LedDisabledChanged => Commands.Create(() =>
{
if (!EnableLedChanging) return;
if (TurnOffLed)
ParentModel.SelectedGamepad.ClearLed();
else
ParentModel.SelectedGamepad.SetLed(LedColor.ToUInt32());
});
[ObservableProperty] private bool _enableLedChanging;
[ObservableProperty] private Color _ledColor;
public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;
private bool _turnOffLed;
public bool TurnOffLed
{
get => _turnOffLed;
set
{
_turnOffLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private bool _useRainbowLed;
public bool UseRainbowLed
{
get => _useRainbowLed;
set
{
_useRainbowLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
}
}

View File

@ -5,7 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
@ -212,7 +212,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDebug { get; set; }
public bool IsOpenAlEnabled { get; set; }
public bool IsSoundIoEnabled { get; set; }
public bool IsSDL2Enabled { get; set; }
public bool IsSDL3Enabled { get; set; }
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
@ -373,13 +373,13 @@ namespace Ryujinx.Ava.UI.ViewModels
{
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
IsSDL3Enabled = SDL3HardwareDeviceDriver.IsSupported;
await Dispatcher.UIThread.InvokeAsync(() =>
{
OnPropertyChanged(nameof(IsOpenAlEnabled));
OnPropertyChanged(nameof(IsSoundIoEnabled));
OnPropertyChanged(nameof(IsSDL2Enabled));
OnPropertyChanged(nameof(IsSDL3Enabled));
});
}

View File

@ -16,7 +16,9 @@
x:DataType="viewModels:ControllerInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
Focusable="True"
Unloaded="Control_OnUnloaded"
>
<Design.DataContext>
<viewModels:ControllerInputViewModel />
</Design.DataContext>
@ -184,6 +186,9 @@
<TextBlock
HorizontalAlignment="Center"
Text="{ext:Locale ControllerSettingsStickDeadzone}" />
<TextBlock
HorizontalAlignment="Center"
Text="{Binding LeftStickPosition }" />
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
@ -318,6 +323,19 @@
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas>
<controls:Motion
x:Name="LeftCubeCanvas"
Canvas.Left="5"
Canvas.Top="5"
ZIndex="1" />
<controls:Motion
x:Name="RightCubeCanvas"
Canvas.Right="5"
Canvas.Top="5"
ZIndex="1" />
</Canvas>
<!-- Controller Picture -->
<Image
Margin="0,10"
@ -429,7 +447,7 @@
</StackPanel>
</StackPanel>
</Border>
<!-- Motion, Rumble, LED -->
<!-- Motion + Rumble -->
<StackPanel
Margin="0,10,0,0"
Spacing="5"
@ -487,32 +505,6 @@
</Button>
</Grid>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox
Margin="10, 10, 5, 10"
MinWidth="0"
Grid.Column="0"
IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}">
<TextBlock Text="{ext:Locale ControllerSettingsLed}" />
</CheckBox>
<Button
Margin="10"
Grid.Column="1"
Command="{Binding ShowLedConfig}">
<TextBlock Text="{ext:Locale ControllerSettingsConfigureGeneral}" />
</Button>
</Grid>
</Border>
</StackPanel>
</StackPanel>
<!-- Right Controls -->
@ -743,6 +735,9 @@
<TextBlock
HorizontalAlignment="Center"
Text="{ext:Locale ControllerSettingsStickDeadzone}" />
<TextBlock
HorizontalAlignment="Center"
Text="{Binding RightStickPosition }" />
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"

View File

@ -4,12 +4,19 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using FluentAvalonia.UI.Controls;
using Avalonia.Threading;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using Ryujinx.Input.HLE;
using System;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Button = Ryujinx.Input.Button;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
@ -18,6 +25,8 @@ namespace Ryujinx.Ava.UI.Views.Input
public partial class ControllerInputView : UserControl
{
private ButtonKeyAssigner _currentAssigner;
private volatile bool _isRunning = true;
private const float StickMaxPosition = 3;
public ControllerInputView()
{
@ -38,6 +47,8 @@ namespace Ryujinx.Ava.UI.Views.Input
break;
}
}
StartUpdatingData();
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
@ -222,7 +233,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{
IButtonAssigner assigner;
ControllerInputViewModel controllerInputViewModel = DataContext as ControllerInputViewModel;
var controllerInputViewModel = DataContext as ControllerInputViewModel;
assigner = new GamepadButtonAssigner(
controllerInputViewModel.ParentModel.SelectedGamepad,
@ -235,14 +246,86 @@ namespace Ryujinx.Ava.UI.Views.Input
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
}
_currentAssigner?.Cancel();
_currentAssigner = null;
}
private void Control_OnUnloaded(object sender, RoutedEventArgs e)
{
_isRunning = false;
}
private string BuildSvgCss(IGamepad gamepad, GamepadInputConfig config, JoystickPosition leftPosition,
JoystickPosition rightPosition)
{
gamepad.SetConfiguration(config.GetConfig());
StringBuilder sb = new();
for (int i = 0; i < (int)GamepadInputId.Count; i++)
{
GamepadButtonInputId button = (GamepadButtonInputId)i;
if (gamepad.GetMappedStateSnapshot().IsPressed(button))
{
sb.Append($"#{button}{{fill:#00bbdb;}}");
}
}
sb.Append(
$"#LeftStick{{transform: translate ({(float)leftPosition.Dx / short.MaxValue * StickMaxPosition} {-(float)leftPosition.Dy / short.MaxValue * StickMaxPosition});}}");
sb.Append(
$"#RightStick{{transform: translate ({(float)rightPosition.Dx / short.MaxValue * StickMaxPosition} {-(float)rightPosition.Dy / short.MaxValue * StickMaxPosition});}}");
return sb.ToString();
}
private void StartUpdatingData()
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
while (_isRunning)
{
var viewModel = DataContext as ControllerInputViewModel;
if (viewModel != null)
{
IGamepad gamepad = viewModel.ParentModel.SelectedGamepad;
var config = viewModel.Config;
JoystickPosition leftPosition = default, rightposition = default;
if (config.LeftJoystick != StickInputId.Unbound)
{
var stickInputId = (Ryujinx.Input.StickInputId)(int)config.LeftJoystick;
(float leftAxisX, float leftAxisY) = gamepad.GetStick(stickInputId);
leftPosition = NpadController.GetJoystickPosition(leftAxisX, leftAxisY,
config.DeadzoneLeft, config.RangeLeft);
viewModel.LeftStickPosition = $"{leftPosition.Dx}, {leftPosition.Dy}";
}
if (config.RightJoystick != StickInputId.Unbound)
{
StickInputId stickInputId = config.RightJoystick;
(float rightAxisX, float rightAxisY) =
gamepad.GetStick((Ryujinx.Input.StickInputId)stickInputId);
rightposition = NpadController.GetJoystickPosition(rightAxisX, rightAxisY,
config.DeadzoneRight, config.RangeRight);
viewModel.RightStickPosition = $"{rightposition.Dx}, {rightposition.Dy}";
}
viewModel.UpdateImageCss(BuildSvgCss(gamepad, config, leftPosition, rightposition));
// 假设你已获得加速度计和陀螺仪数据
Vector3 accelerometerData = gamepad.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscopeData = gamepad.GetMotionData(MotionInputId.Gyroscope);
LeftCubeCanvas.UpdateRotationFromMotionData(accelerometerData, gyroscopeData);
LeftCubeCanvas.InvalidateVisual();
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.RightAccelerometer);
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.RightGyroscope);
RightCubeCanvas.UpdateRotationFromMotionData(rightAccelerometer, rightGyroscope, true);
RightCubeCanvas.InvalidateVisual();
}
await Task.Delay(16);
}
});
}
}
}

View File

@ -1,46 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:LedInputViewModel"
x:Class="Ryujinx.UI.Views.Input.LedInputView">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" IsVisible="{Binding ParentModel.CanClearLed}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColorDisable}" />
<CheckBox
Margin="5"
MinWidth="0"
IsChecked="{Binding TurnOffLed, Mode=TwoWay}"
Command="{Binding LedDisabledChanged}">
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding !TurnOffLed}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColorRainbow}" />
<CheckBox
Margin="5"
MinWidth="0"
IsChecked="{Binding UseRainbowLed, Mode=TwoWay}">
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColor}" />
<ui:ColorPickerButton
Margin="5"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False"
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
ColorChanged="ColorPickerButton_OnColorChanged"
Color="{Binding LedColor, Mode=TwoWay}">
</ui:ColorPickerButton>
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -1,75 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Views.Input;
using System.Threading.Tasks;
namespace Ryujinx.UI.Views.Input
{
public partial class LedInputView : UserControl
{
private readonly LedInputViewModel _viewModel;
public LedInputView(ControllerInputViewModel viewModel)
{
DataContext = _viewModel = new LedInputViewModel
{
ParentModel = viewModel.ParentModel,
TurnOffLed = viewModel.Config.TurnOffLed,
EnableLedChanging = viewModel.Config.EnableLedChanging,
LedColor = viewModel.Config.LedColor,
UseRainbowLed = viewModel.Config.UseRainbowLed,
};
InitializeComponent();
}
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
{
if (!args.NewColor.HasValue) return;
if (DataContext is not LedInputViewModel lvm) return;
if (!lvm.EnableLedChanging) return;
if (lvm.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
}
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (DataContext is not LedInputViewModel lvm) return;
if (!lvm.EnableLedChanging) return;
if (lvm.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(lvm.LedColor.ToUInt32());
}
public static async Task Show(ControllerInputViewModel viewModel)
{
LedInputView content = new(viewModel);
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.ControllerLedTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
GamepadInputConfig config = viewModel.Config;
config.EnableLedChanging = content._viewModel.EnableLedChanging;
config.LedColor = content._viewModel.LedColor;
config.UseRainbowLed = content._viewModel.UseRainbowLed;
config.TurnOffLed = content._viewModel.TurnOffLed;
};
await contentDialog.ShowAsync();
}
}
}

View File

@ -43,8 +43,10 @@
<ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendSoundIO}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendSDL2}" />
<ComboBoxItem IsEnabled="{Binding IsSDL3Enabled}">
<TextBlock>
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendSDL3}" />
</TextBlock>
</ComboBoxItem>
</ComboBox>
</StackPanel>

View File

@ -28,7 +28,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Input.SDL3;
using System;
using System.Collections.Generic;
using System.Linq;
@ -108,8 +108,8 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
this.ScalingChanged += OnScalingChanged;
}

View File

@ -4,14 +4,9 @@ using Avalonia.Input;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using System;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
@ -111,12 +106,6 @@ namespace Ryujinx.Ava.UI.Windows
protected override void OnClosing(WindowClosingEventArgs e)
{
HotkeysPage.Dispose();
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
}
InputPage.Dispose();
base.OnClosing(e);
}

View File

@ -9,6 +9,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
Dummy,
OpenAl,
SoundIo,
SDL2,
SDL3,
}
}

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 61;
public const int CurrentVersion = 60;
/// <summary>
/// Version of the configuration file format

View File

@ -1,4 +1,3 @@
using Avalonia.Media;
using Gommon;
using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI;
@ -264,12 +263,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
}),
(30, static cff =>
{
foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
foreach (InputConfig config in cff.InputConfig)
{
config.Rumble = new RumbleConfigController
if (config is StandardControllerInputConfig controllerConfig)
{
EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
};
controllerConfig.Rumble = new RumbleConfigController
{
EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
};
}
}
}),
(31, static cff => cff.BackendThreading = BackendThreading.Auto),
@ -414,20 +416,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false;
}),
(60, static cff => cff.StartNoUI = false),
(61, static cff =>
{
foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
{
config.Led = new LedConfigController
{
EnableLed = false,
TurnOffLed = false,
UseRainbow = false,
LedColor = new Color(255, 5, 1, 253).ToUInt32()
};
}
})
(60, static cff => cff.StartNoUI = false)
);
}
}

View File

@ -194,7 +194,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.EnableInternetAccess.Value = false;
System.EnableFsIntegrityChecks.Value = true;
System.FsGlobalAccessLogMode.Value = 0;
System.AudioBackend.Value = AudioBackend.SDL2;
System.AudioBackend.Value = AudioBackend.SDL3;
System.AudioVolume.Value = 1;
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;