support color output in console
This commit is contained in:
parent
3b7338eb79
commit
82f59676c7
3 changed files with 81 additions and 41 deletions
94
app/main.py
94
app/main.py
|
|
@ -4,8 +4,10 @@ import argparse
|
|||
import asyncio
|
||||
import base64
|
||||
from datetime import datetime
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import pty
|
||||
import random
|
||||
import shlex
|
||||
import time
|
||||
|
|
@ -603,46 +605,80 @@ class Main:
|
|||
await self.sio.emit('cli_close', {'exitcode': 0})
|
||||
return
|
||||
|
||||
proc = None
|
||||
try:
|
||||
async def _read_stream(streamType, stream):
|
||||
while True:
|
||||
line = await stream.readline()
|
||||
if line:
|
||||
await self.sio.emit('cli_output', {
|
||||
'type': streamType,
|
||||
'line': line.decode('utf-8')
|
||||
})
|
||||
else:
|
||||
break
|
||||
|
||||
LOG.info(f'CLI: {data}')
|
||||
|
||||
args = ['yt-dlp', *shlex.split(data)]
|
||||
args = ['yt-dlp'] + shlex.split(data)
|
||||
_env = os.environ.copy()
|
||||
_env.update({
|
||||
"TERM": "xterm-256color",
|
||||
"LANG": "en_US.UTF-8",
|
||||
"SHELL": "/bin/bash",
|
||||
"LC_ALL": "en_US.UTF-8",
|
||||
"PWD": self.config.download_path,
|
||||
"FORCE_COLOR": "1",
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
})
|
||||
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*args,
|
||||
cwd=self.config.download_path,
|
||||
stdin=asyncio.subprocess.DEVNULL,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=os.environ.copy().update({
|
||||
"TERM": "xterm-256color",
|
||||
"LANG": "en_US.UTF-8",
|
||||
"SHELL": "/bin/bash",
|
||||
"LC_ALL": "en_US.UTF-8",
|
||||
"PWD": self.config.download_path,
|
||||
}),
|
||||
stdout=slave_fd,
|
||||
stderr=slave_fd,
|
||||
env=_env,
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
_read_stream('stdout', proc.stdout),
|
||||
_read_stream('stderr', proc.stderr),
|
||||
)
|
||||
try:
|
||||
os.close(slave_fd)
|
||||
except Exception as e:
|
||||
LOG.error(f'Error closing PTY: {str(e)}')
|
||||
|
||||
while proc.returncode is None:
|
||||
await asyncio.sleep(0.1)
|
||||
async def read_pty():
|
||||
loop = asyncio.get_running_loop()
|
||||
buffer = b''
|
||||
while True:
|
||||
try:
|
||||
chunk = await loop.run_in_executor(None, lambda: os.read(master_fd, 1024))
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
else:
|
||||
raise
|
||||
|
||||
await self.sio.emit('cli_close', {'exitcode': proc.returncode})
|
||||
if not chunk:
|
||||
# No more output
|
||||
if buffer:
|
||||
# Emit any remaining partial line
|
||||
await self.sio.emit('cli_output', {
|
||||
'type': 'stdout',
|
||||
'line': buffer.decode('utf-8', errors='replace')
|
||||
})
|
||||
break
|
||||
buffer += chunk
|
||||
*lines, buffer = buffer.split(b'\n')
|
||||
for line in lines:
|
||||
await self.sio.emit('cli_output', {
|
||||
'type': 'stdout',
|
||||
'line': line.decode('utf-8', errors='replace')
|
||||
})
|
||||
try:
|
||||
os.close(master_fd)
|
||||
except Exception as e:
|
||||
LOG.error(f'Error closing PTY: {str(e)}')
|
||||
|
||||
# Start reading output from PTY
|
||||
read_task = asyncio.create_task(read_pty())
|
||||
|
||||
# Wait until process finishes
|
||||
returncode = await proc.wait()
|
||||
|
||||
# Ensure reading is done
|
||||
await read_task
|
||||
|
||||
await self.sio.emit('cli_close', {'exitcode': returncode})
|
||||
except Exception as e:
|
||||
LOG.error(f'CLI Error: {str(e)}')
|
||||
await self.sio.emit('cli_output', {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
<span class="icon"><font-awesome-icon icon="fa-solid fa-terminal" /></span> Terminal
|
||||
</p>
|
||||
<p class="card-header-icon">
|
||||
<span v-tooltip.top="'Clear console window'" class="icon" @click="clearOutput"><font-awesome-icon icon="fa-solid fa-broom" /></span>
|
||||
<span v-tooltip.top="'Clear console window'" class="icon" @click="clearOutput"><font-awesome-icon
|
||||
icon="fa-solid fa-broom" /></span>
|
||||
</p>
|
||||
</header>
|
||||
<section class="card-content p-0 m-0">
|
||||
|
|
@ -23,16 +24,23 @@
|
|||
</section>
|
||||
<section class="card-content p-1 m-1">
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="control">
|
||||
<span class="icon-text input is-unselectable">
|
||||
<span class="icon"><font-awesome-icon icon="fa-solid fa-terminal" /></span>
|
||||
<span>yt-dlp</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input type="text" class="input" v-model="command" placeholder="ls -la" autocomplete="off"
|
||||
ref="command_input" @keydown.enter="runCommand" :disabled="props.isLoading" id="command">
|
||||
<span class="icon is-left"><font-awesome-icon icon="fa-solid fa-terminal"
|
||||
:spin="props.isLoading" /></span>
|
||||
</div>
|
||||
<p class="control">
|
||||
<button class="button is-primary" type="button" :disabled="props.isLoading || '' === command"
|
||||
@click="runCommand">
|
||||
<span class="icon"><font-awesome-icon icon="fa-solid fa-paper-plane" /></span>
|
||||
<span class="icon">
|
||||
<font-awesome-icon icon="fa-solid fa-spinner" spin v-if="props.isLoading" />
|
||||
<font-awesome-icon icon="fa-solid fa-paper-plane" v-else />
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -132,7 +140,8 @@ const runCommand = async () => {
|
|||
}
|
||||
|
||||
emitter('runCommand', command.value)
|
||||
terminal.value.writeln(`~ ${command.value}`)
|
||||
terminal.value.writeln(`user@YTPTube ~`)
|
||||
terminal.value.writeln(`$ yt-dlp ${command.value}`)
|
||||
}
|
||||
|
||||
const clearOutput = async (withCommand = false) => {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
|||
import {
|
||||
faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
|
||||
faSpinner, faArrowUp, faArrowDown, faTasks, faCalendar, faArrowUpAZ, faArrowDownAZ, faEject, faGlobe, faMoon, faSun,
|
||||
faTerminal,
|
||||
faBroom,
|
||||
faServer,
|
||||
faPowerOff,
|
||||
faPaperPlane,
|
||||
faBoxArchive
|
||||
faTerminal, faBroom, faServer, faPowerOff, faPaperPlane, faBoxArchive,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'
|
||||
|
|
|
|||
Loading…
Reference in a new issue