Compare commits

...

36 Commits

Author SHA1 Message Date
uncavo-hdmi
aa9a37bfea merge with upstream 2025-03-01 09:11:06 +01:00
uncavo-hdmi
f024c8a695 fix incorrect ReloadConfiguration placement 2025-02-28 17:16:55 +01:00
Danik2343
e104ee6be3 Update: Russian Language (Some missing strings) (#732) 2025-02-27 16:53:18 -06:00
Nicola
e65d1ec6c9 JoyCon to Joy-Con (#729)
Joy-Con is the official name
2025-02-26 20:00:35 -06:00
Evan Husted
534f92506b misc: chore: Add warning logs for invalid ips patch attempts 2025-02-26 02:31:18 -06:00
Daniel Nylander
10d20c1ae3 Update Swedish locale (#720) 2025-02-26 02:02:07 -06:00
Evan Husted
e294a79975 UI: dev: [ci skip] Add Avalonia DevTools support to all Windows defined by Ryujinx, accessible via Ctrl + F12 when running in Debug. 2025-02-25 23:12:57 -06:00
uncavo-hdmi
6ecdf690be removed unnecessary logging, formatting and imports. 2025-02-22 18:51:00 +01:00
uncavo-hdmi
c6e82046dd minor fixes 2025-02-22 18:37:01 +01:00
uncavo-hdmi
538a9c83bd class to static; added a controller check on GetConfiguredControllers 2025-02-22 17:04:42 +01:00
uncavo-hdmi
c3bfa67393 updated locales.json AutoAssignTooltip description 2025-02-22 10:28:14 +01:00
uncavo-hdmi
72c3ca7769 changed var name; fixed class name; removed hashset argument from GetConfiguredController function. 2025-02-22 10:15:41 +01:00
uncavo-hdmi
da268ebbee merge with upstream 2025-02-19 20:03:35 +01:00
uncavo-hdmi
55f3a4b7ff simplified logic 2025-02-18 14:43:00 +01:00
uncavo-hdmi
cc905280cd fixed some problems with rainbow led. also, rainbow won't be visible in settings (no easy way to constantly update it) 2025-02-18 14:38:30 +01:00
uncavo-hdmi
38ecb3d5bc added function to handle gamepad led while in settings; added rainbow color handling in the new function. 2025-02-18 13:40:34 +01:00
uncavo-hdmi
fd9bce0f6b fixed led live update while in settings 2025-02-18 11:27:30 +01:00
uncavo-hdmi
b8cb70ef32 possible refactor. to be tested 2025-02-17 22:17:54 +01:00
uncavo-hdmi
e238ea85c9 fixed save config logic 2025-02-17 20:59:51 +01:00
uncavo-hdmi
8cc74dab08 Improved assignment logic in AutoAssignController.cs 2025-02-12 19:44:16 +01:00
uncavo-hdmi
62dfbb5dcb edited NpadManager to better support auto-assign; updated initialization methods; changed some settings for LED color 2025-02-09 18:10:32 +01:00
uncavo-hdmi
5b88a2dd89 enhance AutoAssignController to trigger configuration updates on gamepad connection changes; improve controller assignment logic and ensure proper LED color settings 2025-02-09 16:07:12 +01:00
uncavo-hdmi
5034ef18c9 minor fix: swapped LoadConfiguration() and LoadDevice(). The previous order caused the configuration to load incorrectly. 2025-02-07 15:23:07 +01:00
uncavo-hdmi
287d68c2cc namespace correction 2025-02-07 14:51:46 +01:00
uncavo-hdmi
f07efb751e Relocate AutoAssignController.cs to a more appropriate directory 2025-02-07 14:45:29 +01:00
uncavo-hdmi
6c8a60db08 enhance AutoAssignController to set player colors and enable LED functionality; update gamepad connection handling to load configuration while in Input settings. 2025-02-04 17:02:25 +01:00
uncavo-hdmi
7999a973f3 update GetOrderedConfig to return if new controllers connected and conditionally update configuration 2025-02-04 14:51:48 +01:00
uncavo-hdmi
5513de93e5 code clean up; fix not loading EnableAutoAssign.Value from config file on startup 2025-02-04 11:50:08 +01:00
uncavo-hdmi
9cccaac9d3 minor fix to RefreshControllers logic 2025-02-02 22:56:34 +01:00
uncavo-hdmi
9b7dc6f4ee simplify RefreshControllers logic and introduce GetOrderedConfig for better controller management 2025-02-02 22:40:47 +01:00
uncavo-hdmi
3ff9d1e128 refactor: enhance AutoAssignController to utilize ViewModel and improve controller refresh logic 2025-02-02 16:35:30 +01:00
uncavo-hdmi
ab4bb0a885 refactor: remove auto-assign option from NpadManager initialization and update related components 2025-02-01 19:36:55 +01:00
uncavo-hdmi
97be01d473 refactor: streamline configuration creation for controllers in NpadManager 2025-01-28 22:47:55 +01:00
uncavo-hdmi
24cef89b6c refactor: clean up logging and improve IgnoreApplet logic in settings 2025-01-26 23:52:44 +01:00
uncavo-hdmi
2fe157e2b2 Merge remote-tracking branch 'upstream/master' into auto-assign-controller 2025-01-26 21:23:37 +01:00
uncavo-hdmi
186ed4f984 feat: add option for automatic controller assignment in settings 2025-01-26 21:20:27 +01:00
29 changed files with 609 additions and 103 deletions

View File

@@ -97,7 +97,7 @@ If you are planning to contribute or just want to learn more about this project
- **Input**
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers.
We currently have support for keyboard, mouse, touch input, Joy-Con input support, and nearly all controllers.
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu.

View File

@@ -71,16 +71,24 @@ namespace Ryujinx.HLE.Loaders.Mods
int patchOffset = (int)offset;
int patchSize = patch.Length;
if (patchOffset < protectedOffset || patchOffset > memory.Length)
if (patchOffset < protectedOffset)
{
continue; // Add warning?
Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch protected memory ({patchOffset:x} is within protected boundary of {protectedOffset:x}).");
continue;
}
if (patchOffset > memory.Length)
{
Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch out of bounds memory (offset {patchOffset} ({patchOffset:x}) exceeds memory buffer length {memory.Length}).");
continue;
}
patchOffset -= protectedOffset;
if (patchOffset + patchSize > memory.Length)
{
patchSize = memory.Length - patchOffset; // Add warning?
Logger.Warning?.Print(LogClass.ModLoader, $"Patch offset ({patchOffset:x}) + size ({patchSize}) is greater than the size of the memory buffer ({memory.Length}). Attempting to fix this...");
patchSize = memory.Length - patchOffset;
}
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");

View File

@@ -95,7 +95,7 @@ namespace Ryujinx.Input.SDL2
}
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
{
{
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
{
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))

View File

@@ -14,6 +14,7 @@ using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType;
using PlayerIndex = Ryujinx.HLE.HOS.Services.Hid.PlayerIndex;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Input.HLE
{
public class NpadManager : IDisposable
@@ -38,6 +39,8 @@ namespace Ryujinx.Input.HLE
private bool _enableMouse;
private Switch _device;
public bool AutoAssignEnabled { get; set; } = false;
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
{
_controllers = new NpadController[MaxControllers];
@@ -84,12 +87,14 @@ namespace Ryujinx.Input.HLE
}
}
if (AutoAssignEnabled) return;
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
}
}
private void HandleOnGamepadConnected(string id)
{
if (AutoAssignEnabled) return;
// Force input reload
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
}
@@ -171,7 +176,7 @@ namespace Ryujinx.Input.HLE
_device.Hid.RefreshInputConfig(validInputs);
}
}
public void UnblockInputUpdates()
{
lock (_lock)
@@ -202,11 +207,13 @@ namespace Ryujinx.Input.HLE
}
}
public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse)
public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse, bool enableAutoAssign)
{
_device = device;
_device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE;
AutoAssignEnabled = enableAutoAssign;
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
}

