Add support for Anbernic RG35XX controller and local custom sources ZIP handling

This commit is contained in:
retrogamesets
2025-09-16 00:23:48 +02:00
parent d5bc56be64
commit 158be30667
6 changed files with 203 additions and 46 deletions
+38 -2
View File
@@ -219,6 +219,12 @@ else:
logger.debug(f"Joysticks détectés: YES") logger.debug(f"Joysticks détectés: YES")
for idx, name in enumerate(joystick_names): for idx, name in enumerate(joystick_names):
lname = name.lower() lname = name.lower()
# Détection Anbernic RG35XX
if ("rg35xx" in lname):
config.anbernic_rg35xx_controller = True
logger.debug(f"Anbernic Controller detected : {name}")
print(f"Controller detected : {name}")
# ne pas break ici pour permettre une détection plus spécifique (xbox elite) si nécessaire
# Détection spécifique Elite AVANT la détection générique Xbox # Détection spécifique Elite AVANT la détection générique Xbox
if ("microsoft xbox controller" in lname): if ("microsoft xbox controller" in lname):
config.xbox_elite_controller = True config.xbox_elite_controller = True
@@ -958,11 +964,41 @@ async def main():
try: try:
zip_path = os.path.join(config.SAVE_FOLDER, "data_download.zip") zip_path = os.path.join(config.SAVE_FOLDER, "data_download.zip")
headers = {'User-Agent': 'Mozilla/5.0'} headers = {'User-Agent': 'Mozilla/5.0'}
# Support des sources custom locales: prioriser un ZIP présent dans SAVE_FOLDER
try:
from rgsx_settings import get_sources_mode
from rgsx_settings import find_local_custom_sources_zip
mode = get_sources_mode()
except Exception:
mode = "rgsx"
find_local_custom_sources_zip = lambda: None # type: ignore
local_zip = find_local_custom_sources_zip() if mode == "custom" else None
if local_zip and os.path.isfile(local_zip):
# Extraire directement depuis le ZIP local
config.current_loading_system = _("loading_extracting_data")
config.loading_progress = 60.0
config.needs_redraw = True
dest_dir = config.SAVE_FOLDER
try:
success, message = extract_zip_data(local_zip, dest_dir, local_zip)
if success:
logger.debug(f"Extraction locale réussie : {message}")
config.loading_progress = 70.0
config.needs_redraw = True
else:
raise Exception(f"Échec de l'extraction locale : {message}")
except Exception as de:
logger.error(f"Erreur extraction ZIP local custom: {de}")
config.popup_message = _("sources_mode_custom_download_error")
config.popup_timer = 5000
# Continuer avec jeux vides
else:
# Déterminer l'URL à utiliser selon le mode (RGSX ou custom) # Déterminer l'URL à utiliser selon le mode (RGSX ou custom)
sources_zip_url = get_sources_zip_url(OTA_data_ZIP) sources_zip_url = get_sources_zip_url(OTA_data_ZIP)
if sources_zip_url is None: if sources_zip_url is None:
# Mode custom sans URL valide -> pas de téléchargement, jeux vides # Mode custom sans fichier local ni URL valide -> pas de téléchargement, jeux vides
logger.warning("Mode custom actif mais aucune URL valide fournie. Liste de jeux vide.") logger.warning("Mode custom actif mais aucun ZIP local et aucune URL valide fournie. Liste de jeux vide.")
config.popup_message = _("sources_mode_custom_missing_url").format(config.RGSX_SETTINGS_PATH) config.popup_message = _("sources_mode_custom_missing_url").format(config.RGSX_SETTINGS_PATH)
config.popup_timer = 5000 config.popup_timer = 5000
else: else:
@@ -0,0 +1,72 @@
{
"confirm": {
"type": "button",
"button": 3,
"display": "A"
},
"cancel": {
"type": "button",
"button": 4,
"display": "B"
},
"up": {
"type": "hat",
"value": [0, 1],
"display": "↑"
},
"down": {
"type": "hat",
"value": [0, -1],
"display": "↓"
},
"left": {
"type": "hat",
"value": [-1, 0],
"display": "←"
},
"right": {
"type": "hat",
"value": [1, 0],
"display": "→"
},
"start": {
"type": "button",
"button": 10,
"display": "Start"
},
"filter": {
"type": "button",
"button": 9,
"display": "Select"
},
"page_up": {
"type": "button",
"button": 7,
"display": "LT"
},
"page_down": {
"type": "button",
"button": 8,
"display": "RT"
},
"history": {
"type": "button",
"button": 6,
"display": "Y"
},
"clear_history": {
"type": "button",
"button": 5,
"display": "X"
},
"delete": {
"type": "button",
"button": 13,
"display": "LB"
},
"space": {
"type": "button",
"button": 14,
"display": "RB"
}
}
+1
View File
@@ -190,6 +190,7 @@ steam_controller = False
trimui_controller = False trimui_controller = False
generic_controller = False generic_controller = False
xbox_elite_controller = False # Flag spécifique manette Xbox Elite xbox_elite_controller = False # Flag spécifique manette Xbox Elite
anbernic_rg35xx_controller = False # Flag spécifique Anbernic RG3xxx
# --- 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
+2
View File
@@ -113,6 +113,8 @@ def load_controls_config(path=CONTROLS_CONFIG_PATH):
candidates.append('nintendo_controller.json') candidates.append('nintendo_controller.json')
if getattr(config, 'eightbitdo_controller', False): if getattr(config, 'eightbitdo_controller', False):
candidates.append('8bitdo_controller.json') candidates.append('8bitdo_controller.json')
if getattr(config, 'anbernic_rg35xx_controller', False):
candidates.append('anbernic_rg34xx_sp_controller.json')
# Fallbacks génériques # Fallbacks génériques
if 'generic_controller.json' not in candidates: if 'generic_controller.json' not in candidates:
candidates.append('generic_controller.json') candidates.append('generic_controller.json')
+34
View File
@@ -197,6 +197,40 @@ def get_sources_zip_url(fallback_url):
return None return None
return fallback_url return fallback_url
def find_local_custom_sources_zip():
"""Recherche un fichier ZIP local à la racine de SAVE_FOLDER pour le mode custom.
Priorité sur quelques noms courants afin d'éviter toute ambiguïté.
Retourne le chemin absolu du ZIP si trouvé, sinon None.
"""
try:
from config import SAVE_FOLDER
candidates = [
"games.zip",
"custom_sources.zip",
"rgsx_custom_sources.zip",
"data.zip",
]
if not os.path.isdir(SAVE_FOLDER):
return None
for name in candidates:
p = os.path.join(SAVE_FOLDER, name)
if os.path.isfile(p):
return p
# Option avancée: prendre le plus récent *.zip si aucun nom connu trouvé
try:
zips = [os.path.join(SAVE_FOLDER, f) for f in os.listdir(SAVE_FOLDER) if f.lower().endswith('.zip')]
zips = [z for z in zips if os.path.isfile(z)]
if zips:
newest = max(zips, key=lambda z: os.path.getmtime(z))
return newest
except Exception:
pass
return None
except Exception as e:
logger.debug(f"find_local_custom_sources_zip error: {e}")
return None
# ----------------------- Unsupported platforms toggle ----------------------- # # ----------------------- Unsupported platforms toggle ----------------------- #
def get_show_unsupported_platforms(settings=None): def get_show_unsupported_platforms(settings=None):
+19 -7
View File
@@ -37,13 +37,9 @@ PROMPTS = [
"JOYSTICK_LEFT_DOWN - MOVE DOWN", "JOYSTICK_LEFT_DOWN - MOVE DOWN",
"JOYSTICK_LEFT_LEFT - MOVE LEFT", "JOYSTICK_LEFT_LEFT - MOVE LEFT",
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT", "JOYSTICK_LEFT_RIGHT - MOVE RIGHT",
# Right stick directions
"JOYSTICK_RIGHT_UP - MOVE U P",
"JOYSTICK_RIGHT_DOWN - MOVE DOWN",
"JOYSTICK_RIGHT_LEFT - MOVE LEFT",
"JOYSTICK_RIGHT_RIGHT - MOVE RIGHT",
] ]
INPUT_TIMEOUT_SECONDS = 10 # Temps max par entrée avant "ignored"
# --- Minimal on-screen console (Pygame window) --- # --- Minimal on-screen console (Pygame window) ---
SURFACE = None # type: ignore SURFACE = None # type: ignore
@@ -108,6 +104,8 @@ def init_joystick() -> pygame.joystick.Joystick:
js.init() js.init()
name = js.get_name() name = js.get_name()
log(f"Using joystick 0: {name}") log(f"Using joystick 0: {name}")
log("")
log(f"Note: each input will auto-ignore after {INPUT_TIMEOUT_SECONDS}s if not present (e.g. missing L2/R2)")
return js return js
@@ -147,7 +145,7 @@ def wait_for_stable(js: pygame.joystick.Joystick, settle_ms: int = 250, deadband
pygame.time.wait(10) pygame.time.wait(10)
def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_threshold: float = 0.6) -> Tuple[str, Any]: def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_threshold: float = 0.6, timeout_sec: int = INPUT_TIMEOUT_SECONDS) -> Tuple[str, Any]:
"""Wait for a joystick event for the given logical control. """Wait for a joystick event for the given logical control.
Returns a tuple of (kind, data): Returns a tuple of (kind, data):
@@ -158,10 +156,18 @@ def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_thresho
# Ensure prior motion has settled to avoid capturing a release # Ensure prior motion has settled to avoid capturing a release
wait_for_stable(js) wait_for_stable(js)
log("") log("")
log(f"Press {logical_name} (ESC to skip, close window to quit)…") deadline = time.time() + max(1, int(timeout_sec))
log(f"Press {logical_name} (Wait {timeout_sec}s to skip/ignore) if not present")
# Flush old events # Flush old events
pygame.event.clear() pygame.event.clear()
while True: while True:
# Update window title with countdown if we have a surface
try:
remaining = int(max(0, deadline - time.time()))
if SURFACE is not None:
pygame.display.set_caption(f"Controller Tester — {logical_name} — {remaining}s left")
except Exception:
pass
for event in pygame.event.get(): for event in pygame.event.get():
# Keyboard helpers # Keyboard helpers
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
@@ -195,6 +201,10 @@ def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_thresho
return ("axis", {"axis": axis, "direction": direction, "raw": value}) return ("axis", {"axis": axis, "direction": direction, "raw": value})
draw_log() draw_log()
# Timeout?
if time.time() >= deadline:
log(f"Ignored {logical_name} (timeout {timeout_sec}s)")
return ("ignored", None)
time.sleep(0.005) time.sleep(0.005)
@@ -213,6 +223,8 @@ def write_log(path: str, mapping: Dict[str, Tuple[str, Any]], device_name: str)
lines.append(f"{name} = AXIS {ax} dir {direction}\n") lines.append(f"{name} = AXIS {ax} dir {direction}\n")
elif kind == "skipped": elif kind == "skipped":
lines.append(f"{name} = SKIPPED\n") lines.append(f"{name} = SKIPPED\n")
elif kind == "ignored":
lines.append(f"{name} = IGNORED\n")
else: else:
lines.append(f"{name} = UNKNOWN {data}\n") lines.append(f"{name} = UNKNOWN {data}\n")