Skip to content

Commit abf0402

Browse files
committedMar 29, 2020
Add kitty graphics protocol support (#4)
1 parent d77d57f commit abf0402

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed
 

‎imgcat/imgcat.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,16 @@ def imgcat(data, filename=None,
214214
# image height unavailable, fallback?
215215
height = 10
216216

217-
from . import iterm2
218-
iterm2._write_image(buf, fp,
219-
filename=filename, width=width, height=height,
220-
preserve_aspect_ratio=preserve_aspect_ratio)
217+
# determine backend from
218+
if os.getenv('TERM', default='').endswith('-kitty'):
219+
from . import kitty
220+
# TODO handle other parameters
221+
kitty._write_image(buf, fp, height=height)
222+
else:
223+
from . import iterm2
224+
iterm2._write_image(buf, fp,
225+
filename=filename, width=width, height=height,
226+
preserve_aspect_ratio=preserve_aspect_ratio)
221227

222228

223229
def main():

‎imgcat/kitty.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import sys
2+
import os
3+
import base64
4+
5+
ESC = b'\033'
6+
7+
8+
9+
import sys
10+
from base64 import standard_b64encode
11+
12+
TMUX_WRAP_ST = b'\033Ptmux;'
13+
TMUX_WRAP_ED = b'\033\\'
14+
15+
16+
def serialize_gr_command(cmd, payload=None):
17+
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
18+
ans = []
19+
w = ans.append
20+
21+
is_tmux = 'TMUX' in os.environ and 'tmux' in os.environ['TMUX']
22+
23+
if is_tmux:
24+
w(TMUX_WRAP_ST + b'\033') #!
25+
26+
# kitty graphics sequence start
27+
w(b'\033_G'), w(cmd.encode('ascii'))
28+
29+
if payload:
30+
w(b';')
31+
w(payload)
32+
33+
if is_tmux:
34+
w(b'\033') # escape \033
35+
36+
# kitty graphics sequence end
37+
w(b'\033\\')
38+
39+
if is_tmux:
40+
w(TMUX_WRAP_ED) #!
41+
42+
return b''.join(ans)
43+
44+
45+
def clear():
46+
"""Send the sesquence for clearing all graphics."""
47+
is_tmux = 'TMUX' in os.environ and 'tmux' in os.environ['TMUX']
48+
seq = []
49+
w = seq.append
50+
51+
if is_tmux:
52+
w(b'\033Ptmux;\033')
53+
54+
w(b'\033_Ga=d,d=A')
55+
if is_tmux:
56+
w(b'\033')
57+
w(b'\033\\')
58+
59+
if is_tmux:
60+
w(b'\033\\')
61+
62+
sys.stdout.buffer.write(b''.join(seq))
63+
64+
65+
def _write_image(buf, fp, height):
66+
# https://sw.kovidgoyal.net/kitty/graphics-protocol.html
67+
# print some blank lines
68+
is_tmux = 'TMUX' in os.environ and 'tmux' in os.environ['TMUX']
69+
if is_tmux:
70+
CSI = b'\033['
71+
fp.write(b'\n' * height)
72+
fp.write(CSI + b'?25l')
73+
fp.write(CSI + str(height).encode() + b"F") # PEP-461
74+
fp.flush()
75+
76+
write_chunked({'a': 'T', 'f': 100}, buf)
77+
78+
# move back the cursor
79+
if is_tmux:
80+
fp.write(CSI + str(height).encode() + b"E")
81+
fp.write(CSI + b'?25h')
82+
fp.flush()
83+
84+
85+
def write_chunked(cmd, data):
86+
data = standard_b64encode(data)
87+
while data:
88+
chunk, data = data[:4096], data[4096:]
89+
m = 1 if data else 0
90+
cmd['m'] = m
91+
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
92+
sys.stdout.flush()
93+
cmd.clear()
94+
95+
96+
if __name__ == '__main__':
97+
with open(sys.argv[-1], 'rb') as f:
98+
_write_image(fp=sys.stdout.buffer, buf=f.read(), height=10)

0 commit comments

Comments
 (0)