View File

@@ -463,7 +463,7 @@ namespace Ryujinx.Ava
DisplaySleep.Prevent();
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse, ConfigurationState.Instance.Hid.EnableAutoAssign);
TouchScreenManager.Initialize(Device);
_viewModel.IsGameRunning = true;

View File

@@ -147,6 +147,31 @@
"zh_TW": "滑鼠直接存取"
}
},
{
"ID": "SettingsTabInputAutoAssign",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Auto-assign controllers",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "Assegnamento controller automatico",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabSystemMemoryManagerMode",
"Translations": {
@@ -464,7 +489,7 @@
"pl_PL": "",
"pt_BR": "Abrir Pasta de Capturas de Tela",
"ru_RU": "Открыть папку со скриншотами",
"sv_SE": "",
"sv_SE": "Öppna skärmbildsmappen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку скріншотів",
@@ -1564,7 +1589,7 @@
"pl_PL": "",
"pt_BR": "Desenvolvido por {0}",
"ru_RU": "Разработана {0}",
"sv_SE": "",
"sv_SE": "Utvecklat av {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розроблено: {0}",
@@ -1864,7 +1889,7 @@
"pl_PL": "",
"pt_BR": "Compatibilidade:",
"ru_RU": "Совместимость:",
"sv_SE": "",
"sv_SE": "Kompatibilitet:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Сумісність:",
@@ -1889,7 +1914,7 @@
"pl_PL": "",
"pt_BR": "ID do Título:",
"ru_RU": "ID приложения",
"sv_SE": "",
"sv_SE": "Titel-id:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "ID гри:",
@@ -1914,7 +1939,7 @@
"pl_PL": "",
"pt_BR": "Jogos Hospedados: {0}",
"ru_RU": "Запущенно игр: {0}",
"sv_SE": "",
"sv_SE": "Värdskap för spel: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розміщені ігри: {0}",
@@ -1939,7 +1964,7 @@
"pl_PL": "",
"pt_BR": "Jogadores Online: {0}",
"ru_RU": "Игроков онлайн: {0}",
"sv_SE": "",
"sv_SE": "Online-spelare: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Гравців онлайн: {0}",
@@ -2289,7 +2314,7 @@
"pl_PL": "",
"pt_BR": "Limpar Cache PPTC",
"ru_RU": "Очистить кэш PPTC",
"sv_SE": "",
"sv_SE": "Rensa PPTC-cache",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Очистити кеш PPTC",
@@ -2314,7 +2339,7 @@
"pl_PL": "",
"pt_BR": "Apaga os arquivos de cache PPTC do aplicativo",
"ru_RU": "Удаляет все файлы кэша PPTC для приложения",
"sv_SE": "",
"sv_SE": "Tar bort alla PPTC-cachefiler för applikationen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Видаляє всі файли кешу PPTC для застосунку",
@@ -2763,7 +2788,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Создать пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -2788,7 +2813,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Изменить пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -2863,7 +2888,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Отредактировать существующую независимую конфигурацию для выбранной игры.",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -2889,7 +2914,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Dados de Compatibilidade",
"ru_RU": "Показать записи о совместимости",
"sv_SE": "",
"sv_SE": "Visa kompatibilitetspost",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Iнформація про сумісність",
@@ -2914,7 +2939,7 @@
"pl_PL": "",
"pt_BR": "Exibe o jogo selecionado na Lista de Compatibilidade, que normalmente pode ser acessada pelo menu Ajuda.",
"ru_RU": "Отобразить выбранную игру в списке совместимости, доступ к которому вы обычно можете получить через меню Справки.",
"sv_SE": "",
"sv_SE": "Visa valt spel i kompatibilitetslistan som du normalt sett kan komma åt via hjälpmenyn.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.",
@@ -2939,7 +2964,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Informações do Jogo",
"ru_RU": "Показать информацию об игре",
"sv_SE": "",
"sv_SE": "Visa spelinformation",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Інформація про гру",
@@ -2964,7 +2989,7 @@
"pl_PL": "",
"pt_BR": "Exibe estatísticas e detalhes sobre o jogo selecionado.",
"ru_RU": "Показывать статистику и подробную информацию о выбранной игре.",
"sv_SE": "",
"sv_SE": "Visa statistik och detaljer om det aktuella spelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати статистику та деталі обраної гри.",
@@ -3514,7 +3539,7 @@
"pl_PL": "",
"pt_BR": "Verificar Atualizações:",
"ru_RU": "Проверка наличия обновлений",
"sv_SE": "",
"sv_SE": "Leta efter uppdateringar:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірка оновлень:",
@@ -3539,7 +3564,7 @@
"pl_PL": "",
"pt_BR": "Desligado",
"ru_RU": "Отключить",
"sv_SE": "",
"sv_SE": "Av",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути",
@@ -3564,7 +3589,7 @@
"pl_PL": "",
"pt_BR": "Ao Abrir",
"ru_RU": "При запуске",
"sv_SE": "",
"sv_SE": "Fråga",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запитувати щоразу",
@@ -3589,7 +3614,7 @@
"pl_PL": "",
"pt_BR": "2° Plano",
"ru_RU": "В фоне",
"sv_SE": "",
"sv_SE": "Bakgrund",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Оновлювати в фоні",
@@ -3614,7 +3639,7 @@
"pl_PL": "",
"pt_BR": "Ao Perder o Foco:",
"ru_RU": "При выходе эмулятора из фокуса",
"sv_SE": "",
"sv_SE": "När emulatorn tappar fokus:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "При втраті фокуса емулятором:",
@@ -3639,7 +3664,7 @@
"pl_PL": "",
"pt_BR": "Não Fazer Nada",
"ru_RU": "Ничего не делать",
"sv_SE": "",
"sv_SE": "Gör ingenting",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Нічого не робити",
@@ -3664,7 +3689,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles",
"ru_RU": "Блокировать управление",
"sv_SE": "",
"sv_SE": "Blockera inmatning",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення",
@@ -3689,7 +3714,7 @@
"pl_PL": "",
"pt_BR": "Ficar Mudo",
"ru_RU": "Отключить звук",
"sv_SE": "",
"sv_SE": "Stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути звук",
@@ -3714,7 +3739,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles & Ficar Mudo",
"ru_RU": "Блокировать управление и отключить звук",
"sv_SE": "",
"sv_SE": "Blockera inmatningar och stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення та Вимкнути звук",
@@ -3739,7 +3764,7 @@
"pl_PL": "",
"pt_BR": "Pausar a Emulação",
"ru_RU": "Поставить паузу",
"sv_SE": "",
"sv_SE": "Pausa emuleringen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Поставити на паузу",
@@ -3814,7 +3839,7 @@
"pl_PL": "",
"pt_BR": "Desativar Controles Quando Estiver Fora de Foco",
"ru_RU": "Отключает управление при выходе из фокуса",
"sv_SE": "",
"sv_SE": "Inaktivera inmatning när fokus tappas",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -4839,7 +4864,7 @@
"pl_PL": "",
"pt_BR": "Sincronizar com o Sistema PC",
"ru_RU": "Соответствовать времени в системе",
"sv_SE": "",
"sv_SE": "Matcha systemtid",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -5013,7 +5038,7 @@
"no_NO": "Lyd Inn/Ut",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Выход/Вход звука",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -5264,7 +5289,7 @@
"pl_PL": "",
"pt_BR": "Ignorar Applet do Controlador",
"ru_RU": "Игнорировать апплет контроллера",
"sv_SE": "",
"sv_SE": "Ignorera kontroller-applet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ігнорувати Аплет Контролера",
@@ -6114,7 +6139,7 @@
"pl_PL": "",
"pt_BR": "Habilitar Logs da IU",
"ru_RU": "Включить журнал интерфейса",
"sv_SE": "",
"sv_SE": "Aktivera gränssnittsloggar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Увімкнути журнали інтерфейсу",
@@ -6514,7 +6539,7 @@
"pl_PL": "",
"pt_BR": "Redefinir Configurações",
"ru_RU": "Сбросить настройки",
"sv_SE": "",
"sv_SE": "Nollställ inställningar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Скинути налаштування",
@@ -6539,7 +6564,7 @@
"pl_PL": "",
"pt_BR": "Quero redefinir minhas configurações.",
"ru_RU": "Я хочу сбросить свои настройки.",
"sv_SE": "",
"sv_SE": "Jag vill nollställa mina inställningar.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Я хочу скинути налаштування.",
@@ -6563,7 +6588,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Ок",
"sv_SE": "Ok",
"th_TH": "ตกลง",
"tr_TR": "Tamam",
@@ -7013,7 +7038,7 @@
"no_NO": "",
"pl_PL": "Pro Kontroler",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Pro контроллер",
"sv_SE": "",
"th_TH": "โปรคอนโทรลเลอร์",
"tr_TR": "Profesyonel Kumanda",
@@ -8514,7 +8539,7 @@
"pl_PL": "",
"pt_BR": "Velocidade do Arco-íris",
"ru_RU": "Скорость переливания",
"sv_SE": "",
"sv_SE": "Regnbågshastighet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -13814,7 +13839,7 @@
"pl_PL": "",
"pt_BR": "Você está prestes a limpar todos os dados PPTC de:\n\n{0}\n\nTem certeza de que deseja continuar?",
"ru_RU": "Вы собираетесь удалить все данные PPTC из:\n\n{0}\n\nВы уверены, что хотите продолжить?",
"sv_SE": "",
"sv_SE": "Du är på väg att ta bort allt PPTC-data från:\n\n{0}\n\nÄr du säker på att du vill fortsätta?",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?",
@@ -16222,6 +16247,31 @@
"zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後觸控螢幕功能可能無法使用。\n\n如果不確定請保持關閉狀態。"
}
},
{
"ID": "AutoAssignTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Automatic controllers assignment support.\n\nAutomatically assigns connected controllers to each player.\n\nManual configuration remains available, even when this option is activated.\n\nLeave OFF if you prefer to manually assign controllers.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "Supporto per l'assegnazione automatica dei controller.\n\nAssegna automaticamente i controller connessi a ciascun giocatore.\n\nÈ possibile configurare manualmente i controller anche quando questa opzione è attivata.\n\nLascia disattivato se preferisci assegnare i controller manualmente.",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": "。"
}
},
{
"ID": "RegionTooltip",
"Translations": {
@@ -16664,7 +16714,7 @@
"pl_PL": "",
"pt_BR": "A caixa de diálogo do Applet do controlador não aparecerá se o controle for desconectado enquanto um aplicativo estiver em execução.\n\nDeixe a opção DESLIGADO se não tiver certeza.",
"ru_RU": "Диалоговое окно апплета контроллера не будет отображаться, если геймпад отключен во время работы приложения.\n\nОставьте выключенным, если не уверены.",
"sv_SE": "",
"sv_SE": "Handkontroller-appleten kommer inte att visas om gamepaden är frånkopplad under tiden en applikation körs.\n\nLämna AV om du är osäker.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.",
@@ -17139,7 +17189,7 @@
"pl_PL": "",
"pt_BR": "Imprime mensagens de log do Avalonia (UI) no console.",
"ru_RU": "Выводит сообщения журнала Avalonia (интерфейс) в консоли.",
"sv_SE": "",
"sv_SE": "Skriver ut loggmeddelanden från Avalonia (användargränssnittet) i konsollen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль",
@@ -17339,7 +17389,7 @@
"pl_PL": "",
"pt_BR": "Abre a pasta de capturas de tela do Ryujinx",
"ru_RU": "Открывает папку скриншотов Ryujinx",
"sv_SE": "",
"sv_SE": "Öppna Ryujinx skärmbildsmapp",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx",
@@ -18089,7 +18139,7 @@
"pl_PL": "",
"pt_BR": "Atualização Disponível!",
"ru_RU": "Доступно обновление!",
"sv_SE": "",
"sv_SE": "Uppdatering finns tillgänglig!",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Доступне оновлення!",
@@ -20013,7 +20063,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Амибо",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -24064,7 +24114,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e roda sem travamentos ou bugs de GPU de qualquer tipo, e em uma velocidade rápida o suficiente para ser aproveitado em um PC comum.",
"ru_RU": "Запускается и работает без любого рода сбоев или графисечких ошибок и на скорости, достаточной для работы на обычном ПК.",
"sv_SE": "",
"sv_SE": "Startar upp och spelas utan några krascher eller GPU-fel av några slag och med en hastighet som är snabb nog för bra upplevelse på en genomsnittlig PC.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.",
@@ -24089,7 +24139,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e entra no jogo, mas sofre de um ou mais dos seguintes: travamentos, deadlocks, bugs de GPU, áudio ruim que distrai ou é simplesmente muito lento. O jogo ainda pode ser jogado até o fim, mas não da forma como foi criado para ser jogado.",
"ru_RU": "Запускается и работает, но возникает одна или несколько из следующих проблем: сбои, взаимоблокировки, ошибки GPU, отвлекающие звуки или просто слишком медленная работа. Возможно, игру всё же удастся пройти до конца, но не так, как она задумана.",
"sv_SE": "",
"sv_SE": "Startar och går in i spelet men lider av ett eller flera av följande: kraschar, deadlocks, GPU-buggar, distraherande dåligt ljud eller är helt enkelt för långsamt. Spelet kan fortfarande spelas hela vägen igenom, men inte så som spelet är avsett att spelas.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.",
@@ -24114,7 +24164,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e passa da tela de título, mas não entra no jogo principal.",
"ru_RU": "Загружается титульный экран и можно перейти дальше, но сама игра не работает.",
"sv_SE": "",
"sv_SE": "Startar upp och går förbi titelskärmen men tar sig inte in i huvudspelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.",
@@ -24139,7 +24189,7 @@
"pl_PL": "",
"pt_BR": "Inizializa, mas não passa da tela de título.",
"ru_RU": "Загружается, но не проходит дальше титульного экрана.",
"sv_SE": "",
"sv_SE": "Startar upp men tar sig inte förbi titelskärmen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але не відображає навіть початкового екрану.",
@@ -24164,7 +24214,7 @@
"pl_PL": "",
"pt_BR": "Não inicializa ou não mostra sinais de atividade.",
"ru_RU": "Не запускается или не подаёт признаков жизни.",
"sv_SE": "",
"sv_SE": "Startar inte upp eller visar någon form av aktivitet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Взагалі не запускається.",
@@ -24264,7 +24314,7 @@
"pl_PL": "",
"pt_BR": "Imagem da Presença do Discord",
"ru_RU": "Изображение для статуса активности",
"sv_SE": "",
"sv_SE": "Bild för Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зображення картки активності Discord",
@@ -24289,7 +24339,7 @@
"pl_PL": "",
"pt_BR": "Presença Dinâmica do Discord",
"ru_RU": "Динамический статус активности",
"sv_SE": "",
"sv_SE": "Dynamisk Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Динамічна картка активності Discord",
@@ -24298,4 +24348,4 @@
}
}
]
}
}

