Compare commits

...

3 Commits

Author SHA1 Message Date
skymike03
7d2d55fe5f v2.5.0.0 (2026.01.17)
- add "disable auto-extract" function in MENU>SETTINGS
- add  "ROMS folder" option to select a custom folder for all downloads in MENU>SETTINGS (or Web Interface settings)
- add new menu to choose custom download folder for a specific system (long press CONFIRM button on a selected system/platform)
- add pause option in history when downloading games
- update submenus display layout to have more space for new options
- add missing settings options in rsgx_web  (disable auto-extract, API keys, activate web service / custom dns at boot)
2026-01-17 00:54:10 +01:00
skymike03
14a5416d2d v2.5.0.0 (2026.01.17)
- add "disable auto-extract" function in MENU>SETTINGS
- add  "ROMS folder" option to select a custom folder for all downloads in MENU>SETTINGS (or Web Interface settings)
- add new menu to choose custom download folder for a specific system (long press CONFIRM button on a selected system/platform)
- add pause option in history when downloading games
- update submenus display layout to have more space for new options
- add missing settings options in rsgx_web  (disable auto-extract, API keys, activate web service / custom dns at boot)
2026-01-17 00:45:41 +01:00
skymike03
3193dc90f6 v2.4.2.0 (2026.01.15)
- add menu to choose custom download folder for a specific system (long press validate on a system/platform)
- add pause menu when downloading game
2026-01-14 23:19:58 +01:00
18 changed files with 1784 additions and 424 deletions

View File

@@ -61,52 +61,78 @@ jobs:
echo "✓ All packages created successfully"
ls -lh dist/
- name: Generate release notes with commit message
shell: bash
run: |
# Récupérer le message de commit associé au tag
COMMIT_MSG=$(git log -1 --format=%B ${{ github.ref_name }})
echo "Commit message:"
echo "$COMMIT_MSG"
# Créer le fichier de release notes
cat > dist/RELEASE_NOTES.md << 'RELEASE_EOF'
# 📦 RGSX Release ${{ github.ref_name }}
## 📝 Changelog
RELEASE_EOF
# Ajouter le message de commit
echo "$COMMIT_MSG" >> dist/RELEASE_NOTES.md
# Ajouter le reste des instructions
cat >> dist/RELEASE_NOTES.md << 'RELEASE_EOF'
---
## 📥 Automatic Installation (Only for Batocera/Knulli)
### ON PC, NUC, SteamDeck or any x86_64 based:
1. Open File Manager (F1) then "Applications" and launch xterm
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
### ON RASPBERRY/ ARM based SBC / HANDHELD :
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
## 📥 Manual Installation
### Batocera/Knulli
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
### Retrobat/Full Installation on Windows
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
## 📥 Manual Update (only if automatic doesn't work for some obscure reason)
#### Batocera/Knulli/Retrobat
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract zip content in `/userdata/roms/ports/RGSX`
3. Launch RGSX
### 📖 Documentation
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
RELEASE_EOF
echo "✓ Release notes generated"
cat dist/RELEASE_NOTES.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: RGSX ${{ github.ref_name }}
generate_release_notes: true
generate_release_notes: false
draft: false
prerelease: false
body: |
# 📦 RGSX Release ${{ github.ref_name }}
## 📥 Automatic Installation (Only for batocera Knulli)
### ON PC , NUC, SteamDeck or any x86_64 based:
1. Open File Manager (F1) then "Applications" and launch xterm
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
### ON RASPBERRY/ ARM based SBC / HANDHELD :
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
3. Launch RGSX from "Ports" menu
## 📥 Manual Installation
### Batocera/Knulli
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract only PORTS folder in `/userdata/roms/`
3. Launch RGSX from the Ports menu
### Retrobat/Full Installation on Windows
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
2. Extract all folders in your Retrobat\roms folder
3. Launch RGSX from system "Windows"
## 📥 Manual Update (only if automatic doesn't work for some obcure reason)
#### Batocera/Knulli/Retrobat
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
2. Extract zip content on in `/userdata/roms/ports/RGSX`
3. Launch RGSX
### 📖 Documentation
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
body_path: dist/RELEASE_NOTES.md
files: |
dist/RGSX_update_latest.zip
dist/RGSX_full_latest.zip

View File

@@ -575,6 +575,24 @@ async def main():
thread = threading.Thread(target=scrape_async, daemon=True)
thread.start()
# Gestion de l'appui long sur confirm dans le menu platform pour configurer le dossier de destination
if (config.menu_state == "platform" and
getattr(config, 'platform_confirm_press_start_time', 0) > 0 and
not getattr(config, 'platform_confirm_long_press_triggered', False)):
press_duration = current_time - config.platform_confirm_press_start_time
if press_duration >= config.confirm_long_press_threshold:
# Appui long détecté, ouvrir le dialogue de configuration du dossier
if config.platforms:
platform = config.platforms[config.selected_platform]
platform_name = platform["name"] if isinstance(platform, dict) else platform
config.platform_config_name = platform_name
config.previous_menu_state = "platform"
config.menu_state = "platform_folder_config"
config.platform_folder_selection = 0 # 0=Current, 1=Browse, 2=Reset, 3=Cancel
config.needs_redraw = True
config.platform_confirm_long_press_triggered = True
logger.debug(f"Appui long détecté ({press_duration}ms), ouverture config dossier pour {platform_name}")
# Gestion des événements
events = pygame.event.get()
for event in events:
@@ -741,6 +759,21 @@ async def main():
config.needs_redraw = True
continue
if config.menu_state == "platform_folder_config":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
continue
if config.menu_state == "folder_browser":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
continue
if config.menu_state == "folder_browser_new_folder":
action = handle_controls(event, sources, joystick, screen)
config.needs_redraw = True
continue
if config.menu_state == "extension_warning":
logger.debug(f"[EXTENSION_WARNING] Processing extension_warning, previous_menu_state={config.previous_menu_state}, pending_download={bool(config.pending_download)}")
action = handle_controls(event, sources, joystick, screen)
@@ -1158,6 +1191,15 @@ async def main():
elif config.menu_state == "gamelist_update_prompt":
from display import draw_gamelist_update_prompt
draw_gamelist_update_prompt(screen)
elif config.menu_state == "platform_folder_config":
from display import draw_platform_folder_config_dialog
draw_platform_folder_config_dialog(screen)
elif config.menu_state == "folder_browser":
from display import draw_folder_browser
draw_folder_browser(screen)
elif config.menu_state == "folder_browser_new_folder":
from display import draw_folder_browser_new_folder
draw_folder_browser_new_folder(screen)
elif config.menu_state == "restart_popup":
draw_popup(screen)
elif config.menu_state == "accessibility_menu":

View File

@@ -13,7 +13,7 @@ except Exception:
pygame = None # type: ignore
# Version actuelle de l'application
app_version = "2.4.1.0"
app_version = "2.5.0.0"
# Nombre de jours avant de proposer la mise à jour de la liste des jeux
GAMELIST_UPDATE_DAYS = 7
@@ -451,6 +451,22 @@ confirm_press_start_time = 0 # Timestamp du début de l'appui sur confirm
confirm_long_press_threshold = 2000 # Durée en ms pour déclencher l'appui long (2 secondes)
confirm_long_press_triggered = False # Flag pour éviter de déclencher plusieurs fois
# Détection d'appui long sur confirm (menu platform - pour config dossier)
platform_confirm_press_start_time = 0 # Timestamp du début de l'appui sur confirm dans le menu platform
platform_confirm_long_press_triggered = False # Flag pour éviter de déclencher plusieurs fois
# Configuration dossier personnalisé par plateforme
platform_config_name = "" # Nom de la plateforme en cours de configuration
platform_folder_selection = 0 # Index de sélection dans le menu de config dossier (0=Current, 1=Browse, 2=Reset, 3=Cancel)
# Navigateur de dossiers intégré (folder browser)
folder_browser_path = "" # Chemin actuel dans le navigateur
folder_browser_items = [] # Liste des éléments (dossiers) dans le répertoire actuel
folder_browser_selection = 0 # Index de l'élément sélectionné
folder_browser_scroll_offset = 0 # Offset de défilement
folder_browser_visible_items = 10 # Nombre d'éléments visibles
folder_browser_mode = "platform" # "platform" pour dossier plateforme, "roms_root" pour dossier ROMs principal
# Tenter la récupération de la famille de police sauvegardée
try:
from rgsx_settings import get_font_family # import tardif pour éviter dépendances circulaires lors de l'exécution initiale

View File

