first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,5 @@
|
||||
from .app import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
152
hypenv/lib/python3.11/site-packages/pyxtermjs/app.py
Normal file
152
hypenv/lib/python3.11/site-packages/pyxtermjs/app.py
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from flask import Flask, render_template
|
||||
from flask_socketio import SocketIO
|
||||
import pty
|
||||
import os
|
||||
import subprocess
|
||||
import select
|
||||
import termios
|
||||
import struct
|
||||
import fcntl
|
||||
import shlex
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
||||
|
||||
__version__ = "0.5.0.2"
|
||||
|
||||
app = Flask(__name__, template_folder=".", static_folder=".", static_url_path="")
|
||||
app.config["SECRET_KEY"] = "secret!"
|
||||
app.config["fd"] = None
|
||||
app.config["child_pid"] = None
|
||||
socketio = SocketIO(app)
|
||||
|
||||
|
||||
def set_winsize(fd, row, col, xpix=0, ypix=0):
|
||||
logging.debug("setting window size with termios")
|
||||
winsize = struct.pack("HHHH", row, col, xpix, ypix)
|
||||
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
||||
|
||||
|
||||
def read_and_forward_pty_output():
|
||||
max_read_bytes = 1024 * 20
|
||||
while True:
|
||||
socketio.sleep(0.01)
|
||||
if app.config["fd"]:
|
||||
timeout_sec = 0
|
||||
(data_ready, _, _) = select.select([app.config["fd"]], [], [], timeout_sec)
|
||||
if data_ready:
|
||||
output = os.read(app.config["fd"], max_read_bytes).decode(
|
||||
errors="ignore"
|
||||
)
|
||||
socketio.emit("pty-output", {"output": output}, namespace="/pty")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@socketio.on("pty-input", namespace="/pty")
|
||||
def pty_input(data):
|
||||
"""write to the child pty. The pty sees this as if you are typing in a real
|
||||
terminal.
|
||||
"""
|
||||
if app.config["fd"]:
|
||||
logging.debug("received input from browser: %s" % data["input"])
|
||||
os.write(app.config["fd"], data["input"].encode())
|
||||
|
||||
|
||||
@socketio.on("resize", namespace="/pty")
|
||||
def resize(data):
|
||||
if app.config["fd"]:
|
||||
logging.debug(f"Resizing window to {data['rows']}x{data['cols']}")
|
||||
set_winsize(app.config["fd"], data["rows"], data["cols"])
|
||||
|
||||
|
||||
@socketio.on("connect", namespace="/pty")
|
||||
def connect():
|
||||
"""new client connected"""
|
||||
logging.info("new client connected")
|
||||
if app.config["child_pid"]:
|
||||
# already started child process, don't start another
|
||||
return
|
||||
|
||||
# create child process attached to a pty we can read from and write to
|
||||
(child_pid, fd) = pty.fork()
|
||||
if child_pid == 0:
|
||||
# this is the child process fork.
|
||||
# anything printed here will show up in the pty, including the output
|
||||
# of this subprocess
|
||||
subprocess.run(app.config["cmd"])
|
||||
else:
|
||||
# this is the parent process fork.
|
||||
# store child fd and pid
|
||||
app.config["fd"] = fd
|
||||
app.config["child_pid"] = child_pid
|
||||
set_winsize(fd, 50, 50)
|
||||
cmd = " ".join(shlex.quote(c) for c in app.config["cmd"])
|
||||
# logging/print statements must go after this because... I have no idea why
|
||||
# but if they come before the background task never starts
|
||||
socketio.start_background_task(target=read_and_forward_pty_output)
|
||||
|
||||
logging.info("child pid is " + child_pid)
|
||||
logging.info(
|
||||
f"starting background task with command `{cmd}` to continously read "
|
||||
"and forward pty output to client"
|
||||
)
|
||||
logging.info("task started")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"A fully functional terminal in your browser. "
|
||||
"https://github.com/cs01/pyxterm.js"
|
||||
),
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--port", default=5000, help="port to run server on", type=int
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default="127.0.0.1",
|
||||
help="host to run server on (use 0.0.0.0 to allow access from other hosts)",
|
||||
)
|
||||
parser.add_argument("--debug", action="store_true", help="debug the server")
|
||||
parser.add_argument("--version", action="store_true", help="print version and exit")
|
||||
parser.add_argument(
|
||||
"--command", default="bash", help="Command to run in the terminal"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cmd-args",
|
||||
default="",
|
||||
help="arguments to pass to command (i.e. --cmd-args='arg1 arg2 --flag')",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.version:
|
||||
print(__version__)
|
||||
exit(0)
|
||||
app.config["cmd"] = [args.command] + shlex.split(args.cmd_args)
|
||||
green = "\033[92m"
|
||||
end = "\033[0m"
|
||||
log_format = (
|
||||
green
|
||||
+ "pyxtermjs > "
|
||||
+ end
|
||||
+ "%(levelname)s (%(funcName)s:%(lineno)s) %(message)s"
|
||||
)
|
||||
logging.basicConfig(
|
||||
format=log_format,
|
||||
stream=sys.stdout,
|
||||
level=logging.DEBUG if args.debug else logging.INFO,
|
||||
)
|
||||
logging.info(f"serving on http://{args.host}:{args.port}")
|
||||
socketio.run(app, debug=args.debug, port=args.port, host=args.host)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
138
hypenv/lib/python3.11/site-packages/pyxtermjs/index.html
Normal file
138
hypenv/lib/python3.11/site-packages/pyxtermjs/index.html
Normal file
@@ -0,0 +1,138 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>pyxterm.js</title>
|
||||
<style>
|
||||
html {
|
||||
font-family: arial;
|
||||
}
|
||||
</style>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/xterm@4.11.0/css/xterm.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://github.com/cs01/pyxtermjs" target="_blank" style="font-size: 1.4em; text-decoration: none; color:black">pyxterm.js</a
|
||||
>
|
||||
</a>
|
||||
<span style="font-size: small"
|
||||
>status:
|
||||
<span style="font-size: small" id="status">connecting...</span></span
|
||||
>
|
||||
|
||||
<div style="width: 100%; height: calc(100% - 50px)" id="terminal"></div>
|
||||
|
||||
<p style="text-align: right; font-size: small">
|
||||
built by <a href="https://chadsmith.dev">Chad Smith</a>
|
||||
<a href="https://github.com/cs01">GitHub</a>
|
||||
</p>
|
||||
<!-- xterm -->
|
||||
<script src="https://unpkg.com/xterm@4.11.0/lib/xterm.js"></script>
|
||||
<script src="https://unpkg.com/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script>
|
||||
<script src="https://unpkg.com/xterm-addon-web-links@0.4.0/lib/xterm-addon-web-links.js"></script>
|
||||
<script src="https://unpkg.com/xterm-addon-search@0.8.0/lib/xterm-addon-sear
|
||||
ch.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script>
|
||||
|
||||
<script>
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
macOptionIsMeta: true,
|
||||
scrollback: true,
|
||||
});
|
||||
term.attachCustomKeyEventHandler(customKeyEventHandler);
|
||||
// https://github.com/xtermjs/xterm.js/issues/2941
|
||||
const fit = new FitAddon.FitAddon();
|
||||
term.loadAddon(fit);
|
||||
term.loadAddon(new WebLinksAddon.WebLinksAddon());
|
||||
term.loadAddon(new SearchAddon.SearchAddon());
|
||||
|
||||
term.open(document.getElementById("terminal"));
|
||||
fit.fit();
|
||||
term.resize(15, 50);
|
||||
console.log(`size: ${term.cols} columns, ${term.rows} rows`);
|
||||
fit.fit();
|
||||
term.writeln("Welcome to pyxterm.js!");
|
||||
term.writeln("https://github.com/cs01/pyxterm.js");
|
||||
term.writeln('')
|
||||
term.writeln("You can copy with ctrl+shift+x");
|
||||
term.writeln("You can paste with ctrl+shift+v");
|
||||
term.writeln('')
|
||||
term.onData((data) => {
|
||||
console.log("browser terminal received new data:", data);
|
||||
socket.emit("pty-input", { input: data });
|
||||
});
|
||||
|
||||
const socket = io.connect("/pty");
|
||||
const status = document.getElementById("status");
|
||||
|
||||
socket.on("pty-output", function (data) {
|
||||
console.log("new output received from server:", data.output);
|
||||
term.write(data.output);
|
||||
});
|
||||
|
||||
socket.on("connect", () => {
|
||||
fitToscreen();
|
||||
status.innerHTML =
|
||||
'<span style="background-color: lightgreen;">connected</span>';
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
status.innerHTML =
|
||||
'<span style="background-color: #ff8383;">disconnected</span>';
|
||||
});
|
||||
|
||||
function fitToscreen() {
|
||||
fit.fit();
|
||||
const dims = { cols: term.cols, rows: term.rows };
|
||||
console.log("sending new dimensions to server's pty", dims);
|
||||
socket.emit("resize", dims);
|
||||
}
|
||||
|
||||
function debounce(func, wait_ms) {
|
||||
let timeout;
|
||||
return function (...args) {
|
||||
const context = this;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(context, args), wait_ms);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle copy and paste events
|
||||
*/
|
||||
function customKeyEventHandler(e) {
|
||||
if (e.type !== "keydown") {
|
||||
return true;
|
||||
}
|
||||
if (e.ctrlKey && e.shiftKey) {
|
||||
const key = e.key.toLowerCase();
|
||||
if (key === "v") {
|
||||
// ctrl+shift+v: paste whatever is in the clipboard
|
||||
navigator.clipboard.readText().then((toPaste) => {
|
||||
term.writeText(toPaste);
|
||||
});
|
||||
return false;
|
||||
} else if (key === "c" || key === "x") {
|
||||
// ctrl+shift+x: copy whatever is highlighted to clipboard
|
||||
|
||||
// 'x' is used as an alternate to 'c' because ctrl+c is taken
|
||||
// by the terminal (SIGINT) and ctrl+shift+c is taken by the browser
|
||||
// (open devtools).
|
||||
// I'm not aware of ctrl+shift+x being used by anything in the terminal
|
||||
// or browser
|
||||
const toCopy = term.getSelection();
|
||||
navigator.clipboard.writeText(toCopy);
|
||||
term.focus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const wait_ms = 50;
|
||||
window.onresize = debounce(fitToscreen, wait_ms);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user