View File

@@ -46,6 +46,7 @@ namespace Ryujinx.Headless
private static List<InputConfig> _inputConfiguration = [];
private static bool _enableKeyboard;
private static bool _enableMouse;
private static bool _enableAutoAssign;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -230,6 +231,7 @@ namespace Ryujinx.Headless
_inputConfiguration ??= [];
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
_enableAutoAssign = option.EnableAutoAssign;
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
@@ -371,7 +373,7 @@ namespace Ryujinx.Headless
DisplaySleep.Prevent();
_window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
_window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse, _enableAutoAssign);
_window.Execute();

View File

@@ -26,6 +26,9 @@ namespace Ryujinx.Headless
if (NeedsOverride(nameof(EnableMouse)))
EnableMouse = configurationState.Hid.EnableMouse;
if (NeedsOverride(nameof(EnableAutoAssign)))
EnableAutoAssign = configurationState.Hid.EnableAutoAssign;
if (NeedsOverride(nameof(HideCursorMode)))
HideCursorMode = configurationState.HideCursor;
@@ -272,6 +275,9 @@ namespace Ryujinx.Headless
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
public bool EnableMouse { get; set; }
[Option("enable-auto-assign", Required = false, Default = false, HelpText = "Enable or disable auto-assigning controllers to players.")]
public bool EnableAutoAssign { get; set; }
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
public HideCursorMode HideCursorMode { get; set; }

