1
0
forked from Mirrors/RGSX

v1.9.7.2 - Mappage auto des controles grace au fichier es_input (en test) et correction de bug téléchargement sur myrient

This commit is contained in:
skymike03
2025-07-24 17:12:30 +02:00
parent 5226ddb909
commit cd2a2d96fa
16 changed files with 5891 additions and 202 deletions

View File

@@ -40,10 +40,7 @@ logger = logging.getLogger(__name__)
pygame.init()
config.init_font()
pygame.joystick.init()
pygame.mouse.set_visible(True)
# Initialisation du sélecteur de langue
update_valid_states()
# Chargement et initialisation de la langue
from language import initialize_language
@@ -55,9 +52,10 @@ config.is_non_pc = detect_non_pc()
# Initialisation de lécran
screen = init_display()
pygame.display.set_caption("RGSX")
clock = pygame.time.Clock()
pygame.display.set_caption("RGSX")
# Initialisation des polices
try:
font_path = os.path.join(config.APP_FOLDER, "assets", "Pixel-UniCode.ttf")
@@ -66,18 +64,18 @@ try:
config.search_font = pygame.font.Font(font_path, 48) # Police pour la recherche
config.progress_font = pygame.font.Font(font_path, 36) # Police pour l'affichage de la progression
config.small_font = pygame.font.Font(font_path, 28) # Police pour les petits textes
logger.debug("Police Pixel-UniCode chargée")
#logger.debug("Police Pixel-UniCode chargée")
except:
config.font = pygame.font.SysFont("arial", 48) # Police fallback
config.title_font = pygame.font.SysFont("arial", 60) # Police fallback pour les titres
config.search_font = pygame.font.SysFont("arial", 60) # Police fallback pour la recherche
config.progress_font = pygame.font.SysFont("arial", 36) # Police fallback pour l'affichage de la progression
config.small_font = pygame.font.SysFont("arial", 28) # Police fallback pour les petits textes
logger.debug("Police Arial chargée")
#logger.debug("Police Arial chargée")
# Mise à jour de la résolution dans config
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
logger.debug(f"Résolution réelle : {config.screen_width}x{config.screen_height}")
logger.debug(f"Résolution d'écran : {config.screen_width}x{config.screen_height}")
# Initialisation des variables de grille
config.current_page = 0
@@ -106,21 +104,17 @@ else:
# Chargement de l'historique
config.history = load_history()
logger.debug(f"Historique chargé: {len(config.history)} entrées")
# Vérifier si le fichier de configuration des contrôles existe
controls_file_exists = os.path.exists(config.CONTROLS_CONFIG_PATH)
logger.debug(f"Fichier controls.json existe: {controls_file_exists} à {config.CONTROLS_CONFIG_PATH}")
logger.debug(f"Historique de téléchargement : {len(config.history)} entrées")
# Vérification et chargement de la configuration des contrôles
config.controls_config = load_controls_config()
# Déterminer l'état initial de l'application
if not controls_file_exists:
# Si pas de fichier de contrôles, on commence par les configurer
# Vérifier si la configuration est vide (pas de fichier ou importation échouée)
if not config.controls_config:
# Si pas de configuration, on commence par les configurer
config.menu_state = "controls_mapping"
config.needs_redraw = True # Forcer le redraw immédiatement
logger.info(f"Pas de fichier de contrôles à {config.CONTROLS_CONFIG_PATH}, configuration des contrôles")
logger.info("Aucune configuration de contrôles disponible, configuration manuelle nécessaire")
logger.debug("Menu initial: mappage des contrôles")
else:
# Sinon, chargement normal
@@ -153,8 +147,6 @@ async def main():
last_redraw_time = pygame.time.get_ticks()
config.last_frame_time = pygame.time.get_ticks() # Initialisation pour éviter erreur
screen = init_display()
clock = pygame.time.Clock()
while running:
clock.tick(30) # Limite à 60 FPS
@@ -219,11 +211,11 @@ async def main():
start_config = config.controls_config.get("start", {})
if start_config and (
(event.type == pygame.KEYDOWN and start_config.get("type") == "key" and event.key == start_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and start_config.get("type") == "button" and event.button == start_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and start_config.get("type") == "axis" and event.axis == start_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == start_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and start_config.get("type") == "hat" and event.value == tuple(start_config.get("value"))) or
(event.type == pygame.MOUSEBUTTONDOWN and start_config.get("type") == "mouse" and event.button == start_config.get("value"))
(event.type == pygame.KEYDOWN and start_config.get("type") == "key" and event.key == start_config.get("key")) or
(event.type == pygame.JOYBUTTONDOWN and start_config.get("type") == "button" and event.button == start_config.get("button")) or
(event.type == pygame.JOYAXISMOTION and start_config.get("type") == "axis" and event.axis == start_config.get("axis") and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == start_config.get("direction")) or
(event.type == pygame.JOYHATMOTION and start_config.get("type") == "hat" and event.value == tuple(start_config.get("value") if isinstance(start_config.get("value"), list) else start_config.get("value"))) or
(event.type == pygame.MOUSEBUTTONDOWN and start_config.get("type") == "mouse" and event.button == start_config.get("button"))
):
if config.menu_state not in ["pause_menu", "controls_help", "controls_mapping", "history", "confirm_clear_history"]:
config.previous_menu_state = config.menu_state
@@ -236,33 +228,25 @@ async def main():
if config.menu_state == "pause_menu":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
#logger.debug(f"Événement transmis à handle_controls dans pause_menu: {event.type}")
continue
if config.menu_state == "controls_help":
cancel_config = config.controls_config.get("cancel", {})
if (
(event.type == pygame.KEYDOWN and cancel_config and event.key == cancel_config.get("value")) or
(event.type == pygame.JOYBUTTONDOWN and cancel_config and cancel_config.get("type") == "button" and event.button == cancel_config.get("value")) or
(event.type == pygame.JOYAXISMOTION and cancel_config and cancel_config.get("type") == "axis" and event.axis == cancel_config.get("value")[0] and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == cancel_config.get("value")[1]) or
(event.type == pygame.JOYHATMOTION and cancel_config and cancel_config.get("type") == "hat" and event.value == tuple(cancel_config.get("value")))
):
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug("Controls_help: Annulation, retour à pause_menu")
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
#logger.debug(f"Événement transmis à handle_controls dans controls_help: {event.type}")
continue
if config.menu_state == "confirm_clear_history":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans confirm_clear_history: {event.type}")
#logger.debug(f"Événement transmis à handle_controls dans confirm_clear_history: {event.type}")
continue
if config.menu_state == "redownload_game_cache":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}")
#logger.debug(f"Événement transmis à handle_controls dans redownload_game_cache: {event.type}")
continue
if config.menu_state == "extension_warning":
@@ -570,7 +554,7 @@ async def main():
draw_extension_warning(screen)
elif config.menu_state == "pause_menu":
draw_pause_menu(screen, config.selected_option)
logger.debug("Rendu de draw_pause_menu")
#logger.debug("Rendu de draw_pause_menu")
elif config.menu_state == "controls_help":
draw_controls_help(screen, config.previous_menu_state)
elif config.menu_state == "history":
@@ -582,10 +566,6 @@ async def main():
draw_redownload_game_cache_dialog(screen)
elif config.menu_state == "restart_popup":
draw_popup(screen)
elif config.menu_state == "language_select":
draw_language_menu(screen)
# Ajout de log pour déboguer
logger.debug(f"Affichage du sélecteur de langue, index={config.selected_language_index}")
else:
config.menu_state = "platform"
draw_platform_grid(screen)
@@ -646,7 +626,7 @@ async def main():
config.needs_redraw = True
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
elif loading_step == "test_internet":
logger.debug("Exécution de test_internet()")
#logger.debug("Exécution de test_internet()")
if test_internet():
loading_step = "check_ota"
config.current_loading_system = "Verification Mise à jour en cours... Patientez..."