@@ -45,7 +45,7 @@ VALID_STATES = [
"extension_warning", "pause_menu", "controls_help", "history", "controls_mapping",
"reload_games_data", "restart_popup", "error", "loading", "confirm_clear_history",
"language_select", "filter_platforms", "display_menu", "confirm_cancel_download",
"gamelist_update_prompt",
"gamelist_update_prompt", "platform_folder_config",
# Nouveaux sous-menus hiérarchiques (refonte pause menu)
"pause_controls_menu", # sous-menu Controls (aide, remap)
"pause_display_menu", # sous-menu Display (layout, font size, unsupported, unknown ext, filter)
@@ -68,6 +68,9 @@ VALID_STATES = [
"filter_search", # recherche par nom (existant, mais renommé)
"filter_advanced", # filtrage avancé par région, etc.
"filter_priority_config", # configuration priorité régions pour one-rom-per-game
"platform_folder_config", # configuration du dossier personnalisé pour une plateforme
"folder_browser", # navigateur de dossiers intégré
"folder_browser_new_folder", # création d'un nouveau dossier
]
def validate_menu_state(state):
@@ -482,29 +485,17 @@ def handle_controls(event, sources, joystick, screen):
config.needs_redraw = True
logger.debug("Ouverture history depuis platform")
elif is_input_matched(event, "confirm"):
if config.platforms:
config.current_platform = config.selected_platform
config.games = load_games(config.platforms[config.current_platform])
# Apply saved filters automatically if any
if config.game_filter_obj and config.game_filter_obj.is_active():
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
config.filter_active = True
else:
config.filtered_games = config.games
config.filter_active = False
config.current_game = 0
config.scroll_offset = 0
# Désactiver l'animation de transition en mode performance (light mode)
from rgsx_settings import get_light_mode
if not get_light_mode():
draw_validation_transition(screen, config.current_platform)
config.menu_state = "game"
config.needs_redraw = True
#logger.debug(f"Plateforme sélectionnée: {config.platforms[config.current_platform]}, {len(config.games)} jeux chargés")
# Démarrer le chronomètre pour l'appui long - ne pas exécuter immédiatement
# L'action sera exécutée au relâchement si appui court, ou config dossier si appui long
if not hasattr(config, 'platform_confirm_press_start_time'):
config.platform_confirm_press_start_time = 0
if not hasattr(config, 'platform_confirm_long_press_triggered'):
config.platform_confirm_long_press_triggered = False
config.platform_confirm_press_start_time = current_time
config.platform_confirm_long_press_triggered = False
config.needs_redraw = True
# Note: la navigation vers les jeux sera gérée au BUTTONUP/KEYUP si appui court
elif is_input_matched(event, "cancel"):
# Capturer l'origine (plateformes) pour un retour correct si l'utilisateur choisit "Non"
config.confirm_exit_origin = "platform"
@@ -1099,8 +1090,9 @@ def handle_controls(event, sources, joystick, screen):
if status == "Queued":
# En attente dans la queue
options.append("remove_from_queue")
elif status in ["Downloading", "Téléchargement", "Extracting"]:
# Téléchargement en cours
elif status in ["Downloading", "Téléchargement", "Extracting", "Paused"]:
# Téléchargement en cours ou en pause - ajouter pause/resume avant cancel
options.append("pause_resume_download")
options.append("cancel_download")
elif status == "Download_OK" or status == "Completed":
# Vérifier si c'est une archive ET si le fichier existe
@@ -1167,6 +1159,21 @@ def handle_controls(event, sources, joystick, screen):
config.menu_state = "history"
config.needs_redraw = True
elif selected_option == "pause_resume_download":
# Mettre en pause ou reprendre le téléchargement
task_id = entry.get("task_id")
if task_id:
from network import toggle_pause_download, is_download_paused
is_paused = toggle_pause_download(task_id)
if is_paused:
entry["status"] = "Paused"
else:
entry["status"] = "Downloading"
save_history(config.history)
config.needs_redraw = True
# Retour à l'historique
config.menu_state = "history"
elif selected_option == "cancel_download":
# Rediriger vers le dialogue de confirmation (même que bouton cancel)
config.previous_menu_state = "history"
@@ -2033,19 +2040,21 @@ def handle_controls(event, sources, joystick, screen):
elif config.menu_state == "pause_settings_menu":
sel = getattr(config, 'pause_settings_selection', 0)
# Calculer le nombre total d'options selon le système
# Liste des options : music, symlink, [web_service], [custom_dns], api keys, back
total = 4 # music, symlink, api keys, back (Windows)
# Liste des options : music, symlink, auto_extract, roms_folder, [web_service], [custom_dns], api keys, back
total = 6 # music, symlink, auto_extract, roms_folder, api keys, back (Windows)
auto_extract_index = 2
roms_folder_index = 3
web_service_index = -1
custom_dns_index = -1
api_keys_index = 2
back_index = 3
api_keys_index = 4
back_index = 5
if config.OPERATING_SYSTEM == "Linux":
total = 6 # music, symlink, web_service, custom_dns, api keys, back
web_service_index = 2
custom_dns_index = 3
api_keys_index = 4
back_index = 5
total = 8 # music, symlink, auto_extract, roms_folder, web_service, custom_dns, api keys, back
web_service_index = 4
custom_dns_index = 5
api_keys_index = 6
back_index = 7
if is_input_matched(event, "up"):
config.pause_settings_selection = (sel - 1) % total
@@ -2053,7 +2062,7 @@ def handle_controls(event, sources, joystick, screen):
elif is_input_matched(event, "down"):
config.pause_settings_selection = (sel + 1) % total
config.needs_redraw = True
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "clear_history"):
# Option 0: Music toggle
if sel == 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
config.music_enabled = not config.music_enabled
@@ -2075,7 +2084,55 @@ def handle_controls(event, sources, joystick, screen):
config.popup_timer = 3000 if success else 5000
config.needs_redraw = True
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via settings")
# Option 2: Web Service toggle (seulement si Linux)
# Option 2: Auto Extract toggle
elif sel == auto_extract_index and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
from rgsx_settings import get_auto_extract, set_auto_extract
current_status = get_auto_extract()
set_auto_extract(not current_status)
config.needs_redraw = True
logger.info(f"Auto extract {'activée' if not current_status else 'désactivée'} via settings")
# Option 3: ROMs folder - ouvrir le navigateur (confirm) ou reset (clear_history)
elif sel == roms_folder_index:
if is_input_matched(event, "confirm"):
from rgsx_settings import get_roms_folder
# Ouvrir le navigateur de dossiers en mode roms_root
current_custom = get_roms_folder()
if current_custom and os.path.isdir(current_custom):
start_path = current_custom
else:
# Démarrer depuis le dossier parent de ROMS_FOLDER actuel
start_path = os.path.dirname(config.ROMS_FOLDER) if config.ROMS_FOLDER else "/"
if not os.path.isdir(start_path):
start_path = "/"
config.folder_browser_path = start_path
config.folder_browser_selection = 0
config.folder_browser_scroll_offset = 0
config.folder_browser_mode = "roms_root"
# Charger la liste des dossiers
try:
items = [".."]
for item in sorted(os.listdir(start_path)):
full_path = os.path.join(start_path, item)
if os.path.isdir(full_path):
items.append(item)
config.folder_browser_items = items
except Exception as e:
logger.error(f"Erreur lecture dossier {start_path}: {e}")
config.folder_browser_items = [".."]
config.menu_state = "folder_browser"
config.needs_redraw = True
logger.info("Ouverture navigateur dossier ROMs principal")
elif is_input_matched(event, "clear_history"):
# Réinitialiser le dossier ROMs par défaut
from rgsx_settings import set_roms_folder, get_roms_folder
current = get_roms_folder()
if current: # Si un dossier custom est défini, le réinitialiser
set_roms_folder("")
config.popup_message = _("roms_folder_reset") if _ else "ROMs folder reset to default\nRestart required!"
config.popup_timer = 5000
logger.info("Dossier ROMs réinitialisé par défaut")
config.needs_redraw = True
# Option 4: Web Service toggle (seulement si Linux)
elif sel == web_service_index and web_service_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
current_status = check_web_service_status()
@@ -2315,6 +2372,249 @@ def handle_controls(event, sources, joystick, screen):
config.menu_state = "platform"
config.needs_redraw = True
# Configuration du dossier personnalisé pour une plateforme
elif config.menu_state == "platform_folder_config":
# Options: 0=Current path, 1=Browse, 2=Reset, 3=Cancel
if is_input_matched(event, "up") or is_input_matched(event, "down"):
total_options = 4
if is_input_matched(event, "up"):
config.platform_folder_selection = (config.platform_folder_selection - 1) % total_options
else:
config.platform_folder_selection = (config.platform_folder_selection + 1) % total_options
config.needs_redraw = True
elif is_input_matched(event, "confirm"):
from rgsx_settings import get_platform_custom_path, set_platform_custom_path
platform_name = config.platform_config_name
if config.platform_folder_selection == 0: # Show current path
current_path = get_platform_custom_path(platform_name)
if current_path:
config.popup_message = current_path
else:
# Afficher le chemin par défaut en utilisant le vrai nom de dossier
folder_name = _get_dest_folder_name(platform_name)
default_path = os.path.join(config.ROMS_FOLDER, folder_name)
config.popup_message = _("platform_folder_default_path").format(default_path) if _ else f"Default: {default_path}"
config.popup_timer = 5000
config.needs_redraw = True
elif config.platform_folder_selection == 1: # Browse
# Ouvrir le navigateur de dossiers intégré
current_path = get_platform_custom_path(platform_name)
if not current_path or not os.path.isdir(current_path):
# Démarrer depuis le dossier ROMS par défaut
current_path = config.ROMS_FOLDER
config.folder_browser_path = current_path
config.folder_browser_selection = 0
config.folder_browser_scroll_offset = 0
config.folder_browser_mode = "platform"
# Charger la liste des dossiers
try:
items = [".."]
for item in sorted(os.listdir(current_path)):
full_path = os.path.join(current_path, item)
if os.path.isdir(full_path):
items.append(item)
config.folder_browser_items = items
except Exception as e:
logger.error(f"Erreur lecture dossier {current_path}: {e}")
config.folder_browser_items = [".."]
config.menu_state = "folder_browser"
config.needs_redraw = True
elif config.platform_folder_selection == 2: # Reset
set_platform_custom_path(platform_name, "")
config.popup_message = _("platform_folder_reset").format(platform_name) if _ else f"Folder reset for {platform_name}"
config.popup_timer = 3000
logger.info(f"Dossier personnalisé réinitialisé pour {platform_name}")
config.menu_state = "platform"
config.needs_redraw = True
elif config.platform_folder_selection == 3: # Cancel
config.menu_state = "platform"
config.needs_redraw = True
elif is_input_matched(event, "cancel"):
config.menu_state = "platform"
config.needs_redraw = True
# Navigateur de dossiers intégré
elif config.menu_state == "folder_browser":
if is_input_matched(event, "up"):
if config.folder_browser_selection > 0:
config.folder_browser_selection -= 1
# Ajuster le scroll
if config.folder_browser_selection < config.folder_browser_scroll_offset:
config.folder_browser_scroll_offset = config.folder_browser_selection
config.needs_redraw = True
elif is_input_matched(event, "down"):
if config.folder_browser_selection < len(config.folder_browser_items) - 1:
config.folder_browser_selection += 1
# Ajuster le scroll
if config.folder_browser_selection >= config.folder_browser_scroll_offset + config.folder_browser_visible_items:
config.folder_browser_scroll_offset = config.folder_browser_selection - config.folder_browser_visible_items + 1
config.needs_redraw = True
elif is_input_matched(event, "confirm"):
if config.folder_browser_items:
selected_item = config.folder_browser_items[config.folder_browser_selection]
if selected_item == "..":
# Remonter d'un niveau
parent = os.path.dirname(config.folder_browser_path)
if parent and parent != config.folder_browser_path:
config.folder_browser_path = parent
config.folder_browser_selection = 0
config.folder_browser_scroll_offset = 0
try:
items = [".."]
for item in sorted(os.listdir(parent)):
full_path = os.path.join(parent, item)
if os.path.isdir(full_path):
items.append(item)
config.folder_browser_items = items
except Exception as e:
logger.error(f"Erreur lecture dossier {parent}: {e}")
config.folder_browser_items = [".."]
else:
# Entrer dans le dossier sélectionné
new_path = os.path.join(config.folder_browser_path, selected_item)
if os.path.isdir(new_path):
config.folder_browser_path = new_path
config.folder_browser_selection = 0
config.folder_browser_scroll_offset = 0
try:
items = [".."]
for item in sorted(os.listdir(new_path)):
full_path = os.path.join(new_path, item)
if os.path.isdir(full_path):
items.append(item)
config.folder_browser_items = items
except Exception as e:
logger.error(f"Erreur lecture dossier {new_path}: {e}")
config.folder_browser_items = [".."]
config.needs_redraw = True
elif is_input_matched(event, "history"):
# Valider et sélectionner le dossier actuel (touche X/Y)
browser_mode = getattr(config, 'folder_browser_mode', 'platform')
selected_path = config.folder_browser_path
if browser_mode == "roms_root":
# Mode dossier ROMs principal
from rgsx_settings import set_roms_folder
set_roms_folder(selected_path)
config.popup_message = _("roms_folder_set").format(selected_path) if _ else f"ROMs folder set: {selected_path}"
config.popup_timer = 5000
logger.info(f"Dossier ROMs principal défini: {selected_path}")
# Informer qu'un redémarrage est nécessaire
config.popup_message = _("roms_folder_set_restart").format(selected_path) if _ else f"ROMs folder set: {selected_path}\nRestart required!"
config.menu_state = "pause_settings_menu"
else:
# Mode dossier plateforme
from rgsx_settings import set_platform_custom_path
platform_name = config.platform_config_name
set_platform_custom_path(platform_name, selected_path)
config.popup_message = _("platform_folder_set").format(platform_name, selected_path) if _ else f"Folder set for {platform_name}: {selected_path}"
config.popup_timer = 3000
logger.info(f"Dossier personnalisé défini pour {platform_name}: {selected_path}")
config.menu_state = "platform"
config.needs_redraw = True
elif is_input_matched(event, "cancel"):
# Annuler et revenir au menu approprié selon le mode
browser_mode = getattr(config, 'folder_browser_mode', 'platform')
if browser_mode == "roms_root":
config.menu_state = "pause_settings_menu"
else:
config.menu_state = "platform_folder_config"
config.needs_redraw = True
elif is_input_matched(event, "clear_history"):
# Créer un nouveau dossier
config.new_folder_name = ""
config.new_folder_selected_key = (0, 0)
config.menu_state = "folder_browser_new_folder"
config.needs_redraw = True
logger.debug("Ouverture mode création de dossier")
# Création d'un nouveau dossier dans le folder browser
elif config.menu_state == "folder_browser_new_folder":
keyboard_layout = [
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
['Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M'],
['W', 'X', 'C', 'V', 'B', 'N', '-', '_', '.']
]
row, col = getattr(config, 'new_folder_selected_key', (0, 0))
max_row = len(keyboard_layout) - 1
max_col = len(keyboard_layout[row]) - 1
if is_input_matched(event, "up"):
if row == 0:
row = max_row + (1 if col <= len(keyboard_layout[max_row]) - 1 else 0)
if row > 0:
config.new_folder_selected_key = (row - 1, min(col, len(keyboard_layout[row - 1]) - 1))
config.needs_redraw = True
elif is_input_matched(event, "down"):
if row == max_row:
row = -1
if row < max_row:
config.new_folder_selected_key = (row + 1, min(col, len(keyboard_layout[row + 1]) - 1))
config.needs_redraw = True
elif is_input_matched(event, "left"):
if col == 0:
col = max_col + 1
if col > 0:
config.new_folder_selected_key = (row, col - 1)
config.needs_redraw = True
elif is_input_matched(event, "right"):
if col == max_col:
col = -1
if col < max_col:
config.new_folder_selected_key = (row, col + 1)
config.needs_redraw = True
elif is_input_matched(event, "confirm"):
# Ajouter le caractère sélectionné
config.new_folder_name = getattr(config, 'new_folder_name', '') + keyboard_layout[row][col]
config.needs_redraw = True
elif is_input_matched(event, "delete"):
# Supprimer le dernier caractère
if getattr(config, 'new_folder_name', ''):
config.new_folder_name = config.new_folder_name[:-1]
config.needs_redraw = True
elif is_input_matched(event, "space"):
# Ajouter un espace
config.new_folder_name = getattr(config, 'new_folder_name', '') + " "
config.needs_redraw = True
elif is_input_matched(event, "history"):
# Valider et créer le dossier
folder_name = getattr(config, 'new_folder_name', '').strip()
if folder_name:
new_folder_path = os.path.join(config.folder_browser_path, folder_name)
try:
os.makedirs(new_folder_path, exist_ok=True)
logger.info(f"Dossier créé: {new_folder_path}")
config.popup_message = _("folder_created").format(folder_name) if _ else f"Folder created: {folder_name}"
config.popup_timer = 2000
# Rafraîchir la liste des dossiers et sélectionner le nouveau
try:
items = [".."]
for item in sorted(os.listdir(config.folder_browser_path)):
full_path = os.path.join(config.folder_browser_path, item)
if os.path.isdir(full_path):
items.append(item)
config.folder_browser_items = items
# Sélectionner le nouveau dossier
if folder_name in items:
config.folder_browser_selection = items.index(folder_name)
# Ajuster le scroll si nécessaire
if config.folder_browser_selection >= config.folder_browser_visible_items:
config.folder_browser_scroll_offset = config.folder_browser_selection - config.folder_browser_visible_items + 1
except Exception as e:
logger.error(f"Erreur rafraîchissement liste: {e}")
except Exception as e:
logger.error(f"Erreur création dossier {new_folder_path}: {e}")
config.popup_message = _("folder_create_error").format(str(e)) if _ else f"Error: {e}"
config.popup_timer = 3000
config.menu_state = "folder_browser"
config.needs_redraw = True
elif is_input_matched(event, "cancel"):
# Annuler et revenir au folder browser
config.menu_state = "folder_browser"
config.needs_redraw = True
# Redownload game cache
elif config.menu_state == "reload_games_data":
if is_input_matched(event, "left") or is_input_matched(event, "right"):
@@ -2911,6 +3211,42 @@ def handle_controls(event, sources, joystick, screen):
# Réinitialiser les flags
config.confirm_press_start_time = 0
config.confirm_long_press_triggered = False
# Gestion spéciale pour confirm dans le menu platform (appui court = aller aux jeux)
if action_name == "confirm" and config.menu_state == "platform" and \
((action_name in keyboard_fallback and keyboard_fallback[action_name] == event.key) or \
(config.controls_config.get(action_name, {}).get("type") == "key" and \
config.controls_config.get(action_name, {}).get("key") == event.key)):
press_duration = current_time - getattr(config, 'platform_confirm_press_start_time', 0)
# Si appui court (< 2 secondes) et pas déjà traité par l'appui long
if press_duration < config.confirm_long_press_threshold and not getattr(config, 'platform_confirm_long_press_triggered', False):
# Naviguer vers les jeux
if config.platforms:
config.current_platform = config.selected_platform
config.games = load_games(config.platforms[config.current_platform])
# Apply saved filters automatically if any
if config.game_filter_obj and config.game_filter_obj.is_active():
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
config.filter_active = True
else:
config.filtered_games = config.games
config.filter_active = False
config.current_game = 0
config.scroll_offset = 0
# Désactiver l'animation de transition en mode performance (light mode)
from rgsx_settings import get_light_mode
if not get_light_mode():
draw_validation_transition(screen, config.current_platform)
config.menu_state = "game"
config.needs_redraw = True
logger.debug(f"Appui court clavier sur confirm ({press_duration}ms), navigation vers les jeux de {config.platforms[config.current_platform]}")
# Réinitialiser les flags platform
config.platform_confirm_press_start_time = 0
config.platform_confirm_long_press_triggered = False
elif event.type == pygame.JOYBUTTONUP:
# Vérifier quel bouton a été relâché
@@ -3007,6 +3343,39 @@ def handle_controls(event, sources, joystick, screen):
# Réinitialiser les flags
config.confirm_press_start_time = 0
config.confirm_long_press_triggered = False
# Gestion spéciale pour confirm dans le menu platform (appui court = aller aux jeux)
if action_name == "confirm" and config.menu_state == "platform":
press_duration = current_time - getattr(config, 'platform_confirm_press_start_time', 0)
# Si appui court (< 2 secondes) et pas déjà traité par l'appui long
if press_duration < config.confirm_long_press_threshold and not getattr(config, 'platform_confirm_long_press_triggered', False):
# Naviguer vers les jeux
if config.platforms:
config.current_platform = config.selected_platform
config.games = load_games(config.platforms[config.current_platform])
# Apply saved filters automatically if any
if config.game_filter_obj and config.game_filter_obj.is_active():
config.filtered_games = config.game_filter_obj.apply_filters(config.games)
config.filter_active = True
else:
config.filtered_games = config.games
config.filter_active = False
config.current_game = 0
config.scroll_offset = 0
# Désactiver l'animation de transition en mode performance (light mode)
from rgsx_settings import get_light_mode
if not get_light_mode():
draw_validation_transition(screen, config.current_platform)
config.menu_state = "game"
config.needs_redraw = True
logger.debug(f"Appui court sur confirm ({press_duration}ms), navigation vers les jeux de {config.platforms[config.current_platform]}")
# Réinitialiser les flags platform
config.platform_confirm_press_start_time = 0
config.platform_confirm_long_press_triggered = False
elif event.type == pygame.JOYAXISMOTION:
# Détection de relâchement d'axe

