kovidgoyal / kitty

Cross-platform, fast, feature-rich, GPU based terminal
https://sw.kovidgoyal.net/kitty/
GNU General Public License v3.0
24.15k stars 972 forks source link

Sending png file to console using direct transmission. #1080

Closed grst closed 5 years ago

grst commented 5 years ago

I want to send a png image using direct transmission (d) transfer mode from a python script to kitty. It does not work. Maybe you can help me to figure out what I am doing wrong.

kitty icat works. However that's not an option as later, I want to execute the script via ssh on a remote server that does not have kitty installed:

from subprocess import call

# works
call(["kitty", "icat", "/tmp/test.png"])

Therefore, I read the png image, encode it as base64 and send it to stdout enclosed in the escape characters. While kitty seems to recognize the escape sequence (no output text is shown) the image does not appear. The output text does appear on a terminal that does not recognize the escape sequence.

import base64
import sys

# does not work
with open('/tmp/test.png', 'rb') as f:
    png = f.read()
png = base64.standard_b64encode(png)

protocol_start = b'\033_Gf=100;'
protocol_end = b'\033\\'

stdbout = getattr(sys.stdout, 'buffer', sys.stdout)
msg = protocol_start + png + protocol_end
stdbout.write(msg)
stdbout.flush()

Here's the /tmp/test.png image. It only has 124 bytes, so the chunk size limit of 4096 should not be an issue here? test https://user-images.githubusercontent.com/7051479/47207461-92b43a80-d38b-11e8-82fb-d9c2abb6461f.png

This is the output of kitty --dump-commands

graphics_command {'action': b'\x00', 'delete_action': b'\x00', 'transmission_type': b'\x00', 'compressed': b'\x00', 'format': 100, 'more': 0, 'id': 0, 'width': 0, 'height': 0, 'x_offset': 0, 'y_offset': 0, 'data_height': 0, 'data_width': 0, 'data_sz': 0, 'data_offset': 0, 'num_cells': 0, 'num_lines': 0, 'cell_x_offset': 0, 'cell_y_offset': 0, 'z_index': 0, 'payload_sz': 124} b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00!\x00\x00\x00\x15\x08\x02\x00\x00\x00Gm\x10\x83\x00\x00\x00\x03sBIT\x08\x08\x08\xdb\xe1O\xe0\x00\x00\x004IDATHKc|\xff\xfe=\x03\x8d\x01\x13\x8d\xcd\x07\x19?j\x07\xf1\x81<\x1aV\xa3aE|\x08\x10\xafr4]\x8d\x86\x15\xf1!@\xbc\xca\xd1t5\xb8\xc2\n\x00\xa9\xdb\x02\xf7\x1e\x82\xe2_\x00\x00\x00\x00IEND\xaeB`\x82'
kovidgoyal commented 5 years ago

I'll look at your script when I have a moment, but the icat kitten is itself a simple script. it should be trivial to modify it, replace the little bits of kitty functionality it uses with stdlib quivalents and use it.

kovidgoyal commented 5 years ago

See icat/main.py in the kitty source code.

grst commented 5 years ago

Thanks @kovidgoyal,

I indeed had a look there to come up with the minimal working example... however it did not work out. While I agree that I probably should refactor the icat code for my purposes I really want to understand the underlying basics.

From what I understand from the docs this should be the minimal code to make it work, yet it does not.

with open('file.png', 'rb') as f:
    png = base64.standard_b64encode(f.read())
protocol_start = b'\033_Gf=100;'
protocol_end = b'\033\\'
sys.stdout.buffer.write(protocol_start + png + protocol_end)
sys.stdout.buffer.flush()
kovidgoyal commented 5 years ago

well ok I'll see if I can cook up a minimal example when I have a moment.

kovidgoyal commented 5 years ago

looking at your example, you need a=T which means transmit and display the default is a=t which is only transmit. This patch will show you the command icat uses:

diff --git a/kittens/icat/main.py b/kittens/icat/main.py
index 5f2f9815..ef2da784 100755
--- a/kittens/icat/main.py
+++ b/kittens/icat/main.py
@@ -93,6 +93,7 @@ def options_spec():

 def write_gr_cmd(cmd, payload=None):
+    print(11111, cmd)
     sys.stdout.buffer.write(serialize_gr_command(cmd, payload))
     sys.stdout.flush()

@@ -204,6 +205,8 @@ def scan(d):

 def detect_support(wait_for=10, silent=False):
+    detect_support.has_files = False
+    return True
     if not silent:
         print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r')
     sys.stdout.flush()

for direct transmission. Probably it has some redundant keys, you can experiment with removing some to reach a minimal example. If you do feel free to update this bug report and I will add it to the docs.

Luflosi commented 5 years ago

@grst Replacing protocol_start = b'\033_Gf=100;' with protocol_start = b'\033_Gf=100,a=T;' in your minimal example prints the image for me.

kovidgoyal commented 5 years ago

I have added a minimal example to the docs, that works with arbitrary sized PNG files.

grst commented 5 years ago

Awesome! Works like a charm now.

Thanks a lot!