Compare commits

..

4 Commits

Author SHA1 Message Date
skymike03
b437f31854 v2.4.1.0 (2026.01.14)
- add gamelist update check at rgsx start to warn if you didn't update gamlist since few days
- add view for grid mode (Settings > Display)
- use submenu for fonts (Settings > Display)
- performance mode (Settings>Display) updated, now it runs faster without any effects
2026-01-14 21:32:14 +01:00
skymike03
08f3e64d2a v2.4.0.2 (2026.01.14)
- correct some bugs/errors on display and logging functionality; update language files with new options
2026-01-14 20:25:47 +01:00
RGS
4968af2da9 Update image source in README.md 2026-01-07 14:48:30 +01:00
skymike03
920914b374 v2.4.0.1 (2026.01.07 bis)
- remove windowed mode (useless)
2026-01-07 14:36:00 +01:00
14 changed files with 700 additions and 354 deletions

View File

@@ -5,7 +5,7 @@
A free, user-friendly ROM downloader for Batocera, Knulli, and RetroBat with multi-source support.
<p align="center">
<img width="69%" alt="platform menu" src="https://github.com/user-attachments/assets/4464b57b-06a8-45e9-a411-cc12b421545a" />
<img width="69%" alt="main" src="https://github.com/user-attachments/assets/a98f1189-9a50-4cc3-b588-3f85245640d8" />
<img width="30%" alt="controls help" src="https://github.com/user-attachments/assets/38cac7e6-14f2-4e83-91da-0679669822ee" />
</p>
<p align="center">

View File

@@ -97,6 +97,7 @@ _run_windows_gamelist_update()
try:
config.update_checked = False
config.gamelist_update_prompted = False # Flag pour ne pas redemander la mise à jour plusieurs fois
except Exception:
pass
@@ -176,7 +177,6 @@ config.init_footer_font()
# Mise à jour de la résolution dans config
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
logger.debug(f"Resolution d'ecran : {config.screen_width}x{config.screen_height}")
print(f"Resolution ecran validee: {config.screen_width}x{config.screen_height}")
# Afficher un premier écran de chargement immédiatement pour éviter un écran noir
@@ -276,7 +276,6 @@ logger.debug(f"Historique de téléchargement : {len(config.history)} entrées")
# Chargement des jeux téléchargés
config.downloaded_games = load_downloaded_games()
logger.debug(f"Jeux téléchargés : {sum(len(v) for v in config.downloaded_games.values())} jeux")
# Vérification et chargement de la configuration des contrôles (après mises à jour et détection manette)
config.controls_config = load_controls_config()
@@ -302,6 +301,9 @@ try:
if config.controls_config:
summary = {}
for action, mapping in config.controls_config.items():
# Vérifier que mapping est bien un dictionnaire
if not isinstance(mapping, dict):
continue
mtype = mapping.get("type")
val = None
if mtype == "key":
@@ -341,9 +343,6 @@ def start_web_server():
global web_server_process
try:
web_server_script = os.path.join(config.APP_FOLDER, "rgsx_web.py")
logger.info(f"Tentative de démarrage du serveur web...")
logger.info(f"Script: {web_server_script}")
logger.info(f"Fichier existe: {os.path.exists(web_server_script)}")
if not os.path.exists(web_server_script):
logger.warning(f"Script serveur web introuvable: {web_server_script}")
@@ -384,7 +383,6 @@ def start_web_server():
logger.info(f"✅ Serveur web démarré (PID: {web_server_process.pid})")
logger.info(f"🌐 Serveur accessible sur http://localhost:5000")
logger.info(f"📝 Logs de démarrage: {web_server_log}")
# Attendre un peu pour voir si le processus crash immédiatement
import time
@@ -447,7 +445,6 @@ async def main():
# Démarrer le worker de la queue de téléchargement
queue_worker_thread = threading.Thread(target=download_queue_worker, daemon=True)
queue_worker_thread.start()
logger.info("Worker de la queue de téléchargement démarré")
running = True
loading_step = "none"
@@ -681,6 +678,8 @@ async def main():
"pause_menu",
"pause_controls_menu",
"pause_display_menu",
"pause_display_layout_menu",
"pause_display_font_menu",
"pause_games_menu",
"pause_settings_menu",
"pause_api_keys_status",
@@ -694,11 +693,11 @@ async def main():
"history_game_options",
"history_show_folder",
"history_scraper_info",
"scraper", # Ajout du scraper pour gérer les contrôles
"scraper",
"history_error_details",
"history_confirm_delete",
"history_extract_archive",
"text_file_viewer", # Visualiseur de fichiers texte
"text_file_viewer",
# Menus filtrage avancé
"filter_menu_choice",
"filter_advanced",
@@ -737,6 +736,11 @@ async def main():
config.needs_redraw = True
continue
if config.menu_state == "gamelist_update_prompt":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
continue
if config.menu_state == "extension_warning":
logger.debug(f"[EXTENSION_WARNING] Processing extension_warning, previous_menu_state={config.previous_menu_state}, pending_download={bool(config.pending_download)}")
action = handle_controls(event, sources, joystick, screen)
@@ -1089,6 +1093,12 @@ async def main():
elif config.menu_state == "pause_display_menu":
from display import draw_pause_display_menu
draw_pause_display_menu(screen, getattr(config, 'pause_display_selection', 0))
elif config.menu_state == "pause_display_layout_menu":
from display import draw_pause_display_layout_menu
draw_pause_display_layout_menu(screen, getattr(config, 'pause_display_layout_selection', 0))
elif config.menu_state == "pause_display_font_menu":
from display import draw_pause_display_font_menu
draw_pause_display_font_menu(screen, getattr(config, 'pause_display_font_selection', 0))
elif config.menu_state == "pause_games_menu":
from display import draw_pause_games_menu
draw_pause_games_menu(screen, getattr(config, 'pause_games_selection', 0))
@@ -1145,6 +1155,9 @@ async def main():
draw_cancel_download_dialog(screen)
elif config.menu_state == "reload_games_data":
draw_reload_games_data_dialog(screen)
elif config.menu_state == "gamelist_update_prompt":
from display import draw_gamelist_update_prompt
draw_gamelist_update_prompt(screen)
elif config.menu_state == "restart_popup":
draw_popup(screen)
elif config.menu_state == "accessibility_menu":
@@ -1235,6 +1248,7 @@ async def main():
config.loading_progress = 20.0
config.needs_redraw = True
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
continue # Passer immédiatement à check_ota
else:
config.menu_state = "error"
config.error_message = _("error_no_internet")
@@ -1264,6 +1278,7 @@ async def main():
config.loading_progress = 50.0
config.needs_redraw = True
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
continue # Passer immédiatement à check_data
elif loading_step == "check_data":
is_data_empty = not os.path.exists(config.GAMES_FOLDER) or not any(os.scandir(config.GAMES_FOLDER))
if is_data_empty:
@@ -1271,6 +1286,7 @@ async def main():
config.loading_progress = 30.0
config.needs_redraw = True
logger.debug("Dossier Data vide, début du téléchargement du ZIP")
sources_zip_url = None # Initialiser pour éviter les erreurs
try:
zip_path = os.path.join(config.SAVE_FOLDER, "data_download.zip")
headers = {'User-Agent': 'Mozilla/5.0'}
@@ -1376,13 +1392,51 @@ async def main():
config.loading_progress = 80.0
config.needs_redraw = True
logger.debug(f"Dossier Data non vide, passage à {loading_step}")
continue # Passer immédiatement à load_sources
elif loading_step == "load_sources":
logger.debug(f"Étape chargement : {loading_step}, progress={config.loading_progress}")
sources = load_sources()
config.menu_state = "platform"
config.loading_progress = 100.0
config.current_loading_system = ""
# Vérifier si une mise à jour de la liste des jeux est nécessaire (seulement si pas déjà demandé)
if not config.gamelist_update_prompted:
from rgsx_settings import get_last_gamelist_update
from config import GAMELIST_UPDATE_DAYS
from datetime import datetime, timedelta
last_update = get_last_gamelist_update()
should_prompt_update = False
if last_update is None:
# Première utilisation, proposer la mise à jour
logger.info("Première utilisation détectée, proposition de mise à jour de la liste des jeux")
should_prompt_update = True
else:
try:
last_update_date = datetime.strptime(last_update, "%Y-%m-%d")
days_since_update = (datetime.now() - last_update_date).days
logger.info(f"Dernière mise à jour de la liste des jeux: {last_update} ({days_since_update} jours)")
if days_since_update >= GAMELIST_UPDATE_DAYS:
logger.info(f"Mise à jour de la liste des jeux recommandée (>{GAMELIST_UPDATE_DAYS} jours)")
should_prompt_update = True
except Exception as e:
logger.error(f"Erreur lors de la vérification de la date de mise à jour: {e}")
if should_prompt_update:
config.menu_state = "gamelist_update_prompt"
config.gamelist_update_selection = 1 # 0=Non, 1=Oui (par défaut)
config.gamelist_update_prompted = True # Marquer comme déjà demandé
logger.debug("Affichage du prompt de mise à jour de la liste des jeux")
else:
config.menu_state = "platform"
logger.debug(f"Fin chargement, passage à platform, progress={config.loading_progress}")
else:
config.menu_state = "platform"
logger.debug(f"Prompt déjà affiché, passage à platform, progress={config.loading_progress}")
config.needs_redraw = True
logger.debug(f"Fin chargement, passage à platform, progress={config.loading_progress}")
# Gestion de l'état de transition
if config.transition_state == "to_game":