File diff suppressed because it is too large Load Diff

View File

@@ -221,9 +221,20 @@
"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren",
"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren",
"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten",
"instruction_settings_auto_extract": "Automatische Archivextraktion nach Download aktivieren/deaktivieren",
"instruction_settings_roms_folder": "Standard-Download-Verzeichnis für ROMs ändern",
"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen",
"instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren",
"instruction_settings_custom_dns": "Custom DNS (Cloudflare 1.1.1.1) beim Booten aktivieren/deaktivieren",
"settings_auto_extract": "Auto-Extraktion Archive",
"settings_auto_extract_enabled": "Aktiviert",
"settings_auto_extract_disabled": "Deaktiviert",
"settings_roms_folder": "ROMs-Ordner",
"settings_roms_folder_default": "Standard",
"roms_folder_set": "ROMs-Ordner festgelegt: {0}",
"roms_folder_set_restart": "ROMs-Ordner festgelegt: {0}\nNeustart erforderlich!",
"roms_folder_reset": "ROMs-Ordner auf Standard zurückgesetzt\nNeustart erforderlich!",
"folder_browser_title_roms_root": "Standard-ROMs-Ordner auswählen",
"settings_web_service": "Web-Dienst beim Booten",
"settings_web_service_enabled": "Aktiviert",
"settings_web_service_disabled": "Deaktiviert",
@@ -266,6 +277,8 @@
"history_option_scraper": "Metadaten abrufen",
"history_option_remove_from_queue": "Aus Warteschlange entfernen",
"history_option_cancel_download": "Download abbrechen",
"history_option_pause_download": "Download pausieren",
"history_option_resume_download": "Download fortsetzen",
"history_option_delete_game": "Spiel löschen",
"history_option_error_info": "Fehlerdetails",
"history_option_retry": "Download wiederholen",
@@ -328,6 +341,9 @@
"web_settings_source_mode": "Spielequelle",
"web_settings_custom_url": "Benutzerdefinierte URL",
"web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip",
"web_settings_auto_extract": "Archive nach dem Download automatisch entpacken",
"web_settings_web_service": "Webdienst beim Booten starten",
"web_settings_custom_dns": "Benutzerdefinierten DNS beim Booten aktivieren",
"web_settings_save": "Einstellungen speichern",
"web_settings_saved": "Einstellungen erfolgreich gespeichert!",
"web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden.",
@@ -351,6 +367,7 @@
"web_history_status_completed": "Abgeschlossen",
"web_history_status_error": "Fehler",
"web_settings_os": "Betriebssystem",
"web_system_info_title": "Systeminformationen",
"web_settings_platforms_count": "Anzahl der Plattformen",
"web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)",
"web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)",
@@ -429,5 +446,23 @@
"filter_reset_filters": "Zurücksetzen",
"filter_back": "Zurück",
"filter_active": "Filter aktiv",
"filter_games_shown": "{0} Spiel(e) angezeigt"
"filter_games_shown": "{0} Spiel(e) angezeigt",
"platform_folder_config_current": "Download-Ordner für {0} konfigurieren\nAktuell: {1}",
"platform_folder_config_default": "Download-Ordner für {0} konfigurieren\nStandardordner wird verwendet",
"platform_folder_show_current": "Aktuellen Pfad anzeigen",
"platform_folder_browse": "Durchsuchen",
"platform_folder_reset": "Auf Standard zurücksetzen",
"platform_folder_set": "Ordner für {0} festgelegt: {1}",
"platform_folder_default_path": "Standard: {0}",
"folder_browser_title": "Ordner für {0} auswählen",
"folder_browser_parent": "Übergeordneter Ordner",
"folder_browser_enter": "Öffnen",
"folder_browser_select": "Auswählen",
"folder_new_folder": "Neuer Ordner",
"folder_new_title": "Neuen Ordner erstellen",
"folder_new_confirm": "Erstellen",
"folder_created": "Ordner erstellt: {0}",
"folder_create_error": "Fehler beim Erstellen: {0}",
"controls_action_select_char": "Zeichen",
"folder_browser_browse": "Durchsuchen"
}

