forked from Mirrors/RGSX
correction du mappage des controles
This commit is contained in:
18
README.md
18
README.md
@@ -27,7 +27,7 @@ RGSX est une application Python basée sur Pygame.
|
|||||||
- Manette (optionnelle, mais recommandée pour une expérience optimale) ou Clavier.
|
- Manette (optionnelle, mais recommandée pour une expérience optimale) ou Clavier.
|
||||||
|
|
||||||
### Espace disque
|
### Espace disque
|
||||||
- Espace suffisant dans `/userdata/roms/ports/RGSX` pour stocker les ROMs, images et fichiers de configuration.
|
- Espace suffisant pour stocker les ROMs, images et fichiers de configuration.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ Entrez la commande :
|
|||||||
|
|
||||||
Patientez et regardez le retour à l'écran ou sur la commande (à améliorer).
|
Patientez et regardez le retour à l'écran ou sur la commande (à améliorer).
|
||||||
Mettez à jour la liste des jeux via : `Menu > Paramètres de jeux > Mettre à jour la liste des jeux `.
|
Mettez à jour la liste des jeux via : `Menu > Paramètres de jeux > Mettre à jour la liste des jeux `.
|
||||||
Vous trouverez RGSX dans le système "PORTS" ou "Jeux Amateurs et portages" et dans `/userdata/roms/ports/RGSX`
|
Vous trouverez RGSX dans le système "PORTS" ou "Jeux Amateurs et portages" et dans `/roms/ports/RGSX`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Méthode 2 : Copie manuelle
|
### Méthode 2 : Copie manuelle
|
||||||
|
|
||||||
- Téléchargez le contenu du dépôt en zip : https://github.com/RetroGameSets/RGSX/archive/refs/heads/main.zip
|
- Téléchargez le contenu du dépôt en zip : https://github.com/RetroGameSets/RGSX/archive/refs/heads/main.zip
|
||||||
- Extrayez le tout dans `/userdata/roms/ports/RGSX` (le dossier RGSX devra être créé manuellement). Attention de bien respecter la structure indiquée plus bas.
|
- Extrayez le tout dans `roms/ports/RGSX` (le dossier RGSX devra être créé manuellement). Attention de bien respecter la structure indiquée plus bas.
|
||||||
- Mettez à jour la liste des jeux via le menu :
|
- Mettez à jour la liste des jeux via le menu :
|
||||||
`Paramètres de jeux > Mettre à jour la liste`.
|
`Paramètres de jeux > Mettre à jour la liste`.
|
||||||
|
|
||||||
@@ -59,13 +59,13 @@ Vous trouverez RGSX dans le système "PORTS" ou "Jeux Amateurs et portages" et d
|
|||||||
---
|
---
|
||||||
> ## IMPORTANT
|
> ## IMPORTANT
|
||||||
> Si vous avez une clé API 1Fichier, vous devez la renseigner dans
|
> Si vous avez une clé API 1Fichier, vous devez la renseigner dans
|
||||||
> `/userdata/saves/ports/RGSX/1FichierAPI.txt`
|
> `/saves/ports/RGSX/1FichierAPI.txt`
|
||||||
> si vous souhaitez télécharger depuis des liens 1Fichier.
|
> si vous souhaitez télécharger depuis des liens 1Fichier.
|
||||||
---
|
---
|
||||||
|
|
||||||
- Lancez RGSX depuis ports.
|
- Lancez RGSX depuis ports.
|
||||||
- Configurez les contrôles. Ils pourront être reconfigurés via le menu pause par la suite si erreur.
|
- Configurez les contrôles. Ils pourront être reconfigurés via le menu pause par la suite si erreur.
|
||||||
- Supprimez le fichier `/userdata/saves/ports/rgsx/controls.json` en cas de problème puis relancez l'application.
|
- Supprimez le fichier `/saves/ports/rgsx/controls.json` en cas de problème puis relancez l'application.
|
||||||
- L'application téléchargera toutes les données nécessaires automatiquement ensuite.
|
- L'application téléchargera toutes les données nécessaires automatiquement ensuite.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -106,13 +106,13 @@ Vous trouverez RGSX dans le système "PORTS" ou "Jeux Amateurs et portages" et d
|
|||||||
|
|
||||||
### Logs
|
### Logs
|
||||||
|
|
||||||
Les logs sont enregistrés dans `/userdata/roms/ports/RGSX/logs/RGSX.log` pour diagnostiquer les problèmes.
|
Les logs sont enregistrés dans `/roms/ports/RGSX/logs/RGSX.log` pour diagnostiquer les problèmes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Structure du projet
|
## 📁 Structure du projet
|
||||||
```
|
```
|
||||||
/userdata/roms/ports/
|
/roms/ports/
|
||||||
RGSX-INSTALL.log # LOG d'installation uniquement
|
RGSX-INSTALL.log # LOG d'installation uniquement
|
||||||
RGSX/
|
RGSX/
|
||||||
│
|
│
|
||||||
@@ -127,7 +127,7 @@ RGSX/
|
|||||||
└── logs/
|
└── logs/
|
||||||
└── RGSX.log # Fichier de logs.
|
└── RGSX.log # Fichier de logs.
|
||||||
|
|
||||||
/userdata/saves/ports/
|
/saves/ports/
|
||||||
RGSX/
|
RGSX/
|
||||||
│
|
│
|
||||||
├── controls.json # Fichier de mappage des contrôles (généré après le 1er demarrage)
|
├── controls.json # Fichier de mappage des contrôles (généré après le 1er demarrage)
|
||||||
@@ -145,7 +145,7 @@ RGSX/
|
|||||||
|
|
||||||
### Signaler un bug
|
### Signaler un bug
|
||||||
|
|
||||||
1. Consultez les logs dans `/userdata/roms/ports/RGSX/logs/RGSX.log`.
|
1. Consultez les logs dans `/roms/ports/RGSX/logs/RGSX.log`.
|
||||||
2. Ouvrez une issue sur GitHub avec une description détaillée et les logs pertinents.
|
2. Ouvrez une issue sur GitHub avec une description détaillée et les logs pertinents.
|
||||||
|
|
||||||
### Proposer une fonctionnalité
|
### Proposer une fonctionnalité
|
||||||
|
|||||||
10
__main__.py
10
__main__.py
@@ -18,12 +18,10 @@ import config
|
|||||||
from config import OTA_data_ZIP
|
from config import OTA_data_ZIP
|
||||||
|
|
||||||
# Configuration du logging
|
# Configuration du logging
|
||||||
log_dir = os.path.join(config.APP_FOLDER, "logs")
|
|
||||||
log_file = os.path.join(log_dir, "RGSX.log")
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(config.log_dir, exist_ok=True)
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=log_file,
|
filename=config.log_file,
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
@@ -32,7 +30,7 @@ except Exception as e:
|
|||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logging.error(f"Échec de la configuration du logging dans {log_file}: {str(e)}")
|
logging.error(f"Échec de la configuration du logging dans {config.log_file}: {str(e)}")
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -310,7 +308,7 @@ async def main():
|
|||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "error"
|
config.menu_state = "error"
|
||||||
config.error_message = (
|
config.error_message = (
|
||||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
|
f"Attention il faut renseigner sa clé API (premium only) dans le fichier {os.path.join(config.SAVE_FOLDER, '1fichierAPI.txt')}"
|
||||||
)
|
)
|
||||||
# Mettre à jour l'entrée temporaire avec l'erreur
|
# Mettre à jour l'entrée temporaire avec l'erreur
|
||||||
config.history[-1]["status"] = "Erreur"
|
config.history[-1]["status"] = "Erreur"
|
||||||
|
|||||||
32
config.py
32
config.py
@@ -2,8 +2,6 @@ import pygame # type: ignore
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Version actuelle de l'application
|
# Version actuelle de l'application
|
||||||
app_version = "1.9.7.2"
|
app_version = "1.9.7.2"
|
||||||
|
|
||||||
@@ -11,21 +9,30 @@ app_version = "1.9.7.2"
|
|||||||
current_language = "fr"
|
current_language = "fr"
|
||||||
|
|
||||||
|
|
||||||
# URL
|
|
||||||
OTA_SERVER_URL = "https://retrogamesets.fr/softs"
|
|
||||||
OTA_VERSION_ENDPOINT = f"{OTA_SERVER_URL}/version.json"
|
|
||||||
OTA_UPDATE_ZIP = f"{OTA_SERVER_URL}/RGSX.zip"
|
|
||||||
OTA_data_ZIP = f"{OTA_SERVER_URL}/rgsx-data.zip"
|
|
||||||
|
|
||||||
# Chemins de base
|
# Chemins de base
|
||||||
APP_FOLDER = "/userdata/roms/ports/RGSX"
|
SYSTEM_FOLDER = "/userdata"
|
||||||
SAVE_FOLDER = "/userdata/saves/ports/rgsx"
|
ROMS_FOLDER = os.path.join(SYSTEM_FOLDER, "roms")
|
||||||
UPDATE_FOLDER = f"{APP_FOLDER}/update"
|
APP_FOLDER = os.path.join(ROMS_FOLDER, "ports", "RGSX")
|
||||||
|
UPDATE_FOLDER = os.path.join(APP_FOLDER, "update")
|
||||||
|
SAVE_FOLDER = os.path.join(SYSTEM_FOLDER, "saves", "ports", "rgsx")
|
||||||
|
IMAGES_FOLDER = os.path.join(APP_FOLDER, "images", "systemes")
|
||||||
|
GAMES_FOLDER = os.path.join(APP_FOLDER, "games")
|
||||||
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
|
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
|
||||||
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
|
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
|
||||||
LANGUAGE_CONFIG_PATH = os.path.join(SAVE_FOLDER, "language.json")
|
LANGUAGE_CONFIG_PATH = os.path.join(SAVE_FOLDER, "language.json")
|
||||||
JSON_EXTENSIONS = os.path.join(APP_FOLDER, "rom_extensions.json")
|
JSON_EXTENSIONS = os.path.join(APP_FOLDER, "rom_extensions.json")
|
||||||
|
|
||||||
|
# Configuration du logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
log_dir = os.path.join(APP_FOLDER, "logs")
|
||||||
|
log_file = os.path.join(log_dir, "RGSX.log")
|
||||||
|
|
||||||
|
# URL
|
||||||
|
OTA_SERVER_URL = "https://retrogamesets.fr/softs"
|
||||||
|
OTA_VERSION_ENDPOINT = os.path.join(OTA_SERVER_URL, "version.json")
|
||||||
|
OTA_UPDATE_ZIP = os.path.join(OTA_SERVER_URL, "RGSX.zip")
|
||||||
|
OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "rgsx-data.zip")
|
||||||
|
|
||||||
|
|
||||||
# Constantes pour la répétition automatique dans pause_menu
|
# Constantes pour la répétition automatique dans pause_menu
|
||||||
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
|
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
|
||||||
@@ -50,6 +57,7 @@ transition_state = "idle"
|
|||||||
transition_progress = 0.0
|
transition_progress = 0.0
|
||||||
transition_duration = 18
|
transition_duration = 18
|
||||||
games_count = {}
|
games_count = {}
|
||||||
|
API_KEY_1FICHIER = "" # Initialisation de la variable globale pour la clé API
|
||||||
|
|
||||||
# Variables pour la sélection de langue
|
# Variables pour la sélection de langue
|
||||||
selected_language_index = 0
|
selected_language_index = 0
|
||||||
@@ -149,4 +157,4 @@ def validate_resolution():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
API_KEY_1FICHIER = "" # Initialisation de la variable globale pour la clé API
|
|
||||||
|
|||||||
75
controls.py
75
controls.py
@@ -45,52 +45,35 @@ def validate_menu_state(state):
|
|||||||
|
|
||||||
def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
||||||
"""Charge la configuration des contrôles depuis un fichier JSON."""
|
"""Charge la configuration des contrôles depuis un fichier JSON."""
|
||||||
|
default_config = {
|
||||||
|
"confirm": {"type": "key", "key": pygame.K_RETURN},
|
||||||
|
"cancel": {"type": "key", "key": pygame.K_ESCAPE},
|
||||||
|
"left": {"type": "key", "key": pygame.K_LEFT},
|
||||||
|
"right": {"type": "key", "key": pygame.K_RIGHT},
|
||||||
|
"up": {"type": "key", "key": pygame.K_UP},
|
||||||
|
"down": {"type": "key", "key": pygame.K_DOWN},
|
||||||
|
"start": {"type": "key", "key": pygame.K_p},
|
||||||
|
"progress": {"type": "key", "key": pygame.K_x},
|
||||||
|
"history": {"type": "key", "key": pygame.K_h},
|
||||||
|
"page_up": {"type": "key", "key": pygame.K_PAGEUP},
|
||||||
|
"page_down": {"type": "key", "key": pygame.K_PAGEDOWN},
|
||||||
|
"filter": {"type": "key", "key": pygame.K_f},
|
||||||
|
"delete": {"type": "key", "key": pygame.K_BACKSPACE},
|
||||||
|
"space": {"type": "key", "key": pygame.K_SPACE}
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
config_data = json.load(f)
|
config_data = json.load(f)
|
||||||
# Vérifier les actions nécessaires
|
# Vérifier et compléter les actions manquantes
|
||||||
required_actions = ["confirm", "cancel", "up", "down"]
|
for action, default_mapping in default_config.items():
|
||||||
for action in required_actions:
|
|
||||||
if action not in config_data:
|
if action not in config_data:
|
||||||
logger.warning(f"Action {action} manquante dans {path}, utilisation de la valeur par défaut")
|
logger.warning(f"Action {action} manquante dans {path}, utilisation de la valeur par défaut")
|
||||||
config_data[action] = {
|
config_data[action] = default_mapping
|
||||||
"type": "key",
|
|
||||||
"value": {
|
|
||||||
"confirm": {"type": "key", "value": pygame.K_RETURN},
|
|
||||||
"cancel": {"type": "key", "value": pygame.K_ESCAPE},
|
|
||||||
"left": {"type": "key", "value": pygame.K_LEFT},
|
|
||||||
"right": {"type": "key", "value": pygame.K_RIGHT},
|
|
||||||
"up": {"type": "key", "value": pygame.K_UP},
|
|
||||||
"down": {"type": "key", "value": pygame.K_DOWN},
|
|
||||||
"start": {"type": "key", "value": pygame.K_p},
|
|
||||||
"progress": {"type": "key", "value": pygame.K_x},
|
|
||||||
"history": {"type": "key", "value": pygame.K_h},
|
|
||||||
"page_up": {"type": "key", "value": pygame.K_PAGEUP},
|
|
||||||
"page_down": {"type": "key", "value": pygame.K_PAGEDOWN},
|
|
||||||
"filter": {"type": "key", "value": pygame.K_f},
|
|
||||||
"delete": {"type": "key", "value": pygame.K_BACKSPACE},
|
|
||||||
"space": {"type": "key", "value": pygame.K_SPACE}
|
|
||||||
}[action]
|
|
||||||
}
|
|
||||||
return config_data
|
return config_data
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
logger.error(f"Erreur lors de la lecture de {path} : {e}, utilisation de la configuration par défaut")
|
logger.error(f"Erreur lors de la lecture de {path} : {e}, utilisation de la configuration par défaut")
|
||||||
return {
|
return default_config
|
||||||
"confirm": {"type": "key", "value": pygame.K_RETURN},
|
|
||||||
"cancel": {"type": "key", "value": pygame.K_ESCAPE},
|
|
||||||
"left": {"type": "key", "value": pygame.K_LEFT},
|
|
||||||
"right": {"type": "key", "value": pygame.K_RIGHT},
|
|
||||||
"up": {"type": "key", "value": pygame.K_UP},
|
|
||||||
"down": {"type": "key", "value": pygame.K_DOWN},
|
|
||||||
"start": {"type": "key", "value": pygame.K_p},
|
|
||||||
"progress": {"type": "key", "value": pygame.K_x},
|
|
||||||
"history": {"type": "key", "value": pygame.K_h},
|
|
||||||
"page_up": {"type": "key", "value": pygame.K_PAGEUP},
|
|
||||||
"page_down": {"type": "key", "value": pygame.K_PAGEDOWN},
|
|
||||||
"filter": {"type": "key", "value": pygame.K_f},
|
|
||||||
"delete": {"type": "key", "value": pygame.K_BACKSPACE},
|
|
||||||
"space": {"type": "key", "value": pygame.K_SPACE}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fonction pour vérifier si un événement correspond à une action
|
# Fonction pour vérifier si un événement correspond à une action
|
||||||
def is_input_matched(event, action_name):
|
def is_input_matched(event, action_name):
|
||||||
@@ -623,7 +606,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.menu_state = "error"
|
config.menu_state = "error"
|
||||||
config.error_message = _(
|
config.error_message = _(
|
||||||
"error_api_key"
|
"error_api_key"
|
||||||
).format("/userdata/saves/ports/rgsx/1fichierAPI.txt")
|
).format(os.join(config.SAVE_FOLDER,"1fichierAPI.txt"))
|
||||||
config.history[-1]["status"] = "Erreur"
|
config.history[-1]["status"] = "Erreur"
|
||||||
config.history[-1]["progress"] = 0
|
config.history[-1]["progress"] = 0
|
||||||
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
||||||
@@ -728,9 +711,9 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
if not config.API_KEY_1FICHIER:
|
if not config.API_KEY_1FICHIER:
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "error"
|
config.menu_state = "error"
|
||||||
config.error_message = (
|
logger.warning("clé api absente dans os.path.join(config.SAVE_FOLDER, '1fichierAPI.txt')\n")
|
||||||
"Attention il faut renseigner sa clé API (premium only) dans le fichier /userdata/saves/ports/rgsx/1fichierAPI.txt"
|
config.error_message = _("error_api_key").format(os.path.join(config.SAVE_FOLDER, "1fichierAPI.txt"))
|
||||||
)
|
|
||||||
config.history[-1]["status"] = "Erreur"
|
config.history[-1]["status"] = "Erreur"
|
||||||
config.history[-1]["progress"] = 0
|
config.history[-1]["progress"] = 0
|
||||||
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
||||||
@@ -930,11 +913,11 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
try:
|
try:
|
||||||
os.remove(config.APP_FOLDER + "/sources.json")
|
os.remove(config.APP_FOLDER + "/sources.json")
|
||||||
logger.debug("Fichier sources.json supprimé avec succès")
|
logger.debug("Fichier sources.json supprimé avec succès")
|
||||||
if os.path.exists(config.APP_FOLDER + "/games"):
|
if os.path.exists(config.GAMES_FOLDER):
|
||||||
shutil.rmtree(config.APP_FOLDER + "/games")
|
shutil.rmtree(config.GAMES_FOLDER)
|
||||||
logger.debug("Dossier games supprimé avec succès")
|
logger.debug("Dossier games supprimé avec succès")
|
||||||
if os.path.exists(config.APP_FOLDER + "/images"):
|
if os.path.exists(config.IMAGES_FOLDER):
|
||||||
shutil.rmtree(config.APP_FOLDER + "/images")
|
shutil.rmtree(config.IMAGES_FOLDER)
|
||||||
logger.debug("Dossier images supprimé avec succès")
|
logger.debug("Dossier images supprimé avec succès")
|
||||||
config.menu_state = "restart_popup"
|
config.menu_state = "restart_popup"
|
||||||
config.popup_message = _("popup_redownload_success")
|
config.popup_message = _("popup_redownload_success")
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import logging
|
|||||||
import config
|
import config
|
||||||
from config import CONTROLS_CONFIG_PATH
|
from config import CONTROLS_CONFIG_PATH
|
||||||
from display import draw_gradient
|
from display import draw_gradient
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Chemin du fichier de configuration des contrôles
|
# Chemin du fichier de configuration des contrôles
|
||||||
CONTROLS_CONFIG_PATH = "/userdata/saves/ports/rgsx/controls.json"
|
CONTROLS_CONFIG_PATH = os.path.join(config.SAVE_FOLDER, "controls.json")
|
||||||
|
|
||||||
# Actions internes de RGSX à mapper
|
# Actions internes de RGSX à mapper
|
||||||
ACTIONS = [
|
ACTIONS = [
|
||||||
@@ -36,11 +37,11 @@ SDL_TO_PYGAME_KEY = {
|
|||||||
1073741904: pygame.K_LEFT, # Flèche Gauche
|
1073741904: pygame.K_LEFT, # Flèche Gauche
|
||||||
1073741903: pygame.K_RIGHT, # Flèche Droite
|
1073741903: pygame.K_RIGHT, # Flèche Droite
|
||||||
1073742050: pygame.K_LALT, # Alt gauche
|
1073742050: pygame.K_LALT, # Alt gauche
|
||||||
1073742051: pygame.K_RSHIFT, # Alt droit
|
1073742054: pygame.K_RALT, # Alt droit (AltGr)
|
||||||
1073742049: pygame.K_LCTRL, # Ctrl gauche
|
1073742049: pygame.K_LCTRL, # Ctrl gauche
|
||||||
1073742053: pygame.K_RCTRL, # Ctrl droit
|
1073742053: pygame.K_RCTRL, # Ctrl droit
|
||||||
1073742048: pygame.K_LSHIFT, # Shift gauche
|
1073742048: pygame.K_LSHIFT, # Shift gauche
|
||||||
1073742054: pygame.K_RALT, # Shift droit
|
1073742052: pygame.K_RSHIFT, # Shift droit
|
||||||
}
|
}
|
||||||
|
|
||||||
# Noms lisibles pour les touches clavier
|
# Noms lisibles pour les touches clavier
|
||||||
@@ -156,31 +157,100 @@ KEY_NAMES = {
|
|||||||
pygame.K_SLASH: "/",
|
pygame.K_SLASH: "/",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Noms lisibles pour les boutons de manette
|
def get_controller_button_names():
|
||||||
BUTTON_NAMES = {
|
"""Récupère les noms des boutons depuis es_input.cfg"""
|
||||||
0: "A",
|
es_input_path = "/usr/share/emulationstation/es_input.cfg"
|
||||||
1: "B",
|
button_names = {}
|
||||||
2: "X",
|
|
||||||
3: "Y",
|
if not os.path.exists(es_input_path):
|
||||||
4: "LB",
|
return {i: f"Bouton {i}" for i in range(16)}
|
||||||
5: "RB",
|
|
||||||
6: "LT",
|
try:
|
||||||
7: "RT",
|
tree = ET.parse(es_input_path)
|
||||||
8: "Select",
|
root = tree.getroot()
|
||||||
9: "Start",
|
|
||||||
}
|
# Mapping des noms ES vers des noms lisibles
|
||||||
|
es_button_names = {
|
||||||
|
"a": "A", "b": "B", "x": "X", "y": "Y",
|
||||||
|
"leftshoulder": "LB", "rightshoulder": "RB",
|
||||||
|
"lefttrigger": "LT", "righttrigger": "RT",
|
||||||
|
"select": "Select", "start": "Start",
|
||||||
|
"leftstick": "L3", "rightstick": "R3"
|
||||||
|
}
|
||||||
|
|
||||||
|
for inputConfig in root.findall("inputConfig"):
|
||||||
|
if inputConfig.get("type") == "joystick":
|
||||||
|
for input_tag in inputConfig.findall("input"):
|
||||||
|
if input_tag.get("type") == "button":
|
||||||
|
es_name = input_tag.get("name")
|
||||||
|
button_id = int(input_tag.get("id"))
|
||||||
|
readable_name = es_button_names.get(es_name, es_name.upper())
|
||||||
|
button_names[button_id] = readable_name
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur parsing es_input.cfg: {e}")
|
||||||
|
|
||||||
|
# Compléter avec des noms génériques
|
||||||
|
for i in range(16):
|
||||||
|
if i not in button_names:
|
||||||
|
button_names[i] = f"Bouton {i}"
|
||||||
|
|
||||||
|
return button_names
|
||||||
|
|
||||||
|
def get_controller_axis_names():
|
||||||
|
"""Récupère les noms des axes depuis es_input.cfg"""
|
||||||
|
es_input_path = "/usr/share/emulationstation/es_input.cfg"
|
||||||
|
axis_names = {}
|
||||||
|
|
||||||
|
if not os.path.exists(es_input_path):
|
||||||
|
return {(i, d): f"Axe {i}{'+' if d > 0 else '-'}" for i in range(8) for d in [-1, 1]}
|
||||||
|
|
||||||
|
try:
|
||||||
|
tree = ET.parse(es_input_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Mapping des noms ES vers des noms lisibles
|
||||||
|
es_axis_names = {
|
||||||
|
"leftx": "Joy G", "lefty": "Joy G",
|
||||||
|
"rightx": "Joy D", "righty": "Joy D",
|
||||||
|
"lefttrigger": "LT", "righttrigger": "RT"
|
||||||
|
}
|
||||||
|
|
||||||
|
for inputConfig in root.findall("inputConfig"):
|
||||||
|
if inputConfig.get("type") == "joystick":
|
||||||
|
for input_tag in inputConfig.findall("input"):
|
||||||
|
if input_tag.get("type") == "axis":
|
||||||
|
es_name = input_tag.get("name")
|
||||||
|
axis_id = int(input_tag.get("id"))
|
||||||
|
value = int(input_tag.get("value", "1"))
|
||||||
|
direction = 1 if value > 0 else -1
|
||||||
|
|
||||||
|
if es_name in es_axis_names:
|
||||||
|
base_name = es_axis_names[es_name]
|
||||||
|
if "Joy" in base_name:
|
||||||
|
if "leftx" in es_name or "rightx" in es_name:
|
||||||
|
axis_names[(axis_id, direction)] = f"{base_name} {'Droite' if direction > 0 else 'Gauche'}"
|
||||||
|
else:
|
||||||
|
axis_names[(axis_id, direction)] = f"{base_name} {'Bas' if direction > 0 else 'Haut'}"
|
||||||
|
else:
|
||||||
|
axis_names[(axis_id, direction)] = base_name
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur parsing es_input.cfg: {e}")
|
||||||
|
|
||||||
|
# Compléter avec des noms génériques
|
||||||
|
for i in range(8):
|
||||||
|
for d in [-1, 1]:
|
||||||
|
if (i, d) not in axis_names:
|
||||||
|
axis_names[(i, d)] = f"Axe {i}{'+' if d > 0 else '-'}"
|
||||||
|
|
||||||
|
return axis_names
|
||||||
|
|
||||||
|
# Charger les noms depuis es_input.cfg
|
||||||
|
BUTTON_NAMES = get_controller_button_names()
|
||||||
|
AXIS_NAMES = get_controller_axis_names()
|
||||||
|
|
||||||
|
|
||||||
# Noms pour les axes de joystick
|
|
||||||
AXIS_NAMES = {
|
|
||||||
(0, 1): "Joy G Haut",
|
|
||||||
(0, -1): "Joy G Bas",
|
|
||||||
(1, 1): "Joy G Gauche",
|
|
||||||
(1, -1): "Joy G Droite",
|
|
||||||
(2, 1): "Joy D Haut",
|
|
||||||
(2, -1): "Joy D Bas",
|
|
||||||
(3, 1): "Joy D Gauche",
|
|
||||||
(3, -1): "Joy D Droite",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Noms pour la croix directionnelle
|
# Noms pour la croix directionnelle
|
||||||
HAT_NAMES = {
|
HAT_NAMES = {
|
||||||
@@ -227,7 +297,7 @@ def load_controls_config():
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def save_controls_config(controls_config):
|
def save_controls_config(controls_config):
|
||||||
#Enregistre la configuration des contrôles dans controls.json
|
"""Enregistre la configuration des contrôles dans controls.json"""
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(CONTROLS_CONFIG_PATH), exist_ok=True)
|
os.makedirs(os.path.dirname(CONTROLS_CONFIG_PATH), exist_ok=True)
|
||||||
with open(CONTROLS_CONFIG_PATH, "w") as f:
|
with open(CONTROLS_CONFIG_PATH, "w") as f:
|
||||||
@@ -236,8 +306,41 @@ def save_controls_config(controls_config):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
|
logger.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
|
||||||
|
|
||||||
|
def validate_controls_config(controls_config):
|
||||||
|
"""Valide la structure de la configuration des contrôles"""
|
||||||
|
required_actions = ["confirm", "cancel", "up", "down", "left", "right"]
|
||||||
|
|
||||||
|
for action in required_actions:
|
||||||
|
if action not in controls_config:
|
||||||
|
logger.warning(f"Action {action} manquante dans la configuration")
|
||||||
|
return False
|
||||||
|
|
||||||
|
mapping = controls_config[action]
|
||||||
|
if "type" not in mapping:
|
||||||
|
logger.warning(f"Type manquant pour l'action {action}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
input_type = mapping["type"]
|
||||||
|
if input_type == "key" and "key" not in mapping:
|
||||||
|
logger.warning(f"Clé 'key' manquante pour l'action {action}")
|
||||||
|
return False
|
||||||
|
elif input_type == "button" and "button" not in mapping:
|
||||||
|
logger.warning(f"Clé 'button' manquante pour l'action {action}")
|
||||||
|
return False
|
||||||
|
elif input_type == "axis" and ("axis" not in mapping or "direction" not in mapping):
|
||||||
|
logger.warning(f"Clés 'axis' ou 'direction' manquantes pour l'action {action}")
|
||||||
|
return False
|
||||||
|
elif input_type == "hat" and "value" not in mapping:
|
||||||
|
logger.warning(f"Clé 'value' manquante pour l'action {action}")
|
||||||
|
return False
|
||||||
|
elif input_type == "mouse" and "button" not in mapping:
|
||||||
|
logger.warning(f"Clé 'button' manquante pour l'action {action}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def get_readable_input_name(event):
|
def get_readable_input_name(event):
|
||||||
#Retourne un nom lisible pour une entrée (touche, bouton, axe, hat, ou souris)
|
"""Retourne un nom lisible pour une entrée (touche, bouton, axe, hat, ou souris)"""
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
key_value = SDL_TO_PYGAME_KEY.get(event.key, event.key)
|
key_value = SDL_TO_PYGAME_KEY.get(event.key, event.key)
|
||||||
return KEY_NAMES.get(key_value, pygame.key.name(key_value) or f"Touche {key_value}")
|
return KEY_NAMES.get(key_value, pygame.key.name(key_value) or f"Touche {key_value}")
|
||||||
@@ -245,202 +348,208 @@ def get_readable_input_name(event):
|
|||||||
return BUTTON_NAMES.get(event.button, f"Bouton {event.button}")
|
return BUTTON_NAMES.get(event.button, f"Bouton {event.button}")
|
||||||
elif event.type == pygame.JOYAXISMOTION:
|
elif event.type == pygame.JOYAXISMOTION:
|
||||||
if abs(event.value) > 0.5: # Seuil pour détecter un mouvement significatif
|
if abs(event.value) > 0.5: # Seuil pour détecter un mouvement significatif
|
||||||
return AXIS_NAMES.get((event.axis, 1 if event.value > 0 else -1), f"Axe {event.axis}")
|
return AXIS_NAMES.get((event.axis, 1 if event.value > 0 else -1), f"Axe {event.axis} {'Positif' if event.value > 0 else 'Négatif'}")
|
||||||
elif event.type == pygame.JOYHATMOTION:
|
elif event.type == pygame.JOYHATMOTION:
|
||||||
return HAT_NAMES.get(event.value, f"D-Pad {event.value}")
|
if event.value != (0, 0): # Ignorer la position neutre
|
||||||
|
return HAT_NAMES.get(event.value, f"D-Pad {event.value}")
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
return MOUSE_BUTTON_NAMES.get(event.button, f"Souris Bouton {event.button}")
|
return MOUSE_BUTTON_NAMES.get(event.button, f"Souris Bouton {event.button}")
|
||||||
return "Inconnu"
|
return "Inconnu"
|
||||||
|
|
||||||
|
|
||||||
def map_controls(screen):
|
def map_controls(screen):
|
||||||
mapping = True
|
"""Interface de mappage des contrôles avec maintien de 3 secondes"""
|
||||||
current_action = 0
|
controls_config = load_controls_config()
|
||||||
clock = pygame.time.Clock()
|
current_action_index = 0
|
||||||
while mapping:
|
current_input = None
|
||||||
clock.tick(100) # 100 FPS
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
|
last_frame_time = pygame.time.get_ticks()
|
||||||
|
config.needs_redraw = True
|
||||||
|
last_joyhat_time = 0
|
||||||
|
|
||||||
|
# État des entrées maintenues
|
||||||
|
held_keys = set()
|
||||||
|
held_buttons = set()
|
||||||
|
held_axes = {}
|
||||||
|
held_hats = {}
|
||||||
|
held_mouse_buttons = set()
|
||||||
|
|
||||||
|
while current_action_index < len(ACTIONS):
|
||||||
|
if config.needs_redraw:
|
||||||
|
progress = min(input_held_time / HOLD_DURATION, 1.0) if current_input else 0.0
|
||||||
|
draw_controls_mapping(screen, ACTIONS[current_action_index], last_input_name, current_input is not None, progress)
|
||||||
|
pygame.display.flip()
|
||||||
|
config.needs_redraw = False
|
||||||
|
|
||||||
|
current_time = pygame.time.get_ticks()
|
||||||
|
delta_time = current_time - last_frame_time
|
||||||
|
last_frame_time = current_time
|
||||||
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
# Initialisation des variables de contrôle
|
if event.type == pygame.QUIT:
|
||||||
controls_config = load_controls_config()
|
return False
|
||||||
current_action_index = 0
|
|
||||||
current_input = None
|
# Gestion des relâchements
|
||||||
input_held_time = 0
|
if event.type == pygame.KEYUP and event.key in held_keys:
|
||||||
last_input_name = None
|
held_keys.remove(event.key)
|
||||||
last_frame_time = pygame.time.get_ticks()
|
if current_input and current_input["type"] == "key" and current_input["value"] == event.key:
|
||||||
config.needs_redraw = True
|
current_input = None
|
||||||
last_joyhat_time = 0 # Pour le débouncing des événements JOYHATMOTION
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
# Initialiser l'état des boutons et axes pour suivre les relâchements
|
config.needs_redraw = True
|
||||||
held_keys = set()
|
elif event.type == pygame.JOYBUTTONUP and event.button in held_buttons:
|
||||||
held_buttons = set()
|
held_buttons.remove(event.button)
|
||||||
held_axes = {} # {axis: direction}
|
if current_input and current_input["type"] == "button" and current_input["value"] == event.button:
|
||||||
held_hats = {} # {hat: value}
|
current_input = None
|
||||||
held_mouse_buttons = set()
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
while current_action_index < len(ACTIONS):
|
config.needs_redraw = True
|
||||||
if config.needs_redraw:
|
elif event.type == pygame.JOYAXISMOTION and abs(event.value) < 0.5:
|
||||||
progress = min(input_held_time / HOLD_DURATION, 1.0) if current_input else 0.0
|
if event.axis in held_axes:
|
||||||
draw_controls_mapping(screen, ACTIONS[current_action_index], last_input_name, current_input is not None, progress)
|
held_direction = held_axes[event.axis]
|
||||||
pygame.display.flip()
|
if current_input and current_input["type"] == "axis" and current_input["value"][0] == event.axis and current_input["value"][1] == held_direction:
|
||||||
config.needs_redraw = False
|
|
||||||
|
|
||||||
current_time = pygame.time.get_ticks()
|
|
||||||
delta_time = current_time - last_frame_time
|
|
||||||
last_frame_time = current_time
|
|
||||||
|
|
||||||
events = pygame.event.get()
|
|
||||||
for event in events:
|
|
||||||
if event.type == pygame.QUIT:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Détecter les relâchements pour réinitialiser
|
|
||||||
if event.type == pygame.KEYUP:
|
|
||||||
if event.key in held_keys:
|
|
||||||
held_keys.remove(event.key)
|
|
||||||
if current_input and current_input["type"] == "key" and current_input["value"] == event.key:
|
|
||||||
current_input = None
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = None
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"Touche relâchée: {event.key}")
|
|
||||||
elif event.type == pygame.JOYBUTTONUP:
|
|
||||||
if event.button in held_buttons:
|
|
||||||
held_buttons.remove(event.button)
|
|
||||||
if current_input and current_input["type"] == "button" and current_input["value"] == event.button:
|
|
||||||
current_input = None
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = None
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"Bouton relâché: {event.button}")
|
|
||||||
elif event.type == pygame.MOUSEBUTTONUP:
|
|
||||||
if event.button in held_mouse_buttons:
|
|
||||||
held_mouse_buttons.remove(event.button)
|
|
||||||
if current_input and current_input["type"] == "mouse" and current_input["value"] == event.button:
|
|
||||||
current_input = None
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = None
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"Bouton souris relâché: {event.button}")
|
|
||||||
elif event.type == pygame.JOYAXISMOTION:
|
|
||||||
if abs(event.value) < 0.5: # Axe revenu à la position neutre
|
|
||||||
if event.axis in held_axes:
|
|
||||||
del held_axes[event.axis]
|
|
||||||
if current_input and current_input["type"] == "axis" and current_input["value"][0] == event.axis:
|
|
||||||
current_input = None
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = None
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"Axe relâché: {event.axis}")
|
|
||||||
elif event.type == pygame.JOYHATMOTION:
|
|
||||||
logger.debug(f"JOYHATMOTION détecté: hat={event.hat}, value={event.value}")
|
|
||||||
if event.value == (0, 0): # D-Pad revenu à la position neutre
|
|
||||||
if event.hat in held_hats:
|
|
||||||
del held_hats[event.hat]
|
|
||||||
if current_input and current_input["type"] == "hat" and current_input["value"] == event.value:
|
|
||||||
current_input = None
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = None
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"D-Pad relâché: {event.hat}")
|
|
||||||
continue # Ignorer les événements (0, 0) pour la détection des nouvelles entrées
|
|
||||||
|
|
||||||
# Détecter les nouvelles entrées
|
|
||||||
if event.type in (pygame.KEYDOWN, pygame.JOYBUTTONDOWN, pygame.JOYAXISMOTION, pygame.JOYHATMOTION, pygame.MOUSEBUTTONDOWN):
|
|
||||||
# Appliquer le débouncing pour JOYHATMOTION
|
|
||||||
if event.type == pygame.JOYHATMOTION and (current_time - last_joyhat_time) < JOYHAT_DEBOUNCE:
|
|
||||||
logger.debug(f"Événement JOYHATMOTION ignoré (debounce): hat={event.hat}, value={event.value}")
|
|
||||||
continue
|
|
||||||
if event.type == pygame.JOYHATMOTION:
|
|
||||||
last_joyhat_time = current_time
|
|
||||||
|
|
||||||
|
|
||||||
input_name = get_readable_input_name(event)
|
|
||||||
if input_name != "Inconnu":
|
|
||||||
input_type = {
|
|
||||||
pygame.KEYDOWN: "key",
|
|
||||||
pygame.JOYBUTTONDOWN: "button",
|
|
||||||
pygame.JOYAXISMOTION: "axis",
|
|
||||||
pygame.JOYHATMOTION: "hat",
|
|
||||||
pygame.MOUSEBUTTONDOWN: "mouse",
|
|
||||||
}[event.type]
|
|
||||||
input_value = (
|
|
||||||
SDL_TO_PYGAME_KEY.get(event.key, event.key) if event.type == pygame.KEYDOWN else
|
|
||||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
|
||||||
(event.axis, 1 if event.value > 0 else -1) if event.type == pygame.JOYAXISMOTION and abs(event.value) > 0.5 else
|
|
||||||
event.value if event.type == pygame.JOYHATMOTION else
|
|
||||||
event.button
|
|
||||||
)
|
|
||||||
|
|
||||||
# Vérifier si l'entrée est nouvelle ou différente
|
|
||||||
if (current_input is None or
|
|
||||||
(input_type == "key" and current_input["value"] != input_value) or
|
|
||||||
(input_type == "button" and current_input["value"] != input_value) or
|
|
||||||
(input_type == "axis" and current_input["value"] != input_value) or
|
|
||||||
(input_type == "hat" and current_input["value"] != input_value) or
|
|
||||||
(input_type == "mouse" and current_input["value"] != input_value)):
|
|
||||||
current_input = {"type": input_type, "value": input_value}
|
|
||||||
input_held_time = 0
|
|
||||||
last_input_name = input_name
|
|
||||||
config.needs_redraw = True
|
|
||||||
logger.debug(f"Nouvelle entrée détectée: {input_type}:{input_value} ({input_name})")
|
|
||||||
|
|
||||||
# Mettre à jour les entrées maintenues
|
|
||||||
if input_type == "key":
|
|
||||||
held_keys.add(input_value)
|
|
||||||
elif input_type == "button":
|
|
||||||
held_buttons.add(input_value)
|
|
||||||
elif input_type == "axis":
|
|
||||||
held_axes[input_value[0]] = input_value[1]
|
|
||||||
elif input_type == "hat":
|
|
||||||
held_hats[event.hat] = input_value
|
|
||||||
elif input_type == "mouse":
|
|
||||||
held_mouse_buttons.add(input_value)
|
|
||||||
|
|
||||||
# Désactivation du passage avec Échap
|
|
||||||
# Aucun code ici pour empêcher de sauter les actions avec Échap
|
|
||||||
|
|
||||||
# Mettre à jour le temps de maintien
|
|
||||||
if current_input:
|
|
||||||
input_held_time += delta_time
|
|
||||||
if input_held_time >= HOLD_DURATION:
|
|
||||||
action_name = ACTIONS[current_action_index]["name"]
|
|
||||||
logger.debug(f"Entrée validée pour {action_name}: {current_input['type']}:{current_input['value']} ({last_input_name})")
|
|
||||||
controls_config[action_name] = {
|
|
||||||
"type": current_input["type"],
|
|
||||||
"value": current_input["value"],
|
|
||||||
"display": last_input_name
|
|
||||||
}
|
|
||||||
current_action_index += 1
|
|
||||||
current_input = None
|
current_input = None
|
||||||
input_held_time = 0
|
input_held_time = 0
|
||||||
last_input_name = None
|
last_input_name = None
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
# Réinitialiser les entrées maintenues pour éviter les interférences
|
del held_axes[event.axis]
|
||||||
held_keys.clear()
|
elif event.type == pygame.JOYHATMOTION and event.value == (0, 0):
|
||||||
held_buttons.clear()
|
if event.hat in held_hats:
|
||||||
held_axes.clear()
|
del held_hats[event.hat]
|
||||||
held_hats.clear()
|
if current_input and current_input["type"] == "hat":
|
||||||
held_mouse_buttons.clear()
|
current_input = None
|
||||||
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
|
config.needs_redraw = True
|
||||||
|
continue
|
||||||
|
elif event.type == pygame.MOUSEBUTTONUP and event.button in held_mouse_buttons:
|
||||||
|
held_mouse_buttons.remove(event.button)
|
||||||
|
if current_input and current_input["type"] == "mouse" and current_input["value"] == event.button:
|
||||||
|
current_input = None
|
||||||
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
|
|
||||||
pygame.time.wait(10)
|
# Détection des nouvelles entrées
|
||||||
|
if event.type in (pygame.KEYDOWN, pygame.JOYBUTTONDOWN, pygame.JOYAXISMOTION, pygame.JOYHATMOTION, pygame.MOUSEBUTTONDOWN):
|
||||||
save_controls_config(controls_config)
|
if event.type == pygame.JOYHATMOTION:
|
||||||
config.controls_config = controls_config
|
if (current_time - last_joyhat_time) < JOYHAT_DEBOUNCE:
|
||||||
return True
|
continue
|
||||||
pass
|
last_joyhat_time = current_time
|
||||||
|
|
||||||
def save_controls_config(config):
|
input_name = get_readable_input_name(event)
|
||||||
#Enregistre la configuration des contrôles dans un fichier JSON
|
if input_name == "Inconnu":
|
||||||
try:
|
continue
|
||||||
with open(CONTROLS_CONFIG_PATH, "w") as f:
|
|
||||||
json.dump(config, f, indent=4)
|
# Déterminer le type et la valeur
|
||||||
logger.debug("Configuration des contrôles enregistrée")
|
if event.type == pygame.KEYDOWN:
|
||||||
except Exception as e:
|
input_type = "key"
|
||||||
logger.error(f"Erreur lors de l'enregistrement de controls.json : {e}")
|
input_value = SDL_TO_PYGAME_KEY.get(event.key, event.key)
|
||||||
return False
|
elif event.type == pygame.JOYBUTTONDOWN:
|
||||||
|
input_type = "button"
|
||||||
|
input_value = event.button
|
||||||
|
elif event.type == pygame.JOYAXISMOTION and abs(event.value) > 0.5:
|
||||||
|
input_type = "axis"
|
||||||
|
direction = 1 if event.value > 0 else -1
|
||||||
|
input_value = (event.axis, direction)
|
||||||
|
# Ignorer si c'est juste un changement de direction du même axe
|
||||||
|
if event.axis in held_axes and held_axes[event.axis] != direction:
|
||||||
|
continue
|
||||||
|
elif event.type == pygame.JOYHATMOTION:
|
||||||
|
input_type = "hat"
|
||||||
|
input_value = event.value
|
||||||
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
input_type = "mouse"
|
||||||
|
input_value = event.button
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Nouvelle entrée détectée
|
||||||
|
if (current_input is None or
|
||||||
|
current_input["type"] != input_type or
|
||||||
|
current_input["value"] != input_value):
|
||||||
|
current_input = {"type": input_type, "value": input_value}
|
||||||
|
input_held_time = 0
|
||||||
|
last_input_name = input_name
|
||||||
|
config.needs_redraw = True
|
||||||
|
|
||||||
|
# Mettre à jour les entrées maintenues
|
||||||
|
if input_type == "key":
|
||||||
|
held_keys.add(input_value)
|
||||||
|
elif input_type == "button":
|
||||||
|
held_buttons.add(input_value)
|
||||||
|
elif input_type == "axis":
|
||||||
|
held_axes[input_value[0]] = input_value[1]
|
||||||
|
elif input_type == "hat":
|
||||||
|
held_hats[event.hat] = input_value
|
||||||
|
elif input_type == "mouse":
|
||||||
|
held_mouse_buttons.add(input_value)
|
||||||
|
|
||||||
|
# Mise à jour du temps de maintien
|
||||||
|
if current_input:
|
||||||
|
input_held_time += delta_time
|
||||||
|
if input_held_time >= HOLD_DURATION:
|
||||||
|
action_name = ACTIONS[current_action_index]["name"]
|
||||||
|
|
||||||
|
# Sauvegarder avec la structure attendue par controls.py
|
||||||
|
if current_input["type"] == "key":
|
||||||
|
controls_config[action_name] = {
|
||||||
|
"type": "key",
|
||||||
|
"key": current_input["value"],
|
||||||
|
"display": last_input_name
|
||||||
|
}
|
||||||
|
elif current_input["type"] == "button":
|
||||||
|
controls_config[action_name] = {
|
||||||
|
"type": "button",
|
||||||
|
"button": current_input["value"],
|
||||||
|
"display": last_input_name
|
||||||
|
}
|
||||||
|
elif current_input["type"] == "axis":
|
||||||
|
axis, direction = current_input["value"]
|
||||||
|
controls_config[action_name] = {
|
||||||
|
"type": "axis",
|
||||||
|
"axis": axis,
|
||||||
|
"direction": direction,
|
||||||
|
"display": last_input_name
|
||||||
|
}
|
||||||
|
elif current_input["type"] == "hat":
|
||||||
|
controls_config[action_name] = {
|
||||||
|
"type": "hat",
|
||||||
|
"value": current_input["value"],
|
||||||
|
"display": last_input_name
|
||||||
|
}
|
||||||
|
elif current_input["type"] == "mouse":
|
||||||
|
controls_config[action_name] = {
|
||||||
|
"type": "mouse",
|
||||||
|
"button": current_input["value"],
|
||||||
|
"display": last_input_name
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"Contrôle mappé: {action_name} -> {controls_config[action_name]}")
|
||||||
|
current_action_index += 1
|
||||||
|
current_input = None
|
||||||
|
input_held_time = 0
|
||||||
|
last_input_name = None
|
||||||
|
config.needs_redraw = True
|
||||||
|
|
||||||
|
# Réinitialiser les entrées maintenues
|
||||||
|
held_keys.clear()
|
||||||
|
held_buttons.clear()
|
||||||
|
held_axes.clear()
|
||||||
|
held_hats.clear()
|
||||||
|
held_mouse_buttons.clear()
|
||||||
|
|
||||||
|
config.needs_redraw = True
|
||||||
|
|
||||||
|
pygame.time.wait(10)
|
||||||
|
|
||||||
|
save_controls_config(controls_config)
|
||||||
|
config.controls_config = controls_config
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_progress):
|
def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_progress):
|
||||||
#Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien
|
#Affiche l'interface de mappage des contrôles avec une barre de progression pour le maintien
|
||||||
draw_gradient(screen, (28, 37, 38), (47, 59, 61))
|
draw_gradient(screen, (28, 37, 38), (47, 59, 61))
|
||||||
|
|||||||
@@ -680,10 +680,10 @@ def draw_history_list(screen):
|
|||||||
# logger.debug(f"Affichage terminé: {game_name}, status={status_text}")
|
# logger.debug(f"Affichage terminé: {game_name}, status={status_text}")
|
||||||
elif status == "Erreur":
|
elif status == "Erreur":
|
||||||
status_text = _("history_status_error").format(entry.get('message', 'Échec'))
|
status_text = _("history_status_error").format(entry.get('message', 'Échec'))
|
||||||
logger.debug(f"Affichage erreur: {game_name}, status={status_text}")
|
#logger.debug(f"Affichage erreur: {game_name}, status={status_text}")
|
||||||
else:
|
else:
|
||||||
status_text = status
|
status_text = status
|
||||||
logger.debug(f"Affichage statut inconnu: {game_name}, status={status_text}")
|
#logger.debug(f"Affichage statut inconnu: {game_name}, status={status_text}")
|
||||||
|
|
||||||
color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"]
|
color = THEME_COLORS["fond_lignes"] if i == config.current_history_item else THEME_COLORS["text"]
|
||||||
platform_text = truncate_text_end(platform, config.small_font, col_platform_width - 10)
|
platform_text = truncate_text_end(platform, config.small_font, col_platform_width - 10)
|
||||||
|
|||||||
30
network.py
30
network.py
@@ -176,7 +176,9 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
|||||||
dest_dir = None
|
dest_dir = None
|
||||||
for platform_dict in config.platform_dicts:
|
for platform_dict in config.platform_dicts:
|
||||||
if platform_dict["platform"] == platform:
|
if platform_dict["platform"] == platform:
|
||||||
dest_dir = platform_dict.get("folder")
|
dest_dir = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder", platform.lower().replace(" ", "")))
|
||||||
|
logger.debug(f"Répertoire de destination trouvé pour {platform}: {dest_dir}")
|
||||||
|
#dest_dir = platform_dict.get("folder")
|
||||||
break
|
break
|
||||||
if not dest_dir:
|
if not dest_dir:
|
||||||
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform.lower().replace(" ", ""))
|
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform.lower().replace(" ", ""))
|
||||||
@@ -204,15 +206,15 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
|||||||
session.headers.update(headers)
|
session.headers.update(headers)
|
||||||
|
|
||||||
# Première requête HEAD pour obtenir la vraie URL
|
# Première requête HEAD pour obtenir la vraie URL
|
||||||
logger.debug(f"Première requête HEAD vers {url}")
|
#logger.debug(f"Première requête HEAD vers {url}")
|
||||||
head_response = session.head(url, timeout=30, allow_redirects=False)
|
head_response = session.head(url, timeout=30, allow_redirects=False)
|
||||||
logger.debug(f"HEAD Status: {head_response.status_code}, Headers: {dict(head_response.headers)}")
|
#logger.debug(f"HEAD Status: {head_response.status_code}, Headers: {dict(head_response.headers)}")
|
||||||
|
|
||||||
# Suivre la redirection manuellement si nécessaire
|
# Suivre la redirection manuellement si nécessaire
|
||||||
final_url = url
|
final_url = url
|
||||||
if head_response.status_code in [301, 302, 303, 307, 308]:
|
if head_response.status_code in [301, 302, 303, 307, 308]:
|
||||||
final_url = head_response.headers.get('Location', url)
|
final_url = head_response.headers.get('Location', url)
|
||||||
logger.debug(f"Redirection détectée vers: {final_url}")
|
#logger.debug(f"Redirection détectée vers: {final_url}")
|
||||||
|
|
||||||
# Requête GET vers l'URL finale avec en-têtes spécifiques
|
# Requête GET vers l'URL finale avec en-têtes spécifiques
|
||||||
download_headers = headers.copy()
|
download_headers = headers.copy()
|
||||||
@@ -368,7 +370,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
|||||||
logger.error(f"Erreur mise à jour progression: {str(e)}")
|
logger.error(f"Erreur mise à jour progression: {str(e)}")
|
||||||
|
|
||||||
thread.join()
|
thread.join()
|
||||||
logger.debug(f"Thread joined for {url}, task_id={task_id}")
|
#logger.debug(f"Thread joined for {url}, task_id={task_id}")
|
||||||
return result[0], result[1]
|
return result[0], result[1]
|
||||||
|
|
||||||
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
|
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
|
||||||
@@ -391,7 +393,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
dest_dir = None
|
dest_dir = None
|
||||||
for platform_dict in config.platform_dicts:
|
for platform_dict in config.platform_dicts:
|
||||||
if platform_dict["platform"] == platform:
|
if platform_dict["platform"] == platform:
|
||||||
dest_dir = platform_dict.get("folder")
|
dest_dir = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder", platform.lower().replace(" ", "")))
|
||||||
break
|
break
|
||||||
if not dest_dir:
|
if not dest_dir:
|
||||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||||
@@ -411,9 +413,9 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
"pretty": 1
|
"pretty": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}")
|
#logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/file/info.cgi pour {url}")
|
||||||
response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30)
|
response = requests.post("https://api.1fichier.com/v1/file/info.cgi", headers=headers, json=payload, timeout=30)
|
||||||
logger.debug(f"Réponse reçue, status: {response.status_code}")
|
#logger.debug(f"Réponse reçue, status: {response.status_code}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
file_info = response.json()
|
file_info = response.json()
|
||||||
|
|
||||||
@@ -434,9 +436,9 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
dest_path = os.path.join(dest_dir, sanitized_filename)
|
dest_path = os.path.join(dest_dir, sanitized_filename)
|
||||||
logger.debug(f"Chemin destination: {dest_path}")
|
logger.debug(f"Chemin destination: {dest_path}")
|
||||||
|
|
||||||
logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}")
|
#logger.debug(f"Envoi requête POST à https://api.1fichier.com/v1/download/get_token.cgi pour {url}")
|
||||||
response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30)
|
response = requests.post("https://api.1fichier.com/v1/download/get_token.cgi", headers=headers, json=payload, timeout=30)
|
||||||
logger.debug(f"Réponse reçue, status: {response.status_code}")
|
#logger.debug(f"Réponse reçue, status: {response.status_code}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
download_info = response.json()
|
download_info = response.json()
|
||||||
|
|
||||||
@@ -452,12 +454,12 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
retry_delay = 10
|
retry_delay = 10
|
||||||
# Initialiser la progression avec task_id
|
# Initialiser la progression avec task_id
|
||||||
progress_queue.put((task_id, 0, 0)) # Taille initiale inconnue
|
progress_queue.put((task_id, 0, 0)) # Taille initiale inconnue
|
||||||
logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")
|
#logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")
|
||||||
for attempt in range(retries):
|
for attempt in range(retries):
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}")
|
#logger.debug(f"Tentative {attempt + 1} : Envoi requête GET à {final_url}")
|
||||||
with requests.get(final_url, stream=True, headers={'User-Agent': 'Mozilla/5.0'}, timeout=30) as response:
|
with requests.get(final_url, stream=True, headers={'User-Agent': 'Mozilla/5.0'}, timeout=30) as response:
|
||||||
logger.debug(f"Réponse reçue, status: {response.status_code}")
|
#logger.debug(f"Réponse reçue, status: {response.status_code}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
total_size = int(response.headers.get('content-length', 0))
|
total_size = int(response.headers.get('content-length', 0))
|
||||||
logger.debug(f"Taille totale: {total_size} octets")
|
logger.debug(f"Taille totale: {total_size} octets")
|
||||||
@@ -496,7 +498,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
entry["downloaded_size"] = downloaded
|
entry["downloaded_size"] = downloaded
|
||||||
entry["total_size"] = total_size
|
entry["total_size"] = total_size
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Progression mise à jour: {entry['progress']:.1f}% pour {game_name}")
|
#logger.debug(f"Progression mise à jour: {entry['progress']:.1f}% pour {game_name}")
|
||||||
break
|
break
|
||||||
progress_queue.put((task_id, downloaded, total_size))
|
progress_queue.put((task_id, downloaded, total_size))
|
||||||
last_update_time = current_time
|
last_update_time = current_time
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
4
utils.py
4
utils.py
@@ -86,7 +86,7 @@ def is_extension_supported(filename, platform, extensions_data):
|
|||||||
dest_dir = None
|
dest_dir = None
|
||||||
for platform_dict in config.platform_dicts:
|
for platform_dict in config.platform_dicts:
|
||||||
if platform_dict["platform"] == platform:
|
if platform_dict["platform"] == platform:
|
||||||
dest_dir = platform_dict.get("folder")
|
dest_dir = os.path.join(config.ROMS_FOLDER, platform_dict.get("folder", platform.lower().replace(" ", "")))
|
||||||
break
|
break
|
||||||
if not dest_dir:
|
if not dest_dir:
|
||||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
||||||
@@ -300,7 +300,7 @@ def wrap_text(text, font, max_width):
|
|||||||
|
|
||||||
def load_system_image(platform_dict):
|
def load_system_image(platform_dict):
|
||||||
"""Charge une image système depuis le chemin spécifié dans system_image."""
|
"""Charge une image système depuis le chemin spécifié dans system_image."""
|
||||||
image_path = platform_dict.get("system_image")
|
image_path = os.path.join(config.IMAGES_FOLDER, platform_dict.get("system_image", "default.png"))
|
||||||
platform_name = platform_dict.get("platform", "unknown")
|
platform_name = platform_dict.get("platform", "unknown")
|
||||||
#logger.debug(f"Chargement de l'image système pour {platform_name} depuis {image_path}")
|
#logger.debug(f"Chargement de l'image système pour {platform_name} depuis {image_path}")
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user