View File

@@ -13,7 +13,10 @@ except Exception:
pygame = None # type: ignore
# Version actuelle de l'application
app_version = "2.4.0.0"
app_version = "2.4.1.0"
# Nombre de jours avant de proposer la mise à jour de la liste des jeux
GAMELIST_UPDATE_DAYS = 7
def get_application_root():

View File

@@ -45,9 +45,12 @@ VALID_STATES = [
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
"reload_games_data", "restart_popup", "error", "loading", "confirm_clear_history",
"language_select", "filter_platforms", "display_menu", "confirm_cancel_download",
"gamelist_update_prompt",
# Nouveaux sous-menus hiérarchiques (refonte pause menu)
"pause_controls_menu", # sous-menu Controls (aide, remap)
"pause_display_menu", # sous-menu Display (layout, font size, unsupported, unknown ext, filter)
"pause_display_layout_menu",# sous-menu Display > Layout (disposition avec visualisation)
"pause_display_font_menu", # sous-menu Display > Font (taille police + footer)
"pause_games_menu", # sous-menu Games (source mode, update/redownload cache)
"pause_settings_menu", # sous-menu Settings (music on/off, symlink toggle, api keys status)
"pause_api_keys_status", # sous-menu API Keys (affichage statut des clés)
@@ -493,7 +496,12 @@ def handle_controls(event, sources, joystick, screen):
config.current_game = 0
config.scroll_offset = 0
draw_validation_transition(screen, config.current_platform)
# Désactiver l'animation de transition en mode performance (light mode)
from rgsx_settings import get_light_mode
if not get_light_mode():
draw_validation_transition(screen, config.current_platform)
config.menu_state = "game"
config.needs_redraw = True
#logger.debug(f"Plateforme sélectionnée: {config.platforms[config.current_platform]}, {len(config.games)} jeux chargés")
@@ -1755,10 +1763,11 @@ def handle_controls(event, sources, joystick, screen):
# Sous-menu Display
elif config.menu_state == "pause_display_menu":
sel = getattr(config, 'pause_display_selection', 0)
# 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
# layout, font submenu, family, [monitor if multi], light, unknown, back
from rgsx_settings import get_available_monitors
monitors = get_available_monitors()
show_monitor = len(monitors) > 1
total = 7 if show_monitor else 6 # dynamic total based on monitor count
if is_input_matched(event, "up"):
config.pause_display_selection = (sel - 1) % total
config.needs_redraw = True
@@ -1766,61 +1775,24 @@ def handle_controls(event, sources, joystick, screen):
config.pause_display_selection = (sel + 1) % total
config.needs_redraw = True
elif is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm"):
# 0 layout cycle
if sel == 0 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
# 0 layout submenu - open submenu on confirm
if sel == 0 and is_input_matched(event, "confirm"):
config.menu_state = "pause_display_layout_menu"
# Trouver l'index actuel pour la sélection
layouts = [(3,3),(3,4),(4,3),(4,4)]
try:
idx = layouts.index((config.GRID_COLS, config.GRID_ROWS))
except ValueError:
idx = 0
idx = (idx + 1) % len(layouts) if is_input_matched(event, "right") else (idx - 1) % len(layouts)
new_cols, new_rows = layouts[idx]
try:
set_display_grid(new_cols, new_rows)
except Exception as e:
logger.error(f"Erreur set_display_grid: {e}")
config.GRID_COLS = new_cols
config.GRID_ROWS = new_rows
# Afficher un popup indiquant que le changement sera effectif après redémarrage
try:
config.popup_message = _("popup_layout_changed_restart_required") if _ else "Layout changed. Restart required to apply."
config.popup_timer = 3000
except Exception as e:
logger.error(f"Erreur popup layout: {e}")
config.pause_display_layout_selection = idx
config.needs_redraw = True
# 1 font size
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
opts = getattr(config, 'font_scale_options', [0.75,1.0,1.25,1.5,1.75])
idx = getattr(config, 'current_font_scale_index', 1)
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(opts)-1, idx+1)
if idx != getattr(config, 'current_font_scale_index', 1):
config.current_font_scale_index = idx
scale = opts[idx]
config.accessibility_settings["font_scale"] = scale
try:
save_accessibility_settings(config.accessibility_settings)
except Exception as e:
logger.error(f"Erreur sauvegarde accessibilité: {e}")
try:
config.init_font()
except Exception as e:
logger.error(f"Erreur init polices: {e}")
config.needs_redraw = True
# 2 footer font size
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
from accessibility import update_footer_font_scale
footer_opts = getattr(config, 'footer_font_scale_options', [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0])
idx = getattr(config, 'current_footer_font_scale_index', 3)
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(footer_opts)-1, idx+1)
if idx != getattr(config, 'current_footer_font_scale_index', 3):
config.current_footer_font_scale_index = idx
try:
update_footer_font_scale()
except Exception as e:
logger.error(f"Erreur update footer font scale: {e}")
config.needs_redraw = True
# 3 font family cycle
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
# 1 font size submenu - open submenu on confirm
elif sel == 1 and is_input_matched(event, "confirm"):
config.menu_state = "pause_display_font_menu"
config.pause_display_font_selection = 0
config.needs_redraw = True
# 2 font family cycle
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
try:
families = getattr(config, 'FONT_FAMILIES', ["pixel"]) or ["pixel"]
current = get_font_family()
@@ -1853,89 +1825,41 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
except Exception as e:
logger.error(f"Erreur changement font family: {e}")
# 4 monitor selection
elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
# 3 monitor selection (only if multiple monitors)
elif sel == 3 and show_monitor 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_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
from rgsx_settings import get_display_monitor, set_display_monitor
current = get_display_monitor()
new_monitor = (current - 1) % len(monitors) if is_input_matched(event, "left") else (current + 1) % len(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
config.needs_redraw = True
except Exception as e:
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()
# light mode toggle (index 4 if show_monitor, else 3)
elif ((sel == 4 and show_monitor) or (sel == 3 and not show_monitor)) and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
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
# 8 back (Windows only)
elif sel == 8 and is_windows and is_input_matched(event, "confirm"):
except Exception as e:
logger.error(f"Erreur toggle light mode: {e}")
# allow unknown extensions (index 5 if show_monitor, else 4)
elif ((sel == 5 and show_monitor) or (sel == 4 and not show_monitor)) 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}")
# back (index 6 if show_monitor, else 5)
elif ((sel == 6 and show_monitor) or (sel == 5 and not show_monitor)) and is_input_matched(event, "confirm"):
config.menu_state = "pause_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
@@ -1944,6 +1868,95 @@ def handle_controls(event, sources, joystick, screen):
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
# Sous-menu Display > Layout (disposition avec visualisation)
elif config.menu_state == "pause_display_layout_menu":
sel = getattr(config, 'pause_display_layout_selection', 0)
total = 5 # 3x3, 3x4, 4x3, 4x4, back
if is_input_matched(event, "up"):
config.pause_display_layout_selection = (sel - 1) % total
config.needs_redraw = True
elif is_input_matched(event, "down"):
config.pause_display_layout_selection = (sel + 1) % total
config.needs_redraw = True
elif is_input_matched(event, "confirm"):
if sel < 4: # Une des dispositions
layouts = [(3,3),(3,4),(4,3),(4,4)]
new_cols, new_rows = layouts[sel]
try:
set_display_grid(new_cols, new_rows)
except Exception as e:
logger.error(f"Erreur set_display_grid: {e}")
config.GRID_COLS = new_cols
config.GRID_ROWS = new_rows
# Afficher un popup indiquant que le changement sera effectif après redémarrage
try:
config.popup_message = _("popup_layout_changed_restart").format(new_cols, new_rows) if _ else f"Layout changed to {new_cols}x{new_rows}. Restart required to apply."
config.popup_timer = 3000
except Exception as e:
logger.error(f"Erreur popup layout: {e}")
config.menu_state = "pause_display_menu"
config.needs_redraw = True
elif sel == 4: # Back
config.menu_state = "pause_display_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
elif is_input_matched(event, "cancel") or is_input_matched(event, "start"):
config.menu_state = "pause_display_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
# Sous-menu Display > Font (tailles de police)
elif config.menu_state == "pause_display_font_menu":
sel = getattr(config, 'pause_display_font_selection', 0)
total = 3 # font size, footer font size, back
if is_input_matched(event, "up"):
config.pause_display_font_selection = (sel - 1) % total
config.needs_redraw = True
elif is_input_matched(event, "down"):
config.pause_display_font_selection = (sel + 1) % total
config.needs_redraw = True
elif is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm"):
# 0 font size
if sel == 0 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
opts = getattr(config, 'font_scale_options', [0.75,1.0,1.25,1.5,1.75])
idx = getattr(config, 'current_font_scale_index', 1)
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(opts)-1, idx+1)
if idx != getattr(config, 'current_font_scale_index', 1):
config.current_font_scale_index = idx
scale = opts[idx]
config.accessibility_settings["font_scale"] = scale
try:
save_accessibility_settings(config.accessibility_settings)
except Exception as e:
logger.error(f"Erreur sauvegarde accessibilité: {e}")
try:
config.init_font()
except Exception as e:
logger.error(f"Erreur init polices: {e}")
config.needs_redraw = True
# 1 footer font size
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
from accessibility import update_footer_font_scale
footer_opts = getattr(config, 'footer_font_scale_options', [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0])
idx = getattr(config, 'current_footer_font_scale_index', 3)
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(footer_opts)-1, idx+1)
if idx != getattr(config, 'current_footer_font_scale_index', 3):
config.current_footer_font_scale_index = idx
try:
update_footer_font_scale()
except Exception as e:
logger.error(f"Erreur update footer font scale: {e}")
config.needs_redraw = True
# 2 back
elif sel == 2 and is_input_matched(event, "confirm"):
config.menu_state = "pause_display_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
elif is_input_matched(event, "cancel") or is_input_matched(event, "start"):
config.menu_state = "pause_display_menu"
config.last_state_change_time = pygame.time.get_ticks()
config.needs_redraw = True
# Sous-menu Games
elif config.menu_state == "pause_games_menu":
sel = getattr(config, 'pause_games_selection', 0)
@@ -2253,6 +2266,55 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.debug("Retour à pause_menu depuis controls_mapping")
# Prompt de mise à jour automatique de la liste des jeux
elif config.menu_state == "gamelist_update_prompt":
if is_input_matched(event, "left") or is_input_matched(event, "right"):
config.gamelist_update_selection = 1 - config.gamelist_update_selection
config.needs_redraw = True
elif is_input_matched(event, "confirm"):
if config.gamelist_update_selection == 1: # Oui
logger.info("Utilisateur a accepté la mise à jour de la liste des jeux")
# Lancer le téléchargement
config.download_tasks.clear()
config.pending_download = None
if os.path.exists(config.SOURCES_FILE):
try:
if os.path.exists(config.SOURCES_FILE):
os.remove(config.SOURCES_FILE)
if os.path.exists(os.path.join(config.SAVE_FOLDER, "sources.json")):
os.remove(os.path.join(config.SAVE_FOLDER, "sources.json"))
if os.path.exists(config.GAMES_FOLDER):
shutil.rmtree(config.GAMES_FOLDER)
if os.path.exists(config.IMAGES_FOLDER):
shutil.rmtree(config.IMAGES_FOLDER)
# Mettre à jour la date
from rgsx_settings import set_last_gamelist_update
set_last_gamelist_update()
config.menu_state = "restart_popup"
config.popup_message = _("popup_gamelist_updating") if _ else "Updating game list... Restarting..."
config.popup_timer = 2000
config.needs_redraw = True
restart_application(2000)
except Exception as e:
logger.error(f"Erreur lors de la mise à jour: {e}")
config.menu_state = "loading"
config.needs_redraw = True
else:
# Pas de cache existant, juste mettre à jour la date et continuer
from rgsx_settings import set_last_gamelist_update
set_last_gamelist_update()
config.menu_state = "loading"
config.needs_redraw = True
else: # Non
logger.info("Utilisateur a refusé la mise à jour de la liste des jeux")
# Ne pas mettre à jour la date pour redemander plus tard
config.menu_state = "platform"
config.needs_redraw = True
elif is_input_matched(event, "cancel"):
logger.info("Utilisateur a annulé le prompt de mise à jour")
config.menu_state = "platform"
config.needs_redraw = True
# Redownload game cache
elif config.menu_state == "reload_games_data":
if is_input_matched(event, "left") or is_input_matched(event, "right"):
@@ -2278,6 +2340,9 @@ def handle_controls(event, sources, joystick, screen):
if os.path.exists(config.IMAGES_FOLDER):
shutil.rmtree(config.IMAGES_FOLDER)
logger.debug("Dossier images supprimé avec succès")
# Mettre à jour la date de dernière mise à jour
from rgsx_settings import set_last_gamelist_update
set_last_gamelist_update()
config.menu_state = "restart_popup"
config.popup_message = _("popup_redownload_success")
config.popup_timer = 2000 # bref message

