mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-05 20:09:15 +00:00
Compare commits
30 Commits
5aab5f205d
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0040f884d4 | ||
|
|
bd3b147002 | ||
|
|
b3ac7a2b94 | ||
|
|
96f8d519e6 | ||
|
|
433dd58f8c | ||
|
|
4347afff77 | ||
|
|
46f5d1d31a | ||
|
|
578b6bf00e | ||
|
|
9905ee17c2 | ||
|
|
7f0e82fe48 | ||
|
|
69c22a744e | ||
|
|
35b1531102 | ||
|
|
ea1185e96a | ||
|
|
31e0430b06 | ||
|
|
efb8658194 | ||
|
|
e909ea3210 | ||
|
|
2a688abeec | ||
|
|
9f88d68268 | ||
|
|
fe685c3f8f | ||
|
|
2edc165e26 | ||
|
|
fa7cb22240 | ||
|
|
c5b4add186 | ||
|
|
0079e0bfa5 | ||
|
|
25b70e05d7 | ||
|
|
78eb95733a | ||
|
|
17a768a5df | ||
|
|
339cddd0e2 | ||
|
|
66d9bc5a35 | ||
|
|
3e393449aa | ||
|
|
744c41937b |
@@ -10,6 +10,10 @@ gpu:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.*/**', 'src/Spv.Generator/**', 'src/Ryujinx.ShaderTools/**']
|
||||
|
||||
input:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx.Input*/**', 'src/Ryujinx/UI/Views/Input/**']
|
||||
|
||||
'graphics-backend:opengl':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**'
|
||||
@@ -18,17 +22,17 @@ gpu:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
|
||||
|
||||
'graphics-backend:metal':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Metal/**', 'src/Ryujinx.Graphics.Metal.SharpMetalExtensions/**']
|
||||
|
||||
gui:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']
|
||||
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.LocaleGenerator/**']
|
||||
|
||||
horizon:
|
||||
'horizon/hle':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.Horizon/**']
|
||||
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.HLE.Generators/**', 'src/Ryujinx.Horizon/**']
|
||||
|
||||
i18n:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['assets/**/*.json', 'src/Ryujinx.UI.LocaleGenerator/**']
|
||||
|
||||
kernel:
|
||||
- changed-files:
|
||||
@@ -36,7 +40,7 @@ kernel:
|
||||
|
||||
infra:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
|
||||
- any-glob-to-any-file: ['.forgejo/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
@@ -44,4 +48,4 @@ documentation:
|
||||
|
||||
ldn:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'
|
||||
- any-glob-to-any-file: ['src/Ryujinx.HLE/HOS/Services/Ldn/**', 'src/Ryujinx/UI/Windows/LdnGamesListWindow.*', 'src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs']
|
||||
204
.forgejo/workflows/build.yml
Normal file
204
.forgejo/workflows/build.yml
Normal file
@@ -0,0 +1,204 @@
|
||||
name: Build PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!.forgejo/**'
|
||||
- '!*.yml'
|
||||
- '!*.config'
|
||||
- '!*.md'
|
||||
- '.forgejo/workflows/*.yml'
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RELEASE: 0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Release]
|
||||
platform:
|
||||
- { name: win-x64, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, zip_os_name: linux_arm64 }
|
||||
#- { name: osx-x64, zip_os_name: osx_x64 }
|
||||
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y 7zip
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.forgejo/csc.json"
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "result=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: forgejo.event_name == 'pull_request'
|
||||
|
||||
- name: 'Cache: ~/.nuget/packages'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ steps.version_info.outputs.result }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
||||
- name: Test
|
||||
uses: actions/unstable-commands@v1
|
||||
with:
|
||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
timeout-minutes: 10
|
||||
retry-codes: 139
|
||||
if: matrix.platform.name != 'linux-arm64'
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.result }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
|
||||
if: forgejo.event_name == 'pull_request'
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: contains(matrix.platform.name, 'win')
|
||||
run: |
|
||||
7z a artifact/ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}.7z publish
|
||||
shell: bash
|
||||
|
||||
- name: Upload Ryujinx Windows artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
|
||||
path: artifact
|
||||
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'win')
|
||||
|
||||
- name: Build AppImage
|
||||
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
|
||||
|
||||
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
|
||||
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
shell: bash
|
||||
|
||||
- name: Upload Ryujinx AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}-AppImage
|
||||
path: publish_appimage
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [ Release ]
|
||||
|
||||
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 GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
sudo mv rcodesign /usr/bin/rcodesign
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "result=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: forgejo.event_name == 'pull_request'
|
||||
|
||||
- name: 'Cache: ~/.nuget/packages'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
bash distribution/macos/create_macos_pr_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.result }}" "${{ steps.version_info.outputs.git_short_hash }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
shell: bash
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: forgejo.event_name == 'pull_request'
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.forgejo/**'
|
||||
- 'docs/**'
|
||||
- 'assets/**'
|
||||
- '*.yml'
|
||||
@@ -25,44 +25,41 @@ env:
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
run: echo "::add-matcher::.forgejo/csc.json"
|
||||
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
sudo apt update && sudo apt install -y 7zip
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -87,12 +84,9 @@ jobs:
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -101,9 +95,8 @@ jobs:
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
tar -cJvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
@@ -112,7 +105,7 @@ jobs:
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
@@ -139,17 +132,28 @@ jobs:
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
repository: "Ryubing/Canary"
|
||||
token: ${{ secrets.RELEASER_TOKEN }}
|
||||
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||
files: |-
|
||||
release_output/**
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -159,33 +163,24 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.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 }}
|
||||
mv rcodesign /usr/bin/rcodesign
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -201,46 +196,53 @@ jobs:
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
runs-on: ubuntu-24.04
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
repository: "Ryubing/Canary"
|
||||
token: ${{ secrets.RELEASER_TOKEN }}
|
||||
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||
files: |-
|
||||
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
post_ci:
|
||||
name: Post CI Steps
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
||||
needs:
|
||||
- macos_release
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
gli create-tag -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
||||
|
||||
- name: Link to actual source archives for Canary
|
||||
run: |
|
||||
gli canary-release -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -r ${{ steps.version_info.outputs.build_version }}
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
@@ -5,10 +5,6 @@ on:
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -18,11 +14,13 @@ jobs:
|
||||
with:
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: GreemDev/Ryujinx
|
||||
repository: projects/Ryubing
|
||||
ref: master
|
||||
|
||||
- name: Update labels based on changes
|
||||
uses: actions/labeler@v5
|
||||
uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: ${{ secrets.LABELER_TOKEN }}
|
||||
configuration-path: .forgejo/labeler.yml
|
||||
sync-labels: true
|
||||
dot: true
|
||||
@@ -19,18 +19,20 @@ env:
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -41,26 +43,21 @@ jobs:
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -84,12 +81,9 @@ jobs:
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -98,12 +92,9 @@ jobs:
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
tar -cJvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -138,17 +129,27 @@ jobs:
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
name: "${{ steps.version_info.outputs.build_version }}"
|
||||
repository: "projects/Ryubing"
|
||||
token: ${{ secrets.RELEASER_TOKEN }}
|
||||
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||
files: |-
|
||||
release_output/**
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -158,37 +159,28 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.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 }}
|
||||
mv rcodesign /usr/bin/rcodesign
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -201,12 +193,20 @@ jobs:
|
||||
|
||||
- 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
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
bash 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: Create release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
name: "${{ steps.version_info.outputs.build_version }}"
|
||||
repository: "projects/Ryubing"
|
||||
token: ${{ secrets.RELEASER_TOKEN }}
|
||||
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||
files: |-
|
||||
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
post_ci:
|
||||
name: Post-CI Steps
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- macos_release
|
||||
@@ -214,36 +214,26 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}"
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]"
|
||||
labels: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Description of the issue
|
||||
description: What's the issue you encountered?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: How can the issue be reproduced?
|
||||
placeholder: Describe each step as precisely as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log file
|
||||
description: "A log file will help our developers to better diagnose and fix the issue. UPLOAD THE FILE. DO NOT COPY AND PASTE THE FILE'S CONTENT."
|
||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: "e.g. Windows 10"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ryujinx-version
|
||||
attributes:
|
||||
label: Ryujinx version
|
||||
placeholder: "e.g. 1.0.470"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: game-version
|
||||
attributes:
|
||||
label: Game version
|
||||
placeholder: "e.g. 1.1.1"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: "e.g. i7-6700"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: gpu
|
||||
attributes:
|
||||
label: GPU
|
||||
placeholder: "e.g. NVIDIA RTX 2070"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: ram
|
||||
attributes:
|
||||
label: RAM
|
||||
placeholder: "e.g. 16GB"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: List of applied mods
|
||||
placeholder: You can list applied mods here.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context?
|
||||
description: |
|
||||
- Additional info about your environment:
|
||||
- Any other information relevant to your issue.
|
||||
validations:
|
||||
required: false
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ryujinx Discord
|
||||
url: https://discord.gg/N2FmfVc
|
||||
about: This is for development related issues. For support and technical issues, please come to our Discord server.
|
||||
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature for Ryujinx.
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: textarea
|
||||
id: overview
|
||||
attributes:
|
||||
label: Overview
|
||||
description: Include the basic, high-level concepts for this feature here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Smaller details
|
||||
description: These may include specific methods of implementation etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Nature of request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Missing CPU Instruction
|
||||
description: CPU Instruction is missing in Ryujinx.
|
||||
title: "[CPU]"
|
||||
labels: [cpu, not-implemented]
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: CPU instruction
|
||||
description: What CPU instruction is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Instruction name
|
||||
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||
validations:
|
||||
required: true
|
||||
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Missing Service Call
|
||||
description: Service call is missing in Ryujinx.
|
||||
labels: not-implemented
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: Service call
|
||||
description: What service call is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Service description
|
||||
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service.
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Missing Shader Instruction
|
||||
description: Shader Instruction is missing in Ryujinx.
|
||||
title: "[GPU]"
|
||||
labels: [gpu, not-implemented]
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: Shader instruction
|
||||
description: What shader instruction is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||
validations:
|
||||
required: true
|
||||
168
.github/workflows/build.yml
vendored
168
.github/workflows/build.yml
vendored
@@ -1,168 +0,0 @@
|
||||
name: Build job
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.2.0"
|
||||
RELEASE: 0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release]
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
|
||||
|
||||
fail-fast: false
|
||||
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 git short hash
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Change config filename for macOS
|
||||
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
||||
- name: Test
|
||||
uses: TSRBerry/unstable-commands@v1
|
||||
with:
|
||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
timeout-minutes: 10
|
||||
retry-codes: 139
|
||||
if: matrix.platform.name != 'linux-arm64'
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
|
||||
- name: Build AppImage
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
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
|
||||
shell: bash
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Upload Ryujinx (AppImage) artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
|
||||
path: publish_appimage
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [ Debug, Release ]
|
||||
|
||||
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 git short hash
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
25
.github/workflows/checks.yml
vendored
25
.github/workflows/checks.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Build PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**'
|
||||
- '!.github/**'
|
||||
- '!*.yml'
|
||||
- '!*.config'
|
||||
- '!*.md'
|
||||
- '.github/workflows/*.yml'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
concurrency:
|
||||
group: pr-checks-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr_build:
|
||||
uses: ./.github/workflows/build.yml
|
||||
secrets: inherit
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -72,9 +72,6 @@ ipch/
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
#.NET
|
||||
.dotnet-home/
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.47.4" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
@@ -16,7 +17,7 @@
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.6.2" />
|
||||
<PackageVersion Include="ppy.SDL3-CS" Version="2025.920.0" />
|
||||
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.426.0" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||
@@ -42,13 +43,12 @@
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="Sep" Version="0.11.1" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||
|
||||
24
README.md
24
README.md
@@ -7,8 +7,8 @@
|
||||
|
||||
# Ryujinx
|
||||
|
||||
[](https://update.ryujinx.app/latest/stable)
|
||||
[](https://update.ryujinx.app/latest/canary)
|
||||
[](https://update.ryujinx.app/latest/stable)
|
||||
[](https://update.ryujinx.app/latest/canary)
|
||||
<br>
|
||||
<a href="https://discord.gg/PEuzjrFXUA">
|
||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||
@@ -21,7 +21,7 @@
|
||||
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||
It was written from scratch and development on the project began in September 2017.
|
||||
Ryujinx is available on a self-managed GitLab instance under the <a href="https://git.ryujinx.app/ryubing/ryujinx/-/blob/master/LICENSE.txt?ref_type=heads" target="_blank">MIT license</a>.
|
||||
Ryujinx is available on a self-managed <a href="https://github.com/Ryubing/forgejo" target="_blank">modified</a> <a href="https://forgejo.org/" target="_blank">Forgejo</a> instance under the <a href="https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||
<br />
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -31,11 +31,11 @@
|
||||
<br>
|
||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||
<br>
|
||||
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
|
||||
Guides and documentation can be found on the <a href="https://git.ryujinx.app/projects/Ryubing/wiki/Home">Wiki tab</a>.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://git.ryujinx.app/ryubing/ryujinx/-/raw/master/docs/shell.png?ref_type=heads&inline=false" alt="Ryujinx example">
|
||||
<img src="https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png" alt="Ryujinx example">
|
||||
</p>
|
||||
|
||||
## Usage
|
||||
@@ -49,17 +49,17 @@ Stable builds are made every so often, based on the `master` branch, that then g
|
||||
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
||||
|
||||
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/releases).
|
||||
You can find the stable releases [here](https://git.ryujinx.app/projects/Ryubing/releases).
|
||||
|
||||
Canary builds are compiled automatically for each commit on the `master` branch.
|
||||
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
||||
These canary builds are only recommended for experienced users.
|
||||
|
||||
You can find the canary releases [here](https://git.ryujinx.app/ryubing/canary/-/releases).
|
||||
You can find the canary releases [here](https://git.ryujinx.app/Ryubing/Canary/releases).
|
||||
|
||||
## Documentation
|
||||
|
||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](https://git.ryujinx.app/projects/Ryubing/src/branch/master/docs/README.md).
|
||||
|
||||
## Features
|
||||
|
||||
@@ -105,13 +105,13 @@ If you are planning to contribute or just want to learn more about this project
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
||||
This software is licensed under the terms of the [MIT license](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt).
|
||||
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
|
||||
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
|
||||
See [LICENSE.txt](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt) and [THIRDPARTY.md](https://git.ryujinx.app/projects/Ryubing/src/branch/master/distribution/legal/THIRDPARTY.md) for more details.
|
||||
|
||||
## Credits
|
||||
|
||||
- [LibHac](https://git.ryujinx.app/ryubing/libhac) is used for our file-system.
|
||||
- [LibHac](https://git.ryujinx.app/projects/LibHac) is used for our file-system.
|
||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||
|
||||
16
Ryujinx.sln
16
Ryujinx.sln
@@ -86,11 +86,11 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
||||
.forgejo\workflows\build.yml = .forgejo\workflows\build.yml
|
||||
.forgejo\workflows\canary.yml = .forgejo\workflows\canary.yml
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
Directory.Build.props = Directory.Build.props
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
.forgejo\workflows\release.yml = .forgejo\workflows\release.yml
|
||||
nuget.config = nuget.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@@ -573,6 +573,16 @@ Global
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
"zh_TW": "啟動 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -47,7 +47,7 @@
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "结束 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
"zh_TW": "停止 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -72,7 +72,7 @@
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "丢弃 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
"zh_TW": "捨棄 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,7 +97,7 @@
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
|
||||
"zh_TW": ""
|
||||
"zh_TW": "停止正在執行的 RenderDoc 畫格擷取,且立即捨棄其結果。"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,5 +22,5 @@ chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||
120
distribution/macos/create_macos_pr_build_ava.sh
Normal file
120
distribution/macos/create_macos_pr_build_ava.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 8 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$1"
|
||||
mkdir -p "$2"
|
||||
mkdir -p "$3"
|
||||
|
||||
BASE_DIR=$(readlink -f "$1")
|
||||
TEMP_DIRECTORY=$(readlink -f "$2")
|
||||
OUTPUT_DIRECTORY=$(readlink -f "$3")
|
||||
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||
VERSION=$5
|
||||
SOURCE_REVISION_ID=$6
|
||||
CONFIGURATION=$7
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
echo "Clearing xattr on all dot undercsore files"
|
||||
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||
for f; do
|
||||
dir=$(dirname "$f")
|
||||
base=$(basename "$f")
|
||||
orig="$dir/${base#._}"
|
||||
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||
done
|
||||
' sh {} +
|
||||
fi
|
||||
|
||||
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
||||
|
||||
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
||||
X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
|
||||
UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
|
||||
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
|
||||
|
||||
rm -rf "$TEMP_DIRECTORY"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
|
||||
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||
|
||||
dotnet restore
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||
|
||||
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||
|
||||
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
|
||||
# TODO: remove this once done
|
||||
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
|
||||
|
||||
pushd "$BASE_DIR/distribution/macos"
|
||||
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
|
||||
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
|
||||
popd
|
||||
|
||||
rm -rf "$UNIVERSAL_APP_BUNDLE"
|
||||
mkdir -p "$OUTPUT_DIRECTORY"
|
||||
|
||||
# Let's copy one of the two different app bundle and remove the executable
|
||||
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
||||
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
||||
|
||||
# Make its libraries universal
|
||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
||||
|
||||
if ! [ -x "$(command -v lipo)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v llvm-lipo-17)" ];
|
||||
then
|
||||
LIPO=llvm-lipo
|
||||
else
|
||||
LIPO=llvm-lipo-17
|
||||
fi
|
||||
else
|
||||
LIPO=lipo
|
||||
fi
|
||||
|
||||
# Make the executable universal
|
||||
$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
|
||||
|
||||
# Patch up the Info.plist to have appropriate version
|
||||
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||
rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
|
||||
|
||||
# Now sign it
|
||||
if ! [ -x "$(command -v codesign)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v rcodesign)" ];
|
||||
then
|
||||
echo "Cannot find rcodesign on your system, please install rcodesign."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||
echo "Using rcodesign for ad-hoc signing"
|
||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
||||
else
|
||||
echo "Using codesign for ad-hoc signing"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$UNIVERSAL_APP_BUNDLE"
|
||||
fi
|
||||
|
||||
echo "Creating archive"
|
||||
pushd "$OUTPUT_DIRECTORY"
|
||||
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" Ryujinx.app 1> /dev/null
|
||||
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
|
||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||
rm "$RELEASE_TAR_FILE_NAME"
|
||||
|
||||
popd
|
||||
|
||||
echo "Done"
|
||||
@@ -5,8 +5,7 @@
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
||||
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/packages/projects/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||
@@ -14,10 +13,6 @@
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="Ryujinx.UpdateClient">
|
||||
<package pattern="Ryujinx.UpdateClient" />
|
||||
<package pattern="Ryujinx.Systems.Update.Common" />
|
||||
</packageSource>
|
||||
<packageSource key="LibHacAlpha">
|
||||
<package pattern="Ryujinx.LibHac" />
|
||||
</packageSource>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
|
||||
public enum PhysicalKey
|
||||
{
|
||||
Unknown,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
ControlLeft,
|
||||
ControlRight,
|
||||
AltLeft,
|
||||
AltRight,
|
||||
WinLeft,
|
||||
WinRight,
|
||||
Menu,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
F25,
|
||||
F26,
|
||||
F27,
|
||||
F28,
|
||||
F29,
|
||||
F30,
|
||||
F31,
|
||||
F32,
|
||||
F33,
|
||||
F34,
|
||||
F35,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Enter,
|
||||
Escape,
|
||||
Space,
|
||||
Tab,
|
||||
BackSpace,
|
||||
Insert,
|
||||
Delete,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
NumLock,
|
||||
Clear,
|
||||
Keypad0,
|
||||
Keypad1,
|
||||
Keypad2,
|
||||
Keypad3,
|
||||
Keypad4,
|
||||
Keypad5,
|
||||
Keypad6,
|
||||
Keypad7,
|
||||
Keypad8,
|
||||
Keypad9,
|
||||
KeypadDivide,
|
||||
KeypadMultiply,
|
||||
KeypadSubtract,
|
||||
KeypadAdd,
|
||||
KeypadDecimal,
|
||||
KeypadEnter,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Number0,
|
||||
Number1,
|
||||
Number2,
|
||||
Number3,
|
||||
Number4,
|
||||
Number5,
|
||||
Number6,
|
||||
Number7,
|
||||
Number8,
|
||||
Number9,
|
||||
Tilde,
|
||||
Grave,
|
||||
Minus,
|
||||
Plus,
|
||||
BracketLeft,
|
||||
BracketRight,
|
||||
Semicolon,
|
||||
Quote,
|
||||
Comma,
|
||||
Period,
|
||||
Slash,
|
||||
BackSlash,
|
||||
Unbound,
|
||||
|
||||
Count,
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,6 @@ namespace Ryujinx.Common.Helper
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
private static partial nint GetForegroundWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
|
||||
|
||||
public static void SetConsoleWindowState(bool show)
|
||||
@@ -53,10 +44,6 @@ namespace Ryujinx.Common.Helper
|
||||
return;
|
||||
}
|
||||
|
||||
SetForegroundWindow(hWnd);
|
||||
|
||||
hWnd = GetForegroundWindow();
|
||||
|
||||
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace Ryujinx.Common
|
||||
|
||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||
IsCanaryBuild
|
||||
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
|
||||
? $"https://git.ryujinx.app/projects/Ryubing/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||
: $"https://git.ryujinx.app/projects/Ryubing/releases/tag/{newVersion}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ namespace Ryujinx.Common
|
||||
|
||||
public const string AmiiboTagsUrl = "https://raw.githubusercontent.com/Ryubing/Nfc/refs/heads/main/tags.json";
|
||||
|
||||
public const string FaqWikiUrl = "https://git.ryujinx.app/ryubing/ryujinx/-/wikis/FAQ-&-Troubleshooting";
|
||||
public const string FaqWikiUrl = "https://git.ryujinx.app/projects/Ryubing/wiki/FAQ-%26-Troubleshooting";
|
||||
|
||||
public const string SetupGuideWikiUrl =
|
||||
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide";
|
||||
"https://git.ryujinx.app/projects/Ryubing/wiki/Setup-%26-Configuration-Guide";
|
||||
|
||||
public const string MultiplayerWikiUrl =
|
||||
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
|
||||
"https://git.ryujinx.app/projects/Ryubing/wiki/Multiplayer-(LDN-Local-Wireless)-Guide";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
}
|
||||
|
||||
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
|
||||
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
|
||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
|
||||
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888, SKAlphaType.Premul));
|
||||
int dataLen = screenshotData.Length > bitmap.ByteCount ? bitmap.ByteCount : screenshotData.Length;
|
||||
|
||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), dataLen);
|
||||
|
||||
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
using FileStream file = File.OpenWrite(filePath);
|
||||
data.SaveTo(file);
|
||||
|
||||
@@ -151,7 +151,9 @@ namespace Ryujinx.Input.SDL3
|
||||
result |= GamepadFeaturesFlag.Led;
|
||||
}
|
||||
SDL_UnlockProperties(propID);
|
||||
SDL_DestroyProperties(propID);
|
||||
|
||||
// NOTE: Do not call SDL_DestroyProperties here. These properties are owned
|
||||
// internally by SDL and are freed when SDL_CloseGamepad is called (in Dispose).
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -331,28 +331,18 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads()
|
||||
{
|
||||
lock (_gamepadsIds)
|
||||
string[] ids;
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var gamepad in _gamepadsIds)
|
||||
{
|
||||
yield return GetGamepad(gamepad.Value);
|
||||
}
|
||||
ids = _gamepadsIds.Values
|
||||
.Concat(_joyConsIds.Values)
|
||||
.Concat(_linkedJoyConsIds.Values)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
lock (_joyConsIds)
|
||||
foreach (string id in ids)
|
||||
{
|
||||
foreach (var gamepad in _joyConsIds)
|
||||
{
|
||||
yield return GetGamepad(gamepad.Value);
|
||||
}
|
||||
}
|
||||
|
||||
lock (_linkedJoyConsIds)
|
||||
{
|
||||
foreach (var gamepad in _linkedJoyConsIds)
|
||||
{
|
||||
yield return GetGamepad(gamepad.Value);
|
||||
}
|
||||
yield return GetGamepad(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,25 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
class SDL3Keyboard : IKeyboard
|
||||
{
|
||||
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
|
||||
}
|
||||
|
||||
private readonly Lock _userMappingLock = new();
|
||||
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly SDL3KeyboardDriver _driver;
|
||||
#pragma warning restore IDE0052
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
|
||||
|
||||
private static readonly SDL_Keycode[] _keysDriverMapping =
|
||||
@@ -161,8 +171,9 @@ namespace Ryujinx.Input.SDL3
|
||||
SDL_Keycode.SDLK_0
|
||||
];
|
||||
|
||||
public SDL3Keyboard(string id, string name)
|
||||
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
|
||||
{
|
||||
_driver = driver;
|
||||
Id = id;
|
||||
Name = name;
|
||||
_buttonsUserMapping = [];
|
||||
@@ -184,9 +195,9 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key)
|
||||
private unsafe static int ToSDL3Scancode(Key key)
|
||||
{
|
||||
if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu)
|
||||
if (key is >= Key.Unknown and <= Key.Menu)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -194,18 +205,18 @@ namespace Ryujinx.Input.SDL3
|
||||
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
|
||||
}
|
||||
|
||||
private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key)
|
||||
private static SDL_Keymod GetKeyboardModifierMask(Key key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
|
||||
ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
|
||||
ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
|
||||
ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
|
||||
ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
|
||||
ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT,
|
||||
ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
|
||||
ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
|
||||
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
|
||||
};
|
||||
@@ -221,9 +232,9 @@ namespace Ryujinx.Input.SDL3
|
||||
rawKeyboardState = SDL_GetKeyboardState(null);
|
||||
}
|
||||
|
||||
bool[] keysState = new bool[(int)ConfigPhysicalKey.Count];
|
||||
bool[] keysState = new bool[(int)Key.Count];
|
||||
|
||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
int index = ToSDL3Scancode(key);
|
||||
if (index == -1)
|
||||
@@ -253,6 +264,36 @@ namespace Ryujinx.Input.SDL3
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
||||
@@ -265,9 +306,9 @@ namespace Ryujinx.Input.SDL3
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
{
|
||||
if (!entry.IsValid)
|
||||
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -279,8 +320,8 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
@@ -316,15 +357,38 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
_configuration = (StandardKeyboardInputConfig)configuration;
|
||||
|
||||
// First clear the buttons mapping
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
||||
// Then configure left joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Finally configure right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
||||
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards");
|
||||
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
|
||||
}
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Input.Assigner
|
||||
{
|
||||
/// <summary>
|
||||
@@ -10,42 +8,22 @@ namespace Ryujinx.Input.Assigner
|
||||
private readonly IKeyboard _keyboard;
|
||||
|
||||
private KeyboardStateSnapshot _keyboardState;
|
||||
private Button? _pressedButton;
|
||||
|
||||
public KeyboardKeyAssigner(IKeyboard keyboard)
|
||||
{
|
||||
_keyboard = keyboard;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_pressedButton = null;
|
||||
}
|
||||
public void Initialize() { }
|
||||
|
||||
public void ReadInput()
|
||||
{
|
||||
_keyboardState = _keyboard.GetKeyboardStateSnapshot();
|
||||
|
||||
if (_pressedButton is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Button? buttonFromState = GetPressedButtonFromState();
|
||||
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
|
||||
|
||||
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
|
||||
|
||||
if (_pressedButton is not null)
|
||||
{
|
||||
string source = buttonFromState is not null ? "state" : "buffered-press";
|
||||
Logger.Debug?.Print(LogClass.UI, $"Keyboard assigner registered key={_pressedButton.Value.AsHidType<Key>()}, source={source}, cancelPressed={ShouldCancel()}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAnyButtonPressed()
|
||||
{
|
||||
return _pressedButton is not null;
|
||||
return GetPressedButton() is not null;
|
||||
}
|
||||
|
||||
public bool ShouldCancel()
|
||||
@@ -55,53 +33,18 @@ namespace Ryujinx.Input.Assigner
|
||||
|
||||
public Button? GetPressedButton()
|
||||
{
|
||||
return !ShouldCancel() ? _pressedButton : null;
|
||||
}
|
||||
|
||||
private Button? GetPressedButtonFromState()
|
||||
{
|
||||
Key aliasedKey = GetAliasedPressedKey();
|
||||
|
||||
if (aliasedKey != Key.Unknown)
|
||||
{
|
||||
return new Button(aliasedKey);
|
||||
}
|
||||
Button? keyPressed = null;
|
||||
|
||||
for (Key key = Key.Unknown; key < Key.Count; key++)
|
||||
{
|
||||
if (_keyboardState.IsPressed(key))
|
||||
{
|
||||
return new Button(key);
|
||||
keyPressed = new(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Button? GetPressedButtonFromBufferedPress()
|
||||
{
|
||||
return _keyboard.TryConsumePressedKey(out Key key) ? new Button(key) : null;
|
||||
}
|
||||
|
||||
private Key GetAliasedPressedKey()
|
||||
{
|
||||
// On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt.
|
||||
// Prefer AltRight in that case so the binding reflects the physical key used.
|
||||
if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight))
|
||||
{
|
||||
return Key.AltRight;
|
||||
}
|
||||
|
||||
// On some Copilot keyboards, the key in the right-control position is reported as
|
||||
// ShiftLeft+Win+F23. Prefer ControlRight so the binding reflects that physical key.
|
||||
if (_keyboardState.IsPressed(Key.ShiftLeft) &&
|
||||
_keyboardState.IsPressed(Key.F23) &&
|
||||
(_keyboardState.IsPressed(Key.WinLeft) || _keyboardState.IsPressed(Key.WinRight)))
|
||||
{
|
||||
return Key.ControlRight;
|
||||
}
|
||||
|
||||
return Key.Unknown;
|
||||
return !ShouldCancel() ? keyPressed : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
@@ -235,9 +234,7 @@ namespace Ryujinx.Input.HLE
|
||||
_gamepad?.Dispose();
|
||||
|
||||
Id = config.Id;
|
||||
_gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver
|
||||
? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical)
|
||||
: GamepadDriver.GetGamepad(Id);
|
||||
_gamepad = GamepadDriver.GetGamepad(Id);
|
||||
|
||||
UpdateUserConfiguration(config);
|
||||
|
||||
@@ -566,7 +563,7 @@ namespace Ryujinx.Input.HLE
|
||||
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
||||
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
||||
|
||||
_gamepad.Rumble(low, high, uint.MaxValue);
|
||||
_gamepad?.Rumble(low, high, uint.MaxValue);
|
||||
|
||||
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
||||
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Ryujinx.Input.HLE
|
||||
private bool _isDisposed;
|
||||
|
||||
private List<InputConfig> _inputConfig;
|
||||
private List<InputConfig> _requestedInputConfig;
|
||||
private bool _enableKeyboard;
|
||||
private bool _enableMouse;
|
||||
private Switch _device;
|
||||
@@ -53,7 +52,6 @@ namespace Ryujinx.Input.HLE
|
||||
_gamepadDriver = gamepadDriver;
|
||||
_mouseDriver = mouseDriver;
|
||||
_inputConfig = [];
|
||||
_requestedInputConfig = [];
|
||||
|
||||
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||
@@ -91,14 +89,14 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
{
|
||||
// Force input reload
|
||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -129,13 +127,11 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_requestedInputConfig = inputConfig?.ToList() ?? [];
|
||||
|
||||
NpadController[] oldControllers = _controllers.ToArray();
|
||||
|
||||
List<InputConfig> validInputs = [];
|
||||
|
||||
foreach (InputConfig inputConfigEntry in _requestedInputConfig)
|
||||
foreach (InputConfig inputConfigEntry in inputConfig)
|
||||
{
|
||||
NpadController controller;
|
||||
int index = (int)inputConfigEntry.PlayerIndex;
|
||||
@@ -151,17 +147,7 @@ namespace Ryujinx.Input.HLE
|
||||
controller = new(_cemuHookClient);
|
||||
}
|
||||
|
||||
InputConfig activeConfig = inputConfigEntry;
|
||||
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
||||
|
||||
if (!isValid &&
|
||||
enableKeyboard &&
|
||||
inputConfigEntry is StandardControllerInputConfig &&
|
||||
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
|
||||
{
|
||||
activeConfig = fallbackConfig;
|
||||
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
||||
}
|
||||
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
@@ -171,7 +157,7 @@ namespace Ryujinx.Input.HLE
|
||||
else
|
||||
{
|
||||
_controllers[index] = controller;
|
||||
validInputs.Add(activeConfig);
|
||||
validInputs.Add(inputConfigEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +169,7 @@ namespace Ryujinx.Input.HLE
|
||||
oldControllers[i] = null;
|
||||
}
|
||||
|
||||
_inputConfig = validInputs;
|
||||
_inputConfig = inputConfig;
|
||||
_enableKeyboard = enableKeyboard;
|
||||
_enableMouse = enableMouse;
|
||||
|
||||
@@ -191,79 +177,6 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig)
|
||||
{
|
||||
fallbackConfig = null;
|
||||
|
||||
ReadOnlySpan<string> keyboardIds = _keyboardDriver.GamepadsIds;
|
||||
|
||||
if (keyboardIds.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string keyboardId = keyboardIds[0];
|
||||
|
||||
using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId);
|
||||
|
||||
if (keyboard == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fallbackConfig = new StandardKeyboardInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = keyboardId,
|
||||
Name = keyboard.Name,
|
||||
PlayerIndex = inputConfig.PlayerIndex,
|
||||
ControllerType = inputConfig.ControllerType,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
},
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UnblockInputUpdates()
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -421,7 +334,7 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
public InputConfig GetPlayerInputConfigByIndex(int index)
|
||||
internal InputConfig GetPlayerInputConfigByIndex(int index)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
@@ -34,26 +33,15 @@ namespace Ryujinx.Input
|
||||
{
|
||||
if (_keyState is null)
|
||||
{
|
||||
_keyState = new bool[(int)ConfigPhysicalKey.Count];
|
||||
_keyState = new bool[(int)Key.Count];
|
||||
}
|
||||
|
||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
_keyState[(int)key] = keyboard.IsPressed((Key)(int)key);
|
||||
_keyState[(int)key] = keyboard.IsPressed(key);
|
||||
}
|
||||
|
||||
return new KeyboardStateSnapshot(_keyState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to consume a recently pressed key.
|
||||
/// </summary>
|
||||
/// <param name="key">The pressed key, if available.</param>
|
||||
/// <returns>True if a key press was consumed.</returns>
|
||||
bool TryConsumePressedKey(out Key key)
|
||||
{
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public interface IKeyboardModeDriver : IGamepadDriver
|
||||
{
|
||||
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public static class KeyboardInputMappingHelper
|
||||
{
|
||||
public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound;
|
||||
}
|
||||
|
||||
public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) =>
|
||||
[
|
||||
// Left JoyCon
|
||||
new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton),
|
||||
new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp),
|
||||
new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown),
|
||||
new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft),
|
||||
new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight),
|
||||
new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus),
|
||||
new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL),
|
||||
new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl),
|
||||
new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr),
|
||||
new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl),
|
||||
|
||||
// Right JoyCon
|
||||
new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton),
|
||||
new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA),
|
||||
new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB),
|
||||
new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX),
|
||||
new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY),
|
||||
new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus),
|
||||
new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR),
|
||||
new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr),
|
||||
new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr),
|
||||
new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl),
|
||||
];
|
||||
|
||||
public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigPhysicalKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
if (stickX == 0 && stickY == 0)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public enum KeyboardInputMode
|
||||
{
|
||||
Semantic,
|
||||
Physical,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
@@ -26,8 +25,5 @@ namespace Ryujinx.Input
|
||||
/// <returns>True if the given key is pressed</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsPressed(Key key) => KeysState[(int)key];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.SDL3-CS" />
|
||||
<PackageReference Include="Ryujinx.SDL3-CS" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -61,6 +61,14 @@ namespace Ryujinx.SDL3.Common
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
// When hid_nintendo is loaded, it creates separate evdev devices for the gamepad
|
||||
// and IMU which SDL3's evdev backend combines via UNIQ matching. Using HIDAPI
|
||||
// instead conflicts with the kernel driver and breaks motion and hotplug.
|
||||
if (OperatingSystem.IsLinux() && Directory.Exists("/sys/module/hid_nintendo"))
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
|
||||
}
|
||||
|
||||
// NOTE: As of SDL3 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");
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
|
||||
public void EnsureTypeSize()
|
||||
{
|
||||
Assert.AreEqual(0x18, Unsafe.SizeOf<BiquadFilterEffectParameter1>());
|
||||
Assert.AreEqual(0x24, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
||||
Assert.AreEqual(0x28, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/Ryujinx/Assets/UIImages/Logo_Forgejo.png
Normal file
BIN
src/Ryujinx/Assets/UIImages/Logo_Forgejo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -24,7 +24,7 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Headless
|
||||
{
|
||||
@@ -105,48 +105,48 @@ namespace Ryujinx.Headless
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = null,
|
||||
ControllerType = ControllerType.JoyconPair,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
},
|
||||
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal class AvaloniaKeyboard : IKeyboard
|
||||
{
|
||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
private readonly AvaloniaKeyboardDriver _driver;
|
||||
private readonly KeyboardInputMode _mode;
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
|
||||
private readonly Lock _userMappingLock = new();
|
||||
@@ -24,12 +24,18 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public bool IsConnected => true;
|
||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode)
|
||||
|
||||
private class ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||
{
|
||||
public readonly GamepadButtonInputId To = to;
|
||||
public readonly Key From = from;
|
||||
}
|
||||
|
||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
||||
{
|
||||
_buttonsUserMapping = [];
|
||||
|
||||
_driver = driver;
|
||||
_mode = mode;
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
@@ -51,18 +57,22 @@ namespace Ryujinx.Ava.Input
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
{
|
||||
if (!entry.IsValid || result.IsPressed(entry.To))
|
||||
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
// NOTE: Do not touch state of the button already pressed.
|
||||
if (!result.IsPressed(entry.To))
|
||||
{
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
@@ -90,7 +100,7 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
return _driver.IsPressed(key, _mode);
|
||||
return _driver.IsPressed(key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -98,19 +108,6 @@ namespace Ryujinx.Ava.Input
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryConsumePressedKey(out Key key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _driver.TryConsumePressedKey(_mode, out key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
lock (_userMappingLock)
|
||||
@@ -119,13 +116,37 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
// Left JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Right JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
#pragma warning restore IDE0055
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
||||
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold) { }
|
||||
@@ -141,9 +162,41 @@ namespace Ryujinx.Ava.Input
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
Vector2 stick = new(stickX, stickY);
|
||||
|
||||
stick = Vector2.Normalize(stick);
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_driver?.Clear(_mode);
|
||||
_driver?.Clear();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
@@ -1,35 +1,19 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using AvaKey = Avalonia.Input.Key;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal class AvaloniaKeyboardDriver : IKeyboardModeDriver
|
||||
internal class AvaloniaKeyboardDriver : IGamepadDriver
|
||||
{
|
||||
private enum PhysicalKeySource
|
||||
{
|
||||
Direct,
|
||||
ObservedFallback,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
private static readonly string[] _keyboardIdentifers = ["0"];
|
||||
private readonly Control _control;
|
||||
private readonly HashSet<Key> _semanticPressedKeys;
|
||||
private readonly HashSet<ConfigPhysicalKey> _physicalPressedKeys;
|
||||
private readonly Dictionary<Key, ConfigPhysicalKey> _observedPhysicalKeysBySemanticKey;
|
||||
private readonly Queue<Key> _semanticPressedKeyQueue;
|
||||
private readonly Queue<Key> _physicalPressedKeyQueue;
|
||||
private readonly Lock _pressedKeyQueueLock;
|
||||
private readonly KeyboardInputMode _defaultMode;
|
||||
private readonly HashSet<AvaKey> _pressedKeys;
|
||||
|
||||
public event EventHandler<KeyEventArgs> KeyPressed;
|
||||
public event EventHandler<KeyEventArgs> KeyRelease;
|
||||
@@ -38,22 +22,13 @@ namespace Ryujinx.Ava.Input
|
||||
public string DriverName => "AvaloniaKeyboardDriver";
|
||||
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
||||
|
||||
public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic)
|
||||
public AvaloniaKeyboardDriver(Control control)
|
||||
{
|
||||
_control = control;
|
||||
_semanticPressedKeys = [];
|
||||
_physicalPressedKeys = [];
|
||||
_observedPhysicalKeysBySemanticKey = [];
|
||||
_semanticPressedKeyQueue = [];
|
||||
_physicalPressedKeyQueue = [];
|
||||
_pressedKeyQueueLock = new();
|
||||
_defaultMode = defaultMode;
|
||||
_pressedKeys = [];
|
||||
|
||||
// Use routed handlers so keys consumed earlier in the Avalonia pipeline
|
||||
// can still be observed by the input driver. This is needed for keys like
|
||||
// Caps Lock on macOS, which may not reach the plain CLR event path.
|
||||
_control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true);
|
||||
_control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true);
|
||||
_control.KeyDown += OnKeyPress;
|
||||
_control.KeyUp += OnKeyRelease;
|
||||
_control.TextInput += Control_TextInput;
|
||||
}
|
||||
|
||||
@@ -75,18 +50,13 @@ namespace Ryujinx.Ava.Input
|
||||
}
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
return GetKeyboard(id, _defaultMode);
|
||||
}
|
||||
|
||||
public IKeyboard GetKeyboard(string id, KeyboardInputMode mode)
|
||||
{
|
||||
if (!_keyboardIdentifers[0].Equals(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_KeyboardInputMode], mode);
|
||||
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
|
||||
}
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
|
||||
@@ -95,179 +65,40 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress);
|
||||
_control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease);
|
||||
_control.TextInput -= Control_TextInput;
|
||||
_control.KeyUp -= OnKeyPress;
|
||||
_control.KeyDown -= OnKeyRelease;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnKeyPress(object sender, KeyEventArgs args)
|
||||
{
|
||||
UpdateKeyStates(args, isPressed: true);
|
||||
_pressedKeys.Add(args.Key);
|
||||
|
||||
KeyPressed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
protected void OnKeyRelease(object sender, KeyEventArgs args)
|
||||
{
|
||||
UpdateKeyStates(args, isPressed: false);
|
||||
_pressedKeys.Remove(args.Key);
|
||||
|
||||
KeyRelease?.Invoke(this, args);
|
||||
}
|
||||
|
||||
internal bool IsPressed(Key key, KeyboardInputMode mode)
|
||||
internal bool IsPressed(Key key)
|
||||
{
|
||||
if (key is Key.Unbound or Key.Unknown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return mode == KeyboardInputMode.Physical
|
||||
? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key)
|
||||
: _semanticPressedKeys.Contains(key);
|
||||
}
|
||||
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey);
|
||||
|
||||
internal void Clear(KeyboardInputMode mode)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
if (mode == KeyboardInputMode.Physical)
|
||||
{
|
||||
_physicalPressedKeys.Clear();
|
||||
_physicalPressedKeyQueue.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_semanticPressedKeys.Clear();
|
||||
_semanticPressedKeyQueue.Clear();
|
||||
}
|
||||
}
|
||||
return _pressedKeys.Contains(nativeKey);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
_semanticPressedKeys.Clear();
|
||||
_physicalPressedKeys.Clear();
|
||||
_semanticPressedKeyQueue.Clear();
|
||||
_physicalPressedKeyQueue.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TryConsumePressedKey(KeyboardInputMode mode, out Key key)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
Queue<Key> queue = mode == KeyboardInputMode.Physical ? _physicalPressedKeyQueue : _semanticPressedKeyQueue;
|
||||
|
||||
if (queue.TryDequeue(out key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void UpdateKeyState(HashSet<Key> pressedKeys, Key key, bool isPressed)
|
||||
{
|
||||
if (key is Key.Unknown or Key.Unbound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
pressedKeys.Add(key);
|
||||
return;
|
||||
}
|
||||
|
||||
pressedKeys.Remove(key);
|
||||
}
|
||||
|
||||
private static void UpdateKeyState(HashSet<ConfigPhysicalKey> pressedKeys, ConfigPhysicalKey key, bool isPressed)
|
||||
{
|
||||
if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
pressedKeys.Add(key);
|
||||
return;
|
||||
}
|
||||
|
||||
pressedKeys.Remove(key);
|
||||
}
|
||||
|
||||
private void UpdateKeyStates(KeyEventArgs args, bool isPressed)
|
||||
{
|
||||
Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key);
|
||||
Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key);
|
||||
ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey, out PhysicalKeySource physicalKeySource);
|
||||
bool semanticWasPressed = _semanticPressedKeys.Contains(resolvedSemanticKey);
|
||||
bool physicalWasPressed = _physicalPressedKeys.Contains(physicalKey);
|
||||
bool bufferedSemanticPress = false;
|
||||
bool bufferedPhysicalPress = false;
|
||||
|
||||
UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed);
|
||||
UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed);
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
if (!semanticWasPressed && resolvedSemanticKey is not Key.Unknown and not Key.Unbound)
|
||||
{
|
||||
_semanticPressedKeyQueue.Enqueue(resolvedSemanticKey);
|
||||
bufferedSemanticPress = true;
|
||||
}
|
||||
|
||||
if (!physicalWasPressed && physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
_physicalPressedKeyQueue.Enqueue((Key)(int)physicalKey);
|
||||
bufferedPhysicalPress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPressed &&
|
||||
semanticKey is not Key.Unknown and not Key.Unbound &&
|
||||
physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
_observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey;
|
||||
}
|
||||
|
||||
Logger.Trace?.Print(
|
||||
LogClass.UI,
|
||||
$"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}");
|
||||
}
|
||||
|
||||
private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey, out PhysicalKeySource source)
|
||||
{
|
||||
Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
|
||||
|
||||
if (key is >= Key.Unknown and < Key.Count)
|
||||
{
|
||||
source = PhysicalKeySource.Direct;
|
||||
return (ConfigPhysicalKey)(int)key;
|
||||
}
|
||||
|
||||
if (semanticKey is not Key.Unknown and not Key.Unbound &&
|
||||
_observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey))
|
||||
{
|
||||
source = PhysicalKeySource.ObservedFallback;
|
||||
return observedPhysicalKey;
|
||||
}
|
||||
|
||||
source = PhysicalKeySource.Unknown;
|
||||
return ConfigPhysicalKey.Unknown;
|
||||
}
|
||||
|
||||
private static string FormatKeySymbol(string keySymbol)
|
||||
{
|
||||
return string.IsNullOrEmpty(keySymbol) ? "<none>" : keySymbol;
|
||||
_pressedKeys.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -2,7 +2,6 @@ using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AvaKey = Avalonia.Input.Key;
|
||||
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
@@ -133,8 +132,7 @@ namespace Ryujinx.Ava.Input
|
||||
AvaKey.D8,
|
||||
AvaKey.D9,
|
||||
AvaKey.OemTilde,
|
||||
AvaKey.Oem102,
|
||||
AvaKey.OemMinus,
|
||||
AvaKey.OemTilde,AvaKey.OemMinus,
|
||||
AvaKey.OemPlus,
|
||||
AvaKey.OemOpenBrackets,
|
||||
AvaKey.OemCloseBrackets,
|
||||
@@ -149,149 +147,7 @@ namespace Ryujinx.Ava.Input
|
||||
AvaKey.None
|
||||
];
|
||||
|
||||
private static readonly AvaPhysicalKey[] _physicalKeyMapping =
|
||||
[
|
||||
// NOTE: Invalid
|
||||
AvaPhysicalKey.None,
|
||||
|
||||
AvaPhysicalKey.ShiftLeft,
|
||||
AvaPhysicalKey.ShiftRight,
|
||||
AvaPhysicalKey.ControlLeft,
|
||||
AvaPhysicalKey.ControlRight,
|
||||
AvaPhysicalKey.AltLeft,
|
||||
AvaPhysicalKey.AltRight,
|
||||
AvaPhysicalKey.MetaLeft,
|
||||
AvaPhysicalKey.MetaRight,
|
||||
AvaPhysicalKey.ContextMenu,
|
||||
AvaPhysicalKey.F1,
|
||||
AvaPhysicalKey.F2,
|
||||
AvaPhysicalKey.F3,
|
||||
AvaPhysicalKey.F4,
|
||||
AvaPhysicalKey.F5,
|
||||
AvaPhysicalKey.F6,
|
||||
AvaPhysicalKey.F7,
|
||||
AvaPhysicalKey.F8,
|
||||
AvaPhysicalKey.F9,
|
||||
AvaPhysicalKey.F10,
|
||||
AvaPhysicalKey.F11,
|
||||
AvaPhysicalKey.F12,
|
||||
AvaPhysicalKey.F13,
|
||||
AvaPhysicalKey.F14,
|
||||
AvaPhysicalKey.F15,
|
||||
AvaPhysicalKey.F16,
|
||||
AvaPhysicalKey.F17,
|
||||
AvaPhysicalKey.F18,
|
||||
AvaPhysicalKey.F19,
|
||||
AvaPhysicalKey.F20,
|
||||
AvaPhysicalKey.F21,
|
||||
AvaPhysicalKey.F22,
|
||||
AvaPhysicalKey.F23,
|
||||
AvaPhysicalKey.F24,
|
||||
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
|
||||
AvaPhysicalKey.ArrowUp,
|
||||
AvaPhysicalKey.ArrowDown,
|
||||
AvaPhysicalKey.ArrowLeft,
|
||||
AvaPhysicalKey.ArrowRight,
|
||||
AvaPhysicalKey.Enter,
|
||||
AvaPhysicalKey.Escape,
|
||||
AvaPhysicalKey.Space,
|
||||
AvaPhysicalKey.Tab,
|
||||
AvaPhysicalKey.Backspace,
|
||||
AvaPhysicalKey.Insert,
|
||||
AvaPhysicalKey.Delete,
|
||||
AvaPhysicalKey.PageUp,
|
||||
AvaPhysicalKey.PageDown,
|
||||
AvaPhysicalKey.Home,
|
||||
AvaPhysicalKey.End,
|
||||
AvaPhysicalKey.CapsLock,
|
||||
AvaPhysicalKey.ScrollLock,
|
||||
AvaPhysicalKey.PrintScreen,
|
||||
AvaPhysicalKey.Pause,
|
||||
AvaPhysicalKey.NumLock,
|
||||
AvaPhysicalKey.NumPadClear,
|
||||
AvaPhysicalKey.NumPad0,
|
||||
AvaPhysicalKey.NumPad1,
|
||||
AvaPhysicalKey.NumPad2,
|
||||
AvaPhysicalKey.NumPad3,
|
||||
AvaPhysicalKey.NumPad4,
|
||||
AvaPhysicalKey.NumPad5,
|
||||
AvaPhysicalKey.NumPad6,
|
||||
AvaPhysicalKey.NumPad7,
|
||||
AvaPhysicalKey.NumPad8,
|
||||
AvaPhysicalKey.NumPad9,
|
||||
AvaPhysicalKey.NumPadDivide,
|
||||
AvaPhysicalKey.NumPadMultiply,
|
||||
AvaPhysicalKey.NumPadSubtract,
|
||||
AvaPhysicalKey.NumPadAdd,
|
||||
AvaPhysicalKey.NumPadDecimal,
|
||||
AvaPhysicalKey.NumPadEnter,
|
||||
AvaPhysicalKey.A,
|
||||
AvaPhysicalKey.B,
|
||||
AvaPhysicalKey.C,
|
||||
AvaPhysicalKey.D,
|
||||
AvaPhysicalKey.E,
|
||||
AvaPhysicalKey.F,
|
||||
AvaPhysicalKey.G,
|
||||
AvaPhysicalKey.H,
|
||||
AvaPhysicalKey.I,
|
||||
AvaPhysicalKey.J,
|
||||
AvaPhysicalKey.K,
|
||||
AvaPhysicalKey.L,
|
||||
AvaPhysicalKey.M,
|
||||
AvaPhysicalKey.N,
|
||||
AvaPhysicalKey.O,
|
||||
AvaPhysicalKey.P,
|
||||
AvaPhysicalKey.Q,
|
||||
AvaPhysicalKey.R,
|
||||
AvaPhysicalKey.S,
|
||||
AvaPhysicalKey.T,
|
||||
AvaPhysicalKey.U,
|
||||
AvaPhysicalKey.V,
|
||||
AvaPhysicalKey.W,
|
||||
AvaPhysicalKey.X,
|
||||
AvaPhysicalKey.Y,
|
||||
AvaPhysicalKey.Z,
|
||||
AvaPhysicalKey.Digit0,
|
||||
AvaPhysicalKey.Digit1,
|
||||
AvaPhysicalKey.Digit2,
|
||||
AvaPhysicalKey.Digit3,
|
||||
AvaPhysicalKey.Digit4,
|
||||
AvaPhysicalKey.Digit5,
|
||||
AvaPhysicalKey.Digit6,
|
||||
AvaPhysicalKey.Digit7,
|
||||
AvaPhysicalKey.Digit8,
|
||||
AvaPhysicalKey.Digit9,
|
||||
AvaPhysicalKey.Backquote,
|
||||
AvaPhysicalKey.IntlBackslash,
|
||||
AvaPhysicalKey.Minus,
|
||||
AvaPhysicalKey.Equal,
|
||||
AvaPhysicalKey.BracketLeft,
|
||||
AvaPhysicalKey.BracketRight,
|
||||
AvaPhysicalKey.Semicolon,
|
||||
AvaPhysicalKey.Quote,
|
||||
AvaPhysicalKey.Comma,
|
||||
AvaPhysicalKey.Period,
|
||||
AvaPhysicalKey.Slash,
|
||||
AvaPhysicalKey.Backslash,
|
||||
|
||||
// NOTE: invalid
|
||||
AvaPhysicalKey.None
|
||||
];
|
||||
|
||||
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
||||
private static readonly Dictionary<AvaPhysicalKey, Key> _avaPhysicalKeyMapping;
|
||||
|
||||
static AvaloniaKeyboardMappingHelper()
|
||||
{
|
||||
@@ -299,42 +155,21 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
||||
_avaKeyMapping = new Dictionary<AvaKey, Key>();
|
||||
_avaPhysicalKeyMapping = new Dictionary<AvaPhysicalKey, Key>();
|
||||
|
||||
foreach (Key key in inputKeys)
|
||||
{
|
||||
if (TryGetAvaKey(key, out AvaKey avaKey))
|
||||
if (TryGetAvaKey(key, out AvaKey index))
|
||||
{
|
||||
_avaKeyMapping[avaKey] = key;
|
||||
}
|
||||
|
||||
if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey))
|
||||
{
|
||||
_avaPhysicalKeyMapping[avaPhysicalKey] = key;
|
||||
_avaKeyMapping[index] = key;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias additional Avalonia key values to improve non-US layout support.
|
||||
_avaKeyMapping[AvaKey.Oem1] = Key.Semicolon;
|
||||
_avaKeyMapping[AvaKey.Oem2] = Key.Slash;
|
||||
_avaKeyMapping[AvaKey.Oem3] = Key.Tilde;
|
||||
_avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft;
|
||||
_avaKeyMapping[AvaKey.Oem5] = Key.BackSlash;
|
||||
_avaKeyMapping[AvaKey.Oem6] = Key.BracketRight;
|
||||
_avaKeyMapping[AvaKey.Oem7] = Key.Quote;
|
||||
_avaKeyMapping[AvaKey.OemBackslash] = Key.Grave;
|
||||
_avaKeyMapping[AvaKey.Oem102] = Key.Grave;
|
||||
|
||||
// Common alternates for non-US/JIS physical keys.
|
||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash;
|
||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash;
|
||||
}
|
||||
|
||||
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
|
||||
{
|
||||
avaKey = AvaKey.None;
|
||||
|
||||
bool keyExist = key < Key.Count && (int)key < _keyMapping.Length;
|
||||
bool keyExist = (int)key < _keyMapping.Length;
|
||||
if (keyExist)
|
||||
{
|
||||
avaKey = _keyMapping[(int)key];
|
||||
@@ -343,34 +178,9 @@ namespace Ryujinx.Ava.Input
|
||||
return keyExist;
|
||||
}
|
||||
|
||||
public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey)
|
||||
{
|
||||
avaPhysicalKey = AvaPhysicalKey.None;
|
||||
|
||||
bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length;
|
||||
if (keyExist)
|
||||
{
|
||||
avaPhysicalKey = _physicalKeyMapping[(int)key];
|
||||
}
|
||||
|
||||
return keyExist;
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaKey key)
|
||||
{
|
||||
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaPhysicalKey key)
|
||||
{
|
||||
return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key)
|
||||
{
|
||||
Key inputKey = ToInputKey(key);
|
||||
|
||||
return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ using Ryujinx.Headless;
|
||||
using Ryujinx.SDL3.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
@@ -52,6 +54,22 @@ namespace Ryujinx.Ava
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
#if !DEBUG
|
||||
// this fixes the "hide console" option by forcing the emulator to launch in an old-school cmd
|
||||
if (!Console.Title.Contains("conhost.exe"))
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
sb.Append(arg.Contains(' ') ? $" \"{arg}\"" : $" {arg}");
|
||||
}
|
||||
|
||||
Process.Start("conhost.exe", $"{Environment.ProcessPath} {sb}");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
|
||||
{
|
||||
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||
<PackageReference Include="SharpCompress" />
|
||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
@@ -68,7 +69,6 @@
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="SPB" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -169,9 +169,8 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Dark.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Light.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Forgejo.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,8 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration
|
||||
@@ -271,45 +269,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Id = "0",
|
||||
PlayerIndex = PlayerIndex.Player1,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
@@ -8,8 +8,6 @@ using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.HLE;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration
|
||||
{
|
||||
@@ -287,45 +285,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Name = "Keyboard",
|
||||
PlayerIndex = PlayerIndex.Player1,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Ryujinx.Ava.Systems
|
||||
}
|
||||
|
||||
// If build URL not found, assume no new update is available.
|
||||
if (_versionResponse.ArtifactUrl is null or "")
|
||||
if (string.IsNullOrEmpty(_versionResponse.ArtifactUrl))
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
@@ -123,6 +123,8 @@ namespace Ryujinx.Ava.Systems
|
||||
return default;
|
||||
}
|
||||
|
||||
_connectionCount = (int)_versionResponse.MaxConcurrency;
|
||||
|
||||
return (currentVersion, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Gommon;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Helper;
|
||||
using Ryujinx.Common.Logging;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Formats.Tar;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -21,7 +22,6 @@ using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Ryujinx.Ava.Systems
|
||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||
private const int ConnectionCount = 4;
|
||||
|
||||
private static int _connectionCount = 1;
|
||||
private static long _buildSize;
|
||||
private static bool _updateSuccessful;
|
||||
private static bool _running;
|
||||
@@ -73,27 +73,6 @@ namespace Ryujinx.Ava.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
// GitLab instance is located in Ukraine. Connection times will vary across the world.
|
||||
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
string newVersionString = ReleaseInformation.IsCanaryBuild
|
||||
@@ -143,6 +122,14 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
Directory.CreateDirectory(_updateDir);
|
||||
|
||||
// If we get a .zip url switch it to the preferred .7z file instead
|
||||
// The update server still returns the .zip url by default for legacy support
|
||||
downloadUrl = downloadUrl.Replace(".zip", ".7z");
|
||||
|
||||
// If we get a .tar.gz url switch it to the preferred .tar.xz file instead
|
||||
// The update server still returns the .tar.gz url by default for legacy support
|
||||
downloadUrl = downloadUrl.Replace(".tar.gz", ".tar.xz");
|
||||
|
||||
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||
|
||||
TaskDialog taskDialog = new()
|
||||
@@ -153,6 +140,27 @@ namespace Ryujinx.Ava.Systems
|
||||
ShowProgressBar = true,
|
||||
XamlRoot = RyujinxApp.MainWindow,
|
||||
};
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
// Forgejo instance is located in Ukraine. Connection times will vary across the world.
|
||||
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
@@ -234,22 +242,22 @@ namespace Ryujinx.Ava.Systems
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long remainderChunk = _buildSize % ConnectionCount;
|
||||
long chunkSize = _buildSize / _connectionCount;
|
||||
long remainderChunk = _buildSize % _connectionCount;
|
||||
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
int[] progressPercentage = new int[_connectionCount];
|
||||
|
||||
List<byte[]> list = new(ConnectionCount);
|
||||
List<WebClient> webClients = new(ConnectionCount);
|
||||
List<byte[]> list = new(_connectionCount);
|
||||
List<WebClient> webClients = new(_connectionCount);
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
for (int i = 0; i < _connectionCount; i++)
|
||||
{
|
||||
list.Add([]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
for (int i = 0; i < _connectionCount; i++)
|
||||
{
|
||||
#pragma warning disable SYSLIB0014
|
||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||
@@ -258,7 +266,7 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
if (i == _connectionCount - 1)
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
@@ -275,7 +283,7 @@ namespace Ryujinx.Ava.Systems
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
@@ -294,10 +302,10 @@ namespace Ryujinx.Ava.Systems
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
if (Equals(completedRequests, _connectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
@@ -402,73 +410,33 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
private static void ExtractTarGzipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using FileStream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
using GZipStream gzipStream = new(inStream, CompressionMode.Decompress);
|
||||
|
||||
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarXzipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using FileStream inStream = File.OpenRead(archivePath);
|
||||
using XZStream gzipStream = new(inStream);
|
||||
|
||||
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
private static void ExtractZipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
|
||||
zipStream.CopyTo(outStream);
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
ZipFile.ExtractToDirectory(archivePath, outputDirectoryPath);
|
||||
}
|
||||
|
||||
private static void Extract7ZipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
IArchive archive = ArchiveFactory.OpenArchive(archivePath);
|
||||
archive.WriteToDirectory(outputDirectoryPath);
|
||||
}
|
||||
|
||||
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
@@ -479,16 +447,20 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
||||
ExtractTarXzipFile(updateFile, _updateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
||||
Extract7ZipFile(updateFile, _updateDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// The new decompression implementations don't have a way to show progress
|
||||
// so the progressbar is just set to 100% after the decompression is done
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
|
||||
{
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
|
||||
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
@@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
|
||||
{
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
|
||||
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
@@ -26,7 +25,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
private bool _isWaitingForInput;
|
||||
private bool _shouldUnbind;
|
||||
private IKeyboard _keyboard;
|
||||
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
|
||||
|
||||
public ButtonKeyAssigner(ToggleButton toggleButton)
|
||||
@@ -36,9 +34,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
|
||||
{
|
||||
_keyboard = keyboard;
|
||||
ClearKeyboardState(_keyboard);
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ToggledButton.IsChecked = true;
|
||||
@@ -87,7 +82,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
_isWaitingForInput = false;
|
||||
|
||||
ToggledButton.IsChecked = false;
|
||||
ClearKeyboardState(_keyboard);
|
||||
|
||||
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
|
||||
{
|
||||
@@ -104,15 +98,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
_isWaitingForInput = false;
|
||||
ToggledButton.IsChecked = false;
|
||||
_shouldUnbind = shouldUnbind;
|
||||
ClearKeyboardState(_keyboard);
|
||||
}
|
||||
|
||||
private static void ClearKeyboardState(IKeyboard keyboard)
|
||||
{
|
||||
if (keyboard is AvaloniaKeyboard avaloniaKeyboard)
|
||||
{
|
||||
avaloniaKeyboard.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
@@ -13,6 +12,79 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public static readonly KeyValueConverter Instance = new();
|
||||
|
||||
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
|
||||
{
|
||||
{ Key.Unknown, LocaleKeys.KeyUnknown },
|
||||
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
|
||||
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
|
||||
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
|
||||
{ Key.ControlRight, LocaleKeys.KeyControlRight },
|
||||
{ Key.AltLeft, LocaleKeys.KeyAltLeft },
|
||||
{ Key.AltRight, LocaleKeys.KeyAltRight },
|
||||
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
|
||||
{ Key.WinRight, LocaleKeys.KeyWinRight },
|
||||
{ Key.Up, LocaleKeys.KeyUp },
|
||||
{ Key.Down, LocaleKeys.KeyDown },
|
||||
{ Key.Left, LocaleKeys.KeyLeft },
|
||||
{ Key.Right, LocaleKeys.KeyRight },
|
||||
{ Key.Enter, LocaleKeys.KeyEnter },
|
||||
{ Key.Escape, LocaleKeys.KeyEscape },
|
||||
{ Key.Space, LocaleKeys.KeySpace },
|
||||
{ Key.Tab, LocaleKeys.KeyTab },
|
||||
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
|
||||
{ Key.Insert, LocaleKeys.KeyInsert },
|
||||
{ Key.Delete, LocaleKeys.KeyDelete },
|
||||
{ Key.PageUp, LocaleKeys.KeyPageUp },
|
||||
{ Key.PageDown, LocaleKeys.KeyPageDown },
|
||||
{ Key.Home, LocaleKeys.KeyHome },
|
||||
{ Key.End, LocaleKeys.KeyEnd },
|
||||
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
|
||||
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
|
||||
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
|
||||
{ Key.Pause, LocaleKeys.KeyPause },
|
||||
{ Key.NumLock, LocaleKeys.KeyNumLock },
|
||||
{ Key.Clear, LocaleKeys.KeyClear },
|
||||
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
|
||||
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
|
||||
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
|
||||
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
|
||||
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
|
||||
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
|
||||
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
|
||||
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
|
||||
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
|
||||
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
|
||||
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
|
||||
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
|
||||
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
|
||||
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
|
||||
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
|
||||
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
|
||||
{ Key.Number0, LocaleKeys.KeyNumber0 },
|
||||
{ Key.Number1, LocaleKeys.KeyNumber1 },
|
||||
{ Key.Number2, LocaleKeys.KeyNumber2 },
|
||||
{ Key.Number3, LocaleKeys.KeyNumber3 },
|
||||
{ Key.Number4, LocaleKeys.KeyNumber4 },
|
||||
{ Key.Number5, LocaleKeys.KeyNumber5 },
|
||||
{ Key.Number6, LocaleKeys.KeyNumber6 },
|
||||
{ Key.Number7, LocaleKeys.KeyNumber7 },
|
||||
{ Key.Number8, LocaleKeys.KeyNumber8 },
|
||||
{ Key.Number9, LocaleKeys.KeyNumber9 },
|
||||
{ Key.Tilde, LocaleKeys.KeyTilde },
|
||||
{ Key.Grave, LocaleKeys.KeyGrave },
|
||||
{ Key.Minus, LocaleKeys.KeyMinus },
|
||||
{ Key.Plus, LocaleKeys.KeyPlus },
|
||||
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
|
||||
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
|
||||
{ Key.Semicolon, LocaleKeys.KeySemicolon },
|
||||
{ Key.Quote, LocaleKeys.KeyQuote },
|
||||
{ Key.Comma, LocaleKeys.KeyComma },
|
||||
{ Key.Period, LocaleKeys.KeyPeriod },
|
||||
{ Key.Slash, LocaleKeys.KeySlash },
|
||||
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
|
||||
{ Key.Unbound, LocaleKeys.KeyUnbound },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
|
||||
{
|
||||
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
|
||||
@@ -38,38 +110,49 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
|
||||
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
|
||||
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
|
||||
{ GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
||||
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
|
||||
{
|
||||
{ StickInputId.Left, LocaleKeys.StickLeft},
|
||||
{ StickInputId.Right, LocaleKeys.StickRight},
|
||||
{ StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
||||
{ StickInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||
};
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
string keyString = string.Empty;
|
||||
LocaleKeys localeKey;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case Key key:
|
||||
if (KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel))
|
||||
if (_keysMap.TryGetValue(key, out localeKey))
|
||||
{
|
||||
keyString = localizedKeyLabel;
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
localeKey = localeKey switch
|
||||
{
|
||||
LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft,
|
||||
LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight,
|
||||
LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft,
|
||||
LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight,
|
||||
LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft,
|
||||
LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight,
|
||||
_ => localeKey
|
||||
};
|
||||
}
|
||||
|
||||
keyString = LocaleManager.Instance[localeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
keyString = key.ToString();
|
||||
}
|
||||
|
||||
break;
|
||||
case PhysicalKey physicalKey:
|
||||
keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey);
|
||||
break;
|
||||
case GamepadInputId gamepadInputId:
|
||||
LocaleKeys localeKey;
|
||||
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
|
||||
{
|
||||
keyString = LocaleManager.Instance[localeKey];
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal class InputDeviceNameConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
public static readonly InputDeviceNameConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is ValueTuple<DeviceType, string, string> device ? device.Item3 : string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using InputKey = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class KeyboardLayoutLocaleHelper
|
||||
{
|
||||
private static readonly Dictionary<InputKey, LocaleKeys> _sharedLocalizedKeysMap = new()
|
||||
{
|
||||
[InputKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown,
|
||||
[InputKey.ShiftLeft] = LocaleKeys.KeyboardLayout_KeyShiftLeft,
|
||||
[InputKey.ShiftRight] = LocaleKeys.KeyboardLayout_KeyShiftRight,
|
||||
[InputKey.ControlLeft] = LocaleKeys.KeyboardLayout_KeyControlLeft,
|
||||
[InputKey.ControlRight] = LocaleKeys.KeyboardLayout_KeyControlRight,
|
||||
[InputKey.AltLeft] = LocaleKeys.KeyboardLayout_KeyAltLeft,
|
||||
[InputKey.AltRight] = LocaleKeys.KeyboardLayout_KeyAltRight,
|
||||
[InputKey.WinLeft] = LocaleKeys.KeyboardLayout_KeyWinLeft,
|
||||
[InputKey.WinRight] = LocaleKeys.KeyboardLayout_KeyWinRight,
|
||||
[InputKey.Up] = LocaleKeys.KeyboardLayout_KeyUp,
|
||||
[InputKey.Down] = LocaleKeys.KeyboardLayout_KeyDown,
|
||||
[InputKey.Left] = LocaleKeys.KeyboardLayout_KeyLeft,
|
||||
[InputKey.Right] = LocaleKeys.KeyboardLayout_KeyRight,
|
||||
[InputKey.Enter] = LocaleKeys.KeyboardLayout_KeyEnter,
|
||||
[InputKey.Escape] = LocaleKeys.KeyboardLayout_KeyEscape,
|
||||
[InputKey.Space] = LocaleKeys.KeyboardLayout_KeySpace,
|
||||
[InputKey.Tab] = LocaleKeys.KeyboardLayout_KeyTab,
|
||||
[InputKey.BackSpace] = LocaleKeys.KeyboardLayout_KeyBackSpace,
|
||||
[InputKey.Insert] = LocaleKeys.KeyboardLayout_KeyInsert,
|
||||
[InputKey.Delete] = LocaleKeys.KeyboardLayout_KeyDelete,
|
||||
[InputKey.PageUp] = LocaleKeys.KeyboardLayout_KeyPageUp,
|
||||
[InputKey.PageDown] = LocaleKeys.KeyboardLayout_KeyPageDown,
|
||||
[InputKey.Home] = LocaleKeys.KeyboardLayout_KeyHome,
|
||||
[InputKey.End] = LocaleKeys.KeyboardLayout_KeyEnd,
|
||||
[InputKey.CapsLock] = LocaleKeys.KeyboardLayout_KeyCapsLock,
|
||||
[InputKey.ScrollLock] = LocaleKeys.KeyboardLayout_KeyScrollLock,
|
||||
[InputKey.PrintScreen] = LocaleKeys.KeyboardLayout_KeyPrintScreen,
|
||||
[InputKey.Pause] = LocaleKeys.KeyboardLayout_KeyPause,
|
||||
[InputKey.NumLock] = LocaleKeys.KeyboardLayout_KeyNumLock,
|
||||
[InputKey.Clear] = LocaleKeys.KeyboardLayout_KeyClear,
|
||||
[InputKey.Keypad0] = LocaleKeys.KeyboardLayout_KeyKeypad0,
|
||||
[InputKey.Keypad1] = LocaleKeys.KeyboardLayout_KeyKeypad1,
|
||||
[InputKey.Keypad2] = LocaleKeys.KeyboardLayout_KeyKeypad2,
|
||||
[InputKey.Keypad3] = LocaleKeys.KeyboardLayout_KeyKeypad3,
|
||||
[InputKey.Keypad4] = LocaleKeys.KeyboardLayout_KeyKeypad4,
|
||||
[InputKey.Keypad5] = LocaleKeys.KeyboardLayout_KeyKeypad5,
|
||||
[InputKey.Keypad6] = LocaleKeys.KeyboardLayout_KeyKeypad6,
|
||||
[InputKey.Keypad7] = LocaleKeys.KeyboardLayout_KeyKeypad7,
|
||||
[InputKey.Keypad8] = LocaleKeys.KeyboardLayout_KeyKeypad8,
|
||||
[InputKey.Keypad9] = LocaleKeys.KeyboardLayout_KeyKeypad9,
|
||||
[InputKey.KeypadDivide] = LocaleKeys.KeyboardLayout_KeyKeypadDivide,
|
||||
[InputKey.KeypadMultiply] = LocaleKeys.KeyboardLayout_KeyKeypadMultiply,
|
||||
[InputKey.KeypadSubtract] = LocaleKeys.KeyboardLayout_KeyKeypadSubtract,
|
||||
[InputKey.KeypadAdd] = LocaleKeys.KeyboardLayout_KeyKeypadAdd,
|
||||
[InputKey.KeypadDecimal] = LocaleKeys.KeyboardLayout_KeyKeypadDecimal,
|
||||
[InputKey.KeypadEnter] = LocaleKeys.KeyboardLayout_KeyKeypadEnter,
|
||||
[InputKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<InputKey, LocaleKeys> _semanticPrintableKeysMap = new()
|
||||
{
|
||||
[InputKey.Number0] = LocaleKeys.KeyboardLayout_KeyNumber0,
|
||||
[InputKey.Number1] = LocaleKeys.KeyboardLayout_KeyNumber1,
|
||||
[InputKey.Number2] = LocaleKeys.KeyboardLayout_KeyNumber2,
|
||||
[InputKey.Number3] = LocaleKeys.KeyboardLayout_KeyNumber3,
|
||||
[InputKey.Number4] = LocaleKeys.KeyboardLayout_KeyNumber4,
|
||||
[InputKey.Number5] = LocaleKeys.KeyboardLayout_KeyNumber5,
|
||||
[InputKey.Number6] = LocaleKeys.KeyboardLayout_KeyNumber6,
|
||||
[InputKey.Number7] = LocaleKeys.KeyboardLayout_KeyNumber7,
|
||||
[InputKey.Number8] = LocaleKeys.KeyboardLayout_KeyNumber8,
|
||||
[InputKey.Number9] = LocaleKeys.KeyboardLayout_KeyNumber9,
|
||||
[InputKey.Tilde] = LocaleKeys.KeyboardLayout_KeyTilde,
|
||||
[InputKey.Grave] = LocaleKeys.KeyboardLayout_KeyGrave,
|
||||
[InputKey.Minus] = LocaleKeys.KeyboardLayout_KeyMinus,
|
||||
[InputKey.Plus] = LocaleKeys.KeyboardLayout_KeyPlus,
|
||||
[InputKey.BracketLeft] = LocaleKeys.KeyboardLayout_KeyBracketLeft,
|
||||
[InputKey.BracketRight] = LocaleKeys.KeyboardLayout_KeyBracketRight,
|
||||
[InputKey.Semicolon] = LocaleKeys.KeyboardLayout_KeySemicolon,
|
||||
[InputKey.Quote] = LocaleKeys.KeyboardLayout_KeyQuote,
|
||||
[InputKey.Comma] = LocaleKeys.KeyboardLayout_KeyComma,
|
||||
[InputKey.Period] = LocaleKeys.KeyboardLayout_KeyPeriod,
|
||||
[InputKey.Slash] = LocaleKeys.KeyboardLayout_KeySlash,
|
||||
[InputKey.BackSlash] = LocaleKeys.KeyboardLayout_KeyBackSlash,
|
||||
};
|
||||
|
||||
public static bool TryGetSemanticLabel(InputKey key, out string label)
|
||||
{
|
||||
if (TryGetSemanticLocaleKey(key, out LocaleKeys localeKey))
|
||||
{
|
||||
label = GetLocalizedString(localeKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetPhysicalLabel(ConfigPhysicalKey key, out string label)
|
||||
{
|
||||
if (TryGetPhysicalLocaleKey(key, out LocaleKeys localeKey))
|
||||
{
|
||||
label = GetLocalizedString(localeKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetPhysicalLocaleKey(ConfigPhysicalKey key, out LocaleKeys localeKey)
|
||||
{
|
||||
return _sharedLocalizedKeysMap.TryGetValue((InputKey)(int)key, out localeKey);
|
||||
}
|
||||
|
||||
private static bool TryGetSemanticLocaleKey(InputKey key, out LocaleKeys localeKey)
|
||||
{
|
||||
return _sharedLocalizedKeysMap.TryGetValue(key, out localeKey) ||
|
||||
_semanticPrintableKeysMap.TryGetValue(key, out localeKey);
|
||||
}
|
||||
|
||||
private static string GetLocalizedString(LocaleKeys localeKey)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
localeKey = localeKey switch
|
||||
{
|
||||
LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight,
|
||||
LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight,
|
||||
LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight,
|
||||
_ => localeKey
|
||||
};
|
||||
}
|
||||
|
||||
return LocaleManager.Instance[localeKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
using Avalonia.Input;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using InputKey = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class PhysicalKeyLabelHelper
|
||||
{
|
||||
private const string ObservedLabelsFileName = "keyboard_layout_labels.json";
|
||||
private static readonly ConcurrentDictionary<ConfigPhysicalKey, string> _observedLayoutLabels = new();
|
||||
private static readonly object _observedLayoutLabelsLock = new();
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
private static bool _observedLayoutLabelsLoaded;
|
||||
public static event Action LabelsChanged;
|
||||
|
||||
public static string GetDisplayString(ConfigPhysicalKey key)
|
||||
{
|
||||
EnsureObservedLayoutLabelsLoaded();
|
||||
|
||||
if (KeyboardLayoutLocaleHelper.TryGetPhysicalLabel(key, out string localizedLabel))
|
||||
{
|
||||
return localizedLabel;
|
||||
}
|
||||
|
||||
if (_observedLayoutLabels.TryGetValue(key, out string observedLabel))
|
||||
{
|
||||
return observedLabel;
|
||||
}
|
||||
|
||||
if (TryGetFallbackPrintableKeyLabel(key, out string label))
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
return key.ToString();
|
||||
}
|
||||
|
||||
public static void ObserveKeyPress(object sender, KeyEventArgs args)
|
||||
{
|
||||
EnsureObservedLayoutLabelsLoaded();
|
||||
|
||||
if (args.KeyModifiers != KeyModifiers.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputKey inputKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
|
||||
if (!TryConvertToConfigPhysicalKey(inputKey, out ConfigPhysicalKey physicalKey) ||
|
||||
KeyboardLayoutLocaleHelper.TryGetPhysicalLocaleKey(physicalKey, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryNormalizeObservedPrintableLabel(args.KeySymbol, out string label))
|
||||
{
|
||||
if (IsCapsLockOn() && !char.IsLetter(label[0]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_observedLayoutLabels.TryGetValue(physicalKey, out string existingLabel) && existingLabel == label)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_observedLayoutLabels[physicalKey] = label;
|
||||
SaveObservedLayoutLabels();
|
||||
LabelsChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureObservedLayoutLabelsLoaded()
|
||||
{
|
||||
if (_observedLayoutLabelsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_observedLayoutLabelsLock)
|
||||
{
|
||||
if (_observedLayoutLabelsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string labelsPath = GetObservedLabelsPath();
|
||||
|
||||
if (File.Exists(labelsPath))
|
||||
{
|
||||
Dictionary<string, string> labels = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(labelsPath), _serializerOptions);
|
||||
|
||||
if (labels != null)
|
||||
{
|
||||
foreach ((string key, string value) in labels)
|
||||
{
|
||||
if (Enum.TryParse(key, out ConfigPhysicalKey physicalKey) &&
|
||||
!string.IsNullOrEmpty(value))
|
||||
{
|
||||
_observedLayoutLabels[physicalKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_observedLayoutLabelsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveObservedLayoutLabels()
|
||||
{
|
||||
lock (_observedLayoutLabelsLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> labels = [];
|
||||
|
||||
foreach ((ConfigPhysicalKey key, string value) in _observedLayoutLabels)
|
||||
{
|
||||
labels[key.ToString()] = value;
|
||||
}
|
||||
|
||||
File.WriteAllText(GetObservedLabelsPath(), JsonSerializer.Serialize(labels, _serializerOptions));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetObservedLabelsPath()
|
||||
{
|
||||
return Path.Combine(AppDataManager.BaseDirPath, ObservedLabelsFileName);
|
||||
}
|
||||
|
||||
private static bool TryGetFallbackPrintableKeyLabel(ConfigPhysicalKey key, out string label)
|
||||
{
|
||||
// The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label.
|
||||
if (key == ConfigPhysicalKey.Grave)
|
||||
{
|
||||
label = "<>";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!AvaloniaKeyboardMappingHelper.TryGetAvaPhysicalKey((InputKey)(int)key, out AvaPhysicalKey avaPhysicalKey))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
label = PhysicalKeyExtensions.ToQwertyKeySymbol(avaPhysicalKey, false);
|
||||
|
||||
if (string.IsNullOrEmpty(label) || label.Length != 1 || char.IsControl(label[0]))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char.IsLetter(label[0]))
|
||||
{
|
||||
label = char.ToUpperInvariant(label[0]).ToString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsCapsLockOn()
|
||||
{
|
||||
try
|
||||
{
|
||||
return OperatingSystem.IsWindows() && Console.CapsLock;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryNormalizeObservedPrintableLabel(string keySymbol, out string label)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keySymbol) || keySymbol.Length != 1 || char.IsControl(keySymbol[0]))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
label = char.IsLetter(keySymbol[0])
|
||||
? char.ToUpperInvariant(keySymbol[0]).ToString()
|
||||
: keySymbol;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryConvertToConfigPhysicalKey(InputKey key, out ConfigPhysicalKey physicalKey)
|
||||
{
|
||||
if (key is >= InputKey.Unknown and < InputKey.Count)
|
||||
{
|
||||
physicalKey = (ConfigPhysicalKey)(int)key;
|
||||
return true;
|
||||
}
|
||||
|
||||
physicalKey = ConfigPhysicalKey.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,88 +13,88 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftStickUp { get; set; }
|
||||
public partial Key LeftStickUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftStickDown { get; set; }
|
||||
public partial Key LeftStickDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftStickLeft { get; set; }
|
||||
public partial Key LeftStickLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftStickRight { get; set; }
|
||||
public partial Key LeftStickRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftStickButton { get; set; }
|
||||
public partial Key LeftStickButton { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightStickUp { get; set; }
|
||||
public partial Key RightStickUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightStickDown { get; set; }
|
||||
public partial Key RightStickDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightStickLeft { get; set; }
|
||||
public partial Key RightStickLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightStickRight { get; set; }
|
||||
public partial Key RightStickRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightStickButton { get; set; }
|
||||
public partial Key RightStickButton { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey DpadUp { get; set; }
|
||||
public partial Key DpadUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey DpadDown { get; set; }
|
||||
public partial Key DpadDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey DpadLeft { get; set; }
|
||||
public partial Key DpadLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey DpadRight { get; set; }
|
||||
public partial Key DpadRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonMinus { get; set; }
|
||||
public partial Key ButtonMinus { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonPlus { get; set; }
|
||||
public partial Key ButtonPlus { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonA { get; set; }
|
||||
public partial Key ButtonA { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonB { get; set; }
|
||||
public partial Key ButtonB { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonX { get; set; }
|
||||
public partial Key ButtonX { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonY { get; set; }
|
||||
public partial Key ButtonY { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonL { get; set; }
|
||||
public partial Key ButtonL { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonR { get; set; }
|
||||
public partial Key ButtonR { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonZl { get; set; }
|
||||
public partial Key ButtonZl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey ButtonZr { get; set; }
|
||||
public partial Key ButtonZr { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftButtonSl { get; set; }
|
||||
public partial Key LeftButtonSl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey LeftButtonSr { get; set; }
|
||||
public partial Key LeftButtonSr { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightButtonSl { get; set; }
|
||||
public partial Key RightButtonSl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PhysicalKey RightButtonSr { get; set; }
|
||||
public partial Key RightButtonSr { get; set; }
|
||||
|
||||
public KeyboardInputConfig(InputConfig config)
|
||||
{
|
||||
@@ -153,7 +153,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
PlayerIndex = PlayerIndex,
|
||||
ControllerType = ControllerType,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = DpadUp,
|
||||
DpadDown = DpadDown,
|
||||
@@ -165,7 +165,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonSl = LeftButtonSl,
|
||||
ButtonSr = LeftButtonSr,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = ButtonA,
|
||||
ButtonB = ButtonB,
|
||||
@@ -177,7 +177,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonR = ButtonR,
|
||||
ButtonZr = ButtonZr,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = LeftStickUp,
|
||||
StickDown = LeftStickDown,
|
||||
@@ -185,7 +185,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
StickLeft = LeftStickLeft,
|
||||
StickButton = LeftStickButton,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = RightStickUp,
|
||||
StickDown = RightStickDown,
|
||||
@@ -198,37 +198,5 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public void NotifyKeyLabelsChanged()
|
||||
{
|
||||
OnPropertiesChanged(nameof(LeftStickUp),
|
||||
nameof(LeftStickDown),
|
||||
nameof(LeftStickLeft),
|
||||
nameof(LeftStickRight),
|
||||
nameof(LeftStickButton),
|
||||
nameof(RightStickUp),
|
||||
nameof(RightStickDown),
|
||||
nameof(RightStickLeft),
|
||||
nameof(RightStickRight),
|
||||
nameof(RightStickButton),
|
||||
nameof(DpadUp),
|
||||
nameof(DpadDown),
|
||||
nameof(DpadLeft),
|
||||
nameof(DpadRight),
|
||||
nameof(ButtonMinus),
|
||||
nameof(ButtonPlus),
|
||||
nameof(ButtonA),
|
||||
nameof(ButtonB),
|
||||
nameof(ButtonX),
|
||||
nameof(ButtonY),
|
||||
nameof(ButtonL),
|
||||
nameof(ButtonR),
|
||||
nameof(ButtonZl),
|
||||
nameof(ButtonZr),
|
||||
nameof(LeftButtonSl),
|
||||
nameof(LeftButtonSr),
|
||||
nameof(RightButtonSl),
|
||||
nameof(RightButtonSr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,42 +154,42 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
{
|
||||
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickRight))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight))
|
||||
{
|
||||
leftBuffer.Item1 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft))
|
||||
{
|
||||
leftBuffer.Item1 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickUp))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp))
|
||||
{
|
||||
leftBuffer.Item2 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickDown))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown))
|
||||
{
|
||||
leftBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickRight))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight))
|
||||
{
|
||||
rightBuffer.Item1 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickLeft))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft))
|
||||
{
|
||||
rightBuffer.Item1 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickUp))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp))
|
||||
{
|
||||
rightBuffer.Item2 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickDown))
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown))
|
||||
{
|
||||
rightBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
|
||||
Content = EmbeddedWindow;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (EmbeddedWindow != null)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class AboutWindowViewModel : BaseModel, IDisposable
|
||||
{
|
||||
[ObservableProperty] public partial Bitmap GitLabLogo { get; set; }
|
||||
[ObservableProperty] public partial Bitmap ForgejoLogo { get; set; }
|
||||
|
||||
[ObservableProperty] public partial Bitmap DiscordLogo { get; set; }
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
|
||||
private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx";
|
||||
private const string UnthemedLogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}.png?assembly=Ryujinx";
|
||||
|
||||
private void UpdateLogoTheme(string theme)
|
||||
{
|
||||
@@ -46,7 +47,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
string themeName = isDarkTheme ? "Dark" : "Light";
|
||||
|
||||
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
|
||||
GitLabLogo = LoadBitmap(LogoPathFormat.Format("GitLab", themeName));
|
||||
ForgejoLogo = LoadBitmap(UnthemedLogoPathFormat.Format("Forgejo"));
|
||||
}
|
||||
|
||||
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
|
||||
@@ -55,7 +56,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
RyujinxApp.ThemeChanged -= Ryujinx_ThemeChanged;
|
||||
|
||||
GitLabLogo.Dispose();
|
||||
ForgejoLogo.Dispose();
|
||||
DiscordLogo.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -88,19 +88,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public async void ShowMotionConfig()
|
||||
{
|
||||
await MotionInputView.Show(this);
|
||||
ParentModel.RefreshModifiedState();
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public async void ShowRumbleConfig()
|
||||
{
|
||||
await RumbleInputView.Show(this);
|
||||
ParentModel.RefreshModifiedState();
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public async void ShowLedConfig()
|
||||
{
|
||||
await LedInputView.Show(this);
|
||||
ParentModel.RefreshModifiedState();
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public void OnParentModelChanged()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
@@ -29,7 +28,7 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
@@ -43,7 +42,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
private const string KeyboardString = "keyboard";
|
||||
private const string ControllerString = "controller";
|
||||
private readonly MainWindow _mainWindow;
|
||||
private Control _keyboardDriverControl;
|
||||
|
||||
private PlayerIndex _playerId;
|
||||
private PlayerIndex _playerIdChoose;
|
||||
@@ -67,7 +65,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; private set; }
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
|
||||
public IGamepad SelectedGamepad
|
||||
{
|
||||
@@ -91,6 +89,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
|
||||
internal ObservableCollection<ControllerModel> Controllers { get; set; }
|
||||
public AvaloniaList<string> ProfilesList { get; set; }
|
||||
public AvaloniaList<string> DeviceList { get; set; }
|
||||
|
||||
public bool UseGlobalConfig;
|
||||
|
||||
@@ -100,6 +99,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public bool IsKeyboard => !IsController;
|
||||
public bool IsRight { get; set; }
|
||||
public bool IsLeft { get; set; }
|
||||
public string RevertDeviceId { get; set; }
|
||||
public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0;
|
||||
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
LoadDevice();
|
||||
LoadProfiles();
|
||||
|
||||
RevertDeviceId = Devices[Device].Id;
|
||||
_isLoaded = true;
|
||||
_isChangeTrackingActive = true;
|
||||
OnPropertyChanged();
|
||||
@@ -174,56 +175,50 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
get => _controller;
|
||||
set
|
||||
{
|
||||
int controllerIndex = value < 0 ? 0 : value;
|
||||
MarkAsChanged();
|
||||
|
||||
if (controllerIndex == _controller)
|
||||
_controller = value;
|
||||
|
||||
if (_controller == -1)
|
||||
{
|
||||
return;
|
||||
_controller = 0;
|
||||
}
|
||||
|
||||
ApplyControllerSelection(controllerIndex);
|
||||
RefreshModifiedState();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyControllerSelection(int controllerIndex)
|
||||
{
|
||||
_controller = controllerIndex;
|
||||
|
||||
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
|
||||
{
|
||||
ControllerType controller = Controllers[_controller].Type;
|
||||
|
||||
IsLeft = true;
|
||||
IsRight = true;
|
||||
|
||||
switch (controller)
|
||||
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
|
||||
{
|
||||
case ControllerType.Handheld:
|
||||
ControllerImage = JoyConPairResource;
|
||||
break;
|
||||
case ControllerType.ProController:
|
||||
ControllerImage = ProControllerResource;
|
||||
break;
|
||||
case ControllerType.JoyconPair:
|
||||
ControllerImage = JoyConPairResource;
|
||||
break;
|
||||
case ControllerType.JoyconLeft:
|
||||
ControllerImage = JoyConLeftResource;
|
||||
IsRight = false;
|
||||
break;
|
||||
case ControllerType.JoyconRight:
|
||||
ControllerImage = JoyConRightResource;
|
||||
IsLeft = false;
|
||||
break;
|
||||
ControllerType controller = Controllers[_controller].Type;
|
||||
|
||||
IsLeft = true;
|
||||
IsRight = true;
|
||||
|
||||
switch (controller)
|
||||
{
|
||||
case ControllerType.Handheld:
|
||||
ControllerImage = JoyConPairResource;
|
||||
break;
|
||||
case ControllerType.ProController:
|
||||
ControllerImage = ProControllerResource;
|
||||
break;
|
||||
case ControllerType.JoyconPair:
|
||||
ControllerImage = JoyConPairResource;
|
||||
break;
|
||||
case ControllerType.JoyconLeft:
|
||||
ControllerImage = JoyConLeftResource;
|
||||
IsRight = false;
|
||||
break;
|
||||
case ControllerType.JoyconRight:
|
||||
ControllerImage = JoyConRightResource;
|
||||
IsLeft = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LoadInputDriver();
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
LoadInputDriver();
|
||||
LoadProfiles();
|
||||
OnPropertyChanged();
|
||||
NotifyChanges();
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(Controller));
|
||||
NotifyChanges();
|
||||
}
|
||||
|
||||
public string ControllerImage
|
||||
@@ -260,83 +255,33 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
get => _device;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value >= Devices.Count)
|
||||
MarkAsChanged();
|
||||
|
||||
_device = value < 0 ? 0 : value;
|
||||
|
||||
if (_device >= Devices.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_device = value;
|
||||
|
||||
DeviceType selected = Devices[_device].Type;
|
||||
|
||||
if (selected != DeviceType.None)
|
||||
{
|
||||
LoadControllers();
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
LoadSelectedDeviceDefaults();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadSelectedDeviceControllers();
|
||||
LoadConfiguration(LoadDefaultConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
RefreshModifiedState();
|
||||
FindPairedDeviceInConfigFile();
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SelectedDeviceItem));
|
||||
NotifyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetCurrentDeviceToDefaults()
|
||||
{
|
||||
RefreshAvailableDevices();
|
||||
|
||||
if (_device <= 0 || _device >= Devices.Count || Devices[_device].Type == DeviceType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadSelectedDeviceDefaults();
|
||||
RefreshModifiedState();
|
||||
FindPairedDeviceInConfigFile();
|
||||
NotifyChanges();
|
||||
}
|
||||
|
||||
public void RefreshInputDevices()
|
||||
{
|
||||
RefreshAvailableDevices();
|
||||
}
|
||||
|
||||
public object SelectedDeviceItem
|
||||
{
|
||||
get => _device >= 0 && _device < Devices.Count ? Devices[_device] : null;
|
||||
set
|
||||
{
|
||||
if (value is not ValueTuple<DeviceType, string, string> selectedDevice)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int deviceIndex = Devices.ToList().FindIndex(device =>
|
||||
device.Type == selectedDevice.Item1 &&
|
||||
device.Id == selectedDevice.Item2);
|
||||
|
||||
if (deviceIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (deviceIndex == _device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Device = deviceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public InputConfig Config { get; set; }
|
||||
|
||||
public InputViewModel(UserControl owner, bool useGlobal = false) : this()
|
||||
@@ -345,8 +290,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
_mainWindow = RyujinxApp.MainWindow;
|
||||
|
||||
ReplaceKeyboardDriver(owner);
|
||||
PhysicalKeyLabelHelper.LabelsChanged += OnPhysicalKeyLabelsChanged;
|
||||
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
|
||||
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||
@@ -357,7 +301,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
_isLoaded = false;
|
||||
|
||||
RefreshAvailableDevices();
|
||||
LoadDevices();
|
||||
|
||||
PlayerId = PlayerIndex.Player1;
|
||||
}
|
||||
@@ -365,22 +309,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
_isChangeTrackingActive = true;
|
||||
}
|
||||
|
||||
public void RetargetKeyboardDriver(Control owner)
|
||||
{
|
||||
if (!Program.PreviewerDetached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReplaceKeyboardDriver(owner);
|
||||
}
|
||||
|
||||
public InputViewModel()
|
||||
{
|
||||
PlayerIndexes = [];
|
||||
Controllers = [];
|
||||
Devices = [];
|
||||
ProfilesList = [];
|
||||
DeviceList = [];
|
||||
VisualStick = new StickVisualizer(this);
|
||||
|
||||
ControllerImage = ProControllerResource;
|
||||
@@ -398,19 +333,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
|
||||
|
||||
private InputConfig GetPersistedInputConfig()
|
||||
private void LoadConfiguration(InputConfig inputConfig = null)
|
||||
{
|
||||
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||
{
|
||||
return ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||
Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||
}
|
||||
|
||||
return ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||
}
|
||||
|
||||
private void LoadConfiguration(InputConfig inputConfig = null)
|
||||
{
|
||||
Config = inputConfig ?? GetDisplayedInputConfig(GetPersistedInputConfig());
|
||||
|
||||
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
||||
{
|
||||
@@ -423,18 +355,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
private InputConfig GetDisplayedInputConfig(InputConfig persistedConfig)
|
||||
{
|
||||
if (persistedConfig is not StandardControllerInputConfig)
|
||||
{
|
||||
return persistedConfig;
|
||||
}
|
||||
|
||||
InputConfig activeConfig = _mainWindow?.ViewModel.AppHost?.NpadManager?.GetPlayerInputConfigByIndex((int)_playerId);
|
||||
|
||||
return activeConfig is StandardKeyboardInputConfig ? activeConfig : persistedConfig;
|
||||
}
|
||||
|
||||
private void FindPairedDeviceInConfigFile()
|
||||
{
|
||||
// This function allows you to output a message about the device configuration found in the file
|
||||
@@ -455,6 +375,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkAsChanged()
|
||||
{
|
||||
//If tracking is active, then allow changing the modifier
|
||||
if (!IsModified && _isChangeTrackingActive)
|
||||
{
|
||||
RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnlinkDevice()
|
||||
{
|
||||
// "Disabled" mode is available after unbinding the device
|
||||
@@ -465,117 +395,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
public void LoadDevice()
|
||||
{
|
||||
int deviceIndex = 0;
|
||||
|
||||
if (Config == null || Config.Backend == InputBackendType.Invalid)
|
||||
{
|
||||
ApplyLoadedDevice(deviceIndex);
|
||||
return;
|
||||
Device = 0;
|
||||
}
|
||||
|
||||
DeviceType type = DeviceType.None;
|
||||
|
||||
if (Config is StandardKeyboardInputConfig)
|
||||
else
|
||||
{
|
||||
type = DeviceType.Keyboard;
|
||||
DeviceType type = DeviceType.None;
|
||||
|
||||
if (Config is StandardKeyboardInputConfig)
|
||||
{
|
||||
type = DeviceType.Keyboard;
|
||||
}
|
||||
|
||||
if (Config is StandardControllerInputConfig)
|
||||
{
|
||||
type = DeviceType.Controller;
|
||||
}
|
||||
|
||||
(DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
|
||||
if (item != default)
|
||||
{
|
||||
Device = Devices.ToList().FindIndex(x => x.Id == item.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (Config is StandardControllerInputConfig)
|
||||
{
|
||||
type = DeviceType.Controller;
|
||||
}
|
||||
|
||||
(DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
|
||||
|
||||
if (item != default)
|
||||
{
|
||||
deviceIndex = Devices.ToList().FindIndex(x => x.Id == item.Id);
|
||||
}
|
||||
|
||||
ApplyLoadedDevice(deviceIndex);
|
||||
}
|
||||
|
||||
private void ApplyLoadedDevice(int deviceIndex)
|
||||
{
|
||||
_device = deviceIndex is >= 0 and < int.MaxValue ? deviceIndex : 0;
|
||||
|
||||
if (_device >= Devices.Count)
|
||||
{
|
||||
_device = 0;
|
||||
}
|
||||
|
||||
if (_device > 0 && Devices[_device].Type != DeviceType.None)
|
||||
{
|
||||
LoadControllers();
|
||||
}
|
||||
|
||||
FindPairedDeviceInConfigFile();
|
||||
OnPropertyChanged(nameof(Device));
|
||||
OnPropertyChanged(nameof(SelectedDeviceItem));
|
||||
NotifyChanges();
|
||||
}
|
||||
|
||||
private void LoadSelectedDeviceControllers()
|
||||
{
|
||||
if (_device > 0 && _device < Devices.Count && Devices[_device].Type != DeviceType.None)
|
||||
{
|
||||
LoadControllers();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSelectedDeviceDefaults()
|
||||
{
|
||||
LoadSelectedDeviceControllers();
|
||||
LoadConfiguration(LoadDefaultConfiguration());
|
||||
}
|
||||
|
||||
public void RefreshModifiedState()
|
||||
{
|
||||
if (!_isChangeTrackingActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsModified = !ConfigsMatch(GetSelectedDeviceConfig(), GetDisplayedInputConfig(GetPersistedInputConfig()));
|
||||
}
|
||||
|
||||
private static bool ConfigsMatch(InputConfig currentConfig, InputConfig otherConfig)
|
||||
{
|
||||
if (currentConfig == null || otherConfig == null)
|
||||
{
|
||||
return currentConfig == otherConfig;
|
||||
}
|
||||
|
||||
return JsonHelper.Serialize(currentConfig, _serializerContext.InputConfig) ==
|
||||
JsonHelper.Serialize(otherConfig, _serializerContext.InputConfig);
|
||||
}
|
||||
|
||||
private InputConfig GetSelectedDeviceConfig()
|
||||
{
|
||||
if (_device <= 0 || _device >= Devices.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
(DeviceType Type, string Id, string Name) device = Devices[_device];
|
||||
InputConfig config = device.Type switch
|
||||
{
|
||||
DeviceType.Keyboard => (ConfigViewModel as KeyboardInputViewModel)?.Config.GetConfig(),
|
||||
DeviceType.Controller => (ConfigViewModel as ControllerInputViewModel)?.Config.GetConfig(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
config.Id = device.Type == DeviceType.Keyboard ? device.Id : device.Id.Split(" ")[0];
|
||||
config.Name = device.Name;
|
||||
config.PlayerIndex = _playerId;
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private void LoadInputDriver()
|
||||
@@ -615,24 +462,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
_isChangeTrackingActive = false; // Disable configuration change tracking
|
||||
|
||||
bool shouldApplyKeyboardFallback = Config is StandardControllerInputConfig controllerConfig && controllerConfig.Id == id;
|
||||
LoadDevices();
|
||||
|
||||
RefreshAvailableDevices();
|
||||
|
||||
if (shouldApplyKeyboardFallback)
|
||||
{
|
||||
LoadConfiguration();
|
||||
LoadDevice();
|
||||
NotificationIsVisible = false;
|
||||
IsModified = false;
|
||||
NotifyChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsModified = true;
|
||||
RevertChanges();
|
||||
FindPairedDeviceInConfigFile();
|
||||
}
|
||||
IsModified = true;
|
||||
RevertChanges();
|
||||
FindPairedDeviceInConfigFile();
|
||||
|
||||
_isChangeTrackingActive = true; // Enable configuration change tracking
|
||||
|
||||
@@ -642,7 +476,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
_isChangeTrackingActive = false; // Disable configuration change tracking
|
||||
|
||||
RefreshAvailableDevices();
|
||||
LoadDevices();
|
||||
|
||||
IsModified = true;
|
||||
RevertChanges();
|
||||
@@ -668,23 +502,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
return device.Id.Split(" ")[0];
|
||||
}
|
||||
|
||||
private string GetCurrentConfigDeviceId()
|
||||
{
|
||||
if (_device < 0 || _device >= Devices.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
(DeviceType Type, string Id, string Name) device = Devices[_device];
|
||||
|
||||
return device.Type switch
|
||||
{
|
||||
DeviceType.Keyboard => device.Id,
|
||||
DeviceType.Controller => device.Id.Split(" ")[0],
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public void LoadControllers()
|
||||
{
|
||||
Controllers.Clear();
|
||||
@@ -693,7 +510,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld]));
|
||||
|
||||
ApplyControllerSelection(0);
|
||||
Controller = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -711,14 +528,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
// Workaround: set the box to 1 and then 0
|
||||
if (controllerIndex == 0)
|
||||
{
|
||||
ApplyControllerSelection(1);
|
||||
Controller = 1;
|
||||
}
|
||||
|
||||
ApplyControllerSelection(controllerIndex);
|
||||
Controller = controllerIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyControllerSelection(0);
|
||||
Controller = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,16 +561,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
return str[(str.IndexOf(Hyphen) + Offset)..];
|
||||
}
|
||||
|
||||
private void RefreshAvailableDevices()
|
||||
public void LoadDevices()
|
||||
{
|
||||
int selectedDeviceIndex = 0;
|
||||
(DeviceType Type, string Id, string Name) selectedDevice = default;
|
||||
|
||||
if (_device >= 0 && _device < Devices.Count)
|
||||
{
|
||||
selectedDevice = Devices[_device];
|
||||
}
|
||||
|
||||
string GetGamepadName(IGamepad gamepad, int controllerNumber)
|
||||
{
|
||||
return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
|
||||
@@ -774,6 +583,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
lock (Devices)
|
||||
{
|
||||
Devices.Clear();
|
||||
DeviceList.Clear();
|
||||
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
|
||||
|
||||
|
||||
@@ -799,20 +609,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDevice != default)
|
||||
{
|
||||
selectedDeviceIndex = Devices.ToList().FindIndex(device =>
|
||||
device.Type == selectedDevice.Type &&
|
||||
device.Id == selectedDevice.Id);
|
||||
}
|
||||
|
||||
if (selectedDeviceIndex < 0)
|
||||
{
|
||||
selectedDeviceIndex = Math.Clamp(_device, 0, Devices.Count - 1);
|
||||
}
|
||||
DeviceList.AddRange(Devices.Select(x => x.Name));
|
||||
Device = Math.Min(Device, DeviceList.Count);
|
||||
}
|
||||
|
||||
ApplyLoadedDevice(selectedDeviceIndex);
|
||||
}
|
||||
|
||||
private string GetProfileBasePath()
|
||||
@@ -878,46 +677,46 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
Id = id,
|
||||
Name = name,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick =
|
||||
new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1061,14 +860,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
_isLoaded = false;
|
||||
|
||||
string currentDeviceId = Config?.Id ?? GetCurrentConfigDeviceId();
|
||||
if (string.IsNullOrEmpty(currentDeviceId))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Configuration, $"Ignoring profile load for {ProfileName} because no active input device is selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
config.Id = currentDeviceId; // Set current device id instead of changing device(independent profiles)
|
||||
config.Id = Config.Id; // Set current device id instead of changing device(independent profiles)
|
||||
|
||||
LoadConfiguration(config);
|
||||
|
||||
@@ -1166,6 +958,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
public void RevertChanges()
|
||||
{
|
||||
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
|
||||
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
|
||||
|
||||
_isLoaded = false;
|
||||
LoadConfiguration();
|
||||
LoadDevice();
|
||||
@@ -1185,6 +980,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
IsModified = false;
|
||||
|
||||
RevertDeviceId = Devices[Device].Id; // Remember selected device after saving
|
||||
|
||||
List<InputConfig> newConfig = [];
|
||||
|
||||
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||
@@ -1204,7 +1001,25 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
else
|
||||
{
|
||||
InputConfig config = GetSelectedDeviceConfig();
|
||||
(DeviceType Type, string Id, string Name) device = Devices[Device];
|
||||
|
||||
if (device.Type == DeviceType.Keyboard)
|
||||
{
|
||||
KeyboardInputConfig inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
|
||||
inputConfig.Id = device.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
GamepadInputConfig inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
|
||||
inputConfig.Id = device.Id.Split(" ")[0];
|
||||
}
|
||||
|
||||
InputConfig config = !IsController
|
||||
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
|
||||
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
config.PlayerIndex = _playerId;
|
||||
config.Name = device.Name;
|
||||
|
||||
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
|
||||
if (i == -1)
|
||||
@@ -1245,46 +1060,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
NotifyChangesEvent?.Invoke();
|
||||
}
|
||||
|
||||
private void OnPhysicalKeyLabelsChanged()
|
||||
{
|
||||
if (ConfigViewModel is KeyboardInputViewModel keyboardInputViewModel)
|
||||
{
|
||||
Dispatcher.UIThread.Post(keyboardInputViewModel.Config.NotifyKeyLabelsChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceKeyboardDriver(Control owner)
|
||||
{
|
||||
Control target = TopLevel.GetTopLevel(owner) as Control ?? owner;
|
||||
|
||||
if (ReferenceEquals(_keyboardDriverControl, target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AvaloniaKeyboardDriver is AvaloniaKeyboardDriver oldKeyboardDriver)
|
||||
{
|
||||
oldKeyboardDriver.KeyPressed -= PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
oldKeyboardDriver.Dispose();
|
||||
}
|
||||
|
||||
_keyboardDriverControl = target;
|
||||
|
||||
AvaloniaKeyboardDriver keyboardDriver = new(target, KeyboardInputMode.Physical);
|
||||
keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
AvaloniaKeyboardDriver = keyboardDriver;
|
||||
|
||||
if (_isLoaded && Device > 0 && Device < Devices.Count && Devices[Device].Type == DeviceType.Keyboard)
|
||||
{
|
||||
SelectedGamepad?.Dispose();
|
||||
LoadInputDriver();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
PhysicalKeyLabelHelper.LabelsChanged -= OnPhysicalKeyLabelsChanged;
|
||||
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
|
||||
@@ -1295,7 +1073,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
SelectedGamepad?.Dispose();
|
||||
|
||||
AvaloniaKeyboardDriver?.Dispose();
|
||||
AvaloniaKeyboardDriver.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
Click="Button_OnClick"
|
||||
CornerRadius="15"
|
||||
Tag="https://src.ryujinx.app"
|
||||
ToolTip.Tip="{ext:Locale AboutGitLabUrlTooltipMessage}">
|
||||
<Image Source="{Binding GitLabLogo}" />
|
||||
ToolTip.Tip="{ext:Locale AboutForgejoUrlTooltipMessage}">
|
||||
<Image Source="{Binding ForgejoLogo}" />
|
||||
</Button>
|
||||
<Button
|
||||
MinWidth="30"
|
||||
|
||||
@@ -116,6 +116,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
if (e.ButtonValue.HasValue)
|
||||
{
|
||||
Button buttonValue = e.ButtonValue.Value;
|
||||
FlagInputConfigChanged();
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
@@ -186,8 +187,6 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>();
|
||||
break;
|
||||
}
|
||||
|
||||
FlagInputConfigChanged();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -213,7 +212,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
private void FlagInputConfigChanged()
|
||||
{
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.RefreshModifiedState();
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
@@ -149,7 +148,7 @@
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="5,0,10,0"
|
||||
@@ -162,38 +161,19 @@
|
||||
Name="DeviceBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding Devices}"
|
||||
SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ., Converter={x:Static helpers:InputDeviceNameConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
ItemsSource="{Binding DeviceList}"
|
||||
SelectedIndex="{Binding Device}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsRefresh}"
|
||||
Command="{Binding RefreshInputDevices}">
|
||||
Command="{Binding LoadDevice}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Refresh"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsResetKeybindsToDefault}"
|
||||
Command="{Binding ResetCurrentDeviceToDefaults}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Undo"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Controller Type -->
|
||||
<Grid
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
@@ -16,14 +15,9 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
public InputView()
|
||||
{
|
||||
ReplaceViewModel(ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||
}
|
||||
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
ViewModel?.RetargetKeyboardDriver(this);
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SaveCurrentProfile()
|
||||
@@ -34,18 +28,8 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||
{
|
||||
Dispose();
|
||||
ReplaceViewModel(enableConfigGlobal);
|
||||
}
|
||||
|
||||
private void ReplaceViewModel(bool useGlobalConfig)
|
||||
{
|
||||
ViewModel = new InputViewModel(this, useGlobalConfig); // Create new Input Page with the selected input config scope.
|
||||
ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs
|
||||
InitializeComponent();
|
||||
|
||||
if (VisualRoot is not null)
|
||||
{
|
||||
ViewModel.RetargetKeyboardDriver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
@@ -12,7 +12,7 @@ using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Button = Ryujinx.Input.Button;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
@@ -73,96 +73,95 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
if (be.ButtonValue.HasValue)
|
||||
{
|
||||
Button buttonValue = be.ButtonValue.Value;
|
||||
ViewModel.ParentModel.IsModified = true;
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
case "ButtonZl":
|
||||
ViewModel.Config.ButtonZl = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonL":
|
||||
ViewModel.Config.ButtonL = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonMinus":
|
||||
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftStickButton":
|
||||
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftStickUp":
|
||||
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftStickDown":
|
||||
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftStickRight":
|
||||
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftStickLeft":
|
||||
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "DpadUp":
|
||||
ViewModel.Config.DpadUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "DpadDown":
|
||||
ViewModel.Config.DpadDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "DpadLeft":
|
||||
ViewModel.Config.DpadLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "DpadRight":
|
||||
ViewModel.Config.DpadRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftButtonSr":
|
||||
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "LeftButtonSl":
|
||||
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightButtonSr":
|
||||
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightButtonSl":
|
||||
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonZr":
|
||||
ViewModel.Config.ButtonZr = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonR":
|
||||
ViewModel.Config.ButtonR = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonPlus":
|
||||
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonA":
|
||||
ViewModel.Config.ButtonA = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonB":
|
||||
ViewModel.Config.ButtonB = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonX":
|
||||
ViewModel.Config.ButtonX = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonY":
|
||||
ViewModel.Config.ButtonY = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightStickButton":
|
||||
ViewModel.Config.RightStickButton = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightStickUp":
|
||||
ViewModel.Config.RightStickUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightStickDown":
|
||||
ViewModel.Config.RightStickDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightStickRight":
|
||||
ViewModel.Config.RightStickRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "RightStickLeft":
|
||||
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
}
|
||||
|
||||
ViewModel.ParentModel.RefreshModifiedState();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -208,40 +207,40 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
Dictionary<string, Action> buttonActions = new()
|
||||
{
|
||||
{ "ButtonZl", () => ViewModel.Config.ButtonZl = PhysicalKey.Unbound },
|
||||
{ "ButtonL", () => ViewModel.Config.ButtonL = PhysicalKey.Unbound },
|
||||
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = PhysicalKey.Unbound },
|
||||
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = PhysicalKey.Unbound },
|
||||
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = PhysicalKey.Unbound },
|
||||
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = PhysicalKey.Unbound },
|
||||
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = PhysicalKey.Unbound },
|
||||
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = PhysicalKey.Unbound },
|
||||
{ "DpadUp", () => ViewModel.Config.DpadUp = PhysicalKey.Unbound },
|
||||
{ "DpadDown", () => ViewModel.Config.DpadDown = PhysicalKey.Unbound },
|
||||
{ "DpadLeft", () => ViewModel.Config.DpadLeft = PhysicalKey.Unbound },
|
||||
{ "DpadRight", () => ViewModel.Config.DpadRight = PhysicalKey.Unbound },
|
||||
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = PhysicalKey.Unbound },
|
||||
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = PhysicalKey.Unbound },
|
||||
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = PhysicalKey.Unbound },
|
||||
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = PhysicalKey.Unbound },
|
||||
{ "ButtonZr", () => ViewModel.Config.ButtonZr = PhysicalKey.Unbound },
|
||||
{ "ButtonR", () => ViewModel.Config.ButtonR = PhysicalKey.Unbound },
|
||||
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = PhysicalKey.Unbound },
|
||||
{ "ButtonA", () => ViewModel.Config.ButtonA = PhysicalKey.Unbound },
|
||||
{ "ButtonB", () => ViewModel.Config.ButtonB = PhysicalKey.Unbound },
|
||||
{ "ButtonX", () => ViewModel.Config.ButtonX = PhysicalKey.Unbound },
|
||||
{ "ButtonY", () => ViewModel.Config.ButtonY = PhysicalKey.Unbound },
|
||||
{ "RightStickButton", () => ViewModel.Config.RightStickButton = PhysicalKey.Unbound },
|
||||
{ "RightStickUp", () => ViewModel.Config.RightStickUp = PhysicalKey.Unbound },
|
||||
{ "RightStickDown", () => ViewModel.Config.RightStickDown = PhysicalKey.Unbound },
|
||||
{ "RightStickRight", () => ViewModel.Config.RightStickRight = PhysicalKey.Unbound },
|
||||
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = PhysicalKey.Unbound }
|
||||
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound },
|
||||
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound },
|
||||
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound },
|
||||
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound },
|
||||
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound },
|
||||
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound },
|
||||
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound },
|
||||
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound },
|
||||
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound },
|
||||
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound },
|
||||
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound },
|
||||
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound },
|
||||
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound },
|
||||
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound },
|
||||
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound },
|
||||
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound },
|
||||
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound },
|
||||
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound },
|
||||
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound },
|
||||
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound },
|
||||
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound },
|
||||
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound },
|
||||
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound },
|
||||
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound },
|
||||
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound },
|
||||
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound },
|
||||
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound },
|
||||
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound }
|
||||
};
|
||||
|
||||
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||
{
|
||||
action();
|
||||
ViewModel.ParentModel.RefreshModifiedState();
|
||||
ViewModel.ParentModel.IsModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,17 +295,17 @@
|
||||
<MenuItem
|
||||
Name="SetupGuideMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpSetup}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
Icon="{ext:Icon fa-solid fa-circle-info}"
|
||||
CommandParameter="{x:Static common:SharedConstants.SetupGuideWikiUrl}" />
|
||||
<MenuItem
|
||||
Name="LdnGuideMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpMultiplayer}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
Icon="{ext:Icon fa-solid fa-circle-info}"
|
||||
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}" />
|
||||
<MenuItem
|
||||
Name="FaqMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpFaq}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
Icon="{ext:Icon fa-solid fa-circle-info}"
|
||||
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
|
||||
@@ -34,8 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
}
|
||||
}
|
||||
|
||||
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic);
|
||||
_avaloniaKeyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
|
||||
@@ -30,7 +30,6 @@ using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Input.SDL3;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -106,9 +105,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
AvaloniaKeyboardDriver keyboardDriver = new(this, KeyboardInputMode.Semantic);
|
||||
keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
InputManager = new InputManager(keyboardDriver, new SDL3GamepadDriver());
|
||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
|
||||
|
||||
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
|
||||
this.ScalingChanged += OnScalingChanged;
|
||||
|
||||
Reference in New Issue
Block a user