diff --git a/Directory.Packages.props b/Directory.Packages.props index a480d3d29..c2ac358ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,13 +3,13 @@ true - - - - - - - + + + + + + + @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -48,11 +48,11 @@ - - - - - + + + + + diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index 55ae0cd81..6a2a76a88 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -92,7 +92,7 @@ namespace Ryujinx.Graphics.Vulkan DriverId.MesaDozen => "Dozen", DriverId.MesaNvk => "NVK", DriverId.ImaginationOpenSourceMesa => "Imagination (Open)", - DriverId.MesaAgxv => "Honeykrisp", + DriverId.MesaHoneykrisp => "Honeykrisp", _ => id.ToString(), }; } diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs index 12bfab4bb..7e8bd4167 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -253,11 +253,23 @@ namespace Ryujinx.Input.SDL2 return IGamepad.GetStateSnapshot(this); } + private static bool hotButtonMinus = false; + private static bool hotExit = false; + + public bool SpecialExit() + { + if (hotButtonMinus) + { + hotButtonMinus = false; + return hotExit; + } + return hotExit = false; + } + public GamepadStateSnapshot GetMappedStateSnapshot() { GamepadStateSnapshot rawState = GetStateSnapshot(); GamepadStateSnapshot result = default; - lock (_userMappingLock) { if (_buttonsUserMapping.Count == 0) @@ -270,6 +282,28 @@ namespace Ryujinx.Input.SDL2 if (!entry.IsValid) continue; + if (GamepadButtonInputId.Minus == entry.To) + { + if (rawState.IsPressed(entry.From) && !hotButtonMinus) + { + hotButtonMinus = true; + } + else if (!result.IsPressed(entry.From) && hotButtonMinus) + { + hotButtonMinus = false; + } + } + + if (GamepadButtonInputId.Plus == entry.To) + { + if (rawState.IsPressed(entry.To) && hotButtonMinus) + { + + hotExit = true; + } + + } + // Do not touch state of button already pressed if (!result.IsPressed(entry.To)) { @@ -376,5 +410,7 @@ namespace Ryujinx.Input.SDL2 return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1; } + + } } diff --git a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs index 8d6a30d11..ab01a9bbc 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs @@ -329,6 +329,11 @@ namespace Ryujinx.Input.SDL2 return result; } + public bool SpecialExit() + { + return false; + } + public GamepadStateSnapshot GetStateSnapshot() { throw new NotSupportedException(); diff --git a/src/Ryujinx.Input.SDL2/SDL2Mouse.cs b/src/Ryujinx.Input.SDL2/SDL2Mouse.cs index 37b356b76..da0622db3 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Mouse.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Mouse.cs @@ -25,6 +25,10 @@ namespace Ryujinx.Input.SDL2 { _driver = driver; } + public bool SpecialExit() + { + return false; + } public Vector2 GetPosition() { diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 53426f71a..18601f6c7 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.HOS.Services.Hid; using System; using System.Collections.Concurrent; @@ -274,15 +275,21 @@ namespace Ryujinx.Input.HLE } } - public void Update() + public bool Update() { + // _gamepad may be altered by other threads var gamepad = _gamepad; - + if (gamepad != null && GamepadDriver != null) { State = gamepad.GetMappedStateSnapshot(); + if (gamepad.SpecialExit()) + { + return true; + } + if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) { if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) @@ -349,6 +356,7 @@ namespace Ryujinx.Input.HLE _leftMotionInput = null; _rightMotionInput = null; } + return false; } public GamepadInput GetHLEInputState() diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 08f222a91..4ebb8401d 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -200,8 +200,10 @@ namespace Ryujinx.Input.HLE ReloadConfiguration(inputConfig, enableKeyboard, enableMouse); } - public void Update(float aspectRatio = 1) + public bool Update(float aspectRatio = 1) { + bool specialExit = false; + lock (_lock) { List hleInputStates = new(); @@ -225,9 +227,10 @@ namespace Ryujinx.Input.HLE DriverConfigurationUpdate(ref controller, inputConfig); controller.UpdateUserConfiguration(inputConfig); - controller.Update(); - controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex)); + specialExit = controller.Update(); //hotkey press check + controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex)); + inputState = controller.GetHLEInputState(); inputState.Buttons |= _device.Hid.UpdateStickButtons(inputState.LStick, inputState.RStick); @@ -315,6 +318,8 @@ namespace Ryujinx.Input.HLE _device.TamperMachine.UpdateInput(hleInputStates); } + + return specialExit; } internal InputConfig GetPlayerInputConfigByIndex(int index) diff --git a/src/Ryujinx.Input/IGamepad.cs b/src/Ryujinx.Input/IGamepad.cs index 3853f2819..f52703e19 100644 --- a/src/Ryujinx.Input/IGamepad.cs +++ b/src/Ryujinx.Input/IGamepad.cs @@ -79,6 +79,12 @@ namespace Ryujinx.Input /// A remapped snaphost of the state of the gamepad. GamepadStateSnapshot GetMappedStateSnapshot(); + /// + /// Gets the state if the minus and plus buttons were pressed on the gamepad. + /// + /// returns true if the buttons were pressed. + bool SpecialExit(); + /// /// Get a snaphost of the state of the gamepad. /// diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index a35a79e86..c94e869d5 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -18,6 +18,7 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Renderer; using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Views.Main; using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; @@ -70,6 +71,7 @@ namespace Ryujinx.Ava private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. private const int TargetFps = 60; private const float VolumeDelta = 0.05f; + static bool SpecialExit = false; private static readonly Cursor _invisibleCursor = new(StandardCursorType.None); private readonly nint _invisibleCursorWin; @@ -96,6 +98,7 @@ namespace Ryujinx.Ava private bool _isCursorInRenderer = true; private bool _ignoreCursorState = false; + private enum CursorStates { CursorIsHidden, @@ -503,10 +506,15 @@ namespace Ryujinx.Ava _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; MainLoop(); - + Exit(); } + public bool IsSpecialExit() + { + return SpecialExit; + } + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) { if (Device != null) @@ -589,6 +597,7 @@ namespace Ryujinx.Ava _isStopped = true; Stop(); + } public void DisposeContext() @@ -1135,6 +1144,7 @@ namespace Ryujinx.Ava string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; string vSyncMode = Device.VSyncMode.ToString(); + UpdateShaderCount(); if (GraphicsConfig.ResScale != 1) @@ -1200,7 +1210,17 @@ namespace Ryujinx.Ava return false; } - NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + if (NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat())) + { + if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value == 1) + { + SpecialExit = true; // close App + } + if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value > 0) + { + _isActive = false; //close game + } + } if (_viewModel.IsActive) { @@ -1335,6 +1355,8 @@ namespace Ryujinx.Ava Device.Hid.DebugPad.Update(); + + return true; } diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 221c898ea..753752e0d 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -147,6 +147,31 @@ "zh_TW": "滑鼠直接存取" } }, + { + "ID": "SettingsExtraCloseApp", + "Translations": { + "ar_SA": "خروج سريع من التطبيق", + "de_DE": "Schneller Ausstieg aus der Anwendung", + "el_GR": "Γρήγορη έξοδος από την εφαρμογή", + "en_US": "Quick Exit from Application", + "es_ES": "Salida rápida de la aplicación", + "fr_FR": "Sortie rapide de l'application", + "he_IL": "יציאה מהירה מהאפליקציה", + "it_IT": "Uscita rapida dall'applicazione", + "ja_JP": "アプリケーションからの迅速な終了", + "ko_KR": "애플리케이션에서 빠른 종료", + "no_NO": "Rask avslutning av applikasjonen", + "pl_PL": "Szybkie wyjście z aplikacji", + "pt_BR": "Saída rápida do aplicativo", + "ru_RU": "Быстрый выход из приложения", + "sv_SE": "Snabb avslutning från applikationen", + "th_TH": "ออกจากแอปพลิเคชันอย่างรวดเร็ว", + "tr_TR": "Uygulamadan Hızlı Çıkış", + "uk_UA": "Швидкий вихід з програми", + "zh_CN": "快速退出应用程序", + "zh_TW": "快速退出應用程式" + } + }, { "ID": "SettingsTabSystemMemoryManagerMode", "Translations": { @@ -11047,6 +11072,81 @@ "zh_TW": "淺色" } }, + { + "ID": "SettingsTabInputDisableExitHotKey", + "Translations": { + "ar_SA": "الخروج السريع معطل", + "de_DE": "Schneller Ausstieg deaktiviert", + "el_GR": "Η γρήγορη έξοδος είναι απενεργοποιημένη", + "en_US": "Quick exit disabled", + "es_ES": "Salida rápida desactivada", + "fr_FR": "Sortie rapide désactivée", + "he_IL": "יציאה מהירה מושבתת", + "it_IT": "Uscita rapida disabilitata", + "ja_JP": "クイック終了が無効です", + "ko_KR": "빠른 종료 비활성화됨", + "no_NO": "Rask avslutning er deaktivert", + "pl_PL": "Szybkie wyjście wyłączone", + "pt_BR": "Saída rápida desativada", + "ru_RU": "Быстрый выход выключен", + "sv_SE": "Snabb avslutning inaktiverad", + "th_TH": "ปิดใช้งานออกอย่างรวดเร็ว", + "tr_TR": "Hızlı çıkış devre dışı bırakıldı", + "uk_UA": "Швидкий вихід вимкнено", + "zh_CN": "快速退出已禁用", + "zh_TW": "快速退出已停用" + } + }, + { + "ID": "SettingsTabInputHotkeyIsCloseApp", + "Translations": { + "ar_SA": "إغلاق التطبيق بالضغط على الزرين '+' و '-'.", + "de_DE": "App schließen mit den '+' und '-' Tasten.", + "el_GR": "Κλείσιμο της εφαρμογής με τα κουμπιά '+' και '-'.", + "en_US": "Close app by '+' and '-' buttons.", + "es_ES": "Cerrar la aplicación pulsando los botones '+' y '-'.", + "fr_FR": "Fermer l'application avec les boutons '+' et '-'.", + "he_IL": "סגור את האפליקציה בלחיצה על '+' ו-'-'.", + "it_IT": "Chiudi l'app premendo i pulsanti '+' e '-'.", + "ja_JP": "「+」と「-」ボタンを押してアプリを終了します。", + "ko_KR": "'+' 및 '-' 버튼을 눌러 앱을 종료합니다.", + "no_NO": "Lukk appen med '+' og '-' knappene.", + "pl_PL": "Zamknij aplikację przyciskiem '+' i '-'.", + "pt_BR": "Fechar o aplicativo pressionando os botões '+' e '-'.", + "ru_RU": "Закрыть приложение нажатием '+' и '-'.", + "sv_SE": "Stäng appen med '+' och '-' knapparna.", + "th_TH": "ปิดแอปโดยกดปุ่ม '+' และ '-'.", + "tr_TR": "'+' ve '-' düğmelerine basarak uygulamayı kapatın.", + "uk_UA": "Закрити додаток натисканням '+' та '-'.", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabInputHotkeyIsCloseGame", + "Translations": { + "ar_SA": "الخروج من اللعبة بالضغط على الزرين '+' و '-'.", + "de_DE": "Spiel beenden mit den '+' und '-' Tasten.", + "el_GR": "Έξοδος από το παιχνίδι με τα κουμπιά '+' και '-'.", + "en_US": "Exit game by '+' and '-' buttons.", + "es_ES": "Salir del juego pulsando los botones '+' y '-'.", + "fr_FR": "Quitter le jeu avec les boutons '+' et '-'.", + "he_IL": "יציאה מהמשחק בלחיצה על '+' ו-'-'.", + "it_IT": "Esci dal gioco premendo i pulsanti '+' e '-'.", + "ja_JP": "「+」と「-」ボタンを押してゲームを終了します。", + "ko_KR": "'+' 및 '-' 버튼을 눌러 게임을 종료합니다.", + "no_NO": "Avslutt spillet med '+' og '-' knappene.", + "pl_PL": "Wyjście z gry przyciskiem '+' i '-'.", + "pt_BR": "Sair do jogo pressionando os botões '+' e '-'.", + "ru_RU": "Выйти из игры нажатием '+' и '-'.", + "sv_SE": "Avsluta spelet med '+' och '-' knapparna.", + "th_TH": "ออกจากเกมโดยกดปุ่ม '+' และ '-'.", + "tr_TR": "'+' ve '-' düğmelerine basarak oyundan çıkın.", + "uk_UA": "Вийти з гри натисканням '+' та '-'.", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "ControllerSettingsConfigureGeneral", "Translations": { @@ -15147,6 +15247,31 @@ "zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。" } }, + { + "ID": "SpecialExitTooltip", + "Translations": { + "ar_SA": "يقوم بتفعيل مفاتيح الاختصار 'زائد' و 'ناقص'.\nاضغط على زرّي زائد وناقص في نفس الوقت لتنفيذ إحدى العمليات:\n\n1) إغلاق التطبيق باستخدام مفاتيح الاختصار.\n2) الخروج من اللعبة دون إغلاق التطبيق.\n\nيعمل فقط مع أذرع التحكم.", + "de_DE": "Aktiviert die Hotkeys 'Plus' und 'Minus'.\nDrücken Sie gleichzeitig die Plus- und Minus-Tasten, um eine der folgenden Aktionen auszuführen:\n\n1) Schließt die Anwendung durch Drücken der Hotkeys.\n2) Beendet das Spiel, ohne die Anwendung zu schließen.\n\nFunktioniert nur mit Gamepads.", + "el_GR": "Ενεργοποιεί τα πλήκτρα πρόσβασης 'συν' και 'πλην'.\nΠατήστε ταυτόχρονα τα κουμπιά συν και πλην για μία από τις ενέργειες:\n\n1) Κλείνει την εφαρμογή πατώντας τα πλήκτρα πρόσβασης.\n2) Εξέρχεται από το παιχνίδι χωρίς να κλείσει η εφαρμογή.\n\nΛειτουργεί μόνο με gamepads.", + "en_US": "Activates the hot keys 'plus' and 'minus'.\nPress buttons plus and minus at the same time to get one of the actions:\n\n1) Closes the application by pressing the hot keys.\n2) Exits the game without closing the application.\n\nWorks only with gamepads.", + "es_ES": "Activa las teclas rápidas 'más' y 'menos'.\nPresiona los botones más y menos al mismo tiempo para realizar una de las siguientes acciones:\n\n1) Cierra la aplicación presionando las teclas rápidas.\n2) Salir del juego sin cerrar la aplicación.\n\nFunciona solo con mandos.", + "fr_FR": "Active les raccourcis 'plus' et 'moins'.\nAppuyez simultanément sur les boutons plus et moins pour effectuer l'une des actions suivantes :\n\n1) Ferme l'application en appuyant sur les raccourcis.\n2) Quitte le jeu sans fermer l'application.\n\nFonctionne uniquement avec les manettes.", + "he_IL": "מפעיל את המקשים הקצרים 'פלוס' ו-'מינוס'.\nלחץ על הכפתורים פלוס ומינוס בו זמנית כדי לבצע אחת מהפעולות:\n\n1) סוגר את היישום באמצעות המקשים הקצרים.\n2) יוצא מהמשחק מבלי לסגור את היישום.\n\nפועל רק עם בקרי משחק.", + "it_IT": "Attiva i tasti rapidi 'più' e 'meno'.\nPremere i pulsanti più e meno contemporaneamente per eseguire una delle seguenti azioni:\n\n1) Chiude l'applicazione premendo i tasti rapidi.\n2) Esce dal gioco senza chiudere l'applicazione.\n\nFunziona solo con i gamepad.", + "ja_JP": "ホットキー「プラス」と「マイナス」を有効化します。\nプラスとマイナスのボタンを同時に押して、次のいずれかの操作を実行します:\n\n1) ホットキーを押すことでアプリを閉じます。\n2) アプリを閉じずにゲームを終了します。\n\nゲームパッドでのみ動作します。", + "ko_KR": "'플러스' 및 '마이너스' 단축키를 활성화합니다.\n플러스 및 마이너스 버튼을 동시에 눌러 다음 작업 중 하나를 수행합니다:\n\n1) 단축키를 눌러 애플리케이션을 닫습니다.\n2) 애플리케이션을 닫지 않고 게임을 종료합니다.\n\n게임패드에서만 작동합니다.", + "no_NO": "Aktiverer hurtigtastene 'pluss' og 'minus'.\nTrykk på knappene pluss og minus samtidig for å utføre en av følgende handlinger:\n\n1) Lukker applikasjonen ved å trykke på hurtigtastene.\n2) Avslutter spillet uten å lukke applikasjonen.\n\nFungerer kun med spillkontroller.", + "pl_PL": "Aktywuje klawisze skrótu 'plus' i 'minus'.\nNaciśnij jednocześnie przyciski plus i minus, aby wykonać jedną z akcji:\n\n1) Zamknij aplikację, naciskając klawisze skrótu.\n2) Wyjdź z gry bez zamykania aplikacji.\n\nDziała tylko z gamepadami.", + "pt_BR": "Ativa as teclas de atalho 'mais' e 'menos'.\nPressione os botões mais e menos ao mesmo tempo para realizar uma das ações:\n\n1) Fecha o aplicativo pressionando as teclas de atalho.\n2) Sai do jogo sem fechar o aplicativo.\n\nFunciona apenas com gamepads.", + "ru_RU": "Активирует горячие клавиши 'плюс' и 'минус'.\nНажмите одновременно кнопки плюс и минус чтобы получить одно из действий:\n\n1) Закрывает приложение по нажатию горячих кнопок.\n2) Выходит из игры без закрытия приложения.\n\nРаботает только с геймпадами.", + "sv_SE": "Aktiverar snabbtangenterna 'plus' och 'minus'.\nTryck samtidigt på plus- och minusknapparna för att utföra en av följande åtgärder:\n\n1) Stänger applikationen med snabbtangenterna.\n2) Avslutar spelet utan att stänga applikationen.\n\nFungerar bara med spelkontroller.", + "th_TH": "เปิดใช้งานปุ่มลัด '+' และ '-'.\nกดปุ่ม '+' และ '-' พร้อมกันเพื่อทำการอย่างใดอย่างหนึ่ง:\n\n1) ปิดแอปพลิเคชันด้วยการกดปุ่มลัด\n2) ออกจากเกมโดยไม่ปิดแอปพลิเคชัน\n\nใช้งานได้เฉพาะกับจอยเกม", + "tr_TR": "'Artı' ve 'eksi' kısayol tuşlarını etkinleştirir.\nArtı ve eksi tuşlarına aynı anda basarak aşağıdaki işlemlerden birini gerçekleştirin:\n\n1) Kısayol tuşlarına basarak uygulamayı kapatır.\n2) Uygulamayı kapatmadan oyundan çıkar.\n\nYalnızca gamepad'lerle çalışır.", + "uk_UA": "Активує гарячі клавіші '+' та '-'.\nНатисніть одночасно кнопки плюс та мінус для виконання однієї з дій:\n\n1) Закриває додаток за допомогою гарячих клавіш.\n2) Виходить із гри без закриття додатка.\n\nПрацює лише з геймпадами.", + "zh_CN": "激活快捷键“加号”和“减号”。\n同时按下加号和减号按钮以执行以下操作之一:\n\n1) 按快捷键关闭应用程序。\n2) 退出游戏而不关闭应用程序。\n\n仅适用于游戏手柄。", + "zh_TW": "啟用快捷鍵「加號」和「減號」。\n同時按下加號和減號按鈕以執行以下其中一項操作:\n\n1) 按快捷鍵關閉應用程式。\n2) 離開遊戲而不關閉應用程式。\n\n僅適用於遊戲手柄。" + } + }, { "ID": "RegionTooltip", "Translations": { diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 5730254f7..98636d4ff 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -350,11 +350,11 @@ namespace Ryujinx.Headless { return options.GraphicsBackend switch { - GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet), + GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit), GraphicsBackend.Metal => OperatingSystem.IsMacOS() ? - new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet) : + new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit) : throw new Exception("Attempted to use Metal renderer on non-macOS platform!"), - _ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet) + _ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit) }; } diff --git a/src/Ryujinx/Headless/Metal/MetalWindow.cs b/src/Ryujinx/Headless/Metal/MetalWindow.cs index a2693c69d..1ae8f5ee4 100644 --- a/src/Ryujinx/Headless/Metal/MetalWindow.cs +++ b/src/Ryujinx/Headless/Metal/MetalWindow.cs @@ -23,8 +23,9 @@ namespace Ryujinx.Headless AspectRatio aspectRatio, bool enableMouse, HideCursorMode hideCursorMode, - bool ignoreControllerApplet) - : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) { } + bool ignoreControllerApplet, + int specialExitEmulator) + : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator) { } public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL; diff --git a/src/Ryujinx/Headless/OpenGL/OpenGLWindow.cs b/src/Ryujinx/Headless/OpenGL/OpenGLWindow.cs index c00a0648f..ca4f48861 100644 --- a/src/Ryujinx/Headless/OpenGL/OpenGLWindow.cs +++ b/src/Ryujinx/Headless/OpenGL/OpenGLWindow.cs @@ -118,8 +118,9 @@ namespace Ryujinx.Headless AspectRatio aspectRatio, bool enableMouse, HideCursorMode hideCursorMode, - bool ignoreControllerApplet) - : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) + bool ignoreControllerApplet, + int specialExitEmulator) + : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator) { _glLogLevel = glLogLevel; } diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 0d7e46285..c575416be 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -150,7 +150,10 @@ namespace Ryujinx.Headless if (NeedsOverride(nameof(IgnoreControllerApplet))) IgnoreControllerApplet = configurationState.IgnoreApplet; - + + if (NeedsOverride(nameof(SpecialExit))) + SpecialExit = configurationState.Hid.SpecialExitEmulator; + return; bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey))); @@ -274,6 +277,9 @@ namespace Ryujinx.Headless [Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")] public bool EnableMouse { get; set; } + [Option("enable-press-hotkeys-to-exit", Required = false, Default = 0, HelpText = "press the minus and plus buttons to: 0 -disable, 1 - exit app, 2 - exit game.")] + public int SpecialExit { get; set; } + [Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")] public HideCursorMode HideCursorMode { get; set; } @@ -414,6 +420,7 @@ namespace Ryujinx.Headless [Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")] public bool IgnoreControllerApplet { get; set; } + // Values [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] diff --git a/src/Ryujinx/Headless/Vulkan/VulkanWindow.cs b/src/Ryujinx/Headless/Vulkan/VulkanWindow.cs index 92caad34e..9819bbc9b 100644 --- a/src/Ryujinx/Headless/Vulkan/VulkanWindow.cs +++ b/src/Ryujinx/Headless/Vulkan/VulkanWindow.cs @@ -18,8 +18,9 @@ namespace Ryujinx.Headless AspectRatio aspectRatio, bool enableMouse, HideCursorMode hideCursorMode, - bool ignoreControllerApplet) - : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) + bool ignoreControllerApplet, + int specialExitEmulator) + : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator) { _glLogLevel = glLogLevel; } diff --git a/src/Ryujinx/Headless/WindowBase.cs b/src/Ryujinx/Headless/WindowBase.cs index d89638cc1..4e4d5a4d4 100644 --- a/src/Ryujinx/Headless/WindowBase.cs +++ b/src/Ryujinx/Headless/WindowBase.cs @@ -88,6 +88,7 @@ namespace Ryujinx.Headless private readonly AspectRatio _aspectRatio; private readonly bool _enableMouse; + private readonly int _specialExitEmulator; private readonly bool _ignoreControllerApplet; public WindowBase( @@ -96,7 +97,8 @@ namespace Ryujinx.Headless AspectRatio aspectRatio, bool enableMouse, HideCursorMode hideCursorMode, - bool ignoreControllerApplet) + bool ignoreControllerApplet, + int specialExitEmulator) { MouseDriver = new SDL2MouseDriver(hideCursorMode); _inputManager = inputManager; @@ -112,6 +114,7 @@ namespace Ryujinx.Headless _gpuDoneEvent = new ManualResetEvent(false); _aspectRatio = aspectRatio; _enableMouse = enableMouse; + _specialExitEmulator = specialExitEmulator; _ignoreControllerApplet = ignoreControllerApplet; HostUITheme = new HeadlessHostUiTheme(); diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 0b63af2d9..8936513ca 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -30,6 +30,11 @@ namespace Ryujinx.Ava.Input public readonly Key From = from; } + public bool SpecialExit() + { + return false; + } + public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name) { _buttonsUserMapping = []; diff --git a/src/Ryujinx/Input/AvaloniaMouse.cs b/src/Ryujinx/Input/AvaloniaMouse.cs index 1aa2d586a..cdcdc2106 100644 --- a/src/Ryujinx/Input/AvaloniaMouse.cs +++ b/src/Ryujinx/Input/AvaloniaMouse.cs @@ -13,6 +13,11 @@ namespace Ryujinx.Ava.Input public string Id => "0"; public string Name => "AvaloniaMouse"; + public bool SpecialExit() + { + return false; + } + public bool IsConnected => true; public GamepadFeaturesFlag Features => throw new NotImplementedException(); public bool[] Buttons => _driver.PressedButtons; diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 17b9ea98c..5ae9ef9ef 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1049,6 +1049,7 @@ namespace Ryujinx.Ava.UI.ViewModels private void InitializeGame() { + RendererHostControl.WindowCreated += RendererHost_Created; AppHost.StatusUpdatedEvent += Update_StatusBar; @@ -1058,7 +1059,13 @@ namespace Ryujinx.Ava.UI.ViewModels AppHost?.Start(); + if (AppHost?.IsSpecialExit() == true) + { + Window.ForceExit(); + } + AppHost?.DisposeContext(); + } private async Task HandleRelaunch() diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 2678bbf98..ad82b4d62 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -128,6 +128,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } public bool EnableMouse { get; set; } + public int EnableSpecialExit { get; set; } public VSyncMode VSyncMode { get => _vSyncMode; @@ -259,6 +260,8 @@ namespace Ryujinx.Ava.UI.ViewModels public int OpenglDebugLevel { get; set; } public int MemoryMode { get; set; } public int BaseStyleIndex { get; set; } + + public int GraphicsBackendIndex { get => _graphicsBackendIndex; @@ -511,6 +514,13 @@ namespace Ryujinx.Ava.UI.ViewModels EnableDockedMode = config.System.EnableDockedMode; EnableKeyboard = config.Hid.EnableKeyboard; EnableMouse = config.Hid.EnableMouse; + EnableSpecialExit = config.Hid.SpecialExitEmulator.Value switch + { + 0 => 0, // "Hotkey 'Exit' is Disabled" + 1 => 1, // "Close app. by hotkey" + 2 => 2, // "Close game by hotkey" + _ => 0 + }; // Keyboard Hotkeys KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value); @@ -618,6 +628,13 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.EnableDockedMode.Value = EnableDockedMode; config.Hid.EnableKeyboard.Value = EnableKeyboard; config.Hid.EnableMouse.Value = EnableMouse; + config.Hid.SpecialExitEmulator.Value = EnableSpecialExit switch + { + 0 => 0, // "Hotkey 'Exit' is Disabled", + 1 => 1, // "Close app. by hotkey", + 2 => 2, // "Close game by hotkey", + _ => 0 + }; // Keyboard Hotkeys config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig(); diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml index b0edc51a5..033352548 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -1,4 +1,4 @@ - + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 2aaac4098..3487da385 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -45,6 +45,7 @@ namespace Ryujinx.Ava.UI.Windows internal readonly AvaHostUIHandler UiHandler; private bool _isLoading; + private bool _isExitWithoutConfirm = false; private bool _applicationsLoadedOnce; private UserChannelPersistence _userChannelPersistence; @@ -571,11 +572,11 @@ namespace Ryujinx.Ava.UI.Windows protected override void OnClosing(WindowClosingEventArgs e) { - if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit) + if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit && !_isExitWithoutConfirm) { e.Cancel = true; - ConfirmExit(); + ConfirmExit(); return; } @@ -616,6 +617,12 @@ namespace Ryujinx.Ava.UI.Windows base.OnClosing(e); } + public void ForceExit() + { + _isExitWithoutConfirm = true; + Close(); + } + private void ConfirmExit() { Dispatcher.UIThread.InvokeAsync(async () => diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index 947dd5c8f..a0cfaec0c 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 59; + public const int CurrentVersion = 60; /// /// Version of the configuration file format @@ -366,6 +366,12 @@ namespace Ryujinx.Ava.Utilities.Configuration /// public bool EnableMouse { get; set; } + /// + /// Allows you to choose one of several behaviors when pressing hotkeys: + /// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game. + /// + public int SpecialExitEmulator { get; set; } + /// /// Hotkey Keyboard Bindings /// diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index ec66bcaac..f341c5f15 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -136,6 +136,7 @@ namespace Ryujinx.Ava.Utilities.Configuration Hid.EnableKeyboard.Value = cff.EnableKeyboard; Hid.EnableMouse.Value = cff.EnableMouse; + Hid.SpecialExitEmulator.Value = cff.SpecialExitEmulator; Hid.Hotkeys.Value = cff.Hotkeys; Hid.InputConfig.Value = cff.InputConfig ?? []; @@ -414,6 +415,10 @@ namespace Ryujinx.Ava.Utilities.Configuration // This was accidentally enabled by default when it was PRed. That is not what we want, // so as a compromise users who want to use it will simply need to re-enable it once after updating. cff.IgnoreApplet = false; + }), + (60, static cff => + { + cff.SpecialExitEmulator = 0; }) ); } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index fe5f2c3ad..85bede2d5 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -420,6 +420,13 @@ namespace Ryujinx.Ava.Utilities.Configuration /// public ReactiveObject EnableMouse { get; private set; } + /// + /// Allows you to choose one of several behaviors when pressing hotkeys: + /// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game. + /// + public ReactiveObject SpecialExitEmulator { get; private set; } + + /// /// Hotkey Keyboard Bindings /// @@ -436,6 +443,7 @@ namespace Ryujinx.Ava.Utilities.Configuration { EnableKeyboard = new ReactiveObject(); EnableMouse = new ReactiveObject(); + SpecialExitEmulator = new ReactiveObject(); Hotkeys = new ReactiveObject(); InputConfig = new ReactiveObject>(); } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 95ec62e83..b68f97826 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -128,6 +128,7 @@ namespace Ryujinx.Ava.Utilities.Configuration ShowConsole = UI.ShowConsole, EnableKeyboard = Hid.EnableKeyboard, EnableMouse = Hid.EnableMouse, + SpecialExitEmulator = Hid.SpecialExitEmulator, Hotkeys = Hid.Hotkeys, KeyboardConfig = [], ControllerConfig = [], @@ -241,6 +242,7 @@ namespace Ryujinx.Ava.Utilities.Configuration UI.WindowStartup.WindowMaximized.Value = false; Hid.EnableKeyboard.Value = false; Hid.EnableMouse.Value = false; + Hid.SpecialExitEmulator.Value = 0; Hid.Hotkeys.Value = new KeyboardHotkeys { ToggleVSyncMode = Key.F1,