View File

@@ -119,7 +119,7 @@ namespace Ryujinx.Headless
SDL2Driver.Instance.Initialize();
}
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse, bool enableAutoAssign)
{
Device = device;
@@ -132,7 +132,7 @@ namespace Ryujinx.Headless
Renderer = renderer;
NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse);
NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse, enableAutoAssign);
TouchScreenManager.Initialize(device);
}

View File

@@ -0,0 +1,75 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Input
{
public class AutoAssignController
{
private readonly InputManager _inputManager;
private readonly MainWindowViewModel _viewModel;
private readonly ConfigurationState _configurationState;
public event Action ConfigurationUpdated;
public AutoAssignController(InputManager inputManager, MainWindowViewModel mainWindowViewModel)
{
_inputManager = inputManager;
_viewModel = mainWindowViewModel;
_configurationState = ConfigurationState.Instance;
_inputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_inputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
RefreshControllers();
}
private void HandleOnGamepadConnected(string id)
{
Logger.Warning?.Print(LogClass.Application, $"Gamepad connected: {id}");
RefreshControllers();
}
private void HandleOnGamepadDisconnected(string id)
{
Logger.Warning?.Print(LogClass.Application, $"Gamepad disconnected: {id}");
RefreshControllers();
}
public void RefreshControllers()
{
if (!_configurationState.Hid.EnableAutoAssign) return;
List<IGamepad> controllers = _inputManager.GamepadDriver.GetGamepads().ToList();
List<InputConfig> oldConfig = _configurationState.Hid.InputConfig.Value.Where(x => x != null).ToList();
List<InputConfig> newConfig = ControllerAssignmentManager.GetConfiguredControllers(
controllers, oldConfig, out bool hasNewControllersConnected);
_viewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, _configurationState.Hid.EnableKeyboard, _configurationState.Hid.EnableMouse);
if (!hasNewControllersConnected)
{
// there is no *new* controller, we must switch the order of the controllers in
// oldConfig to match the new order since probably a controller was disconnected
// or an old controller was reconnected
newConfig = ControllerAssignmentManager.ReorderControllers(newConfig, oldConfig);
}
_configurationState.Hid.InputConfig.Value = newConfig;
// we want to save the configuration only if a *new* controller was connected
if(hasNewControllersConnected)
{
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
ConfigurationUpdated?.Invoke();
}
}
}