View File

@@ -223,9 +223,20 @@
"instruction_games_update_cache": "Redownload & refresh current games list",
"instruction_settings_music": "Enable or disable background music playback",
"instruction_settings_symlink": "Toggle using filesystem symlinks for installs",
"instruction_settings_auto_extract": "Toggle automatic archive extraction after download",
"instruction_settings_roms_folder": "Change the default ROMs download directory",
"instruction_settings_api_keys": "See detected premium provider API keys",
"instruction_settings_web_service": "Enable/disable web service auto-start at boot",
"instruction_settings_custom_dns": "Enable/disable custom DNS (Cloudflare 1.1.1.1) at boot",
"settings_auto_extract": "Auto Extract Archives",
"settings_auto_extract_enabled": "Enabled",
"settings_auto_extract_disabled": "Disabled",
"settings_roms_folder": "ROMs Folder",
"settings_roms_folder_default": "Default",
"roms_folder_set": "ROMs folder set: {0}",
"roms_folder_set_restart": "ROMs folder set: {0}\nRestart required to apply!",
"roms_folder_reset": "ROMs folder reset to default\nRestart required to apply!",
"folder_browser_title_roms_root": "Select default ROMs folder",
"settings_web_service": "Web Service at Boot",
"settings_web_service_enabled": "Enabled",
"settings_web_service_disabled": "Disabled",
@@ -268,6 +279,8 @@
"history_option_scraper": "Scrape metadata",
"history_option_remove_from_queue": "Remove from queue",
"history_option_cancel_download": "Cancel download",
"history_option_pause_download": "Pause download",
"history_option_resume_download": "Resume download",
"history_option_delete_game": "Delete game",
"history_option_error_info": "Error details",
"history_option_retry": "Retry download",
@@ -330,6 +343,9 @@
"web_settings_source_mode": "Games source",
"web_settings_custom_url": "Custom URL",
"web_settings_custom_url_placeholder": "Let empty for local /saves/ports/rgsx/games.zip or use a direct URL like https://example.com/games.zip",
"web_settings_auto_extract": "Auto-extract archives after download",
"web_settings_web_service": "Start web service at boot",
"web_settings_custom_dns": "Enable custom DNS at boot",
"web_settings_save": "Save Settings",
"web_settings_saved": "Settings saved successfully!",
"web_settings_saved_restart": "Settings saved successfully!\\n\\n⚠ Some settings require a server restart:\\n- Custom ROMs folder\\n- Language\\n\\nPlease restart the web server to apply these changes.",
@@ -353,6 +369,7 @@
"web_history_status_completed": "Completed",
"web_history_status_error": "Error",
"web_settings_os": "Operating System",
"web_system_info_title": "System Information",
"web_settings_platforms_count": "Number of platforms",
"web_settings_show_unsupported": "Show unsupported platforms (system not found in es_systems.cfg)",
"web_settings_allow_unknown": "Allow unknown extensions (don't show warnings)",
@@ -429,5 +446,23 @@
"filter_reset_filters": "Reset",
"filter_back": "Back",
"filter_active": "Filter active",
"filter_games_shown": "{0} game(s) shown"
"filter_games_shown": "{0} game(s) shown",
"platform_folder_config_current": "Configure download folder for {0}\nCurrent: {1}",
"platform_folder_config_default": "Configure download folder for {0}\nUsing default location",
"platform_folder_show_current": "Show current path",
"platform_folder_browse": "Browse",
"platform_folder_reset": "Reset to default",
"platform_folder_set": "Folder set for {0}: {1}",
"platform_folder_default_path": "Default: {0}",
"folder_browser_title": "Select folder for {0}",
"folder_browser_parent": "Parent folder",
"folder_browser_enter": "Enter",
"folder_browser_select": "Select",
"folder_new_folder": "New folder",
"folder_new_title": "Create New Folder",
"folder_new_confirm": "Create",
"folder_created": "Folder created: {0}",
"folder_create_error": "Error creating folder: {0}",
"controls_action_select_char": "Add char",
"folder_browser_browse": "Browse"
}

View File

