From 6f113c417590b70d03036f71d7d720943177c6e7 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Tue, 15 Jul 2025 15:43:12 +0200 Subject: [PATCH 01/55] Update file locales.json --- assets/locales.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index c15dfb654..c0b2248f1 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -21754,8 +21754,8 @@ "de_DE": "", "el_GR": "", "en_US": "{0} missing update(s) removed", - "es_ES": "Se eliminaron {0} actualización(es) faltantes", - "fr_FR": "{0} mises à jour manquantes supprimées", + "es_ES": "Se eliminaron {0} actualización(es) faltante(s)", + "fr_FR": "{0} mise(s) à jour manquante(s) supprimée(s)", "he_IL": "", "it_IT": "{0} aggiornamento/i mancante/i rimosso/i", "ja_JP": "", From c5528d59a009781d9b25b0622c9f66fd2c8c2854 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Tue, 15 Jul 2025 15:46:52 +0200 Subject: [PATCH 02/55] Update file locales.json --- assets/locales.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales.json b/assets/locales.json index c0b2248f1..d85b8ff00 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -21704,7 +21704,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} missing downloadable content(s) removed", - "es_ES": "Se eliminaron {0} contenido(s) descargable(s) faltantes", + "es_ES": "Se eliminaron {0} contenido(s) descargable(s) faltante(s)", "fr_FR": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)", "he_IL": "", "it_IT": "{0} DLC mancante/i rimosso/i", From 385e9c869fb5af95b8019b023e185b1e1f459ee5 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 16 Jul 2025 15:20:04 +0200 Subject: [PATCH 03/55] Update file locales.json --- assets/locales.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index d85b8ff00..dc760c4fa 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -21530,7 +21530,7 @@ "el_GR": "", "en_US": "Bundled updates cannot be removed, only disabled.", "es_ES": "Las actualizaciones agrupadas no pueden ser eliminadas, solamente deshabilitadas.", - "fr_FR": "Les mises à jour incluses avec le jeu ne peuvent pas être supprimées mais peuvent être désactivées.", + "fr_FR": "Les mises à jour incluses avec le jeu ne peuvent être supprimées mais peuvent être désactivées.", "he_IL": "", "it_IT": "Gli aggiornamenti inclusi non possono essere rimossi, ma solo disabilitati.", "ja_JP": "", @@ -21605,7 +21605,7 @@ "el_GR": "", "en_US": "Bundled DLC cannot be removed, only disabled.", "es_ES": "", - "fr_FR": "Le DLC inclus ne peut pas être supprimé, seulement désactivé.", + "fr_FR": "Le DLC inclus ne peut être supprimé, seulement désactivé.", "he_IL": "", "it_IT": "I DLC inclusi non possono essere rimossi, ma solo disabilitati.", "ja_JP": "", @@ -21654,7 +21654,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} new downloadable content(s) added", - "es_ES": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)", + "es_ES": "Se agregó/aron {0} nuevo(s) contenido(s) descargable(s)", "fr_FR": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "he_IL": "", "it_IT": "{0} nuovo/i DLC aggiunto/i", @@ -21679,7 +21679,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} new downloadable content(s) added", - "es_ES": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)", + "es_ES": "Se agregó/aron {0} nuevo(s) contenido(s) descargable(s)", "fr_FR": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "he_IL": "", "it_IT": "{0} nuovo/i DLC aggiunto/i", @@ -21704,7 +21704,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} missing downloadable content(s) removed", - "es_ES": "Se eliminaron {0} contenido(s) descargable(s) faltante(s)", + "es_ES": "Se eliminó/aron {0} contenido(s) descargable(s) faltante(s)", "fr_FR": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)", "he_IL": "", "it_IT": "{0} DLC mancante/i rimosso/i", @@ -21729,7 +21729,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} new update(s) added", - "es_ES": "Se agregaron {0} nueva(s) actualización(es)", + "es_ES": "Se agregó/aron {0} nueva(s) actualización(es)", "fr_FR": "{0} nouvelle(s) mise(s) à jour ajoutée(s)", "he_IL": "", "it_IT": "{0} nuovo/i aggiornamento/i aggiunto/i", @@ -21754,7 +21754,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} missing update(s) removed", - "es_ES": "Se eliminaron {0} actualización(es) faltante(s)", + "es_ES": "Se eliminó/aron {0} actualización(es) faltante(s)", "fr_FR": "{0} mise(s) à jour manquante(s) supprimée(s)", "he_IL": "", "it_IT": "{0} aggiornamento/i mancante/i rimosso/i", From e3ea13bc4541785ff0e20b471a48e106f04b0518 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Mon, 25 Aug 2025 12:48:13 +0200 Subject: [PATCH 04/55] Update file locales.json --- assets/locales.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales.json b/assets/locales.json index 303c9ed02..5bf118071 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -15550,7 +15550,7 @@ "el_GR": "", "en_US": "Show incompatible Amiibo for this game", "es_ES": "Mostrar Amiibo incompatibles con este juego", - "fr_FR": "", + "fr_FR": "Montrer les Amiibo incompatibles avec ce jeu", "he_IL": "", "it_IT": "", "ja_JP": "", From 127d0c7ac139da6453cc6053466a455d3471e276 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 28 Aug 2025 14:10:51 +0200 Subject: [PATCH 05/55] Update file locales.json --- assets/locales.json | 192 ++++++++++++++++++++++---------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index ad5cc23b1..435341485 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -199,7 +199,7 @@ "de_DE": "Programme", "el_GR": "Λογισμικό", "en_US": "Software", - "es_ES": "", + "es_ES": null, "fr_FR": "Logiciel", "he_IL": "תוכנה", "it_IT": "", @@ -474,8 +474,8 @@ "de_DE": "", "el_GR": "", "en_US": "Open Screenshots Folder", - "es_ES": "", - "fr_FR": "Ouvrir le Dossier de Capture d'Écrans", + "es_ES": "Abrir la carpeta de capturas de pantalla", + "fr_FR": "Ouvrir le Dossier des Captures d’Écran", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -624,7 +624,7 @@ "de_DE": "Spiele ohne Benutzeroberfläche starten", "el_GR": "", "en_US": "Start Games with UI Hidden", - "es_ES": "", + "es_ES": "Iniziar juegos con la interfaz oculta", "fr_FR": "Démarrer les Jeux avec l’Interface Cachée", "he_IL": "", "it_IT": "", @@ -799,7 +799,7 @@ "de_DE": "Amiibo scannen (aus Bin-Datei)", "el_GR": "", "en_US": "Scan An Amiibo (from .bin)", - "es_ES": "", + "es_ES": "Escanear un Amiibo (desde un .bin)", "fr_FR": "Scanner un Amiibo (à partir d'un .bin)", "he_IL": "", "it_IT": "Scansiona un Amiibo (da file .bin)", @@ -899,7 +899,7 @@ "de_DE": "Schlüssel installieren", "el_GR": "", "en_US": "Install Keys", - "es_ES": "", + "es_ES": "Instalar claves", "fr_FR": "Installer des Clés", "he_IL": "", "it_IT": "Installa chiavi", @@ -1274,7 +1274,7 @@ "de_DE": "FAQ & Anleitungen", "el_GR": "", "en_US": "FAQ & Guides", - "es_ES": "", + "es_ES": "FAQ y Guías", "fr_FR": null, "he_IL": "", "it_IT": "Guide e domande frequenti", @@ -1299,7 +1299,7 @@ "de_DE": "FAQ & Fehlerbehebung Seite", "el_GR": "", "en_US": "FAQ & Troubleshooting Page", - "es_ES": "", + "es_ES": "FAQ y resolución de problemas", "fr_FR": "FAQ et Dépannage", "he_IL": "", "it_IT": "Domande frequenti e risoluzione dei problemi", @@ -1324,7 +1324,7 @@ "de_DE": "Öffnet die FAQ- und Fehlerbehebungsseite im offiziellen Ryujinx-Wiki", "el_GR": "", "en_US": "Opens the FAQ and Troubleshooting page on the official Ryujinx wiki", - "es_ES": "", + "es_ES": "Abre la FAQ y la página de resolución de problemas en la wiki oficial de Ryujinx", "fr_FR": "Ouvre la page FAQ et Dépannage sur le wiki officiel de Ryujinx", "he_IL": "", "it_IT": "Apre la pagina della wiki ufficiale di Ryujinx relativa alle domande frequenti e alla risoluzione dei problemi", @@ -1349,7 +1349,7 @@ "de_DE": "Setup- und Konfigurationsanleitung", "el_GR": "", "en_US": "Setup & Configuration Guide", - "es_ES": "", + "es_ES": "Guía de instalación y de configuración", "fr_FR": "Guide d'Installation et de Configuration", "he_IL": "", "it_IT": "Guida all'installazione e alla configurazione", @@ -1374,7 +1374,7 @@ "de_DE": "Öffnet die Setup- und Konfigurationsanleitung im offiziellen Ryujinx-Wiki", "el_GR": "", "en_US": "Opens the Setup & Configuration guide on the official Ryujinx wiki", - "es_ES": "", + "es_ES": "Abre el guía de instalación y configuración en la wiki oficial de Ryujinx", "fr_FR": "Ouvre le guide d'Installation et de Configuration sur le wiki officiel de Ryujinx", "he_IL": "", "it_IT": "Apre la guida all'installazione e alla configurazione presente nella wiki ufficiale di Ryujinx", @@ -1399,7 +1399,7 @@ "de_DE": "Multiplayer (LDN/LAN) Anleitung", "el_GR": "", "en_US": "Multiplayer (LDN/LAN) Guide", - "es_ES": "", + "es_ES": "Guía Multijugador (LDN/LAN)", "fr_FR": "Guide Multijoueur (LDN/LAN)", "he_IL": "", "it_IT": "Guida alla modalità multigiocatore (LDN/LAN)", @@ -1424,7 +1424,7 @@ "de_DE": "Öffnet die Multiplayer-Anleitung im offiziellen Ryujinx-Wiki", "el_GR": "", "en_US": "Opens the Multiplayer guide on the official Ryujinx wiki", - "es_ES": "", + "es_ES": "Abre el guía Multijugadores en la wiki oficial de Ryujinx", "fr_FR": "Ouvre le guide de Multijoueur sur le wiki officiel de Ryujinx", "he_IL": "", "it_IT": "Apre la guida alla modalità multigiocatore presente nella wiki ufficiale di Ryujinx", @@ -1574,7 +1574,7 @@ "de_DE": "Entwickelt von {0}", "el_GR": "", "en_US": "Developed by {0}", - "es_ES": "", + "es_ES": "Desarrollado por {0}", "fr_FR": "Développé par {0}", "he_IL": "", "it_IT": "", @@ -1949,7 +1949,7 @@ "de_DE": "Status: Aufsteigend", "el_GR": "Κατάσταση: Αναγόμενη", "en_US": "Status: Ascending", - "es_ES": "", + "es_ES": "Estato: Ascendente", "fr_FR": "Statut : Croissant", "he_IL": "סטטוס: עולה", "it_IT": "Stato: Crescente", @@ -1974,7 +1974,7 @@ "de_DE": "Status: Absteigend", "el_GR": "Κατάσταση: Καθοδική", "en_US": "Status: Descending", - "es_ES": "", + "es_ES": "Estado: Descendiente", "fr_FR": "Statut : Décroissant", "he_IL": "סטטוס: יורד", "it_IT": "Stato: Decrescente", @@ -1999,7 +1999,7 @@ "de_DE": "Kompatibilität:", "el_GR": "", "en_US": "Compatibility:", - "es_ES": "", + "es_ES": "Compatibilidad", "fr_FR": "Compatibilité :", "he_IL": "", "it_IT": "", @@ -2024,7 +2024,7 @@ "de_DE": "", "el_GR": "", "en_US": "Title ID:", - "es_ES": "", + "es_ES": "ID del titulo", "fr_FR": "ID du titre :", "he_IL": "", "it_IT": "", @@ -2049,7 +2049,7 @@ "de_DE": "", "el_GR": "", "en_US": "Hosted Games: {0}", - "es_ES": "", + "es_ES": "Juegos alojados: {0}", "fr_FR": "Jeux Hébergés : {0}", "he_IL": "", "it_IT": "", @@ -2074,7 +2074,7 @@ "de_DE": "", "el_GR": "", "en_US": "Online Players: {0}", - "es_ES": "", + "es_ES": "Jugadores en linea: {0}", "fr_FR": "Joueurs en Ligne : {0}", "he_IL": "", "it_IT": "", @@ -2549,7 +2549,7 @@ "de_DE": "", "el_GR": "", "en_US": "DLC RomFS", - "es_ES": "", + "es_ES": null, "fr_FR": "RomFS du DLC", "he_IL": "", "it_IT": "", @@ -2574,7 +2574,7 @@ "de_DE": "", "el_GR": "", "en_US": "Extract the RomFS from a selected DLC file", - "es_ES": "", + "es_ES": "Extraer el RomFS desde un archivo DLC", "fr_FR": "Extrait la RomFS du fichier DLC sélectionné", "he_IL": "", "it_IT": "", @@ -2699,7 +2699,7 @@ "de_DE": "", "el_GR": "", "en_US": "Create Custom Configuration", - "es_ES": "", + "es_ES": "Crear configuración personalizada", "fr_FR": "Créer une Configuration Personnalisée", "he_IL": "", "it_IT": "", @@ -2724,7 +2724,7 @@ "de_DE": "", "el_GR": "", "en_US": "Edit Custom Configuration", - "es_ES": "", + "es_ES": "Editar configuración personalizada", "fr_FR": "Modifier la Configuration Personnalisée", "he_IL": "", "it_IT": "", @@ -2849,7 +2849,7 @@ "de_DE": "", "el_GR": "", "en_US": "Edit your existing independent configuration for the selected game", - "es_ES": "", + "es_ES": "Editar su configuración independiente existente para el juego seleccionado", "fr_FR": "Modifie votre configuration indépendante existante pour le jeu sélectionné", "he_IL": "", "it_IT": "", @@ -2874,7 +2874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Show Game Info", - "es_ES": "", + "es_ES": "Mostrar la información del juego", "fr_FR": "Afficher les Informations du Jeu", "he_IL": "", "it_IT": "", @@ -3049,8 +3049,8 @@ "de_DE": "", "el_GR": "", "en_US": "Firmware Version: {0}", - "es_ES": "", - "fr_FR": "Version du Firmware: {0}", + "es_ES": "Versión del Firmware: {0}", + "fr_FR": "Version du Firmware : {0}", "he_IL": "", "it_IT": "Versione firmware: {0}", "ja_JP": "", @@ -3349,7 +3349,7 @@ "de_DE": "Allgemein", "el_GR": "Γενικά", "en_US": "General", - "es_ES": "", + "es_ES": null, "fr_FR": "Général", "he_IL": "כללי", "it_IT": "Generali", @@ -3399,8 +3399,8 @@ "de_DE": "", "el_GR": "", "en_US": "Check for Updates:", - "es_ES": "", - "fr_FR": "Vérifier les Mises à Jour : ", + "es_ES": "Buscar actualizaciones:", + "fr_FR": "Vérifier les Mises à Jour :", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -3424,7 +3424,7 @@ "de_DE": "", "el_GR": "", "en_US": "Off", - "es_ES": "", + "es_ES": "Desactivado", "fr_FR": "Désactivé", "he_IL": "", "it_IT": "", @@ -3449,7 +3449,7 @@ "de_DE": "", "el_GR": "", "en_US": "Prompt", - "es_ES": "", + "es_ES": "Al inicio", "fr_FR": "Demande", "he_IL": "", "it_IT": "", @@ -3474,7 +3474,7 @@ "de_DE": "", "el_GR": "", "en_US": "Background", - "es_ES": "", + "es_ES": "Cambiar fondo", "fr_FR": "En arrière-plan", "he_IL": "", "it_IT": "", @@ -3524,7 +3524,7 @@ "de_DE": "", "el_GR": "", "en_US": "Do Nothing", - "es_ES": "", + "es_ES": "No Hacer Nada", "fr_FR": "Ne Rien Faire", "he_IL": "", "it_IT": "", @@ -3549,7 +3549,7 @@ "de_DE": "", "el_GR": "", "en_US": "Block Input", - "es_ES": "", + "es_ES": "Bloquear entrada", "fr_FR": "Bloquer la Saisie", "he_IL": "", "it_IT": "", @@ -3574,7 +3574,7 @@ "de_DE": "", "el_GR": "", "en_US": "Mute Volume", - "es_ES": "", + "es_ES": "Silenciar el volumen", "fr_FR": "Couper le Son", "he_IL": "", "it_IT": "", @@ -3599,7 +3599,7 @@ "de_DE": "", "el_GR": "", "en_US": "Block Input & Mute Volume", - "es_ES": "", + "es_ES": "Bloquear entrada y silenciar el volumen", "fr_FR": "Bloquer la Saisie & Couper le Son", "he_IL": "", "it_IT": "", @@ -3624,7 +3624,7 @@ "de_DE": "", "el_GR": "", "en_US": "Pause Emulation", - "es_ES": "", + "es_ES": "Pausar emulación", "fr_FR": "Pauser l'Émulation", "he_IL": "", "it_IT": "", @@ -3724,7 +3724,7 @@ "de_DE": "", "el_GR": "", "en_US": "Show Original UI Style (Requires Restart)", - "es_ES": "", + "es_ES": "Mostrar la interfaz original (requiere un reincio)", "fr_FR": "Afficher le Style d’Interface Utilisateur Original (Redémarrage requis)", "he_IL": "", "it_IT": "", @@ -4149,7 +4149,7 @@ "de_DE": "Australien", "el_GR": "Αυστραλία", "en_US": "Australia", - "es_ES": "", + "es_ES": null, "fr_FR": "Australie", "he_IL": "אוסטרליה", "it_IT": "", @@ -4174,7 +4174,7 @@ "de_DE": "", "el_GR": "Κίνα", "en_US": "China", - "es_ES": "", + "es_ES": null, "fr_FR": "Chine", "he_IL": "סין", "it_IT": "Cina", @@ -4749,7 +4749,7 @@ "de_DE": "", "el_GR": "", "en_US": "Match System Time", - "es_ES": "", + "es_ES": "Usar hora del systema ", "fr_FR": "Synchroniser avec l’heure du système", "he_IL": "", "it_IT": "", @@ -4824,7 +4824,7 @@ "de_DE": "", "el_GR": "", "en_US": "Turbo Mode Multiplier:", - "es_ES": "", + "es_ES": "Multiplicator del Modo Turbo:", "fr_FR": "Multiplicateur du Mode Turbo :", "he_IL": "", "it_IT": "", @@ -4849,7 +4849,7 @@ "de_DE": "", "el_GR": "", "en_US": "The Turbo mode multiplier target value.\n\nLeave at 200 if unsure.", - "es_ES": "", + "es_ES": "Valor objetivo del multiplicador del modo turbo.\n\nDéjar en 200 si no está seguro.", "fr_FR": "La valeur souhaitée du multiplicateur du mode Turbo.\n\nLaissez à 200 si vous n'êtes pas sûr.", "he_IL": "", "it_IT": "", @@ -4874,7 +4874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Turbo Mode is an emulator feature which effectively causes speed up or slow down when a game is not frame-rate sensitive.\nYou can toggle this feature in-game with a hotkey, configurable in Ryujinx Keyboard Hotkeys settings.\n\nLeave at 200 if unsure.", - "es_ES": "", + "es_ES": "El modo turbo es una función del emulador que permite acelerar o ralentizar el juego cuando no depende de la tasa de frames.\nPuedes activar o desactivar esta función durante el juego usando una tecla rápida, configurable en los ajustes de Teclas rápidas de Ryujinx.\n\nDéjar en 200 si no está seguro", "fr_FR": "Le Mode Turbo est une fonctionnalité de l’émulateur qui permet d’accélérer ou de ralentir le jeu lorsque celui-ci n’est pas sensible au taux de rafraîchissement.\nVous pouvez activer ou désactiver cette fonction en jeu via un raccourci clavier, configurable dans les paramètres des raccourcis clavier de Ryujinx.\n\nLaissez à 200 si vous n’êtes pas sûr.", "he_IL": "", "it_IT": "", @@ -5049,7 +5049,7 @@ "de_DE": "", "el_GR": "Μικροδιορθώσεις", "en_US": "Hacks", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "האצות", "it_IT": "Espedienti", @@ -5124,7 +5124,7 @@ "de_DE": "", "el_GR": "", "en_US": "4GiB", - "es_ES": "", + "es_ES": null, "fr_FR": "4GiO", "he_IL": "", "it_IT": "", @@ -5149,7 +5149,7 @@ "de_DE": "", "el_GR": "", "en_US": "6GiB", - "es_ES": "", + "es_ES": null, "fr_FR": "6GiO", "he_IL": "", "it_IT": "", @@ -5174,7 +5174,7 @@ "de_DE": "", "el_GR": "", "en_US": "8GiB", - "es_ES": "", + "es_ES": null, "fr_FR": "8GiO", "he_IL": "", "it_IT": "", @@ -5199,7 +5199,7 @@ "de_DE": "", "el_GR": "", "en_US": "12GiB", - "es_ES": "", + "es_ES": null, "fr_FR": "12GiO", "he_IL": "", "it_IT": "", @@ -6124,7 +6124,7 @@ "de_DE": "", "el_GR": "", "en_US": "Enable UI Logs", - "es_ES": "", + "es_ES": "Activar los registros UI", "fr_FR": "Activer les Journaux de l'Interface Utilisateur", "he_IL": "", "it_IT": "", @@ -6549,7 +6549,7 @@ "de_DE": "", "el_GR": "", "en_US": "Reset Settings", - "es_ES": "", + "es_ES": "Restablecer la configuración", "fr_FR": "Réinitialiser les Paramètres", "he_IL": "", "it_IT": "", @@ -6574,7 +6574,7 @@ "de_DE": "", "el_GR": "", "en_US": "I want to reset my settings.", - "es_ES": "", + "es_ES": "Quiero restablecer mi configuración", "fr_FR": "Je veux réinitialiser mes paramètres.", "he_IL": "", "it_IT": "", @@ -6949,7 +6949,7 @@ "de_DE": "", "el_GR": "", "en_US": "Configuration found:\n\nName:\t{0}\nGUID:\t{1}\n\n Waiting for controller connection...", - "es_ES": "", + "es_ES": "Configuración encontrada:\n\nNombre:\t{0}\nGUID:\t{1}\n\nEsperando la conexión del controlador...", "fr_FR": "Configuration trouvée:\n\nNom:\t{0}\nGUID:\t{1}\n\n En attente de connexion de la manette...", "he_IL": "", "it_IT": "", @@ -8574,7 +8574,7 @@ "de_DE": "", "el_GR": "", "en_US": "LED", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -8599,7 +8599,7 @@ "de_DE": "", "el_GR": "", "en_US": "Disable", - "es_ES": "", + "es_ES": "Desactivar", "fr_FR": "Désactiver", "he_IL": "", "it_IT": "", @@ -8624,7 +8624,7 @@ "de_DE": "", "el_GR": "", "en_US": "Rainbow", - "es_ES": "", + "es_ES": "Arcoíris", "fr_FR": "Arc-en-ciel", "he_IL": "", "it_IT": "", @@ -8649,7 +8649,7 @@ "de_DE": "", "el_GR": "", "en_US": "Rainbow Speed", - "es_ES": "", + "es_ES": "Velocidad del arcoíris", "fr_FR": "Vitesse de l'Arc-en-ciel", "he_IL": "", "it_IT": "", @@ -8674,7 +8674,7 @@ "de_DE": "", "el_GR": "", "en_US": "Color", - "es_ES": "", + "es_ES": null, "fr_FR": "Couleur", "he_IL": "", "it_IT": "", @@ -8824,7 +8824,7 @@ "de_DE": "", "el_GR": "", "en_US": "Ctrl Left", - "es_ES": "", + "es_ES": "Ctrl izquierdo", "fr_FR": "Ctrl Gauche", "he_IL": "", "it_IT": "Ctrl sinistro", @@ -8849,7 +8849,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌃ Left", - "es_ES": "", + "es_ES": "⌃ Izquierdo", "fr_FR": "⌃ Gauche", "he_IL": "", "it_IT": "⌃ sinistro", @@ -8874,7 +8874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Ctrl Right", - "es_ES": "", + "es_ES": "Ctrl Derecho", "fr_FR": "Ctrl Droite", "he_IL": "", "it_IT": "Ctrl destro", @@ -8899,7 +8899,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌃ Right", - "es_ES": "", + "es_ES": "⌃ Derecho", "fr_FR": "⌃ Droite", "he_IL": "", "it_IT": "⌃ destro", @@ -8924,7 +8924,7 @@ "de_DE": "", "el_GR": "", "en_US": "Alt Left", - "es_ES": "", + "es_ES": "Alt Izquierdo", "fr_FR": "Alt Gauche", "he_IL": "", "it_IT": "Alt sinistro", @@ -8949,7 +8949,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌥ Left", - "es_ES": "", + "es_ES": "⌥ Izquierdo", "fr_FR": "⌥ Gauche", "he_IL": "", "it_IT": "⌥ sinistro", @@ -8974,7 +8974,7 @@ "de_DE": "", "el_GR": "", "en_US": "Alt Right", - "es_ES": "", + "es_ES": "Alt Derecho", "fr_FR": "Alt Droite", "he_IL": "", "it_IT": "Alt destro", @@ -8999,7 +8999,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌥ Right", - "es_ES": "", + "es_ES": "⌥ Derecho", "fr_FR": "⌥ Droite", "he_IL": "", "it_IT": "⌥ destro", @@ -9024,7 +9024,7 @@ "de_DE": "", "el_GR": "", "en_US": "⊞ Left", - "es_ES": "", + "es_ES": "⊞ Izquierdo", "fr_FR": "⊞ Gauche", "he_IL": "", "it_IT": "⊞ sinistro", @@ -9049,7 +9049,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌘ Left", - "es_ES": "", + "es_ES": "⌘ Izqierdo", "fr_FR": "⌘ Gauche", "he_IL": "", "it_IT": "⌘ sinistro", @@ -9074,7 +9074,7 @@ "de_DE": "", "el_GR": "", "en_US": "⊞ Right", - "es_ES": "", + "es_ES": "⊞ Derecho", "fr_FR": "⊞ Droite", "he_IL": "", "it_IT": "⊞ destro", @@ -9099,7 +9099,7 @@ "de_DE": "", "el_GR": "", "en_US": "⌘ Right", - "es_ES": "", + "es_ES": "⌘ Derecho", "fr_FR": "⌘ Droite", "he_IL": "", "it_IT": "⌘ destro", @@ -9124,7 +9124,7 @@ "de_DE": "", "el_GR": "", "en_US": "Menu", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "Menù", @@ -9149,7 +9149,7 @@ "de_DE": "", "el_GR": "", "en_US": "Up", - "es_ES": "", + "es_ES": "Arriba", "fr_FR": "Haut", "he_IL": "", "it_IT": "Su", @@ -9174,7 +9174,7 @@ "de_DE": "", "el_GR": "", "en_US": "Down", - "es_ES": "", + "es_ES": "Abajo", "fr_FR": "Bas", "he_IL": "", "it_IT": "Giù", @@ -9199,7 +9199,7 @@ "de_DE": "", "el_GR": "", "en_US": "Left", - "es_ES": "", + "es_ES": "Izquierda", "fr_FR": "Gauche", "he_IL": "", "it_IT": "Sinistra", @@ -9224,7 +9224,7 @@ "de_DE": "", "el_GR": "", "en_US": "Right", - "es_ES": "", + "es_ES": "Derecha", "fr_FR": "Droite", "he_IL": "", "it_IT": "Destra", @@ -9274,7 +9274,7 @@ "de_DE": "", "el_GR": "", "en_US": "Escape", - "es_ES": "", + "es_ES": "Esc", "fr_FR": "Esc", "he_IL": "", "it_IT": "Esc", @@ -9299,7 +9299,7 @@ "de_DE": "", "el_GR": "", "en_US": "Space", - "es_ES": "", + "es_ES": "Espacio", "fr_FR": "Espace", "he_IL": "", "it_IT": "Spazio", @@ -9324,7 +9324,7 @@ "de_DE": "", "el_GR": "", "en_US": "Tab", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -9349,7 +9349,7 @@ "de_DE": "", "el_GR": "", "en_US": "Backspace", - "es_ES": "", + "es_ES": "Retroceso", "fr_FR": "Retour arrière", "he_IL": "", "it_IT": "", @@ -9374,7 +9374,7 @@ "de_DE": "", "el_GR": "", "en_US": "Insert", - "es_ES": "", + "es_ES": "Inser", "fr_FR": "Inser", "he_IL": "", "it_IT": "Ins", @@ -9399,7 +9399,7 @@ "de_DE": "", "el_GR": "", "en_US": "Delete", - "es_ES": "", + "es_ES": "Supr", "fr_FR": "Suppr", "he_IL": "", "it_IT": "Canc", @@ -9499,7 +9499,7 @@ "de_DE": "", "el_GR": "", "en_US": "End", - "es_ES": "", + "es_ES": "Fin", "fr_FR": "Fin", "he_IL": "", "it_IT": "Fine", @@ -9599,7 +9599,7 @@ "de_DE": "", "el_GR": "", "en_US": "Pause", - "es_ES": "", + "es_ES": "Pausa", "fr_FR": null, "he_IL": "", "it_IT": "Pausa", @@ -9674,7 +9674,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 0", - "es_ES": "", + "es_ES": "Num. 0", "fr_FR": "Num. 0", "he_IL": "", "it_IT": "Tast. num. 0", @@ -9699,7 +9699,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 1", - "es_ES": "", + "es_ES": "Num. 1", "fr_FR": "Num. 1", "he_IL": "", "it_IT": "Tast. num. 1", @@ -9724,7 +9724,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 2", - "es_ES": "", + "es_ES": "Num. 2", "fr_FR": "Num. 2", "he_IL": "", "it_IT": "Tast. num. 2", @@ -9749,7 +9749,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 3", - "es_ES": "", + "es_ES": "Num. 3", "fr_FR": "Num. 3", "he_IL": "", "it_IT": "Tast. num. 3", @@ -9774,7 +9774,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 4", - "es_ES": "", + "es_ES": "Num. 4", "fr_FR": "Num. 4", "he_IL": "", "it_IT": "Tast. num. 4", @@ -9799,7 +9799,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 5", - "es_ES": "", + "es_ES": "Num. 5", "fr_FR": "Num. 5", "he_IL": "", "it_IT": "Tast. num. 5", @@ -9824,7 +9824,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 6", - "es_ES": "", + "es_ES": "Num. 6", "fr_FR": "Num. 6", "he_IL": "", "it_IT": "Tast. num. 6", @@ -9849,7 +9849,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 7", - "es_ES": "", + "es_ES": "Num. 7", "fr_FR": "Num. 7", "he_IL": "", "it_IT": "Tast. num. 7", @@ -9874,7 +9874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 8", - "es_ES": "", + "es_ES": "Num. 8", "fr_FR": "Num. 8", "he_IL": "", "it_IT": "Tast. num. 8", @@ -9899,7 +9899,7 @@ "de_DE": "", "el_GR": "", "en_US": "Keypad 9", - "es_ES": "", + "es_ES": "Num. 9", "fr_FR": "Num. 9", "he_IL": "", "it_IT": "Tast. num. 9", @@ -10624,7 +10624,7 @@ "de_DE": "", "el_GR": "", "en_US": "Unbound", - "es_ES": "", + "es_ES": "Sin asignar", "fr_FR": "Non Attribuée", "he_IL": "", "it_IT": "Non assegnato", From fc62ae41ae6da3913a7713a3d15dda29cd5a2498 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 28 Aug 2025 14:52:22 +0200 Subject: [PATCH 06/55] Update file locales.json --- assets/locales.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 435341485..3fcdc01da 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -1999,7 +1999,7 @@ "de_DE": "Kompatibilität:", "el_GR": "", "en_US": "Compatibility:", - "es_ES": "Compatibilidad", + "es_ES": "Compatibilidad:", "fr_FR": "Compatibilité :", "he_IL": "", "it_IT": "", @@ -2024,7 +2024,7 @@ "de_DE": "", "el_GR": "", "en_US": "Title ID:", - "es_ES": "ID del titulo", + "es_ES": "ID del titulo:", "fr_FR": "ID du titre :", "he_IL": "", "it_IT": "", @@ -4749,7 +4749,7 @@ "de_DE": "", "el_GR": "", "en_US": "Match System Time", - "es_ES": "Usar hora del systema ", + "es_ES": "Usar hora del systema", "fr_FR": "Synchroniser avec l’heure du système", "he_IL": "", "it_IT": "", From 59eba8f38be5d4170b97157b184cd4b7df08fe6b Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 28 Aug 2025 15:48:41 +0200 Subject: [PATCH 07/55] Update file locales.json --- assets/locales.json | 198 ++++++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 3fcdc01da..6996954a3 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -10074,7 +10074,7 @@ "de_DE": "", "el_GR": "", "en_US": "0", - "es_ES": "", + "es_ES": null, "fr_FR": "à", "he_IL": "", "it_IT": "", @@ -10099,7 +10099,7 @@ "de_DE": "", "el_GR": "", "en_US": "1", - "es_ES": "", + "es_ES": null, "fr_FR": "&", "he_IL": "", "it_IT": "", @@ -10124,7 +10124,7 @@ "de_DE": "", "el_GR": "", "en_US": "2", - "es_ES": "", + "es_ES": null, "fr_FR": "é", "he_IL": "", "it_IT": "", @@ -10149,7 +10149,7 @@ "de_DE": "", "el_GR": "", "en_US": "3", - "es_ES": "", + "es_ES": null, "fr_FR": "\"", "he_IL": "", "it_IT": "", @@ -10174,7 +10174,7 @@ "de_DE": "", "el_GR": "", "en_US": "4", - "es_ES": "", + "es_ES": null, "fr_FR": "'", "he_IL": "", "it_IT": "", @@ -10198,7 +10198,7 @@ "ar_SA": "٥", "de_DE": "", "el_GR": "", - "en_US": "5", + "en_US": null, "es_ES": "", "fr_FR": "(", "he_IL": "", @@ -10224,7 +10224,7 @@ "de_DE": "", "el_GR": "", "en_US": "6", - "es_ES": "", + "es_ES": null, "fr_FR": "-", "he_IL": "", "it_IT": "", @@ -10249,7 +10249,7 @@ "de_DE": "", "el_GR": "", "en_US": "7", - "es_ES": "", + "es_ES": null, "fr_FR": "è", "he_IL": "", "it_IT": "", @@ -10274,7 +10274,7 @@ "de_DE": "", "el_GR": "", "en_US": "8", - "es_ES": "", + "es_ES": null, "fr_FR": "_", "he_IL": "", "it_IT": "", @@ -10299,7 +10299,7 @@ "de_DE": "", "el_GR": "", "en_US": "9", - "es_ES": "", + "es_ES": null, "fr_FR": "ç", "he_IL": "", "it_IT": "", @@ -10799,7 +10799,7 @@ "de_DE": "", "el_GR": "", "en_US": "Up", - "es_ES": "", + "es_ES": "Arriba", "fr_FR": "Haut", "he_IL": "", "it_IT": "Su", @@ -10824,7 +10824,7 @@ "de_DE": "", "el_GR": "", "en_US": "Down", - "es_ES": "", + "es_ES": "Abajo", "fr_FR": "Bas", "he_IL": "", "it_IT": "Giù", @@ -10849,7 +10849,7 @@ "de_DE": "", "el_GR": "", "en_US": "Left", - "es_ES": "", + "es_ES": "Izquierda", "fr_FR": "Gauche", "he_IL": "", "it_IT": "Sinistra", @@ -10874,7 +10874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Right", - "es_ES": "", + "es_ES": "Derecha", "fr_FR": "Droite", "he_IL": "", "it_IT": "Destra", @@ -10949,7 +10949,7 @@ "de_DE": "", "el_GR": "", "en_US": "Guide", - "es_ES": "", + "es_ES": "Guía", "fr_FR": null, "he_IL": "", "it_IT": "Guida", @@ -11224,7 +11224,7 @@ "de_DE": "", "el_GR": "", "en_US": "Left Stick", - "es_ES": "", + "es_ES": "Joystick Izquierdo", "fr_FR": "Joystick Gauche", "he_IL": "", "it_IT": "Levetta sinistra", @@ -11249,7 +11249,7 @@ "de_DE": "", "el_GR": "", "en_US": "Right Stick", - "es_ES": "", + "es_ES": "Joystick Derecho", "fr_FR": "Joystick Droite", "he_IL": "", "it_IT": "Levetta destra", @@ -11674,7 +11674,7 @@ "de_DE": "", "el_GR": "", "en_US": "Cancelling", - "es_ES": "", + "es_ES": "Anulación en curso", "fr_FR": "Annulation en Cours", "he_IL": "", "it_IT": "Annullamento in corso", @@ -11699,7 +11699,7 @@ "de_DE": "", "el_GR": "", "en_US": "Close", - "es_ES": "", + "es_ES": "Cerrar", "fr_FR": "Fermer", "he_IL": "", "it_IT": "Chiudi", @@ -12149,7 +12149,7 @@ "de_DE": "", "el_GR": "", "en_US": "Auto", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "Automatico", @@ -12424,7 +12424,7 @@ "de_DE": "{0} - Fehler", "el_GR": "{0} - Σφάλμα", "en_US": "{0} - Error", - "es_ES": "", + "es_ES": null, "fr_FR": "{0} - Erreur", "he_IL": "{0} - שגיאה", "it_IT": "{0} - Errore", @@ -12849,7 +12849,7 @@ "de_DE": "", "el_GR": "", "en_US": "Failed to convert the Ryujinx version received from the update server.", - "es_ES": "", + "es_ES": "Error al convertir la versión de Ryujinx recibida del servidor de actualizaciones.", "fr_FR": "Échec de la conversion de la version de Ryujinx reçue du serveur de Mise à Jour.", "he_IL": "", "it_IT": "", @@ -12999,7 +12999,7 @@ "de_DE": "", "el_GR": "", "en_US": "Show Changelog", - "es_ES": "", + "es_ES": "Mostrar Changelog", "fr_FR": "Afficher Changelog", "he_IL": "", "it_IT": "Mostra il changelog", @@ -13599,7 +13599,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0}: {1}", - "es_ES": "", + "es_ES": null, "fr_FR": "{0} : {1}", "he_IL": "", "it_IT": "", @@ -13649,7 +13649,7 @@ "de_DE": "Ryujinx Fehler ({0})", "el_GR": "Σφάλμα Ryujinx ({0})", "en_US": "Ryujinx Error ({0})", - "es_ES": "", + "es_ES": "Error Ryujinx ({0})", "fr_FR": "Erreur Ryujinx ({0})", "he_IL": "שגיאת Ryujinx ({0})", "it_IT": "Errore di Ryujinx ({0})", @@ -13674,7 +13674,7 @@ "de_DE": "Amiibo-API", "el_GR": "API για Amiibo.", "en_US": "Amiibo API", - "es_ES": "", + "es_ES": "API Amiibo", "fr_FR": "API Amiibo", "he_IL": "ממשק תכנות אמיבו", "it_IT": "API Amiibo", @@ -13924,7 +13924,7 @@ "de_DE": "", "el_GR": "", "en_US": "You are about to purge all PPTC data from:\n\n{0}\n\nAre you sure you want to proceed?", - "es_ES": "", + "es_ES": "Está a punto de eliminar todos los datos PPTC de:\n\n{0}\n\n¿Está seguro de que desea continuar?", "fr_FR": "Vous êtes sur le point de supprimer toutes les données PPTC de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", "he_IL": "", "it_IT": "", @@ -15124,7 +15124,7 @@ "de_DE": "Nein", "el_GR": "Όχι", "en_US": "No", - "es_ES": "", + "es_ES": null, "fr_FR": "Non", "he_IL": "לא", "it_IT": "", @@ -15374,7 +15374,7 @@ "de_DE": "", "el_GR": "", "en_US": "Ryujinx is an emulator for the Nintendo Switch™ 1.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitLab or Discord.", - "es_ES": "", + "es_ES": "Ryujinx es un emulador para Nintendo Switch™ 1. Obtén todas las novedades en nuestro Discord. Los desarrolladores interesados en contribuir pueden obtener más información en nuestro GitLab o Discord.", "fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez les dernières nouvelles sur notre Discord.\nLes développeurs souhaitant contribuer peuvent en savoir plus sur notre GitLab ou Discord.", "he_IL": "", "it_IT": "Ryujinx è un emulatore della console Nintendo Switch™ 1.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitLab.", @@ -15424,7 +15424,7 @@ "de_DE": "", "el_GR": "", "en_US": "Formerly Maintained By:", - "es_ES": "", + "es_ES": "Anteriormente mantenido por:", "fr_FR": "Anciennement Maintenu par :", "he_IL": "", "it_IT": "Mantenuto in precedenza da:", @@ -16374,7 +16374,7 @@ "de_DE": "", "el_GR": "", "en_US": "Sync System Time to match your PC's current date & time.", - "es_ES": "", + "es_ES": "Sincronizar la hora del sistema con la fecha y hora actual de tu PC.", "fr_FR": "Synchronise l’Heure Système avec la date et l’heure actuelle de votre PC.", "he_IL": "", "it_IT": "Sincronizza data e ora del sistema con quelle del PC.", @@ -16699,7 +16699,7 @@ "de_DE": "", "el_GR": "", "en_US": "The Controller Applet dialog will not appear if the gamepad is disconnected while an application is running.\n\nLeave OFF if unsure.", - "es_ES": "", + "es_ES": "El cuadro de diálogo del Applet de controladores no aparecerá si el gamepad se desconecta mientras una aplicación está en ejecución.\n\nDéjalo DESACTIVADO si no estás seguro.", "fr_FR": "La fenêtre de l’Applet Manette ne s’affichera pas si la manette est déconnectée pendant l’exécution d’une application\n\nLaissez DÉSACTIVÉ si vous n’êtes pas sûr.", "he_IL": "", "it_IT": "", @@ -17174,7 +17174,7 @@ "de_DE": "", "el_GR": "", "en_US": "Prints Avalonia (UI) log messages in the console.", - "es_ES": "", + "es_ES": "Muestra los mensajes del registro de Avalonia (UI) en la consola.", "fr_FR": "Affiche les journaux Avalonia (Interface Utilisateur) dans la console.", "he_IL": "", "it_IT": "", @@ -17524,7 +17524,7 @@ "de_DE": "", "el_GR": "Επεξεργαστής", "en_US": "CPU", - "es_ES": "", + "es_ES": "Procesador", "fr_FR": "Processeur", "he_IL": "מעבד", "it_IT": "", @@ -17699,7 +17699,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} FPS ({1}ms)", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -17724,7 +17724,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} FPS ({1}ms), Turbo ({2}%)", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -17749,7 +17749,7 @@ "de_DE": "", "el_GR": "", "en_US": "Update Available!", - "es_ES": "", + "es_ES": "Actualizacion disponible!", "fr_FR": "Mise à jour Disponible !", "he_IL": "", "it_IT": "", @@ -18449,7 +18449,7 @@ "de_DE": "", "el_GR": "{0} - Πληροφορίες", "en_US": "{0} - Info", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "{0} - מידע", "it_IT": "{0} - Informazioni", @@ -19549,7 +19549,7 @@ "de_DE": "", "el_GR": "", "en_US": "LED Settings", - "es_ES": "", + "es_ES": "Configuración LED", "fr_FR": "Paramètres LED", "he_IL": "", "it_IT": "", @@ -19649,7 +19649,7 @@ "de_DE": "", "el_GR": "", "en_US": "Amiibo", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "אמיבו", "it_IT": "", @@ -20474,7 +20474,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0:n0} Mb", - "es_ES": "", + "es_ES": null, "fr_FR": "{0:n0} Mo", "he_IL": "", "it_IT": "{0:n0} MB", @@ -20749,7 +20749,7 @@ "de_DE": "", "el_GR": "", "en_US": "Bundled DLC cannot be removed, only disabled.", - "es_ES": "", + "es_ES": "El DLC incluido no se puede eliminar, solo desactivar.", "fr_FR": "Les DLC inclus ne peuvent pas être supprimé, seulement désactivé.", "he_IL": "", "it_IT": "I DLC inclusi non possono essere rimossi, ma solo disabilitati.", @@ -20774,8 +20774,8 @@ "de_DE": "", "el_GR": "", "en_US": "{0} DLC(s) available", - "es_ES": "", - "fr_FR": "{0} DLC(s) disponibles", + "es_ES": "{0} DLC(s) disponible(s)", + "fr_FR": "{0} DLC(s) disponible(s)", "he_IL": "{0} הרחבות משחק", "it_IT": "{0} DLC disponibile/i", "ja_JP": "", @@ -20924,7 +20924,7 @@ "de_DE": "", "el_GR": "", "en_US": "{0} Mod(s)", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "{0} מוד(ים)", "it_IT": "{0} mod", @@ -22124,7 +22124,7 @@ "de_DE": "", "el_GR": "", "en_US": "Bilinear", - "es_ES": "", + "es_ES": null, "fr_FR": "Bilinéaire", "he_IL": "", "it_IT": "Bilineare", @@ -22199,7 +22199,7 @@ "de_DE": "", "el_GR": "", "en_US": "Area", - "es_ES": "", + "es_ES": "Área", "fr_FR": "Zone", "he_IL": "", "it_IT": "", @@ -22349,7 +22349,7 @@ "de_DE": "", "el_GR": "Oύλτρα SMAA", "en_US": "SMAA Ultra", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "SMAA אולטרה", "it_IT": "", @@ -22524,7 +22524,7 @@ "de_DE": "", "el_GR": "", "en_US": "View Changelog", - "es_ES": "", + "es_ES": "Ver el Changelog", "fr_FR": "Afficher les Changements", "he_IL": "", "it_IT": "", @@ -22999,7 +22999,7 @@ "de_DE": "", "el_GR": "", "en_US": "VSync:", - "es_ES": "", + "es_ES": null, "fr_FR": "VSync :", "he_IL": "", "it_IT": "", @@ -23024,7 +23024,7 @@ "de_DE": "", "el_GR": "", "en_US": "Enable Custom Refresh Rate (Experimental)", - "es_ES": "", + "es_ES": "Activar frecuencia de actualización personalizada (Experimental)", "fr_FR": "Activer le taux de rafraîchissement customisé (Expérimental)", "he_IL": "", "it_IT": "Attiva la frequenza di aggiornamento personalizzata (sperimentale)", @@ -23049,7 +23049,7 @@ "de_DE": "", "el_GR": "", "en_US": "Switch", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -23074,7 +23074,7 @@ "de_DE": "", "el_GR": "", "en_US": "Unbounded", - "es_ES": "", + "es_ES": "Sin límite", "fr_FR": "Sans Limite", "he_IL": "", "it_IT": "Nessun limite", @@ -23099,7 +23099,7 @@ "de_DE": "", "el_GR": "", "en_US": "Custom Refresh Rate", - "es_ES": "", + "es_ES": "Frecuencia de actualización personalizada", "fr_FR": "Taux de Rafraîchissement Customisé", "he_IL": "", "it_IT": "Frequenza di aggiornamento personalizzata", @@ -23124,7 +23124,7 @@ "de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. \"Unbounded\" ist eine unbegrenzte Bildwiederholfrequenz.", "el_GR": "", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.", - "es_ES": "", + "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite.", "fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité.", "he_IL": "", "it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento.", @@ -23149,7 +23149,7 @@ "de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. „Unbounded“ ist eine unbegrenzte Bildwiederholfrequenz. „Benutzerdefinierte Bildwiederholfrequenz“ emuliert die angegebene benutzerdefinierte Bildwiederholfrequenz.", "el_GR": "", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.", - "es_ES": "", + "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite. ‘Frecuencia de actualización personalizada’ emula la frecuencia de actualización personalizada especificada.", "fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité. 'Taux de Rafraîchissement Customisé' émule le taux de rafraîchissement spécifié.", "he_IL": "", "it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento. \"Frequenza di aggiornamento personalizzata\" emula la frequenza di aggiornamento specificata.", @@ -23174,7 +23174,7 @@ "de_DE": "Ermöglicht es dem Benutzer, eine emulierte Bildwiederholfrequenz festzulegen. In einigen Titeln kann dies die Geschwindigkeit der Spiel-Logik erhöhen oder verringern. In anderen Titeln kann dies dazu führen, dass die FPS auf ein Vielfaches der Bildwiederholfrequenz begrenzt werden oder zu unvorhersehbarem Verhalten führen. Dies ist eine experimentelle Funktion, ohne Garantien dafür, wie sich das Gameplay auswirkt. \n\nLassen Sie diese Option deaktiviert, wenn Sie sich nicht sicher sind.", "el_GR": "", "en_US": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.", - "es_ES": "", + "es_ES": "Permite al usuario especificar una frecuencia de actualización emulada. En algunos títulos, esto puede acelerar o ralentizar la lógica del juego. En otros títulos, puede permitir limitar los FPS a algún múltiplo de la frecuencia de actualización, o provocar un comportamiento impredecible. Esta es una función experimental, sin garantías sobre cómo se verá afectada la jugabilidad.\n\nDéjalo DESACTIVADO si no estás seguro.", "fr_FR": "Permet à l'utilisateur de spécifier un taux de rafraîchissement émulé. Dans certains jeux, ceci pourrait accélérer ou ralentir le taux de logique du gameplay. Dans d'autre titres, cela permettrait limiter le FPS à un multiple du taux de rafraîchissement, ou conduire à un comportement imprévisible. Ceci est une fonctionnalité expérimentale, avec aucune garanties pour comment le gameplay sera affecté. \n\nLaisser DÉSACTIVER en cas de doute.", "he_IL": "", "it_IT": "Consente all'utente di specificare una frequenza di aggiornamento emulata. In alcuni titoli potrebbe aumentare o diminuire la velocità del gameplay, mentre in altri potrebbe consentire di limitare il framerate a un multiplo della frequenza di aggiornamento, o causare comportamenti imprevedibili. Questa funzionalità è sperimentale, e non ci sono certezze sul modo in cui influenzerà il gameplay.\n\nNel dubbio, lascia l'opzione disattivata.", @@ -23199,7 +23199,7 @@ "de_DE": "Der Zielwert für die benutzerdefinierte Bildwiederholfrequenz.", "el_GR": "", "en_US": "The custom refresh rate target value.", - "es_ES": "", + "es_ES": "El valor objetivo de la frecuencia de actualización personalizada.", "fr_FR": "La valeur cible du taux de rafraîchissement customisé.", "he_IL": "", "it_IT": "Il valore desiderato della frequenza di aggiornamento personalizzata.", @@ -23224,7 +23224,7 @@ "de_DE": "Die benutzerdefinierte Bildwiederholfrequenz als Prozentsatz der normalen Switch-Bildwiederholfrequenz.", "el_GR": "", "en_US": "The custom refresh rate, as a percentage of the normal Switch refresh rate.", - "es_ES": "", + "es_ES": "La frecuencia de actualización personalizada, expresada como un porcentaje de la frecuencia de actualización normal de la Switch.", "fr_FR": "Le taux de rafraîchissement customisé, comme un pourcentage du taux de rafraîchissement normal de la Switch.", "he_IL": "", "it_IT": "La frequenza di aggiornamento personalizzata, espressa in percentuale della normale frequenza di aggiornamento di Switch.", @@ -23249,7 +23249,7 @@ "de_DE": "Benutzerdefinierte Bildwiederholfrequenz %:", "el_GR": "", "en_US": "Custom Refresh Rate %:", - "es_ES": "", + "es_ES": "Frecuencia de actualización personalizada (%):", "fr_FR": "% du Taux de Rafraîchissement Personnalisé :", "he_IL": "", "it_IT": "Frequenza di aggiornamento personalizzata (%):", @@ -23274,7 +23274,7 @@ "de_DE": "Wert für benutzerdefinierte Bildwiederholfrequenz:", "el_GR": "", "en_US": "Custom Refresh Rate Value:", - "es_ES": "", + "es_ES": "Valor de la frecuencia de actualización personalizada:", "fr_FR": "Valeur du Taux de Rafraîchissement Customisé :", "he_IL": "", "it_IT": "Valore della frequenza di aggiornamento personalizzata:", @@ -23299,7 +23299,7 @@ "de_DE": "", "el_GR": "", "en_US": "Interval", - "es_ES": "", + "es_ES": "Intervalo", "fr_FR": "Intervalle", "he_IL": "", "it_IT": "Intervallo", @@ -23324,7 +23324,7 @@ "de_DE": "VSync-Modus umschalten:", "el_GR": "", "en_US": "Toggle VSync Mode:", - "es_ES": "", + "es_ES": "Alternar modo VSync", "fr_FR": "Basculer le Mode VSync :", "he_IL": "", "it_IT": "Cambia modalità VSync:", @@ -23349,7 +23349,7 @@ "de_DE": "Benutzerdefinierte Bildwiederholfrequenz erhöhen:", "el_GR": "", "en_US": "Raise Custom Refresh Rate", - "es_ES": "", + "es_ES": "Aumentar la frecuencia de actualización personalizada", "fr_FR": "Augmenter le Taux de Rafraîchissement Customisé :", "he_IL": "", "it_IT": "Aumenta la frequenza di aggiornamento personalizzata:", @@ -23374,7 +23374,7 @@ "de_DE": "Benutzerdefinierte Bildwiederholfrequenz senken:", "el_GR": "", "en_US": "Lower Custom Refresh Rate:", - "es_ES": "", + "es_ES": "Bajar la frecuencia de actualización personalizada", "fr_FR": "Baisser le Taux de Rafraîchissement Customisé :", "he_IL": "", "it_IT": "Riduci la frequenza di aggiornamento personalizzata:", @@ -23399,7 +23399,7 @@ "de_DE": "", "el_GR": "", "en_US": "Turbo Mode:", - "es_ES": "", + "es_ES": "Modo Turbo:", "fr_FR": "Mode Turbo :", "he_IL": "", "it_IT": "", @@ -23424,8 +23424,8 @@ "de_DE": "", "el_GR": "", "en_US": "The Turbo mode hotkey.\nConfigure the behavior of Turbo mode in Ryujinx CPU settings.\n\nLeave Unbound if unsure.", - "es_ES": "", - "fr_FR": "Le raccourci clavier Mode Turbo.\nConfigurez le comportement du Mode Turbo dans les paramètres de CPU de Ryujinx.\n\nLaisser Non Attribuée si incertain.", + "es_ES": "La tecla de acceso rápido del modo Turbo.\nConfigura el comportamiento del modo Turbo en los ajustes de CPU de Ryujinx.\n\nDejar sin asignar si no está seguro.", + "fr_FR": "Le raccourci clavier Mode Turbo.\nConfigurez le comportement du Mode Turbo dans les paramètres de CPU de Ryujinx.\n\nLaisser Non Attribué si incertain.", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -23449,7 +23449,7 @@ "de_DE": "", "el_GR": "", "en_US": "Only While Pressed", - "es_ES": "", + "es_ES": "Solo mientras se mantiene pulsado", "fr_FR": "Seulement Quand le Raccourci est Maintenu", "he_IL": "", "it_IT": "", @@ -23474,7 +23474,7 @@ "de_DE": "Zuletzt aktualisiert: {0}", "el_GR": "", "en_US": "Last updated: {0}", - "es_ES": "", + "es_ES": "Última actualización: {0}", "fr_FR": "Dernière Mise à Jour : {0}", "he_IL": "", "it_IT": "", @@ -23499,7 +23499,7 @@ "de_DE": "", "el_GR": "", "en_US": "Compatibility List - {0} entries", - "es_ES": "", + "es_ES": "Lista de compatibilidad - {0} entradas", "fr_FR": "Liste de Compatibilité – {0} entrées", "he_IL": "", "it_IT": "", @@ -23524,7 +23524,7 @@ "de_DE": "Diese Kompatibilitätsliste könnte veraltete Einträge enthalten. Teste dennoch Spiele im \"Ingame\"-Status.", "el_GR": "", "en_US": "This compatibility list might contain out of date entries.\nDo not be opposed to testing games in the \"Ingame\" status.", - "es_ES": "", + "es_ES": "Esta lista de compatibilidad podría contener entradas desactualizadas.\nNo dudes en probar los juegos en estado \"En el juego\".", "fr_FR": "Cette liste de compatibilité peut contenir des entrées obsolètes.N’hésitez pas à tester les jeux dont le statut est « En cours ».", "he_IL": "", "it_IT": "", @@ -23549,7 +23549,7 @@ "de_DE": "Kompatibilitätseinträge durchsuchen...", "el_GR": "", "en_US": "Search compatibility entries...", - "es_ES": "", + "es_ES": "Buscar entradas de compatibilidad…", "fr_FR": "Rechercher les entrées de compatibilité...", "he_IL": "", "it_IT": "", @@ -23574,7 +23574,7 @@ "de_DE": "", "el_GR": "", "en_US": "Search {0} compatibility entries...", - "es_ES": "", + "es_ES": "Buscar {0} entradas de compatibilidad…", "fr_FR": "Rechercher {0} entrées de compatibilité...", "he_IL": "", "it_IT": "", @@ -23599,7 +23599,7 @@ "de_DE": "", "el_GR": "", "en_US": "Open Compatibility List", - "es_ES": "", + "es_ES": "Abrir lista de compatibilidad", "fr_FR": "Ouvrir la liste de compatibilité", "he_IL": "", "it_IT": "", @@ -23724,7 +23724,7 @@ "de_DE": "Nur eigene Spiele anzeigen", "el_GR": "", "en_US": "Only show owned games", - "es_ES": "", + "es_ES": "Solo mostrar juegos que posees", "fr_FR": "Afficher uniquement les jeux possédés", "he_IL": "", "it_IT": "", @@ -23749,7 +23749,7 @@ "de_DE": "Spielbar", "el_GR": "", "en_US": "Playable", - "es_ES": "", + "es_ES": "Jugable", "fr_FR": "Jouable", "he_IL": "", "it_IT": "", @@ -23774,7 +23774,7 @@ "de_DE": "Im Spiel", "el_GR": "", "en_US": "Ingame", - "es_ES": "", + "es_ES": "En el Juego", "fr_FR": "En Jeu", "he_IL": "", "it_IT": "", @@ -23799,7 +23799,7 @@ "de_DE": "", "el_GR": "", "en_US": "Menus", - "es_ES": "", + "es_ES": "Menu", "fr_FR": "Menu", "he_IL": "", "it_IT": "", @@ -23824,7 +23824,7 @@ "de_DE": "", "el_GR": "", "en_US": "Boots", - "es_ES": "", + "es_ES": "Inicia", "fr_FR": "Démarre", "he_IL": "", "it_IT": "", @@ -23849,7 +23849,7 @@ "de_DE": "", "el_GR": "", "en_US": "Nothing", - "es_ES": "", + "es_ES": "Nada", "fr_FR": "Rien", "he_IL": "", "it_IT": "", @@ -23874,7 +23874,7 @@ "de_DE": "", "el_GR": "", "en_US": "Boots and plays without any crashes or GPU bugs of any kind, and at a speed fast enough to reasonably enjoy on an average PC.", - "es_ES": "", + "es_ES": "Se inicia y funciona sin ningún fallo ni error de GPU, y a una velocidad lo suficientemente rápida como para disfrutarlo razonablemente en un PC promedio", "fr_FR": "Démarre et fonctionne aucun crash ou bugs graphiques, et à une vitesse raisonable pour pouvoir en profiter sur un PC ordinaire.", "he_IL": "", "it_IT": "", @@ -23899,7 +23899,7 @@ "de_DE": "", "el_GR": "", "en_US": "Boots and goes in-game but suffers from one or more of the following: crashes, deadlocks, GPU bugs, distractingly bad audio, or is simply too slow. Game still might able to be played all the way through, but not as the game is intended to play.", - "es_ES": "", + "es_ES": "Se inicia y llega al juego, pero presenta uno o más de los siguientes problemas: cierres inesperados, bloqueos, errores de GPU, audio molesto o deficiente, o simplemente es demasiado lento. El juego aún podría jugarse hasta el final, pero no como fue pensado originalmente.", "fr_FR": "Démarre et va en jeux mais souffre d'un ou plusieurs des éléments suivants: crashs, bloacages, bugs graphiques, problèmes audios, ou est trop lent. Le jeu peut toujours être joué jusqu'au bout, mais pas dans des conditions prévues.", "he_IL": "", "it_IT": "", @@ -23924,7 +23924,7 @@ "de_DE": "", "el_GR": "", "en_US": "Boots and goes past the title screen but does not make it into main gameplay.", - "es_ES": "", + "es_ES": "Se inicia y pasa de la pantalla de título, pero no llega al juego principal.", "fr_FR": "Démarre et dépasse l'écran titre, mais n'arrive pas au gameplay principal.", "he_IL": "", "it_IT": "", @@ -23949,7 +23949,7 @@ "de_DE": "Startet, kommt aber nicht über den Titelbildschirm hinaus.", "el_GR": "", "en_US": "Boots but does not make it past the title screen.", - "es_ES": "", + "es_ES": "Se inicia, pero no pasa de la pantalla de título.", "fr_FR": "Démarre mais ne dépasse pas l'écran titre.", "he_IL": "", "it_IT": "", @@ -23974,7 +23974,7 @@ "de_DE": "Startet nicht oder zeigt keine Anzeichen von Aktivität.", "el_GR": "", "en_US": "Does not boot or shows no signs of activity.", - "es_ES": "", + "es_ES": "No se inicia o no muestra ningún signo de actividad.", "fr_FR": "Ne démarre pas ou ne montre aucun signe d'activité.", "he_IL": "", "it_IT": "", @@ -23999,7 +23999,7 @@ "de_DE": "", "el_GR": "", "en_US": "Custom Config", - "es_ES": "", + "es_ES": "Configuración personalizada", "fr_FR": "Configuration Personnalisée", "he_IL": "", "it_IT": "", @@ -24024,7 +24024,7 @@ "de_DE": "", "el_GR": "", "en_US": "(Global)", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -24049,7 +24049,7 @@ "de_DE": "Wähle ein DLC zum Extrahieren aus", "el_GR": "", "en_US": "Select a DLC to Extract", - "es_ES": "", + "es_ES": "Selecciona un DLC para extraer", "fr_FR": "Choisissez un DLC à Extraire", "he_IL": "", "it_IT": "", @@ -24074,7 +24074,7 @@ "de_DE": "", "el_GR": "", "en_US": "Rich Presence Image", - "es_ES": "", + "es_ES": "Imagen Rich Presence", "fr_FR": "Image Rich Presence", "he_IL": "", "it_IT": "", @@ -24099,7 +24099,7 @@ "de_DE": "", "el_GR": "", "en_US": "Dynamic Rich Presence", - "es_ES": "", + "es_ES": "Rich Presence Dinámico", "fr_FR": "Rich Presence Dynamique", "he_IL": "", "it_IT": "", @@ -24124,7 +24124,7 @@ "de_DE": "", "el_GR": "", "en_US": "Debug", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -24149,7 +24149,7 @@ "de_DE": "", "el_GR": "", "en_US": "Debug", - "es_ES": "", + "es_ES": null, "fr_FR": null, "he_IL": "", "it_IT": "", @@ -24174,7 +24174,7 @@ "de_DE": "", "el_GR": "", "en_US": "WARNING: For developer use only, will reduce performance", - "es_ES": "", + "es_ES": "ADVERTENCIA: Solo para uso de desarrolladores, reducirá el rendimiento", "fr_FR": "ATTENTION: Uniquement pour les développeurs, réduit la performance", "he_IL": "", "it_IT": "", @@ -24199,7 +24199,7 @@ "de_DE": "", "el_GR": "", "en_US": "Enable GDB Stub", - "es_ES": "", + "es_ES": "Activar Stub GDB", "fr_FR": "Activer GDB Stub", "he_IL": "", "it_IT": "", @@ -24224,7 +24224,7 @@ "de_DE": "", "el_GR": "", "en_US": "Enables the GDB stub which makes it possible to debug the running application. For development use only!", - "es_ES": "", + "es_ES": "Activa el stub GDB, lo que permite depurar la aplicación en ejecución. ¡Solo para uso de desarrollo!", "fr_FR": "Active le GDB stub, ce qui rend le débogage de l'application possible. Pour les développeurs uniquement !", "he_IL": "", "it_IT": "", @@ -24249,7 +24249,7 @@ "de_DE": "", "el_GR": "", "en_US": "GDB Stub Port:", - "es_ES": "", + "es_ES": "Puerto del stub GDB:", "fr_FR": "Port du GDB Stub :", "he_IL": "", "it_IT": "", @@ -24274,7 +24274,7 @@ "de_DE": "", "el_GR": "", "en_US": "Suspend Application on Start", - "es_ES": "", + "es_ES": "Suspender la aplicación al iniciar", "fr_FR": "Mettre en Pause l'Application au Démarrage", "he_IL": "", "it_IT": "", @@ -24299,7 +24299,7 @@ "de_DE": "", "el_GR": "", "en_US": "Suspends the application before executing the first instruction, allowing for debugging from the earliest point.", - "es_ES": "", + "es_ES": "Suspende la aplicación antes de ejecutar la primera instrucción, permitiendo depurar desde el punto más temprano.", "fr_FR": "Met en pause l'application avant d'éxécuter la première instruction, permet de déboger au plus tôt que possible.", "he_IL": "", "it_IT": "", From c76eda2c1ab153d203b803a6711845d7b131d81e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 28 Aug 2025 15:54:51 +0200 Subject: [PATCH 08/55] Update file locales.json --- assets/locales.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 6996954a3..5cb33aac7 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -11674,7 +11674,7 @@ "de_DE": "", "el_GR": "", "en_US": "Cancelling", - "es_ES": "Anulación en curso", + "es_ES": "Anulación en Curso", "fr_FR": "Annulation en Cours", "he_IL": "", "it_IT": "Annullamento in corso", @@ -15374,7 +15374,7 @@ "de_DE": "", "el_GR": "", "en_US": "Ryujinx is an emulator for the Nintendo Switch™ 1.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitLab or Discord.", - "es_ES": "Ryujinx es un emulador para Nintendo Switch™ 1. Obtén todas las novedades en nuestro Discord. Los desarrolladores interesados en contribuir pueden obtener más información en nuestro GitLab o Discord.", + "es_ES": "Ryujinx es un emulador para Nintendo Switch™ 1.\nObtén todas las novedades en nuestro Discord.\nLos desarrolladores interesados en contribuir pueden obtener más información en nuestro GitLab o Discord.", "fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez les dernières nouvelles sur notre Discord.\nLes développeurs souhaitant contribuer peuvent en savoir plus sur notre GitLab ou Discord.", "he_IL": "", "it_IT": "Ryujinx è un emulatore della console Nintendo Switch™ 1.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitLab.", @@ -16699,7 +16699,7 @@ "de_DE": "", "el_GR": "", "en_US": "The Controller Applet dialog will not appear if the gamepad is disconnected while an application is running.\n\nLeave OFF if unsure.", - "es_ES": "El cuadro de diálogo del Applet de controladores no aparecerá si el gamepad se desconecta mientras una aplicación está en ejecución.\n\nDéjalo DESACTIVADO si no estás seguro.", + "es_ES": "El cuadro de diálogo del Applet de controladores no aparecerá si el gamepad se desconecta mientras una aplicación está en ejecución.\n\nDéjar DESACTIVADO si no está seguro.", "fr_FR": "La fenêtre de l’Applet Manette ne s’affichera pas si la manette est déconnectée pendant l’exécution d’une application\n\nLaissez DÉSACTIVÉ si vous n’êtes pas sûr.", "he_IL": "", "it_IT": "", From c02263abd7b2ce66abb44b5c59a047565866c28f Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 18 Oct 2025 18:41:35 +0200 Subject: [PATCH 09/55] Update file locales.json --- assets/locales.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 3a5b45f5f..9c9a2cc9f 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -400,7 +400,7 @@ "el_GR": "", "en_US": "Load Title Updates...", "es_ES": "Cargar Actualizaciones de Títulos...", - "fr_FR": "Charger les Mises à Jour du Titre...", + "fr_FR": "Charger des Mises à Jour de Titres...", "he_IL": "", "it_IT": "Carica aggiornamenti...", "ja_JP": "", @@ -19001,7 +19001,7 @@ "el_GR": "", "en_US": "Choose one or more FOLDERS to bulk load title updates from", "es_ES": "Elige una o más CARPETAS para cargar actualizaciones de título de forma masiva", - "fr_FR": "Choisissez un ou plusieurs DOSSIERS pour charger en masse des mises à jour du titre", + "fr_FR": "Choisissez un ou plusieurs DOSSIERS pour charger en masse des mises à jour de titres", "he_IL": "", "it_IT": "Scegli una o più CARTELLE da cui caricare in blocco gli aggiornamenti del titolo", "ja_JP": "", From 914d4c8a79a7c7e54ed5ea6ecffe7e9afb53c270 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 18 Oct 2025 19:05:40 +0200 Subject: [PATCH 10/55] Update file locales.json --- assets/locales.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales.json b/assets/locales.json index 013cbec6f..f84898591 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -575,7 +575,7 @@ "el_GR": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", "en_US": "Start Games in Fullscreen Mode", "es_ES": "Iniciar Juegos en Pantalla Completa", - "fr_FR": "Démarrer les Jeux en Mode Plein Écran", + "fr_FR": "Démarrer les Jeux en Plein Écran", "he_IL": "התחל משחקים במסך מלא", "it_IT": "Avvia i giochi a schermo intero", "ja_JP": "全画面モードでゲームを開始", From c980dc00aae481c8100cb197d33647074f0142be Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 11:10:15 +0100 Subject: [PATCH 11/55] Update file locales.json --- assets/locales.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 0f25f115a..6ffe0a14b 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -14650,8 +14650,8 @@ "de_DE": "", "el_GR": "", "en_US": "{0} DRAM Enabled", - "es_ES": "", - "fr_FR": "", + "es_ES": "{0} DRAM Habilitada", + "fr_FR": "{0} DRAM Activée", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -14675,8 +14675,8 @@ "de_DE": "", "el_GR": "", "en_US": "Using above 4GiB DRAM may cause crashes in some applications.", - "es_ES": "", - "fr_FR": "", + "es_ES": "Usar más de 4 GiB de DRAM puede causar fallos en algunas aplicaciones.", + "fr_FR": "L’utilisation de plus de 4 Gio de DRAM peut provoquer des plantages dans certaines applications.", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -14700,8 +14700,8 @@ "de_DE": "", "el_GR": "", "en_US": "Debug: GDB Stub Enabled (Port: {0})", - "es_ES": "", - "fr_FR": "", + "es_ES": "Debug: GDB Stub habilitado (Puerto: {0})", + "fr_FR": "Debug : GDB Stub activé (Port : {0})", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -14725,8 +14725,8 @@ "de_DE": "", "el_GR": "", "en_US": "This will affect performance.", - "es_ES": "", - "fr_FR": "", + "es_ES": "Esto afectará el rendimiento.", + "fr_FR": "Cela affectera les performances.", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -14750,8 +14750,8 @@ "de_DE": "", "el_GR": "", "en_US": "Debug: Suspend on Start Enabled", - "es_ES": "", - "fr_FR": "", + "es_ES": "Debug: Suspender al Inicio Habilitado", + "fr_FR": "Debug : Suspension au Démarrage Activée", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -14775,8 +14775,8 @@ "de_DE": "", "el_GR": "", "en_US": "Application has been suspended. Attach a debugger to continue.", - "es_ES": "", - "fr_FR": "", + "es_ES": "L’application a été suspendue. Attachez un débogueur pour continuer.", + "fr_FR": "La aplicación ha sido suspendida. Adjunte un depurador para continuar.", "he_IL": "", "it_IT": "", "ja_JP": "", From 2a74d2284d4dd19b425c4debe5ddb5647ad4cc71 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 11:12:44 +0100 Subject: [PATCH 12/55] Capitalisation fix --- assets/locales.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 6ffe0a14b..f40b24951 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -14700,8 +14700,8 @@ "de_DE": "", "el_GR": "", "en_US": "Debug: GDB Stub Enabled (Port: {0})", - "es_ES": "Debug: GDB Stub habilitado (Puerto: {0})", - "fr_FR": "Debug : GDB Stub activé (Port : {0})", + "es_ES": "Debug: GDB Stub Habilitado (Puerto: {0})", + "fr_FR": "Debug : GDB Stub Activé (Port : {0})", "he_IL": "", "it_IT": "", "ja_JP": "", From b1bd46989740a1840ec529c4b35d865d2ab2e06f Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 21:53:23 +0100 Subject: [PATCH 13/55] Update file locales.json --- assets/locales.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index f40b24951..d998ac4ed 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -14775,8 +14775,8 @@ "de_DE": "", "el_GR": "", "en_US": "Application has been suspended. Attach a debugger to continue.", - "es_ES": "L’application a été suspendue. Attachez un débogueur pour continuer.", - "fr_FR": "La aplicación ha sido suspendida. Adjunte un depurador para continuar.", + "es_ES": "La aplicación ha sido suspendida. Adjunte un depurador para continuar.", + "fr_FR": "L’application a été suspendue. Attachez un débogueur pour continuer.", "he_IL": "", "it_IT": "", "ja_JP": "", From 726491d0bac1e55e6a740ef3dc546b625916147f Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 21:59:20 +0100 Subject: [PATCH 14/55] Fix Debug being Deboguage in some fr_FR translations for consistency --- assets/locales.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index d998ac4ed..717a4836f 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -6250,7 +6250,7 @@ "el_GR": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", "en_US": "Enable Debug Logs", "es_ES": "Habilitar Registros de Debug", - "fr_FR": "Activer les Journaux de Débogage", + "fr_FR": "Activer les Journaux de Debug", "he_IL": "אפשר רישום ניפוי באגים", "it_IT": "Attiva log di debug", "ja_JP": "デバッグログを有効にする", @@ -14789,7 +14789,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_hoisN": "", "zh_TW": "" } }, @@ -17101,7 +17101,7 @@ "el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", "en_US": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", "es_ES": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", - "fr_FR": "Affiche dans la console les journaux de débogage.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", + "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", "he_IL": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", "it_IT": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", "ja_JP": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", From 1ab78040aa7ce9b095a70c6792d1262214e85716 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 22:07:17 +0100 Subject: [PATCH 15/55] more general fixes --- assets/locales.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 717a4836f..7f9f20890 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -375,7 +375,7 @@ "el_GR": "", "en_US": "Load DLC...", "es_ES": "Cargar DLC...", - "fr_FR": "Charger les DLC...", + "fr_FR": "Charger des DLC...", "he_IL": "", "it_IT": "Carica DLC...", "ja_JP": "", @@ -17101,7 +17101,7 @@ "el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", "en_US": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", "es_ES": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", - "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", + "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’uties LDisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", "he_IL": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", "it_IT": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", "ja_JP": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", @@ -19101,7 +19101,7 @@ "el_GR": "", "en_US": "Choose a Switch compatible FILE to load", "es_ES": "Elige un ARCHIVO compatible con Switch para cargar", - "fr_FR": "Choisissez un FICHIER compatible avec Switch à charger", + "fr_FR": "Choisissez un FICHIER compatible Switch à charger", "he_IL": "", "it_IT": "Scegli un FILE compatibile con Switch da caricare", "ja_JP": "", From 5327853f728464b22743133b2c7caa5adf4442b8 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 26 Oct 2025 22:10:25 +0100 Subject: [PATCH 16/55] Update file locales.json --- assets/locales.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 7f9f20890..0cfe9d079 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -14789,7 +14789,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_hoisN": "", + "zh_CN": "", "zh_TW": "" } }, @@ -17101,7 +17101,7 @@ "el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", "en_US": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", "es_ES": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", - "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’uties LDisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", + "fr_FR": "Affiche dans la console les journaux de debug.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera la performance de l’émulateur.", "he_IL": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", "it_IT": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", "ja_JP": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", From 27c3231433339d64a2348285edbbf335b130a697 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Fri, 31 Oct 2025 18:00:21 +0100 Subject: [PATCH 17/55] nullification of a french translation --- assets/locales.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales.json b/assets/locales.json index 3326c6907..24f1e648b 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -3250,7 +3250,7 @@ "el_GR": "Διεπαφή", "en_US": "Interface", "es_ES": "Interfaz", - "fr_FR": "", + "fr_FR": null, "he_IL": "ממשק", "it_IT": "Interfaccia", "ja_JP": "インターフェース", From 041c088d61a551df1b59fe6301fae0e2e68cdf73 Mon Sep 17 00:00:00 2001 From: Babib3l3l <145063624+Babib3l3l@users.noreply.github.com> Date: Sat, 3 Jan 2026 13:36:36 +0100 Subject: [PATCH 18/55] french and spanish translations --- assets/Locales/RenderDoc.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/Locales/RenderDoc.json b/assets/Locales/RenderDoc.json index 132e24067..894ff07ca 100644 --- a/assets/Locales/RenderDoc.json +++ b/assets/Locales/RenderDoc.json @@ -7,8 +7,8 @@ "de_DE": "", "el_GR": "", "en_US": "Start RenderDoc Frame Capture", - "es_ES": "", - "fr_FR": "", + "es_ES": "Iniciar una captura de fotograma de RenderDoc", + "fr_FR": "Démarrer une capture de trame RenderDoc", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -32,8 +32,8 @@ "de_DE": "", "el_GR": "", "en_US": "End RenderDoc Frame Capture", - "es_ES": "", - "fr_FR": "", + "es_ES": "Detener la captura de fotograma de RenderDoc", + "fr_FR": "Arrêter la capture de trame RenderDoc", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -57,8 +57,8 @@ "de_DE": "", "el_GR": "", "en_US": "Discard RenderDoc Frame Capture", - "es_ES": "", - "fr_FR": "", + "es_ES": "Descartar la captura de fotograma de RenderDoc", + "fr_FR": "Supprimer la capture de trame RenderDoc", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -82,8 +82,8 @@ "de_DE": "", "el_GR": "", "en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.", - "es_ES": "", - "fr_FR": "", + "es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.", + "fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.", "he_IL": "", "it_IT": "", "ja_JP": "", From 7df5299d5a7b364a6ba30cc0b8c7f0cd8200c79e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 16:38:31 +0100 Subject: [PATCH 19/55] Split keyboard localisation into dedicated locale file --- assets/Locales/KeyboardLayout.json | 1929 +++++++++++++++++ assets/Locales/Root.json | 1925 ---------------- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 2 +- .../Helpers/Converters/KeyValueConverter.cs | 154 +- 4 files changed, 2007 insertions(+), 2003 deletions(-) create mode 100644 assets/Locales/KeyboardLayout.json diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json new file mode 100644 index 000000000..2f7167422 --- /dev/null +++ b/assets/Locales/KeyboardLayout.json @@ -0,0 +1,1929 @@ +{ + "Locales": [ + { + "ID": "KeyUnknown", + "Translations": { + "ar_SA": "مجهول", + "de_DE": "Unbekannt", + "el_GR": "", + "en_US": "Unknown", + "es_ES": "Desconocido", + "fr_FR": "Inconnu", + "he_IL": "", + "it_IT": "Sconosciuto", + "ja_JP": "", + "ko_KR": "알 수 없음", + "no_NO": "Ukjent", + "pl_PL": "", + "pt_BR": "Desconhecido", + "ru_RU": "Неизвестно", + "sv_SE": "Okänd", + "th_TH": "ไม่รู้จัก", + "tr_TR": "", + "uk_UA": "Невизначено", + "zh_CN": "未知", + "zh_TW": "未知" + } + }, + { + "ID": "KeyShiftLeft", + "Translations": { + "ar_SA": "زر ‫Shift الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "Shift Left", + "es_ES": "Mayús Izquierda", + "fr_FR": "Maj Gauche", + "he_IL": "", + "it_IT": "Maiusc sinistro", + "ja_JP": "", + "ko_KR": "좌측 Shift", + "no_NO": "Skift venstre", + "pl_PL": "", + "pt_BR": "Shift Esquerdo", + "ru_RU": "Левый Shift", + "sv_SE": "Skift vänster", + "th_TH": "Shift ซ้าย", + "tr_TR": "Sol Shift", + "uk_UA": "Shift Лівий", + "zh_CN": "左侧Shift", + "zh_TW": "左 Shift" + } + }, + { + "ID": "KeyShiftRight", + "Translations": { + "ar_SA": "زر ‫Shift الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "Shift Right", + "es_ES": "Mayús Derecha", + "fr_FR": "Maj Droite", + "he_IL": "", + "it_IT": "Maiusc destro", + "ja_JP": "", + "ko_KR": "우측 Shift", + "no_NO": "Skift høyre", + "pl_PL": "", + "pt_BR": "Shift Direito", + "ru_RU": "Правый Shift", + "sv_SE": "Skift höger", + "th_TH": "Shift ขวา", + "tr_TR": "Sağ Shift", + "uk_UA": "Shift Правий", + "zh_CN": "右侧Shift", + "zh_TW": "右 Shift" + } + }, + { + "ID": "KeyControlLeft", + "Translations": { + "ar_SA": "زر ‫Ctrl الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "Ctrl Left", + "es_ES": "Alt Gr", + "fr_FR": "Alt Gr", + "he_IL": "", + "it_IT": "Ctrl sinistro", + "ja_JP": "", + "ko_KR": "좌측 Ctrl", + "no_NO": "Ctrl venstre", + "pl_PL": "", + "pt_BR": "Ctrl Esquerdo", + "ru_RU": "Левый Ctrl", + "sv_SE": "Ctrl vänster", + "th_TH": "Ctrl ซ้าย", + "tr_TR": "Sol Ctrl", + "uk_UA": "Ctrl Лівий", + "zh_CN": "左侧Ctrl", + "zh_TW": "左 Ctrl" + } + }, + { + "ID": "KeyMacControlLeft", + "Translations": { + "ar_SA": "زر ⌃ الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "⌃ Left", + "es_ES": "⌃ Izquierdo", + "fr_FR": "⌃ Gauche", + "he_IL": "", + "it_IT": "⌃ sinistro", + "ja_JP": "", + "ko_KR": "좌측 ⌃", + "no_NO": "⌃ Venstre", + "pl_PL": "", + "pt_BR": "⌃ Esquerda", + "ru_RU": "Левый ⌃", + "sv_SE": "^ Vänster", + "th_TH": "^ ซ้าย", + "tr_TR": "⌃ Sol", + "uk_UA": "⌃ Лівий", + "zh_CN": "左侧⌃", + "zh_TW": "左 ⌃" + } + }, + { + "ID": "KeyControlRight", + "Translations": { + "ar_SA": "زر ‫Ctrl الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "Ctrl Right", + "es_ES": "Ctrl Derecho", + "fr_FR": "Ctrl Droite", + "he_IL": "", + "it_IT": "Ctrl destro", + "ja_JP": "", + "ko_KR": "우측 Ctrl", + "no_NO": "Ctrl høyre", + "pl_PL": "", + "pt_BR": "Ctrl Direito", + "ru_RU": "Правый Ctrl", + "sv_SE": "Ctrl höger", + "th_TH": "Ctrl ขวา", + "tr_TR": "Sağ Control", + "uk_UA": "Ctrl Правий", + "zh_CN": "右侧Ctrl", + "zh_TW": "右 Ctrl" + } + }, + { + "ID": "KeyMacControlRight", + "Translations": { + "ar_SA": "زر ⌃ الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "⌃ Right", + "es_ES": "⌃ Derecho", + "fr_FR": "⌃ Droite", + "he_IL": "", + "it_IT": "⌃ destro", + "ja_JP": "", + "ko_KR": "우측 ⌃", + "no_NO": "⌃ Høyre", + "pl_PL": "", + "pt_BR": "⌃ Direito", + "ru_RU": "Правый ⌃", + "sv_SE": "^ Höger", + "th_TH": "⌃ ขวา", + "tr_TR": "⌃ Sağ", + "uk_UA": "⌃ Правий", + "zh_CN": "右侧⌃", + "zh_TW": "右 ⌃" + } + }, + { + "ID": "KeyAltLeft", + "Translations": { + "ar_SA": "زر ‫Alt الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "Alt Left", + "es_ES": "Alt Izquierdo", + "fr_FR": "Alt Gauche", + "he_IL": "", + "it_IT": "Alt sinistro", + "ja_JP": "", + "ko_KR": "좌측 Alt", + "no_NO": "Alt Venstre", + "pl_PL": "", + "pt_BR": "Alt Esquerdo", + "ru_RU": "Левый Alt", + "sv_SE": "Alt vänster", + "th_TH": "Alt ซ้าย", + "tr_TR": "Sol Alt", + "uk_UA": "Alt Лівий", + "zh_CN": "左侧Alt", + "zh_TW": "左 Alt" + } + }, + { + "ID": "KeyMacAltLeft", + "Translations": { + "ar_SA": "زر ⌥ الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "⌥ Left", + "es_ES": "⌥ Izquierdo", + "fr_FR": "⌥ Gauche", + "he_IL": "", + "it_IT": "⌥ sinistro", + "ja_JP": "", + "ko_KR": "좌측 ⌥", + "no_NO": "⌥ Venstre", + "pl_PL": "", + "pt_BR": "⌥ Esquerda", + "ru_RU": "Левый ⌥", + "sv_SE": "⌥ vänster", + "th_TH": "⌥ ซ้าย", + "tr_TR": "⌥ Sol", + "uk_UA": "⌥ Лівий", + "zh_CN": "左侧⌥", + "zh_TW": "左 ⌥" + } + }, + { + "ID": "KeyAltRight", + "Translations": { + "ar_SA": "زر ‫Alt الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "Alt Right", + "es_ES": "Alt Derecho", + "fr_FR": "Alt Droite", + "he_IL": "", + "it_IT": "Alt destro", + "ja_JP": "", + "ko_KR": "우측 Alt", + "no_NO": "Alt høyre", + "pl_PL": "", + "pt_BR": "Alt Direito", + "ru_RU": "Правый Alt", + "sv_SE": "Alt höger", + "th_TH": "Alt ขวา", + "tr_TR": "Sağ Alt", + "uk_UA": "Alt Правий", + "zh_CN": "右侧Alt", + "zh_TW": "右 Alt" + } + }, + { + "ID": "KeyMacAltRight", + "Translations": { + "ar_SA": "زر ⌥ الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "⌥ Right", + "es_ES": "⌥ Derecho", + "fr_FR": "⌥ Droite", + "he_IL": "", + "it_IT": "⌥ destro", + "ja_JP": "", + "ko_KR": "우측 ⌥", + "no_NO": "⌥ Høyre", + "pl_PL": "", + "pt_BR": "⌥ Direito", + "ru_RU": "Правый ⌥", + "sv_SE": "⌥ höger", + "th_TH": "⌥ ขวา", + "tr_TR": "⌥ Sağ", + "uk_UA": "⌥ Правий", + "zh_CN": "右侧⌥", + "zh_TW": "右 ⌥" + } + }, + { + "ID": "KeyWinLeft", + "Translations": { + "ar_SA": "زر ⊞ الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "⊞ Left", + "es_ES": "⊞ Izquierdo", + "fr_FR": "⊞ Gauche", + "he_IL": "", + "it_IT": "⊞ sinistro", + "ja_JP": "", + "ko_KR": "좌측 ⊞", + "no_NO": "⊞ Venstre", + "pl_PL": "", + "pt_BR": "⊞ Esquerdo", + "ru_RU": "Левый ⊞", + "sv_SE": "⊞ vänster", + "th_TH": "⊞ ซ้าย", + "tr_TR": "⊞ Sol", + "uk_UA": "⊞ Лівий", + "zh_CN": "左侧⊞", + "zh_TW": "左 ⊞" + } + }, + { + "ID": "KeyMacWinLeft", + "Translations": { + "ar_SA": "زر ⌘ الأيسر", + "de_DE": "", + "el_GR": "", + "en_US": "⌘ Left", + "es_ES": "⌘ Izqierdo", + "fr_FR": "⌘ Gauche", + "he_IL": "", + "it_IT": "⌘ sinistro", + "ja_JP": "", + "ko_KR": "좌측 ⌘", + "no_NO": "⌘ Venstre", + "pl_PL": "", + "pt_BR": "⌘ Esquerdo", + "ru_RU": "Левый ⌘", + "sv_SE": "⌘ vänster", + "th_TH": "⌘ ซ้าย", + "tr_TR": "⌘ Sol", + "uk_UA": "⌘ Лівий", + "zh_CN": "左侧⌘", + "zh_TW": "左 ⌘" + } + }, + { + "ID": "KeyWinRight", + "Translations": { + "ar_SA": "زر ⊞ الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "⊞ Right", + "es_ES": "⊞ Derecho", + "fr_FR": "⊞ Droite", + "he_IL": "", + "it_IT": "⊞ destro", + "ja_JP": "", + "ko_KR": "우측 ⊞", + "no_NO": "⊞ Høyre", + "pl_PL": "", + "pt_BR": "⊞ Direito", + "ru_RU": "Правый ⊞", + "sv_SE": "⊞ höger", + "th_TH": "⊞ ขวา", + "tr_TR": "⊞ Sağ", + "uk_UA": "⊞ Правий", + "zh_CN": "右侧⊞", + "zh_TW": "右 ⊞" + } + }, + { + "ID": "KeyMacWinRight", + "Translations": { + "ar_SA": "زر ⌘ الأيمن", + "de_DE": "", + "el_GR": "", + "en_US": "⌘ Right", + "es_ES": "⌘ Derecho", + "fr_FR": "⌘ Droite", + "he_IL": "", + "it_IT": "⌘ destro", + "ja_JP": "", + "ko_KR": "우측 ⌘", + "no_NO": "⌘ Høyre", + "pl_PL": "", + "pt_BR": "⌘ Direito", + "ru_RU": "Правый ⌘", + "sv_SE": "⌘ höger", + "th_TH": "⌘ ขวา", + "tr_TR": "⌘ Sağ", + "uk_UA": "⌘ Правий", + "zh_CN": "右侧⌘", + "zh_TW": "右 ⌘" + } + }, + { + "ID": "KeyMenu", + "Translations": { + "ar_SA": "زر القائمة", + "de_DE": "", + "el_GR": "", + "en_US": "Menu", + "es_ES": null, + "fr_FR": null, + "he_IL": "", + "it_IT": "Menù", + "ja_JP": "", + "ko_KR": "메뉴", + "no_NO": "Meny", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Меню", + "sv_SE": "Meny", + "th_TH": "เมนู", + "tr_TR": "Menü", + "uk_UA": "Меню", + "zh_CN": "菜单键", + "zh_TW": "功能表鍵" + } + }, + { + "ID": "KeyUp", + "Translations": { + "ar_SA": "فوق", + "de_DE": "", + "el_GR": "", + "en_US": "Up", + "es_ES": "Arriba", + "fr_FR": "Haut", + "he_IL": "", + "it_IT": "Su", + "ja_JP": "", + "ko_KR": "↑", + "no_NO": "Opp", + "pl_PL": "", + "pt_BR": "Cima", + "ru_RU": "Вверх", + "sv_SE": "Upp", + "th_TH": "ขึ้น", + "tr_TR": "Yukarı", + "uk_UA": "Вгору ↑", + "zh_CN": "上", + "zh_TW": "上" + } + }, + { + "ID": "KeyDown", + "Translations": { + "ar_SA": "اسفل", + "de_DE": "", + "el_GR": "", + "en_US": "Down", + "es_ES": "Abajo", + "fr_FR": "Bas", + "he_IL": "", + "it_IT": "Giù", + "ja_JP": "", + "ko_KR": "↓", + "no_NO": "Ned", + "pl_PL": "", + "pt_BR": "Baixo", + "ru_RU": "Вниз", + "sv_SE": "Ner", + "th_TH": "ลง", + "tr_TR": "Aşağı", + "uk_UA": "Вниз ↓", + "zh_CN": "下", + "zh_TW": "下" + } + }, + { + "ID": "KeyLeft", + "Translations": { + "ar_SA": "يسار", + "de_DE": "", + "el_GR": "", + "en_US": "Left", + "es_ES": "Izquierda", + "fr_FR": "Gauche", + "he_IL": "", + "it_IT": "Sinistra", + "ja_JP": "", + "ko_KR": "←", + "no_NO": "Venstre", + "pl_PL": "", + "pt_BR": "Esquerda", + "ru_RU": "Влево", + "sv_SE": "Vänster", + "th_TH": "ซ้าย", + "tr_TR": "Sol", + "uk_UA": "Вліво ←", + "zh_CN": "左", + "zh_TW": "左" + } + }, + { + "ID": "KeyRight", + "Translations": { + "ar_SA": "يمين", + "de_DE": "", + "el_GR": "", + "en_US": "Right", + "es_ES": "Derecha", + "fr_FR": "Droite", + "he_IL": "", + "it_IT": "Destra", + "ja_JP": "", + "ko_KR": "→", + "no_NO": "Høyre", + "pl_PL": "", + "pt_BR": "Direita", + "ru_RU": "Вправо", + "sv_SE": "Höger", + "th_TH": "ขวา", + "tr_TR": "Sağ", + "uk_UA": "Вправо →", + "zh_CN": "右", + "zh_TW": "右" + } + }, + { + "ID": "KeyEnter", + "Translations": { + "ar_SA": "مفتاح الإدخال", + "de_DE": "", + "el_GR": "", + "en_US": "Enter", + "es_ES": "Intro", + "fr_FR": "Entrée", + "he_IL": "", + "it_IT": "Invio", + "ja_JP": "", + "ko_KR": "엔터", + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Enter", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "回车键", + "zh_TW": "Enter 鍵" + } + }, + { + "ID": "KeyEscape", + "Translations": { + "ar_SA": "زر ‫Escape", + "de_DE": "", + "el_GR": "", + "en_US": "Escape", + "es_ES": "Esc", + "fr_FR": "Esc", + "he_IL": "", + "it_IT": "Esc", + "ja_JP": "", + "ko_KR": "Esc", + "no_NO": "Esc-tast", + "pl_PL": "", + "pt_BR": "Esc", + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Escape", + "tr_TR": "Esc", + "uk_UA": "Esc", + "zh_CN": "Esc", + "zh_TW": "Esc 鍵" + } + }, + { + "ID": "KeySpace", + "Translations": { + "ar_SA": "مسافة", + "de_DE": "", + "el_GR": "", + "en_US": "Space", + "es_ES": "Espacio", + "fr_FR": "Espace", + "he_IL": "", + "it_IT": "Spazio", + "ja_JP": "", + "ko_KR": "스페이스", + "no_NO": "Mellomrom", + "pl_PL": "", + "pt_BR": "Espaço", + "ru_RU": "Пробел", + "sv_SE": "Blanksteg", + "th_TH": "ปุ่ม Spacebar", + "tr_TR": "", + "uk_UA": "Пробіл", + "zh_CN": "空格键", + "zh_TW": "空白鍵" + } + }, + { + "ID": "KeyTab", + "Translations": { + "ar_SA": "زر ‫Tab", + "de_DE": "", + "el_GR": "", + "en_US": "Tab", + "es_ES": null, + "fr_FR": null, + "he_IL": "", + "it_IT": null, + "ja_JP": "", + "ko_KR": "탭", + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Tab", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Tab 鍵" + } + }, + { + "ID": "KeyBackSpace", + "Translations": { + "ar_SA": "زر المسح للخلف", + "de_DE": "", + "el_GR": "", + "en_US": "Backspace", + "es_ES": "Retroceso", + "fr_FR": "Retour arrière", + "he_IL": "", + "it_IT": "Canc", + "ja_JP": "", + "ko_KR": "백스페이스", + "no_NO": "Tilbaketast", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": "Backsteg", + "th_TH": "ปุ่ม Backspace", + "tr_TR": "Geri tuşu", + "uk_UA": "", + "zh_CN": "退格键", + "zh_TW": "Backspace 鍵" + } + }, + { + "ID": "KeyInsert", + "Translations": { + "ar_SA": "زر Insert", + "de_DE": "", + "el_GR": "", + "en_US": "Insert", + "es_ES": null, + "fr_FR": "Inser", + "he_IL": "", + "it_IT": "Ins", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Sett inn", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Insert", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Insert 鍵" + } + }, + { + "ID": "KeyDelete", + "Translations": { + "ar_SA": "زر الحذف", + "de_DE": "", + "el_GR": "", + "en_US": "Delete", + "es_ES": "Supr", + "fr_FR": "Suppr", + "he_IL": "", + "it_IT": "Canc", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Slett", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Delete", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Delete 鍵" + } + }, + { + "ID": "KeyPageUp", + "Translations": { + "ar_SA": "زر ‫Page Up", + "de_DE": "", + "el_GR": "", + "en_US": "Page Up", + "es_ES": "Re Pág", + "fr_FR": "Pg.Suiv", + "he_IL": "", + "it_IT": "Pag. Su", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Side opp", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Page Up", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "向上捲頁鍵" + } + }, + { + "ID": "KeyPageDown", + "Translations": { + "ar_SA": "زر ‫Page Down", + "de_DE": "", + "el_GR": "", + "en_US": "Page Down", + "es_ES": "Av Pág", + "fr_FR": "Pg.Préc", + "he_IL": "", + "it_IT": "Pag. Giù", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Side ned", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Page Down", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "向下捲頁鍵" + } + }, + { + "ID": "KeyHome", + "Translations": { + "ar_SA": "زر ‫Home", + "de_DE": "", + "el_GR": "", + "en_US": "Home", + "es_ES": "Inicio", + "fr_FR": null, + "he_IL": "", + "it_IT": "Inizio", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Hjem", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Home", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Home 鍵" + } + }, + { + "ID": "KeyEnd", + "Translations": { + "ar_SA": "زر ‫End", + "de_DE": "", + "el_GR": "", + "en_US": "End", + "es_ES": "Fin", + "fr_FR": "Fin", + "he_IL": "", + "it_IT": "Fine", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Avslutt", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม End", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "End 鍵" + } + }, + { + "ID": "KeyCapsLock", + "Translations": { + "ar_SA": "زر الحروف الكبيرة", + "de_DE": "", + "el_GR": "", + "en_US": "Caps Lock", + "es_ES": "Bloq Mayús", + "fr_FR": "Verr. Maj", + "he_IL": "", + "it_IT": "Bloc Maiusc", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Skiftelås", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Caps Lock", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Caps Lock 鍵" + } + }, + { + "ID": "KeyScrollLock", + "Translations": { + "ar_SA": "زر ‫Scroll Lock", + "de_DE": "", + "el_GR": "", + "en_US": "Scroll Lock", + "es_ES": "Bloq Despl", + "fr_FR": "Arr. Déf.", + "he_IL": "", + "it_IT": "Bloc Scorr", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Rullelås", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Scroll Lock", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Scroll Lock 鍵" + } + }, + { + "ID": "KeyPrintScreen", + "Translations": { + "ar_SA": "زر ‫Print Screen", + "de_DE": "", + "el_GR": "", + "en_US": "Print Screen", + "es_ES": "Impr Pant", + "fr_FR": "Impr Écran", + "he_IL": "", + "it_IT": "Stamp", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Skjermbilde", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Print Screen", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Print Screen 鍵" + } + }, + { + "ID": "KeyPause", + "Translations": { + "ar_SA": "زر Pause", + "de_DE": "", + "el_GR": "", + "en_US": "Pause", + "es_ES": "Pausa", + "fr_FR": null, + "he_IL": "", + "it_IT": "Pausa", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Stans midlertidig", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Pause", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Pause 鍵" + } + }, + { + "ID": "KeyNumLock", + "Translations": { + "ar_SA": "زر Num Lock", + "de_DE": "", + "el_GR": "", + "en_US": "Num Lock", + "es_ES": "Bloq Num", + "fr_FR": "Verr. Num", + "he_IL": "", + "it_IT": "Bloc Num", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Numerisk Lås", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "ปุ่ม Num Lock", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": "Num Lock 鍵" + } + }, + { + "ID": "KeyClear", + "Translations": { + "ar_SA": "زر Clear", + "de_DE": "", + "el_GR": "", + "en_US": "Clear", + "es_ES": "Borrar", + "fr_FR": "Effacer", + "he_IL": "", + "it_IT": "Cancella", + "ja_JP": "", + "ko_KR": null, + "no_NO": "Tøm", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": "Töm", + "th_TH": "ล้าง", + "tr_TR": "", + "uk_UA": "Очистити", + "zh_CN": "清除键", + "zh_TW": "清除" + } + }, + { + "ID": "KeyKeypad0", + "Translations": { + "ar_SA": "لوحة الأرقام 0", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 0", + "es_ES": "Num. 0", + "fr_FR": "Num. 0", + "he_IL": "", + "it_IT": "Tast. num. 0", + "ja_JP": "", + "ko_KR": "키패드 0", + "no_NO": "Numerisk 0", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 0", + "sv_SE": "Numerisk 0", + "th_TH": "ปุ่ม 0 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "Блок цифр 0", + "zh_CN": "小键盘0", + "zh_TW": "數字鍵 0" + } + }, + { + "ID": "KeyKeypad1", + "Translations": { + "ar_SA": "لوحة الأرقام 1", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 1", + "es_ES": "Num. 1", + "fr_FR": "Num. 1", + "he_IL": "", + "it_IT": "Tast. num. 1", + "ja_JP": "", + "ko_KR": "키패드 1", + "no_NO": "Numerisk 1", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 1", + "sv_SE": "Numerisk 1", + "th_TH": "ปุ่ม 1 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "Блок цифр 1", + "zh_CN": "小键盘1", + "zh_TW": "數字鍵 1" + } + }, + { + "ID": "KeyKeypad2", + "Translations": { + "ar_SA": "لوحة الأرقام 2", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 2", + "es_ES": "Num. 2", + "fr_FR": "Num. 2", + "he_IL": "", + "it_IT": "Tast. num. 2", + "ja_JP": "", + "ko_KR": "키패드 2", + "no_NO": "Numerisk 2", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 2", + "sv_SE": "Numerisk 2", + "th_TH": "ปุ่ม 2 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "2 (цифровий блок)", + "zh_CN": "小键盘2", + "zh_TW": "數字鍵 2" + } + }, + { + "ID": "KeyKeypad3", + "Translations": { + "ar_SA": "لوحة الأرقام 3", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 3", + "es_ES": "Num. 3", + "fr_FR": "Num. 3", + "he_IL": "", + "it_IT": "Tast. num. 3", + "ja_JP": "", + "ko_KR": "키패드 3", + "no_NO": "Numerisk 3", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 3", + "sv_SE": "Numerisk 3", + "th_TH": "ปุ่ม 3 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "3 (цифровий блок)", + "zh_CN": "小键盘3", + "zh_TW": "數字鍵 3" + } + }, + { + "ID": "KeyKeypad4", + "Translations": { + "ar_SA": "لوحة الأرقام 4", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 4", + "es_ES": "Num. 4", + "fr_FR": "Num. 4", + "he_IL": "", + "it_IT": "Tast. num. 4", + "ja_JP": "", + "ko_KR": "키패드 4", + "no_NO": "Numerisk 4", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 4", + "sv_SE": "Numerisk 4", + "th_TH": "ปุ่ม 4 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "4 (цифровий блок)", + "zh_CN": "小键盘4", + "zh_TW": "數字鍵 4" + } + }, + { + "ID": "KeyKeypad5", + "Translations": { + "ar_SA": "لوحة الأرقام 5", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 5", + "es_ES": "Num. 5", + "fr_FR": "Num. 5", + "he_IL": "", + "it_IT": "Tast. num. 5", + "ja_JP": "", + "ko_KR": "키패드 5", + "no_NO": "Numerisk 5", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 5", + "sv_SE": "Numerisk 5", + "th_TH": "ปุ่ม 5 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "5 (цифровий блок)", + "zh_CN": "小键盘5", + "zh_TW": "數字鍵 5" + } + }, + { + "ID": "KeyKeypad6", + "Translations": { + "ar_SA": "لوحة الأرقام 6", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 6", + "es_ES": "Num. 6", + "fr_FR": "Num. 6", + "he_IL": "", + "it_IT": "Tast. num. 6", + "ja_JP": "", + "ko_KR": "키패드 6", + "no_NO": "Numerisk 6", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 6", + "sv_SE": "Numerisk 6", + "th_TH": "ปุ่ม 6 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "6 (цифровий блок)", + "zh_CN": "小键盘6", + "zh_TW": "數字鍵 6" + } + }, + { + "ID": "KeyKeypad7", + "Translations": { + "ar_SA": "لوحة الأرقام 7", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 7", + "es_ES": "Num. 7", + "fr_FR": "Num. 7", + "he_IL": "", + "it_IT": "Tast. num. 7", + "ja_JP": "", + "ko_KR": "키패드 7", + "no_NO": "Numerisk 7", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 7", + "sv_SE": "Numerisk 7", + "th_TH": "ปุ่ม 7 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "7 (цифровий блок)", + "zh_CN": "小键盘7", + "zh_TW": "數字鍵 7" + } + }, + { + "ID": "KeyKeypad8", + "Translations": { + "ar_SA": "لوحة الأرقام 8", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 8", + "es_ES": "Num. 8", + "fr_FR": "Num. 8", + "he_IL": "", + "it_IT": "Tast. num. 8", + "ja_JP": "", + "ko_KR": "키패드 8", + "no_NO": "Numerisk 8", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 8", + "sv_SE": "Numerisk 8", + "th_TH": "ปุ่ม 8 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "8 (цифровий блок)", + "zh_CN": "小键盘8", + "zh_TW": "數字鍵 8" + } + }, + { + "ID": "KeyKeypad9", + "Translations": { + "ar_SA": "لوحة الأرقام 9", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad 9", + "es_ES": "Num. 9", + "fr_FR": "Num. 9", + "he_IL": "", + "it_IT": "Tast. num. 9", + "ja_JP": "", + "ko_KR": "키패드 9", + "no_NO": "Numerisk 9", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Блок цифр 9", + "sv_SE": "Numerisk 9", + "th_TH": "ปุ่ม 9 บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "9 (цифровий блок)", + "zh_CN": "小键盘9", + "zh_TW": "數字鍵 9" + } + }, + { + "ID": "KeyKeypadDivide", + "Translations": { + "ar_SA": "لوحة الأرقام علامة القسمة", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Divide", + "es_ES": "Num. /", + "fr_FR": "Num. Diviser", + "he_IL": "", + "it_IT": "Tast. num. /", + "ja_JP": "", + "ko_KR": "키패드 분할", + "no_NO": "Numerisk Dele", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "/ (блок цифр)", + "sv_SE": "Keypad /", + "th_TH": "ปุ่ม / บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "/ (цифровий блок)", + "zh_CN": "小键盘/", + "zh_TW": "數字鍵除號" + } + }, + { + "ID": "KeyKeypadMultiply", + "Translations": { + "ar_SA": "لوحة الأرقام علامة الضرب", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Multiply", + "es_ES": "Num. *", + "fr_FR": "Num. Multiplier", + "he_IL": "", + "it_IT": "Tast. num. *", + "ja_JP": "", + "ko_KR": "키패드 멀티플", + "no_NO": "Numerisk Multiplisere", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "* (блок цифр)", + "sv_SE": "Keypad *", + "th_TH": "ปุ่ม * บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "* (цифровий блок)", + "zh_CN": "小键盘*", + "zh_TW": "數字鍵乘號" + } + }, + { + "ID": "KeyKeypadSubtract", + "Translations": { + "ar_SA": "لوحة الأرقام علامة الطرح\n", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Subtract", + "es_ES": "Num. -", + "fr_FR": "Num. Soustraire", + "he_IL": "", + "it_IT": "Tast. num. -", + "ja_JP": "", + "ko_KR": "키패드 빼기", + "no_NO": "Numerisk Subtrakt", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "- (блок цифр)", + "sv_SE": "Keypad -", + "th_TH": "ปุ่ม - บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "- (цифровий блок)", + "zh_CN": "小键盘-", + "zh_TW": "數字鍵減號" + } + }, + { + "ID": "KeyKeypadAdd", + "Translations": { + "ar_SA": "لوحة الأرقام علامة الزائد", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Add", + "es_ES": "Num. +", + "fr_FR": "Num. Ajouter", + "he_IL": "", + "it_IT": "Tast. num. +", + "ja_JP": "", + "ko_KR": "키패드 추가", + "no_NO": "Numerisk Legg til", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "+ (блок цифр)", + "sv_SE": "Keypad +", + "th_TH": "ปุ่ม + บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "+ (цифровий блок)", + "zh_CN": "小键盘+", + "zh_TW": "數字鍵加號" + } + }, + { + "ID": "KeyKeypadDecimal", + "Translations": { + "ar_SA": "لوحة الأرقام الفاصلة العشرية", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Decimal", + "es_ES": "Num. .", + "fr_FR": "Num. Point", + "he_IL": "", + "it_IT": "Tast. num. sep. decimale", + "ja_JP": "", + "ko_KR": "숫자 키패드", + "no_NO": "Numerisk Desimal", + "pl_PL": "", + "pt_BR": null, + "ru_RU": ". (блок цифр)", + "sv_SE": "Keypad ,", + "th_TH": "ปุ่ม . บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": ". (цифровий блок)", + "zh_CN": "小键盘.", + "zh_TW": "數字鍵小數點" + } + }, + { + "ID": "KeyKeypadEnter", + "Translations": { + "ar_SA": "لوحة الأرقام زر الإدخال", + "de_DE": "", + "el_GR": "", + "en_US": "Keypad Enter", + "es_ES": "Num. Intro", + "fr_FR": "Num. Ent", + "he_IL": "", + "it_IT": "Tast. num. Invio", + "ja_JP": "", + "ko_KR": "키패드 엔터", + "no_NO": "Numerisk Enter", + "pl_PL": "", + "pt_BR": null, + "ru_RU": "Enter (блок цифр)", + "sv_SE": "Enter (numerisk)", + "th_TH": "ปุ่ม Enter บนแป้นตัวเลข", + "tr_TR": "", + "uk_UA": "Enter (цифровий блок)", + "zh_CN": "小键盘回车键", + "zh_TW": "數字鍵 Enter" + } + }, + { + "ID": "KeyNumber0", + "Translations": { + "ar_SA": "٠", + "de_DE": "", + "el_GR": "", + "en_US": "0", + "es_ES": null, + "fr_FR": "à", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber1", + "Translations": { + "ar_SA": "١", + "de_DE": "", + "el_GR": "", + "en_US": "1", + "es_ES": null, + "fr_FR": "&", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber2", + "Translations": { + "ar_SA": "٢", + "de_DE": "", + "el_GR": "", + "en_US": "2", + "es_ES": null, + "fr_FR": "é", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber3", + "Translations": { + "ar_SA": "٣", + "de_DE": "", + "el_GR": "", + "en_US": "3", + "es_ES": null, + "fr_FR": "\"", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber4", + "Translations": { + "ar_SA": "٤", + "de_DE": "", + "el_GR": "", + "en_US": "4", + "es_ES": null, + "fr_FR": "'", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber5", + "Translations": { + "ar_SA": "٥", + "de_DE": "", + "el_GR": "", + "en_US": "5", + "es_ES": null, + "fr_FR": "(", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber6", + "Translations": { + "ar_SA": "٦", + "de_DE": "", + "el_GR": "", + "en_US": "6", + "es_ES": null, + "fr_FR": "-", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber7", + "Translations": { + "ar_SA": "٧", + "de_DE": "", + "el_GR": "", + "en_US": "7", + "es_ES": null, + "fr_FR": "è", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber8", + "Translations": { + "ar_SA": "٨", + "de_DE": "", + "el_GR": "", + "en_US": "8", + "es_ES": null, + "fr_FR": "_", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyNumber9", + "Translations": { + "ar_SA": "٩", + "de_DE": "", + "el_GR": "", + "en_US": "9", + "es_ES": null, + "fr_FR": "ç", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyTilde", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "~", + "es_ES": "ñ", + "fr_FR": "ù", + "he_IL": "", + "it_IT": "ò", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyGrave", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "`", + "es_ES": "º", + "fr_FR": null, + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyMinus", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "-", + "es_ES": null, + "fr_FR": null, + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyPlus", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "+", + "es_ES": null, + "fr_FR": "=", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyBracketLeft", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "[", + "es_ES": "'", + "fr_FR": ")", + "he_IL": "", + "it_IT": "'", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyBracketRight", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "]", + "es_ES": "¡", + "fr_FR": "^", + "he_IL": "", + "it_IT": "ì", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeySemicolon", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": ";", + "es_ES": "`", + "fr_FR": "$", + "he_IL": "", + "it_IT": "è", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyQuote", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "\"", + "es_ES": "´", + "fr_FR": "²", + "he_IL": "", + "it_IT": "à", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyComma", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": ",", + "es_ES": null, + "fr_FR": null, + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyPeriod", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": ".", + "es_ES": null, + "fr_FR": ";", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeySlash", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "/", + "es_ES": "ç", + "fr_FR": ":", + "he_IL": "", + "it_IT": "ù", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyBackSlash", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "\\", + "es_ES": "<", + "fr_FR": "<", + "he_IL": "", + "it_IT": "<", + "ja_JP": "", + "ko_KR": null, + "no_NO": "", + "pl_PL": "", + "pt_BR": null, + "ru_RU": null, + "sv_SE": null, + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": null, + "zh_TW": null + } + }, + { + "ID": "KeyUnbound", + "Translations": { + "ar_SA": "غير مرتبط", + "de_DE": "", + "el_GR": "", + "en_US": "Unbound", + "es_ES": "Sin Asignar", + "fr_FR": "Non Attribuée", + "he_IL": "", + "it_IT": "Non assegnato", + "ja_JP": "", + "ko_KR": "설정 안함", + "no_NO": "Ikke bundet", + "pl_PL": "", + "pt_BR": "Não Atribuído", + "ru_RU": "Не привязано", + "sv_SE": "Obunden", + "th_TH": "ยังไม่กำหนดปุ่ม", + "tr_TR": "", + "uk_UA": "Відв'язати", + "zh_CN": "未分配", + "zh_TW": "未分配" + } + }, + { + "ID": "AllKeyboards", + "Translations": { + "ar_SA": "كل لوحات المفاتيح", + "de_DE": "Alle Tastaturen", + "el_GR": "Όλα τα πληκτρολόγια", + "en_US": "All Keyboards", + "es_ES": "Todos los Teclados", + "fr_FR": "Tous les Claviers", + "he_IL": "כל המקלדות", + "it_IT": "Tutte le tastiere", + "ja_JP": "すべてのキーボード", + "ko_KR": "모든 키보드", + "no_NO": "Alle tastaturer", + "pl_PL": "Wszystkie klawiatury", + "pt_BR": "Todos os Teclados", + "ru_RU": "Все клавиатуры", + "sv_SE": "Alla tangentbord", + "th_TH": "คีย์บอร์ดทั้งหมด", + "tr_TR": "Tüm Klavyeler", + "uk_UA": "Усі клавіатури", + "zh_CN": "所有键盘", + "zh_TW": "所有鍵盤" + } + } + ] +} \ No newline at end of file diff --git a/assets/Locales/Root.json b/assets/Locales/Root.json index ac9aa09a4..dd6cab6f7 100644 --- a/assets/Locales/Root.json +++ b/assets/Locales/Root.json @@ -8650,1906 +8650,6 @@ "zh_TW": "關閉" } }, - { - "ID": "KeyUnknown", - "Translations": { - "ar_SA": "مجهول", - "de_DE": "Unbekannt", - "el_GR": "", - "en_US": "Unknown", - "es_ES": "Desconocido", - "fr_FR": "Inconnu", - "he_IL": "", - "it_IT": "Sconosciuto", - "ja_JP": "", - "ko_KR": "알 수 없음", - "no_NO": "Ukjent", - "pl_PL": "", - "pt_BR": "Desconhecido", - "ru_RU": "Неизвестно", - "sv_SE": "Okänd", - "th_TH": "ไม่รู้จัก", - "tr_TR": "", - "uk_UA": "Невизначено", - "zh_CN": "未知", - "zh_TW": "未知" - } - }, - { - "ID": "KeyShiftLeft", - "Translations": { - "ar_SA": "زر ‫Shift الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "Shift Left", - "es_ES": "Mayús Izquierda", - "fr_FR": "Maj Gauche", - "he_IL": "", - "it_IT": "Maiusc sinistro", - "ja_JP": "", - "ko_KR": "좌측 Shift", - "no_NO": "Skift venstre", - "pl_PL": "", - "pt_BR": "Shift Esquerdo", - "ru_RU": "Левый Shift", - "sv_SE": "Skift vänster", - "th_TH": "Shift ซ้าย", - "tr_TR": "Sol Shift", - "uk_UA": "Shift Лівий", - "zh_CN": "左侧Shift", - "zh_TW": "左 Shift" - } - }, - { - "ID": "KeyShiftRight", - "Translations": { - "ar_SA": "زر ‫Shift الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "Shift Right", - "es_ES": "Mayús Derecha", - "fr_FR": "Maj Droite", - "he_IL": "", - "it_IT": "Maiusc destro", - "ja_JP": "", - "ko_KR": "우측 Shift", - "no_NO": "Skift høyre", - "pl_PL": "", - "pt_BR": "Shift Direito", - "ru_RU": "Правый Shift", - "sv_SE": "Skift höger", - "th_TH": "Shift ขวา", - "tr_TR": "Sağ Shift", - "uk_UA": "Shift Правий", - "zh_CN": "右侧Shift", - "zh_TW": "右 Shift" - } - }, - { - "ID": "KeyControlLeft", - "Translations": { - "ar_SA": "زر ‫Ctrl الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "Ctrl Left", - "es_ES": "Alt Gr", - "fr_FR": "Alt Gr", - "he_IL": "", - "it_IT": "Ctrl sinistro", - "ja_JP": "", - "ko_KR": "좌측 Ctrl", - "no_NO": "Ctrl venstre", - "pl_PL": "", - "pt_BR": "Ctrl Esquerdo", - "ru_RU": "Левый Ctrl", - "sv_SE": "Ctrl vänster", - "th_TH": "Ctrl ซ้าย", - "tr_TR": "Sol Ctrl", - "uk_UA": "Ctrl Лівий", - "zh_CN": "左侧Ctrl", - "zh_TW": "左 Ctrl" - } - }, - { - "ID": "KeyMacControlLeft", - "Translations": { - "ar_SA": "زر ⌃ الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "⌃ Left", - "es_ES": "⌃ Izquierdo", - "fr_FR": "⌃ Gauche", - "he_IL": "", - "it_IT": "⌃ sinistro", - "ja_JP": "", - "ko_KR": "좌측 ⌃", - "no_NO": "⌃ Venstre", - "pl_PL": "", - "pt_BR": "⌃ Esquerda", - "ru_RU": "Левый ⌃", - "sv_SE": "^ Vänster", - "th_TH": "^ ซ้าย", - "tr_TR": "⌃ Sol", - "uk_UA": "⌃ Лівий", - "zh_CN": "左侧⌃", - "zh_TW": "左 ⌃" - } - }, - { - "ID": "KeyControlRight", - "Translations": { - "ar_SA": "زر ‫Ctrl الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "Ctrl Right", - "es_ES": "Ctrl Derecho", - "fr_FR": "Ctrl Droite", - "he_IL": "", - "it_IT": "Ctrl destro", - "ja_JP": "", - "ko_KR": "우측 Ctrl", - "no_NO": "Ctrl høyre", - "pl_PL": "", - "pt_BR": "Ctrl Direito", - "ru_RU": "Правый Ctrl", - "sv_SE": "Ctrl höger", - "th_TH": "Ctrl ขวา", - "tr_TR": "Sağ Control", - "uk_UA": "Ctrl Правий", - "zh_CN": "右侧Ctrl", - "zh_TW": "右 Ctrl" - } - }, - { - "ID": "KeyMacControlRight", - "Translations": { - "ar_SA": "زر ⌃ الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "⌃ Right", - "es_ES": "⌃ Derecho", - "fr_FR": "⌃ Droite", - "he_IL": "", - "it_IT": "⌃ destro", - "ja_JP": "", - "ko_KR": "우측 ⌃", - "no_NO": "⌃ Høyre", - "pl_PL": "", - "pt_BR": "⌃ Direito", - "ru_RU": "Правый ⌃", - "sv_SE": "^ Höger", - "th_TH": "⌃ ขวา", - "tr_TR": "⌃ Sağ", - "uk_UA": "⌃ Правий", - "zh_CN": "右侧⌃", - "zh_TW": "右 ⌃" - } - }, - { - "ID": "KeyAltLeft", - "Translations": { - "ar_SA": "زر ‫Alt الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "Alt Left", - "es_ES": "Alt Izquierdo", - "fr_FR": "Alt Gauche", - "he_IL": "", - "it_IT": "Alt sinistro", - "ja_JP": "", - "ko_KR": "좌측 Alt", - "no_NO": "Alt Venstre", - "pl_PL": "", - "pt_BR": "Alt Esquerdo", - "ru_RU": "Левый Alt", - "sv_SE": "Alt vänster", - "th_TH": "Alt ซ้าย", - "tr_TR": "Sol Alt", - "uk_UA": "Alt Лівий", - "zh_CN": "左侧Alt", - "zh_TW": "左 Alt" - } - }, - { - "ID": "KeyMacAltLeft", - "Translations": { - "ar_SA": "زر ⌥ الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "⌥ Left", - "es_ES": "⌥ Izquierdo", - "fr_FR": "⌥ Gauche", - "he_IL": "", - "it_IT": "⌥ sinistro", - "ja_JP": "", - "ko_KR": "좌측 ⌥", - "no_NO": "⌥ Venstre", - "pl_PL": "", - "pt_BR": "⌥ Esquerda", - "ru_RU": "Левый ⌥", - "sv_SE": "⌥ vänster", - "th_TH": "⌥ ซ้าย", - "tr_TR": "⌥ Sol", - "uk_UA": "⌥ Лівий", - "zh_CN": "左侧⌥", - "zh_TW": "左 ⌥" - } - }, - { - "ID": "KeyAltRight", - "Translations": { - "ar_SA": "زر ‫Alt الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "Alt Right", - "es_ES": "Alt Derecho", - "fr_FR": "Alt Droite", - "he_IL": "", - "it_IT": "Alt destro", - "ja_JP": "", - "ko_KR": "우측 Alt", - "no_NO": "Alt høyre", - "pl_PL": "", - "pt_BR": "Alt Direito", - "ru_RU": "Правый Alt", - "sv_SE": "Alt höger", - "th_TH": "Alt ขวา", - "tr_TR": "Sağ Alt", - "uk_UA": "Alt Правий", - "zh_CN": "右侧Alt", - "zh_TW": "右 Alt" - } - }, - { - "ID": "KeyMacAltRight", - "Translations": { - "ar_SA": "زر ⌥ الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "⌥ Right", - "es_ES": "⌥ Derecho", - "fr_FR": "⌥ Droite", - "he_IL": "", - "it_IT": "⌥ destro", - "ja_JP": "", - "ko_KR": "우측 ⌥", - "no_NO": "⌥ Høyre", - "pl_PL": "", - "pt_BR": "⌥ Direito", - "ru_RU": "Правый ⌥", - "sv_SE": "⌥ höger", - "th_TH": "⌥ ขวา", - "tr_TR": "⌥ Sağ", - "uk_UA": "⌥ Правий", - "zh_CN": "右侧⌥", - "zh_TW": "右 ⌥" - } - }, - { - "ID": "KeyWinLeft", - "Translations": { - "ar_SA": "زر ⊞ الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "⊞ Left", - "es_ES": "⊞ Izquierdo", - "fr_FR": "⊞ Gauche", - "he_IL": "", - "it_IT": "⊞ sinistro", - "ja_JP": "", - "ko_KR": "좌측 ⊞", - "no_NO": "⊞ Venstre", - "pl_PL": "", - "pt_BR": "⊞ Esquerdo", - "ru_RU": "Левый ⊞", - "sv_SE": "⊞ vänster", - "th_TH": "⊞ ซ้าย", - "tr_TR": "⊞ Sol", - "uk_UA": "⊞ Лівий", - "zh_CN": "左侧⊞", - "zh_TW": "左 ⊞" - } - }, - { - "ID": "KeyMacWinLeft", - "Translations": { - "ar_SA": "زر ⌘ الأيسر", - "de_DE": "", - "el_GR": "", - "en_US": "⌘ Left", - "es_ES": "⌘ Izqierdo", - "fr_FR": "⌘ Gauche", - "he_IL": "", - "it_IT": "⌘ sinistro", - "ja_JP": "", - "ko_KR": "좌측 ⌘", - "no_NO": "⌘ Venstre", - "pl_PL": "", - "pt_BR": "⌘ Esquerdo", - "ru_RU": "Левый ⌘", - "sv_SE": "⌘ vänster", - "th_TH": "⌘ ซ้าย", - "tr_TR": "⌘ Sol", - "uk_UA": "⌘ Лівий", - "zh_CN": "左侧⌘", - "zh_TW": "左 ⌘" - } - }, - { - "ID": "KeyWinRight", - "Translations": { - "ar_SA": "زر ⊞ الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "⊞ Right", - "es_ES": "⊞ Derecho", - "fr_FR": "⊞ Droite", - "he_IL": "", - "it_IT": "⊞ destro", - "ja_JP": "", - "ko_KR": "우측 ⊞", - "no_NO": "⊞ Høyre", - "pl_PL": "", - "pt_BR": "⊞ Direito", - "ru_RU": "Правый ⊞", - "sv_SE": "⊞ höger", - "th_TH": "⊞ ขวา", - "tr_TR": "⊞ Sağ", - "uk_UA": "⊞ Правий", - "zh_CN": "右侧⊞", - "zh_TW": "右 ⊞" - } - }, - { - "ID": "KeyMacWinRight", - "Translations": { - "ar_SA": "زر ⌘ الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "⌘ Right", - "es_ES": "⌘ Derecho", - "fr_FR": "⌘ Droite", - "he_IL": "", - "it_IT": "⌘ destro", - "ja_JP": "", - "ko_KR": "우측 ⌘", - "no_NO": "⌘ Høyre", - "pl_PL": "", - "pt_BR": "⌘ Direito", - "ru_RU": "Правый ⌘", - "sv_SE": "⌘ höger", - "th_TH": "⌘ ขวา", - "tr_TR": "⌘ Sağ", - "uk_UA": "⌘ Правий", - "zh_CN": "右侧⌘", - "zh_TW": "右 ⌘" - } - }, - { - "ID": "KeyMenu", - "Translations": { - "ar_SA": "زر القائمة", - "de_DE": "", - "el_GR": "", - "en_US": "Menu", - "es_ES": null, - "fr_FR": null, - "he_IL": "", - "it_IT": "Menù", - "ja_JP": "", - "ko_KR": "메뉴", - "no_NO": "Meny", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Меню", - "sv_SE": "Meny", - "th_TH": "เมนู", - "tr_TR": "Menü", - "uk_UA": "Меню", - "zh_CN": "菜单键", - "zh_TW": "功能表鍵" - } - }, - { - "ID": "KeyUp", - "Translations": { - "ar_SA": "فوق", - "de_DE": "", - "el_GR": "", - "en_US": "Up", - "es_ES": "Arriba", - "fr_FR": "Haut", - "he_IL": "", - "it_IT": "Su", - "ja_JP": "", - "ko_KR": "↑", - "no_NO": "Opp", - "pl_PL": "", - "pt_BR": "Cima", - "ru_RU": "Вверх", - "sv_SE": "Upp", - "th_TH": "ขึ้น", - "tr_TR": "Yukarı", - "uk_UA": "Вгору ↑", - "zh_CN": "上", - "zh_TW": "上" - } - }, - { - "ID": "KeyDown", - "Translations": { - "ar_SA": "اسفل", - "de_DE": "", - "el_GR": "", - "en_US": "Down", - "es_ES": "Abajo", - "fr_FR": "Bas", - "he_IL": "", - "it_IT": "Giù", - "ja_JP": "", - "ko_KR": "↓", - "no_NO": "Ned", - "pl_PL": "", - "pt_BR": "Baixo", - "ru_RU": "Вниз", - "sv_SE": "Ner", - "th_TH": "ลง", - "tr_TR": "Aşağı", - "uk_UA": "Вниз ↓", - "zh_CN": "下", - "zh_TW": "下" - } - }, - { - "ID": "KeyLeft", - "Translations": { - "ar_SA": "يسار", - "de_DE": "", - "el_GR": "", - "en_US": "Left", - "es_ES": "Izquierda", - "fr_FR": "Gauche", - "he_IL": "", - "it_IT": "Sinistra", - "ja_JP": "", - "ko_KR": "←", - "no_NO": "Venstre", - "pl_PL": "", - "pt_BR": "Esquerda", - "ru_RU": "Влево", - "sv_SE": "Vänster", - "th_TH": "ซ้าย", - "tr_TR": "Sol", - "uk_UA": "Вліво ←", - "zh_CN": "左", - "zh_TW": "左" - } - }, - { - "ID": "KeyRight", - "Translations": { - "ar_SA": "يمين", - "de_DE": "", - "el_GR": "", - "en_US": "Right", - "es_ES": "Derecha", - "fr_FR": "Droite", - "he_IL": "", - "it_IT": "Destra", - "ja_JP": "", - "ko_KR": "→", - "no_NO": "Høyre", - "pl_PL": "", - "pt_BR": "Direita", - "ru_RU": "Вправо", - "sv_SE": "Höger", - "th_TH": "ขวา", - "tr_TR": "Sağ", - "uk_UA": "Вправо →", - "zh_CN": "右", - "zh_TW": "右" - } - }, - { - "ID": "KeyEnter", - "Translations": { - "ar_SA": "مفتاح الإدخال", - "de_DE": "", - "el_GR": "", - "en_US": "Enter", - "es_ES": "Intro", - "fr_FR": "Entrée", - "he_IL": "", - "it_IT": "Invio", - "ja_JP": "", - "ko_KR": "엔터", - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Enter", - "tr_TR": "", - "uk_UA": "", - "zh_CN": "回车键", - "zh_TW": "Enter 鍵" - } - }, - { - "ID": "KeyEscape", - "Translations": { - "ar_SA": "زر ‫Escape", - "de_DE": "", - "el_GR": "", - "en_US": "Escape", - "es_ES": "Esc", - "fr_FR": "Esc", - "he_IL": "", - "it_IT": "Esc", - "ja_JP": "", - "ko_KR": "Esc", - "no_NO": "Esc-tast", - "pl_PL": "", - "pt_BR": "Esc", - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Escape", - "tr_TR": "Esc", - "uk_UA": "Esc", - "zh_CN": "Esc", - "zh_TW": "Esc 鍵" - } - }, - { - "ID": "KeySpace", - "Translations": { - "ar_SA": "مسافة", - "de_DE": "", - "el_GR": "", - "en_US": "Space", - "es_ES": "Espacio", - "fr_FR": "Espace", - "he_IL": "", - "it_IT": "Spazio", - "ja_JP": "", - "ko_KR": "스페이스", - "no_NO": "Mellomrom", - "pl_PL": "", - "pt_BR": "Espaço", - "ru_RU": "Пробел", - "sv_SE": "Blanksteg", - "th_TH": "ปุ่ม Spacebar", - "tr_TR": "", - "uk_UA": "Пробіл", - "zh_CN": "空格键", - "zh_TW": "空白鍵" - } - }, - { - "ID": "KeyTab", - "Translations": { - "ar_SA": "زر ‫Tab", - "de_DE": "", - "el_GR": "", - "en_US": "Tab", - "es_ES": null, - "fr_FR": null, - "he_IL": "", - "it_IT": null, - "ja_JP": "", - "ko_KR": "탭", - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Tab", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Tab 鍵" - } - }, - { - "ID": "KeyBackSpace", - "Translations": { - "ar_SA": "زر المسح للخلف", - "de_DE": "", - "el_GR": "", - "en_US": "Backspace", - "es_ES": "Retroceso", - "fr_FR": "Retour arrière", - "he_IL": "", - "it_IT": "Canc", - "ja_JP": "", - "ko_KR": "백스페이스", - "no_NO": "Tilbaketast", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": "Backsteg", - "th_TH": "ปุ่ม Backspace", - "tr_TR": "Geri tuşu", - "uk_UA": "", - "zh_CN": "退格键", - "zh_TW": "Backspace 鍵" - } - }, - { - "ID": "KeyInsert", - "Translations": { - "ar_SA": "زر Insert", - "de_DE": "", - "el_GR": "", - "en_US": "Insert", - "es_ES": null, - "fr_FR": "Inser", - "he_IL": "", - "it_IT": "Ins", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Sett inn", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Insert", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Insert 鍵" - } - }, - { - "ID": "KeyDelete", - "Translations": { - "ar_SA": "زر الحذف", - "de_DE": "", - "el_GR": "", - "en_US": "Delete", - "es_ES": "Supr", - "fr_FR": "Suppr", - "he_IL": "", - "it_IT": "Canc", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Slett", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Delete", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Delete 鍵" - } - }, - { - "ID": "KeyPageUp", - "Translations": { - "ar_SA": "زر ‫Page Up", - "de_DE": "", - "el_GR": "", - "en_US": "Page Up", - "es_ES": "Re Pág", - "fr_FR": "Pg.Suiv", - "he_IL": "", - "it_IT": "Pag. Su", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Side opp", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Page Up", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "向上捲頁鍵" - } - }, - { - "ID": "KeyPageDown", - "Translations": { - "ar_SA": "زر ‫Page Down", - "de_DE": "", - "el_GR": "", - "en_US": "Page Down", - "es_ES": "Av Pág", - "fr_FR": "Pg.Préc", - "he_IL": "", - "it_IT": "Pag. Giù", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Side ned", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Page Down", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "向下捲頁鍵" - } - }, - { - "ID": "KeyHome", - "Translations": { - "ar_SA": "زر ‫Home", - "de_DE": "", - "el_GR": "", - "en_US": "Home", - "es_ES": "Inicio", - "fr_FR": null, - "he_IL": "", - "it_IT": "Inizio", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Hjem", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Home", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Home 鍵" - } - }, - { - "ID": "KeyEnd", - "Translations": { - "ar_SA": "زر ‫End", - "de_DE": "", - "el_GR": "", - "en_US": "End", - "es_ES": "Fin", - "fr_FR": "Fin", - "he_IL": "", - "it_IT": "Fine", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Avslutt", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม End", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "End 鍵" - } - }, - { - "ID": "KeyCapsLock", - "Translations": { - "ar_SA": "زر الحروف الكبيرة", - "de_DE": "", - "el_GR": "", - "en_US": "Caps Lock", - "es_ES": "Bloq Mayús", - "fr_FR": "Verr. Maj", - "he_IL": "", - "it_IT": "Bloc Maiusc", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Skiftelås", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Caps Lock", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Caps Lock 鍵" - } - }, - { - "ID": "KeyScrollLock", - "Translations": { - "ar_SA": "زر ‫Scroll Lock", - "de_DE": "", - "el_GR": "", - "en_US": "Scroll Lock", - "es_ES": "Bloq Despl", - "fr_FR": "Arr. Déf.", - "he_IL": "", - "it_IT": "Bloc Scorr", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Rullelås", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Scroll Lock", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Scroll Lock 鍵" - } - }, - { - "ID": "KeyPrintScreen", - "Translations": { - "ar_SA": "زر ‫Print Screen", - "de_DE": "", - "el_GR": "", - "en_US": "Print Screen", - "es_ES": "Impr Pant", - "fr_FR": "Impr Écran", - "he_IL": "", - "it_IT": "Stamp", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Skjermbilde", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Print Screen", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Print Screen 鍵" - } - }, - { - "ID": "KeyPause", - "Translations": { - "ar_SA": "زر Pause", - "de_DE": "", - "el_GR": "", - "en_US": "Pause", - "es_ES": "Pausa", - "fr_FR": null, - "he_IL": "", - "it_IT": "Pausa", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Stans midlertidig", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Pause", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Pause 鍵" - } - }, - { - "ID": "KeyNumLock", - "Translations": { - "ar_SA": "زر Num Lock", - "de_DE": "", - "el_GR": "", - "en_US": "Num Lock", - "es_ES": "Bloq Num", - "fr_FR": "Verr. Num", - "he_IL": "", - "it_IT": "Bloc Num", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Numerisk Lås", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "ปุ่ม Num Lock", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": "Num Lock 鍵" - } - }, - { - "ID": "KeyClear", - "Translations": { - "ar_SA": "زر Clear", - "de_DE": "", - "el_GR": "", - "en_US": "Clear", - "es_ES": "Borrar", - "fr_FR": "Effacer", - "he_IL": "", - "it_IT": "Cancella", - "ja_JP": "", - "ko_KR": null, - "no_NO": "Tøm", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": "Töm", - "th_TH": "ล้าง", - "tr_TR": "", - "uk_UA": "Очистити", - "zh_CN": "清除键", - "zh_TW": "清除" - } - }, - { - "ID": "KeyKeypad0", - "Translations": { - "ar_SA": "لوحة الأرقام 0", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 0", - "es_ES": "Num. 0", - "fr_FR": "Num. 0", - "he_IL": "", - "it_IT": "Tast. num. 0", - "ja_JP": "", - "ko_KR": "키패드 0", - "no_NO": "Numerisk 0", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 0", - "sv_SE": "Numerisk 0", - "th_TH": "ปุ่ม 0 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "Блок цифр 0", - "zh_CN": "小键盘0", - "zh_TW": "數字鍵 0" - } - }, - { - "ID": "KeyKeypad1", - "Translations": { - "ar_SA": "لوحة الأرقام 1", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 1", - "es_ES": "Num. 1", - "fr_FR": "Num. 1", - "he_IL": "", - "it_IT": "Tast. num. 1", - "ja_JP": "", - "ko_KR": "키패드 1", - "no_NO": "Numerisk 1", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 1", - "sv_SE": "Numerisk 1", - "th_TH": "ปุ่ม 1 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "Блок цифр 1", - "zh_CN": "小键盘1", - "zh_TW": "數字鍵 1" - } - }, - { - "ID": "KeyKeypad2", - "Translations": { - "ar_SA": "لوحة الأرقام 2", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 2", - "es_ES": "Num. 2", - "fr_FR": "Num. 2", - "he_IL": "", - "it_IT": "Tast. num. 2", - "ja_JP": "", - "ko_KR": "키패드 2", - "no_NO": "Numerisk 2", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 2", - "sv_SE": "Numerisk 2", - "th_TH": "ปุ่ม 2 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "2 (цифровий блок)", - "zh_CN": "小键盘2", - "zh_TW": "數字鍵 2" - } - }, - { - "ID": "KeyKeypad3", - "Translations": { - "ar_SA": "لوحة الأرقام 3", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 3", - "es_ES": "Num. 3", - "fr_FR": "Num. 3", - "he_IL": "", - "it_IT": "Tast. num. 3", - "ja_JP": "", - "ko_KR": "키패드 3", - "no_NO": "Numerisk 3", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 3", - "sv_SE": "Numerisk 3", - "th_TH": "ปุ่ม 3 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "3 (цифровий блок)", - "zh_CN": "小键盘3", - "zh_TW": "數字鍵 3" - } - }, - { - "ID": "KeyKeypad4", - "Translations": { - "ar_SA": "لوحة الأرقام 4", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 4", - "es_ES": "Num. 4", - "fr_FR": "Num. 4", - "he_IL": "", - "it_IT": "Tast. num. 4", - "ja_JP": "", - "ko_KR": "키패드 4", - "no_NO": "Numerisk 4", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 4", - "sv_SE": "Numerisk 4", - "th_TH": "ปุ่ม 4 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "4 (цифровий блок)", - "zh_CN": "小键盘4", - "zh_TW": "數字鍵 4" - } - }, - { - "ID": "KeyKeypad5", - "Translations": { - "ar_SA": "لوحة الأرقام 5", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 5", - "es_ES": "Num. 5", - "fr_FR": "Num. 5", - "he_IL": "", - "it_IT": "Tast. num. 5", - "ja_JP": "", - "ko_KR": "키패드 5", - "no_NO": "Numerisk 5", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 5", - "sv_SE": "Numerisk 5", - "th_TH": "ปุ่ม 5 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "5 (цифровий блок)", - "zh_CN": "小键盘5", - "zh_TW": "數字鍵 5" - } - }, - { - "ID": "KeyKeypad6", - "Translations": { - "ar_SA": "لوحة الأرقام 6", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 6", - "es_ES": "Num. 6", - "fr_FR": "Num. 6", - "he_IL": "", - "it_IT": "Tast. num. 6", - "ja_JP": "", - "ko_KR": "키패드 6", - "no_NO": "Numerisk 6", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 6", - "sv_SE": "Numerisk 6", - "th_TH": "ปุ่ม 6 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "6 (цифровий блок)", - "zh_CN": "小键盘6", - "zh_TW": "數字鍵 6" - } - }, - { - "ID": "KeyKeypad7", - "Translations": { - "ar_SA": "لوحة الأرقام 7", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 7", - "es_ES": "Num. 7", - "fr_FR": "Num. 7", - "he_IL": "", - "it_IT": "Tast. num. 7", - "ja_JP": "", - "ko_KR": "키패드 7", - "no_NO": "Numerisk 7", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 7", - "sv_SE": "Numerisk 7", - "th_TH": "ปุ่ม 7 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "7 (цифровий блок)", - "zh_CN": "小键盘7", - "zh_TW": "數字鍵 7" - } - }, - { - "ID": "KeyKeypad8", - "Translations": { - "ar_SA": "لوحة الأرقام 8", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 8", - "es_ES": "Num. 8", - "fr_FR": "Num. 8", - "he_IL": "", - "it_IT": "Tast. num. 8", - "ja_JP": "", - "ko_KR": "키패드 8", - "no_NO": "Numerisk 8", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 8", - "sv_SE": "Numerisk 8", - "th_TH": "ปุ่ม 8 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "8 (цифровий блок)", - "zh_CN": "小键盘8", - "zh_TW": "數字鍵 8" - } - }, - { - "ID": "KeyKeypad9", - "Translations": { - "ar_SA": "لوحة الأرقام 9", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad 9", - "es_ES": "Num. 9", - "fr_FR": "Num. 9", - "he_IL": "", - "it_IT": "Tast. num. 9", - "ja_JP": "", - "ko_KR": "키패드 9", - "no_NO": "Numerisk 9", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Блок цифр 9", - "sv_SE": "Numerisk 9", - "th_TH": "ปุ่ม 9 บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "9 (цифровий блок)", - "zh_CN": "小键盘9", - "zh_TW": "數字鍵 9" - } - }, - { - "ID": "KeyKeypadDivide", - "Translations": { - "ar_SA": "لوحة الأرقام علامة القسمة", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Divide", - "es_ES": "Num. /", - "fr_FR": "Num. Diviser", - "he_IL": "", - "it_IT": "Tast. num. /", - "ja_JP": "", - "ko_KR": "키패드 분할", - "no_NO": "Numerisk Dele", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "/ (блок цифр)", - "sv_SE": "Keypad /", - "th_TH": "ปุ่ม / บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "/ (цифровий блок)", - "zh_CN": "小键盘/", - "zh_TW": "數字鍵除號" - } - }, - { - "ID": "KeyKeypadMultiply", - "Translations": { - "ar_SA": "لوحة الأرقام علامة الضرب", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Multiply", - "es_ES": "Num. *", - "fr_FR": "Num. Multiplier", - "he_IL": "", - "it_IT": "Tast. num. *", - "ja_JP": "", - "ko_KR": "키패드 멀티플", - "no_NO": "Numerisk Multiplisere", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "* (блок цифр)", - "sv_SE": "Keypad *", - "th_TH": "ปุ่ม * บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "* (цифровий блок)", - "zh_CN": "小键盘*", - "zh_TW": "數字鍵乘號" - } - }, - { - "ID": "KeyKeypadSubtract", - "Translations": { - "ar_SA": "لوحة الأرقام علامة الطرح\n", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Subtract", - "es_ES": "Num. -", - "fr_FR": "Num. Soustraire", - "he_IL": "", - "it_IT": "Tast. num. -", - "ja_JP": "", - "ko_KR": "키패드 빼기", - "no_NO": "Numerisk Subtrakt", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "- (блок цифр)", - "sv_SE": "Keypad -", - "th_TH": "ปุ่ม - บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "- (цифровий блок)", - "zh_CN": "小键盘-", - "zh_TW": "數字鍵減號" - } - }, - { - "ID": "KeyKeypadAdd", - "Translations": { - "ar_SA": "لوحة الأرقام علامة الزائد", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Add", - "es_ES": "Num. +", - "fr_FR": "Num. Ajouter", - "he_IL": "", - "it_IT": "Tast. num. +", - "ja_JP": "", - "ko_KR": "키패드 추가", - "no_NO": "Numerisk Legg til", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "+ (блок цифр)", - "sv_SE": "Keypad +", - "th_TH": "ปุ่ม + บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "+ (цифровий блок)", - "zh_CN": "小键盘+", - "zh_TW": "數字鍵加號" - } - }, - { - "ID": "KeyKeypadDecimal", - "Translations": { - "ar_SA": "لوحة الأرقام الفاصلة العشرية", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Decimal", - "es_ES": "Num. .", - "fr_FR": "Num. Point", - "he_IL": "", - "it_IT": "Tast. num. sep. decimale", - "ja_JP": "", - "ko_KR": "숫자 키패드", - "no_NO": "Numerisk Desimal", - "pl_PL": "", - "pt_BR": null, - "ru_RU": ". (блок цифр)", - "sv_SE": "Keypad ,", - "th_TH": "ปุ่ม . บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": ". (цифровий блок)", - "zh_CN": "小键盘.", - "zh_TW": "數字鍵小數點" - } - }, - { - "ID": "KeyKeypadEnter", - "Translations": { - "ar_SA": "لوحة الأرقام زر الإدخال", - "de_DE": "", - "el_GR": "", - "en_US": "Keypad Enter", - "es_ES": "Num. Intro", - "fr_FR": "Num. Ent", - "he_IL": "", - "it_IT": "Tast. num. Invio", - "ja_JP": "", - "ko_KR": "키패드 엔터", - "no_NO": "Numerisk Enter", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Enter (блок цифр)", - "sv_SE": "Enter (numerisk)", - "th_TH": "ปุ่ม Enter บนแป้นตัวเลข", - "tr_TR": "", - "uk_UA": "Enter (цифровий блок)", - "zh_CN": "小键盘回车键", - "zh_TW": "數字鍵 Enter" - } - }, - { - "ID": "KeyNumber0", - "Translations": { - "ar_SA": "٠", - "de_DE": "", - "el_GR": "", - "en_US": "0", - "es_ES": null, - "fr_FR": "à", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber1", - "Translations": { - "ar_SA": "١", - "de_DE": "", - "el_GR": "", - "en_US": "1", - "es_ES": null, - "fr_FR": "&", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber2", - "Translations": { - "ar_SA": "٢", - "de_DE": "", - "el_GR": "", - "en_US": "2", - "es_ES": null, - "fr_FR": "é", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber3", - "Translations": { - "ar_SA": "٣", - "de_DE": "", - "el_GR": "", - "en_US": "3", - "es_ES": null, - "fr_FR": "\"", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber4", - "Translations": { - "ar_SA": "٤", - "de_DE": "", - "el_GR": "", - "en_US": "4", - "es_ES": null, - "fr_FR": "'", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber5", - "Translations": { - "ar_SA": "٥", - "de_DE": "", - "el_GR": "", - "en_US": "5", - "es_ES": null, - "fr_FR": "(", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber6", - "Translations": { - "ar_SA": "٦", - "de_DE": "", - "el_GR": "", - "en_US": "6", - "es_ES": null, - "fr_FR": "-", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber7", - "Translations": { - "ar_SA": "٧", - "de_DE": "", - "el_GR": "", - "en_US": "7", - "es_ES": null, - "fr_FR": "è", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber8", - "Translations": { - "ar_SA": "٨", - "de_DE": "", - "el_GR": "", - "en_US": "8", - "es_ES": null, - "fr_FR": "_", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyNumber9", - "Translations": { - "ar_SA": "٩", - "de_DE": "", - "el_GR": "", - "en_US": "9", - "es_ES": null, - "fr_FR": "ç", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyTilde", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "~", - "es_ES": "ñ", - "fr_FR": "ù", - "he_IL": "", - "it_IT": "ò", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyGrave", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "`", - "es_ES": "º", - "fr_FR": null, - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyMinus", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "-", - "es_ES": null, - "fr_FR": null, - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyPlus", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "+", - "es_ES": null, - "fr_FR": "=", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyBracketLeft", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "[", - "es_ES": "'", - "fr_FR": ")", - "he_IL": "", - "it_IT": "'", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyBracketRight", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "]", - "es_ES": "¡", - "fr_FR": "^", - "he_IL": "", - "it_IT": "ì", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeySemicolon", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": ";", - "es_ES": "`", - "fr_FR": "$", - "he_IL": "", - "it_IT": "è", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyQuote", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "\"", - "es_ES": "´", - "fr_FR": "²", - "he_IL": "", - "it_IT": "à", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyComma", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": ",", - "es_ES": null, - "fr_FR": null, - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyPeriod", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": ".", - "es_ES": null, - "fr_FR": ";", - "he_IL": "", - "it_IT": "", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeySlash", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "/", - "es_ES": "ç", - "fr_FR": ":", - "he_IL": "", - "it_IT": "ù", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyBackSlash", - "Translations": { - "ar_SA": "", - "de_DE": "", - "el_GR": "", - "en_US": "\\", - "es_ES": "<", - "fr_FR": "<", - "he_IL": "", - "it_IT": "<", - "ja_JP": "", - "ko_KR": null, - "no_NO": "", - "pl_PL": "", - "pt_BR": null, - "ru_RU": null, - "sv_SE": null, - "th_TH": "", - "tr_TR": "", - "uk_UA": "", - "zh_CN": null, - "zh_TW": null - } - }, - { - "ID": "KeyUnbound", - "Translations": { - "ar_SA": "غير مرتبط", - "de_DE": "", - "el_GR": "", - "en_US": "Unbound", - "es_ES": "Sin Asignar", - "fr_FR": "Non Attribuée", - "he_IL": "", - "it_IT": "Non assegnato", - "ja_JP": "", - "ko_KR": "설정 안함", - "no_NO": "Ikke bundet", - "pl_PL": "", - "pt_BR": "Não Atribuído", - "ru_RU": "Не привязано", - "sv_SE": "Obunden", - "th_TH": "ยังไม่กำหนดปุ่ม", - "tr_TR": "", - "uk_UA": "Відв'язати", - "zh_CN": "未分配", - "zh_TW": "未分配" - } - }, { "ID": "GamepadLeftStick", "Translations": { @@ -19100,31 +17200,6 @@ "zh_TW": "正在編譯著色器" } }, - { - "ID": "AllKeyboards", - "Translations": { - "ar_SA": "كل لوحات المفاتيح", - "de_DE": "Alle Tastaturen", - "el_GR": "Όλα τα πληκτρολόγια", - "en_US": "All Keyboards", - "es_ES": "Todos los Teclados", - "fr_FR": "Tous les Claviers", - "he_IL": "כל המקלדות", - "it_IT": "Tutte le tastiere", - "ja_JP": "すべてのキーボード", - "ko_KR": "모든 키보드", - "no_NO": "Alle tastaturer", - "pl_PL": "Wszystkie klawiatury", - "pt_BR": "Todos os Teclados", - "ru_RU": "Все клавиатуры", - "sv_SE": "Alla tangentbord", - "th_TH": "คีย์บอร์ดทั้งหมด", - "tr_TR": "Tüm Klavyeler", - "uk_UA": "Усі клавіатури", - "zh_CN": "所有键盘", - "zh_TW": "所有鍵盤" - } - }, { "ID": "OpenFileDialogTitle", "Translations": { diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 5fbfb1bbf..95287954a 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -56,7 +56,7 @@ namespace Ryujinx.Ava.Input return null; } - return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]); + return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_AllKeyboards]); } public IEnumerable GetGamepads() => [GetGamepad("0")]; diff --git a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs index d153adc74..05ec55a4b 100644 --- a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs @@ -14,75 +14,75 @@ namespace Ryujinx.Ava.UI.Helpers private static readonly Dictionary _keysMap = new() { - { Key.Unknown, LocaleKeys.KeyUnknown }, - { Key.ShiftLeft, LocaleKeys.KeyShiftLeft }, - { Key.ShiftRight, LocaleKeys.KeyShiftRight }, - { Key.ControlLeft, LocaleKeys.KeyControlLeft }, - { Key.ControlRight, LocaleKeys.KeyControlRight }, - { Key.AltLeft, LocaleKeys.KeyAltLeft }, - { Key.AltRight, LocaleKeys.KeyAltRight }, - { Key.WinLeft, LocaleKeys.KeyWinLeft }, - { Key.WinRight, LocaleKeys.KeyWinRight }, - { Key.Up, LocaleKeys.KeyUp }, - { Key.Down, LocaleKeys.KeyDown }, - { Key.Left, LocaleKeys.KeyLeft }, - { Key.Right, LocaleKeys.KeyRight }, - { Key.Enter, LocaleKeys.KeyEnter }, - { Key.Escape, LocaleKeys.KeyEscape }, - { Key.Space, LocaleKeys.KeySpace }, - { Key.Tab, LocaleKeys.KeyTab }, - { Key.BackSpace, LocaleKeys.KeyBackSpace }, - { Key.Insert, LocaleKeys.KeyInsert }, - { Key.Delete, LocaleKeys.KeyDelete }, - { Key.PageUp, LocaleKeys.KeyPageUp }, - { Key.PageDown, LocaleKeys.KeyPageDown }, - { Key.Home, LocaleKeys.KeyHome }, - { Key.End, LocaleKeys.KeyEnd }, - { Key.CapsLock, LocaleKeys.KeyCapsLock }, - { Key.ScrollLock, LocaleKeys.KeyScrollLock }, - { Key.PrintScreen, LocaleKeys.KeyPrintScreen }, - { Key.Pause, LocaleKeys.KeyPause }, - { Key.NumLock, LocaleKeys.KeyNumLock }, - { Key.Clear, LocaleKeys.KeyClear }, - { Key.Keypad0, LocaleKeys.KeyKeypad0 }, - { Key.Keypad1, LocaleKeys.KeyKeypad1 }, - { Key.Keypad2, LocaleKeys.KeyKeypad2 }, - { Key.Keypad3, LocaleKeys.KeyKeypad3 }, - { Key.Keypad4, LocaleKeys.KeyKeypad4 }, - { Key.Keypad5, LocaleKeys.KeyKeypad5 }, - { Key.Keypad6, LocaleKeys.KeyKeypad6 }, - { Key.Keypad7, LocaleKeys.KeyKeypad7 }, - { Key.Keypad8, LocaleKeys.KeyKeypad8 }, - { Key.Keypad9, LocaleKeys.KeyKeypad9 }, - { Key.KeypadDivide, LocaleKeys.KeyKeypadDivide }, - { Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply }, - { Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract }, - { Key.KeypadAdd, LocaleKeys.KeyKeypadAdd }, - { Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal }, - { Key.KeypadEnter, LocaleKeys.KeyKeypadEnter }, - { Key.Number0, LocaleKeys.KeyNumber0 }, - { Key.Number1, LocaleKeys.KeyNumber1 }, - { Key.Number2, LocaleKeys.KeyNumber2 }, - { Key.Number3, LocaleKeys.KeyNumber3 }, - { Key.Number4, LocaleKeys.KeyNumber4 }, - { Key.Number5, LocaleKeys.KeyNumber5 }, - { Key.Number6, LocaleKeys.KeyNumber6 }, - { Key.Number7, LocaleKeys.KeyNumber7 }, - { Key.Number8, LocaleKeys.KeyNumber8 }, - { Key.Number9, LocaleKeys.KeyNumber9 }, - { Key.Tilde, LocaleKeys.KeyTilde }, - { Key.Grave, LocaleKeys.KeyGrave }, - { Key.Minus, LocaleKeys.KeyMinus }, - { Key.Plus, LocaleKeys.KeyPlus }, - { Key.BracketLeft, LocaleKeys.KeyBracketLeft }, - { Key.BracketRight, LocaleKeys.KeyBracketRight }, - { Key.Semicolon, LocaleKeys.KeySemicolon }, - { Key.Quote, LocaleKeys.KeyQuote }, - { Key.Comma, LocaleKeys.KeyComma }, - { Key.Period, LocaleKeys.KeyPeriod }, - { Key.Slash, LocaleKeys.KeySlash }, - { Key.BackSlash, LocaleKeys.KeyBackSlash }, - { Key.Unbound, LocaleKeys.KeyUnbound }, + { Key.Unknown, LocaleKeys.KeyboardLayout_KeyUnknown }, + { Key.ShiftLeft, LocaleKeys.KeyboardLayout_KeyShiftLeft }, + { Key.ShiftRight, LocaleKeys.KeyboardLayout_KeyShiftRight }, + { Key.ControlLeft, LocaleKeys.KeyboardLayout_KeyControlLeft }, + { Key.ControlRight, LocaleKeys.KeyboardLayout_KeyControlRight }, + { Key.AltLeft, LocaleKeys.KeyboardLayout_KeyAltLeft }, + { Key.AltRight, LocaleKeys.KeyboardLayout_KeyAltRight }, + { Key.WinLeft, LocaleKeys.KeyboardLayout_KeyWinLeft }, + { Key.WinRight, LocaleKeys.KeyboardLayout_KeyWinRight }, + { Key.Up, LocaleKeys.KeyboardLayout_KeyUp }, + { Key.Down, LocaleKeys.KeyboardLayout_KeyDown }, + { Key.Left, LocaleKeys.KeyboardLayout_KeyLeft }, + { Key.Right, LocaleKeys.KeyboardLayout_KeyRight }, + { Key.Enter, LocaleKeys.KeyboardLayout_KeyEnter }, + { Key.Escape, LocaleKeys.KeyboardLayout_KeyEscape }, + { Key.Space, LocaleKeys.KeyboardLayout_KeySpace }, + { Key.Tab, LocaleKeys.KeyboardLayout_KeyTab }, + { Key.BackSpace, LocaleKeys.KeyboardLayout_KeyBackSpace }, + { Key.Insert, LocaleKeys.KeyboardLayout_KeyInsert }, + { Key.Delete, LocaleKeys.KeyboardLayout_KeyDelete }, + { Key.PageUp, LocaleKeys.KeyboardLayout_KeyPageUp }, + { Key.PageDown, LocaleKeys.KeyboardLayout_KeyPageDown }, + { Key.Home, LocaleKeys.KeyboardLayout_KeyHome }, + { Key.End, LocaleKeys.KeyboardLayout_KeyEnd }, + { Key.CapsLock, LocaleKeys.KeyboardLayout_KeyCapsLock }, + { Key.ScrollLock, LocaleKeys.KeyboardLayout_KeyScrollLock }, + { Key.PrintScreen, LocaleKeys.KeyboardLayout_KeyPrintScreen }, + { Key.Pause, LocaleKeys.KeyboardLayout_KeyPause }, + { Key.NumLock, LocaleKeys.KeyboardLayout_KeyNumLock }, + { Key.Clear, LocaleKeys.KeyboardLayout_KeyClear }, + { Key.Keypad0, LocaleKeys.KeyboardLayout_KeyKeypad0 }, + { Key.Keypad1, LocaleKeys.KeyboardLayout_KeyKeypad1 }, + { Key.Keypad2, LocaleKeys.KeyboardLayout_KeyKeypad2 }, + { Key.Keypad3, LocaleKeys.KeyboardLayout_KeyKeypad3 }, + { Key.Keypad4, LocaleKeys.KeyboardLayout_KeyKeypad4 }, + { Key.Keypad5, LocaleKeys.KeyboardLayout_KeyKeypad5 }, + { Key.Keypad6, LocaleKeys.KeyboardLayout_KeyKeypad6 }, + { Key.Keypad7, LocaleKeys.KeyboardLayout_KeyKeypad7 }, + { Key.Keypad8, LocaleKeys.KeyboardLayout_KeyKeypad8 }, + { Key.Keypad9, LocaleKeys.KeyboardLayout_KeyKeypad9 }, + { Key.KeypadDivide, LocaleKeys.KeyboardLayout_KeyKeypadDivide }, + { Key.KeypadMultiply, LocaleKeys.KeyboardLayout_KeyKeypadMultiply }, + { Key.KeypadSubtract, LocaleKeys.KeyboardLayout_KeyKeypadSubtract }, + { Key.KeypadAdd, LocaleKeys.KeyboardLayout_KeyKeypadAdd }, + { Key.KeypadDecimal, LocaleKeys.KeyboardLayout_KeyKeypadDecimal }, + { Key.KeypadEnter, LocaleKeys.KeyboardLayout_KeyKeypadEnter }, + { Key.Number0, LocaleKeys.KeyboardLayout_KeyNumber0 }, + { Key.Number1, LocaleKeys.KeyboardLayout_KeyNumber1 }, + { Key.Number2, LocaleKeys.KeyboardLayout_KeyNumber2 }, + { Key.Number3, LocaleKeys.KeyboardLayout_KeyNumber3 }, + { Key.Number4, LocaleKeys.KeyboardLayout_KeyNumber4 }, + { Key.Number5, LocaleKeys.KeyboardLayout_KeyNumber5 }, + { Key.Number6, LocaleKeys.KeyboardLayout_KeyNumber6 }, + { Key.Number7, LocaleKeys.KeyboardLayout_KeyNumber7 }, + { Key.Number8, LocaleKeys.KeyboardLayout_KeyNumber8 }, + { Key.Number9, LocaleKeys.KeyboardLayout_KeyNumber9 }, + { Key.Tilde, LocaleKeys.KeyboardLayout_KeyTilde }, + { Key.Grave, LocaleKeys.KeyboardLayout_KeyGrave }, + { Key.Minus, LocaleKeys.KeyboardLayout_KeyMinus }, + { Key.Plus, LocaleKeys.KeyboardLayout_KeyPlus }, + { Key.BracketLeft, LocaleKeys.KeyboardLayout_KeyBracketLeft }, + { Key.BracketRight, LocaleKeys.KeyboardLayout_KeyBracketRight }, + { Key.Semicolon, LocaleKeys.KeyboardLayout_KeySemicolon }, + { Key.Quote, LocaleKeys.KeyboardLayout_KeyQuote }, + { Key.Comma, LocaleKeys.KeyboardLayout_KeyComma }, + { Key.Period, LocaleKeys.KeyboardLayout_KeyPeriod }, + { Key.Slash, LocaleKeys.KeyboardLayout_KeySlash }, + { Key.BackSlash, LocaleKeys.KeyboardLayout_KeyBackSlash }, + { Key.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound }, }; private static readonly Dictionary _gamepadInputIdMap = new() @@ -110,14 +110,14 @@ namespace Ryujinx.Ava.UI.Helpers { GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0}, { GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1}, { GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1}, - { GamepadInputId.Unbound, LocaleKeys.KeyUnbound}, + { GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound}, }; private static readonly Dictionary _stickInputIdMap = new() { { StickInputId.Left, LocaleKeys.StickLeft}, { StickInputId.Right, LocaleKeys.StickRight}, - { StickInputId.Unbound, LocaleKeys.KeyUnbound}, + { StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound}, }; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) @@ -134,12 +134,12 @@ namespace Ryujinx.Ava.UI.Helpers { localeKey = localeKey switch { - LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft, - LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight, - LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft, - LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight, - LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft, - LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight, + LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft, + LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight, + LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft, + LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight, + LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft, + LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight, _ => localeKey }; } From 13c8b57063266eda56bffb3f0cf2ccacafd8d54f Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 17:12:33 +0100 Subject: [PATCH 20/55] Fix AltGr key assignment and silence keyboard SetLed logs --- assets/Locales/KeyboardLayout.json | 8 ++++---- src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs | 7 +++++++ src/Ryujinx/Input/AvaloniaKeyboard.cs | 3 +-- src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json index 2f7167422..3020c6656 100644 --- a/assets/Locales/KeyboardLayout.json +++ b/assets/Locales/KeyboardLayout.json @@ -82,8 +82,8 @@ "de_DE": "", "el_GR": "", "en_US": "Ctrl Left", - "es_ES": "Alt Gr", - "fr_FR": "Alt Gr", + "es_ES": "Ctrl Izquierdo", + "fr_FR": "Ctrl Gauche", "he_IL": "", "it_IT": "Ctrl sinistro", "ja_JP": "", @@ -232,8 +232,8 @@ "de_DE": "", "el_GR": "", "en_US": "Alt Right", - "es_ES": "Alt Derecho", - "fr_FR": "Alt Droite", + "es_ES": "Alt Gr", + "fr_FR": "Alt Gr", "he_IL": "", "it_IT": "Alt destro", "ja_JP": "", diff --git a/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs index 3c011a63b..252fadb66 100644 --- a/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs +++ b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs @@ -33,6 +33,13 @@ namespace Ryujinx.Input.Assigner public Button? GetPressedButton() { + // On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt. + // Prefer AltRight in that case so the binding reflects the physical key used. + if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight)) + { + return !ShouldCancel() ? new Button(Key.AltRight) : null; + } + Button? keyPressed = null; for (Key key = Key.Unknown; key < Key.Count; key++) diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 031d8b033..3e068aa39 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -1,6 +1,5 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; using Ryujinx.Input; using System; using System.Collections.Generic; @@ -146,7 +145,7 @@ namespace Ryujinx.Ava.Input public void SetLed(uint packedRgb) { - Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard"); + // Keyboard LED is not supported by this backend. } public void SetTriggerThreshold(float triggerThreshold) { } diff --git a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs index a3939abf7..922897018 100644 --- a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs +++ b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs @@ -1,5 +1,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Threading; +using Ryujinx.Ava.Input; using Ryujinx.Input; using Ryujinx.Input.Assigner; using System; @@ -25,6 +26,7 @@ namespace Ryujinx.Ava.UI.Helpers private bool _isWaitingForInput; private bool _shouldUnbind; + private IKeyboard _keyboard; public event EventHandler ButtonAssigned; public ButtonKeyAssigner(ToggleButton toggleButton) @@ -34,6 +36,9 @@ namespace Ryujinx.Ava.UI.Helpers public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) { + _keyboard = keyboard; + ClearKeyboardState(_keyboard); + Dispatcher.UIThread.Post(() => { ToggledButton.IsChecked = true; @@ -82,6 +87,7 @@ namespace Ryujinx.Ava.UI.Helpers _isWaitingForInput = false; ToggledButton.IsChecked = false; + ClearKeyboardState(_keyboard); if (pressedButton.HasValue && pressedButton.Value.AsHidType() == Key.BackSpace) { @@ -98,6 +104,15 @@ namespace Ryujinx.Ava.UI.Helpers _isWaitingForInput = false; ToggledButton.IsChecked = false; _shouldUnbind = shouldUnbind; + ClearKeyboardState(_keyboard); + } + + private static void ClearKeyboardState(IKeyboard keyboard) + { + if (keyboard is AvaloniaKeyboard avaloniaKeyboard) + { + avaloniaKeyboard.Clear(); + } } } } From ebd8cc4f4a78f50487a179ec3400262af24c0c45 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 18:17:06 +0100 Subject: [PATCH 21/55] Added better support for the different keyboards. --- assets/Locales/KeyboardLayout.json | 4 +- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 42 +++- .../Input/AvaloniaKeyboardMappingHelper.cs | 198 +++++++++++++++++- .../Applet/AvaloniaDynamicTextInputHandler.cs | 4 +- 4 files changed, 231 insertions(+), 17 deletions(-) diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json index 3020c6656..86d9772c1 100644 --- a/assets/Locales/KeyboardLayout.json +++ b/assets/Locales/KeyboardLayout.json @@ -1608,7 +1608,7 @@ "el_GR": "", "en_US": "`", "es_ES": "º", - "fr_FR": null, + "fr_FR": "<", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -1858,7 +1858,7 @@ "el_GR": "", "en_US": "\\", "es_ES": "<", - "fr_FR": "<", + "fr_FR": "*", "he_IL": "", "it_IT": "<", "ja_JP": "", diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 95287954a..c7a5bd395 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Input; using System; using System.Collections.Generic; -using AvaKey = Avalonia.Input.Key; using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input @@ -13,7 +12,7 @@ namespace Ryujinx.Ava.Input { private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; - private readonly HashSet _pressedKeys; + private readonly Dictionary _pressedKeys; public event EventHandler KeyPressed; public event EventHandler KeyRelease; @@ -65,21 +64,48 @@ namespace Ryujinx.Ava.Input { if (disposing) { - _control.KeyUp -= OnKeyPress; - _control.KeyDown -= OnKeyRelease; + _control.KeyDown -= OnKeyPress; + _control.KeyUp -= OnKeyRelease; } } protected void OnKeyPress(object sender, KeyEventArgs args) { - _pressedKeys.Add(args.Key); + Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + + if (key != Key.Unknown) + { + if (_pressedKeys.TryGetValue(key, out int count)) + { + _pressedKeys[key] = count + 1; + } + else + { + _pressedKeys[key] = 1; + } + } KeyPressed?.Invoke(this, args); } protected void OnKeyRelease(object sender, KeyEventArgs args) { - _pressedKeys.Remove(args.Key); + Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + + if (key != Key.Unknown) + { + if (_pressedKeys.TryGetValue(key, out int count)) + { + if (count <= 1) + { + _pressedKeys.Remove(key); + } + else + { + _pressedKeys[key] = count - 1; + } + } + } KeyRelease?.Invoke(this, args); } @@ -91,9 +117,7 @@ namespace Ryujinx.Ava.Input return false; } - AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey); - - return _pressedKeys.Contains(nativeKey); + return _pressedKeys.ContainsKey(key); } public void Clear() diff --git a/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs index 4aa8692dd..b95b8c56f 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs @@ -2,6 +2,7 @@ using Ryujinx.Input; using System; using System.Collections.Generic; using AvaKey = Avalonia.Input.Key; +using AvaPhysicalKey = Avalonia.Input.PhysicalKey; namespace Ryujinx.Ava.Input { @@ -132,7 +133,8 @@ namespace Ryujinx.Ava.Input AvaKey.D8, AvaKey.D9, AvaKey.OemTilde, - AvaKey.OemTilde,AvaKey.OemMinus, + AvaKey.Oem102, + AvaKey.OemMinus, AvaKey.OemPlus, AvaKey.OemOpenBrackets, AvaKey.OemCloseBrackets, @@ -147,7 +149,149 @@ namespace Ryujinx.Ava.Input AvaKey.None ]; + private static readonly AvaPhysicalKey[] _physicalKeyMapping = + [ + // NOTE: Invalid + AvaPhysicalKey.None, + + AvaPhysicalKey.ShiftLeft, + AvaPhysicalKey.ShiftRight, + AvaPhysicalKey.ControlLeft, + AvaPhysicalKey.ControlRight, + AvaPhysicalKey.AltLeft, + AvaPhysicalKey.AltRight, + AvaPhysicalKey.MetaLeft, + AvaPhysicalKey.MetaRight, + AvaPhysicalKey.ContextMenu, + AvaPhysicalKey.F1, + AvaPhysicalKey.F2, + AvaPhysicalKey.F3, + AvaPhysicalKey.F4, + AvaPhysicalKey.F5, + AvaPhysicalKey.F6, + AvaPhysicalKey.F7, + AvaPhysicalKey.F8, + AvaPhysicalKey.F9, + AvaPhysicalKey.F10, + AvaPhysicalKey.F11, + AvaPhysicalKey.F12, + AvaPhysicalKey.F13, + AvaPhysicalKey.F14, + AvaPhysicalKey.F15, + AvaPhysicalKey.F16, + AvaPhysicalKey.F17, + AvaPhysicalKey.F18, + AvaPhysicalKey.F19, + AvaPhysicalKey.F20, + AvaPhysicalKey.F21, + AvaPhysicalKey.F22, + AvaPhysicalKey.F23, + AvaPhysicalKey.F24, + + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + + AvaPhysicalKey.ArrowUp, + AvaPhysicalKey.ArrowDown, + AvaPhysicalKey.ArrowLeft, + AvaPhysicalKey.ArrowRight, + AvaPhysicalKey.Enter, + AvaPhysicalKey.Escape, + AvaPhysicalKey.Space, + AvaPhysicalKey.Tab, + AvaPhysicalKey.Backspace, + AvaPhysicalKey.Insert, + AvaPhysicalKey.Delete, + AvaPhysicalKey.PageUp, + AvaPhysicalKey.PageDown, + AvaPhysicalKey.Home, + AvaPhysicalKey.End, + AvaPhysicalKey.CapsLock, + AvaPhysicalKey.ScrollLock, + AvaPhysicalKey.PrintScreen, + AvaPhysicalKey.Pause, + AvaPhysicalKey.NumLock, + AvaPhysicalKey.NumPadClear, + AvaPhysicalKey.NumPad0, + AvaPhysicalKey.NumPad1, + AvaPhysicalKey.NumPad2, + AvaPhysicalKey.NumPad3, + AvaPhysicalKey.NumPad4, + AvaPhysicalKey.NumPad5, + AvaPhysicalKey.NumPad6, + AvaPhysicalKey.NumPad7, + AvaPhysicalKey.NumPad8, + AvaPhysicalKey.NumPad9, + AvaPhysicalKey.NumPadDivide, + AvaPhysicalKey.NumPadMultiply, + AvaPhysicalKey.NumPadSubtract, + AvaPhysicalKey.NumPadAdd, + AvaPhysicalKey.NumPadDecimal, + AvaPhysicalKey.NumPadEnter, + AvaPhysicalKey.A, + AvaPhysicalKey.B, + AvaPhysicalKey.C, + AvaPhysicalKey.D, + AvaPhysicalKey.E, + AvaPhysicalKey.F, + AvaPhysicalKey.G, + AvaPhysicalKey.H, + AvaPhysicalKey.I, + AvaPhysicalKey.J, + AvaPhysicalKey.K, + AvaPhysicalKey.L, + AvaPhysicalKey.M, + AvaPhysicalKey.N, + AvaPhysicalKey.O, + AvaPhysicalKey.P, + AvaPhysicalKey.Q, + AvaPhysicalKey.R, + AvaPhysicalKey.S, + AvaPhysicalKey.T, + AvaPhysicalKey.U, + AvaPhysicalKey.V, + AvaPhysicalKey.W, + AvaPhysicalKey.X, + AvaPhysicalKey.Y, + AvaPhysicalKey.Z, + AvaPhysicalKey.Digit0, + AvaPhysicalKey.Digit1, + AvaPhysicalKey.Digit2, + AvaPhysicalKey.Digit3, + AvaPhysicalKey.Digit4, + AvaPhysicalKey.Digit5, + AvaPhysicalKey.Digit6, + AvaPhysicalKey.Digit7, + AvaPhysicalKey.Digit8, + AvaPhysicalKey.Digit9, + AvaPhysicalKey.Backquote, + AvaPhysicalKey.IntlBackslash, + AvaPhysicalKey.Minus, + AvaPhysicalKey.Equal, + AvaPhysicalKey.BracketLeft, + AvaPhysicalKey.BracketRight, + AvaPhysicalKey.Semicolon, + AvaPhysicalKey.Quote, + AvaPhysicalKey.Comma, + AvaPhysicalKey.Period, + AvaPhysicalKey.Slash, + AvaPhysicalKey.Backslash, + + // NOTE: invalid + AvaPhysicalKey.None + ]; + private static readonly Dictionary _avaKeyMapping; + private static readonly Dictionary _avaPhysicalKeyMapping; static AvaloniaKeyboardMappingHelper() { @@ -155,21 +299,42 @@ namespace Ryujinx.Ava.Input // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array. _avaKeyMapping = new Dictionary(); + _avaPhysicalKeyMapping = new Dictionary(); foreach (Key key in inputKeys) { - if (TryGetAvaKey(key, out AvaKey index)) + if (TryGetAvaKey(key, out AvaKey avaKey)) { - _avaKeyMapping[index] = key; + _avaKeyMapping[avaKey] = key; + } + + if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey)) + { + _avaPhysicalKeyMapping[avaPhysicalKey] = key; } } + + // Alias additional Avalonia key values to improve non-US layout support. + _avaKeyMapping[AvaKey.Oem1] = Key.Semicolon; + _avaKeyMapping[AvaKey.Oem2] = Key.Slash; + _avaKeyMapping[AvaKey.Oem3] = Key.Tilde; + _avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft; + _avaKeyMapping[AvaKey.Oem5] = Key.BackSlash; + _avaKeyMapping[AvaKey.Oem6] = Key.BracketRight; + _avaKeyMapping[AvaKey.Oem7] = Key.Quote; + _avaKeyMapping[AvaKey.OemBackslash] = Key.Grave; + _avaKeyMapping[AvaKey.Oem102] = Key.Grave; + + // Common alternates for non-US/JIS physical keys. + _avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash; + _avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash; } public static bool TryGetAvaKey(Key key, out AvaKey avaKey) { avaKey = AvaKey.None; - bool keyExist = (int)key < _keyMapping.Length; + bool keyExist = key < Key.Count && (int)key < _keyMapping.Length; if (keyExist) { avaKey = _keyMapping[(int)key]; @@ -178,9 +343,34 @@ namespace Ryujinx.Ava.Input return keyExist; } + public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey) + { + avaPhysicalKey = AvaPhysicalKey.None; + + bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length; + if (keyExist) + { + avaPhysicalKey = _physicalKeyMapping[(int)key]; + } + + return keyExist; + } + public static Key ToInputKey(AvaKey key) { return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); } + + public static Key ToInputKey(AvaPhysicalKey key) + { + return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown); + } + + public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key) + { + Key inputKey = ToInputKey(key); + + return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey); + } } } diff --git a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs index 397eab72c..340af15e4 100644 --- a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) { - HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) { @@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) { - HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) { From 2fe5e8c40d7a812c3d598074bc93bb5ce97566f6 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 20:21:24 +0100 Subject: [PATCH 22/55] Cache Avalonia keyboard LED state --- src/Ryujinx/Input/AvaloniaKeyboard.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 3e068aa39..fe7ec2670 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; using Ryujinx.Input; using System; using System.Collections.Generic; @@ -15,6 +16,7 @@ namespace Ryujinx.Ava.Input private readonly List _buttonsUserMapping; private readonly AvaloniaKeyboardDriver _driver; private StandardKeyboardInputConfig _configuration; + private uint _ledValue; private readonly Lock _userMappingLock = new(); @@ -145,6 +147,15 @@ namespace Ryujinx.Ava.Input public void SetLed(uint packedRgb) { + if (_ledValue == packedRgb) + { + return; + } + + _ledValue = packedRgb; + + Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard"); + // Keyboard LED is not supported by this backend. } From 84f3ce2ca5e71a6dda820baeb4cf53ef588ce4a7 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 21:10:28 +0100 Subject: [PATCH 23/55] Update keyboard localisation refactor snapshot --- .../Keyboard/StandardKeyboardInputConfig.cs | 2 +- .../Hid/KeyboardKeyExtensions.cs | 19 +++ .../Configuration/Hid/PhysicalKey.cs | 142 ++++++++++++++++++ src/Ryujinx.Input.SDL3/SDL3Keyboard.cs | 52 +++---- src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs | 7 +- src/Ryujinx.Input/HLE/NpadController.cs | 5 +- src/Ryujinx.Input/IKeyboardModeDriver.cs | 7 + src/Ryujinx.Input/KeyboardInputMode.cs | 8 + src/Ryujinx.Input/PhysicalKeyExtensions.cs | 14 ++ src/Ryujinx/Headless/HeadlessRyujinx.Init.cs | 66 ++++---- src/Ryujinx/Input/AvaloniaKeyboard.cs | 60 ++++---- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 118 ++++++++++----- .../ConfigurationState.Migration.cs | 66 ++++---- .../Configuration/ConfigurationState.cs | 66 ++++---- .../Helpers/Converters/KeyValueConverter.cs | 3 + .../UI/Helpers/PhysicalKeyLabelHelper.cs | 131 ++++++++++++++++ .../UI/Models/Input/KeyboardInputConfig.cs | 64 ++++---- .../UI/Models/Input/StickVisualizer.cs | 16 +- .../UI/ViewModels/Input/InputViewModel.cs | 68 ++++----- .../UI/Views/Input/KeyboardInputView.axaml.cs | 114 +++++++------- .../Settings/SettingsHotkeysView.axaml.cs | 2 +- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 3 +- 22 files changed, 708 insertions(+), 325 deletions(-) create mode 100644 src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs create mode 100644 src/Ryujinx.Input/IKeyboardModeDriver.cs create mode 100644 src/Ryujinx.Input/KeyboardInputMode.cs create mode 100644 src/Ryujinx.Input/PhysicalKeyExtensions.cs create mode 100644 src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs diff --git a/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs index 1e8b188e7..548b0762c 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs @@ -1,4 +1,4 @@ namespace Ryujinx.Common.Configuration.Hid.Keyboard { - public class StandardKeyboardInputConfig : GenericKeyboardInputConfig { } + public class StandardKeyboardInputConfig : GenericKeyboardInputConfig { } } diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs new file mode 100644 index 000000000..f6bf50987 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public static class KeyboardKeyExtensions + { + public static Key ToKey(this PhysicalKey key) + { + return key is >= PhysicalKey.Unknown and < PhysicalKey.Count + ? (Key)(int)key + : Key.Unknown; + } + + public static PhysicalKey ToPhysicalKey(this Key key) + { + return key is >= Key.Unknown and < Key.Count + ? (PhysicalKey)(int)key + : PhysicalKey.Unknown; + } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs b/src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs new file mode 100644 index 000000000..c9cc1b8cd --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs @@ -0,0 +1,142 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum PhysicalKey + { + Unknown, + ShiftLeft, + ShiftRight, + ControlLeft, + ControlRight, + AltLeft, + AltRight, + WinLeft, + WinRight, + Menu, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + F26, + F27, + F28, + F29, + F30, + F31, + F32, + F33, + F34, + F35, + Up, + Down, + Left, + Right, + Enter, + Escape, + Space, + Tab, + BackSpace, + Insert, + Delete, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + PrintScreen, + Pause, + NumLock, + Clear, + Keypad0, + Keypad1, + Keypad2, + Keypad3, + Keypad4, + Keypad5, + Keypad6, + Keypad7, + Keypad8, + Keypad9, + KeypadDivide, + KeypadMultiply, + KeypadSubtract, + KeypadAdd, + KeypadDecimal, + KeypadEnter, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Number0, + Number1, + Number2, + Number3, + Number4, + Number5, + Number6, + Number7, + Number8, + Number9, + Tilde, + Grave, + Minus, + Plus, + BracketLeft, + BracketRight, + Semicolon, + Quote, + Comma, + Period, + Slash, + BackSlash, + Unbound, + + Count, + } +} diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index f5da11a19..8dec2ba35 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -9,7 +9,7 @@ using System.Threading; using SDL; using static SDL.SDL3; -using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Input.SDL3 { @@ -264,27 +264,27 @@ namespace Ryujinx.Input.SDL3 return value * ConvertRate; } - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) { short stickX = 0; short stickY = 0; - if (snapshot.IsPressed((Key)stickConfig.StickUp)) + if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) { stickY += 1; } - if (snapshot.IsPressed((Key)stickConfig.StickDown)) + if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) { stickY -= 1; } - if (snapshot.IsPressed((Key)stickConfig.StickRight)) + if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) { stickX += 1; } - if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) { stickX -= 1; } @@ -361,28 +361,28 @@ namespace Ryujinx.Input.SDL3 _buttonsUserMapping.Clear(); // Then configure left joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, _configuration.LeftJoyconStick.StickButton.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, _configuration.LeftJoycon.DpadUp.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, _configuration.LeftJoycon.DpadDown.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, _configuration.LeftJoycon.DpadLeft.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, _configuration.LeftJoycon.DpadRight.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, _configuration.LeftJoycon.ButtonMinus.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, _configuration.LeftJoycon.ButtonL.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, _configuration.LeftJoycon.ButtonZl.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, _configuration.LeftJoycon.ButtonSr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, _configuration.LeftJoycon.ButtonSl.ToInputKey())); // Finally configure right joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, _configuration.RightJoyconStick.StickButton.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, _configuration.RightJoycon.ButtonA.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, _configuration.RightJoycon.ButtonB.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, _configuration.RightJoycon.ButtonX.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, _configuration.RightJoycon.ButtonY.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, _configuration.RightJoycon.ButtonPlus.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, _configuration.RightJoycon.ButtonR.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, _configuration.RightJoycon.ButtonZr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, _configuration.RightJoycon.ButtonSr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, _configuration.RightJoycon.ButtonSl.ToInputKey())); } } diff --git a/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs index cd2a067be..c401ab947 100644 --- a/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs +++ b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Ryujinx.Input.SDL3 { - public class SDL3KeyboardDriver : IGamepadDriver + public class SDL3KeyboardDriver : IKeyboardModeDriver { public SDL3KeyboardDriver() { @@ -44,6 +44,11 @@ namespace Ryujinx.Input.SDL3 } public IGamepad GetGamepad(string id) + { + return GetKeyboard(id, KeyboardInputMode.Semantic); + } + + public IKeyboard GetKeyboard(string id, KeyboardInputMode mode) { if (!_keyboardIdentifers[0].Equals(id)) { diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 84f9e89ab..732319107 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -2,6 +2,7 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System; @@ -234,7 +235,9 @@ namespace Ryujinx.Input.HLE _gamepad?.Dispose(); Id = config.Id; - _gamepad = GamepadDriver.GetGamepad(Id); + _gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver + ? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical) + : GamepadDriver.GetGamepad(Id); UpdateUserConfiguration(config); diff --git a/src/Ryujinx.Input/IKeyboardModeDriver.cs b/src/Ryujinx.Input/IKeyboardModeDriver.cs new file mode 100644 index 000000000..62a835eb6 --- /dev/null +++ b/src/Ryujinx.Input/IKeyboardModeDriver.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Input +{ + public interface IKeyboardModeDriver : IGamepadDriver + { + IKeyboard GetKeyboard(string id, KeyboardInputMode mode); + } +} diff --git a/src/Ryujinx.Input/KeyboardInputMode.cs b/src/Ryujinx.Input/KeyboardInputMode.cs new file mode 100644 index 000000000..a46f3760d --- /dev/null +++ b/src/Ryujinx.Input/KeyboardInputMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Input +{ + public enum KeyboardInputMode + { + Semantic, + Physical, + } +} diff --git a/src/Ryujinx.Input/PhysicalKeyExtensions.cs b/src/Ryujinx.Input/PhysicalKeyExtensions.cs new file mode 100644 index 000000000..f2359d0d8 --- /dev/null +++ b/src/Ryujinx.Input/PhysicalKeyExtensions.cs @@ -0,0 +1,14 @@ +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; + +namespace Ryujinx.Input +{ + public static class PhysicalKeyExtensions + { + public static Key ToInputKey(this ConfigPhysicalKey key) + { + return key is >= ConfigPhysicalKey.Unknown and < ConfigPhysicalKey.Count + ? (Key)(int)key + : Key.Unknown; + } + } +} diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs index af61b7b63..722da6637 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs @@ -24,7 +24,7 @@ using System.Text.Json; using System.Threading.Tasks; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; -using Key = Ryujinx.Common.Configuration.Hid.Key; +using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Headless { @@ -105,48 +105,48 @@ namespace Ryujinx.Headless Backend = InputBackendType.WindowKeyboard, Id = null, ControllerType = ControllerType.JoyconPair, - LeftJoycon = new LeftJoyconCommonConfig + LeftJoycon = new LeftJoyconCommonConfig { - DpadUp = Key.Up, - DpadDown = Key.Down, - DpadLeft = Key.Left, - DpadRight = Key.Right, - ButtonMinus = Key.Minus, - ButtonL = Key.E, - ButtonZl = Key.Q, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - LeftJoyconStick = new JoyconConfigKeyboardStick + LeftJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, }, - RightJoycon = new RightJoyconCommonConfig + RightJoycon = new RightJoyconCommonConfig { - ButtonA = Key.Z, - ButtonB = Key.X, - ButtonX = Key.C, - ButtonY = Key.V, - ButtonPlus = Key.Plus, - ButtonR = Key.U, - ButtonZr = Key.O, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - RightJoyconStick = new JoyconConfigKeyboardStick + RightJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, }, }; } diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index fe7ec2670..0c18918c4 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.Numerics; using System.Threading; -using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input @@ -15,6 +15,7 @@ namespace Ryujinx.Ava.Input { private readonly List _buttonsUserMapping; private readonly AvaloniaKeyboardDriver _driver; + private readonly KeyboardInputMode _mode; private StandardKeyboardInputConfig _configuration; private uint _ledValue; @@ -32,11 +33,12 @@ namespace Ryujinx.Ava.Input public readonly Key From = from; } - public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name) + public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode) { _buttonsUserMapping = []; _driver = driver; + _mode = mode; Id = id; Name = name; } @@ -101,7 +103,7 @@ namespace Ryujinx.Ava.Input { try { - return _driver.IsPressed(key); + return _driver.IsPressed(key, _mode); } catch { @@ -119,28 +121,28 @@ namespace Ryujinx.Ava.Input #pragma warning disable IDE0055 // Disable formatting // Left JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, _configuration.LeftJoyconStick.StickButton.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, _configuration.LeftJoycon.DpadUp.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, _configuration.LeftJoycon.DpadDown.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, _configuration.LeftJoycon.DpadLeft.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, _configuration.LeftJoycon.DpadRight.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, _configuration.LeftJoycon.ButtonMinus.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, _configuration.LeftJoycon.ButtonL.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, _configuration.LeftJoycon.ButtonZl.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, _configuration.LeftJoycon.ButtonSr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, _configuration.LeftJoycon.ButtonSl.ToInputKey())); // Right JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, _configuration.RightJoyconStick.StickButton.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, _configuration.RightJoycon.ButtonA.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, _configuration.RightJoycon.ButtonB.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, _configuration.RightJoycon.ButtonX.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, _configuration.RightJoycon.ButtonY.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, _configuration.RightJoycon.ButtonPlus.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, _configuration.RightJoycon.ButtonR.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, _configuration.RightJoycon.ButtonZr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, _configuration.RightJoycon.ButtonSr.ToInputKey())); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, _configuration.RightJoycon.ButtonSl.ToInputKey())); #pragma warning restore IDE0055 } } @@ -172,27 +174,27 @@ namespace Ryujinx.Ava.Input return value * ConvertRate; } - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) { short stickX = 0; short stickY = 0; - if (snapshot.IsPressed((Key)stickConfig.StickUp)) + if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) { stickY += 1; } - if (snapshot.IsPressed((Key)stickConfig.StickDown)) + if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) { stickY -= 1; } - if (snapshot.IsPressed((Key)stickConfig.StickRight)) + if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) { stickX += 1; } - if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) { stickX -= 1; } @@ -206,7 +208,7 @@ namespace Ryujinx.Ava.Input public void Clear() { - _driver?.Clear(); + _driver?.Clear(_mode); } public void Dispose() { } diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index c7a5bd395..f68d1cfb5 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -8,11 +8,13 @@ using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input { - internal class AvaloniaKeyboardDriver : IGamepadDriver + internal class AvaloniaKeyboardDriver : IKeyboardModeDriver { private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; - private readonly Dictionary _pressedKeys; + private readonly Dictionary _semanticPressedKeys; + private readonly Dictionary _physicalPressedKeys; + private readonly KeyboardInputMode _defaultMode; public event EventHandler KeyPressed; public event EventHandler KeyRelease; @@ -21,10 +23,12 @@ namespace Ryujinx.Ava.Input public string DriverName => "AvaloniaKeyboardDriver"; public ReadOnlySpan GamepadsIds => _keyboardIdentifers; - public AvaloniaKeyboardDriver(Control control) + public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic) { _control = control; - _pressedKeys = []; + _semanticPressedKeys = []; + _physicalPressedKeys = []; + _defaultMode = defaultMode; _control.KeyDown += OnKeyPress; _control.KeyUp += OnKeyRelease; @@ -49,13 +53,18 @@ namespace Ryujinx.Ava.Input } public IGamepad GetGamepad(string id) + { + return GetKeyboard(id, _defaultMode); + } + + public IKeyboard GetKeyboard(string id, KeyboardInputMode mode) { if (!_keyboardIdentifers[0].Equals(id)) { return null; } - return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_AllKeyboards]); + return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_AllKeyboards], mode); } public IEnumerable GetGamepads() => [GetGamepad("0")]; @@ -66,63 +75,98 @@ namespace Ryujinx.Ava.Input { _control.KeyDown -= OnKeyPress; _control.KeyUp -= OnKeyRelease; + _control.TextInput -= Control_TextInput; } } protected void OnKeyPress(object sender, KeyEventArgs args) { - Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); - - if (key != Key.Unknown) - { - if (_pressedKeys.TryGetValue(key, out int count)) - { - _pressedKeys[key] = count + 1; - } - else - { - _pressedKeys[key] = 1; - } - } + UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), true); + UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), true); KeyPressed?.Invoke(this, args); } protected void OnKeyRelease(object sender, KeyEventArgs args) { - Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); - - if (key != Key.Unknown) - { - if (_pressedKeys.TryGetValue(key, out int count)) - { - if (count <= 1) - { - _pressedKeys.Remove(key); - } - else - { - _pressedKeys[key] = count - 1; - } - } - } + UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), false); + UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), false); KeyRelease?.Invoke(this, args); } - internal bool IsPressed(Key key) + internal bool IsPressed(Key key, KeyboardInputMode mode) { if (key is Key.Unbound or Key.Unknown) { return false; } - return _pressedKeys.ContainsKey(key); + return GetPressedKeys(mode).ContainsKey(key); + } + + internal void Clear(KeyboardInputMode mode) + { + GetPressedKeys(mode).Clear(); } public void Clear() { - _pressedKeys.Clear(); + _semanticPressedKeys.Clear(); + _physicalPressedKeys.Clear(); + } + + private Dictionary GetPressedKeys(KeyboardInputMode mode) + { + return mode == KeyboardInputMode.Physical ? _physicalPressedKeys : _semanticPressedKeys; + } + + private static void UpdateKeyState(Dictionary pressedKeys, Key key, bool isPressed) + { + if (key is Key.Unknown or Key.Unbound) + { + return; + } + + if (isPressed) + { + if (pressedKeys.TryGetValue(key, out int count)) + { + pressedKeys[key] = count + 1; + } + else + { + pressedKeys[key] = 1; + } + + return; + } + + if (pressedKeys.TryGetValue(key, out int currentCount)) + { + if (currentCount <= 1) + { + pressedKeys.Remove(key); + } + else + { + pressedKeys[key] = currentCount - 1; + } + } + } + + private static Key GetInputKey(KeyEventArgs args, KeyboardInputMode mode) + { + if (mode == KeyboardInputMode.Physical) + { + Key physicalKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); + + return physicalKey != Key.Unknown + ? physicalKey + : AvaloniaKeyboardMappingHelper.ToInputKey(args.Key); + } + + return AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); } public void Dispose() diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs index 163b7e98f..aad6d1119 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs @@ -13,6 +13,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Key = Ryujinx.Common.Configuration.Hid.Key; +using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using RyuLogger = Ryujinx.Common.Logging.Logger; namespace Ryujinx.Ava.Systems.Configuration @@ -269,45 +271,45 @@ namespace Ryujinx.Ava.Systems.Configuration Id = "0", PlayerIndex = PlayerIndex.Player1, ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig + LeftJoycon = new LeftJoyconCommonConfig { - DpadUp = Key.Up, - DpadDown = Key.Down, - DpadLeft = Key.Left, - DpadRight = Key.Right, - ButtonMinus = Key.Minus, - ButtonL = Key.E, - ButtonZl = Key.Q, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - LeftJoyconStick = new JoyconConfigKeyboardStick + LeftJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, }, - RightJoycon = new RightJoyconCommonConfig + RightJoycon = new RightJoyconCommonConfig { - ButtonA = Key.Z, - ButtonB = Key.X, - ButtonX = Key.C, - ButtonY = Key.V, - ButtonPlus = Key.Plus, - ButtonR = Key.U, - ButtonZr = Key.O, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - RightJoyconStick = new JoyconConfigKeyboardStick + RightJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, }, } ]; diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs index 0e2f6aaec..7cb1143ac 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs @@ -8,6 +8,8 @@ using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE; using System; using System.Linq; +using Key = Ryujinx.Common.Configuration.Hid.Key; +using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Ava.Systems.Configuration { @@ -285,45 +287,45 @@ namespace Ryujinx.Ava.Systems.Configuration Name = "Keyboard", PlayerIndex = PlayerIndex.Player1, ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig + LeftJoycon = new LeftJoyconCommonConfig { - DpadUp = Key.Up, - DpadDown = Key.Down, - DpadLeft = Key.Left, - DpadRight = Key.Right, - ButtonMinus = Key.Minus, - ButtonL = Key.E, - ButtonZl = Key.Q, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - LeftJoyconStick = new JoyconConfigKeyboardStick + LeftJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, }, - RightJoycon = new RightJoyconCommonConfig + RightJoycon = new RightJoyconCommonConfig { - ButtonA = Key.Z, - ButtonB = Key.X, - ButtonX = Key.C, - ButtonY = Key.V, - ButtonPlus = Key.Plus, - ButtonR = Key.U, - ButtonZr = Key.O, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - RightJoyconStick = new JoyconConfigKeyboardStick + RightJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, }, } ]; diff --git a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs index 05ec55a4b..87c60edd4 100644 --- a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs @@ -151,6 +151,9 @@ namespace Ryujinx.Ava.UI.Helpers keyString = key.ToString(); } + break; + case PhysicalKey physicalKey: + keyString = PhysicalKeyLabelHelper.GetString(physicalKey); break; case GamepadInputId gamepadInputId: if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs new file mode 100644 index 000000000..a3f437457 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -0,0 +1,131 @@ +using Avalonia.Input; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Common.Configuration.Hid; +using System; +using System.Collections.Generic; +using AvaPhysicalKey = Avalonia.Input.PhysicalKey; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; +using InputKey = Ryujinx.Input.Key; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal static class PhysicalKeyLabelHelper + { + private static readonly Dictionary _localizedKeysMap = new() + { + [ConfigPhysicalKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown, + [ConfigPhysicalKey.ShiftLeft] = LocaleKeys.KeyboardLayout_KeyShiftLeft, + [ConfigPhysicalKey.ShiftRight] = LocaleKeys.KeyboardLayout_KeyShiftRight, + [ConfigPhysicalKey.ControlLeft] = LocaleKeys.KeyboardLayout_KeyControlLeft, + [ConfigPhysicalKey.ControlRight] = LocaleKeys.KeyboardLayout_KeyControlRight, + [ConfigPhysicalKey.AltLeft] = LocaleKeys.KeyboardLayout_KeyAltLeft, + [ConfigPhysicalKey.AltRight] = LocaleKeys.KeyboardLayout_KeyAltRight, + [ConfigPhysicalKey.WinLeft] = LocaleKeys.KeyboardLayout_KeyWinLeft, + [ConfigPhysicalKey.WinRight] = LocaleKeys.KeyboardLayout_KeyWinRight, + [ConfigPhysicalKey.Up] = LocaleKeys.KeyboardLayout_KeyUp, + [ConfigPhysicalKey.Down] = LocaleKeys.KeyboardLayout_KeyDown, + [ConfigPhysicalKey.Left] = LocaleKeys.KeyboardLayout_KeyLeft, + [ConfigPhysicalKey.Right] = LocaleKeys.KeyboardLayout_KeyRight, + [ConfigPhysicalKey.Enter] = LocaleKeys.KeyboardLayout_KeyEnter, + [ConfigPhysicalKey.Escape] = LocaleKeys.KeyboardLayout_KeyEscape, + [ConfigPhysicalKey.Space] = LocaleKeys.KeyboardLayout_KeySpace, + [ConfigPhysicalKey.Tab] = LocaleKeys.KeyboardLayout_KeyTab, + [ConfigPhysicalKey.BackSpace] = LocaleKeys.KeyboardLayout_KeyBackSpace, + [ConfigPhysicalKey.Insert] = LocaleKeys.KeyboardLayout_KeyInsert, + [ConfigPhysicalKey.Delete] = LocaleKeys.KeyboardLayout_KeyDelete, + [ConfigPhysicalKey.PageUp] = LocaleKeys.KeyboardLayout_KeyPageUp, + [ConfigPhysicalKey.PageDown] = LocaleKeys.KeyboardLayout_KeyPageDown, + [ConfigPhysicalKey.Home] = LocaleKeys.KeyboardLayout_KeyHome, + [ConfigPhysicalKey.End] = LocaleKeys.KeyboardLayout_KeyEnd, + [ConfigPhysicalKey.CapsLock] = LocaleKeys.KeyboardLayout_KeyCapsLock, + [ConfigPhysicalKey.ScrollLock] = LocaleKeys.KeyboardLayout_KeyScrollLock, + [ConfigPhysicalKey.PrintScreen] = LocaleKeys.KeyboardLayout_KeyPrintScreen, + [ConfigPhysicalKey.Pause] = LocaleKeys.KeyboardLayout_KeyPause, + [ConfigPhysicalKey.NumLock] = LocaleKeys.KeyboardLayout_KeyNumLock, + [ConfigPhysicalKey.Clear] = LocaleKeys.KeyboardLayout_KeyClear, + [ConfigPhysicalKey.Keypad0] = LocaleKeys.KeyboardLayout_KeyKeypad0, + [ConfigPhysicalKey.Keypad1] = LocaleKeys.KeyboardLayout_KeyKeypad1, + [ConfigPhysicalKey.Keypad2] = LocaleKeys.KeyboardLayout_KeyKeypad2, + [ConfigPhysicalKey.Keypad3] = LocaleKeys.KeyboardLayout_KeyKeypad3, + [ConfigPhysicalKey.Keypad4] = LocaleKeys.KeyboardLayout_KeyKeypad4, + [ConfigPhysicalKey.Keypad5] = LocaleKeys.KeyboardLayout_KeyKeypad5, + [ConfigPhysicalKey.Keypad6] = LocaleKeys.KeyboardLayout_KeyKeypad6, + [ConfigPhysicalKey.Keypad7] = LocaleKeys.KeyboardLayout_KeyKeypad7, + [ConfigPhysicalKey.Keypad8] = LocaleKeys.KeyboardLayout_KeyKeypad8, + [ConfigPhysicalKey.Keypad9] = LocaleKeys.KeyboardLayout_KeyKeypad9, + [ConfigPhysicalKey.KeypadDivide] = LocaleKeys.KeyboardLayout_KeyKeypadDivide, + [ConfigPhysicalKey.KeypadMultiply] = LocaleKeys.KeyboardLayout_KeyKeypadMultiply, + [ConfigPhysicalKey.KeypadSubtract] = LocaleKeys.KeyboardLayout_KeyKeypadSubtract, + [ConfigPhysicalKey.KeypadAdd] = LocaleKeys.KeyboardLayout_KeyKeypadAdd, + [ConfigPhysicalKey.KeypadDecimal] = LocaleKeys.KeyboardLayout_KeyKeypadDecimal, + [ConfigPhysicalKey.KeypadEnter] = LocaleKeys.KeyboardLayout_KeyKeypadEnter, + [ConfigPhysicalKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound, + }; + + public static string GetString(ConfigPhysicalKey key) + { + if (_localizedKeysMap.TryGetValue(key, out LocaleKeys localeKey)) + { + return GetLocalizedString(localeKey); + } + + if (TryGetPrintableKeySymbol(key, out string label)) + { + return label; + } + + return key.ToString(); + } + + private static bool TryGetPrintableKeySymbol(ConfigPhysicalKey key, out string label) + { + // The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label. + if (key == ConfigPhysicalKey.Grave) + { + label = "<>"; + return true; + } + + if (!AvaloniaKeyboardMappingHelper.TryGetAvaPhysicalKey((InputKey)(int)key, out AvaPhysicalKey avaPhysicalKey)) + { + label = string.Empty; + return false; + } + + label = PhysicalKeyExtensions.ToQwertyKeySymbol(avaPhysicalKey, false); + + if (string.IsNullOrEmpty(label) || label.Length != 1 || char.IsControl(label[0])) + { + label = string.Empty; + return false; + } + + if (char.IsLetter(label[0])) + { + label = char.ToUpperInvariant(label[0]).ToString(); + } + + return true; + } + + private static string GetLocalizedString(LocaleKeys localeKey) + { + if (OperatingSystem.IsMacOS()) + { + localeKey = localeKey switch + { + LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft, + LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight, + LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft, + LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight, + LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft, + LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight, + _ => localeKey + }; + } + + return LocaleManager.Instance[localeKey]; + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs index de51d9d70..7f3cb121b 100644 --- a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -13,88 +13,88 @@ namespace Ryujinx.Ava.UI.Models.Input public PlayerIndex PlayerIndex { get; set; } [ObservableProperty] - public partial Key LeftStickUp { get; set; } + public partial PhysicalKey LeftStickUp { get; set; } [ObservableProperty] - public partial Key LeftStickDown { get; set; } + public partial PhysicalKey LeftStickDown { get; set; } [ObservableProperty] - public partial Key LeftStickLeft { get; set; } + public partial PhysicalKey LeftStickLeft { get; set; } [ObservableProperty] - public partial Key LeftStickRight { get; set; } + public partial PhysicalKey LeftStickRight { get; set; } [ObservableProperty] - public partial Key LeftStickButton { get; set; } + public partial PhysicalKey LeftStickButton { get; set; } [ObservableProperty] - public partial Key RightStickUp { get; set; } + public partial PhysicalKey RightStickUp { get; set; } [ObservableProperty] - public partial Key RightStickDown { get; set; } + public partial PhysicalKey RightStickDown { get; set; } [ObservableProperty] - public partial Key RightStickLeft { get; set; } + public partial PhysicalKey RightStickLeft { get; set; } [ObservableProperty] - public partial Key RightStickRight { get; set; } + public partial PhysicalKey RightStickRight { get; set; } [ObservableProperty] - public partial Key RightStickButton { get; set; } + public partial PhysicalKey RightStickButton { get; set; } [ObservableProperty] - public partial Key DpadUp { get; set; } + public partial PhysicalKey DpadUp { get; set; } [ObservableProperty] - public partial Key DpadDown { get; set; } + public partial PhysicalKey DpadDown { get; set; } [ObservableProperty] - public partial Key DpadLeft { get; set; } + public partial PhysicalKey DpadLeft { get; set; } [ObservableProperty] - public partial Key DpadRight { get; set; } + public partial PhysicalKey DpadRight { get; set; } [ObservableProperty] - public partial Key ButtonMinus { get; set; } + public partial PhysicalKey ButtonMinus { get; set; } [ObservableProperty] - public partial Key ButtonPlus { get; set; } + public partial PhysicalKey ButtonPlus { get; set; } [ObservableProperty] - public partial Key ButtonA { get; set; } + public partial PhysicalKey ButtonA { get; set; } [ObservableProperty] - public partial Key ButtonB { get; set; } + public partial PhysicalKey ButtonB { get; set; } [ObservableProperty] - public partial Key ButtonX { get; set; } + public partial PhysicalKey ButtonX { get; set; } [ObservableProperty] - public partial Key ButtonY { get; set; } + public partial PhysicalKey ButtonY { get; set; } [ObservableProperty] - public partial Key ButtonL { get; set; } + public partial PhysicalKey ButtonL { get; set; } [ObservableProperty] - public partial Key ButtonR { get; set; } + public partial PhysicalKey ButtonR { get; set; } [ObservableProperty] - public partial Key ButtonZl { get; set; } + public partial PhysicalKey ButtonZl { get; set; } [ObservableProperty] - public partial Key ButtonZr { get; set; } + public partial PhysicalKey ButtonZr { get; set; } [ObservableProperty] - public partial Key LeftButtonSl { get; set; } + public partial PhysicalKey LeftButtonSl { get; set; } [ObservableProperty] - public partial Key LeftButtonSr { get; set; } + public partial PhysicalKey LeftButtonSr { get; set; } [ObservableProperty] - public partial Key RightButtonSl { get; set; } + public partial PhysicalKey RightButtonSl { get; set; } [ObservableProperty] - public partial Key RightButtonSr { get; set; } + public partial PhysicalKey RightButtonSr { get; set; } public KeyboardInputConfig(InputConfig config) { @@ -153,7 +153,7 @@ namespace Ryujinx.Ava.UI.Models.Input Backend = InputBackendType.WindowKeyboard, PlayerIndex = PlayerIndex, ControllerType = ControllerType, - LeftJoycon = new LeftJoyconCommonConfig + LeftJoycon = new LeftJoyconCommonConfig { DpadUp = DpadUp, DpadDown = DpadDown, @@ -165,7 +165,7 @@ namespace Ryujinx.Ava.UI.Models.Input ButtonSl = LeftButtonSl, ButtonSr = LeftButtonSr, }, - RightJoycon = new RightJoyconCommonConfig + RightJoycon = new RightJoyconCommonConfig { ButtonA = ButtonA, ButtonB = ButtonB, @@ -177,7 +177,7 @@ namespace Ryujinx.Ava.UI.Models.Input ButtonR = ButtonR, ButtonZr = ButtonZr, }, - LeftJoyconStick = new JoyconConfigKeyboardStick + LeftJoyconStick = new JoyconConfigKeyboardStick { StickUp = LeftStickUp, StickDown = LeftStickDown, @@ -185,7 +185,7 @@ namespace Ryujinx.Ava.UI.Models.Input StickLeft = LeftStickLeft, StickButton = LeftStickButton, }, - RightJoyconStick = new JoyconConfigKeyboardStick + RightJoyconStick = new JoyconConfigKeyboardStick { StickUp = RightStickUp, StickDown = RightStickDown, diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs index f88f4ea72..936268601 100644 --- a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs +++ b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs @@ -154,42 +154,42 @@ namespace Ryujinx.Ava.UI.Models.Input { KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot(); - if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight)) + if (snapshot.IsPressed(KeyboardConfig.LeftStickRight.ToInputKey())) { leftBuffer.Item1 += 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft)) + if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft.ToInputKey())) { leftBuffer.Item1 -= 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp)) + if (snapshot.IsPressed(KeyboardConfig.LeftStickUp.ToInputKey())) { leftBuffer.Item2 += 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown)) + if (snapshot.IsPressed(KeyboardConfig.LeftStickDown.ToInputKey())) { leftBuffer.Item2 -= 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight)) + if (snapshot.IsPressed(KeyboardConfig.RightStickRight.ToInputKey())) { rightBuffer.Item1 += 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft)) + if (snapshot.IsPressed(KeyboardConfig.RightStickLeft.ToInputKey())) { rightBuffer.Item1 -= 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp)) + if (snapshot.IsPressed(KeyboardConfig.RightStickUp.ToInputKey())) { rightBuffer.Item2 += 1; } - if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown)) + if (snapshot.IsPressed(KeyboardConfig.RightStickDown.ToInputKey())) { rightBuffer.Item2 -= 1; } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index e5f085e0f..5c91bf5ce 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -28,7 +28,7 @@ using System.Linq; using System.Text.Json; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; -using Key = Ryujinx.Common.Configuration.Hid.Key; +using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Ava.UI.ViewModels.Input { @@ -290,7 +290,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _mainWindow = RyujinxApp.MainWindow; - AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); + AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner, KeyboardInputMode.Physical); _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; @@ -677,46 +677,46 @@ namespace Ryujinx.Ava.UI.ViewModels.Input Id = id, Name = name, ControllerType = ControllerType.ProController, - LeftJoycon = new LeftJoyconCommonConfig + LeftJoycon = new LeftJoyconCommonConfig { - DpadUp = Key.Up, - DpadDown = Key.Down, - DpadLeft = Key.Left, - DpadRight = Key.Right, - ButtonMinus = Key.Minus, - ButtonL = Key.E, - ButtonZl = Key.Q, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, LeftJoyconStick = - new JoyconConfigKeyboardStick + new JoyconConfigKeyboardStick { - StickUp = Key.W, - StickDown = Key.S, - StickLeft = Key.A, - StickRight = Key.D, - StickButton = Key.F, + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, }, - RightJoycon = new RightJoyconCommonConfig + RightJoycon = new RightJoyconCommonConfig { - ButtonA = Key.Z, - ButtonB = Key.X, - ButtonX = Key.C, - ButtonY = Key.V, - ButtonPlus = Key.Plus, - ButtonR = Key.U, - ButtonZr = Key.O, - ButtonSl = Key.Unbound, - ButtonSr = Key.Unbound, + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, }, - RightJoyconStick = new JoyconConfigKeyboardStick + RightJoyconStick = new JoyconConfigKeyboardStick { - StickUp = Key.I, - StickDown = Key.K, - StickLeft = Key.J, - StickRight = Key.L, - StickButton = Key.H, + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, }, }; } diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs index 668e1220c..e7b0daf80 100644 --- a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs @@ -12,7 +12,7 @@ using Ryujinx.Input.Assigner; using System; using System.Collections.Generic; using Button = Ryujinx.Input.Button; -using Key = Ryujinx.Common.Configuration.Hid.Key; +using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Ava.UI.Views.Input { @@ -78,88 +78,88 @@ namespace Ryujinx.Ava.UI.Views.Input switch (button.Name) { case "ButtonZl": - ViewModel.Config.ButtonZl = buttonValue.AsHidType(); + ViewModel.Config.ButtonZl = buttonValue.AsHidType(); break; case "ButtonL": - ViewModel.Config.ButtonL = buttonValue.AsHidType(); + ViewModel.Config.ButtonL = buttonValue.AsHidType(); break; case "ButtonMinus": - ViewModel.Config.ButtonMinus = buttonValue.AsHidType(); + ViewModel.Config.ButtonMinus = buttonValue.AsHidType(); break; case "LeftStickButton": - ViewModel.Config.LeftStickButton = buttonValue.AsHidType(); + ViewModel.Config.LeftStickButton = buttonValue.AsHidType(); break; case "LeftStickUp": - ViewModel.Config.LeftStickUp = buttonValue.AsHidType(); + ViewModel.Config.LeftStickUp = buttonValue.AsHidType(); break; case "LeftStickDown": - ViewModel.Config.LeftStickDown = buttonValue.AsHidType(); + ViewModel.Config.LeftStickDown = buttonValue.AsHidType(); break; case "LeftStickRight": - ViewModel.Config.LeftStickRight = buttonValue.AsHidType(); + ViewModel.Config.LeftStickRight = buttonValue.AsHidType(); break; case "LeftStickLeft": - ViewModel.Config.LeftStickLeft = buttonValue.AsHidType(); + ViewModel.Config.LeftStickLeft = buttonValue.AsHidType(); break; case "DpadUp": - ViewModel.Config.DpadUp = buttonValue.AsHidType(); + ViewModel.Config.DpadUp = buttonValue.AsHidType(); break; case "DpadDown": - ViewModel.Config.DpadDown = buttonValue.AsHidType(); + ViewModel.Config.DpadDown = buttonValue.AsHidType(); break; case "DpadLeft": - ViewModel.Config.DpadLeft = buttonValue.AsHidType(); + ViewModel.Config.DpadLeft = buttonValue.AsHidType(); break; case "DpadRight": - ViewModel.Config.DpadRight = buttonValue.AsHidType(); + ViewModel.Config.DpadRight = buttonValue.AsHidType(); break; case "LeftButtonSr": - ViewModel.Config.LeftButtonSr = buttonValue.AsHidType(); + ViewModel.Config.LeftButtonSr = buttonValue.AsHidType(); break; case "LeftButtonSl": - ViewModel.Config.LeftButtonSl = buttonValue.AsHidType(); + ViewModel.Config.LeftButtonSl = buttonValue.AsHidType(); break; case "RightButtonSr": - ViewModel.Config.RightButtonSr = buttonValue.AsHidType(); + ViewModel.Config.RightButtonSr = buttonValue.AsHidType(); break; case "RightButtonSl": - ViewModel.Config.RightButtonSl = buttonValue.AsHidType(); + ViewModel.Config.RightButtonSl = buttonValue.AsHidType(); break; case "ButtonZr": - ViewModel.Config.ButtonZr = buttonValue.AsHidType(); + ViewModel.Config.ButtonZr = buttonValue.AsHidType(); break; case "ButtonR": - ViewModel.Config.ButtonR = buttonValue.AsHidType(); + ViewModel.Config.ButtonR = buttonValue.AsHidType(); break; case "ButtonPlus": - ViewModel.Config.ButtonPlus = buttonValue.AsHidType(); + ViewModel.Config.ButtonPlus = buttonValue.AsHidType(); break; case "ButtonA": - ViewModel.Config.ButtonA = buttonValue.AsHidType(); + ViewModel.Config.ButtonA = buttonValue.AsHidType(); break; case "ButtonB": - ViewModel.Config.ButtonB = buttonValue.AsHidType(); + ViewModel.Config.ButtonB = buttonValue.AsHidType(); break; case "ButtonX": - ViewModel.Config.ButtonX = buttonValue.AsHidType(); + ViewModel.Config.ButtonX = buttonValue.AsHidType(); break; case "ButtonY": - ViewModel.Config.ButtonY = buttonValue.AsHidType(); + ViewModel.Config.ButtonY = buttonValue.AsHidType(); break; case "RightStickButton": - ViewModel.Config.RightStickButton = buttonValue.AsHidType(); + ViewModel.Config.RightStickButton = buttonValue.AsHidType(); break; case "RightStickUp": - ViewModel.Config.RightStickUp = buttonValue.AsHidType(); + ViewModel.Config.RightStickUp = buttonValue.AsHidType(); break; case "RightStickDown": - ViewModel.Config.RightStickDown = buttonValue.AsHidType(); + ViewModel.Config.RightStickDown = buttonValue.AsHidType(); break; case "RightStickRight": - ViewModel.Config.RightStickRight = buttonValue.AsHidType(); + ViewModel.Config.RightStickRight = buttonValue.AsHidType(); break; case "RightStickLeft": - ViewModel.Config.RightStickLeft = buttonValue.AsHidType(); + ViewModel.Config.RightStickLeft = buttonValue.AsHidType(); break; } } @@ -207,34 +207,34 @@ namespace Ryujinx.Ava.UI.Views.Input { Dictionary buttonActions = new() { - { "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound }, - { "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound }, - { "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound }, - { "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound }, - { "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound }, - { "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound }, - { "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound }, - { "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound }, - { "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound }, - { "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound }, - { "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound }, - { "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound }, - { "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound }, - { "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound }, - { "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound }, - { "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound }, - { "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound }, - { "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound }, - { "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound }, - { "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound }, - { "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound }, - { "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound }, - { "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound }, - { "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound }, - { "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound }, - { "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound }, - { "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound }, - { "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound } + { "ButtonZl", () => ViewModel.Config.ButtonZl = PhysicalKey.Unbound }, + { "ButtonL", () => ViewModel.Config.ButtonL = PhysicalKey.Unbound }, + { "ButtonMinus", () => ViewModel.Config.ButtonMinus = PhysicalKey.Unbound }, + { "LeftStickButton", () => ViewModel.Config.LeftStickButton = PhysicalKey.Unbound }, + { "LeftStickUp", () => ViewModel.Config.LeftStickUp = PhysicalKey.Unbound }, + { "LeftStickDown", () => ViewModel.Config.LeftStickDown = PhysicalKey.Unbound }, + { "LeftStickRight", () => ViewModel.Config.LeftStickRight = PhysicalKey.Unbound }, + { "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = PhysicalKey.Unbound }, + { "DpadUp", () => ViewModel.Config.DpadUp = PhysicalKey.Unbound }, + { "DpadDown", () => ViewModel.Config.DpadDown = PhysicalKey.Unbound }, + { "DpadLeft", () => ViewModel.Config.DpadLeft = PhysicalKey.Unbound }, + { "DpadRight", () => ViewModel.Config.DpadRight = PhysicalKey.Unbound }, + { "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = PhysicalKey.Unbound }, + { "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = PhysicalKey.Unbound }, + { "RightButtonSr", () => ViewModel.Config.RightButtonSr = PhysicalKey.Unbound }, + { "RightButtonSl", () => ViewModel.Config.RightButtonSl = PhysicalKey.Unbound }, + { "ButtonZr", () => ViewModel.Config.ButtonZr = PhysicalKey.Unbound }, + { "ButtonR", () => ViewModel.Config.ButtonR = PhysicalKey.Unbound }, + { "ButtonPlus", () => ViewModel.Config.ButtonPlus = PhysicalKey.Unbound }, + { "ButtonA", () => ViewModel.Config.ButtonA = PhysicalKey.Unbound }, + { "ButtonB", () => ViewModel.Config.ButtonB = PhysicalKey.Unbound }, + { "ButtonX", () => ViewModel.Config.ButtonX = PhysicalKey.Unbound }, + { "ButtonY", () => ViewModel.Config.ButtonY = PhysicalKey.Unbound }, + { "RightStickButton", () => ViewModel.Config.RightStickButton = PhysicalKey.Unbound }, + { "RightStickUp", () => ViewModel.Config.RightStickUp = PhysicalKey.Unbound }, + { "RightStickDown", () => ViewModel.Config.RightStickDown = PhysicalKey.Unbound }, + { "RightStickRight", () => ViewModel.Config.RightStickRight = PhysicalKey.Unbound }, + { "RightStickLeft", () => ViewModel.Config.RightStickLeft = PhysicalKey.Unbound } }; if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action)) diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index b9a5462b2..a0a924c5a 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings } } - _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); + _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic); } protected override void OnPointerReleased(PointerReleasedEventArgs e) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index e7934f38a..1d8bcdfe9 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -30,6 +30,7 @@ using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Input.HLE; using Ryujinx.Input.SDL3; +using Ryujinx.Input; using System; using System.Collections.Generic; using System.Linq; @@ -105,7 +106,7 @@ namespace Ryujinx.Ava.UI.Windows if (Program.PreviewerDetached) { - InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver()); + InputManager = new InputManager(new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic), new SDL3GamepadDriver()); _ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it); this.ScalingChanged += OnScalingChanged; From 39f58e453b93b2cfd58f3d9ea8127925ab5b81f9 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 21:19:35 +0100 Subject: [PATCH 24/55] Track keyboard labels from host layout events --- .../Hid/KeyboardKeyExtensions.cs | 19 ------- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 2 + .../UI/Helpers/PhysicalKeyLabelHelper.cs | 54 +++++++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) delete mode 100644 src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs deleted file mode 100644 index f6bf50987..000000000 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardKeyExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.Common.Configuration.Hid -{ - public static class KeyboardKeyExtensions - { - public static Key ToKey(this PhysicalKey key) - { - return key is >= PhysicalKey.Unknown and < PhysicalKey.Count - ? (Key)(int)key - : Key.Unknown; - } - - public static PhysicalKey ToPhysicalKey(this Key key) - { - return key is >= Key.Unknown and < Key.Count - ? (PhysicalKey)(int)key - : PhysicalKey.Unknown; - } - } -} diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index f68d1cfb5..5386fb0ec 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -1,6 +1,7 @@ using Avalonia.Controls; using Avalonia.Input; using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; using Ryujinx.Input; using System; using System.Collections.Generic; @@ -83,6 +84,7 @@ namespace Ryujinx.Ava.Input { UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), true); UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), true); + PhysicalKeyLabelHelper.UpdateFromEvent(args); KeyPressed?.Invoke(this, args); } diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index a3f437457..b272003ae 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -3,6 +3,7 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; using Ryujinx.Common.Configuration.Hid; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using AvaPhysicalKey = Avalonia.Input.PhysicalKey; using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; @@ -12,6 +13,8 @@ namespace Ryujinx.Ava.UI.Helpers { internal static class PhysicalKeyLabelHelper { + private static readonly ConcurrentDictionary _observedLayoutLabels = new(); + private static readonly Dictionary _localizedKeysMap = new() { [ConfigPhysicalKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown, @@ -70,6 +73,11 @@ namespace Ryujinx.Ava.UI.Helpers return GetLocalizedString(localeKey); } + if (_observedLayoutLabels.TryGetValue(key, out string observedLabel)) + { + return observedLabel; + } + if (TryGetPrintableKeySymbol(key, out string label)) { return label; @@ -78,6 +86,25 @@ namespace Ryujinx.Ava.UI.Helpers return key.ToString(); } + public static void UpdateFromEvent(KeyEventArgs args) + { + if (args.KeyModifiers != KeyModifiers.None) + { + return; + } + + InputKey inputKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); + if (!TryConvertToConfigPhysicalKey(inputKey, out ConfigPhysicalKey physicalKey) || _localizedKeysMap.ContainsKey(physicalKey)) + { + return; + } + + if (TryNormalizePrintableSymbol(args.KeySymbol, out string label)) + { + _observedLayoutLabels[physicalKey] = label; + } + } + private static bool TryGetPrintableKeySymbol(ConfigPhysicalKey key, out string label) { // The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label. @@ -109,6 +136,33 @@ namespace Ryujinx.Ava.UI.Helpers return true; } + private static bool TryNormalizePrintableSymbol(string keySymbol, out string label) + { + if (string.IsNullOrEmpty(keySymbol) || keySymbol.Length != 1 || char.IsControl(keySymbol[0])) + { + label = string.Empty; + return false; + } + + label = char.IsLetter(keySymbol[0]) + ? char.ToUpperInvariant(keySymbol[0]).ToString() + : keySymbol; + + return true; + } + + private static bool TryConvertToConfigPhysicalKey(InputKey key, out ConfigPhysicalKey physicalKey) + { + if (key is >= InputKey.Unknown and < InputKey.Count) + { + physicalKey = (ConfigPhysicalKey)(int)key; + return true; + } + + physicalKey = ConfigPhysicalKey.Unknown; + return false; + } + private static string GetLocalizedString(LocaleKeys localeKey) { if (OperatingSystem.IsMacOS()) From 3401c29b81b863df95924411e46fed944a1645f9 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 21:34:13 +0100 Subject: [PATCH 25/55] Overall Code Cleanup --- src/Ryujinx.Input.SDL3/SDL3Keyboard.cs | 65 ++-------------- .../KeyboardInputMappingHelper.cs | 78 +++++++++++++++++++ src/Ryujinx/Input/AvaloniaKeyboard.cs | 74 +++--------------- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 16 ++-- .../Helpers/Converters/KeyValueConverter.cs | 2 +- .../UI/Helpers/PhysicalKeyLabelHelper.cs | 12 +-- .../UI/ViewModels/Input/InputViewModel.cs | 1 + .../Settings/SettingsHotkeysView.axaml.cs | 1 + src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 4 +- 9 files changed, 113 insertions(+), 140 deletions(-) create mode 100644 src/Ryujinx.Input/KeyboardInputMappingHelper.cs diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 8dec2ba35..3e64f3447 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -3,14 +3,11 @@ using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using System; using System.Collections.Generic; -using System.Numerics; using System.Runtime.CompilerServices; using System.Threading; using SDL; using static SDL.SDL3; -using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; - namespace Ryujinx.Input.SDL3 { class SDL3Keyboard : IKeyboard @@ -264,36 +261,6 @@ namespace Ryujinx.Input.SDL3 return value * ConvertRate; } - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) - { - short stickX = 0; - short stickY = 0; - - if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) - { - stickY += 1; - } - - if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) - { - stickY -= 1; - } - - if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) - { - stickX += 1; - } - - if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) - { - stickX -= 1; - } - - Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY)); - - return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); - } - public GamepadStateSnapshot GetMappedStateSnapshot() { KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); @@ -320,8 +287,8 @@ namespace Ryujinx.Input.SDL3 } } - (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); - (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + (short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); @@ -357,32 +324,12 @@ namespace Ryujinx.Input.SDL3 { _configuration = (StandardKeyboardInputConfig)configuration; - // First clear the buttons mapping _buttonsUserMapping.Clear(); - // Then configure left joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, _configuration.LeftJoyconStick.StickButton.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, _configuration.LeftJoycon.DpadUp.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, _configuration.LeftJoycon.DpadDown.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, _configuration.LeftJoycon.DpadLeft.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, _configuration.LeftJoycon.DpadRight.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, _configuration.LeftJoycon.ButtonMinus.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, _configuration.LeftJoycon.ButtonL.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, _configuration.LeftJoycon.ButtonZl.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, _configuration.LeftJoycon.ButtonSr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, _configuration.LeftJoycon.ButtonSl.ToInputKey())); - - // Finally configure right joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, _configuration.RightJoyconStick.StickButton.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, _configuration.RightJoycon.ButtonA.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, _configuration.RightJoycon.ButtonB.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, _configuration.RightJoycon.ButtonX.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, _configuration.RightJoycon.ButtonY.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, _configuration.RightJoycon.ButtonPlus.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, _configuration.RightJoycon.ButtonR.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, _configuration.RightJoycon.ButtonZr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, _configuration.RightJoycon.ButtonSr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, _configuration.RightJoycon.ButtonSl.ToInputKey())); + foreach (KeyboardInputMappingHelper.KeyboardButtonMapping mapping in KeyboardInputMappingHelper.BuildButtonMappings(_configuration)) + { + _buttonsUserMapping.Add(new ButtonMappingEntry(mapping.To, mapping.From)); + } } } diff --git a/src/Ryujinx.Input/KeyboardInputMappingHelper.cs b/src/Ryujinx.Input/KeyboardInputMappingHelper.cs new file mode 100644 index 000000000..7b9c5a4ef --- /dev/null +++ b/src/Ryujinx.Input/KeyboardInputMappingHelper.cs @@ -0,0 +1,78 @@ +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System.Collections.Generic; +using System.Numerics; + +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; + +namespace Ryujinx.Input +{ + public static class KeyboardInputMappingHelper + { + public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, Key From) + { + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unknown and not Key.Unbound; + } + + public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) => + [ + // Left JoyCon + new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton.ToInputKey()), + new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp.ToInputKey()), + new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown.ToInputKey()), + new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft.ToInputKey()), + new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight.ToInputKey()), + new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus.ToInputKey()), + new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL.ToInputKey()), + new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl.ToInputKey()), + new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr.ToInputKey()), + new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl.ToInputKey()), + + // Right JoyCon + new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton.ToInputKey()), + new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA.ToInputKey()), + new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB.ToInputKey()), + new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX.ToInputKey()), + new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY.ToInputKey()), + new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus.ToInputKey()), + new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR.ToInputKey()), + new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr.ToInputKey()), + new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr.ToInputKey()), + new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl.ToInputKey()), + ]; + + public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) + { + short stickX = 0; + short stickY = 0; + + if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) + { + stickY += 1; + } + + if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) + { + stickY -= 1; + } + + if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) + { + stickX += 1; + } + + if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) + { + stickX -= 1; + } + + if (stickX == 0 && stickY == 0) + { + return (0, 0); + } + + Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY)); + + return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); + } + } +} diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 0c18918c4..526ec7981 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -4,9 +4,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Input; using System; using System.Collections.Generic; -using System.Numerics; using System.Threading; -using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input @@ -27,10 +25,9 @@ namespace Ryujinx.Ava.Input public bool IsConnected => true; public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; - private class ButtonMappingEntry(GamepadButtonInputId to, Key from) + private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From) { - public readonly GamepadButtonInputId To = to; - public readonly Key From = from; + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unknown and not Key.Unbound; } public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode) @@ -62,7 +59,7 @@ namespace Ryujinx.Ava.Input foreach (ButtonMappingEntry entry in _buttonsUserMapping) { - if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + if (!entry.IsValid) { continue; } @@ -74,8 +71,8 @@ namespace Ryujinx.Ava.Input } } - (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); - (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + (short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); @@ -119,31 +116,10 @@ namespace Ryujinx.Ava.Input _buttonsUserMapping.Clear(); -#pragma warning disable IDE0055 // Disable formatting - // Left JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, _configuration.LeftJoyconStick.StickButton.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, _configuration.LeftJoycon.DpadUp.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, _configuration.LeftJoycon.DpadDown.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, _configuration.LeftJoycon.DpadLeft.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, _configuration.LeftJoycon.DpadRight.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, _configuration.LeftJoycon.ButtonMinus.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, _configuration.LeftJoycon.ButtonL.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, _configuration.LeftJoycon.ButtonZl.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, _configuration.LeftJoycon.ButtonSr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, _configuration.LeftJoycon.ButtonSl.ToInputKey())); - - // Right JoyCon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, _configuration.RightJoyconStick.StickButton.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, _configuration.RightJoycon.ButtonA.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, _configuration.RightJoycon.ButtonB.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, _configuration.RightJoycon.ButtonX.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, _configuration.RightJoycon.ButtonY.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, _configuration.RightJoycon.ButtonPlus.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, _configuration.RightJoycon.ButtonR.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, _configuration.RightJoycon.ButtonZr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, _configuration.RightJoycon.ButtonSr.ToInputKey())); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, _configuration.RightJoycon.ButtonSl.ToInputKey())); -#pragma warning restore IDE0055 + foreach (KeyboardInputMappingHelper.KeyboardButtonMapping mapping in KeyboardInputMappingHelper.BuildButtonMappings(_configuration)) + { + _buttonsUserMapping.Add(new ButtonMappingEntry(mapping.To, mapping.From)); + } } } @@ -174,38 +150,6 @@ namespace Ryujinx.Ava.Input return value * ConvertRate; } - private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) - { - short stickX = 0; - short stickY = 0; - - if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) - { - stickY += 1; - } - - if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) - { - stickY -= 1; - } - - if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) - { - stickX += 1; - } - - if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) - { - stickX -= 1; - } - - Vector2 stick = new(stickX, stickY); - - stick = Vector2.Normalize(stick); - - return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); - } - public void Clear() { _driver?.Clear(_mode); diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 5386fb0ec..5528d0d6f 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -1,7 +1,6 @@ using Avalonia.Controls; using Avalonia.Input; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; using Ryujinx.Input; using System; using System.Collections.Generic; @@ -82,18 +81,13 @@ namespace Ryujinx.Ava.Input protected void OnKeyPress(object sender, KeyEventArgs args) { - UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), true); - UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), true); - PhysicalKeyLabelHelper.UpdateFromEvent(args); - + UpdateKeyStates(args, isPressed: true); KeyPressed?.Invoke(this, args); } protected void OnKeyRelease(object sender, KeyEventArgs args) { - UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), false); - UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), false); - + UpdateKeyStates(args, isPressed: false); KeyRelease?.Invoke(this, args); } @@ -157,6 +151,12 @@ namespace Ryujinx.Ava.Input } } + private void UpdateKeyStates(KeyEventArgs args, bool isPressed) + { + UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), isPressed); + UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), isPressed); + } + private static Key GetInputKey(KeyEventArgs args, KeyboardInputMode mode) { if (mode == KeyboardInputMode.Physical) diff --git a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs index 87c60edd4..8c1046563 100644 --- a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs @@ -153,7 +153,7 @@ namespace Ryujinx.Ava.UI.Helpers break; case PhysicalKey physicalKey: - keyString = PhysicalKeyLabelHelper.GetString(physicalKey); + keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey); break; case GamepadInputId gamepadInputId: if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index b272003ae..fce993006 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -66,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers [ConfigPhysicalKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound, }; - public static string GetString(ConfigPhysicalKey key) + public static string GetDisplayString(ConfigPhysicalKey key) { if (_localizedKeysMap.TryGetValue(key, out LocaleKeys localeKey)) { @@ -78,7 +78,7 @@ namespace Ryujinx.Ava.UI.Helpers return observedLabel; } - if (TryGetPrintableKeySymbol(key, out string label)) + if (TryGetFallbackPrintableKeyLabel(key, out string label)) { return label; } @@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Helpers return key.ToString(); } - public static void UpdateFromEvent(KeyEventArgs args) + public static void ObserveKeyPress(object sender, KeyEventArgs args) { if (args.KeyModifiers != KeyModifiers.None) { @@ -99,13 +99,13 @@ namespace Ryujinx.Ava.UI.Helpers return; } - if (TryNormalizePrintableSymbol(args.KeySymbol, out string label)) + if (TryNormalizeObservedPrintableLabel(args.KeySymbol, out string label)) { _observedLayoutLabels[physicalKey] = label; } } - private static bool TryGetPrintableKeySymbol(ConfigPhysicalKey key, out string label) + private static bool TryGetFallbackPrintableKeyLabel(ConfigPhysicalKey key, out string label) { // The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label. if (key == ConfigPhysicalKey.Grave) @@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Helpers return true; } - private static bool TryNormalizePrintableSymbol(string keySymbol, out string label) + private static bool TryNormalizeObservedPrintableLabel(string keySymbol, out string label) { if (string.IsNullOrEmpty(keySymbol) || keySymbol.Length != 1 || char.IsControl(keySymbol[0])) { diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 5c91bf5ce..7e8151150 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -291,6 +291,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow = RyujinxApp.MainWindow; AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner, KeyboardInputMode.Physical); + AvaloniaKeyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index a0a924c5a..2e1e452af 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -35,6 +35,7 @@ namespace Ryujinx.Ava.UI.Views.Settings } _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic); + _avaloniaKeyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; } protected override void OnPointerReleased(PointerReleasedEventArgs e) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 1d8bcdfe9..7dac9a3d7 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -106,7 +106,9 @@ namespace Ryujinx.Ava.UI.Windows if (Program.PreviewerDetached) { - InputManager = new InputManager(new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic), new SDL3GamepadDriver()); + AvaloniaKeyboardDriver keyboardDriver = new(this, KeyboardInputMode.Semantic); + keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; + InputManager = new InputManager(keyboardDriver, new SDL3GamepadDriver()); _ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it); this.ScalingChanged += OnScalingChanged; From 1e5c4fedbdb459259080c5bfd84438b84936ea42 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 21:55:50 +0100 Subject: [PATCH 26/55] Fix Build error --- src/Ryujinx.Input.SDL3/SDL3Keyboard.cs | 1 + src/Ryujinx/Input/AvaloniaKeyboard.cs | 1 + src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 3e64f3447..d72b31c20 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using System; using System.Collections.Generic; +using System.Numerics; using System.Runtime.CompilerServices; using System.Threading; using SDL; diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 526ec7981..198c60971 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -4,6 +4,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Input; using System; using System.Collections.Generic; +using System.Numerics; using System.Threading; using Key = Ryujinx.Input.Key; diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 7e8151150..43ebef6f4 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -290,8 +290,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _mainWindow = RyujinxApp.MainWindow; - AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner, KeyboardInputMode.Physical); - AvaloniaKeyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; + AvaloniaKeyboardDriver keyboardDriver = new(owner, KeyboardInputMode.Physical); + keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; + AvaloniaKeyboardDriver = keyboardDriver; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; From 818399ecfc59908c8c7134779da874b9093a3185 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 18 Mar 2026 22:12:24 +0100 Subject: [PATCH 27/55] add .dotnet-home/ to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6f887e638..99a325de5 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,9 @@ ipch/ _ReSharper*/ *.[Rr]e[Ss]harper +#.NET +.dotnet-home/ + # TeamCity is a build add-in _TeamCity* From 327f90b4209d640c556aa2af0cd538e874fb5c5e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 19 Mar 2026 20:25:01 +0100 Subject: [PATCH 28/55] Refresh keyboard labels when layout changes --- .../UI/Helpers/PhysicalKeyLabelHelper.cs | 7 ++++ .../UI/Models/Input/KeyboardInputConfig.cs | 32 +++++++++++++++++++ .../UI/ViewModels/Input/InputViewModel.cs | 11 +++++++ 3 files changed, 50 insertions(+) diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index fce993006..a23bae4de 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -14,6 +14,7 @@ namespace Ryujinx.Ava.UI.Helpers internal static class PhysicalKeyLabelHelper { private static readonly ConcurrentDictionary _observedLayoutLabels = new(); + public static event Action LabelsChanged; private static readonly Dictionary _localizedKeysMap = new() { @@ -101,7 +102,13 @@ namespace Ryujinx.Ava.UI.Helpers if (TryNormalizeObservedPrintableLabel(args.KeySymbol, out string label)) { + if (_observedLayoutLabels.TryGetValue(physicalKey, out string existingLabel) && existingLabel == label) + { + return; + } + _observedLayoutLabels[physicalKey] = label; + LabelsChanged?.Invoke(); } } diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs index 7f3cb121b..6b27162fd 100644 --- a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -198,5 +198,37 @@ namespace Ryujinx.Ava.UI.Models.Input return config; } + + public void NotifyKeyLabelsChanged() + { + OnPropertiesChanged(nameof(LeftStickUp), + nameof(LeftStickDown), + nameof(LeftStickLeft), + nameof(LeftStickRight), + nameof(LeftStickButton), + nameof(RightStickUp), + nameof(RightStickDown), + nameof(RightStickLeft), + nameof(RightStickRight), + nameof(RightStickButton), + nameof(DpadUp), + nameof(DpadDown), + nameof(DpadLeft), + nameof(DpadRight), + nameof(ButtonMinus), + nameof(ButtonPlus), + nameof(ButtonA), + nameof(ButtonB), + nameof(ButtonX), + nameof(ButtonY), + nameof(ButtonL), + nameof(ButtonR), + nameof(ButtonZl), + nameof(ButtonZr), + nameof(LeftButtonSl), + nameof(LeftButtonSr), + nameof(RightButtonSl), + nameof(RightButtonSr)); + } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 43ebef6f4..1ae54b325 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -1,6 +1,7 @@ using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Svg.Skia; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using Gommon; using Ryujinx.Ava.Common.Locale; @@ -293,6 +294,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input AvaloniaKeyboardDriver keyboardDriver = new(owner, KeyboardInputMode.Physical); keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; AvaloniaKeyboardDriver = keyboardDriver; + PhysicalKeyLabelHelper.LabelsChanged += OnPhysicalKeyLabelsChanged; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; @@ -1062,9 +1064,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input NotifyChangesEvent?.Invoke(); } + private void OnPhysicalKeyLabelsChanged() + { + if (ConfigViewModel is KeyboardInputViewModel keyboardInputViewModel) + { + Dispatcher.UIThread.Post(keyboardInputViewModel.Config.NotifyKeyLabelsChanged); + } + } + public void Dispose() { GC.SuppressFinalize(this); + PhysicalKeyLabelHelper.LabelsChanged -= OnPhysicalKeyLabelsChanged; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; From 3cbe372b181992100ecb13921a8c704d1ff957e9 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Thu, 19 Mar 2026 20:45:48 +0100 Subject: [PATCH 29/55] Simplify gameplay keyboard physical-key paths --- assets/Locales/KeyboardLayout.json | 27 +------ src/Ryujinx.Input.SDL3/SDL3Keyboard.cs | 43 +++++----- src/Ryujinx.Input/IKeyboard.cs | 7 +- .../KeyboardInputMappingHelper.cs | 52 ++++++------- src/Ryujinx.Input/KeyboardStateSnapshot.cs | 4 + src/Ryujinx.Input/PhysicalKeyExtensions.cs | 14 ---- src/Ryujinx/Input/AvaloniaKeyboard.cs | 15 +--- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 78 +++++++++++++------ .../UI/Models/Input/StickVisualizer.cs | 16 ++-- 9 files changed, 120 insertions(+), 136 deletions(-) delete mode 100644 src/Ryujinx.Input/PhysicalKeyExtensions.cs diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json index 86d9772c1..6b02f4834 100644 --- a/assets/Locales/KeyboardLayout.json +++ b/assets/Locales/KeyboardLayout.json @@ -375,31 +375,6 @@ "zh_TW": "右 ⌘" } }, - { - "ID": "KeyMenu", - "Translations": { - "ar_SA": "زر القائمة", - "de_DE": "", - "el_GR": "", - "en_US": "Menu", - "es_ES": null, - "fr_FR": null, - "he_IL": "", - "it_IT": "Menù", - "ja_JP": "", - "ko_KR": "메뉴", - "no_NO": "Meny", - "pl_PL": "", - "pt_BR": null, - "ru_RU": "Меню", - "sv_SE": "Meny", - "th_TH": "เมนู", - "tr_TR": "Menü", - "uk_UA": "Меню", - "zh_CN": "菜单键", - "zh_TW": "功能表鍵" - } - }, { "ID": "KeyUp", "Translations": { @@ -1926,4 +1901,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index d72b31c20..93a9369e4 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -8,23 +8,19 @@ using System.Runtime.CompilerServices; using System.Threading; using SDL; using static SDL.SDL3; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Input.SDL3 { class SDL3Keyboard : IKeyboard { - private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From) - { - public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound; - } - private readonly Lock _userMappingLock = new(); #pragma warning disable IDE0052 // Remove unread private member private readonly SDL3KeyboardDriver _driver; #pragma warning restore IDE0052 private StandardKeyboardInputConfig _configuration; - private readonly List _buttonsUserMapping; + private readonly List _buttonsUserMapping; private static readonly SDL_Keycode[] _keysDriverMapping = @@ -193,9 +189,9 @@ namespace Ryujinx.Input.SDL3 } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe static int ToSDL3Scancode(Key key) + private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key) { - if (key is >= Key.Unknown and <= Key.Menu) + if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu) { return -1; } @@ -203,18 +199,18 @@ namespace Ryujinx.Input.SDL3 return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null); } - private static SDL_Keymod GetKeyboardModifierMask(Key key) + private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key) { return key switch { - Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT, - Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT, - Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL, - Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL, - Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT, - Key.AltRight => SDL_Keymod.SDL_KMOD_RALT, - Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI, - Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI, + ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT, + ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT, + ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL, + ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL, + ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT, + ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT, + ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI, + ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI, // NOTE: Menu key isn't supported by SDL3. _ => SDL_Keymod.SDL_KMOD_NONE }; @@ -230,9 +226,9 @@ namespace Ryujinx.Input.SDL3 rawKeyboardState = SDL_GetKeyboardState(null); } - bool[] keysState = new bool[(int)Key.Count]; + bool[] keysState = new bool[(int)ConfigPhysicalKey.Count]; - for (Key key = 0; key < Key.Count; key++) + for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++) { int index = ToSDL3Scancode(key); if (index == -1) @@ -274,9 +270,9 @@ namespace Ryujinx.Input.SDL3 return result; } - foreach (ButtonMappingEntry entry in _buttonsUserMapping) + foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping) { - if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + if (!entry.IsValid) { continue; } @@ -327,10 +323,7 @@ namespace Ryujinx.Input.SDL3 _buttonsUserMapping.Clear(); - foreach (KeyboardInputMappingHelper.KeyboardButtonMapping mapping in KeyboardInputMappingHelper.BuildButtonMappings(_configuration)) - { - _buttonsUserMapping.Add(new ButtonMappingEntry(mapping.To, mapping.From)); - } + _buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration)); } } diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs index c51d5aea3..74ac50457 100644 --- a/src/Ryujinx.Input/IKeyboard.cs +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Runtime.CompilerServices; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Input { @@ -33,12 +34,12 @@ namespace Ryujinx.Input { if (_keyState is null) { - _keyState = new bool[(int)Key.Count]; + _keyState = new bool[(int)ConfigPhysicalKey.Count]; } - for (Key key = 0; key < Key.Count; key++) + for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++) { - _keyState[(int)key] = keyboard.IsPressed(key); + _keyState[(int)key] = keyboard.IsPressed((Key)(int)key); } return new KeyboardStateSnapshot(_keyState); diff --git a/src/Ryujinx.Input/KeyboardInputMappingHelper.cs b/src/Ryujinx.Input/KeyboardInputMappingHelper.cs index 7b9c5a4ef..8c4aeb3bc 100644 --- a/src/Ryujinx.Input/KeyboardInputMappingHelper.cs +++ b/src/Ryujinx.Input/KeyboardInputMappingHelper.cs @@ -8,36 +8,36 @@ namespace Ryujinx.Input { public static class KeyboardInputMappingHelper { - public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, Key From) + public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From) { - public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unknown and not Key.Unbound; + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound; } public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) => [ // Left JoyCon - new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton.ToInputKey()), - new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp.ToInputKey()), - new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown.ToInputKey()), - new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft.ToInputKey()), - new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight.ToInputKey()), - new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus.ToInputKey()), - new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL.ToInputKey()), - new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl.ToInputKey()), - new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr.ToInputKey()), - new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl.ToInputKey()), + new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton), + new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp), + new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown), + new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft), + new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight), + new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus), + new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL), + new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl), + new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr), + new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl), // Right JoyCon - new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton.ToInputKey()), - new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA.ToInputKey()), - new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB.ToInputKey()), - new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX.ToInputKey()), - new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY.ToInputKey()), - new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus.ToInputKey()), - new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR.ToInputKey()), - new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr.ToInputKey()), - new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr.ToInputKey()), - new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl.ToInputKey()), + new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton), + new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA), + new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB), + new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX), + new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY), + new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus), + new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR), + new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr), + new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr), + new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl), ]; public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) @@ -45,22 +45,22 @@ namespace Ryujinx.Input short stickX = 0; short stickY = 0; - if (snapshot.IsPressed(stickConfig.StickUp.ToInputKey())) + if (snapshot.IsPressed(stickConfig.StickUp)) { stickY += 1; } - if (snapshot.IsPressed(stickConfig.StickDown.ToInputKey())) + if (snapshot.IsPressed(stickConfig.StickDown)) { stickY -= 1; } - if (snapshot.IsPressed(stickConfig.StickRight.ToInputKey())) + if (snapshot.IsPressed(stickConfig.StickRight)) { stickX += 1; } - if (snapshot.IsPressed(stickConfig.StickLeft.ToInputKey())) + if (snapshot.IsPressed(stickConfig.StickLeft)) { stickX -= 1; } diff --git a/src/Ryujinx.Input/KeyboardStateSnapshot.cs b/src/Ryujinx.Input/KeyboardStateSnapshot.cs index 9b40b46db..91765ceb8 100644 --- a/src/Ryujinx.Input/KeyboardStateSnapshot.cs +++ b/src/Ryujinx.Input/KeyboardStateSnapshot.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; namespace Ryujinx.Input { @@ -25,5 +26,8 @@ namespace Ryujinx.Input /// True if the given key is pressed [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsPressed(Key key) => KeysState[(int)key]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key]; } } diff --git a/src/Ryujinx.Input/PhysicalKeyExtensions.cs b/src/Ryujinx.Input/PhysicalKeyExtensions.cs deleted file mode 100644 index f2359d0d8..000000000 --- a/src/Ryujinx.Input/PhysicalKeyExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; - -namespace Ryujinx.Input -{ - public static class PhysicalKeyExtensions - { - public static Key ToInputKey(this ConfigPhysicalKey key) - { - return key is >= ConfigPhysicalKey.Unknown and < ConfigPhysicalKey.Count - ? (Key)(int)key - : Key.Unknown; - } - } -} diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 198c60971..19613adef 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Input { internal class AvaloniaKeyboard : IKeyboard { - private readonly List _buttonsUserMapping; + private readonly List _buttonsUserMapping; private readonly AvaloniaKeyboardDriver _driver; private readonly KeyboardInputMode _mode; private StandardKeyboardInputConfig _configuration; @@ -25,12 +25,6 @@ namespace Ryujinx.Ava.Input public bool IsConnected => true; public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; - - private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From) - { - public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unknown and not Key.Unbound; - } - public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode) { _buttonsUserMapping = []; @@ -58,7 +52,7 @@ namespace Ryujinx.Ava.Input return result; } - foreach (ButtonMappingEntry entry in _buttonsUserMapping) + foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping) { if (!entry.IsValid) { @@ -117,10 +111,7 @@ namespace Ryujinx.Ava.Input _buttonsUserMapping.Clear(); - foreach (KeyboardInputMappingHelper.KeyboardButtonMapping mapping in KeyboardInputMappingHelper.BuildButtonMappings(_configuration)) - { - _buttonsUserMapping.Add(new ButtonMappingEntry(mapping.To, mapping.From)); - } + _buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration)); } } diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 5528d0d6f..7010e14b1 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -4,6 +4,7 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Input; using System; using System.Collections.Generic; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input @@ -13,7 +14,7 @@ namespace Ryujinx.Ava.Input private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; private readonly Dictionary _semanticPressedKeys; - private readonly Dictionary _physicalPressedKeys; + private readonly Dictionary _physicalPressedKeys; private readonly KeyboardInputMode _defaultMode; public event EventHandler KeyPressed; @@ -98,12 +99,21 @@ namespace Ryujinx.Ava.Input return false; } - return GetPressedKeys(mode).ContainsKey(key); + return mode == KeyboardInputMode.Physical + ? _physicalPressedKeys.ContainsKey((ConfigPhysicalKey)(int)key) + : _semanticPressedKeys.ContainsKey(key); } internal void Clear(KeyboardInputMode mode) { - GetPressedKeys(mode).Clear(); + if (mode == KeyboardInputMode.Physical) + { + _physicalPressedKeys.Clear(); + } + else + { + _semanticPressedKeys.Clear(); + } } public void Clear() @@ -112,11 +122,6 @@ namespace Ryujinx.Ava.Input _physicalPressedKeys.Clear(); } - private Dictionary GetPressedKeys(KeyboardInputMode mode) - { - return mode == KeyboardInputMode.Physical ? _physicalPressedKeys : _semanticPressedKeys; - } - private static void UpdateKeyState(Dictionary pressedKeys, Key key, bool isPressed) { if (key is Key.Unknown or Key.Unbound) @@ -151,24 +156,53 @@ namespace Ryujinx.Ava.Input } } - private void UpdateKeyStates(KeyEventArgs args, bool isPressed) + private static void UpdateKeyState(Dictionary pressedKeys, ConfigPhysicalKey key, bool isPressed) { - UpdateKeyState(_semanticPressedKeys, GetInputKey(args, KeyboardInputMode.Semantic), isPressed); - UpdateKeyState(_physicalPressedKeys, GetInputKey(args, KeyboardInputMode.Physical), isPressed); - } - - private static Key GetInputKey(KeyEventArgs args, KeyboardInputMode mode) - { - if (mode == KeyboardInputMode.Physical) + if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound) { - Key physicalKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); - - return physicalKey != Key.Unknown - ? physicalKey - : AvaloniaKeyboardMappingHelper.ToInputKey(args.Key); + return; } - return AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + if (isPressed) + { + if (pressedKeys.TryGetValue(key, out int count)) + { + pressedKeys[key] = count + 1; + } + else + { + pressedKeys[key] = 1; + } + + return; + } + + if (pressedKeys.TryGetValue(key, out int currentCount)) + { + if (currentCount <= 1) + { + pressedKeys.Remove(key); + } + else + { + pressedKeys[key] = currentCount - 1; + } + } + } + + private void UpdateKeyStates(KeyEventArgs args, bool isPressed) + { + UpdateKeyState(_semanticPressedKeys, AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key), isPressed); + UpdateKeyState(_physicalPressedKeys, GetPhysicalInputKey(args), isPressed); + } + + private static ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args) + { + Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); + + return key is >= Key.Unknown and < Key.Count + ? (ConfigPhysicalKey)(int)key + : ConfigPhysicalKey.Unknown; } public void Dispose() diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs index 936268601..da5d1853b 100644 --- a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs +++ b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs @@ -154,42 +154,42 @@ namespace Ryujinx.Ava.UI.Models.Input { KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot(); - if (snapshot.IsPressed(KeyboardConfig.LeftStickRight.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.LeftStickRight)) { leftBuffer.Item1 += 1; } - if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft)) { leftBuffer.Item1 -= 1; } - if (snapshot.IsPressed(KeyboardConfig.LeftStickUp.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.LeftStickUp)) { leftBuffer.Item2 += 1; } - if (snapshot.IsPressed(KeyboardConfig.LeftStickDown.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.LeftStickDown)) { leftBuffer.Item2 -= 1; } - if (snapshot.IsPressed(KeyboardConfig.RightStickRight.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.RightStickRight)) { rightBuffer.Item1 += 1; } - if (snapshot.IsPressed(KeyboardConfig.RightStickLeft.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.RightStickLeft)) { rightBuffer.Item1 -= 1; } - if (snapshot.IsPressed(KeyboardConfig.RightStickUp.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.RightStickUp)) { rightBuffer.Item2 += 1; } - if (snapshot.IsPressed(KeyboardConfig.RightStickDown.ToInputKey())) + if (snapshot.IsPressed(KeyboardConfig.RightStickDown)) { rightBuffer.Item2 -= 1; } From b3d18f78452d9bfd14eb178ba300a2f56eac846c Mon Sep 17 00:00:00 2001 From: _Neo_ Date: Fri, 20 Mar 2026 22:55:47 +0200 Subject: [PATCH 30/55] Initial Prep for Keyboard Label Transfer Preparing to transfer updated keyboard labels from another MR into this one. --- assets/Locales/KeyboardLayout.json | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json index 6b02f4834..69fd59fe5 100644 --- a/assets/Locales/KeyboardLayout.json +++ b/assets/Locales/KeyboardLayout.json @@ -53,26 +53,26 @@ { "ID": "KeyShiftRight", "Translations": { - "ar_SA": "زر ‫Shift الأيمن", - "de_DE": "", - "el_GR": "", - "en_US": "Shift Right", - "es_ES": "Mayús Derecha", - "fr_FR": "Maj Droite", - "he_IL": "", - "it_IT": "Maiusc destro", - "ja_JP": "", - "ko_KR": "우측 Shift", - "no_NO": "Skift høyre", - "pl_PL": "", - "pt_BR": "Shift Direito", - "ru_RU": "Правый Shift", - "sv_SE": "Skift höger", - "th_TH": "Shift ขวา", - "tr_TR": "Sağ Shift", - "uk_UA": "Shift Правий", - "zh_CN": "右侧Shift", - "zh_TW": "右 Shift" + "ar_SA": "⇧ يمين", + "de_DE": "⇧ Rechts", + "el_GR": "⇧ Δεξί", + "en_US": "⇧ Right", + "es_ES": "⇧ Derecha", + "fr_FR": "⇧ Droit", + "he_IL": "⇧ ימין", + "it_IT": "⇧ Destro", + "ja_JP": "⇧右", + "ko_KR": "우측 ⇧", + "no_NO": "⇧ Høyre", + "pl_PL": "⇧ Prawy", + "pt_BR": "⇧ Direito", + "ru_RU": "Правый ⇧", + "sv_SE": "⇧ Höger", + "th_TH": "⇧ ขวา", + "tr_TR": "⇧ Sağ", + "uk_UA": "⇧ Правий", + "zh_CN": "右侧⇧", + "zh_TW": "右 ⇧" } }, { From cd4aa41a8f6890b957fc19b025c501196f97c32a Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 21 Mar 2026 17:55:04 +0100 Subject: [PATCH 31/55] Guard input profile loading when config is missing --- .../UI/ViewModels/Input/InputViewModel.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 1ae54b325..30a1a94d7 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -506,6 +506,23 @@ namespace Ryujinx.Ava.UI.ViewModels.Input return device.Id.Split(" ")[0]; } + private string GetCurrentConfigDeviceId() + { + if (_device < 0 || _device >= Devices.Count) + { + return null; + } + + (DeviceType Type, string Id, string Name) device = Devices[_device]; + + return device.Type switch + { + DeviceType.Keyboard => device.Id, + DeviceType.Controller => device.Id.Split(" ")[0], + _ => null, + }; + } + public void LoadControllers() { Controllers.Clear(); @@ -864,7 +881,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _isLoaded = false; - config.Id = Config.Id; // Set current device id instead of changing device(independent profiles) + string currentDeviceId = Config?.Id ?? GetCurrentConfigDeviceId(); + if (string.IsNullOrEmpty(currentDeviceId)) + { + Logger.Warning?.Print(LogClass.Configuration, $"Ignoring profile load for {ProfileName} because no active input device is selected."); + return; + } + + config.Id = currentDeviceId; // Set current device id instead of changing device(independent profiles) LoadConfiguration(config); From 7becde9d8e33e1b4c85017d671bf53295c9d9f22 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 21 Mar 2026 22:45:19 +0100 Subject: [PATCH 32/55] Fix settings keyboard input focus loss --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 34 +++++++++++--- .../UI/ViewModels/Input/InputViewModel.cs | 47 +++++++++++++++++-- src/Ryujinx/UI/Views/Input/InputView.axaml.cs | 9 ++++ 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 7010e14b1..0bdfd8755 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Ava.Input private readonly Control _control; private readonly Dictionary _semanticPressedKeys; private readonly Dictionary _physicalPressedKeys; + private readonly Dictionary _observedPhysicalKeysBySemanticKey; private readonly KeyboardInputMode _defaultMode; public event EventHandler KeyPressed; @@ -29,6 +30,7 @@ namespace Ryujinx.Ava.Input _control = control; _semanticPressedKeys = []; _physicalPressedKeys = []; + _observedPhysicalKeysBySemanticKey = []; _defaultMode = defaultMode; _control.KeyDown += OnKeyPress; @@ -192,17 +194,37 @@ namespace Ryujinx.Ava.Input private void UpdateKeyStates(KeyEventArgs args, bool isPressed) { - UpdateKeyState(_semanticPressedKeys, AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key), isPressed); - UpdateKeyState(_physicalPressedKeys, GetPhysicalInputKey(args), isPressed); + Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key); + Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey); + + UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed); + UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed); + + if (isPressed && + semanticKey is not Key.Unknown and not Key.Unbound && + physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound) + { + _observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey; + } } - private static ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args) + private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey) { Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); - return key is >= Key.Unknown and < Key.Count - ? (ConfigPhysicalKey)(int)key - : ConfigPhysicalKey.Unknown; + if (key is >= Key.Unknown and < Key.Count) + { + return (ConfigPhysicalKey)(int)key; + } + + if (semanticKey is not Key.Unknown and not Key.Unbound && + _observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey)) + { + return observedPhysicalKey; + } + + return ConfigPhysicalKey.Unknown; } public void Dispose() diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 30a1a94d7..1f3512315 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -43,6 +43,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private const string KeyboardString = "keyboard"; private const string ControllerString = "controller"; private readonly MainWindow _mainWindow; + private Control _keyboardDriverControl; private PlayerIndex _playerId; private PlayerIndex _playerIdChoose; @@ -66,7 +67,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public IGamepadDriver AvaloniaKeyboardDriver { get; } + public IGamepadDriver AvaloniaKeyboardDriver { get; private set; } public IGamepad SelectedGamepad { @@ -291,9 +292,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _mainWindow = RyujinxApp.MainWindow; - AvaloniaKeyboardDriver keyboardDriver = new(owner, KeyboardInputMode.Physical); - keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; - AvaloniaKeyboardDriver = keyboardDriver; + ReplaceKeyboardDriver(owner); PhysicalKeyLabelHelper.LabelsChanged += OnPhysicalKeyLabelsChanged; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; @@ -313,6 +312,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _isChangeTrackingActive = true; } + public void RetargetKeyboardDriver(Control owner) + { + if (!Program.PreviewerDetached) + { + return; + } + + ReplaceKeyboardDriver(owner); + } + public InputViewModel() { PlayerIndexes = []; @@ -1096,6 +1105,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } + private void ReplaceKeyboardDriver(Control owner) + { + Control target = TopLevel.GetTopLevel(owner) as Control ?? owner; + + if (ReferenceEquals(_keyboardDriverControl, target)) + { + return; + } + + if (AvaloniaKeyboardDriver is AvaloniaKeyboardDriver oldKeyboardDriver) + { + oldKeyboardDriver.KeyPressed -= PhysicalKeyLabelHelper.ObserveKeyPress; + oldKeyboardDriver.Dispose(); + } + + _keyboardDriverControl = target; + + AvaloniaKeyboardDriver keyboardDriver = new(target, KeyboardInputMode.Physical); + keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; + AvaloniaKeyboardDriver = keyboardDriver; + + if (_isLoaded && Device > 0 && Device < Devices.Count && Devices[Device].Type == DeviceType.Keyboard) + { + SelectedGamepad?.Dispose(); + LoadInputDriver(); + } + } + public void Dispose() { GC.SuppressFinalize(this); @@ -1110,7 +1147,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input SelectedGamepad?.Dispose(); - AvaloniaKeyboardDriver.Dispose(); + AvaloniaKeyboardDriver?.Dispose(); } } } diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs index f8ba04f5d..391feffaf 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Systems.Configuration; @@ -20,6 +21,13 @@ namespace Ryujinx.Ava.UI.Views.Input InitializeComponent(); } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + ViewModel?.RetargetKeyboardDriver(this); + } + public void SaveCurrentProfile() { ViewModel.Save(); @@ -30,6 +38,7 @@ namespace Ryujinx.Ava.UI.Views.Input Dispose(); ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs InitializeComponent(); + ViewModel.RetargetKeyboardDriver(this); } private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) From 0b02e71a66b6eec6c1012665a0bb4eef768e98c6 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 21 Mar 2026 23:32:46 +0100 Subject: [PATCH 33/55] fixed KeyDown events latching gameplay keys --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 56 ++++----------------- src/Ryujinx/UI/Renderer/RendererHost.cs | 1 - 2 files changed, 10 insertions(+), 47 deletions(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 0bdfd8755..da149c9d9 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -13,8 +13,8 @@ namespace Ryujinx.Ava.Input { private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; - private readonly Dictionary _semanticPressedKeys; - private readonly Dictionary _physicalPressedKeys; + private readonly HashSet _semanticPressedKeys; + private readonly HashSet _physicalPressedKeys; private readonly Dictionary _observedPhysicalKeysBySemanticKey; private readonly KeyboardInputMode _defaultMode; @@ -102,8 +102,8 @@ namespace Ryujinx.Ava.Input } return mode == KeyboardInputMode.Physical - ? _physicalPressedKeys.ContainsKey((ConfigPhysicalKey)(int)key) - : _semanticPressedKeys.ContainsKey(key); + ? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key) + : _semanticPressedKeys.Contains(key); } internal void Clear(KeyboardInputMode mode) @@ -124,7 +124,7 @@ namespace Ryujinx.Ava.Input _physicalPressedKeys.Clear(); } - private static void UpdateKeyState(Dictionary pressedKeys, Key key, bool isPressed) + private static void UpdateKeyState(HashSet pressedKeys, Key key, bool isPressed) { if (key is Key.Unknown or Key.Unbound) { @@ -133,32 +133,14 @@ namespace Ryujinx.Ava.Input if (isPressed) { - if (pressedKeys.TryGetValue(key, out int count)) - { - pressedKeys[key] = count + 1; - } - else - { - pressedKeys[key] = 1; - } - + pressedKeys.Add(key); return; } - if (pressedKeys.TryGetValue(key, out int currentCount)) - { - if (currentCount <= 1) - { - pressedKeys.Remove(key); - } - else - { - pressedKeys[key] = currentCount - 1; - } - } + pressedKeys.Remove(key); } - private static void UpdateKeyState(Dictionary pressedKeys, ConfigPhysicalKey key, bool isPressed) + private static void UpdateKeyState(HashSet pressedKeys, ConfigPhysicalKey key, bool isPressed) { if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound) { @@ -167,29 +149,11 @@ namespace Ryujinx.Ava.Input if (isPressed) { - if (pressedKeys.TryGetValue(key, out int count)) - { - pressedKeys[key] = count + 1; - } - else - { - pressedKeys[key] = 1; - } - + pressedKeys.Add(key); return; } - if (pressedKeys.TryGetValue(key, out int currentCount)) - { - if (currentCount <= 1) - { - pressedKeys.Remove(key); - } - else - { - pressedKeys[key] = currentCount - 1; - } - } + pressedKeys.Remove(key); } private void UpdateKeyStates(KeyEventArgs args, bool isPressed) diff --git a/src/Ryujinx/UI/Renderer/RendererHost.cs b/src/Ryujinx/UI/Renderer/RendererHost.cs index 9d24fbbad..df1d77dc5 100644 --- a/src/Ryujinx/UI/Renderer/RendererHost.cs +++ b/src/Ryujinx/UI/Renderer/RendererHost.cs @@ -45,7 +45,6 @@ namespace Ryujinx.Ava.UI.Renderer Content = EmbeddedWindow; } - public void Dispose() { if (EmbeddedWindow != null) From e4b920002f7d66a70d1e323a99a186bc25e698b8 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 21 Mar 2026 23:44:39 +0100 Subject: [PATCH 34/55] Fix input device refresh button --- src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs | 2 +- src/Ryujinx/UI/Views/Input/InputView.axaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 1f3512315..fcfc94aa6 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -640,7 +640,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } DeviceList.AddRange(Devices.Select(x => x.Name)); - Device = Math.Min(Device, DeviceList.Count); + Device = Math.Min(Device, DeviceList.Count - 1); } } diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml b/src/Ryujinx/UI/Views/Input/InputView.axaml index c4f61e78e..e8e19ab6c 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml @@ -168,7 +168,7 @@ MinWidth="0" Margin="5,0,0,0" VerticalAlignment="Center" - Command="{Binding LoadDevice}"> + Command="{Binding LoadDevices}"> Date: Sun, 22 Mar 2026 00:40:45 +0100 Subject: [PATCH 35/55] Simplify keyboard input cleanup paths --- src/Ryujinx.Input.SDL3/SDL3Keyboard.cs | 10 +- src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs | 9 +- src/Ryujinx/Input/AvaloniaKeyboard.cs | 13 +- .../Helpers/Converters/KeyValueConverter.cs | 94 +----------- .../UI/Helpers/KeyboardLayoutLocaleHelper.cs | 142 ++++++++++++++++++ .../UI/Helpers/PhysicalKeyLabelHelper.cs | 78 +--------- src/Ryujinx/UI/Views/Input/InputView.axaml.cs | 17 ++- 7 files changed, 167 insertions(+), 196 deletions(-) create mode 100644 src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 93a9369e4..77040bf09 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -1,6 +1,5 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; using System; using System.Collections.Generic; using System.Numerics; @@ -15,10 +14,6 @@ namespace Ryujinx.Input.SDL3 class SDL3Keyboard : IKeyboard { private readonly Lock _userMappingLock = new(); - -#pragma warning disable IDE0052 // Remove unread private member - private readonly SDL3KeyboardDriver _driver; -#pragma warning restore IDE0052 private StandardKeyboardInputConfig _configuration; private readonly List _buttonsUserMapping; @@ -165,9 +160,8 @@ namespace Ryujinx.Input.SDL3 SDL_Keycode.SDLK_0 ]; - public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name) + public SDL3Keyboard(string id, string name) { - _driver = driver; Id = id; Name = name; _buttonsUserMapping = []; @@ -329,7 +323,7 @@ namespace Ryujinx.Input.SDL3 public void SetLed(uint packedRgb) { - Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard"); + // Keyboard LEDs are not supported by this backend. } public void SetTriggerThreshold(float triggerThreshold) diff --git a/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs index c401ab947..00d5d6899 100644 --- a/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs +++ b/src/Ryujinx.Input.SDL3/SDLKeyboardDriver.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Ryujinx.Input.SDL3 { - public class SDL3KeyboardDriver : IKeyboardModeDriver + public class SDL3KeyboardDriver : IGamepadDriver { public SDL3KeyboardDriver() { @@ -44,18 +44,13 @@ namespace Ryujinx.Input.SDL3 } public IGamepad GetGamepad(string id) - { - return GetKeyboard(id, KeyboardInputMode.Semantic); - } - - public IKeyboard GetKeyboard(string id, KeyboardInputMode mode) { if (!_keyboardIdentifers[0].Equals(id)) { return null; } - return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); + return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards"); } public IEnumerable GetGamepads() diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index 19613adef..30237207b 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -1,6 +1,5 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Logging; using Ryujinx.Input; using System; using System.Collections.Generic; @@ -16,7 +15,6 @@ namespace Ryujinx.Ava.Input private readonly AvaloniaKeyboardDriver _driver; private readonly KeyboardInputMode _mode; private StandardKeyboardInputConfig _configuration; - private uint _ledValue; private readonly Lock _userMappingLock = new(); @@ -117,16 +115,7 @@ namespace Ryujinx.Ava.Input public void SetLed(uint packedRgb) { - if (_ledValue == packedRgb) - { - return; - } - - _ledValue = packedRgb; - - Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard"); - - // Keyboard LED is not supported by this backend. + // Keyboard LEDs are not supported by this backend. } public void SetTriggerThreshold(float triggerThreshold) { } diff --git a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs index 8c1046563..76033c7e1 100644 --- a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs @@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller; using System; using System.Collections.Generic; using System.Globalization; +using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.UI.Helpers { @@ -12,79 +13,6 @@ namespace Ryujinx.Ava.UI.Helpers { public static readonly KeyValueConverter Instance = new(); - private static readonly Dictionary _keysMap = new() - { - { Key.Unknown, LocaleKeys.KeyboardLayout_KeyUnknown }, - { Key.ShiftLeft, LocaleKeys.KeyboardLayout_KeyShiftLeft }, - { Key.ShiftRight, LocaleKeys.KeyboardLayout_KeyShiftRight }, - { Key.ControlLeft, LocaleKeys.KeyboardLayout_KeyControlLeft }, - { Key.ControlRight, LocaleKeys.KeyboardLayout_KeyControlRight }, - { Key.AltLeft, LocaleKeys.KeyboardLayout_KeyAltLeft }, - { Key.AltRight, LocaleKeys.KeyboardLayout_KeyAltRight }, - { Key.WinLeft, LocaleKeys.KeyboardLayout_KeyWinLeft }, - { Key.WinRight, LocaleKeys.KeyboardLayout_KeyWinRight }, - { Key.Up, LocaleKeys.KeyboardLayout_KeyUp }, - { Key.Down, LocaleKeys.KeyboardLayout_KeyDown }, - { Key.Left, LocaleKeys.KeyboardLayout_KeyLeft }, - { Key.Right, LocaleKeys.KeyboardLayout_KeyRight }, - { Key.Enter, LocaleKeys.KeyboardLayout_KeyEnter }, - { Key.Escape, LocaleKeys.KeyboardLayout_KeyEscape }, - { Key.Space, LocaleKeys.KeyboardLayout_KeySpace }, - { Key.Tab, LocaleKeys.KeyboardLayout_KeyTab }, - { Key.BackSpace, LocaleKeys.KeyboardLayout_KeyBackSpace }, - { Key.Insert, LocaleKeys.KeyboardLayout_KeyInsert }, - { Key.Delete, LocaleKeys.KeyboardLayout_KeyDelete }, - { Key.PageUp, LocaleKeys.KeyboardLayout_KeyPageUp }, - { Key.PageDown, LocaleKeys.KeyboardLayout_KeyPageDown }, - { Key.Home, LocaleKeys.KeyboardLayout_KeyHome }, - { Key.End, LocaleKeys.KeyboardLayout_KeyEnd }, - { Key.CapsLock, LocaleKeys.KeyboardLayout_KeyCapsLock }, - { Key.ScrollLock, LocaleKeys.KeyboardLayout_KeyScrollLock }, - { Key.PrintScreen, LocaleKeys.KeyboardLayout_KeyPrintScreen }, - { Key.Pause, LocaleKeys.KeyboardLayout_KeyPause }, - { Key.NumLock, LocaleKeys.KeyboardLayout_KeyNumLock }, - { Key.Clear, LocaleKeys.KeyboardLayout_KeyClear }, - { Key.Keypad0, LocaleKeys.KeyboardLayout_KeyKeypad0 }, - { Key.Keypad1, LocaleKeys.KeyboardLayout_KeyKeypad1 }, - { Key.Keypad2, LocaleKeys.KeyboardLayout_KeyKeypad2 }, - { Key.Keypad3, LocaleKeys.KeyboardLayout_KeyKeypad3 }, - { Key.Keypad4, LocaleKeys.KeyboardLayout_KeyKeypad4 }, - { Key.Keypad5, LocaleKeys.KeyboardLayout_KeyKeypad5 }, - { Key.Keypad6, LocaleKeys.KeyboardLayout_KeyKeypad6 }, - { Key.Keypad7, LocaleKeys.KeyboardLayout_KeyKeypad7 }, - { Key.Keypad8, LocaleKeys.KeyboardLayout_KeyKeypad8 }, - { Key.Keypad9, LocaleKeys.KeyboardLayout_KeyKeypad9 }, - { Key.KeypadDivide, LocaleKeys.KeyboardLayout_KeyKeypadDivide }, - { Key.KeypadMultiply, LocaleKeys.KeyboardLayout_KeyKeypadMultiply }, - { Key.KeypadSubtract, LocaleKeys.KeyboardLayout_KeyKeypadSubtract }, - { Key.KeypadAdd, LocaleKeys.KeyboardLayout_KeyKeypadAdd }, - { Key.KeypadDecimal, LocaleKeys.KeyboardLayout_KeyKeypadDecimal }, - { Key.KeypadEnter, LocaleKeys.KeyboardLayout_KeyKeypadEnter }, - { Key.Number0, LocaleKeys.KeyboardLayout_KeyNumber0 }, - { Key.Number1, LocaleKeys.KeyboardLayout_KeyNumber1 }, - { Key.Number2, LocaleKeys.KeyboardLayout_KeyNumber2 }, - { Key.Number3, LocaleKeys.KeyboardLayout_KeyNumber3 }, - { Key.Number4, LocaleKeys.KeyboardLayout_KeyNumber4 }, - { Key.Number5, LocaleKeys.KeyboardLayout_KeyNumber5 }, - { Key.Number6, LocaleKeys.KeyboardLayout_KeyNumber6 }, - { Key.Number7, LocaleKeys.KeyboardLayout_KeyNumber7 }, - { Key.Number8, LocaleKeys.KeyboardLayout_KeyNumber8 }, - { Key.Number9, LocaleKeys.KeyboardLayout_KeyNumber9 }, - { Key.Tilde, LocaleKeys.KeyboardLayout_KeyTilde }, - { Key.Grave, LocaleKeys.KeyboardLayout_KeyGrave }, - { Key.Minus, LocaleKeys.KeyboardLayout_KeyMinus }, - { Key.Plus, LocaleKeys.KeyboardLayout_KeyPlus }, - { Key.BracketLeft, LocaleKeys.KeyboardLayout_KeyBracketLeft }, - { Key.BracketRight, LocaleKeys.KeyboardLayout_KeyBracketRight }, - { Key.Semicolon, LocaleKeys.KeyboardLayout_KeySemicolon }, - { Key.Quote, LocaleKeys.KeyboardLayout_KeyQuote }, - { Key.Comma, LocaleKeys.KeyboardLayout_KeyComma }, - { Key.Period, LocaleKeys.KeyboardLayout_KeyPeriod }, - { Key.Slash, LocaleKeys.KeyboardLayout_KeySlash }, - { Key.BackSlash, LocaleKeys.KeyboardLayout_KeyBackSlash }, - { Key.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound }, - }; - private static readonly Dictionary _gamepadInputIdMap = new() { { GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick }, @@ -123,28 +51,13 @@ namespace Ryujinx.Ava.UI.Helpers public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string keyString = string.Empty; - LocaleKeys localeKey; switch (value) { case Key key: - if (_keysMap.TryGetValue(key, out localeKey)) + if (KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel)) { - if (OperatingSystem.IsMacOS()) - { - localeKey = localeKey switch - { - LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft, - LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight, - LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft, - LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight, - LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft, - LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight, - _ => localeKey - }; - } - - keyString = LocaleManager.Instance[localeKey]; + keyString = localizedKeyLabel; } else { @@ -156,6 +69,7 @@ namespace Ryujinx.Ava.UI.Helpers keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey); break; case GamepadInputId gamepadInputId: + LocaleKeys localeKey; if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) { keyString = LocaleManager.Instance[localeKey]; diff --git a/src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs b/src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs new file mode 100644 index 000000000..17f6a8b56 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs @@ -0,0 +1,142 @@ +using Ryujinx.Ava.Common.Locale; +using System; +using System.Collections.Generic; +using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; +using InputKey = Ryujinx.Input.Key; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal static class KeyboardLayoutLocaleHelper + { + private static readonly Dictionary _sharedLocalizedKeysMap = new() + { + [InputKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown, + [InputKey.ShiftLeft] = LocaleKeys.KeyboardLayout_KeyShiftLeft, + [InputKey.ShiftRight] = LocaleKeys.KeyboardLayout_KeyShiftRight, + [InputKey.ControlLeft] = LocaleKeys.KeyboardLayout_KeyControlLeft, + [InputKey.ControlRight] = LocaleKeys.KeyboardLayout_KeyControlRight, + [InputKey.AltLeft] = LocaleKeys.KeyboardLayout_KeyAltLeft, + [InputKey.AltRight] = LocaleKeys.KeyboardLayout_KeyAltRight, + [InputKey.WinLeft] = LocaleKeys.KeyboardLayout_KeyWinLeft, + [InputKey.WinRight] = LocaleKeys.KeyboardLayout_KeyWinRight, + [InputKey.Up] = LocaleKeys.KeyboardLayout_KeyUp, + [InputKey.Down] = LocaleKeys.KeyboardLayout_KeyDown, + [InputKey.Left] = LocaleKeys.KeyboardLayout_KeyLeft, + [InputKey.Right] = LocaleKeys.KeyboardLayout_KeyRight, + [InputKey.Enter] = LocaleKeys.KeyboardLayout_KeyEnter, + [InputKey.Escape] = LocaleKeys.KeyboardLayout_KeyEscape, + [InputKey.Space] = LocaleKeys.KeyboardLayout_KeySpace, + [InputKey.Tab] = LocaleKeys.KeyboardLayout_KeyTab, + [InputKey.BackSpace] = LocaleKeys.KeyboardLayout_KeyBackSpace, + [InputKey.Insert] = LocaleKeys.KeyboardLayout_KeyInsert, + [InputKey.Delete] = LocaleKeys.KeyboardLayout_KeyDelete, + [InputKey.PageUp] = LocaleKeys.KeyboardLayout_KeyPageUp, + [InputKey.PageDown] = LocaleKeys.KeyboardLayout_KeyPageDown, + [InputKey.Home] = LocaleKeys.KeyboardLayout_KeyHome, + [InputKey.End] = LocaleKeys.KeyboardLayout_KeyEnd, + [InputKey.CapsLock] = LocaleKeys.KeyboardLayout_KeyCapsLock, + [InputKey.ScrollLock] = LocaleKeys.KeyboardLayout_KeyScrollLock, + [InputKey.PrintScreen] = LocaleKeys.KeyboardLayout_KeyPrintScreen, + [InputKey.Pause] = LocaleKeys.KeyboardLayout_KeyPause, + [InputKey.NumLock] = LocaleKeys.KeyboardLayout_KeyNumLock, + [InputKey.Clear] = LocaleKeys.KeyboardLayout_KeyClear, + [InputKey.Keypad0] = LocaleKeys.KeyboardLayout_KeyKeypad0, + [InputKey.Keypad1] = LocaleKeys.KeyboardLayout_KeyKeypad1, + [InputKey.Keypad2] = LocaleKeys.KeyboardLayout_KeyKeypad2, + [InputKey.Keypad3] = LocaleKeys.KeyboardLayout_KeyKeypad3, + [InputKey.Keypad4] = LocaleKeys.KeyboardLayout_KeyKeypad4, + [InputKey.Keypad5] = LocaleKeys.KeyboardLayout_KeyKeypad5, + [InputKey.Keypad6] = LocaleKeys.KeyboardLayout_KeyKeypad6, + [InputKey.Keypad7] = LocaleKeys.KeyboardLayout_KeyKeypad7, + [InputKey.Keypad8] = LocaleKeys.KeyboardLayout_KeyKeypad8, + [InputKey.Keypad9] = LocaleKeys.KeyboardLayout_KeyKeypad9, + [InputKey.KeypadDivide] = LocaleKeys.KeyboardLayout_KeyKeypadDivide, + [InputKey.KeypadMultiply] = LocaleKeys.KeyboardLayout_KeyKeypadMultiply, + [InputKey.KeypadSubtract] = LocaleKeys.KeyboardLayout_KeyKeypadSubtract, + [InputKey.KeypadAdd] = LocaleKeys.KeyboardLayout_KeyKeypadAdd, + [InputKey.KeypadDecimal] = LocaleKeys.KeyboardLayout_KeyKeypadDecimal, + [InputKey.KeypadEnter] = LocaleKeys.KeyboardLayout_KeyKeypadEnter, + [InputKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound, + }; + + private static readonly Dictionary _semanticPrintableKeysMap = new() + { + [InputKey.Number0] = LocaleKeys.KeyboardLayout_KeyNumber0, + [InputKey.Number1] = LocaleKeys.KeyboardLayout_KeyNumber1, + [InputKey.Number2] = LocaleKeys.KeyboardLayout_KeyNumber2, + [InputKey.Number3] = LocaleKeys.KeyboardLayout_KeyNumber3, + [InputKey.Number4] = LocaleKeys.KeyboardLayout_KeyNumber4, + [InputKey.Number5] = LocaleKeys.KeyboardLayout_KeyNumber5, + [InputKey.Number6] = LocaleKeys.KeyboardLayout_KeyNumber6, + [InputKey.Number7] = LocaleKeys.KeyboardLayout_KeyNumber7, + [InputKey.Number8] = LocaleKeys.KeyboardLayout_KeyNumber8, + [InputKey.Number9] = LocaleKeys.KeyboardLayout_KeyNumber9, + [InputKey.Tilde] = LocaleKeys.KeyboardLayout_KeyTilde, + [InputKey.Grave] = LocaleKeys.KeyboardLayout_KeyGrave, + [InputKey.Minus] = LocaleKeys.KeyboardLayout_KeyMinus, + [InputKey.Plus] = LocaleKeys.KeyboardLayout_KeyPlus, + [InputKey.BracketLeft] = LocaleKeys.KeyboardLayout_KeyBracketLeft, + [InputKey.BracketRight] = LocaleKeys.KeyboardLayout_KeyBracketRight, + [InputKey.Semicolon] = LocaleKeys.KeyboardLayout_KeySemicolon, + [InputKey.Quote] = LocaleKeys.KeyboardLayout_KeyQuote, + [InputKey.Comma] = LocaleKeys.KeyboardLayout_KeyComma, + [InputKey.Period] = LocaleKeys.KeyboardLayout_KeyPeriod, + [InputKey.Slash] = LocaleKeys.KeyboardLayout_KeySlash, + [InputKey.BackSlash] = LocaleKeys.KeyboardLayout_KeyBackSlash, + }; + + public static bool TryGetSemanticLabel(InputKey key, out string label) + { + if (TryGetSemanticLocaleKey(key, out LocaleKeys localeKey)) + { + label = GetLocalizedString(localeKey); + return true; + } + + label = string.Empty; + return false; + } + + public static bool TryGetPhysicalLabel(ConfigPhysicalKey key, out string label) + { + if (TryGetPhysicalLocaleKey(key, out LocaleKeys localeKey)) + { + label = GetLocalizedString(localeKey); + return true; + } + + label = string.Empty; + return false; + } + + public static bool TryGetPhysicalLocaleKey(ConfigPhysicalKey key, out LocaleKeys localeKey) + { + return _sharedLocalizedKeysMap.TryGetValue((InputKey)(int)key, out localeKey); + } + + private static bool TryGetSemanticLocaleKey(InputKey key, out LocaleKeys localeKey) + { + return _sharedLocalizedKeysMap.TryGetValue(key, out localeKey) || + _semanticPrintableKeysMap.TryGetValue(key, out localeKey); + } + + private static string GetLocalizedString(LocaleKeys localeKey) + { + if (OperatingSystem.IsMacOS()) + { + localeKey = localeKey switch + { + LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft, + LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight, + LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft, + LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight, + LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft, + LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight, + _ => localeKey + }; + } + + return LocaleManager.Instance[localeKey]; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index a23bae4de..ed0d37451 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -4,7 +4,6 @@ using Ryujinx.Ava.Input; using Ryujinx.Common.Configuration.Hid; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using AvaPhysicalKey = Avalonia.Input.PhysicalKey; using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using InputKey = Ryujinx.Input.Key; @@ -16,62 +15,11 @@ namespace Ryujinx.Ava.UI.Helpers private static readonly ConcurrentDictionary _observedLayoutLabels = new(); public static event Action LabelsChanged; - private static readonly Dictionary _localizedKeysMap = new() - { - [ConfigPhysicalKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown, - [ConfigPhysicalKey.ShiftLeft] = LocaleKeys.KeyboardLayout_KeyShiftLeft, - [ConfigPhysicalKey.ShiftRight] = LocaleKeys.KeyboardLayout_KeyShiftRight, - [ConfigPhysicalKey.ControlLeft] = LocaleKeys.KeyboardLayout_KeyControlLeft, - [ConfigPhysicalKey.ControlRight] = LocaleKeys.KeyboardLayout_KeyControlRight, - [ConfigPhysicalKey.AltLeft] = LocaleKeys.KeyboardLayout_KeyAltLeft, - [ConfigPhysicalKey.AltRight] = LocaleKeys.KeyboardLayout_KeyAltRight, - [ConfigPhysicalKey.WinLeft] = LocaleKeys.KeyboardLayout_KeyWinLeft, - [ConfigPhysicalKey.WinRight] = LocaleKeys.KeyboardLayout_KeyWinRight, - [ConfigPhysicalKey.Up] = LocaleKeys.KeyboardLayout_KeyUp, - [ConfigPhysicalKey.Down] = LocaleKeys.KeyboardLayout_KeyDown, - [ConfigPhysicalKey.Left] = LocaleKeys.KeyboardLayout_KeyLeft, - [ConfigPhysicalKey.Right] = LocaleKeys.KeyboardLayout_KeyRight, - [ConfigPhysicalKey.Enter] = LocaleKeys.KeyboardLayout_KeyEnter, - [ConfigPhysicalKey.Escape] = LocaleKeys.KeyboardLayout_KeyEscape, - [ConfigPhysicalKey.Space] = LocaleKeys.KeyboardLayout_KeySpace, - [ConfigPhysicalKey.Tab] = LocaleKeys.KeyboardLayout_KeyTab, - [ConfigPhysicalKey.BackSpace] = LocaleKeys.KeyboardLayout_KeyBackSpace, - [ConfigPhysicalKey.Insert] = LocaleKeys.KeyboardLayout_KeyInsert, - [ConfigPhysicalKey.Delete] = LocaleKeys.KeyboardLayout_KeyDelete, - [ConfigPhysicalKey.PageUp] = LocaleKeys.KeyboardLayout_KeyPageUp, - [ConfigPhysicalKey.PageDown] = LocaleKeys.KeyboardLayout_KeyPageDown, - [ConfigPhysicalKey.Home] = LocaleKeys.KeyboardLayout_KeyHome, - [ConfigPhysicalKey.End] = LocaleKeys.KeyboardLayout_KeyEnd, - [ConfigPhysicalKey.CapsLock] = LocaleKeys.KeyboardLayout_KeyCapsLock, - [ConfigPhysicalKey.ScrollLock] = LocaleKeys.KeyboardLayout_KeyScrollLock, - [ConfigPhysicalKey.PrintScreen] = LocaleKeys.KeyboardLayout_KeyPrintScreen, - [ConfigPhysicalKey.Pause] = LocaleKeys.KeyboardLayout_KeyPause, - [ConfigPhysicalKey.NumLock] = LocaleKeys.KeyboardLayout_KeyNumLock, - [ConfigPhysicalKey.Clear] = LocaleKeys.KeyboardLayout_KeyClear, - [ConfigPhysicalKey.Keypad0] = LocaleKeys.KeyboardLayout_KeyKeypad0, - [ConfigPhysicalKey.Keypad1] = LocaleKeys.KeyboardLayout_KeyKeypad1, - [ConfigPhysicalKey.Keypad2] = LocaleKeys.KeyboardLayout_KeyKeypad2, - [ConfigPhysicalKey.Keypad3] = LocaleKeys.KeyboardLayout_KeyKeypad3, - [ConfigPhysicalKey.Keypad4] = LocaleKeys.KeyboardLayout_KeyKeypad4, - [ConfigPhysicalKey.Keypad5] = LocaleKeys.KeyboardLayout_KeyKeypad5, - [ConfigPhysicalKey.Keypad6] = LocaleKeys.KeyboardLayout_KeyKeypad6, - [ConfigPhysicalKey.Keypad7] = LocaleKeys.KeyboardLayout_KeyKeypad7, - [ConfigPhysicalKey.Keypad8] = LocaleKeys.KeyboardLayout_KeyKeypad8, - [ConfigPhysicalKey.Keypad9] = LocaleKeys.KeyboardLayout_KeyKeypad9, - [ConfigPhysicalKey.KeypadDivide] = LocaleKeys.KeyboardLayout_KeyKeypadDivide, - [ConfigPhysicalKey.KeypadMultiply] = LocaleKeys.KeyboardLayout_KeyKeypadMultiply, - [ConfigPhysicalKey.KeypadSubtract] = LocaleKeys.KeyboardLayout_KeyKeypadSubtract, - [ConfigPhysicalKey.KeypadAdd] = LocaleKeys.KeyboardLayout_KeyKeypadAdd, - [ConfigPhysicalKey.KeypadDecimal] = LocaleKeys.KeyboardLayout_KeyKeypadDecimal, - [ConfigPhysicalKey.KeypadEnter] = LocaleKeys.KeyboardLayout_KeyKeypadEnter, - [ConfigPhysicalKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound, - }; - public static string GetDisplayString(ConfigPhysicalKey key) { - if (_localizedKeysMap.TryGetValue(key, out LocaleKeys localeKey)) + if (KeyboardLayoutLocaleHelper.TryGetPhysicalLabel(key, out string localizedLabel)) { - return GetLocalizedString(localeKey); + return localizedLabel; } if (_observedLayoutLabels.TryGetValue(key, out string observedLabel)) @@ -95,7 +43,8 @@ namespace Ryujinx.Ava.UI.Helpers } InputKey inputKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); - if (!TryConvertToConfigPhysicalKey(inputKey, out ConfigPhysicalKey physicalKey) || _localizedKeysMap.ContainsKey(physicalKey)) + if (!TryConvertToConfigPhysicalKey(inputKey, out ConfigPhysicalKey physicalKey) || + KeyboardLayoutLocaleHelper.TryGetPhysicalLocaleKey(physicalKey, out _)) { return; } @@ -169,24 +118,5 @@ namespace Ryujinx.Ava.UI.Helpers physicalKey = ConfigPhysicalKey.Unknown; return false; } - - private static string GetLocalizedString(LocaleKeys localeKey) - { - if (OperatingSystem.IsMacOS()) - { - localeKey = localeKey switch - { - LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft, - LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight, - LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft, - LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight, - LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft, - LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight, - _ => localeKey - }; - } - - return LocaleManager.Instance[localeKey]; - } } } diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs index 391feffaf..50a16f443 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs @@ -16,9 +16,7 @@ namespace Ryujinx.Ava.UI.Views.Input public InputView() { - ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig); - - InitializeComponent(); + ReplaceViewModel(ConfigurationState.Instance.System.UseInputGlobalConfig); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -36,9 +34,18 @@ namespace Ryujinx.Ava.UI.Views.Input public void ToggleLocalGlobalInput(bool enableConfigGlobal) { Dispose(); - ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs + ReplaceViewModel(enableConfigGlobal); + } + + private void ReplaceViewModel(bool useGlobalConfig) + { + ViewModel = new InputViewModel(this, useGlobalConfig); // Create new Input Page with the selected input config scope. InitializeComponent(); - ViewModel.RetargetKeyboardDriver(this); + + if (VisualRoot is not null) + { + ViewModel.RetargetKeyboardDriver(this); + } } private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) From 9aeb0c8c8c0f860114958d7a4459ef643f8bae3e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 22 Mar 2026 01:05:17 +0100 Subject: [PATCH 36/55] Fallback to keyboard on controller disconnect --- src/Ryujinx.Input/HLE/NpadManager.cs | 101 ++++++++++++++++-- .../UI/ViewModels/Input/InputViewModel.cs | 39 ++++++- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index f2936aa72..73a64f313 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -36,6 +36,7 @@ namespace Ryujinx.Input.HLE private bool _isDisposed; private List _inputConfig; + private List _requestedInputConfig; private bool _enableKeyboard; private bool _enableMouse; private Switch _device; @@ -52,6 +53,7 @@ namespace Ryujinx.Input.HLE _gamepadDriver = gamepadDriver; _mouseDriver = mouseDriver; _inputConfig = []; + _requestedInputConfig = []; _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; @@ -89,14 +91,14 @@ namespace Ryujinx.Input.HLE } } - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); } } private void HandleOnGamepadConnected(string id) { // Force input reload - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -127,11 +129,13 @@ namespace Ryujinx.Input.HLE { lock (_lock) { + _requestedInputConfig = inputConfig?.ToList() ?? []; + NpadController[] oldControllers = _controllers.ToArray(); List validInputs = []; - foreach (InputConfig inputConfigEntry in inputConfig) + foreach (InputConfig inputConfigEntry in _requestedInputConfig) { NpadController controller; int index = (int)inputConfigEntry.PlayerIndex; @@ -147,7 +151,17 @@ namespace Ryujinx.Input.HLE controller = new(_cemuHookClient); } - bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); + InputConfig activeConfig = inputConfigEntry; + bool isValid = DriverConfigurationUpdate(ref controller, activeConfig); + + if (!isValid && + enableKeyboard && + inputConfigEntry is StandardControllerInputConfig && + TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig)) + { + activeConfig = fallbackConfig; + isValid = DriverConfigurationUpdate(ref controller, activeConfig); + } if (!isValid) { @@ -157,7 +171,7 @@ namespace Ryujinx.Input.HLE else { _controllers[index] = controller; - validInputs.Add(inputConfigEntry); + validInputs.Add(activeConfig); } } @@ -169,7 +183,7 @@ namespace Ryujinx.Input.HLE oldControllers[i] = null; } - _inputConfig = inputConfig; + _inputConfig = validInputs; _enableKeyboard = enableKeyboard; _enableMouse = enableMouse; @@ -177,6 +191,79 @@ namespace Ryujinx.Input.HLE } } + private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig) + { + fallbackConfig = null; + + ReadOnlySpan keyboardIds = _keyboardDriver.GamepadsIds; + + if (keyboardIds.IsEmpty) + { + return false; + } + + string keyboardId = keyboardIds[0]; + + using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId); + + if (keyboard == null) + { + return false; + } + + fallbackConfig = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = keyboardId, + Name = keyboard.Name, + PlayerIndex = inputConfig.PlayerIndex, + ControllerType = inputConfig.ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, + }, + }; + + return true; + } + public void UnblockInputUpdates() { lock (_lock) @@ -334,7 +421,7 @@ namespace Ryujinx.Input.HLE } } - internal InputConfig GetPlayerInputConfigByIndex(int index) + public InputConfig GetPlayerInputConfigByIndex(int index) { lock (_lock) { diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index fcfc94aa6..7892cac08 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -348,15 +348,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private void LoadConfiguration(InputConfig inputConfig = null) { + InputConfig persistedConfig; + if (UseGlobalConfig && Program.UseExtraConfig) { - Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); + persistedConfig = ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); } else { - Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); + persistedConfig = ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); } + Config = inputConfig ?? GetDisplayedInputConfig(persistedConfig); + if (Config is StandardKeyboardInputConfig keyboardInputConfig) { ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick); @@ -368,6 +372,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } + private InputConfig GetDisplayedInputConfig(InputConfig persistedConfig) + { + if (persistedConfig is not StandardControllerInputConfig) + { + return persistedConfig; + } + + InputConfig activeConfig = _mainWindow?.ViewModel.AppHost?.NpadManager?.GetPlayerInputConfigByIndex((int)_playerId); + + return activeConfig is StandardKeyboardInputConfig ? activeConfig : persistedConfig; + } + private void FindPairedDeviceInConfigFile() { // This function allows you to output a message about the device configuration found in the file @@ -475,11 +491,24 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _isChangeTrackingActive = false; // Disable configuration change tracking + bool shouldApplyKeyboardFallback = Config is StandardControllerInputConfig controllerConfig && controllerConfig.Id == id; + LoadDevices(); - IsModified = true; - RevertChanges(); - FindPairedDeviceInConfigFile(); + if (shouldApplyKeyboardFallback) + { + LoadConfiguration(); + LoadDevice(); + NotificationIsVisible = false; + IsModified = false; + NotifyChanges(); + } + else + { + IsModified = true; + RevertChanges(); + FindPairedDeviceInConfigFile(); + } _isChangeTrackingActive = true; // Enable configuration change tracking From 5809661664861d8ef96c2f0449595f01055ba587 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 22 Mar 2026 12:43:52 +0100 Subject: [PATCH 37/55] fix for caps lock --- .../UI/Helpers/PhysicalKeyLabelHelper.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index ed0d37451..b3661a439 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -51,6 +51,11 @@ namespace Ryujinx.Ava.UI.Helpers if (TryNormalizeObservedPrintableLabel(args.KeySymbol, out string label)) { + if (IsCapsLockOn() && !char.IsLetter(label[0])) + { + return; + } + if (_observedLayoutLabels.TryGetValue(physicalKey, out string existingLabel) && existingLabel == label) { return; @@ -92,6 +97,18 @@ namespace Ryujinx.Ava.UI.Helpers return true; } + private static bool IsCapsLockOn() + { + try + { + return OperatingSystem.IsWindows() && Console.CapsLock; + } + catch + { + return false; + } + } + private static bool TryNormalizeObservedPrintableLabel(string keySymbol, out string label) { if (string.IsNullOrEmpty(keySymbol) || keySymbol.Length != 1 || char.IsControl(keySymbol[0])) From ca015200f17cb0e094b61acfff7bf79f35f3e458 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 22 Mar 2026 17:29:55 +0100 Subject: [PATCH 38/55] Persist observed physical key labels --- .../UI/Helpers/PhysicalKeyLabelHelper.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs index b3661a439..3dd2c6f07 100644 --- a/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs +++ b/src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs @@ -1,9 +1,13 @@ using Avalonia.Input; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; +using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; using AvaPhysicalKey = Avalonia.Input.PhysicalKey; using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using InputKey = Ryujinx.Input.Key; @@ -12,11 +16,22 @@ namespace Ryujinx.Ava.UI.Helpers { internal static class PhysicalKeyLabelHelper { + private const string ObservedLabelsFileName = "keyboard_layout_labels.json"; private static readonly ConcurrentDictionary _observedLayoutLabels = new(); + private static readonly object _observedLayoutLabelsLock = new(); + private static readonly JsonSerializerOptions _serializerOptions = new() + { + WriteIndented = true, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + private static bool _observedLayoutLabelsLoaded; public static event Action LabelsChanged; public static string GetDisplayString(ConfigPhysicalKey key) { + EnsureObservedLayoutLabelsLoaded(); + if (KeyboardLayoutLocaleHelper.TryGetPhysicalLabel(key, out string localizedLabel)) { return localizedLabel; @@ -37,6 +52,8 @@ namespace Ryujinx.Ava.UI.Helpers public static void ObserveKeyPress(object sender, KeyEventArgs args) { + EnsureObservedLayoutLabelsLoaded(); + if (args.KeyModifiers != KeyModifiers.None) { return; @@ -62,10 +79,80 @@ namespace Ryujinx.Ava.UI.Helpers } _observedLayoutLabels[physicalKey] = label; + SaveObservedLayoutLabels(); LabelsChanged?.Invoke(); } } + private static void EnsureObservedLayoutLabelsLoaded() + { + if (_observedLayoutLabelsLoaded) + { + return; + } + + lock (_observedLayoutLabelsLock) + { + if (_observedLayoutLabelsLoaded) + { + return; + } + + try + { + string labelsPath = GetObservedLabelsPath(); + + if (File.Exists(labelsPath)) + { + Dictionary labels = JsonSerializer.Deserialize>(File.ReadAllText(labelsPath), _serializerOptions); + + if (labels != null) + { + foreach ((string key, string value) in labels) + { + if (Enum.TryParse(key, out ConfigPhysicalKey physicalKey) && + !string.IsNullOrEmpty(value)) + { + _observedLayoutLabels[physicalKey] = value; + } + } + } + } + } + catch + { + } + + _observedLayoutLabelsLoaded = true; + } + } + + private static void SaveObservedLayoutLabels() + { + lock (_observedLayoutLabelsLock) + { + try + { + Dictionary labels = []; + + foreach ((ConfigPhysicalKey key, string value) in _observedLayoutLabels) + { + labels[key.ToString()] = value; + } + + File.WriteAllText(GetObservedLabelsPath(), JsonSerializer.Serialize(labels, _serializerOptions)); + } + catch + { + } + } + } + + private static string GetObservedLabelsPath() + { + return Path.Combine(AppDataManager.BaseDirPath, ObservedLabelsFileName); + } + private static bool TryGetFallbackPrintableKeyLabel(ConfigPhysicalKey key, out string label) { // The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label. From c8367335d4d75baae5146cca4bce099892045811 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 22 Mar 2026 18:15:12 +0100 Subject: [PATCH 39/55] Fix input settings device selection state --- .../UI/Helpers/InputDeviceNameConverter.cs | 28 +++ .../UI/ViewModels/Input/InputViewModel.cs | 236 +++++++++++------- src/Ryujinx/UI/Views/Input/InputView.axaml | 11 +- 3 files changed, 186 insertions(+), 89 deletions(-) create mode 100644 src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs diff --git a/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs b/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs new file mode 100644 index 000000000..44abad47c --- /dev/null +++ b/src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs @@ -0,0 +1,28 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.UI.Models; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class InputDeviceNameConverter : MarkupExtension, IValueConverter + { + public static readonly InputDeviceNameConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is ValueTuple device ? device.Item3 : string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 7892cac08..bec7b8131 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -91,7 +91,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } internal ObservableCollection Controllers { get; set; } public AvaloniaList ProfilesList { get; set; } - public AvaloniaList DeviceList { get; set; } public bool UseGlobalConfig; @@ -101,7 +100,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool IsKeyboard => !IsController; public bool IsRight { get; set; } public bool IsLeft { get; set; } - public string RevertDeviceId { get; set; } public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); @@ -165,7 +163,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input LoadDevice(); LoadProfiles(); - RevertDeviceId = Devices[Device].Id; _isLoaded = true; _isChangeTrackingActive = true; OnPropertyChanged(); @@ -177,52 +174,58 @@ namespace Ryujinx.Ava.UI.ViewModels.Input get => _controller; set { + int controllerIndex = value < 0 ? 0 : value; + + if (controllerIndex == _controller) + { + return; + } + MarkAsChanged(); - - _controller = value; - - if (_controller == -1) - { - _controller = 0; - } - - if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) - { - ControllerType controller = Controllers[_controller].Type; - - IsLeft = true; - IsRight = true; - - switch (controller) - { - case ControllerType.Handheld: - ControllerImage = JoyConPairResource; - break; - case ControllerType.ProController: - ControllerImage = ProControllerResource; - break; - case ControllerType.JoyconPair: - ControllerImage = JoyConPairResource; - break; - case ControllerType.JoyconLeft: - ControllerImage = JoyConLeftResource; - IsRight = false; - break; - case ControllerType.JoyconRight: - ControllerImage = JoyConRightResource; - IsLeft = false; - break; - } - - LoadInputDriver(); - LoadProfiles(); - } - - OnPropertyChanged(); - NotifyChanges(); + ApplyControllerSelection(controllerIndex); } } + private void ApplyControllerSelection(int controllerIndex) + { + _controller = controllerIndex; + + if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) + { + ControllerType controller = Controllers[_controller].Type; + + IsLeft = true; + IsRight = true; + + switch (controller) + { + case ControllerType.Handheld: + ControllerImage = JoyConPairResource; + break; + case ControllerType.ProController: + ControllerImage = ProControllerResource; + break; + case ControllerType.JoyconPair: + ControllerImage = JoyConPairResource; + break; + case ControllerType.JoyconLeft: + ControllerImage = JoyConLeftResource; + IsRight = false; + break; + case ControllerType.JoyconRight: + ControllerImage = JoyConRightResource; + IsLeft = false; + break; + } + + LoadInputDriver(); + LoadProfiles(); + } + + OnPropertyChanged(nameof(Controller)); + NotifyChanges(); + } + public string ControllerImage { get => _controllerImage; @@ -257,15 +260,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input get => _device; set { - MarkAsChanged(); - - _device = value < 0 ? 0 : value; - - if (_device >= Devices.Count) + if (value < 0 || value >= Devices.Count) { return; } + MarkAsChanged(); + _device = value; + DeviceType selected = Devices[_device].Type; if (selected != DeviceType.None) @@ -280,10 +282,39 @@ namespace Ryujinx.Ava.UI.ViewModels.Input FindPairedDeviceInConfigFile(); OnPropertyChanged(); + OnPropertyChanged(nameof(SelectedDeviceItem)); NotifyChanges(); } } + public object SelectedDeviceItem + { + get => _device >= 0 && _device < Devices.Count ? Devices[_device] : null; + set + { + if (value is not ValueTuple selectedDevice) + { + return; + } + + int deviceIndex = Devices.ToList().FindIndex(device => + device.Type == selectedDevice.Item1 && + device.Id == selectedDevice.Item2); + + if (deviceIndex < 0) + { + return; + } + + if (deviceIndex == _device) + { + return; + } + + Device = deviceIndex; + } + } + public InputConfig Config { get; set; } public InputViewModel(UserControl owner, bool useGlobal = false) : this() @@ -328,7 +359,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input Controllers = []; Devices = []; ProfilesList = []; - DeviceList = []; VisualStick = new StickVisualizer(this); ControllerImage = ProControllerResource; @@ -409,7 +439,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input //If tracking is active, then allow changing the modifier if (!IsModified && _isChangeTrackingActive) { - RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes IsModified = true; } } @@ -424,34 +453,54 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public void LoadDevice() { + int deviceIndex = 0; + if (Config == null || Config.Backend == InputBackendType.Invalid) { - Device = 0; + ApplyLoadedDevice(deviceIndex); + return; } - else + + DeviceType type = DeviceType.None; + + if (Config is StandardKeyboardInputConfig) { - DeviceType type = DeviceType.None; - - if (Config is StandardKeyboardInputConfig) - { - type = DeviceType.Keyboard; - } - - if (Config is StandardControllerInputConfig) - { - type = DeviceType.Controller; - } - - (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); - if (item != default) - { - Device = Devices.ToList().FindIndex(x => x.Id == item.Id); - } - else - { - Device = 0; - } + type = DeviceType.Keyboard; } + + if (Config is StandardControllerInputConfig) + { + type = DeviceType.Controller; + } + + (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); + + if (item != default) + { + deviceIndex = Devices.ToList().FindIndex(x => x.Id == item.Id); + } + + ApplyLoadedDevice(deviceIndex); + } + + private void ApplyLoadedDevice(int deviceIndex) + { + _device = deviceIndex is >= 0 and < int.MaxValue ? deviceIndex : 0; + + if (_device >= Devices.Count) + { + _device = 0; + } + + if (_device > 0 && Devices[_device].Type != DeviceType.None) + { + LoadControllers(); + } + + FindPairedDeviceInConfigFile(); + OnPropertyChanged(nameof(Device)); + OnPropertyChanged(nameof(SelectedDeviceItem)); + NotifyChanges(); } private void LoadInputDriver() @@ -569,7 +618,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); - Controller = 0; + ApplyControllerSelection(0); } else { @@ -587,14 +636,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input // Workaround: set the box to 1 and then 0 if (controllerIndex == 0) { - Controller = 1; + ApplyControllerSelection(1); } - Controller = controllerIndex; + ApplyControllerSelection(controllerIndex); } else { - Controller = 0; + ApplyControllerSelection(0); } } } @@ -622,6 +671,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public void LoadDevices() { + int selectedDeviceIndex = 0; + (DeviceType Type, string Id, string Name) selectedDevice = default; + + if (_device >= 0 && _device < Devices.Count) + { + selectedDevice = Devices[_device]; + } + string GetGamepadName(IGamepad gamepad, int controllerNumber) { return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})"; @@ -642,7 +699,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input lock (Devices) { Devices.Clear(); - DeviceList.Clear(); Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); @@ -668,9 +724,20 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } - DeviceList.AddRange(Devices.Select(x => x.Name)); - Device = Math.Min(Device, DeviceList.Count - 1); + if (selectedDevice != default) + { + selectedDeviceIndex = Devices.ToList().FindIndex(device => + device.Type == selectedDevice.Type && + device.Id == selectedDevice.Id); + } + + if (selectedDeviceIndex < 0) + { + selectedDeviceIndex = Math.Clamp(_device, 0, Devices.Count - 1); + } } + + ApplyLoadedDevice(selectedDeviceIndex); } private string GetProfileBasePath() @@ -1024,9 +1091,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public void RevertChanges() { - LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad - Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId); - _isLoaded = false; LoadConfiguration(); LoadDevice(); @@ -1046,8 +1110,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input IsModified = false; - RevertDeviceId = Devices[Device].Id; // Remember selected device after saving - List newConfig = []; if (UseGlobalConfig && Program.UseExtraConfig) diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml b/src/Ryujinx/UI/Views/Input/InputView.axaml index e8e19ab6c..b7ae4fc08 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" + xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" @@ -161,8 +162,14 @@ Name="DeviceBox" HorizontalAlignment="Stretch" VerticalAlignment="Center" - ItemsSource="{Binding DeviceList}" - SelectedIndex="{Binding Device}" /> + ItemsSource="{Binding Devices}" + SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}"> + + + + + + + Date: Wed, 8 Apr 2026 17:59:16 +0200 Subject: [PATCH 53/55] Gate keyboard event logs behind the Avalonia UI log setting --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 215729bab..c98090f39 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Input; using System; @@ -239,9 +240,12 @@ namespace Ryujinx.Ava.Input _observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey; } - Logger.Trace?.Print( - LogClass.UI, - $"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}"); + if (ConfigurationState.Instance.Logger.EnableAvaloniaLog) + { + Logger.Info?.Print( + LogClass.UI, + $"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}"); + } } private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey, out PhysicalKeySource source) From 031cd90048063e4f6a23de3eb9ca50a3eea8e66b Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 8 Apr 2026 18:19:19 +0200 Subject: [PATCH 54/55] Log keyboard UI events only on key state changes --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index c98090f39..61d233992 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -209,6 +209,8 @@ namespace Ryujinx.Ava.Input ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey, out PhysicalKeySource physicalKeySource); bool semanticWasPressed = _semanticPressedKeys.Contains(resolvedSemanticKey); bool physicalWasPressed = _physicalPressedKeys.Contains(physicalKey); + bool semanticStateChanged = resolvedSemanticKey is not Key.Unknown and not Key.Unbound && semanticWasPressed != isPressed; + bool physicalStateChanged = physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound && physicalWasPressed != isPressed; bool bufferedSemanticPress = false; bool bufferedPhysicalPress = false; @@ -240,7 +242,8 @@ namespace Ryujinx.Ava.Input _observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey; } - if (ConfigurationState.Instance.Logger.EnableAvaloniaLog) + if (ConfigurationState.Instance.Logger.EnableAvaloniaLog && + (semanticStateChanged || physicalStateChanged)) { Logger.Info?.Print( LogClass.UI, From 0a59f8d154fe2cfea70f4f61228c55aeb6b8577e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Wed, 8 Apr 2026 19:45:47 +0200 Subject: [PATCH 55/55] Clear stuck keyboard input when windows lose focus --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 12 ++++++++++++ src/Ryujinx/Systems/AppHost.cs | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 61d233992..56c633181 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -24,6 +24,7 @@ namespace Ryujinx.Ava.Input private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; + private readonly Window _window; private readonly HashSet _semanticPressedKeys; private readonly HashSet _physicalPressedKeys; private readonly Dictionary _observedPhysicalKeysBySemanticKey; @@ -42,6 +43,7 @@ namespace Ryujinx.Ava.Input public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic) { _control = control; + _window = control as Window ?? TopLevel.GetTopLevel(control) as Window; _semanticPressedKeys = []; _physicalPressedKeys = []; _observedPhysicalKeysBySemanticKey = []; @@ -56,6 +58,12 @@ namespace Ryujinx.Ava.Input _control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true); _control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true); _control.TextInput += Control_TextInput; + _window?.Deactivated += Window_Deactivated; + } + + private void Window_Deactivated(object sender, EventArgs e) + { + Clear(); } private void Control_TextInput(object sender, TextInputEventArgs e) @@ -99,6 +107,10 @@ namespace Ryujinx.Ava.Input _control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress); _control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease); _control.TextInput -= Control_TextInput; + if (_window != null) + { + _window.Deactivated -= Window_Deactivated; + } } } diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs index 4b1e9cdb5..329a3ce55 100644 --- a/src/Ryujinx/Systems/AppHost.cs +++ b/src/Ryujinx/Systems/AppHost.cs @@ -1234,9 +1234,17 @@ namespace Ryujinx.Ava.Systems return false; } + bool hasModalFocusLoss = _viewModel.Window is MainWindow mainWindow && + mainWindow.SettingsWindow?.IsActive == true; + + if (!_viewModel.IsActive || hasModalFocusLoss) + { + _inputManager.KeyboardDriver.Clear(); + } + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - if (_viewModel.IsActive) + if (_viewModel.IsActive && !hasModalFocusLoss) { bool isCursorVisible = true; @@ -1369,7 +1377,7 @@ namespace Ryujinx.Ava.Systems // Touchscreen. bool hasTouch = false; - if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value) + if (_viewModel.IsActive && !hasModalFocusLoss && !ConfigurationState.Instance.Hid.EnableMouse.Value) { hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); }