diff --git a/ports/RGSX/__main__.py b/ports/RGSX/__main__.py index e4d0708..c826d1a 100644 --- a/ports/RGSX/__main__.py +++ b/ports/RGSX/__main__.py @@ -26,7 +26,7 @@ from controls import handle_controls, validate_menu_state, process_key_repeats, from controls_mapper import load_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_music_config + play_random_music, load_music_config, silence_alsa_warnings, enable_alsa_stderr_filter ) from history import load_history, save_history from config import OTA_data_ZIP @@ -77,6 +77,7 @@ def _run_windows_gamelist_update(): _run_windows_gamelist_update() + # Initialisation de Pygame pygame.init() pygame.joystick.init() @@ -84,6 +85,15 @@ logger.debug("------------------------------------------------------------------ logger.debug("---------------------------DEBUT LOG--------------------------------") logger.debug("--------------------------------------------------------------------") +# Nettoyage des anciens fichiers de paramètres au démarrage +try: + from rgsx_settings import delete_old_files + delete_old_files() + logger.info("Nettoyage des anciens fichiers effectué au démarrage") +except Exception as e: + logger.exception(f"Échec du nettoyage des anciens fichiers: {e}") + + #Récupération des noms des joysticks si pas de joystick connecté, verifier si clavier connecté joystick_names = [pygame.joystick.Joystick(i).get_name() for i in range(pygame.joystick.get_count())] if not joystick_names: @@ -186,6 +196,13 @@ config.repeat_last_action = 0 # Initialisation des variables pour la popup de musique +# Réduction du bruit ALSA (VM Batocera/alsa) +try: + silence_alsa_warnings() + enable_alsa_stderr_filter() +except Exception: + pass + # Initialisation du mixer Pygame pygame.mixer.pre_init(44100, -16, 2, 4096) pygame.mixer.init() diff --git a/ports/RGSX/config.py b/ports/RGSX/config.py index cb5ee02..72ad4dd 100644 --- a/ports/RGSX/config.py +++ b/ports/RGSX/config.py @@ -2,10 +2,9 @@ import pygame # type: ignore import os import logging import platform -from rgsx_settings import load_rgsx_settings, save_rgsx_settings # Version actuelle de l'application -app_version = "2.1.0.0" +app_version = "2.1.0.1" def get_operating_system(): """Renvoie le nom du système d'exploitation.""" diff --git a/ports/RGSX/network.py b/ports/RGSX/network.py index c2dcf0c..cf7db4c 100644 --- a/ports/RGSX/network.py +++ b/ports/RGSX/network.py @@ -275,7 +275,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder) # Spécifique: si le système est "BIOS" on force le dossier BIOS - if platform == "- BIOS by TMCTV": + if platform_folder == "bios" or platform == "BIOS" or platform == "- BIOS by TMCTV -": dest_dir = config.RETROBAT_DATA_FOLDER logger.debug(f"Plateforme 'BIOS' détectée, destination forcée vers RETROBAT_DATA_FOLDER: {dest_dir}") @@ -417,7 +417,18 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas os.chmod(dest_path, 0o644) logger.debug(f"Téléchargement terminé: {dest_path}") - if is_zip_non_supported: + # Forcer extraction si plateforme BIOS même si le pré-check ne l'avait pas marqué + force_extract = is_zip_non_supported + if not force_extract: + try: + bios_like = {"BIOS", "- BIOS by TMCTV -", "- BIOS"} + if platform_folder == "bios" or platform in bios_like: + force_extract = True + logger.debug("Extraction forcée activée pour BIOS") + except Exception: + pass + + if force_extract: logger.debug(f"Extraction automatique nécessaire pour {dest_path}") extension = os.path.splitext(dest_path)[1].lower() if extension == ".zip": @@ -559,10 +570,10 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported= dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder) logger.debug(f"Répertoire destination déterminé: {dest_dir}") - # Spécifique: si le système est "00 BIOS" on force le dossier BIOS - if platform == "00 BIOS": + # Spécifique: si le système est "- BIOS by TMCTV -" on force le dossier BIOS + if platform_folder == "bios" or platform == "BIOS" or platform == "- BIOS by TMCTV -": dest_dir = config.RETROBAT_DATA_FOLDER - logger.debug(f"Plateforme '00 BIOS' détectée, destination forcée vers RETROBAT_DATA_FOLDER: {dest_dir}") + logger.debug(f"Plateforme '- BIOS by TMCTV -' détectée, destination forcée vers RETROBAT_DATA_FOLDER: {dest_dir}") logger.debug(f"Vérification répertoire destination: {dest_dir}") os.makedirs(dest_dir, exist_ok=True) diff --git a/ports/RGSX/rgsx_settings.py b/ports/RGSX/rgsx_settings.py index d3342fa..0ef5480 100644 --- a/ports/RGSX/rgsx_settings.py +++ b/ports/RGSX/rgsx_settings.py @@ -1,20 +1,50 @@ #!/usr/bin/env python3 -""" -Module de gestion des paramètres RGSX -Gère le fichier unifié rgsx_settings.json qui remplace les anciens fichiers : -- accessibility.json -- language.json -- music_config.json -- symlink_settings.json -""" - import json import os import logging +import config logger = logging.getLogger(__name__) + +#Fonction pour supprimer les anciens fichiers de paramètres non utilisés +def delete_old_files(): + old_files_saves = [ + "accessibility.json", + "language.json", + "music_config.json", + "symlink_settings.json", + "sources.json" + ] + old_files_app = [ + "rom_extensions.json", + "es_input_parser.py", + "sources.json" + ] + + + for filename in old_files_saves: + file_path = os.path.join(config.SAVE_FOLDER, filename) + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"Ancien fichier supprimé : {file_path}") + logger.info(f"Ancien fichier supprimé : {file_path}") + except Exception as e: + print(f"Erreur lors de la suppression de {file_path} : {str(e)}") + logger.error(f"Erreur lors de la suppression de {file_path} : {str(e)}") + for filename in old_files_app: + file_path = os.path.join(config.APP_FOLDER, filename) + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"Ancien fichier supprimé : {file_path}") + logger.info(f"Ancien fichier supprimé : {file_path}") + except Exception as e: + print(f"Erreur lors de la suppression de {file_path} : {str(e)}") + logger.error(f"Erreur lors de la suppression de {file_path} : {str(e)}") + def load_rgsx_settings(): """Charge tous les paramètres depuis rgsx_settings.json.""" from config import RGSX_SETTINGS_PATH diff --git a/ports/RGSX/utils.py b/ports/RGSX/utils.py index cc5d6c3..a24a69d 100644 --- a/ports/RGSX/utils.py +++ b/ports/RGSX/utils.py @@ -31,6 +31,114 @@ unavailable_systems = [] # Cache/process flags for extensions generation/loading +def silence_alsa_warnings(): + """Silence ALSA stderr spam (e.g., 'underrun occurred') on Linux by overriding the error handler. + + Safe no-op on non-Linux or if libasound is unavailable. + """ + try: + if platform.system() == "Linux": + import ctypes + import ctypes.util + lib = ctypes.util.find_library('asound') + if not lib: + return + asound = ctypes.CDLL(lib) + CErrorHandler = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p) + + def py_error_handler(filename, line, function, err, fmt): + return + + handler = CErrorHandler(py_error_handler) + try: + asound.snd_lib_error_set_handler(handler) + logger.info("ALSA warnings silenced via snd_lib_error_set_handler") + except Exception as inner: + logger.debug(f"snd_lib_error_set_handler not available: {inner}") + except Exception as e: + logger.debug(f"Unable to silence ALSA warnings: {e}") + + +def enable_alsa_stderr_filter(): + """Filter ALSA 'underrun occurred' spam from stderr by intercepting FD 2. + + Works on Linux by routing stderr through a pipe and dropping matching lines. + No-op on non-Linux systems. Safe to call multiple times; installs once. + """ + try: + if platform.system() != "Linux": + return + # Avoid double-install + if getattr(config, "_alsa_filter_installed", False): + return + + import os as _os + import threading as _threading + + patterns = [ + "ALSA lib pcm.c:", + "snd_pcm_recover) underrun occurred", + ] + + # Save original stderr fd and create pipe + save_fd = _os.dup(2) + rfd, wfd = _os.pipe() + _os.dup2(wfd, 2) # redirect current process stderr to pipe writer + _os.close(wfd) + + stop_event = _threading.Event() + + def _reader(): + try: + with _os.fdopen(rfd, 'rb', buffering=0) as r, _os.fdopen(save_fd, 'wb', buffering=0) as orig: + buf = b'' + while not stop_event.is_set(): + chunk = r.read(1024) + if not chunk: + break + buf += chunk + while b"\n" in buf: + line, buf = buf.split(b"\n", 1) + try: + s = line.decode('utf-8', errors='ignore') + if not any(p in s for p in patterns): + orig.write(line + b"\n") + orig.flush() + except Exception: + # Swallow any decode/write errors; keep filtering + pass + if buf: + try: + s = buf.decode('utf-8', errors='ignore') + if not any(p in s for p in patterns): + orig.write(buf) + orig.flush() + except Exception: + pass + except Exception as e: + try: + # Best-effort: restore original stderr on failure + _os.dup2(save_fd, 2) + except Exception: + pass + logger.debug(f"ALSA stderr filter reader error: {e}") + + t = _threading.Thread(target=_reader, daemon=True) + t.start() + + def _restore(): + try: + _os.dup2(save_fd, 2) + except Exception: + pass + stop_event.set() + + # Expose restore in config for future use if needed + config._alsa_filter_installed = True + config._alsa_filter_restore = _restore + logger.info("ALSA underrun stderr filter installed") + except Exception as e: + logger.debug(f"Unable to install ALSA stderr filter: {e}") def restart_application(delay_ms: int = 2000): """Schedule a restart with a visible popup; actual restart happens in the main loop. @@ -268,6 +376,15 @@ def check_extension_before_download(url, platform, game_name): dest_folder_name = _get_dest_folder_name(platform) system_known = any(s.get("folder") == dest_folder_name for s in extensions_data) + # Traitement spécifique BIOS: forcer extraction des archives même si le système n'est pas connu + try: + bios_like = {"BIOS", "- BIOS by TMCTV -", "- BIOS"} + if (dest_folder_name == "bios" or platform in bios_like) and is_archive: + logger.debug(f"Plateforme BIOS détectée pour {sanitized_name}, extraction auto forcée pour {extension}") + return (url, platform, game_name, True) + except Exception: + pass + if is_supported: logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}") return (url, platform, game_name, False)