diff --git a/AppImage/components/terminal-panel.tsx b/AppImage/components/terminal-panel.tsx index 8dc8e97..9157240 100644 --- a/AppImage/components/terminal-panel.tsx +++ b/AppImage/components/terminal-panel.tsx @@ -276,13 +276,13 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl }, [terminals, isMobile]) const initializeTerminal = async (terminal: TerminalInstance, container: HTMLDivElement) => { - const [Terminal, FitAddon] = await Promise.all([ + const [TerminalClass, FitAddonClass] = await Promise.all([ import("xterm").then((mod) => mod.Terminal), import("xterm-addon-fit").then((mod) => mod.FitAddon), import("xterm/css/xterm.css"), ]).then(([Terminal, FitAddon]) => [Terminal, FitAddon]) - const term = new Terminal({ + const term = new TerminalClass({ fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace", fontSize: isMobile ? 11 : 13, cursorBlink: true, @@ -314,7 +314,7 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl }, }) - const fitAddon = new FitAddon() + const fitAddon = new FitAddonClass() term.loadAddon(fitAddon) term.open(container) @@ -323,9 +323,31 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl const wsUrl = websocketUrl || getWebSocketUrl() const ws = new WebSocket(wsUrl) + // Ajusta al contenedor y envía tamaño al backend + const syncSizeWithBackend = () => { + try { + fitAddon.fit() + if (ws.readyState === WebSocket.OPEN) { + const cols = term.cols + const rows = term.rows + ws.send( + JSON.stringify({ + type: "resize", + cols, + rows, + }), + ) + } + } catch (err) { + console.warn("[Terminal] resize failed:", err) + } + } + ws.onopen = () => { setTerminals((prev) => prev.map((t) => (t.id === terminal.id ? { ...t, isConnected: true, term, ws } : t))) term.writeln("\x1b[32mConnected to ProxMenux terminal.\x1b[0m") + // Tamaño inicial + syncSizeWithBackend() } ws.onmessage = (event) => { @@ -343,18 +365,16 @@ export const TerminalPanel: React.FC = ({ websocketUrl, onCl term.writeln("\r\n\x1b[33m[INFO] Connection closed\x1b[0m") } + // Teclas → backend term.onData((data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(data) } }) + // Solo reaccionamos a cambios de tamaño de la ventana const handleResize = () => { - try { - fitAddon.fit() - } catch { - // Ignore resize errors - } + syncSizeWithBackend() } window.addEventListener("resize", handleResize) diff --git a/AppImage/scripts/flask_terminal_routes.py b/AppImage/scripts/flask_terminal_routes.py index f76c59a..65410ae 100644 --- a/AppImage/scripts/flask_terminal_routes.py +++ b/AppImage/scripts/flask_terminal_routes.py @@ -16,6 +16,7 @@ import termios import threading import time import requests +import json terminal_bp = Blueprint('terminal', __name__) sock = Sock() @@ -168,15 +169,34 @@ def terminal_websocket(ws): if data is None: # Client closed connection break - - # Handle terminal resize (optional) - if data.startswith('\x1b[8;'): + + handled = False + + # Try to handle JSON control messages (e.g. resize) + if isinstance(data, str): + try: + msg = json.loads(data) + except Exception: + msg = None + + if isinstance(msg, dict) and msg.get('type') == 'resize': + cols = int(msg.get('cols', 120)) + rows = int(msg.get('rows', 30)) + set_winsize(master_fd, rows, cols) + handled = True + + if handled: + # Control message processed, do not send to bash + continue + + # Optional: legacy resize escape sequence support + if isinstance(data, str) and data.startswith('\x1b[8;'): try: parts = data[4:-1].split(';') rows, cols = int(parts[0]), int(parts[1]) set_winsize(master_fd, rows, cols) continue - except: + except Exception: pass # Send input to bash @@ -216,6 +236,7 @@ def terminal_websocket(ws): if session_id in active_sessions: del active_sessions[session_id] + def init_terminal_routes(app): """Initialize terminal routes with Flask app""" sock.init_app(app)