@@ -221,9 +221,20 @@
"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos",
"instruction_settings_music": "Activar o desactivar música de fondo",
"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones",
"instruction_settings_auto_extract": "Activar/desactivar extracción automática de archivos después de descargar",
"instruction_settings_roms_folder": "Cambiar el directorio de descarga de ROMs por defecto",
"instruction_settings_api_keys": "Ver claves API premium detectadas",
"instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web",
"instruction_settings_custom_dns": "Activar/desactivar DNS personalizado (Cloudflare 1.1.1.1) al inicio",
"settings_auto_extract": "Extracción auto de archivos",
"settings_auto_extract_enabled": "Activado",
"settings_auto_extract_disabled": "Desactivado",
"settings_roms_folder": "Carpeta ROMs",
"settings_roms_folder_default": "Por defecto",
"roms_folder_set": "Carpeta ROMs configurada: {0}",
"roms_folder_set_restart": "Carpeta ROMs configurada: {0}\n¡Reinicio necesario para aplicar!",
"roms_folder_reset": "Carpeta ROMs restablecida por defecto\n¡Reinicio necesario para aplicar!",
"folder_browser_title_roms_root": "Seleccionar carpeta ROMs por defecto",
"settings_web_service": "Servicio Web al Inicio",
"settings_web_service_enabled": "Activado",
"settings_web_service_disabled": "Desactivado",
@@ -266,6 +277,8 @@
"history_option_scraper": "Obtener metadatos",
"history_option_remove_from_queue": "Quitar de la cola",
"history_option_cancel_download": "Cancelar descarga",
"history_option_pause_download": "Pausar descarga",
"history_option_resume_download": "Reanudar descarga",
"history_option_delete_game": "Eliminar juego",
"history_option_error_info": "Detalles del error",
"history_option_retry": "Reintentar descarga",
@@ -328,6 +341,9 @@
"web_settings_source_mode": "Fuente de juegos",
"web_settings_custom_url": "URL personalizada",
"web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip",
"web_settings_auto_extract": "Extraer archivos automáticamente después de descargar",
"web_settings_web_service": "Iniciar servicio web al arrancar",
"web_settings_custom_dns": "Activar DNS personalizado al arrancar",
"web_settings_save": "Guardar configuración",
"web_settings_saved": "¡Configuración guardada con éxito!",
"web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios.",
@@ -351,6 +367,7 @@
"web_history_status_completed": "Completado",
"web_history_status_error": "Error",
"web_settings_os": "Sistema operativo",
"web_system_info_title": "Información del sistema",
"web_settings_platforms_count": "Número de plataformas",
"web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)",
"web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)",
@@ -427,5 +444,23 @@
"filter_reset_filters": "Restablecer",
"filter_back": "Volver",
"filter_active": "Filtro activo",
"filter_games_shown": "{0} juego(s) mostrado(s)"
"filter_games_shown": "{0} juego(s) mostrado(s)",
"platform_folder_config_current": "Configurar carpeta de descarga para {0}\nActual: {1}",
"platform_folder_config_default": "Configurar carpeta de descarga para {0}\nUsando ubicación predeterminada",
"platform_folder_show_current": "Mostrar ruta actual",
"platform_folder_browse": "Examinar",
"platform_folder_reset": "Restablecer predeterminado",
"platform_folder_set": "Carpeta establecida para {0}: {1}",
"platform_folder_default_path": "Por defecto: {0}",
"folder_browser_title": "Seleccionar carpeta para {0}",
"folder_browser_parent": "Carpeta superior",
"folder_browser_enter": "Entrar",
"folder_browser_select": "Seleccionar",
"folder_new_folder": "Nueva carpeta",
"folder_new_title": "Crear nueva carpeta",
"folder_new_confirm": "Crear",
"folder_created": "Carpeta creada: {0}",
"folder_create_error": "Error al crear: {0}",
"controls_action_select_char": "Añadir",
"folder_browser_browse": "Explorar"
}

View File

@@ -223,9 +223,20 @@
"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux",
"instruction_settings_music": "Activer ou désactiver la lecture musicale",
"instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation",
"instruction_settings_auto_extract": "Activer/désactiver l'extraction automatique des archives après téléchargement",
"instruction_settings_roms_folder": "Changer le répertoire de téléchargement des ROMs par défaut",
"instruction_settings_api_keys": "Voir les clés API détectées des services premium",
"instruction_settings_web_service": "Activer/désactiver le démarrage automatique du service web",
"instruction_settings_custom_dns": "Activer/désactiver les DNS personnalisés (Cloudflare 1.1.1.1) au démarrage",
"settings_auto_extract": "Extraction auto des archives",
"settings_auto_extract_enabled": "Activé",
"settings_auto_extract_disabled": "Désactivé",
"settings_roms_folder": "Dossier ROMs",
"settings_roms_folder_default": "Par défaut",
"roms_folder_set": "Dossier ROMs défini: {0}",
"roms_folder_set_restart": "Dossier ROMs défini: {0}\nRedémarrage nécessaire pour appliquer!",
"roms_folder_reset": "Dossier ROMs réinitialisé par défaut\nRedémarrage nécessaire pour appliquer!",
"folder_browser_title_roms_root": "Sélectionner le dossier ROMs par défaut",
"settings_web_service": "Service Web au démarrage",
"settings_web_service_enabled": "Activé",
"settings_web_service_disabled": "Désactivé",
@@ -268,6 +279,8 @@
"history_option_scraper": "Récupérer métadonnées",
"history_option_remove_from_queue": "Retirer de la file d'attente",
"history_option_cancel_download": "Annuler le téléchargement",
"history_option_pause_download": "Mettre en pause",
"history_option_resume_download": "Reprendre le téléchargement",
"history_option_delete_game": "Supprimer le jeu",
"history_option_error_info": "Détails de l'erreur",
"history_option_retry": "Retenter le téléchargement",
@@ -330,6 +343,9 @@
"web_settings_source_mode": "Source des jeux",
"web_settings_custom_url": "URL personnalisée",
"web_settings_custom_url_placeholder": "Laisser vide pour /saves/ports/rgsx/games.zip ou utiliser une URL directe comme https://exemple.com/jeux.zip",
"web_settings_auto_extract": "Extraction auto des archives après téléchargement",
"web_settings_web_service": "Lancer le service web au démarrage",
"web_settings_custom_dns": "Activer le DNS personnalisé au démarrage",
"web_settings_save": "Enregistrer les paramètres",
"web_settings_saved": "Paramètres enregistrés avec succès !",
"web_settings_saved_restart": "Paramètres enregistrés avec succès !\\n\\n⚠ Certains paramètres nécessitent un redémarrage du serveur :\\n- Dossier ROMs personnalisé\\n- Langue\\n\\nVeuillez redémarrer le serveur web pour appliquer ces changements.",
@@ -353,6 +369,7 @@
"web_history_status_completed": "Terminé",
"web_history_status_error": "Erreur",
"web_settings_os": "Système d'exploitation",
"web_system_info_title": "Informations système",
"web_settings_platforms_count": "Nombre de plateformes",
"web_settings_show_unsupported": "Afficher les systèmes non supportés (absents de es_systems.cfg)",
"web_settings_allow_unknown": "Autoriser les extensions inconnues (ne pas afficher d'avertissement)",
@@ -429,5 +446,23 @@
"filter_reset_filters": "Réinitialiser",
"filter_back": "Retour",
"filter_active": "Filtre actif",
"filter_games_shown": "{0} jeu(x) affiché(s)"
"filter_games_shown": "{0} jeu(x) affiché(s)",
"platform_folder_config_current": "Configurer le dossier de téléchargement pour {0}\nActuel: {1}",
"platform_folder_config_default": "Configurer le dossier de téléchargement pour {0}\nUtilise le dossier par défaut",
"platform_folder_show_current": "Afficher le chemin actuel",
"platform_folder_browse": "Parcourir",
"platform_folder_reset": "Rétablir par défaut",
"platform_folder_set": "Dossier défini pour {0}: {1}",
"platform_folder_default_path": "Par défaut: {0}",
"folder_browser_title": "Sélectionner le dossier pour {0}",
"folder_browser_parent": "Dossier parent",
"folder_browser_enter": "Entrer",
"folder_browser_select": "Valider",
"folder_new_folder": "Nouveau dossier",
"folder_new_title": "Créer un nouveau dossier",
"folder_new_confirm": "Créer",
"folder_created": "Dossier créé: {0}",
"folder_create_error": "Erreur lors de la création: {0}",
"controls_action_select_char": "Ajouter",
"folder_browser_browse": "Parcourir"
}

View File

@@ -216,9 +216,20 @@
"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi",
"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo",
"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni",
"instruction_settings_auto_extract": "Attivare/disattivare estrazione automatica archivi dopo il download",
"instruction_settings_roms_folder": "Cambiare la directory di download ROMs predefinita",
"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate",
"instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio",
"instruction_settings_custom_dns": "Attivare/disattivare DNS personalizzato (Cloudflare 1.1.1.1) all'avvio",
"settings_auto_extract": "Estrazione auto archivi",
"settings_auto_extract_enabled": "Attivato",
"settings_auto_extract_disabled": "Disattivato",
"settings_roms_folder": "Cartella ROMs",
"settings_roms_folder_default": "Predefinita",
"roms_folder_set": "Cartella ROMs impostata: {0}",
"roms_folder_set_restart": "Cartella ROMs impostata: {0}\nRiavvio necessario per applicare!",
"roms_folder_reset": "Cartella ROMs ripristinata predefinita\nRiavvio necessario per applicare!",
"folder_browser_title_roms_root": "Seleziona cartella ROMs predefinita",
"settings_web_service": "Servizio Web all'Avvio",
"settings_web_service_enabled": "Abilitato",
"settings_web_service_disabled": "Disabilitato",
@@ -261,6 +272,8 @@
"history_option_scraper": "Scraper metadati",
"history_option_remove_from_queue": "Rimuovi dalla coda",
"history_option_cancel_download": "Annulla download",
"history_option_pause_download": "Pausa download",
"history_option_resume_download": "Riprendi download",
"history_option_delete_game": "Elimina gioco",
"history_option_error_info": "Dettagli errore",
"history_option_retry": "Riprova download",
@@ -323,6 +336,9 @@
"web_settings_source_mode": "Fonte giochi",
"web_settings_custom_url": "URL personalizzato",
"web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip",
"web_settings_auto_extract": "Estrai automaticamente gli archivi dopo il download",
"web_settings_web_service": "Avvia servizio web all'avvio",
"web_settings_custom_dns": "Abilita DNS personalizzato all'avvio",
"web_settings_save": "Salva impostazioni",
"web_settings_saved": "Impostazioni salvate con successo!",
"web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche.",
@@ -346,6 +362,7 @@
"web_history_status_completed": "Completato",
"web_history_status_error": "Errore",
"web_settings_os": "Sistema operativo",
"web_system_info_title": "Informazioni di sistema",
"web_settings_platforms_count": "Numero di piattaforme",
"web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)",
"web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)",
@@ -425,5 +442,23 @@
"filter_reset_filters": "Reimposta",
"filter_back": "Indietro",
"filter_active": "Filtro attivo",
"filter_games_shown": "{0} gioco/i mostrato/i"
"filter_games_shown": "{0} gioco/i mostrato/i",
"platform_folder_config_current": "Configura cartella download per {0}\nAttuale: {1}",
"platform_folder_config_default": "Configura cartella download per {0}\nUsando posizione predefinita",
"platform_folder_show_current": "Mostra percorso attuale",
"platform_folder_browse": "Sfoglia",
"platform_folder_reset": "Ripristina predefinito",
"platform_folder_set": "Cartella impostata per {0}: {1}",
"platform_folder_default_path": "Predefinito: {0}",
"folder_browser_title": "Seleziona cartella per {0}",
"folder_browser_parent": "Cartella superiore",
"folder_browser_enter": "Entra",
"folder_browser_select": "Seleziona",
"folder_new_folder": "Nuova cartella",
"folder_new_title": "Crea nuova cartella",
"folder_new_confirm": "Crea",
"folder_created": "Cartella creata: {0}",
"folder_create_error": "Errore nella creazione: {0}",
"controls_action_select_char": "Aggiungi",
"folder_browser_browse": "Sfoglia"
}

