From 6df128a377289159bfb3bb94b0aaa39412b243a2 Mon Sep 17 00:00:00 2001 From: hacklab Date: Fri, 22 May 2026 18:11:01 +0200 Subject: [PATCH] Soporte multi-distro en portatil, titulos mas grandes, panel mas ancho y refresco de microfono install.sh --laptop ahora detecta el gestor de paquetes (apt, dnf, pacman o zypper) e instala las dependencias en Debian/Ubuntu/Mint, Fedora, Arch/Manjaro y openSUSE; en el resto avisa de los 5 paquetes a instalar a mano. En portatil no se compila projectM (opcional). Panel: titulo superior mas grande y cada titulo de tarjeta mas grande y en fuente Xirod; en pantalla ancha el panel ocupa el 94% (hasta 1600px) para aprovechar el portatil. Audio: nuevo boton 'Aplicar microfono' que reconecta la captura desde el panel (evento reacquire_audio); el microfono integrado del portatil se capta como entrada por defecto. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 8 ++- backend/server.py | 6 ++ docs/portatil.md | 22 ++++-- install.sh | 163 +++++++++++++++++++++++++++++++------------ web/panel/index.html | 3 +- web/panel/panel.css | 14 ++-- web/panel/panel.js | 10 ++- web/stage/stage.js | 8 +++ 8 files changed, 172 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 77648d3..046d1f6 100644 --- a/README.md +++ b/README.md @@ -91,9 +91,11 @@ Al reiniciar, la Pi arranca sola en modo kiosko mostrando las visuales. ### En un portátil Linux -FOSFENO no necesita la Raspberry: corre igual en un portátil con Debian, -Ubuntu o Mint. El portátil ya trae micrófono y cámara, y lo conectas al -proyector por HDMI como cualquier otra cosa. +FOSFENO no necesita la Raspberry: corre igual en un portátil con Linux. El +instalador reconoce las familias más comunes —Debian/Ubuntu/Mint, Fedora, +Arch/Manjaro y openSUSE— e instala las dependencias con el gestor de cada +una (`apt`, `dnf`, `pacman` o `zypper`). El portátil ya trae micrófono y +cámara, y lo conectas al proyector por HDMI como cualquier otra cosa. ``` [ Música de la sala ] diff --git a/backend/server.py b/backend/server.py index b8f7007..dd14f96 100644 --- a/backend/server.py +++ b/backend/server.py @@ -382,6 +382,12 @@ def on_rescan_devices(): socketio.emit("stage_rescan") +@socketio.on("reacquire_audio") +def on_reacquire_audio(): + """Pide al escenario que vuelva a conectar la captura del microfono.""" + socketio.emit("stage_reacquire") + + @socketio.on("stage_status") def on_stage_status(data): if isinstance(data, dict): diff --git a/docs/portatil.md b/docs/portatil.md index 5d920c4..aa77fd6 100644 --- a/docs/portatil.md +++ b/docs/portatil.md @@ -5,8 +5,17 @@ que funciona igual de bien en un portátil con Linux. De hecho un portátil va más sobrado, ya trae micrófono y cámara, y lo conectas al proyector por HDMI como cualquier otra cosa. -Este modo está pensado para Debian, Ubuntu o Linux Mint, que son las -distribuciones que usan `apt`. +El instalador funciona en las distribuciones más comunes. Reconoce solo el +gestor de paquetes de cada una e instala lo necesario con él: + +- `apt` — Debian, Ubuntu, Linux Mint, Pop!_OS y derivadas. +- `dnf` — Fedora. +- `pacman` — Arch, Manjaro, EndeavourOS. +- `zypper` — openSUSE. + +En otras distribuciones el instalador avisará y bastará con instalar a mano +cinco paquetes: `python3` (con `venv`), `nodejs`, `npm`, `git` y `chromium`. +El resto del proceso es idéntico. ## Instalar @@ -16,10 +25,11 @@ Una sola vez, desde la carpeta del proyecto: bash install.sh --laptop ``` -Hace lo mismo que en la Raspberry (entorno de Python, librerías de visuales, -y projectM si quieres), pero sin las cosas propias de un aparato dedicado: no -toca el arranque del sistema, no cambia el nombre de red y no necesita -permisos especiales de puertos. +En el portátil solo hace falta un núcleo pequeño: Python, Node, Git y +Chromium. Los motores de visuales son web y no necesitan nada más. No se +compila projectM (es opcional; si tu distro lo trae empaquetado y lo +instalas, FOSFENO lo detecta y lo usa). El instalador no toca el arranque del +sistema, no cambia el nombre de red y no necesita permisos especiales. ## Arrancar diff --git a/install.sh b/install.sh index 5f36775..0967f98 100755 --- a/install.sh +++ b/install.sh @@ -3,7 +3,7 @@ # FOSFENO :: instalador para Raspberry Pi OS Bookworm (Raspberry Pi 4 y 5) # # Uso: bash install.sh # Raspberry Pi: instala todo -# bash install.sh --laptop # portatil Linux (Debian/Ubuntu/Mint) +# bash install.sh --laptop # portatil Linux (cualquier distro) # bash install.sh --no-projectm # omite la compilacion de projectM # bash install.sh --check # solo comprueba el sistema, no instala # =========================================================================== @@ -46,11 +46,6 @@ fi if [ "$LAPTOP" = "yes" ]; then log_ok "Modo portatil (Linux de escritorio)" - if ! need_cmd apt-get; then - log_fail "El modo --laptop usa apt (Debian, Ubuntu o Mint)." - log_fail "Tu sistema no tiene apt; este instalador no sirve aqui." - exit 1 - fi else MODELO="$(pi_model)" log_info "Modelo detectado: $MODELO" @@ -91,45 +86,117 @@ fi # --------------------------------------------------------------------------- # 2. Paquetes del sistema # --------------------------------------------------------------------------- -log_step "[2/9] Instalando dependencias del sistema (apt)" -sudo apt-get update -if sudo apt-get install -y \ - python3 python3-venv python3-pip \ - git cmake build-essential pkg-config \ - libsdl2-dev libgles2-mesa-dev mesa-common-dev libglm-dev libpoco-dev \ - pulseaudio-utils \ - v4l-utils \ - avahi-daemon \ - xdotool unclutter; then - log_ok "Paquetes apt instalados" -else - log_fail "Fallo al instalar paquetes apt" - exit 1 -fi -# Node.js y npm. Si ya estan (por ejemplo instalados desde NodeSource) no se -# tocan: el paquete 'npm' de Debian entra en conflicto con el 'nodejs' de -# NodeSource, y forzarlo rompe la instalacion entera. -if need_cmd node && need_cmd npm; then - log_ok "Node.js y npm ya estaban instalados ($(node --version 2>/dev/null))" -elif need_cmd node; then - warn "Node.js esta, pero falta npm; instalalo con el mismo metodo que node" -elif sudo apt-get install -y nodejs npm 2>/dev/null; then - log_ok "Node.js y npm instalados" -else - warn "no se pudo instalar Node.js; instalalo a mano (apt o NodeSource) y reintenta" -fi +# Instala las dependencias en un portatil. Soporta apt, dnf, pacman y zypper, +# de modo que FOSFENO corre en Debian/Ubuntu/Mint, Fedora, Arch/Manjaro y +# openSUSE. En portatil solo hace falta un nucleo pequeno (Python, Node, git +# y Chromium): los motores de visuales son web y no necesitan nada mas. +install_laptop_deps() { + local mgr="" + if need_cmd apt-get; then mgr="apt" + elif need_cmd dnf; then mgr="dnf" + elif need_cmd pacman; then mgr="pacman" + elif need_cmd zypper; then mgr="zypper" + else + log_fail "No reconozco el gestor de paquetes de tu distribucion." + log_fail "Instala a mano y reintenta: python3 (con venv), nodejs, npm," + log_fail "git y chromium." + return 1 + fi + log_info "Gestor de paquetes detectado: $mgr" -# El navegador se llama 'chromium' en Debian/Ubuntu/Mint actuales y -# 'chromium-browser' en Raspberry Pi OS. Instalamos el que exista. -if need_cmd chromium || need_cmd chromium-browser; then - log_ok "Chromium ya estaba instalado" -elif sudo apt-get install -y chromium 2>/dev/null; then - log_ok "Chromium instalado (paquete 'chromium')" -elif sudo apt-get install -y chromium-browser 2>/dev/null; then - log_ok "Chromium instalado (paquete 'chromium-browser')" + # --- Python y git --- + case "$mgr" in + apt) sudo apt-get update + sudo apt-get install -y python3 python3-venv python3-pip git ;; + dnf) sudo dnf install -y python3 python3-pip git ;; + pacman) sudo pacman -Sy --needed --noconfirm python python-pip git ;; + zypper) sudo zypper --non-interactive install python3 python3-pip git ;; + esac || { log_fail "no se pudieron instalar Python y git"; return 1; } + log_ok "Python y git instalados" + + # --- Node.js y npm: solo si faltan (respeta NodeSource, nvm, etc.) --- + if need_cmd node && need_cmd npm; then + log_ok "Node.js y npm ya estaban ($(node --version 2>/dev/null))" + else + case "$mgr" in + apt) sudo apt-get install -y nodejs npm ;; + dnf) sudo dnf install -y nodejs npm ;; + pacman) sudo pacman -S --needed --noconfirm nodejs npm ;; + zypper) sudo zypper --non-interactive install nodejs npm ;; + esac + need_cmd node && need_cmd npm \ + && log_ok "Node.js y npm instalados" \ + || warn "no se pudo instalar Node.js; instalalo a mano y reintenta" + fi + + # --- Chromium --- + if need_cmd chromium || need_cmd chromium-browser || need_cmd google-chrome; then + log_ok "Chromium ya estaba instalado" + else + case "$mgr" in + apt) sudo apt-get install -y chromium 2>/dev/null \ + || sudo apt-get install -y chromium-browser ;; + dnf) sudo dnf install -y chromium ;; + pacman) sudo pacman -S --needed --noconfirm chromium ;; + zypper) sudo zypper --non-interactive install chromium ;; + esac + need_cmd chromium || need_cmd chromium-browser \ + && log_ok "Chromium instalado" \ + || warn "no se pudo instalar Chromium; instalalo a mano (chromium o Chrome)" + fi + + # --- avahi: opcional, permite llegar al panel por nombre .local --- + case "$mgr" in + apt) sudo apt-get install -y avahi-daemon 2>/dev/null || true ;; + dnf) sudo dnf install -y avahi 2>/dev/null || true ;; + pacman) sudo pacman -S --needed --noconfirm avahi 2>/dev/null || true ;; + zypper) sudo zypper --non-interactive install avahi 2>/dev/null || true ;; + esac + return 0 +} + +log_step "[2/9] Instalando dependencias del sistema" +if [ "$LAPTOP" = "yes" ]; then + install_laptop_deps || { log_fail "Faltan dependencias; aborto."; exit 1; } else - warn "no se pudo instalar Chromium automaticamente; instalalo a mano" + sudo apt-get update + if sudo apt-get install -y \ + python3 python3-venv python3-pip \ + git cmake build-essential pkg-config \ + libsdl2-dev libgles2-mesa-dev mesa-common-dev libglm-dev libpoco-dev \ + pulseaudio-utils \ + v4l-utils \ + avahi-daemon \ + xdotool unclutter; then + log_ok "Paquetes apt instalados" + else + log_fail "Fallo al instalar paquetes apt" + exit 1 + fi + + # Node.js y npm: si ya estan (p.ej. NodeSource) no se tocan, porque el + # paquete 'npm' de Debian choca con el 'nodejs' de NodeSource. + if need_cmd node && need_cmd npm; then + log_ok "Node.js y npm ya estaban instalados ($(node --version 2>/dev/null))" + elif need_cmd node; then + warn "Node.js esta, pero falta npm; instalalo con el mismo metodo que node" + elif sudo apt-get install -y nodejs npm 2>/dev/null; then + log_ok "Node.js y npm instalados" + else + warn "no se pudo instalar Node.js; instalalo a mano y reintenta" + fi + + # Chromium: 'chromium' o 'chromium-browser' segun la version del sistema. + if need_cmd chromium || need_cmd chromium-browser; then + log_ok "Chromium ya estaba instalado" + elif sudo apt-get install -y chromium 2>/dev/null; then + log_ok "Chromium instalado (paquete 'chromium')" + elif sudo apt-get install -y chromium-browser 2>/dev/null; then + log_ok "Chromium instalado (paquete 'chromium-browser')" + else + warn "no se pudo instalar Chromium automaticamente; instalalo a mano" + fi fi # --------------------------------------------------------------------------- @@ -141,7 +208,7 @@ require_version "Python" "python3 --version" "$MIN_PYTHON" \ require_version "Node.js" "node --version" "$MIN_NODE" \ || warn "Node.js antiguo: 'npm install' podria fallar" require_version "npm" "npm --version" "$MIN_NPM" || true -if [ "$SKIP_PM" = "no" ]; then +if [ "$SKIP_PM" = "no" ] && [ "$LAPTOP" = "no" ]; then require_version "CMake" "cmake --version" "$MIN_CMAKE" \ || { warn "CMake antiguo: projectM no se compilara"; SKIP_PM="yes"; } fi @@ -222,10 +289,14 @@ cd "$DIR" # 6. projectM nativo (compilado desde fuente) # --------------------------------------------------------------------------- log_step "[6/9] projectM (visualizador nativo)" -if [ "$SKIP_PM" = "yes" ]; then - log_info "projectM omitido" -elif command -v projectMSDL >/dev/null 2>&1; then +if command -v projectMSDL >/dev/null 2>&1; then log_ok "projectM ya estaba instalado ($(command -v projectMSDL))" +elif [ "$LAPTOP" = "yes" ]; then + log_info "projectM nativo no instalado (es opcional)." + log_info "Los motores Butterchurn, Hydra, Shaders y Mezclador funcionan sin el." + log_info "Si lo quieres, instala el paquete 'projectm' de tu distribucion." +elif [ "$SKIP_PM" = "yes" ]; then + log_info "projectM omitido" else log_info "Compilando projectM desde fuente (puede tardar 10-20 min en una Pi)..." if bash "$DIR/scripts/build-projectm.sh"; then diff --git a/web/panel/index.html b/web/panel/index.html index 2f6c3e6..0680559 100644 --- a/web/panel/index.html +++ b/web/panel/index.html @@ -41,6 +41,7 @@ +
BPM detectado @@ -53,7 +54,7 @@
- Sensibilidad al audio: + Sensibilidad: 1.0
diff --git a/web/panel/panel.css b/web/panel/panel.css index a29e23e..3618788 100644 --- a/web/panel/panel.css +++ b/web/panel/panel.css @@ -44,8 +44,8 @@ header { } header h1 { font-family: "Xirod", "Arial Black", sans-serif; - font-size: 21px; font-weight: 400; letter-spacing: 0.16em; - color: var(--green); text-shadow: 0 0 16px rgba(180, 255, 0, 0.6); + font-size: 32px; font-weight: 400; letter-spacing: 0.09em; + color: var(--green); text-shadow: 0 0 18px rgba(180, 255, 0, 0.65); } #conn { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); @@ -74,7 +74,7 @@ main { /* --- Disposicion: pantalla ancha (portatil) en dos columnas --- */ @media (min-width: 880px) { main { - max-width: 1080px; flex-direction: row; + width: 94%; max-width: 1600px; flex-direction: row; align-items: flex-start; gap: 18px; } .column { @@ -94,6 +94,12 @@ main { .cardhead { position: relative; width: 100%; display: flex; align-items: center; justify-content: center; + min-height: 30px; +} +/* Titulo de cada tarjeta: mas grande y en la fuente Xirod */ +.cardhead .label { + font-family: "Xirod", "Arial Black", sans-serif; + font-size: 16px; font-weight: 400; letter-spacing: 0.04em; } .label { color: var(--green); font-size: 13px; text-transform: uppercase; @@ -220,7 +226,7 @@ input[type=range] { width: 100%; accent-color: var(--green); height: 30px; } .notif { display: flex; align-items: center; gap: 10px; padding: 12px 16px; font-size: 14px; font-weight: 600; - position: sticky; top: 56px; z-index: 9; + position: sticky; top: 66px; z-index: 9; } .notif.info { background: var(--green); color: var(--ink); } .notif.warn { background: var(--orange); color: var(--ink); } diff --git a/web/panel/panel.js b/web/panel/panel.js index dde97e0..725c0e2 100644 --- a/web/panel/panel.js +++ b/web/panel/panel.js @@ -239,8 +239,7 @@ function renderEditor() { if (state.engine !== editorEngine) { editorEngine = state.engine; const isHydra = state.engine === "hydra"; - $("#editor-title").textContent = isHydra - ? "Editor Hydra (JavaScript)" : "Editor de shaders (GLSL)"; + $("#editor-title").textContent = isHydra ? "Editor Hydra" : "Editor Shaders"; $("#editor-hint").textContent = isHydra ? "Variables: time, a.fft[0..4], bpm. Escribe o pega codigo Hydra." : "Uniforms: u_time, u_bass, u_mid, u_treble, u_bpm, u_beat, u_fft."; @@ -299,6 +298,13 @@ $("#notif-close").addEventListener("click", () => { $("#notif").hidden = true; } $("#audio-device").addEventListener("change", (e) => socket.emit("update_settings", { engine: "audio", patch: { device: e.target.value } })); +// Boton "Aplicar microfono": fija la entrada elegida y obliga al escenario +// a re-conectar la captura de audio (util tras enchufar o cambiar el micro). +$("#audio-apply").addEventListener("click", () => { + socket.emit("update_settings", + { engine: "audio", patch: { device: $("#audio-device").value } }); + socket.emit("reacquire_audio"); +}); $("#sens").addEventListener("input", (e) => $("#sens-val").textContent = Number(e.target.value).toFixed(1)); diff --git a/web/stage/stage.js b/web/stage/stage.js index 1c73c95..125db56 100644 --- a/web/stage/stage.js +++ b/web/stage/stage.js @@ -575,6 +575,14 @@ socket.on("stage_rescan", () => { report("warn", "No se pudieron volver a leer los dispositivos.")); }); +// El panel pide reconectar la captura del microfono (boton "Aplicar"). +socket.on("stage_reacquire", () => { + const dev = (state && state.audio && state.audio.device) || null; + acquireMic(dev) + .then(() => report("info", "Microfono conectado.")) + .catch(() => report("error", "No se pudo conectar con el microfono elegido.")); +}); + window.addEventListener("resize", () => { if (hydra) hydra.setResolution(window.innerWidth, window.innerHeight); });