11 Commits

28 changed files with 359 additions and 86 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# ---> jlauncher # ---> jail-launcher
# Datos descargados en tiempo de ejecución (clones git + cache de metadata) # Datos descargados en tiempo de ejecución (clones git + cache de metadata)
jlauncher_data/ jail_launcher_data/
# Preferencias locales del usuario # Preferencias locales del usuario
settings.json settings.json
*.dist/ *.dist/
+14 -14
View File
@@ -1,4 +1,4 @@
# jlauncher # jail-launcher
Lanzador de juegos de **jailgames**. A partir de `games.toml`, lista los juegos, los Lanzador de juegos de **jailgames**. A partir de `games.toml`, lista los juegos, los
clona/actualiza desde sus repos Gitea, lee su icono y descripción, y los compila/ejecuta. clona/actualiza desde sus repos Gitea, lee su icono y descripción, y los compila/ejecuta.
@@ -15,14 +15,14 @@ GUI en **Python + PySide6**, pensada para compilarse a binario nativo con **Nuit
```bash ```bash
pip install PySide6 pip install PySide6
python -m jlauncher python -m jail_launcher
``` ```
La app crea una carpeta `jlauncher_data/` junto al proyecto (o junto al binario, si está La app crea una carpeta `jail_launcher_data/` junto al proyecto (o junto al binario, si está
compilado) con esta estructura **anidada (Versión 1)**: compilado) con esta estructura **anidada (Versión 1)**:
``` ```
jlauncher_data/ jail_launcher_data/
<id_juego>/ <id_juego>/
repo/ # git clone del juego repo/ # git clone del juego
metadata/ metadata/
@@ -40,7 +40,7 @@ jlauncher_data/
- icono desde `release/icons/icon.png`. - icono desde `release/icons/icon.png`.
- **Juga**: si hay `build_cmd`, compila primero; luego ejecuta `run_cmd`. Para estos - **Juga**: si hay `build_cmd`, compila primero; luego ejecuta `run_cmd`. Para estos
juegos basta `run_cmd = "make run"` (compila y ejecuta), con `build_cmd` vacío. juegos basta `run_cmd = "make run"` (compila y ejecuta), con `build_cmd` vacío.
- **Esborra**: elimina la descarga local (carpeta del juego en `jlauncher_data/`), - **Esborra**: elimina la descarga local (carpeta del juego en `jail_launcher_data/`),
sin quitar el juego del `games.toml`. sin quitar el juego del `games.toml`.
Menú **Opcions**: *Amaga els jocs no descarregats* (filtro persistente) y Menú **Opcions**: *Amaga els jocs no descarregats* (filtro persistente) y
@@ -55,7 +55,7 @@ Una entrada `[[game]]` por juego. Campos:
| Campo | Obligatorio | Descripción | | Campo | Obligatorio | Descripción |
|---------------|-------------|--------------------------------------------------------------------| |---------------|-------------|--------------------------------------------------------------------|
| `id` | sí | slug → nombre de carpeta en `jlauncher_data/` | | `id` | sí | slug → nombre de carpeta en `jail_launcher_data/` |
| `name` | sí | nombre visible | | `name` | sí | nombre visible |
| `clone_url` | sí | URL de git clone / pull | | `clone_url` | sí | URL de git clone / pull |
| `run_cmd` | sí | comando que ejecuta el juego (cwd = repo) | | `run_cmd` | sí | comando que ejecuta el juego (cwd = repo) |
@@ -68,16 +68,16 @@ Una entrada `[[game]]` por juego. Campos:
`build.sh` lo hace todo: crea el `.venv`, instala dependencias (PySide6 + Nuitka + `build.sh` lo hace todo: crea el `.venv`, instala dependencias (PySide6 + Nuitka +
zstandard) y compila un único ejecutable comprimido, empaquetándolo en zstandard) y compila un único ejecutable comprimido, empaquetándolo en
`dist/jlauncher-v<versión>-<os>-<arch>.tar.gz` junto a `games.toml`. `dist/jail-launcher-v<versión>-<os>-<arch>.tar.gz` junto a `games.toml`.
```bash ```bash
./build.sh ./build.sh
# binario: dist/jlauncher (+ dist/games.toml) # binario: dist/jail-launcher (+ dist/games.toml)
``` ```
El binario crea `jlauncher_data/` y `settings.json` **junto a sí mismo** (resuelto vía El binario crea `jail_launcher_data/` y `settings.json` **junto a sí mismo** (resuelto vía
`NUITKA_ONEFILE_DIRECTORY`). El punto de entrada para empaquetar es `app.py` (desde `NUITKA_ONEFILE_DIRECTORY`). El punto de entrada para empaquetar es `app.py` (desde
fuente se ejecuta con `python -m jlauncher`). fuente se ejecuta con `python -m jail_launcher`).
### Prerequisitos del sistema (no los instala el script) ### Prerequisitos del sistema (no los instala el script)
@@ -95,8 +95,8 @@ binario suelto sino una **app nativa**:
```bash ```bash
./build.sh ./build.sh
# -> dist/jlauncher.app # -> dist/Jail Launcher.app
# -> dist/jlauncher-v<versión>-macos-<arch>.dmg (arrastrar la app a Aplicaciones) # -> dist/jail-launcher-v<versión>-macos-<arch>.dmg (arrastrar la app a Aplicaciones)
``` ```
El icono vive en `icon/`: el build usa `icon/icon.icns` para el bundle y copia El icono vive en `icon/`: el build usa `icon/icon.icns` para el bundle y copia
@@ -105,9 +105,9 @@ sustituye esos ficheros (regenerables con `icon/create_icons.py`).
A diferencia del onefile, la `.app` **no** escribe junto a sí misma (rompería al moverla a A diferencia del onefile, la `.app` **no** escribe junto a sí misma (rompería al moverla a
`/Applications`): guarda sus datos en `/Applications`): guarda sus datos en
`~/Library/Application Support/jailgames/jlauncher/` (`jlauncher_data/`, `settings.json` y `~/Library/Application Support/jailgames/jail-launcher/` (`jail_launcher_data/`, `settings.json` y
una copia editable de `games.toml`, sembrada la primera vez desde el bundle). una copia editable de `games.toml`, sembrada la primera vez desde el bundle).
La app va **sin firma Developer ID** (firma ad-hoc), así que Gatekeeper avisará la primera La app va **sin firma Developer ID** (firma ad-hoc), así que Gatekeeper avisará la primera
vez: ábrela con **clic derecho → Abrir**, o ejecuta vez: ábrela con **clic derecho → Abrir**, o ejecuta
`xattr -dr com.apple.quarantine /Applications/jlauncher.app`. `xattr -dr com.apple.quarantine "/Applications/Jail Launcher.app"`.
+3 -3
View File
@@ -1,11 +1,11 @@
"""Punto de entrada para empaquetar con Nuitka. """Punto de entrada para empaquetar con Nuitka.
Importa el paquete ``jlauncher`` con imports absolutos para que los imports relativos Importa el paquete ``jail_launcher`` con imports absolutos para que los imports relativos
internos (``from .config import …``) resuelvan correctamente al compilar. Para ejecutar internos (``from .config import …``) resuelvan correctamente al compilar. Para ejecutar
desde fuente sigue valiendo ``python -m jlauncher``. desde fuente sigue valiendo ``python -m jail_launcher``.
""" """
from jlauncher.__main__ import main from jail_launcher.__main__ import main
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())
+138
View File
@@ -0,0 +1,138 @@
# Compila jail-launcher con Nuitka y empaqueta un release para Windows.
# - jail-launcher.exe (onefile, GUI sin consola, con icono) + games.toml + .zip.
# Equivalente nativo de build.sh (que cubre Linux/macOS).
# Requisitos del sistema (no los instala el script):
# - Python 3.10+ (con el launcher «py» o «python» en el PATH).
# - Un compilador C: Nuitka usa MSVC (Build Tools de Visual Studio) si existe;
# si no, ofrece descargar MinGW automáticamente (-AssumeYesForDownloads).
# - git en el PATH (lo usa la app en tiempo de ejecución, no el build).
#
# Uso: pwsh -File build.ps1 (o: .\build.ps1 desde PowerShell)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
# Situarnos en la carpeta del script.
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $Here
# --- Versión (leída de jail_launcher/__init__.py) ---------------------------------
$initPath = Join-Path $Here 'jail_launcher\__init__.py'
$verMatch = Select-String -Path $initPath -Pattern '^__version__\s*=\s*"([^"]*)"' |
Select-Object -First 1
if (-not $verMatch) {
Write-Error '[build] no se pudo leer __version__ de jail_launcher/__init__.py'
}
$Version = $verMatch.Matches[0].Groups[1].Value
# Arquitectura al estilo uname -m (x86_64 / arm64).
$Arch = switch ($env:PROCESSOR_ARCHITECTURE) {
'AMD64' { 'x86_64' }
'ARM64' { 'arm64' }
default { $env:PROCESSOR_ARCHITECTURE.ToLower() }
}
# --- Intérprete de Python -----------------------------------------------------
# Preferimos el launcher «py -3»; si no, «python».
function Resolve-Python {
if (Get-Command py -ErrorAction SilentlyContinue) { return @('py', '-3') }
if (Get-Command python -ErrorAction SilentlyContinue) { return @('python') }
Write-Error '[build] no se encontró Python (ni «py» ni «python» en el PATH).'
}
$Py = Resolve-Python
# Comprueba si un módulo se puede importar en el venv, SIN abortar el script:
# bajo $ErrorActionPreference='Stop', un código de salida ≠ 0 (módulo ausente)
# se trataría como error terminante. Aquí relajamos eso solo para la sonda.
function Test-PyModule([string]$Module) {
$prev = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
& $VenvPython -c "import $Module" 2>&1 | Out-Null
return ($LASTEXITCODE -eq 0)
}
finally {
$ErrorActionPreference = $prev
}
}
# --- venv (en Windows los ejecutables viven en .venv\Scripts) -----------------
$VenvPython = Join-Path $Here '.venv\Scripts\python.exe'
if (-not (Test-Path $VenvPython)) {
Write-Host '[build] creando venv…'
& $Py[0] $Py[1..($Py.Count - 1)] -m venv .venv
& $VenvPython -m pip install --quiet --upgrade pip
}
Write-Host '[build] sincronizando dependencias…'
& $VenvPython -m pip install --quiet -r requirements.txt
# Nuitka + zstandard (compresión del onefile → binario más pequeño).
if (-not (Test-PyModule 'nuitka')) {
Write-Host '[build] instalando nuitka en el venv…'
& $VenvPython -m pip install --quiet 'nuitka[onefile]'
}
if (-not (Test-PyModule 'zstandard')) {
Write-Host '[build] instalando zstandard (compresión onefile)…'
& $VenvPython -m pip install --quiet zstandard
}
Write-Host "[build] versión: v$Version"
Write-Host '[build] limpiando artefactos previos…'
foreach ($d in 'dist', 'build', 'app.build', 'app.dist', 'app.onefile-build') {
if (Test-Path $d) { Remove-Item -Recurse -Force $d }
}
$IconIco = 'icon\icon.ico'
$IconPng = 'icon\icon.png'
if (-not (Test-Path $IconIco)) {
Write-Error "[build] falta $IconIco"
}
# --- Compilación onefile ------------------------------------------------------
Write-Host '[build] compilando jail-launcher.exe (PySide6 onefile; puede tardar varios minutos)…'
& $VenvPython -m nuitka `
--onefile `
--assume-yes-for-downloads `
--enable-plugin=pyside6 `
--include-package=jail_launcher `
--windows-icon-from-ico=$IconIco `
--windows-console-mode=disable `
--company-name=jailgames `
--product-name=jail-launcher `
--product-version=$Version `
--file-version=$Version `
--output-dir=dist `
--output-filename=jail-launcher `
--remove-output `
app.py
if ($LASTEXITCODE -ne 0) {
Write-Error '[build] Nuitka falló.'
}
$Exe = 'dist\jail-launcher.exe'
if (-not (Test-Path $Exe)) {
Write-Error '[build] Nuitka no produjo dist\jail-launcher.exe'
}
# games.toml junto al .exe (la app lo lee desde ahí: base_dir junto al binario).
Write-Host '[build] copiando games.toml junto al ejecutable…'
Copy-Item games.toml dist\games.toml -Force
# icon.png para el diálogo «Quant a» (paths.app_icon_path → base_dir()\icon\icon.png).
Write-Host '[build] sembrando icon\icon.png para el diálogo «Quant a»…'
New-Item -ItemType Directory -Force -Path dist\icon | Out-Null
Copy-Item $IconPng dist\icon\icon.png -Force
# --- Empaquetado .zip ---------------------------------------------------------
$ReleaseName = "jail-launcher-v$Version-windows-$Arch"
$Zip = "dist\$ReleaseName.zip"
Write-Host "[build] empaquetando release $ReleaseName.zip…"
Compress-Archive -Path dist\jail-launcher.exe, dist\games.toml, dist\icon `
-DestinationPath $Zip -Force
Write-Host '[build] hecho:'
Get-Item dist\jail-launcher.exe, dist\games.toml, $Zip |
Format-Table -AutoSize Name, @{Name = 'Size'; Expression = { '{0:N0} B' -f $_.Length } }
Write-Host '[build] el binario crea jail_launcher_data\ y settings.json junto a sí mismo.'
Write-Host '[build] distribuir: descomprimir el .zip (jail-launcher.exe + games.toml + icon\ juntos).'
+24 -23
View File
@@ -1,16 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Compila jlauncher con Nuitka y empaqueta un release. # Compila jail-launcher con Nuitka y empaqueta un release.
# - Linux: binario onefile + tar.gz. # - Linux: binario onefile + tar.gz.
# - macOS: jlauncher.app (con icono) dentro de un .dmg arrastrable a /Applications. # - macOS: «Jail Launcher.app» (con icono) dentro de un .dmg arrastrable a /Applications.
# Requisitos del sistema (no los instala el script): ver README (compilador C, git…). # Requisitos del sistema (no los instala el script): ver README (compilador C, git…).
set -euo pipefail set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)" HERE="$(cd "$(dirname "$0")" && pwd)"
cd "$HERE" cd "$HERE"
VERSION="$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' jlauncher/__init__.py | head -n1)" VERSION="$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' jail_launcher/__init__.py | head -n1)"
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo "[build] no se pudo leer __version__ de jlauncher/__init__.py" >&2 echo "[build] no se pudo leer __version__ de jail_launcher/__init__.py" >&2
exit 1 exit 1
fi fi
ARCH="$(uname -m)" ARCH="$(uname -m)"
@@ -52,28 +52,29 @@ if [ "$OS" = "darwin" ]; then
exit 1 exit 1
fi fi
echo "[build] compilando jlauncher.app (PySide6; puede tardar varios minutos)…" echo "[build] compilando «Jail Launcher.app» (PySide6; puede tardar varios minutos)…"
.venv/bin/python -m nuitka \ .venv/bin/python -m nuitka \
--standalone \ --standalone \
--macos-create-app-bundle \ --macos-create-app-bundle \
--macos-app-icon="$ICON_ICNS" \ --macos-app-icon="$ICON_ICNS" \
--macos-app-name=jlauncher \ --macos-app-name="Jail Launcher" \
--macos-app-version="$VERSION" \ --macos-app-version="$VERSION" \
--macos-signed-app-name=com.jailgames.jlauncher \ --macos-signed-app-name=com.jailgames.jail-launcher \
--company-name=jailgames \ --company-name=jailgames \
--product-name=jlauncher \ --product-name=jail-launcher \
--product-version="$VERSION" \ --product-version="$VERSION" \
--assume-yes-for-downloads \ --assume-yes-for-downloads \
--enable-plugin=pyside6 \ --enable-plugin=pyside6 \
--include-package=jlauncher \ --include-package=jail_launcher \
--output-dir=dist \ --output-dir=dist \
--output-filename=jlauncher \ --output-filename=jail-launcher \
--remove-output \ --remove-output \
app.py app.py
# Según la versión, Nuitka nombra el bundle a partir del script de entrada # Según la versión, Nuitka nombra el bundle a partir del script de entrada
# (app.py -> app.app). Lo normalizamos a jlauncher.app. # (app.py -> app.app). Lo normalizamos a «Jail Launcher.app» (nombre visible en
APP="dist/jlauncher.app" # Finder/Dock; el ejecutable interno sigue siendo jail-launcher).
APP="dist/Jail Launcher.app"
if [ ! -d "$APP" ]; then if [ ! -d "$APP" ]; then
PRODUCED="$(find dist -maxdepth 1 -name '*.app' | head -n1)" PRODUCED="$(find dist -maxdepth 1 -name '*.app' | head -n1)"
if [ -z "$PRODUCED" ]; then if [ -z "$PRODUCED" ]; then
@@ -91,35 +92,35 @@ if [ "$OS" = "darwin" ]; then
# Bundle ad-hoc (sin Developer ID): quitamos quarantine para abrir sin fricción local. # Bundle ad-hoc (sin Developer ID): quitamos quarantine para abrir sin fricción local.
xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true
DMG="dist/jlauncher-v${VERSION}-macos-${ARCH}.dmg" DMG="dist/jail-launcher-v${VERSION}-macos-${ARCH}.dmg"
echo "[build] empaquetando ${DMG}" echo "[build] empaquetando ${DMG}"
STAGE="$(mktemp -d)" STAGE="$(mktemp -d)"
cp -R "$APP" "$STAGE/" cp -R "$APP" "$STAGE/"
ln -s /Applications "$STAGE/Applications" ln -s /Applications "$STAGE/Applications"
rm -f "$DMG" rm -f "$DMG"
hdiutil create -volname "jlauncher" -srcfolder "$STAGE" \ hdiutil create -volname "Jail Launcher" -srcfolder "$STAGE" \
-ov -format UDZO "$DMG" >/dev/null -ov -format UDZO "$DMG" >/dev/null
rm -rf "$STAGE" rm -rf "$STAGE"
echo "[build] hecho:" echo "[build] hecho:"
du -sh "$APP" | sed 's/^/[build] /' du -sh "$APP" | sed 's/^/[build] /'
ls -lh "$DMG" ls -lh "$DMG"
echo "[build] la app guarda datos en ~/Library/Application Support/jailgames/jlauncher/." echo "[build] la app guarda datos en ~/Library/Application Support/jailgames/jail-launcher/."
echo "[build] distribuir: el .dmg (arrastrar jlauncher.app a Aplicaciones)." echo "[build] distribuir: el .dmg (arrastrar «Jail Launcher.app» a Aplicaciones)."
echo "[build] sin firma Developer ID: primera apertura con clic derecho → Abrir." echo "[build] sin firma Developer ID: primera apertura con clic derecho → Abrir."
else else
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Linux (y resto): binario onefile + tar.gz # Linux (y resto): binario onefile + tar.gz
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
RELEASE_NAME="jlauncher-v${VERSION}-${OS}-${ARCH}" RELEASE_NAME="jail-launcher-v${VERSION}-${OS}-${ARCH}"
echo "[build] compilando (PySide6 onefile; puede tardar varios minutos)…" echo "[build] compilando (PySide6 onefile; puede tardar varios minutos)…"
.venv/bin/python -m nuitka \ .venv/bin/python -m nuitka \
--onefile \ --onefile \
--assume-yes-for-downloads \ --assume-yes-for-downloads \
--enable-plugin=pyside6 \ --enable-plugin=pyside6 \
--include-package=jlauncher \ --include-package=jail_launcher \
--output-dir=dist \ --output-dir=dist \
--output-filename=jlauncher \ --output-filename=jail-launcher \
--remove-output \ --remove-output \
--lto=yes \ --lto=yes \
app.py app.py
@@ -128,10 +129,10 @@ else
cp games.toml dist/games.toml cp games.toml dist/games.toml
echo "[build] empaquetando release ${RELEASE_NAME}.tar.gz…" echo "[build] empaquetando release ${RELEASE_NAME}.tar.gz…"
tar -czf "dist/${RELEASE_NAME}.tar.gz" -C dist jlauncher games.toml tar -czf "dist/${RELEASE_NAME}.tar.gz" -C dist jail-launcher games.toml
echo "[build] hecho:" echo "[build] hecho:"
ls -lh "dist/jlauncher" "dist/games.toml" "dist/${RELEASE_NAME}.tar.gz" ls -lh "dist/jail-launcher" "dist/games.toml" "dist/${RELEASE_NAME}.tar.gz"
echo "[build] el binario crea jlauncher_data/ y settings.json junto a sí mismo." echo "[build] el binario crea jail_launcher_data/ y settings.json junto a sí mismo."
echo "[build] distribuir: descomprimir el tar.gz (jlauncher + games.toml juntos)." echo "[build] distribuir: descomprimir el tar.gz (jail-launcher + games.toml juntos)."
fi fi
+13 -13
View File
@@ -1,7 +1,7 @@
# Configuración de jlauncher — lista de juegos. # Configuración de jail-launcher — lista de juegos.
# #
# Campos por juego ([[game]]): # Campos por juego ([[game]]):
# id (obligatorio) slug interno → nombre de carpeta en jlauncher_data/ # id (obligatorio) slug interno → nombre de carpeta en jail_launcher_data/
# name (obligatorio) nombre visible en la lista # name (obligatorio) nombre visible en la lista
# clone_url (obligatorio) URL para git clone / git pull # clone_url (obligatorio) URL para git clone / git pull
# run_cmd (obligatorio) comando que ejecuta el juego (cwd = repo clonado) # run_cmd (obligatorio) comando que ejecuta el juego (cwd = repo clonado)
@@ -15,30 +15,30 @@
# Otros pills (topics, descripción, fecha de lanzamiento, versión) salen # Otros pills (topics, descripción, fecha de lanzamiento, versión) salen
# automáticamente de Gitea / git; no hace falta escribirlos aquí. # automáticamente de Gitea / git; no hace falta escribirlos aquí.
data_dir = "jlauncher_data" data_dir = "jail_launcher_data"
[[game]] [[game]]
id = "coffee_crisis" id = "coffee-crisis"
name = "Coffee Crisis" name = "Coffee Crisis"
clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/coffee_crisis.git" clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/coffee-crisis.git"
build_cmd = "" build_cmd = ""
run_cmd = "make run" run_cmd = "make run"
players = "1-2 jugadors" players = "1-2 jugadors"
author = "JailDesigner" author = "JailDesigner"
[[game]] [[game]]
id = "coffee_crisis_arcade_edition" id = "coffee-crisis-ae"
name = "Coffee Crisis Arcade Edition" name = "Coffee Crisis Arcade Edition"
clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/coffee_crisis_arcade_edition.git" clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/coffee-crisis-ae.git"
build_cmd = "" build_cmd = ""
run_cmd = "make run" run_cmd = "make run"
players = "1-2 jugadors" players = "1-2 jugadors"
author = "JailDesigner" author = "JailDesigner"
[[game]] [[game]]
id = "jaildoctors_dilemma" id = "jaildoctors-dilemma"
name = "JailDoctor's Dilemma" name = "JailDoctor's Dilemma"
clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/jaildoctors_dilemma.git" clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/jaildoctors-dilemma.git"
build_cmd = "" build_cmd = ""
run_cmd = "make run" run_cmd = "make run"
players = "1 jugador" players = "1 jugador"
@@ -54,18 +54,18 @@ players = "1 jugador"
author = "JailDesigner" author = "JailDesigner"
[[game]] [[game]]
id = "orni_attack" id = "orni-attack"
name = "Orni Attack" name = "Orni Attack"
clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/orni_attack.git" clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/orni-attack.git"
build_cmd = "" build_cmd = ""
run_cmd = "make run" run_cmd = "make run"
players = "1-2 jugadors" players = "1-2 jugadors"
author = "JailDesigner" author = "JailDesigner"
[[game]] [[game]]
id = "projecte_2026" id = "projecte-2026"
name = "Projecte 2026" name = "Projecte 2026"
clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/projecte_2026.git" clone_url = "https://gitea.sustancia.synology.me/jaildesigner-jailgames/projecte-2026.git"
build_cmd = "" build_cmd = ""
run_cmd = "make run" run_cmd = "make run"
players = "1 jugador" players = "1 jugador"
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
"""jail-launcher — lanzador de juegos jailgames."""
__version__ = "1.0.5"
@@ -15,7 +15,10 @@ from .ui.theme import apply_theme
def main() -> int: def main() -> int:
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setApplicationName("jlauncher") app.setApplicationName("jail-launcher")
# No fixem applicationDisplayName: a Windows/X11 Qt l'afegeix al títol de la
# finestra ("títol - displayName") i duplicaria «Jail Launcher», que ja surt
# a WINDOW_TITLE. El nom de l'app al menú de macOS ve del bundle (Nuitka).
icon_path = app_icon_path() icon_path = app_icon_path()
if icon_path is not None: if icon_path is not None:
app.setWindowIcon(QIcon(str(icon_path))) app.setWindowIcon(QIcon(str(icon_path)))
@@ -31,7 +31,7 @@ class Game:
@dataclass @dataclass
class Config: class Config:
data_dir: str = "jlauncher_data" data_dir: str = "jail_launcher_data"
games: list[Game] = field(default_factory=list) games: list[Game] = field(default_factory=list)
@@ -80,4 +80,4 @@ def load_config(path: Path) -> Config:
if not games: if not games:
raise ValueError("games.toml no define ningún [[game]]") raise ValueError("games.toml no define ningún [[game]]")
return Config(data_dir=raw.get("data_dir", "jlauncher_data"), games=games) return Config(data_dir=raw.get("data_dir", "jail_launcher_data"), games=games)
@@ -10,7 +10,9 @@ import datetime as _dt
import json import json
import os import os
import shutil import shutil
import stat
import subprocess import subprocess
import sys
import urllib.error import urllib.error
import urllib.request import urllib.request
from collections.abc import Callable from collections.abc import Callable
@@ -44,11 +46,41 @@ class NetConfig:
DEFAULT_NET = NetConfig() DEFAULT_NET = NetConfig()
# Windows: evita que cada `git` (aplicación de consola) abra una ventana negra
# cuando el lanzador corre como GUI sin consola (el .exe compilado con Nuitka
# --windows-console-mode=disable). En el resto de SO es 0 (sin efecto).
_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
def _noop(_: str) -> None: def _noop(_: str) -> None:
pass pass
def _force_rmtree(path: Path, log: LogFn = _noop) -> None:
"""Esborra un arbre de fitxers, fins i tot amb fitxers de només-lectura.
A Windows els objectes de ``.git`` es creen com a només-lectura i fan que
``shutil.rmtree`` falli amb ``PermissionError``; cal llevar el bit d'escriptura
i reintentar. A diferència de ``ignore_errors=True``, aquí els errors que no
puguem resoldre es registren al log en lloc d'empassar-se en silenci.
"""
if not path.exists():
return
def handle(func: Callable, p: str, _exc: object) -> None:
try:
os.chmod(p, stat.S_IWRITE)
func(p) # reintenta l'operació (unlink/rmdir) que havia fallat
except OSError as exc:
log(f"No s'ha pogut esborrar {p}: {exc}")
# Python 3.12 va renombrar el paràmetre `onerror` a `onexc`; suportem tots dos.
if sys.version_info >= (3, 12):
shutil.rmtree(path, onexc=handle)
else:
shutil.rmtree(path, onerror=handle)
def _auth_args(token: str) -> list[str]: def _auth_args(token: str) -> list[str]:
"""Args -c para autenticar git ante Gitea con un token, sin tocar .git/config.""" """Args -c para autenticar git ante Gitea con un token, sin tocar .git/config."""
if not token: if not token:
@@ -99,6 +131,7 @@ def _run_git(
text=True, text=True,
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"}, env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
timeout=timeout, timeout=timeout,
creationflags=_NO_WINDOW,
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
emit( emit(
@@ -166,7 +199,7 @@ def delete_local(root: Path, game: Game, log: LogFn = _noop) -> None:
log(f"{game.name}: no hi ha res a esborrar") log(f"{game.name}: no hi ha res a esborrar")
return return
log(f"Esborrant la descàrrega local de {game.name}") log(f"Esborrant la descàrrega local de {game.name}")
shutil.rmtree(target, ignore_errors=True) _force_rmtree(target, log)
def download( def download(
@@ -201,7 +234,7 @@ def download(
else: else:
log(f"Clonant {game.name}") log(f"Clonant {game.name}")
if repo.exists(): # carpeta a medias sin .git: limpiarla if repo.exists(): # carpeta a medias sin .git: limpiarla
shutil.rmtree(repo, ignore_errors=True) _force_rmtree(repo, log)
_run_git( _run_git(
["clone", game.clone_url, str(repo)], ["clone", game.clone_url, str(repo)],
None, None,
@@ -272,6 +305,7 @@ def _read_version(game: Game, repo: Path, log: LogFn) -> str:
capture_output=True, capture_output=True,
text=True, text=True,
timeout=20, timeout=20,
creationflags=_NO_WINDOW,
) )
except (OSError, subprocess.SubprocessError) as exc: except (OSError, subprocess.SubprocessError) as exc:
log(f"version_cmd ha fallat: {exc}") log(f"version_cmd ha fallat: {exc}")
@@ -3,7 +3,7 @@
Compilado con Nuitka, ``__compiled__`` existe. En modo ``--onefile`` la carpeta del Compilado con Nuitka, ``__compiled__`` existe. En modo ``--onefile`` la carpeta del
binario real la expone ``NUITKA_ONEFILE_DIRECTORY`` (Nuitka 4.x); con versiones que usan binario real la expone ``NUITKA_ONEFILE_DIRECTORY`` (Nuitka 4.x); con versiones que usan
``NUITKA_ONEFILE_BINARY`` tomamos su carpeta; si no, ``sys.executable`` (standalone). ``NUITKA_ONEFILE_BINARY`` tomamos su carpeta; si no, ``sys.executable`` (standalone).
Ejecutando desde fuente usamos la raíz del proyecto (la carpeta que contiene ``jlauncher``). Ejecutando desde fuente usamos la raíz del proyecto (la carpeta que contiene ``jail_launcher``).
""" """
from __future__ import annotations from __future__ import annotations
@@ -17,7 +17,7 @@ CONFIG_NAME = "games.toml"
# Carpeta de soporte en macOS cuando corremos como .app: ~/Library/Application Support/… # Carpeta de soporte en macOS cuando corremos como .app: ~/Library/Application Support/…
APP_SUPPORT_VENDOR = "jailgames" APP_SUPPORT_VENDOR = "jailgames"
APP_SUPPORT_APP = "jlauncher" APP_SUPPORT_APP = "jail-launcher"
def is_compiled() -> bool: def is_compiled() -> bool:
@@ -26,7 +26,7 @@ def is_compiled() -> bool:
def base_dir() -> Path: def base_dir() -> Path:
"""Carpeta base junto a la que viven games.toml y jlauncher_data.""" """Carpeta base junto a la que viven games.toml y jail_launcher_data."""
if is_compiled(): if is_compiled():
directory = os.environ.get("NUITKA_ONEFILE_DIRECTORY") directory = os.environ.get("NUITKA_ONEFILE_DIRECTORY")
if directory: if directory:
@@ -35,14 +35,14 @@ def base_dir() -> Path:
if binary: if binary:
return Path(binary).resolve().parent return Path(binary).resolve().parent
return Path(sys.executable).resolve().parent return Path(sys.executable).resolve().parent
# Desde fuente: raíz del proyecto = padre del paquete jlauncher/ # Desde fuente: raíz del proyecto = padre del paquete jail_launcher/
return Path(__file__).resolve().parent.parent return Path(__file__).resolve().parent.parent
def macos_app_bundle() -> Path | None: def macos_app_bundle() -> Path | None:
"""Ruta del .app si corremos como bundle de macOS (``…/jlauncher.app``); si no, None. """Ruta del .app si corremos como bundle de macOS (``…/jail-launcher.app``); si no, None.
En un app bundle el ejecutable vive en ``/jlauncher.app/Contents/MacOS/jlauncher``, En un app bundle el ejecutable vive en ``/jail-launcher.app/Contents/MacOS/jail-launcher``,
dentro de un árbol no escribible al instalarse en ``/Applications``. Detectarlo nos dentro de un árbol no escribible al instalarse en ``/Applications``. Detectarlo nos
deja redirigir datos y settings a ``~/Library/Application Support``. deja redirigir datos y settings a ``~/Library/Application Support``.
""" """
@@ -106,7 +106,7 @@ def config_file() -> Path:
return base_dir() / CONFIG_NAME return base_dir() / CONFIG_NAME
def data_root(data_dir: str = "jlauncher_data") -> Path: def data_root(data_dir: str = "jail_launcher_data") -> Path:
"""Carpeta raíz de datos; se crea si no existe.""" """Carpeta raíz de datos; se crea si no existe."""
root = writable_base() / data_dir root = writable_base() / data_dir
root.mkdir(parents=True, exist_ok=True) root.mkdir(parents=True, exist_ok=True)
@@ -4,6 +4,7 @@ from __future__ import annotations
import os import os
import subprocess import subprocess
import sys
from collections.abc import Callable from collections.abc import Callable
from pathlib import Path from pathlib import Path
@@ -12,6 +13,11 @@ from .paths import repo_dir
LogFn = Callable[[str], None] LogFn = Callable[[str], None]
# Windows: evita que la compilación/ejecución (shell + make/gcc, apps de consola)
# abra ventanas negras cuando el lanzador corre como GUI sin consola (el .exe).
# En el resto de SO es 0 (sin efecto).
_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
# Directorios habituales de herramientas de línea de comandos (Homebrew, MacPorts…). # Directorios habituales de herramientas de línea de comandos (Homebrew, MacPorts…).
# Una .app de macOS lanzada desde Finder/Dock NO hereda el PATH del shell de login, # Una .app de macOS lanzada desde Finder/Dock NO hereda el PATH del shell de login,
# así que herramientas como cmake, instaladas con Homebrew en /opt/homebrew/bin # así que herramientas como cmake, instaladas con Homebrew en /opt/homebrew/bin
@@ -26,6 +32,17 @@ _EXTRA_PATH_DIRS = (
"/opt/local/bin", "/opt/local/bin",
) )
# Prefijos de Homebrew/MacPorts. Una .app lanzada desde Finder/Dock tampoco
# hereda CPATH/LIBRARY_PATH/PKG_CONFIG_PATH del shell, así que el compilador no
# encuentra las cabeceras (p.ej. <SDL3/SDL_filesystem.h>) ni las librerías
# instaladas con `brew`. Añadimos sus include/lib explícitamente. En Linux estos
# paths no existen y se filtran solos.
_EXTRA_PREFIXES = (
"/opt/homebrew",
"/usr/local",
"/opt/local",
)
def _noop(_: str) -> None: def _noop(_: str) -> None:
pass pass
@@ -41,9 +58,32 @@ def _launch_env() -> dict[str, str]:
if d not in parts and os.path.isdir(d): if d not in parts and os.path.isdir(d):
parts.append(d) parts.append(d)
env["PATH"] = os.pathsep.join(parts) env["PATH"] = os.pathsep.join(parts)
# Rutas de cabeceras (CPATH), librerías (LIBRARY_PATH) y pkg-config para que
# el compilador encuentre dependencias instaladas con brew/MacPorts.
includes, libs, pkgconfigs = [], [], []
for prefix in _EXTRA_PREFIXES:
inc, lib = f"{prefix}/include", f"{prefix}/lib"
if os.path.isdir(inc):
includes.append(inc)
if os.path.isdir(lib):
libs.append(lib)
pkgconfigs.append(f"{lib}/pkgconfig")
_prepend(env, "CPATH", includes)
_prepend(env, "LIBRARY_PATH", libs)
_prepend(env, "PKG_CONFIG_PATH", pkgconfigs)
return env return env
def _prepend(env: dict[str, str], var: str, dirs: list[str]) -> None:
"""Antepone dirs al valor de env[var] (lista separada por os.pathsep), sin
duplicar entradas ya presentes."""
existing = [p for p in env.get(var, "").split(os.pathsep) if p]
new = [d for d in dirs if d not in existing]
if new:
env[var] = os.pathsep.join(new + existing)
def _stream(cmd: str, cwd: Path, log: LogFn) -> int: def _stream(cmd: str, cwd: Path, log: LogFn) -> int:
"""Ejecuta un comando de shell en cwd, retransmitiendo stdout/err línea a línea.""" """Ejecuta un comando de shell en cwd, retransmitiendo stdout/err línea a línea."""
log(f"$ {cmd} (cwd={cwd})") log(f"$ {cmd} (cwd={cwd})")
@@ -56,6 +96,7 @@ def _stream(cmd: str, cwd: Path, log: LogFn) -> int:
text=True, text=True,
bufsize=1, bufsize=1,
env=_launch_env(), env=_launch_env(),
creationflags=_NO_WINDOW,
) )
assert proc.stdout is not None assert proc.stdout is not None
for line in proc.stdout: for line in proc.stdout:
@@ -26,6 +26,14 @@ def _valid_console_mode(value) -> str:
return value if value in _CONSOLE_MODES else "auto" return value if value in _CONSOLE_MODES else "auto"
_SORT_ORDERS = ("default", "name")
def _valid_sort_order(value) -> str:
"""Normaliza el orden de la lista; cae a 'default' (orden del TOML) si es desconocido."""
return value if value in _SORT_ORDERS else "default"
@dataclass @dataclass
class Settings: class Settings:
hide_not_downloaded: bool = False hide_not_downloaded: bool = False
@@ -34,6 +42,7 @@ class Settings:
check_updates_on_start: bool = False # comprobar updates automáticamente al iniciar check_updates_on_start: bool = False # comprobar updates automáticamente al iniciar
theme: str = "system" # tema de la UI: "system" | "light" | "dark" theme: str = "system" # tema de la UI: "system" | "light" | "dark"
console_mode: str = "auto" # consola de log: "show" | "auto" | "hide" console_mode: str = "auto" # consola de log: "show" | "auto" | "hide"
sort_order: str = "default" # orden de la lista: "default" (TOML) | "name"
# Tolerancia a repos offline/inalcanzables (segundos, salvo stall_limit en bytes/s). # Tolerancia a repos offline/inalcanzables (segundos, salvo stall_limit en bytes/s).
git_fetch_timeout: int = 60 # techo para fetch / comprobar update git_fetch_timeout: int = 60 # techo para fetch / comprobar update
git_clone_timeout: int = 900 # techo para clone (repo grande) git_clone_timeout: int = 900 # techo para clone (repo grande)
@@ -61,6 +70,7 @@ def load_settings() -> Settings:
check_updates_on_start=bool(data.get("check_updates_on_start", False)), check_updates_on_start=bool(data.get("check_updates_on_start", False)),
theme=_valid_theme(data.get("theme", "system")), theme=_valid_theme(data.get("theme", "system")),
console_mode=_valid_console_mode(data.get("console_mode", "auto")), console_mode=_valid_console_mode(data.get("console_mode", "auto")),
sort_order=_valid_sort_order(data.get("sort_order", "default")),
git_fetch_timeout=int(data.get("git_fetch_timeout", 60)), git_fetch_timeout=int(data.get("git_fetch_timeout", 60)),
git_clone_timeout=int(data.get("git_clone_timeout", 900)), git_clone_timeout=int(data.get("git_clone_timeout", 900)),
http_timeout=int(data.get("http_timeout", 15)), http_timeout=int(data.get("http_timeout", 15)),
+1
View File
@@ -0,0 +1 @@
"""Componentes de interfaz de jail-launcher."""
@@ -36,12 +36,15 @@ WINDOW_TITLE = f"© 2026 {APP_NAME} — JailDesigner"
CONSOLE_HEIGHT = 150 # alçada de la consola desplegada (px) CONSOLE_HEIGHT = 150 # alçada de la consola desplegada (px)
CONSOLE_ANIM_MS = 220 # durada de l'animació de desplegar/replegar CONSOLE_ANIM_MS = 220 # durada de l'animació de desplegar/replegar
CONSOLE_IDLE_MS = 4000 # marge sense activitat abans de replegar en mode auto CONSOLE_IDLE_MS = 3000 # marge sense activitat abans de replegar en mode auto
CONSOLE_SHOW = "show" CONSOLE_SHOW = "show"
CONSOLE_AUTO = "auto" CONSOLE_AUTO = "auto"
CONSOLE_HIDE = "hide" CONSOLE_HIDE = "hide"
SORT_DEFAULT = "default" # ordre del games.toml
SORT_NAME = "name" # alfabètic pel nom
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, config: Config, root: Path, parent=None) -> None: def __init__(self, config: Config, root: Path, parent=None) -> None:
@@ -76,16 +79,15 @@ class MainWindow(QMainWindow):
# --- Lista de juegos con scroll --- # --- Lista de juegos con scroll ---
list_container = QWidget() list_container = QWidget()
list_layout = QVBoxLayout(list_container) self.list_layout = QVBoxLayout(list_container)
list_layout.setContentsMargins(6, 6, 6, 6) self.list_layout.setContentsMargins(6, 6, 6, 6)
list_layout.setSpacing(6) self.list_layout.setSpacing(6)
for game in config.games: for game in config.games:
row = GameRow(game, root) row = GameRow(game, root)
row.activated.connect(self._on_activate) row.activated.connect(self._on_activate)
row.delete_requested.connect(self._on_delete) row.delete_requested.connect(self._on_delete)
self.rows[game.id] = row self.rows[game.id] = row
list_layout.addWidget(row) self._populate_list()
list_layout.addStretch(1)
scroll = QScrollArea() scroll = QScrollArea()
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
@@ -238,6 +240,7 @@ class MainWindow(QMainWindow):
menu.addAction(self.action_check_on_start) menu.addAction(self.action_check_on_start)
menu.addSeparator() menu.addSeparator()
self._build_sort_menu(menu)
self._build_theme_menu(menu) self._build_theme_menu(menu)
self._build_console_menu(menu) self._build_console_menu(menu)
@@ -293,13 +296,12 @@ class MainWindow(QMainWindow):
name.setFont(nf) name.setFont(nf)
name.setStyleSheet("color: #7c4dff;") name.setStyleSheet("color: #7c4dff;")
lay.addWidget(name) lay.addWidget(name)
lay.addSpacing(4) lay.addSpacing(2)
# Versió en cursiva i atenuada. # Versió: petita i atenuada, just davall del nom (estil macOS).
ver = QLabel(f"Versió {__version__}", alignment=Qt.AlignCenter) ver = QLabel(f"v{__version__}", alignment=Qt.AlignCenter)
vf = ver.font() vf = ver.font()
vf.setItalic(True) vf.setPointSize(vf.pointSize() - 1)
vf.setPointSize(vf.pointSize() + 1)
ver.setFont(vf) ver.setFont(vf)
ver.setStyleSheet("color: #8a8a8a;") ver.setStyleSheet("color: #8a8a8a;")
lay.addWidget(ver) lay.addWidget(ver)
@@ -330,6 +332,27 @@ class MainWindow(QMainWindow):
dlg.exec() dlg.exec()
def _build_sort_menu(self, parent_menu) -> None:
"""Submenú Ordena amb dues opcions exclusives: Per defecte / Per nom."""
submenu = parent_menu.addMenu("Ordena")
group = QActionGroup(self)
group.setExclusive(True)
options = [
("Per defecte", SORT_DEFAULT),
("Per nom", SORT_NAME),
]
for label, mode in options:
action = QAction(label, self, checkable=True)
action.setChecked(self.settings.sort_order == mode)
action.triggered.connect(lambda _checked, m=mode: self._on_sort_selected(m))
group.addAction(action)
submenu.addAction(action)
def _on_sort_selected(self, mode: str) -> None:
self.settings.sort_order = mode
save_settings(self.settings)
self._populate_list()
def _build_theme_menu(self, parent_menu) -> None: def _build_theme_menu(self, parent_menu) -> None:
"""Submenú Tema amb tres opcions exclusives: Sistema / Clar / Fosc.""" """Submenú Tema amb tres opcions exclusives: Sistema / Clar / Fosc."""
submenu = parent_menu.addMenu("Tema") submenu = parent_menu.addMenu("Tema")
@@ -413,6 +436,23 @@ class MainWindow(QMainWindow):
self.settings.check_updates_on_start = checked self.settings.check_updates_on_start = checked
save_settings(self.settings) save_settings(self.settings)
def _ordered_games(self) -> list[Game]:
"""Jocs en l'ordre triat: alfabètic pel nom, o l'ordre original del games.toml."""
if self.settings.sort_order == SORT_NAME:
return sorted(self.config.games, key=lambda g: g.name.casefold())
return list(self.config.games)
def _populate_list(self) -> None:
"""(Re)col·loca les files al layout segons l'ordre triat, sense destruir-les."""
while self.list_layout.count():
item = self.list_layout.takeAt(0)
w = item.widget() if item else None
if w is not None:
w.setParent(None) # treu del layout però conserva la fila (viu a self.rows)
for game in self._ordered_games():
self.list_layout.addWidget(self.rows[game.id])
self.list_layout.addStretch(1)
def _apply_filter(self) -> None: def _apply_filter(self) -> None:
hide = self.action_hide.isChecked() hide = self.action_hide.isChecked()
for row in self.rows.values(): for row in self.rows.values():
@@ -463,7 +503,13 @@ class MainWindow(QMainWindow):
# --------------------------------------------------------------- helpers # --------------------------------------------------------------- helpers
def _log(self, text: str) -> None: def _log(self, text: str) -> None:
# Autoscroll intel·ligent: només seguim el final si la barra ja hi estava.
# Si l'usuari ha pujat a llegir una línia anterior, no l'arrosseguem avall.
bar = self.log_view.verticalScrollBar()
at_bottom = bar.value() >= bar.maximum() - 4
self.log_view.appendPlainText(text) self.log_view.appendPlainText(text)
if at_bottom:
bar.setValue(bar.maximum())
# En mode auto, qualsevol línia desplega la consola; si no hi ha cap worker # En mode auto, qualsevol línia desplega la consola; si no hi ha cap worker
# actiu (p.ex. un missatge solt), arrenca el compte enrere per replegar-la. # actiu (p.ex. un missatge solt), arrenca el compte enrere per replegar-la.
if self.settings.console_mode == CONSOLE_AUTO: if self.settings.console_mode == CONSOLE_AUTO:
-3
View File
@@ -1,3 +0,0 @@
"""jlauncher — lanzador de juegos jailgames."""
__version__ = "1.0.2"
-1
View File
@@ -1 +0,0 @@
"""Componentes de interfaz de jlauncher."""
+4 -4
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "jlauncher" name = "jail-launcher"
version = "1.0.1" version = "1.0.4"
description = "Lanzador de juegos jailgames: clona, compila y ejecuta repos Gitea" description = "Lanzador de juegos jailgames: clona, compila y ejecuta repos Gitea"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
@@ -8,11 +8,11 @@ dependencies = [
] ]
[project.scripts] [project.scripts]
jlauncher = "jlauncher.__main__:main" jail-launcher = "jail_launcher.__main__:main"
[build-system] [build-system]
requires = ["setuptools>=68"] requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.setuptools] [tool.setuptools]
packages = ["jlauncher", "jlauncher.ui"] packages = ["jail_launcher", "jail_launcher.ui"]