BIN
assets/music/game_8bit.mp3 Normal file

Binary file not shown.

BIN
assets/music/level_IV.mp3 Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -5,7 +5,7 @@ import logging
logger = logging.getLogger(__name__)
# Version actuelle de l'application
app_version = "1.9.7.1"
app_version = "1.9.7.2"
# Langue par défaut
current_language = "fr"
@@ -117,6 +117,10 @@ small_font = None
def init_font():
"""Initialise les polices après pygame.init()."""
logger.debug("--------------------------------------------------------------------")
logger.debug("---------------------------DEBUT LOG--------------------------------")
logger.debug("--------------------------------------------------------------------")
global FONT, progress_font, title_font, search_font, small_font
try:
FONT = pygame.font.Font(None, 36)

View File

@@ -98,23 +98,22 @@ def is_input_matched(event, action_name):
return False
mapping = config.controls_config[action_name]
input_type = mapping["type"]
input_value = mapping["value"]
# Convertir input_value en tuple si c'est une liste (pour JOYHATMOTION)
if input_type == "hat" and isinstance(input_value, list):
input_value = tuple(input_value)
if input_type == "key" and event.type == pygame.KEYDOWN:
return event.key == input_value
return event.key == mapping.get("key")
elif input_type == "button" and event.type == pygame.JOYBUTTONDOWN:
return event.button == input_value
return event.button == mapping.get("button")
elif input_type == "axis" and event.type == pygame.JOYAXISMOTION:
axis, direction = input_value
axis = mapping.get("axis")
direction = mapping.get("direction")
return event.axis == axis and abs(event.value) > 0.5 and (1 if event.value > 0 else -1) == direction
elif input_type == "hat" and event.type == pygame.JOYHATMOTION:
return event.value == input_value
hat_value = mapping.get("value")
if isinstance(hat_value, list):
hat_value = tuple(hat_value)
return event.value == hat_value
elif input_type == "mouse" and event.type == pygame.MOUSEBUTTONDOWN:
return event.button == input_value
return event.button == mapping.get("button")
return False
def handle_controls(event, sources, joystick, screen):
@@ -146,7 +145,7 @@ def handle_controls(event, sources, joystick, screen):
return "quit"
# Menu pause
if is_input_matched(event, "start") and config.menu_state not in ("pause_menu", "controls_help", "controls_mapping", "redownload_game_cache"):
if is_input_matched(event, "start") and config.menu_state not in ("pause_menu", "controls_mapping", "redownload_game_cache"):
config.previous_menu_state = config.menu_state
config.menu_state = "pause_menu"
config.selected_option = 0
@@ -375,13 +374,30 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
elif is_input_matched(event, "filter"):
elif is_input_matched(event, "filter") or is_input_matched(event, "confirm"):
config.search_mode = False
config.filter_active = bool(config.search_query)
config.needs_redraw = True
config.needs_redraw = True
logger.debug(f"Validation du filtre avec manette: query={config.search_query}, filter_active={config.filter_active}")
elif config.search_mode and not config.is_non_pc:
# Gestion de la recherche sur PC
if event.type == pygame.KEYDOWN:
# Gestion de la recherche sur PC (clavier et manette)
if is_input_matched(event, "filter"):
config.search_mode = False
config.filter_active = True
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation du filtre avec bouton filter sur PC: query={config.search_query}")
elif is_input_matched(event, "cancel"):
config.search_mode = False
config.search_query = ""
config.filtered_games = config.games
config.filter_active = False
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche avec bouton cancel sur PC")
elif event.type == pygame.KEYDOWN:
# Saisie de texte alphanumérique
if event.unicode.isalnum() or event.unicode == ' ':
config.search_query += event.unicode
@@ -416,6 +432,14 @@ def handle_controls(event, sources, joystick, screen):
config.scroll_offset = 0
config.needs_redraw = True
logger.debug("Sortie du mode recherche")
# Gestion de la validation avec le bouton filter
elif is_input_matched(event, "filter"):
config.search_mode = False
config.filter_active = True
config.current_game = 0
config.scroll_offset = 0
config.needs_redraw = True
logger.debug(f"Validation du filtre avec bouton filter: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
else:
if is_input_matched(event, "up"):
@@ -813,27 +837,27 @@ def handle_controls(event, sources, joystick, screen):
# Menu pause
elif config.menu_state == "pause_menu":
logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
#logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
if is_input_matched(event, "up"):
config.selected_option = max(0, config.selected_option - 1)
# La répétition est gérée par update_key_state
config.needs_redraw = True
logger.debug(f"Navigation vers le haut: selected_option={config.selected_option}")
#logger.debug(f"Navigation vers le haut: selected_option={config.selected_option}")
elif is_input_matched(event, "down"):
config.selected_option = min(5, config.selected_option + 1)
# La répétition est gérée par update_key_state
config.needs_redraw = True
logger.debug(f"Navigation vers le bas: selected_option={config.selected_option}")
#logger.debug(f"Navigation vers le bas: selected_option={config.selected_option}")
elif is_input_matched(event, "confirm"):
logger.debug(f"Confirmation dans pause_menu avec selected_option={config.selected_option}")
#logger.debug(f"Confirmation dans pause_menu avec selected_option={config.selected_option}")
if config.selected_option == 0: # Controls
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "controls_help"
config.needs_redraw = True
logger.debug(f"Passage à controls_help depuis pause_menu")
#logger.debug(f"Passage à controls_help depuis pause_menu")
elif config.selected_option == 1: # Remap controls
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
logger.debug(f"Previous menu state avant controls_mapping: {config.previous_menu_state}")
#logger.debug(f"Previous menu state avant controls_mapping: {config.previous_menu_state}")
#Supprimer le fichier de configuration des contrôles s'il existe
if os.path.exists(config.CONTROLS_CONFIG_PATH):
try:
@@ -878,9 +902,9 @@ def handle_controls(event, sources, joystick, screen):
# Aide contrôles
elif config.menu_state == "controls_help":
if is_input_matched(event, "cancel"):
config.menu_state = validate_menu_state(config.previous_menu_state)
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug(f"Retour à {config.menu_state} depuis controls_help")
logger.debug("Retour au menu pause depuis controls_help")
# Remap controls
elif config.menu_state == "controls_mapping":
@@ -962,41 +986,33 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
return action
# Navigation directe avec les touches du clavier
if event.type == pygame.KEYDOWN:
# Navigation vers le haut
if event.key == pygame.K_UP:
config.selected_language_index = (config.selected_language_index - 1) % len(available_languages)
config.needs_redraw = True
logger.debug(f"Navigation vers le haut dans le sélecteur de langue: {config.selected_language_index}")
# Navigation vers le bas
elif event.key == pygame.K_DOWN:
config.selected_language_index = (config.selected_language_index + 1) % len(available_languages)
config.needs_redraw = True
logger.debug(f"Navigation vers le bas dans le sélecteur de langue: {config.selected_language_index}")
# Sélection de la langue
elif event.key == pygame.K_RETURN:
lang_code = available_languages[config.selected_language_index]
if set_language(lang_code):
logger.info(f"Langue changée pour {lang_code}")
config.current_language = lang_code
# Afficher un message de confirmation
config.menu_state = "restart_popup"
config.popup_message = _("language_changed").format(lang_code)
config.popup_timer = 2000 # 2 secondes
else:
# Retour au menu pause en cas d'erreur
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug(f"Sélection de la langue: {lang_code}")
# Annulation
elif event.key == pygame.K_ESCAPE:
# Navigation avec clavier et manette
if is_input_matched(event, "up"):
config.selected_language_index = (config.selected_language_index - 1) % len(available_languages)
config.needs_redraw = True
logger.debug(f"Navigation vers le haut dans le sélecteur de langue: {config.selected_language_index}")
elif is_input_matched(event, "down"):
config.selected_language_index = (config.selected_language_index + 1) % len(available_languages)
config.needs_redraw = True
logger.debug(f"Navigation vers le bas dans le sélecteur de langue: {config.selected_language_index}")
elif is_input_matched(event, "confirm"):
lang_code = available_languages[config.selected_language_index]
if set_language(lang_code):
logger.info(f"Langue changée pour {lang_code}")
config.current_language = lang_code
# Afficher un message de confirmation
config.menu_state = "restart_popup"
config.popup_message = _("language_changed").format(lang_code)
config.popup_timer = 2000 # 2 secondes
else:
# Retour au menu pause en cas d'erreur
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug("Annulation de la sélection de langue, retour au menu pause")
config.needs_redraw = True
logger.debug(f"Sélection de la langue: {lang_code}")
elif is_input_matched(event, "cancel"):
config.menu_state = "pause_menu"
config.needs_redraw = True
logger.debug("Annulation de la sélection de langue, retour au menu pause")
# Gestion des relâchements de touches
@@ -1004,21 +1020,27 @@ def handle_controls(event, sources, joystick, screen):
# Vérifier quelle touche a été relâchée
for action_name in ["up", "down", "left", "right", "confirm", "cancel"]:
if config.controls_config.get(action_name, {}).get("type") == "key" and \
config.controls_config.get(action_name, {}).get("value") == event.key:
config.controls_config.get(action_name, {}).get("key") == event.key:
update_key_state(action_name, False)
elif event.type == pygame.JOYBUTTONUP:
# Vérifier quel bouton a été relâché
for action_name in ["up", "down", "left", "right", "confirm", "cancel"]:
if config.controls_config.get(action_name, {}).get("type") == "button" and \
config.controls_config.get(action_name, {}).get("value") == event.button:
config.controls_config.get(action_name, {}).get("button") == event.button:
update_key_state(action_name, False)
elif event.type == pygame.JOYAXISMOTION and abs(event.value) < 0.5:
# Vérifier quel axe a été relâché
for action_name in ["up", "down", "left", "right"]:
if config.controls_config.get(action_name, {}).get("type") == "axis" and \
config.controls_config.get(action_name, {}).get("value")[0] == event.axis:
config.controls_config.get(action_name, {}).get("axis") == event.axis:
update_key_state(action_name, False)
elif event.type == pygame.JOYHATMOTION and event.value == (0, 0):
# Vérifier quel hat a été relâché
for action_name in ["up", "down", "left", "right"]:
if config.controls_config.get(action_name, {}).get("type") == "hat":
update_key_state(action_name, False)
return action

View File

@@ -203,7 +203,7 @@ HOLD_DURATION = 1000
JOYHAT_DEBOUNCE = 200 # Délai anti-rebond pour JOYHATMOTION (ms)
def load_controls_config():
#Charge la configuration des contrôles depuis controls.json
"""Charge la configuration des contrôles depuis controls.json ou EmulationStation"""
try:
if os.path.exists(CONTROLS_CONFIG_PATH):
with open(CONTROLS_CONFIG_PATH, "r") as f:
@@ -211,8 +211,17 @@ def load_controls_config():
logger.debug(f"Configuration des contrôles chargée : {config}")
return config
else:
logger.debug("Aucun fichier controls.json trouvé, configuration par défaut.")
return {}
logger.debug("Aucun fichier controls.json trouvé, tentative d'importation depuis EmulationStation")
# Essayer d'importer depuis EmulationStation
from es_input_parser import parse_es_input_config
es_config = parse_es_input_config()
if es_config:
logger.info("Configuration importée depuis EmulationStation")
save_controls_config(es_config)
return es_config
else:
logger.debug("Importation depuis EmulationStation échouée, configuration par défaut")
return {}
except Exception as e:
logger.error(f"Erreur lors du chargement de controls.json : {e}")
return {}

View File

@@ -240,7 +240,58 @@ def get_control_display(action, default):
if not config.controls_config:
logger.warning(f"controls_config vide pour l'action {action}, utilisation de la valeur par défaut")
return default
return config.controls_config.get(action, {}).get('display', default)
control_config = config.controls_config.get(action, {})
control_type = control_config.get('type', '')
# Générer le nom d'affichage basé sur la configuration réelle
if control_type == 'key':
key_code = control_config.get('key')
key_names = {
pygame.K_RETURN: "Entrée",
pygame.K_BACKSPACE: "Retour",
pygame.K_UP: "",
pygame.K_DOWN: "",
pygame.K_LEFT: "",
pygame.K_RIGHT: "",
pygame.K_SPACE: "Espace",
pygame.K_DELETE: "Suppr",
pygame.K_PAGEUP: "PgUp",
pygame.K_PAGEDOWN: "PgDn",
pygame.K_p: "P",
pygame.K_h: "H",
pygame.K_f: "F",
pygame.K_x: "X"
}
return key_names.get(key_code, chr(key_code) if 32 <= key_code <= 126 else f"Key{key_code}")
elif control_type == 'button':
button_id = control_config.get('button')
button_names = {
0: "A", 1: "B", 2: "X", 3: "Y",
4: "LB", 5: "RB", 6: "Select", 7: "Start"
}
return button_names.get(button_id, f"Btn{button_id}")
elif control_type == 'hat':
hat_value = control_config.get('value', (0, 0))
hat_names = {
(0, 1): "D↑", (0, -1): "D↓",
(-1, 0): "D←", (1, 0): "D→"
}
return hat_names.get(tuple(hat_value) if isinstance(hat_value, list) else hat_value, "D-Pad")
elif control_type == 'axis':
axis_id = control_config.get('axis')
direction = control_config.get('direction')
axis_names = {
(0, -1): "J←", (0, 1): "J→",
(1, -1): "J↑", (1, 1): "J↓"
}
return axis_names.get((axis_id, direction), f"Joy{axis_id}")
# Fallback vers l'ancien système ou valeur par défaut
return control_config.get('display', default)
# Cache pour les images des plateformes
platform_images_cache = {}
@@ -901,9 +952,8 @@ def draw_extension_warning(screen):
def draw_controls(screen, menu_state):
"""Affiche les contrôles sur une seule ligne en bas de lécran."""
start_button = get_control_display('start', 'START')
history_button = get_control_display('history', 'H')
filter_button = get_control_display('filter', 'F')
control_text = _("footer_version").format(config.app_version, start_button, history_button, filter_button)
start_text = _("controls_action_start")
control_text = f"RGSX v{config.app_version} - {start_button} : {start_text}"
max_width = config.screen_width - 40
wrapped_controls = wrap_text(control_text, config.small_font, max_width)
line_height = config.small_font.get_height() + 5
@@ -1029,18 +1079,20 @@ def draw_controls_help(screen, previous_state):
space_text = _("controls_action_space")
common_controls = {
"confirm": lambda action: f"{get_control_display('confirm', confirm_text)} : {action}",
"cancel": lambda action: f"{get_control_display('cancel', cancel_text)} : {action}",
"start": lambda: f"{get_control_display('start', start_text)} : {start_text}",
"progress": lambda action: f"{get_control_display('progress', progress_text)} : {action}",
"up": lambda action: f"{get_control_display('up', up_text)} : {action}",
"down": lambda action: f"{get_control_display('down', down_text)} : {action}",
"page_up": lambda action: f"{get_control_display('page_up', page_up_text)} : {action}",
"page_down": lambda action: f"{get_control_display('page_down', page_down_text)} : {action}",
"filter": lambda action: f"{get_control_display('filter', filter_text)} : {action}",
"history": lambda action: f"{get_control_display('history', history_text)} : {action}",
"delete": lambda: f"{get_control_display('delete', delete_text)} : {delete_text}",
"space": lambda: f"{get_control_display('space', space_text)} : {space_text}"
"confirm": lambda action: f"{get_control_display('confirm', 'A')} : {action}",
"cancel": lambda action: f"{get_control_display('cancel', 'B')} : {action}",
"start": lambda: f"{get_control_display('start', 'Start')} : {start_text}",
"progress": lambda action: f"{get_control_display('progress', 'X')} : {action}",
"up": lambda action: f"{get_control_display('up', '')} : {action}",
"down": lambda action: f"{get_control_display('down', '')} : {action}",
"left": lambda action: f"{get_control_display('left', '')} : {action}",
"right": lambda action: f"{get_control_display('right', '')} : {action}",
"page_up": lambda action: f"{get_control_display('page_up', 'LB')} : {action}",
"page_down": lambda action: f"{get_control_display('page_down', 'RB')} : {action}",
"filter": lambda action: f"{get_control_display('filter', 'Select')} : {action}",
"history": lambda action: f"{get_control_display('history', 'Y')} : {action}",
"delete": lambda: f"{get_control_display('delete', 'Suppr')} : {delete_text}",
"space": lambda: f"{get_control_display('space', 'Espace')} : {space_text}"
}
# Utiliser des variables pour les traductions d'actions
@@ -1063,91 +1115,109 @@ def draw_controls_help(screen, previous_state):
"clear_history": _("action_clear_history")
}
state_controls = {
"error": [
common_controls["confirm"](action_translations["retry"]),
common_controls["cancel"](action_translations["quit"])
# Catégories de contrôles
control_categories = {
"Navigation": [
f"{get_control_display('up', '')} {get_control_display('down', '')} {get_control_display('left', '')} {get_control_display('right', '')} : Navigation",
f"{get_control_display('page_up', 'LB')} {get_control_display('page_down', 'RB')} : Pages"
],
"platform": [
common_controls["confirm"](action_translations["select"]),
common_controls["cancel"](action_translations["quit"]),
common_controls["start"](),
common_controls["history"](action_translations["history"]),
*( [common_controls["progress"](action_translations["progress"])] if config.download_tasks else [])
"Actions principales": [
f"{get_control_display('confirm', 'A')} : Confirmer/Sélectionner",
f"{get_control_display('cancel', 'B')} : Annuler/Retour",
f"{get_control_display('start', 'Start')} : {start_text}"
],
"game": [
common_controls["confirm"](action_translations["select"] if config.search_mode else action_translations["download"]),
common_controls["filter"](action_translations["filter"]),
common_controls["cancel"](action_translations["cancel"] if config.search_mode else action_translations["back"]),
common_controls["history"](action_translations["history"]),
*( [
common_controls["delete"](),
common_controls["space"]()
] if config.search_mode and config.is_non_pc else []),
*( [
f"{common_controls['up'](action_translations['navigate'])} / {common_controls['down'](action_translations['navigate'])}",
f"{common_controls['page_up'](action_translations['page'])} / {common_controls['page_down'](action_translations['page'])}",
common_controls["filter"](action_translations["filter"])
] if not config.is_non_pc or not config.search_mode else []),
common_controls["start"](),
*( [common_controls["progress"](action_translations["progress"])] if config.download_tasks and not config.search_mode else [])
"Téléchargements": [
f"{get_control_display('history', 'Y')} : Historique",
f"{get_control_display('progress', 'X')} : Effacer historique"
],
"download_progress": [
common_controls["cancel"](action_translations["cancel_download"]),
common_controls["progress"](action_translations["background"]),
common_controls["start"]()
],
"download_result": [
common_controls["confirm"](action_translations["back"])
],
"confirm_exit": [
common_controls["confirm"](action_translations["confirm"])
],
"extension_warning": [
common_controls["confirm"](action_translations["confirm"])
],
"history": [
common_controls["confirm"](action_translations["redownload"]),
common_controls["cancel"](action_translations["back"]),
common_controls["progress"](action_translations["clear_history"]),
f"{common_controls['up'](action_translations['navigate'])} / {common_controls['down'](action_translations['navigate'])}",
f"{common_controls['page_up'](action_translations['page'])} / {common_controls['page_down'](action_translations['page'])}",
common_controls["start"]()
"Recherche": [
f"{get_control_display('filter', 'Select')} : Filtrer/Rechercher",
f"{get_control_display('delete', 'Suppr')} : {delete_text}",
f"{get_control_display('space', 'Espace')} : {space_text}"
]
}
state_controls = {
"error": control_categories,
"platform": control_categories,
"game": control_categories,
"download_progress": control_categories,
"download_result": control_categories,
"confirm_exit": control_categories,
"extension_warning": control_categories,
"history": control_categories
}
controls = state_controls.get(previous_state, [])
if not controls:
control_columns = state_controls.get(previous_state, {})
if not control_columns:
return
screen.blit(OVERLAY, (0, 0))
max_width = config.screen_width - 80
wrapped_controls = []
current_line = ""
for control in controls:
test_line = f"{current_line} | {control}" if current_line else control
if config.font.size(test_line)[0] <= max_width:
current_line = test_line
else:
wrapped_controls.append(current_line)
current_line = control
if current_line:
wrapped_controls.append(current_line)
line_height = config.font.get_height() + 10
popup_width = max_width + 40
popup_height = len(wrapped_controls) * line_height + 60
# Organisation en 2x2
categories = list(control_columns.keys())
col1 = [categories[0], categories[2]] # Navigation, Historique/Téléchargements
col2 = [categories[1], categories[3]] # Actions principales, Recherche / Filtre
# Calculer la largeur nécessaire
max_text_width = 0
for category, controls in control_columns.items():
for control in controls:
text_width = config.small_font.size(control)[0]
max_text_width = max(max_text_width, text_width)
col_width = max_text_width + 40
popup_width = col_width * 2 + 100 # Plus d'espace entre colonnes
popup_height = 320
popup_x = (config.screen_width - popup_width) // 2
popup_y = (config.screen_height - popup_height) // 2
# Fond principal
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (popup_x, popup_y, popup_width, popup_height), border_radius=12)
pygame.draw.rect(screen, THEME_COLORS["border"], (popup_x, popup_y, popup_width, popup_height), 2, border_radius=12)
for i, line in enumerate(wrapped_controls):
text = config.font.render(line, True, THEME_COLORS["text"])
text_rect = text.get_rect(center=(config.screen_width // 2, popup_y + 40 + i * line_height))
screen.blit(text, text_rect)
# Titre
title_text = "Aide des contrôles"
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
title_rect = title_surface.get_rect(center=(config.screen_width // 2, popup_y + 25))
screen.blit(title_surface, title_rect)
# Affichage en colonnes
start_y = popup_y + 60
# Colonne 1
current_y = start_y
for category in col1:
controls = control_columns[category]
# Titre
cat_surface = config.font.render(category, True, THEME_COLORS["fond_lignes"])
cat_rect = cat_surface.get_rect(x=popup_x + 20, y=current_y)
screen.blit(cat_surface, cat_rect)
current_y += 30 # Plus d'espace après titre
# Contrôles
for control in controls:
ctrl_surface = config.small_font.render(f"{control}", True, THEME_COLORS["text"])
ctrl_rect = ctrl_surface.get_rect(x=popup_x + 30, y=current_y)
screen.blit(ctrl_surface, ctrl_rect)
current_y += 20
current_y += 20 # Plus d'espace entre sections
# Colonne 2
current_y = start_y
for category in col2:
controls = control_columns[category]
# Titre
cat_surface = config.font.render(category, True, THEME_COLORS["fond_lignes"])
cat_rect = cat_surface.get_rect(x=popup_x + col_width + 40, y=current_y) # Plus d'espace entre colonnes
screen.blit(cat_surface, cat_rect)
current_y += 30 # Plus d'espace après titre
# Contrôles
for control in controls:
ctrl_surface = config.small_font.render(f"{control}", True, THEME_COLORS["text"])
ctrl_rect = ctrl_surface.get_rect(x=popup_x + col_width + 50, y=current_y) # Plus d'espace entre colonnes
screen.blit(ctrl_surface, ctrl_rect)
current_y += 20
current_y += 20 # Plus d'espace entre sections
# Menu Quitter Appli
def draw_confirm_dialog(screen):

5403
es_input.cfg Normal file

File diff suppressed because it is too large Load Diff

166
es_input_parser.py Normal file
View File

@@ -0,0 +1,166 @@
import xml.etree.ElementTree as ET
import os
import logging
import pygame #type: ignore
logger = logging.getLogger(__name__)
def parse_es_input_config():
"""Parse le fichier es_input.cfg d'EmulationStation et retourne la configuration des contrôles"""
es_input_path = "/usr/share/emulationstation/es_input.cfg"
if not os.path.exists(es_input_path):
logger.debug(f"Fichier {es_input_path} non trouvé")
return None
try:
tree = ET.parse(es_input_path)
root = tree.getroot()
# Mapping des boutons EmulationStation vers les actions RGSX
# Priorité: D-pad > Joystick pour la navigation
es_to_rgsx_mapping = {
"b": "confirm",
"a": "cancel",
"up": "up",
"down": "down",
"left": "left",
"right": "right",
"pageup": "page_up",
"pagedown": "page_down",
"y": "progress",
"x": "history",
"select": "filter",
"leftshoulder": "delete",
"rightshoulder": "space",
"start": "start"
}
# Priorité pour les entrées directionnelles (hat > axis)
direction_priority = {"up": [], "down": [], "left": [], "right": []}
controls_config = {}
# Chercher la première configuration de joystick
for inputConfig in root.findall("inputConfig"):
if inputConfig.get("type") == "joystick":
logger.debug(f"Configuration trouvée pour: {inputConfig.get('deviceName', 'Manette inconnue')}")
# Première passe: collecter toutes les entrées par action
for input_tag in inputConfig.findall("input"):
es_name = input_tag.get("name")
es_type = input_tag.get("type")
es_id = input_tag.get("id")
es_value = input_tag.get("value", "1")
logger.debug(f"Entrée trouvée: {es_name} = {es_type}:{es_id} (value={es_value})")
if es_name in es_to_rgsx_mapping:
rgsx_action = es_to_rgsx_mapping[es_name]
if es_type == "hat" and rgsx_action in direction_priority:
# Priorité maximale pour le D-pad
hat_mapping = {
"1": (0, 1), # Haut
"2": (1, 0), # Droite
"4": (0, -1), # Bas
"8": (-1, 0) # Gauche
}
if es_value in hat_mapping:
logger.debug(f"D-pad trouvé pour {rgsx_action}: hat {es_id}, value {es_value}")
direction_priority[rgsx_action].append(("hat", {
"type": "hat",
"joy": 0,
"hat": int(es_id),
"value": hat_mapping[es_value]
}))
elif es_type == "axis" and rgsx_action in direction_priority:
# Priorité secondaire pour les axes
direction = 1 if int(es_value) > 0 else -1
logger.debug(f"Axe trouvé pour {rgsx_action}: axis {es_id}, direction {direction}")
direction_priority[rgsx_action].append(("axis", {
"type": "axis",
"joy": 0,
"axis": int(es_id),
"direction": direction
}))
elif es_type == "button":
controls_config[rgsx_action] = {
"type": "button",
"joy": 0,
"button": int(es_id)
}
elif es_type == "key":
controls_config[rgsx_action] = {
"type": "key",
"key": int(es_id)
}
# Deuxième passe: assigner les directions avec priorité
for action, entries in direction_priority.items():
if entries:
logger.debug(f"Priorité pour {action}: {[(e[0], e[1]['type']) for e in entries]}")
# Trier par priorité: hat d'abord, puis axis
entries.sort(key=lambda x: 0 if x[0] == "hat" else 1)
controls_config[action] = entries[0][1]
logger.debug(f"Sélectionné pour {action}: {entries[0][1]['type']}")
logger.debug(f"Configuration finale: {controls_config}")
# Forcer l'utilisation du D-pad pour les directions si disponible, sinon clavier
if any(controls_config.get(action, {}).get("type") == "axis" for action in ["up", "down", "left", "right"]):
# Vérifier si une manette est connectée
pygame.joystick.init()
if pygame.joystick.get_count() > 0:
logger.debug("Remplacement des axes par le D-pad pour la navigation")
controls_config["up"] = {"type": "hat", "joy": 0, "hat": 0, "value": (0, 1)}
controls_config["down"] = {"type": "hat", "joy": 0, "hat": 0, "value": (0, -1)}
controls_config["left"] = {"type": "hat", "joy": 0, "hat": 0, "value": (-1, 0)}
controls_config["right"] = {"type": "hat", "joy": 0, "hat": 0, "value": (1, 0)}
else:
logger.debug("Aucune manette détectée, utilisation du clavier pour toutes les actions")
controls_config["up"] = {"type": "key", "key": pygame.K_UP}
controls_config["down"] = {"type": "key", "key": pygame.K_DOWN}
controls_config["left"] = {"type": "key", "key": pygame.K_LEFT}
controls_config["right"] = {"type": "key", "key": pygame.K_RIGHT}
controls_config["confirm"] = {"type": "key", "key": pygame.K_RETURN}
controls_config["cancel"] = {"type": "key", "key": pygame.K_BACKSPACE}
controls_config["start"] = {"type": "key", "key": pygame.K_p}
controls_config["filter"] = {"type": "key", "key": pygame.K_f}
controls_config["history"] = {"type": "key", "key": pygame.K_h}
controls_config["progress"] = {"type": "key", "key": pygame.K_x}
controls_config["page_up"] = {"type": "key", "key": pygame.K_PAGEUP}
controls_config["page_down"] = {"type": "key", "key": pygame.K_PAGEDOWN}
# Ajouter les actions manquantes avec des valeurs par défaut
default_actions = {
"confirm": {"type": "key", "key": pygame.K_RETURN},
"cancel": {"type": "key", "key": pygame.K_BACKSPACE},
"up": {"type": "key", "key": pygame.K_UP},
"down": {"type": "key", "key": pygame.K_DOWN},
"left": {"type": "key", "key": pygame.K_LEFT},
"right": {"type": "key", "key": pygame.K_RIGHT},
"page_up": {"type": "key", "key": pygame.K_PAGEUP},
"page_down": {"type": "key", "key": pygame.K_PAGEDOWN},
"progress": {"type": "key", "key": pygame.K_x},
"history": {"type": "key", "key": pygame.K_h},
"filter": {"type": "key", "key": pygame.K_f},
"delete": {"type": "key", "key": pygame.K_DELETE},
"space": {"type": "key", "key": pygame.K_SPACE},
"start": {"type": "key", "key": pygame.K_p}
}
for action, default_config in default_actions.items():
if action not in controls_config:
controls_config[action] = default_config
logger.info(f"Configuration importée depuis EmulationStation pour {len(controls_config)} actions")
return controls_config
logger.debug("Aucune configuration de joystick trouvée dans es_input.cfg")
return None
except Exception as e:
logger.error(f"Erreur lors du parsing de es_input.cfg: {str(e)}")
return None

View File

@@ -39,7 +39,7 @@ def load_history():
if not all(key in entry for key in ['platform', 'game_name', 'status']):
logger.warning(f"Entrée d'historique invalide : {entry}")
return []
logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
#logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
return history
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Erreur lors de la lecture de {history_path} : {e}")

View File

@@ -34,7 +34,7 @@ def load_language(lang_code=None):
translations = json.load(f)
current_language = lang_code
logger.debug(f"Langue {lang_code} chargée avec succès ({len(translations)} traductions)")
#logger.debug(f"Langue {lang_code} chargée avec succès ({len(translations)} traductions)")
return True
except Exception as e:
@@ -114,7 +114,6 @@ def load_language_preference():
data = json.load(f)
lang_code = data.get("language", DEFAULT_LANGUAGE)
logger.debug(f"Préférence de langue chargée: {lang_code}")
return lang_code
except json.JSONDecodeError:
logger.warning("Fichier de préférence de langue corrompu, utilisation du français par défaut")

View File

@@ -91,7 +91,7 @@
"controls_action_filter": "Filter",
"controls_action_delete": "Delete",
"controls_action_space": "Space",
"controls_action_start": "Menu",
"controls_action_start": "Help / Settings",
"controls_desc_confirm": "Validate (e.g. A, Enter)",
"controls_desc_cancel": "Cancel/Back (e.g. B, Backspace)",

View File

@@ -91,7 +91,7 @@
"controls_action_filter": "Filtrer",
"controls_action_delete": "Supprimer",
"controls_action_space": "Espace",
"controls_action_start": "Menu",
"controls_action_start": "Aide / Réglages",
"controls_desc_confirm": "Valider (ex: A, Entrée)",
"controls_desc_cancel": "Annuler/Retour (ex: B, RetourArrière)",

View File

@@ -189,13 +189,49 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
dest_path = os.path.join(dest_dir, f"{sanitized_name}")
logger.debug(f"Chemin destination: {dest_path}")
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, stream=True, headers=headers, timeout=30)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
# Utiliser une session pour gérer les cookies
session = requests.Session()
session.headers.update(headers)
# Première requête HEAD pour obtenir la vraie URL
logger.debug(f"Première requête HEAD vers {url}")
head_response = session.head(url, timeout=30, allow_redirects=False)
logger.debug(f"HEAD Status: {head_response.status_code}, Headers: {dict(head_response.headers)}")
# Suivre la redirection manuellement si nécessaire
final_url = url
if head_response.status_code in [301, 302, 303, 307, 308]:
final_url = head_response.headers.get('Location', url)
logger.debug(f"Redirection détectée vers: {final_url}")
# Requête GET vers l'URL finale avec en-têtes spécifiques
download_headers = headers.copy()
download_headers['Accept'] = 'application/octet-stream, */*'
download_headers['Referer'] = 'https://myrient.erista.me/'
response = session.get(final_url, stream=True, timeout=30, allow_redirects=False, headers=download_headers)
logger.debug(f"Status code: {response.status_code}")
logger.debug(f"Headers: {dict(response.headers)}")
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
logger.debug(f"Taille totale: {total_size} octets")
if total_size == 0:
logger.warning(f"Taille de fichier 0, possible redirection ou erreur. URL finale: {response.url}")
# Vérifier si c'est une redirection
if response.url != url:
logger.debug(f"Redirection détectée: {url} -> {response.url}")
# Initialiser la progression avec task_id
progress_queue.put((task_id, 0, total_size))
logger.debug(f"Progression initiale envoyée: 0% pour {game_name}, task_id={task_id}")

View File

@@ -34,7 +34,7 @@ def detect_non_pc():
result = subprocess.run(["batocera-es-swissknife", "--arch"], capture_output=True, text=True, timeout=2)
if result.returncode == 0:
arch = result.stdout.strip()
logger.debug(f"Architecture via batocera-es-swissknife: {arch}")
#logger.debug(f"Architecture via batocera-es-swissknife: {arch}")
except (subprocess.SubprocessError, FileNotFoundError):
logger.debug(f"batocera-es-swissknife non disponible, utilisation de platform.machine(): {arch}")
@@ -130,7 +130,7 @@ def load_sources():
def load_games(platform_id):
"""Charge les jeux pour une plateforme donnée en utilisant platform_id et teste la première URL."""
games_path = os.path.join(config.APP_FOLDER, "games", f"{platform_id}.json")
logger.debug(f"Chargement des jeux pour {platform_id} depuis {games_path}")
#logger.debug(f"Chargement des jeux pour {platform_id} depuis {games_path}")
try:
with open(games_path, 'r', encoding='utf-8') as f:
games = json.load(f)