View File

@@ -222,9 +222,20 @@
"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos",
"instruction_settings_music": "Ativar ou desativar música de fundo",
"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações",
"instruction_settings_auto_extract": "Ativar/desativar extração automática de arquivos após download",
"instruction_settings_roms_folder": "Alterar o diretório de download de ROMs padrão",
"instruction_settings_api_keys": "Ver chaves API premium detectadas",
"instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização",
"instruction_settings_custom_dns": "Ativar/desativar DNS personalizado (Cloudflare 1.1.1.1) na inicialização",
"settings_auto_extract": "Extração auto de arquivos",
"settings_auto_extract_enabled": "Ativado",
"settings_auto_extract_disabled": "Desativado",
"settings_roms_folder": "Pasta ROMs",
"settings_roms_folder_default": "Padrão",
"roms_folder_set": "Pasta ROMs definida: {0}",
"roms_folder_set_restart": "Pasta ROMs definida: {0}\nReinício necessário para aplicar!",
"roms_folder_reset": "Pasta ROMs redefinida para padrão\nReinício necessário para aplicar!",
"folder_browser_title_roms_root": "Selecionar pasta ROMs padrão",
"settings_web_service": "Serviço Web na Inicialização",
"settings_web_service_enabled": "Ativado",
"settings_web_service_disabled": "Desativado",
@@ -267,6 +278,8 @@
"history_option_scraper": "Obter metadados",
"history_option_remove_from_queue": "Remover da fila",
"history_option_cancel_download": "Cancelar download",
"history_option_pause_download": "Pausar download",
"history_option_resume_download": "Retomar download",
"history_option_delete_game": "Excluir jogo",
"history_option_error_info": "Detalhes do erro",
"history_option_retry": "Tentar novamente",
@@ -329,6 +342,9 @@
"web_settings_source_mode": "Fonte de jogos",
"web_settings_custom_url": "URL personalizada",
"web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip",
"web_settings_auto_extract": "Extrair arquivos automaticamente após o download",
"web_settings_web_service": "Iniciar serviço web na inicialização",
"web_settings_custom_dns": "Ativar DNS personalizado na inicialização",
"web_settings_save": "Salvar configurações",
"web_settings_saved": "Configurações salvas com sucesso!",
"web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações.",
@@ -352,6 +368,7 @@
"web_history_status_completed": "Concluído",
"web_history_status_error": "Erro",
"web_settings_os": "Sistema operacional",
"web_system_info_title": "Informações do sistema",
"web_settings_platforms_count": "Número de plataformas",
"web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)",
"web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)",
@@ -429,5 +446,25 @@
"filter_reset_filters": "Redefinir",
"filter_back": "Voltar",
"filter_active": "Filtro ativo",
"filter_games_shown": "{0} jogo(s) exibido(s)"
"filter_games_shown": "{0} jogo(s) exibido(s)",
"platform_folder_config_current": "Configurar pasta de download para {0}\nAtual: {1}",
"platform_folder_config_default": "Configurar pasta de download para {0}\nUsando localização padrão",
"platform_folder_show_current": "Mostrar caminho atual",
"platform_folder_browse": "Navegar",
"platform_folder_reset": "Redefinir para padrão",
"platform_folder_set": "Pasta definida para {0}: {1}",
"platform_folder_default_path": "Padrão: {0}",
"folder_browser_title": "Selecionar pasta para {0}",
"folder_browser_parent": "Pasta superior",
"folder_browser_enter": "Entrar",
"folder_browser_select": "Selecionar",
"folder_new_folder": "Nova pasta",
"folder_new_title": "Criar nova pasta",
"folder_new_confirm": "Criar",
"folder_created": "Pasta criada: {0}",
"folder_create_error": "Erro ao criar: {0}",
"controls_action_select_char": "Adicionar",
"folder_browser_browse": "Explorar"
}
}
}

View File

@@ -694,6 +694,8 @@ def extract_update(zip_path, dest_dir, source_url):
progress_queues = {}
# Cancellation and thread tracking per download task
cancel_events = {}
# Pause events for downloads
pause_events = {} # {task_id: threading.Event} - Event is set when paused
download_threads = {}
# URLs actuellement en cours de téléchargement (pour éviter les doublons)
urls_in_progress = set()
@@ -717,6 +719,32 @@ def request_cancel(task_id: str) -> bool:
logger.debug(f"No cancel event found for task_id={task_id}")
return False
def toggle_pause_download(task_id: str) -> bool:
"""Toggle pause state for a running download task. Returns True if now paused, False if resumed."""
ev = pause_events.get(task_id)
if ev is None:
# Créer l'événement de pause s'il n'existe pas
pause_events[task_id] = threading.Event()
ev = pause_events[task_id]
if ev.is_set():
# Actuellement en pause, reprendre
ev.clear()
logger.debug(f"Download resumed for task_id={task_id}")
return False # Retourne False = pas en pause (repris)
else:
# Actuellement actif, mettre en pause
ev.set()
logger.debug(f"Download paused for task_id={task_id}")
return True # Retourne True = en pause
def is_download_paused(task_id: str) -> bool:
"""Check if a download is currently paused."""
ev = pause_events.get(task_id)
if ev is not None:
return ev.is_set()
return False
def cancel_all_downloads():
"""Cancel all active downloads and queued downloads, and attempt to stop threads quickly."""
# Annuler tous les téléchargements actifs via cancel_events
@@ -836,19 +864,27 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
cancel_ev = cancel_events.get(task_id)
# Use symlink path if enabled
from rgsx_settings import apply_symlink_path
from rgsx_settings import apply_symlink_path, get_platform_custom_path
dest_dir = None
for platform_dict in config.platform_dicts:
if platform_dict.get("platform_name") == platform:
# Priorité: clé 'folder'; fallback legacy: 'dossier'; sinon normalisation du nom de plateforme
platform_folder = platform_dict.get("folder") or platform_dict.get("dossier") or normalize_platform_name(platform)
# Vérifier si un dossier personnalisé est configuré pour cette plateforme
custom_path = get_platform_custom_path(platform)
if custom_path and os.path.isdir(custom_path):
dest_dir = custom_path
platform_folder = os.path.basename(dest_dir)
logger.debug(f"Utilisation du dossier personnalisé pour {platform}: {dest_dir}")
else:
dest_dir = None
platform_folder = None
for platform_dict in config.platform_dicts:
if platform_dict.get("platform_name") == platform:
# Priorité: clé 'folder'; fallback legacy: 'dossier'; sinon normalisation du nom de plateforme
platform_folder = platform_dict.get("folder") or platform_dict.get("dossier") or normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
logger.debug(f"Répertoire de destination trouvé pour {platform}: {dest_dir}")
break
if not dest_dir:
platform_folder = normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
logger.debug(f"Répertoire de destination trouvé pour {platform}: {dest_dir}")
break
if not dest_dir:
platform_folder = normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
# Spécifique: si le système est "BIOS" on force le dossier BIOS
if platform_folder == "bios" or platform == "BIOS" or platform == "- BIOS by TMCTV -":
@@ -1202,6 +1238,15 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
download_canceled = False
with open(dest_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunk_size):
# Vérifier la pause (dynamiquement car l'événement peut être créé après le début)
while True:
pause_ev = pause_events.get(task_id)
if pause_ev is None or not pause_ev.is_set():
break # Pas en pause, continuer le téléchargement
if cancel_ev is not None and cancel_ev.is_set():
break # Sortir de la boucle de pause si annulation demandée
time.sleep(0.1) # Attendre en pause
if cancel_ev is not None and cancel_ev.is_set():
logger.debug(f"Annulation détectée, arrêt du téléchargement pour task_id={task_id}")
result[0] = False
@@ -1252,9 +1297,13 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
os.chmod(dest_path, 0o644)
logger.debug(f"Téléchargement terminé: {dest_path}")
# Vérifier si l'extraction automatique est activée dans les paramètres
from rgsx_settings import get_auto_extract
auto_extract_enabled = get_auto_extract()
# Forcer extraction si plateforme BIOS même si le pré-check ne l'avait pas marqué
force_extract = is_zip_non_supported
if not force_extract:
force_extract = is_zip_non_supported and auto_extract_enabled
if not force_extract and auto_extract_enabled:
try:
bios_like = {"BIOS", "- BIOS by TMCTV -", "- BIOS"}
if platform_folder == "bios" or platform in bios_like:
@@ -1608,18 +1657,26 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
save_history(config.history)
# Use symlink path if enabled
from rgsx_settings import apply_symlink_path
from rgsx_settings import apply_symlink_path, get_platform_custom_path
dest_dir = None
for platform_dict in config.platform_dicts:
if platform_dict.get("platform_name") == platform:
platform_folder = platform_dict.get("folder") or platform_dict.get("dossier") or normalize_platform_name(platform)
# Vérifier si un dossier personnalisé est configuré pour cette plateforme
custom_path = get_platform_custom_path(platform)
if custom_path and os.path.isdir(custom_path):
dest_dir = custom_path
logger.debug(f"Utilisation du dossier personnalisé pour {platform}: {dest_dir}")
platform_folder = os.path.basename(dest_dir)
else:
dest_dir = None
platform_folder = None
for platform_dict in config.platform_dicts:
if platform_dict.get("platform_name") == platform:
platform_folder = platform_dict.get("folder") or platform_dict.get("dossier") or normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
break
if not dest_dir:
logger.warning(f"Aucun dossier 'folder'/'dossier' trouvé pour la plateforme {platform}")
platform_folder = normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
break
if not dest_dir:
logger.warning(f"Aucun dossier 'folder'/'dossier' trouvé pour la plateforme {platform}")
platform_folder = normalize_platform_name(platform)
dest_dir = apply_symlink_path(config.ROMS_FOLDER, platform_folder)
logger.debug(f"Répertoire destination déterminé: {dest_dir}")
# Spécifique: si le système est "- BIOS by TMCTV -" on force le dossier BIOS
@@ -2236,6 +2293,15 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
logger.debug(f"Ouverture fichier: {dest_path}")
with open(dest_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunk_size):
# Vérifier la pause (dynamiquement car l'événement peut être créé après le début)
while True:
pause_ev = pause_events.get(task_id)
if pause_ev is None or not pause_ev.is_set():
break # Pas en pause, continuer le téléchargement
if cancel_ev is not None and cancel_ev.is_set():
break # Sortir de la boucle de pause si annulation demandée
time.sleep(0.1) # Attendre en pause
if cancel_ev is not None and cancel_ev.is_set():
logger.debug(f"Annulation détectée, arrêt du téléchargement 1fichier pour task_id={task_id}")
result[0] = False
@@ -2279,9 +2345,13 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
if download_canceled:
return
# Vérifier si l'extraction automatique est activée dans les paramètres
from rgsx_settings import get_auto_extract
auto_extract_enabled = get_auto_extract()
# Déterminer si extraction est nécessaire
force_extract = is_zip_non_supported
if not force_extract:
force_extract = is_zip_non_supported and auto_extract_enabled
if not force_extract and auto_extract_enabled:
try:
ps3_platforms = {"ps3", "PlayStation 3"}
if platform_folder == "ps3" or platform in ps3_platforms:

View File

@@ -77,7 +77,8 @@ def load_rgsx_settings():
"allow_unknown_extensions": False,
"roms_folder": "",
"web_service_at_boot": False,
"last_gamelist_update": None
"last_gamelist_update": None,
"platform_custom_paths": {} # Chemins personnalisés par plateforme
}
try:
@@ -479,3 +480,66 @@ def save_game_filters(filters_dict):
except Exception as e:
logger.error(f"Error saving game filters: {str(e)}")
return False
def get_platform_custom_path(platform_name):
"""Récupère le chemin personnalisé pour une plateforme."""
try:
settings = load_rgsx_settings()
custom_paths = settings.get("platform_custom_paths", {})
return custom_paths.get(platform_name, "")
except Exception as e:
logger.error(f"Error getting platform custom path: {str(e)}")
return ""
def set_platform_custom_path(platform_name, path):
"""Définit le chemin personnalisé pour une plateforme."""
try:
settings = load_rgsx_settings()
if "platform_custom_paths" not in settings:
settings["platform_custom_paths"] = {}
if path:
settings["platform_custom_paths"][platform_name] = path
else:
# Si le chemin est vide, supprimer l'entrée
settings["platform_custom_paths"].pop(platform_name, None)
save_rgsx_settings(settings)
logger.info(f"Platform custom path set: {platform_name} -> {path}")
return True
except Exception as e:
logger.error(f"Error setting platform custom path: {str(e)}")
return False
def get_all_platform_custom_paths():
"""Récupère tous les chemins personnalisés des plateformes."""
try:
settings = load_rgsx_settings()
return settings.get("platform_custom_paths", {})
except Exception as e:
logger.error(f"Error getting all platform custom paths: {str(e)}")
return {}
def get_auto_extract():
"""Récupère le paramètre d'extraction automatique des archives après téléchargement."""
try:
settings = load_rgsx_settings()
return settings.get("auto_extract", True) # Activé par défaut
except Exception as e:
logger.error(f"Error getting auto_extract setting: {str(e)}")
return True
def set_auto_extract(enabled: bool):
"""Définit le paramètre d'extraction automatique des archives après téléchargement."""
try:
settings = load_rgsx_settings()
settings["auto_extract"] = enabled
save_rgsx_settings(settings)
logger.info(f"Auto extract set to: {enabled}")
return True
except Exception as e:
logger.error(f"Error setting auto_extract: {str(e)}")
return False

View File

