v1.9.8.5 Refactor controls mapping to use dynamic translations and update language files for improved localization + fix xbox conversionafter extraction

This commit is contained in:
skymike03
2025-08-10 01:38:01 +02:00
parent fb63861c7d
commit ea8132851b
9 changed files with 142 additions and 105 deletions

View File

@@ -19,7 +19,7 @@ from display import (
from language import handle_language_menu_events, _
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
from controls import handle_controls, validate_menu_state, process_key_repeats, get_emergency_controls
from controls_mapper import load_controls_config, save_controls_config, map_controls, draw_controls_mapping, ACTIONS
from controls_mapper import load_controls_config, save_controls_config, map_controls, draw_controls_mapping, get_actions
from utils import (
detect_non_pc, load_sources, check_extension_before_download, extract_zip_data,
play_random_music, load_accessibility_settings, load_music_config
@@ -639,7 +639,7 @@ async def main():
logger.debug("Initialisation de config.controls_config avec un dictionnaire vide")
# Forcer l'affichage de l'interface de mappage des contrôles
action = ACTIONS[0]
action = get_actions()[0]
draw_controls_mapping(screen, action, None, True, 0.0)
pygame.display.flip()
logger.debug("Interface de mappage des contrôles affichée")

View File

@@ -4,7 +4,7 @@ import sys
import logging
# Version actuelle de l'application
app_version = "1.9.8.4"
app_version = "1.9.8.5"
def get_application_root():
"""Détermine le dossier de l'application de manière portable."""

View File

@@ -3,6 +3,7 @@ import json
import os
import logging
import config
import language
from config import CONTROLS_CONFIG_PATH
from display import draw_gradient
import xml.etree.ElementTree as ET
@@ -13,23 +14,37 @@ logger = logging.getLogger(__name__)
CONTROLS_CONFIG_PATH = os.path.join(config.SAVE_FOLDER, "controls.json")
# Actions internes de RGSX à mapper
ACTIONS = [
{"name": "confirm", "display": "Confirmer", "description": "Valider (Recommandé: Entrée, A/Croix)"},
{"name": "cancel", "display": "Annuler", "description": "Annuler/Retour (Recommandé: Retour Arrière, B/Rond)"},
{"name": "up", "display": "Haut", "description": "Naviguer vers le haut"},
{"name": "down", "display": "Bas", "description": "Naviguer vers le bas"},
{"name": "left", "display": "Gauche", "description": "Naviguer à gauche"},
{"name": "right", "display": "Droite", "description": "Naviguer à droite"},
{"name": "start", "display": "Start", "description": "Menu pause / Paramètres (Recommandé: Start, AltGr)"},
{"name": "filter", "display": "Filtrer", "description": "Ouvrir filtre (Recommandé: F, Select)"},
{"name": "page_up", "display": "Page Précédente", "description": "Page précédente/Défilement Rapide Haut (Recommandé: PageUp, LB/L1)"},
{"name": "page_down", "display": "Page Suivante", "description": "Page suivante/Défilement Rapide Bas (Recommandé: PageDown, RB/R1)"},
{"name": "history", "display": "Historique", "description": "Ouvrir l'historique (Recommandé: H, Y/Carré)"},
{"name": "progress", "display": "Progression", "description": "Historique : Effacer la liste (Recommandé: X/Triangle)"},
{"name": "delete", "display": "Supprimer", "description": "Mode Fitre : Supprimer caractère en mode recherche (Recommandé: DEL, LT/L2)"},
{"name": "space", "display": "Espace", "description": "Mode Filtre : Ajouter espace (Recommandé: Espace, RT/R2)"},
# Actions internes de RGSX à mapper (labels et descriptions traduits dynamiquement)
ACTION_DEFS = [
{"name": "confirm"},
{"name": "cancel"},
{"name": "up"},
{"name": "down"},
{"name": "left"},
{"name": "right"},
{"name": "start"},
{"name": "filter"},
{"name": "page_up"},
{"name": "page_down"},
{"name": "history"},
{"name": "progress"},
{"name": "delete"},
{"name": "space"},
]
def get_actions(lang=None):
"""Retourne la liste des actions avec labels/descriptions traduits selon la langue courante."""
actions = []
for a in ACTION_DEFS:
name = a["name"]
display = language.get_text(f"controls_action_{name}", name.capitalize())
description = language.get_text(f"controls_desc_{name}", "")
actions.append({"name": name, "display": display, "description": description})
return actions
# ...existing code...
# Mappage des valeurs SDL vers les constantes Pygame
SDL_TO_PYGAME_KEY = {
1073741906: pygame.K_UP, # Flèche Haut
@@ -335,10 +350,11 @@ def map_controls(screen):
held_hats = {}
held_mouse_buttons = set()
while current_action_index < len(ACTIONS):
actions = get_actions()
while current_action_index < len(actions):
if config.needs_redraw:
progress = min(input_held_time / HOLD_DURATION, 1.0) if current_input else 0.0
draw_controls_mapping(screen, ACTIONS[current_action_index], last_input_name, current_input is not None, progress)
draw_controls_mapping(screen, actions[current_action_index], last_input_name, current_input is not None, progress)
pygame.display.flip()
config.needs_redraw = False
@@ -450,7 +466,7 @@ def map_controls(screen):
if current_input:
input_held_time += delta_time
if input_held_time >= HOLD_DURATION:
action_name = ACTIONS[current_action_index]["name"]
action_name = actions[current_action_index]["name"]
# Sauvegarder avec la structure attendue par controls.py
if current_input["type"] == "key":
@@ -522,22 +538,24 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
border_width = 4
shadow_offset = 8
# Titre principal
title_text = "Configuration des contrôles"
# Titre principal (traduction)
title_text = language.get_text("controls_mapping_title", "Configuration des contrôles")
title_surface = config.title_font.render(title_text, True, (255, 255, 255))
title_rect = title_surface.get_rect(center=(config.screen_width // 2, 80))
screen.blit(title_surface, title_rect)
# Instructions
instruction_text = "Maintenez pendant 3s pour configurer :"
description_text = action['description']
# Instructions (traduction)
instruction_text = language.get_text("controls_mapping_instruction", "Maintenez pendant 3s pour configurer :")
description_text = action.get('description', '')
instruction_surface = config.small_font.render(instruction_text, True, (255, 255, 255))
description_surface = config.font.render(description_text, True, (200, 200, 200))
instruction_width, instruction_height = instruction_surface.get_size()
description_width, description_height = description_surface.get_size()
# Input détecté
input_text = last_input or (f"En attente d'une touche ou bouton..." if waiting_for_input else "Appuyez sur une touche ou un bouton")
# Input détecté (traduction)
waiting_text = language.get_text("controls_mapping_waiting", "En attente d'une touche ou bouton...")
press_text = language.get_text("controls_mapping_press", "Appuyez sur une touche ou un bouton")
input_text = last_input or (waiting_text if waiting_for_input else press_text)
input_surface = config.small_font.render(input_text, True, (0, 255, 0) if last_input else (255, 255, 255))
input_width, input_height = input_surface.get_size()

View File

@@ -1250,23 +1250,15 @@ def draw_pause_menu(screen, selected_option):
# Menu aide contrôles
def draw_controls_help(screen, previous_state):
"""Affiche la liste des contrôles avec un style moderne."""
# Définir les noms d'actions traduits en dehors des f-strings pour éviter les problèmes de syntaxe
start_text = _("controls_action_start")
progress_text = _("controls_action_progress")
up_text = _("controls_action_up")
down_text = _("controls_action_down")
filter_text = _("controls_action_filter")
history_text = _("controls_action_history")
delete_text = _("controls_action_delete")
space_text = _("controls_action_space")
# Catégories de contrôles
nav_text = _("controls_navigation")
pages_text = _("controls_pages")
confirm_select_text = _("controls_confirm_select")
cancel_back_text = _("controls_cancel_back")
history_text = _("controls_history")
clear_history_text = _("controls_clear_history")
clear_history_text = _("controls_action_delete_history")
filter_search_text = _("controls_filter_search")
control_categories = {

View File

@@ -1,4 +1,8 @@
{
"controls_mapping_title": "Steuerung konfigurieren",
"controls_mapping_instruction": "3 Sekunden halten zum Konfigurieren:",
"controls_mapping_waiting": "Warte auf eine Taste oder einen Button...",
"controls_mapping_press": "Drücke eine Taste oder einen Button",
"welcome_message": "Willkommen bei RGSX",
"disclaimer_line1": "Es ist gefährlich, allein zu gehen, nimm alles, was du brauchst!",
"disclaimer_line2": "Aber lade nur Spiele herunter,",
@@ -92,7 +96,7 @@
"controls_action_right": "Rechts",
"controls_action_page_up": "Vorherige Seite",
"controls_action_page_down": "Nächste Seite",
"controls_action_progress": "Fortschritt",
"controls_action_delete_history": "Verlauf leeren",
"controls_action_history": "Verlauf",
"controls_action_filter": "Filtern",
"controls_action_delete": "Löschen",
@@ -107,7 +111,7 @@
"controls_desc_right": "Nach rechts navigieren",
"controls_desc_page_up": "Vorherige Seite/Schnelles Scrollen nach oben (z.B.: BildAuf, LB)",
"controls_desc_page_down": "Nächste Seite/Schnelles Scrollen nach unten (z.B.: BildAb, RB)",
"controls_desc_progress": "Fortschritt anzeigen (z.B.: X)",
"controls_desc_delete_history": "Verlauf löschen (z.B.: X)",
"controls_desc_history": "Verlauf öffnen (z.B.: H, Y)",
"controls_desc_filter": "Filter öffnen (z.B.: F, Select)",
"controls_desc_delete": "Zeichen löschen (z.B.: LT, Entf)",

View File

@@ -1,4 +1,8 @@
{
"controls_mapping_title": "Controls configuration",
"controls_mapping_instruction": "Hold for 3s to configure:",
"controls_mapping_waiting": "Waiting for a key or button...",
"controls_mapping_press": "Press a key or button",
"welcome_message": "Welcome to RGSX",
"disclaimer_line1": "It's dangerous to go alone, take all you need!",
"disclaimer_line2": "But only download games",
@@ -92,7 +96,7 @@
"controls_action_right": "Right",
"controls_action_page_up": "Previous Page",
"controls_action_page_down": "Next Page",
"controls_action_progress": "Progress",
"controls_action_delete_history": "Clear History",
"controls_action_history": "History",
"controls_action_filter": "Filter",
"controls_action_delete": "Delete",
@@ -107,7 +111,7 @@
"controls_desc_right": "Navigate right",
"controls_desc_page_up": "Previous page/Fast scroll up (e.g. PageUp, LB)",
"controls_desc_page_down": "Next page/Fast scroll down (e.g. PageDown, RB)",
"controls_desc_progress": "View progress (e.g. X)",
"controls_desc_delete_history": "Clear History (e.g. X)",
"controls_desc_history": "Open history (e.g. H, Y)",
"controls_desc_filter": "Open filter (e.g. F, Select)",
"controls_desc_delete": "Delete character (e.g. LT, Delete)",

View File

@@ -1,4 +1,8 @@
{
"controls_mapping_title": "Configuración de controles",
"controls_mapping_instruction": "Mantén pulsado 3s para configurar:",
"controls_mapping_waiting": "Esperando una tecla o botón...",
"controls_mapping_press": "Pulsa una tecla o botón",
"welcome_message": "Bienvenido a RGSX",
"disclaimer_line1": "¡Es peligroso ir solo, toma todo lo que necesites!",
"disclaimer_line2": "Pero solo descarga juegos",
@@ -92,7 +96,7 @@
"controls_action_right": "Derecha",
"controls_action_page_up": "Página anterior",
"controls_action_page_down": "Página siguiente",
"controls_action_progress": "Progreso",
"controls_action_delete_history": "Vaciar Historial",
"controls_action_history": "Historial",
"controls_action_filter": "Filtrar",
"controls_action_delete": "Eliminar",
@@ -107,7 +111,7 @@
"controls_desc_right": "Navegar a derecha",
"controls_desc_page_up": "Página anterior/Desplazamiento rápido arriba (ej: RePág, LB)",
"controls_desc_page_down": "Página siguiente/Desplazamiento rápido abajo (ej: AvPág, RB)",
"controls_desc_progress": "Ver progreso (ej: X)",
"controls_desc_delete_history": "Borrar Historial (ej: X)",
"controls_desc_history": "Abrir historial (ej: H, Y)",
"controls_desc_filter": "Abrir filtro (ej: F, Select)",
"controls_desc_delete": "Eliminar carácter (ej: LT, Supr)",

View File

@@ -92,7 +92,7 @@
"controls_action_right": "Droite",
"controls_action_page_up": "Page Précédente",
"controls_action_page_down": "Page Suivante",
"controls_action_progress": "Progression",
"controls_action_delete_history": "Vider Historique",
"controls_action_history": "Historique",
"controls_action_filter": "Filtrer",
"controls_action_delete": "Supprimer",
@@ -107,7 +107,7 @@
"controls_desc_right": "Naviguer à droite",
"controls_desc_page_up": "Page précédente/Défilement Rapide Haut (ex: PageUp, LB)",
"controls_desc_page_down": "Page suivante/Défilement Rapide Bas (ex: PageDown, RB)",
"controls_desc_progress": "Voir progression (ex: X)",
"controls_desc_delete_history": "Effacer Historique (ex: X)",
"controls_desc_history": "Ouvrir l'historique (ex: H, Y)",
"controls_desc_filter": "Ouvrir filtre (ex: F, Select)",
"controls_desc_delete": "Supprimer caractère (ex: LT, Suppr)",
@@ -176,5 +176,9 @@
"utils_permission_denied": "Permission refusée lors de l'extraction: {0}",
"utils_extraction_failed": "Échec de l'extraction: {0}",
"utils_unrar_unavailable": "Commande unrar non disponible",
"utils_rar_list_failed": "Échec de la liste des fichiers RAR: {0}"
"utils_rar_list_failed": "Échec de la liste des fichiers RAR: {0}",
"controls_mapping_title": "Controls configuration",
"controls_mapping_instruction": "Hold for 3s to configure:",
"controls_mapping_waiting": "Waiting for a key or button...",
"controls_mapping_press": "Press a key or button"
}

View File

@@ -354,65 +354,76 @@ def extract_zip(zip_path, dest_dir, url):
zip_ref.testzip() # Vérifier l'intégrité de l'archive
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
logger.info(f"Taille totale à extraire: {total_size} octets")
if total_size == 0:
logger.warning("ZIP vide ou ne contenant que des dossiers")
return True, "ZIP vide extrait avec succès"
lock = threading.Lock()
# Lister les ISO avant extraction
iso_before = set()
for root, dirs, files in os.walk(dest_dir):
for file in files:
if file.lower().endswith('.iso'):
iso_before.add(os.path.abspath(os.path.join(root, file)))
extracted_size = 0
os.makedirs(dest_dir, exist_ok=True)
chunk_size = 2048 # Réduire pour plus de mises à jour
last_save_time = time.time()
save_interval = 0.5 # Sauvegarder toutes les 0.5 secondes
for info in zip_ref.infolist():
if info.is_dir():
continue
file_path = os.path.join(dest_dir, info.filename)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
file_size = info.file_size
file_extracted = 0
while True:
chunk = source.read(chunk_size)
if not chunk:
break
dest.write(chunk)
file_extracted += len(chunk)
extracted_size += len(chunk)
current_time = time.time()
with lock:
# Vérifier si config.history est une liste avant d'itérer
if isinstance(config.history, list):
for entry in config.history:
# Vérifier si l'entrée a les clés nécessaires et correspond à notre téléchargement
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
# Chercher par URL si disponible
if "url" in entry and entry["url"] == url:
# Calculer le pourcentage correctement et le limiter entre 0 et 100
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
entry["status"] = "Extracting"
entry["progress"] = progress_percent
entry["message"] = "Extraction en cours"
if current_time - last_save_time >= save_interval:
save_history(config.history)
last_save_time = current_time
# logger.debug(f"Extraction en cours: {info.filename}, file_extracted={file_extracted}/{file_size}, total_extracted={extracted_size}/{total_size}, progression={progress_percent:.1f}%")
config.needs_redraw = True
break
os.chmod(file_path, 0o644)
# Vérifier si c'est un dossier xbox et le traiter si nécessaire
xbox_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), "xbox")
if dest_dir == xbox_dir:
success, error_msg = handle_xbox(dest_dir)
if not success:
return False, error_msg
for root, dirs, files in os.walk(dest_dir):
for dir_name in dirs:
os.chmod(os.path.join(root, dir_name), 0o755)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.testzip() # Vérifier l'intégrité de l'archive
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
logger.info(f"Taille totale à extraire: {total_size} octets")
if total_size == 0:
logger.warning("ZIP vide ou ne contenant que des dossiers")
return True, "ZIP vide extrait avec succès"
extracted_size = 0
os.makedirs(dest_dir, exist_ok=True)
chunk_size = 2048 # Réduire pour plus de mises à jour
last_save_time = time.time()
save_interval = 0.5 # Sauvegarder toutes les 0.5 secondes
for info in zip_ref.infolist():
if info.is_dir():
continue
file_path = os.path.join(dest_dir, info.filename)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
file_size = info.file_size
file_extracted = 0
while True:
chunk = source.read(chunk_size)
if not chunk:
break
dest.write(chunk)
file_extracted += len(chunk)
extracted_size += len(chunk)
current_time = time.time()
with lock:
if isinstance(config.history, list):
for entry in config.history:
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
if "url" in entry and entry["url"] == url:
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
progress_percent = max(0, min(100, progress_percent))
entry["status"] = "Extracting"
entry["progress"] = progress_percent
entry["message"] = "Extraction en cours"
if current_time - last_save_time >= save_interval:
save_history(config.history)
last_save_time = current_time
config.needs_redraw = True
break
os.chmod(file_path, 0o644)
# Vérifier si c'est un dossier xbox et le traiter si nécessaire
xbox_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), "xbox")
if dest_dir == xbox_dir:
# Lister les ISO après extraction
iso_after = set()
for root, dirs, files in os.walk(dest_dir):
for file in files:
if file.lower().endswith('.iso'):
iso_after.add(os.path.abspath(os.path.join(root, file)))
new_isos = list(iso_after - iso_before)
if new_isos:
success, error_msg = handle_xbox(dest_dir, new_isos)
if not success:
return False, error_msg
else:
logger.warning("Aucun nouvel ISO détecté après extraction pour conversion Xbox.")
# On ne retourne pas d'erreur fatale ici, on continue
try:
os.remove(zip_path)
@@ -657,7 +668,7 @@ def handle_ps3(dest_dir):
return True, None
def handle_xbox(dest_dir):
def handle_xbox(dest_dir, iso_files):
"""Gère la conversion des fichiers Xbox extraits."""
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")