mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b437f31854 | ||
|
|
08f3e64d2a | ||
|
|
4968af2da9 | ||
|
|
920914b374 | ||
|
|
a326cb0b67 | ||
|
|
c9fdf93221 | ||
|
|
184a8c64fe | ||
|
|
9a2e4ce0db | ||
|
|
73eceeb777 | ||
|
|
2fcc4ca6df | ||
|
|
2ed889540b | ||
|
|
e9a610b5dd | ||
|
|
bd3b885736 | ||
|
|
1592671ddc | ||
|
|
4e029aabf1 | ||
|
|
970fcaf197 | ||
|
|
ff30e6d297 |
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -30,16 +30,9 @@ jobs:
|
||||
zip -r "../../dist/RGSX_update_latest.zip" . \
|
||||
-x "logs/*" \
|
||||
"logs/**" \
|
||||
"images/*" \
|
||||
"images/**" \
|
||||
"games/*" \
|
||||
"games/**" \
|
||||
"scripts/*" \
|
||||
"scripts/**" \
|
||||
"__pycache__/*" \
|
||||
"__pycache__/**" \
|
||||
"*.pyc" \
|
||||
"sources.json" \
|
||||
"*.log"
|
||||
|
||||
cd ../..
|
||||
@@ -52,16 +45,9 @@ jobs:
|
||||
zip -r "dist/RGSX_full_latest.zip" ports windows \
|
||||
-x "ports/RGSX/logs/*" \
|
||||
"ports/RGSX/logs/**" \
|
||||
"ports/RGSX/images/*" \
|
||||
"ports/RGSX/images/**" \
|
||||
"ports/RGSX/games/*" \
|
||||
"ports/RGSX/games/**" \
|
||||
"ports/RGSX/scripts/*" \
|
||||
"ports/RGSX/scripts/**" \
|
||||
"ports/RGSX/__pycache__/*" \
|
||||
"ports/RGSX/__pycache__/**" \
|
||||
"ports/RGSX/*.pyc" \
|
||||
"ports/RGSX/sources.json" \
|
||||
"ports/RGSX/*.log" \
|
||||
"windows/logs/*" \
|
||||
"windows/*.xml" \
|
||||
@@ -88,12 +74,12 @@ jobs:
|
||||
|
||||
## 📥 Automatic Installation (Only for batocera Knulli)
|
||||
|
||||
### ON PC :
|
||||
### ON PC , NUC, SteamDeck or any x86_64 based:
|
||||
1. Open File Manager (F1) then "Applications" and launch xterm
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
|
||||
### ON RASPBERRY/ARM SBC / HANDHELD :
|
||||
### ON RASPBERRY/ ARM based SBC / HANDHELD :
|
||||
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
@@ -111,17 +97,12 @@ jobs:
|
||||
3. Launch RGSX from system "Windows"
|
||||
|
||||
|
||||
## 📥 Manual Update (you shouldn't need to do this as RGSX updates automatically on each start)
|
||||
|
||||
#### Batocera/Knulli
|
||||
## 📥 Manual Update (only if automatic doesn't work for some obcure reason)
|
||||
|
||||
#### Batocera/Knulli/Retrobat
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract only PORTS folder in `/userdata/roms/`
|
||||
3. Launch RGSX from the Ports menu
|
||||
|
||||
#### Retrobat
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract all folders in your Retrobat\roms folder
|
||||
3. Launch RGSX from system "Windows"
|
||||
2. Extract zip content on in `/userdata/roms/ports/RGSX`
|
||||
3. Launch RGSX
|
||||
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
13
README.md
13
README.md
@@ -1,11 +1,11 @@
|
||||
# 🎮 Retro Game Sets Xtra (RGSX)
|
||||
|
||||
**[Discord Support](https://discord.gg/Vph9jwg3VV)** • **[Installation](#-installation)** • **[French Documentation](https://github.com/RetroGameSets/RGSX/blob/main/README_FR.md)**
|
||||
**[Discord Support](https://discord.gg/Vph9jwg3VV)** • **[Installation](#-installation)** • **[French Documentation](https://github.com/RetroGameSets/RGSX/blob/main/README_FR.md)** • **[Troubleshoot / Common Errors](https://github.com/RetroGameSets/RGSX#%EF%B8%8F-troubleshooting)** •
|
||||
|
||||
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">
|
||||
@@ -201,11 +201,12 @@ RGSX includes a web interface that launched automatically when using RGSX for re
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Controls not working | Delete `/saves/ports/rgsx/controls.json` + restart |
|
||||
| Games not showing | Pause Menu > Games > Update Game Cache |
|
||||
| Download stuck | Check API keys in `/saves/ports/rgsx/` |
|
||||
| App crashes | Check `/roms/ports/RGSX/logs/RGSX.log` |
|
||||
| Controls not working | Delete `/saves/ports/rgsx/controls.json` + restart app, you can try delete /roms/ports/RGSX/assets/controls/xx.json too |
|
||||
| No games ? | Pause Menu > Games > Update Game Cache |
|
||||
| Missing systems on the list? | RGSX read es_systems.cfg to show only supported systems, if you want all systems : Pause Menu > Games > Show unsupported systems |
|
||||
| App crashes | Check `/roms/ports/RGSX/logs/RGSX.log` or `/roms/windows/logs/Retrobat_RGSX_log.txt` |
|
||||
| Layout change not applied | Restart RGSX after changing layout |
|
||||
| Downloading BIOS file is ok but you can't download any games? | Activate custom DNS on Pause Menu> Settings and reboot , server can be blocked by your ISP. check any threat/website protection on your router too, especially on ASUS one|
|
||||
|
||||
**Need help?** Share logs from `/roms/ports/RGSX/logs/` on [Discord](https://discord.gg/Vph9jwg3VV).
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -28,7 +34,7 @@ from display import (
|
||||
draw_toast, show_toast, THEME_COLORS
|
||||
)
|
||||
from language import _
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates, cancel_all_downloads
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates, cancel_all_downloads, download_queue_worker
|
||||
from controls import handle_controls, validate_menu_state, process_key_repeats, get_emergency_controls
|
||||
from controls_mapper import map_controls, draw_controls_mapping, get_actions
|
||||
from controls import load_controls_config
|
||||
@@ -91,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
|
||||
|
||||
@@ -170,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
|
||||
@@ -213,7 +219,7 @@ except Exception:
|
||||
normalized_names = [n.lower() for n in joystick_names]
|
||||
if not joystick_names:
|
||||
joystick_names = ["Clavier"]
|
||||
print("Aucun joystick détecté, utilisation du clavier par défaut")
|
||||
print("Aucun joystick detecte, utilisation du clavier par defaut")
|
||||
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
||||
config.joystick = False
|
||||
config.keyboard = True
|
||||
@@ -270,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()
|
||||
@@ -296,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":
|
||||
@@ -335,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}")
|
||||
@@ -378,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
|
||||
@@ -438,6 +442,10 @@ async def main():
|
||||
# Démarrer le serveur web en arrière-plan
|
||||
start_web_server()
|
||||
|
||||
# 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()
|
||||
|
||||
running = True
|
||||
loading_step = "none"
|
||||
sources = []
|
||||
@@ -473,7 +481,7 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
last_redraw_time = current_time
|
||||
# Forcer redraw toutes les 100 ms dans history avec téléchargement actif
|
||||
if config.menu_state == "history" and any(entry["status"] == "Téléchargement" for entry in config.history):
|
||||
if config.menu_state == "history" and any(entry["status"] in ["Downloading", "Téléchargement"] for entry in config.history):
|
||||
if current_time - last_redraw_time >= 100:
|
||||
config.needs_redraw = True
|
||||
last_redraw_time = current_time
|
||||
@@ -670,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",
|
||||
@@ -683,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",
|
||||
@@ -726,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)
|
||||
@@ -1078,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))
|
||||
@@ -1134,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":
|
||||
@@ -1224,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")
|
||||
@@ -1253,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:
|
||||
@@ -1260,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'}
|
||||
@@ -1365,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":
|
||||
|
||||
@@ -13,7 +13,10 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.2.9"
|
||||
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():
|
||||
@@ -133,6 +136,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# File d'attente de téléchargements (jobs en attente)
|
||||
download_queue = [] # Liste de dicts: {url, platform, game_name, ...}
|
||||
pending_download_is_queue = False # Indique si pending_download doit être ajouté à la queue
|
||||
# Indique si un téléchargement est en cours
|
||||
download_active = False
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -159,7 +162,7 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
|
||||
dev_field = preset.get('device') if isinstance(preset, dict) else None
|
||||
if isinstance(dev_field, str) and _sanitize(dev_field) == target_norm:
|
||||
logging.getLogger(__name__).info(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
print(f"Chargement préréglage (device) depuis le fichier: {fname}")
|
||||
print(f"Chargement prereglage (device) depuis le fichier: {fname}")
|
||||
return preset
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).warning(f"Échec scan préréglages par device: {e}")
|
||||
@@ -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")
|
||||
@@ -559,7 +567,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
config.search_query += keyboard_layout[row][col]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -567,14 +579,22 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "delete"):
|
||||
if config.search_query:
|
||||
config.search_query = config.search_query[:-1]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Suppression caractère: query={config.search_query}, jeux filtrés={len(config.filtered_games)}")
|
||||
elif is_input_matched(event, "space"):
|
||||
config.search_query += " "
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -583,7 +603,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.search_mode = False
|
||||
config.search_query = ""
|
||||
config.selected_key = (0, 0)
|
||||
config.filtered_games = config.games
|
||||
# Restaurer les jeux filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filter_active = True
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -605,8 +631,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "cancel"):
|
||||
config.search_mode = False
|
||||
config.search_query = ""
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
# Restaurer les jeux filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filter_active = True
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.filter_active = False
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -615,7 +646,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Saisie de texte alphanumérique
|
||||
if event.unicode.isalnum() or event.unicode == ' ':
|
||||
config.search_query += event.unicode
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -624,7 +659,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "delete"):
|
||||
if config.search_query:
|
||||
config.search_query = config.search_query[:-1]
|
||||
config.filtered_games = [game for game in config.games if config.search_query.lower() in game[0].lower()]
|
||||
# Appliquer d'abord les filtres avancés si actifs, puis le filtre par nom
|
||||
base_games = config.games
|
||||
if config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
base_games = config.game_filter_obj.apply_filters(config.games)
|
||||
config.filtered_games = [game for game in base_games if config.search_query.lower() in game[0].lower()]
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.needs_redraw = True
|
||||
@@ -709,6 +748,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Si extension non supportée ET pas en archive connu, afficher avertissement
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.pending_download = pending_download
|
||||
config.pending_download_is_queue = True # Marquer comme action queue
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
config.extension_confirm_selection = 0
|
||||
@@ -782,29 +822,69 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if config.extension_confirm_selection == 0: # 0 = Oui, 1 = Non
|
||||
if config.pending_download and len(config.pending_download) == 4:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
if is_1fichier_url(url):
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
|
||||
# Vérifier si c'est une action queue
|
||||
is_queue_action = getattr(config, 'pending_download_is_queue', False)
|
||||
|
||||
if is_queue_action:
|
||||
# Ajouter à la queue au lieu de télécharger immédiatement
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
queue_item = {
|
||||
'url': url,
|
||||
'platform': platform,
|
||||
'game_name': game_name,
|
||||
'is_zip_non_supported': is_zip_non_supported,
|
||||
'is_1fichier': is_1fichier_url(url),
|
||||
'task_id': task_id,
|
||||
'status': 'Queued'
|
||||
}
|
||||
config.download_queue.append(queue_item)
|
||||
|
||||
# Ajouter une entrée à l'historique avec status "Queued"
|
||||
config.history.append({
|
||||
'platform': platform,
|
||||
'game_name': game_name,
|
||||
'status': 'Queued',
|
||||
'url': url,
|
||||
'progress': 0,
|
||||
'message': _("download_queued"),
|
||||
'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'downloaded_size': 0,
|
||||
'total_size': 0,
|
||||
'task_id': task_id
|
||||
})
|
||||
save_history(config.history)
|
||||
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{game_name}\n{_('download_queued')}")
|
||||
|
||||
# Le worker de la queue détectera automatiquement le nouvel élément
|
||||
logger.debug(f"{game_name} ajouté à la file d'attente après confirmation. Queue size: {len(config.download_queue)}")
|
||||
else:
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
config.download_tasks[task_id] = (task, url, game_name, platform)
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{_('download_started')}: {game_name}")
|
||||
# Téléchargement immédiat
|
||||
if is_1fichier_url(url):
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
if missing_all_provider_keys():
|
||||
logger.warning("Aucune clé API - Mode gratuit 1fichier sera utilisé (attente requise)")
|
||||
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_from_1fichier(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
else:
|
||||
task_id = str(pygame.time.get_ticks())
|
||||
task = asyncio.create_task(download_rom(url, platform, game_name, is_zip_non_supported, task_id))
|
||||
config.download_tasks[task_id] = (task, url, game_name, platform)
|
||||
# Afficher un toast de notification
|
||||
show_toast(f"{_('download_started')}: {game_name}")
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
|
||||
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Téléchargement confirmé après avertissement: {game_name} pour {platform} depuis {url}, task_id={task_id}")
|
||||
config.pending_download = None
|
||||
config.pending_download_is_queue = False
|
||||
config.extension_confirm_selection = 0 # Réinitialiser la sélection
|
||||
action = "download"
|
||||
# Téléchargement simple - retourner au menu précédent
|
||||
# Retourner au menu précédent
|
||||
config.menu_state = config.previous_menu_state if config.previous_menu_state else "game"
|
||||
logger.debug(f"[CONTROLS_EXT_WARNING] Retour au menu {config.menu_state} après confirmation")
|
||||
else:
|
||||
@@ -912,22 +992,35 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.confirm_cancel_selection == 1: # Oui
|
||||
entry = config.history[config.current_history_item]
|
||||
task_id = entry.get("task_id")
|
||||
url = entry.get("url")
|
||||
# Annuler la tâche correspondante
|
||||
for task_id, (task, task_url, game_name, platform) in list(config.download_tasks.items()):
|
||||
if task_url == url:
|
||||
game_name = entry.get("game_name", "Unknown")
|
||||
|
||||
# Annuler via cancel_events (pour les threads de téléchargement)
|
||||
try:
|
||||
request_cancel(task_id)
|
||||
logger.debug(f"Signal d'annulation envoyé pour task_id={task_id}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de l'envoi du signal d'annulation: {e}")
|
||||
|
||||
# Annuler aussi la tâche asyncio si elle existe (pour les téléchargements directs)
|
||||
for tid, (task, task_url, tname, tplatform) in list(config.download_tasks.items()):
|
||||
if tid == task_id or task_url == url:
|
||||
try:
|
||||
request_cancel(task_id)
|
||||
except Exception:
|
||||
pass
|
||||
task.cancel()
|
||||
del config.download_tasks[task_id]
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
logger.debug(f"Téléchargement annulé: {game_name}")
|
||||
task.cancel()
|
||||
del config.download_tasks[tid]
|
||||
logger.debug(f"Tâche asyncio annulée: {tname}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de l'annulation de la tâche asyncio: {e}")
|
||||
break
|
||||
|
||||
# Mettre à jour l'entrée historique
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
logger.debug(f"Téléchargement annulé: {game_name}")
|
||||
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
else: # Non
|
||||
@@ -1003,7 +1096,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
options.append("scraper")
|
||||
|
||||
# Options selon statut
|
||||
if status == "Download_OK" or status == "Completed":
|
||||
if status == "Queued":
|
||||
# En attente dans la queue
|
||||
options.append("remove_from_queue")
|
||||
elif status in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
# Téléchargement en cours
|
||||
options.append("cancel_download")
|
||||
elif status == "Download_OK" or status == "Completed":
|
||||
# Vérifier si c'est une archive ET si le fichier existe
|
||||
if actual_filename and file_exists:
|
||||
ext = os.path.splitext(actual_filename)[1].lower()
|
||||
@@ -1046,7 +1145,37 @@ def handle_controls(event, sources, joystick, screen):
|
||||
selected_option = options[sel]
|
||||
logger.debug(f"history_game_options: CONFIRM option={selected_option}")
|
||||
|
||||
if selected_option == "download_folder":
|
||||
if selected_option == "remove_from_queue":
|
||||
# Retirer de la queue
|
||||
task_id = entry.get("task_id")
|
||||
url = entry.get("url")
|
||||
|
||||
# Chercher et retirer de la queue
|
||||
for i, queue_item in enumerate(config.download_queue):
|
||||
if queue_item.get("task_id") == task_id or queue_item.get("url") == url:
|
||||
config.download_queue.pop(i)
|
||||
logger.debug(f"Jeu retiré de la queue: {game_name}")
|
||||
break
|
||||
|
||||
# Mettre à jour l'entrée historique avec status Canceled
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
save_history(config.history)
|
||||
|
||||
# Retour à l'historique
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
|
||||
elif selected_option == "cancel_download":
|
||||
# Rediriger vers le dialogue de confirmation (même que bouton cancel)
|
||||
config.previous_menu_state = "history"
|
||||
config.menu_state = "confirm_cancel_download"
|
||||
config.confirm_cancel_selection = 0
|
||||
config.needs_redraw = True
|
||||
logger.debug("Redirection vers confirm_cancel_download depuis history_game_options")
|
||||
|
||||
elif selected_option == "download_folder":
|
||||
# Afficher le chemin de destination
|
||||
config.previous_menu_state = "history_game_options"
|
||||
config.menu_state = "history_show_folder"
|
||||
@@ -1482,8 +1611,15 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Confirmation quitter
|
||||
elif config.menu_state == "confirm_exit":
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.confirm_selection == 1:
|
||||
# Sous-menu Quit: 0=Quit RGSX, 1=Restart RGSX, 2=Back
|
||||
if is_input_matched(event, "up"):
|
||||
config.confirm_selection = max(0, config.confirm_selection - 1)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
config.confirm_selection = min(2, config.confirm_selection + 1)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if config.confirm_selection == 0: # Quit RGSX
|
||||
# Mark all in-progress downloads as canceled in history
|
||||
try:
|
||||
for entry in getattr(config, 'history', []) or []:
|
||||
@@ -1495,7 +1631,9 @@ def handle_controls(event, sources, joystick, screen):
|
||||
except Exception:
|
||||
pass
|
||||
return "quit"
|
||||
else:
|
||||
elif config.confirm_selection == 1: # Restart RGSX
|
||||
restart_application(2000)
|
||||
elif config.confirm_selection == 2: # Back
|
||||
# Retour à l'état capturé (confirm_exit_origin) sinon previous_menu_state sinon platform
|
||||
target = getattr(config, 'confirm_exit_origin', getattr(config, 'previous_menu_state', 'platform'))
|
||||
config.menu_state = validate_menu_state(target)
|
||||
@@ -1505,11 +1643,18 @@ def handle_controls(event, sources, joystick, screen):
|
||||
except Exception:
|
||||
pass
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (annulation)")
|
||||
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
config.confirm_selection = 1 - config.confirm_selection
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (back)")
|
||||
elif is_input_matched(event, "cancel"):
|
||||
# Retour à l'état capturé
|
||||
target = getattr(config, 'confirm_exit_origin', getattr(config, 'previous_menu_state', 'platform'))
|
||||
config.menu_state = validate_menu_state(target)
|
||||
if hasattr(config, 'confirm_exit_origin'):
|
||||
try:
|
||||
delattr(config, 'confirm_exit_origin')
|
||||
except Exception:
|
||||
pass
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Changement sélection confirm_exit: {config.confirm_selection}")
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (cancel)")
|
||||
|
||||
# Menu pause
|
||||
elif config.menu_state == "pause_menu":
|
||||
@@ -1521,46 +1666,46 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Start: retour à {config.menu_state} depuis pause_menu")
|
||||
elif is_input_matched(event, "up"):
|
||||
config.selected_option = max(0, config.selected_option - 1)
|
||||
# Menu racine hiérarchique: nombre dynamique (langue + catégories)
|
||||
total = getattr(config, 'pause_menu_total_options', 7)
|
||||
config.selected_option = (config.selected_option - 1) % total
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
# Menu racine hiérarchique: nombre dynamique (langue + catégories)
|
||||
total = getattr(config, 'pause_menu_total_options', 7)
|
||||
config.selected_option = min(total - 1, config.selected_option + 1)
|
||||
config.selected_option = (config.selected_option + 1) % total
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if config.selected_option == 0: # Language selector direct
|
||||
if config.selected_option == 0: # Games submenu
|
||||
config.menu_state = "pause_games_menu"
|
||||
if not hasattr(config, 'pause_games_selection'):
|
||||
config.pause_games_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 1: # Language selector direct
|
||||
config.menu_state = "language_select"
|
||||
config.previous_menu_state = "pause_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 1: # Controls submenu
|
||||
elif config.selected_option == 2: # Controls submenu
|
||||
config.menu_state = "pause_controls_menu"
|
||||
if not hasattr(config, 'pause_controls_selection'):
|
||||
config.pause_controls_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 2: # Display submenu
|
||||
elif config.selected_option == 3: # Display submenu
|
||||
config.menu_state = "pause_display_menu"
|
||||
if not hasattr(config, 'pause_display_selection'):
|
||||
config.pause_display_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 3: # Games submenu
|
||||
config.menu_state = "pause_games_menu"
|
||||
if not hasattr(config, 'pause_games_selection'):
|
||||
config.pause_games_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 4: # Settings submenu
|
||||
config.menu_state = "pause_settings_menu"
|
||||
if not hasattr(config, 'pause_settings_selection'):
|
||||
config.pause_settings_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 5: # Restart
|
||||
restart_application(2000)
|
||||
elif config.selected_option == 6: # Support
|
||||
elif config.selected_option == 5: # Support
|
||||
success, message, zip_path = generate_support_zip()
|
||||
if success:
|
||||
config.support_zip_path = zip_path
|
||||
@@ -1571,7 +1716,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.menu_state = "support_dialog"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 7: # Quit
|
||||
elif config.selected_option == 6: # Quit submenu
|
||||
# Capturer l'origine pause_menu pour retour si annulation
|
||||
config.confirm_exit_origin = "pause_menu"
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
@@ -1618,7 +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)
|
||||
total = 6 # layout, font size, footer font size, font family, allow unknown extensions, back
|
||||
# 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
|
||||
@@ -1626,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()
|
||||
@@ -1713,8 +1825,31 @@ 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")):
|
||||
# 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
|
||||
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}")
|
||||
# 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
|
||||
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)
|
||||
@@ -1723,8 +1858,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}")
|
||||
# 5 back
|
||||
elif sel == 5 and is_input_matched(event, "confirm"):
|
||||
# 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
|
||||
@@ -1733,10 +1868,99 @@ 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)
|
||||
total = 7 # history, source, redownload, unsupported, hide premium, filter, back
|
||||
total = 7 # update cache, history, source, unsupported, hide premium, filter, back
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_games_selection = (sel - 1) % total
|
||||
config.needs_redraw = True
|
||||
@@ -1744,14 +1968,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.pause_games_selection = (sel + 1) % total
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
if sel == 0 and is_input_matched(event, "confirm"): # history
|
||||
if sel == 0 and is_input_matched(event, "confirm"): # update cache
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "reload_games_data"
|
||||
config.redownload_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and is_input_matched(event, "confirm"): # history
|
||||
config.history = load_history()
|
||||
config.current_history_item = 0
|
||||
config.history_scroll_offset = 0
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
elif sel == 2 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")): # source mode
|
||||
try:
|
||||
current_mode = get_sources_mode()
|
||||
new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx')
|
||||
@@ -1766,11 +1995,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
logger.info(f"Changement du mode des sources vers {new_mode}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur changement mode sources: {e}")
|
||||
elif sel == 2 and is_input_matched(event, "confirm"): # redownload cache
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "reload_games_data"
|
||||
config.redownload_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
elif sel == 3 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")): # unsupported toggle
|
||||
try:
|
||||
current = get_show_unsupported_platforms()
|
||||
@@ -1809,11 +2033,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif config.menu_state == "pause_settings_menu":
|
||||
sel = getattr(config, 'pause_settings_selection', 0)
|
||||
# Calculer le nombre total d'options selon le système
|
||||
total = 4 # music, symlink, api keys, back
|
||||
# Liste des options : music, symlink, [web_service], [custom_dns], api keys, back
|
||||
total = 4 # music, symlink, api keys, back (Windows)
|
||||
web_service_index = -1
|
||||
custom_dns_index = -1
|
||||
api_keys_index = 2
|
||||
back_index = 3
|
||||
|
||||
if config.OPERATING_SYSTEM == "Linux":
|
||||
total = 5 # music, symlink, web_service, api keys, back
|
||||
total = 6 # music, symlink, web_service, custom_dns, api keys, back
|
||||
web_service_index = 2
|
||||
custom_dns_index = 3
|
||||
api_keys_index = 4
|
||||
back_index = 5
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_settings_selection = (sel - 1) % total
|
||||
@@ -1862,12 +2094,31 @@ def handle_controls(event, sources, joystick, screen):
|
||||
else:
|
||||
logger.error(f"Erreur toggle service web: {message}")
|
||||
threading.Thread(target=toggle_service, daemon=True).start()
|
||||
# Option API Keys (index varie selon Linux ou pas)
|
||||
elif sel == (web_service_index + 1 if web_service_index >= 0 else 2) and is_input_matched(event, "confirm"):
|
||||
# Option 3: Custom DNS toggle (seulement si Linux)
|
||||
elif sel == custom_dns_index and custom_dns_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from utils import check_custom_dns_status, toggle_custom_dns_at_boot
|
||||
current_status = check_custom_dns_status()
|
||||
# Afficher un message de chargement
|
||||
config.popup_message = _("settings_custom_dns_enabling") if not current_status else _("settings_custom_dns_disabling")
|
||||
config.popup_timer = 1000
|
||||
config.needs_redraw = True
|
||||
# Exécuter en thread pour ne pas bloquer l'UI
|
||||
def toggle_dns():
|
||||
success, message = toggle_custom_dns_at_boot(not current_status)
|
||||
config.popup_message = message
|
||||
config.popup_timer = 5000 if success else 7000
|
||||
config.needs_redraw = True
|
||||
if success:
|
||||
logger.info(f"Custom DNS {'activé' if not current_status else 'désactivé'} au démarrage")
|
||||
else:
|
||||
logger.error(f"Erreur toggle custom DNS: {message}")
|
||||
threading.Thread(target=toggle_dns, daemon=True).start()
|
||||
# Option API Keys
|
||||
elif sel == api_keys_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_api_keys_status"
|
||||
config.needs_redraw = True
|
||||
# Option Back (dernière option)
|
||||
elif sel == (total - 1) and is_input_matched(event, "confirm"):
|
||||
elif sel == back_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
@@ -1889,14 +2140,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
|
||||
@@ -1942,8 +2194,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)
|
||||
@@ -1953,8 +2237,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)
|
||||
@@ -1963,8 +2247,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"
|
||||
@@ -1982,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"):
|
||||
@@ -2007,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
|
||||
@@ -2099,7 +2435,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Recherche par nom (mode existant)
|
||||
config.search_mode = True
|
||||
config.search_query = ""
|
||||
config.filtered_games = config.games
|
||||
# Initialiser avec les jeux déjà filtrés par les filtres avancés si actifs
|
||||
if hasattr(config, 'game_filter_obj') and config.game_filter_obj and config.game_filter_obj.is_active():
|
||||
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
|
||||
else:
|
||||
config.filtered_games = config.games
|
||||
config.current_game = 0
|
||||
config.scroll_offset = 0
|
||||
config.selected_key = (0, 0)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,30 +69,67 @@ class GameFilters:
|
||||
name = game_name.upper()
|
||||
regions = []
|
||||
|
||||
# Patterns de région communs
|
||||
if 'USA' in name or 'US)' in name:
|
||||
regions.append('USA')
|
||||
# Patterns de région communs - chercher les codes entre parenthèses d'abord
|
||||
# Codes de région/langue dans les parenthèses (Ex: (Fr,De) ou (En,Nl))
|
||||
paren_content = re.findall(r'\(([^)]+)\)', name)
|
||||
for content in paren_content:
|
||||
# Codes de langue/région séparés par virgules
|
||||
codes = [c.strip() for c in content.split(',')]
|
||||
for code in codes:
|
||||
if code in ['FR', 'FRA']:
|
||||
if 'France' not in regions:
|
||||
regions.append('France')
|
||||
elif code in ['DE', 'GER', 'DEU']:
|
||||
if 'Germany' not in regions:
|
||||
regions.append('Germany')
|
||||
elif code in ['EN', 'ENG'] or code.startswith('EN-'):
|
||||
# EN peut être USA, Europe ou autre - on vérifie le contexte
|
||||
if 'EU' in codes or 'EUR' in codes:
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
elif code in ['ES', 'ESP', 'SPA']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
elif code in ['IT', 'ITA']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
elif code in ['NL', 'NLD', 'DU', 'DUT']:
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
elif code in ['PT', 'POR']:
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
|
||||
# Patterns de région complets (mots entiers)
|
||||
if 'USA' in name or 'US)' in name or re.search(r'\bUS\b', name):
|
||||
if 'USA' not in regions:
|
||||
regions.append('USA')
|
||||
if 'CANADA' in name or 'CA)' in name:
|
||||
regions.append('Canada')
|
||||
if 'EUROPE' in name or 'EU)' in name:
|
||||
regions.append('Europe')
|
||||
if 'Canada' not in regions:
|
||||
regions.append('Canada')
|
||||
if 'EUROPE' in name or 'EU)' in name or re.search(r'\bEU\b', name):
|
||||
if 'Europe' not in regions:
|
||||
regions.append('Europe')
|
||||
if 'FRANCE' in name or 'FR)' in name:
|
||||
regions.append('France')
|
||||
if 'France' not in regions:
|
||||
regions.append('France')
|
||||
if 'GERMANY' in name or 'DE)' in name or 'GER)' in name:
|
||||
regions.append('Germany')
|
||||
if 'JAPAN' in name or 'JP)' in name or 'JPN)' in name:
|
||||
regions.append('Japan')
|
||||
if 'Germany' not in regions:
|
||||
regions.append('Germany')
|
||||
if 'JAPAN' in name or 'JP)' in name or 'JPN)' in name or re.search(r'\bJP\b', name):
|
||||
if 'Japan' not in regions:
|
||||
regions.append('Japan')
|
||||
if 'KOREA' in name or 'KR)' in name or 'KOR)' in name:
|
||||
regions.append('Korea')
|
||||
if 'Korea' not in regions:
|
||||
regions.append('Korea')
|
||||
if 'WORLD' in name:
|
||||
regions.append('World')
|
||||
if 'World' not in regions:
|
||||
regions.append('World')
|
||||
|
||||
# Autres régions
|
||||
if re.search(r'\b(AUSTRALIA|ASIA|KOREA|BRAZIL|CHINA|RUSSIA|SCANDINAVIA|'
|
||||
r'SPAIN|FRANCE|GERMANY|ITALY|CANADA)\b', name):
|
||||
if 'CANADA' in name:
|
||||
regions.append('Canada')
|
||||
else:
|
||||
if re.search(r'\b(AUSTRALIA|ASIA|BRAZIL|CHINA|RUSSIA|SCANDINAVIA|'
|
||||
r'SPAIN|ITALY)\b', name):
|
||||
if 'Other' not in regions:
|
||||
regions.append('Other')
|
||||
|
||||
# Si aucune région trouvée
|
||||
@@ -157,14 +194,22 @@ class GameFilters:
|
||||
|
||||
def get_region_priority(self, game_name: str) -> int:
|
||||
"""Obtient la priorité de région pour un jeu (pour one-rom-per-game)"""
|
||||
name = game_name.upper()
|
||||
# Utiliser la fonction de détection de régions pour être cohérent
|
||||
game_regions = self.get_game_regions(game_name)
|
||||
|
||||
for i, region in enumerate(self.region_priority):
|
||||
region_upper = region.upper()
|
||||
if region_upper in name:
|
||||
return i
|
||||
# Trouver la meilleure priorité parmi toutes les régions détectées
|
||||
best_priority = len(self.region_priority) # Par défaut: priorité la plus basse
|
||||
|
||||
return len(self.region_priority) # Autres régions (priorité la plus basse)
|
||||
for region in game_regions:
|
||||
try:
|
||||
priority = self.region_priority.index(region)
|
||||
if priority < best_priority:
|
||||
best_priority = priority
|
||||
except ValueError:
|
||||
# La région n'est pas dans la liste de priorité
|
||||
continue
|
||||
|
||||
return best_priority
|
||||
|
||||
def apply_filters(self, games: List[Tuple]) -> List[Tuple]:
|
||||
"""
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Kostenloser Modus] Abgeschlossen: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download vom Benutzer abgebrochen.",
|
||||
"download_removed_from_queue": "Aus der Download-Warteschlange entfernt",
|
||||
"extension_warning_zip": "Die Datei '{0}' ist ein Archiv und Batocera unterstützt keine Archive für dieses System. Die automatische Extraktion der Datei erfolgt nach dem Download, fortfahren?",
|
||||
"extension_warning_unsupported": "Die Dateierweiterung für '{0}' wird laut der Konfiguration es_systems.cfg von Batocera nicht unterstützt. Möchtest du fortfahren?",
|
||||
"extension_warning_enable_unknown_hint": "\nUm diese Meldung auszublenden: \"Warnung bei unbekannter Erweiterung ausblenden\" in Pausenmenü > Anzeige aktivieren",
|
||||
@@ -51,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}",
|
||||
@@ -64,10 +67,21 @@
|
||||
"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",
|
||||
"menu_restart": "Neustart",
|
||||
"menu_restart": "RGSX neu starten",
|
||||
"menu_support": "Unterstützung",
|
||||
"menu_filter_platforms": "Systeme filtern",
|
||||
"filter_platforms_title": "Systemsichtbarkeit",
|
||||
@@ -80,6 +94,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Ausblenden der Warnung bei unbekannter Erweiterung aktiviert",
|
||||
"menu_allow_unknown_ext_disabled": "Ausblenden der Warnung bei unbekannter Erweiterung deaktiviert",
|
||||
"menu_quit": "Beenden",
|
||||
"menu_quit_app": "RGSX beenden",
|
||||
"support_dialog_title": "Support-Datei",
|
||||
"support_dialog_message": "Eine Support-Datei wurde mit allen Ihren Konfigurations- und Protokolldateien erstellt.\n\nDatei: {0}\n\nUm Hilfe zu erhalten:\n1. Treten Sie dem RGSX Discord-Server bei\n2. Beschreiben Sie Ihr Problem\n3. Teilen Sie diese ZIP-Datei\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
"support_dialog_error": "Fehler beim Erstellen der Support-Datei:\n{0}\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
@@ -184,7 +199,9 @@
|
||||
"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus",
|
||||
"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden",
|
||||
"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen",
|
||||
"instruction_pause_quit": "RGSX Anwendung beenden",
|
||||
"instruction_pause_quit": "Menü für Beenden oder Neustart aufrufen",
|
||||
"instruction_quit_app": "RGSX Anwendung beenden",
|
||||
"instruction_quit_restart": "RGSX Anwendung neu starten",
|
||||
"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen",
|
||||
"instruction_controls_remap": "Tasten / Buttons neu zuordnen",
|
||||
"instruction_generic_back": "Zum vorherigen Menü zurückkehren",
|
||||
@@ -192,6 +209,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}",
|
||||
@@ -244,6 +264,8 @@
|
||||
"history_option_extract_archive": "Archiv extrahieren",
|
||||
"history_option_open_file": "Datei öffnen",
|
||||
"history_option_scraper": "Metadaten abrufen",
|
||||
"history_option_remove_from_queue": "Aus Warteschlange entfernen",
|
||||
"history_option_cancel_download": "Download abbrechen",
|
||||
"history_option_delete_game": "Spiel löschen",
|
||||
"history_option_error_info": "Fehlerdetails",
|
||||
"history_option_retry": "Download wiederholen",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Free mode] Completed: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download canceled by user.",
|
||||
"download_removed_from_queue": "Removed from download queue",
|
||||
"extension_warning_zip": "The file '{0}' is an archive and Batocera does not support archives for this system. Automatic extraction will occur after download, continue?",
|
||||
"extension_warning_unsupported": "The file extension for '{0}' is not supported by Batocera according to the es_systems.cfg configuration. Do you want to continue?",
|
||||
"extension_warning_enable_unknown_hint": "\nTo hide this message: enable \"Hide unknown extension warning\" in Pause Menu > Display",
|
||||
@@ -51,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}",
|
||||
@@ -64,10 +67,21 @@
|
||||
"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",
|
||||
"menu_restart": "Restart",
|
||||
"menu_restart": "Restart RGSX",
|
||||
"menu_filter_platforms": "Filter systems",
|
||||
"filter_platforms_title": "Systems visibility",
|
||||
"filter_platforms_info": "Visible: {0} | Hidden: {1} / Total: {2}",
|
||||
@@ -80,6 +94,7 @@
|
||||
"menu_allow_unknown_ext_disabled": "Hide unknown extension warning disabled",
|
||||
"menu_support": "Support",
|
||||
"menu_quit": "Quit",
|
||||
"menu_quit_app": "Quit RGSX",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"button_OK": "OK",
|
||||
@@ -186,7 +201,9 @@
|
||||
"instruction_pause_settings": "Music, symlink option & API keys status",
|
||||
"instruction_pause_restart": "Restart RGSX to reload configuration",
|
||||
"instruction_pause_support": "Generate a diagnostic ZIP file for support",
|
||||
"instruction_pause_quit": "Exit the RGSX application",
|
||||
"instruction_pause_quit": "Access menu to quit or restart",
|
||||
"instruction_quit_app": "Exit the RGSX application",
|
||||
"instruction_quit_restart": "Restart the RGSX application",
|
||||
"instruction_controls_help": "Show full controller & keyboard reference",
|
||||
"instruction_controls_remap": "Change button / key bindings",
|
||||
"instruction_generic_back": "Return to the previous menu",
|
||||
@@ -194,6 +211,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}",
|
||||
@@ -246,6 +266,8 @@
|
||||
"history_option_extract_archive": "Extract archive",
|
||||
"history_option_open_file": "Open file",
|
||||
"history_option_scraper": "Scrape metadata",
|
||||
"history_option_remove_from_queue": "Remove from queue",
|
||||
"history_option_cancel_download": "Cancel download",
|
||||
"history_option_delete_game": "Delete game",
|
||||
"history_option_error_info": "Error details",
|
||||
"history_option_retry": "Retry download",
|
||||
|
||||
@@ -44,14 +44,15 @@
|
||||
"free_mode_completed": "[Modo gratuito] Completado: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Descarga cancelada por el usuario.",
|
||||
"download_removed_from_queue": "Eliminado de la cola de descarga",
|
||||
"extension_warning_zip": "El archivo '{0}' es un archivo comprimido y Batocera no soporta archivos comprimidos para este sistema. La extracción automática del archivo se realizará después de la descarga, ¿continuar?",
|
||||
"extension_warning_unsupported": "La extensión del archivo '{0}' no está soportada por Batocera según la configuración es_systems.cfg. ¿Deseas continuar?",
|
||||
"extension_warning_enable_unknown_hint": "\nPara no mostrar este mensaje: activa \"Ocultar aviso de extensión desconocida\" en Menú de pausa > Pantalla",
|
||||
"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",
|
||||
@@ -64,10 +65,21 @@
|
||||
"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",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_restart": "Reiniciar RGSX",
|
||||
"menu_support": "Soporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidad de sistemas",
|
||||
@@ -80,6 +92,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensión desconocida oculto (activado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensión desconocida visible (desactivado)",
|
||||
"menu_quit": "Salir",
|
||||
"menu_quit_app": "Salir de RGSX",
|
||||
"support_dialog_title": "Archivo de soporte",
|
||||
"support_dialog_message": "Se ha creado un archivo de soporte con todos sus archivos de configuración y registros.\n\nArchivo: {0}\n\nPara obtener ayuda:\n1. Únete al servidor Discord de RGSX\n2. Describe tu problema\n3. Comparte este archivo ZIP\n\nPresiona {1} para volver al menú.",
|
||||
"support_dialog_error": "Error al generar el archivo de soporte:\n{0}\n\nPresiona {1} para volver al menú.",
|
||||
@@ -186,7 +199,9 @@
|
||||
"instruction_pause_settings": "Música, opción symlink y estado de claves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recargar configuración",
|
||||
"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte",
|
||||
"instruction_pause_quit": "Salir de la aplicación RGSX",
|
||||
"instruction_pause_quit": "Acceder al menú para salir o reiniciar",
|
||||
"instruction_quit_app": "Salir de la aplicación RGSX",
|
||||
"instruction_quit_restart": "Reiniciar la aplicación RGSX",
|
||||
"instruction_controls_help": "Mostrar referencia completa de mando y teclado",
|
||||
"instruction_controls_remap": "Cambiar asignación de botones / teclas",
|
||||
"instruction_generic_back": "Volver al menú anterior",
|
||||
@@ -194,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}",
|
||||
@@ -246,6 +264,8 @@
|
||||
"history_option_extract_archive": "Extraer archivo",
|
||||
"history_option_open_file": "Abrir archivo",
|
||||
"history_option_scraper": "Obtener metadatos",
|
||||
"history_option_remove_from_queue": "Quitar de la cola",
|
||||
"history_option_cancel_download": "Cancelar descarga",
|
||||
"history_option_delete_game": "Eliminar juego",
|
||||
"history_option_error_info": "Detalles del error",
|
||||
"history_option_retry": "Reintentar descarga",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Mode gratuit] Terminé: {0}",
|
||||
"download_status": "{0} : {1}",
|
||||
"download_canceled": "Téléchargement annulé par l'utilisateur.",
|
||||
"download_removed_from_queue": "Retiré de la file de téléchargement",
|
||||
"extension_warning_zip": "Le fichier '{0}' est une archive et Batocera ne prend pas en charge les archives pour ce système. L'extraction automatique du fichier aura lieu après le téléchargement, continuer ?",
|
||||
"extension_warning_unsupported": "L'extension du fichier '{0}' n'est pas supportée par Batocera d'après la configuration es_systems.cfg. Voulez-vous continuer ?",
|
||||
"extension_warning_enable_unknown_hint": "\nPour ne plus afficher ce messager : Activer l'option \"Masquer avertissement\" dans le Menu Pause>Display",
|
||||
@@ -51,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}",
|
||||
@@ -64,12 +67,24 @@
|
||||
"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",
|
||||
"menu_quit_app": "Quitter RGSX",
|
||||
"menu_music_enabled": "Musique activée : {0}",
|
||||
"menu_music_disabled": "Musique désactivée",
|
||||
"menu_restart": "Redémarrer",
|
||||
"menu_restart": "Redémarrer RGSX",
|
||||
"menu_filter_platforms": "Filtrer les systèmes",
|
||||
"filter_platforms_title": "Affichage des systèmes",
|
||||
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
||||
@@ -186,7 +201,9 @@
|
||||
"instruction_pause_settings": "Musique, option symlink & statut des clés API",
|
||||
"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration",
|
||||
"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance",
|
||||
"instruction_pause_quit": "Quitter l'application RGSX",
|
||||
"instruction_pause_quit": "Accéder au menu pour quitter ou redémarrer",
|
||||
"instruction_quit_app": "Quitter l'application RGSX",
|
||||
"instruction_quit_restart": "Redémarrer l'application RGSX",
|
||||
"instruction_controls_help": "Afficher la référence complète manette & clavier",
|
||||
"instruction_controls_remap": "Modifier l'association boutons / touches",
|
||||
"instruction_generic_back": "Revenir au menu précédent",
|
||||
@@ -194,6 +211,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}",
|
||||
@@ -246,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",
|
||||
|
||||
@@ -44,14 +44,15 @@
|
||||
"free_mode_completed": "[Modalità gratuita] Completato: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download annullato dall'utente.",
|
||||
"download_removed_from_queue": "Rimosso dalla coda di download",
|
||||
"extension_warning_zip": "Il file '{0}' è un archivio e Batocera non supporta archivi per questo sistema. L'estrazione automatica avverrà dopo il download, continuare?",
|
||||
"extension_warning_unsupported": "L'estensione del file '{0}' non è supportata da Batocera secondo la configurazione di es_systems.cfg. Vuoi continuare?",
|
||||
"extension_warning_enable_unknown_hint": "\nPer non visualizzare questo messaggio: abilita \"Nascondi avviso estensione sconosciuta\" in Menu Pausa > Schermo",
|
||||
"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",
|
||||
@@ -64,10 +65,19 @@
|
||||
"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",
|
||||
"menu_restart": "Riavvia RGSX",
|
||||
"menu_support": "Supporto",
|
||||
"menu_filter_platforms": "Filtra sistemi",
|
||||
"filter_platforms_title": "Visibilità sistemi",
|
||||
@@ -80,6 +90,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Nascondi avviso estensione sconosciuta abilitato",
|
||||
"menu_allow_unknown_ext_disabled": "Nascondi avviso estensione sconosciuta disabilitato",
|
||||
"menu_quit": "Esci",
|
||||
"menu_quit_app": "Esci da RGSX",
|
||||
"support_dialog_title": "File di supporto",
|
||||
"support_dialog_message": "È stato creato un file di supporto con tutti i file di configurazione e di registro.\n\nFile: {0}\n\nPer ottenere aiuto:\n1. Unisciti al server Discord RGSX\n2. Descrivi il tuo problema\n3. Condividi questo file ZIP\n\nPremi {1} per tornare al menu.",
|
||||
"support_dialog_error": "Errore durante la generazione del file di supporto:\n{0}\n\nPremi {1} per tornare al menu.",
|
||||
@@ -183,7 +194,9 @@
|
||||
"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API",
|
||||
"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione",
|
||||
"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto",
|
||||
"instruction_pause_quit": "Uscire dall'applicazione RGSX",
|
||||
"instruction_pause_quit": "Accedere al menu per uscire o riavviare",
|
||||
"instruction_quit_app": "Uscire dall'applicazione RGSX",
|
||||
"instruction_quit_restart": "Riavviare l'applicazione RGSX",
|
||||
"instruction_controls_help": "Mostrare riferimento completo controller & tastiera",
|
||||
"instruction_controls_remap": "Modificare associazione pulsanti / tasti",
|
||||
"instruction_generic_back": "Tornare al menu precedente",
|
||||
@@ -191,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}",
|
||||
@@ -243,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",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"free_mode_completed": "[Modo gratuito] Concluído: {0}",
|
||||
"download_status": "{0}: {1}",
|
||||
"download_canceled": "Download cancelado pelo usuário.",
|
||||
"download_removed_from_queue": "Removido da fila de download",
|
||||
"extension_warning_zip": "O arquivo '{0}' é um arquivo compactado e o Batocera não suporta arquivos compactados para este sistema. A extração automática ocorrerá após o download, continuar?",
|
||||
"extension_warning_unsupported": "A extensão do arquivo '{0}' não é suportada pelo Batocera segundo a configuração es_systems.cfg. Deseja continuar?",
|
||||
"extension_warning_enable_unknown_hint": "\nPara não ver esta mensagem: ative \"Ocultar aviso de extensão desconhecida\" em Menu de Pausa > Exibição",
|
||||
@@ -51,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}",
|
||||
@@ -64,10 +67,21 @@
|
||||
"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",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_restart": "Reiniciar RGSX",
|
||||
"menu_support": "Suporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidade dos sistemas",
|
||||
@@ -80,6 +94,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensão desconhecida oculto (ativado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensão desconhecida visível (desativado)",
|
||||
"menu_quit": "Sair",
|
||||
"menu_quit_app": "Sair do RGSX",
|
||||
"support_dialog_title": "Arquivo de suporte",
|
||||
"support_dialog_message": "Foi criado um arquivo de suporte com todos os seus arquivos de configuração e logs.\n\nArquivo: {0}\n\nPara obter ajuda:\n1. Junte-se ao servidor Discord RGSX\n2. Descreva seu problema\n3. Compartilhe este arquivo ZIP\n\nPressione {1} para voltar ao menu.",
|
||||
"support_dialog_error": "Erro ao gerar o arquivo de suporte:\n{0}\n\nPressione {1} para voltar ao menu.",
|
||||
@@ -185,7 +200,9 @@
|
||||
"instruction_pause_settings": "Música, opção symlink e status das chaves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração",
|
||||
"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte",
|
||||
"instruction_pause_quit": "Sair da aplicação RGSX",
|
||||
"instruction_pause_quit": "Acessar menu para sair ou reiniciar",
|
||||
"instruction_quit_app": "Sair da aplicação RGSX",
|
||||
"instruction_quit_restart": "Reiniciar a aplicação RGSX",
|
||||
"instruction_controls_help": "Mostrar referência completa de controle e teclado",
|
||||
"instruction_controls_remap": "Modificar associação de botões / teclas",
|
||||
"instruction_generic_back": "Voltar ao menu anterior",
|
||||
@@ -193,6 +210,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}",
|
||||
@@ -245,6 +265,8 @@
|
||||
"history_option_extract_archive": "Extrair arquivo",
|
||||
"history_option_open_file": "Abrir arquivo",
|
||||
"history_option_scraper": "Obter metadados",
|
||||
"history_option_remove_from_queue": "Remover da fila",
|
||||
"history_option_cancel_download": "Cancelar download",
|
||||
"history_option_delete_game": "Excluir jogo",
|
||||
"history_option_error_info": "Detalhes do erro",
|
||||
"history_option_retry": "Tentar novamente",
|
||||
|
||||
@@ -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:
|
||||
@@ -453,8 +452,102 @@ async def check_for_updates():
|
||||
config.loading_progress = 5.0
|
||||
config.needs_redraw = True
|
||||
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
# Liste des endpoints à essayer (GitHub principal, puis fallback)
|
||||
endpoints = [
|
||||
OTA_VERSION_ENDPOINT,
|
||||
"https://retrogamesets.fr/softs/version.json"
|
||||
]
|
||||
|
||||
response = None
|
||||
last_error = None
|
||||
|
||||
for endpoint_index, endpoint in enumerate(endpoints):
|
||||
is_fallback = endpoint_index > 0
|
||||
if is_fallback:
|
||||
logger.info(f"Tentative sur endpoint de secours : {endpoint}")
|
||||
|
||||
# Gestion des erreurs de rate limit GitHub (429) avec retry
|
||||
max_retries = 3 if not is_fallback else 1 # Moins de retries sur fallback
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
response = requests.get(endpoint, timeout=10)
|
||||
|
||||
# Gestion spécifique des erreurs 429 (Too Many Requests) - surtout pour GitHub
|
||||
if response.status_code == 429:
|
||||
retry_after = response.headers.get('retry-after')
|
||||
x_ratelimit_remaining = response.headers.get('x-ratelimit-remaining', '1')
|
||||
x_ratelimit_reset = response.headers.get('x-ratelimit-reset')
|
||||
|
||||
if retry_after:
|
||||
# En-tête retry-after présent : attendre le nombre de secondes spécifié
|
||||
wait_time = int(retry_after)
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s (retry-after header)")
|
||||
elif x_ratelimit_remaining == '0' and x_ratelimit_reset:
|
||||
# x-ratelimit-remaining est 0 : attendre jusqu'à x-ratelimit-reset
|
||||
import time
|
||||
reset_time = int(x_ratelimit_reset)
|
||||
current_time = int(time.time())
|
||||
wait_time = max(reset_time - current_time, 60) # Minimum 60s
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s (x-ratelimit-reset)")
|
||||
else:
|
||||
# Pas d'en-têtes spécifiques : attendre au moins 60s
|
||||
wait_time = 60
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s par défaut")
|
||||
|
||||
if retry_count < max_retries - 1:
|
||||
logger.info(f"Nouvelle tentative dans {wait_time}s... ({retry_count + 1}/{max_retries})")
|
||||
await asyncio.sleep(wait_time)
|
||||
retry_count += 1
|
||||
continue
|
||||
else:
|
||||
# Si rate limit persistant et qu'on est sur GitHub, essayer le fallback
|
||||
if not is_fallback:
|
||||
logger.warning(f"Rate limit GitHub persistant, passage au serveur de secours")
|
||||
break # Sortir de la boucle retry pour essayer le prochain endpoint
|
||||
raise requests.exceptions.HTTPError(
|
||||
f"Limite de débit atteinte (429). Veuillez réessayer plus tard."
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
# Succès, sortir de toutes les boucles
|
||||
logger.debug(f"Version récupérée avec succès depuis : {endpoint}")
|
||||
break
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
last_error = e
|
||||
if response and response.status_code == 429:
|
||||
# 429 géré au-dessus, continuer la boucle ou passer au fallback
|
||||
retry_count += 1
|
||||
if retry_count >= max_retries:
|
||||
break # Passer au prochain endpoint
|
||||
else:
|
||||
# Erreur HTTP autre que 429
|
||||
logger.warning(f"Erreur HTTP {response.status_code if response else 'inconnue'} sur {endpoint}")
|
||||
break # Passer au prochain endpoint
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
last_error = e
|
||||
if retry_count < max_retries - 1:
|
||||
# Erreur réseau, réessayer avec backoff exponentiel
|
||||
wait_time = 2 ** retry_count # 1s, 2s, 4s
|
||||
logger.warning(f"Erreur réseau sur {endpoint}. Nouvelle tentative dans {wait_time}s...")
|
||||
await asyncio.sleep(wait_time)
|
||||
retry_count += 1
|
||||
else:
|
||||
logger.warning(f"Erreur réseau persistante sur {endpoint} : {e}")
|
||||
break # Passer au prochain endpoint
|
||||
|
||||
# Si on a une réponse valide, sortir de la boucle des endpoints
|
||||
if response and response.status_code == 200:
|
||||
break
|
||||
|
||||
# Si aucun endpoint n'a fonctionné
|
||||
if not response or response.status_code != 200:
|
||||
raise last_error if last_error else requests.exceptions.RequestException(
|
||||
"Impossible de vérifier les mises à jour sur tous les serveurs"
|
||||
)
|
||||
|
||||
# Accepter différents content-types (application/json, text/plain, text/html)
|
||||
content_type = response.headers.get("content-type", "")
|
||||
@@ -831,6 +924,16 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
logger.info(f"Le fichier {dest_path} existe déjà et la taille est correcte, téléchargement ignoré")
|
||||
result[0] = True
|
||||
result[1] = _("network_download_ok").format(game_name) + _("download_already_present")
|
||||
|
||||
# Mettre à jour l'historique
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry["status"] = "Download_OK"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = result[1]
|
||||
save_history(config.history)
|
||||
break
|
||||
|
||||
# Afficher un toast au lieu d'ouvrir l'historique
|
||||
try:
|
||||
show_toast(result[1])
|
||||
@@ -839,6 +942,13 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
with urls_lock:
|
||||
urls_in_progress.discard(url)
|
||||
logger.debug(f"URL supprimée du set des téléchargements en cours: {url} (URLs restantes: {len(urls_in_progress)})")
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
file_found = True
|
||||
|
||||
@@ -881,6 +991,16 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
logger.info(f"Un fichier avec le même nom de base existe déjà: {existing_path}, téléchargement ignoré")
|
||||
result[0] = True
|
||||
result[1] = _("network_download_ok").format(game_name) + _("download_already_extracted")
|
||||
|
||||
# Mettre à jour l'historique
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry["status"] = "Download_OK"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = result[1]
|
||||
save_history(config.history)
|
||||
break
|
||||
|
||||
# Afficher un toast au lieu d'ouvrir l'historique
|
||||
try:
|
||||
show_toast(result[1])
|
||||
@@ -889,6 +1009,13 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
with urls_lock:
|
||||
urls_in_progress.discard(url)
|
||||
logger.debug(f"URL supprimée du set des téléchargements en cours: {url} (URLs restantes: {len(urls_in_progress)})")
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
except Exception as e:
|
||||
logger.debug(f"Erreur lors de la vérification des fichiers existants: {e}")
|
||||
@@ -1115,6 +1242,11 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
|
||||
# Si annulé, ne pas continuer avec extraction
|
||||
if download_canceled:
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
os.chmod(dest_path, 0o644)
|
||||
@@ -1336,6 +1468,12 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
if url in url_done_events:
|
||||
url_done_events[url].set()
|
||||
|
||||
# Libérer le slot de la queue
|
||||
try:
|
||||
notify_download_finished()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result[0], result[1]
|
||||
|
||||
async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=False, task_id=None):
|
||||
|
||||
@@ -29,7 +29,7 @@ def delete_old_files():
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Ancien fichier supprimé : {file_path}")
|
||||
print(f"Ancien fichier supprime : {file_path}")
|
||||
logger.info(f"Ancien fichier supprimé : {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la suppression de {file_path} : {str(e)}")
|
||||
@@ -39,7 +39,7 @@ def delete_old_files():
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Ancien fichier supprimé : {file_path}")
|
||||
print(f"Ancien fichier supprime : {file_path}")
|
||||
logger.info(f"Ancien fichier supprimé : {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la suppression de {file_path} : {str(e)}")
|
||||
@@ -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,
|
||||
@@ -71,20 +76,25 @@ 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', {})}")
|
||||
# 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
|
||||
|
||||
@@ -101,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."""
|
||||
@@ -307,6 +338,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()
|
||||
|
||||
@@ -364,7 +364,7 @@ try:
|
||||
logger.info("Test d'écriture dans le fichier de log réussi")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du test d'écriture : {e}")
|
||||
print(f"ERREUR: Impossible d'écrire dans {config.log_file_web}: {e}", file=sys.stderr)
|
||||
print(f"ERREUR: Impossible d'ecrire dans {config.log_file_web}: {e}", file=sys.stderr)
|
||||
|
||||
# Initialiser les données au démarrage
|
||||
logger.info("Chargement initial des données...")
|
||||
@@ -772,7 +772,7 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
# Lire depuis history.json - filtrer seulement les téléchargements en cours
|
||||
history = load_history() or []
|
||||
|
||||
print(f"\n[DEBUG PROGRESS] history.json chargé avec {len(history)} entrées totales")
|
||||
print(f"\n[DEBUG PROGRESS] history.json charge avec {len(history)} entrees totales")
|
||||
|
||||
# Filtrer les entrées avec status "Downloading", "Téléchargement", "Connecting", "Try X/Y"
|
||||
in_progress_statuses = ["Downloading", "Téléchargement", "Downloading", "Connecting", "Extracting"]
|
||||
@@ -797,9 +797,9 @@ class RGSXHandler(BaseHTTPRequestHandler):
|
||||
else:
|
||||
# Debug: afficher les premiers status qui ne matchent pas
|
||||
if len(downloads) < 3:
|
||||
print(f" [DEBUG] Ignoré - Status: '{status}', Game: {entry.get('game_name', '')[:50]}")
|
||||
print(f" [DEBUG] Ignore - Status: '{status}', Game: {entry.get('game_name', '')[:50]}")
|
||||
|
||||
print(f"[DEBUG PROGRESS] {len(downloads)} téléchargements en cours trouvés")
|
||||
print(f"[DEBUG PROGRESS] {len(downloads)} telechargements en cours trouves")
|
||||
if downloads:
|
||||
for url, data in list(downloads.items())[:2]:
|
||||
print(f" - URL: {url[:80]}...")
|
||||
@@ -2088,7 +2088,7 @@ def run_server(host='0.0.0.0', port=5000):
|
||||
if __name__ == '__main__':
|
||||
print("="*60, flush=True)
|
||||
print("Demarrage du serveur RGSX Web...", flush=True)
|
||||
print(f"Fichier de log prévu: {config.log_file_web}", flush=True)
|
||||
print(f"Fichier de log prevu: {config.log_file_web}", flush=True)
|
||||
print("="*60, flush=True)
|
||||
|
||||
parser = argparse.ArgumentParser(description='RGSX Web Server')
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.3.2.9"
|
||||
"version": "2.4.1.0"
|
||||
}
|
||||
@@ -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%"
|
||||
|
||||
<nul set /p "= ["
|
||||
tar -xf "%ZIP_FILE%" -C "%PYTHON_DIR%" --strip-components=0
|
||||
set TAR_RESULT=!ERRORLEVEL!
|
||||
echo %ESC%%GREEN%##########%ESC%%RESET%] Done
|
||||
|
||||
if !TAR_RESULT! NEQ 0 (
|
||||
echo.
|
||||
echo %ESC%%RED% ERROR: Extraction failed!%ESC%%RESET%
|
||||
echo [%DATE% %TIME%] ERROR: tar extraction failed with code !TAR_RESULT! >> "%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
|
||||
Reference in New Issue
Block a user