View File

@@ -0,0 +1,274 @@
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using System.Collections.Generic;
using System.Linq;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.Input
{
public static class ControllerAssignmentManager
{
private static readonly uint[] _playerColors =
[
0xFF0000FF, // Player 1 - Blue
0xFFFF0000, // Player 2 - Red
0xFF00FF00, // Player 3 - Green
0xFFFFFF00, // Player 4 - Yellow
0xFFFF00FF, // Player 5 - Magenta
0xFFFFA500, // Player 6 - Orange
0xFF00FFFF, // Player 7 - Cyan
0xFF800080 // Player 8 - Purple
];
private const int MaxControllers = 9;
public static List<InputConfig> ReorderControllers(List<InputConfig> newConfig, List<InputConfig> oldConfig)
{
if(newConfig == null || oldConfig == null || newConfig.Count == 0 || oldConfig.Count == 0) return [];
List<InputConfig> reorderedConfig = oldConfig.Select(config => new GamepadInputConfig(config).GetConfig()).ToList();
foreach (var config in newConfig)
{
InputConfig substitute = reorderedConfig.FirstOrDefault(x => x.Id == config.Id);
InputConfig toBeReplaced = reorderedConfig.FirstOrDefault(x => x.PlayerIndex == config.PlayerIndex);
if (substitute == null || toBeReplaced == null || substitute.PlayerIndex == toBeReplaced.PlayerIndex) continue;
(substitute.PlayerIndex, toBeReplaced.PlayerIndex) = (toBeReplaced.PlayerIndex, substitute.PlayerIndex);
}
return reorderedConfig;
}
public static List<InputConfig> GetConfiguredControllers(
List<IGamepad> controllers,
List<InputConfig> oldConfig,
out bool hasNewControllersConnected)
{
if(controllers == null || controllers.Count == 0)
{
hasNewControllersConnected = false;
return [];
}
Dictionary<string, InputConfig> oldConfigMap = oldConfig
.Where(c => c?.Id != null)
.ToDictionary(x => x.Id);
Dictionary<int, InputConfig> playerIndexMap = new();
HashSet<int> usedIndices = [];
int recognizedControllersCount = 0;
List<IGamepad> remainingControllers = controllers.Where(c => c?.Id != null).ToList();
// Add controllers with existing configurations
AddExistingControllers(remainingControllers, oldConfigMap, playerIndexMap, usedIndices, ref recognizedControllersCount);
// Add new controllers
AddNewControllers(remainingControllers, playerIndexMap, usedIndices);
List<InputConfig> orderedConfigs = playerIndexMap
.OrderBy(x => x.Key)
.Select(x => x.Value)
.ToList();
// Update player indices and LED colors
UpdatePlayerIndicesAndLEDs(orderedConfigs);
hasNewControllersConnected = controllers.Count > recognizedControllersCount;
return orderedConfigs;
}
private static void AddExistingControllers(
List<IGamepad> controllers,
Dictionary<string, InputConfig> oldConfigMap,
Dictionary<int, InputConfig> playerIndexMap,
HashSet<int> usedIndices,
ref int recognizedControllersCount)
{
foreach (var controller in controllers.ToList())
{
if (!oldConfigMap.TryGetValue(controller.Id, out InputConfig existingConfig))
{
continue;
}
int desiredIndex = (int)existingConfig.PlayerIndex;
// Ensure the index is valid and available
if (desiredIndex < 0 || desiredIndex >= MaxControllers || usedIndices.Contains(desiredIndex))
{
desiredIndex = GetFirstAvailableIndex(usedIndices);
}
if(desiredIndex == -1) continue;
InputConfig config = new GamepadInputConfig(existingConfig).GetConfig();
config.PlayerIndex = (PlayerIndex)desiredIndex;
usedIndices.Add(desiredIndex);
playerIndexMap[desiredIndex] = config;
recognizedControllersCount++;
controllers.Remove(controller);
}
}
private static void AddNewControllers(
List<IGamepad> controllers,
Dictionary<int, InputConfig> playerIndexMap,
HashSet<int> usedIndices)
{
foreach (var controller in controllers)
{
InputConfig config = CreateConfigFromController(controller);
int freeIndex = GetFirstAvailableIndex(usedIndices);
config.PlayerIndex = (PlayerIndex)freeIndex;
usedIndices.Add(freeIndex);
playerIndexMap[freeIndex] = config;
}
}
private static int GetFirstAvailableIndex(HashSet<int> usedIndices)
{
for (int i = 0; i < MaxControllers; i++)
{
if (!usedIndices.Contains(i)) return i;
}
return -1; // Should not happen unless MaxControllers is exceeded
}
private static void UpdatePlayerIndicesAndLEDs(List<InputConfig> orderedConfigs)
{
for (int index = 0; index < orderedConfigs.Count; index++)
{
orderedConfigs[index].PlayerIndex = (PlayerIndex)index;
if (orderedConfigs[index] is not StandardControllerInputConfig standardConfig ||
standardConfig.Led.UseRainbow)
{
continue;
}
Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index + 1}");
standardConfig.Led = new LedConfigController
{
EnableLed = true,
LedColor = _playerColors[index]
};
}
}
private static InputConfig CreateConfigFromController(IGamepad controller)
{
if (controller == null) return null;
Logger.Warning?.Print(LogClass.Application, $"Creating config for controller: {controller.Id}");
string id = controller.Id.Split(" ")[0];
bool isNintendoStyle = controller.Name.Contains("Nintendo");
ControllerType controllerType;
if (isNintendoStyle && !controller.Name.Contains("(L/R)"))
{
if (controller.Name.Contains("(L)"))
{
controllerType = ControllerType.JoyconLeft;
}
else if (controller.Name.Contains("(R)"))
{
controllerType = ControllerType.JoyconRight;
}
else
{
controllerType = ControllerType.ProController;
}
}
else
{
// if it's not a nintendo controller, we assume it's a pro controller or a joy-con pair
controllerType = ControllerType.ProController;
}
InputConfig config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = id,
ControllerType = controllerType,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
{
DpadUp = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.Y : GamepadInputId.DpadUp,
DpadDown = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.A : GamepadInputId.DpadDown,
DpadLeft = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.B : GamepadInputId.DpadLeft,
DpadRight = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.X : GamepadInputId.DpadRight,
ButtonMinus = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.Plus : GamepadInputId.Minus,
ButtonL = GamepadInputId.LeftShoulder,
ButtonZl = GamepadInputId.LeftTrigger,
ButtonSl = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.LeftShoulder : GamepadInputId.Unbound,
ButtonSr = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.RightShoulder : GamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = StickInputId.Left,
StickButton = GamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = (controllerType == ControllerType.JoyconLeft),
},
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
{
ButtonA = GamepadInputId.B,
ButtonB = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.Y : GamepadInputId.A,
ButtonX = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.A : GamepadInputId.Y,
ButtonY = GamepadInputId.X,
ButtonPlus = GamepadInputId.Plus,
ButtonR = GamepadInputId.RightShoulder,
ButtonZr = GamepadInputId.RightTrigger,
ButtonSl = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.LeftShoulder : GamepadInputId.Unbound,
ButtonSr = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.RightShoulder : GamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = (controllerType == ControllerType.JoyconRight) ? StickInputId.Left : StickInputId.Right,
StickButton = GamepadInputId.RightStick,
InvertStickX = (controllerType == ControllerType.JoyconRight),
InvertStickY = (controllerType == ControllerType.JoyconRight),
Rotate90CW = (controllerType == ControllerType.JoyconRight),
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false,
},
Led = new LedConfigController
{
EnableLed = true,
TurnOffLed = false,
UseRainbow = false,
LedColor = 0,
},
};
return config;
}
}
}