@@ -865,9 +865,26 @@ class RGSXHandler(BaseHTTPRequestHandler):
# Route: API - Settings (lecture)
elif path == '/api/settings':
try:
from rgsx_settings import load_rgsx_settings
from rgsx_settings import load_rgsx_settings, get_auto_extract
from utils import check_web_service_status, check_custom_dns_status, load_api_keys
settings = load_rgsx_settings()
# Ajouter les options dynamiques
settings['auto_extract'] = get_auto_extract()
# Options Linux/Batocera
if config.OPERATING_SYSTEM == "Linux":
settings['web_service_at_boot'] = check_web_service_status()
settings['custom_dns_at_boot'] = check_custom_dns_status()
# API Keys (filtrer la clé 'reloaded' qui n'est pas utile pour l'UI)
api_keys_data = load_api_keys()
settings['api_keys'] = {
'1fichier': api_keys_data.get('1fichier', ''),
'alldebrid': api_keys_data.get('alldebrid', ''),
'realdebrid': api_keys_data.get('realdebrid', '')
}
self._send_json({
'success': True,
'settings': settings,
@@ -1470,7 +1487,8 @@ class RGSXHandler(BaseHTTPRequestHandler):
# Route: Sauvegarder les settings
elif path == '/api/settings':
try:
from rgsx_settings import save_rgsx_settings
from rgsx_settings import save_rgsx_settings, set_auto_extract
from utils import toggle_web_service_at_boot, toggle_custom_dns_at_boot, save_api_keys
settings = data.get('settings')
if not settings:
@@ -1480,6 +1498,37 @@ class RGSXHandler(BaseHTTPRequestHandler):
}, status=400)
return
# Gérer auto_extract séparément
if 'auto_extract' in settings:
set_auto_extract(settings['auto_extract'])
del settings['auto_extract'] # Ne pas sauvegarder dans le fichier principal
# Gérer web_service_at_boot (Linux only)
if 'web_service_at_boot' in settings:
if config.OPERATING_SYSTEM == "Linux":
try:
toggle_web_service_at_boot(settings['web_service_at_boot'])
except Exception as e:
logger.error(f"Erreur toggle web service: {e}")
del settings['web_service_at_boot']
# Gérer custom_dns_at_boot (Linux only)
if 'custom_dns_at_boot' in settings:
if config.OPERATING_SYSTEM == "Linux":
try:
toggle_custom_dns_at_boot(settings['custom_dns_at_boot'])
except Exception as e:
logger.error(f"Erreur toggle custom DNS: {e}")
del settings['custom_dns_at_boot']
# Gérer API keys séparément
if 'api_keys' in settings:
try:
save_api_keys(settings['api_keys'])
except Exception as e:
logger.error(f"Erreur sauvegarde API keys: {e}")
del settings['api_keys']
save_rgsx_settings(settings)
self._send_json({

View File

@@ -540,3 +540,24 @@ header p { opacity: 0.9; font-size: 1.1em; }
from { opacity: 1; }
to { opacity: 0; }
}
/* System Info Collapse/Details */
details summary {
list-style: none;
}
details summary::-webkit-details-marker {
display: none;
}
details summary .collapse-arrow {
transition: transform 0.3s ease;
display: inline-block;
}
details[open] summary .collapse-arrow {
transform: rotate(90deg);
}
details[open] summary {
border-radius: 8px 8px 0 0;
}
details summary:hover {
opacity: 0.9;
}

View File

@@ -1866,101 +1866,107 @@
const showUnsupportedLabel = t('web_settings_show_unsupported');
const allowUnknownLabel = t('web_settings_allow_unknown');
// Construire la section d'informations système détaillées
// Construire la section d'informations système détaillées (dans un collapse fermé par défaut)
let systemInfoHTML = '';
if (systemInfo && (systemInfo.model || systemInfo.cpu_model)) {
systemInfoHTML = `
<h3 style="margin-top: 20px; margin-bottom: 15px;">🖥️ System Information</h3>
<div class="info-grid" style="margin-bottom: 20px; background: #f0f8ff; padding: 15px; border-radius: 8px; border: 2px solid #007bff;">
${systemInfo.model ? `
<details style="margin-top: 20px; margin-bottom: 20px;">
<summary style="cursor: pointer; padding: 12px 15px; background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); color: white; border-radius: 8px; font-weight: bold; font-size: 1.1em; list-style: none; display: flex; align-items: center; gap: 10px;">
<span class="collapse-arrow">▶</span>
🖥️ ${t('web_system_info_title') || 'System Information'}
<span style="margin-left: auto; font-size: 0.85em; opacity: 0.9;">${systemInfo.model || systemInfo.system || ''}</span>
</summary>
<div class="info-grid" style="margin-top: 10px; background: #f0f8ff; padding: 15px; border-radius: 0 0 8px 8px; border: 2px solid #007bff; border-top: none;">
${systemInfo.model ? `
<div class="info-item">
<strong>💻 Model</strong>
${systemInfo.model}
</div>
` : ''}
${systemInfo.system ? `
<div class="info-item">
<strong>🐧 System</strong>
${systemInfo.system}
</div>
` : ''}
${systemInfo.architecture ? `
<div class="info-item">
<strong>⚙️ Architecture</strong>
${systemInfo.architecture}
</div>
` : ''}
${systemInfo.cpu_model ? `
<div class="info-item">
<strong>🔧 CPU Model</strong>
${systemInfo.cpu_model}
</div>
` : ''}
${systemInfo.cpu_cores ? `
<div class="info-item">
<strong>🧮 CPU Cores</strong>
${systemInfo.cpu_cores}
</div>
` : ''}
${systemInfo.cpu_max_frequency ? `
<div class="info-item">
<strong>⚡ CPU Frequency</strong>
${systemInfo.cpu_max_frequency}
</div>
` : ''}
${systemInfo.cpu_features ? `
<div class="info-item">
<strong>✨ CPU Features</strong>
${systemInfo.cpu_features}
</div>
` : ''}
${systemInfo.temperature ? `
<div class="info-item">
<strong>🌡️ Temperature</strong>
${systemInfo.temperature}
</div>
` : ''}
${systemInfo.available_memory && systemInfo.total_memory ? `
<div class="info-item">
<strong>💾 Memory</strong>
${systemInfo.available_memory} / ${systemInfo.total_memory}
</div>
` : ''}
${systemInfo.display_resolution ? `
<div class="info-item">
<strong>🖥️ Display Resolution</strong>
${systemInfo.display_resolution}
</div>
` : ''}
${systemInfo.display_refresh_rate ? `
<div class="info-item">
<strong>🔄 Refresh Rate</strong>
${systemInfo.display_refresh_rate}
</div>
` : ''}
${systemInfo.data_partition_format ? `
<div class="info-item">
<strong>💽 Partition Format</strong>
${systemInfo.data_partition_format}
</div>
` : ''}
${systemInfo.data_partition_space ? `
<div class="info-item">
<strong>💿 Available Space</strong>
${systemInfo.data_partition_space}
</div>
` : ''}
${systemInfo.network_ip ? `
<div class="info-item">
<strong>🌐 Network IP</strong>
${systemInfo.network_ip}
</div>
` : ''}
<div class="info-item">
<strong>💻 Model</strong>
${systemInfo.model}
<strong>🎮 ${platformsCountLabel}</strong>
${info.platforms_count}
</div>
` : ''}
${systemInfo.system ? `
<div class="info-item">
<strong>🐧 System</strong>
${systemInfo.system}
</div>
` : ''}
${systemInfo.architecture ? `
<div class="info-item">
<strong>⚙️ Architecture</strong>
${systemInfo.architecture}
</div>
` : ''}
${systemInfo.cpu_model ? `
<div class="info-item">
<strong>🔧 CPU Model</strong>
${systemInfo.cpu_model}
</div>
` : ''}
${systemInfo.cpu_cores ? `
<div class="info-item">
<strong>🧮 CPU Cores</strong>
${systemInfo.cpu_cores}
</div>
` : ''}
${systemInfo.cpu_max_frequency ? `
<div class="info-item">
<strong>⚡ CPU Frequency</strong>
${systemInfo.cpu_max_frequency}
</div>
` : ''}
${systemInfo.cpu_features ? `
<div class="info-item">
<strong>✨ CPU Features</strong>
${systemInfo.cpu_features}
</div>
` : ''}
${systemInfo.temperature ? `
<div class="info-item">
<strong>🌡️ Temperature</strong>
${systemInfo.temperature}
</div>
` : ''}
${systemInfo.available_memory && systemInfo.total_memory ? `
<div class="info-item">
<strong>💾 Memory</strong>
${systemInfo.available_memory} / ${systemInfo.total_memory}
</div>
` : ''}
${systemInfo.display_resolution ? `
<div class="info-item">
<strong>🖥️ Display Resolution</strong>
${systemInfo.display_resolution}
</div>
` : ''}
${systemInfo.display_refresh_rate ? `
<div class="info-item">
<strong>🔄 Refresh Rate</strong>
${systemInfo.display_refresh_rate}
</div>
` : ''}
${systemInfo.data_partition_format ? `
<div class="info-item">
<strong>💽 Partition Format</strong>
${systemInfo.data_partition_format}
</div>
` : ''}
${systemInfo.data_partition_space ? `
<div class="info-item">
<strong>💿 Available Space</strong>
${systemInfo.data_partition_space}
</div>
` : ''}
${systemInfo.network_ip ? `
<div class="info-item">
<strong>🌐 Network IP</strong>
${systemInfo.network_ip}
</div>
` : ''}
<div class="info-item">
<strong>🎮 ${platformsCountLabel}</strong>
${info.platforms_count}
</div>
</div>
</details>
`;
}
@@ -2057,6 +2063,13 @@
placeholder="${t('web_settings_custom_url_placeholder')}">
</div>
<div style="margin-bottom: 20px;">
<label class="checkbox-label">
<input type="checkbox" id="setting-auto-extract" ${settings.auto_extract !== false ? 'checked' : ''}>
<span>📦 ${t('web_settings_auto_extract')}</span>
</label>
</div>
<div style="margin-bottom: 20px;">
<label class="checkbox-label">
<input type="checkbox" id="setting-show-unsupported" ${settings.show_unsupported_platforms ? 'checked' : ''}>
@@ -2071,6 +2084,44 @@
</label>
</div>
${info.system === 'Linux' ? `
<h4 style="margin-top: 25px; margin-bottom: 15px; border-top: 1px solid #ddd; padding-top: 15px;">🐧 Linux/Batocera Options</h4>
<div style="margin-bottom: 20px;">
<label class="checkbox-label">
<input type="checkbox" id="setting-web-service" ${settings.web_service_at_boot ? 'checked' : ''}>
<span>🌐 ${t('web_settings_web_service')}</span>
</label>
</div>
<div style="margin-bottom: 20px;">
<label class="checkbox-label">
<input type="checkbox" id="setting-custom-dns" ${settings.custom_dns_at_boot ? 'checked' : ''}>
<span>🔒 ${t('web_settings_custom_dns')}</span>
</label>
</div>
` : ''}
<h4 style="margin-top: 25px; margin-bottom: 15px; border-top: 1px solid #ddd; padding-top: 15px;">🔑 API Keys</h4>
<div style="margin-bottom: 15px;">
<label>1fichier API Key</label>
<input type="password" id="setting-api-1fichier" value="${settings.api_keys?.['1fichier'] || ''}"
placeholder="Enter 1fichier API key">
</div>
<div style="margin-bottom: 15px;">
<label>AllDebrid API Key</label>
<input type="password" id="setting-api-alldebrid" value="${settings.api_keys?.alldebrid || ''}"
placeholder="Enter AllDebrid API key">
</div>
<div style="margin-bottom: 20px;">
<label>RealDebrid API Key</label>
<input type="password" id="setting-api-realdebrid" value="${settings.api_keys?.realdebrid || ''}"
placeholder="Enter RealDebrid API key">
</div>
<button id="save-settings-btn" style="width: 100%; background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; padding: 15px; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; margin-top: 10px;">
💾 ${t('web_settings_save')}
</button>
@@ -2127,7 +2178,17 @@
},
show_unsupported_platforms: document.getElementById('setting-show-unsupported').checked,
allow_unknown_extensions: document.getElementById('setting-allow-unknown').checked,
auto_extract: document.getElementById('setting-auto-extract').checked,
roms_folder: document.getElementById('setting-roms-folder').value.trim(),
// Linux/Batocera options (only if elements exist)
web_service_at_boot: document.getElementById('setting-web-service')?.checked || false,
custom_dns_at_boot: document.getElementById('setting-custom-dns')?.checked || false,
// API Keys
api_keys: {
'1fichier': document.getElementById('setting-api-1fichier')?.value.trim() || '',
'alldebrid': document.getElementById('setting-api-alldebrid')?.value.trim() || '',
'realdebrid': document.getElementById('setting-api-realdebrid')?.value.trim() || ''
},
game_filters: {
region_filters: regionFiltersObj,
hide_non_release: document.getElementById('hide-non-release')?.checked || savedHideNonRelease,

View File

@@ -2414,6 +2414,66 @@ def load_api_keys(force: bool = False):
'reloaded': False
}
def save_api_keys(api_keys: dict):
"""Sauvegarde les clés API (1fichier, AllDebrid, RealDebrid) dans leurs fichiers respectifs.
Args:
api_keys: dict avec les clés '1fichier', 'alldebrid', 'realdebrid'
Retourne: True si au moins une clé a été sauvegardée avec succès
"""
if not api_keys:
return False
paths = {
'1fichier': getattr(config, 'API_KEY_1FICHIER_PATH', ''),
'alldebrid': getattr(config, 'API_KEY_ALLDEBRID_PATH', ''),
'realdebrid': getattr(config, 'API_KEY_REALDEBRID_PATH', ''),
}
saved_any = False
for key_name, path in paths.items():
if not path:
continue
# Récupérer la valeur (utiliser la clé telle quelle ou en minuscule)
value = api_keys.get(key_name, api_keys.get(key_name.lower(), None))
if value is None:
continue # Ne pas modifier si la clé n'est pas fournie
try:
# Créer le dossier si nécessaire
os.makedirs(os.path.dirname(path), exist_ok=True)
# Écrire la clé (valeur nettoyée)
with open(path, 'w', encoding='utf-8') as f:
f.write(value.strip())
# Mettre à jour le cache config
if key_name == '1fichier':
config.API_KEY_1FICHIER = value.strip()
elif key_name == 'alldebrid':
config.API_KEY_ALLDEBRID = value.strip()
elif key_name == 'realdebrid':
config.API_KEY_REALDEBRID = value.strip()
# Invalider le cache mtime
cache_attr = '_api_keys_cache'
if hasattr(config, cache_attr):
cache_data = getattr(config, cache_attr)
cache_data[f"{key_name}_mtime"] = None
saved_any = True
logger.info(f"Clé API {key_name} sauvegardée avec succès")
except Exception as e:
logger.error(f"Erreur sauvegarde clé {key_name}: {e}")
return saved_any
# Wrappers rétro-compatibilité (dépréciés)
def load_api_key_1fichier(force: bool = False): # pragma: no cover
return load_api_keys(force).get('1fichier', '')

View File

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