diff --git a/__main__.py b/__main__.py index 4d2c045..7d2a8e0 100644 --- a/__main__.py +++ b/__main__.py @@ -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..." diff --git a/assets/music/game_8bit.mp3 b/assets/music/game_8bit.mp3 new file mode 100644 index 0000000..e9575db Binary files /dev/null and b/assets/music/game_8bit.mp3 differ diff --git a/assets/music/level_IV.mp3 b/assets/music/level_IV.mp3 new file mode 100644 index 0000000..f435fa2 Binary files /dev/null and b/assets/music/level_IV.mp3 differ diff --git a/assets/music/retro_chill.mp3 b/assets/music/retro_chill.mp3 new file mode 100644 index 0000000..4403aa9 Binary files /dev/null and b/assets/music/retro_chill.mp3 differ diff --git a/config.py b/config.py index 313ace4..c023f2e 100644 --- a/config.py +++ b/config.py @@ -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) diff --git a/controls.py b/controls.py index 4ea22ee..583c718 100644 --- a/controls.py +++ b/controls.py @@ -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 diff --git a/controls_mapper.py b/controls_mapper.py index 92f2305..9fe3ab3 100644 --- a/controls_mapper.py +++ b/controls_mapper.py @@ -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 {} diff --git a/display.py b/display.py index 62a93aa..4f73d84 100644 --- a/display.py +++ b/display.py @@ -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): diff --git a/es_input.cfg b/es_input.cfg new file mode 100644 index 0000000..d8c79ee --- /dev/null +++ b/es_input.cfg @@ -0,0 +1,5403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/es_input_parser.py b/es_input_parser.py new file mode 100644 index 0000000..b7994a8 --- /dev/null +++ b/es_input_parser.py @@ -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 \ No newline at end of file diff --git a/history.py b/history.py index fd59398..6725cd2 100644 --- a/history.py +++ b/history.py @@ -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}") diff --git a/language.py b/language.py index 0cd5bb8..226b6a2 100644 --- a/language.py +++ b/language.py @@ -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") diff --git a/languages/en.json b/languages/en.json index 37abd22..5fb8cd0 100644 --- a/languages/en.json +++ b/languages/en.json @@ -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)", diff --git a/languages/fr.json b/languages/fr.json index 41279a1..9d98258 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -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)", diff --git a/network.py b/network.py index 91288d0..c75e8fe 100644 --- a/network.py +++ b/network.py @@ -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}") diff --git a/utils.py b/utils.py index ea2b970..8e29383 100644 --- a/utils.py +++ b/utils.py @@ -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)