View File

@@ -134,7 +134,7 @@ namespace Ryujinx.Ava
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Logging system information.
@@ -158,16 +158,6 @@ namespace Ryujinx.Ava
}
}
public static bool FindGameConfig(string gameDir)
{
if (File.Exists(gameDir))
{
return true;
}
return false;
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))

View File

@@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.SettingsWindow =
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window);
await StyleableAppWindow.ShowAsync(_parent.SettingsWindow, window);
_parent.SettingsWindow = null;

View File

@@ -91,11 +91,14 @@ namespace Ryujinx.Ava.UI.Controls
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel);
await StyleableAppWindow.ShowAsync(
new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path
)
);
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
@@ -391,9 +394,9 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
//just checking for file presence
// just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
@@ -384,6 +385,10 @@ namespace Ryujinx.Ava.UI.Helpers
Position = parent.PointToScreen(new Point()),
ShowInTaskbar = false,
};
#if DEBUG
_contentDialogOverlayWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
parent.PositionChanged += OverlayOnPositionChanged;

View File

@@ -252,6 +252,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
_mainWindow.AutoAssignController.ConfigurationUpdated += OnConfigurationUpdated;
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false;
@@ -365,14 +367,47 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private void HandleOnGamepadDisconnected(string id)
{
if(ConfigurationState.Instance.Hid.EnableAutoAssign) return;
Dispatcher.UIThread.Post(LoadDevices);
}
private void HandleOnGamepadConnected(string id)
{
if(ConfigurationState.Instance.Hid.EnableAutoAssign) return;
Dispatcher.UIThread.Post(LoadDevices);
}
private void OnConfigurationUpdated()
{
Dispatcher.UIThread.Post(() => {
LoadDevices();
_isLoaded = false;
LoadConfiguration();
LoadDevice();
_isLoaded = true;
UpdateGamepadLed();
});
}
private void UpdateGamepadLed()
{
if (ConfigViewModel is not ControllerInputViewModel controllerInputViewModel) return;
GamepadInputConfig inputConfig = controllerInputViewModel.Config;
if (inputConfig is not { EnableLedChanging: true }) return;
if (inputConfig.TurnOffLed)
{
SelectedGamepad.ClearLed();
}
if (!inputConfig.TurnOffLed && !inputConfig.UseRainbowLed)
{
SelectedGamepad.SetLed(inputConfig.LedColor.ToUInt32());
}
}
private string GetCurrentGamepadId()
{
if (_device < 0)
@@ -859,6 +894,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
if (_mainWindow.ViewModel.AppHost != null)
{
_mainWindow.ViewModel.AppHost.NpadManager.AutoAssignEnabled =
ConfigurationState.Instance.Hid.EnableAutoAssign;
}
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change.
@@ -890,6 +931,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.AutoAssignController.ConfigurationUpdated -= OnConfigurationUpdated;
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();

View File

@@ -1747,7 +1747,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
await StyleableAppWindow.ShowAsync(window);
if (window.IsScanned)
{

View File

@@ -140,6 +140,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
public bool EnableAutoAssign { get; set; }
public bool DisableInputWhenOutOfFocus { get; set; }
public int FocusLostActionType { get; set; }
@@ -563,6 +564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDockedMode = config.System.EnableDockedMode;
EnableKeyboard = config.Hid.EnableKeyboard;
EnableMouse = config.Hid.EnableMouse;
EnableAutoAssign = config.Hid.EnableAutoAssign;
DisableInputWhenOutOfFocus = config.Hid.DisableInputWhenOutOfFocus;
// Keyboard Hotkeys
@@ -668,6 +670,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableDockedMode.Value = EnableDockedMode;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
config.Hid.EnableMouse.Value = EnableMouse;
bool activatingAutoAssign = EnableAutoAssign && !config.Hid.EnableAutoAssign;
config.Hid.EnableAutoAssign.Value = EnableAutoAssign;
config.Hid.DisableInputWhenOutOfFocus.Value = DisableInputWhenOutOfFocus;
// Keyboard Hotkeys
@@ -690,7 +694,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.DramSize.Value = DramSize;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
config.System.IgnoreControllerApplet.Value = IgnoreApplet;
config.System.IgnoreControllerApplet.Value = (EnableAutoAssign) || IgnoreApplet;
// CPU
config.System.EnablePtc.Value = EnablePptc;
@@ -766,7 +770,14 @@ namespace Ryujinx.Ava.UI.ViewModels
MainWindow.UpdateGraphicsConfig();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke();
if(activatingAutoAssign)
{
RyujinxApp.MainWindow.AutoAssignController.RefreshControllers();
}
else
{
SaveSettingsEvent?.Invoke();
}
GameListNeedsRefresh = false;
}

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -133,20 +134,20 @@ namespace Ryujinx.Ava.UI.Views.Main
if (ViewModel.SelectedApplication is null) // Checks if game data exists
{
await Window.SettingsWindow.ShowDialog(Window);
await StyleableAppWindow.ShowAsync(Window.SettingsWindow);
}
else
{
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
bool customConfigExists = File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString));
if (!ViewModel.IsGameRunning || !userConfigExist)
if (!ViewModel.IsGameRunning || !customConfigExists)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(ViewModel, customConfigExists));
}
}
@@ -175,11 +176,13 @@ namespace Ryujinx.Ava.UI.Views.Main
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path).ShowDialog(Window);
await StyleableAppWindow.ShowAsync(
new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path)
);
ViewModel.AppHost.Device.EnableCheats();
}

