kglobe/kitty.py
2025-07-21 10:42:23 +02:00

102 lines
2.4 KiB
Python
Executable File

#!/usr/bin/env python3
import re
import sys
import termios
import tty
from base64 import standard_b64encode
from io import BytesIO
def draw_to_terminal(buffer: BytesIO) -> None:
write_chunked(a="T", i=1, f=100, q=2, data=buffer.getvalue())
def serialize_gr_command(**cmd) -> bytes:
payload = cmd.pop("payload", None)
cmd = ",".join(f"{k}={v}" for k, v in cmd.items())
ans = []
w = ans.append
w(b"\033_G"), w(cmd.encode("ascii"))
if payload:
w(b";")
w(payload)
w(b"\033\\")
return b"".join(ans)
def write_chunked(**cmd) -> None:
data = standard_b64encode(cmd.pop("data"))
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m, **cmd))
sys.stdout.flush()
cmd.clear()
def hide_cursor() -> None:
sys.stdout.write("\x1b[?25l")
sys.stdout.flush()
def show_cursor() -> None:
sys.stdout.write("\x1b[?25h")
sys.stdout.flush()
def set_position(y: int, x: int) -> None:
sys.stdout.write(f"\x1b[{y};{x}H")
sys.stdout.flush()
def get_position() -> tuple[int, int]:
# Save the current terminal settings
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
# Set terminal to raw mode
tty.setraw(fd)
# Send the ESC[6n command to request cursor position
sys.stdout.write("\x1b[6n")
sys.stdout.flush()
# Read the response: ESC [ row ; col R
response = ""
while True:
ch = sys.stdin.read(1)
response += ch
if ch == "R":
break
finally:
# Restore the terminal settings
termios.tcsetattr(fd, termios.TCSANOW, old_settings)
# Parse the response using regex
match = re.search(r"\[(\d+);(\d+)R", response)
if match:
y, x = map(int, match.groups())
return y, x
else:
raise ValueError("Failed to parse cursor position response")
if __name__ == "__main__":
import pyvista as pv
s = pv.Sphere()
pl = pv.Plotter()
pl.add_mesh(s)
b = BytesIO()
pl.show()
pl.screenshot(b)
# i = Image.new("RGB", (100, 100), (0, 0, 0))
# d = ImageDraw.Draw(i)
# d.ellipse([(5, 5), (95, 95)])
# d.ellipse([(10, 10), (90, 90)])
# d.line(((50, 0), (50, 100)))
# d.line(((0, 50), (100, 50)))
draw_to_terminal(b)