try waiting for confirmation from the terminal - works somewhat, but messes with input

This commit is contained in:
Felix Pankratz 2025-07-26 18:33:32 +02:00
parent a0376ae9a4
commit 8f3b63efc4

View File

@ -1,21 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from base64 import standard_b64encode
from enum import Enum
import array
import asyncio import asyncio
import fcntl
import mmap
import os
import re import re
import select
import sys import sys
import termios import termios
import tty import tty
from base64 import standard_b64encode
import array
import fcntl
import zlib import zlib
import mmap
import os
import select
from enum import Enum
SHM_NAME = "/kitty-shm-frame" SHM_NAME = "/kitty-shm-frame"
@ -24,6 +22,9 @@ class Kitty_Format(Enum):
RGBA = 32 RGBA = 32
PNG = 100 PNG = 100
class TerminalProtocolError(Exception):
pass
async def draw_to_terminal( async def draw_to_terminal(
buffer, buffer,
@ -36,11 +37,11 @@ async def draw_to_terminal(
) -> None: ) -> None:
"""Display an image in the terminal.""" """Display an image in the terminal."""
if (pixel_format != Kitty_Format.PNG or use_shm) and (width is None or height is None): if (pixel_format != Kitty_Format.PNG or use_shm) and (width is None or height is None):
raise ValueError( raise TerminalProtocolError(
"shm transfer or using image formats other than PNG and require height and width" "shm transfer or using image formats other than PNG and require height and width"
) )
kwargs = {"a": "T", "i": image_num, "f": pixel_format.value, "t": "s" if use_shm else "d", "q": 2} kwargs = {"a": "T", "i": image_num, "f": pixel_format.value, "t": "s" if use_shm else "d"}
if width: if width:
kwargs["s"] = width kwargs["s"] = width
@ -59,7 +60,7 @@ async def draw_to_terminal(
# set shm name as payload # set shm name as payload
data = SHM_NAME data = SHM_NAME
await _write_chunked(**kwargs, data=data) await _write_chunked(**kwargs, data=data, wait_for_ok=True)
await asyncio.sleep(0.001) await asyncio.sleep(0.001)
@ -113,7 +114,7 @@ async def get_terminal_cell_size() -> tuple[int, int]:
v_pix, h_pix = map(int, match.groups()) v_pix, h_pix = map(int, match.groups())
return v_pix, h_pix return v_pix, h_pix
print(reply) print(reply)
raise ValueError("Failed to parse terminal cell size response") raise TerminalProtocolError("Failed to parse terminal cell size response")
async def get_terminal_size_pixel() -> tuple[int, int]: async def get_terminal_size_pixel() -> tuple[int, int]:
@ -124,7 +125,7 @@ async def get_terminal_size_pixel() -> tuple[int, int]:
if match: if match:
height, width = map(int, match.groups()) height, width = map(int, match.groups())
return height, width return height, width
raise ValueError("Failed to parse terminal pixel size response") raise TerminalProtocolError("Failed to parse terminal pixel size response")
def get_terminal_size() -> tuple[int, int]: def get_terminal_size() -> tuple[int, int]:
@ -148,11 +149,33 @@ def _serialize_gr_command(**cmd) -> bytes:
return b"".join(ans) return b"".join(ans)
async def _write_chunked(**cmd) -> None: #async def _write_chunked(**cmd) -> None:
# if cmd["t"] == "s":
# data = standard_b64encode(bytes(cmd.pop("data"), "utf-8"))
# else:
# data = standard_b64encode(cmd.pop("data"))
# while data:
# chunk, data = data[:4096], data[4096:]
# m = 1 if data else 0
# payload = _serialize_gr_command(payload=chunk, m=m, **cmd)
# sys.stdout.buffer.write(payload)
# sys.stdout.flush()
# cmd.clear()
async def _write_chunked(wait_for_ok: bool = True, **cmd) -> None:
image_id = cmd['i']
if cmd["t"] == "s": if cmd["t"] == "s":
data = standard_b64encode(bytes(cmd.pop("data"), "utf-8")) data = standard_b64encode(bytes(cmd.pop("data"), "utf-8"))
else: else:
data = standard_b64encode(cmd.pop("data")) data = standard_b64encode(cmd.pop("data"))
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
tty.setraw(fd)
loop = asyncio.get_running_loop()
try:
while data: while data:
chunk, data = data[:4096], data[4096:] chunk, data = data[:4096], data[4096:]
m = 1 if data else 0 m = 1 if data else 0
@ -161,6 +184,24 @@ async def _write_chunked(**cmd) -> None:
sys.stdout.flush() sys.stdout.flush()
cmd.clear() cmd.clear()
if wait_for_ok:
# Wait for terminal to confirm OK response
response = os.read(fd, 1024).decode("utf-8", "ignore")
if not response.startswith(f"\033_Gi={image_id};OK"):
return
#raise RuntimeError(f"Unexpected response: {repr(response)}")
#return response.startswith("\033_GOK")
# def _read_ok():
# response = ""
# while not response.endswith("\033\\"):
# response += sys.stdin.read(1)
# return response
# response = await loop.run_in_executor(None, _read_ok)
# if not response.startswith(f"\033_Gi={image_id};OK"):
# raise RuntimeError(f"Unexpected response: {repr(response)}")
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
def hide_cursor() -> None: def hide_cursor() -> None:
"""Tell the terminal to hide the cursor.""" """Tell the terminal to hide the cursor."""