aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTSRBerry <20988865+TSRBerry@users.noreply.github.com>2023-01-09 04:55:37 +0100
committerGitHub <noreply@github.com>2023-01-09 04:55:37 +0100
commit51b3953cfc3b440aa0b7b1b06d33626426e54556 (patch)
treeb6bc77a46d8d0b2d635ce135ac822d188787344c
parent610eecc1c1fec2203fff1ebd71cd10798fbcc05a (diff)
[Headless] Add missing arguments & Fix typos (#4193)1.1.525
* headless: Fix typos in command line options * Remove nullable from command line options Add EnableMacroHLE option Add HideCursorOnIdle option * headless: Adjust enable-ptc help text * headless: Use switch statement instead of if-else chain * headless: Improve formatting for long constructors * headless: Remove discards from SDL_ShowCursor() * headless: Add window icon * Fix hiding cursor on idle At least on Wayland, SDL2 doesn't produce any mouse motion events. * Add new command line args: BaseDataDir and UserProfile * headless: Read icon from embedded resource * headless: Skip SetWindowIcon() on Windows if dll isn't present * headless: Fix division by zero * headless: Fix command line options not working correctly * headless: Fix crash when viewing command line options * headless: Load window icon bmp from memory * Add comment to the workaround for SDL_LoadBMP_RW * headless: Enable logging to file by default * headless: Add 3 options for --hide-cursor Replaces --disable-hide-cursor-on-idle
-rw-r--r--Ryujinx.Headless.SDL2/HideCursor.cs9
-rw-r--r--Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs11
-rw-r--r--Ryujinx.Headless.SDL2/Options.cs82
-rw-r--r--Ryujinx.Headless.SDL2/Program.cs92
-rw-r--r--Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj4
-rw-r--r--Ryujinx.Headless.SDL2/Ryujinx.bmpbin0 -> 1122442 bytes
-rw-r--r--Ryujinx.Headless.SDL2/SDL2MouseDriver.cs87
-rw-r--r--Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs10
-rw-r--r--Ryujinx.Headless.SDL2/WindowBase.cs55
-rw-r--r--Ryujinx.Input/HLE/NpadManager.cs2
10 files changed, 249 insertions, 103 deletions
diff --git a/Ryujinx.Headless.SDL2/HideCursor.cs b/Ryujinx.Headless.SDL2/HideCursor.cs
new file mode 100644
index 00000000..2dc0bd6a
--- /dev/null
+++ b/Ryujinx.Headless.SDL2/HideCursor.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Headless.SDL2
+{
+ public enum HideCursor
+ {
+ Never,
+ OnIdle,
+ Always
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs
index d1d0872b..69b0f42f 100644
--- a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs
+++ b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs
@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using System;
-
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.OpenGL
@@ -103,7 +102,13 @@ namespace Ryujinx.Headless.SDL2.OpenGL
private GraphicsDebugLevel _glLogLevel;
private SDL2OpenGLContext _openGLContext;
- public OpenGLWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
+ public OpenGLWindow(
+ InputManager inputManager,
+ GraphicsDebugLevel glLogLevel,
+ AspectRatio aspectRatio,
+ bool enableMouse,
+ HideCursor hideCursor)
+ : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}
@@ -161,4 +166,4 @@ namespace Ryujinx.Headless.SDL2.OpenGL
SDL_GL_SwapWindow(WindowHandle);
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Headless.SDL2/Options.cs b/Ryujinx.Headless.SDL2/Options.cs
index 209ce228..49233bce 100644
--- a/Ryujinx.Headless.SDL2/Options.cs
+++ b/Ryujinx.Headless.SDL2/Options.cs
@@ -6,6 +6,14 @@ namespace Ryujinx.Headless.SDL2
{
public class Options
{
+ // General
+
+ [Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")]
+ public string BaseDataDir { get; set; }
+
+ [Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
+ public string UserProfile { get; set; }
+
// Input
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
@@ -23,7 +31,7 @@ namespace Ryujinx.Headless.SDL2
[Option("input-profile-5", Required = false, HelpText = "Set the input profile in use for Player 5.")]
public string InputProfile5Name { get; set; }
- [Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 5.")]
+ [Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 6.")]
public string InputProfile6Name { get; set; }
[Option("input-profile-7", Required = false, HelpText = "Set the input profile in use for Player 7.")]
@@ -63,42 +71,45 @@ namespace Ryujinx.Headless.SDL2
public string InputIdHandheld { get; set; }
[Option("enable-keyboard", Required = false, Default = false, HelpText = "Enable or disable keyboard support (Independent from controllers binding).")]
- public bool? EnableKeyboard { get; set; }
+ public bool EnableKeyboard { get; set; }
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
- public bool? EnableMouse { get; set; }
+ public bool EnableMouse { get; set; }
+
+ [Option("hide-cursor", Required = false, Default = HideCursor.OnIdle, HelpText = "Change when the cursor gets hidden.")]
+ public HideCursor HideCursor { get; set; }
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
- public bool? ListInputProfiles { get; set; }
+ public bool ListInputProfiles { get; set; }
[Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")]
public bool ListInputIds { get; set; }
// System
- [Option("enable-ptc", Required = false, Default = true, HelpText = "Enables profiled translation cache persistency.")]
- public bool? EnablePtc { get; set; }
+ [Option("disable-ptc", Required = false, HelpText = "Disables profiled persistent translation cache.")]
+ public bool DisablePtc { get; set; }
[Option("enable-internet-connection", Required = false, Default = false, HelpText = "Enables guest Internet connection.")]
- public bool? EnableInternetAccess { get; set; }
+ public bool EnableInternetAccess { get; set; }
- [Option("enable-fs-integrity-checks", Required = false, Default = true, HelpText = "Enables integrity checks on Game content files.")]
- public bool? EnableFsIntegrityChecks { get; set; }
+ [Option("disable-fs-integrity-checks", Required = false, HelpText = "Disables integrity checks on Game content files.")]
+ public bool DisableFsIntegrityChecks { get; set; }
[Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")]
public int FsGlobalAccessLogMode { get; set; }
- [Option("enable-vsync", Required = false, Default = true, HelpText = "Enables Vertical Sync.")]
- public bool? EnableVsync { get; set; }
+ [Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")]
+ public bool DisableVsync { get; set; }
- [Option("enable-shader-cache", Required = false, Default = true, HelpText = "Enables Shader cache.")]
- public bool? EnableShaderCache { get; set; }
+ [Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")]
+ public bool DisableShaderCache { get; set; }
[Option("enable-texture-recompression", Required = false, Default = false, HelpText = "Enables Texture recompression.")]
- public bool? EnableTextureRecompression { get; set; }
+ public bool EnableTextureRecompression { get; set; }
- [Option("enable-docked-mode", Required = false, Default = true, HelpText = "Enables Docked Mode.")]
- public bool? EnableDockedMode { get; set; }
+ [Option("disable-docked-mode", Required = false, HelpText = "Disables Docked Mode.")]
+ public bool DisableDockedMode { get; set; }
[Option("system-language", Required = false, Default = SystemLanguage.AmericanEnglish, HelpText = "Change System Language.")]
public SystemLanguage SystemLanguage { get; set; }
@@ -120,32 +131,32 @@ namespace Ryujinx.Headless.SDL2
// Logging
- [Option("enable-file-logging", Required = false, Default = false, HelpText = "Enables logging to a file on disk.")]
- public bool? EnableFileLog { get; set; }
+ [Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]
+ public bool DisableFileLog { get; set; }
[Option("enable-debug-logs", Required = false, Default = false, HelpText = "Enables printing debug log messages.")]
- public bool? LoggingEnableDebug { get; set; }
+ public bool LoggingEnableDebug { get; set; }
- [Option("enable-stub-logs", Required = false, Default = true, HelpText = "Enables printing stub log messages.")]
- public bool? LoggingEnableStub { get; set; }
+ [Option("disable-stub-logs", Required = false, HelpText = "Disables printing stub log messages.")]
+ public bool LoggingDisableStub { get; set; }
- [Option("enable-info-logs", Required = false, Default = true, HelpText = "Enables printing info log messages.")]
- public bool? LoggingEnableInfo { get; set; }
+ [Option("disable-info-logs", Required = false, HelpText = "Disables printing info log messages.")]
+ public bool LoggingDisableInfo { get; set; }
- [Option("enable-warning-logs", Required = false, Default = true, HelpText = "Enables printing warning log messages.")]
- public bool? LoggingEnableWarning { get; set; }
+ [Option("disable-warning-logs", Required = false, HelpText = "Disables printing warning log messages.")]
+ public bool LoggingDisableWarning { get; set; }
- [Option("enable-error-logs", Required = false, Default = true, HelpText = "Enables printing error log messages.")]
- public bool? LoggingEnableError { get; set; }
+ [Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
+ public bool LoggingEnableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
- public bool? LoggingEnableTrace { get; set; }
+ public bool LoggingEnableTrace { get; set; }
- [Option("enable-guest-logs", Required = false, Default = true, HelpText = "Enables printing guest log messages.")]
- public bool? LoggingEnableGuest { get; set; }
+ [Option("disable-guest-logs", Required = false, HelpText = "Disables printing guest log messages.")]
+ public bool LoggingDisableGuest { get; set; }
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
- public bool? LoggingEnableFsAccessLog { get; set; }
+ public bool LoggingEnableFsAccessLog { get; set; }
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
@@ -164,6 +175,9 @@ namespace Ryujinx.Headless.SDL2
[Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
public BackendThreading BackendThreading { get; set; }
+ [Option("disable-macro-hle", Required= false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
+ public bool DisableMacroHLE { get; set; }
+
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
public string GraphicsShadersDumpPath { get; set; }
@@ -176,14 +190,14 @@ namespace Ryujinx.Headless.SDL2
// Hacks
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
- public bool? ExpandRam { get; set; }
+ public bool ExpandRam { get; set; }
[Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")]
- public bool? IgnoreMissingServices { get; set; }
+ public bool IgnoreMissingServices { get; set; }
// Values
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
public string InputPath { get; set; }
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs
index 84363e1f..6ea3a98d 100644
--- a/Ryujinx.Headless.SDL2/Program.cs
+++ b/Ryujinx.Headless.SDL2/Program.cs
@@ -33,7 +33,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
-
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
@@ -63,20 +62,6 @@ namespace Ryujinx.Headless.SDL2
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
- AppDataManager.Initialize(null);
-
- _virtualFileSystem = VirtualFileSystem.CreateInstance();
- _libHacHorizonManager = new LibHacHorizonManager();
-
- _libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
- _libHacHorizonManager.InitializeArpServer();
- _libHacHorizonManager.InitializeBcatServer();
- _libHacHorizonManager.InitializeSystemClients();
-
- _contentManager = new ContentManager(_virtualFileSystem);
- _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
- _userChannelPersistence = new UserChannelPersistence();
-
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
AutoResetEvent invoked = new AutoResetEvent(false);
@@ -97,15 +82,9 @@ namespace Ryujinx.Headless.SDL2
};
}
- _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
-
- GraphicsConfig.EnableShaderCache = true;
-
Parser.Default.ParseArguments<Options>(args)
- .WithParsed(options => Load(options))
+ .WithParsed(Load)
.WithNotParsed(errors => errors.Output());
-
- _inputManager.Dispose();
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
@@ -343,6 +322,24 @@ namespace Ryujinx.Headless.SDL2
static void Load(Options option)
{
+ AppDataManager.Initialize(option.BaseDataDir);
+
+ _virtualFileSystem = VirtualFileSystem.CreateInstance();
+ _libHacHorizonManager = new LibHacHorizonManager();
+
+ _libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
+ _libHacHorizonManager.InitializeArpServer();
+ _libHacHorizonManager.InitializeBcatServer();
+ _libHacHorizonManager.InitializeSystemClients();
+
+ _contentManager = new ContentManager(_virtualFileSystem);
+ _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
+ _userChannelPersistence = new UserChannelPersistence();
+
+ _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
+
+ GraphicsConfig.EnableShaderCache = true;
+
IGamepad gamepad;
if (option.ListInputIds)
@@ -378,8 +375,8 @@ namespace Ryujinx.Headless.SDL2
}
_inputConfiguration = new List<InputConfig>();
- _enableKeyboard = (bool)option.EnableKeyboard;
- _enableMouse = (bool)option.EnableMouse;
+ _enableKeyboard = option.EnableKeyboard;
+ _enableMouse = option.EnableMouse;
void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
@@ -407,16 +404,16 @@ namespace Ryujinx.Headless.SDL2
}
// Setup logging level
- Logger.SetEnable(LogLevel.Debug, (bool)option.LoggingEnableDebug);
- Logger.SetEnable(LogLevel.Stub, (bool)option.LoggingEnableStub);
- Logger.SetEnable(LogLevel.Info, (bool)option.LoggingEnableInfo);
- Logger.SetEnable(LogLevel.Warning, (bool)option.LoggingEnableWarning);
- Logger.SetEnable(LogLevel.Error, (bool)option.LoggingEnableError);
- Logger.SetEnable(LogLevel.Trace, (bool)option.LoggingEnableTrace);
- Logger.SetEnable(LogLevel.Guest, (bool)option.LoggingEnableGuest);
- Logger.SetEnable(LogLevel.AccessLog, (bool)option.LoggingEnableFsAccessLog);
-
- if ((bool)option.EnableFileLog)
+ Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
+ Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
+ Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
+ Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
+ Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
+ Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
+ Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
+ Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
+
+ if (!option.DisableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
@@ -426,11 +423,12 @@ namespace Ryujinx.Headless.SDL2
}
// Setup graphics configuration
- GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache;
- GraphicsConfig.EnableTextureRecompression = (bool)option.EnableTextureRecompression;
+ GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
+ GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
GraphicsConfig.ResScale = option.ResScale;
GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
+ GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
while (true)
{
@@ -443,6 +441,8 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence.ShouldRestart = false;
}
+
+ _inputManager.Dispose();
}
private static void SetupProgressHandler()
@@ -479,8 +479,8 @@ namespace Ryujinx.Headless.SDL2
private static WindowBase CreateWindow(Options options)
{
return options.GraphicsBackend == GraphicsBackend.Vulkan
- ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse)
- : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse);
+ ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor)
+ : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor);
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
@@ -533,20 +533,20 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
- (bool)options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
+ options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
window,
options.SystemLanguage,
options.SystemRegion,
- (bool)options.EnableVsync,
- (bool)options.EnableDockedMode,
- (bool)options.EnablePtc,
- (bool)options.EnableInternetAccess,
- (bool)options.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
+ !options.DisableVsync,
+ !options.DisableDockedMode,
+ !options.DisablePtc,
+ options.EnableInternetAccess,
+ !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
- (bool)options.IgnoreMissingServices,
+ options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume);
@@ -649,7 +649,7 @@ namespace Ryujinx.Headless.SDL2
}
else
{
- Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
+ Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
_emulationContext.Dispose();
diff --git a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
index 44a16205..81ef53fe 100644
--- a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
+++ b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
@@ -42,6 +42,10 @@
</Content>
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Ryujinx.bmp" />
+ </ItemGroup>
+
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
diff --git a/Ryujinx.Headless.SDL2/Ryujinx.bmp b/Ryujinx.Headless.SDL2/Ryujinx.bmp
new file mode 100644
index 00000000..413f3b21
--- /dev/null
+++ b/Ryujinx.Headless.SDL2/Ryujinx.bmp
Binary files differ
diff --git a/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs b/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs
index 236d4769..bdf428cc 100644
--- a/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs
+++ b/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs
@@ -10,7 +10,12 @@ namespace Ryujinx.Headless.SDL2
{
class SDL2MouseDriver : IGamepadDriver
{
+ private const int CursorHideIdleTime = 8; // seconds
+
private bool _isDisposed;
+ private HideCursor _hideCursor;
+ private bool _isHidden;
+ private long _lastCursorMoveTime;
public bool[] PressedButtons { get; }
@@ -18,9 +23,16 @@ namespace Ryujinx.Headless.SDL2
public Vector2 Scroll { get; private set; }
public Size _clientSize;
- public SDL2MouseDriver()
+ public SDL2MouseDriver(HideCursor hideCursor)
{
PressedButtons = new bool[(int)MouseButton.Count];
+ _hideCursor = hideCursor;
+
+ if (_hideCursor == HideCursor.Always)
+ {
+ SDL_ShowCursor(SDL_DISABLE);
+ _isHidden = true;
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -31,26 +43,75 @@ namespace Ryujinx.Headless.SDL2
return (MouseButton)(rawButton - 1);
}
- public void Update(SDL_Event evnt)
+ public void UpdatePosition()
{
- if (evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP)
+ SDL_GetMouseState(out int posX, out int posY);
+ Vector2 position = new(posX, posY);
+
+ if (CurrentPosition != position)
{
- uint rawButton = evnt.button.button;
+ CurrentPosition = position;
+ _lastCursorMoveTime = Stopwatch.GetTimestamp();
+ }
- if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
- {
- PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN;
+ CheckIdle();
+ }
+
+ private void CheckIdle()
+ {
+ if (_hideCursor != HideCursor.OnIdle)
+ {
+ return;
+ }
+
+ long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
- CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
+ if (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency)
+ {
+ if (!_isHidden)
+ {
+ SDL_ShowCursor(SDL_DISABLE);
+ _isHidden = true;
}
}
- else if (evnt.type == SDL_EventType.SDL_MOUSEMOTION)
+ else
{
- CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
+ if (_isHidden)
+ {
+ SDL_ShowCursor(SDL_ENABLE);
+ _isHidden = false;
+ }
}
- else if (evnt.type == SDL_EventType.SDL_MOUSEWHEEL)
+ }
+
+ public void Update(SDL_Event evnt)
+ {
+ switch (evnt.type)
{
- Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
+ case SDL_EventType.SDL_MOUSEBUTTONDOWN:
+ case SDL_EventType.SDL_MOUSEBUTTONUP:
+ uint rawButton = evnt.button.button;
+
+ if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
+ {
+ PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN;
+
+ CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
+ }
+
+ break;
+
+ // NOTE: On Linux using Wayland mouse motion events won't be received at all.
+ case SDL_EventType.SDL_MOUSEMOTION:
+ CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
+ _lastCursorMoveTime = Stopwatch.GetTimestamp();
+
+ break;
+
+ case SDL_EventType.SDL_MOUSEWHEEL:
+ Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
+
+ break;
}
}
@@ -100,4 +161,4 @@ namespace Ryujinx.Headless.SDL2
_isDisposed = true;
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs
index 18323339..172b7685 100644
--- a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs
+++ b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs
@@ -12,7 +12,13 @@ namespace Ryujinx.Headless.SDL2.Vulkan
{
private GraphicsDebugLevel _glLogLevel;
- public VulkanWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
+ public VulkanWindow(
+ InputManager inputManager,
+ GraphicsDebugLevel glLogLevel,
+ AspectRatio aspectRatio,
+ bool enableMouse,
+ HideCursor hideCursor)
+ : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}
@@ -95,4 +101,4 @@ namespace Ryujinx.Headless.SDL2.Vulkan
protected override void SwapBuffers() { }
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs
index 88b0d573..db6c8ec4 100644
--- a/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -14,13 +14,16 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
{
- abstract class WindowBase : IHostUiHandler, IDisposable
+ abstract partial class WindowBase : IHostUiHandler, IDisposable
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
@@ -29,6 +32,10 @@ namespace Ryujinx.Headless.SDL2
private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
+ [LibraryImport("SDL2")]
+ // TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly
+ private static partial IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc);
+
public static void QueueMainThreadAction(Action action)
{
MainThreadActions.Enqueue(action);
@@ -66,9 +73,14 @@ namespace Ryujinx.Headless.SDL2
private AspectRatio _aspectRatio;
private bool _enableMouse;
- public WindowBase(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse)
+ public WindowBase(
+ InputManager inputManager,
+ GraphicsDebugLevel glLogLevel,
+ AspectRatio aspectRatio,
+ bool enableMouse,
+ HideCursor hideCursor)
{
- MouseDriver = new SDL2MouseDriver();
+ MouseDriver = new SDL2MouseDriver(hideCursor);
_inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager();
@@ -103,6 +115,34 @@ namespace Ryujinx.Headless.SDL2
TouchScreenManager.Initialize(device);
}
+ private void SetWindowIcon()
+ {
+ Stream iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp");
+ byte[] iconBytes = new byte[iconStream!.Length];
+
+ if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, "Failed to read icon to byte array.");
+ iconStream.Close();
+
+ return;
+ }
+
+ iconStream.Close();
+
+ unsafe
+ {
+ fixed (byte* iconPtr = iconBytes)
+ {
+ IntPtr rwOpsStruct = SDL_RWFromConstMem((IntPtr)iconPtr, iconBytes.Length);
+ IntPtr iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
+
+ SDL_SetWindowIcon(WindowHandle, iconHandle);
+ SDL_FreeSurface(iconHandle);
+ }
+ }
+ }
+
private void InitializeWindow()
{
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
@@ -127,6 +167,8 @@ namespace Ryujinx.Headless.SDL2
throw new Exception(errorMessage);
}
+ SetWindowIcon();
+
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
@@ -146,9 +188,11 @@ namespace Ryujinx.Headless.SDL2
Renderer?.Window.SetSize(Width, Height);
MouseDriver.SetClientSize(Width, Height);
break;
+
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
Exit();
break;
+
default:
break;
}
@@ -331,6 +375,9 @@ namespace Ryujinx.Headless.SDL2
Device.Hid.DebugPad.Update();
+ // TODO: Replace this with MouseDriver.CheckIdle() when mouse motion events are received on every supported platform.
+ MouseDriver.UpdatePosition();
+
return true;
}
@@ -451,4 +498,4 @@ namespace Ryujinx.Headless.SDL2
}
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Input/HLE/NpadManager.cs b/Ryujinx.Input/HLE/NpadManager.cs
index 34e05687..5290ecbb 100644
--- a/Ryujinx.Input/HLE/NpadManager.cs
+++ b/Ryujinx.Input/HLE/NpadManager.cs
@@ -163,7 +163,7 @@ namespace Ryujinx.Input.HLE
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
}
- public void Update(float aspectRatio = 0)
+ public void Update(float aspectRatio = 1)
{
lock (_lock)
{