View File

@@ -3,12 +3,21 @@
import pygame # type: ignore
import os
import io
import platform
import random
import config
from utils import truncate_text_middle, wrap_text, load_system_image, truncate_text_end
from utils import (truncate_text_middle, wrap_text, load_system_image, truncate_text_end,
check_web_service_status, check_custom_dns_status, load_api_keys,
_get_dest_folder_name, find_file_with_or_without_extension)
import logging
import math
from history import load_history, is_game_downloaded
from language import _, get_size_units, get_speed_unit
from language import _, get_size_units, get_speed_unit, get_available_languages, get_language_name
from rgsx_settings import (load_rgsx_settings, get_light_mode, get_show_unsupported_platforms,
get_allow_unknown_extensions, get_display_monitor, get_display_fullscreen,
get_available_monitors, get_font_family, get_sources_mode,
get_hide_premium_systems, get_symlink_option)
from game_filters import GameFilters
logger = logging.getLogger(__name__)
@@ -226,38 +235,27 @@ THEME_COLORS = {
# Général, résolution, overlay
def init_display():
"""Initialise l'écran et les ressources globales.
Supporte la sélection de moniteur et le mode fenêtré/plein écran.
Supporte la sélection de moniteur en 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")
# Charger les paramètres d'affichage avec debug
# Charger les paramètres d'affichage
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
@@ -295,13 +293,14 @@ def init_display():
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
# Créer la fenêtre en plein écran
flags = pygame.FULLSCREEN
# Sur Linux/Batocera, utiliser SCALED pour respecter la résolution forcée d'EmulationStation
if platform.system() == "Linux":
flags |= pygame.SCALED
# Sur certains systèmes Windows, NOFRAME aide pour le multi-écran
elif platform.system() == "Windows":
flags |= pygame.NOFRAME
try:
screen = pygame.display.set_mode((screen_width, screen_height), flags, display=target_monitor)
@@ -315,19 +314,17 @@ def init_display():
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é: {screen_width}x{screen_height} sur moniteur {target_monitor} (fullscreen={fullscreen})")
logger.debug(f"Écran initialisé: {screen_width}x{screen_height} sur moniteur {target_monitor}")
return screen
# Fond d'écran dégradé
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()
@@ -355,7 +352,6 @@ def draw_gradient(screen, top_color, bottom_color, light_mode=None):
# Ajouter une texture de grain subtile pour plus de profondeur
grain_surface = pygame.Surface((width, height), pygame.SRCALPHA)
import random
random.seed(42) # Seed fixe pour cohérence
for _ in range(width * height // 200): # Réduire la densité pour performance
x = random.randint(0, width - 1)
@@ -367,7 +363,6 @@ def draw_gradient(screen, top_color, bottom_color, light_mode=None):
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:
@@ -379,7 +374,6 @@ def draw_shadow(surface, rect, offset=6, alpha=120, light_mode=None):
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:
@@ -396,7 +390,6 @@ def draw_glow_effect(screen, rect, color, intensity=80, size=10, light_mode=None
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()
@@ -806,6 +799,10 @@ def draw_platform_grid(screen):
"""Affiche la grille des plateformes avec un style moderne et fluide."""
global platform_images_cache
# Vérifier si le mode performance est activé
from rgsx_settings import get_light_mode
light_mode = get_light_mode()
if not config.platforms or config.selected_platform >= len(config.platforms):
platform_name = _("platform_no_platform")
logger.warning("Aucune plateforme ou selected_platform hors limites")
@@ -834,35 +831,45 @@ def draw_platform_grid(screen):
# Effet de pulsation subtil pour le titre - calculé une seule fois par frame
current_time = pygame.time.get_ticks()
pulse_factor = 0.08 * (1 + math.sin(current_time / 400))
# Ombre portée pour le titre
shadow_surf = pygame.Surface((title_rect_inflated.width + 12, title_rect_inflated.height + 12), pygame.SRCALPHA)
pygame.draw.rect(shadow_surf, (0, 0, 0, 140), (6, 6, title_rect_inflated.width, title_rect_inflated.height), border_radius=16)
screen.blit(shadow_surf, (title_rect_inflated.left - 6, title_rect_inflated.top - 6))
if not light_mode:
# Mode normal : effets visuels complets
pulse_factor = 0.08 * (1 + math.sin(current_time / 400))
# Ombre portée pour le titre
shadow_surf = pygame.Surface((title_rect_inflated.width + 12, title_rect_inflated.height + 12), pygame.SRCALPHA)
pygame.draw.rect(shadow_surf, (0, 0, 0, 140), (6, 6, title_rect_inflated.width, title_rect_inflated.height), border_radius=16)
screen.blit(shadow_surf, (title_rect_inflated.left - 6, title_rect_inflated.top - 6))
# Glow multicouche pour le titre
for i in range(2):
glow_size = title_rect_inflated.inflate(15 + i * 8, 15 + i * 8)
title_glow = pygame.Surface((glow_size.width, glow_size.height), pygame.SRCALPHA)
alpha = int((30 + 20 * pulse_factor) * (1 - i / 2))
pygame.draw.rect(title_glow, (*THEME_COLORS["neon"][:3], alpha),
title_glow.get_rect(), border_radius=16 + i * 2)
screen.blit(title_glow, (title_rect_inflated.left - 8 - i * 4, title_rect_inflated.top - 8 - i * 4))
# Fond du titre avec dégradé
title_bg = pygame.Surface((title_rect_inflated.width, title_rect_inflated.height), pygame.SRCALPHA)
for i in range(title_rect_inflated.height):
ratio = i / title_rect_inflated.height
alpha = int(THEME_COLORS["button_idle"][3] * (1 + ratio * 0.1))
pygame.draw.line(title_bg, (*THEME_COLORS["button_idle"][:3], alpha),
(0, i), (title_rect_inflated.width, i))
screen.blit(title_bg, title_rect_inflated.topleft)
# Reflet en haut du titre
highlight = pygame.Surface((title_rect_inflated.width - 8, title_rect_inflated.height // 3), pygame.SRCALPHA)
highlight.fill((255, 255, 255, 25))
screen.blit(highlight, (title_rect_inflated.left + 4, title_rect_inflated.top + 4))
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=14)
else:
# Mode performance : rendu simplifié
pygame.draw.rect(screen, THEME_COLORS["button_idle"], title_rect_inflated, border_radius=14)
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=14)
# Glow multicouche pour le titre
for i in range(2):
glow_size = title_rect_inflated.inflate(15 + i * 8, 15 + i * 8)
title_glow = pygame.Surface((glow_size.width, glow_size.height), pygame.SRCALPHA)
alpha = int((30 + 20 * pulse_factor) * (1 - i / 2))
pygame.draw.rect(title_glow, (*THEME_COLORS["neon"][:3], alpha),
title_glow.get_rect(), border_radius=16 + i * 2)
screen.blit(title_glow, (title_rect_inflated.left - 8 - i * 4, title_rect_inflated.top - 8 - i * 4)) # Fond du titre avec dégradé
title_bg = pygame.Surface((title_rect_inflated.width, title_rect_inflated.height), pygame.SRCALPHA)
for i in range(title_rect_inflated.height):
ratio = i / title_rect_inflated.height
alpha = int(THEME_COLORS["button_idle"][3] * (1 + ratio * 0.1))
pygame.draw.line(title_bg, (*THEME_COLORS["button_idle"][:3], alpha),
(0, i), (title_rect_inflated.width, i))
screen.blit(title_bg, title_rect_inflated.topleft)
# Reflet en haut du titre
highlight = pygame.Surface((title_rect_inflated.width - 8, title_rect_inflated.height // 3), pygame.SRCALPHA)
highlight.fill((255, 255, 255, 25))
screen.blit(highlight, (title_rect_inflated.left + 4, title_rect_inflated.top + 4))
pygame.draw.rect(screen, THEME_COLORS["border"], title_rect_inflated, 2, border_radius=14)
screen.blit(title_surface, title_rect)
# Configuration de la grille - calculée une seule fois
@@ -927,8 +934,12 @@ def draw_platform_grid(screen):
screen.blit(page_indicator, page_rect)
# Calculer une seule fois la pulsation pour les éléments sélectionnés (réduite)
pulse = 0.05 * math.sin(current_time / 300) # Réduit de 0.1 à 0.05
glow_intensity = 40 + int(30 * math.sin(current_time / 300))
if not light_mode:
pulse = 0.05 * math.sin(current_time / 300) # Réduit de 0.1 à 0.05
glow_intensity = 40 + int(30 * math.sin(current_time / 300))
else:
pulse = 0
glow_intensity = 0
# Pré-calcul des images pour optimiser le rendu
start_idx = config.current_page * systems_per_page
@@ -943,8 +954,14 @@ def draw_platform_grid(screen):
# Animation fluide pour l'item sélectionné (réduite pour éviter chevauchement)
is_selected = idx == config.selected_platform
scale_base = 1.15 if is_selected else 1.0 # Réduit de 1.5 à 1.15
scale = scale_base + pulse if is_selected else scale_base
if light_mode:
# Mode performance : pas d'animation, taille fixe
scale_base = 1.0
scale = 1.0
else:
# Mode normal : animation réduite
scale_base = 1.15 if is_selected else 1.0 # Réduit de 1.5 à 1.15
scale = scale_base + pulse if is_selected else scale_base
# Récupération robuste du dict via nom
display_name = visible_platforms[idx]
@@ -1013,6 +1030,7 @@ def draw_platform_grid(screen):
image_rect = scaled_image.get_rect(center=(x, y))
# Effet visuel moderne similaire au titre pour toutes les images
border_radius = 12
padding = 12
@@ -1025,57 +1043,80 @@ def draw_platform_grid(screen):
container_left = x - rect_width // 2
container_top = y - rect_height // 2
# Ombre portée
shadow_surf = pygame.Surface((rect_width + 12, rect_height + 12), pygame.SRCALPHA)
pygame.draw.rect(shadow_surf, (0, 0, 0, 160), (6, 6, rect_width, rect_height), border_radius=border_radius + 4)
screen.blit(shadow_surf, (container_left - 6, container_top - 6))
# Effet de glow multicouche pour l'item sélectionné
if is_selected:
neon_color = THEME_COLORS["neon"]
if not light_mode:
# Mode normal : effets visuels complets
# Ombre portée
shadow_surf = pygame.Surface((rect_width + 12, rect_height + 12), pygame.SRCALPHA)
pygame.draw.rect(shadow_surf, (0, 0, 0, 160), (6, 6, rect_width, rect_height), border_radius=border_radius + 4)
screen.blit(shadow_surf, (container_left - 6, container_top - 6))
# Glow multicouche (2 couches pour effet profondeur)
for i in range(2):
glow_size = (rect_width + 15 + i * 8, rect_height + 15 + i * 8)
glow_surf = pygame.Surface(glow_size, pygame.SRCALPHA)
alpha = int((glow_intensity + 40) * (1 - i / 2))
pygame.draw.rect(glow_surf, neon_color + (alpha,), glow_surf.get_rect(), border_radius=border_radius + i * 2)
screen.blit(glow_surf, (container_left - 8 - i * 4, container_top - 8 - i * 4))
# Effet de glow multicouche pour l'item sélectionné
if is_selected:
neon_color = THEME_COLORS["neon"]
# Glow multicouche (2 couches pour effet profondeur)
for i in range(2):
glow_size = (rect_width + 15 + i * 8, rect_height + 15 + i * 8)
glow_surf = pygame.Surface(glow_size, pygame.SRCALPHA)
alpha = int((glow_intensity + 40) * (1 - i / 2))
pygame.draw.rect(glow_surf, neon_color + (alpha,), glow_surf.get_rect(), border_radius=border_radius + i * 2)
screen.blit(glow_surf, (container_left - 8 - i * 4, container_top - 8 - i * 4))
# Fond avec dégradé vertical (similaire au titre)
bg_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
base_color = THEME_COLORS["button_idle"] if is_selected else THEME_COLORS["fond_image"]
for i in range(rect_height):
ratio = i / rect_height
# Dégradé du haut (plus clair) vers le bas (plus foncé)
alpha = int(base_color[3] * (1 + ratio * 0.15)) if len(base_color) > 3 else int(200 * (1 + ratio * 0.15))
color = (*base_color[:3], min(255, alpha))
pygame.draw.line(bg_surface, color, (0, i), (rect_width, i))
screen.blit(bg_surface, (container_left, container_top))
# Reflet en haut (highlight pour effet glossy)
highlight_height = rect_height // 3
highlight = pygame.Surface((rect_width - 8, highlight_height), pygame.SRCALPHA)
highlight.fill((255, 255, 255, 35 if is_selected else 20))
screen.blit(highlight, (container_left + 4, container_top + 4))
else:
# Mode performance : fond simple sans effets
bg_color = THEME_COLORS["button_idle"] if is_selected else THEME_COLORS["fond_image"]
pygame.draw.rect(screen, bg_color, (container_left, container_top, rect_width, rect_height), border_radius=border_radius)
# Fond avec dégradé vertical (similaire au titre)
bg_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
base_color = THEME_COLORS["button_idle"] if is_selected else THEME_COLORS["fond_image"]
# Bordure
if light_mode and is_selected:
# Mode performance : bordure épaisse et très visible pour l'item sélectionné
border_color = THEME_COLORS["neon"] # Couleur verte bien visible
border_width = 4 # Bordure plus épaisse
elif not light_mode and is_selected:
# Mode normal : bordure neon
border_color = THEME_COLORS["neon"]
border_width = 2
else:
# Non sélectionné : bordure standard
border_color = THEME_COLORS["border"]
border_width = 2
for i in range(rect_height):
ratio = i / rect_height
# Dégradé du haut (plus clair) vers le bas (plus foncé)
alpha = int(base_color[3] * (1 + ratio * 0.15)) if len(base_color) > 3 else int(200 * (1 + ratio * 0.15))
color = (*base_color[:3], min(255, alpha))
pygame.draw.line(bg_surface, color, (0, i), (rect_width, i))
screen.blit(bg_surface, (container_left, container_top))
# Reflet en haut (highlight pour effet glossy)
highlight_height = rect_height // 3
highlight = pygame.Surface((rect_width - 8, highlight_height), pygame.SRCALPHA)
highlight.fill((255, 255, 255, 35 if is_selected else 20))
screen.blit(highlight, (container_left + 4, container_top + 4))
# Bordure avec effet 3D
border_color = THEME_COLORS["neon"] if is_selected else THEME_COLORS["border"]
border_rect = pygame.Rect(container_left, container_top, rect_width, rect_height)
pygame.draw.rect(screen, border_color, border_rect, 2, border_radius=border_radius)
pygame.draw.rect(screen, border_color, border_rect, border_width, border_radius=border_radius)
# Centrer l'image dans le container (l'image peut être plus petite que le container)
centered_image_rect = scaled_image.get_rect(center=(x, y))
# Affichage de l'image avec un léger effet de transparence pour les items non sélectionnés
if not is_selected:
temp_image = scaled_image.copy()
temp_image.set_alpha(220)
screen.blit(temp_image, centered_image_rect)
else:
# Affichage de l'image
if light_mode:
# Mode performance : pas d'effet de transparence
screen.blit(scaled_image, centered_image_rect)
else:
# Mode normal : effet de transparence pour les items non sélectionnés
if not is_selected:
temp_image = scaled_image.copy()
temp_image.set_alpha(220)
screen.blit(temp_image, centered_image_rect)
else:
screen.blit(scaled_image, centered_image_rect)
# Nettoyer le cache périodiquement (garder seulement les images utilisées récemment)
if len(platform_images_cache) > 50: # Limite arbitraire pour éviter une croissance excessive
@@ -1843,49 +1884,46 @@ def draw_extension_warning(screen):
def draw_controls(screen, menu_state, current_music_name=None, music_popup_start_time=0):
"""Affiche les contrôles contextuels en bas de l'écran selon le menu_state."""
# Import local de la fonction de traduction pour éviter les conflits de scope
from language import _ as i18n
# Mapping des contrôles par menu_state
controls_map = {
"platform": [
("history", i18n("controls_action_history")),
("confirm", i18n("controls_confirm_select")),
("start", i18n("controls_action_start")),
("history", _("controls_action_history")),
("confirm", _("controls_confirm_select")),
("start", _("controls_action_start")),
],
"game": [
("confirm", i18n("controls_confirm_select")),
("clear_history", i18n("controls_action_queue")),
(("page_up", "page_down"), i18n("controls_pages")),
("filter", i18n("controls_filter_search")),
("history", i18n("controls_action_history")),
("confirm", _("controls_confirm_select")),
("clear_history", _("controls_action_queue")),
(("page_up", "page_down"), _("controls_pages")),
("filter", _("controls_filter_search")),
("history", _("controls_action_history")),
],
"history": [
("confirm", i18n("history_game_options_title")),
("clear_history", i18n("controls_action_clear_history")),
("history", i18n("controls_action_close_history")),
("cancel", i18n("controls_cancel_back")),
("confirm", _("history_game_options_title")),
("clear_history", _("controls_action_clear_history")),
("history", _("controls_action_close_history")),
("cancel", _("controls_cancel_back")),
],
"scraper": [
("confirm", i18n("controls_confirm_select")),
("cancel", i18n("controls_cancel_back")),
("confirm", _("controls_confirm_select")),
("cancel", _("controls_cancel_back")),
],
"error": [
("confirm", i18n("controls_confirm_select")),
("confirm", _("controls_confirm_select")),
],
"confirm_exit": [
("confirm", i18n("controls_confirm_select")),
("cancel", i18n("controls_cancel_back")),
("confirm", _("controls_confirm_select")),
("cancel", _("controls_cancel_back")),
],
"extension_warning": [
("confirm", i18n("controls_confirm_select")),
("confirm", _("controls_confirm_select")),
],
}
# Récupérer les contrôles pour ce menu, sinon affichage par défaut
controls_list = controls_map.get(menu_state, [
("confirm", i18n("controls_confirm_select")),
("cancel", i18n("controls_cancel_back")),
("confirm", _("controls_confirm_select")),
("cancel", _("controls_cancel_back")),
])
# Construire les lignes avec icônes
@@ -1899,7 +1937,7 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
# Si aucun joystick, afficher la touche entre crochets
if not getattr(config, 'joystick', True):
start_button = f"[{start_button}]"
start_text = i18n("controls_action_start")
start_text = _("controls_action_start")
control_parts.append(f"RGSX v{config.app_version} - {start_button} : {start_text}")
# Afficher le nom du joystick s'il est détecté
@@ -1907,7 +1945,7 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
device_name = getattr(config, 'controller_device_name', '') or ''
if device_name:
try:
joy_label = i18n("footer_joystick")
joy_label = _("footer_joystick")
except Exception:
joy_label = "Joystick: {0}"
if isinstance(joy_label, str) and "{0}" in joy_label:
@@ -1962,7 +2000,7 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
combined_surf = pygame.Surface((max_width, 50), pygame.SRCALPHA)
x_pos = 10
for action_tuple in all_controls:
_, actions, label = action_tuple
ignored, actions, label = action_tuple
try:
surf = _render_icons_line(actions, label, max_width - x_pos - 10, config.tiny_font, THEME_COLORS["text"], icon_size=scaled_icon_size, icon_gap=scaled_icon_gap, icon_text_gap=scaled_icon_text_gap)
if x_pos + surf.get_width() > max_width - 10:
@@ -1977,7 +2015,7 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
final_surf.blit(combined_surf, (0, 0), (0, 0, x_pos - 10, 50))
icon_surfs.append(final_surf)
elif line_data[0] == "icons" and len(line_data) == 3:
_, actions, label = line_data
ignored, actions, label = line_data
try:
surf = _render_icons_line(actions, label, max_width, config.tiny_font, THEME_COLORS["text"], icon_size=scaled_icon_size, icon_gap=scaled_icon_gap, icon_text_gap=scaled_icon_text_gap)
icon_surfs.append(surf)
@@ -2015,7 +2053,6 @@ def draw_language_menu(screen):
- Bloc (titre + liste de langues) centré verticalement.
- Gestion d'overflow: réduit légèrement la hauteur/espacement si nécessaire.
"""
from language import get_available_languages, get_language_name
screen.blit(OVERLAY, (0, 0))
@@ -2161,8 +2198,6 @@ def draw_display_menu(screen):
# É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,
get_display_monitor, get_display_fullscreen, get_available_monitors)
show_unsupported = get_show_unsupported_platforms()
allow_unknown = get_allow_unknown_extensions()
@@ -2418,22 +2453,8 @@ def draw_pause_controls_menu(screen, selected_index):
draw_menu_instruction(screen, text, last_button_bottom)
def draw_pause_display_menu(screen, selected_index):
from rgsx_settings import (
get_allow_unknown_extensions,
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)]
try:
idx = layouts.index((config.GRID_COLS, config.GRID_ROWS))
except ValueError:
idx = 0
layout_value = f"{layouts[idx][0]}x{layouts[idx][1]}"
layout_txt = f"{_('submenu_display_layout') if _ else 'Layout'}: < {layout_value} >"
# Layout label - now opens a submenu
layout_txt = f"{_('submenu_display_layout') if _ else 'Layout'} >"
# Font size
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
cur_idx = getattr(config, 'current_font_scale_index', 1)
@@ -2454,24 +2475,17 @@ 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
# Monitor selection - only show if multiple monitors
current_monitor = get_display_monitor()
monitors = get_available_monitors()
num_monitors = len(monitors)
if num_monitors > 1:
show_monitor_option = num_monitors > 1
if show_monitor_option:
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} >"
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')
@@ -2487,34 +2501,26 @@ def draw_pause_display_menu(screen, selected_index):
back_txt = _("menu_back") if _ else "Back"
# 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",
]
# Build options list - conditional monitor option
# layout, font submenu, family, [monitor if multi], light, unknown, back
font_submenu_txt = f"{_('submenu_display_font_size') if _ else 'Font Size'} >"
options = [layout_txt, font_submenu_txt, font_family_txt]
instruction_keys = [
"instruction_display_layout",
"instruction_display_font_size",
"instruction_display_font_family",
]
if show_monitor_option:
options.append(monitor_txt)
instruction_keys.append("instruction_display_monitor")
options.extend([light_txt, unknown_txt, back_txt])
instruction_keys.extend([
"instruction_display_light_mode",
"instruction_display_unknown_ext",
"instruction_generic_back",
])
_draw_submenu_generic(screen, _("menu_display"), options, selected_index)
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
@@ -2530,8 +2536,156 @@ def draw_pause_display_menu(screen, selected_index):
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
draw_menu_instruction(screen, _(key), last_button_bottom)
def draw_pause_display_layout_menu(screen, selected_index):
"""Sous-menu pour la disposition avec visualisation schématique des grilles."""
layouts = [(3,3),(3,4),(4,3),(4,4)]
layout_labels = ["3x3", "3x4", "4x3", "4x4"]
# Trouver le layout actuel
try:
current_idx = layouts.index((config.GRID_COLS, config.GRID_ROWS))
except ValueError:
current_idx = 0
# Créer les options avec indicateur du layout actuel
options = []
for i, label in enumerate(layout_labels):
if i == current_idx:
options.append(f"{label} [CURRENT]" if not _ else f"{label} [{_('status_current') if _ else 'ACTUEL'}]")
else:
options.append(label)
options.append(_("menu_back") if _ else "Back")
# Dessiner le menu de base
title = _("submenu_display_layout") if _ else "Layout"
# Calculer les dimensions
button_height = int(config.screen_height * 0.045)
menu_width = int(config.screen_width * 0.72)
margin_top_bottom = 26
# Calculer la hauteur nécessaire pour les boutons
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
menu_x = (config.screen_width - menu_width) // 2
menu_y = (config.screen_height - menu_height) // 2
# Fond du menu
menu_rect = pygame.Rect(menu_x, menu_y, menu_width, menu_height)
pygame.draw.rect(screen, THEME_COLORS["button_idle"], menu_rect, border_radius=14)
pygame.draw.rect(screen, THEME_COLORS["border"], menu_rect, 3, border_radius=14)
# Titre
title_surface = config.font.render(title, True, THEME_COLORS["text"])
title_rect = title_surface.get_rect(center=(config.screen_width // 2, menu_y + margin_top_bottom//2 + title_surface.get_height()//2))
screen.blit(title_surface, title_rect)
# Position de départ pour le contenu
content_start_y = title_rect.bottom + 20
# Division en deux colonnes : gauche pour la grille, droite pour les options
left_column_x = menu_x + 20
left_column_width = int(menu_width * 0.4)
right_column_x = left_column_x + left_column_width + 20
right_column_width = menu_width - left_column_width - 60
# COLONNE GAUCHE : Dessiner uniquement la grille sélectionnée
if selected_index < len(layouts):
cols, rows = layouts[selected_index]
# Calculer la taille des cellules pour le schéma
cell_size = min(60, (left_column_width - 20) // max(cols, rows))
grid_width = cols * cell_size
grid_height = rows * cell_size
# Centrer la grille verticalement dans l'espace disponible
available_height = (len(options) * (button_height + 10)) - 10
grid_x = left_column_x + (left_column_width - grid_width) // 2
grid_y = content_start_y + (available_height - grid_height) // 2
# Dessiner le schéma de la grille sélectionnée
for row in range(rows):
for col in range(cols):
cell_rect = pygame.Rect(
grid_x + col * cell_size,
grid_y + row * cell_size,
cell_size - 3,
cell_size - 3
)
# Couleur selon si c'est aussi le layout actuel
if selected_index == current_idx:
# Sélectionné ET actuel : vert brillant
pygame.draw.rect(screen, THEME_COLORS["fond_lignes"], cell_rect)
pygame.draw.rect(screen, THEME_COLORS["text"], cell_rect, 2)
else:
# Seulement sélectionné : bleu clair
pygame.draw.rect(screen, THEME_COLORS["button_selected"], cell_rect)
pygame.draw.rect(screen, THEME_COLORS["text"], cell_rect, 2)
# COLONNE DROITE : Dessiner les boutons d'options
for i, option in enumerate(options):
button_x = right_column_x
button_y = content_start_y + i * (button_height + 10)
button_width = right_column_width
button_rect = pygame.Rect(button_x, button_y, button_width, button_height)
if i == selected_index:
pygame.draw.rect(screen, THEME_COLORS["button_selected"], button_rect, border_radius=8)
else:
pygame.draw.rect(screen, THEME_COLORS["button_idle"], button_rect, border_radius=8)
pygame.draw.rect(screen, THEME_COLORS["border"], button_rect, 2, border_radius=8)
text_surface = config.font.render(option, True, THEME_COLORS["text"])
text_rect = text_surface.get_rect(center=button_rect.center)
screen.blit(text_surface, text_rect)
# Instruction en bas
last_button_bottom = content_start_y + (len(options)-1) * (button_height + 10) + button_height
if selected_index < len(layouts):
instruction = _("instruction_display_layout") if _ else "Left/Right: Navigate • Confirm: Select"
else:
instruction = _("instruction_generic_back") if _ else "Confirm: Go back"
draw_menu_instruction(screen, instruction, last_button_bottom)
def draw_pause_display_font_menu(screen, selected_index):
"""Sous-menu pour les tailles de police."""
# Font size
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
cur_idx = getattr(config, 'current_font_scale_index', 1)
font_value = f"{opts[cur_idx]}x"
font_txt = f"{_('submenu_display_font_size') if _ else 'Font Size'}: < {font_value} >"
# Footer font size
footer_opts = getattr(config, 'footer_font_scale_options', [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0])
footer_cur_idx = getattr(config, 'current_footer_font_scale_index', 3)
footer_font_value = f"{footer_opts[footer_cur_idx]}x"
footer_font_txt = f"{_('accessibility_footer_font_size').split(':')[0] if _ else 'Footer Font Size'}: < {footer_font_value} >"
back_txt = _("menu_back") if _ else "Back"
options = [font_txt, footer_font_txt, back_txt]
instruction_keys = [
"instruction_display_font_size",
"instruction_display_footer_font_size",
"instruction_generic_back",
]
_draw_submenu_generic(screen, _("submenu_display_font_size") if _ else "Font Size", options, selected_index)
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
if key:
button_height = int(config.screen_height * 0.045)
menu_width = int(config.screen_width * 0.72)
margin_top_bottom = 26
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
menu_y = (config.screen_height - menu_height) // 2
title_surface = config.font.render("X", True, THEME_COLORS["text"])
title_rect_height = title_surface.get_height()
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
draw_menu_instruction(screen, _(key), last_button_bottom)
def draw_pause_games_menu(screen, selected_index):
from rgsx_settings import get_sources_mode, get_show_unsupported_platforms, get_hide_premium_systems
mode = get_sources_mode()
source_label = _("games_source_rgsx") if mode == "rgsx" else _("games_source_custom")
source_txt = f"{_('menu_games_source_prefix')}: < {source_label} >"
@@ -2604,8 +2758,6 @@ def draw_pause_games_menu(screen, selected_index):
draw_menu_instruction(screen, text, last_button_bottom)
def draw_pause_settings_menu(screen, selected_index):
from rgsx_settings import get_symlink_option
from utils import check_web_service_status, check_custom_dns_status
# Music
if config.music_enabled:
music_name = config.current_music_name or ""
@@ -2675,7 +2827,6 @@ def draw_pause_settings_menu(screen, selected_index):
def draw_pause_api_keys_status(screen):
screen.blit(OVERLAY, (0,0))
from utils import load_api_keys
keys = load_api_keys()
title = _("api_keys_status_title") if _ else "API Keys Status"
# Préparer données avec masquage partiel des clés (afficher 4 premiers et 2 derniers caractères si longueur > 10)
@@ -2779,7 +2930,6 @@ def draw_pause_api_keys_status(screen):
def draw_filter_platforms_menu(screen):
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
from rgsx_settings import load_rgsx_settings
screen.blit(OVERLAY, (0, 0))
settings = load_rgsx_settings()
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
@@ -3130,6 +3280,54 @@ def draw_reload_games_data_dialog(screen):
draw_stylized_button(screen, _("button_no"), no_x, buttons_y, button_width, button_height, selected=config.redownload_confirm_selection == 0)
def draw_gamelist_update_prompt(screen):
"""Affiche la boîte de dialogue pour proposer la mise à jour de la liste des jeux."""
global OVERLAY
if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height):
OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA)
OVERLAY.fill((0, 0, 0, 150))
screen.blit(OVERLAY, (0, 0))
from config import GAMELIST_UPDATE_DAYS
from rgsx_settings import get_last_gamelist_update
last_update = get_last_gamelist_update()
if last_update:
message = _("gamelist_update_prompt_with_date").format(GAMELIST_UPDATE_DAYS, last_update) if _ else f"Game list hasn't been updated for more than {GAMELIST_UPDATE_DAYS} days (last update: {last_update}). Download the latest version?"
else:
message = _("gamelist_update_prompt_first_time") if _ else "Would you like to download the latest game list?"
wrapped_message = wrap_text(message, config.small_font, config.screen_width - 80)
line_height = config.small_font.get_height() + 5
text_height = len(wrapped_message) * line_height
sample_text = config.small_font.render("Sample", True, THEME_COLORS["text"])
font_height = sample_text.get_height()
button_height = max(int(config.screen_height * 0.0463), font_height + 15)
margin_top_bottom = 20
rect_height = text_height + button_height + 2 * margin_top_bottom
max_text_width = max([config.small_font.size(line)[0] for line in wrapped_message], default=300)
rect_width = max_text_width + 80
rect_x = (config.screen_width - rect_width) // 2
rect_y = (config.screen_height - rect_height) // 2
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
for i, line in enumerate(wrapped_message):
text = config.small_font.render(line, True, THEME_COLORS["text"])
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
screen.blit(text, text_rect)
button_width = min(160, (rect_width - 60) // 2)
yes_x = rect_x + rect_width // 2 - button_width - 10
no_x = rect_x + rect_width // 2 + 10
buttons_y = rect_y + text_height + margin_top_bottom
draw_stylized_button(screen, _("button_yes"), yes_x, buttons_y, button_width, button_height, selected=config.gamelist_update_selection == 1)
draw_stylized_button(screen, _("button_no"), no_x, buttons_y, button_width, button_height, selected=config.gamelist_update_selection == 0)
def draw_support_dialog(screen):
"""Affiche la boîte de dialogue du fichier de support généré."""
global OVERLAY
@@ -3324,8 +3522,6 @@ def show_toast(message, duration=2000):
config.toast_start_time = pygame.time.get_ticks()
def draw_history_game_options(screen):
"""Affiche le menu d'options pour un jeu de l'historique."""
import os
from utils import _get_dest_folder_name, find_file_with_or_without_extension
screen.blit(OVERLAY, (0, 0))
@@ -3432,8 +3628,6 @@ def draw_history_game_options(screen):
def draw_history_show_folder(screen):
"""Affiche le chemin complet du fichier téléchargé."""
import os
from utils import _get_dest_folder_name
screen.blit(OVERLAY, (0, 0))
@@ -3978,7 +4172,6 @@ def draw_filter_menu_choice(screen):
def draw_filter_advanced(screen):
"""Affiche l'écran de filtrage avancé"""
from game_filters import GameFilters
screen.blit(OVERLAY, (0, 0))
@@ -4258,7 +4451,6 @@ def draw_filter_advanced(screen):
def draw_filter_priority_config(screen):
"""Affiche l'écran de configuration de la priorité des régions pour One ROM per game"""
from game_filters import GameFilters
screen.blit(OVERLAY, (0, 0))
@@ -4276,7 +4468,6 @@ def draw_filter_priority_config(screen):
# Initialiser le filtre si nécessaire
if not hasattr(config, 'game_filter_obj'):
from game_filters import GameFilters
from rgsx_settings import load_game_filters
config.game_filter_obj = GameFilters()
filter_dict = load_game_filters()

View File

@@ -52,6 +52,8 @@
"confirm_exit_with_downloads": "Achtung: {0} Download(s) laufen. Trotzdem beenden?",
"confirm_clear_history": "Verlauf löschen?",
"confirm_redownload_cache": "Spieleliste aktualisieren?",
"gamelist_update_prompt_with_date": "Die Spieleliste wurde seit mehr als {0} Tagen nicht aktualisiert (letzte Aktualisierung: {1}). Die neueste Version herunterladen?",
"gamelist_update_prompt_first_time": "Möchten Sie die neueste Spieleliste herunterladen?",
"popup_redownload_success": "Cache gelöscht, bitte die Anwendung neu starten",
"popup_no_cache": "Kein Cache gefunden.\nBitte starte die Anwendung neu, um die Spiele zu laden.",
"popup_countdown": "Diese Nachricht schließt in {0} Sekunde{1}",

View File

@@ -52,6 +52,8 @@
"confirm_exit_with_downloads": "Attention: {0} download(s) in progress. Quit anyway?",
"confirm_clear_history": "Clear history?",
"confirm_redownload_cache": "Update games list?",
"gamelist_update_prompt_with_date": "Game list hasn't been updated for more than {0} days (last update: {1}). Download the latest version?",
"gamelist_update_prompt_first_time": "Would you like to download the latest game list?",
"popup_redownload_success": "Cache cleared, please restart the application",
"popup_no_cache": "No cache found.\nPlease restart the application to load games.",
"popup_countdown": "This message will close in {0} second{1}",

View File

@@ -51,8 +51,8 @@
"confirm_exit": "¿Salir de la aplicación?",
"confirm_exit_with_downloads": "Atención: {0} descarga(s) en curso. ¿Salir de todas formas?",
"confirm_clear_history": "¿Vaciar el historial?",
"confirm_redownload_cache": "¿Actualizar la lista de juegos?",
"popup_redownload_success": "Caché borrada, por favor reinicia la aplicación",
"confirm_redownload_cache": "¿Actualizar la lista de juegos?", "gamelist_update_prompt_with_date": "La lista de juegos no se ha actualizado durante más de {0} días (última actualización: {1}). ¿Descargar la última versión?",
"gamelist_update_prompt_first_time": "¿Desea descargar la última lista de juegos?", "popup_redownload_success": "Caché borrada, por favor reinicia la aplicación",
"popup_no_cache": "No se encontró caché.\nPor favor, reinicia la aplicación para cargar los juegos.",
"popup_countdown": "Este mensaje se cerrará en {0} segundo{1}",
"language_select_title": "Selección de idioma",

View File

@@ -52,6 +52,8 @@
"confirm_exit_with_downloads": "Attention : {0} téléchargement(s) en cours. Quitter quand même ?",
"confirm_clear_history": "Vider l'historique ?",
"confirm_redownload_cache": "Mettre à jour la liste des jeux ?",
"gamelist_update_prompt_with_date": "La liste des jeux n'a pas été mise à jour depuis plus de {0} jours (dernière mise à jour : {1}). Télécharger la dernière version ?",
"gamelist_update_prompt_first_time": "Souhaitez-vous télécharger la dernière liste des jeux ?",
"popup_redownload_success": "Le cache a été effacé, merci de relancer l'application",
"popup_no_cache": "Aucun cache trouvé.\nVeuillez redémarrer l'application pour charger les jeux.",
"popup_countdown": "Ce message se fermera dans {0} seconde{1}",
@@ -264,6 +266,8 @@
"history_option_extract_archive": "Extraire l'archive",
"history_option_open_file": "Ouvrir le fichier",
"history_option_scraper": "Récupérer métadonnées",
"history_option_remove_from_queue": "Retirer de la file d'attente",
"history_option_cancel_download": "Annuler le téléchargement",
"history_option_delete_game": "Supprimer le jeu",
"history_option_error_info": "Détails de l'erreur",
"history_option_retry": "Retenter le téléchargement",

View File

@@ -51,8 +51,8 @@
"confirm_exit": "Uscire dall'applicazione?",
"confirm_exit_with_downloads": "Attenzione: {0} download in corso. Uscire comunque?",
"confirm_clear_history": "Cancellare la cronologia?",
"confirm_redownload_cache": "Aggiornare l'elenco dei giochi?",
"popup_redownload_success": "Cache pulita, riavvia l'applicazione",
"confirm_redownload_cache": "Aggiornare l'elenco dei giochi?", "gamelist_update_prompt_with_date": "L'elenco dei giochi non è stato aggiornato da più di {0} giorni (ultimo aggiornamento: {1}). Scaricare l'ultima versione?",
"gamelist_update_prompt_first_time": "Vuoi scaricare l'ultimo elenco dei giochi?", "popup_redownload_success": "Cache pulita, riavvia l'applicazione",
"popup_no_cache": "Nessuna cache trovata.\nRiavvia l'applicazione per caricare i giochi.",
"popup_countdown": "Questo messaggio si chiuderà tra {0} secondo{1}",
"language_select_title": "Selezione lingua",
@@ -259,6 +259,8 @@
"history_option_extract_archive": "Estrai archivio",
"history_option_open_file": "Apri file",
"history_option_scraper": "Scraper metadati",
"history_option_remove_from_queue": "Rimuovi dalla coda",
"history_option_cancel_download": "Annulla download",
"history_option_delete_game": "Elimina gioco",
"history_option_error_info": "Dettagli errore",
"history_option_retry": "Riprova download",

View File

@@ -52,6 +52,8 @@
"confirm_exit_with_downloads": "Atenção: {0} download(s) em andamento. Sair mesmo assim?",
"confirm_clear_history": "Limpar histórico?",
"confirm_redownload_cache": "Atualizar lista de jogos?",
"gamelist_update_prompt_with_date": "A lista de jogos não foi atualizada há mais de {0} dias (última atualização: {1}). Baixar a versão mais recente?",
"gamelist_update_prompt_first_time": "Gostaria de baixar a última lista de jogos?",
"popup_redownload_success": "Cache limpo, reinicie a aplicação",
"popup_no_cache": "Nenhum cache encontrado.\nReinicie a aplicação para carregar os jogos.",
"popup_countdown": "Esta mensagem fechará em {0} segundo{1}",

View File

@@ -404,7 +404,6 @@ def test_internet():
]
for test_url in test_urls:
logger.debug(f"Test connexion HTTP vers {test_url}")
try:
response = requests.get(test_url, timeout=5, allow_redirects=True)
if response.status_code == 200:

View File

@@ -49,7 +49,7 @@ 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}")
#logger.debug(f"Chargement des settings depuis: {RGSX_SETTINGS_PATH}")
default_settings = {
"language": "en",
@@ -76,14 +76,15 @@ def load_rgsx_settings():
"show_unsupported_platforms": False,
"allow_unknown_extensions": False,
"roms_folder": "",
"web_service_at_boot": False
"web_service_at_boot": False,
"last_gamelist_update": None
}
try:
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', {})}")
#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:
@@ -110,6 +111,27 @@ def save_rgsx_settings(settings):
print(f"Erreur lors de la sauvegarde de rgsx_settings.json: {str(e)}")
def get_last_gamelist_update(settings=None):
"""Récupère la date de dernière mise à jour de la liste des jeux."""
if settings is None:
settings = load_rgsx_settings()
return settings.get("last_gamelist_update", None)
def set_last_gamelist_update(date_string=None):
"""Définit la date de dernière mise à jour de la liste des jeux.
Si date_string est None, utilise la date actuelle.
"""
from datetime import datetime
settings = load_rgsx_settings()
if date_string is None:
date_string = datetime.now().strftime("%Y-%m-%d")
settings["last_gamelist_update"] = date_string
save_rgsx_settings(settings)
logger.info(f"Date de dernière mise à jour de la liste des jeux: {date_string}")
return date_string
def load_symlink_settings():
"""Load symlink settings from rgsx_settings.json."""

View File

@@ -1,3 +1,3 @@
{
"version": "2.4.0.0"
"version": "2.4.1.0"
}