forked from Mirrors/RGSX
v2.2.2.2
- add new instructions on menus to describe each function - upgrade controller_debug.pygame file to create a controller support - update command-line interface to be more effiscient and readable
This commit is contained in:
214
README_CLI.md
214
README_CLI.md
@@ -2,11 +2,114 @@
|
|||||||
|
|
||||||
Ce guide couvre toutes les commandes disponibles du CLI et fournit des exemples prêts à copier (Windows PowerShell).
|
Ce guide couvre toutes les commandes disponibles du CLI et fournit des exemples prêts à copier (Windows PowerShell).
|
||||||
|
|
||||||
|
## Nouveau: mode interactif
|
||||||
|
Vous pouvez maintenant lancer une session interactive et enchaîner les commandes sans retaper `python rgsx_cli.py` à chaque fois :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py
|
||||||
|
```
|
||||||
|
Vous verrez :
|
||||||
|
```
|
||||||
|
RGSX CLI interactive mode. Type 'help' for commands, 'exit' to quit.
|
||||||
|
rgsx>
|
||||||
|
```
|
||||||
|
Dans cette session tapez directement les sous-commandes :
|
||||||
|
```
|
||||||
|
rgsx> platforms
|
||||||
|
rgsx> games --platform snes --search mario
|
||||||
|
rgsx> download --platform snes --game "Super Mario World (USA).zip"
|
||||||
|
rgsx> history --tail 10
|
||||||
|
rgsx> exit
|
||||||
|
```
|
||||||
|
Extras :
|
||||||
|
- `help` ou `?` affiche l’aide globale.
|
||||||
|
- `exit` ou `quit` quitte la session.
|
||||||
|
- `--verbose` une fois active les logs détaillés pour toute la session.
|
||||||
|
|
||||||
|
## Tableau formaté (platforms)
|
||||||
|
La commande `platforms` affiche maintenant un tableau ASCII à largeur fixe (sauf avec `--json`) :
|
||||||
|
```
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
| Nom de plateforme | Dossier |
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
| Nintendo Entertainment System | nes |
|
||||||
|
| Super Nintendo Entertainment.. | snes |
|
||||||
|
| Sega Mega Drive | megadrive |
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
```
|
||||||
|
Colonnes : 30 caractères pour le nom, 15 pour le dossier (troncature par `...`).
|
||||||
|
|
||||||
|
## Aliases & synonymes d’options (mis à jour)
|
||||||
|
Aliases des sous-commandes :
|
||||||
|
- `platforms` → `p`
|
||||||
|
- `games` → `g`
|
||||||
|
- `download` → `dl`
|
||||||
|
- `clear-history` → `clear`
|
||||||
|
|
||||||
|
Options équivalentes (toutes les formes listées sont acceptées) :
|
||||||
|
- Plateforme : `--platform`, `--p`, `-p`
|
||||||
|
- Jeu : `--game`, `--g`, `-g`
|
||||||
|
- Recherche : `--search`, `--s`, `-s`
|
||||||
|
- Forcer (download) : `--force`, `-f`
|
||||||
|
- Mode interactif (download) : `--interactive`, `-i`
|
||||||
|
|
||||||
|
Exemples avec alias :
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py dl -p snes -g "Super Mario World (USA).zip"
|
||||||
|
python rgsx_cli.py g --p snes --s mario
|
||||||
|
python rgsx_cli.py p --json
|
||||||
|
python rgsx_cli.py clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sélection ambiguë lors d’un download (nouveau tableau)
|
||||||
|
Quand vous tentez un téléchargement avec un titre non exact et que le mode interactif est actif (TTY ou `--interactive`), les correspondances s’affichent en tableau :
|
||||||
|
```
|
||||||
|
No exact result found for this game: mario super yoshi
|
||||||
|
Select a match to download:
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
| # | Title | Size |
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
| 1 | Super Mario - Yoshi Island (Japan).zip | 3.2M |
|
||||||
|
| 2 | Super Mario - Yoshi Island (Japan) (Rev 1).zip | 3.2M |
|
||||||
|
| 3 | Super Mario - Yoshi Island (Japan) (Rev 2).zip | 3.2M |
|
||||||
|
| 4 | Super Mario World 2 - Yoshi's Island (USA).zip | 3.3M |
|
||||||
|
| 5 | Super Mario - Yoshi Island (Japan) (Beta) (1995-07-10).zip | 3.1M |
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
Enter number (or press Enter to cancel):
|
||||||
|
```
|
||||||
|
Si vous annulez ou que le mode interactif n’est pas actif, un tableau similaire est affiché (sans le prompt) suivi d’un conseil.
|
||||||
|
|
||||||
|
## Recherche améliorée (multi‑tokens) pour `games`
|
||||||
|
L’option `--search` / `--s` / `-s` utilise maintenant la même logique de classement que les suggestions du download :
|
||||||
|
1. Correspondance sous-chaîne (position la plus tôt) — priorité 0
|
||||||
|
2. Séquence de tokens dans l’ordre (non contiguë) — priorité 1 (écart le plus faible)
|
||||||
|
3. Tous les tokens présents dans n’importe quel ordre — priorité 2 (ensemble de tokens plus petit privilégié)
|
||||||
|
|
||||||
|
Les doublons sont dédupliqués en gardant le meilleur score. Ainsi une requête :
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py games --p snes --s "super mario yoshi"
|
||||||
|
```
|
||||||
|
affiche toutes les variantes pertinentes de "Super Mario World 2 - Yoshi's Island" même si l’ordre des mots diffère.
|
||||||
|
|
||||||
|
Exemple de sortie :
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
| Game Title | Size |
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
| Super Mario World 2 - Yoshi's Island (USA).zip | 3.3M |
|
||||||
|
| Super Mario World 2 - Yoshi's Island (Europe) (En,Fr,De).zip | 3.3M |
|
||||||
|
| Super Mario - Yoshi Island (Japan).zip | 3.2M |
|
||||||
|
| Super Mario - Yoshi Island (Japan) (Rev 1).zip | 3.2M |
|
||||||
|
| Super Mario - Yoshi Island (Japan) (Rev 2).zip | 3.2M |
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
```
|
||||||
|
Si aucun résultat n’est trouvé, seul l’en-tête est affiché puis un message.
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
- Python installé et accessible (le projet utilise un mode headless; aucune fenêtre ne s’ouvrira).
|
- Python installé et accessible (le projet utilise un mode headless; aucune fenêtre ne s’ouvrira).
|
||||||
- Exécuter depuis le dossier contenant `rgsx_cli.py`.
|
- Exécuter depuis le dossier contenant `rgsx_cli.py`.
|
||||||
|
|
||||||
## Syntaxe générale
|
## Syntaxe générale (mode classique)
|
||||||
Les options globales peuvent être placées avant ou après la sous-commande.
|
Les options globales peuvent être placées avant ou après la sous-commande.
|
||||||
|
|
||||||
- Forme 1:
|
- Forme 1:
|
||||||
@@ -18,73 +121,69 @@ Les options globales peuvent être placées avant ou après la sous-commande.
|
|||||||
python rgsx_cli.py <commande> [options] [--verbose] [--force-update|-force-update]
|
python rgsx_cli.py <commande> [options] [--verbose] [--force-update|-force-update]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--verbose` active les logs détaillés (DEBUG) sur la sortie standard d’erreur.
|
- `--verbose` active les logs détaillés (DEBUG) sur stderr.
|
||||||
- `--force-update` (ou `-force-update`) purge les données locales et force le re-téléchargement du pack de données (systems_list, games/*.json, images).
|
- `--force-update` (ou `-force-update`) purge les données locales et force le re-téléchargement du pack de données (systems_list, games/*.json, images).
|
||||||
|
|
||||||
Lorsque les données sources sont manquantes, le CLI télécharge et extrait automatiquement le pack (avec une barre de progression).
|
Quand les données sources sont manquantes, le CLI télécharge et extrait automatiquement le pack (avec progression).
|
||||||
|
|
||||||
## Commandes
|
## Commandes
|
||||||
|
|
||||||
### 1) platforms — lister les plateformes
|
### 1) platforms (`platforms` / `p`) — lister les plateformes
|
||||||
- Options:
|
- Options:
|
||||||
- `--json`: sortie JSON (objets `{ name, folder }`).
|
- `--json`: sortie JSON (objets `{ name, folder }`).
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py platforms
|
python rgsx_cli.py platforms
|
||||||
python rgsx_cli.py platforms --json
|
python rgsx_cli.py p --json
|
||||||
python rgsx_cli.py --verbose platforms
|
python rgsx_cli.py --verbose p
|
||||||
python rgsx_cli.py platforms --verbose
|
python rgsx_cli.py p --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
Sortie texte: une ligne par plateforme, au format `Nom<TAB>Dossier`.
|
Sortie texte: une ligne par plateforme, au format `Nom<TAB>Dossier`.
|
||||||
|
|
||||||
### 2) games — lister les jeux d’une plateforme
|
### 2) games (`games` / `g`) — lister les jeux d’une plateforme
|
||||||
- Options:
|
- Options:
|
||||||
- `--platform <nom_ou_dossier>` (ex: `n64` ou "Nintendo 64").
|
- `--platform | --p | -p <nom_ou_dossier>` (ex: `n64` ou "Nintendo 64").
|
||||||
- `--search <texte>`: filtre par sous-chaîne dans le nom du jeu.
|
- `--search | --s | -s <texte>`: filtre par sous-chaîne.
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py games --platform n64
|
python rgsx_cli.py games --platform n64
|
||||||
python rgsx_cli.py games --platform "Nintendo 64" --search zelda
|
python rgsx_cli.py g --p "Nintendo 64" --s zelda
|
||||||
python rgsx_cli.py games --platform n64 --verbose
|
python rgsx_cli.py g -p n64 --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
Remarques:
|
Remarques:
|
||||||
- La plateforme est résolue par nom affiché (platform_name) ou par dossier (folder), sans tenir compte de la casse.
|
- La plateforme est résolue par nom affiché (platform_name) ou dossier, insensible à la casse.
|
||||||
|
|
||||||
### 3) download — télécharger un jeu
|
### 3) download (`download` / `dl`) — télécharger un jeu
|
||||||
- Options:
|
- Options:
|
||||||
- `--platform <nom_ou_dossier>`
|
- `--platform | --p | -p <nom_ou_dossier>`
|
||||||
- `--game "<titre exact ou partiel>"`
|
- `--game | --g | -g "<titre exact ou partiel>"`
|
||||||
- `--force`: ignorer l’avertissement si l’extension du fichier n’est pas répertoriée comme supportée pour la plateforme.
|
- `--force | -f`: ignorer l’avertissement d’extension non supportée.
|
||||||
|
- `--interactive | -i`: choisir un titre parmi des correspondances quand aucun exact n’est trouvé.
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
```powershell
|
```powershell
|
||||||
# Titre exact
|
# Titre exact
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
python rgsx_cli.py dl --p n64 --g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
||||||
|
|
||||||
# Correspondance partielle
|
# Titre partiel (sélection numérotée si aucun exact)
|
||||||
# Si aucun titre exact n’est trouvé, le CLI n’autosélectionne plus. Il affiche des correspondances possibles.
|
python rgsx_cli.py dl -p n64 -g "Ocarina of Time (Beta)"
|
||||||
python rgsx_cli.py download --platform n64 --game "Ocarina of Time (Beta)"
|
|
||||||
# ➜ Le CLI proposera une liste de titres potentiels (à relancer ensuite avec le titre exact).
|
|
||||||
|
|
||||||
Mode interactif par défaut:
|
# Forcer malgré extension
|
||||||
- Si aucun titre exact n’est trouvé et que vous êtes dans un terminal interactif (TTY), une liste numérotée s’affiche automatiquement pour choisir un match et lancer le téléchargement.
|
python rgsx_cli.py dl -p snes -g "pack_roms.rar" -f
|
||||||
|
|
||||||
# Forcer si l’extension semble non supportée (ex: .rar)
|
# Verbose après sous-commande
|
||||||
python rgsx_cli.py download --platform snes --game "pack_roms.rar" --force
|
python rgsx_cli.py dl -p n64 -g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip" --verbose
|
||||||
|
|
||||||
# Verbose positionné après la sous-commande
|
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip" --verbose
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Pendant le téléchargement, une progression en pourcentage, taille (MB) et vitesse (MB/s) s’affiche. Le résultat final est également écrit dans l’historique.
|
Pendant le téléchargement: progression %, taille (MB), vitesse (MB/s). Résultat final aussi dans l’historique.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Les ROMs sont enregistrées dans le dossier de la plateforme correspondante (ex: `R:\roms\n64`).
|
- Les ROMs sont enregistrées dans le dossier plateforme correspondant (ex: `R:\roms\n64`).
|
||||||
- Si le fichier est une archive (zip/rar) et que la plateforme ne supporte pas l’extension, un avertissement est affiché (vous pouvez utiliser `--force`).
|
- Si le fichier est une archive (zip/rar) et que la plateforme ne supporte pas l’extension, un avertissement apparaît (utiliser `--force`).
|
||||||
|
|
||||||
### 4) history — afficher l’historique
|
### 4) history — afficher l’historique
|
||||||
- Options:
|
- Options:
|
||||||
@@ -98,62 +197,55 @@ python rgsx_cli.py history --tail 20
|
|||||||
python rgsx_cli.py history --json
|
python rgsx_cli.py history --json
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5) clear-history — vider l’historique
|
### 5) clear-history (`clear-history` / `clear`) — vider l’historique
|
||||||
Exemple:
|
Exemple:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py clear-history
|
python rgsx_cli.py clear
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option globale: --force-update — purge + re-téléchargement des données
|
### Option globale: --force-update — purge + re-téléchargement des données
|
||||||
- Supprime `systems_list.json`, le dossier `games/` et `images/`, puis télécharge/extrait à nouveau le pack de données.
|
- Supprime `systems_list.json`, `games/`, `images/` puis retélécharge/extrait le pack.
|
||||||
|
|
||||||
Exemples:
|
Exemples:
|
||||||
```powershell
|
```powershell
|
||||||
# Sans sous-commande: purge + re-téléchargement puis sortie
|
|
||||||
python rgsx_cli.py --force-update
|
python rgsx_cli.py --force-update
|
||||||
|
python rgsx_cli.py p --force-update
|
||||||
# Placé après une sous-commande (accepté aussi)
|
|
||||||
python rgsx_cli.py platforms --force-update
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Comportements et conseils
|
## Comportements et conseils
|
||||||
- Résolution de plateforme: par nom affiché ou dossier, insensible à la casse. Pour la commande `games` et `download`, une recherche par sous-chaîne est utilisée si la correspondance exacte n’est pas trouvée.
|
- Résolution plateforme: par nom affiché ou dossier, insensible à la casse.
|
||||||
- Logs `--verbose`: principalement utiles lors des téléchargements/extractions; émis en DEBUG.
|
- `--verbose`: utile surtout pour téléchargements/extractions.
|
||||||
- Téléchargement de données manquantes: automatique avec progression harmonisée (téléchargement puis extraction).
|
- Données manquantes: téléchargement + extraction automatiques.
|
||||||
- Codes de sortie (indicatif):
|
- Codes de sortie (indicatif):
|
||||||
- `0`: succès
|
- `0`: succès
|
||||||
- `1`: échec du téléchargement/erreur générique
|
- `1`: échec téléchargement/erreur générique
|
||||||
- `2`: plateforme introuvable
|
- `2`: plateforme introuvable
|
||||||
- `3`: jeu introuvable
|
- `3`: jeu introuvable
|
||||||
- `4`: extension non supportée (sans `--force`)
|
- `4`: extension non supportée (sans `--force`)
|
||||||
|
|
||||||
## Exemples rapides (copier-coller)
|
## Exemples rapides (copier-coller)
|
||||||
```powershell
|
```powershell
|
||||||
# Lister plateformes (texte)
|
# Démarrer le shell interactif
|
||||||
python rgsx_cli.py platforms
|
python rgsx_cli.py
|
||||||
|
|
||||||
|
# Lister plateformes (alias)
|
||||||
|
python rgsx_cli.py p
|
||||||
|
|
||||||
# Lister plateformes (JSON)
|
# Lister plateformes (JSON)
|
||||||
python rgsx_cli.py platforms --json
|
python rgsx_cli.py p --json
|
||||||
|
|
||||||
# Lister jeux N64 avec filtre
|
# Lister jeux N64 avec filtre (synonymes)
|
||||||
python rgsx_cli.py games --platform n64 --search zelda
|
python rgsx_cli.py g --p n64 --s zelda
|
||||||
|
|
||||||
# Télécharger un jeu N64 (titre exact)
|
# Télécharger un jeu N64 (titre exact) avec alias
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
python rgsx_cli.py dl --p n64 --g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
||||||
|
|
||||||
# Télécharger un jeu N64 (titre aproximatif)
|
# Télécharger (titre partiel) + sélection
|
||||||
python rgsx_cli.py download --platform n64 --game "Ocarina of Time"
|
python rgsx_cli.py dl -p n64 -g "Ocarina of Time"
|
||||||
Resultat (exemple) :
|
|
||||||
No exact result found for this game: Ocarina of Time
|
|
||||||
Select a match to download:
|
|
||||||
1. Legend of Zelda, The - Ocarina of Time (Europe) (Beta) (2003-02-13) (GameCube).zip
|
|
||||||
2. Legend of Zelda, The - Ocarina of Time (Europe) (Beta) (2003-02-21) (GameCube) (Debug).zip
|
|
||||||
...
|
|
||||||
15. F-Zero X (USA) (Beta) (The Legend of Zelda - Ocarina of Time leftover data).zip
|
|
||||||
|
|
||||||
# Voir l’historique (20 dernières entrées)
|
# Historique (20 dernières entrées)
|
||||||
python rgsx_cli.py history --tail 20
|
python rgsx_cli.py history --tail 20
|
||||||
|
|
||||||
# Purger et recharger les données de listes des systèmes et des jeux
|
# Purger et recharger le pack
|
||||||
python rgsx_cli.py --force-update
|
python rgsx_cli.py --force-update
|
||||||
```
|
```
|
||||||
|
|||||||
174
README_CLI_EN.md
174
README_CLI_EN.md
@@ -6,7 +6,110 @@ This guide covers all available CLI commands with copy-ready Windows PowerShell
|
|||||||
- Python installed and on PATH (the app runs in headless mode; no window will open).
|
- Python installed and on PATH (the app runs in headless mode; no window will open).
|
||||||
- Run commands from the folder that contains `rgsx_cli.py`.
|
- Run commands from the folder that contains `rgsx_cli.py`.
|
||||||
|
|
||||||
## General syntax
|
## Quick interactive mode (new)
|
||||||
|
You can now start an interactive shell once and issue multiple commands without retyping `python rgsx_cli.py` each time:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py
|
||||||
|
```
|
||||||
|
You will see a prompt like:
|
||||||
|
```
|
||||||
|
RGSX CLI interactive mode. Type 'help' for commands, 'exit' to quit.
|
||||||
|
rgsx>
|
||||||
|
```
|
||||||
|
Inside this shell type subcommands exactly as you would after `python rgsx_cli.py`:
|
||||||
|
```
|
||||||
|
rgsx> platforms
|
||||||
|
rgsx> games --platform snes --search mario
|
||||||
|
rgsx> download --platform snes --game "Super Mario World (USA).zip"
|
||||||
|
rgsx> history --tail 10
|
||||||
|
rgsx> exit
|
||||||
|
```
|
||||||
|
Extras:
|
||||||
|
- `help` or `?` prints the global help.
|
||||||
|
- `exit` or `quit` leaves the shell.
|
||||||
|
- `--verbose` once sets persistent verbose logging for the rest of the session.
|
||||||
|
|
||||||
|
## Formatted table output (platforms)
|
||||||
|
The `platforms` command now renders a fixed-width ASCII table (unless `--json` is used):
|
||||||
|
```
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
| Platform Name | Folder |
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
| Nintendo Entertainment System | nes |
|
||||||
|
| Super Nintendo Entertainment.. | snes |
|
||||||
|
| Sega Mega Drive | megadrive |
|
||||||
|
+--------------------------------+-----------------+
|
||||||
|
```
|
||||||
|
Columns: 30 chars for name, 15 for folder (values longer are truncated with `...`).
|
||||||
|
|
||||||
|
## Aliases & option synonyms (updated)
|
||||||
|
Subcommand aliases:
|
||||||
|
- `platforms` → `p`
|
||||||
|
- `games` → `g`
|
||||||
|
- `download` → `dl`
|
||||||
|
- `clear-history` → `clear`
|
||||||
|
|
||||||
|
Option aliases (all shown forms are accepted; they are equivalent):
|
||||||
|
- Platform: `--platform`, `--p`, `-p`
|
||||||
|
- Game: `--game`, `--g`, `-g`
|
||||||
|
- Search: `--search`, `--s`, `-s`
|
||||||
|
- Force (download): `--force`, `-f`
|
||||||
|
- Interactive (download): `--interactive`, `-i`
|
||||||
|
|
||||||
|
Examples with aliases:
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py dl -p snes -g "Super Mario World (USA).zip"
|
||||||
|
python rgsx_cli.py g --p snes --s mario
|
||||||
|
python rgsx_cli.py p --json
|
||||||
|
python rgsx_cli.py clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ambiguous download selection (new table)
|
||||||
|
When you attempt a download with a non-exact title and interactive mode is active (TTY or `--interactive`), matches are displayed in a table:
|
||||||
|
```
|
||||||
|
No exact result found for this game: mario super yoshi
|
||||||
|
Select a match to download:
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
| # | Title | Size |
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
| 1 | Super Mario - Yoshi Island (Japan).zip | 3.2M |
|
||||||
|
| 2 | Super Mario - Yoshi Island (Japan) (Rev 1).zip | 3.2M |
|
||||||
|
| 3 | Super Mario - Yoshi Island (Japan) (Rev 2).zip | 3.2M |
|
||||||
|
| 4 | Super Mario World 2 - Yoshi's Island (USA).zip | 3.3M |
|
||||||
|
| 5 | Super Mario - Yoshi Island (Japan) (Beta) (1995-07-10).zip | 3.1M |
|
||||||
|
+------+--------------------------------------------------------------+------------+
|
||||||
|
Enter number (or press Enter to cancel):
|
||||||
|
```
|
||||||
|
If you cancel or are not in interactive mode, a similar table is still shown (without the prompt) followed by a tip.
|
||||||
|
|
||||||
|
## Improved fuzzy search for games (multi-token)
|
||||||
|
The `--search` / `--s` / `-s` option now uses the same multi-strategy ranking as the download suggestion logic:
|
||||||
|
1. Substring match (position-based) — highest priority
|
||||||
|
2. Ordered non-contiguous token sequence (smallest gap wins)
|
||||||
|
3. All tokens present in any order (smaller token set size wins)
|
||||||
|
|
||||||
|
Duplicate titles are deduplicated by keeping the best scoring strategy. This means queries like:
|
||||||
|
```powershell
|
||||||
|
python rgsx_cli.py games --p snes --s "super mario yoshi"
|
||||||
|
```
|
||||||
|
will surface all relevant "Super Mario World 2 - Yoshi's Island" variants even if the word order differs.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
| Game Title | Size |
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
| Super Mario World 2 - Yoshi's Island (USA).zip | 3.3M |
|
||||||
|
| Super Mario World 2 - Yoshi's Island (Europe) (En,Fr,De).zip | 3.3M |
|
||||||
|
| Super Mario - Yoshi Island (Japan).zip | 3.2M |
|
||||||
|
| Super Mario - Yoshi Island (Japan) (Rev 1).zip | 3.2M |
|
||||||
|
| Super Mario - Yoshi Island (Japan) (Rev 2).zip | 3.2M |
|
||||||
|
+--------------------------------------------------------------+------------+
|
||||||
|
```
|
||||||
|
If no results are found the table displays only headers followed by a message.
|
||||||
|
|
||||||
|
## General syntax (non-interactive)
|
||||||
Global options can be placed before or after the subcommand.
|
Global options can be placed before or after the subcommand.
|
||||||
|
|
||||||
- Form 1:
|
- Form 1:
|
||||||
@@ -25,59 +128,55 @@ When source data is missing, the CLI will automatically download and extract the
|
|||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### 1) platforms — list platforms
|
### 1) platforms (`platforms` / `p`) — list platforms
|
||||||
- Options:
|
- Options:
|
||||||
- `--json`: JSON output (objects `{ name, folder }`).
|
- `--json`: JSON output (objects `{ name, folder }`).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py platforms
|
python rgsx_cli.py platforms
|
||||||
python rgsx_cli.py platforms --json
|
python rgsx_cli.py p --json
|
||||||
python rgsx_cli.py --verbose platforms
|
python rgsx_cli.py --verbose p
|
||||||
python rgsx_cli.py platforms --verbose
|
python rgsx_cli.py p --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
Text output: one line per platform, formatted as `Name<TAB>Folder`.
|
Text output: one line per platform, formatted as `Name<TAB>Folder`.
|
||||||
|
|
||||||
### 2) games — list games for a platform
|
### 2) games (`games` / `g`) — list games for a platform
|
||||||
- Options:
|
- Options:
|
||||||
- `--platform <name_or_folder>` (e.g., `n64` or "Nintendo 64").
|
- `--platform | --p | -p <name_or_folder>` (e.g., `n64` or "Nintendo 64").
|
||||||
- `--search <text>`: filter by substring in game title.
|
- `--search | --s | -s <text>`: filter by substring in game title.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py games --platform n64
|
python rgsx_cli.py games --platform n64
|
||||||
python rgsx_cli.py games --platform "Nintendo 64" --search zelda
|
python rgsx_cli.py g --p "Nintendo 64" --s zelda
|
||||||
python rgsx_cli.py games --platform n64 --verbose
|
python rgsx_cli.py g -p n64 --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- The platform is resolved by display name (platform_name) or folder, case-insensitively.
|
- The platform is resolved by display name (platform_name) or folder, case-insensitively.
|
||||||
|
|
||||||
### 3) download — download a game
|
### 3) download (`download` / `dl`) — download a game
|
||||||
- Options:
|
- Options:
|
||||||
- `--platform <name_or_folder>`
|
- `--platform | --p | -p <name_or_folder>`
|
||||||
- `--game "<exact or partial title>"`
|
- `--game | --g | -g "<exact or partial title>"`
|
||||||
- `--force`: ignore unsupported-extension warning for the platform.
|
- `--force | -f`: ignore unsupported-extension warning for the platform.
|
||||||
|
- `--interactive | -i`: prompt to choose from matches when no exact title is found.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```powershell
|
```powershell
|
||||||
# Exact title
|
# Exact title
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
python rgsx_cli.py dl --p n64 --g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
||||||
|
|
||||||
# Partial match
|
# Partial match (interactive numbered selection if no exact match)
|
||||||
# If no exact title is found, the CLI no longer auto-selects; it displays suggestions.
|
python rgsx_cli.py dl -p n64 -g "Ocarina of Time (Beta)"
|
||||||
python rgsx_cli.py download --platform n64 --game "Ocarina of Time (Beta)"
|
|
||||||
# ➜ The CLI shows a list of candidates (then run again with the exact title).
|
|
||||||
|
|
||||||
Interactive mode by default:
|
# Forced despite extension
|
||||||
- If no exact title is found and you are in an interactive terminal (TTY), a numbered list is shown automatically so you can pick and start the download.
|
python rgsx_cli.py dl -p snes -g "pack_roms.rar" -f
|
||||||
|
|
||||||
# Force if the file extension seems unsupported (e.g., .rar)
|
# Verbose after subcommand
|
||||||
python rgsx_cli.py download --platform snes --game "pack_roms.rar" --force
|
python rgsx_cli.py dl -p n64 -g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip" --verbose
|
||||||
|
|
||||||
# Verbose placed after the subcommand
|
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip" --verbose
|
|
||||||
```
|
```
|
||||||
|
|
||||||
During download, progress %, size (MB) and speed (MB/s) are shown. The final result is also written to history.
|
During download, progress %, size (MB) and speed (MB/s) are shown. The final result is also written to history.
|
||||||
@@ -98,10 +197,10 @@ python rgsx_cli.py history --tail 20
|
|||||||
python rgsx_cli.py history --json
|
python rgsx_cli.py history --json
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5) clear-history — clear history
|
### 5) clear-history (`clear-history` / `clear`) — clear history
|
||||||
Example:
|
Example:
|
||||||
```powershell
|
```powershell
|
||||||
python rgsx_cli.py clear-history
|
python rgsx_cli.py clear
|
||||||
```
|
```
|
||||||
|
|
||||||
### Global option: --force-update — purge + re-download data
|
### Global option: --force-update — purge + re-download data
|
||||||
@@ -113,7 +212,7 @@ Examples:
|
|||||||
python rgsx_cli.py --force-update
|
python rgsx_cli.py --force-update
|
||||||
|
|
||||||
# Placed after a subcommand (also accepted)
|
# Placed after a subcommand (also accepted)
|
||||||
python rgsx_cli.py platforms --force-update
|
python rgsx_cli.py p --force-update
|
||||||
```
|
```
|
||||||
|
|
||||||
## Behavior and tips
|
## Behavior and tips
|
||||||
@@ -129,20 +228,23 @@ python rgsx_cli.py platforms --force-update
|
|||||||
|
|
||||||
## Quick examples (copy/paste)
|
## Quick examples (copy/paste)
|
||||||
```powershell
|
```powershell
|
||||||
|
# Start interactive shell
|
||||||
|
python rgsx_cli.py
|
||||||
|
|
||||||
# List platforms (text)
|
# List platforms (text)
|
||||||
python rgsx_cli.py platforms
|
python rgsx_cli.py p
|
||||||
|
|
||||||
# List platforms (JSON)
|
# List platforms (JSON)
|
||||||
python rgsx_cli.py platforms --json
|
python rgsx_cli.py p --json
|
||||||
|
|
||||||
# List N64 games with filter
|
# List N64 games with filter (using alias synonyms)
|
||||||
python rgsx_cli.py games --platform n64 --search zelda
|
python rgsx_cli.py g --p n64 --s zelda
|
||||||
|
|
||||||
# Download an N64 game (exact title)
|
# Download an N64 game (exact title) using aliases
|
||||||
python rgsx_cli.py download --platform n64 --game "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
python rgsx_cli.py dl --p n64 --g "Legend of Zelda, The - Ocarina of Time (USA) (Beta).zip"
|
||||||
|
|
||||||
# Download with approximate title (suggestions + interactive pick)
|
# Download with approximate title (suggestions + interactive pick)
|
||||||
python rgsx_cli.py download --platform n64 --game "Ocarina of Time"
|
python rgsx_cli.py dl -p n64 -g "Ocarina of Time"
|
||||||
|
|
||||||
# View last 20 history entries
|
# View last 20 history entries
|
||||||
python rgsx_cli.py history --tail 20
|
python rgsx_cli.py history --tail 20
|
||||||
|
|||||||
@@ -215,38 +215,38 @@ else:
|
|||||||
# 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
|
||||||
logger.debug(f"Controller detected (Xbox Elite): {name}")
|
|
||||||
print(f"Controller detected (Xbox Elite): {name}")
|
|
||||||
break
|
|
||||||
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}")
|
logger.debug(f"Controller detected: {name}")
|
||||||
print(f"Controller detected: {name}")
|
print(f"Controller detected: {name}")
|
||||||
break
|
break
|
||||||
elif "playstation" in lname:
|
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"Xbox Controller detected : {name}")
|
||||||
|
print(f"Controller detected : {name}")
|
||||||
|
break
|
||||||
|
elif "playstation" in lname or "ps3" in lname or "sony" in lname:
|
||||||
config.playstation_controller = True
|
config.playstation_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"Playstation Controller detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
break
|
break
|
||||||
elif "nintendo" in lname:
|
elif "nintendo" in lname:
|
||||||
config.nintendo_controller = True
|
config.nintendo_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"Nintendo Controller detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
elif "trimui" in lname:
|
elif "trimui" in lname:
|
||||||
config.trimui_controller = True
|
config.trimui_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"Trimui Controller detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
elif "logitech" in lname:
|
elif "logitech" in lname:
|
||||||
config.logitech_controller = True
|
config.logitech_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"Logitech Controller detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
elif "8bitdo" in lname or "8-bitdo" in lname:
|
elif "8bitdo" in lname or "8-bitdo" in lname:
|
||||||
config.eightbitdo_controller = True
|
config.eightbitdo_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"8bitdoController detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
elif "steam" in lname:
|
elif "steam" in lname:
|
||||||
config.steam_controller = True
|
config.steam_controller = True
|
||||||
logger.debug(f"Controller detected : {name}")
|
logger.debug(f"Steam Controller detected : {name}")
|
||||||
print(f"Controller detected : {name}")
|
print(f"Controller detected : {name}")
|
||||||
# Note: virtual keyboard display now depends on controller presence (config.joystick)
|
# Note: virtual keyboard display now depends on controller presence (config.joystick)
|
||||||
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}")
|
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}")
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ except Exception:
|
|||||||
pygame = None # type: ignore
|
pygame = None # type: ignore
|
||||||
|
|
||||||
# Version actuelle de l'application
|
# Version actuelle de l'application
|
||||||
app_version = "2.2.2.1"
|
app_version = "2.2.2.2"
|
||||||
|
|
||||||
|
|
||||||
def get_application_root():
|
def get_application_root():
|
||||||
@@ -173,14 +173,9 @@ 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
|
||||||
|
|
||||||
# --- Premium systems filtering ---
|
|
||||||
# Liste des marqueurs (substrings) indiquant qu'un système/plateforme requiert un compte premium ou une clé API.
|
|
||||||
# On teste la présence (case-insensitive) de ces marqueurs dans le nom du système (ex: "Microsoft Windows (1Fichier)").
|
|
||||||
# Ajoutez librement d'autres valeurs (ex: 'RealDebrid', 'AllDebrid') si de futurs systèmes nécessitent un compte.
|
|
||||||
PREMIUM_HOST_MARKERS = [
|
PREMIUM_HOST_MARKERS = [
|
||||||
"1Fichier",
|
"1Fichier",
|
||||||
]
|
]
|
||||||
# Flag runtime contrôlant le masquage des systèmes premium dans le menu pause > games.
|
|
||||||
hide_premium_systems = False
|
hide_premium_systems = False
|
||||||
|
|
||||||
# Indicateurs d'entrée (détectés au démarrage)
|
# Indicateurs d'entrée (détectés au démarrage)
|
||||||
|
|||||||
@@ -1389,6 +1389,28 @@ def draw_language_menu(screen):
|
|||||||
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
||||||
screen.blit(instruction_surface, instruction_rect)
|
screen.blit(instruction_surface, instruction_rect)
|
||||||
|
|
||||||
|
def draw_menu_instruction(screen, instruction_text, last_button_bottom=None):
|
||||||
|
"""Dessine une ligne d'instruction centrée au-dessus du footer.
|
||||||
|
|
||||||
|
- Réserve une zone footer (72px) + marge bas.
|
||||||
|
- Si last_button_bottom est fourni, s'assure d'un écart minimal (16px).
|
||||||
|
- Utilise la petite police et couleurs du thème.
|
||||||
|
"""
|
||||||
|
if not instruction_text:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
instruction_surface = config.small_font.render(instruction_text, True, THEME_COLORS["text"])
|
||||||
|
footer_reserved = 72
|
||||||
|
bottom_margin = 12
|
||||||
|
instruction_y = config.screen_height - footer_reserved - bottom_margin
|
||||||
|
min_gap = 16
|
||||||
|
if last_button_bottom is not None and instruction_y - last_button_bottom < min_gap:
|
||||||
|
instruction_y = last_button_bottom + min_gap
|
||||||
|
instruction_rect = instruction_surface.get_rect(center=(config.screen_width // 2, instruction_y))
|
||||||
|
screen.blit(instruction_surface, instruction_rect)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur draw_menu_instruction: {e}")
|
||||||
|
|
||||||
def draw_display_menu(screen):
|
def draw_display_menu(screen):
|
||||||
"""Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés)."""
|
"""Affiche le sous-menu Affichage (layout, taille de police, systèmes non supportés)."""
|
||||||
screen.blit(OVERLAY, (0, 0))
|
screen.blit(OVERLAY, (0, 0))
|
||||||
@@ -1494,6 +1516,28 @@ def draw_pause_menu(screen, selected_option):
|
|||||||
)
|
)
|
||||||
config.pause_menu_total_options = len(options)
|
config.pause_menu_total_options = len(options)
|
||||||
|
|
||||||
|
# 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_language",
|
||||||
|
"instruction_pause_controls",
|
||||||
|
"instruction_pause_display",
|
||||||
|
"instruction_pause_games",
|
||||||
|
"instruction_pause_settings",
|
||||||
|
"instruction_pause_restart",
|
||||||
|
"instruction_pause_quit",
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
key = instruction_keys[selected_option]
|
||||||
|
instruction_text = _(key)
|
||||||
|
except Exception:
|
||||||
|
instruction_text = "" # Sécurité si index hors borne
|
||||||
|
|
||||||
|
if instruction_text:
|
||||||
|
# Calcul de la position du dernier bouton pour éviter chevauchement
|
||||||
|
last_button_bottom = menu_y + margin_top_bottom + (len(options) - 1) * (button_height + 12) + button_height
|
||||||
|
draw_menu_instruction(screen, instruction_text, last_button_bottom)
|
||||||
|
|
||||||
def _draw_submenu_generic(screen, title, options, selected_index):
|
def _draw_submenu_generic(screen, title, options, selected_index):
|
||||||
"""Helper générique pour dessiner un sous-menu hiérarchique."""
|
"""Helper générique pour dessiner un sous-menu hiérarchique."""
|
||||||
screen.blit(OVERLAY, (0, 0))
|
screen.blit(OVERLAY, (0, 0))
|
||||||
@@ -1529,6 +1573,57 @@ def draw_pause_controls_menu(screen, selected_index):
|
|||||||
_("menu_back") if _ else "Back"
|
_("menu_back") if _ else "Back"
|
||||||
]
|
]
|
||||||
_draw_submenu_generic(screen, _("menu_controls") if _ else "Controls", options, selected_index)
|
_draw_submenu_generic(screen, _("menu_controls") if _ else "Controls", options, selected_index)
|
||||||
|
# Instructions contextuelles
|
||||||
|
instruction_keys = [
|
||||||
|
"instruction_controls_help", # pour menu_controls (afficher l'aide)
|
||||||
|
"instruction_controls_remap", # remap
|
||||||
|
"instruction_generic_back", # retour
|
||||||
|
]
|
||||||
|
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||||||
|
if key:
|
||||||
|
last_button_bottom = None # recalculer via géométrie si nécessaire; ici on réutilise calcul simple
|
||||||
|
# Reconstituer la position du dernier bouton comme dans _draw_submenu_generic
|
||||||
|
menu_width = int(config.screen_width * 0.72)
|
||||||
|
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 height approximatif
|
||||||
|
title_surface = config.font.render("X", True, THEME_COLORS["text"]) # hauteur représentative
|
||||||
|
title_rect_height = title_surface.get_height()
|
||||||
|
start_y = menu_y + margin_top_bottom//2 + title_rect_height + 10 + 10 # approx: title center adjust + bottom spacing
|
||||||
|
last_button_bottom = start_y + (len(options)-1) * (button_height + 10) + button_height
|
||||||
|
text = _(key)
|
||||||
|
if key == "instruction_display_hide_premium":
|
||||||
|
# Inject dynamic list of premium providers from config.PREMIUM_HOST_MARKERS
|
||||||
|
try:
|
||||||
|
from config import PREMIUM_HOST_MARKERS
|
||||||
|
# Clean, preserve order, remove duplicates (case-insensitive)
|
||||||
|
seen = set()
|
||||||
|
providers_clean = []
|
||||||
|
for p in PREMIUM_HOST_MARKERS:
|
||||||
|
if not p: continue
|
||||||
|
norm = p.strip()
|
||||||
|
if not norm: continue
|
||||||
|
low = norm.lower()
|
||||||
|
if low in seen: continue
|
||||||
|
seen.add(low)
|
||||||
|
providers_clean.append(norm)
|
||||||
|
providers_str = ", ".join(providers_clean)
|
||||||
|
if not providers_str:
|
||||||
|
providers_str = "-"
|
||||||
|
if "{providers}" in text:
|
||||||
|
try:
|
||||||
|
text = text.format(providers=providers_str)
|
||||||
|
except Exception:
|
||||||
|
# Fallback if formatting fails
|
||||||
|
text = f"{text.replace('{providers}','').strip()} {providers_str}".strip()
|
||||||
|
else:
|
||||||
|
# Append providers if placeholder missing (backward compatibility)
|
||||||
|
text = f"{text} : {providers_str}" if providers_str else text
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
draw_menu_instruction(screen, text, last_button_bottom)
|
||||||
|
|
||||||
def draw_pause_display_menu(screen, selected_index):
|
def draw_pause_display_menu(screen, selected_index):
|
||||||
from rgsx_settings import (
|
from rgsx_settings import (
|
||||||
@@ -1583,6 +1678,28 @@ def draw_pause_display_menu(screen, selected_index):
|
|||||||
back_txt = _("menu_back") if _ else "Back"
|
back_txt = _("menu_back") if _ else "Back"
|
||||||
options = [layout_txt, font_txt, font_family_txt, unsupported_txt, unknown_txt, hide_premium_txt, filter_txt, back_txt]
|
options = [layout_txt, font_txt, font_family_txt, unsupported_txt, unknown_txt, hide_premium_txt, filter_txt, back_txt]
|
||||||
_draw_submenu_generic(screen, _("menu_display"), options, selected_index)
|
_draw_submenu_generic(screen, _("menu_display"), options, selected_index)
|
||||||
|
instruction_keys = [
|
||||||
|
"instruction_display_layout",
|
||||||
|
"instruction_display_font_size",
|
||||||
|
"instruction_display_font_family",
|
||||||
|
"instruction_display_show_unsupported",
|
||||||
|
"instruction_display_unknown_ext",
|
||||||
|
"instruction_display_hide_premium",
|
||||||
|
"instruction_display_filter_platforms",
|
||||||
|
"instruction_generic_back",
|
||||||
|
]
|
||||||
|
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||||||
|
if key:
|
||||||
|
button_height = int(config.screen_height * 0.045)
|
||||||
|
menu_width = int(config.screen_width * 0.72)
|
||||||
|
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_pause_games_menu(screen, selected_index):
|
def draw_pause_games_menu(screen, selected_index):
|
||||||
from rgsx_settings import get_sources_mode
|
from rgsx_settings import get_sources_mode
|
||||||
@@ -1594,6 +1711,23 @@ def draw_pause_games_menu(screen, selected_index):
|
|||||||
back_txt = _("menu_back") if _ else "Back"
|
back_txt = _("menu_back") if _ else "Back"
|
||||||
options = [history_txt, source_txt, update_txt, back_txt]
|
options = [history_txt, source_txt, update_txt, back_txt]
|
||||||
_draw_submenu_generic(screen, _("menu_games") if _ else "Games", options, selected_index)
|
_draw_submenu_generic(screen, _("menu_games") if _ else "Games", options, selected_index)
|
||||||
|
instruction_keys = [
|
||||||
|
"instruction_games_history",
|
||||||
|
"instruction_games_source_mode",
|
||||||
|
"instruction_games_update_cache",
|
||||||
|
"instruction_generic_back",
|
||||||
|
]
|
||||||
|
key = instruction_keys[selected_index] if 0 <= selected_index < 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_pause_settings_menu(screen, selected_index):
|
def draw_pause_settings_menu(screen, selected_index):
|
||||||
from rgsx_settings import get_symlink_option
|
from rgsx_settings import get_symlink_option
|
||||||
@@ -1618,6 +1752,23 @@ def draw_pause_settings_menu(screen, selected_index):
|
|||||||
back_txt = _("menu_back") if _ else "Back"
|
back_txt = _("menu_back") if _ else "Back"
|
||||||
options = [music_option, symlink_option, api_keys_txt, back_txt]
|
options = [music_option, symlink_option, api_keys_txt, back_txt]
|
||||||
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
|
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
|
||||||
|
instruction_keys = [
|
||||||
|
"instruction_settings_music",
|
||||||
|
"instruction_settings_symlink",
|
||||||
|
"instruction_settings_api_keys",
|
||||||
|
"instruction_generic_back",
|
||||||
|
]
|
||||||
|
key = instruction_keys[selected_index] if 0 <= selected_index < 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_pause_api_keys_status(screen):
|
def draw_pause_api_keys_status(screen):
|
||||||
screen.blit(OVERLAY, (0,0))
|
screen.blit(OVERLAY, (0,0))
|
||||||
|
|||||||
@@ -155,4 +155,27 @@
|
|||||||
"popup_hide_premium_off": "Premium-Systeme sichtbar"
|
"popup_hide_premium_off": "Premium-Systeme sichtbar"
|
||||||
,"submenu_display_font_family": "Schrift"
|
,"submenu_display_font_family": "Schrift"
|
||||||
,"popup_font_family_changed": "Schrift geändert: {0}"
|
,"popup_font_family_changed": "Schrift geändert: {0}"
|
||||||
|
,"instruction_pause_language": "Sprache der Oberfläche ändern"
|
||||||
|
,"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen"
|
||||||
|
,"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren"
|
||||||
|
,"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren"
|
||||||
|
,"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus"
|
||||||
|
,"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden"
|
||||||
|
,"instruction_pause_quit": "RGSX Anwendung beenden"
|
||||||
|
,"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"
|
||||||
|
,"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten"
|
||||||
|
,"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen"
|
||||||
|
,"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln"
|
||||||
|
,"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden"
|
||||||
|
,"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten"
|
||||||
|
,"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind"
|
||||||
|
,"instruction_games_history": "Vergangene Downloads und Status anzeigen"
|
||||||
|
,"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln"
|
||||||
|
,"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren"
|
||||||
|
,"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren"
|
||||||
|
,"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten"
|
||||||
|
,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen"
|
||||||
}
|
}
|
||||||
@@ -154,5 +154,28 @@
|
|||||||
,"popup_hide_premium_on": "Premium systems hidden"
|
,"popup_hide_premium_on": "Premium systems hidden"
|
||||||
,"popup_hide_premium_off": "Premium systems visible"
|
,"popup_hide_premium_off": "Premium systems visible"
|
||||||
,"submenu_display_font_family": "Font"
|
,"submenu_display_font_family": "Font"
|
||||||
,"popup_font_family_changed": "Font changed: {0}"
|
,"popup_font_family_changed": "Font changed: {0}",
|
||||||
|
"instruction_pause_language": "Change the interface language",
|
||||||
|
"instruction_pause_controls": "View control layout or start remapping",
|
||||||
|
"instruction_pause_display": "Configure layout, fonts and system visibility",
|
||||||
|
"instruction_pause_games": "Open history, switch source or refresh list",
|
||||||
|
"instruction_pause_settings": "Music, symlink option & API keys status",
|
||||||
|
"instruction_pause_restart": "Restart RGSX to reload configuration"
|
||||||
|
,"instruction_pause_quit": "Exit 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"
|
||||||
|
,"instruction_display_layout": "Cycle grid dimensions (columns × rows)"
|
||||||
|
,"instruction_display_font_size": "Adjust text scale for readability"
|
||||||
|
,"instruction_display_font_family": "Switch between available font families"
|
||||||
|
,"instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg"
|
||||||
|
,"instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg"
|
||||||
|
,"instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Manually choose which systems are visible"
|
||||||
|
,"instruction_games_history": "List past downloads and statuses"
|
||||||
|
,"instruction_games_source_mode": "Switch between RGSX or your own custom list source"
|
||||||
|
,"instruction_games_update_cache": "Redownload & refresh current games list"
|
||||||
|
,"instruction_settings_music": "Enable or disable background music playback"
|
||||||
|
,"instruction_settings_symlink": "Toggle using filesystem symlinks for installs"
|
||||||
|
,"instruction_settings_api_keys": "See detected premium provider API keys"
|
||||||
}
|
}
|
||||||
@@ -155,4 +155,27 @@
|
|||||||
"popup_hide_premium_off": "Sistemas Premium visibles"
|
"popup_hide_premium_off": "Sistemas Premium visibles"
|
||||||
,"submenu_display_font_family": "Fuente"
|
,"submenu_display_font_family": "Fuente"
|
||||||
,"popup_font_family_changed": "Fuente cambiada: {0}"
|
,"popup_font_family_changed": "Fuente cambiada: {0}"
|
||||||
|
,"instruction_pause_language": "Cambiar el idioma de la interfaz"
|
||||||
|
,"instruction_pause_controls": "Ver esquema de controles o remapear"
|
||||||
|
,"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas"
|
||||||
|
,"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista"
|
||||||
|
,"instruction_pause_settings": "Música, opción symlink y estado de claves API"
|
||||||
|
,"instruction_pause_restart": "Reiniciar RGSX para recargar configuración"
|
||||||
|
,"instruction_pause_quit": "Salir de 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"
|
||||||
|
,"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)"
|
||||||
|
,"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad"
|
||||||
|
,"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles"
|
||||||
|
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg"
|
||||||
|
,"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg"
|
||||||
|
,"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles"
|
||||||
|
,"instruction_games_history": "Ver descargas pasadas y su estado"
|
||||||
|
,"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada"
|
||||||
|
,"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos"
|
||||||
|
,"instruction_settings_music": "Activar o desactivar música de fondo"
|
||||||
|
,"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones"
|
||||||
|
,"instruction_settings_api_keys": "Ver claves API premium detectadas"
|
||||||
}
|
}
|
||||||
@@ -155,4 +155,27 @@
|
|||||||
"popup_hide_premium_off": "Systèmes Premium visibles"
|
"popup_hide_premium_off": "Systèmes Premium visibles"
|
||||||
,"submenu_display_font_family": "Police"
|
,"submenu_display_font_family": "Police"
|
||||||
,"popup_font_family_changed": "Police changée : {0}"
|
,"popup_font_family_changed": "Police changée : {0}"
|
||||||
|
,"instruction_pause_language": "Changer la langue de l'interface"
|
||||||
|
,"instruction_pause_controls": "Afficher la configuration ou remapper"
|
||||||
|
,"instruction_pause_display": "Agencer l'affichage, polices et systèmes visibles"
|
||||||
|
,"instruction_pause_games": "Historique, source de liste ou rafraîchissement"
|
||||||
|
,"instruction_pause_settings": "Musique, option symlink & statut des clés API"
|
||||||
|
,"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration"
|
||||||
|
,"instruction_pause_quit": "Quitter 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"
|
||||||
|
,"instruction_display_layout": "Changer les dimensions de la grille"
|
||||||
|
,"instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité"
|
||||||
|
,"instruction_display_font_family": "Basculer entre les polices disponibles"
|
||||||
|
,"instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg"
|
||||||
|
,"instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg"
|
||||||
|
,"instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Choisir manuellement les systèmes visibles"
|
||||||
|
,"instruction_games_history": "Lister les téléchargements passés et leur statut"
|
||||||
|
,"instruction_games_source_mode": "Basculer entre liste RGSX ou source personnalisée"
|
||||||
|
,"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux"
|
||||||
|
,"instruction_settings_music": "Activer ou désactiver la lecture musicale"
|
||||||
|
,"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"
|
||||||
}
|
}
|
||||||
@@ -155,4 +155,27 @@
|
|||||||
"popup_hide_premium_off": "Sistemi Premium visibili"
|
"popup_hide_premium_off": "Sistemi Premium visibili"
|
||||||
,"submenu_display_font_family": "Font"
|
,"submenu_display_font_family": "Font"
|
||||||
,"popup_font_family_changed": "Font cambiato: {0}"
|
,"popup_font_family_changed": "Font cambiato: {0}"
|
||||||
|
,"instruction_pause_language": "Cambiare la lingua dell'interfaccia"
|
||||||
|
,"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura"
|
||||||
|
,"instruction_pause_display": "Configurare layout, font e visibilità sistemi"
|
||||||
|
,"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco"
|
||||||
|
,"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API"
|
||||||
|
,"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione"
|
||||||
|
,"instruction_pause_quit": "Uscire dall'applicazione RGSX"
|
||||||
|
,"instruction_controls_help": "Mostrare riferimento completo controller & tastiera"
|
||||||
|
,"instruction_controls_remap": "Modificare associazione pulsanti / tasti"
|
||||||
|
,"instruction_generic_back": "Tornare al menu precedente"
|
||||||
|
,"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)"
|
||||||
|
,"instruction_display_font_size": "Regolare dimensione testo per leggibilità"
|
||||||
|
,"instruction_display_font_family": "Cambiare famiglia di font disponibile"
|
||||||
|
,"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg"
|
||||||
|
,"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg"
|
||||||
|
,"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili"
|
||||||
|
,"instruction_games_history": "Elencare download passati e stato"
|
||||||
|
,"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata"
|
||||||
|
,"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi"
|
||||||
|
,"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo"
|
||||||
|
,"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni"
|
||||||
|
,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate"
|
||||||
}
|
}
|
||||||
@@ -155,4 +155,27 @@
|
|||||||
"popup_hide_premium_off": "Sistemas Premium visíveis"
|
"popup_hide_premium_off": "Sistemas Premium visíveis"
|
||||||
,"submenu_display_font_family": "Fonte"
|
,"submenu_display_font_family": "Fonte"
|
||||||
,"popup_font_family_changed": "Fonte alterada: {0}"
|
,"popup_font_family_changed": "Fonte alterada: {0}"
|
||||||
|
,"instruction_pause_language": "Alterar o idioma da interface"
|
||||||
|
,"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento"
|
||||||
|
,"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas"
|
||||||
|
,"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista"
|
||||||
|
,"instruction_pause_settings": "Música, opção symlink e status das chaves API"
|
||||||
|
,"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração"
|
||||||
|
,"instruction_pause_quit": "Sair da 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"
|
||||||
|
,"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)"
|
||||||
|
,"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade"
|
||||||
|
,"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis"
|
||||||
|
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg"
|
||||||
|
,"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg"
|
||||||
|
,"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}"
|
||||||
|
,"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis"
|
||||||
|
,"instruction_games_history": "Listar downloads anteriores e status"
|
||||||
|
,"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada"
|
||||||
|
,"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos"
|
||||||
|
,"instruction_settings_music": "Ativar ou desativar música de fundo"
|
||||||
|
,"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações"
|
||||||
|
,"instruction_settings_api_keys": "Ver chaves API premium detectadas"
|
||||||
}
|
}
|
||||||
@@ -1233,6 +1233,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
|||||||
cancel_events.pop(task_id, None)
|
cancel_events.pop(task_id, None)
|
||||||
logger.debug(f"Fin download_from_1fichier, résultat: success={result[0]}, message={result[1]}")
|
logger.debug(f"Fin download_from_1fichier, résultat: success={result[0]}, message={result[1]}")
|
||||||
return result[0], result[1]
|
return result[0], result[1]
|
||||||
|
|
||||||
def is_1fichier_url(url):
|
def is_1fichier_url(url):
|
||||||
"""Détecte si l'URL est un lien 1fichier."""
|
"""Détecte si l'URL est un lien 1fichier."""
|
||||||
return "1fichier.com" in url
|
return "1fichier.com" in url
|
||||||
@@ -24,6 +24,53 @@ from rgsx_settings import get_sources_zip_url
|
|||||||
logger = logging.getLogger("rgsx.cli")
|
logger = logging.getLogger("rgsx.cli")
|
||||||
|
|
||||||
|
|
||||||
|
# Unified size display helper: preserve pre-formatted locale strings (MiB, GiB, Go, Mo, Ko, MB, KB, bytes).
|
||||||
|
# If numeric (int/float or pure digit string), convert to binary units with suffix B, KiB, MiB, GiB.
|
||||||
|
# Otherwise return original string.
|
||||||
|
def display_size(val):
|
||||||
|
try:
|
||||||
|
if val is None:
|
||||||
|
return ''
|
||||||
|
if isinstance(val, (list, tuple)):
|
||||||
|
return ''
|
||||||
|
s = str(val).strip()
|
||||||
|
if not s:
|
||||||
|
return ''
|
||||||
|
lower = s.lower()
|
||||||
|
# Already human formatted (English or French common units or contains a space + unit token)
|
||||||
|
known_tokens = ("mib", "gib", "kib", "kb", "mb", "gb", "bytes", " b", " mo", " go", " ko", "mb ", "gb ")
|
||||||
|
if any(tok in lower for tok in known_tokens):
|
||||||
|
return s
|
||||||
|
# Pure numeric => treat as bytes
|
||||||
|
import re as _re
|
||||||
|
if _re.fullmatch(r"\d+", s):
|
||||||
|
b = float(s)
|
||||||
|
else:
|
||||||
|
# Leading numeric? if not, return original
|
||||||
|
m = _re.match(r"^([0-9]+(?:\.[0-9]+)?)", s)
|
||||||
|
if not m:
|
||||||
|
return s
|
||||||
|
# If trailing unit unknown, assume already human string
|
||||||
|
if len(s) > len(m.group(0)):
|
||||||
|
return s
|
||||||
|
b = float(m.group(1))
|
||||||
|
if b < 1024:
|
||||||
|
return f"{int(b)} B"
|
||||||
|
kib = b / 1024
|
||||||
|
if kib < 1024:
|
||||||
|
return f"{kib:.2f} KiB"
|
||||||
|
mib = kib / 1024
|
||||||
|
if mib < 1024:
|
||||||
|
return f"{mib:.2f} MiB"
|
||||||
|
gib = mib / 1024
|
||||||
|
return f"{gib:.2f} GiB"
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
return str(val)
|
||||||
|
except Exception:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbose: bool):
|
def setup_logging(verbose: bool):
|
||||||
level = logging.DEBUG if verbose else logging.WARNING
|
level = logging.DEBUG if verbose else logging.WARNING
|
||||||
logging.basicConfig(level=level, format='%(levelname)s: %(message)s')
|
logging.basicConfig(level=level, format='%(levelname)s: %(message)s')
|
||||||
@@ -153,9 +200,26 @@ def cmd_platforms(args):
|
|||||||
if getattr(args, 'json', False):
|
if getattr(args, 'json', False):
|
||||||
print(json.dumps(items, ensure_ascii=False, indent=2))
|
print(json.dumps(items, ensure_ascii=False, indent=2))
|
||||||
else:
|
else:
|
||||||
|
# Hint before table
|
||||||
|
print("hint: you can use either the exact platform name or folder in --platform (e.g. 'SNK Neo Geo' or 'neogeo')")
|
||||||
|
# ASCII table with fixed widths: name=35, folder=15
|
||||||
|
NAME_W = 35
|
||||||
|
FOLDER_W = 15
|
||||||
|
def fmt_cell(text, width):
|
||||||
|
if len(text) <= width:
|
||||||
|
return text + ' ' * (width - len(text))
|
||||||
|
if width <= 3:
|
||||||
|
return text[:width]
|
||||||
|
return text[:width-3] + '...'
|
||||||
|
border = "+" + "-" * (NAME_W + 2) + "+" + "-" * (FOLDER_W + 2) + "+"
|
||||||
|
header = f"| {'Platform Name'.ljust(NAME_W)} | {'Folder'.ljust(FOLDER_W)} |"
|
||||||
|
print(border)
|
||||||
|
print(header)
|
||||||
|
print(border)
|
||||||
for it in items:
|
for it in items:
|
||||||
# name TAB folder (folder may be empty for BIOS/virtual)
|
row = f"| {fmt_cell(it['name'], NAME_W)} | {fmt_cell(it['folder'], FOLDER_W)} |"
|
||||||
print(f"{it['name']}\t{it['folder']}")
|
print(row)
|
||||||
|
print(border)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_platform(sources, platform_name: str):
|
def _resolve_platform(sources, platform_name: str):
|
||||||
@@ -188,13 +252,100 @@ def cmd_games(args):
|
|||||||
or args.platform
|
or args.platform
|
||||||
)
|
)
|
||||||
games = load_games(platform_id)
|
games = load_games(platform_id)
|
||||||
|
|
||||||
|
# Fuzzy ranking similar to download suggestions when --search provided
|
||||||
if args.search:
|
if args.search:
|
||||||
q = args.search.lower()
|
query_raw = args.search.strip()
|
||||||
games = [g for g in games if q in (g[0] or '').lower()]
|
def _strip_ext(name: str) -> str:
|
||||||
|
try:
|
||||||
|
base, _ = os.path.splitext(name)
|
||||||
|
return base
|
||||||
|
except Exception:
|
||||||
|
return name
|
||||||
|
def _tokens(s: str) -> list[str]:
|
||||||
|
return re.findall(r"[a-z0-9]+", s.lower())
|
||||||
|
q_lower = query_raw.lower()
|
||||||
|
q_no_ext = _strip_ext(query_raw).lower()
|
||||||
|
q_tokens = _tokens(query_raw)
|
||||||
|
suggestions = [] # (priority, score, game_obj)
|
||||||
|
# 1) Substring match (full or sans extension) priority 0, score = position
|
||||||
for g in games:
|
for g in games:
|
||||||
# games items can be (name, url) or (name, url, size)
|
title = g[0] if isinstance(g, (list, tuple)) and g else None
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
t_lower = title.lower()
|
||||||
|
t_no_ext = _strip_ext(t_lower)
|
||||||
|
pos_full = t_lower.find(q_lower) if q_lower else -1
|
||||||
|
pos_noext = t_no_ext.find(q_no_ext) if q_no_ext else -1
|
||||||
|
if pos_full != -1 or pos_noext != -1:
|
||||||
|
pos = pos_full if pos_full != -1 else pos_noext
|
||||||
|
suggestions.append((0, max(0, pos), g))
|
||||||
|
# Helper for ordered gap score
|
||||||
|
def ordered_gap_score(qt: list[str], tt: list[str]):
|
||||||
|
pos = []
|
||||||
|
start = 0
|
||||||
|
for tok in qt:
|
||||||
|
try:
|
||||||
|
i = next(i for i in range(start, len(tt)) if tt[i] == tok)
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
pos.append(i)
|
||||||
|
start = i + 1
|
||||||
|
gap = (pos[-1] - pos[0]) - (len(qt) - 1)
|
||||||
|
return max(0, gap)
|
||||||
|
# 2) Ordered non-contiguous tokens (priority 1)
|
||||||
|
if q_tokens:
|
||||||
|
for g in games:
|
||||||
|
title = g[0] if isinstance(g, (list, tuple)) and g else None
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
tt = _tokens(title)
|
||||||
|
score = ordered_gap_score(q_tokens, tt)
|
||||||
|
if score is not None:
|
||||||
|
suggestions.append((1, score, g))
|
||||||
|
# 3) All tokens present, any order (priority 2), score = token set size
|
||||||
|
if q_tokens:
|
||||||
|
for g in games:
|
||||||
|
title = g[0] if isinstance(g, (list, tuple)) and g else None
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
t_tokens = set(_tokens(title))
|
||||||
|
if all(tok in t_tokens for tok in q_tokens):
|
||||||
|
suggestions.append((2, len(t_tokens), g))
|
||||||
|
# Deduplicate by title keeping best (lowest priority, then score)
|
||||||
|
best = {}
|
||||||
|
for prio, score, g in suggestions:
|
||||||
title = g[0] if isinstance(g, (list, tuple)) and g else str(g)
|
title = g[0] if isinstance(g, (list, tuple)) and g else str(g)
|
||||||
print(title)
|
key = title.lower()
|
||||||
|
cur = best.get(key)
|
||||||
|
if cur is None or (prio, score) < (cur[0], cur[1]):
|
||||||
|
best[key] = (prio, score, g)
|
||||||
|
ranked = sorted(best.values(), key=lambda x: (x[0], x[1], (x[2][0] if isinstance(x[2], (list, tuple)) and x[2] else str(x[2])).lower()))
|
||||||
|
games = [g for _, _, g in ranked]
|
||||||
|
# Table: Name (60) | Size (12) to allow "xxxx.xx MiB"
|
||||||
|
NAME_W = 60
|
||||||
|
SIZE_W = 12
|
||||||
|
def trunc(text, width):
|
||||||
|
if len(text) <= width:
|
||||||
|
return text + ' ' * (width - len(text))
|
||||||
|
if width <= 3:
|
||||||
|
return text[:width]
|
||||||
|
return text[:width-3] + '...'
|
||||||
|
border = "+" + "-" * (NAME_W + 2) + "+" + "-" * (SIZE_W + 2) + "+"
|
||||||
|
header = f"| {'Game Title'.ljust(NAME_W)} | {'Size'.ljust(SIZE_W)} |"
|
||||||
|
print(border)
|
||||||
|
print(header)
|
||||||
|
print(border)
|
||||||
|
for g in games:
|
||||||
|
title = g[0] if isinstance(g, (list, tuple)) and g else str(g)
|
||||||
|
size_val = ''
|
||||||
|
if isinstance(g, (list, tuple)) and len(g) >= 3:
|
||||||
|
size_val = display_size(g[2])
|
||||||
|
row = f"| {trunc(title, NAME_W)} | {trunc(size_val, SIZE_W)} |"
|
||||||
|
print(row)
|
||||||
|
print(border)
|
||||||
|
if args.search and not games:
|
||||||
|
print("No results for search.")
|
||||||
|
|
||||||
|
|
||||||
def cmd_history(args):
|
def cmd_history(args):
|
||||||
@@ -382,10 +533,37 @@ def cmd_download(args):
|
|||||||
interactive = bool(getattr(args, 'interactive', False))
|
interactive = bool(getattr(args, 'interactive', False))
|
||||||
if interactive:
|
if interactive:
|
||||||
print("Select a match to download:")
|
print("Select a match to download:")
|
||||||
|
# Tableau formaté: # (4) | Title (60) | Size (12)
|
||||||
|
NUM_W = 4
|
||||||
|
TITLE_W = 60
|
||||||
|
SIZE_W = 12
|
||||||
|
def trunc(text, width):
|
||||||
|
if len(text) <= width:
|
||||||
|
return text + ' ' * (width - len(text))
|
||||||
|
if width <= 3:
|
||||||
|
return text[:width]
|
||||||
|
return text[:width-3] + '...'
|
||||||
|
# Use shared display_size
|
||||||
|
border = "+" + "-" * (NUM_W + 2) + "+" + "-" * (TITLE_W + 2) + "+" + "-" * (SIZE_W + 2) + "+"
|
||||||
|
header = f"| {'#'.ljust(NUM_W)} | {'Title'.ljust(TITLE_W)} | {'Size'.ljust(SIZE_W)} |"
|
||||||
|
print(border)
|
||||||
|
print(header)
|
||||||
|
print(border)
|
||||||
for i, s in enumerate(shown, start=1):
|
for i, s in enumerate(shown, start=1):
|
||||||
print(f" {i}. {s[2]}")
|
title = s[2]
|
||||||
|
size_val = ''
|
||||||
|
size_raw = None
|
||||||
|
for g in games:
|
||||||
|
if isinstance(g, (list, tuple)) and g and g[0] == title and len(g) >= 3:
|
||||||
|
size_raw = g[2]
|
||||||
|
break
|
||||||
|
if size_raw is not None:
|
||||||
|
size_val = display_size(size_raw)
|
||||||
|
row = f"| {str(i).ljust(NUM_W)} | {trunc(title, TITLE_W)} | {trunc(size_val, SIZE_W)} |"
|
||||||
|
print(row)
|
||||||
|
print(border)
|
||||||
if len(suggestions) > limit:
|
if len(suggestions) > limit:
|
||||||
print(f" ... and {len(suggestions) - limit} more not shown")
|
print(f"... {len(suggestions) - limit} more not shown")
|
||||||
try:
|
try:
|
||||||
choice = input("Enter number (or press Enter to cancel): ").strip()
|
choice = input("Enter number (or press Enter to cancel): ").strip()
|
||||||
except EOFError:
|
except EOFError:
|
||||||
@@ -400,15 +578,41 @@ def cmd_download(args):
|
|||||||
pass
|
pass
|
||||||
if not match:
|
if not match:
|
||||||
print("Here are potential matches (use the exact title with --game):")
|
print("Here are potential matches (use the exact title with --game):")
|
||||||
|
NUM_W = 4
|
||||||
|
TITLE_W = 60
|
||||||
|
SIZE_W = 12
|
||||||
|
def trunc(text, width):
|
||||||
|
if len(text) <= width:
|
||||||
|
return text + ' ' * (width - len(text))
|
||||||
|
if width <= 3:
|
||||||
|
return text[:width]
|
||||||
|
return text[:width-3] + '...'
|
||||||
|
# Use shared display_size
|
||||||
|
border = "+" + "-" * (NUM_W + 2) + "+" + "-" * (TITLE_W + 2) + "+" + "-" * (SIZE_W + 2) + "+"
|
||||||
|
header = f"| {'#'.ljust(NUM_W)} | {'Title'.ljust(TITLE_W)} | {'Size'.ljust(SIZE_W)} |"
|
||||||
|
print(border)
|
||||||
|
print(header)
|
||||||
|
print(border)
|
||||||
for i, s in enumerate(shown, start=1):
|
for i, s in enumerate(shown, start=1):
|
||||||
print(f" {i}. {s[2]}")
|
title = s[2]
|
||||||
|
size_val = ''
|
||||||
|
size_raw = None
|
||||||
|
for g in games:
|
||||||
|
if isinstance(g, (list, tuple)) and g and g[0] == title and len(g) >= 3:
|
||||||
|
size_raw = g[2]
|
||||||
|
break
|
||||||
|
if size_raw is not None:
|
||||||
|
size_val = display_size(size_raw)
|
||||||
|
row = f"| {str(i).ljust(NUM_W)} | {trunc(title, TITLE_W)} | {trunc(size_val, SIZE_W)} |"
|
||||||
|
print(row)
|
||||||
|
print(border)
|
||||||
if len(suggestions) > limit:
|
if len(suggestions) > limit:
|
||||||
print(f" ... and {len(suggestions) - limit} more")
|
print(f"... {len(suggestions) - limit} more")
|
||||||
print("Tip: list games with: python rgsx_cli.py games --platform \"%s\" --search \"%s\"" % (args.platform, query_raw))
|
print("Tip: list games with: games --platform \"%s\" --search \"%s\"" % (args.platform, query_raw))
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
else:
|
else:
|
||||||
print("No similar titles found.")
|
print("No similar titles found.")
|
||||||
print("Tip: list games with: python rgsx_cli.py games --platform \"%s\" --search \"%s\"" % (args.platform, query_raw))
|
print("Tip: list games with: games --platform \"%s\" --search \"%s\"" % (args.platform, query_raw))
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
title, url = match
|
title, url = match
|
||||||
@@ -454,33 +658,109 @@ def cmd_download(args):
|
|||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_loop(parser):
|
||||||
|
"""Simple REPL so user can run multiple subcommands without retyping python rgsx_cli.py.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Empty line: ignore
|
||||||
|
- help / ?: show help
|
||||||
|
- exit / quit: leave loop
|
||||||
|
- Global flags like --verbose can be set per command; verbose persists for session once set.
|
||||||
|
"""
|
||||||
|
persistent_verbose = False
|
||||||
|
print("RGSX CLI interactive mode. Type 'help' for commands, 'exit' to quit.")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = input("rgsx> ").strip()
|
||||||
|
except EOFError:
|
||||||
|
print()
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print()
|
||||||
|
break
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line in {"exit", "quit"}:
|
||||||
|
break
|
||||||
|
if line in {"help", "?"}:
|
||||||
|
parser.print_help()
|
||||||
|
continue
|
||||||
|
# Tokenize respecting simple quotes
|
||||||
|
try:
|
||||||
|
import shlex
|
||||||
|
argv = shlex.split(line)
|
||||||
|
except Exception:
|
||||||
|
argv = line.split()
|
||||||
|
# Inject persistent verbose if previously enabled and not explicitly disabled
|
||||||
|
if persistent_verbose and "--verbose" not in argv:
|
||||||
|
argv.insert(0, "--verbose")
|
||||||
|
try:
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
except SystemExit as se:
|
||||||
|
# argparse already printed error; continue loop
|
||||||
|
continue
|
||||||
|
# Update persistent verbose state
|
||||||
|
if getattr(args, 'verbose', False):
|
||||||
|
persistent_verbose = True
|
||||||
|
# Dispatch
|
||||||
|
if not getattr(args, 'cmd', None):
|
||||||
|
# If user typed e.g. just global flags
|
||||||
|
print("No command provided. Type 'help' to list commands.")
|
||||||
|
continue
|
||||||
|
setup_logging(getattr(args, 'verbose', False))
|
||||||
|
# Global force-update handling (duplicate minimal logic to avoid leaving loop early)
|
||||||
|
if getattr(args, 'force_update', False):
|
||||||
|
try:
|
||||||
|
if os.path.exists(config.SOURCES_FILE):
|
||||||
|
os.remove(config.SOURCES_FILE)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
shutil.rmtree(config.GAMES_FOLDER, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
shutil.rmtree(config.IMAGES_FOLDER, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
ok = ensure_data_present(verbose=True)
|
||||||
|
if not ok:
|
||||||
|
print("force-update failed; aborting command.")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
args.func(args)
|
||||||
|
except SystemExit:
|
||||||
|
# Subcommand may sys.exit on errors; swallow in REPL
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
p = argparse.ArgumentParser(prog="rgsx-cli", description="RGSX headless CLI")
|
p = argparse.ArgumentParser(prog="rgsx-cli", description="RGSX headless CLI")
|
||||||
p.add_argument("--verbose", action="store_true", help="Verbose logging")
|
p.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
p.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
p.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sub = p.add_subparsers(dest="cmd")
|
sub = p.add_subparsers(dest="cmd")
|
||||||
|
|
||||||
sp = sub.add_parser("platforms", help="List available platforms")
|
sp = sub.add_parser("platforms", aliases=["p"], help="List available platforms")
|
||||||
sp.add_argument("--json", action="store_true", help="Output JSON with name and folder")
|
sp.add_argument("--json", action="store_true", help="Output JSON with name and folder")
|
||||||
# Also accept global flags after the subcommand
|
|
||||||
sp.add_argument("--verbose", action="store_true", help="Verbose logging")
|
sp.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
sp.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
sp.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sp.set_defaults(func=cmd_platforms)
|
sp.set_defaults(func=cmd_platforms)
|
||||||
|
|
||||||
sg = sub.add_parser("games", help="List games for a platform")
|
sg = sub.add_parser("games", aliases=["g"], help="List games for a platform")
|
||||||
sg.add_argument("--platform", required=True, help="Platform name or key")
|
sg.add_argument("--platform", "--p", "-p", required=True, help="Platform name or key")
|
||||||
sg.add_argument("--search", help="Filter by name contains")
|
sg.add_argument("--search", "--s", "-s", help="Filter by name contains")
|
||||||
# Also accept global flags after the subcommand
|
|
||||||
sg.add_argument("--verbose", action="store_true", help="Verbose logging")
|
sg.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
sg.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
sg.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sg.set_defaults(func=cmd_games)
|
sg.set_defaults(func=cmd_games)
|
||||||
|
|
||||||
sd = sub.add_parser("download", help="Download a game by title")
|
sd = sub.add_parser("download", aliases=["dl"], help="Download a game by title")
|
||||||
sd.add_argument("--platform", required=True)
|
sd.add_argument("--platform", "--p", "-p", required=True)
|
||||||
sd.add_argument("--game", required=True)
|
sd.add_argument("--game", "--g", "-g", required=True)
|
||||||
sd.add_argument("--force", action="store_true", help="Override unsupported extension warning")
|
sd.add_argument("--force", "-f", action="store_true", help="Override unsupported extension warning")
|
||||||
sd.add_argument("--interactive", "-i", action="store_true", help="Prompt to choose from matches when no exact title is found")
|
sd.add_argument("--interactive", "-i", action="store_true", help="Prompt to choose from matches when no exact title is found")
|
||||||
# Also accept global flags after the subcommand
|
|
||||||
sd.add_argument("--verbose", action="store_true", help="Verbose logging")
|
sd.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
sd.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
sd.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sd.set_defaults(func=cmd_download)
|
sd.set_defaults(func=cmd_download)
|
||||||
@@ -488,13 +768,11 @@ def build_parser():
|
|||||||
sh = sub.add_parser("history", help="Show recent history")
|
sh = sub.add_parser("history", help="Show recent history")
|
||||||
sh.add_argument("--tail", type=int, default=50, help="Last N entries")
|
sh.add_argument("--tail", type=int, default=50, help="Last N entries")
|
||||||
sh.add_argument("--json", action="store_true")
|
sh.add_argument("--json", action="store_true")
|
||||||
# Also accept global flags after the subcommand
|
|
||||||
sh.add_argument("--verbose", action="store_true", help="Verbose logging")
|
sh.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
sh.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
sh.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sh.set_defaults(func=cmd_history)
|
sh.set_defaults(func=cmd_history)
|
||||||
|
|
||||||
sc = sub.add_parser("clear-history", help="Clear history")
|
sc = sub.add_parser("clear-history", aliases=["clear"], help="Clear history")
|
||||||
# Also accept global flags after the subcommand
|
|
||||||
sc.add_argument("--verbose", action="store_true", help="Verbose logging")
|
sc.add_argument("--verbose", action="store_true", help="Verbose logging")
|
||||||
sc.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
sc.add_argument("--force-update", "-force-update", action="store_true", help="Purge data (games/images/systems_list) and redownload")
|
||||||
sc.set_defaults(func=cmd_clear_history)
|
sc.set_defaults(func=cmd_clear_history)
|
||||||
@@ -507,6 +785,12 @@ def main(argv=None):
|
|||||||
# Force headless mode for CLI
|
# Force headless mode for CLI
|
||||||
os.environ.setdefault("RGSX_HEADLESS", "1")
|
os.environ.setdefault("RGSX_HEADLESS", "1")
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
|
if not argv:
|
||||||
|
# Start interactive mode
|
||||||
|
try:
|
||||||
|
interactive_loop(parser)
|
||||||
|
finally:
|
||||||
|
return
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
setup_logging(args.verbose)
|
setup_logging(args.verbose)
|
||||||
|
|
||||||
|
|||||||
@@ -14,34 +14,34 @@ except Exception as e:
|
|||||||
|
|
||||||
PROMPTS = [
|
PROMPTS = [
|
||||||
# Face buttons
|
# Face buttons
|
||||||
"SOUTH_BUTTON", # A on Xbox
|
"SOUTH_BUTTON - CONFIRM", # A on Xbox
|
||||||
"EAST_BUTTON", # B on Xbox
|
"EAST_BUTTON - CANCEL", # B on Xbox
|
||||||
"WEST_BUTTON", # X on Xbox
|
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES", # X on Xbox
|
||||||
"NORTH_BUTTON", # Y on Xbox
|
"NORTH_BUTTON - HISTORY", # Y on Xbox
|
||||||
# Meta
|
# Meta
|
||||||
"START",
|
"START - PAUSE",
|
||||||
"SELECT",
|
"SELECT - FILTER",
|
||||||
# D-Pad
|
# D-Pad
|
||||||
"DPAD_UP",
|
"DPAD_UP - MOVE UP",
|
||||||
"DPAD_DOWN",
|
"DPAD_DOWN - MOVE DOWN",
|
||||||
"DPAD_LEFT",
|
"DPAD_LEFT - MOVE LEFT",
|
||||||
"DPAD_RIGHT",
|
"DPAD_RIGHT - MOVE RIGHT",
|
||||||
# Bumpers
|
# Bumpers
|
||||||
"LEFT_BUMPER",
|
"LEFT_BUMPER - LB/L1 - Delete last char",
|
||||||
"RIGHT_BUMPER",
|
"RIGHT_BUMPER - RB/R1 - Add space",
|
||||||
# Triggers
|
# Triggers
|
||||||
"LEFT_TRIGGER",
|
"LEFT_TRIGGER - LT/L2 - Page +",
|
||||||
"RIGHT_TRIGGER",
|
"RIGHT_TRIGGER - RT/R2 - Page -",
|
||||||
# Left stick directions
|
# Left stick directions
|
||||||
"JOYSTICK_LEFT_UP",
|
"JOYSTICK_LEFT_UP - MOVE UP",
|
||||||
"JOYSTICK_LEFT_DOWN",
|
"JOYSTICK_LEFT_DOWN - MOVE DOWN",
|
||||||
"JOYSTICK_LEFT_LEFT",
|
"JOYSTICK_LEFT_LEFT - MOVE LEFT",
|
||||||
"JOYSTICK_LEFT_RIGHT",
|
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT",
|
||||||
# Right stick directions
|
# Right stick directions
|
||||||
"JOYSTICK_RIGHT_UP",
|
"JOYSTICK_RIGHT_UP - MOVE U P",
|
||||||
"JOYSTICK_RIGHT_DOWN",
|
"JOYSTICK_RIGHT_DOWN - MOVE DOWN",
|
||||||
"JOYSTICK_RIGHT_LEFT",
|
"JOYSTICK_RIGHT_LEFT - MOVE LEFT",
|
||||||
"JOYSTICK_RIGHT_RIGHT",
|
"JOYSTICK_RIGHT_RIGHT - MOVE RIGHT",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user