From c9fdf93221be64eb8cd5ca702f5acd498cfc0d9d Mon Sep 17 00:00:00 2001 From: skymike03 Date: Wed, 7 Jan 2026 14:25:15 +0100 Subject: [PATCH] v2.4.0.0 (2026.01.07) - add performance mode to disable some 3d effects for a better experience on low-end devices - multi screen support (choose default screen in Pause>Display menu ) - update windows launcher for retrobat - ignore some useless warnings - Merge pull request [#34](https://github.com/RetroGameSets/RGSX/issues/34) from aaronson2012/start-menu-fix (feat: enable circular navigation for pause menu options) --- ports/RGSX/__main__.py | 6 + ports/RGSX/config.py | 2 +- ports/RGSX/controls.py | 145 ++++++++++-- ports/RGSX/display.py | 331 ++++++++++++++++++++------ ports/RGSX/languages/de.json | 14 ++ ports/RGSX/languages/en.json | 14 ++ ports/RGSX/languages/es.json | 14 ++ ports/RGSX/languages/fr.json | 14 ++ ports/RGSX/languages/it.json | 14 +- ports/RGSX/languages/pt.json | 14 ++ ports/RGSX/rgsx_settings.py | 97 +++++++- version.json | 2 +- windows/RGSX Retrobat.bat | 448 ++++++++++++++++++++++++++--------- 13 files changed, 915 insertions(+), 200 deletions(-) diff --git a/ports/RGSX/__main__.py b/ports/RGSX/__main__.py index 7af265d..82f76cf 100644 --- a/ports/RGSX/__main__.py +++ b/ports/RGSX/__main__.py @@ -1,5 +1,11 @@ import os import platform +import warnings + +# Ignorer le warning de deprecation de pkg_resources dans pygame +warnings.filterwarnings("ignore", category=UserWarning, module="pygame.pkgdata") +warnings.filterwarnings("ignore", message="pkg_resources is deprecated") + # Ne pas forcer SDL_FBDEV ici; si déjà défini par l'environnement, on le garde try: if "SDL_FBDEV" in os.environ: diff --git a/ports/RGSX/config.py b/ports/RGSX/config.py index d587c43..4fa876b 100644 --- a/ports/RGSX/config.py +++ b/ports/RGSX/config.py @@ -13,7 +13,7 @@ except Exception: pygame = None # type: ignore # Version actuelle de l'application -app_version = "2.3.3.3" +app_version = "2.4.0.0" def get_application_root(): diff --git a/ports/RGSX/controls.py b/ports/RGSX/controls.py index f34c3e1..b14fa29 100644 --- a/ports/RGSX/controls.py +++ b/ports/RGSX/controls.py @@ -1755,7 +1755,10 @@ def handle_controls(event, sources, joystick, screen): # Sous-menu Display elif config.menu_state == "pause_display_menu": sel = getattr(config, 'pause_display_selection', 0) - total = 6 # layout, font size, footer font size, font family, allow unknown extensions, back + # Windows: layout, font size, footer font size, font family, monitor, mode, light, allow unknown extensions, back (9) + # Linux: layout, font size, footer font size, font family, monitor, light, allow unknown extensions, back (8) + is_windows = config.OPERATING_SYSTEM == "Windows" + total = 9 if is_windows else 8 if is_input_matched(event, "up"): config.pause_display_selection = (sel - 1) % total config.needs_redraw = True @@ -1850,18 +1853,89 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True except Exception as e: logger.error(f"Erreur changement font family: {e}") - # 4 allow unknown extensions - elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + # 4 monitor selection + elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right")): try: - current = get_allow_unknown_extensions() - new_val = set_allow_unknown_extensions(not current) - config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled") - config.popup_timer = 3000 + from rgsx_settings import get_display_monitor, set_display_monitor, get_available_monitors + monitors = get_available_monitors() + num_monitors = len(monitors) + if num_monitors > 1: + current = get_display_monitor() + new_monitor = (current - 1) % num_monitors if is_input_matched(event, "left") else (current + 1) % num_monitors + set_display_monitor(new_monitor) + config.popup_message = _("display_monitor_restart_required") if _ else "Restart required to apply monitor change" + config.popup_timer = 3000 + else: + config.popup_message = _("display_monitor_single_only") if _ else "Only one monitor detected" + config.popup_timer = 2000 config.needs_redraw = True except Exception as e: - logger.error(f"Erreur toggle allow_unknown_extensions: {e}") - # 5 back - elif sel == 5 and is_input_matched(event, "confirm"): + logger.error(f"Erreur changement moniteur: {e}") + # 5 fullscreen/windowed toggle (Windows) or light mode (Linux) + elif sel == 5 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + if is_windows: + # Fullscreen/windowed toggle + try: + from rgsx_settings import get_display_fullscreen, set_display_fullscreen + current = get_display_fullscreen() + new_val = set_display_fullscreen(not current) + config.popup_message = _("display_mode_restart_required") if _ else "Restart required to apply screen mode" + config.popup_timer = 3000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur toggle fullscreen: {e}") + else: + # Linux: light mode toggle + try: + from rgsx_settings import get_light_mode, set_light_mode + current = get_light_mode() + new_val = set_light_mode(not current) + config.popup_message = _("display_light_mode_enabled") if new_val else _("display_light_mode_disabled") + config.popup_timer = 2000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur toggle light mode: {e}") + # 6 light mode (Windows) or allow unknown extensions (Linux) + elif sel == 6 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + if is_windows: + # Windows: light mode toggle + try: + from rgsx_settings import get_light_mode, set_light_mode + current = get_light_mode() + new_val = set_light_mode(not current) + config.popup_message = _("display_light_mode_enabled") if new_val else _("display_light_mode_disabled") + config.popup_timer = 2000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur toggle light mode: {e}") + else: + # Linux: allow unknown extensions + try: + current = get_allow_unknown_extensions() + new_val = set_allow_unknown_extensions(not current) + config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled") + config.popup_timer = 3000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur toggle allow_unknown_extensions: {e}") + # 7 allow unknown extensions (Windows) or back (Linux) + elif sel == 7: + if is_windows and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + try: + current = get_allow_unknown_extensions() + new_val = set_allow_unknown_extensions(not current) + config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled") + config.popup_timer = 3000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur toggle allow_unknown_extensions: {e}") + elif not is_windows and is_input_matched(event, "confirm"): + # Linux: back + config.menu_state = "pause_menu" + config.last_state_change_time = pygame.time.get_ticks() + config.needs_redraw = True + # 8 back (Windows only) + elif sel == 8 and is_windows and is_input_matched(event, "confirm"): config.menu_state = "pause_menu" config.last_state_change_time = pygame.time.get_ticks() config.needs_redraw = True @@ -2053,14 +2127,15 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True logger.debug("Retour au menu pause depuis controls_help") - # Menu Affichage (layout, police, unsupported) + # Menu Affichage (layout, police, moniteur, mode écran, unsupported, extensions, filtres) elif config.menu_state == "display_menu": sel = getattr(config, 'display_menu_selection', 0) + num_options = 7 # Layout, Font, Monitor, Mode, Unsupported, Extensions, Filters if is_input_matched(event, "up"): - config.display_menu_selection = (sel - 1) % 5 + config.display_menu_selection = (sel - 1) % num_options config.needs_redraw = True elif is_input_matched(event, "down"): - config.display_menu_selection = (sel + 1) % 5 + config.display_menu_selection = (sel + 1) % num_options config.needs_redraw = True elif is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm"): # 0: layout change @@ -2106,8 +2181,40 @@ def handle_controls(event, sources, joystick, screen): except Exception as e: logger.error(f"Erreur init polices: {e}") config.needs_redraw = True - # 2: toggle unsupported - elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + # 2: monitor selection (new) + elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right")): + try: + from rgsx_settings import get_display_monitor, set_display_monitor, get_available_monitors + monitors = get_available_monitors() + num_monitors = len(monitors) + if num_monitors > 1: + current = get_display_monitor() + new_monitor = (current - 1) % num_monitors if is_input_matched(event, "left") else (current + 1) % num_monitors + set_display_monitor(new_monitor) + config.needs_redraw = True + # Informer l'utilisateur qu'un redémarrage est nécessaire + config.popup_message = _("display_monitor_restart_required") + config.popup_timer = 3000 + else: + config.popup_message = _("display_monitor_single_only") + config.popup_timer = 2000 + config.needs_redraw = True + except Exception as e: + logger.error(f"Erreur changement moniteur: {e}") + # 3: fullscreen/windowed toggle (new) + elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + try: + from rgsx_settings import get_display_fullscreen, set_display_fullscreen + current = get_display_fullscreen() + new_val = set_display_fullscreen(not current) + config.needs_redraw = True + # Informer l'utilisateur qu'un redémarrage est nécessaire + config.popup_message = _("display_mode_restart_required") + config.popup_timer = 3000 + except Exception as e: + logger.error(f"Erreur toggle fullscreen: {e}") + # 4: toggle unsupported (was 2) + elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): try: current = get_show_unsupported_platforms() new_val = set_show_unsupported_platforms(not current) @@ -2117,8 +2224,8 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True except Exception as e: logger.error(f"Erreur toggle unsupported: {e}") - # 3: toggle allow unknown extensions - elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): + # 5: toggle allow unknown extensions (was 3) + elif sel == 5 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")): try: current = get_allow_unknown_extensions() new_val = set_allow_unknown_extensions(not current) @@ -2127,8 +2234,8 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True except Exception as e: logger.error(f"Erreur toggle allow_unknown_extensions: {e}") - # 4: open filter platforms menu - elif sel == 4 and (is_input_matched(event, "confirm") or is_input_matched(event, "right")): + # 6: open filter platforms menu (was 4) + elif sel == 6 and (is_input_matched(event, "confirm") or is_input_matched(event, "right")): # Remember return target so the filter menu can go back to display config.filter_return_to = "display_menu" config.menu_state = "filter_platforms" diff --git a/ports/RGSX/display.py b/ports/RGSX/display.py index e485bd9..631ee33 100644 --- a/ports/RGSX/display.py +++ b/ports/RGSX/display.py @@ -225,26 +225,125 @@ THEME_COLORS = { # Général, résolution, overlay def init_display(): - """Initialise l'écran et les ressources globales.""" + """Initialise l'écran et les ressources globales. + Supporte la sélection de moniteur et le mode fenêtré/plein écran. + Compatible Windows et Linux (Batocera). + """ global OVERLAY + import platform + import os + from rgsx_settings import get_display_monitor, get_display_fullscreen, load_rgsx_settings + logger.debug("Initialisation de l'écran") - display_info = pygame.display.Info() - screen_width = display_info.current_w - screen_height = display_info.current_h - screen = pygame.display.set_mode((screen_width, screen_height)) + + # Charger les paramètres d'affichage avec debug + settings = load_rgsx_settings() + logger.debug(f"Settings chargés: display={settings.get('display', {})}") + target_monitor = settings.get("display", {}).get("monitor", 0) + fullscreen = settings.get("display", {}).get("fullscreen", True) + + logger.debug(f"Paramètres lus: monitor={target_monitor}, fullscreen={fullscreen}") + + # Vérifier les variables d'environnement (priorité sur les settings) + env_display = os.environ.get("RGSX_DISPLAY") + env_windowed = os.environ.get("RGSX_WINDOWED") + if env_display is not None: + try: + target_monitor = int(env_display) + logger.debug(f"Override par RGSX_DISPLAY: monitor={target_monitor}") + except ValueError: + pass + if env_windowed == "1": + fullscreen = False + logger.debug("Override par RGSX_WINDOWED: fullscreen=False") + + logger.debug(f"Configuration finale: monitor={target_monitor}, fullscreen={fullscreen}") + + # Configurer SDL pour utiliser le bon moniteur + # Cette variable d'environnement doit être définie AVANT la création de la fenêtre + os.environ["SDL_VIDEO_FULLSCREEN_HEAD"] = str(target_monitor) + + # Obtenir les informations d'affichage + num_displays = 1 + try: + num_displays = pygame.display.get_num_displays() + except Exception: + pass + + # S'assurer que le moniteur cible existe + if target_monitor >= num_displays: + logger.warning(f"Monitor {target_monitor} not available, using monitor 0") + target_monitor = 0 + + # Obtenir la résolution du moniteur cible + try: + if hasattr(pygame.display, 'get_desktop_sizes') and num_displays > 1: + desktop_sizes = pygame.display.get_desktop_sizes() + if target_monitor < len(desktop_sizes): + screen_width, screen_height = desktop_sizes[target_monitor] + else: + display_info = pygame.display.Info() + screen_width = display_info.current_w + screen_height = display_info.current_h + else: + display_info = pygame.display.Info() + screen_width = display_info.current_w + screen_height = display_info.current_h + except Exception as e: + logger.error(f"Error getting display info: {e}") + display_info = pygame.display.Info() + screen_width = display_info.current_w + screen_height = display_info.current_h + + # Créer la fenêtre + flags = 0 + if fullscreen: + flags = pygame.FULLSCREEN + # Sur certains systèmes, NOFRAME aide pour le multi-écran + if platform.system() == "Windows": + flags |= pygame.NOFRAME + + try: + screen = pygame.display.set_mode((screen_width, screen_height), flags, display=target_monitor) + except TypeError: + # Anciennes versions de pygame ne supportent pas le paramètre display= + screen = pygame.display.set_mode((screen_width, screen_height), flags) + except Exception as e: + logger.error(f"Error creating display on monitor {target_monitor}: {e}") + screen = pygame.display.set_mode((screen_width, screen_height), flags) + config.screen_width = screen_width config.screen_height = screen_height + config.current_monitor = target_monitor + config.is_fullscreen = fullscreen + # Initialisation de OVERLAY avec effet glassmorphism OVERLAY = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA) OVERLAY.fill((5, 10, 20, 160)) # Bleu très foncé semi-transparent pour effet verre - logger.debug(f"Écran initialisé avec résolution : {screen_width}x{screen_height}") + logger.debug(f"Écran initialisé: {screen_width}x{screen_height} sur moniteur {target_monitor} (fullscreen={fullscreen})") return screen # Fond d'écran dégradé -def draw_gradient(screen, top_color, bottom_color): - """Dessine un fond dégradé vertical avec des couleurs vibrantes et texture de grain.""" +def draw_gradient(screen, top_color, bottom_color, light_mode=None): + """Dessine un fond dégradé vertical avec des couleurs vibrantes et texture de grain. + En mode light, utilise une couleur unie pour de meilleures performances.""" + from rgsx_settings import get_light_mode + if light_mode is None: + light_mode = get_light_mode() + height = screen.get_height() width = screen.get_width() + + if light_mode: + # Mode light: couleur unie (moyenne des deux couleurs) + avg_color = ( + (top_color[0] + bottom_color[0]) // 2, + (top_color[1] + bottom_color[1]) // 2, + (top_color[2] + bottom_color[2]) // 2 + ) + screen.fill(avg_color) + return + top_color = pygame.Color(*top_color) bottom_color = pygame.Color(*bottom_color) @@ -266,15 +365,25 @@ def draw_gradient(screen, top_color, bottom_color): screen.blit(grain_surface, (0, 0)) -def draw_shadow(surface, rect, offset=6, alpha=120): - """Dessine une ombre portée pour un rectangle.""" +def draw_shadow(surface, rect, offset=6, alpha=120, light_mode=None): + """Dessine une ombre portée pour un rectangle. Désactivé en mode light.""" + from rgsx_settings import get_light_mode + if light_mode is None: + light_mode = get_light_mode() + if light_mode: + return None # Pas d'ombre en mode light shadow = pygame.Surface((rect.width + offset, rect.height + offset), pygame.SRCALPHA) pygame.draw.rect(shadow, (0, 0, 0, alpha), (0, 0, rect.width + offset, rect.height + offset), border_radius=15) return shadow -def draw_glow_effect(screen, rect, color, intensity=80, size=10): - """Dessine un effet de glow autour d'un rectangle.""" +def draw_glow_effect(screen, rect, color, intensity=80, size=10, light_mode=None): + """Dessine un effet de glow autour d'un rectangle. Désactivé en mode light.""" + from rgsx_settings import get_light_mode + if light_mode is None: + light_mode = get_light_mode() + if light_mode: + return # Pas de glow en mode light glow = pygame.Surface((rect.width + size * 2, rect.height + size * 2), pygame.SRCALPHA) for i in range(size): alpha = int(intensity * (1 - i / size)) @@ -284,55 +393,68 @@ def draw_glow_effect(screen, rect, color, intensity=80, size=10): screen.blit(glow, (rect.x - size, rect.y - size)) # Nouvelle fonction pour dessiner un bouton stylisé -def draw_stylized_button(screen, text, x, y, width, height, selected=False): - """Dessine un bouton moderne avec effet de survol, ombre et bordure arrondie.""" - # Ombre portée subtile - shadow_surf = pygame.Surface((width + 6, height + 6), pygame.SRCALPHA) - pygame.draw.rect(shadow_surf, THEME_COLORS["shadow"], (3, 3, width, height), border_radius=12) - screen.blit(shadow_surf, (x - 3, y - 3)) +def draw_stylized_button(screen, text, x, y, width, height, selected=False, light_mode=None): + """Dessine un bouton moderne avec effet de survol, ombre et bordure arrondie. + En mode light, utilise un style simplifié pour de meilleures performances.""" + from rgsx_settings import get_light_mode + if light_mode is None: + light_mode = get_light_mode() button_color = THEME_COLORS["button_hover"] if selected else THEME_COLORS["button_idle"] - button_surface = pygame.Surface((width, height), pygame.SRCALPHA) - - # Fond avec dégradé subtil pour bouton sélectionné - if selected: - # Créer le dégradé - for i in range(height): - ratio = i / height - brightness = 1 + 0.2 * ratio - r = min(255, int(button_color[0] * brightness)) - g = min(255, int(button_color[1] * brightness)) - b = min(255, int(button_color[2] * brightness)) - alpha = button_color[3] if len(button_color) > 3 else 255 - rect = pygame.Rect(0, i, width, 1) - pygame.draw.rect(button_surface, (r, g, b, alpha), rect) - - # Appliquer les coins arrondis avec un masque - mask_surface = pygame.Surface((width, height), pygame.SRCALPHA) - pygame.draw.rect(mask_surface, (255, 255, 255, 255), (0, 0, width, height), border_radius=12) - button_surface.blit(mask_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MIN) + if light_mode: + # Mode light: bouton simple sans effets + pygame.draw.rect(screen, button_color[:3], (x, y, width, height), border_radius=8) + if selected: + # Bordure simple pour indiquer la sélection + pygame.draw.rect(screen, THEME_COLORS["neon"], (x, y, width, height), width=2, border_radius=8) else: - pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12) - - # Reflet en haut - highlight = pygame.Surface((width - 4, height // 3), pygame.SRCALPHA) - highlight.fill(THEME_COLORS["highlight"]) - button_surface.blit(highlight, (2, 2)) - - # Bordure - pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12) - - if selected: - # Effet glow doux pour sélection - glow_surface = pygame.Surface((width + 16, height + 16), pygame.SRCALPHA) - for i in range(6): - alpha = int(40 * (1 - i / 6)) - pygame.draw.rect(glow_surface, (*THEME_COLORS["glow"][:3], alpha), - (i, i, width + 16 - i*2, height + 16 - i*2), border_radius=15) - screen.blit(glow_surface, (x - 8, y - 8)) - - screen.blit(button_surface, (x, y)) + # Mode normal avec tous les effets + # Ombre portée subtile + shadow_surf = pygame.Surface((width + 6, height + 6), pygame.SRCALPHA) + pygame.draw.rect(shadow_surf, THEME_COLORS["shadow"], (3, 3, width, height), border_radius=12) + screen.blit(shadow_surf, (x - 3, y - 3)) + + button_surface = pygame.Surface((width, height), pygame.SRCALPHA) + + # Fond avec dégradé subtil pour bouton sélectionné + if selected: + # Créer le dégradé + for i in range(height): + ratio = i / height + brightness = 1 + 0.2 * ratio + r = min(255, int(button_color[0] * brightness)) + g = min(255, int(button_color[1] * brightness)) + b = min(255, int(button_color[2] * brightness)) + alpha = button_color[3] if len(button_color) > 3 else 255 + rect = pygame.Rect(0, i, width, 1) + pygame.draw.rect(button_surface, (r, g, b, alpha), rect) + + # Appliquer les coins arrondis avec un masque + mask_surface = pygame.Surface((width, height), pygame.SRCALPHA) + pygame.draw.rect(mask_surface, (255, 255, 255, 255), (0, 0, width, height), border_radius=12) + button_surface.blit(mask_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MIN) + else: + pygame.draw.rect(button_surface, button_color, (0, 0, width, height), border_radius=12) + + # Reflet en haut + highlight = pygame.Surface((width - 4, height // 3), pygame.SRCALPHA) + highlight.fill(THEME_COLORS["highlight"]) + button_surface.blit(highlight, (2, 2)) + + # Bordure + pygame.draw.rect(button_surface, THEME_COLORS["border"], (0, 0, width, height), 2, border_radius=12) + + if selected: + # Effet glow doux pour sélection + glow_surface = pygame.Surface((width + 16, height + 16), pygame.SRCALPHA) + for i in range(6): + alpha = int(40 * (1 - i / 6)) + pygame.draw.rect(glow_surface, (*THEME_COLORS["glow"][:3], alpha), + (i, i, width + 16 - i*2, height + 16 - i*2), border_radius=15) + screen.blit(glow_surface, (x - 8, y - 8)) + + screen.blit(button_surface, (x, y)) # Vérifier si le texte dépasse la largeur disponible text_surface = config.font.render(text, True, THEME_COLORS["text"]) @@ -2033,15 +2155,32 @@ def draw_menu_instruction(screen, instruction_text, last_button_bottom=None): logger.error(f"Erreur draw_menu_instruction: {e}") def draw_display_menu(screen): - """Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés).""" + """Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés, moniteur).""" screen.blit(OVERLAY, (0, 0)) # États actuels layout_str = f"{getattr(config, 'GRID_COLS', 3)}x{getattr(config, 'GRID_ROWS', 4)}" font_scale = config.accessibility_settings.get("font_scale", 1.0) - from rgsx_settings import get_show_unsupported_platforms, get_allow_unknown_extensions + from rgsx_settings import (get_show_unsupported_platforms, get_allow_unknown_extensions, + get_display_monitor, get_display_fullscreen, get_available_monitors) show_unsupported = get_show_unsupported_platforms() allow_unknown = get_allow_unknown_extensions() + + # Monitor info + current_monitor = get_display_monitor() + is_fullscreen = get_display_fullscreen() + monitors = get_available_monitors() + num_monitors = len(monitors) + + # Construire le label du moniteur + if num_monitors > 1: + monitor_info = monitors[current_monitor] if current_monitor < num_monitors else monitors[0] + monitor_label = f"{_('display_monitor')}: {monitor_info['name']} ({monitor_info['resolution']})" + else: + monitor_label = f"{_('display_monitor')}: {_('display_monitor_single')}" + + # Label mode écran + fullscreen_label = f"{_('display_mode')}: {_('display_fullscreen') if is_fullscreen else _('display_windowed')}" # Compter les systèmes non supportés actuellement masqués unsupported_list = getattr(config, "unsupported_platforms", []) or [] @@ -2054,11 +2193,13 @@ def draw_display_menu(screen): else: unsupported_label = _("menu_show_unsupported_all_displayed") - # Libellés + # Libellés - ajout des options moniteur et mode écran options = [ f"{_('display_layout')}: {layout_str}", _("accessibility_font_size").format(f"{font_scale:.1f}"), - unsupported_label, + monitor_label, + fullscreen_label, + unsupported_label, _("menu_allow_unknown_ext_on") if allow_unknown else _("menu_allow_unknown_ext_off"), _("menu_filter_platforms"), ] @@ -2279,7 +2420,11 @@ def draw_pause_controls_menu(screen, selected_index): def draw_pause_display_menu(screen, selected_index): from rgsx_settings import ( get_allow_unknown_extensions, - get_font_family + get_font_family, + get_display_monitor, + get_display_fullscreen, + get_available_monitors, + get_light_mode ) # Layout label layouts = [(3,3),(3,4),(4,3),(4,4)] @@ -2309,6 +2454,24 @@ def draw_pause_display_menu(screen, selected_index): fam_label = family_map.get(current_family, current_family) font_family_txt = f"{_('submenu_display_font_family') if _ else 'Font'}: < {fam_label} >" + # Monitor selection + current_monitor = get_display_monitor() + monitors = get_available_monitors() + num_monitors = len(monitors) + if num_monitors > 1: + monitor_info = monitors[current_monitor] if current_monitor < num_monitors else monitors[0] + monitor_value = f"{monitor_info['name']} ({monitor_info['resolution']})" + else: + monitor_value = _('display_monitor_single') if _ else "Single monitor" + monitor_txt = f"{_('display_monitor') if _ else 'Monitor'}: < {monitor_value} >" + + # Fullscreen/Windowed (Windows only) + is_windows = config.OPERATING_SYSTEM == "Windows" + if is_windows: + is_fullscreen = get_display_fullscreen() + mode_value = _('display_fullscreen') if is_fullscreen else _('display_windowed') + mode_txt = f"{_('display_mode') if _ else 'Screen mode'}: < {mode_value} >" + # Allow unknown extensions allow_unknown = get_allow_unknown_extensions() status_unknown = _('status_on') if allow_unknown else _('status_off') @@ -2317,17 +2480,43 @@ def draw_pause_display_menu(screen, selected_index): raw_unknown_label = raw_unknown_label.split('{status}')[0].rstrip(' :') unknown_txt = f"{raw_unknown_label}: < {status_unknown} >" + # Light mode (performance) + light_mode = get_light_mode() + light_status = _('status_on') if light_mode else _('status_off') + light_txt = f"{_('display_light_mode') if _ else 'Light mode'}: < {light_status} >" + back_txt = _("menu_back") if _ else "Back" - options = [layout_txt, font_txt, footer_font_txt, font_family_txt, unknown_txt, back_txt] + + # Build options list - mode only on Windows + # Windows: layout, font, footer, family, monitor, mode, light, unknown, back (9) + # Linux: layout, font, footer, family, monitor, light, unknown, back (8) + if is_windows: + options = [layout_txt, font_txt, footer_font_txt, font_family_txt, monitor_txt, mode_txt, light_txt, unknown_txt, back_txt] + instruction_keys = [ + "instruction_display_layout", + "instruction_display_font_size", + "instruction_display_footer_font_size", + "instruction_display_font_family", + "instruction_display_monitor", + "instruction_display_mode", + "instruction_display_light_mode", + "instruction_display_unknown_ext", + "instruction_generic_back", + ] + else: + options = [layout_txt, font_txt, footer_font_txt, font_family_txt, monitor_txt, light_txt, unknown_txt, back_txt] + instruction_keys = [ + "instruction_display_layout", + "instruction_display_font_size", + "instruction_display_footer_font_size", + "instruction_display_font_family", + "instruction_display_monitor", + "instruction_display_light_mode", + "instruction_display_unknown_ext", + "instruction_generic_back", + ] + _draw_submenu_generic(screen, _("menu_display"), options, selected_index) - instruction_keys = [ - "instruction_display_layout", - "instruction_display_font_size", - "instruction_display_footer_font_size", - "instruction_display_font_family", - "instruction_display_unknown_ext", - "instruction_generic_back", - ] key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None if key: button_height = int(config.screen_height * 0.045) diff --git a/ports/RGSX/languages/de.json b/ports/RGSX/languages/de.json index 9f4b4af..68803b7 100644 --- a/ports/RGSX/languages/de.json +++ b/ports/RGSX/languages/de.json @@ -65,6 +65,17 @@ "menu_accessibility": "Barrierefreiheit", "menu_display": "Anzeige", "display_layout": "Anzeigelayout", + "display_monitor": "Monitor", + "display_monitor_single": "Einzelner Monitor", + "display_monitor_single_only": "Nur ein Monitor erkannt", + "display_monitor_restart_required": "Neustart erforderlich um Monitor zu ändern", + "display_mode": "Anzeigemodus", + "display_fullscreen": "Vollbild", + "display_windowed": "Fenster", + "display_mode_restart_required": "Neustart erforderlich für Modusänderung", + "display_light_mode": "Performance-Modus", + "display_light_mode_enabled": "Performance-Modus aktiviert - Effekte deaktiviert", + "display_light_mode_disabled": "Performance-Modus deaktiviert - Effekte aktiviert", "menu_redownload_cache": "Spieleliste aktualisieren", "menu_music_enabled": "Musik aktiviert: {0}", "menu_music_disabled": "Musik deaktiviert", @@ -196,6 +207,9 @@ "instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen", "instruction_display_footer_font_size": "Fußzeilen-Textgröße anpassen (Version & Steuerelemente)", "instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln", + "instruction_display_monitor": "Monitor für RGSX-Anzeige auswählen", + "instruction_display_mode": "Zwischen Vollbild und Fenstermodus wechseln", + "instruction_display_light_mode": "Performance-Modus für bessere FPS aktivieren", "instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden", "instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten", "instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}", diff --git a/ports/RGSX/languages/en.json b/ports/RGSX/languages/en.json index ece2cf6..7c65ffb 100644 --- a/ports/RGSX/languages/en.json +++ b/ports/RGSX/languages/en.json @@ -65,6 +65,17 @@ "menu_accessibility": "Accessibility", "menu_display": "Display", "display_layout": "Display layout", + "display_monitor": "Monitor", + "display_monitor_single": "Single monitor", + "display_monitor_single_only": "Only one monitor detected", + "display_monitor_restart_required": "Restart required to apply monitor change", + "display_mode": "Screen mode", + "display_fullscreen": "Fullscreen", + "display_windowed": "Windowed", + "display_mode_restart_required": "Restart required to apply screen mode", + "display_light_mode": "Performance mode", + "display_light_mode_enabled": "Performance mode enabled - effects disabled", + "display_light_mode_disabled": "Performance mode disabled - effects enabled", "menu_redownload_cache": "Update games list", "menu_music_enabled": "Music enabled: {0}", "menu_music_disabled": "Music disabled", @@ -198,6 +209,9 @@ "instruction_display_font_size": "Adjust text scale for readability", "instruction_display_footer_font_size": "Adjust footer text scale (version & controls display)", "instruction_display_font_family": "Switch between available font families", + "instruction_display_monitor": "Select which monitor to display RGSX on", + "instruction_display_mode": "Toggle between fullscreen and windowed mode", + "instruction_display_light_mode": "Enable performance mode for better FPS on low-end devices", "instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg", "instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg", "instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}", diff --git a/ports/RGSX/languages/es.json b/ports/RGSX/languages/es.json index 15fb84e..239cc34 100644 --- a/ports/RGSX/languages/es.json +++ b/ports/RGSX/languages/es.json @@ -65,6 +65,17 @@ "menu_accessibility": "Accesibilidad", "menu_display": "Pantalla", "display_layout": "Distribución", + "display_monitor": "Monitor", + "display_monitor_single": "Monitor único", + "display_monitor_single_only": "Solo un monitor detectado", + "display_monitor_restart_required": "Reinicio necesario para cambiar de monitor", + "display_mode": "Modo de pantalla", + "display_fullscreen": "Pantalla completa", + "display_windowed": "Ventana", + "display_mode_restart_required": "Reinicio necesario para cambiar el modo", + "display_light_mode": "Modo rendimiento", + "display_light_mode_enabled": "Modo rendimiento activado - efectos desactivados", + "display_light_mode_disabled": "Modo rendimiento desactivado - efectos activados", "menu_redownload_cache": "Actualizar lista de juegos", "menu_music_enabled": "Música activada: {0}", "menu_music_disabled": "Música desactivada", @@ -198,6 +209,9 @@ "instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad", "instruction_display_footer_font_size": "Ajustar el tamaño del texto del pie de página (versión y controles)", "instruction_display_font_family": "Cambiar entre familias de fuentes disponibles", + "instruction_display_monitor": "Seleccionar monitor para mostrar RGSX", + "instruction_display_mode": "Alternar entre pantalla completa y ventana", + "instruction_display_light_mode": "Activar modo rendimiento para mejores FPS", "instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg", "instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg", "instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}", diff --git a/ports/RGSX/languages/fr.json b/ports/RGSX/languages/fr.json index 0346204..17db977 100644 --- a/ports/RGSX/languages/fr.json +++ b/ports/RGSX/languages/fr.json @@ -65,6 +65,17 @@ "menu_accessibility": "Accessibilité", "menu_display": "Affichage", "display_layout": "Disposition", + "display_monitor": "Écran", + "display_monitor_single": "Écran unique", + "display_monitor_single_only": "Un seul écran détecté", + "display_monitor_restart_required": "Redémarrage requis pour changer d'écran", + "display_mode": "Mode d'affichage", + "display_fullscreen": "Plein écran", + "display_windowed": "Fenêtré", + "display_mode_restart_required": "Redémarrage requis pour changer le mode", + "display_light_mode": "Mode performance", + "display_light_mode_enabled": "Mode performance activé - effets désactivés", + "display_light_mode_disabled": "Mode performance désactivé - effets activés", "menu_redownload_cache": "Mettre à jour la liste des jeux", "menu_support": "Support", "menu_quit": "Quitter", @@ -198,6 +209,9 @@ "instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité", "instruction_display_footer_font_size": "Ajuster la taille du texte du pied de page (version et contrôles)", "instruction_display_font_family": "Basculer entre les polices disponibles", + "instruction_display_monitor": "Sélectionner l'écran pour afficher RGSX", + "instruction_display_mode": "Basculer entre plein écran et fenêtré", + "instruction_display_light_mode": "Activer le mode performance pour de meilleurs FPS", "instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg", "instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg", "instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}", diff --git a/ports/RGSX/languages/it.json b/ports/RGSX/languages/it.json index 9ec2397..11271ad 100644 --- a/ports/RGSX/languages/it.json +++ b/ports/RGSX/languages/it.json @@ -65,7 +65,16 @@ "menu_accessibility": "Accessibilità", "menu_display": "Schermo", "display_layout": "Layout schermo", - "menu_redownload_cache": "Aggiorna elenco giochi", + "display_monitor": "Monitor", + "display_monitor_single": "Monitor singolo", + "display_monitor_single_only": "Rilevato un solo monitor", + "display_monitor_restart_required": "Riavvio necessario per cambiare monitor", + "display_mode": "Modalità schermo", + "display_fullscreen": "Schermo intero", + "display_windowed": "Finestra", + "display_mode_restart_required": "Riavvio necessario per cambiare modalità", "display_light_mode": "Modalità performance", + "display_light_mode_enabled": "Modalità performance attivata - effetti disattivati", + "display_light_mode_disabled": "Modalità performance disattivata - effetti attivati", "menu_redownload_cache": "Aggiorna elenco giochi", "menu_music_enabled": "Musica attivata: {0}", "menu_music_disabled": "Musica disattivata", "menu_restart": "Riavvia RGSX", @@ -195,6 +204,9 @@ "instruction_display_font_size": "Regolare dimensione testo per leggibilità", "instruction_display_footer_font_size": "Regola dimensione testo piè di pagina (versione e controlli)", "instruction_display_font_family": "Cambiare famiglia di font disponibile", + "instruction_display_monitor": "Selezionare monitor per visualizzare RGSX", + "instruction_display_mode": "Alternare tra schermo intero e finestra", + "instruction_display_light_mode": "Attivare modalità performance per FPS migliori", "instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg", "instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg", "instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}", diff --git a/ports/RGSX/languages/pt.json b/ports/RGSX/languages/pt.json index 53d4ac8..4495eb8 100644 --- a/ports/RGSX/languages/pt.json +++ b/ports/RGSX/languages/pt.json @@ -65,6 +65,17 @@ "menu_accessibility": "Acessibilidade", "menu_display": "Exibição", "display_layout": "Layout de exibição", + "display_monitor": "Monitor", + "display_monitor_single": "Monitor único", + "display_monitor_single_only": "Apenas um monitor detectado", + "display_monitor_restart_required": "Reinício necessário para mudar de monitor", + "display_mode": "Modo de tela", + "display_fullscreen": "Tela cheia", + "display_windowed": "Janela", + "display_mode_restart_required": "Reinício necessário para mudar o modo", + "display_light_mode": "Modo performance", + "display_light_mode_enabled": "Modo performance ativado - efeitos desativados", + "display_light_mode_disabled": "Modo performance desativado - efeitos ativados", "menu_redownload_cache": "Atualizar lista de jogos", "menu_music_enabled": "Música ativada: {0}", "menu_music_disabled": "Música desativada", @@ -197,6 +208,9 @@ "instruction_display_font_size": "Ajustar tamanho do texto para legibilidade", "instruction_display_footer_font_size": "Ajustar tamanho do texto do rodapé (versão e controles)", "instruction_display_font_family": "Alternar entre famílias de fontes disponíveis", + "instruction_display_monitor": "Selecionar monitor para exibir RGSX", + "instruction_display_mode": "Alternar entre tela cheia e janela", + "instruction_display_light_mode": "Ativar modo performance para melhor FPS", "instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg", "instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg", "instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}", diff --git a/ports/RGSX/rgsx_settings.py b/ports/RGSX/rgsx_settings.py index ba4fe7f..5225295 100644 --- a/ports/RGSX/rgsx_settings.py +++ b/ports/RGSX/rgsx_settings.py @@ -49,6 +49,8 @@ def load_rgsx_settings(): """Charge tous les paramètres depuis rgsx_settings.json.""" from config import RGSX_SETTINGS_PATH + logger.debug(f"Chargement des settings depuis: {RGSX_SETTINGS_PATH}") + default_settings = { "language": "en", "music_enabled": True, @@ -58,7 +60,10 @@ def load_rgsx_settings(): }, "display": { "grid": "3x4", - "font_family": "pixel" + "font_family": "pixel", + "monitor": 0, + "fullscreen": True, + "light_mode": False }, "symlink": { "enabled": False, @@ -78,13 +83,17 @@ def load_rgsx_settings(): if os.path.exists(RGSX_SETTINGS_PATH): with open(RGSX_SETTINGS_PATH, 'r', encoding='utf-8') as f: settings = json.load(f) + logger.debug(f"Settings JSON chargé: display={settings.get('display', {})}") # Fusionner avec les valeurs par défaut pour assurer la compatibilité for key, value in default_settings.items(): if key not in settings: settings[key] = value return settings + else: + logger.warning(f"Fichier settings non trouvé: {RGSX_SETTINGS_PATH}") except Exception as e: print(f"Erreur lors du chargement de rgsx_settings.json: {str(e)}") + logger.error(f"Erreur chargement settings: {e}") return default_settings @@ -307,6 +316,92 @@ def set_display_grid(cols: int, rows: int): save_rgsx_settings(settings) return cols, rows +# ----------------------- Monitor/Display settings ----------------------- # + +def get_display_monitor(settings=None): + """Retourne l'index du moniteur configuré (par défaut 0 = principal).""" + if settings is None: + settings = load_rgsx_settings() + return settings.get("display", {}).get("monitor", 0) + +def set_display_monitor(monitor_index: int): + """Définit et sauvegarde l'index du moniteur à utiliser.""" + settings = load_rgsx_settings() + disp = settings.setdefault("display", {}) + disp["monitor"] = max(0, int(monitor_index)) + save_rgsx_settings(settings) + return disp["monitor"] + +def get_display_fullscreen(settings=None): + """Retourne True si le mode plein écran est activé.""" + if settings is None: + settings = load_rgsx_settings() + return settings.get("display", {}).get("fullscreen", True) + +def set_display_fullscreen(fullscreen: bool): + """Définit et sauvegarde le mode plein écran.""" + settings = load_rgsx_settings() + disp = settings.setdefault("display", {}) + disp["fullscreen"] = bool(fullscreen) + save_rgsx_settings(settings) + return disp["fullscreen"] + +def get_light_mode(settings=None): + """Retourne True si le mode léger (performance) est activé.""" + if settings is None: + settings = load_rgsx_settings() + return settings.get("display", {}).get("light_mode", False) + +def set_light_mode(enabled: bool): + """Définit et sauvegarde le mode léger (performance).""" + settings = load_rgsx_settings() + disp = settings.setdefault("display", {}) + disp["light_mode"] = bool(enabled) + save_rgsx_settings(settings) + return disp["light_mode"] + +def get_available_monitors(): + """Retourne la liste des moniteurs disponibles avec leurs informations. + Compatible Windows, Linux (Batocera), et autres plateformes. + Retourne une liste de dicts: [{"index": 0, "name": "Monitor 1", "resolution": "1920x1080"}, ...] + """ + monitors = [] + try: + import pygame + if not pygame.display.get_init(): + pygame.display.init() + + num_displays = pygame.display.get_num_displays() + for i in range(num_displays): + try: + # Essayer d'obtenir le mode desktop pour ce display + mode = pygame.display.get_desktop_sizes()[i] if hasattr(pygame.display, 'get_desktop_sizes') else None + if mode: + width, height = mode + else: + # Fallback: utiliser la résolution actuelle si disponible + info = pygame.display.Info() + width, height = info.current_w, info.current_h + + monitors.append({ + "index": i, + "name": f"Monitor {i + 1}", + "resolution": f"{width}x{height}" + }) + except Exception as e: + # Si on ne peut pas obtenir les infos, ajouter quand même le moniteur + monitors.append({ + "index": i, + "name": f"Monitor {i + 1}", + "resolution": "Unknown" + }) + except Exception as e: + logger.error(f"Error getting monitors: {e}") + # Fallback: au moins un moniteur + monitors = [{"index": 0, "name": "Monitor 1 (Default)", "resolution": "Auto"}] + + return monitors if monitors else [{"index": 0, "name": "Monitor 1 (Default)", "resolution": "Auto"}] + def get_font_family(settings=None): if settings is None: settings = load_rgsx_settings() diff --git a/version.json b/version.json index bcdfc06..e5579bb 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "2.3.3.3" + "version": "2.4.0.0" } \ No newline at end of file diff --git a/windows/RGSX Retrobat.bat b/windows/RGSX Retrobat.bat index 293c092..c0da75f 100644 --- a/windows/RGSX Retrobat.bat +++ b/windows/RGSX Retrobat.bat @@ -1,149 +1,385 @@ @echo off setlocal EnableDelayedExpansion -:: Fichier de log -if not exist "%CD%\logs" MD "%CD%\logs" -set "LOG_FILE=%CD%\logs\Retrobat_RGSX_log.txt" -:: Fichier de log (chemin absolu pour fiabilité) -:: Détecter la racine (ROOT_DIR) d'abord pour construire un chemin stable -set CURRENT_DIR=%CD% -pushd "%CURRENT_DIR%\..\.." -set "ROOT_DIR=%CD%" -popd -if not exist "%ROOT_DIR%\roms\windows\logs" MD "%ROOT_DIR%\roms\windows\logs" -set "LOG_FILE=%ROOT_DIR%\roms\windows\logs\Retrobat_RGSX_log.txt" +:: ============================================================================= +:: RGSX Retrobat Launcher v1.3 +:: ============================================================================= +:: Usage: "RGSX Retrobat.bat" [options] +:: --display=N Launch on display N (0=primary, 1=secondary, etc.) +:: --windowed Launch in windowed mode instead of fullscreen +:: --help Show this help +:: ============================================================================= -:: Ajouter un horodatage au début du log -echo [%DATE% %TIME%] Script start >> "%LOG_FILE%" +:: Configuration des couleurs (codes ANSI) +for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do ( + set "ESC=%%b" +) -:: Afficher un message de démarrage +:: Couleurs +set "GREEN=[92m" +set "YELLOW=[93m" +set "RED=[91m" +set "CYAN=[96m" +set "RESET=[0m" +set "BOLD=[1m" + +:: ============================================================================= +:: Traitement des arguments +:: ============================================================================= +set "DISPLAY_NUM=" +set "WINDOWED_MODE=" +set "CONFIG_FILE=" + +:parse_args +if "%~1"=="" goto :args_done +if /i "%~1"=="--help" goto :show_help +if /i "%~1"=="-h" goto :show_help +if /i "%~1"=="--windowed" ( + set "WINDOWED_MODE=1" + shift + goto :parse_args +) +:: Check for --display=N format +echo %~1 | findstr /r "^--display=" >nul +if !ERRORLEVEL! EQU 0 ( + for /f "tokens=2 delims==" %%a in ("%~1") do set "DISPLAY_NUM=%%a" + shift + goto :parse_args +) +shift +goto :parse_args + +:show_help +echo. +echo %ESC%%CYAN%RGSX Retrobat Launcher - Help%ESC%%RESET% +echo. +echo Usage: "RGSX Retrobat.bat" [options] +echo. +echo Options: +echo --display=N Launch on display N (0=primary, 1=secondary, etc.) +echo --windowed Launch in windowed mode instead of fullscreen +echo --help, -h Show this help +echo. +echo Examples: +echo "RGSX Retrobat.bat" Launch on primary display +echo "RGSX Retrobat.bat" --display=1 Launch on secondary display (TV) +echo "RGSX Retrobat.bat" --windowed Launch in windowed mode +echo. +echo You can also create shortcuts with different display settings. +echo. +pause +exit /b 0 + +:args_done + +:: URL de telechargement Python +set "PYTHON_ZIP_URL=https://github.com/RetroGameSets/RGSX/raw/main/windows/python.zip" + +:: Obtenir le chemin du script de maniere fiable +set "SCRIPT_DIR=%~dp0" +set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" + +:: Detecter le repertoire racine +for %%I in ("%SCRIPT_DIR%\..\.." ) do set "ROOT_DIR=%%~fI" + +:: Configuration des logs +set "LOG_DIR=%ROOT_DIR%\roms\windows\logs" +if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" +set "LOG_FILE=%LOG_DIR%\Retrobat_RGSX_log.txt" +set "LOG_BACKUP=%LOG_DIR%\Retrobat_RGSX_log.old.txt" + +:: Rotation des logs avec backup +if exist "%LOG_FILE%" ( + for %%A in ("%LOG_FILE%") do ( + if %%~zA GTR 100000 ( + if exist "%LOG_BACKUP%" del /q "%LOG_BACKUP%" + move /y "%LOG_FILE%" "%LOG_BACKUP%" >nul 2>&1 + echo [%DATE% %TIME%] Log rotated - previous log saved as .old.txt > "%LOG_FILE%" + ) + ) +) + +:: ============================================================================= +:: Ecran d'accueil +:: ============================================================================= cls -echo Running __main__.py for RetroBat... -echo [%DATE% %TIME%] Running __main__.py for RetroBat >> "%LOG_FILE%" +echo. +echo %ESC%%CYAN% ____ ____ ______ __ %ESC%%RESET% +echo %ESC%%CYAN% ^| _ \ / ___^/ ___\ \/ / %ESC%%RESET% +echo %ESC%%CYAN% ^| ^|_) ^| ^| _\___ \\ / %ESC%%RESET% +echo %ESC%%CYAN% ^| _ ^<^| ^|_^| ^|___) / \ %ESC%%RESET% +echo %ESC%%CYAN% ^|_^| \_\\____^|____/_/\_\ %ESC%%RESET% +echo. +echo %ESC%%BOLD% RetroBat Launcher v1.3%ESC%%RESET% +echo -------------------------------- +if "!DISPLAY_NUM!" NEQ "0" ( + echo %ESC%%CYAN%Display: !DISPLAY_NUM!%ESC%%RESET% +) +if "!WINDOWED_MODE!"=="1" ( + echo %ESC%%CYAN%Mode: Windowed%ESC%%RESET% +) +echo. -:: Définir les chemins relatifs et les convertir en absolus -set CURRENT_DIR=%CD% -set PYTHON_EXE=python.exe +:: Debut du log +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" +echo [%DATE% %TIME%] RGSX Launcher v1.3 started >> "%LOG_FILE%" +echo [%DATE% %TIME%] Display: !DISPLAY_NUM!, Windowed: !WINDOWED_MODE! >> "%LOG_FILE%" +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" -:: Détecter le répertoire racine en remontant de deux niveaux depuis le script -pushd "%CURRENT_DIR%\..\.." -set "ROOT_DIR=%CD%" -popd - -:: Définir le chemin du script principal selon les spécifications +:: Configuration des chemins +set "PYTHON_DIR=%ROOT_DIR%\system\tools\Python" +set "PYTHON_EXE=%PYTHON_DIR%\python.exe" set "MAIN_SCRIPT=%ROOT_DIR%\roms\ports\RGSX\__main__.py" +set "ZIP_FILE=%ROOT_DIR%\roms\windows\python.zip" -:: Definir le chemin du script de mise à jour de la gamelist Windows -set "UPDATE_GAMELIST_SCRIPT=%ROOT_DIR%\roms\ports\RGSX\update_gamelist_windows.py" +:: Exporter RGSX_ROOT pour le script Python +set "RGSX_ROOT=%ROOT_DIR%" -:: Convertir les chemins relatifs en absolus avec pushd/popd -pushd "%ROOT_DIR%\system\tools\Python" -set "PYTHON_EXE_FULL=%ROOT_DIR%\system\tools\Python\!PYTHON_EXE!" -set "PYTHONW_EXE_FULL=%ROOT_DIR%\system\tools\Python\pythonw.exe" -popd +:: Logger les chemins +echo [%DATE% %TIME%] System info: >> "%LOG_FILE%" +echo [%DATE% %TIME%] ROOT_DIR: %ROOT_DIR% >> "%LOG_FILE%" +echo [%DATE% %TIME%] PYTHON_EXE: %PYTHON_EXE% >> "%LOG_FILE%" +echo [%DATE% %TIME%] MAIN_SCRIPT: %MAIN_SCRIPT% >> "%LOG_FILE%" +echo [%DATE% %TIME%] RGSX_ROOT: %RGSX_ROOT% >> "%LOG_FILE%" -:: Afficher et logger les variables +:: ============================================================================= +:: Verification Python +:: ============================================================================= +echo %ESC%%YELLOW%[1/3]%ESC%%RESET% Checking Python environment... +echo [%DATE% %TIME%] Step 1/3: Checking Python >> "%LOG_FILE%" -echo ROOT_DIR : %ROOT_DIR% >> "%LOG_FILE%" -echo CURRENT_DIR : !CURRENT_DIR! >> "%LOG_FILE%" -echo ROOT_DIR : !ROOT_DIR! >> "%LOG_FILE%" -echo PYTHON_EXE_FULL : !PYTHON_EXE_FULL! >> "%LOG_FILE%" -echo MAIN_SCRIPT : !MAIN_SCRIPT! >> "%LOG_FILE%" -echo UPDATE_GAMELIST_SCRIPT : !UPDATE_GAMELIST_SCRIPT! >> "%LOG_FILE%" - -:: Vérifier si l'exécutable Python existe -echo Checking python.exe... -echo [%DATE% %TIME%] Checking python.exe at !PYTHON_EXE_FULL! >> "%LOG_FILE%" -if not exist "!PYTHON_EXE_FULL!" ( - echo python.exe not found in system/tools. Preparing to extract.. - echo [%DATE% %TIME%] python.exe not found in system/tools. Preparing to extract.. >> "%LOG_FILE%" +if not exist "%PYTHON_EXE%" ( + echo %ESC%%YELLOW%^> Python not found, installing...%ESC%%RESET% + echo [%DATE% %TIME%] Python not found, starting installation >> "%LOG_FILE%" - :: Créer le dossier Python s'il n'existe pas - set "TOOLS_FOLDER_FULL=!ROOT_DIR!\system\tools" - - if not exist "!TOOLS_FOLDER_FULL!\Python" ( - echo Creating folder !TOOLS_FOLDER_FULL!\Python... - echo [%DATE% %TIME%] Creating folder !TOOLS_FOLDER_FULL!\Python... >> "%LOG_FILE%" - mkdir "!TOOLS_FOLDER_FULL!\Python" + :: Creer le dossier Python + if not exist "%PYTHON_DIR%" ( + mkdir "%PYTHON_DIR%" 2>nul + echo [%DATE% %TIME%] Created folder: %PYTHON_DIR% >> "%LOG_FILE%" ) - set "ZIP_FILE=%ROOT_DIR%\roms\windows\python.zip" - echo Extracting ZIP_FILE : !ZIP_FILE! in /system/tools/Python - echo [%DATE% %TIME%] ZIP_FILE : !ZIP_FILE! >> "%LOG_FILE%" - - if exist "!ZIP_FILE!" ( - echo [%DATE% %TIME%] Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%" - tar -xf "!ZIP_FILE!" -C "!TOOLS_FOLDER_FULL!\Python" --strip-components=0 - echo Extraction finished. - echo [%DATE% %TIME%] Extraction finished. >> "%LOG_FILE%" - del /s /q "!ZIP_FILE!" - echo python.zip file deleted. - echo [%DATE% %TIME%] python.zip file deleted. >> "%LOG_FILE%" - ) else ( - echo Error: Error python.zip not found please download it from github and put in /roms/windows folder. - echo [%DATE% %TIME%] Error: Error python.zip not found please download it from github and put in /roms/windows folder >> "%LOG_FILE%" + :: Verifier si le ZIP existe, sinon le telecharger + if not exist "%ZIP_FILE%" ( + echo %ESC%%YELLOW%^> python.zip not found, downloading from GitHub...%ESC%%RESET% + echo [%DATE% %TIME%] python.zip not found, attempting download >> "%LOG_FILE%" + echo [%DATE% %TIME%] Download URL: %PYTHON_ZIP_URL% >> "%LOG_FILE%" + + :: Verifier si curl est disponible + where curl.exe >nul 2>&1 + if !ERRORLEVEL! EQU 0 ( + echo %ESC%%CYAN%^> Using curl to download...%ESC%%RESET% + echo [%DATE% %TIME%] Using curl.exe for download >> "%LOG_FILE%" + curl.exe -L -# -o "%ZIP_FILE%" "%PYTHON_ZIP_URL%" + set DOWNLOAD_RESULT=!ERRORLEVEL! + ) else ( + :: Fallback sur PowerShell + echo %ESC%%CYAN%^> Using PowerShell to download...%ESC%%RESET% + echo [%DATE% %TIME%] curl not found, using PowerShell >> "%LOG_FILE%" + powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -Uri '%PYTHON_ZIP_URL%' -OutFile '%ZIP_FILE%'" + set DOWNLOAD_RESULT=!ERRORLEVEL! + ) + + :: Verifier le resultat du telechargement + if !DOWNLOAD_RESULT! NEQ 0 ( + echo. + echo %ESC%%RED% ERROR: Download failed!%ESC%%RESET% + echo. + echo Please download python.zip manually from: + echo %ESC%%CYAN%%PYTHON_ZIP_URL%%ESC%%RESET% + echo. + echo And place it in: + echo %ESC%%CYAN%%ROOT_DIR%\roms\windows\%ESC%%RESET% + echo. + echo [%DATE% %TIME%] ERROR: Download failed with code !DOWNLOAD_RESULT! >> "%LOG_FILE%" + goto :error + ) + + :: Verifier que le fichier a bien ete telecharge et n'est pas vide + if not exist "%ZIP_FILE%" ( + echo. + echo %ESC%%RED% ERROR: Download failed - file not created!%ESC%%RESET% + echo [%DATE% %TIME%] ERROR: ZIP file not created after download >> "%LOG_FILE%" + goto :error + ) + + :: Verifier la taille du fichier (doit etre > 1MB pour etre valide) + for %%A in ("%ZIP_FILE%") do set ZIP_SIZE=%%~zA + if !ZIP_SIZE! LSS 1000000 ( + echo. + echo %ESC%%RED% ERROR: Downloaded file appears invalid ^(too small^)!%ESC%%RESET% + echo [%DATE% %TIME%] ERROR: Downloaded file too small: !ZIP_SIZE! bytes >> "%LOG_FILE%" + del /q "%ZIP_FILE%" 2>nul + goto :error + ) + + echo %ESC%%GREEN%^> Download complete ^(!ZIP_SIZE! bytes^)%ESC%%RESET% + echo [%DATE% %TIME%] Download successful: !ZIP_SIZE! bytes >> "%LOG_FILE%" + ) + + :: Verifier que tar existe (Windows 10 1803+) + where tar >nul 2>&1 + if !ERRORLEVEL! NEQ 0 ( + echo. + echo %ESC%%RED% ERROR: tar command not available!%ESC%%RESET% + echo. + echo Please update Windows 10 or extract manually to: + echo %ESC%%CYAN%%PYTHON_DIR%%ESC%%RESET% + echo. + echo [%DATE% %TIME%] ERROR: tar command not found >> "%LOG_FILE%" goto :error ) - :: Vérifier à nouveau si python.exe existe après extraction - if not exist "!PYTHON_EXE_FULL!" ( - echo Error: python.exe not found after extraction at !PYTHON_EXE_FULL!. - echo [%DATE% %TIME%] Error: python.exe not found after extraction at !PYTHON_EXE_FULL! >> "%LOG_FILE%" + :: Extraction avec progression simulee + echo %ESC%%YELLOW%^> Extracting Python...%ESC%%RESET% + echo [%DATE% %TIME%] Extracting python.zip >> "%LOG_FILE%" + + > "%LOG_FILE%" + goto :error + ) + + echo [%DATE% %TIME%] Extraction completed >> "%LOG_FILE%" + + :: Supprimer ZIP + del /q "%ZIP_FILE%" 2>nul + echo %ESC%%GREEN%^> python.zip cleaned up%ESC%%RESET% + echo [%DATE% %TIME%] python.zip deleted >> "%LOG_FILE%" + + :: Verifier installation + if not exist "%PYTHON_EXE%" ( + echo. + echo %ESC%%RED% ERROR: Python not found after extraction!%ESC%%RESET% + echo [%DATE% %TIME%] ERROR: python.exe not found after extraction >> "%LOG_FILE%" goto :error ) ) -echo python.exe found. -echo [%DATE% %TIME%] python.exe found. >> "%LOG_FILE%" -:: Vérifier si le script Python existe -echo Checking __main__.py... -echo [%DATE% %TIME%] Checking __main__.py at !MAIN_SCRIPT! >> "%LOG_FILE%" -if not exist "!MAIN_SCRIPT!" ( - echo Error: __main__.py not found at !MAIN_SCRIPT!. - echo [%DATE% %TIME%] Error: __main__.py not found at !MAIN_SCRIPT! >> "%LOG_FILE%" +:: Afficher et logger la version Python +for /f "tokens=*" %%v in ('"%PYTHON_EXE%" --version 2^>^&1') do set "PYTHON_VERSION=%%v" +echo %ESC%%GREEN%^> %PYTHON_VERSION% found%ESC%%RESET% +echo [%DATE% %TIME%] %PYTHON_VERSION% detected >> "%LOG_FILE%" + +:: ============================================================================= +:: Verification script principal +:: ============================================================================= +echo %ESC%%YELLOW%[2/3]%ESC%%RESET% Checking RGSX application... +echo [%DATE% %TIME%] Step 2/3: Checking RGSX files >> "%LOG_FILE%" + +if not exist "%MAIN_SCRIPT%" ( + echo. + echo %ESC%%RED% ERROR: __main__.py not found!%ESC%%RESET% + echo. + echo Expected location: + echo %ESC%%CYAN%%MAIN_SCRIPT%%ESC%%RESET% + echo. + echo [%DATE% %TIME%] ERROR: __main__.py not found at %MAIN_SCRIPT% >> "%LOG_FILE%" goto :error ) -echo __main__.py found. -echo [%DATE% %TIME%] __main__.py found. >> "%LOG_FILE%" -:: L'étape de mise à jour de la gamelist est désormais appelée depuis __main__.py -echo [%DATE% %TIME%] Skipping external gamelist update (handled in app). >> "%LOG_FILE%" +echo %ESC%%GREEN%^> RGSX files OK%ESC%%RESET% +echo [%DATE% %TIME%] RGSX files verified >> "%LOG_FILE%" -echo Launching __main__.py (attached)... -echo [%DATE% %TIME%] Preparing to launch main. >> "%LOG_FILE%" +:: ============================================================================= +:: Lancement +:: ============================================================================= +echo %ESC%%YELLOW%[3/3]%ESC%%RESET% Launching RGSX... +echo [%DATE% %TIME%] Step 3/3: Launching application >> "%LOG_FILE%" -:: Assurer le bon dossier de travail pour l'application +:: Changer le repertoire de travail cd /d "%ROOT_DIR%\roms\ports\RGSX" +echo [%DATE% %TIME%] Working directory: %CD% >> "%LOG_FILE%" -:: Forcer les drivers SDL côté Windows et réduire le bruit console +:: Configuration SDL/Pygame set PYGAME_HIDE_SUPPORT_PROMPT=1 set SDL_VIDEODRIVER=windows set SDL_AUDIODRIVER=directsound -echo [%DATE% %TIME%] CWD before launch: %CD% >> "%LOG_FILE%" +set PYTHONWARNINGS=ignore::UserWarning:pygame.pkgdata -:: Lancer l'application dans la même console et attendre sa fin -:: Forcer python.exe pour capturer la sortie -set "PY_MAIN_EXE=!PYTHON_EXE_FULL!" -echo [%DATE% %TIME%] Using interpreter: !PY_MAIN_EXE! >> "%LOG_FILE%" -echo [%DATE% %TIME%] Launching "!MAIN_SCRIPT!" now... >> "%LOG_FILE%" -"!PY_MAIN_EXE!" "!MAIN_SCRIPT!" >> "%LOG_FILE%" 2>&1 -set EXITCODE=!ERRORLEVEL! -echo [%DATE% %TIME%] __main__.py exit code: !EXITCODE! >> "%LOG_FILE%" -if "!EXITCODE!"=="0" ( - echo Execution finished successfully. - echo [%DATE% %TIME%] Execution of __main__.py finished successfully. >> "%LOG_FILE%" +:: ============================================================================= +:: Configuration multi-ecran +:: ============================================================================= +:: SDL_VIDEO_FULLSCREEN_HEAD: Selectionne l'ecran pour le mode plein ecran +:: 0 = ecran principal, 1 = ecran secondaire, etc. +:: Ces variables ne sont definies que si --display=N ou --windowed est passe +:: Sinon, le script Python utilisera les parametres de rgsx_settings.json + +echo [%DATE% %TIME%] Display configuration: >> "%LOG_FILE%" +if defined DISPLAY_NUM ( + set SDL_VIDEO_FULLSCREEN_HEAD=!DISPLAY_NUM! + set RGSX_DISPLAY=!DISPLAY_NUM! + echo [%DATE% %TIME%] SDL_VIDEO_FULLSCREEN_HEAD=!DISPLAY_NUM! ^(from --display arg^) >> "%LOG_FILE%" + echo [%DATE% %TIME%] RGSX_DISPLAY=!DISPLAY_NUM! ^(from --display arg^) >> "%LOG_FILE%" ) else ( - echo Error: Failed to execute __main__.py (code !EXITCODE!). - echo [%DATE% %TIME%] Error: Failed to execute __main__.py with error code !EXITCODE!. >> "%LOG_FILE%" + echo [%DATE% %TIME%] Display: using rgsx_settings.json config >> "%LOG_FILE%" +) +if defined WINDOWED_MODE ( + set RGSX_WINDOWED=!WINDOWED_MODE! + echo [%DATE% %TIME%] RGSX_WINDOWED=!WINDOWED_MODE! ^(from --windowed arg^) >> "%LOG_FILE%" +) else ( + echo [%DATE% %TIME%] Windowed: using rgsx_settings.json config >> "%LOG_FILE%" +) + +:: Log environnement +echo [%DATE% %TIME%] Environment variables set: >> "%LOG_FILE%" +echo [%DATE% %TIME%] RGSX_ROOT=%RGSX_ROOT% >> "%LOG_FILE%" +echo [%DATE% %TIME%] SDL_VIDEODRIVER=%SDL_VIDEODRIVER% >> "%LOG_FILE%" +echo [%DATE% %TIME%] SDL_AUDIODRIVER=%SDL_AUDIODRIVER% >> "%LOG_FILE%" + +echo. +if defined DISPLAY_NUM ( + echo %ESC%%CYAN%Launching on display !DISPLAY_NUM!...%ESC%%RESET% +) +if defined WINDOWED_MODE ( + echo %ESC%%CYAN%Windowed mode enabled%ESC%%RESET% +) +echo %ESC%%CYAN%Starting RGSX application...%ESC%%RESET% +echo %ESC%%BOLD%Press Ctrl+C to force quit if needed%ESC%%RESET% +echo. +echo [%DATE% %TIME%] Executing: "%PYTHON_EXE%" "%MAIN_SCRIPT%" >> "%LOG_FILE%" +echo [%DATE% %TIME%] --- Application output start --- >> "%LOG_FILE%" + +"%PYTHON_EXE%" "%MAIN_SCRIPT%" >> "%LOG_FILE%" 2>&1 +set EXITCODE=!ERRORLEVEL! + +echo [%DATE% %TIME%] --- Application output end --- >> "%LOG_FILE%" +echo [%DATE% %TIME%] Exit code: !EXITCODE! >> "%LOG_FILE%" + +if "!EXITCODE!"=="0" ( + echo. + echo %ESC%%GREEN%RGSX closed successfully.%ESC%%RESET% + echo. + echo [%DATE% %TIME%] Application closed successfully >> "%LOG_FILE%" +) else ( + echo. + echo %ESC%%RED%RGSX exited with error code !EXITCODE!%ESC%%RESET% + echo. + echo [%DATE% %TIME%] ERROR: Application exited with code !EXITCODE! >> "%LOG_FILE%" goto :error ) :end -echo Task completed. -echo [%DATE% %TIME%] Task completed successfully. >> "%LOG_FILE%" +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" +echo [%DATE% %TIME%] Session ended normally >> "%LOG_FILE%" +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" +timeout /t 2 >nul exit /b 0 :error -echo An error occurred. -echo [%DATE% %TIME%] An error occurred. >> "%LOG_FILE%" +echo. +echo %ESC%%RED%An error occurred. Check the log file:%ESC%%RESET% +echo %ESC%%CYAN%%LOG_FILE%%ESC%%RESET% +echo. +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" +echo [%DATE% %TIME%] Session ended with errors >> "%LOG_FILE%" +echo [%DATE% %TIME%] ========================================== >> "%LOG_FILE%" +echo. +echo Press any key to close... +pause >nul exit /b 1 \ No newline at end of file