From 2e9785e4e1190fe385fa163ed45186d1fd6a260b Mon Sep 17 00:00:00 2001 From: skymike03 Date: Wed, 3 Sep 2025 18:38:15 +0200 Subject: [PATCH] v1.9.9.4 - implement custom source mode (test) --- ports/RGSX/__main__.py | 107 +++++++++++++++++++++++------------ ports/RGSX/config.py | 4 +- ports/RGSX/controls.py | 30 +++++++--- ports/RGSX/display.py | 39 +++++++------ ports/RGSX/languages/de.json | 9 +++ ports/RGSX/languages/en.json | 9 +++ ports/RGSX/languages/es.json | 9 +++ ports/RGSX/languages/fr.json | 8 +++ ports/RGSX/rgsx_settings.py | 39 +++++++++++++ 9 files changed, 190 insertions(+), 64 deletions(-) diff --git a/ports/RGSX/__main__.py b/ports/RGSX/__main__.py index 2560f1b..cb71b89 100644 --- a/ports/RGSX/__main__.py +++ b/ports/RGSX/__main__.py @@ -27,6 +27,7 @@ from utils import ( ) from history import load_history, save_history from config import OTA_data_ZIP +from rgsx_settings import get_sources_mode, get_custom_sources_url, get_sources_zip_url from accessibility import load_accessibility_settings # Configuration du logging @@ -64,6 +65,10 @@ for i, scale in enumerate(config.font_scale_options): # Chargement et initialisation de la langue from language import initialize_language initialize_language() +# Initialiser le mode sources et URL personnalisée +config.sources_mode = get_sources_mode() +config.custom_sources_url = get_custom_sources_url() +logger.debug(f"Mode sources initial: {config.sources_mode}, URL custom: {config.custom_sources_url}") # Détection du système non-PC config.is_non_pc = detect_non_pc() @@ -624,6 +629,10 @@ async def main(): config.needs_redraw = True logger.error(f"État de menu non valide détecté: {config.menu_state}, retour à platform") draw_controls(screen, config.menu_state, getattr(config, 'current_music_name', None), getattr(config, 'music_popup_start_time', 0)) + + # Popup générique (affiché dans n'importe quel état si timer actif), sauf si un état popup dédié déjà l'affiche + if config.popup_timer > 0 and config.popup_message and config.menu_state not in ["update_result", "restart_popup"]: + draw_popup(screen) pygame.display.flip() @@ -722,45 +731,60 @@ async def main(): try: zip_path = os.path.join(config.SAVE_FOLDER, "data_download.zip") headers = {'User-Agent': 'Mozilla/5.0'} - with requests.get(OTA_data_ZIP, stream=True, headers=headers, timeout=30) as response: - response.raise_for_status() - total_size = int(response.headers.get('content-length', 0)) - logger.debug(f"Taille totale du ZIP : {total_size} octets") - downloaded = 0 - os.makedirs(os.path.dirname(zip_path), exist_ok=True) - with open(zip_path, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - downloaded += len(chunk) - config.download_progress[OTA_data_ZIP] = { - "downloaded_size": downloaded, - "total_size": total_size, - "status": "Téléchargement", - "progress_percent": (downloaded / total_size * 100) if total_size > 0 else 0 - } - config.loading_progress = 15.0 + (35.0 * downloaded / total_size) if total_size > 0 else 15.0 - config.needs_redraw = True - await asyncio.sleep(0) - logger.debug(f"ZIP téléchargé : {zip_path}") - - config.current_loading_system = _("loading_extracting_data") - config.loading_progress = 60.0 - config.needs_redraw = True - dest_dir = config.SAVE_FOLDER - success, message = extract_zip_data(zip_path, dest_dir, OTA_data_ZIP) - if success: - logger.debug(f"Extraction réussie : {message}") - config.loading_progress = 70.0 - config.needs_redraw = True + # Déterminer l'URL à utiliser selon le mode (RGSX ou custom) + sources_zip_url = get_sources_zip_url(OTA_data_ZIP) + if sources_zip_url is None: + # Mode custom sans URL valide -> pas de téléchargement, jeux vides + logger.warning("Mode custom actif mais aucune URL valide fournie. Liste de jeux vide.") + config.popup_message = _("sources_mode_custom_missing_url").format(config.RGSX_SETTINGS_PATH) + config.popup_timer = 5000 else: - raise Exception(f"Échec de l'extraction : {message}") + try: + with requests.get(sources_zip_url, stream=True, headers=headers, timeout=30) as response: + response.raise_for_status() + total_size = int(response.headers.get('content-length', 0)) + logger.debug(f"Taille totale du ZIP : {total_size} octets") + downloaded = 0 + os.makedirs(os.path.dirname(zip_path), exist_ok=True) + with open(zip_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + downloaded += len(chunk) + config.download_progress[sources_zip_url] = { + "downloaded_size": downloaded, + "total_size": total_size, + "status": "Téléchargement", + "progress_percent": (downloaded / total_size * 100) if total_size > 0 else 0 + } + config.loading_progress = 15.0 + (35.0 * downloaded / total_size) if total_size > 0 else 15.0 + config.needs_redraw = True + await asyncio.sleep(0) + logger.debug(f"ZIP téléchargé : {zip_path}") + + config.current_loading_system = _("loading_extracting_data") + config.loading_progress = 60.0 + config.needs_redraw = True + dest_dir = config.SAVE_FOLDER + success, message = extract_zip_data(zip_path, dest_dir, sources_zip_url) + if success: + logger.debug(f"Extraction réussie : {message}") + config.loading_progress = 70.0 + config.needs_redraw = True + else: + raise Exception(f"Échec de l'extraction : {message}") + except Exception as de: + logger.error(f"Erreur téléchargement custom source: {de}") + config.popup_message = _("sources_mode_custom_download_error") + config.popup_timer = 5000 + # Pas d'arrêt : continuer avec jeux vides except Exception as e: logger.error(f"Erreur lors du téléchargement/extraction du Dossier Data : {str(e)}") - config.menu_state = "error" - # Message UI générique (les détails restent dans les logs) - config.error_message = _("error_extract_data_failed") - config.needs_redraw = True + # En mode custom on ne bloque pas le chargement ; en mode RGSX (sources_zip_url non None et OTA) on affiche une erreur + if sources_zip_url is not None: + config.menu_state = "error" + config.error_message = _("error_extract_data_failed") + config.needs_redraw = True loading_step = "load_sources" if os.path.exists(zip_path): os.remove(zip_path) @@ -797,6 +821,17 @@ async def main(): config.needs_redraw = True logger.debug("Transition terminée, passage à game") + # Mise à jour du timer popup générique (en dehors des états spéciaux) AVANT mise à jour last_frame_time + if config.popup_timer > 0 and config.popup_message and config.menu_state not in ["update_result", "restart_popup"]: + delta = current_time - config.last_frame_time + if delta > 0: + config.popup_timer -= delta + # Forcer redraw pour mettre à jour le compte à rebours + config.needs_redraw = True + if config.popup_timer <= 0: + config.popup_timer = 0 + config.popup_message = "" + # Mettre à jour last_frame_time après tous les calculs dépendants config.last_frame_time = current_time clock.tick(60) await asyncio.sleep(0.01) diff --git a/ports/RGSX/config.py b/ports/RGSX/config.py index 70cf932..e4d4531 100644 --- a/ports/RGSX/config.py +++ b/ports/RGSX/config.py @@ -5,7 +5,7 @@ import platform from rgsx_settings import load_rgsx_settings, save_rgsx_settings, migrate_old_settings # Version actuelle de l'application -app_version = "1.9.9.3" +app_version = "1.9.9.4" def get_operating_system(): """Renvoie le nom du système d'exploitation.""" @@ -147,6 +147,8 @@ transition_duration = 18 games_count = {} music_enabled = True # Par défaut la musique est activée API_KEY_1FICHIER = "" # Initialisation de la variable globale pour la clé API +sources_mode = "rgsx" # Mode des sources de jeux (rgsx/custom) +custom_sources_url = "" # URL personnalisée si mode custom # Variables pour la sélection de langue selected_language_index = 0 diff --git a/ports/RGSX/controls.py b/ports/RGSX/controls.py index 7df1676..c06c7d1 100644 --- a/ports/RGSX/controls.py +++ b/ports/RGSX/controls.py @@ -994,7 +994,9 @@ def handle_controls(event, sources, joystick, screen): config.selected_option = max(0, config.selected_option - 1) config.needs_redraw = True elif is_input_matched(event, "down"): - config.selected_option = min(8, config.selected_option + 1) # 9 options maintenant (0-8) + # Nombre d'options dynamique (inclut éventuellement l'option source des jeux) + total = getattr(config, 'pause_menu_total_options', 9) # fallback 9 + config.selected_option = min(total - 1, config.selected_option + 1) config.needs_redraw = True elif is_input_matched(event, "confirm"): if config.selected_option == 0: # Controls @@ -1034,18 +1036,32 @@ def handle_controls(event, sources, joystick, screen): config.menu_state = "accessibility_menu" config.needs_redraw = True logger.debug("Passage au menu accessibilité") - elif config.selected_option == 5: # Redownload game cache + elif config.selected_option == 5: # Source toggle + try: + from rgsx_settings import get_sources_mode, set_sources_mode + current_mode = get_sources_mode() + new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx') + config.sources_mode = new_mode + if new_mode == 'custom': + config.popup_message = _("sources_mode_custom_select_info").format(config.RGSX_SETTINGS_PATH) + config.popup_timer = 10000 + else: + config.popup_message = _("sources_mode_rgsx_select_info") + config.popup_timer = 4000 + config.needs_redraw = True + logger.info(f"Changement du mode des sources vers {new_mode}") + except Exception as e: + logger.error(f"Erreur changement mode sources: {e}") + elif config.selected_option == 6: # Redownload game cache config.previous_menu_state = validate_menu_state(config.previous_menu_state) config.menu_state = "redownload_game_cache" config.redownload_confirm_selection = 0 config.needs_redraw = True logger.debug(f"Passage à redownload_game_cache depuis pause_menu") - elif config.selected_option == 6: # Music toggle + elif config.selected_option == 7: # Music toggle config.music_enabled = not config.music_enabled save_music_config() if config.music_enabled: - # Relancer la musique si activée - # Utilise les variables globales si elles existent music_files = getattr(config, "music_files", None) music_folder = getattr(config, "music_folder", None) if music_files and music_folder: @@ -1054,7 +1070,7 @@ def handle_controls(event, sources, joystick, screen): pygame.mixer.music.stop() config.needs_redraw = True logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via menu pause") - elif config.selected_option == 7: # Symlink option + elif config.selected_option == 8: # Symlink option from rgsx_settings import set_symlink_option, get_symlink_option current_status = get_symlink_option() success, message = set_symlink_option(not current_status) @@ -1062,7 +1078,7 @@ def handle_controls(event, sources, joystick, screen): config.popup_timer = 3000 if success else 5000 config.needs_redraw = True logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via menu pause") - elif config.selected_option == 8: # Quit + elif config.selected_option == 9: # Quit config.previous_menu_state = validate_menu_state(config.previous_menu_state) config.menu_state = "confirm_exit" config.confirm_selection = 0 diff --git a/ports/RGSX/display.py b/ports/RGSX/display.py index 0f0bdba..19fbfee 100644 --- a/ports/RGSX/display.py +++ b/ports/RGSX/display.py @@ -1187,33 +1187,27 @@ def draw_language_menu(screen): def draw_pause_menu(screen, selected_option): """Dessine le menu pause avec un style moderne.""" screen.blit(OVERLAY, (0, 0)) - - # Option musique dynamique + from rgsx_settings import get_symlink_option, get_sources_mode + mode = get_sources_mode() + source_label = _("games_source_rgsx") if mode == "rgsx" else _("games_source_custom") if config.music_enabled: music_name = config.current_music_name or "" music_option = _("menu_music_enabled").format(music_name) else: music_option = _("menu_music_disabled") - - # Option symlink dynamique - from rgsx_settings import get_symlink_option - if get_symlink_option(): - symlink_option = _("symlink_option_enabled") - else: - symlink_option = _("symlink_option_disabled") - + symlink_option = _("symlink_option_enabled") if get_symlink_option() else _("symlink_option_disabled") options = [ - _("menu_controls"), - _("menu_remap_controls"), - _("menu_history"), - _("menu_language"), - _("menu_accessibility"), - _("menu_redownload_cache"), - music_option, # Ici l'option dynamique - symlink_option, - _("menu_quit") + _("menu_controls"), # 0 + _("menu_remap_controls"), # 1 + _("menu_history"), # 2 + _("menu_language"), # 3 + _("menu_accessibility"), # 4 + f"{_('menu_games_source_prefix')}: {source_label}", # 5 + _("menu_redownload_cache"), # 6 + music_option, # 7 + symlink_option, # 8 + _("menu_quit") # 9 ] - menu_width = int(config.screen_width * 0.8) line_height = config.font.get_height() + 10 button_height = int(config.screen_height * 0.0463) @@ -1235,6 +1229,8 @@ def draw_pause_menu(screen, selected_option): button_height, selected=i == selected_option ) + # Stocker le nombre total d'options pour la navigation dynamique + config.pause_menu_total_options = len(options) # Menu aide contrôles def draw_controls_help(screen, previous_state): @@ -1313,6 +1309,9 @@ def draw_controls_help(screen, previous_state): else: if cur: line_surf = font.render(cur, True, THEME_COLORS["text"]) + + + wrapped.append((False, line_surf)) total_height += line_surf.get_height() + line_spacing max_width = max(max_width, line_surf.get_width()) diff --git a/ports/RGSX/languages/de.json b/ports/RGSX/languages/de.json index d7f7a89..a246a3a 100644 --- a/ports/RGSX/languages/de.json +++ b/ports/RGSX/languages/de.json @@ -187,4 +187,13 @@ "symlink_option_disabled": "Symlink-Option deaktiviert", "symlink_settings_saved_successfully": "Symlink-Einstellungen erfolgreich gespeichert", "symlink_settings_save_error": "Fehler beim Speichern der Symlink-Einstellungen" + , + "menu_games_source_prefix": "Spielquelle", + "games_source_rgsx": "RGSX", + "sources_mode_rgsx_select_info": "RGSX: Spieleliste aktualisieren", + "games_source_custom": "Benutzerdefiniert" + , + "sources_mode_custom_select_info": "Benutzerdefiniert: URL in {0} setzen und Spieleliste aktualisieren", + "sources_mode_custom_missing_url": "Keine benutzerdefinierte URL gesetzt (bearbeite {0})", + "sources_mode_custom_download_error": "Download der benutzerdefinierten Quelle fehlgeschlagen" } \ No newline at end of file diff --git a/ports/RGSX/languages/en.json b/ports/RGSX/languages/en.json index 4527aca..db22fb3 100644 --- a/ports/RGSX/languages/en.json +++ b/ports/RGSX/languages/en.json @@ -187,4 +187,13 @@ "symlink_option_disabled": "Symlink option disabled", "symlink_settings_saved_successfully": "Symlink settings saved successfully", "symlink_settings_save_error": "Error saving symlink settings" + , + "menu_games_source_prefix": "Game source", + "games_source_rgsx": "RGSX", + "sources_mode_rgsx_select_info": "RGSX: update the games list", + "games_source_custom": "Custom" + , + "sources_mode_custom_select_info": "Custom mode: set URL in {0} then update games list", + "sources_mode_custom_missing_url": "No custom URL set (edit {0})", + "sources_mode_custom_download_error": "Custom source download failed" } \ No newline at end of file diff --git a/ports/RGSX/languages/es.json b/ports/RGSX/languages/es.json index e258fbf..bd55cf1 100644 --- a/ports/RGSX/languages/es.json +++ b/ports/RGSX/languages/es.json @@ -188,4 +188,13 @@ "symlink_option_disabled": "Opción symlink deshabilitada", "symlink_settings_saved_successfully": "Configuración symlink guardada con éxito", "symlink_settings_save_error": "Error al guardar la configuración symlink" + , + "menu_games_source_prefix": "Origen de juegos", + "games_source_rgsx": "RGSX", + "sources_mode_rgsx_select_info": "RGSX: actualizar la lista de juegos", + "games_source_custom": "Personalizado" + , + "sources_mode_custom_select_info": "Modo personalizado: define la URL en {0} y actualiza la lista de juegos", + "sources_mode_custom_missing_url": "No se ha establecido URL personalizada (editar {0})", + "sources_mode_custom_download_error": "Fallo en la descarga de la fuente personalizada" } \ No newline at end of file diff --git a/ports/RGSX/languages/fr.json b/ports/RGSX/languages/fr.json index 3671853..d5963c5 100644 --- a/ports/RGSX/languages/fr.json +++ b/ports/RGSX/languages/fr.json @@ -188,4 +188,12 @@ "symlink_option_disabled": "Option symlink désactivée", "symlink_settings_saved_successfully": "Paramètres symlink sauvegardés avec succès", "symlink_settings_save_error": "Erreur lors de la sauvegarde des paramètres symlink" + , + "menu_games_source_prefix": "Source des jeux", + "games_source_rgsx": "RGSX", + "sources_mode_rgsx_select_info": "RGSX : mettre à jour la liste des jeux", + "games_source_custom": "Personnalisée", + "sources_mode_custom_select_info": "Mode personnalisé : définir l'URL dans {0} puis mettre à jour la liste des jeux", + "sources_mode_custom_missing_url": "Aucune URL personnalisée définie (modifier {0})", + "sources_mode_custom_download_error": "Échec du téléchargement de la source personnalisée" } \ No newline at end of file diff --git a/ports/RGSX/rgsx_settings.py b/ports/RGSX/rgsx_settings.py index 787c2c4..e3a04b1 100644 --- a/ports/RGSX/rgsx_settings.py +++ b/ports/RGSX/rgsx_settings.py @@ -28,6 +28,10 @@ def load_rgsx_settings(): "symlink": { "enabled": False, "target_directory": "" + }, + "sources": { # Nouvelle section pour la source des jeux + "mode": "rgsx", # "rgsx" ou "custom" + "custom_url": "" # URL personnalisée pour le ZIP des sources } } @@ -217,3 +221,38 @@ def apply_symlink_path(base_path, platform_folder): else: # Return original path return os.path.join(base_path, platform_folder) + +# ----------------------- Sources (RGSX / Custom) ----------------------- # + +def get_sources_mode(settings=None): + """Retourne le mode des sources: 'rgsx' (par défaut) ou 'custom'.""" + if settings is None: + settings = load_rgsx_settings() + return settings.get("sources", {}).get("mode", "rgsx") + +def set_sources_mode(mode): + """Définit le mode des sources et sauvegarde le fichier.""" + if mode not in ("rgsx", "custom"): + mode = "rgsx" + settings = load_rgsx_settings() + sources = settings.setdefault("sources", {}) + sources["mode"] = mode + save_rgsx_settings(settings) + return mode + +def get_custom_sources_url(settings=None): + """Retourne l'URL personnalisée configurée (ou chaîne vide).""" + if settings is None: + settings = load_rgsx_settings() + return settings.get("sources", {}).get("custom_url", "").strip() + +def get_sources_zip_url(fallback_url): + """Retourne l'URL ZIP à utiliser selon le mode. Fallback sur l'URL standard si custom invalide.""" + settings = load_rgsx_settings() + if get_sources_mode(settings) == "custom": + custom = get_custom_sources_url(settings) + if custom.startswith("http://") or custom.startswith("https://"): + return custom + # Pas de fallback : retourner None pour signaler une source vide + return None + return fallback_url