forked from Mirrors/RGSX
v2.2.0.3 - fix virtual keyboard now showing when controller is plugged in or handheld like steamdeck
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
os.environ["SDL_FBDEV"] = "/dev/fb0"
|
|
||||||
import pygame # type: ignore
|
|
||||||
import asyncio
|
|
||||||
import platform
|
import platform
|
||||||
|
# Ne pas forcer SDL_FBDEV ici; si déjà défini par l'environnement, on le garde
|
||||||
|
try:
|
||||||
|
if "SDL_FBDEV" in os.environ:
|
||||||
|
pass # respecter la configuration existante
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
import pygame # type: ignore
|
||||||
|
import time
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import queue
|
import queue
|
||||||
@@ -27,8 +33,8 @@ from controls import handle_controls, validate_menu_state, process_key_repeats,
|
|||||||
from controls_mapper import map_controls, draw_controls_mapping, get_actions
|
from controls_mapper import map_controls, draw_controls_mapping, get_actions
|
||||||
from controls import load_controls_config
|
from controls import load_controls_config
|
||||||
from utils import (
|
from utils import (
|
||||||
detect_non_pc, load_sources, check_extension_before_download, extract_zip_data,
|
load_sources, check_extension_before_download, extract_zip_data,
|
||||||
play_random_music, load_music_config, silence_alsa_warnings, enable_alsa_stderr_filter
|
play_random_music, load_music_config
|
||||||
)
|
)
|
||||||
from history import load_history, save_history
|
from history import load_history, save_history
|
||||||
from config import OTA_data_ZIP
|
from config import OTA_data_ZIP
|
||||||
@@ -79,6 +85,37 @@ def _run_windows_gamelist_update():
|
|||||||
|
|
||||||
_run_windows_gamelist_update()
|
_run_windows_gamelist_update()
|
||||||
|
|
||||||
|
# Vérifier et appliquer les mises à jour AVANT tout chargement des contrôles
|
||||||
|
try:
|
||||||
|
# Internet rapide (synchrone) avant init graphique complète
|
||||||
|
if test_internet():
|
||||||
|
logger.debug("Pré-boot: connexion Internet OK, vérification des mises à jour")
|
||||||
|
# Initialiser un mini-contexte de chargement pour feedback si l'écran est prêt
|
||||||
|
config.menu_state = "loading"
|
||||||
|
config.current_loading_system = _("loading_check_updates")
|
||||||
|
config.loading_progress = 5.0
|
||||||
|
config.needs_redraw = True
|
||||||
|
# Lancer la vérification en mode synchrone via boucle temporaire
|
||||||
|
# Utilise une boucle d'événements courte pour permettre pygame d'initialiser
|
||||||
|
try:
|
||||||
|
# Créer une boucle asyncio ad-hoc si non présente
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
success, _msg = loop.run_until_complete(check_for_updates())
|
||||||
|
logger.debug(f"Pré-boot: check_for_updates terminé success={success}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Pré-boot: échec check_for_updates: {e}")
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
loop.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
config.update_checked = True
|
||||||
|
else:
|
||||||
|
logger.debug("Pré-boot: pas d'Internet, pas de vérification des mises à jour")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Pré-boot update check a échoué: {e}")
|
||||||
|
|
||||||
|
|
||||||
# Initialisation de Pygame
|
# Initialisation de Pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
@@ -96,56 +133,7 @@ except Exception as e:
|
|||||||
logger.exception(f"Échec du nettoyage des anciens fichiers: {e}")
|
logger.exception(f"Échec du nettoyage des anciens fichiers: {e}")
|
||||||
|
|
||||||
|
|
||||||
#Récupération des noms des joysticks si pas de joystick connecté, verifier si clavier connecté
|
|
||||||
joystick_names = [pygame.joystick.Joystick(i).get_name() for i in range(pygame.joystick.get_count())]
|
|
||||||
if not joystick_names:
|
|
||||||
joystick_names = ["Clavier"]
|
|
||||||
print("Aucun joystick détecté, utilisation du clavier par défaut")
|
|
||||||
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
|
||||||
config.joystick = False
|
|
||||||
config.keyboard = True
|
|
||||||
else:
|
|
||||||
config.joystick = True
|
|
||||||
config.keyboard = False
|
|
||||||
print(f"Joysticks détectés: {joystick_names}")
|
|
||||||
logger.debug(f"Joysticks détectés: {joystick_names}, utilisation du joystick par défaut.")
|
|
||||||
# Test des boutons du joystick
|
|
||||||
for name in joystick_names:
|
|
||||||
if "Xbox" in name or "360" in name or "X-Box" in name:
|
|
||||||
config.xbox_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
break
|
|
||||||
elif "PlayStation" in name:
|
|
||||||
config.playstation_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
break
|
|
||||||
elif "Nintendo" in name:
|
|
||||||
config.nintendo_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
elif "Logitech" in name:
|
|
||||||
config.logitech_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
elif "8Bitdo" in name:
|
|
||||||
config.eightbitdo_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
elif "Steam" in name:
|
|
||||||
config.steam_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
elif "TRIMUI Smart Pro" in name:
|
|
||||||
config.trimui_controller = True
|
|
||||||
logger.debug(f"Controller detected : {name}")
|
|
||||||
print(f"Controller detected : {name}")
|
|
||||||
else:
|
|
||||||
config.generic_controller = True
|
|
||||||
logger.debug(f"Generic controller detected : {name}")
|
|
||||||
print(f"Generic controller detected : {name}")
|
|
||||||
|
|
||||||
# Chargement des paramètres d'accessibilité
|
# Chargement des paramètres d'accessibilité
|
||||||
config.accessibility_settings = load_accessibility_settings()
|
config.accessibility_settings = load_accessibility_settings()
|
||||||
# Appliquer la grille d'affichage depuis les paramètres
|
# Appliquer la grille d'affichage depuis les paramètres
|
||||||
@@ -169,8 +157,22 @@ config.sources_mode = get_sources_mode()
|
|||||||
config.custom_sources_url = get_custom_sources_url()
|
config.custom_sources_url = get_custom_sources_url()
|
||||||
logger.debug(f"Mode sources initial: {config.sources_mode}, URL custom: {config.custom_sources_url}")
|
logger.debug(f"Mode sources initial: {config.sources_mode}, URL custom: {config.custom_sources_url}")
|
||||||
|
|
||||||
# Détection du système non-PC
|
# Détection du système grace a une commande windows / linux (on oublie is non-pc c'est juste pour connaitre le materiel et le systeme d'exploitation)
|
||||||
config.is_non_pc = detect_non_pc()
|
def detect_system_info():
|
||||||
|
"""Détecte les informations système (OS, architecture) via des commandes appropriées."""
|
||||||
|
try:
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
# Commande pour Windows
|
||||||
|
result = subprocess.run(["wmic", "os", "get", "caption"], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
logger.info(f"Système détecté (Windows): {result.stdout.strip()}")
|
||||||
|
else:
|
||||||
|
# Commande pour Linux
|
||||||
|
result = subprocess.run(["lsb_release", "-d"], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
logger.info(f"Système détecté (Linux): {result.stdout.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lors de la détection du système: {e}")
|
||||||
|
|
||||||
# Initialisation de l’écran
|
# Initialisation de l’écran
|
||||||
screen = init_display()
|
screen = init_display()
|
||||||
@@ -185,6 +187,64 @@ config.init_font()
|
|||||||
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
|
config.screen_width, config.screen_height = pygame.display.get_surface().get_size()
|
||||||
logger.debug(f"Résolution d'écran : {config.screen_width}x{config.screen_height}")
|
logger.debug(f"Résolution d'écran : {config.screen_width}x{config.screen_height}")
|
||||||
|
|
||||||
|
# Détection des joysticks après init_display (plus stable sur Batocera)
|
||||||
|
try:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
time.sleep(0.05) # petite latence pour stabiliser SDL sur certains builds
|
||||||
|
count = pygame.joystick.get_count()
|
||||||
|
except Exception:
|
||||||
|
count = 0
|
||||||
|
joystick_names = []
|
||||||
|
for i in range(count):
|
||||||
|
try:
|
||||||
|
j = pygame.joystick.Joystick(i)
|
||||||
|
joystick_names.append(j.get_name())
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Impossible de lire le nom du joystick {i}: {e}")
|
||||||
|
normalized_names = [n.lower() for n in joystick_names]
|
||||||
|
if not joystick_names:
|
||||||
|
joystick_names = ["Clavier"]
|
||||||
|
print("Aucun joystick détecté, utilisation du clavier par défaut")
|
||||||
|
logger.debug("Aucun joystick détecté, utilisation du clavier par défaut.")
|
||||||
|
config.joystick = False
|
||||||
|
config.keyboard = True
|
||||||
|
else:
|
||||||
|
config.joystick = True
|
||||||
|
config.keyboard = False
|
||||||
|
print(f"Joysticks détectés: {joystick_names}")
|
||||||
|
logger.debug(f"Joysticks détectés: {joystick_names}, utilisation du joystick par défaut.")
|
||||||
|
for idx, name in enumerate(joystick_names):
|
||||||
|
lname = name.lower()
|
||||||
|
if ("xbox" in lname) or ("x-box" in lname) or ("xinput" in lname) or ("microsoft x-box" in lname) or ("x-box 360" in lname) or ("360" in lname):
|
||||||
|
config.xbox_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
break
|
||||||
|
elif "playstation" in lname:
|
||||||
|
config.playstation_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
break
|
||||||
|
elif "nintendo" in lname:
|
||||||
|
config.nintendo_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
elif "logitech" in lname:
|
||||||
|
config.logitech_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
elif "8bitdo" in name or "8-bitdo" in lname:
|
||||||
|
config.eightbitdo_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
elif "steam" in lname:
|
||||||
|
config.steam_controller = True
|
||||||
|
logger.debug(f"Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
# Note: virtual keyboard display now depends on controller presence (config.joystick)
|
||||||
|
print(f"Generic controller detected : {name}")
|
||||||
|
logger.debug(f"Flags contrôleur: xbox={config.xbox_controller}, ps={config.playstation_controller}, nintendo={config.nintendo_controller}, eightbitdo={config.eightbitdo_controller}, steam={config.steam_controller}, trimui={config.trimui_controller}, logitech={config.logitech_controller}, generic={config.generic_controller}")
|
||||||
|
|
||||||
|
|
||||||
# Vérification des dossiers pour le débogage
|
# Vérification des dossiers pour le débogage
|
||||||
logger.debug(f"SYSTEM_FOLDER: {config.SYSTEM_FOLDER}")
|
logger.debug(f"SYSTEM_FOLDER: {config.SYSTEM_FOLDER}")
|
||||||
@@ -205,19 +265,12 @@ config.repeat_key = None
|
|||||||
config.repeat_start_time = 0
|
config.repeat_start_time = 0
|
||||||
config.repeat_last_action = 0
|
config.repeat_last_action = 0
|
||||||
|
|
||||||
# Initialisation des variables pour la popup de musique
|
|
||||||
|
|
||||||
|
|
||||||
# Réduction du bruit ALSA (VM Batocera/alsa)
|
|
||||||
try:
|
|
||||||
silence_alsa_warnings()
|
|
||||||
enable_alsa_stderr_filter()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Initialisation du mixer Pygame
|
# Initialisation du mixer Pygame
|
||||||
pygame.mixer.pre_init(44100, -16, 2, 4096)
|
pygame.mixer.pre_init(44100, -16, 2, 4096)
|
||||||
pygame.mixer.init()
|
try:
|
||||||
|
pygame.mixer.init()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Échec init mixer: {e}")
|
||||||
|
|
||||||
# Charger la configuration de la musique AVANT de lancer la musique
|
# Charger la configuration de la musique AVANT de lancer la musique
|
||||||
load_music_config()
|
load_music_config()
|
||||||
@@ -245,7 +298,7 @@ config.current_music = current_music # Met à jour la musique en cours dans con
|
|||||||
config.history = load_history()
|
config.history = load_history()
|
||||||
logger.debug(f"Historique de téléchargement : {len(config.history)} entrées")
|
logger.debug(f"Historique de téléchargement : {len(config.history)} entrées")
|
||||||
|
|
||||||
# Vérification et chargement de la configuration des contrôles
|
# Vérification et chargement de la configuration des contrôles (après mises à jour et détection manette)
|
||||||
config.controls_config = load_controls_config()
|
config.controls_config = load_controls_config()
|
||||||
|
|
||||||
# S'assurer que config.controls_config n'est jamais None
|
# S'assurer que config.controls_config n'est jamais None
|
||||||
@@ -267,14 +320,16 @@ else:
|
|||||||
# Initialisation du gamepad
|
# Initialisation du gamepad
|
||||||
joystick = None
|
joystick = None
|
||||||
if pygame.joystick.get_count() > 0:
|
if pygame.joystick.get_count() > 0:
|
||||||
joystick = pygame.joystick.Joystick(0)
|
try:
|
||||||
joystick.init()
|
joystick = pygame.joystick.Joystick(0)
|
||||||
logger.debug("Gamepad initialisé")
|
joystick.init()
|
||||||
|
logger.debug("Gamepad initialisé")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Échec initialisation gamepad: {e}")
|
||||||
|
|
||||||
|
|
||||||
# Boucle principale
|
# Boucle principale
|
||||||
async def main():
|
async def main():
|
||||||
# amazonq-ignore-next-line
|
|
||||||
global current_music, music_files, music_folder
|
global current_music, music_files, music_folder
|
||||||
logger.debug("Début main")
|
logger.debug("Début main")
|
||||||
running = True
|
running = True
|
||||||
@@ -728,7 +783,7 @@ async def main():
|
|||||||
draw_game_list(screen)
|
draw_game_list(screen)
|
||||||
if config.search_mode:
|
if config.search_mode:
|
||||||
draw_game_list(screen)
|
draw_game_list(screen)
|
||||||
if config.is_non_pc:
|
if getattr(config, 'joystick', False):
|
||||||
draw_virtual_keyboard(screen)
|
draw_virtual_keyboard(screen)
|
||||||
elif config.menu_state == "download_progress":
|
elif config.menu_state == "download_progress":
|
||||||
draw_progress_screen(screen)
|
draw_progress_screen(screen)
|
||||||
@@ -847,6 +902,14 @@ async def main():
|
|||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Erreur : {config.error_message}")
|
logger.debug(f"Erreur : {config.error_message}")
|
||||||
elif loading_step == "check_ota":
|
elif loading_step == "check_ota":
|
||||||
|
# Si mise à jour déjà vérifiée au pré-boot, sauter cette étape
|
||||||
|
if getattr(config, "update_checked", False):
|
||||||
|
logger.debug("Mises à jour déjà vérifiées au pré-boot, on saute check_for_updates()")
|
||||||
|
loading_step = "check_data"
|
||||||
|
config.current_loading_system = _("loading_downloading_games_images")
|
||||||
|
config.loading_progress = max(config.loading_progress, 50.0)
|
||||||
|
config.needs_redraw = True
|
||||||
|
continue
|
||||||
logger.debug("Exécution de check_for_updates()")
|
logger.debug("Exécution de check_for_updates()")
|
||||||
success, message = await check_for_updates()
|
success, message = await check_for_updates()
|
||||||
logger.debug(f"Résultat de check_for_updates : success={success}, message={message}")
|
logger.debug(f"Résultat de check_for_updates : success={success}, message={message}")
|
||||||
@@ -986,11 +1049,15 @@ async def main():
|
|||||||
pygame.quit()
|
pygame.quit()
|
||||||
logger.debug("Application terminée")
|
logger.debug("Application terminée")
|
||||||
|
|
||||||
result2 = subprocess.run(["batocera-es-swissknife", "--emukill"])
|
try:
|
||||||
if result2 == 0:
|
if platform.system() != "Windows":
|
||||||
logger.debug(f"Quitté avec succès")
|
result2 = subprocess.run(["batocera-es-swissknife", "--emukill"])
|
||||||
else:
|
if result2 == 0:
|
||||||
logger.debug("Error en essayant de quitter batocera-es-swissknife.")
|
logger.debug(f"Quitté avec succès")
|
||||||
|
else:
|
||||||
|
logger.debug("Error en essayant de quitter batocera-es-swissknife.")
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.debug("batocera-es-swissknife introuvable, saut de l'étape d'arrêt (environnement non Batocera)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
# Version actuelle de l'application
|
# Version actuelle de l'application
|
||||||
app_version = "2.2.0.0"
|
app_version = "2.2.0.3"
|
||||||
|
|
||||||
def get_operating_system():
|
def get_operating_system():
|
||||||
"""Renvoie le nom du système d'exploitation."""
|
"""Renvoie le nom du système d'exploitation."""
|
||||||
@@ -196,7 +196,6 @@ last_state_change_time = 0 # Temps du dernier changement d'état pour debounce
|
|||||||
debounce_delay = 200 # Délai de debounce en millisecondes
|
debounce_delay = 200 # Délai de debounce en millisecondes
|
||||||
platform_dicts = [] # Liste des dictionnaires de plateformes
|
platform_dicts = [] # Liste des dictionnaires de plateformes
|
||||||
selected_key = (0, 0) # Position du curseur dans le clavier virtuel
|
selected_key = (0, 0) # Position du curseur dans le clavier virtuel
|
||||||
is_non_pc = True # Indicateur pour plateforme non-PC (par exemple, console)
|
|
||||||
redownload_confirm_selection = 0 # Sélection pour la confirmation de redownload
|
redownload_confirm_selection = 0 # Sélection pour la confirmation de redownload
|
||||||
popup_message = "" # Message à afficher dans les popups
|
popup_message = "" # Message à afficher dans les popups
|
||||||
popup_timer = 0 # Temps restant pour le popup en millisecondes (0 = inactif)
|
popup_timer = 0 # Temps restant pour le popup en millisecondes (0 = inactif)
|
||||||
@@ -208,6 +207,18 @@ batch_download_indices = [] # File d'attente des indices de jeux à traiter en
|
|||||||
batch_in_progress = False # Indique qu'un lot est en cours
|
batch_in_progress = False # Indique qu'un lot est en cours
|
||||||
batch_pending_game = None # Données du jeu en attente de confirmation d'extension
|
batch_pending_game = None # Données du jeu en attente de confirmation d'extension
|
||||||
|
|
||||||
|
# Indicateurs d'entrée (détectés au démarrage)
|
||||||
|
joystick = False
|
||||||
|
keyboard = False
|
||||||
|
xbox_controller = False
|
||||||
|
playstation_controller = False
|
||||||
|
nintendo_controller = False
|
||||||
|
logitech_controller = False
|
||||||
|
eightbitdo_controller = False
|
||||||
|
steam_controller = False
|
||||||
|
trimui_controller = False
|
||||||
|
generic_controller = False
|
||||||
|
|
||||||
# --- Filtre plateformes (UI) ---
|
# --- Filtre plateformes (UI) ---
|
||||||
selected_filter_index = 0 # index dans la liste visible triée
|
selected_filter_index = 0 # index dans la liste visible triée
|
||||||
filter_platforms_scroll_offset = 0 # défilement si liste longue
|
filter_platforms_scroll_offset = 0 # défilement si liste longue
|
||||||
@@ -266,6 +277,9 @@ def init_font():
|
|||||||
search_font = None
|
search_font = None
|
||||||
small_font = None
|
small_font = None
|
||||||
|
|
||||||
|
# Indique si une vérification/installation des mises à jour a déjà été effectuée au démarrage
|
||||||
|
update_checked = False
|
||||||
|
|
||||||
def validate_resolution():
|
def validate_resolution():
|
||||||
"""Valide la résolution de l'écran par rapport aux capacités de l'écran."""
|
"""Valide la résolution de l'écran par rapport aux capacités de l'écran."""
|
||||||
display_info = pygame.display.Info()
|
display_info = pygame.display.Info()
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
# Jeux
|
# Jeux
|
||||||
elif config.menu_state == "game":
|
elif config.menu_state == "game":
|
||||||
games = config.filtered_games if config.filter_active or config.search_mode else config.games
|
games = config.filtered_games if config.filter_active or config.search_mode else config.games
|
||||||
if config.search_mode and config.is_non_pc:
|
if config.search_mode and getattr(config, 'joystick', False):
|
||||||
keyboard_layout = [
|
keyboard_layout = [
|
||||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||||
['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
['A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
||||||
@@ -416,7 +416,7 @@ def handle_controls(event, sources, joystick, screen):
|
|||||||
config.filter_active = bool(config.search_query)
|
config.filter_active = bool(config.search_query)
|
||||||
config.needs_redraw = True
|
config.needs_redraw = True
|
||||||
logger.debug(f"Validation du filtre avec manette: query={config.search_query}, filter_active={config.filter_active}")
|
logger.debug(f"Validation du filtre avec manette: query={config.search_query}, filter_active={config.filter_active}")
|
||||||
elif config.search_mode and not config.is_non_pc:
|
elif config.search_mode and not getattr(config, 'joystick', False):
|
||||||
# Gestion de la recherche sur PC (clavier et manette)
|
# Gestion de la recherche sur PC (clavier et manette)
|
||||||
if is_input_matched(event, "confirm"):
|
if is_input_matched(event, "confirm"):
|
||||||
config.search_mode = False
|
config.search_mode = False
|
||||||
|
|||||||
@@ -30,115 +30,7 @@ unavailable_systems = []
|
|||||||
|
|
||||||
# Cache/process flags for extensions generation/loading
|
# Cache/process flags for extensions generation/loading
|
||||||
|
|
||||||
|
|
||||||
def silence_alsa_warnings():
|
|
||||||
"""Silence ALSA stderr spam (e.g., 'underrun occurred') on Linux by overriding the error handler.
|
|
||||||
|
|
||||||
Safe no-op on non-Linux or if libasound is unavailable.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if platform.system() == "Linux":
|
|
||||||
import ctypes
|
|
||||||
import ctypes.util
|
|
||||||
lib = ctypes.util.find_library('asound')
|
|
||||||
if not lib:
|
|
||||||
return
|
|
||||||
asound = ctypes.CDLL(lib)
|
|
||||||
CErrorHandler = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p)
|
|
||||||
|
|
||||||
def py_error_handler(filename, line, function, err, fmt):
|
|
||||||
return
|
|
||||||
|
|
||||||
handler = CErrorHandler(py_error_handler)
|
|
||||||
try:
|
|
||||||
asound.snd_lib_error_set_handler(handler)
|
|
||||||
logger.info("ALSA warnings silenced via snd_lib_error_set_handler")
|
|
||||||
except Exception as inner:
|
|
||||||
logger.debug(f"snd_lib_error_set_handler not available: {inner}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Unable to silence ALSA warnings: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def enable_alsa_stderr_filter():
|
|
||||||
"""Filter ALSA 'underrun occurred' spam from stderr by intercepting FD 2.
|
|
||||||
|
|
||||||
Works on Linux by routing stderr through a pipe and dropping matching lines.
|
|
||||||
No-op on non-Linux systems. Safe to call multiple times; installs once.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if platform.system() != "Linux":
|
|
||||||
return
|
|
||||||
# Avoid double-install
|
|
||||||
if getattr(config, "_alsa_filter_installed", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
import os as _os
|
|
||||||
import threading as _threading
|
|
||||||
|
|
||||||
patterns = [
|
|
||||||
"ALSA lib pcm.c:",
|
|
||||||
"snd_pcm_recover) underrun occurred",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Save original stderr fd and create pipe
|
|
||||||
save_fd = _os.dup(2)
|
|
||||||
rfd, wfd = _os.pipe()
|
|
||||||
_os.dup2(wfd, 2) # redirect current process stderr to pipe writer
|
|
||||||
_os.close(wfd)
|
|
||||||
|
|
||||||
stop_event = _threading.Event()
|
|
||||||
|
|
||||||
def _reader():
|
|
||||||
try:
|
|
||||||
with _os.fdopen(rfd, 'rb', buffering=0) as r, _os.fdopen(save_fd, 'wb', buffering=0) as orig:
|
|
||||||
buf = b''
|
|
||||||
while not stop_event.is_set():
|
|
||||||
chunk = r.read(1024)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
buf += chunk
|
|
||||||
while b"\n" in buf:
|
|
||||||
line, buf = buf.split(b"\n", 1)
|
|
||||||
try:
|
|
||||||
s = line.decode('utf-8', errors='ignore')
|
|
||||||
if not any(p in s for p in patterns):
|
|
||||||
orig.write(line + b"\n")
|
|
||||||
orig.flush()
|
|
||||||
except Exception:
|
|
||||||
# Swallow any decode/write errors; keep filtering
|
|
||||||
pass
|
|
||||||
if buf:
|
|
||||||
try:
|
|
||||||
s = buf.decode('utf-8', errors='ignore')
|
|
||||||
if not any(p in s for p in patterns):
|
|
||||||
orig.write(buf)
|
|
||||||
orig.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
try:
|
|
||||||
# Best-effort: restore original stderr on failure
|
|
||||||
_os.dup2(save_fd, 2)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
logger.debug(f"ALSA stderr filter reader error: {e}")
|
|
||||||
|
|
||||||
t = _threading.Thread(target=_reader, daemon=True)
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def _restore():
|
|
||||||
try:
|
|
||||||
_os.dup2(save_fd, 2)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
stop_event.set()
|
|
||||||
|
|
||||||
# Expose restore in config for future use if needed
|
|
||||||
config._alsa_filter_installed = True
|
|
||||||
config._alsa_filter_restore = _restore
|
|
||||||
logger.info("ALSA underrun stderr filter installed")
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Unable to install ALSA stderr filter: {e}")
|
|
||||||
def restart_application(delay_ms: int = 2000):
|
def restart_application(delay_ms: int = 2000):
|
||||||
"""Schedule a restart with a visible popup; actual restart happens in the main loop.
|
"""Schedule a restart with a visible popup; actual restart happens in the main loop.
|
||||||
|
|
||||||
@@ -181,22 +73,6 @@ _extensions_cache = None # type: ignore
|
|||||||
_extensions_json_regenerated = False
|
_extensions_json_regenerated = False
|
||||||
|
|
||||||
|
|
||||||
# Détection système non-PC
|
|
||||||
def detect_non_pc():
|
|
||||||
arch = platform.machine()
|
|
||||||
try:
|
|
||||||
result = subprocess.run(["batocera-es-swissknife", "--arch"], capture_output=True, text=True, timeout=2)
|
|
||||||
if result.returncode == 0:
|
|
||||||
arch = result.stdout.strip()
|
|
||||||
#logger.debug(f"Architecture via batocera-es-swissknife: {arch}")
|
|
||||||
except (subprocess.SubprocessError, FileNotFoundError):
|
|
||||||
logger.debug(f"batocera-es-swissknife non disponible, utilisation de platform.machine(): {arch}")
|
|
||||||
|
|
||||||
is_non_pc = arch not in ["x86_64", "amd64", "AMD64"]
|
|
||||||
logger.debug(f"Système détecté: {platform.system()}, architecture: {arch}, is_non_pc={is_non_pc}")
|
|
||||||
return is_non_pc
|
|
||||||
|
|
||||||
|
|
||||||
# Fonction pour charger le fichier JSON des extensions supportées
|
# Fonction pour charger le fichier JSON des extensions supportées
|
||||||
def load_extensions_json():
|
def load_extensions_json():
|
||||||
"""Charge le JSON des extensions supportées.
|
"""Charge le JSON des extensions supportées.
|
||||||
|
|||||||
Reference in New Issue
Block a user