diff --git a/.gitignore b/.gitignore index d6e1c70..d818f39 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,9 @@ ports/RGSX.bat audit_i18n.py prune_i18n.py Info.txt +pygame/ # Docker test data data/ - docker-compose.test.yml config/ diff --git a/ports/RGSX/__main__.py b/ports/RGSX/__main__.py index 9b184ea..6252844 100644 --- a/ports/RGSX/__main__.py +++ b/ports/RGSX/__main__.py @@ -238,11 +238,12 @@ except Exception as e: # Initialisation du mixer Pygame (déférée/évitable si musique désactivée) if getattr(config, 'music_enabled', True): - pygame.mixer.pre_init(44100, -16, 2, 4096) try: + pygame.mixer.pre_init(44100, -16, 2, 4096) pygame.mixer.init() - except Exception as e: - logger.warning(f"Échec init mixer: {e}") + except (NotImplementedError, AttributeError, Exception) as e: + logger.warning(f"Mixer non disponible ou échec init: {e}") + config.music_enabled = False # Désactiver la musique si mixer non disponible # Dossier musique Batocera music_folder = os.path.join(config.APP_FOLDER, "assets", "music") @@ -1368,7 +1369,11 @@ async def main(): clock.tick(60) await asyncio.sleep(0.01) - pygame.mixer.music.stop() + try: + if pygame.mixer.get_init() is not None: + pygame.mixer.music.stop() + except (AttributeError, NotImplementedError): + pass # Cancel any ongoing downloads to prevent lingering background threads try: cancel_all_downloads() diff --git a/ports/RGSX/assets/progs/custom_dns b/ports/RGSX/assets/progs/custom_dns new file mode 100644 index 0000000..35f66ae --- /dev/null +++ b/ports/RGSX/assets/progs/custom_dns @@ -0,0 +1,133 @@ +#!/bin/bash +# BATOCERA SERVICE +# name: Custom DNS Service for RGSX +# description: Force custom DNS servers (Cloudflare 1.1.1.1) +# author: RetroGameSets +# depends: +# version: 1.0 + +RESOLV_CONF="/tmp/resolv.conf" +PIDFILE="/var/run/custom_dns.pid" +LOGFILE="/userdata/roms/ports/RGSX/logs/custom_dns_service.log" +SERVICE_NAME="custom_dns" + +# Fonction utilitaire : vérifie si le service est activé dans batocera-settings +is_enabled() { + local enabled_services + enabled_services="$(/usr/bin/batocera-settings-get system.services 2>/dev/null)" + for s in $enabled_services; do + if [ "$s" = "$SERVICE_NAME" ]; then + echo "enabled" + return + fi + done + echo "disabled" +} + +# Fonction pour appliquer les DNS personnalisés +apply_custom_dns() { + echo "[${SERVICE_NAME}] Applying custom DNS servers..." + mkdir -p "$(dirname "$LOGFILE")" + { + echo "$(date '+%Y-%m-%d %H:%M:%S') - Applying custom DNS" + # Retirer la protection si elle existe + chattr -i "$RESOLV_CONF" 2>/dev/null || true + # Écrire la nouvelle configuration DNS + echo "# Generated by RGSX Custom DNS Service" > "$RESOLV_CONF" + echo "nameserver 1.1.1.1" >> "$RESOLV_CONF" + echo "nameserver 1.0.0.1" >> "$RESOLV_CONF" + # Protéger le fichier contre les modifications + chattr +i "$RESOLV_CONF" 2>/dev/null || true + echo "$(date '+%Y-%m-%d %H:%M:%S') - Custom DNS applied successfully" + } >> "$LOGFILE" 2>&1 + echo "[${SERVICE_NAME}] Custom DNS applied (1.1.1.1, 1.0.0.1)" +} + +# Fonction pour restaurer les DNS par défaut +restore_default_dns() { + echo "[${SERVICE_NAME}] Restoring default DNS..." + mkdir -p "$(dirname "$LOGFILE")" + { + echo "$(date '+%Y-%m-%d %H:%M:%S') - Restoring default DNS" + # Retirer la protection + chattr -i "$RESOLV_CONF" 2>/dev/null || true + echo "$(date '+%Y-%m-%d %H:%M:%S') - DNS protection removed" + } >> "$LOGFILE" 2>&1 + echo "[${SERVICE_NAME}] Default DNS restored" +} + +case "$1" in + start) + if [ -f "$PIDFILE" ]; then + echo "[${SERVICE_NAME}] Already running (PID $(cat "$PIDFILE"))" + exit 0 + fi + apply_custom_dns + echo $$ > "$PIDFILE" + echo "[${SERVICE_NAME}] Started (PID $(cat "$PIDFILE"))" + ;; + + stop) + if [ -f "$PIDFILE" ]; then + echo "[${SERVICE_NAME}] Stopping..." + restore_default_dns + rm -f "$PIDFILE" + echo "[${SERVICE_NAME}] Stopped" + else + echo "[${SERVICE_NAME}] Not running" + fi + ;; + + restart) + echo "[${SERVICE_NAME}] Restarting..." + "$0" stop + sleep 1 + "$0" start + ;; + + status) + ENABLE_STATE=$(is_enabled) + if [ -f "$PIDFILE" ]; then + if chattr -i "$RESOLV_CONF" 2>/dev/null; then + chattr +i "$RESOLV_CONF" 2>/dev/null + echo "[${SERVICE_NAME}] Running (DNS protected) - ${ENABLE_STATE} on boot" + exit 0 + else + echo "[${SERVICE_NAME}] Running (DNS not protected) - ${ENABLE_STATE} on boot" + exit 0 + fi + else + echo "[${SERVICE_NAME}] Not running - ${ENABLE_STATE} on boot" + exit 1 + fi + ;; + + enable) + current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null) + if echo "$current" | grep -qw "$SERVICE_NAME"; then + echo "[${SERVICE_NAME}] Already enabled on boot" + else + new_value="$current $SERVICE_NAME" + /usr/bin/batocera-settings-set system.services "$new_value" + echo "[${SERVICE_NAME}] Enabled on boot" + fi + ;; + + disable) + current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null) + if echo "$current" | grep -qw "$SERVICE_NAME"; then + new_value=$(echo "$current" | sed "s/\b$SERVICE_NAME\b//g" | xargs) + /usr/bin/batocera-settings-set system.services "$new_value" + echo "[${SERVICE_NAME}] Disabled on boot" + else + echo "[${SERVICE_NAME}] Already disabled" + fi + ;; + + *) + echo "Usage: $0 {start|stop|restart|status|enable|disable}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/ports/RGSX/config.py b/ports/RGSX/config.py index 232bd20..7034533 100644 --- a/ports/RGSX/config.py +++ b/ports/RGSX/config.py @@ -13,7 +13,7 @@ except Exception: pygame = None # type: ignore # Version actuelle de l'application -app_version = "2.3.2.0" +app_version = "2.3.2.1" def get_application_root(): diff --git a/ports/RGSX/controls.py b/ports/RGSX/controls.py index ba7e756..b09251a 100644 --- a/ports/RGSX/controls.py +++ b/ports/RGSX/controls.py @@ -15,7 +15,9 @@ from utils import ( load_games, check_extension_before_download, is_extension_supported, load_extensions_json, play_random_music, sanitize_filename, 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, + extract_zip, extract_rar, find_file_with_or_without_extension, + toggle_web_service_at_boot, check_web_service_status, + toggle_custom_dns_at_boot, check_custom_dns_status, restart_application, generate_support_zip, load_sources, ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string ) @@ -593,31 +595,31 @@ def handle_controls(event, sources, joystick, screen): config.needs_redraw = True elif is_input_matched(event, "page_up"): config.current_game = max(0, config.current_game - config.visible_games) - config.repeat_action = None - config.repeat_key = None - config.repeat_start_time = 0 - config.repeat_last_action = current_time + update_key_state("page_up", True, event.type, event.key if event.type == pygame.KEYDOWN else + event.button if event.type == pygame.JOYBUTTONDOWN else + (event.axis, event.value) if event.type == pygame.JOYAXISMOTION else + event.value) config.needs_redraw = True elif is_input_matched(event, "left"): config.current_game = max(0, config.current_game - config.visible_games) - config.repeat_action = None - config.repeat_key = None - config.repeat_start_time = 0 - config.repeat_last_action = current_time + update_key_state("left", True, event.type, event.key if event.type == pygame.KEYDOWN else + event.button if event.type == pygame.JOYBUTTONDOWN else + (event.axis, event.value) if event.type == pygame.JOYAXISMOTION else + event.value) config.needs_redraw = True elif is_input_matched(event, "page_down"): config.current_game = min(len(games) - 1, config.current_game + config.visible_games) - config.repeat_action = None - config.repeat_key = None - config.repeat_start_time = 0 - config.repeat_last_action = current_time + update_key_state("page_down", True, event.type, event.key if event.type == pygame.KEYDOWN else + event.button if event.type == pygame.JOYBUTTONDOWN else + (event.axis, event.value) if event.type == pygame.JOYAXISMOTION else + event.value) config.needs_redraw = True elif is_input_matched(event, "right"): config.current_game = min(len(games) - 1, config.current_game + config.visible_games) - config.repeat_action = None - config.repeat_key = None - config.repeat_start_time = 0 - config.repeat_last_action = current_time + update_key_state("right", True, event.type, event.key if event.type == pygame.KEYDOWN else + event.button if event.type == pygame.JOYBUTTONDOWN else + (event.axis, event.value) if event.type == pygame.JOYAXISMOTION else + event.value) config.needs_redraw = True elif is_input_matched(event, "filter"): config.search_mode = True @@ -1620,9 +1622,11 @@ def handle_controls(event, sources, joystick, screen): # Calculer le nombre total d'options selon le système total = 4 # music, symlink, api keys, back web_service_index = -1 + custom_dns_index = -1 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 if is_input_matched(event, "up"): config.pause_settings_selection = (sel - 1) % total @@ -1642,7 +1646,11 @@ def handle_controls(event, sources, joystick, screen): if music_files and music_folder: config.current_music = play_random_music(music_files, music_folder, getattr(config, "current_music", None)) else: - pygame.mixer.music.stop() + try: + if pygame.mixer.get_init() is not None: + pygame.mixer.music.stop() + except (AttributeError, NotImplementedError): + pass config.needs_redraw = True logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via settings") # Option 1: Symlink toggle @@ -1655,7 +1663,6 @@ def handle_controls(event, sources, joystick, screen): 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) 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() # Afficher un message de chargement config.popup_message = _("settings_web_service_enabling") if not current_status else _("settings_web_service_disabling") @@ -1672,8 +1679,26 @@ 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 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")): + 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"Service custom DNS {'activé' if not current_status else 'désactivé'} au démarrage") + else: + logger.error(f"Erreur toggle service custom DNS: {message}") + threading.Thread(target=toggle_dns, 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"): + elif sel == (custom_dns_index + 1 if custom_dns_index >= 0 else 2) and is_input_matched(event, "confirm"): config.menu_state = "pause_api_keys_status" config.needs_redraw = True # Option Back (dernière option) @@ -2000,7 +2025,7 @@ def handle_controls(event, sources, joystick, screen): # Gestion des relâchements de touches if event.type == pygame.KEYUP: # Vérifier quelle touche a été relâchée - for action_name in ["up", "down", "left", "right", "confirm", "cancel"]: + for action_name in ["up", "down", "left", "right", "page_up", "page_down", "confirm", "cancel"]: if config.controls_config.get(action_name, {}).get("type") == "key" and \ config.controls_config.get(action_name, {}).get("key") == event.key: update_key_state(action_name, False) @@ -2094,7 +2119,7 @@ def handle_controls(event, sources, joystick, screen): elif event.type == pygame.JOYBUTTONUP: # Vérifier quel bouton a été relâché - for action_name in ["up", "down", "left", "right", "confirm", "cancel"]: + for action_name in ["up", "down", "left", "right", "page_up", "page_down", "confirm", "cancel"]: if config.controls_config.get(action_name, {}).get("type") == "button" and \ config.controls_config.get(action_name, {}).get("button") == event.button: update_key_state(action_name, False) @@ -2188,14 +2213,14 @@ def handle_controls(event, sources, joystick, screen): elif event.type == pygame.JOYAXISMOTION and abs(event.value) < 0.5: # Vérifier quel axe a été relâché - for action_name in ["up", "down", "left", "right"]: + for action_name in ["up", "down", "left", "right", "page_up", "page_down"]: if config.controls_config.get(action_name, {}).get("type") == "axis" and \ config.controls_config.get(action_name, {}).get("axis") == event.axis: update_key_state(action_name, False) elif event.type == pygame.JOYHATMOTION and event.value == (0, 0): # Vérifier quel hat a été relâché - for action_name in ["up", "down", "left", "right"]: + for action_name in ["up", "down", "left", "right", "page_up", "page_down"]: if config.controls_config.get(action_name, {}).get("type") == "hat": update_key_state(action_name, False) diff --git a/ports/RGSX/display.py b/ports/RGSX/display.py index a928823..948bfab 100644 --- a/ports/RGSX/display.py +++ b/ports/RGSX/display.py @@ -2119,7 +2119,7 @@ def draw_pause_games_menu(screen, selected_index): def draw_pause_settings_menu(screen, selected_index): from rgsx_settings import get_symlink_option - from utils import check_web_service_status + from utils import check_web_service_status, check_custom_dns_status # Music if config.music_enabled: music_name = config.current_music_name or "" @@ -2140,10 +2140,16 @@ def draw_pause_settings_menu(screen, selected_index): # Web Service at boot (only on Linux/Batocera) web_service_txt = "" + custom_dns_txt = "" if config.OPERATING_SYSTEM == "Linux": web_service_enabled = check_web_service_status() web_service_status = _("settings_web_service_enabled") if web_service_enabled else _("settings_web_service_disabled") web_service_txt = f"{_('settings_web_service')} : < {web_service_status} >" + + # Custom DNS at boot + custom_dns_enabled = check_custom_dns_status() + custom_dns_status = _("settings_custom_dns_enabled") if custom_dns_enabled else _("settings_custom_dns_disabled") + custom_dns_txt = f"{_('settings_custom_dns')} : < {custom_dns_status} >" api_keys_txt = _("menu_api_keys_status") if _ else "API Keys" back_txt = _("menu_back") if _ else "Back" @@ -2152,6 +2158,8 @@ def draw_pause_settings_menu(screen, selected_index): options = [music_option, symlink_option] if web_service_txt: # Ajouter seulement si Linux/Batocera 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]) _draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index) @@ -2161,6 +2169,8 @@ def draw_pause_settings_menu(screen, selected_index): ] if web_service_txt: instruction_keys.append("instruction_settings_web_service") + if custom_dns_txt: + instruction_keys.append("instruction_settings_custom_dns") instruction_keys.extend([ "instruction_settings_api_keys", "instruction_generic_back", diff --git a/ports/RGSX/languages/de.json b/ports/RGSX/languages/de.json index fc17a25..0fa1c9f 100644 --- a/ports/RGSX/languages/de.json +++ b/ports/RGSX/languages/de.json @@ -203,6 +203,7 @@ "instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten", "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_web_service": "Web-Dienst beim Booten", "settings_web_service_enabled": "Aktiviert", "settings_web_service_disabled": "Deaktiviert", @@ -211,6 +212,13 @@ "settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert", "settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert", "settings_web_service_error": "Fehler: {0}", + "settings_custom_dns": "Custom DNS beim Booten", + "settings_custom_dns_enabled": "Aktiviert", + "settings_custom_dns_disabled": "Deaktiviert", + "settings_custom_dns_enabling": "Custom DNS wird aktiviert...", + "settings_custom_dns_disabling": "Custom DNS wird deaktiviert...", + "settings_custom_dns_success_enabled": "Custom DNS beim Booten aktiviert (1.1.1.1)", + "settings_custom_dns_success_disabled": "Custom DNS beim Booten deaktiviert", "controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)", "controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/languages/en.json b/ports/RGSX/languages/en.json index bdc1959..a70b84c 100644 --- a/ports/RGSX/languages/en.json +++ b/ports/RGSX/languages/en.json @@ -205,6 +205,7 @@ "instruction_settings_symlink": "Toggle using filesystem symlinks for installs", "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_web_service": "Web Service at Boot", "settings_web_service_enabled": "Enabled", "settings_web_service_disabled": "Disabled", @@ -213,6 +214,13 @@ "settings_web_service_success_enabled": "Web service enabled at boot", "settings_web_service_success_disabled": "Web service disabled at boot", "settings_web_service_error": "Error: {0}", + "settings_custom_dns": "Custom DNS at Boot", + "settings_custom_dns_enabled": "Enabled", + "settings_custom_dns_disabled": "Disabled", + "settings_custom_dns_enabling": "Enabling custom DNS...", + "settings_custom_dns_disabling": "Disabling custom DNS...", + "settings_custom_dns_success_enabled": "Custom DNS enabled at boot (1.1.1.1)", + "settings_custom_dns_success_disabled": "Custom DNS disabled at boot", "controls_desc_confirm": "Confirm (e.g. A/Cross)", "controls_desc_cancel": "Cancel/Back (e.g. B/Circle)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/languages/es.json b/ports/RGSX/languages/es.json index e6fccb9..dcd27b7 100644 --- a/ports/RGSX/languages/es.json +++ b/ports/RGSX/languages/es.json @@ -205,6 +205,7 @@ "instruction_settings_symlink": "Alternar uso de symlinks en instalaciones", "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_web_service": "Servicio Web al Inicio", "settings_web_service_enabled": "Activado", "settings_web_service_disabled": "Desactivado", @@ -213,6 +214,13 @@ "settings_web_service_success_enabled": "Servicio web activado al inicio", "settings_web_service_success_disabled": "Servicio web desactivado al inicio", "settings_web_service_error": "Error: {0}", + "settings_custom_dns": "DNS Personalizado al Inicio", + "settings_custom_dns_enabled": "Activado", + "settings_custom_dns_disabled": "Desactivado", + "settings_custom_dns_enabling": "Activando DNS personalizado...", + "settings_custom_dns_disabling": "Desactivando DNS personalizado...", + "settings_custom_dns_success_enabled": "DNS personalizado activado al inicio (1.1.1.1)", + "settings_custom_dns_success_disabled": "DNS personalizado desactivado al inicio", "controls_desc_confirm": "Confirmar (ej. A/Cruz)", "controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/languages/fr.json b/ports/RGSX/languages/fr.json index f43865c..5a9cb43 100644 --- a/ports/RGSX/languages/fr.json +++ b/ports/RGSX/languages/fr.json @@ -205,6 +205,7 @@ "instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation", "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_web_service": "Service Web au démarrage", "settings_web_service_enabled": "Activé", "settings_web_service_disabled": "Désactivé", @@ -213,6 +214,13 @@ "settings_web_service_success_enabled": "Service web activé au démarrage", "settings_web_service_success_disabled": "Service web désactivé au démarrage", "settings_web_service_error": "Erreur : {0}", + "settings_custom_dns": "DNS Personnalisé au démarrage", + "settings_custom_dns_enabled": "Activé", + "settings_custom_dns_disabled": "Désactivé", + "settings_custom_dns_enabling": "Activation du DNS personnalisé...", + "settings_custom_dns_disabling": "Désactivation du DNS personnalisé...", + "settings_custom_dns_success_enabled": "DNS personnalisé activé au démarrage (1.1.1.1)", + "settings_custom_dns_success_disabled": "DNS personnalisé désactivé au démarrage", "controls_desc_confirm": "Valider (ex: A/Croix)", "controls_desc_cancel": "Annuler/Retour (ex: B/Rond)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/languages/it.json b/ports/RGSX/languages/it.json index 0b13c40..d2d2774 100644 --- a/ports/RGSX/languages/it.json +++ b/ports/RGSX/languages/it.json @@ -202,6 +202,7 @@ "instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni", "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_web_service": "Servizio Web all'Avvio", "settings_web_service_enabled": "Abilitato", "settings_web_service_disabled": "Disabilitato", @@ -210,6 +211,13 @@ "settings_web_service_success_enabled": "Servizio web abilitato all'avvio", "settings_web_service_success_disabled": "Servizio web disabilitato all'avvio", "settings_web_service_error": "Errore: {0}", + "settings_custom_dns": "DNS Personalizzato all'Avvio", + "settings_custom_dns_enabled": "Abilitato", + "settings_custom_dns_disabled": "Disabilitato", + "settings_custom_dns_enabling": "Abilitazione DNS personalizzato...", + "settings_custom_dns_disabling": "Disabilitazione DNS personalizzato...", + "settings_custom_dns_success_enabled": "DNS personalizzato abilitato all'avvio (1.1.1.1)", + "settings_custom_dns_success_disabled": "DNS personalizzato disabilitato all'avvio", "controls_desc_confirm": "Confermare (es. A/Croce)", "controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/languages/pt.json b/ports/RGSX/languages/pt.json index 0cc4e4b..2dab251 100644 --- a/ports/RGSX/languages/pt.json +++ b/ports/RGSX/languages/pt.json @@ -204,6 +204,7 @@ "instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações", "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_web_service": "Serviço Web na Inicialização", "settings_web_service_enabled": "Ativado", "settings_web_service_disabled": "Desativado", @@ -212,6 +213,13 @@ "settings_web_service_success_enabled": "Serviço web ativado na inicialização", "settings_web_service_success_disabled": "Serviço web desativado na inicialização", "settings_web_service_error": "Erro: {0}", + "settings_custom_dns": "DNS Personalizado na Inicialização", + "settings_custom_dns_enabled": "Ativado", + "settings_custom_dns_disabled": "Desativado", + "settings_custom_dns_enabling": "Ativando DNS personalizado...", + "settings_custom_dns_disabling": "Desativando DNS personalizado...", + "settings_custom_dns_success_enabled": "DNS personalizado ativado na inicialização (1.1.1.1)", + "settings_custom_dns_success_disabled": "DNS personalizado desativado na inicialização", "controls_desc_confirm": "Confirmar (ex. A/Cruz)", "controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)", "controls_desc_up": "UP ↑", diff --git a/ports/RGSX/utils.py b/ports/RGSX/utils.py index 20b884a..1af193d 100644 --- a/ports/RGSX/utils.py +++ b/ports/RGSX/utils.py @@ -34,6 +34,14 @@ logger = logging.getLogger(__name__) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("requests").setLevel(logging.WARNING) +# Helper pour vérifier si pygame.mixer est disponible +def is_mixer_available(): + """Vérifie si pygame.mixer est disponible et initialisé.""" + try: + return pygame is not None and hasattr(pygame, 'mixer') and pygame.mixer.get_init() is not None + except (AttributeError, NotImplementedError): + return False + # Liste globale pour stocker les systèmes avec une erreur 404 unavailable_systems = [] @@ -65,7 +73,8 @@ def restart_application(delay_ms: int = 2000): if int(delay_ms) <= 0: try: try: - pygame.mixer.music.stop() + if is_mixer_available(): + pygame.mixer.music.stop() except Exception: pass try: @@ -300,6 +309,176 @@ def toggle_web_service_at_boot(enable: bool): return (False, error_msg) +def toggle_custom_dns_at_boot(enable: bool): + """Active ou désactive le service custom DNS au démarrage de Batocera. + + Args: + enable: True pour activer, False pour désactiver + + Returns: + tuple: (success: bool, message: str) + """ + try: + # Vérifier si on est sur un système compatible (Linux avec batocera-services) + if config.OPERATING_SYSTEM != "Linux": + return (False, "Custom DNS service is only available on Batocera/Linux systems") + + services_dir = "/userdata/system/services" + service_file = os.path.join(services_dir, "custom_dns") + source_file = os.path.join(config.APP_FOLDER, "assets", "progs", "custom_dns") + + if enable: + # Mode ENABLE + logger.debug("Activation du service custom DNS au démarrage...") + + # 1. Créer le dossier services s'il n'existe pas + try: + os.makedirs(services_dir, exist_ok=True) + logger.debug(f"Dossier services vérifié/créé: {services_dir}") + except Exception as e: + error_msg = f"Failed to create services directory: {str(e)}" + logger.error(error_msg) + return (False, error_msg) + + # 2. Copier le fichier custom_dns + try: + 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) + + shutil.copy2(source_file, service_file) + os.chmod(service_file, 0o755) # Rendre exécutable + logger.debug(f"Fichier service copié et rendu exécutable: {service_file}") + except Exception as e: + error_msg = f"Failed to copy service file: {str(e)}" + logger.error(error_msg) + return (False, error_msg) + + # 3. Activer le service avec batocera-services + try: + result = subprocess.run( + ['batocera-services', 'enable', 'custom_dns'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + error_msg = f"batocera-services enable failed: {result.stderr}" + logger.error(error_msg) + return (False, error_msg) + logger.debug(f"Service activé: {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 service: {str(e)}" + logger.error(error_msg) + return (False, error_msg) + + # 4. Démarrer le service immédiatement + try: + result = subprocess.run( + ['batocera-services', 'start', 'custom_dns'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + # Le service peut ne pas démarrer si déjà en cours, ce n'est pas grave + logger.warning(f"batocera-services start warning: {result.stderr}") + else: + logger.debug(f"Service démarré: {result.stdout}") + except Exception as e: + logger.warning(f"Failed to start service (non-critical): {str(e)}") + + success_msg = _("settings_custom_dns_success_enabled") if _ else "Custom DNS enabled at boot" + logger.info(success_msg) + + # Sauvegarder l'état dans rgsx_settings.json + settings = load_rgsx_settings() + settings["custom_dns_at_boot"] = True + save_rgsx_settings(settings) + + return (True, success_msg) + + else: + # Mode DISABLE + logger.debug("Désactivation du service custom DNS au démarrage...") + + # 1. Désactiver le service avec batocera-services + try: + result = subprocess.run( + ['batocera-services', 'disable', 'custom_dns'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + error_msg = f"batocera-services disable failed: {result.stderr}" + logger.error(error_msg) + return (False, error_msg) + logger.debug(f"Service désactivé: {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 disable service: {str(e)}" + logger.error(error_msg) + return (False, error_msg) + + # 2. Arrêter le service immédiatement + try: + result = subprocess.run( + ['batocera-services', 'stop', 'custom_dns'], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + logger.warning(f"batocera-services stop warning: {result.stderr}") + else: + logger.debug(f"Service arrêté: {result.stdout}") + except Exception as e: + logger.warning(f"Failed to stop service (non-critical): {str(e)}") + + success_msg = _("settings_custom_dns_success_disabled") if _ else "✓ Custom DNS disabled at boot" + logger.info(success_msg) + + # Sauvegarder l'état dans rgsx_settings.json + settings = load_rgsx_settings() + settings["custom_dns_at_boot"] = False + save_rgsx_settings(settings) + + return (True, success_msg) + + except Exception as e: + error_msg = f"Unexpected error: {str(e)}" + logger.exception(error_msg) + return (False, error_msg) + + +def check_custom_dns_status(): + """Vérifie si le service custom DNS est activé au démarrage. + + Returns: + bool: True si activé, False sinon + """ + try: + if config.OPERATING_SYSTEM != "Linux": + return False + + # Lire l'état depuis rgsx_settings.json + settings = load_rgsx_settings() + return settings.get("custom_dns_at_boot", False) + + except Exception as e: + logger.debug(f"Failed to check custom DNS status: {e}") + return False + + _extensions_cache = None # type: ignore _extensions_json_regenerated = False @@ -1983,8 +2162,9 @@ def handle_xbox(dest_dir, iso_files, url=None): def play_random_music(music_files, music_folder, current_music=None): - if not getattr(config, "music_enabled", True): - pygame.mixer.music.stop() + if not getattr(config, "music_enabled", True) or not is_mixer_available(): + if is_mixer_available(): + pygame.mixer.music.stop() return current_music if music_files: # Éviter de rejouer la même musique consécutivement @@ -1997,11 +2177,12 @@ def play_random_music(music_files, music_folder, current_music=None): def load_and_play_music(): try: - pygame.mixer.music.load(music_path) - pygame.mixer.music.set_volume(0.5) - pygame.mixer.music.play(loops=0) # Jouer une seule fois - pygame.mixer.music.set_endevent(pygame.USEREVENT + 1) # Événement de fin - set_music_popup(music_file) # Afficher le nom de la musique dans la popup + if is_mixer_available(): + pygame.mixer.music.load(music_path) + pygame.mixer.music.set_volume(0.5) + pygame.mixer.music.play(loops=0) # Jouer une seule fois + pygame.mixer.music.set_endevent(pygame.USEREVENT + 1) # Événement de fin + set_music_popup(music_file) # Afficher le nom de la musique dans la popup except Exception as e: logger.error(f"Erreur lors du chargement de la musique {music_path}: {str(e)}") diff --git a/version.json b/version.json index f4e0e0e..4c38e56 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "2.3.2.0" + "version": "2.3.2.1" } \ No newline at end of file