optimize Kitty streaming and complete terminal input

Add persistent Kitty image presentation with RGB24 framebuffer updates,
tile-based damage detection, adaptive zlib compression, chunked Base64
transmission, and unchanged-frame suppression. Fall back to complete updates
when damage exceeds the configured threshold.

Expose environment controls for damage updates, persistent presentation, and
zlib compression level. Resolve presentation policy once during initialization
to keep environment parsing outside the frame path.

Repeat a=f on animation-frame continuation chunks as required by the Kitty
protocol, and suppress graphics acknowledgements from the terminal input
stream.

Complete Kitty keyboard translation for punctuation, function, keypad,
modifier, lock, and navigation keys. Preserve associated multi-codepoint text
and disambiguate Backspace from Delete using their protocol context.

Fix IOStreamMemory::write() to report bytes written so multi-chunk compression
works correctly, avoid stdout logging in the offscreen examples, and add
regression coverage for compressed streams and chunked frame updates.
This commit is contained in:
Martín Lucas Golini
2026-09-05 17:17:09 -03:00
parent a22489fd58
commit bd9360cf3e
8 changed files with 377 additions and 46 deletions
+31
View File
@@ -544,6 +544,20 @@ UTEST( eterm, kitty_graphics_chunk_continuations_reject_metadata_and_wrong_actio
EXPECT_EQ( KittyGraphicsError::InvalidArgument, protocol.handle( "m=0;BA==" ).error );
}
UTEST( eterm, kitty_graphics_frame_chunks_repeat_frame_action ) {
KittyGraphicsProtocol protocol;
ASSERT_EQ( KittyGraphicsError::None, protocol.handle( "a=t,f=32,s=1,v=1,i=8;AQIDBA==" ).error );
protocol.takeUpdates();
EXPECT_EQ( KittyGraphicsError::None,
protocol.handle( "a=f,i=8,r=1,f=32,s=1,v=1,X=1,m=1;BQYH" ).error );
const auto final = protocol.handle( "a=f,m=0;CA==" );
EXPECT_EQ( KittyGraphicsError::None, final.error );
EXPECT_TRUE( final.changed );
const std::vector<Uint8> expected{ 5, 6, 7, 8 };
ASSERT_TRUE( protocol.imagePixels( 8 ) != nullptr );
EXPECT_TRUE( expected == *protocol.imagePixels( 8 ) );
}
UTEST( eterm, kitty_graphics_image_number_allocates_id_and_echoes_number ) {
KittyGraphicsProtocol protocol;
auto created = protocol.handle( "a=t,f=32,s=1,v=1,I=77;AQIDBA==" );
@@ -579,6 +593,23 @@ UTEST( eterm, kitty_graphics_rgb_and_zlib_preserve_rgb24 ) {
EXPECT_TRUE( updates.front().pixels && expected == *updates.front().pixels );
}
UTEST( eterm, memory_stream_supports_multichunk_compression_output ) {
std::vector<Uint8> sourcePixels( 128 * 1024 );
Uint32 state = 0x12345678;
for ( Uint8& pixel : sourcePixels ) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
pixel = static_cast<Uint8>( state );
}
std::vector<Uint8> compressed( Compression::getMaxCompressedBufferSize( sourcePixels.size() ) );
IOStreamMemory source( reinterpret_cast<const char*>( sourcePixels.data() ),
sourcePixels.size() );
IOStreamMemory destination( reinterpret_cast<char*>( compressed.data() ), compressed.size() );
EXPECT_EQ( Compression::OK, Compression::compress( destination, source ) );
EXPECT_TRUE( destination.tell() > 16 * 1024 );
}
UTEST( eterm, kitty_graphics_png_decodes_to_rgba ) {
KittyGraphicsProtocol protocol;
const auto result = protocol.handle( "a=t,f=100,i=10;"