v2.1.0.0
- Retrobat: automatic `gamelist.xml` update on launch to immediately show scraped images/videos in ES. - System images loading prioritizes explicit `platform_image` from systems JSON. - Auto-detect supported extensions by parsing `es_systems.cfg`; generate and cache automatically `/saves/ports/rgsx/rom_extensions.json`. - Auto-hide unsupported platforms at start if roms folders not exist / not match `es_systems.cfg`) with a toggle to re enable in the Display menu. - Automatic restart after update configuration (beta) - New Display option to change systems grid layout (3x3, 3x4, 4x3, 4x4). - Pause menu reorganized - Translations updated. - Minor display fixes and spacing polish.
This commit is contained in:
@@ -11,18 +11,23 @@ The application supports multiple sources like myrient and 1fichier. These sourc
|
|||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **Game downloads** : Support for ZIP files and handling of unsupported extensions thanks to the `info.txt` file in each folder (batocera), which automatically extracts if the system doesn't support archives.
|
- **Game downloads** : Support for ZIP files and handling of unsupported extensions based on EmulationStation's `es_systems.cfg` (and custom `es_systems_*.cfg` on Batocera). RGSX reads allowed extensions per system from these configs and will automatically extract archives when a system doesn't support them.
|
||||||
- Downloads require no authentication or account for most sources.
|
- Downloads require no authentication or account for most sources.
|
||||||
- Systems marked `(1fichier)` in the name will only be accessible if you provide your 1fichier API key (see below).
|
- Systems marked `(1fichier)` in the name will only be accessible if you provide your 1fichier API key (see below).
|
||||||
- **Download history** : View and re-download previous files.
|
- **Download history** : View and re-download previous files.
|
||||||
- **Multi-select downloads** : Mark multiple games in the game list with the key mapped to Clear History (default X) to enqueue several downloads in one batch. Press Confirm to start batch.
|
- **Multi-select downloads** : Mark multiple games in the game list with the key mapped to Clear History (default X) to enqueue several downloads in one batch. Press Confirm to start batch.
|
||||||
- **Control customization** : Remap keyboard or controller keys to your preference with automatic button name detection from EmulationStation (beta).
|
- **Control customization** : Remap keyboard or controller keys to your preference with automatic button name detection from EmulationStation (beta).
|
||||||
|
- **Systems grid layout**: Change the platforms grid (3x3, 3x4, 4x3, 4x4) from the Display menu.
|
||||||
|
- **Show/hide unsupported systems**: Auto-hide platforms whose ROM folder is missing according to `es_systems.cfg`, with a toggle in the Display menu.
|
||||||
|
- **Smarter system images**: Image loading prioritizes explicit `platform_image` from your systems list JSON before falling back to `<platform_name>.png` or folder images.
|
||||||
- **Font size adjustment** : If you find the text too small/too large, you can change it in the menu.
|
- **Font size adjustment** : If you find the text too small/too large, you can change it in the menu.
|
||||||
- **Search mode** : Filter games by name for quick navigation with virtual keyboard on controller.
|
- **Search mode** : Filter games by name for quick navigation with virtual keyboard on controller.
|
||||||
- **Multilingual support** : Interface available in multiple languages. You can choose the language in the menu.
|
- **Multilingual support** : Interface available in multiple languages. You can choose the language in the menu.
|
||||||
- **Error handling** with informative messages and LOG file.
|
- **Error handling** with informative messages and LOG file.
|
||||||
- **Adaptive interface** : The interface adapts to all resolutions from 800x600 to 4K (not tested beyond 1920x1080).
|
- **Adaptive interface** : The interface adapts to all resolutions from 800x600 to 4K (not tested beyond 1920x1080).
|
||||||
- **Automatic updates** : the application must be restarted after an update.
|
- **Automatic updates** : the application must be restarted after an update.
|
||||||
|
- **Automatic supported extensions cache**: On first use, RGSX reads `es_systems.cfg` (RetroBat/Batocera) and generates `/saves/ports/rgsx/rom_extensions.json` with allowed extensions per system.
|
||||||
|
- **Retrobat gamelist auto-update**: On Retrobat, the Windows `gamelist.xml` is updated automatically at launch so your images/videos appear in EmulationStation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -91,6 +96,13 @@ INFO: for retrobat on first launch, the application will download Python in the
|
|||||||
- From the pause menu, access history, control help (control display changes depending on which menu you're in) or reconfiguration of keys, languages, font size.
|
- From the pause menu, access history, control help (control display changes depending on which menu you're in) or reconfiguration of keys, languages, font size.
|
||||||
- You can also, from the menu, regenerate the cache of the systems/games/images list to be sure to have the latest updates.
|
- You can also, from the menu, regenerate the cache of the systems/games/images list to be sure to have the latest updates.
|
||||||
|
|
||||||
|
#### Display menu
|
||||||
|
|
||||||
|
- Layout: switch platforms grid between 3x3, 3x4, 4x3, 4x4.
|
||||||
|
- Font size: adjust text scale (accessibility).
|
||||||
|
- Show unsupported systems: toggle visibility for platforms whose ROM folder is missing.
|
||||||
|
- Filter systems: quickly show/hide platforms by name (persistent).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
@@ -148,19 +160,23 @@ RGSX/
|
|||||||
├── language.py # Multilingual support management.
|
├── language.py # Multilingual support management.
|
||||||
├── accessibility.py # Accessibility settings management.
|
├── accessibility.py # Accessibility settings management.
|
||||||
├── utils.py # Utility functions (text wrap, truncation etc.).
|
├── utils.py # Utility functions (text wrap, truncation etc.).
|
||||||
├── update_gamelist.py # Game list update.
|
├── update_gamelist.py # Game list update (Batocera/Knulli).
|
||||||
|
├── update_gamelist_windows.py # Retrobat-only: auto-update ES gamelist.xml on launch.
|
||||||
├── assets/ # Application resources (fonts, executables, music).
|
├── assets/ # Application resources (fonts, executables, music).
|
||||||
├── games/ # Game system configuration files.
|
|
||||||
├── images/ # System images.
|
|
||||||
├── languages/ # Translation files.
|
├── languages/ # Translation files.
|
||||||
└── logs/
|
└── logs/
|
||||||
└── RGSX.log # Log file.
|
└── RGSX.log # Log file.
|
||||||
|
|
||||||
/saves/ports/RGSX/
|
/saves/ports/RGSX/
|
||||||
│
|
│
|
||||||
|
├── systems_list.json # Available Systems names / folders / images
|
||||||
|
├── games/ # Links for games.
|
||||||
|
├── images/ # System images.
|
||||||
├── rgsx_settings.json # Unified configuration file (settings, accessibility, language, music, symlinks).
|
├── rgsx_settings.json # Unified configuration file (settings, accessibility, language, music, symlinks).
|
||||||
├── controls.json # Control mapping file (generated after first startup).
|
├── controls.json # Control mapping file (generated after first startup).
|
||||||
├── history.json # Download history database (generated after first download).
|
├── history.json # Download history database (generated after first download).
|
||||||
|
├── rom_extensions.json # Generated from es_systems.cfg: per-system allowed ROM extensions cache.
|
||||||
└── 1FichierAPI.txt # 1fichier API key (premium account and + only) (empty by default).
|
└── 1FichierAPI.txt # 1fichier API key (premium account and + only) (empty by default).
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -207,6 +223,16 @@ Developed with ❤️ for retro gaming enthusiasts.
|
|||||||
|
|
||||||
## 🔄 Changelog
|
## 🔄 Changelog
|
||||||
|
|
||||||
|
### 2.1.0.0 (2025-09-09)
|
||||||
|
- Retrobat: automatic `gamelist.xml` update on launch to immediately show scraped images/videos in ES.
|
||||||
|
- System image loading prioritizes explicit `platform_image` from systems JSON.
|
||||||
|
- Auto-detect supported extensions by parsing `es_systems.cfg`; generate and cache `/saves/ports/rgsx/rom_extensions.json`.
|
||||||
|
- Auto-hide unsupported platforms (missing ROM folder per `es_systems.cfg`) with a toggle in the Display menu.
|
||||||
|
- New Display option to change systems grid layout (3x3, 3x4, 4x3, 4x4).
|
||||||
|
- Pause menu reorganized to surface the most used items.
|
||||||
|
- Translations updated.
|
||||||
|
- Minor display fixes and spacing polish.
|
||||||
|
|
||||||
### 2.0.0.0 (2025-09-05)
|
### 2.0.0.0 (2025-09-05)
|
||||||
- Complete sources system overhaul: centralized management through `/saves/ports/rgsx/systems_list.json` (order preserved), automatic platform addition by dropping its JSON file into `/saves/ports/rgsx/games/` (auto-created if missing) — after first creation edit the generated "dossier" field so it matches your downloads folder structure.
|
- Complete sources system overhaul: centralized management through `/saves/ports/rgsx/systems_list.json` (order preserved), automatic platform addition by dropping its JSON file into `/saves/ports/rgsx/games/` (auto-created if missing) — after first creation edit the generated "dossier" field so it matches your downloads folder structure.
|
||||||
- Systems visibility filter menu (show/hide platforms with persistent hidden list in settings)
|
- Systems visibility filter menu (show/hide platforms with persistent hidden list in settings)
|
||||||
|
|||||||
+30
-4
@@ -10,18 +10,23 @@ L'application prend en charge plusieurs sources comme myrient, 1fichier. Ces sou
|
|||||||
|
|
||||||
## ✨ Fonctionnalités
|
## ✨ Fonctionnalités
|
||||||
|
|
||||||
- **Téléchargement de jeux** : Prise en charge des fichiers ZIP et gestion des extensions non supportées grâce au fichier `info.txt` dans chaque dossier (batocera), qui extrait automatiquement si le système ne supporte pas les archives.
|
- **Téléchargement de jeux** : Prise en charge des fichiers ZIP et gestion des extensions non supportées à partir du fichier `es_systems.cfg` d'EmulationStation (et des `es_systems_*.cfg` personnalisés sur Batocera). RGSX lit les extensions autorisées par système depuis ces configurations et extrait automatiquement les archives si le système ne les supporte pas.
|
||||||
- Les téléchargements ne nécessitent aucune authentification ni compte pour la plupart.
|
- Les téléchargements ne nécessitent aucune authentification ni compte pour la plupart.
|
||||||
- Les systèmes notés `(1fichier)` dans le nom ne seront accessibles que si vous renseignez votre clé API 1fichier (voir plus bas).
|
- Les systèmes notés `(1fichier)` dans le nom ne seront accessibles que si vous renseignez votre clé API 1fichier (voir plus bas).
|
||||||
- **Historique des téléchargements** : Consultez et retéléchargez les anciens fichiers.
|
- **Historique des téléchargements** : Consultez et retéléchargez les anciens fichiers.
|
||||||
- **Téléchargements multi-sélection** : Marquez plusieurs jeux dans la liste avec la touche associée à Vider Historique (par défaut X) pour préparer un lot. Appuyez ensuite sur Confirmer pour lancer les téléchargements en séquence.
|
- **Téléchargements multi-sélection** : Marquez plusieurs jeux dans la liste avec la touche associée à Vider Historique (par défaut X) pour préparer un lot. Appuyez ensuite sur Confirmer pour lancer les téléchargements en séquence.
|
||||||
- **Personnalisation des contrôles** : Remappez les touches du clavier ou de la manette à votre convenance avec détection automatique des noms de boutons depuis EmulationStation(beta).
|
- **Personnalisation des contrôles** : Remappez les touches du clavier ou de la manette à votre convenance avec détection automatique des noms de boutons depuis EmulationStation(beta).
|
||||||
|
- **Grille des plateformes** : changez la disposition de la grille (3x3, 3x4, 4x3, 4x4) depuis le menu Affichage.
|
||||||
|
- **Afficher/Masquer plateformes non supportées** : masquage automatique des systèmes dont le dossier ROM est absent selon `es_systems.cfg`, avec un interrupteur dans le menu Affichage.
|
||||||
|
- **Images système plus intelligentes** : priorité à l’image explicite `platform_image` issue du JSON des systèmes avant les fallback `<platform_name>.png` ou dossier.
|
||||||
- **Changement de taille de police** : Si vous trouvez les écritures trop petites/trop grosses, vous pouvez le changer dans le menu.
|
- **Changement de taille de police** : Si vous trouvez les écritures trop petites/trop grosses, vous pouvez le changer dans le menu.
|
||||||
- **Mode recherche** : Filtrez les jeux par nom pour une navigation rapide avec clavier virtuel sur manette.
|
- **Mode recherche** : Filtrez les jeux par nom pour une navigation rapide avec clavier virtuel sur manette.
|
||||||
- **Support multilingue** : Interface disponible en plusieurs langues. Vous pourrez choisir la langue dans le menu.
|
- **Support multilingue** : Interface disponible en plusieurs langues. Vous pourrez choisir la langue dans le menu.
|
||||||
- **Gestion des erreurs** avec messages informatifs et fichier de LOG.
|
- **Gestion des erreurs** avec messages informatifs et fichier de LOG.
|
||||||
- **Interface adaptative** : L'interface s'adapte à toutes résolutions de 800x600 à 4K (non testé au-delà de 1920x1080).
|
- **Interface adaptative** : L'interface s'adapte à toutes résolutions de 800x600 à 4K (non testé au-delà de 1920x1080).
|
||||||
- **Mise à jour automatique** : l'application doit être relancée après une mise à jour.
|
- **Mise à jour automatique** : l'application doit être relancée après une mise à jour.
|
||||||
|
- **Cache des extensions supportées** : à la première utilisation, RGSX lit `es_systems.cfg` (RetroBat/Batocera) et génère `/saves/ports/rgsx/rom_extensions.json` avec les extensions autorisées par système.
|
||||||
|
- **Mise à jour automatique de la gamelist (Retrobat)** : sur Retrobat, le `gamelist.xml` Windows est mis à jour automatiquement au lancement pour afficher les images/vidéos dans EmulationStation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -89,6 +94,13 @@ INFO : pour retrobat au premier lancement, l'application téléchargera Python d
|
|||||||
- Depuis le menu pause, accédez à l'historique, à l'aide des contrôles (l'affichage des contrôles change suivant le menu où vous êtes) ou à la reconfiguration des touches, des langues, de la taille de la police.
|
- Depuis le menu pause, accédez à l'historique, à l'aide des contrôles (l'affichage des contrôles change suivant le menu où vous êtes) ou à la reconfiguration des touches, des langues, de la taille de la police.
|
||||||
- Vous pouvez aussi, depuis le menu, régénérer le cache de la liste des systèmes/jeux/images pour être sûr d'avoir les dernières mises à jour.
|
- Vous pouvez aussi, depuis le menu, régénérer le cache de la liste des systèmes/jeux/images pour être sûr d'avoir les dernières mises à jour.
|
||||||
|
|
||||||
|
#### Menu Affichage
|
||||||
|
|
||||||
|
- Disposition: basculez la grille des plateformes entre 3x3, 3x4, 4x3, 4x4.
|
||||||
|
- Taille de police: ajustez l’échelle du texte (accessibilité).
|
||||||
|
- Afficher plateformes non supportées: afficher/masquer les systèmes dont le dossier ROM est absent.
|
||||||
|
- Filtrer les systèmes: afficher/masquer rapidement des plateformes par nom (persistant).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Téléchargement
|
### Téléchargement
|
||||||
@@ -127,6 +139,16 @@ Les logs sont enregistrés dans `roms/ports/RGSX/logs/RGSX.log` sur batocera et
|
|||||||
|
|
||||||
## 🔄 Journal des modifications
|
## 🔄 Journal des modifications
|
||||||
|
|
||||||
|
### 2.1.0.0 (2025-09-09)
|
||||||
|
- Retrobat : mise à jour automatique de `gamelist.xml` au lancement pour afficher immédiatement les images/vidéos dans ES.
|
||||||
|
- Chargement des images systèmes : priorité à `platform_image` défini dans le JSON des systèmes.
|
||||||
|
- Détection automatique des extensions supportées via `es_systems.cfg`; génération et cache dans `/saves/ports/rgsx/rom_extensions.json`.
|
||||||
|
- Masquage automatique des plateformes non supportées (dossier ROM manquant selon `es_systems.cfg`) avec interrupteur dans le menu Affichage.
|
||||||
|
- Nouveau réglage dans Affichage pour changer la grille des plateformes (3x3, 3x4, 4x3, 4x4).
|
||||||
|
- Réorganisation du menu pause pour mettre en avant les options courantes.
|
||||||
|
- Traductions mises à jour.
|
||||||
|
- Corrections visuelles mineures et ajustements d’espacements.
|
||||||
|
|
||||||
### 2.0.0.0 (2025-09-05)
|
### 2.0.0.0 (2025-09-05)
|
||||||
- Refonte complète du système de sources : gestion centralisée via `/saves/ports/rgsx/systems_list.json` (ordre conservé), ajout automatique d’une plateforme en déposant son fichier JSON dans `/saves/ports/rgsx/games/` (création si absente) — pensez ensuite à éditer le champ "dossier" généré pour qu’il corresponde à votre organisation de téléchargements.
|
- Refonte complète du système de sources : gestion centralisée via `/saves/ports/rgsx/systems_list.json` (ordre conservé), ajout automatique d’une plateforme en déposant son fichier JSON dans `/saves/ports/rgsx/games/` (création si absente) — pensez ensuite à éditer le champ "dossier" généré pour qu’il corresponde à votre organisation de téléchargements.
|
||||||
- Nouveau menu de filtrage des systèmes (afficher/masquer plateformes avec persistance dans les paramètres)
|
- Nouveau menu de filtrage des systèmes (afficher/masquer plateformes avec persistance dans les paramètres)
|
||||||
@@ -205,19 +227,23 @@ RGSX/
|
|||||||
├── language.py # Gestion du support multilingue.
|
├── language.py # Gestion du support multilingue.
|
||||||
├── accessibility.py # Gestion des paramètres d'accessibilité.
|
├── accessibility.py # Gestion des paramètres d'accessibilité.
|
||||||
├── utils.py # Fonctions utilitaires (wrap du texte, troncage etc.).
|
├── utils.py # Fonctions utilitaires (wrap du texte, troncage etc.).
|
||||||
├── update_gamelist.py # Mise à jour de la liste des jeux.
|
├── update_gamelist.py # Mise à jour de la liste des jeux (Batocera/Knulli).
|
||||||
|
├── update_gamelist_windows.py # Spécifique Retrobat : mise à jour auto de gamelist.xml au lancement.
|
||||||
├── assets/ # Ressources de l'application (polices, exécutables, musique).
|
├── assets/ # Ressources de l'application (polices, exécutables, musique).
|
||||||
├── games/ # Fichiers de configuration des systèmes de jeux.
|
|
||||||
├── images/ # Images des systèmes.
|
|
||||||
├── languages/ # Fichiers de traduction.
|
├── languages/ # Fichiers de traduction.
|
||||||
└── logs/
|
└── logs/
|
||||||
└── RGSX.log # Fichier de logs.
|
└── RGSX.log # Fichier de logs.
|
||||||
|
|
||||||
/saves/ports/RGSX/
|
/saves/ports/RGSX/
|
||||||
│
|
│
|
||||||
|
├── systems_list.json # Liste des systèmes
|
||||||
|
├── games/ # Liens des systèmes
|
||||||
|
├── images/ # Images des systèmes.
|
||||||
├── rgsx_settings.json # Fichier de configuration unifié (paramètres, accessibilité, langue, musique, symlinks).
|
├── rgsx_settings.json # Fichier de configuration unifié (paramètres, accessibilité, langue, musique, symlinks).
|
||||||
├── controls.json # Fichier de mappage des contrôles (généré après le premier démarrage).
|
├── controls.json # Fichier de mappage des contrôles (généré après le premier démarrage).
|
||||||
├── history.json # Base de données de l'historique de téléchargements (généré après le premier téléchargement).
|
├── history.json # Base de données de l'historique de téléchargements (généré après le premier téléchargement).
|
||||||
|
├── rom_extensions.json # Généré depuis es_systems.cfg : cache des extensions autorisées par système.
|
||||||
└── 1FichierAPI.txt # Clé API 1fichier (compte premium et + uniquement) (vide par défaut).
|
└── 1FichierAPI.txt # Clé API 1fichier (compte premium et + uniquement) (vide par défaut).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+107
-7
@@ -7,12 +7,15 @@ import logging
|
|||||||
import requests
|
import requests
|
||||||
import queue
|
import queue
|
||||||
import datetime
|
import datetime
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import config
|
import config
|
||||||
|
|
||||||
from display import (
|
from display import (
|
||||||
init_display, draw_loading_screen, draw_error_screen, draw_platform_grid,
|
init_display, draw_loading_screen, draw_error_screen, draw_platform_grid,
|
||||||
draw_progress_screen, draw_controls, draw_virtual_keyboard,
|
draw_progress_screen, draw_controls, draw_virtual_keyboard,
|
||||||
draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list,
|
draw_extension_warning, draw_pause_menu, draw_controls_help, draw_game_list,
|
||||||
|
draw_display_menu,
|
||||||
draw_history_list, draw_clear_history_dialog, draw_cancel_download_dialog,
|
draw_history_list, draw_clear_history_dialog, draw_cancel_download_dialog,
|
||||||
draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, draw_gradient,
|
draw_confirm_dialog, draw_redownload_game_cache_dialog, draw_popup, draw_gradient,
|
||||||
THEME_COLORS
|
THEME_COLORS
|
||||||
@@ -47,6 +50,33 @@ except Exception as e:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Mise à jour de la gamelist Windows avant toute initialisation graphique (évite les conflits avec ES)
|
||||||
|
def _run_windows_gamelist_update():
|
||||||
|
try:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return
|
||||||
|
script_path = os.path.join(config.APP_FOLDER, "update_gamelist_windows.py")
|
||||||
|
if not os.path.exists(script_path):
|
||||||
|
return
|
||||||
|
logger.info("Lancement de update_gamelist_windows.py depuis __main__ (pré-init)")
|
||||||
|
exe = sys.executable or "python"
|
||||||
|
# Exécuter rapidement avec capture sortie pour journaliser tout message utile
|
||||||
|
result = subprocess.run(
|
||||||
|
[exe, script_path],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
cwd=config.APP_FOLDER,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
logger.info(f"update_gamelist_windows.py terminé avec code {result.returncode}")
|
||||||
|
if result.stdout:
|
||||||
|
logger.debug(result.stdout.strip())
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Échec lors de l'exécution de update_gamelist_windows.py: {e}")
|
||||||
|
|
||||||
|
_run_windows_gamelist_update()
|
||||||
|
|
||||||
# Initialisation de Pygame
|
# Initialisation de Pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
pygame.joystick.init()
|
pygame.joystick.init()
|
||||||
@@ -54,9 +84,56 @@ logger.debug("------------------------------------------------------------------
|
|||||||
logger.debug("---------------------------DEBUT LOG--------------------------------")
|
logger.debug("---------------------------DEBUT LOG--------------------------------")
|
||||||
logger.debug("--------------------------------------------------------------------")
|
logger.debug("--------------------------------------------------------------------")
|
||||||
|
|
||||||
|
#Récupération des noms des joysticks si pas de joystick connecté, verifier si clavier connecté
|
||||||
|
joystick_names = [pygame.joystick.Joystick(i).get_name() for i in range(pygame.joystick.get_count())]
|
||||||
|
if not joystick_names:
|
||||||
|
joystick_names = ["Clavier"]
|
||||||
|
print("Aucun joystick détecté, utilisation du clavier par défaut")
|
||||||
|
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
||||||
|
config.joystick = False
|
||||||
|
config.keyboard = True
|
||||||
|
else:
|
||||||
|
config.joystick = True
|
||||||
|
config.keyboard = False
|
||||||
|
print(f"Joysticks détectés: {joystick_names}")
|
||||||
|
logger.debug(f"Joysticks détectés: {joystick_names}, utilisation du joystick par défaut.")
|
||||||
|
# Test des boutons du joystick
|
||||||
|
for name in joystick_names:
|
||||||
|
if "Xbox" in name or "PlayStation" in name or "Logitech" in name:
|
||||||
|
config.xbox_controller = True
|
||||||
|
logger.debug(f"Manette Xbox/PlayStation/Logitech détectée: {name}")
|
||||||
|
print(f"Manette Xbox/PlayStation/Logitech détectée: {name}")
|
||||||
|
break
|
||||||
|
elif "Nintendo" in name:
|
||||||
|
config.nintendo_controller = True
|
||||||
|
logger.debug(f"Manette Nintendo détectée: {name}")
|
||||||
|
print(f"Manette Nintendo détectée: {name}")
|
||||||
|
elif "8Bitdo" in name:
|
||||||
|
config.eightbitdo_controller = True
|
||||||
|
logger.debug(f"Manette 8Bitdo détectée: {name}")
|
||||||
|
print(f"Manette 8Bitdo détectée: {name}")
|
||||||
|
elif "Steam" in name:
|
||||||
|
config.steam_controller = True
|
||||||
|
logger.debug(f"Manette Steam détectée: {name}")
|
||||||
|
print(f"Manette Steam détectée: {name}")
|
||||||
|
elif "TRIMUI Smart Pro" in name:
|
||||||
|
config.trimui_controller = True
|
||||||
|
logger.debug(f"TRIMUI Smart Pro détectée: {name}")
|
||||||
|
print(f"TRIMUI Smart Pro détectée: {name}")
|
||||||
|
else:
|
||||||
|
config.generic_controller = True
|
||||||
|
logger.debug(f"Manette générique détectée: {name}")
|
||||||
|
print(f"Manette générique détectée: {name}")
|
||||||
# Chargement des paramètres d'accessibilité
|
# Chargement des paramètres d'accessibilité
|
||||||
config.accessibility_settings = load_accessibility_settings()
|
config.accessibility_settings = load_accessibility_settings()
|
||||||
|
# Appliquer la grille d'affichage depuis les paramètres
|
||||||
|
try:
|
||||||
|
from rgsx_settings import get_display_grid
|
||||||
|
gcols, grows = get_display_grid()
|
||||||
|
config.GRID_COLS, config.GRID_ROWS = gcols, grows
|
||||||
|
logger.debug(f"Grille d'affichage initiale: {gcols}x{grows}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur chargement grille d'affichage initiale: {e}")
|
||||||
for i, scale in enumerate(config.font_scale_options):
|
for i, scale in enumerate(config.font_scale_options):
|
||||||
if scale == config.accessibility_settings.get("font_scale", 1.0):
|
if scale == config.accessibility_settings.get("font_scale", 1.0):
|
||||||
config.current_font_scale_index = i
|
config.current_font_scale_index = i
|
||||||
@@ -189,6 +266,18 @@ async def main():
|
|||||||
|
|
||||||
current_time = pygame.time.get_ticks()
|
current_time = pygame.time.get_ticks()
|
||||||
|
|
||||||
|
# Déclenchement d'un redémarrage planifié (permet d'afficher une popup avant)
|
||||||
|
try:
|
||||||
|
pending = getattr(config, 'pending_restart_at', 0)
|
||||||
|
if pending and pygame.time.get_ticks() >= pending:
|
||||||
|
logger.info("Redémarrage planifié déclenché")
|
||||||
|
# Clear the flag to avoid repeated triggers in case restart fails
|
||||||
|
config.pending_restart_at = 0
|
||||||
|
from utils import restart_application
|
||||||
|
restart_application(0)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors du déclenchement du redémarrage planifié: {e}")
|
||||||
|
|
||||||
# Forcer redraw toutes les 100 ms dans download_progress
|
# Forcer redraw toutes les 100 ms dans download_progress
|
||||||
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
|
if config.menu_state == "download_progress" and current_time - last_redraw_time >= 100:
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
@@ -276,6 +365,11 @@ async def main():
|
|||||||
if handle_accessibility_events(event):
|
if handle_accessibility_events(event):
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
continue
|
continue
|
||||||
|
if config.menu_state == "display_menu":
|
||||||
|
# Les événements sont gérés dans controls.handle_controls
|
||||||
|
action = handle_controls(event, sources, joystick, screen)
|
||||||
|
config.needs_redraw = True
|
||||||
|
continue
|
||||||
|
|
||||||
if config.menu_state == "controls_help":
|
if config.menu_state == "controls_help":
|
||||||
action = handle_controls(event, sources, joystick, screen)
|
action = handle_controls(event, sources, joystick, screen)
|
||||||
@@ -583,7 +677,6 @@ async def main():
|
|||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
del config.download_tasks[task_id]
|
del config.download_tasks[task_id]
|
||||||
|
|
||||||
# Popup download_result supprimé : plus de temporisation de 3s
|
|
||||||
|
|
||||||
# Affichage
|
# Affichage
|
||||||
if config.needs_redraw:
|
if config.needs_redraw:
|
||||||
@@ -637,6 +730,8 @@ async def main():
|
|||||||
elif config.menu_state == "accessibility_menu":
|
elif config.menu_state == "accessibility_menu":
|
||||||
from accessibility import draw_accessibility_menu
|
from accessibility import draw_accessibility_menu
|
||||||
draw_accessibility_menu(screen)
|
draw_accessibility_menu(screen)
|
||||||
|
elif config.menu_state == "display_menu":
|
||||||
|
draw_display_menu(screen)
|
||||||
elif config.menu_state == "language_select":
|
elif config.menu_state == "language_select":
|
||||||
from display import draw_language_menu
|
from display import draw_language_menu
|
||||||
draw_language_menu(screen)
|
draw_language_menu(screen)
|
||||||
@@ -854,17 +949,22 @@ async def main():
|
|||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
|
|
||||||
pygame.mixer.music.stop()
|
pygame.mixer.music.stop()
|
||||||
|
result = subprocess.run(["taskkill", "/f", "/im", "emulatorLauncher.exe"])
|
||||||
process_name = "emulatorLauncher.exe"
|
|
||||||
result = os.system(f"taskkill /f /im {process_name}")
|
|
||||||
if result == 0:
|
if result == 0:
|
||||||
logger.debug(f"Quitté avec succès: {process_name}")
|
logger.debug(f"Quitté avec succès: emulatorLauncher.exe")
|
||||||
else:
|
else:
|
||||||
logger.debug("Error en essayant de quitter emulatorlauncher.")
|
logger.debug("Error en essayant de quitter emulatorlauncher.")
|
||||||
|
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
logger.debug("Application terminée")
|
logger.debug("Application terminée")
|
||||||
|
|
||||||
|
result2 = subprocess.run(["batocera-es-swissknife", "--emukill"])
|
||||||
|
if result2 == 0:
|
||||||
|
logger.debug(f"Quitté avec succès")
|
||||||
|
else:
|
||||||
|
logger.debug("Error en essayant de quitter batocera-es-swissknife.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if platform.system() == "Emscripten":
|
if platform.system() == "Emscripten":
|
||||||
asyncio.ensure_future(main())
|
asyncio.ensure_future(main())
|
||||||
else:
|
else:
|
||||||
|
|||||||
+26
-8
@@ -5,7 +5,7 @@ import platform
|
|||||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||||
|
|
||||||
# Version actuelle de l'application
|
# Version actuelle de l'application
|
||||||
app_version = "2.0.0.1"
|
app_version = "2.1.0.0"
|
||||||
|
|
||||||
def get_operating_system():
|
def get_operating_system():
|
||||||
"""Renvoie le nom du système d'exploitation."""
|
"""Renvoie le nom du système d'exploitation."""
|
||||||
@@ -60,17 +60,14 @@ def get_system_root():
|
|||||||
|
|
||||||
return "/" if not OPERATING_SYSTEM == "Windows" else os.path.splitdrive(os.getcwd())[0] + os.sep
|
return "/" if not OPERATING_SYSTEM == "Windows" else os.path.splitdrive(os.getcwd())[0] + os.sep
|
||||||
|
|
||||||
|
|
||||||
# Chemins de base
|
# Chemins de base
|
||||||
SYSTEM_FOLDER = get_system_root()
|
SYSTEM_FOLDER = get_system_root()
|
||||||
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
|
APP_FOLDER = os.path.join(get_application_root(), "RGSX")
|
||||||
ROMS_FOLDER = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "roms")
|
ROMS_FOLDER = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "roms")
|
||||||
SAVE_FOLDER = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "saves", "ports", "rgsx")
|
SAVE_FOLDER = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "saves", "ports", "rgsx")
|
||||||
BIOS_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
|
RETROBAT_DATA_FOLDER = os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER)))
|
||||||
|
|
||||||
print(f"BIOS_FOLDER: {BIOS_FOLDER}")
|
|
||||||
print(f"ROMS_FOLDER: {ROMS_FOLDER}")
|
|
||||||
print(f"SAVE_FOLDER: {SAVE_FOLDER}")
|
|
||||||
print(f"RGSX APP_FOLDER: {APP_FOLDER}")
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration du logging
|
# Configuration du logging
|
||||||
@@ -80,17 +77,17 @@ log_file = os.path.join(log_dir, "RGSX.log")
|
|||||||
|
|
||||||
# Chemins de base
|
# Chemins de base
|
||||||
GAMELISTXML = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "roms", "ports", "gamelist.xml")
|
GAMELISTXML = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "roms", "ports", "gamelist.xml")
|
||||||
|
GAMELISTXML_WINDOWS = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(APP_FOLDER))), "roms", "windows", "gamelist.xml")
|
||||||
#Dossier /roms/ports/rgsx
|
#Dossier /roms/ports/rgsx
|
||||||
UPDATE_FOLDER = os.path.join(APP_FOLDER, "update")
|
UPDATE_FOLDER = os.path.join(APP_FOLDER, "update")
|
||||||
LANGUAGES_FOLDER = os.path.join(APP_FOLDER, "languages")
|
LANGUAGES_FOLDER = os.path.join(APP_FOLDER, "languages")
|
||||||
JSON_EXTENSIONS = os.path.join(APP_FOLDER, "rom_extensions.json")
|
|
||||||
MUSIC_FOLDER = os.path.join(APP_FOLDER, "assets", "music")
|
MUSIC_FOLDER = os.path.join(APP_FOLDER, "assets", "music")
|
||||||
|
|
||||||
#Dossier /saves/ports/rgsx
|
#Dossier /saves/ports/rgsx
|
||||||
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images")
|
IMAGES_FOLDER = os.path.join(SAVE_FOLDER, "images")
|
||||||
GAMES_FOLDER = os.path.join(SAVE_FOLDER, "games")
|
GAMES_FOLDER = os.path.join(SAVE_FOLDER, "games")
|
||||||
SOURCES_FILE = os.path.join(SAVE_FOLDER, "systems_list.json")
|
SOURCES_FILE = os.path.join(SAVE_FOLDER, "systems_list.json")
|
||||||
|
JSON_EXTENSIONS = os.path.join(SAVE_FOLDER, "rom_extensions.json")
|
||||||
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
|
CONTROLS_CONFIG_PATH = os.path.join(SAVE_FOLDER, "controls.json")
|
||||||
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
|
HISTORY_PATH = os.path.join(SAVE_FOLDER, "history.json")
|
||||||
API_KEY_1FICHIER = os.path.join(SAVE_FOLDER, "1fichierAPI.txt")
|
API_KEY_1FICHIER = os.path.join(SAVE_FOLDER, "1fichierAPI.txt")
|
||||||
@@ -112,6 +109,27 @@ xdvdfs_download_exe = os.path.join(OTA_SERVER_URL, "xdvdfs.exe")
|
|||||||
|
|
||||||
xdvdfs_download_linux = os.path.join(OTA_SERVER_URL, "xdvdfs")
|
xdvdfs_download_linux = os.path.join(OTA_SERVER_URL, "xdvdfs")
|
||||||
|
|
||||||
|
# Print des chemins pour debug
|
||||||
|
print(f"RETROBAT_DATA_FOLDER: {RETROBAT_DATA_FOLDER}")
|
||||||
|
print(f"ROMS_FOLDER: {ROMS_FOLDER}")
|
||||||
|
print(f"SAVE_FOLDER: {SAVE_FOLDER}")
|
||||||
|
print(f"RGSX APP_FOLDER: {APP_FOLDER}")
|
||||||
|
print(f"RGSX LOGS_FOLDER: {log_dir}")
|
||||||
|
print(f"RGSX SETTINGS PATH: {RGSX_SETTINGS_PATH}")
|
||||||
|
print(f"GAMELISTXML: {GAMELISTXML}")
|
||||||
|
print(f"GAMELISTXML_WINDOWS: {GAMELISTXML_WINDOWS}")
|
||||||
|
print(f"UPDATE_FOLDER: {UPDATE_FOLDER}")
|
||||||
|
print(f"LANGUAGES_FOLDER: {LANGUAGES_FOLDER}")
|
||||||
|
print(f"JSON_EXTENSIONS: {JSON_EXTENSIONS}")
|
||||||
|
print(f"MUSIC_FOLDER: {MUSIC_FOLDER}")
|
||||||
|
print(f"IMAGES_FOLDER: {IMAGES_FOLDER}")
|
||||||
|
print(f"GAMES_FOLDER: {GAMES_FOLDER}")
|
||||||
|
print(f"SOURCES_FILE: {SOURCES_FILE}")
|
||||||
|
print(f"CONTROLS_CONFIG_PATH: {CONTROLS_CONFIG_PATH}")
|
||||||
|
print(f"HISTORY_PATH: {HISTORY_PATH}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Constantes pour la répétition automatique dans pause_menu
|
# Constantes pour la répétition automatique dans pause_menu
|
||||||
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
|
REPEAT_DELAY = 350 # Délai initial avant répétition (ms) - augmenté pour éviter les doubles actions
|
||||||
REPEAT_INTERVAL = 120 # Intervalle entre répétitions (ms) - ajusté pour une navigation plus contrôlée
|
REPEAT_INTERVAL = 120 # Intervalle entre répétitions (ms) - ajusté pour une navigation plus contrôlée
|
||||||
|
|||||||
+169
-51
@@ -3,10 +3,11 @@ import pygame # type: ignore
|
|||||||
import config
|
import config
|
||||||
# Constantes pour la répétition automatique - importées de config.py
|
# Constantes pour la répétition automatique - importées de config.py
|
||||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
|
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
|
||||||
from config import CONTROLS_CONFIG_PATH , GRID_COLS, GRID_ROWS
|
from config import CONTROLS_CONFIG_PATH
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from display import draw_validation_transition
|
from display import draw_validation_transition
|
||||||
from network import download_rom, download_from_1fichier, is_1fichier_url
|
from network import download_rom, download_from_1fichier, is_1fichier_url
|
||||||
from utils import (
|
from utils import (
|
||||||
@@ -32,7 +33,7 @@ VALID_STATES = [
|
|||||||
"platform", "game", "confirm_exit",
|
"platform", "game", "confirm_exit",
|
||||||
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
|
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
|
||||||
"redownload_game_cache", "restart_popup", "error", "loading", "confirm_clear_history",
|
"redownload_game_cache", "restart_popup", "error", "loading", "confirm_clear_history",
|
||||||
"language_select", "filter_platforms"
|
"language_select", "filter_platforms", "display_menu"
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate_menu_state(state):
|
def validate_menu_state(state):
|
||||||
@@ -157,19 +158,19 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
|
|
||||||
#Plateformes
|
#Plateformes
|
||||||
elif config.menu_state == "platform":
|
elif config.menu_state == "platform":
|
||||||
systems_per_page = GRID_COLS * GRID_ROWS
|
systems_per_page = config.GRID_COLS * config.GRID_ROWS
|
||||||
max_index = min(systems_per_page, len(config.platforms) - config.current_page * systems_per_page) - 1
|
max_index = min(systems_per_page, len(config.platforms) - config.current_page * systems_per_page) - 1
|
||||||
current_grid_index = config.selected_platform - config.current_page * systems_per_page
|
current_grid_index = config.selected_platform - config.current_page * systems_per_page
|
||||||
row = current_grid_index // GRID_COLS
|
row = current_grid_index // config.GRID_COLS
|
||||||
col = current_grid_index % GRID_COLS
|
col = current_grid_index % config.GRID_COLS
|
||||||
|
|
||||||
# Espace réservé pour des fonctions helper si nécessaire
|
# Espace réservé pour des fonctions helper si nécessaire
|
||||||
|
|
||||||
if is_input_matched(event, "down"):
|
if is_input_matched(event, "down"):
|
||||||
# Navigation vers le bas avec gestion des limites de page
|
# Navigation vers le bas avec gestion des limites de page
|
||||||
if current_grid_index + GRID_COLS <= max_index:
|
if current_grid_index + config.GRID_COLS <= max_index:
|
||||||
# Déplacement normal vers le bas
|
# Déplacement normal vers le bas
|
||||||
config.selected_platform += GRID_COLS
|
config.selected_platform += config.GRID_COLS
|
||||||
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||||
@@ -178,7 +179,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
# Passage à la page suivante si on est en bas de la grille
|
# Passage à la page suivante si on est en bas de la grille
|
||||||
config.current_page += 1
|
config.current_page += 1
|
||||||
new_row = 0 # Première ligne de la nouvelle page
|
new_row = 0 # Première ligne de la nouvelle page
|
||||||
config.selected_platform = config.current_page * systems_per_page + new_row * GRID_COLS + col
|
config.selected_platform = config.current_page * systems_per_page + new_row * config.GRID_COLS + col
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("down", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
@@ -187,9 +188,9 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
event.value)
|
event.value)
|
||||||
elif is_input_matched(event, "up"):
|
elif is_input_matched(event, "up"):
|
||||||
# Navigation vers le haut avec gestion des limites de page
|
# Navigation vers le haut avec gestion des limites de page
|
||||||
if current_grid_index - GRID_COLS >= 0:
|
if current_grid_index - config.GRID_COLS >= 0:
|
||||||
# Déplacement normal vers le haut
|
# Déplacement normal vers le haut
|
||||||
config.selected_platform -= GRID_COLS
|
config.selected_platform -= config.GRID_COLS
|
||||||
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
event.button if event.type == pygame.JOYBUTTONDOWN else
|
event.button if event.type == pygame.JOYBUTTONDOWN else
|
||||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||||
@@ -197,8 +198,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
elif config.current_page > 0:
|
elif config.current_page > 0:
|
||||||
# Passage à la page précédente si on est en haut de la grille
|
# Passage à la page précédente si on est en haut de la grille
|
||||||
config.current_page -= 1
|
config.current_page -= 1
|
||||||
new_row = GRID_ROWS - 1 # Dernière ligne de la page précédente
|
new_row = config.GRID_ROWS - 1 # Dernière ligne de la page précédente
|
||||||
config.selected_platform = config.current_page * systems_per_page + new_row * GRID_COLS + col
|
config.selected_platform = config.current_page * systems_per_page + new_row * config.GRID_COLS + col
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("up", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
@@ -216,7 +217,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
elif config.current_page > 0:
|
elif config.current_page > 0:
|
||||||
# Passage à la page précédente si on est à la première colonne
|
# Passage à la page précédente si on est à la première colonne
|
||||||
config.current_page -= 1
|
config.current_page -= 1
|
||||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + (GRID_COLS - 1)
|
config.selected_platform = config.current_page * systems_per_page + row * config.GRID_COLS + (config.GRID_COLS - 1)
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
update_key_state("left", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("left", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
@@ -224,7 +225,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
(event.axis, event.value) if event.type == pygame.JOYAXISMOTION else
|
||||||
event.value)
|
event.value)
|
||||||
elif is_input_matched(event, "right"):
|
elif is_input_matched(event, "right"):
|
||||||
if col < GRID_COLS - 1 and current_grid_index < max_index:
|
if col < config.GRID_COLS - 1 and current_grid_index < max_index:
|
||||||
# Déplacement normal vers la droite
|
# Déplacement normal vers la droite
|
||||||
config.selected_platform += 1
|
config.selected_platform += 1
|
||||||
update_key_state("right", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("right", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
@@ -234,7 +235,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
elif (config.current_page + 1) * systems_per_page < len(config.platforms):
|
elif (config.current_page + 1) * systems_per_page < len(config.platforms):
|
||||||
# Passage à la page suivante si on est à la dernière colonne
|
# Passage à la page suivante si on est à la dernière colonne
|
||||||
config.current_page += 1
|
config.current_page += 1
|
||||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS
|
config.selected_platform = config.current_page * systems_per_page + row * config.GRID_COLS
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
update_key_state("right", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
update_key_state("right", True, event.type, event.key if event.type == pygame.KEYDOWN else
|
||||||
@@ -245,7 +246,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
# Navigation rapide vers la page suivante
|
# Navigation rapide vers la page suivante
|
||||||
if (config.current_page + 1) * systems_per_page < len(config.platforms):
|
if (config.current_page + 1) * systems_per_page < len(config.platforms):
|
||||||
config.current_page += 1
|
config.current_page += 1
|
||||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + col
|
config.selected_platform = config.current_page * systems_per_page + row * config.GRID_COLS + col
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
# Réinitialiser la répétition pour éviter des comportements inattendus
|
# Réinitialiser la répétition pour éviter des comportements inattendus
|
||||||
@@ -258,7 +259,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
# Navigation rapide vers la page précédente
|
# Navigation rapide vers la page précédente
|
||||||
if config.current_page > 0:
|
if config.current_page > 0:
|
||||||
config.current_page -= 1
|
config.current_page -= 1
|
||||||
config.selected_platform = config.current_page * systems_per_page + row * GRID_COLS + col
|
config.selected_platform = config.current_page * systems_per_page + row * config.GRID_COLS + col
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
config.selected_platform = len(config.platforms) - 1
|
config.selected_platform = len(config.platforms) - 1
|
||||||
# Réinitialiser la répétition pour éviter des comportements inattendus
|
# Réinitialiser la répétition pour éviter des comportements inattendus
|
||||||
@@ -514,8 +515,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
platform,
|
platform,
|
||||||
load_extensions_json()
|
load_extensions_json()
|
||||||
)
|
)
|
||||||
ext = os.path.splitext(url)[1].lower()
|
zip_ok = bool(config.pending_download[3]) # True only if archive and system known
|
||||||
if not is_supported and ext not in ARCHIVE_EXTENSIONS:
|
if not is_supported and not zip_ok:
|
||||||
# Stocker comme pending sans dupliquer l'entrée
|
# Stocker comme pending sans dupliquer l'entrée
|
||||||
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
@@ -588,8 +589,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
platform,
|
platform,
|
||||||
load_extensions_json()
|
load_extensions_json()
|
||||||
)
|
)
|
||||||
ext = os.path.splitext(url)[1].lower()
|
zip_ok = bool(config.pending_download[3])
|
||||||
if not is_supported and ext not in ARCHIVE_EXTENSIONS:
|
if not is_supported and not zip_ok:
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "extension_warning"
|
config.menu_state = "extension_warning"
|
||||||
config.extension_confirm_selection = 0
|
config.extension_confirm_selection = 0
|
||||||
@@ -621,8 +622,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
platform,
|
platform,
|
||||||
load_extensions_json()
|
load_extensions_json()
|
||||||
)
|
)
|
||||||
ext = os.path.splitext(url)[1].lower()
|
zip_ok = bool(config.pending_download[3])
|
||||||
if not is_supported and ext not in ARCHIVE_EXTENSIONS:
|
if not is_supported and not zip_ok:
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "extension_warning"
|
config.menu_state = "extension_warning"
|
||||||
config.extension_confirm_selection = 0
|
config.extension_confirm_selection = 0
|
||||||
@@ -669,7 +670,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.menu_state = "error"
|
config.menu_state = "error"
|
||||||
config.error_message = _(
|
config.error_message = _(
|
||||||
"error_api_key"
|
"error_api_key"
|
||||||
).format(os.join(config.SAVE_FOLDER,"1fichierAPI.txt"))
|
).format(os.path.join(config.SAVE_FOLDER,"1fichierAPI.txt"))
|
||||||
config.history[-1]["status"] = "Erreur"
|
config.history[-1]["status"] = "Erreur"
|
||||||
config.history[-1]["progress"] = 0
|
config.history[-1]["progress"] = 0
|
||||||
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
config.history[-1]["message"] = "Erreur API : Clé API 1fichier absente"
|
||||||
@@ -711,8 +712,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
if not config.pending_download:
|
if not config.pending_download:
|
||||||
continue
|
continue
|
||||||
is_supported = is_extension_supported(sanitize_filename(game_name), platform, load_extensions_json())
|
is_supported = is_extension_supported(sanitize_filename(game_name), platform, load_extensions_json())
|
||||||
ext = os.path.splitext(url)[1].lower()
|
zip_ok = bool(config.pending_download[3])
|
||||||
if not is_supported and ext not in ARCHIVE_EXTENSIONS:
|
if not is_supported and not zip_ok:
|
||||||
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "extension_warning"
|
config.menu_state = "extension_warning"
|
||||||
@@ -770,8 +771,8 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
if not config.pending_download:
|
if not config.pending_download:
|
||||||
continue
|
continue
|
||||||
is_supported = is_extension_supported(sanitize_filename(game_name), platform, load_extensions_json())
|
is_supported = is_extension_supported(sanitize_filename(game_name), platform, load_extensions_json())
|
||||||
ext = os.path.splitext(url)[1].lower()
|
zip_ok = bool(config.pending_download[3])
|
||||||
if not is_supported and ext not in ARCHIVE_EXTENSIONS:
|
if not is_supported and not zip_ok:
|
||||||
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
config.batch_pending_game = (url, platform, game_name, config.pending_download[3])
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "extension_warning"
|
config.menu_state = "extension_warning"
|
||||||
@@ -863,7 +864,13 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.pending_download = check_extension_before_download(game[1], platform, game_name)
|
config.pending_download = check_extension_before_download(game[1], platform, game_name)
|
||||||
if config.pending_download:
|
if config.pending_download:
|
||||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||||
if is_zip_non_supported and os.path.splitext(url)[1].lower() not in ARCHIVE_EXTENSIONS:
|
# Recalculer le support exact et décider via le flag is_zip_non_supported
|
||||||
|
is_supported = is_extension_supported(
|
||||||
|
sanitize_filename(game_name),
|
||||||
|
platform,
|
||||||
|
load_extensions_json()
|
||||||
|
)
|
||||||
|
if not is_supported and not is_zip_non_supported:
|
||||||
config.previous_menu_state = config.menu_state
|
config.previous_menu_state = config.menu_state
|
||||||
config.menu_state = "extension_warning"
|
config.menu_state = "extension_warning"
|
||||||
config.extension_confirm_selection = 0
|
config.extension_confirm_selection = 0
|
||||||
@@ -990,12 +997,17 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
# Menu pause
|
# Menu pause
|
||||||
elif config.menu_state == "pause_menu":
|
elif config.menu_state == "pause_menu":
|
||||||
#logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
|
#logger.debug(f"État pause_menu, selected_option={config.selected_option}, événement={event.type}, valeur={getattr(event, 'value', None)}")
|
||||||
if is_input_matched(event, "up"):
|
# Start toggles back to previous state when already in pause
|
||||||
|
if is_input_matched(event, "start"):
|
||||||
|
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||||
|
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)
|
config.selected_option = max(0, config.selected_option - 1)
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
elif is_input_matched(event, "down"):
|
elif is_input_matched(event, "down"):
|
||||||
# Nombre d'options dynamique (inclut éventuellement l'option source des jeux)
|
# Nombre d'options dynamique (inclut éventuellement l'option source des jeux)
|
||||||
total = getattr(config, 'pause_menu_total_options', 9) # fallback 9
|
total = getattr(config, 'pause_menu_total_options', 11) # fallback 11 (Restart added)
|
||||||
config.selected_option = min(total - 1, config.selected_option + 1)
|
config.selected_option = min(total - 1, config.selected_option + 1)
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
elif is_input_matched(event, "confirm"):
|
elif is_input_matched(event, "confirm"):
|
||||||
@@ -1031,18 +1043,14 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.selected_language_index = 0
|
config.selected_language_index = 0
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Passage à language_select depuis pause_menu")
|
logger.debug(f"Passage à language_select depuis pause_menu")
|
||||||
elif config.selected_option == 4: # Accessibility
|
elif config.selected_option == 4: # Display
|
||||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||||
config.menu_state = "accessibility_menu"
|
config.menu_state = "display_menu"
|
||||||
|
if not hasattr(config, 'display_menu_selection'):
|
||||||
|
config.display_menu_selection = 0
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Passage au menu accessibilité")
|
logger.debug("Passage au menu affichage")
|
||||||
elif config.selected_option == 5: # Filter platforms
|
elif config.selected_option == 5: # Source toggle (index shifted by removal of filter)
|
||||||
# Ne pas écraser previous_menu_state; il référence l'état avant l'ouverture du pause menu
|
|
||||||
config.menu_state = "filter_platforms"
|
|
||||||
config.selected_filter_index = 0
|
|
||||||
config.filter_platforms_scroll_offset = 0
|
|
||||||
config.needs_redraw = True
|
|
||||||
elif config.selected_option == 6: # Source toggle (index shifted by new option)
|
|
||||||
try:
|
try:
|
||||||
from rgsx_settings import get_sources_mode, set_sources_mode
|
from rgsx_settings import get_sources_mode, set_sources_mode
|
||||||
current_mode = get_sources_mode()
|
current_mode = get_sources_mode()
|
||||||
@@ -1058,13 +1066,13 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
logger.info(f"Changement du mode des sources vers {new_mode}")
|
logger.info(f"Changement du mode des sources vers {new_mode}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur changement mode sources: {e}")
|
logger.error(f"Erreur changement mode sources: {e}")
|
||||||
elif config.selected_option == 7: # Redownload game cache
|
elif config.selected_option == 6: # Redownload game cache
|
||||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||||
config.menu_state = "redownload_game_cache"
|
config.menu_state = "redownload_game_cache"
|
||||||
config.redownload_confirm_selection = 0
|
config.redownload_confirm_selection = 0
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Passage à redownload_game_cache depuis pause_menu")
|
logger.debug(f"Passage à redownload_game_cache depuis pause_menu")
|
||||||
elif config.selected_option == 8: # Music toggle
|
elif config.selected_option == 7: # Music toggle
|
||||||
config.music_enabled = not config.music_enabled
|
config.music_enabled = not config.music_enabled
|
||||||
save_music_config()
|
save_music_config()
|
||||||
if config.music_enabled:
|
if config.music_enabled:
|
||||||
@@ -1076,7 +1084,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
pygame.mixer.music.stop()
|
pygame.mixer.music.stop()
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via menu pause")
|
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via menu pause")
|
||||||
elif config.selected_option == 9: # Symlink option
|
elif config.selected_option == 8: # Symlink option
|
||||||
from rgsx_settings import set_symlink_option, get_symlink_option
|
from rgsx_settings import set_symlink_option, get_symlink_option
|
||||||
current_status = get_symlink_option()
|
current_status = get_symlink_option()
|
||||||
success, message = set_symlink_option(not current_status)
|
success, message = set_symlink_option(not current_status)
|
||||||
@@ -1084,6 +1092,9 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.popup_timer = 3000 if success else 5000
|
config.popup_timer = 3000 if success else 5000
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via menu pause")
|
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via menu pause")
|
||||||
|
elif config.selected_option == 9: # Restart
|
||||||
|
from utils import restart_application
|
||||||
|
restart_application(2000)
|
||||||
elif config.selected_option == 10: # Quit
|
elif config.selected_option == 10: # Quit
|
||||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||||
config.menu_state = "confirm_exit"
|
config.menu_state = "confirm_exit"
|
||||||
@@ -1102,6 +1113,88 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Retour au menu pause depuis controls_help")
|
logger.debug("Retour au menu pause depuis controls_help")
|
||||||
|
|
||||||
|
# Menu Affichage (layout, police, unsupported)
|
||||||
|
elif config.menu_state == "display_menu":
|
||||||
|
sel = getattr(config, 'display_menu_selection', 0)
|
||||||
|
if is_input_matched(event, "up"):
|
||||||
|
config.display_menu_selection = (sel - 1) % 4
|
||||||
|
config.needs_redraw = True
|
||||||
|
elif is_input_matched(event, "down"):
|
||||||
|
config.display_menu_selection = (sel + 1) % 4
|
||||||
|
config.needs_redraw = True
|
||||||
|
elif is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm"):
|
||||||
|
sel = getattr(config, 'display_menu_selection', 0)
|
||||||
|
# 0: layout change
|
||||||
|
if sel == 0 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||||
|
layouts = [(3,3),(3,4),(4,3),(4,4)]
|
||||||
|
try:
|
||||||
|
idx = layouts.index((config.GRID_COLS, config.GRID_ROWS))
|
||||||
|
except ValueError:
|
||||||
|
idx = 1
|
||||||
|
idx = (idx - 1) % len(layouts) if is_input_matched(event, "left") else (idx + 1) % len(layouts)
|
||||||
|
new_cols, new_rows = layouts[idx]
|
||||||
|
try:
|
||||||
|
from rgsx_settings import set_display_grid
|
||||||
|
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
|
||||||
|
config.needs_redraw = True
|
||||||
|
# Redémarrage automatique pour appliquer proprement la modification de layout
|
||||||
|
try:
|
||||||
|
from utils import restart_application
|
||||||
|
# Montrer brièvement l'info puis redémarrer
|
||||||
|
config.menu_state = "restart_popup"
|
||||||
|
config.popup_message = _("popup_restarting")
|
||||||
|
config.popup_timer = 2000
|
||||||
|
restart_application(2000)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors du redémarrage après changement de layout: {e}")
|
||||||
|
# 1: font size adjust
|
||||||
|
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||||
|
from accessibility import save_accessibility_settings
|
||||||
|
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: toggle unsupported
|
||||||
|
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||||
|
try:
|
||||||
|
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
|
||||||
|
current = get_show_unsupported_platforms()
|
||||||
|
new_val = set_show_unsupported_platforms(not current)
|
||||||
|
from utils import load_sources
|
||||||
|
load_sources()
|
||||||
|
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
|
||||||
|
config.popup_timer = 3000
|
||||||
|
config.needs_redraw = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur toggle unsupported: {e}")
|
||||||
|
# 3: open filter platforms menu
|
||||||
|
elif sel == 3 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"
|
||||||
|
config.selected_filter_index = 0
|
||||||
|
config.filter_platforms_scroll_offset = 0
|
||||||
|
config.needs_redraw = True
|
||||||
|
elif is_input_matched(event, "cancel"):
|
||||||
|
config.menu_state = "pause_menu"
|
||||||
|
config.needs_redraw = True
|
||||||
|
|
||||||
# Remap controls
|
# Remap controls
|
||||||
elif config.menu_state == "controls_mapping":
|
elif config.menu_state == "controls_mapping":
|
||||||
if is_input_matched(event, "cancel"):
|
if is_input_matched(event, "cancel"):
|
||||||
@@ -1137,9 +1230,12 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
logger.debug("Dossier images supprimé avec succès")
|
logger.debug("Dossier images supprimé avec succès")
|
||||||
config.menu_state = "restart_popup"
|
config.menu_state = "restart_popup"
|
||||||
config.popup_message = _("popup_redownload_success")
|
config.popup_message = _("popup_redownload_success")
|
||||||
config.popup_timer = 5000 # 5 secondes
|
config.popup_timer = 2000 # bref message
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Passage à restart_popup")
|
logger.debug("Passage à restart_popup")
|
||||||
|
# Redémarrage automatique
|
||||||
|
from utils import restart_application
|
||||||
|
restart_application(2000)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
|
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
|
||||||
config.menu_state = "error"
|
config.menu_state = "error"
|
||||||
@@ -1150,9 +1246,11 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
logger.debug("Fichier sources.json non trouvé, passage à restart_popup")
|
logger.debug("Fichier sources.json non trouvé, passage à restart_popup")
|
||||||
config.menu_state = "restart_popup"
|
config.menu_state = "restart_popup"
|
||||||
config.popup_message = _("popup_no_cache")
|
config.popup_message = _("popup_no_cache")
|
||||||
config.popup_timer = 5000 # 5 secondes
|
config.popup_timer = 2000
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Passage à restart_popup")
|
logger.debug("Passage à restart_popup")
|
||||||
|
from utils import restart_application
|
||||||
|
restart_application(2000)
|
||||||
else: # Non
|
else: # Non
|
||||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
@@ -1213,7 +1311,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Annulation de la sélection de langue, retour au menu pause")
|
logger.debug("Annulation de la sélection de langue, retour au menu pause")
|
||||||
|
|
||||||
# Menu filtre plateformes
|
# Menu filtre plateformes
|
||||||
elif config.menu_state == "filter_platforms":
|
elif config.menu_state == "filter_platforms":
|
||||||
total_items = len(config.filter_platforms_selection)
|
total_items = len(config.filter_platforms_selection)
|
||||||
action_buttons = 4
|
action_buttons = 4
|
||||||
@@ -1271,7 +1369,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
load_sources()
|
load_sources()
|
||||||
# Recalibrer la sélection et la page courante si elles dépassent la nouvelle liste visible
|
# Recalibrer la sélection et la page courante si elles dépassent la nouvelle liste visible
|
||||||
try:
|
try:
|
||||||
systems_per_page = GRID_COLS * GRID_ROWS
|
systems_per_page = config.GRID_COLS * config.GRID_ROWS
|
||||||
if config.current_page * systems_per_page >= len(config.platforms):
|
if config.current_page * systems_per_page >= len(config.platforms):
|
||||||
config.current_page = 0
|
config.current_page = 0
|
||||||
if config.selected_platform >= len(config.platforms):
|
if config.selected_platform >= len(config.platforms):
|
||||||
@@ -1281,12 +1379,32 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.current_page = 0
|
config.current_page = 0
|
||||||
config.selected_platform = 0
|
config.selected_platform = 0
|
||||||
config.filter_platforms_dirty = False
|
config.filter_platforms_dirty = False
|
||||||
config.menu_state = "pause_menu"
|
# Return either to display menu or pause menu depending on origin
|
||||||
|
target = getattr(config, 'filter_return_to', 'pause_menu')
|
||||||
|
config.menu_state = target
|
||||||
|
if target == 'display_menu':
|
||||||
|
# reset display selection to the Filter item for convenience
|
||||||
|
config.display_menu_selection = 3
|
||||||
|
else:
|
||||||
|
config.selected_option = 5 # keep pointer on Filter in pause menu
|
||||||
|
config.filter_return_to = None
|
||||||
elif btn_idx == 3: # back
|
elif btn_idx == 3: # back
|
||||||
config.menu_state = "pause_menu"
|
target = getattr(config, 'filter_return_to', 'pause_menu')
|
||||||
|
config.menu_state = target
|
||||||
|
if target == 'display_menu':
|
||||||
|
config.display_menu_selection = 3
|
||||||
|
else:
|
||||||
|
config.selected_option = 5
|
||||||
|
config.filter_return_to = None
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
elif is_input_matched(event, "cancel"):
|
elif is_input_matched(event, "cancel"):
|
||||||
config.menu_state = "pause_menu"
|
target = getattr(config, 'filter_return_to', 'pause_menu')
|
||||||
|
config.menu_state = target
|
||||||
|
if target == 'display_menu':
|
||||||
|
config.display_menu_selection = 3
|
||||||
|
else:
|
||||||
|
config.selected_option = 5
|
||||||
|
config.filter_return_to = None
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+84
-17
@@ -27,6 +27,8 @@ THEME_COLORS = {
|
|||||||
"button_hover": (255, 0, 255, 220), # Rose
|
"button_hover": (255, 0, 255, 220), # Rose
|
||||||
# Générique
|
# Générique
|
||||||
"text": (255, 255, 255), # blanc
|
"text": (255, 255, 255), # blanc
|
||||||
|
# Texte sélectionné (alias pour compatibilité)
|
||||||
|
"text_selected": (0, 255, 0), # utilise le même vert que fond_lignes
|
||||||
# Erreur
|
# Erreur
|
||||||
"error_text": (255, 0, 0), # rouge
|
"error_text": (255, 0, 0), # rouge
|
||||||
# Avertissement
|
# Avertissement
|
||||||
@@ -439,8 +441,8 @@ def draw_platform_grid(screen):
|
|||||||
margin_right = int(config.screen_width * 0.026)
|
margin_right = int(config.screen_width * 0.026)
|
||||||
margin_top = int(config.screen_height * 0.140)
|
margin_top = int(config.screen_height * 0.140)
|
||||||
margin_bottom = int(config.screen_height * 0.0648)
|
margin_bottom = int(config.screen_height * 0.0648)
|
||||||
num_cols = 3
|
num_cols = getattr(config, 'GRID_COLS', 3)
|
||||||
num_rows = 4
|
num_rows = getattr(config, 'GRID_ROWS', 4)
|
||||||
systems_per_page = num_cols * num_rows
|
systems_per_page = num_cols * num_rows
|
||||||
|
|
||||||
available_width = config.screen_width - margin_left - margin_right
|
available_width = config.screen_width - margin_left - margin_right
|
||||||
@@ -820,12 +822,22 @@ def draw_history_list(screen):
|
|||||||
screen.blit(text_surface, text_rect)
|
screen.blit(text_surface, text_rect)
|
||||||
return
|
return
|
||||||
|
|
||||||
available_height = config.screen_height - title_height - extra_margin_top - extra_margin_bottom - 2 * margin_top_bottom
|
# Espace visible garanti entre le titre et la liste, et au-dessus du footer
|
||||||
items_per_page = available_height // line_height
|
top_gap = 20
|
||||||
|
bottom_reserved = 70 # réserve pour le footer (barre des contrôles) + marge visuelle (réduit)
|
||||||
|
|
||||||
|
# Positionner la liste juste après le titre, avec un espace dédié
|
||||||
|
# Utiliser le rectangle du titre déjà dessiné pour une meilleure précision
|
||||||
|
title_bottom = title_rect_inflated.bottom
|
||||||
|
rect_y = title_bottom + top_gap
|
||||||
|
|
||||||
|
# Calculer l'espace disponible en bas en réservant une zone pour le footer
|
||||||
|
available_height = max(0, config.screen_height - rect_y - bottom_reserved)
|
||||||
|
# Déterminer le nombre d'éléments par page en tenant compte de l'en-tête et des marges internes
|
||||||
|
items_per_page = max(1, (available_height - header_height - 2 * margin_top_bottom) // line_height)
|
||||||
|
|
||||||
rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom
|
rect_height = header_height + items_per_page * line_height + 2 * margin_top_bottom
|
||||||
rect_x = (config.screen_width - rect_width) // 2
|
rect_x = (config.screen_width - rect_width) // 2
|
||||||
rect_y = title_height + extra_margin_top + (config.screen_height - title_height - extra_margin_top - extra_margin_bottom - rect_height) // 2
|
|
||||||
|
|
||||||
config.history_scroll_offset = max(0, min(config.history_scroll_offset, max(0, len(history) - items_per_page)))
|
config.history_scroll_offset = max(0, min(config.history_scroll_offset, max(0, len(history) - items_per_page)))
|
||||||
if config.current_history_item < config.history_scroll_offset:
|
if config.current_history_item < config.history_scroll_offset:
|
||||||
@@ -1090,9 +1102,6 @@ def draw_progress_screen(screen):
|
|||||||
# Limiter le pourcentage entre 0 et 100 pour l'affichage de la barre
|
# Limiter le pourcentage entre 0 et 100 pour l'affichage de la barre
|
||||||
progress_width = int(bar_width * (min(100, max(0, progress_percent)) / 100))
|
progress_width = int(bar_width * (min(100, max(0, progress_percent)) / 100))
|
||||||
|
|
||||||
|
|
||||||
## Ancienne fonction draw_popup_result_download supprimée (popup de fin de téléchargement retiré)
|
|
||||||
|
|
||||||
# Écran avertissement extension non supportée téléchargement
|
# Écran avertissement extension non supportée téléchargement
|
||||||
def draw_extension_warning(screen):
|
def draw_extension_warning(screen):
|
||||||
"""Affiche un avertissement pour une extension non reconnue ou un fichier ZIP."""
|
"""Affiche un avertissement pour une extension non reconnue ou un fichier ZIP."""
|
||||||
@@ -1103,7 +1112,7 @@ def draw_extension_warning(screen):
|
|||||||
game_name = "Inconnu"
|
game_name = "Inconnu"
|
||||||
else:
|
else:
|
||||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||||
logger.debug(f"config.pending_download: url={url}, platform={platform}, game_name={game_name}, is_zip_non_supported={is_zip_non_supported}")
|
# Log réduit: pas de détail verbeux ici
|
||||||
is_zip = is_zip_non_supported
|
is_zip = is_zip_non_supported
|
||||||
if not game_name:
|
if not game_name:
|
||||||
game_name = "Inconnu"
|
game_name = "Inconnu"
|
||||||
@@ -1116,7 +1125,6 @@ def draw_extension_warning(screen):
|
|||||||
|
|
||||||
max_width = config.screen_width - 80
|
max_width = config.screen_width - 80
|
||||||
lines = wrap_text(message, config.font, max_width)
|
lines = wrap_text(message, config.font, max_width)
|
||||||
logger.debug(f"Lignes générées : {lines}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
line_height = config.font.get_height() + 5
|
line_height = config.font.get_height() + 5
|
||||||
@@ -1218,8 +1226,8 @@ def draw_language_menu(screen):
|
|||||||
button_width = 300
|
button_width = 300
|
||||||
button_spacing = 20
|
button_spacing = 20
|
||||||
|
|
||||||
total_height = len(available_languages) * (button_height + button_spacing) - button_spacing
|
# Démarrer la liste juste sous le titre avec le même écart que les boutons
|
||||||
start_y = (config.screen_height - total_height) // 2
|
start_y = title_bg_rect.bottom + button_spacing
|
||||||
|
|
||||||
for i, lang_code in enumerate(available_languages):
|
for i, lang_code in enumerate(available_languages):
|
||||||
# Obtenir le nom de la langue
|
# Obtenir le nom de la langue
|
||||||
@@ -1245,10 +1253,69 @@ def draw_language_menu(screen):
|
|||||||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 50))
|
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 50))
|
||||||
screen.blit(instruction_surface, instruction_rect)
|
screen.blit(instruction_surface, instruction_rect)
|
||||||
|
|
||||||
|
def draw_display_menu(screen):
|
||||||
|
"""Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés)."""
|
||||||
|
screen.blit(OVERLAY, (0, 0))
|
||||||
|
|
||||||
|
# États actuels
|
||||||
|
layout_str = f"{getattr(config, 'GRID_COLS', 3)}x{getattr(config, 'GRID_ROWS', 4)}"
|
||||||
|
font_scale = config.accessibility_settings.get("font_scale", 1.0)
|
||||||
|
from rgsx_settings import get_show_unsupported_platforms
|
||||||
|
show_unsupported = get_show_unsupported_platforms()
|
||||||
|
|
||||||
|
# Libellés
|
||||||
|
options = [
|
||||||
|
f"{_('display_layout')}: {layout_str}",
|
||||||
|
_("accessibility_font_size").format(f"{font_scale:.1f}"),
|
||||||
|
_("menu_show_unsupported_on") if show_unsupported else _("menu_show_unsupported_off"),
|
||||||
|
_("menu_filter_platforms"),
|
||||||
|
]
|
||||||
|
|
||||||
|
selected = getattr(config, 'display_menu_selection', 0)
|
||||||
|
|
||||||
|
# Dimensions du cadre (cohérent avec le menu pause)
|
||||||
|
title_text = _("menu_display")
|
||||||
|
title_surface = config.title_font.render(title_text, True, THEME_COLORS["text"])
|
||||||
|
title_height = title_surface.get_height() + 10
|
||||||
|
menu_width = int(config.screen_width * 0.7)
|
||||||
|
button_height = int(config.screen_height * 0.0463)
|
||||||
|
margin_top_bottom = 20
|
||||||
|
vertical_spacing = 10
|
||||||
|
menu_height = title_height + len(options) * (button_height + vertical_spacing) + 2 * margin_top_bottom
|
||||||
|
menu_x = (config.screen_width - menu_width) // 2
|
||||||
|
menu_y = (config.screen_height - menu_height) // 2
|
||||||
|
|
||||||
|
# Cadre
|
||||||
|
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=12)
|
||||||
|
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=12)
|
||||||
|
|
||||||
|
# Titre centré dans le cadre
|
||||||
|
title_rect = title_surface.get_rect(center=(config.screen_width // 2, menu_y + margin_top_bottom + title_surface.get_height() // 2))
|
||||||
|
screen.blit(title_surface, title_rect)
|
||||||
|
|
||||||
|
# Boutons des options
|
||||||
|
for i, option_text in enumerate(options):
|
||||||
|
y = menu_y + margin_top_bottom + title_height + i * (button_height + vertical_spacing)
|
||||||
|
draw_stylized_button(
|
||||||
|
screen,
|
||||||
|
option_text,
|
||||||
|
menu_x + 20,
|
||||||
|
y,
|
||||||
|
menu_width - 40,
|
||||||
|
button_height,
|
||||||
|
selected=(i == selected)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Aide en bas de l'écran
|
||||||
|
instruction_text = _("language_select_instruction")
|
||||||
|
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||||||
|
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, config.screen_height - 50))
|
||||||
|
screen.blit(instruction_surface, instruction_rect)
|
||||||
|
|
||||||
def draw_pause_menu(screen, selected_option):
|
def draw_pause_menu(screen, selected_option):
|
||||||
"""Dessine le menu pause avec un style moderne."""
|
"""Dessine le menu pause avec un style moderne."""
|
||||||
screen.blit(OVERLAY, (0, 0))
|
screen.blit(OVERLAY, (0, 0))
|
||||||
from rgsx_settings import get_symlink_option, get_sources_mode
|
from rgsx_settings import get_symlink_option, get_sources_mode, get_show_unsupported_platforms
|
||||||
mode = get_sources_mode()
|
mode = get_sources_mode()
|
||||||
source_label = _("games_source_rgsx") if mode == "rgsx" else _("games_source_custom")
|
source_label = _("games_source_rgsx") if mode == "rgsx" else _("games_source_custom")
|
||||||
if config.music_enabled:
|
if config.music_enabled:
|
||||||
@@ -1262,13 +1329,13 @@ def draw_pause_menu(screen, selected_option):
|
|||||||
_("menu_remap_controls"), # 1
|
_("menu_remap_controls"), # 1
|
||||||
_("menu_history"), # 2
|
_("menu_history"), # 2
|
||||||
_("menu_language"), # 3
|
_("menu_language"), # 3
|
||||||
_("menu_accessibility"), # 4
|
_("menu_display"), # 4 new merged display menu
|
||||||
_("menu_filter_platforms"), # 5 new filter option
|
f"{_('menu_games_source_prefix')}: {source_label}", # 5 (shifted left)
|
||||||
f"{_('menu_games_source_prefix')}: {source_label}", # 5
|
|
||||||
_("menu_redownload_cache"), # 6
|
_("menu_redownload_cache"), # 6
|
||||||
music_option, # 7
|
music_option, # 7
|
||||||
symlink_option, # 8
|
symlink_option, # 8
|
||||||
_("menu_quit") # 9
|
_("menu_restart"), # 9 (new)
|
||||||
|
_("menu_quit") # 10
|
||||||
]
|
]
|
||||||
menu_width = int(config.screen_width * 0.8)
|
menu_width = int(config.screen_width * 0.8)
|
||||||
line_height = config.font.get_height() + 10
|
line_height = config.font.get_height() + 10
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"download_canceled": "Download vom Benutzer abgebrochen.",
|
"download_canceled": "Download vom Benutzer abgebrochen.",
|
||||||
|
|
||||||
"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_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 Erweiterung der Datei '{0}' wird laut der Datei info.txt von Batocera nicht unterstützt. Möchtest du 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?",
|
||||||
|
|
||||||
"confirm_exit": "Anwendung beenden?",
|
"confirm_exit": "Anwendung beenden?",
|
||||||
"confirm_clear_history": "Verlauf löschen?",
|
"confirm_clear_history": "Verlauf löschen?",
|
||||||
@@ -76,10 +76,13 @@
|
|||||||
"menu_history": "Verlauf",
|
"menu_history": "Verlauf",
|
||||||
"menu_language": "Sprache",
|
"menu_language": "Sprache",
|
||||||
"menu_accessibility": "Barrierefreiheit",
|
"menu_accessibility": "Barrierefreiheit",
|
||||||
|
"menu_display": "Anzeige",
|
||||||
|
"display_layout": "Anzeigelayout",
|
||||||
"menu_redownload_cache": "Spieleliste aktualisieren",
|
"menu_redownload_cache": "Spieleliste aktualisieren",
|
||||||
"menu_music_toggle": "Musik ein/aus",
|
"menu_music_toggle": "Musik ein/aus",
|
||||||
"menu_music_enabled": "Musik aktiviert: {0}",
|
"menu_music_enabled": "Musik aktiviert: {0}",
|
||||||
"menu_music_disabled": "Musik deaktiviert",
|
"menu_music_disabled": "Musik deaktiviert",
|
||||||
|
"menu_restart": "Neustart",
|
||||||
"menu_filter_platforms": "Systeme filtern",
|
"menu_filter_platforms": "Systeme filtern",
|
||||||
"filter_platforms_title": "Systemsichtbarkeit",
|
"filter_platforms_title": "Systemsichtbarkeit",
|
||||||
"filter_all": "Alle anzeigen",
|
"filter_all": "Alle anzeigen",
|
||||||
@@ -88,11 +91,16 @@
|
|||||||
"filter_back": "Zurück",
|
"filter_back": "Zurück",
|
||||||
"filter_platforms_info": "Sichtbar: {0} | Versteckt: {1} / Gesamt: {2}",
|
"filter_platforms_info": "Sichtbar: {0} | Versteckt: {1} / Gesamt: {2}",
|
||||||
"filter_unsaved_warning": "Ungespeicherte Änderungen",
|
"filter_unsaved_warning": "Ungespeicherte Änderungen",
|
||||||
|
"menu_show_unsupported_on": "Nicht unterstützte Systeme anzeigen: Ja",
|
||||||
|
"menu_show_unsupported_off": "Nicht unterstützte Systeme anzeigen: Nein",
|
||||||
|
"menu_show_unsupported_enabled": "Sichtbarkeit nicht unterstützter Systeme aktiviert",
|
||||||
|
"menu_show_unsupported_disabled": "Sichtbarkeit nicht unterstützter Systeme deaktiviert",
|
||||||
"menu_quit": "Beenden",
|
"menu_quit": "Beenden",
|
||||||
|
|
||||||
"button_yes": "Ja",
|
"button_yes": "Ja",
|
||||||
"button_no": "Nein",
|
"button_no": "Nein",
|
||||||
"button_OK": "OK",
|
"button_OK": "OK",
|
||||||
|
"popup_restarting": "Neustart...",
|
||||||
|
|
||||||
"controls_hold_message": "3 Sekunden halten für: '{0}'",
|
"controls_hold_message": "3 Sekunden halten für: '{0}'",
|
||||||
"controls_skip_message": "Drücke Esc, um zu überspringen (nur PC)",
|
"controls_skip_message": "Drücke Esc, um zu überspringen (nur PC)",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"download_canceled": "Download canceled by user.",
|
"download_canceled": "Download canceled by user.",
|
||||||
|
|
||||||
"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_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 info.txt file. Do you want to 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?",
|
||||||
|
|
||||||
"confirm_exit": "Exit application?",
|
"confirm_exit": "Exit application?",
|
||||||
"confirm_clear_history": "Clear history?",
|
"confirm_clear_history": "Clear history?",
|
||||||
@@ -76,10 +76,13 @@
|
|||||||
"menu_history": "History",
|
"menu_history": "History",
|
||||||
"menu_language": "Language",
|
"menu_language": "Language",
|
||||||
"menu_accessibility": "Accessibility",
|
"menu_accessibility": "Accessibility",
|
||||||
|
"menu_display": "Display",
|
||||||
|
"display_layout": "Display layout",
|
||||||
"menu_redownload_cache": "Update games list",
|
"menu_redownload_cache": "Update games list",
|
||||||
"menu_music_toggle": "Toggle music",
|
"menu_music_toggle": "Toggle music",
|
||||||
"menu_music_enabled": "Music enabled: {0}",
|
"menu_music_enabled": "Music enabled: {0}",
|
||||||
"menu_music_disabled": "Music disabled",
|
"menu_music_disabled": "Music disabled",
|
||||||
|
"menu_restart": "Restart",
|
||||||
"menu_filter_platforms": "Filter systems",
|
"menu_filter_platforms": "Filter systems",
|
||||||
"filter_platforms_title": "Systems visibility",
|
"filter_platforms_title": "Systems visibility",
|
||||||
"filter_all": "Show all",
|
"filter_all": "Show all",
|
||||||
@@ -88,11 +91,16 @@
|
|||||||
"filter_back": "Back",
|
"filter_back": "Back",
|
||||||
"filter_platforms_info": "Visible: {0} | Hidden: {1} / Total: {2}",
|
"filter_platforms_info": "Visible: {0} | Hidden: {1} / Total: {2}",
|
||||||
"filter_unsaved_warning": "Unsaved changes",
|
"filter_unsaved_warning": "Unsaved changes",
|
||||||
|
"menu_show_unsupported_on": "Show unsupported systems: Yes",
|
||||||
|
"menu_show_unsupported_off": "Show unsupported systems: No",
|
||||||
|
"menu_show_unsupported_enabled": "Unsupported systems visibility enabled",
|
||||||
|
"menu_show_unsupported_disabled": "Unsupported systems visibility disabled",
|
||||||
"menu_quit": "Quit",
|
"menu_quit": "Quit",
|
||||||
|
|
||||||
"button_yes": "Yes",
|
"button_yes": "Yes",
|
||||||
"button_no": "No",
|
"button_no": "No",
|
||||||
"button_OK": "OK",
|
"button_OK": "OK",
|
||||||
|
"popup_restarting": "Restarting...",
|
||||||
|
|
||||||
"controls_hold_message": "Hold for 3s for: '{0}'",
|
"controls_hold_message": "Hold for 3s for: '{0}'",
|
||||||
"controls_skip_message": "Press Esc to skip (PC only)",
|
"controls_skip_message": "Press Esc to skip (PC only)",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"download_canceled": "Descarga cancelada por el usuario.",
|
"download_canceled": "Descarga cancelada por el usuario.",
|
||||||
|
|
||||||
"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_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 es soportada por Batocera según el archivo info.txt. ¿Deseas 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?",
|
||||||
|
|
||||||
"confirm_exit": "¿Salir de la aplicación?",
|
"confirm_exit": "¿Salir de la aplicación?",
|
||||||
"confirm_clear_history": "¿Vaciar el historial?",
|
"confirm_clear_history": "¿Vaciar el historial?",
|
||||||
@@ -77,10 +77,13 @@
|
|||||||
"menu_history": "Historial",
|
"menu_history": "Historial",
|
||||||
"menu_language": "Idioma",
|
"menu_language": "Idioma",
|
||||||
"menu_accessibility": "Accesibilidad",
|
"menu_accessibility": "Accesibilidad",
|
||||||
|
"menu_display": "Pantalla",
|
||||||
|
"display_layout": "Distribución",
|
||||||
"menu_redownload_cache": "Actualizar lista de juegos",
|
"menu_redownload_cache": "Actualizar lista de juegos",
|
||||||
"menu_music_toggle": "Activar/Desactivar música",
|
"menu_music_toggle": "Activar/Desactivar música",
|
||||||
"menu_music_enabled": "Música activada: {0}",
|
"menu_music_enabled": "Música activada: {0}",
|
||||||
"menu_music_disabled": "Música desactivada",
|
"menu_music_disabled": "Música desactivada",
|
||||||
|
"menu_restart": "Reiniciar",
|
||||||
"menu_filter_platforms": "Filtrar sistemas",
|
"menu_filter_platforms": "Filtrar sistemas",
|
||||||
"filter_platforms_title": "Visibilidad de sistemas",
|
"filter_platforms_title": "Visibilidad de sistemas",
|
||||||
"filter_all": "Mostrar todo",
|
"filter_all": "Mostrar todo",
|
||||||
@@ -89,11 +92,16 @@
|
|||||||
"filter_back": "Volver",
|
"filter_back": "Volver",
|
||||||
"filter_platforms_info": "Visibles: {0} | Ocultos: {1} / Total: {2}",
|
"filter_platforms_info": "Visibles: {0} | Ocultos: {1} / Total: {2}",
|
||||||
"filter_unsaved_warning": "Cambios no guardados",
|
"filter_unsaved_warning": "Cambios no guardados",
|
||||||
|
"menu_show_unsupported_on": "Mostrar sistemas no soportados: Sí",
|
||||||
|
"menu_show_unsupported_off": "Mostrar sistemas no soportados: No",
|
||||||
|
"menu_show_unsupported_enabled": "Visibilidad de sistemas no soportados activada",
|
||||||
|
"menu_show_unsupported_disabled": "Visibilidad de sistemas no soportados desactivada",
|
||||||
"menu_quit": "Salir",
|
"menu_quit": "Salir",
|
||||||
|
|
||||||
"button_yes": "Sí",
|
"button_yes": "Sí",
|
||||||
"button_no": "No",
|
"button_no": "No",
|
||||||
"button_OK": "OK",
|
"button_OK": "OK",
|
||||||
|
"popup_restarting": "Reiniciando...",
|
||||||
|
|
||||||
"controls_hold_message": "Mantén presionado durante 3s para: '{0}'",
|
"controls_hold_message": "Mantén presionado durante 3s para: '{0}'",
|
||||||
"controls_skip_message": "Presiona Esc para omitir (solo PC)",
|
"controls_skip_message": "Presiona Esc para omitir (solo PC)",
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"download_canceled": "Téléchargement annulé par l'utilisateur.",
|
"download_canceled": "Téléchargement annulé par l'utilisateur.",
|
||||||
|
|
||||||
"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_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 le fichier info.txt. Voulez-vous 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 ?",
|
||||||
|
|
||||||
"confirm_exit": "Quitter l'application ?",
|
"confirm_exit": "Quitter l'application ?",
|
||||||
"confirm_clear_history": "Vider l'historique ?",
|
"confirm_clear_history": "Vider l'historique ?",
|
||||||
@@ -73,11 +73,14 @@
|
|||||||
"menu_history": "Historique",
|
"menu_history": "Historique",
|
||||||
"menu_language": "Langue",
|
"menu_language": "Langue",
|
||||||
"menu_accessibility": "Accessibilité",
|
"menu_accessibility": "Accessibilité",
|
||||||
|
"menu_display": "Affichage",
|
||||||
|
"display_layout": "Disposition",
|
||||||
"menu_redownload_cache": "Mettre à jour la liste des jeux",
|
"menu_redownload_cache": "Mettre à jour la liste des jeux",
|
||||||
"menu_quit": "Quitter",
|
"menu_quit": "Quitter",
|
||||||
"menu_music_toggle": "Activer/Désactiver la musique",
|
"menu_music_toggle": "Activer/Désactiver la musique",
|
||||||
"menu_music_enabled": "Musique activée : {0}",
|
"menu_music_enabled": "Musique activée : {0}",
|
||||||
"menu_music_disabled": "Musique désactivée",
|
"menu_music_disabled": "Musique désactivée",
|
||||||
|
"menu_restart": "Redémarrer",
|
||||||
"menu_filter_platforms": "Filtrer les systèmes",
|
"menu_filter_platforms": "Filtrer les systèmes",
|
||||||
"filter_platforms_title": "Affichage des systèmes",
|
"filter_platforms_title": "Affichage des systèmes",
|
||||||
"filter_all": "Tout afficher",
|
"filter_all": "Tout afficher",
|
||||||
@@ -86,10 +89,15 @@
|
|||||||
"filter_back": "Retour",
|
"filter_back": "Retour",
|
||||||
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
||||||
"filter_unsaved_warning": "Modifications non sauvegardées",
|
"filter_unsaved_warning": "Modifications non sauvegardées",
|
||||||
|
"menu_show_unsupported_on": "Afficher systèmes non supportés : Oui",
|
||||||
|
"menu_show_unsupported_off": "Afficher systèmes non supportés : Non",
|
||||||
|
"menu_show_unsupported_enabled": "Affichage systèmes non supportés activé",
|
||||||
|
"menu_show_unsupported_disabled": "Affichage systèmes non supportés désactivé",
|
||||||
|
|
||||||
"button_yes": "Oui",
|
"button_yes": "Oui",
|
||||||
"button_no": "Non",
|
"button_no": "Non",
|
||||||
"button_OK": "OK",
|
"button_OK": "OK",
|
||||||
|
"popup_restarting": "Redémarrage...",
|
||||||
|
|
||||||
"controls_hold_message": "Maintenez pendant 3s pour : '{0}'",
|
"controls_hold_message": "Maintenez pendant 3s pour : '{0}'",
|
||||||
"controls_skip_message": "Appuyez sur Échap pour passer(Pc only)",
|
"controls_skip_message": "Appuyez sur Échap pour passer(Pc only)",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"download_canceled": "Download cancelado pelo usuário.",
|
"download_canceled": "Download cancelado pelo usuário.",
|
||||||
|
|
||||||
"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_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 o arquivo info.txt. Deseja continuar?",
|
"extension_warning_unsupported": "A extensão do arquivo '{0}' não é suportada pelo Batocera segundo a configuração es_systems.cfg. Deseja continuar?",
|
||||||
|
|
||||||
"confirm_exit": "Sair da aplicação?",
|
"confirm_exit": "Sair da aplicação?",
|
||||||
"confirm_clear_history": "Limpar histórico?",
|
"confirm_clear_history": "Limpar histórico?",
|
||||||
@@ -76,10 +76,13 @@
|
|||||||
"menu_history": "Histórico",
|
"menu_history": "Histórico",
|
||||||
"menu_language": "Idioma",
|
"menu_language": "Idioma",
|
||||||
"menu_accessibility": "Acessibilidade",
|
"menu_accessibility": "Acessibilidade",
|
||||||
|
"menu_display": "Exibição",
|
||||||
|
"display_layout": "Layout de exibição",
|
||||||
"menu_redownload_cache": "Atualizar lista de jogos",
|
"menu_redownload_cache": "Atualizar lista de jogos",
|
||||||
"menu_music_toggle": "Ativar/Desativar música",
|
"menu_music_toggle": "Ativar/Desativar música",
|
||||||
"menu_music_enabled": "Música ativada: {0}",
|
"menu_music_enabled": "Música ativada: {0}",
|
||||||
"menu_music_disabled": "Música desativada",
|
"menu_music_disabled": "Música desativada",
|
||||||
|
"menu_restart": "Reiniciar",
|
||||||
"menu_filter_platforms": "Filtrar sistemas",
|
"menu_filter_platforms": "Filtrar sistemas",
|
||||||
"filter_platforms_title": "Visibilidade dos sistemas",
|
"filter_platforms_title": "Visibilidade dos sistemas",
|
||||||
"filter_all": "Mostrar todos",
|
"filter_all": "Mostrar todos",
|
||||||
@@ -88,11 +91,16 @@
|
|||||||
"filter_back": "Voltar",
|
"filter_back": "Voltar",
|
||||||
"filter_platforms_info": "Visíveis: {0} | Ocultos: {1} / Total: {2}",
|
"filter_platforms_info": "Visíveis: {0} | Ocultos: {1} / Total: {2}",
|
||||||
"filter_unsaved_warning": "Alterações não salvas",
|
"filter_unsaved_warning": "Alterações não salvas",
|
||||||
|
"menu_show_unsupported_on": "Mostrar sistemas não suportados: Sim",
|
||||||
|
"menu_show_unsupported_off": "Mostrar sistemas não suportados: Não",
|
||||||
|
"menu_show_unsupported_enabled": "Visibilidade de sistemas não suportados ativada",
|
||||||
|
"menu_show_unsupported_disabled": "Visibilidade de sistemas não suportados desativada",
|
||||||
"menu_quit": "Sair",
|
"menu_quit": "Sair",
|
||||||
|
|
||||||
"button_yes": "Sim",
|
"button_yes": "Sim",
|
||||||
"button_no": "Não",
|
"button_no": "Não",
|
||||||
"button_OK": "OK",
|
"button_OK": "OK",
|
||||||
|
"popup_restarting": "Reiniciando...",
|
||||||
|
|
||||||
"controls_hold_message": "Mantenha pressionado por 3s para: '{0}'",
|
"controls_hold_message": "Mantenha pressionado por 3s para: '{0}'",
|
||||||
"controls_skip_message": "Pressione Esc para ignorar (somente PC)",
|
"controls_skip_message": "Pressione Esc para ignorar (somente PC)",
|
||||||
|
|||||||
+16
-12
@@ -183,17 +183,21 @@ async def check_for_updates():
|
|||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug("Mise à jour terminée avec succès")
|
logger.debug("Mise à jour terminée avec succès")
|
||||||
|
|
||||||
# Configurer la popup pour afficher le message de succès
|
# Configurer la popup puis redémarrer automatiquement
|
||||||
config.menu_state = "update_result"
|
config.menu_state = "restart_popup"
|
||||||
# Message succès de mise à jour
|
|
||||||
config.update_result_message = _("network_update_success").format(latest_version)
|
config.update_result_message = _("network_update_success").format(latest_version)
|
||||||
# Utiliser aussi le système générique de popup pour affichage
|
|
||||||
config.popup_message = config.update_result_message
|
config.popup_message = config.update_result_message
|
||||||
config.popup_timer = 5000 # 5 secondes
|
config.popup_timer = 2000
|
||||||
config.update_result_error = False
|
config.update_result_error = False
|
||||||
config.update_result_start_time = pygame.time.get_ticks()
|
config.update_result_start_time = pygame.time.get_ticks()
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Affichage de la popup de mise à jour réussie")
|
logger.debug(f"Affichage de la popup de mise à jour réussie, redémarrage imminent")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from utils import restart_application
|
||||||
|
restart_application(2000)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors du redémarrage après mise à jour: {e}")
|
||||||
|
|
||||||
return True, _("network_update_success_message")
|
return True, _("network_update_success_message")
|
||||||
else:
|
else:
|
||||||
@@ -270,10 +274,10 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
|||||||
platform_folder = normalize_platform_name(platform)
|
platform_folder = normalize_platform_name(platform)
|
||||||
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
|
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
|
||||||
|
|
||||||
# Spécifique: si le système est "00 BIOS" on force le dossier BIOS
|
# Spécifique: si le système est "BIOS" on force le dossier BIOS
|
||||||
if platform == "00 BIOS":
|
if platform == "- BIOS by TMCTV":
|
||||||
dest_dir = config.BIOS_FOLDER
|
dest_dir = config.RETROBAT_DATA_FOLDER
|
||||||
logger.debug(f"Plateforme '00 BIOS' détectée, destination forcée vers BIOS_FOLDER: {dest_dir}")
|
logger.debug(f"Plateforme 'BIOS' détectée, destination forcée vers RETROBAT_DATA_FOLDER: {dest_dir}")
|
||||||
|
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
if not os.access(dest_dir, os.W_OK):
|
if not os.access(dest_dir, os.W_OK):
|
||||||
@@ -557,8 +561,8 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
|
|
||||||
# Spécifique: si le système est "00 BIOS" on force le dossier BIOS
|
# Spécifique: si le système est "00 BIOS" on force le dossier BIOS
|
||||||
if platform == "00 BIOS":
|
if platform == "00 BIOS":
|
||||||
dest_dir = config.BIOS_FOLDER
|
dest_dir = config.RETROBAT_DATA_FOLDER
|
||||||
logger.debug(f"Plateforme '00 BIOS' détectée, destination forcée vers BIOS_FOLDER: {dest_dir}")
|
logger.debug(f"Plateforme '00 BIOS' détectée, destination forcée vers RETROBAT_DATA_FOLDER: {dest_dir}")
|
||||||
|
|
||||||
logger.debug(f"Vérification répertoire destination: {dest_dir}")
|
logger.debug(f"Vérification répertoire destination: {dest_dir}")
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ def load_rgsx_settings():
|
|||||||
"accessibility": {
|
"accessibility": {
|
||||||
"font_scale": 1.0
|
"font_scale": 1.0
|
||||||
},
|
},
|
||||||
|
"display": {
|
||||||
|
"grid": "3x4"
|
||||||
|
},
|
||||||
"symlink": {
|
"symlink": {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"target_directory": ""
|
"target_directory": ""
|
||||||
@@ -32,7 +35,8 @@ def load_rgsx_settings():
|
|||||||
"sources": {
|
"sources": {
|
||||||
"mode": "rgsx",
|
"mode": "rgsx",
|
||||||
"custom_url": ""
|
"custom_url": ""
|
||||||
}
|
},
|
||||||
|
"show_unsupported_platforms": False
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -160,3 +164,44 @@ def get_sources_zip_url(fallback_url):
|
|||||||
# Pas de fallback : retourner None pour signaler une source vide
|
# Pas de fallback : retourner None pour signaler une source vide
|
||||||
return None
|
return None
|
||||||
return fallback_url
|
return fallback_url
|
||||||
|
|
||||||
|
# ----------------------- Unsupported platforms toggle ----------------------- #
|
||||||
|
|
||||||
|
def get_show_unsupported_platforms(settings=None):
|
||||||
|
"""Retourne True si l'affichage des systèmes non supportés est activé."""
|
||||||
|
if settings is None:
|
||||||
|
settings = load_rgsx_settings()
|
||||||
|
return bool(settings.get("show_unsupported_platforms", False))
|
||||||
|
|
||||||
|
|
||||||
|
def set_show_unsupported_platforms(enabled: bool):
|
||||||
|
"""Active/désactive l'affichage des systèmes non supportés et sauvegarde."""
|
||||||
|
settings = load_rgsx_settings()
|
||||||
|
settings["show_unsupported_platforms"] = bool(enabled)
|
||||||
|
save_rgsx_settings(settings)
|
||||||
|
return settings["show_unsupported_platforms"]
|
||||||
|
|
||||||
|
# ----------------------- Display layout (grid) ----------------------- #
|
||||||
|
|
||||||
|
def get_display_grid(settings=None):
|
||||||
|
"""Retourne (cols, rows) pour la grille d'affichage, par défaut (3,4)."""
|
||||||
|
if settings is None:
|
||||||
|
settings = load_rgsx_settings()
|
||||||
|
disp = settings.get("display", {})
|
||||||
|
grid = disp.get("grid", "3x4")
|
||||||
|
try:
|
||||||
|
cols, rows = map(int, grid.lower().split("x"))
|
||||||
|
return cols, rows
|
||||||
|
except Exception:
|
||||||
|
return 3, 4
|
||||||
|
|
||||||
|
def set_display_grid(cols: int, rows: int):
|
||||||
|
"""Définit et sauvegarde la grille d'affichage (cols x rows) parmi options autorisées."""
|
||||||
|
allowed = {(3,3), (3,4), (4,3), (4,4)}
|
||||||
|
if (cols, rows) not in allowed:
|
||||||
|
cols, rows = 3, 4
|
||||||
|
settings = load_rgsx_settings()
|
||||||
|
disp = settings.setdefault("display", {})
|
||||||
|
disp["grid"] = f"{cols}x{rows}"
|
||||||
|
save_rgsx_settings(settings)
|
||||||
|
return cols, rows
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
|||||||
|
import os
|
||||||
|
import xml.dom.minidom
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
RGSX_ENTRY = {
|
||||||
|
"path": "./RGSX Retrobat.bat",
|
||||||
|
# 'name' left optional to preserve ES-chosen display name if already present
|
||||||
|
"name": "RGSX",
|
||||||
|
"desc": "Retro Games Sets X - Games Downloader",
|
||||||
|
"image": "./images/RGSX.png",
|
||||||
|
"video": "./videos/RGSX.mp4",
|
||||||
|
"marquee": "./images/RGSX.png",
|
||||||
|
"thumbnail": "./images/RGSX.png",
|
||||||
|
"fanart": "./images/RGSX.png",
|
||||||
|
# Avoid forcing rating to not conflict with ES metadata; set only if absent
|
||||||
|
# "rating": "1",
|
||||||
|
"releasedate": "20250620T165718",
|
||||||
|
"developer": "RetroGameSets.fr",
|
||||||
|
"genre": "Various / Utilities"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_root_dir():
|
||||||
|
"""Détecte le dossier racine RetroBat sans importer config."""
|
||||||
|
# Ce script est dans .../roms/ports/RGSX/
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
# Remonter à .../roms/ports/
|
||||||
|
ports_dir = os.path.dirname(here)
|
||||||
|
# Remonter à .../roms/
|
||||||
|
roms_dir = os.path.dirname(ports_dir)
|
||||||
|
# Remonter à la racine RetroBat
|
||||||
|
root_dir = os.path.dirname(roms_dir)
|
||||||
|
return root_dir
|
||||||
|
|
||||||
|
|
||||||
|
def update_gamelist():
|
||||||
|
try:
|
||||||
|
root_dir = _get_root_dir()
|
||||||
|
gamelist_xml = os.path.join(root_dir, "roms", "windows", "gamelist.xml")
|
||||||
|
# Si le fichier n'existe pas, est vide ou non valide, créer une nouvelle structure
|
||||||
|
if not os.path.exists(gamelist_xml) or os.path.getsize(gamelist_xml) == 0:
|
||||||
|
logger.info(f"Création de {gamelist_xml}")
|
||||||
|
root = ET.Element("gameList")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
logger.info(f"Lecture de {gamelist_xml}")
|
||||||
|
tree = ET.parse(gamelist_xml)
|
||||||
|
root = tree.getroot()
|
||||||
|
if root.tag != "gameList":
|
||||||
|
logger.info(f"{gamelist_xml} n'a pas de balise <gameList>, création d'une nouvelle structure")
|
||||||
|
root = ET.Element("gameList")
|
||||||
|
except ET.ParseError:
|
||||||
|
logger.info(f"{gamelist_xml} est invalide, création d'une nouvelle structure")
|
||||||
|
root = ET.Element("gameList")
|
||||||
|
|
||||||
|
# Rechercher une entrée existante pour ce chemin
|
||||||
|
game_elem = None
|
||||||
|
for game in root.findall("game"):
|
||||||
|
path = game.find("path")
|
||||||
|
if path is not None and path.text == RGSX_ENTRY["path"]:
|
||||||
|
game_elem = game
|
||||||
|
break
|
||||||
|
|
||||||
|
if game_elem is None:
|
||||||
|
# Créer une nouvelle entrée si absente
|
||||||
|
game_elem = ET.SubElement(root, "game")
|
||||||
|
for key, value in RGSX_ENTRY.items():
|
||||||
|
elem = ET.SubElement(game_elem, key)
|
||||||
|
elem.text = value
|
||||||
|
logger.info("Nouvelle entrée RGSX ajoutée")
|
||||||
|
else:
|
||||||
|
# Fusionner: préserver les champs gérés par ES, compléter/mettre à jour nos champs
|
||||||
|
def ensure(tag, value):
|
||||||
|
elem = game_elem.find(tag)
|
||||||
|
if elem is None:
|
||||||
|
elem = ET.SubElement(game_elem, tag)
|
||||||
|
if elem.text is None or elem.text.strip() == "":
|
||||||
|
elem.text = value
|
||||||
|
|
||||||
|
# S'assurer du chemin
|
||||||
|
ensure("path", RGSX_ENTRY["path"])
|
||||||
|
# Ne pas écraser le nom s'il existe déjà (ES peut le définir selon le fichier)
|
||||||
|
name_elem = game_elem.find("name")
|
||||||
|
existing_name = ""
|
||||||
|
if name_elem is not None and name_elem.text:
|
||||||
|
existing_name = name_elem.text.strip()
|
||||||
|
if not existing_name:
|
||||||
|
ensure("name", RGSX_ENTRY.get("name", "RGSX"))
|
||||||
|
|
||||||
|
# Champs d'habillage que nous voulons imposer/mettre à jour
|
||||||
|
for tag in ("desc", "image", "video", "marquee", "thumbnail", "fanart", "developer", "genre", "releasedate"):
|
||||||
|
val = RGSX_ENTRY.get(tag)
|
||||||
|
if val:
|
||||||
|
elem = game_elem.find(tag)
|
||||||
|
if elem is None:
|
||||||
|
elem = ET.SubElement(game_elem, tag)
|
||||||
|
# Toujours aligner ces champs sur nos valeurs pour garder l'expérience RGSX
|
||||||
|
elem.text = val
|
||||||
|
|
||||||
|
# Ne pas toucher aux champs: playcount, lastplayed, gametime, lang, favorite, kidgame, hidden, rating
|
||||||
|
logger.info("Entrée RGSX mise à jour (fusion)")
|
||||||
|
|
||||||
|
# Générer le XML avec minidom pour une indentation correcte
|
||||||
|
rough_string = '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(root, encoding='unicode')
|
||||||
|
parsed = xml.dom.minidom.parseString(rough_string)
|
||||||
|
pretty_xml = parsed.toprettyxml(indent="\t", encoding='utf-8').decode('utf-8')
|
||||||
|
# Supprimer les lignes vides inutiles générées par minidom
|
||||||
|
pretty_xml = '\n'.join(line for line in pretty_xml.split('\n') if line.strip())
|
||||||
|
with open(gamelist_xml, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(pretty_xml)
|
||||||
|
logger.info(f"{gamelist_xml} mis à jour avec succès")
|
||||||
|
|
||||||
|
# Définir les permissions
|
||||||
|
try:
|
||||||
|
os.chmod(gamelist_xml, 0o644)
|
||||||
|
except Exception:
|
||||||
|
# Sur Windows, chmod peut être partiel; ignorer silencieusement
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors de la mise à jour de la gamelist Windows: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def load_gamelist(path):
|
||||||
|
"""Charge le fichier gamelist.xml."""
|
||||||
|
try:
|
||||||
|
tree = ET.parse(path)
|
||||||
|
return tree.getroot()
|
||||||
|
except (FileNotFoundError, ET.ParseError) as e:
|
||||||
|
logging.error(f"Erreur lors de la lecture de {path} : {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
update_gamelist()
|
||||||
+303
-24
@@ -7,6 +7,7 @@ import logging
|
|||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import config
|
import config
|
||||||
|
import glob
|
||||||
import threading
|
import threading
|
||||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -16,6 +17,7 @@ import config
|
|||||||
from history import save_history
|
from history import save_history
|
||||||
from language import _
|
from language import _
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -26,6 +28,50 @@ logging.getLogger("requests").setLevel(logging.WARNING)
|
|||||||
# Liste globale pour stocker les systèmes avec une erreur 404
|
# Liste globale pour stocker les systèmes avec une erreur 404
|
||||||
unavailable_systems = []
|
unavailable_systems = []
|
||||||
|
|
||||||
|
# Cache/process flags for extensions generation/loading
|
||||||
|
|
||||||
|
|
||||||
|
def restart_application(delay_ms: int = 2000):
|
||||||
|
"""Schedule a restart with a visible popup; actual restart happens in the main loop.
|
||||||
|
|
||||||
|
- Sets popup_restarting and schedules config.pending_restart_at = now + delay_ms.
|
||||||
|
- Main loop (__main__) detects pending_restart_at and calls restart_application(0) to perform the execl.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Show popup and schedule
|
||||||
|
if hasattr(config, 'popup_message'):
|
||||||
|
try:
|
||||||
|
config.popup_message = _("popup_restarting")
|
||||||
|
except Exception:
|
||||||
|
config.popup_message = "Restarting..."
|
||||||
|
config.popup_timer = max(config.popup_timer, int(delay_ms)) if hasattr(config, 'popup_timer') else int(delay_ms)
|
||||||
|
config.menu_state = getattr(config, 'menu_state', 'restart_popup') or 'restart_popup'
|
||||||
|
config.needs_redraw = True
|
||||||
|
# Schedule actual restart in main loop
|
||||||
|
now = pygame.time.get_ticks() if hasattr(pygame, 'time') else 0
|
||||||
|
config.pending_restart_at = now + max(0, int(delay_ms))
|
||||||
|
logger.debug(f"Redémarrage planifié dans {delay_ms} ms (pending_restart_at={getattr(config, 'pending_restart_at', 0)})")
|
||||||
|
|
||||||
|
# If delay_ms is 0, perform immediately here
|
||||||
|
if int(delay_ms) <= 0:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
pygame.quit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
exe = sys.executable or "python"
|
||||||
|
os.execl(exe, exe, *sys.argv)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Failed to restart immediately: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Failed to schedule restart: {e}")
|
||||||
|
_extensions_cache = None # type: ignore
|
||||||
|
_extensions_json_regenerated = False
|
||||||
|
|
||||||
|
|
||||||
# Détection système non-PC
|
# Détection système non-PC
|
||||||
def detect_non_pc():
|
def detect_non_pc():
|
||||||
@@ -45,14 +91,166 @@ def detect_non_pc():
|
|||||||
|
|
||||||
# Fonction pour charger le fichier JSON des extensions supportées
|
# Fonction pour charger le fichier JSON des extensions supportées
|
||||||
def load_extensions_json():
|
def load_extensions_json():
|
||||||
"""Charge le fichier JSON contenant les extensions supportées."""
|
"""Charge le JSON des extensions supportées.
|
||||||
|
- Régénère une seule fois par exécution (au premier appel ou si le fichier est absent).
|
||||||
|
- Met en cache le résultat pour éviter les relectures et logs répétés.
|
||||||
|
"""
|
||||||
|
global _extensions_cache, _extensions_json_regenerated
|
||||||
try:
|
try:
|
||||||
with open(config.JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
# Retour immédiat si déjà en cache
|
||||||
return json.load(f)
|
if _extensions_cache is not None:
|
||||||
|
return _extensions_cache
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(config.JSON_EXTENSIONS), exist_ok=True)
|
||||||
|
|
||||||
|
# Régénération unique au premier appel (ou si le fichier est manquant)
|
||||||
|
if not _extensions_json_regenerated or not os.path.exists(config.JSON_EXTENSIONS):
|
||||||
|
try:
|
||||||
|
generated = generate_extensions_json_from_es_systems()
|
||||||
|
if generated:
|
||||||
|
with open(config.JSON_EXTENSIONS, 'w', encoding='utf-8') as wf:
|
||||||
|
json.dump(generated, wf, ensure_ascii=False, indent=2)
|
||||||
|
logger.info(f"rom_extensions régénéré ({len(generated)} systèmes): {config.JSON_EXTENSIONS}")
|
||||||
|
else:
|
||||||
|
logger.warning("Aucune donnée générée depuis es_systems.cfg; on conserve l'existant si présent")
|
||||||
|
_extensions_json_regenerated = True
|
||||||
|
except Exception as ge:
|
||||||
|
logger.error(f"Échec lors de la régénération de {config.JSON_EXTENSIONS} depuis es_systems.cfg: {ge}")
|
||||||
|
|
||||||
|
# Lecture du fichier (nouveau ou existant)
|
||||||
|
if os.path.exists(config.JSON_EXTENSIONS):
|
||||||
|
with open(config.JSON_EXTENSIONS, 'r', encoding='utf-8') as f:
|
||||||
|
_extensions_cache = json.load(f)
|
||||||
|
return _extensions_cache
|
||||||
|
_extensions_cache = []
|
||||||
|
return _extensions_cache
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la lecture de {config.JSON_EXTENSIONS}: {e}")
|
logger.error(f"Erreur lors de la lecture de {config.JSON_EXTENSIONS}: {e}")
|
||||||
|
_extensions_cache = []
|
||||||
|
return _extensions_cache
|
||||||
|
|
||||||
|
def _detect_es_systems_cfg_paths():
|
||||||
|
"""Retourne une liste de chemins possibles pour es_systems.cfg selon l'OS.
|
||||||
|
- RetroBat (Windows): {config.RETROBAT_DATA_FOLDER}\\system\\templates\\emulationstation\\es_systems.cfg
|
||||||
|
- Batocera (Linux): /usr/share/emulationstation/es_systems.cfg
|
||||||
|
Ajoute aussi les fichiers customs: /userdata/system/configs/emulationstation/es_systems_*.cfg
|
||||||
|
"""
|
||||||
|
candidates = []
|
||||||
|
try:
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
base = getattr(config, 'RETROBAT_DATA_FOLDER', None)
|
||||||
|
if base:
|
||||||
|
candidates.append(os.path.join(base, 'system', 'templates', 'emulationstation', 'es_systems.cfg'))
|
||||||
|
else:
|
||||||
|
# Batocera / Linux classiques
|
||||||
|
candidates.append('/usr/share/emulationstation/es_systems.cfg')
|
||||||
|
candidates.append('/etc/emulationstation/es_systems.cfg')
|
||||||
|
# Batocera customs
|
||||||
|
custom_dir = '/userdata/system/configs/emulationstation'
|
||||||
|
try:
|
||||||
|
for p in glob.glob(os.path.join(custom_dir, 'es_systems_*.cfg')):
|
||||||
|
candidates.append(p)
|
||||||
|
direct_cfg = os.path.join(custom_dir, 'es_systems.cfg')
|
||||||
|
if os.path.exists(direct_cfg):
|
||||||
|
candidates.append(direct_cfg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
existing = [p for p in candidates if p and os.path.exists(p)]
|
||||||
|
# Logs réduits: on ne conserve que les résumés plus loin
|
||||||
|
return existing
|
||||||
|
|
||||||
|
def _parse_es_systems_cfg(cfg_path):
|
||||||
|
"""Parse un es_systems.cfg minimalement pour extraire (folder, extensions).
|
||||||
|
Retourne une liste de dicts: { 'folder': <str>, 'extensions': [..] }
|
||||||
|
- folder: dérivé de la balise <path> en prenant la partie après 'roms/' (ou '\\roms\\' sous Windows)
|
||||||
|
- extensions: liste normalisée de .ext (point + minuscule)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Lire tel quel (pas besoin d'un parseur XML strict, mais ElementTree suffit)
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
# Log détaillé supprimé pour alléger les traces
|
||||||
|
tree = ET.parse(cfg_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
out = []
|
||||||
|
for sys_elem in root.findall('system'):
|
||||||
|
path_text = (sys_elem.findtext('path') or '').strip()
|
||||||
|
ext_text = (sys_elem.findtext('extension') or '').strip()
|
||||||
|
if not path_text:
|
||||||
|
continue
|
||||||
|
# Extraire le dossier après 'roms'
|
||||||
|
folder = None
|
||||||
|
norm = path_text.replace('\\', '/').lower()
|
||||||
|
marker = '/roms/'
|
||||||
|
if marker in norm:
|
||||||
|
after = norm.split(marker, 1)[1]
|
||||||
|
folder = after.strip().strip('/\\')
|
||||||
|
if not folder:
|
||||||
|
# fallback: si le chemin finit par .../roms/<folder>
|
||||||
|
parts = norm.strip('/').split('/')
|
||||||
|
if len(parts) >= 2 and parts[-2] == 'roms':
|
||||||
|
folder = parts[-1]
|
||||||
|
if not folder:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extensions: split par espaces, normaliser en .ext
|
||||||
|
exts = []
|
||||||
|
for tok in ext_text.split():
|
||||||
|
tok = tok.strip().lower()
|
||||||
|
if not tok:
|
||||||
|
continue
|
||||||
|
if not tok.startswith('.'):
|
||||||
|
# Certaines entrées peuvent omettre le point
|
||||||
|
tok = '.' + tok
|
||||||
|
exts.append(tok)
|
||||||
|
# Dédupliquer tout en conservant l'ordre
|
||||||
|
seen = set()
|
||||||
|
norm_exts = []
|
||||||
|
for e in exts:
|
||||||
|
if e not in seen:
|
||||||
|
seen.add(e)
|
||||||
|
norm_exts.append(e)
|
||||||
|
out.append({'folder': folder, 'extensions': norm_exts})
|
||||||
|
# Résumé final affiché ailleurs
|
||||||
|
return out
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur parsing es_systems.cfg ({cfg_path}): {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def generate_extensions_json_from_es_systems():
|
||||||
|
"""Essaie de construire la liste des extensions à partir des es_systems.cfg disponibles.
|
||||||
|
Priorité: RetroBat si présent, sinon Batocera. Fusionne si plusieurs trouvés, en préférant RetroBat.
|
||||||
|
"""
|
||||||
|
combined = {}
|
||||||
|
paths = _detect_es_systems_cfg_paths()
|
||||||
|
if not paths:
|
||||||
|
logger.warning("Aucun chemin es_systems.cfg détecté (RetroBat/Batocera)")
|
||||||
|
return []
|
||||||
|
# Prioriser RetroBat en tête si présent
|
||||||
|
def score(p):
|
||||||
|
return 0 if 'templates' in p.replace('\\', '/').lower() else 1
|
||||||
|
for cfg in sorted(paths, key=score):
|
||||||
|
if not os.path.exists(cfg):
|
||||||
|
continue
|
||||||
|
items = _parse_es_systems_cfg(cfg)
|
||||||
|
for itm in items:
|
||||||
|
folder = itm['folder']
|
||||||
|
exts = itm['extensions']
|
||||||
|
if folder in combined:
|
||||||
|
# Fusionner: ajouter extensions manquantes
|
||||||
|
present = set(combined[folder])
|
||||||
|
for e in exts:
|
||||||
|
if e not in present:
|
||||||
|
combined[folder].append(e)
|
||||||
|
present.add(e)
|
||||||
|
else:
|
||||||
|
combined[folder] = list(exts)
|
||||||
|
# Convertir en liste triée par dossier
|
||||||
|
result = [{'folder': k, 'extensions': v} for k, v in sorted(combined.items(), key=lambda x: x[0])]
|
||||||
|
logger.info(f"Extensions combinées totales: {len(result)} systèmes")
|
||||||
|
return result
|
||||||
|
|
||||||
def check_extension_before_download(url, platform, game_name):
|
def check_extension_before_download(url, platform, game_name):
|
||||||
"""Vérifie l'extension avant de lancer le téléchargement et retourne un tuple de 4 éléments."""
|
"""Vérifie l'extension avant de lancer le téléchargement et retourne un tuple de 4 éléments."""
|
||||||
try:
|
try:
|
||||||
@@ -66,10 +264,14 @@ def check_extension_before_download(url, platform, game_name):
|
|||||||
extension = os.path.splitext(sanitized_name)[1].lower()
|
extension = os.path.splitext(sanitized_name)[1].lower()
|
||||||
is_archive = extension in (".zip", ".rar")
|
is_archive = extension in (".zip", ".rar")
|
||||||
|
|
||||||
|
# Déterminer si le système (dossier) est connu dans extensions_data
|
||||||
|
dest_folder_name = _get_dest_folder_name(platform)
|
||||||
|
system_known = any(s.get("folder") == dest_folder_name for s in extensions_data)
|
||||||
|
|
||||||
if is_supported:
|
if is_supported:
|
||||||
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
|
logger.debug(f"L'extension de {sanitized_name} est supportée pour {platform}")
|
||||||
return (url, platform, game_name, False)
|
return (url, platform, game_name, False)
|
||||||
elif is_archive:
|
elif is_archive and system_known:
|
||||||
logger.debug(f"Archive {extension.upper()} détectée pour {sanitized_name}, extraction automatique prévue")
|
logger.debug(f"Archive {extension.upper()} détectée pour {sanitized_name}, extraction automatique prévue")
|
||||||
return (url, platform, game_name, True)
|
return (url, platform, game_name, True)
|
||||||
else:
|
else:
|
||||||
@@ -93,8 +295,8 @@ def is_extension_supported(filename, platform_key, extensions_data):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not dest_dir:
|
if not dest_dir:
|
||||||
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform}")
|
logger.warning(f"Aucun dossier 'folder' trouvé pour la plateforme {platform_key}")
|
||||||
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform)
|
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform_key)
|
||||||
|
|
||||||
dest_folder_name = os.path.basename(dest_dir)
|
dest_folder_name = os.path.basename(dest_dir)
|
||||||
for i, system in enumerate(extensions_data):
|
for i, system in enumerate(extensions_data):
|
||||||
@@ -106,6 +308,20 @@ def is_extension_supported(filename, platform_key, extensions_data):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_dest_folder_name(platform_key: str) -> str:
|
||||||
|
"""Retourne le nom du dossier de destination pour une plateforme (basename du dossier)."""
|
||||||
|
dest_dir = None
|
||||||
|
for platform_dict in config.platform_dicts:
|
||||||
|
if platform_dict.get("platform_name") == platform_key:
|
||||||
|
folder = platform_dict.get("folder")
|
||||||
|
if folder:
|
||||||
|
dest_dir = os.path.join(config.ROMS_FOLDER, folder)
|
||||||
|
break
|
||||||
|
if not dest_dir:
|
||||||
|
dest_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), platform_key)
|
||||||
|
return os.path.basename(dest_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Fonction pour charger sources.json
|
# Fonction pour charger sources.json
|
||||||
@@ -218,6 +434,39 @@ def load_sources():
|
|||||||
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set()
|
||||||
all_sorted_names = [s.get("platform_name", "") for s in sorted_for_display]
|
all_sorted_names = [s.get("platform_name", "") for s in sorted_for_display]
|
||||||
visible_names = [n for n in all_sorted_names if n and n not in hidden]
|
visible_names = [n for n in all_sorted_names if n and n not in hidden]
|
||||||
|
|
||||||
|
# Masquer automatiquement les systèmes dont le dossier ROM n'existe pas (selon le toggle)
|
||||||
|
unsupported = []
|
||||||
|
try:
|
||||||
|
from rgsx_settings import get_show_unsupported_platforms
|
||||||
|
show_unsupported = get_show_unsupported_platforms(settings)
|
||||||
|
sources_by_name = {s.get("platform_name", ""): s for s in sources if isinstance(s, dict)}
|
||||||
|
for name in list(visible_names):
|
||||||
|
entry = sources_by_name.get(name) or {}
|
||||||
|
folder = entry.get("folder")
|
||||||
|
# Conserver BIOS même sans dossier, et ignorer entrées sans folder
|
||||||
|
bios_name = name.strip()
|
||||||
|
if not folder or bios_name == "- BIOS by TMCTV -" or bios_name == "- BIOS":
|
||||||
|
continue
|
||||||
|
expected_dir = os.path.join(config.ROMS_FOLDER, folder)
|
||||||
|
if not os.path.isdir(expected_dir):
|
||||||
|
unsupported.append(name)
|
||||||
|
if show_unsupported:
|
||||||
|
config.unsupported_platforms = unsupported
|
||||||
|
else:
|
||||||
|
if unsupported:
|
||||||
|
# Filtrer la liste visible
|
||||||
|
visible_names = [n for n in visible_names if n not in set(unsupported)]
|
||||||
|
config.unsupported_platforms = unsupported
|
||||||
|
# Log concis + détaillé en DEBUG uniquement
|
||||||
|
logger.info(f"Plateformes masquées (dossier rom absent): {len(unsupported)}")
|
||||||
|
logger.debug("Détails plateformes masquées: " + ", ".join(unsupported))
|
||||||
|
else:
|
||||||
|
config.unsupported_platforms = []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur détection plateformes non supportées (dossiers manquants): {e}")
|
||||||
|
config.unsupported_platforms = []
|
||||||
|
|
||||||
config.platforms = visible_names
|
config.platforms = visible_names
|
||||||
config.platform_names = {p: p for p in config.platforms}
|
config.platform_names = {p: p for p in config.platforms}
|
||||||
# Nouveau mapping par nom pour éviter décalages index après tri d'affichage
|
# Nouveau mapping par nom pour éviter décalages index après tri d'affichage
|
||||||
@@ -303,7 +552,7 @@ def load_games(platform_id):
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"Format de fichier jeux inattendu pour {platform_id}: {type(data)}")
|
logger.warning(f"Format de fichier jeux inattendu pour {platform_id}: {type(data)}")
|
||||||
|
|
||||||
logger.debug(f"Jeux chargés pour {platform_id} depuis {os.path.basename(game_file)}: {len(normalized)} entrées")
|
logger.debug(f"{os.path.basename(game_file)}: {len(normalized)} jeux")
|
||||||
return normalized
|
return normalized
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id}: {e}")
|
logger.error(f"Erreur lors du chargement des jeux pour {platform_id}: {e}")
|
||||||
@@ -451,27 +700,57 @@ def wrap_text(text, font, max_width):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
def load_system_image(platform_dict):
|
def load_system_image(platform_dict):
|
||||||
"""Charge une image système avec priorité:
|
"""Charge une image système avec la priorité suivante:
|
||||||
1. Fichier nommé exactement <platform_name>.png
|
1. platform_image explicite s'il est défini
|
||||||
2. Champ platform_image si non vide
|
2. <platform_name>.png
|
||||||
3. Fallback default.png"""
|
3. <folder>.png si disponible
|
||||||
|
4. Recherche fallback dans le dossier images de l'app (APP_FOLDER/images) avec le même ordre
|
||||||
|
5. default.png (dans SAVE_FOLDER/images), sinon default.png de l'app
|
||||||
|
|
||||||
|
Cela évite d'échouer lorsque le nom affiché ne correspond pas au fichier image
|
||||||
|
et respecte un mapping explicite fourni par systems_list.json."""
|
||||||
platform_name = platform_dict.get("platform_name", "unknown")
|
platform_name = platform_dict.get("platform_name", "unknown")
|
||||||
preferred_filename = f"{platform_name}.png"
|
folder_name = platform_dict.get("folder") or ""
|
||||||
preferred_path = os.path.join(config.IMAGES_FOLDER, preferred_filename)
|
|
||||||
|
|
||||||
# Normaliser platform_image pouvant être vide
|
# Dossiers d'images
|
||||||
platform_image_field = platform_dict.get("platform_image") or ""
|
save_images = config.IMAGES_FOLDER
|
||||||
explicit_image_path = os.path.join(config.IMAGES_FOLDER, platform_image_field) if platform_image_field else None
|
app_images = os.path.join(config.APP_FOLDER, "images")
|
||||||
default_path = os.path.join(config.IMAGES_FOLDER, "default.png")
|
|
||||||
|
|
||||||
|
# Candidats, par ordre de priorité
|
||||||
|
candidates = []
|
||||||
|
platform_image_field = (platform_dict.get("platform_image") or "").strip()
|
||||||
|
if platform_image_field:
|
||||||
|
candidates.append(os.path.join(save_images, platform_image_field))
|
||||||
|
candidates.append(os.path.join(save_images, f"{platform_name}.png"))
|
||||||
|
if folder_name:
|
||||||
|
candidates.append(os.path.join(save_images, f"{folder_name}.png"))
|
||||||
|
|
||||||
|
# Fallback: images packagées avec l'app
|
||||||
|
if platform_image_field:
|
||||||
|
candidates.append(os.path.join(app_images, platform_image_field))
|
||||||
|
candidates.append(os.path.join(app_images, f"{platform_name}.png"))
|
||||||
|
if folder_name:
|
||||||
|
candidates.append(os.path.join(app_images, f"{folder_name}.png"))
|
||||||
|
|
||||||
|
# Charger le premier fichier existant
|
||||||
try:
|
try:
|
||||||
if os.path.exists(preferred_path):
|
for path in candidates:
|
||||||
return pygame.image.load(preferred_path).convert_alpha()
|
if path and os.path.exists(path):
|
||||||
if explicit_image_path and os.path.exists(explicit_image_path):
|
return pygame.image.load(path).convert_alpha()
|
||||||
return pygame.image.load(explicit_image_path).convert_alpha()
|
|
||||||
if os.path.exists(default_path):
|
# default.png (save d'abord, sinon app)
|
||||||
return pygame.image.load(default_path).convert_alpha()
|
default_save = os.path.join(save_images, "default.png")
|
||||||
logger.error(f"Aucune image trouvée pour {platform_name} (cherché: {preferred_path}, {explicit_image_path}, default.png)")
|
if os.path.exists(default_save):
|
||||||
|
return pygame.image.load(default_save).convert_alpha()
|
||||||
|
default_app = os.path.join(app_images, "default.png")
|
||||||
|
if os.path.exists(default_app):
|
||||||
|
return pygame.image.load(default_app).convert_alpha()
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
f"Aucune image trouvée pour {platform_name}. Candidats: "
|
||||||
|
+ ", ".join(candidates)
|
||||||
|
+ f"; default cherchés: {default_save}, {default_app}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors du chargement de l'image pour {platform_name} : {str(e)}")
|
logger.error(f"Erreur lors du chargement de l'image pour {platform_name} : {str(e)}")
|
||||||
|
|||||||
@@ -2,8 +2,16 @@
|
|||||||
setlocal EnableDelayedExpansion
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
:: Fichier de log
|
:: Fichier de log
|
||||||
if not exist %CD%\logs MD %CD%\logs
|
if not exist "%CD%\logs" MD "%CD%\logs"
|
||||||
set LOG_FILE=%CD%\logs\Retrobat_RGSX_log.txt
|
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"
|
||||||
|
|
||||||
:: Ajouter un horodatage au début du log
|
:: Ajouter un horodatage au début du log
|
||||||
echo [%DATE% %TIME%] Script start >> "%LOG_FILE%"
|
echo [%DATE% %TIME%] Script start >> "%LOG_FILE%"
|
||||||
@@ -25,9 +33,13 @@ popd
|
|||||||
:: Définir le chemin du script principal selon les spécifications
|
:: Définir le chemin du script principal selon les spécifications
|
||||||
set "MAIN_SCRIPT=%ROOT_DIR%\roms\ports\RGSX\__main__.py"
|
set "MAIN_SCRIPT=%ROOT_DIR%\roms\ports\RGSX\__main__.py"
|
||||||
|
|
||||||
|
:: 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"
|
||||||
|
|
||||||
:: Convertir les chemins relatifs en absolus avec pushd/popd
|
:: Convertir les chemins relatifs en absolus avec pushd/popd
|
||||||
pushd "%ROOT_DIR%\system\tools\Python"
|
pushd "%ROOT_DIR%\system\tools\Python"
|
||||||
set "PYTHON_EXE_FULL=%ROOT_DIR%\system\tools\Python\!PYTHON_EXE!"
|
set "PYTHON_EXE_FULL=%ROOT_DIR%\system\tools\Python\!PYTHON_EXE!"
|
||||||
|
set "PYTHONW_EXE_FULL=%ROOT_DIR%\system\tools\Python\pythonw.exe"
|
||||||
popd
|
popd
|
||||||
|
|
||||||
:: Afficher et logger les variables
|
:: Afficher et logger les variables
|
||||||
@@ -37,6 +49,7 @@ echo CURRENT_DIR : !CURRENT_DIR! >> "%LOG_FILE%"
|
|||||||
echo ROOT_DIR : !ROOT_DIR! >> "%LOG_FILE%"
|
echo ROOT_DIR : !ROOT_DIR! >> "%LOG_FILE%"
|
||||||
echo PYTHON_EXE_FULL : !PYTHON_EXE_FULL! >> "%LOG_FILE%"
|
echo PYTHON_EXE_FULL : !PYTHON_EXE_FULL! >> "%LOG_FILE%"
|
||||||
echo MAIN_SCRIPT : !MAIN_SCRIPT! >> "%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
|
:: Vérifier si l'exécutable Python existe
|
||||||
echo Checking python.exe...
|
echo Checking python.exe...
|
||||||
@@ -101,16 +114,35 @@ if not exist "!MAIN_SCRIPT!" (
|
|||||||
echo __main__.py found.
|
echo __main__.py found.
|
||||||
echo [%DATE% %TIME%] __main__.py found. >> "%LOG_FILE%"
|
echo [%DATE% %TIME%] __main__.py found. >> "%LOG_FILE%"
|
||||||
|
|
||||||
:: Exécuter le script Python
|
:: L'étape de mise à jour de la gamelist est désormais appelée depuis __main__.py
|
||||||
echo Executing __main__.py...
|
echo [%DATE% %TIME%] Skipping external gamelist update (handled in app). >> "%LOG_FILE%"
|
||||||
echo [%DATE% %TIME%] Executing "!MAIN_SCRIPT!" with !PYTHON_EXE_FULL! >> "%LOG_FILE%"
|
|
||||||
"!PYTHON_EXE_FULL!" "!MAIN_SCRIPT!" >> "%LOG_FILE%" 2>&1
|
echo Launching __main__.py (attached)...
|
||||||
if %ERRORLEVEL% equ 0 (
|
echo [%DATE% %TIME%] Preparing to launch main. >> "%LOG_FILE%"
|
||||||
|
|
||||||
|
:: Assurer le bon dossier de travail pour l'application
|
||||||
|
cd /d "%ROOT_DIR%\roms\ports\RGSX"
|
||||||
|
|
||||||
|
:: Forcer les drivers SDL côté Windows et réduire le bruit console
|
||||||
|
set PYGAME_HIDE_SUPPORT_PROMPT=1
|
||||||
|
set SDL_VIDEODRIVER=windows
|
||||||
|
set SDL_AUDIODRIVER=directsound
|
||||||
|
echo [%DATE% %TIME%] CWD before launch: %CD% >> "%LOG_FILE%"
|
||||||
|
|
||||||
|
:: 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 Execution finished successfully.
|
||||||
echo [%DATE% %TIME%] Execution of __main__.py finished successfully. >> "%LOG_FILE%"
|
echo [%DATE% %TIME%] Execution of __main__.py finished successfully. >> "%LOG_FILE%"
|
||||||
) else (
|
) else (
|
||||||
echo Error: Failed to execute __main__.py (code %ERRORLEVEL%).
|
echo Error: Failed to execute __main__.py (code !EXITCODE!).
|
||||||
echo [%DATE% %TIME%] Error: Failed to execute __main__.py with error code %ERRORLEVEL%. >> "%LOG_FILE%"
|
echo [%DATE% %TIME%] Error: Failed to execute __main__.py with error code !EXITCODE!. >> "%LOG_FILE%"
|
||||||
goto :error
|
goto :error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user