Add support for Anbernic RG35XX controller and local custom sources ZIP handling
This commit is contained in:
+38
-2
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user