diff --git a/Ryujinx.Ava/Helper/LoggerAdapter.cs b/Ryujinx.Ava/Helper/LoggerAdapter.cs new file mode 100644 index 000000000000..c8f3fea14988 --- /dev/null +++ b/Ryujinx.Ava/Helper/LoggerAdapter.cs @@ -0,0 +1,111 @@ +using Avalonia.Utilities; +using System; +using System.Text; + +namespace Ryujinx.Ava.UI.Helper +{ + using AvaLogger = Avalonia.Logging.Logger; + using AvaLogLevel = Avalonia.Logging.LogEventLevel; + using RyuLogger = Ryujinx.Common.Logging.Logger; + using RyuLogClass = Ryujinx.Common.Logging.LogClass; + + internal class LoggerAdapter : Avalonia.Logging.ILogSink + { + public static void Register() + { + AvaLogger.Sink = new LoggerAdapter(); + } + + private static RyuLogger.Log? GetLog(AvaLogLevel level) + { + return level switch + { + AvaLogLevel.Verbose => RyuLogger.Trace, + AvaLogLevel.Debug => RyuLogger.Debug, + AvaLogLevel.Information => RyuLogger.Info, + AvaLogLevel.Warning => RyuLogger.Warning, + AvaLogLevel.Error => RyuLogger.Error, + AvaLogLevel.Fatal => RyuLogger.Notice, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) + }; + } + + public bool IsEnabled(AvaLogLevel level, string area) + { + return GetLog(level) != null; + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate) + { + GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, null)); + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0 })); + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 })); + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 })); + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + { + GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, propertyValues)); + } + + private static string Format(string area, string template, object source, object[] v) + { + var result = new StringBuilder(); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source != null) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs index 659daa94b2eb..c5420ca3aaa2 100644 --- a/Ryujinx.Ava/Program.cs +++ b/Ryujinx.Ava/Program.cs @@ -89,6 +89,8 @@ public static void Main(string[] args) Initialize(args); + LoggerAdapter.Register(); + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } @@ -111,8 +113,7 @@ public static AppBuilder BuildAvaloniaApp() CompositionBackdropCornerRadius = 8.0f, UseCompositor = true }) - .UseSkia() - .LogToTrace(); + .UseSkia(); } private static void Initialize(string[] args) diff --git a/Ryujinx.Ava/UI/Controls/GameGridView.axaml b/Ryujinx.Ava/UI/Controls/GameGridView.axaml index ca9e56c8c237..35e3f64bf1e4 100644 --- a/Ryujinx.Ava/UI/Controls/GameGridView.axaml +++ b/Ryujinx.Ava/UI/Controls/GameGridView.axaml @@ -122,6 +122,9 @@ + diff --git a/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs b/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs index ca3c49230d55..6c9b11203730 100644 --- a/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs +++ b/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs @@ -37,7 +37,7 @@ public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs a { if (sender is ListBox listBox) { - var selected = listBox.SelectedItem as ApplicationData; + _selectedApplication = listBox.SelectedItem as ApplicationData; SelectedApplication = selected; } diff --git a/Ryujinx.Ava/UI/Controls/GameListView.axaml b/Ryujinx.Ava/UI/Controls/GameListView.axaml index 99a071c59bdf..331ac8da593d 100644 --- a/Ryujinx.Ava/UI/Controls/GameListView.axaml +++ b/Ryujinx.Ava/UI/Controls/GameListView.axaml @@ -115,7 +115,7 @@ - diff --git a/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs b/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs index 5461f57557cc..9f056e327544 100644 --- a/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs +++ b/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs @@ -37,7 +37,7 @@ public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs a { if (sender is ListBox listBox) { - var selected = listBox.SelectedItem as ApplicationData; + _selectedApplication = listBox.SelectedItem as ApplicationData; SelectedApplication = selected; } diff --git a/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs index 86bd2b3680b2..e530046e0325 100644 --- a/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ b/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs @@ -19,86 +19,40 @@ public static class ContentDialogHelper { private static bool _isChoiceDialogOpen; - private async static Task ShowContentDialog( - string title, - string primaryText, - string secondaryText, - string primaryButton, - string secondaryButton, - string closeButton, - int iconSymbol, - UserResult primaryButtonResult = UserResult.Ok, - ManualResetEvent deferResetEvent = null, - Func doWhileDeferred = null, - TypedEventHandler deferCloseAction = null) + public async static Task ShowContentDialog( + string title, + object content, + string primaryButton, + string secondaryButton, + string closeButton, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + Func doWhileDeferred = null, + TypedEventHandler deferCloseAction = null) { UserResult result = UserResult.None; - bool useOverlay = false; - Window mainWindow = null; - - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + ContentDialog contentDialog = new() { - foreach (var item in al.Windows) - { - if (item.IsActive && item is MainWindow window && window.ViewModel.IsGameRunning) - { - mainWindow = window; - useOverlay = true; - break; - } - } - } - - ContentDialog contentDialog = null; - ContentDialogOverlayWindow overlay = null; + Title = title, + PrimaryButtonText = primaryButton, + SecondaryButtonText = secondaryButton, + CloseButtonText = closeButton, + Content = content + }; - if (useOverlay) + contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => { - overlay = new ContentDialogOverlayWindow() - { - Height = mainWindow.Bounds.Height, - Width = mainWindow.Bounds.Width, - Position = mainWindow.PointToScreen(new Point()) - }; - - mainWindow.PositionChanged += OverlayOnPositionChanged; - - void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) - { - overlay.Position = mainWindow.PointToScreen(new Point()); - } - - contentDialog = overlay.ContentDialog; - - bool opened = false; - - overlay.Opened += OverlayOnActivated; - - async void OverlayOnActivated(object sender, EventArgs e) - { - if (opened) - { - return; - } - - opened = true; - - overlay.Position = mainWindow.PointToScreen(new Point()); - - await ShowDialog(); - } + result = primaryButtonResult; + }); - await overlay.ShowDialog(mainWindow); - } - else + contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => { - contentDialog = new ContentDialog(); - - await ShowDialog(); - } + result = UserResult.No; + contentDialog.PrimaryButtonClick -= deferCloseAction; + }); - async Task ShowDialog() + contentDialog.CloseButtonCommand = MiniCommand.Create(() => { contentDialog.Title = title; contentDialog.PrimaryButtonText = primaryButton; @@ -142,15 +96,34 @@ async Task ShowDialog() await contentDialog.ShowAsync(); } - if (useOverlay) + if (deferResetEvent != null) { - overlay.Content = null; - overlay.Close(); + contentDialog.PrimaryButtonClick += deferCloseAction; } + await ShowAsync(contentDialog); + return result; } + private async static Task ShowTextDialog( + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + Func doWhileDeferred = null, + TypedEventHandler deferCloseAction = null) + { + Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol); + + return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction); + } + public async static Task ShowDeferredContentDialog( StyleableWindow window, string title, @@ -166,7 +139,7 @@ async Task ShowDialog() bool startedDeferring = false; UserResult result = UserResult.None; - return await ShowContentDialog( + return await ShowTextDialog( title, primaryText, secondaryText, @@ -196,8 +169,7 @@ async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs ar sender.PrimaryButtonClick -= DeferClose; -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => + _ = Task.Run(() => { deferResetEvent.WaitOne(); @@ -206,7 +178,6 @@ async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs ar deferral.Complete(); }); }); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed if (doWhileDeferred != null) { @@ -217,34 +188,42 @@ async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs ar } } - private static Grid CreateDialogTextContent(string primaryText, string secondaryText, int symbol) + private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol) { - Grid content = new Grid(); - content.RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() }; - content.ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() }; + Grid content = new() + { + RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() }, + ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() }, - content.MinHeight = 80; + MinHeight = 80 + }; + + SymbolIcon icon = new() + { + Symbol = (Symbol)symbol, + Margin = new Thickness(10), + FontSize = 40, + VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center + }; - SymbolIcon icon = new SymbolIcon { Symbol = (Symbol)symbol, Margin = new Thickness(10) }; - icon.FontSize = 40; - icon.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center; Grid.SetColumn(icon, 0); Grid.SetRowSpan(icon, 2); Grid.SetRow(icon, 0); - TextBlock primaryLabel = new TextBlock() + TextBlock primaryLabel = new() { - Text = primaryText, - Margin = new Thickness(5), + Text = primaryText, + Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, - MaxWidth = 450 + MaxWidth = 450 }; - TextBlock secondaryLabel = new TextBlock() + + TextBlock secondaryLabel = new() { - Text = secondaryText, - Margin = new Thickness(5), + Text = secondaryText, + Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, - MaxWidth = 450 + MaxWidth = 450 }; Grid.SetColumn(primaryLabel, 1); @@ -266,7 +245,7 @@ private static Grid CreateDialogTextContent(string primaryText, string secondary string closeButton, string title) { - return await ShowContentDialog( + return await ShowTextDialog( title, primary, secondaryText, @@ -284,7 +263,7 @@ private static Grid CreateDialogTextContent(string primaryText, string secondary string title, UserResult primaryButtonResult = UserResult.Yes) { - return await ShowContentDialog( + return await ShowTextDialog( string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title, primaryText, secondaryText, @@ -302,7 +281,7 @@ internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string sec internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) { - await ShowContentDialog( + await ShowTextDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], primary, secondaryText, @@ -314,7 +293,7 @@ internal static async Task CreateUpdaterInfoDialog(string primary, string second internal static async Task CreateWarningDialog(string primary, string secondaryText) { - await ShowContentDialog( + await ShowTextDialog( LocaleManager.Instance[LocaleKeys.DialogWarningTitle], primary, secondaryText, @@ -328,7 +307,7 @@ internal static async Task CreateErrorDialog(string errorMessage, string seconda { Logger.Error?.Print(LogClass.Application, errorMessage); - await ShowContentDialog( + await ShowTextDialog( LocaleManager.Instance[LocaleKeys.DialogErrorTitle], LocaleManager.Instance[LocaleKeys.DialogErrorMessage], errorMessage, @@ -347,16 +326,15 @@ internal static async Task CreateChoiceDialog(string title, string primary _isChoiceDialogOpen = true; - UserResult response = - await ShowContentDialog( - title, - primary, - secondaryText, - LocaleManager.Instance[LocaleKeys.InputDialogYes], - "", - LocaleManager.Instance[LocaleKeys.InputDialogNo], - (int)Symbol.Help, - UserResult.Yes); + UserResult response = await ShowTextDialog( + title, + primary, + secondaryText, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + "", + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help, + UserResult.Yes); _isChoiceDialogOpen = false; @@ -400,5 +378,98 @@ internal static async Task CreateStopEmulationDialog() return string.Empty; } + + public static async Task ShowAsync(ContentDialog contentDialog) + { + ContentDialogResult result; + + ContentDialogOverlayWindow contentDialogOverlayWindow = null; + + Window parent = GetMainWindow(); + + if (parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning) + { + contentDialogOverlayWindow = new() + { + Height = parent.Bounds.Height, + Width = parent.Bounds.Width, + Position = parent.PointToScreen(new Point()), + ShowInTaskbar = false + }; + + parent.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + } + + contentDialogOverlayWindow.ContentDialog = contentDialog; + + bool opened = false; + + contentDialogOverlayWindow.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + + result = await ShowDialog(); + } + + result = await contentDialogOverlayWindow.ShowDialog(parent); + } + else + { + result = await ShowDialog(); + } + + async Task ShowDialog() + { + if (contentDialogOverlayWindow is not null) + { + result = await contentDialog.ShowAsync(contentDialogOverlayWindow); + + contentDialogOverlayWindow!.Close(); + } + else + { + result = await contentDialog.ShowAsync(); + } + + return result; + } + + if (contentDialogOverlayWindow is not null) + { + contentDialogOverlayWindow.Content = null; + contentDialogOverlayWindow.Close(); + } + + return result; + } + + private static Window GetMainWindow() + { + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + { + foreach (Window item in al.Windows) + { + if (item.IsActive && item is MainWindow window) + { + return window; + } + } + } + + return null; + } } } \ No newline at end of file diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 6630b083570e..d15bab25f91f 100644 --- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -88,8 +88,6 @@ public class MainWindowViewModel : BaseModel private float _volume; private string _backendText; - public ApplicationData ListSelectedApplication; - public ApplicationData GridSelectedApplication; private bool _canUpdate; private Cursor _cursor; private string _title; @@ -98,6 +96,9 @@ public class MainWindowViewModel : BaseModel private WindowState _windowState; private bool _isActive; + public ApplicationData ListSelectedApplication; + public ApplicationData GridSelectedApplication; + public event Action ReloadGameList; private string TitleName { get; set; } diff --git a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs index 105b75fae74b..eaa87414aab0 100644 --- a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs @@ -44,10 +44,7 @@ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) private async void StopEmulation_Click(object sender, RoutedEventArgs e) { - await Task.Run(() => - { - Window.ViewModel.AppHost?.ShowExitPrompt(); - }); + await Window.ViewModel.AppHost?.ShowExitPrompt(); } private async void PauseEmulation_Click(object sender, RoutedEventArgs e) diff --git a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml index 77d0f01cc673..3e77ae7e8b0d 100644 --- a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml +++ b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml @@ -8,10 +8,6 @@ mc:Ignorable="d" d:DesignHeight="260" d:DesignWidth="550" - Height="260" - Width="550" - x:Class="Ryujinx.Ava.UI.Windows.AboutWindow" - x:DataType="viewModel:AboutWindowViewModel" x:CompileBindings="True" Margin="0 -12 0 0" Focusable="True"> diff --git a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs index 9baf960d4f9f..6a832503cc39 100644 --- a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs +++ b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Interactivity; using Avalonia.Threading; using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ui.Common.Helper; using System.Net.Http; @@ -34,10 +35,10 @@ private async Task DownloadPatronsJson() { if (!NetworkInterface.GetIsNetworkAvailable()) { - PrimaryButtonText = "", + PrimaryButtonText = "", SecondaryButtonText = "", - CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], - Content = content + CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], + Content = new AboutWindow() }; return; @@ -49,7 +50,7 @@ private async Task DownloadPatronsJson() { string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); - await contentDialog.ShowAsync(); + await ContentDialogHelper.ShowAsync(contentDialog); } private void Button_OnClick(object sender, RoutedEventArgs e) diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index 2e936fc724a6..7e53c972bbe3 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -7,9 +7,9 @@ public enum LogClass AudioRenderer, Configuration, Cpu, - Font, Emulation, FFmpeg, + Font, Gpu, Hid, Host1x, @@ -66,6 +66,7 @@ public enum LogClass ServiceVi, SurfaceFlinger, TamperMachine, + Ui, Vic } } \ No newline at end of file diff --git a/Ryujinx.Headless.SDL2/HideCursor.cs b/Ryujinx.Headless.SDL2/HideCursor.cs new file mode 100644 index 000000000000..2dc0bd6abffa --- /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 d1d0872b36b3..69b0f42fba74 100644 --- a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs +++ b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -5,7 +5,6 @@ using Ryujinx.Graphics.OpenGL; using Ryujinx.Input.HLE; using System; - using static SDL2.SDL; namespace Ryujinx.Headless.SDL2.OpenGL @@ -103,7 +102,13 @@ public void Dispose() 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 @@ protected override void SwapBuffers() 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 209ce22887a2..49233bceac80 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 @@ public class Options [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 @@ public class Options 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 @@ public class Options // 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 @@ public class Options [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 @@ public class Options // 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 84363e1fb452..6ea3a98d7334 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -33,7 +33,6 @@ 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 @@ static void Main(string[] args) 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 @@ static void Main(string[] args) }; } - _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); - - GraphicsConfig.EnableShaderCache = true; - Parser.Default.ParseArguments(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 @@ private static InputConfig HandlePlayerConfiguration(string inputProfileName, st 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 @@ static void Load(Options option) } _inputConfiguration = new List(); - _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 @@ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerInde } // 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 @@ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerInde } // 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 @@ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerInde _userChannelPersistence.ShouldRestart = false; } + + _inputManager.Dispose(); } private static void SetupProgressHandler() @@ -479,8 +479,8 @@ private static void SetupProgressHandler() 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 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re _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 @@ private static bool LoadApplication(Options options) } 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 44a162051581..81ef53fe74b8 100644 --- a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -42,6 +42,10 @@ + + + + false diff --git a/Ryujinx.Headless.SDL2/Ryujinx.bmp b/Ryujinx.Headless.SDL2/Ryujinx.bmp new file mode 100644 index 000000000000..413f3b21bb8a Binary files /dev/null and b/Ryujinx.Headless.SDL2/Ryujinx.bmp differ diff --git a/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs b/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs index 236d47698979..bdf428cc4e14 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 @@ class SDL2MouseDriver : IGamepadDriver 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 @@ private static MouseButton DriverButtonToMouseButton(uint rawButton) 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 @@ public void Dispose() _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 183233397196..172b7685a28d 100644 --- a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs +++ b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs @@ -12,7 +12,13 @@ class VulkanWindow : WindowBase { 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 @@ protected override void FinalizeWindowRenderer() 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 88b0d5733749..db6c8ec4d002 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -14,13 +14,16 @@ 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 @@ abstract class WindowBase : IHostUiHandler, IDisposable private static ConcurrentQueue MainThreadActions = new ConcurrentQueue(); + [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 @@ public static void QueueMainThreadAction(Action action) 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 @@ public void Initialize(Switch device, List inputConfigs, bool enabl 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 @@ private void InitializeWindow() throw new Exception(errorMessage); } + SetWindowIcon(); + _windowId = SDL_GetWindowID(WindowHandle); SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); @@ -146,9 +188,11 @@ private void HandleWindowEvent(SDL_Event evnt) Renderer?.Window.SetSize(Width, Height); MouseDriver.SetClientSize(Width, Height); break; + case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: Exit(); break; + default: break; } @@ -331,6 +375,9 @@ private bool UpdateFrame() 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 @@ protected virtual void Dispose(bool disposing) } } } -} +} \ No newline at end of file diff --git a/Ryujinx.Horizon.Common/Result.cs b/Ryujinx.Horizon.Common/Result.cs index ac8436888e28..28056310fdaf 100644 --- a/Ryujinx.Horizon.Common/Result.cs +++ b/Ryujinx.Horizon.Common/Result.cs @@ -100,14 +100,6 @@ public void AbortUnless(Result result, Result result2) } } - public void AbortOnFailureUnless(Result result, Result result2) - { - if (this != Success && this != result && this != result2) - { - ThrowInvalidResult(); - } - } - private void ThrowInvalidResult() { throw new InvalidResultException(this); diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs index 822a4b4a6b96..33c42825f531 100644 --- a/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs +++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs @@ -51,22 +51,18 @@ public static Result Reply(int sessionHandle, ReadOnlySpan messageBuffer) { Result result = ReplyImpl(sessionHandle, messageBuffer); - result.AbortOnFailureUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed); + result.AbortUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed); return Result.Success; } private static Result ReplyImpl(int sessionHandle, ReadOnlySpan messageBuffer) { - Span handles = stackalloc int[1]; - - handles[0] = sessionHandle; - var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); if (messageBuffer == tlsSpan) { - return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, sessionHandle, 0); + return HorizonStatic.Syscall.ReplyAndReceive(out _, ReadOnlySpan.Empty, sessionHandle, 0); } else { diff --git a/Ryujinx.Input/HLE/NpadManager.cs b/Ryujinx.Input/HLE/NpadManager.cs index 34e05687fc25..5290ecbb71e1 100644 --- a/Ryujinx.Input/HLE/NpadManager.cs +++ b/Ryujinx.Input/HLE/NpadManager.cs @@ -163,7 +163,7 @@ public void Initialize(Switch device, List inputConfig, bool enable ReloadConfiguration(inputConfig, enableKeyboard, enableMouse); } - public void Update(float aspectRatio = 0) + public void Update(float aspectRatio = 1) { lock (_lock) {