mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 16:26:00 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1592671ddc | ||
|
|
4e029aabf1 | ||
|
|
970fcaf197 | ||
|
|
ff30e6d297 |
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -30,16 +30,9 @@ jobs:
|
||||
zip -r "../../dist/RGSX_update_latest.zip" . \
|
||||
-x "logs/*" \
|
||||
"logs/**" \
|
||||
"images/*" \
|
||||
"images/**" \
|
||||
"games/*" \
|
||||
"games/**" \
|
||||
"scripts/*" \
|
||||
"scripts/**" \
|
||||
"__pycache__/*" \
|
||||
"__pycache__/**" \
|
||||
"*.pyc" \
|
||||
"sources.json" \
|
||||
"*.log"
|
||||
|
||||
cd ../..
|
||||
@@ -52,16 +45,9 @@ jobs:
|
||||
zip -r "dist/RGSX_full_latest.zip" ports windows \
|
||||
-x "ports/RGSX/logs/*" \
|
||||
"ports/RGSX/logs/**" \
|
||||
"ports/RGSX/images/*" \
|
||||
"ports/RGSX/images/**" \
|
||||
"ports/RGSX/games/*" \
|
||||
"ports/RGSX/games/**" \
|
||||
"ports/RGSX/scripts/*" \
|
||||
"ports/RGSX/scripts/**" \
|
||||
"ports/RGSX/__pycache__/*" \
|
||||
"ports/RGSX/__pycache__/**" \
|
||||
"ports/RGSX/*.pyc" \
|
||||
"ports/RGSX/sources.json" \
|
||||
"ports/RGSX/*.log" \
|
||||
"windows/logs/*" \
|
||||
"windows/*.xml" \
|
||||
@@ -88,12 +74,12 @@ jobs:
|
||||
|
||||
## 📥 Automatic Installation (Only for batocera Knulli)
|
||||
|
||||
### ON PC :
|
||||
### ON PC , NUC, SteamDeck or any x86_64 based:
|
||||
1. Open File Manager (F1) then "Applications" and launch xterm
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
|
||||
### ON RASPBERRY/ARM SBC / HANDHELD :
|
||||
### ON RASPBERRY/ ARM based SBC / HANDHELD :
|
||||
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
@@ -111,17 +97,12 @@ jobs:
|
||||
3. Launch RGSX from system "Windows"
|
||||
|
||||
|
||||
## 📥 Manual Update (you shouldn't need to do this as RGSX updates automatically on each start)
|
||||
|
||||
#### Batocera/Knulli
|
||||
## 📥 Manual Update (only if automatic doesn't work for some obcure reason)
|
||||
|
||||
#### Batocera/Knulli/Retrobat
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract only PORTS folder in `/userdata/roms/`
|
||||
3. Launch RGSX from the Ports menu
|
||||
|
||||
#### Retrobat
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract all folders in your Retrobat\roms folder
|
||||
3. Launch RGSX from system "Windows"
|
||||
2. Extract zip content on in `/userdata/roms/ports/RGSX`
|
||||
3. Launch RGSX
|
||||
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.2.9"
|
||||
app_version = "2.3.3.1"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
|
||||
@@ -1482,8 +1482,15 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Confirmation quitter
|
||||
elif config.menu_state == "confirm_exit":
|
||||
if is_input_matched(event, "confirm"):
|
||||
if config.confirm_selection == 1:
|
||||
# Sous-menu Quit: 0=Quit RGSX, 1=Restart RGSX, 2=Back
|
||||
if is_input_matched(event, "up"):
|
||||
config.confirm_selection = max(0, config.confirm_selection - 1)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "down"):
|
||||
config.confirm_selection = min(2, config.confirm_selection + 1)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if config.confirm_selection == 0: # Quit RGSX
|
||||
# Mark all in-progress downloads as canceled in history
|
||||
try:
|
||||
for entry in getattr(config, 'history', []) or []:
|
||||
@@ -1495,7 +1502,9 @@ def handle_controls(event, sources, joystick, screen):
|
||||
except Exception:
|
||||
pass
|
||||
return "quit"
|
||||
else:
|
||||
elif config.confirm_selection == 1: # Restart RGSX
|
||||
restart_application(2000)
|
||||
elif config.confirm_selection == 2: # Back
|
||||
# Retour à l'état capturé (confirm_exit_origin) sinon previous_menu_state sinon platform
|
||||
target = getattr(config, 'confirm_exit_origin', getattr(config, 'previous_menu_state', 'platform'))
|
||||
config.menu_state = validate_menu_state(target)
|
||||
@@ -1505,11 +1514,18 @@ def handle_controls(event, sources, joystick, screen):
|
||||
except Exception:
|
||||
pass
|
||||
config.needs_redraw = True
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (annulation)")
|
||||
elif is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
config.confirm_selection = 1 - config.confirm_selection
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (back)")
|
||||
elif is_input_matched(event, "cancel"):
|
||||
# Retour à l'état capturé
|
||||
target = getattr(config, 'confirm_exit_origin', getattr(config, 'previous_menu_state', 'platform'))
|
||||
config.menu_state = validate_menu_state(target)
|
||||
if hasattr(config, 'confirm_exit_origin'):
|
||||
try:
|
||||
delattr(config, 'confirm_exit_origin')
|
||||
except Exception:
|
||||
pass
|
||||
config.needs_redraw = True
|
||||
#logger.debug(f"Changement sélection confirm_exit: {config.confirm_selection}")
|
||||
logger.debug(f"Retour à {config.menu_state} depuis confirm_exit (cancel)")
|
||||
|
||||
# Menu pause
|
||||
elif config.menu_state == "pause_menu":
|
||||
@@ -1529,38 +1545,36 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.selected_option = min(total - 1, config.selected_option + 1)
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm"):
|
||||
if config.selected_option == 0: # Language selector direct
|
||||
if config.selected_option == 0: # Games submenu
|
||||
config.menu_state = "pause_games_menu"
|
||||
if not hasattr(config, 'pause_games_selection'):
|
||||
config.pause_games_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 1: # Language selector direct
|
||||
config.menu_state = "language_select"
|
||||
config.previous_menu_state = "pause_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 1: # Controls submenu
|
||||
elif config.selected_option == 2: # Controls submenu
|
||||
config.menu_state = "pause_controls_menu"
|
||||
if not hasattr(config, 'pause_controls_selection'):
|
||||
config.pause_controls_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 2: # Display submenu
|
||||
elif config.selected_option == 3: # Display submenu
|
||||
config.menu_state = "pause_display_menu"
|
||||
if not hasattr(config, 'pause_display_selection'):
|
||||
config.pause_display_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 3: # Games submenu
|
||||
config.menu_state = "pause_games_menu"
|
||||
if not hasattr(config, 'pause_games_selection'):
|
||||
config.pause_games_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 4: # Settings submenu
|
||||
config.menu_state = "pause_settings_menu"
|
||||
if not hasattr(config, 'pause_settings_selection'):
|
||||
config.pause_settings_selection = 0
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 5: # Restart
|
||||
restart_application(2000)
|
||||
elif config.selected_option == 6: # Support
|
||||
elif config.selected_option == 5: # Support
|
||||
success, message, zip_path = generate_support_zip()
|
||||
if success:
|
||||
config.support_zip_path = zip_path
|
||||
@@ -1571,7 +1585,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.menu_state = "support_dialog"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 7: # Quit
|
||||
elif config.selected_option == 6: # Quit submenu
|
||||
# Capturer l'origine pause_menu pour retour si annulation
|
||||
config.confirm_exit_origin = "pause_menu"
|
||||
config.previous_menu_state = validate_menu_state(config.previous_menu_state)
|
||||
@@ -1736,7 +1750,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Sous-menu Games
|
||||
elif config.menu_state == "pause_games_menu":
|
||||
sel = getattr(config, 'pause_games_selection', 0)
|
||||
total = 7 # history, source, redownload, unsupported, hide premium, filter, back
|
||||
total = 7 # update cache, history, source, unsupported, hide premium, filter, back
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_games_selection = (sel - 1) % total
|
||||
config.needs_redraw = True
|
||||
@@ -1744,14 +1758,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.pause_games_selection = (sel + 1) % total
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
if sel == 0 and is_input_matched(event, "confirm"): # history
|
||||
if sel == 0 and is_input_matched(event, "confirm"): # update cache
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "reload_games_data"
|
||||
config.redownload_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and is_input_matched(event, "confirm"): # history
|
||||
config.history = load_history()
|
||||
config.current_history_item = 0
|
||||
config.history_scroll_offset = 0
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "history"
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
elif sel == 2 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")): # source mode
|
||||
try:
|
||||
current_mode = get_sources_mode()
|
||||
new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx')
|
||||
@@ -1766,11 +1785,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
logger.info(f"Changement du mode des sources vers {new_mode}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur changement mode sources: {e}")
|
||||
elif sel == 2 and is_input_matched(event, "confirm"): # redownload cache
|
||||
config.previous_menu_state = "pause_games_menu"
|
||||
config.menu_state = "reload_games_data"
|
||||
config.redownload_confirm_selection = 0
|
||||
config.needs_redraw = True
|
||||
elif sel == 3 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")): # unsupported toggle
|
||||
try:
|
||||
current = get_show_unsupported_platforms()
|
||||
@@ -1809,11 +1823,19 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif config.menu_state == "pause_settings_menu":
|
||||
sel = getattr(config, 'pause_settings_selection', 0)
|
||||
# Calculer le nombre total d'options selon le système
|
||||
total = 4 # music, symlink, api keys, back
|
||||
# Liste des options : music, symlink, [web_service], [custom_dns], api keys, back
|
||||
total = 4 # music, symlink, api keys, back (Windows)
|
||||
web_service_index = -1
|
||||
custom_dns_index = -1
|
||||
api_keys_index = 2
|
||||
back_index = 3
|
||||
|
||||
if config.OPERATING_SYSTEM == "Linux":
|
||||
total = 5 # music, symlink, web_service, api keys, back
|
||||
total = 6 # music, symlink, web_service, custom_dns, api keys, back
|
||||
web_service_index = 2
|
||||
custom_dns_index = 3
|
||||
api_keys_index = 4
|
||||
back_index = 5
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_settings_selection = (sel - 1) % total
|
||||
@@ -1862,12 +1884,31 @@ def handle_controls(event, sources, joystick, screen):
|
||||
else:
|
||||
logger.error(f"Erreur toggle service web: {message}")
|
||||
threading.Thread(target=toggle_service, daemon=True).start()
|
||||
# Option API Keys (index varie selon Linux ou pas)
|
||||
elif sel == (web_service_index + 1 if web_service_index >= 0 else 2) and is_input_matched(event, "confirm"):
|
||||
# Option 3: Custom DNS toggle (seulement si Linux)
|
||||
elif sel == custom_dns_index and custom_dns_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from utils import check_custom_dns_status, toggle_custom_dns_at_boot
|
||||
current_status = check_custom_dns_status()
|
||||
# Afficher un message de chargement
|
||||
config.popup_message = _("settings_custom_dns_enabling") if not current_status else _("settings_custom_dns_disabling")
|
||||
config.popup_timer = 1000
|
||||
config.needs_redraw = True
|
||||
# Exécuter en thread pour ne pas bloquer l'UI
|
||||
def toggle_dns():
|
||||
success, message = toggle_custom_dns_at_boot(not current_status)
|
||||
config.popup_message = message
|
||||
config.popup_timer = 5000 if success else 7000
|
||||
config.needs_redraw = True
|
||||
if success:
|
||||
logger.info(f"Custom DNS {'activé' if not current_status else 'désactivé'} au démarrage")
|
||||
else:
|
||||
logger.error(f"Erreur toggle custom DNS: {message}")
|
||||
threading.Thread(target=toggle_dns, daemon=True).start()
|
||||
# Option API Keys
|
||||
elif sel == api_keys_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_api_keys_status"
|
||||
config.needs_redraw = True
|
||||
# Option Back (dernière option)
|
||||
elif sel == (total - 1) and is_input_matched(event, "confirm"):
|
||||
elif sel == back_index and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
|
||||
@@ -755,8 +755,18 @@ def draw_platform_grid(screen):
|
||||
available_width = config.screen_width - margin_left - margin_right
|
||||
available_height = config.screen_height - margin_top - margin_bottom
|
||||
|
||||
# Calculer la taille des cellules en tenant compte de l'espace nécessaire pour le glow
|
||||
# Réduire la taille effective pour laisser de l'espace entre les éléments
|
||||
col_width = available_width // num_cols
|
||||
row_height = available_height // num_rows
|
||||
|
||||
# Calculer la taille du container basée sur la cellule la plus petite
|
||||
# avec marges pour éviter les chevauchements (20% de marge)
|
||||
cell_size = min(col_width, row_height)
|
||||
container_size = int(cell_size * 0.70) # 70% de la cellule pour laisser de l'espace
|
||||
|
||||
# Espacement entre les cellules pour éviter les chevauchements
|
||||
cell_padding = int(cell_size * 0.15) # 15% d'espacement
|
||||
|
||||
x_positions = [margin_left + col_width * i + col_width // 2 for i in range(num_cols)]
|
||||
y_positions = [margin_top + row_height * i + row_height // 2 for i in range(num_rows)]
|
||||
@@ -794,8 +804,8 @@ def draw_platform_grid(screen):
|
||||
page_rect = page_indicator.get_rect(center=(config.screen_width // 2, page_y))
|
||||
screen.blit(page_indicator, page_rect)
|
||||
|
||||
# Calculer une seule fois la pulsation pour les éléments sélectionnés
|
||||
pulse = 0.1 * math.sin(current_time / 300)
|
||||
# Calculer une seule fois la pulsation pour les éléments sélectionnés (réduite)
|
||||
pulse = 0.05 * math.sin(current_time / 300) # Réduit de 0.1 à 0.05
|
||||
glow_intensity = 40 + int(30 * math.sin(current_time / 300))
|
||||
|
||||
# Pré-calcul des images pour optimiser le rendu
|
||||
@@ -809,9 +819,9 @@ def draw_platform_grid(screen):
|
||||
x = x_positions[col]
|
||||
y = y_positions[row]
|
||||
|
||||
# Animation fluide pour l'item sélectionné
|
||||
# Animation fluide pour l'item sélectionné (réduite pour éviter chevauchement)
|
||||
is_selected = idx == config.selected_platform
|
||||
scale_base = 1.5 if is_selected else 1.0
|
||||
scale_base = 1.15 if is_selected else 1.0 # Réduit de 1.5 à 1.15
|
||||
scale = scale_base + pulse if is_selected else scale_base
|
||||
|
||||
# Récupération robuste du dict via nom
|
||||
@@ -830,62 +840,120 @@ def draw_platform_grid(screen):
|
||||
platform_id = platform_dict.get("platform_name") or platform_dict.get("platform") or display_name
|
||||
|
||||
# Utiliser le cache d'images pour éviter de recharger/redimensionner à chaque frame
|
||||
cache_key = f"{platform_id}_{scale:.2f}"
|
||||
cache_key = f"{platform_id}_{scale:.2f}_{container_size}"
|
||||
if cache_key not in platform_images_cache:
|
||||
image = load_system_image(platform_dict)
|
||||
if image:
|
||||
orig_width, orig_height = image.get_width(), image.get_height()
|
||||
max_size = int(min(col_width, row_height) * scale * 1.1) # Légèrement plus grand que la cellule
|
||||
ratio = min(max_size / orig_width, max_size / orig_height)
|
||||
|
||||
# Taille normalisée basée sur container_size calculé en fonction de la grille
|
||||
# Le scale affecte uniquement l'item sélectionné
|
||||
# Adapter la largeur en fonction du nombre de colonnes pour occuper ~25-30% de l'écran
|
||||
if num_cols == 3:
|
||||
# En 3 colonnes, augmenter significativement la largeur (15% de l'écran par carte)
|
||||
actual_container_width = int(config.screen_width * 0.15 * scale)
|
||||
elif num_cols == 4:
|
||||
# En 4 colonnes, largeur plus modérée (10% de l'écran par carte)
|
||||
actual_container_width = int(config.screen_width * 0.15 * scale)
|
||||
else:
|
||||
# Par défaut, utiliser container_size * 1.3
|
||||
actual_container_width = int(container_size * scale * 1.3)
|
||||
|
||||
actual_container_height = int(container_size * scale) # Hauteur normale
|
||||
|
||||
# Calculer le ratio pour fit dans le container en gardant l'aspect ratio
|
||||
ratio = min(actual_container_width / orig_width, actual_container_height / orig_height)
|
||||
new_width = int(orig_width * ratio)
|
||||
new_height = int(orig_height * ratio)
|
||||
|
||||
scaled_image = pygame.transform.smoothscale(image, (new_width, new_height))
|
||||
platform_images_cache[cache_key] = {
|
||||
"image": scaled_image,
|
||||
"width": new_width,
|
||||
"height": new_height,
|
||||
"container_width": actual_container_width,
|
||||
"container_height": actual_container_height,
|
||||
"last_used": current_time
|
||||
}
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
# Mettre à jour le timestamp de dernière utilisation
|
||||
|
||||
# Récupérer les données du cache (que ce soit nouveau ou existant)
|
||||
if cache_key in platform_images_cache:
|
||||
platform_images_cache[cache_key]["last_used"] = current_time
|
||||
scaled_image = platform_images_cache[cache_key]["image"]
|
||||
new_width = platform_images_cache[cache_key]["width"]
|
||||
new_height = platform_images_cache[cache_key]["height"]
|
||||
container_width = platform_images_cache[cache_key]["container_width"]
|
||||
container_height = platform_images_cache[cache_key]["container_height"]
|
||||
else:
|
||||
continue
|
||||
|
||||
image_rect = scaled_image.get_rect(center=(x, y))
|
||||
|
||||
# Effet visuel amélioré pour l'item sélectionné
|
||||
# Effet visuel moderne similaire au titre pour toutes les images
|
||||
border_radius = 12
|
||||
padding = 12
|
||||
|
||||
# Utiliser la taille du container normalisé au lieu de la taille variable de l'image
|
||||
rect_width = container_width + 2 * padding
|
||||
rect_height = container_height + 2 * padding
|
||||
|
||||
# Centrer le conteneur sur la position (x, y)
|
||||
container_left = x - rect_width // 2
|
||||
container_top = y - rect_height // 2
|
||||
|
||||
# Ombre portée
|
||||
shadow_surf = pygame.Surface((rect_width + 12, rect_height + 12), pygame.SRCALPHA)
|
||||
pygame.draw.rect(shadow_surf, (0, 0, 0, 160), (6, 6, rect_width, rect_height), border_radius=border_radius + 4)
|
||||
screen.blit(shadow_surf, (container_left - 6, container_top - 6))
|
||||
|
||||
# Effet de glow multicouche pour l'item sélectionné
|
||||
if is_selected:
|
||||
neon_color = THEME_COLORS["neon"]
|
||||
border_radius = 12
|
||||
padding = 12
|
||||
rect_width = image_rect.width + 2 * padding
|
||||
rect_height = image_rect.height + 2 * padding
|
||||
|
||||
# Effet de glow dynamique
|
||||
neon_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
|
||||
pygame.draw.rect(neon_surface, neon_color + (glow_intensity,), neon_surface.get_rect(), border_radius=border_radius)
|
||||
pygame.draw.rect(neon_surface, neon_color + (100,), neon_surface.get_rect().inflate(-10, -10), border_radius=border_radius)
|
||||
pygame.draw.rect(neon_surface, neon_color + (200,), neon_surface.get_rect().inflate(-20, -20), width=1, border_radius=border_radius)
|
||||
screen.blit(neon_surface, (image_rect.left - padding, image_rect.top - padding), special_flags=pygame.BLEND_RGBA_ADD)
|
||||
|
||||
# Fond pour toutes les images
|
||||
background_surface = pygame.Surface((image_rect.width + 10, image_rect.height + 10), pygame.SRCALPHA)
|
||||
bg_alpha = 220 if is_selected else 180 # Plus opaque pour l'item sélectionné
|
||||
pygame.draw.rect(background_surface, THEME_COLORS["fond_image"] + (bg_alpha,), background_surface.get_rect(), border_radius=12)
|
||||
screen.blit(background_surface, (image_rect.left - 5, image_rect.top - 5))
|
||||
# Glow multicouche (2 couches pour effet profondeur)
|
||||
for i in range(2):
|
||||
glow_size = (rect_width + 15 + i * 8, rect_height + 15 + i * 8)
|
||||
glow_surf = pygame.Surface(glow_size, pygame.SRCALPHA)
|
||||
alpha = int((glow_intensity + 40) * (1 - i / 2))
|
||||
pygame.draw.rect(glow_surf, neon_color + (alpha,), glow_surf.get_rect(), border_radius=border_radius + i * 2)
|
||||
screen.blit(glow_surf, (container_left - 8 - i * 4, container_top - 8 - i * 4))
|
||||
|
||||
# Fond avec dégradé vertical (similaire au titre)
|
||||
bg_surface = pygame.Surface((rect_width, rect_height), pygame.SRCALPHA)
|
||||
base_color = THEME_COLORS["button_idle"] if is_selected else THEME_COLORS["fond_image"]
|
||||
|
||||
for i in range(rect_height):
|
||||
ratio = i / rect_height
|
||||
# Dégradé du haut (plus clair) vers le bas (plus foncé)
|
||||
alpha = int(base_color[3] * (1 + ratio * 0.15)) if len(base_color) > 3 else int(200 * (1 + ratio * 0.15))
|
||||
color = (*base_color[:3], min(255, alpha))
|
||||
pygame.draw.line(bg_surface, color, (0, i), (rect_width, i))
|
||||
|
||||
screen.blit(bg_surface, (container_left, container_top))
|
||||
|
||||
# Reflet en haut (highlight pour effet glossy)
|
||||
highlight_height = rect_height // 3
|
||||
highlight = pygame.Surface((rect_width - 8, highlight_height), pygame.SRCALPHA)
|
||||
highlight.fill((255, 255, 255, 35 if is_selected else 20))
|
||||
screen.blit(highlight, (container_left + 4, container_top + 4))
|
||||
|
||||
# Bordure avec effet 3D
|
||||
border_color = THEME_COLORS["neon"] if is_selected else THEME_COLORS["border"]
|
||||
border_rect = pygame.Rect(container_left, container_top, rect_width, rect_height)
|
||||
pygame.draw.rect(screen, border_color, border_rect, 2, border_radius=border_radius)
|
||||
|
||||
# Centrer l'image dans le container (l'image peut être plus petite que le container)
|
||||
centered_image_rect = scaled_image.get_rect(center=(x, y))
|
||||
|
||||
# Affichage de l'image avec un léger effet de transparence pour les items non sélectionnés
|
||||
if not is_selected:
|
||||
# Appliquer la transparence seulement si nécessaire
|
||||
temp_image = scaled_image.copy()
|
||||
temp_image.set_alpha(220)
|
||||
screen.blit(temp_image, image_rect)
|
||||
screen.blit(temp_image, centered_image_rect)
|
||||
else:
|
||||
screen.blit(scaled_image, image_rect)
|
||||
screen.blit(scaled_image, centered_image_rect)
|
||||
|
||||
# Nettoyer le cache périodiquement (garder seulement les images utilisées récemment)
|
||||
if len(platform_images_cache) > 50: # Limite arbitraire pour éviter une croissance excessive
|
||||
@@ -2037,16 +2105,15 @@ def draw_display_menu(screen):
|
||||
def draw_pause_menu(screen, selected_option):
|
||||
"""Dessine le menu pause racine (catégories)."""
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
# Nouvel ordre: Language / Controls / Display / Games / Settings / Restart / Support / Quit
|
||||
# Nouvel ordre: Games / Language / Controls / Display / Settings / Support / Quit
|
||||
options = [
|
||||
_("menu_language") if _ else "Language", # 0 -> sélecteur de langue direct
|
||||
_("menu_controls"), # 1 -> sous-menu controls
|
||||
_("menu_display"), # 2 -> sous-menu display
|
||||
_("menu_games") if _ else "Games", # 3 -> sous-menu games (history + sources + update)
|
||||
_("menu_settings_category") if _ else "Settings", # 4 -> sous-menu settings
|
||||
_("menu_restart"), # 5 -> reboot
|
||||
_("menu_support"), # 6 -> support
|
||||
_("menu_quit") # 7 -> quit
|
||||
_("menu_games") if _ else "Games", # 0 -> sous-menu games (history + sources + update)
|
||||
_("menu_language") if _ else "Language", # 1 -> sélecteur de langue direct
|
||||
_("menu_controls"), # 2 -> sous-menu controls
|
||||
_("menu_display"), # 3 -> sous-menu display
|
||||
_("menu_settings_category") if _ else "Settings", # 4 -> sous-menu settings
|
||||
_("menu_support"), # 5 -> support
|
||||
_("menu_quit") # 6 -> sous-menu quit (quit + restart)
|
||||
]
|
||||
# Calculer hauteur dynamique basée sur la taille de police
|
||||
sample_text = config.font.render("Sample", True, THEME_COLORS["text"])
|
||||
@@ -2083,12 +2150,11 @@ def draw_pause_menu(screen, selected_option):
|
||||
# Instruction contextuelle pour l'option sélectionnée
|
||||
# Mapping des clés i18n parallèles à la liste options (même ordre)
|
||||
instruction_keys = [
|
||||
"instruction_pause_games",
|
||||
"instruction_pause_language",
|
||||
"instruction_pause_controls",
|
||||
"instruction_pause_display",
|
||||
"instruction_pause_games",
|
||||
"instruction_pause_settings",
|
||||
"instruction_pause_restart",
|
||||
"instruction_pause_support",
|
||||
"instruction_pause_quit",
|
||||
]
|
||||
@@ -2296,12 +2362,12 @@ def draw_pause_games_menu(screen, selected_index):
|
||||
filter_txt = _("submenu_display_filter_platforms") if _ else "Filter Platforms"
|
||||
|
||||
back_txt = _("menu_back") if _ else "Back"
|
||||
options = [history_txt, source_txt, update_txt, unsupported_txt, hide_premium_txt, filter_txt, back_txt]
|
||||
options = [update_txt, history_txt, source_txt, unsupported_txt, hide_premium_txt, filter_txt, back_txt]
|
||||
_draw_submenu_generic(screen, _("menu_games") if _ else "Games", options, selected_index)
|
||||
instruction_keys = [
|
||||
"instruction_games_update_cache",
|
||||
"instruction_games_history",
|
||||
"instruction_games_source_mode",
|
||||
"instruction_games_update_cache",
|
||||
"instruction_display_show_unsupported",
|
||||
"instruction_display_hide_premium",
|
||||
"instruction_display_filter_platforms",
|
||||
@@ -2805,57 +2871,29 @@ def draw_controls_help(screen, previous_state):
|
||||
|
||||
# Menu Quitter Appli
|
||||
def draw_confirm_dialog(screen):
|
||||
"""Affiche la boîte de dialogue de confirmation pour quitter."""
|
||||
global OVERLAY
|
||||
if OVERLAY is None or OVERLAY.get_size() != (config.screen_width, config.screen_height):
|
||||
OVERLAY = pygame.Surface((config.screen_width, config.screen_height), pygame.SRCALPHA)
|
||||
OVERLAY.fill((0, 0, 0, 150))
|
||||
logger.debug("OVERLAY recréé dans draw_confirm_dialog")
|
||||
|
||||
screen.blit(OVERLAY, (0, 0))
|
||||
# Dynamic message: warn when downloads are active
|
||||
active_downloads = 0
|
||||
try:
|
||||
active_downloads = len(getattr(config, 'download_tasks', {}) or {})
|
||||
queued_downloads = len(getattr(config, 'download_queue', []) or [])
|
||||
total_downloads = active_downloads + queued_downloads
|
||||
except Exception:
|
||||
total_downloads = 0
|
||||
if total_downloads > 0:
|
||||
# Try translated key if it exists; otherwise fallback to generic message
|
||||
try:
|
||||
warn_tpl = _("confirm_exit_with_downloads") # optional key
|
||||
# If untranslated key returns the same string, still format
|
||||
message = warn_tpl.format(total_downloads)
|
||||
except Exception:
|
||||
message = f"Attention: {total_downloads} téléchargement(s) en cours. Quitter quand même ?"
|
||||
else:
|
||||
message = _("confirm_exit")
|
||||
wrapped_message = wrap_text(message, config.font, config.screen_width - 80)
|
||||
line_height = config.font.get_height() + 5
|
||||
text_height = len(wrapped_message) * line_height
|
||||
# Adapter hauteur bouton en fonction de la taille de police
|
||||
sample_text = config.font.render("Sample", True, THEME_COLORS["text"])
|
||||
font_height = sample_text.get_height()
|
||||
button_height = max(int(config.screen_height * 0.0463), font_height + 15)
|
||||
margin_top_bottom = 20
|
||||
rect_height = text_height + button_height + 2 * margin_top_bottom
|
||||
max_text_width = max([config.font.size(line)[0] for line in wrapped_message], default=300)
|
||||
rect_width = max_text_width + 150
|
||||
rect_x = (config.screen_width - rect_width) // 2
|
||||
rect_y = (config.screen_height - rect_height) // 2
|
||||
|
||||
pygame.draw.rect(screen, THEME_COLORS["button_idle"], (rect_x, rect_y, rect_width, rect_height), border_radius=12)
|
||||
pygame.draw.rect(screen, THEME_COLORS["border"], (rect_x, rect_y, rect_width, rect_height), 2, border_radius=12)
|
||||
|
||||
for i, line in enumerate(wrapped_message):
|
||||
text = config.font.render(line, True, THEME_COLORS["text"])
|
||||
text_rect = text.get_rect(center=(config.screen_width // 2, rect_y + margin_top_bottom + i * line_height + line_height // 2))
|
||||
screen.blit(text, text_rect)
|
||||
|
||||
button_width = min(160, (rect_width - 60) // 2)
|
||||
draw_stylized_button(screen, _("button_yes"), rect_x + rect_width // 2 - button_width - 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 1)
|
||||
draw_stylized_button(screen, _("button_no"), rect_x + rect_width // 2 + 10, rect_y + text_height + margin_top_bottom, button_width, button_height, selected=config.confirm_selection == 0)
|
||||
"""Affiche le sous-menu Quit avec les options Quit et Restart."""
|
||||
options = [
|
||||
_("menu_quit_app") if _ else "Quit RGSX",
|
||||
_("menu_restart") if _ else "Restart RGSX",
|
||||
_("menu_back") if _ else "Back"
|
||||
]
|
||||
_draw_submenu_generic(screen, _("menu_quit") if _ else "Quit", options, config.confirm_selection)
|
||||
instruction_keys = [
|
||||
"instruction_quit_app",
|
||||
"instruction_quit_restart",
|
||||
"instruction_generic_back",
|
||||
]
|
||||
key = instruction_keys[config.confirm_selection] if 0 <= config.confirm_selection < len(instruction_keys) else None
|
||||
if key:
|
||||
button_height = int(config.screen_height * 0.045)
|
||||
margin_top_bottom = 26
|
||||
menu_height = (len(options)+1) * (button_height + 10) + 2 * margin_top_bottom
|
||||
menu_y = (config.screen_height - menu_height) // 2
|
||||
title_surface = config.font.render("X", True, THEME_COLORS["text"])
|
||||
title_rect_height = title_surface.get_height()
|
||||
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10
|
||||
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||||
draw_menu_instruction(screen, _(key), last_button_bottom)
|
||||
|
||||
|
||||
def draw_reload_games_data_dialog(screen):
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"menu_redownload_cache": "Spieleliste aktualisieren",
|
||||
"menu_music_enabled": "Musik aktiviert: {0}",
|
||||
"menu_music_disabled": "Musik deaktiviert",
|
||||
"menu_restart": "Neustart",
|
||||
"menu_restart": "RGSX neu starten",
|
||||
"menu_support": "Unterstützung",
|
||||
"menu_filter_platforms": "Systeme filtern",
|
||||
"filter_platforms_title": "Systemsichtbarkeit",
|
||||
@@ -80,6 +80,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Ausblenden der Warnung bei unbekannter Erweiterung aktiviert",
|
||||
"menu_allow_unknown_ext_disabled": "Ausblenden der Warnung bei unbekannter Erweiterung deaktiviert",
|
||||
"menu_quit": "Beenden",
|
||||
"menu_quit_app": "RGSX beenden",
|
||||
"support_dialog_title": "Support-Datei",
|
||||
"support_dialog_message": "Eine Support-Datei wurde mit allen Ihren Konfigurations- und Protokolldateien erstellt.\n\nDatei: {0}\n\nUm Hilfe zu erhalten:\n1. Treten Sie dem RGSX Discord-Server bei\n2. Beschreiben Sie Ihr Problem\n3. Teilen Sie diese ZIP-Datei\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
"support_dialog_error": "Fehler beim Erstellen der Support-Datei:\n{0}\n\nDrücken Sie {1}, um zum Menü zurückzukehren.",
|
||||
@@ -184,7 +185,9 @@
|
||||
"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus",
|
||||
"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden",
|
||||
"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen",
|
||||
"instruction_pause_quit": "RGSX Anwendung beenden",
|
||||
"instruction_pause_quit": "Menü für Beenden oder Neustart aufrufen",
|
||||
"instruction_quit_app": "RGSX Anwendung beenden",
|
||||
"instruction_quit_restart": "RGSX Anwendung neu starten",
|
||||
"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen",
|
||||
"instruction_controls_remap": "Tasten / Buttons neu zuordnen",
|
||||
"instruction_generic_back": "Zum vorherigen Menü zurückkehren",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"menu_redownload_cache": "Update games list",
|
||||
"menu_music_enabled": "Music enabled: {0}",
|
||||
"menu_music_disabled": "Music disabled",
|
||||
"menu_restart": "Restart",
|
||||
"menu_restart": "Restart RGSX",
|
||||
"menu_filter_platforms": "Filter systems",
|
||||
"filter_platforms_title": "Systems visibility",
|
||||
"filter_platforms_info": "Visible: {0} | Hidden: {1} / Total: {2}",
|
||||
@@ -80,6 +80,7 @@
|
||||
"menu_allow_unknown_ext_disabled": "Hide unknown extension warning disabled",
|
||||
"menu_support": "Support",
|
||||
"menu_quit": "Quit",
|
||||
"menu_quit_app": "Quit RGSX",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"button_OK": "OK",
|
||||
@@ -186,7 +187,9 @@
|
||||
"instruction_pause_settings": "Music, symlink option & API keys status",
|
||||
"instruction_pause_restart": "Restart RGSX to reload configuration",
|
||||
"instruction_pause_support": "Generate a diagnostic ZIP file for support",
|
||||
"instruction_pause_quit": "Exit the RGSX application",
|
||||
"instruction_pause_quit": "Access menu to quit or restart",
|
||||
"instruction_quit_app": "Exit the RGSX application",
|
||||
"instruction_quit_restart": "Restart the RGSX application",
|
||||
"instruction_controls_help": "Show full controller & keyboard reference",
|
||||
"instruction_controls_remap": "Change button / key bindings",
|
||||
"instruction_generic_back": "Return to the previous menu",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"menu_redownload_cache": "Actualizar lista de juegos",
|
||||
"menu_music_enabled": "Música activada: {0}",
|
||||
"menu_music_disabled": "Música desactivada",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_restart": "Reiniciar RGSX",
|
||||
"menu_support": "Soporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidad de sistemas",
|
||||
@@ -80,6 +80,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensión desconocida oculto (activado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensión desconocida visible (desactivado)",
|
||||
"menu_quit": "Salir",
|
||||
"menu_quit_app": "Salir de RGSX",
|
||||
"support_dialog_title": "Archivo de soporte",
|
||||
"support_dialog_message": "Se ha creado un archivo de soporte con todos sus archivos de configuración y registros.\n\nArchivo: {0}\n\nPara obtener ayuda:\n1. Únete al servidor Discord de RGSX\n2. Describe tu problema\n3. Comparte este archivo ZIP\n\nPresiona {1} para volver al menú.",
|
||||
"support_dialog_error": "Error al generar el archivo de soporte:\n{0}\n\nPresiona {1} para volver al menú.",
|
||||
@@ -186,7 +187,9 @@
|
||||
"instruction_pause_settings": "Música, opción symlink y estado de claves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recargar configuración",
|
||||
"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte",
|
||||
"instruction_pause_quit": "Salir de la aplicación RGSX",
|
||||
"instruction_pause_quit": "Acceder al menú para salir o reiniciar",
|
||||
"instruction_quit_app": "Salir de la aplicación RGSX",
|
||||
"instruction_quit_restart": "Reiniciar la aplicación RGSX",
|
||||
"instruction_controls_help": "Mostrar referencia completa de mando y teclado",
|
||||
"instruction_controls_remap": "Cambiar asignación de botones / teclas",
|
||||
"instruction_generic_back": "Volver al menú anterior",
|
||||
|
||||
@@ -67,9 +67,10 @@
|
||||
"menu_redownload_cache": "Mettre à jour la liste des jeux",
|
||||
"menu_support": "Support",
|
||||
"menu_quit": "Quitter",
|
||||
"menu_quit_app": "Quitter RGSX",
|
||||
"menu_music_enabled": "Musique activée : {0}",
|
||||
"menu_music_disabled": "Musique désactivée",
|
||||
"menu_restart": "Redémarrer",
|
||||
"menu_restart": "Redémarrer RGSX",
|
||||
"menu_filter_platforms": "Filtrer les systèmes",
|
||||
"filter_platforms_title": "Affichage des systèmes",
|
||||
"filter_platforms_info": "Visibles: {0} | Masqués: {1} / Total: {2}",
|
||||
@@ -186,7 +187,9 @@
|
||||
"instruction_pause_settings": "Musique, option symlink & statut des clés API",
|
||||
"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration",
|
||||
"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance",
|
||||
"instruction_pause_quit": "Quitter l'application RGSX",
|
||||
"instruction_pause_quit": "Accéder au menu pour quitter ou redémarrer",
|
||||
"instruction_quit_app": "Quitter l'application RGSX",
|
||||
"instruction_quit_restart": "Redémarrer l'application RGSX",
|
||||
"instruction_controls_help": "Afficher la référence complète manette & clavier",
|
||||
"instruction_controls_remap": "Modifier l'association boutons / touches",
|
||||
"instruction_generic_back": "Revenir au menu précédent",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"menu_redownload_cache": "Aggiorna elenco giochi",
|
||||
"menu_music_enabled": "Musica attivata: {0}",
|
||||
"menu_music_disabled": "Musica disattivata",
|
||||
"menu_restart": "Riavvia",
|
||||
"menu_restart": "Riavvia RGSX",
|
||||
"menu_support": "Supporto",
|
||||
"menu_filter_platforms": "Filtra sistemi",
|
||||
"filter_platforms_title": "Visibilità sistemi",
|
||||
@@ -80,6 +80,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Nascondi avviso estensione sconosciuta abilitato",
|
||||
"menu_allow_unknown_ext_disabled": "Nascondi avviso estensione sconosciuta disabilitato",
|
||||
"menu_quit": "Esci",
|
||||
"menu_quit_app": "Esci da RGSX",
|
||||
"support_dialog_title": "File di supporto",
|
||||
"support_dialog_message": "È stato creato un file di supporto con tutti i file di configurazione e di registro.\n\nFile: {0}\n\nPer ottenere aiuto:\n1. Unisciti al server Discord RGSX\n2. Descrivi il tuo problema\n3. Condividi questo file ZIP\n\nPremi {1} per tornare al menu.",
|
||||
"support_dialog_error": "Errore durante la generazione del file di supporto:\n{0}\n\nPremi {1} per tornare al menu.",
|
||||
@@ -183,7 +184,9 @@
|
||||
"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API",
|
||||
"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione",
|
||||
"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto",
|
||||
"instruction_pause_quit": "Uscire dall'applicazione RGSX",
|
||||
"instruction_pause_quit": "Accedere al menu per uscire o riavviare",
|
||||
"instruction_quit_app": "Uscire dall'applicazione RGSX",
|
||||
"instruction_quit_restart": "Riavviare l'applicazione RGSX",
|
||||
"instruction_controls_help": "Mostrare riferimento completo controller & tastiera",
|
||||
"instruction_controls_remap": "Modificare associazione pulsanti / tasti",
|
||||
"instruction_generic_back": "Tornare al menu precedente",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"menu_redownload_cache": "Atualizar lista de jogos",
|
||||
"menu_music_enabled": "Música ativada: {0}",
|
||||
"menu_music_disabled": "Música desativada",
|
||||
"menu_restart": "Reiniciar",
|
||||
"menu_restart": "Reiniciar RGSX",
|
||||
"menu_support": "Suporte",
|
||||
"menu_filter_platforms": "Filtrar sistemas",
|
||||
"filter_platforms_title": "Visibilidade dos sistemas",
|
||||
@@ -80,6 +80,7 @@
|
||||
"menu_allow_unknown_ext_enabled": "Aviso de extensão desconhecida oculto (ativado)",
|
||||
"menu_allow_unknown_ext_disabled": "Aviso de extensão desconhecida visível (desativado)",
|
||||
"menu_quit": "Sair",
|
||||
"menu_quit_app": "Sair do RGSX",
|
||||
"support_dialog_title": "Arquivo de suporte",
|
||||
"support_dialog_message": "Foi criado um arquivo de suporte com todos os seus arquivos de configuração e logs.\n\nArquivo: {0}\n\nPara obter ajuda:\n1. Junte-se ao servidor Discord RGSX\n2. Descreva seu problema\n3. Compartilhe este arquivo ZIP\n\nPressione {1} para voltar ao menu.",
|
||||
"support_dialog_error": "Erro ao gerar o arquivo de suporte:\n{0}\n\nPressione {1} para voltar ao menu.",
|
||||
@@ -185,7 +186,9 @@
|
||||
"instruction_pause_settings": "Música, opção symlink e status das chaves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração",
|
||||
"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte",
|
||||
"instruction_pause_quit": "Sair da aplicação RGSX",
|
||||
"instruction_pause_quit": "Acessar menu para sair ou reiniciar",
|
||||
"instruction_quit_app": "Sair da aplicação RGSX",
|
||||
"instruction_quit_restart": "Reiniciar a aplicação RGSX",
|
||||
"instruction_controls_help": "Mostrar referência completa de controle e teclado",
|
||||
"instruction_controls_remap": "Modificar associação de botões / teclas",
|
||||
"instruction_generic_back": "Voltar ao menu anterior",
|
||||
|
||||
@@ -453,8 +453,102 @@ async def check_for_updates():
|
||||
config.loading_progress = 5.0
|
||||
config.needs_redraw = True
|
||||
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
# Liste des endpoints à essayer (GitHub principal, puis fallback)
|
||||
endpoints = [
|
||||
OTA_VERSION_ENDPOINT,
|
||||
"https://retrogamesets.fr/softs/version.json"
|
||||
]
|
||||
|
||||
response = None
|
||||
last_error = None
|
||||
|
||||
for endpoint_index, endpoint in enumerate(endpoints):
|
||||
is_fallback = endpoint_index > 0
|
||||
if is_fallback:
|
||||
logger.info(f"Tentative sur endpoint de secours : {endpoint}")
|
||||
|
||||
# Gestion des erreurs de rate limit GitHub (429) avec retry
|
||||
max_retries = 3 if not is_fallback else 1 # Moins de retries sur fallback
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
response = requests.get(endpoint, timeout=10)
|
||||
|
||||
# Gestion spécifique des erreurs 429 (Too Many Requests) - surtout pour GitHub
|
||||
if response.status_code == 429:
|
||||
retry_after = response.headers.get('retry-after')
|
||||
x_ratelimit_remaining = response.headers.get('x-ratelimit-remaining', '1')
|
||||
x_ratelimit_reset = response.headers.get('x-ratelimit-reset')
|
||||
|
||||
if retry_after:
|
||||
# En-tête retry-after présent : attendre le nombre de secondes spécifié
|
||||
wait_time = int(retry_after)
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s (retry-after header)")
|
||||
elif x_ratelimit_remaining == '0' and x_ratelimit_reset:
|
||||
# x-ratelimit-remaining est 0 : attendre jusqu'à x-ratelimit-reset
|
||||
import time
|
||||
reset_time = int(x_ratelimit_reset)
|
||||
current_time = int(time.time())
|
||||
wait_time = max(reset_time - current_time, 60) # Minimum 60s
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s (x-ratelimit-reset)")
|
||||
else:
|
||||
# Pas d'en-têtes spécifiques : attendre au moins 60s
|
||||
wait_time = 60
|
||||
logger.warning(f"Rate limit atteint (429) sur {endpoint}. Attente de {wait_time}s par défaut")
|
||||
|
||||
if retry_count < max_retries - 1:
|
||||
logger.info(f"Nouvelle tentative dans {wait_time}s... ({retry_count + 1}/{max_retries})")
|
||||
await asyncio.sleep(wait_time)
|
||||
retry_count += 1
|
||||
continue
|
||||
else:
|
||||
# Si rate limit persistant et qu'on est sur GitHub, essayer le fallback
|
||||
if not is_fallback:
|
||||
logger.warning(f"Rate limit GitHub persistant, passage au serveur de secours")
|
||||
break # Sortir de la boucle retry pour essayer le prochain endpoint
|
||||
raise requests.exceptions.HTTPError(
|
||||
f"Limite de débit atteinte (429). Veuillez réessayer plus tard."
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
# Succès, sortir de toutes les boucles
|
||||
logger.debug(f"Version récupérée avec succès depuis : {endpoint}")
|
||||
break
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
last_error = e
|
||||
if response and response.status_code == 429:
|
||||
# 429 géré au-dessus, continuer la boucle ou passer au fallback
|
||||
retry_count += 1
|
||||
if retry_count >= max_retries:
|
||||
break # Passer au prochain endpoint
|
||||
else:
|
||||
# Erreur HTTP autre que 429
|
||||
logger.warning(f"Erreur HTTP {response.status_code if response else 'inconnue'} sur {endpoint}")
|
||||
break # Passer au prochain endpoint
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
last_error = e
|
||||
if retry_count < max_retries - 1:
|
||||
# Erreur réseau, réessayer avec backoff exponentiel
|
||||
wait_time = 2 ** retry_count # 1s, 2s, 4s
|
||||
logger.warning(f"Erreur réseau sur {endpoint}. Nouvelle tentative dans {wait_time}s...")
|
||||
await asyncio.sleep(wait_time)
|
||||
retry_count += 1
|
||||
else:
|
||||
logger.warning(f"Erreur réseau persistante sur {endpoint} : {e}")
|
||||
break # Passer au prochain endpoint
|
||||
|
||||
# Si on a une réponse valide, sortir de la boucle des endpoints
|
||||
if response and response.status_code == 200:
|
||||
break
|
||||
|
||||
# Si aucun endpoint n'a fonctionné
|
||||
if not response or response.status_code != 200:
|
||||
raise last_error if last_error else requests.exceptions.RequestException(
|
||||
"Impossible de vérifier les mises à jour sur tous les serveurs"
|
||||
)
|
||||
|
||||
# Accepter différents content-types (application/json, text/plain, text/html)
|
||||
content_type = response.headers.get("content-type", "")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.3.2.9"
|
||||
"version": "2.3.3.1"
|
||||
}
|
||||
Reference in New Issue
Block a user