diff --git a/kitty.py b/kitty.py index b11029a..ded95da 100755 --- a/kitty.py +++ b/kitty.py @@ -5,11 +5,39 @@ import termios import tty from base64 import standard_b64encode from io import BytesIO +import array +import fcntl def draw_to_terminal(buffer: BytesIO) -> None: write_chunked(a="T", i=1, f=100, q=2, data=buffer.getvalue()) +def get_terminal_cell_size() -> tuple[int, int]: + reply = _query_terminal("\x1b[16t", "t") + + match = re.search(r"\[6;(\d+);(\d+)t", reply) + if match: + v_pix, h_pix = map(int, match.groups()) + return v_pix, h_pix + else: + print(reply) + raise ValueError("Failed to parse terminal cell size response") + +def get_terminal_size_pixel() -> tuple[int, int]: + reply = _query_terminal("\x1b[14t", "t") + + match = re.search(r"\[4;(\d+);(\d+)t", reply) + if match: + height, width = map(int, match.groups()) + return height, width + else: + raise ValueError("Failed to parse terminal pixel size response") + +def get_terminal_size() -> tuple[int, int]: + buf = array.array('H', [0, 0, 0, 0]) + fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf) + rows, cols, width, height = buf + return (rows, cols) def serialize_gr_command(**cmd) -> bytes: payload = cmd.pop("payload", None) @@ -35,21 +63,21 @@ def write_chunked(**cmd) -> None: def hide_cursor() -> None: - sys.stdout.write("\x1b[?25l") - sys.stdout.flush() + _write_stdout("\x1b[?25l") def show_cursor() -> None: - sys.stdout.write("\x1b[?25h") - sys.stdout.flush() + _write_stdout("\x1b[?25h") def set_position(y: int, x: int) -> None: - sys.stdout.write(f"\x1b[{y};{x}H") + _write_stdout(f"\x1b[{y};{x}H") + +def _write_stdout(cmd: str) -> None: + sys.stdout.write(cmd) sys.stdout.flush() - -def get_position() -> tuple[int, int]: +def _query_terminal(escape: str, endchar: str) -> str: # Save the current terminal settings fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) @@ -58,23 +86,25 @@ def get_position() -> tuple[int, int]: # 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() + _write_stdout(escape) # Read the response: ESC [ row ; col R response = "" while True: ch = sys.stdin.read(1) response += ch - if ch == "R": + if ch == endchar: break finally: # Restore the terminal settings termios.tcsetattr(fd, termios.TCSANOW, old_settings) + return response - # Parse the response using regex - match = re.search(r"\[(\d+);(\d+)R", response) +def get_position() -> tuple[int, int]: + reply = _query_terminal("\x1b[6n", "R") + + match = re.search(r"\[(\d+);(\d+)R", reply) if match: y, x = map(int, match.groups()) return y, x @@ -85,12 +115,27 @@ def get_position() -> tuple[int, int]: if __name__ == "__main__": import pyvista as pv + cols, rows = get_terminal_size() + v_pix, h_pix = get_terminal_cell_size() + height, width = get_terminal_size_pixel() + + y, x = get_position() + print(f'Terminal has {rows} rows, {cols} cols = {rows * cols} cells') + print(f'Cell size: {h_pix}x{v_pix} px') + print(f'Dimensions: {width}x{height} px') + + + new_lines = int((height/v_pix)-6) + print('\n' * new_lines, end='') + + set_position(y - new_lines - 6, x) + s = pv.Sphere() - pl = pv.Plotter() + pl = pv.Plotter(off_screen=True) pl.add_mesh(s) b = BytesIO() - pl.show() - pl.screenshot(b) + pl.screenshot(b, transparent_background=True, window_size=(width, height)) + # i = Image.new("RGB", (100, 100), (0, 0, 0)) # d = ImageDraw.Draw(i) # d.ellipse([(5, 5), (95, 95)])