mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
059d3988ac |
@@ -709,6 +709,7 @@ async def main():
|
||||
"pause_games_menu",
|
||||
"pause_settings_menu",
|
||||
"pause_api_keys_status",
|
||||
"pause_connection_status",
|
||||
"filter_platforms",
|
||||
"display_menu",
|
||||
"language_select",
|
||||
@@ -1149,6 +1150,9 @@ async def main():
|
||||
elif config.menu_state == "pause_api_keys_status":
|
||||
from display import draw_pause_api_keys_status
|
||||
draw_pause_api_keys_status(screen)
|
||||
elif config.menu_state == "pause_connection_status":
|
||||
from display import draw_pause_connection_status
|
||||
draw_pause_connection_status(screen)
|
||||
elif config.menu_state == "filter_platforms":
|
||||
from display import draw_filter_platforms_menu
|
||||
draw_filter_platforms_menu(screen)
|
||||
|
||||
69
ports/RGSX/assets/progs/versionclean
Normal file
69
ports/RGSX/assets/progs/versionclean
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
# BATOCERA SERVICE
|
||||
# name: Version Clean Service
|
||||
# description: Clean batocera-version output (hide extra services)
|
||||
# author: batocera-unofficial-addons
|
||||
# depends:
|
||||
# version: 1.0
|
||||
|
||||
SERVICE_NAME="versionclean"
|
||||
TARGET="/usr/bin/batocera-version"
|
||||
BACKUP="${TARGET}.bak"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
# If we've already backed up, assume it's already "cleaned"
|
||||
if [ -f "$BACKUP" ]; then
|
||||
echo "${SERVICE_NAME}: already started (backup exists at ${BACKUP})."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "${SERVICE_NAME}: backing up original ${TARGET} to ${BACKUP}..."
|
||||
cp "$TARGET" "$BACKUP"
|
||||
|
||||
echo "${SERVICE_NAME}: writing clean version script to ${TARGET}..."
|
||||
cat << 'EOF' > "$TARGET"
|
||||
#!/bin/bash
|
||||
|
||||
# Clean batocera-version
|
||||
# - "batocera-version --extra" -> "none"
|
||||
# - "batocera-version" -> contents of /usr/share/batocera/batocera.version
|
||||
|
||||
if test "$1" = "--extra"; then
|
||||
echo "none"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat /usr/share/batocera/batocera.version
|
||||
EOF
|
||||
|
||||
chmod +x "$TARGET"
|
||||
echo "${SERVICE_NAME}: clean version applied."
|
||||
;;
|
||||
|
||||
stop)
|
||||
if [ -f "$BACKUP" ]; then
|
||||
echo "${SERVICE_NAME}: restoring original ${TARGET} from ${BACKUP}..."
|
||||
cp "$BACKUP" "$TARGET"
|
||||
rm "$BACKUP"
|
||||
echo "${SERVICE_NAME}: restore complete."
|
||||
else
|
||||
echo "${SERVICE_NAME}: no backup found, nothing to restore."
|
||||
fi
|
||||
;;
|
||||
|
||||
status)
|
||||
if [ -f "$BACKUP" ]; then
|
||||
echo "${SERVICE_NAME}: CLEAN VERSION ACTIVE (backup present)."
|
||||
else
|
||||
echo "${SERVICE_NAME}: ORIGINAL VERSION ACTIVE."
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -14,7 +14,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.5.0.1"
|
||||
app_version = "2.5.0.2"
|
||||
|
||||
# Nombre de jours avant de proposer la mise à jour de la liste des jeux
|
||||
GAMELIST_UPDATE_DAYS = 7
|
||||
@@ -141,6 +141,12 @@ pending_download_is_queue = False # Indique si pending_download doit être ajou
|
||||
# Indique si un téléchargement est en cours
|
||||
download_active = False
|
||||
|
||||
# Cache status de connexion (menu pause > settings)
|
||||
connection_status = {}
|
||||
connection_status_timestamp = 0.0
|
||||
connection_status_in_progress = False
|
||||
connection_status_progress = {"done": 0, "total": 0}
|
||||
|
||||
# Log directory
|
||||
# Docker mode: /config/logs (persisted in config volume)
|
||||
# Traditional mode: /app/RGSX/logs (current behavior)
|
||||
@@ -347,6 +353,7 @@ platforms = [] # Liste des plateformes disponibles
|
||||
current_platform = 0 # Index de la plateforme actuelle sélectionnée
|
||||
platform_names = {} # {platform_id: platform_name}
|
||||
games_count = {} # Dictionnaire comptant le nombre de jeux par plateforme
|
||||
games_count_log_verbose = False # Log détaillé par fichier (sinon résumé compact)
|
||||
platform_dicts = [] # Liste des dictionnaires de plateformes
|
||||
|
||||
# Filtre plateformes
|
||||
|
||||
@@ -17,7 +17,8 @@ from utils import (
|
||||
save_music_config, load_api_keys, _get_dest_folder_name,
|
||||
extract_zip, extract_rar, find_file_with_or_without_extension, toggle_web_service_at_boot, check_web_service_status,
|
||||
restart_application, generate_support_zip, load_sources,
|
||||
ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string,
|
||||
start_connection_status_check
|
||||
)
|
||||
from history import load_history, clear_history, add_to_history, save_history
|
||||
from language import _, get_available_languages, set_language
|
||||
@@ -54,6 +55,7 @@ VALID_STATES = [
|
||||
"pause_games_menu", # sous-menu Games (source mode, update/redownload cache)
|
||||
"pause_settings_menu", # sous-menu Settings (music on/off, symlink toggle, api keys status)
|
||||
"pause_api_keys_status", # sous-menu API Keys (affichage statut des clés)
|
||||
"pause_connection_status", # sous-menu Connection status (statut accès sites)
|
||||
# Nouveaux menus historique
|
||||
"history_game_options", # menu options pour un jeu de l'historique
|
||||
"history_show_folder", # afficher le dossier de téléchargement
|
||||
@@ -74,6 +76,8 @@ VALID_STATES = [
|
||||
]
|
||||
|
||||
def validate_menu_state(state):
|
||||
if not state:
|
||||
return "platform"
|
||||
if state not in VALID_STATES:
|
||||
logger.debug(f"État invalide {state}, retour à platform")
|
||||
return "platform"
|
||||
@@ -2074,21 +2078,23 @@ 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, auto_extract, roms_folder, [web_service], [custom_dns], api keys, back
|
||||
total = 6 # music, symlink, auto_extract, roms_folder, api keys, back (Windows)
|
||||
# Liste des options : music, symlink, auto_extract, roms_folder, [web_service], [custom_dns], api keys, connection_status, back
|
||||
total = 7 # music, symlink, auto_extract, roms_folder, api keys, connection_status, back (Windows)
|
||||
auto_extract_index = 2
|
||||
roms_folder_index = 3
|
||||
web_service_index = -1
|
||||
custom_dns_index = -1
|
||||
api_keys_index = 4
|
||||
back_index = 5
|
||||
connection_status_index = 5
|
||||
back_index = 6
|
||||
|
||||
if config.OPERATING_SYSTEM == "Linux":
|
||||
total = 8 # music, symlink, auto_extract, roms_folder, web_service, custom_dns, api keys, back
|
||||
total = 9 # music, symlink, auto_extract, roms_folder, web_service, custom_dns, api keys, connection_status, back
|
||||
web_service_index = 4
|
||||
custom_dns_index = 5
|
||||
api_keys_index = 6
|
||||
back_index = 7
|
||||
connection_status_index = 7
|
||||
back_index = 8
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_settings_selection = (sel - 1) % total
|
||||
@@ -2208,6 +2214,11 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif sel == api_keys_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_api_keys_status"
|
||||
config.needs_redraw = True
|
||||
# Option Connection Status
|
||||
elif sel == connection_status_index and is_input_matched(event, "confirm"):
|
||||
start_connection_status_check(force=True)
|
||||
config.menu_state = "pause_connection_status"
|
||||
config.needs_redraw = True
|
||||
# Option Back (dernière option)
|
||||
elif sel == back_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_menu"
|
||||
@@ -2224,6 +2235,12 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
|
||||
elif config.menu_state == "pause_connection_status":
|
||||
if is_input_matched(event, "cancel") or is_input_matched(event, "confirm") or is_input_matched(event, "start"):
|
||||
config.menu_state = "pause_settings_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
|
||||
# Aide contrôles
|
||||
elif config.menu_state == "controls_help":
|
||||
if is_input_matched(event, "cancel"):
|
||||
|
||||
@@ -5,10 +5,12 @@ import os
|
||||
import io
|
||||
import platform
|
||||
import random
|
||||
from datetime import datetime
|
||||
import config
|
||||
from utils import (truncate_text_middle, wrap_text, load_system_image, truncate_text_end,
|
||||
check_web_service_status, check_custom_dns_status, load_api_keys,
|
||||
_get_dest_folder_name, find_file_with_or_without_extension)
|
||||
_get_dest_folder_name, find_file_with_or_without_extension,
|
||||
get_connection_status_targets, get_connection_status_snapshot)
|
||||
import logging
|
||||
import math
|
||||
from history import load_history, is_game_downloaded
|
||||
@@ -1965,6 +1967,9 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
|
||||
("clear_history", _("settings_roms_folder_default")),
|
||||
("cancel", _("controls_cancel_back")),
|
||||
],
|
||||
"pause_connection_status": [
|
||||
("cancel", _("controls_cancel_back")),
|
||||
],
|
||||
}
|
||||
|
||||
# Cas spécial : pause_settings_menu avec option roms_folder sélectionnée
|
||||
@@ -2948,6 +2953,7 @@ def draw_pause_settings_menu(screen, selected_index):
|
||||
custom_dns_txt = f"{_('settings_custom_dns')} : < {custom_dns_status} >"
|
||||
|
||||
api_keys_txt = _("menu_api_keys_status") if _ else "API Keys"
|
||||
connection_status_txt = _("menu_connection_status") if _ else "Connection status"
|
||||
back_txt = _("menu_back") if _ else "Back"
|
||||
|
||||
# Construction de la liste des options
|
||||
@@ -2956,7 +2962,7 @@ def draw_pause_settings_menu(screen, selected_index):
|
||||
options.append(web_service_txt)
|
||||
if custom_dns_txt: # Ajouter seulement si Linux/Batocera
|
||||
options.append(custom_dns_txt)
|
||||
options.extend([api_keys_txt, back_txt])
|
||||
options.extend([api_keys_txt, connection_status_txt, back_txt])
|
||||
|
||||
# Index de l'option Dossier ROMs
|
||||
roms_folder_index = 3
|
||||
@@ -2974,6 +2980,7 @@ def draw_pause_settings_menu(screen, selected_index):
|
||||
instruction_keys.append("instruction_settings_custom_dns")
|
||||
instruction_keys.extend([
|
||||
"instruction_settings_api_keys",
|
||||
"instruction_settings_connection_status",
|
||||
"instruction_generic_back",
|
||||
])
|
||||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||||
@@ -3084,6 +3091,147 @@ def draw_pause_api_keys_status(screen):
|
||||
screen.blit(hint_surf, hint_rect)
|
||||
|
||||
|
||||
def draw_pause_connection_status(screen):
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
status_map, last_ts, in_progress, progress = get_connection_status_snapshot()
|
||||
targets = get_connection_status_targets()
|
||||
|
||||
title = _("connection_status_title") if _ else "Connection status"
|
||||
cat_updates = _("connection_status_category_updates") if _ else "Updates"
|
||||
cat_sources = _("connection_status_category_sources") if _ else "Sources"
|
||||
|
||||
# Group rows by category
|
||||
categories_order = ["updates", "sources"]
|
||||
category_labels = {
|
||||
"updates": cat_updates,
|
||||
"sources": cat_sources,
|
||||
}
|
||||
rows = [] # list of (type, data)
|
||||
for cat in categories_order:
|
||||
cat_items = [t for t in targets if t.get("category") == cat]
|
||||
if not cat_items:
|
||||
continue
|
||||
rows.append(("header", category_labels.get(cat, cat)))
|
||||
for item in cat_items:
|
||||
rows.append(("item", item))
|
||||
|
||||
# Title surface (used for sizing)
|
||||
title_surface = config.font.render(title, True, THEME_COLORS["text"])
|
||||
|
||||
# Dimensions
|
||||
row_height = config.small_font.get_height() + 14
|
||||
header_row_height = config.small_font.get_height() + 10
|
||||
title_height = 60
|
||||
footer_height = 55
|
||||
content_height = 0
|
||||
for row_type, row_data in rows:
|
||||
content_height += header_row_height if row_type == "header" else row_height
|
||||
|
||||
# Measure max text width to size the menu
|
||||
max_text_width = title_surface.get_width()
|
||||
for row_type, row_data in rows:
|
||||
if row_type == "header":
|
||||
w = config.small_font.size(str(row_data))[0]
|
||||
else:
|
||||
label = row_data.get("label") or row_data.get("key", "")
|
||||
w = config.small_font.size(str(label))[0]
|
||||
if w > max_text_width:
|
||||
max_text_width = w
|
||||
|
||||
circle_area_width = 46 # status circle + gap
|
||||
inner_padding = 70
|
||||
menu_width = min(int(config.screen_width * 0.70), max(360, max_text_width + circle_area_width + inner_padding))
|
||||
menu_height = title_height + content_height + footer_height
|
||||
menu_x = (config.screen_width - menu_width) // 2
|
||||
menu_y = (config.screen_height - menu_height) // 2
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (menu_x, menu_y, menu_width, menu_height), border_radius=22)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (menu_x, menu_y, menu_width, menu_height), 2, border_radius=22)
|
||||
|
||||
# Title
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, menu_y + 34))
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Columns
|
||||
col_site_x = menu_x + 40
|
||||
col_status_x = menu_x + int(menu_width * 0.70)
|
||||
|
||||
y = menu_y + title_height - 5
|
||||
for row_type, data in rows:
|
||||
if row_type == "header":
|
||||
header_text = data
|
||||
header_surf = config.small_font.render(header_text, True, THEME_COLORS.get("text_dim", THEME_COLORS["text"]))
|
||||
screen.blit(header_surf, (col_site_x, y))
|
||||
# separator line
|
||||
sep_y = y + header_row_height - 6
|
||||
pygame.draw.line(screen, THEME_COLORS["border"], (menu_x + 25, sep_y), (menu_x + menu_width - 25, sep_y), 1)
|
||||
y += header_row_height
|
||||
continue
|
||||
|
||||
item = data
|
||||
key = item.get("key")
|
||||
label = item.get("label") or item.get("key", "")
|
||||
|
||||
status_val = status_map.get(key)
|
||||
if status_val is True:
|
||||
circle_color = (60, 170, 60)
|
||||
circle_bg = (30, 70, 30)
|
||||
elif status_val is False:
|
||||
circle_color = (180, 55, 55)
|
||||
circle_bg = (70, 25, 25)
|
||||
else:
|
||||
circle_color = (140, 140, 140)
|
||||
circle_bg = (60, 60, 60)
|
||||
|
||||
# Site label (indent to distinguish from category title)
|
||||
label_surf = config.small_font.render(label, True, THEME_COLORS["text"])
|
||||
screen.blit(label_surf, (col_site_x + 18, y))
|
||||
|
||||
# Status circle
|
||||
radius = 14
|
||||
center_x = col_status_x + radius
|
||||
center_y = y + config.small_font.get_height() // 2
|
||||
pygame.draw.circle(screen, circle_bg, (center_x, center_y), radius)
|
||||
pygame.draw.circle(screen, circle_color, (center_x, center_y), radius, 2)
|
||||
|
||||
# Separator
|
||||
sep_y = y + row_height - 8
|
||||
pygame.draw.line(screen, THEME_COLORS["border"], (menu_x + 25, sep_y), (menu_x + menu_width - 25, sep_y), 1)
|
||||
y += row_height
|
||||
|
||||
# Footer hint
|
||||
hint_font = config.tiny_font if hasattr(config, "tiny_font") else config.small_font
|
||||
if in_progress:
|
||||
done = int(progress.get("done", 0)) if isinstance(progress, dict) else 0
|
||||
total = int(progress.get("total", 0)) if isinstance(progress, dict) else 0
|
||||
if _ and _("connection_status_progress") != "connection_status_progress":
|
||||
try:
|
||||
hint_txt = _("connection_status_progress").format(done=done, total=total)
|
||||
except Exception:
|
||||
hint_txt = _("connection_status_checking") if _ else "Checking..."
|
||||
else:
|
||||
hint_txt = f"Checking... {done}/{total}" if total else ("Checking..." if not _ else _("connection_status_checking"))
|
||||
elif last_ts:
|
||||
try:
|
||||
time_str = datetime.fromtimestamp(last_ts).strftime("%H:%M:%S")
|
||||
except Exception:
|
||||
time_str = ""
|
||||
if _ and _("connection_status_last_check") != "connection_status_last_check":
|
||||
try:
|
||||
hint_txt = _("connection_status_last_check").format(time=time_str)
|
||||
except Exception:
|
||||
hint_txt = f"Last check: {time_str}" if time_str else ""
|
||||
else:
|
||||
hint_txt = f"Last check: {time_str}" if time_str else ""
|
||||
else:
|
||||
hint_txt = ""
|
||||
|
||||
if hint_txt:
|
||||
hint_surf = hint_font.render(hint_txt, True, THEME_COLORS.get("text_dim", THEME_COLORS["text"]))
|
||||
hint_rect = hint_surf.get_rect(center=(config.screen_width // 2, menu_y + menu_height - 26))
|
||||
screen.blit(hint_surf, hint_rect)
|
||||
|
||||
|
||||
def draw_filter_platforms_menu(screen):
|
||||
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
|
||||
@@ -189,7 +189,14 @@
|
||||
"status_present": "Vorhanden",
|
||||
"status_missing": "Fehlt",
|
||||
"menu_api_keys_status": "API-Schlüssel",
|
||||
"menu_connection_status": "Verbindungsstatus",
|
||||
"api_keys_status_title": "Status der API-Schlüssel",
|
||||
"connection_status_title": "Verbindungsstatus",
|
||||
"connection_status_category_updates": "App-/Gamelist-Update",
|
||||
"connection_status_category_sources": "Spielquellen",
|
||||
"connection_status_checking": "Prüfe...",
|
||||
"connection_status_progress": "Prüfe... {done}/{total}",
|
||||
"connection_status_last_check": "Letzte Prüfung: {time}",
|
||||
"menu_games": "Spiele",
|
||||
"api_keys_hint_manage": "Legen Sie Ihre Schlüssel in {path}",
|
||||
"api_key_empty_suffix": "leer",
|
||||
@@ -230,6 +237,7 @@
|
||||
"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_connection_status": "Zugriff auf Update- und Quellen-Seiten prüfen",
|
||||
"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",
|
||||
|
||||
@@ -188,7 +188,14 @@
|
||||
"status_present": "Present",
|
||||
"status_missing": "Missing",
|
||||
"menu_api_keys_status": "API Keys",
|
||||
"menu_connection_status": "Connection status",
|
||||
"api_keys_status_title": "API Keys Status",
|
||||
"connection_status_title": "Connection status",
|
||||
"connection_status_category_updates": "App/Gamelist update",
|
||||
"connection_status_category_sources": "Game sources",
|
||||
"connection_status_checking": "Checking...",
|
||||
"connection_status_progress": "Checking... {done}/{total}",
|
||||
"connection_status_last_check": "Last check: {time}",
|
||||
"menu_games": "Games",
|
||||
"api_keys_hint_manage": "Put your keys in {path}",
|
||||
"api_key_empty_suffix": "empty",
|
||||
@@ -232,6 +239,7 @@
|
||||
"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_connection_status": "Check access to update and source sites",
|
||||
"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",
|
||||
|
||||
@@ -189,7 +189,14 @@
|
||||
"status_present": "Presente",
|
||||
"status_missing": "Ausente",
|
||||
"menu_api_keys_status": "Claves API",
|
||||
"menu_connection_status": "Estado de conexión",
|
||||
"api_keys_status_title": "Estado de las claves API",
|
||||
"connection_status_title": "Estado de conexión",
|
||||
"connection_status_category_updates": "Actualización app/lista de juegos",
|
||||
"connection_status_category_sources": "Fuentes de juegos",
|
||||
"connection_status_checking": "Comprobando...",
|
||||
"connection_status_progress": "Comprobando... {done}/{total}",
|
||||
"connection_status_last_check": "Última comprobación: {time}",
|
||||
"menu_games": "Juegos",
|
||||
"api_keys_hint_manage": "Coloca tus claves en {path}",
|
||||
"api_key_empty_suffix": "vacío",
|
||||
@@ -230,6 +237,7 @@
|
||||
"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_connection_status": "Comprobar acceso a sitios de actualizaciones y fuentes",
|
||||
"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",
|
||||
|
||||
@@ -185,7 +185,14 @@
|
||||
"status_present": "Présente",
|
||||
"status_missing": "Absente",
|
||||
"menu_api_keys_status": "Clés API",
|
||||
"menu_connection_status": "État de connexion",
|
||||
"api_keys_status_title": "Statut des clés API",
|
||||
"connection_status_title": "État de connexion",
|
||||
"connection_status_category_updates": "Mise à jour App/Liste de jeux",
|
||||
"connection_status_category_sources": "Sources de jeux",
|
||||
"connection_status_checking": "Vérification...",
|
||||
"connection_status_progress": "Vérification... {done}/{total}",
|
||||
"connection_status_last_check": "Dernière vérif : {time}",
|
||||
"menu_games": "Jeux",
|
||||
"api_keys_hint_manage": "Placez vos clés dans {path}",
|
||||
"api_key_empty_suffix": "vide",
|
||||
@@ -232,6 +239,7 @@
|
||||
"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_connection_status": "Vérifier l'accès aux sites d'update et de sources",
|
||||
"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",
|
||||
|
||||
@@ -184,7 +184,14 @@
|
||||
"status_present": "Presente",
|
||||
"status_missing": "Assente",
|
||||
"menu_api_keys_status": "Chiavi API",
|
||||
"menu_connection_status": "Stato connessione",
|
||||
"api_keys_status_title": "Stato delle chiavi API",
|
||||
"connection_status_title": "Stato connessione",
|
||||
"connection_status_category_updates": "Aggiornamento app/lista giochi",
|
||||
"connection_status_category_sources": "Sorgenti giochi",
|
||||
"connection_status_checking": "Verifica in corso...",
|
||||
"connection_status_progress": "Verifica in corso... {done}/{total}",
|
||||
"connection_status_last_check": "Ultimo controllo: {time}",
|
||||
"menu_games": "Giochi",
|
||||
"api_keys_hint_manage": "Metti le tue chiavi in {path}",
|
||||
"api_key_empty_suffix": "vuoto",
|
||||
@@ -225,6 +232,7 @@
|
||||
"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_connection_status": "Verifica accesso ai siti di aggiornamento e sorgenti",
|
||||
"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",
|
||||
|
||||
@@ -190,7 +190,14 @@
|
||||
"status_present": "Presente",
|
||||
"status_missing": "Ausente",
|
||||
"menu_api_keys_status": "Chaves API",
|
||||
"menu_connection_status": "Estado da conexão",
|
||||
"api_keys_status_title": "Status das chaves API",
|
||||
"connection_status_title": "Estado da conexão",
|
||||
"connection_status_category_updates": "Atualização do app/lista de jogos",
|
||||
"connection_status_category_sources": "Fontes de jogos",
|
||||
"connection_status_checking": "Verificando...",
|
||||
"connection_status_progress": "Verificando... {done}/{total}",
|
||||
"connection_status_last_check": "Última verificação: {time}",
|
||||
"menu_games": "Jogos",
|
||||
"api_keys_hint_manage": "Coloque suas chaves em {path}",
|
||||
"api_key_empty_suffix": "vazio",
|
||||
@@ -231,6 +238,7 @@
|
||||
"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_connection_status": "Verificar acesso a sites de atualização e fontes",
|
||||
"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",
|
||||
|
||||
@@ -2068,18 +2068,47 @@ def run_server(host='0.0.0.0', port=5000):
|
||||
class ReuseAddrHTTPServer(HTTPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
# Tuer les processus existants utilisant le port
|
||||
# Tuer les processus existants utilisant le port (plateforme spécifique)
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(['lsof', '-ti', f':{port}'], capture_output=True, text=True, timeout=2)
|
||||
pids = result.stdout.strip().split('\n')
|
||||
for pid in pids:
|
||||
if pid:
|
||||
try:
|
||||
subprocess.run(['kill', '-9', pid], timeout=2)
|
||||
logger.info(f"Processus {pid} tué (port {port} libéré)")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de tuer le processus {pid}: {e}")
|
||||
# Windows: utiliser netstat + taskkill
|
||||
if os.name == 'nt' or getattr(config, 'OPERATING_SYSTEM', '').lower() == 'windows':
|
||||
try:
|
||||
netstat = subprocess.run(['netstat', '-ano'], capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=3)
|
||||
lines = netstat.stdout.splitlines()
|
||||
pids = set()
|
||||
for line in lines:
|
||||
parts = line.split()
|
||||
if len(parts) >= 5:
|
||||
local = parts[1]
|
||||
pid = parts[-1]
|
||||
if local.endswith(f':{port}'):
|
||||
pids.add(pid)
|
||||
for pid in pids:
|
||||
# Safer: ignore PID 0 and non-numeric entries (system / header lines)
|
||||
if not pid or not pid.isdigit():
|
||||
continue
|
||||
pid_int = int(pid)
|
||||
if pid_int <= 0:
|
||||
continue
|
||||
try:
|
||||
subprocess.run(['taskkill', '/PID', pid, '/F'], timeout=3)
|
||||
logger.info(f"Processus {pid} tué (port {port} libéré) [Windows]")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de tuer le processus {pid}: {e}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Windows port release check failed: {e}")
|
||||
else:
|
||||
# Unix-like: utiliser lsof + kill
|
||||
result = subprocess.run(['lsof', '-ti', f':{port}'], capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=2)
|
||||
pids = result.stdout.strip().split('\n')
|
||||
for pid in pids:
|
||||
if pid:
|
||||
try:
|
||||
subprocess.run(['kill', '-9', pid], timeout=2)
|
||||
logger.info(f"Processus {pid} tué (port {port} libéré)")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de tuer le processus {pid}: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de libérer le port {port}: {e}")
|
||||
|
||||
|
||||
@@ -171,6 +171,121 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
return (False, str(e), None)
|
||||
|
||||
|
||||
VERSIONCLEAN_SERVICE_NAME = "versionclean"
|
||||
VERSIONCLEAN_BACKUP_PATH = "/usr/bin/batocera-version.bak"
|
||||
|
||||
|
||||
def _get_enabled_services():
|
||||
"""Retourne la liste des services activés dans batocera-settings, ou None si indisponible."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["batocera-settings-get", "system.services"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"batocera-settings-get failed: {result.stderr}")
|
||||
return None
|
||||
return result.stdout.split()
|
||||
except FileNotFoundError:
|
||||
logger.warning("batocera-settings-get command not found")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read enabled services: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_versionclean_service():
|
||||
"""Installe et active versionclean si nécessaire.
|
||||
|
||||
- Installe uniquement si le service n'est pas déjà présent.
|
||||
- Active uniquement si le service n'est pas déjà activé.
|
||||
- Démarre uniquement si le nettoyage n'est pas déjà appliqué.
|
||||
"""
|
||||
try:
|
||||
if config.OPERATING_SYSTEM != "Linux":
|
||||
return (True, "Versionclean skipped (non-Linux)")
|
||||
|
||||
services_dir = "/userdata/system/services"
|
||||
service_file = os.path.join(services_dir, VERSIONCLEAN_SERVICE_NAME)
|
||||
source_file = os.path.join(config.APP_FOLDER, "assets", "progs", VERSIONCLEAN_SERVICE_NAME)
|
||||
|
||||
if not os.path.exists(service_file):
|
||||
try:
|
||||
os.makedirs(services_dir, exist_ok=True)
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to create services directory: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
if not os.path.exists(source_file):
|
||||
error_msg = f"Source service file not found: {source_file}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
try:
|
||||
shutil.copy2(source_file, service_file)
|
||||
os.chmod(service_file, 0o755)
|
||||
logger.info(f"Versionclean service installed: {service_file}")
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to copy versionclean service file: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
else:
|
||||
logger.debug("Versionclean service already present, skipping install")
|
||||
|
||||
enabled_services = _get_enabled_services()
|
||||
if enabled_services is None or VERSIONCLEAN_SERVICE_NAME not in enabled_services:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["batocera-services", "enable", VERSIONCLEAN_SERVICE_NAME],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = f"batocera-services enable versionclean failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
logger.debug(f"Versionclean enabled: {result.stdout}")
|
||||
except FileNotFoundError:
|
||||
error_msg = "batocera-services command not found"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to enable versionclean: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
else:
|
||||
logger.debug("Versionclean already enabled, skipping enable")
|
||||
|
||||
if os.path.exists(VERSIONCLEAN_BACKUP_PATH):
|
||||
logger.debug("Versionclean already active (backup present), skipping start")
|
||||
return (True, "Versionclean already active")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["batocera-services", "start", VERSIONCLEAN_SERVICE_NAME],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"batocera-services start versionclean warning: {result.stderr}")
|
||||
else:
|
||||
logger.debug(f"Versionclean started: {result.stdout}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to start versionclean (non-critical): {str(e)}")
|
||||
|
||||
return (True, "Versionclean ensured")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Unexpected versionclean error: {str(e)}"
|
||||
logger.exception(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
|
||||
def toggle_web_service_at_boot(enable: bool):
|
||||
"""Active ou désactive le service web au démarrage de Batocera.
|
||||
|
||||
@@ -203,6 +318,11 @@ def toggle_web_service_at_boot(enable: bool):
|
||||
error_msg = f"Failed to create services directory: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
# 1b. Assurer versionclean (install/enable/start si nécessaire)
|
||||
ensure_ok, ensure_msg = _ensure_versionclean_service()
|
||||
if not ensure_ok:
|
||||
return (False, ensure_msg)
|
||||
|
||||
# 2. Copier le fichier rgsx_web
|
||||
try:
|
||||
@@ -339,6 +459,11 @@ def toggle_custom_dns_at_boot(enable: bool):
|
||||
error_msg = f"Failed to create services directory: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
# 1b. Assurer versionclean (install/enable/start si nécessaire)
|
||||
ensure_ok, ensure_msg = _ensure_versionclean_service()
|
||||
if not ensure_ok:
|
||||
return (False, ensure_msg)
|
||||
|
||||
# 2. Copier le fichier custom_dns
|
||||
try:
|
||||
@@ -479,6 +604,149 @@ def check_custom_dns_status():
|
||||
return False
|
||||
|
||||
|
||||
CONNECTION_STATUS_TTL_SECONDS = 120
|
||||
|
||||
|
||||
def get_connection_status_targets():
|
||||
"""Retourne la liste des sites à vérifier pour le status de connexion."""
|
||||
return [
|
||||
{
|
||||
"key": "retrogamesets",
|
||||
"label": "Retrogamesets.fr",
|
||||
"url": "https://retrogamesets.fr",
|
||||
"category": "updates",
|
||||
},
|
||||
{
|
||||
"key": "github",
|
||||
"label": "GitHub.com",
|
||||
"url": "https://github.com",
|
||||
"category": "updates",
|
||||
},
|
||||
{
|
||||
"key": "myrient",
|
||||
"label": "Myrient.erista.me",
|
||||
"url": "https://myrient.erista.me",
|
||||
"category": "sources",
|
||||
},
|
||||
{
|
||||
"key": "1fichier",
|
||||
"label": "1fichier.com",
|
||||
"url": "https://1fichier.com",
|
||||
"category": "sources",
|
||||
},
|
||||
{
|
||||
"key": "archive",
|
||||
"label": "Archive.org",
|
||||
"url": "https://archive.org",
|
||||
"category": "sources",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _check_url_connectivity(url: str, timeout: int = 6) -> bool:
|
||||
"""Teste rapidement la connectivité à une URL (DNS + HTTPS)."""
|
||||
headers = {"User-Agent": "RGSX-Connectivity/1.0"}
|
||||
try:
|
||||
try:
|
||||
import requests # type: ignore
|
||||
|
||||
try:
|
||||
response = requests.head(url, timeout=timeout, allow_redirects=True, headers=headers)
|
||||
if response.status_code < 500:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
response = requests.get(url, timeout=timeout, allow_redirects=True, stream=True, headers=headers)
|
||||
return response.status_code < 500
|
||||
except Exception:
|
||||
return False
|
||||
except Exception:
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, method="HEAD", headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status < 500
|
||||
except Exception:
|
||||
try:
|
||||
req = urllib.request.Request(url, method="GET", headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status < 500
|
||||
except Exception:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def start_connection_status_check(force: bool = False) -> None:
|
||||
"""Lance un check asynchrone des sites (avec cache/TTL)."""
|
||||
try:
|
||||
now = time.time()
|
||||
if getattr(config, "connection_status_in_progress", False):
|
||||
return
|
||||
last_ts = getattr(config, "connection_status_timestamp", 0.0) or 0.0
|
||||
if not force and last_ts and now - last_ts < CONNECTION_STATUS_TTL_SECONDS:
|
||||
return
|
||||
|
||||
targets = get_connection_status_targets()
|
||||
status = getattr(config, "connection_status", {})
|
||||
if not isinstance(status, dict):
|
||||
status = {}
|
||||
if not status:
|
||||
for item in targets:
|
||||
status[item["key"]] = None
|
||||
config.connection_status = status
|
||||
config.connection_status_in_progress = True
|
||||
config.connection_status_progress = {"done": 0, "total": len(targets)}
|
||||
|
||||
def _worker():
|
||||
try:
|
||||
results = {}
|
||||
done = 0
|
||||
total = len(targets)
|
||||
for item in targets:
|
||||
results[item["key"]] = _check_url_connectivity(item["url"])
|
||||
done += 1
|
||||
config.connection_status_progress = {"done": done, "total": total}
|
||||
try:
|
||||
config.needs_redraw = True
|
||||
except Exception:
|
||||
pass
|
||||
config.connection_status.update(results)
|
||||
config.connection_status_timestamp = time.time()
|
||||
try:
|
||||
summary = ", ".join([f"{k}={'OK' if v else 'FAIL'}" for k, v in results.items()])
|
||||
logger.info(f"Connection status results: {summary}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
config.needs_redraw = True
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f"Connection status check failed: {e}")
|
||||
finally:
|
||||
config.connection_status_in_progress = False
|
||||
|
||||
threading.Thread(target=_worker, daemon=True).start()
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to start connection status check: {e}")
|
||||
|
||||
|
||||
def get_connection_status_snapshot():
|
||||
"""Retourne (status_dict, timestamp, in_progress, progress)."""
|
||||
status = getattr(config, "connection_status", {})
|
||||
if not isinstance(status, dict):
|
||||
status = {}
|
||||
ts = getattr(config, "connection_status_timestamp", 0.0) or 0.0
|
||||
in_progress = getattr(config, "connection_status_in_progress", False)
|
||||
progress = getattr(config, "connection_status_progress", {"done": 0, "total": 0})
|
||||
if not isinstance(progress, dict):
|
||||
progress = {"done": 0, "total": 0}
|
||||
return status, ts, in_progress, progress
|
||||
|
||||
|
||||
|
||||
_extensions_cache = None # type: ignore
|
||||
_extensions_json_regenerated = False
|
||||
@@ -886,6 +1154,12 @@ def load_sources():
|
||||
for platform_name in config.platforms:
|
||||
games = load_games(platform_name)
|
||||
config.games_count[platform_name] = len(games)
|
||||
if config.games_count:
|
||||
try:
|
||||
summary = ", ".join([f"{name}: {count}" for name, count in config.games_count.items()])
|
||||
logger.debug(f"Nombre de jeux par système: {summary}")
|
||||
except Exception:
|
||||
pass
|
||||
return sources
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur fusion systèmes + détection jeux: {e}")
|
||||
@@ -958,7 +1232,8 @@ def load_games(platform_id):
|
||||
else:
|
||||
logger.warning(f"Format de fichier jeux inattendu pour {platform_id}: {type(data)}")
|
||||
|
||||
logger.debug(f"{os.path.basename(game_file)}: {len(normalized)} jeux")
|
||||
if getattr(config, "games_count_log_verbose", False):
|
||||
logger.debug(f"{os.path.basename(game_file)}: {len(normalized)} jeux")
|
||||
return normalized
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement des jeux pour {platform_id}: {e}")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.5.0.0"
|
||||
"version": "2.5.0.2"
|
||||
}
|
||||
@@ -301,6 +301,7 @@ set PYGAME_HIDE_SUPPORT_PROMPT=1
|
||||
set SDL_VIDEODRIVER=windows
|
||||
set SDL_AUDIODRIVER=directsound
|
||||
set PYTHONWARNINGS=ignore::UserWarning:pygame.pkgdata
|
||||
set PYTHONIOENCODING=utf-8
|
||||
|
||||
:: =============================================================================
|
||||
:: Configuration multi-ecran
|
||||
@@ -331,6 +332,7 @@ echo [%DATE% %TIME%] Environment variables set: >> "%LOG_FILE%"
|
||||
echo [%DATE% %TIME%] RGSX_ROOT=%RGSX_ROOT% >> "%LOG_FILE%"
|
||||
echo [%DATE% %TIME%] SDL_VIDEODRIVER=%SDL_VIDEODRIVER% >> "%LOG_FILE%"
|
||||
echo [%DATE% %TIME%] SDL_AUDIODRIVER=%SDL_AUDIODRIVER% >> "%LOG_FILE%"
|
||||
echo [%DATE% %TIME%] PYTHONIOENCODING=%PYTHONIOENCODING% >> "%LOG_FILE%"
|
||||
|
||||
echo.
|
||||
if defined DISPLAY_NUM (
|
||||
|
||||
Reference in New Issue
Block a user