View File

@@ -58,6 +58,12 @@
<TextBlock
Text="{ext:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
<CheckBox
ToolTip.Tip="{ext:Locale AutoAssignTooltip}"
IsChecked="{Binding EnableAutoAssign}">
<TextBlock
Text="{ext:Locale SettingsTabInputAutoAssign}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Grid>

View File

@@ -319,6 +319,7 @@
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
<CheckBox
IsEnabled="{Binding !EnableAutoAssign}"
IsChecked="{Binding IgnoreApplet}"
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />

View File

@@ -47,11 +47,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public void SaveSettings()

View File

@@ -64,6 +64,7 @@ namespace Ryujinx.Ava.UI.Windows
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
public InputManager InputManager { get; private set; }
public AutoAssignController AutoAssignController { get; private set; }
public SettingsWindow SettingsWindow { get; set; }
@@ -110,6 +111,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
AutoAssignController = new AutoAssignController(InputManager, ViewModel);
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
this.ScalingChanged += OnScalingChanged;

View File

@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
@@ -8,7 +6,6 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using System;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
@@ -27,10 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public SettingsWindow()

View File

@@ -1,15 +1,26 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows
{
public abstract class StyleableAppWindow : AppWindow
{
public static async Task ShowAsync(StyleableAppWindow appWindow, Window owner = null)
{
#if DEBUG
appWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await appWindow.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableAppWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -36,6 +47,14 @@ namespace Ryujinx.Ava.UI.Windows
public abstract class StyleableWindow : Window
{
public static async Task ShowAsync(StyleableWindow window, Window owner = null)
{
#if DEBUG
window.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await window.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;

View File

@@ -389,6 +389,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public bool EnableMouse { get; set; }
/// <summary>
/// Enable or disable automatic controller assignment for players
/// </summary>
public bool EnableAutoAssign { get; set; }
/// <summary>
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
/// </summary>

View File

@@ -144,6 +144,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.EnableAutoAssign.Value = cff.EnableAutoAssign;
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
Hid.InputConfig.Value = cff.InputConfig ?? [];

View File

@@ -449,6 +449,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public ReactiveObject<bool> EnableMouse { get; private set; }
/// <summary>
/// Enable or disable auto-assigning controllers to players
/// </summary>
public ReactiveObject<bool> EnableAutoAssign { get; private set; }
/// <summary>
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
/// </summary>
@@ -475,6 +480,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
EnableKeyboard = new ReactiveObject<bool>();
EnableMouse = new ReactiveObject<bool>();
EnableAutoAssign = new ReactiveObject<bool>();
DisableInputWhenOutOfFocus = new ReactiveObject<bool>();
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
InputConfig = new ReactiveObject<List<InputConfig>>();

View File

@@ -133,6 +133,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
ShowConsole = UI.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
EnableAutoAssign = Hid.EnableAutoAssign,
DisableInputWhenOutOfFocus = Hid.DisableInputWhenOutOfFocus,
Hotkeys = Hid.Hotkeys,
InputConfig = Hid.InputConfig,
@@ -249,6 +250,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.WindowStartup.WindowMaximized.Value = false;
Hid.EnableKeyboard.Value = false;
Hid.EnableMouse.Value = false;
Hid.EnableAutoAssign.Value = false;
Hid.DisableInputWhenOutOfFocus.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys
{