Compare commits

...

8 Commits

Author SHA1 Message Date
ranidspace
153d632ee2 Fix the canary CI breaking caused by #110 (#121)
whoops

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/121
2026-05-30 21:02:30 +00:00
Babib3l
824643e143 Fix : Fix controller Stick visualizer perfomance (#119)
This PR aims to fix a bug inside the settings window that caused the Controller Stick visualizer to run at extremely degraded performance when the selected profile had a rainbow LED configuration:
Old behaviour : <video src="/attachments/1915ab74-6e1b-4e2f-9182-820cbed5359b" title="Screen Recording 2026-05-30 105859" controls></video>
New / updated behaviour : <video src="/attachments/dd293402-19e7-439e-8aa6-114d4ee22404" title="Screen Recording 2026-05-30 110338" controls></video>

The issue was that the input settings page automatically subscribed controllers to rainbow LED updates when a saved profile with rainbow LEDs enabled was loaded.
Reopening input settings on a non-default controller profile could immediately start the rainbow LED update loop from the settings UI, which caused the stick visualizer to become sluggish or jittery on affected controllers. The preview handler is now only registered when the rainbow LED option is changed in the settings UI, and it uses a stable handler so duplicate callbacks are not stacked.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/119
2026-05-30 20:33:50 +00:00
ranidspace
7be1708146 AppImage fixes and improvements (#110)
Currently the appimages work fine, however I realized there's some room to improve on following the standards and a bit of cleaning.

AppImages now follow the linux FHS and a `.DirIcon` to follow the Spec closer, along with changing the icon and desktop file names (the resulting AppImage will have the same name, it's only used internally)

Metadata has been added, it helps with applications meant to manage appimages, as well as a future possible auto-updater.

The icon has been replaced temporarily as we wait for the updated one, just taken from the windows .ico. The `.DirIcon` needs to be a PNG, however `app.ryujinx.Ryujinx.png` can be an svg as well for some reason.

The launcher script may not need to exist (gamemode and envvars), but as to not break anything the important things have been kept.

The commits messages of this PR contain information as to why each change was made.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/110
2026-05-30 20:31:49 +00:00
Babib3l
279c4cf3b4 Fix hotkey labels (#118)
Fixes the hotkeys settings page after https://git.ryujinx.app/projects/Ryubing/pulls/13 broke it

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/118
2026-05-30 20:27:44 +00:00
Max
fa72b5a298 Revert "fix/sdl3-gamepad-bugs (#19)" (#116)
This reverts commit 7f0e82fe48.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/116
2026-05-27 14:10:33 +00:00
Babib3l
e02081c09e Wire "Start games in fullscreen" option to use the new fullscreen behaviour (#113)
Final (hopefully) fix for https://github.com/Ryubing/Issues/issues/415

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/113
2026-05-27 12:25:09 +00:00
KeatonTheBot
de70f66a27 macOS: Fix black screen on Metal versions older than 3.1 (#114)
Fixes a black screen on Metal versions older than 3.1 by disabling `VK_EXT_extended_dynamic_state` (and `VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE`). Fix imported from [@sunshineinabox](https://github.com/sunshineinabox)'s unpublished extended dynamic states branch.

Co-authored-by: sunshineinabox <aqemail@gmail.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/114
2026-05-27 12:24:41 +00:00
Xam
6c1692ed60 HLE: Applets: Software keyboard: update cursor position on SetInputText (#25)
fixes cursor being at 0 position even when text is present, especially annoying for handheld devices with kde virtual keyboard, which is pretty bad, with no way to move the cursor or delete text backward

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/25
2026-05-27 12:22:05 +00:00
18 changed files with 121 additions and 75 deletions

View File

@@ -68,7 +68,7 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/app.ryujinx.Ryujinx.desktop
shell: bash shell: bash
- name: Create output dir - name: Create output dir

View File

@@ -1,23 +1,11 @@
#!/bin/sh #!/usr/bin/env sh
SCRIPT_DIR=$(dirname "$(realpath "$0")") SCRIPT_DIR=$(dirname "$(realpath "$0")")
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL3" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL3"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx"
fi
if [ -z "$RYUJINX_BIN" ]; then
exit 1
fi
COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1" COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1"
if command -v gamemoderun > /dev/null 2>&1; then if command -v gamemoderun > /dev/null 2>&1; then
COMMAND="$COMMAND gamemoderun" COMMAND="$COMMAND gamemoderun"
fi fi
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" exec $COMMAND "$SCRIPT_DIR/Ryujinx" "$@"

View File

@@ -1,14 +1,12 @@
[Desktop Entry] [Desktop Entry]
Version=1.0 Version=1.5
Name=Ryujinx Name=Ryujinx
Type=Application Type=Application
Icon=Ryujinx Icon=app.ryujinx.Ryujinx
Exec=Ryujinx.sh %f Exec=Ryujinx.sh %f
Comment=A Nintendo Switch Emulator
GenericName=Nintendo Switch Emulator GenericName=Nintendo Switch Emulator
Terminal=false Terminal=false
Categories=Game;Emulator; Categories=Game;Emulator;
MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci; MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx StartupWMClass=Ryujinx
PrefersNonDefaultGPU=true PrefersNonDefaultGPU=true

View File

@@ -1,3 +0,0 @@
#!/bin/sh
CURRENTDIR="$(readlink -f "$(dirname "$0")")"
exec "$CURRENTDIR"/usr/bin/Ryujinx.sh "$@"

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>app.ryujinx.Ryujinx</id>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<name>Ryujinx</name>
<summary>Nintendo Switch 1 emulator written in C#</summary>
<description>
<p>Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.</p>
</description>
<launchable type="desktop-id">app.ryujinx.Ryujinx.desktop</launchable>
<url type="homepage">https://ryujinx.app/</url>
<developer id="app.ryujinx">
<name>Ryubing</name>
</developer>
<screenshots>
<screenshot type="default">
<image>https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png</image>
</screenshot>
</screenshots>
<provides>
<id>app.ryujinx.Ryujinx.desktop</id>
</provides>
<content_rating type="oars-1.1" />
</component>

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/usr/bin/env sh
set -eu set -eu
ROOTDIR="$(readlink -f "$(dirname "$0")")"/../../../ ROOTDIR="$(readlink -f "$(dirname "$0")")"/../../../
@@ -7,20 +7,33 @@ cd "$ROOTDIR"
BUILDDIR=${BUILDDIR:-publish} BUILDDIR=${BUILDDIR:-publish}
OUTDIR=${OUTDIR:-publish_appimage} OUTDIR=${OUTDIR:-publish_appimage}
# AppStream
rm -rf AppDir rm -rf AppDir
mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/lib AppDir/usr/bin
mkdir -p AppDir/usr/share/metainfo AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps/
cp distribution/linux/Ryujinx.desktop AppDir/Ryujinx.desktop cp -r "$BUILDDIR"/* AppDir/usr/lib/
cp distribution/linux/appimage/AppRun AppDir/AppRun
cp distribution/misc/Logo.svg AppDir/Ryujinx.svg
cp distribution/linux/appimage/app.ryujinx.Ryujinx.appdata.xml AppDir/usr/share/metainfo/app.ryujinx.Ryujinx.appdata.xml
cp distribution/linux/app.ryujinx.Ryujinx.desktop AppDir/usr/share/applications/app.ryujinx.Ryujinx.desktop
cp distribution/misc/Logo.png AppDir/usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png
ln -s ../lib/Ryujinx AppDir/usr/bin/Ryujinx
cp -r "$BUILDDIR"/* AppDir/usr/bin/ # AppImage Root
ln -s ./usr/share/applications/app.ryujinx.Ryujinx.desktop AppDir/app.ryujinx.Ryujinx.desktop
ln -s ./usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png AppDir/.DirIcon
ln -s ./usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png AppDir/app.ryujinx.Ryujinx.png
ln -s ./usr/lib/Ryujinx.sh AppDir/AppRun
# Ensure necessary bins are set as executable # Ensure necessary bins are set as executable
chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx* chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR" mkdir -p "$OUTDIR"
# The "-n" flag removes the appstream checks during build, in case the main website is down.
# Run "appstreamcli validate --explain AppDir/usr/share/metainfo/app.ryujinx.Ryujinx.appdata.xml" to check manually
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \ appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage AppDir "$OUTDIR"/Ryujinx.AppImage

BIN
distribution/misc/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -588,8 +588,10 @@ namespace Ryujinx.Graphics.Vulkan
dynamicStates[5] = DynamicState.StencilReference; dynamicStates[5] = DynamicState.StencilReference;
dynamicStates[6] = DynamicState.BlendConstants; dynamicStates[6] = DynamicState.BlendConstants;
if (supportsExtDynamicState) if (supportsExtDynamicState && (gd.SupportsMTL31 || !gd.IsMoltenVk))
{ {
// Requires Metal 3.1 and new MoltenVK, however extended dynamic states extension is not
// available on older versions of MVK, so we can safely check only OS version.
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
} }

View File

@@ -98,6 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
internal bool IsIntelArc { get; private set; } internal bool IsIntelArc { get; private set; }
internal bool IsQualcommProprietary { get; private set; } internal bool IsQualcommProprietary { get; private set; }
internal bool IsMoltenVk { get; private set; } internal bool IsMoltenVk { get; private set; }
internal bool SupportsMTL31 { get; private set; }
internal bool IsTBDR { get; private set; } internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; } internal bool IsSharedMemory { get; private set; }
@@ -123,6 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors. // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
if (IsMoltenVk = OperatingSystem.IsMacOS()) if (IsMoltenVk = OperatingSystem.IsMacOS())
MVKInitialization.Initialize(); MVKInitialization.Initialize();
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
} }
public static VulkanRenderer Create( public static VulkanRenderer Create(

View File

@@ -443,6 +443,7 @@ namespace Ryujinx.HLE.HOS.Applets
if ((newCalc.Flags & KeyboardCalcFlags.SetInputText) != 0) if ((newCalc.Flags & KeyboardCalcFlags.SetInputText) != 0)
{ {
_textValue = newCalc.InputText; _textValue = newCalc.InputText;
_cursorBegin = _textValue.Length;
updateText = true; updateText = true;
Logger.Debug?.Print(LogClass.ServiceAm, $"Input text set to {_textValue}"); Logger.Debug?.Print(LogClass.ServiceAm, $"Input text set to {_textValue}");

View File

@@ -155,9 +155,7 @@ namespace Ryujinx.Input.SDL3
result |= GamepadFeaturesFlag.Led; result |= GamepadFeaturesFlag.Led;
} }
SDL_UnlockProperties(propID); 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; return result;
} }

View File

@@ -331,18 +331,28 @@ namespace Ryujinx.Input.SDL3
public IEnumerable<IGamepad> GetGamepads() public IEnumerable<IGamepad> GetGamepads()
{ {
string[] ids; lock (_gamepadsIds)
lock (_lock)
{ {
ids = _gamepadsIds.Values foreach (var gamepad in _gamepadsIds)
.Concat(_joyConsIds.Values) {
.Concat(_linkedJoyConsIds.Values) yield return GetGamepad(gamepad.Value);
.ToArray(); }
} }
foreach (string id in ids) lock (_joyConsIds)
{ {
yield return GetGamepad(id); foreach (var gamepad in _joyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
lock (_linkedJoyConsIds)
{
foreach (var gamepad in _linkedJoyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
} }
} }
} }

View File

@@ -561,6 +561,7 @@ namespace Ryujinx.Input.HLE
!controllerConfig.Rumble.EnableRumble) !controllerConfig.Rumble.EnableRumble)
{ {
return; return;
} }
VibrationValue leftVibrationValue = dualVibrationValue.Item1; VibrationValue leftVibrationValue = dualVibrationValue.Item1;

View File

@@ -1094,10 +1094,10 @@ namespace Ryujinx.Ava.Systems
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (_viewModel.StartGamesInFullscreen) if (_viewModel.StartGamesInFullscreen && _viewModel.WindowState is not WindowState.FullScreen)
{ {
_viewModel.WindowState = WindowState.FullScreen; // Use the view model toggle so decoration ordering matches user toggles.
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true; _viewModel.ToggleFullscreen();
} }
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUi) if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUi)

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using HidKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
@@ -55,6 +56,9 @@ namespace Ryujinx.Ava.UI.Helpers
Key key => KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel) Key key => KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel)
? localizedKeyLabel ? localizedKeyLabel
: key.ToString(), : key.ToString(),
HidKey key => KeyboardLayoutLocaleHelper.TryGetSemanticLabel((Key)(int)key, out string localizedHidKeyLabel)
? localizedHidKeyLabel
: key.ToString(),
PhysicalKey physicalKey => PhysicalKeyLabelHelper.GetDisplayString(physicalKey), PhysicalKey physicalKey => PhysicalKeyLabelHelper.GetDisplayString(physicalKey),
GamepadInputId gamepadInputId => GetLocalizedMappedValue(gamepadInputId, _gamepadInputIdMap), GamepadInputId gamepadInputId => GetLocalizedMappedValue(gamepadInputId, _gamepadInputIdMap),
StickInputId stickInputId => GetLocalizedMappedValue(stickInputId, _stickInputIdMap), StickInputId stickInputId => GetLocalizedMappedValue(stickInputId, _stickInputIdMap),

View File

@@ -2,6 +2,7 @@ using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.UI.Views.Input; using Ryujinx.UI.Views.Input;
@@ -59,32 +60,46 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public partial SvgImage Image { get; set; } public partial SvgImage Image { get; set; }
public InputViewModel ParentModel { get; } public InputViewModel ParentModel { get; }
private readonly RefEvent<System.Drawing.Color>.Handler _rainbowLedHandler;
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer) public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer)
{ {
ParentModel = model; ParentModel = model;
Visualizer = visualizer; Visualizer = visualizer;
_rainbowLedHandler = SetRainbowLed;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
config.PropertyChanged += (_, args) => config.PropertyChanged += OnConfigPropertyChanged;
{
if (args.PropertyName is nameof(Config.UseRainbowLed))
{
if (Config is { UseRainbowLed: true, TurnOffLed: false, EnableLedChanging: true })
Rainbow.Updated += (ref color) => ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb());
else
{
Rainbow.Reset();
if (Config.TurnOffLed)
ParentModel.SelectedGamepad.ClearLed();
else
ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32());
}
}
};
Config = config; Config = config;
} }
private void OnConfigPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs args)
{
if (args.PropertyName is nameof(Config.UseRainbowLed))
{
if (Config is { UseRainbowLed: true, TurnOffLed: false, EnableLedChanging: true })
{
Rainbow.Updated -= _rainbowLedHandler;
Rainbow.Updated += _rainbowLedHandler;
}
else
{
Rainbow.Reset();
if (Config.TurnOffLed)
ParentModel.SelectedGamepad.ClearLed();
else
ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32());
}
}
}
private void SetRainbowLed(ref System.Drawing.Color color)
{
ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb());
}
public async void ShowMotionConfig() public async void ShowMotionConfig()
{ {
await MotionInputView.Show(this); await MotionInputView.Show(this);

View File

@@ -23,7 +23,6 @@ using Ryujinx.Input.SDL3;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@@ -81,10 +80,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
field = value; field = value;
if ((field?.Features & GamepadFeaturesFlag.Led) != 0 &&
ConfigViewModel is ControllerInputViewModel { Config.UseRainbowLed: true })
Rainbow.Updated += (ref Color color) => field.SetLed((uint)color.ToArgb());
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
} }
} }