mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
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:
@@ -37,9 +37,10 @@ ios_size IOStreamMemory::write( const char* data, ios_size size ) {
|
||||
memcpy( mWritePtr + mPos, data, size );
|
||||
|
||||
mPos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
return mPos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ios_size IOStreamMemory::seek( ios_size position ) {
|
||||
|
||||
@@ -1,16 +1,120 @@
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/base64.hpp>
|
||||
#include <eepp/system/compression.hpp>
|
||||
#include <eepp/system/iostreammemory.hpp>
|
||||
#include <eepp/window/terminal/kittyframepresenter.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
using namespace EE::System;
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Int32 TileSize = 32;
|
||||
constexpr size_t CompressionThreshold = 4096;
|
||||
constexpr Uint32 StreamImageId = 0x45455050;
|
||||
constexpr Uint32 StreamPlacementId = 1;
|
||||
enum class DamageResult : Uint8 { None, Rectangle, Full };
|
||||
|
||||
bool environmentFlag( const char* name, bool defaultValue ) {
|
||||
const char* value = std::getenv( name );
|
||||
if ( !value || !value[0] )
|
||||
return defaultValue;
|
||||
if ( 0 == std::strcmp( value, "0" ) || String::iequals( value, "false" ) ||
|
||||
String::iequals( value, "off" ) || String::iequals( value, "no" ) )
|
||||
return false;
|
||||
if ( 0 == std::strcmp( value, "1" ) || String::iequals( value, "true" ) ||
|
||||
String::iequals( value, "on" ) || String::iequals( value, "yes" ) )
|
||||
return true;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int environmentCompressionLevel() {
|
||||
const char* value = std::getenv( "EEPP_TERMINAL_ZLIB_LEVEL" );
|
||||
if ( !value || !value[0] )
|
||||
return 1;
|
||||
char* end = nullptr;
|
||||
const long level = std::strtol( value, &end, 10 );
|
||||
return end != value && *end == '\0' && level >= 0 && level <= 9 ? static_cast<int>( level ) : 1;
|
||||
}
|
||||
|
||||
template <typename Rectangle>
|
||||
DamageResult findDamage( const std::vector<Uint8>& current, const std::vector<Uint8>& previous,
|
||||
const Math::Sizei& size, std::vector<Rectangle>& rectangles ) {
|
||||
rectangles.clear();
|
||||
const Int32 tilesX = ( size.x + TileSize - 1 ) / TileSize;
|
||||
const Int32 tilesY = ( size.y + TileSize - 1 ) / TileSize;
|
||||
const size_t rowBytes = static_cast<size_t>( size.x ) * 3;
|
||||
const size_t totalPixels = static_cast<size_t>( size.x ) * size.y;
|
||||
size_t changedPixels = 0;
|
||||
Int32 left = size.x;
|
||||
Int32 top = size.y;
|
||||
Int32 right = 0;
|
||||
Int32 bottom = 0;
|
||||
|
||||
for ( Int32 tileY = 0; tileY < tilesY; ++tileY ) {
|
||||
const Int32 y = tileY * TileSize;
|
||||
const Int32 height = eemin( TileSize, size.y - y );
|
||||
for ( Int32 tileX = 0; tileX < tilesX; ++tileX ) {
|
||||
bool changed = false;
|
||||
const Int32 x = tileX * TileSize;
|
||||
const Int32 width = eemin( TileSize, size.x - x );
|
||||
for ( Int32 row = 0; row < height && !changed; ++row ) {
|
||||
const Int32 sourceRow = size.y - 1 - ( y + row );
|
||||
const size_t offset =
|
||||
static_cast<size_t>( sourceRow ) * rowBytes + static_cast<size_t>( x ) * 3;
|
||||
changed = 0 != std::memcmp( current.data() + offset, previous.data() + offset,
|
||||
static_cast<size_t>( width ) * 3 );
|
||||
}
|
||||
if ( changed ) {
|
||||
changedPixels += static_cast<size_t>( width ) * height;
|
||||
left = eemin( left, x );
|
||||
top = eemin( top, y );
|
||||
right = eemax( right, x + width );
|
||||
bottom = eemax( bottom, y + height );
|
||||
if ( changedPixels * 5 >= totalPixels * 3 )
|
||||
return DamageResult::Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( left < right && top < bottom ) {
|
||||
rectangles.push_back( { left, top, right - left, bottom - top } );
|
||||
return DamageResult::Rectangle;
|
||||
}
|
||||
return DamageResult::None;
|
||||
}
|
||||
|
||||
bool compressPixels( const std::vector<Uint8>& input, std::vector<Uint8>& output, int level ) {
|
||||
if ( level == 0 || input.size() < CompressionThreshold )
|
||||
return false;
|
||||
const int maxSize = Compression::getMaxCompressedBufferSize( input.size() );
|
||||
if ( maxSize <= 0 )
|
||||
return false;
|
||||
output.resize( static_cast<size_t>( maxSize ) );
|
||||
IOStreamMemory source( reinterpret_cast<const char*>( input.data() ), input.size() );
|
||||
IOStreamMemory destination( reinterpret_cast<char*>( output.data() ), output.size() );
|
||||
Compression::Config config;
|
||||
config.zlib.level = level;
|
||||
if ( Compression::compress( destination, source, Compression::MODE_DEFLATE, config ) !=
|
||||
Compression::OK )
|
||||
return false;
|
||||
const size_t compressedSize = static_cast<size_t>( destination.tell() );
|
||||
if ( compressedSize + 64 >= input.size() * 9 / 10 )
|
||||
return false;
|
||||
output.resize( compressedSize );
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KittyFramePresenter::~KittyFramePresenter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
@@ -24,6 +128,10 @@ KittyFramePresenter::~KittyFramePresenter() {
|
||||
bool KittyFramePresenter::initialize( Window& window ) {
|
||||
if ( !TerminalRuntime::instance().initialize() )
|
||||
return false;
|
||||
mPersistentUpdatesEnabled = environmentFlag( "EEPP_TERMINAL_PERSISTENT_UPDATES", true );
|
||||
mDamageUpdatesEnabled =
|
||||
mPersistentUpdatesEnabled && environmentFlag( "EEPP_TERMINAL_DAMAGE_UPDATES", true );
|
||||
mZlibCompressionLevel = environmentCompressionLevel();
|
||||
TerminalRuntime::instance().attach( window );
|
||||
mRunning = true;
|
||||
mWorker = std::thread( &KittyFramePresenter::run, this );
|
||||
@@ -76,55 +184,112 @@ void KittyFramePresenter::run() {
|
||||
frame = std::move( mPending );
|
||||
mHasPending = false;
|
||||
}
|
||||
sendFrame( frame );
|
||||
if ( sendFrame( frame ) )
|
||||
std::swap( mPresented, frame );
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
mRecycle = std::move( frame );
|
||||
}
|
||||
}
|
||||
|
||||
void KittyFramePresenter::sendFrame( const Frame& frame ) {
|
||||
std::array<unsigned char, 3072> binary;
|
||||
bool KittyFramePresenter::sendFrame( const Frame& frame ) {
|
||||
if ( frame.size.x <= 0 || frame.size.y <= 0 )
|
||||
return false;
|
||||
const DamageRectangle full{ 0, 0, frame.size.x, frame.size.y };
|
||||
if ( !mDamageUpdatesEnabled ) {
|
||||
if ( mPresented.size == frame.size && mPresented.pixels.size() == frame.pixels.size() &&
|
||||
0 ==
|
||||
std::memcmp( mPresented.pixels.data(), frame.pixels.data(), frame.pixels.size() ) )
|
||||
return true;
|
||||
extractRectangle( frame, full );
|
||||
const bool initial = !mPersistentUpdatesEnabled || mPresented.size != frame.size ||
|
||||
mPresented.pixels.size() != frame.pixels.size();
|
||||
return sendTransfer( mTransferPixels, full, initial );
|
||||
}
|
||||
if ( mPresented.size != frame.size || mPresented.pixels.size() != frame.pixels.size() ) {
|
||||
extractRectangle( frame, full );
|
||||
return sendTransfer( mTransferPixels, full, true );
|
||||
}
|
||||
|
||||
const DamageResult damage =
|
||||
findDamage( frame.pixels, mPresented.pixels, frame.size, mDamageRectangles );
|
||||
if ( damage == DamageResult::Full ) {
|
||||
extractRectangle( frame, full );
|
||||
return sendTransfer( mTransferPixels, full, false );
|
||||
}
|
||||
if ( damage == DamageResult::None )
|
||||
return true;
|
||||
for ( const DamageRectangle& rectangle : mDamageRectangles ) {
|
||||
extractRectangle( frame, rectangle );
|
||||
if ( !sendTransfer( mTransferPixels, rectangle, false ) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KittyFramePresenter::sendTransfer( const std::vector<Uint8>& pixels,
|
||||
const DamageRectangle& rectangle, bool initial ) {
|
||||
const bool useCompression = compressPixels( pixels, mCompressedPixels, mZlibCompressionLevel );
|
||||
const std::vector<Uint8>& payload = useCompression ? mCompressedPixels : pixels;
|
||||
std::array<char, 4097> encoded;
|
||||
std::array<char, 128 + 4096 + 2> packet;
|
||||
const size_t rowBytes = static_cast<size_t>( frame.size.x ) * 3;
|
||||
// glReadPixels returns bottom-left-origin rows while Kitty consumes top-left-origin pixels.
|
||||
// Reverse row traversal during streaming so no full-frame flip buffer is needed.
|
||||
Int32 row = frame.size.y - 1;
|
||||
size_t column = 0;
|
||||
std::array<char, 256 + 4096 + 2> packet;
|
||||
size_t offset = 0;
|
||||
bool first = true;
|
||||
while ( row >= 0 ) {
|
||||
size_t count = 0;
|
||||
while ( count < binary.size() && row >= 0 ) {
|
||||
const size_t copy = eemin( binary.size() - count, rowBytes - column );
|
||||
std::memcpy( binary.data() + count,
|
||||
frame.pixels.data() + static_cast<size_t>( row ) * rowBytes + column,
|
||||
copy );
|
||||
count += copy;
|
||||
column += copy;
|
||||
if ( column == rowBytes ) {
|
||||
column = 0;
|
||||
--row;
|
||||
}
|
||||
}
|
||||
while ( offset < payload.size() ) {
|
||||
const size_t count = eemin( static_cast<size_t>( 3072 ), payload.size() - offset );
|
||||
const size_t encodedSize =
|
||||
Base64::encode( count, binary.data(), encoded.size(), encoded.data() );
|
||||
char header[128];
|
||||
const int headerSize =
|
||||
first
|
||||
? std::snprintf( header, sizeof( header ), "\033_Gf=24,t=d,a=T,C=1,s=%d,v=%d,m=%d;",
|
||||
frame.size.x, frame.size.y, row >= 0 ? 1 : 0 )
|
||||
: std::snprintf( header, sizeof( header ), "\033_Gm=%d;", row >= 0 ? 1 : 0 );
|
||||
Base64::encode( count, payload.data() + offset, encoded.size(), encoded.data() );
|
||||
offset += count;
|
||||
char header[256];
|
||||
int headerSize;
|
||||
if ( first && initial ) {
|
||||
if ( mDamageUpdatesEnabled || mPersistentUpdatesEnabled ) {
|
||||
headerSize = std::snprintf(
|
||||
header, sizeof( header ),
|
||||
"\033_Gf=24,t=d,a=T,C=1,q=2,i=%u,p=%u,s=%d,v=%d%s,m=%d;", StreamImageId,
|
||||
StreamPlacementId, rectangle.width, rectangle.height,
|
||||
useCompression ? ",o=z" : "", offset < payload.size() ? 1 : 0 );
|
||||
} else {
|
||||
headerSize = std::snprintf(
|
||||
header, sizeof( header ), "\033_Gf=24,t=d,a=T,C=1,q=2,s=%d,v=%d%s,m=%d;",
|
||||
rectangle.width, rectangle.height, useCompression ? ",o=z" : "",
|
||||
offset < payload.size() ? 1 : 0 );
|
||||
}
|
||||
} else if ( first ) {
|
||||
headerSize = std::snprintf(
|
||||
header, sizeof( header ),
|
||||
"\033_Gf=24,t=d,a=f,q=2,i=%u,r=1,x=%d,y=%d,s=%d,v=%d,X=1%s,m=%d;", StreamImageId,
|
||||
rectangle.x, rectangle.y, rectangle.width, rectangle.height,
|
||||
useCompression ? ",o=z" : "", offset < payload.size() ? 1 : 0 );
|
||||
} else {
|
||||
headerSize = std::snprintf( header, sizeof( header ),
|
||||
initial ? "\033_Gm=%d;" : "\033_Ga=f,m=%d;",
|
||||
offset < payload.size() ? 1 : 0 );
|
||||
}
|
||||
if ( encodedSize == static_cast<size_t>( -1 ) || headerSize <= 0 ||
|
||||
static_cast<size_t>( headerSize ) >= sizeof( header ) )
|
||||
return;
|
||||
return false;
|
||||
std::memcpy( packet.data(), header, static_cast<size_t>( headerSize ) );
|
||||
std::memcpy( packet.data() + headerSize, encoded.data(), encodedSize );
|
||||
packet[static_cast<size_t>( headerSize ) + encodedSize] = '\033';
|
||||
packet[static_cast<size_t>( headerSize ) + encodedSize + 1] = '\\';
|
||||
if ( !TerminalRuntime::instance().write( packet.data(), static_cast<size_t>( headerSize ) +
|
||||
encodedSize + 2 ) )
|
||||
return;
|
||||
return false;
|
||||
first = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void KittyFramePresenter::extractRectangle( const Frame& frame, const DamageRectangle& rectangle ) {
|
||||
mTransferPixels.resize( static_cast<size_t>( rectangle.width ) * rectangle.height * 3 );
|
||||
const size_t frameStride = static_cast<size_t>( frame.size.x ) * 3;
|
||||
const size_t rectangleStride = static_cast<size_t>( rectangle.width ) * 3;
|
||||
for ( Int32 row = 0; row < rectangle.height; ++row ) {
|
||||
const Int32 sourceRow = frame.size.y - 1 - ( rectangle.y + row );
|
||||
std::memcpy( mTransferPixels.data() + static_cast<size_t>( row ) * rectangleStride,
|
||||
frame.pixels.data() + static_cast<size_t>( sourceRow ) * frameStride +
|
||||
static_cast<size_t>( rectangle.x ) * 3,
|
||||
rectangleStride );
|
||||
}
|
||||
}
|
||||
}} // namespace EE::Window
|
||||
|
||||
@@ -208,11 +208,14 @@ void TerminalRuntime::detach() {
|
||||
namespace {
|
||||
Uint32 decodeModifiers( unsigned int value ) {
|
||||
const unsigned int bits = value ? value - 1 : 0;
|
||||
return ( bits & 1 ? KEYMOD_SHIFT : 0 ) | ( bits & 2 ? KEYMOD_ALT : 0 ) |
|
||||
( bits & 4 ? KEYMOD_CTRL : 0 ) | ( bits & 8 ? KEYMOD_META : 0 );
|
||||
return ( bits & 1 ? KEYMOD_SHIFT : 0 ) | ( bits & 2 ? KEYMOD_LALT : 0 ) |
|
||||
( bits & 4 ? KEYMOD_CTRL : 0 ) | ( bits & 8 ? KEYMOD_META : 0 ) |
|
||||
( bits & 64 ? KEYMOD_CAPS : 0 ) | ( bits & 128 ? KEYMOD_NUM : 0 );
|
||||
}
|
||||
|
||||
Scancode decodeScancode( Uint32 code ) {
|
||||
Scancode decodeScancode( Uint32 code, bool kittyBackspace ) {
|
||||
if ( kittyBackspace && code == 127 )
|
||||
return SCANCODE_BACKSPACE;
|
||||
if ( code >= 'a' && code <= 'z' )
|
||||
return static_cast<Scancode>( SCANCODE_A + code - 'a' );
|
||||
if ( code >= 'A' && code <= 'Z' )
|
||||
@@ -243,6 +246,10 @@ Scancode decodeScancode( Uint32 code ) {
|
||||
return SCANCODE_DELETE;
|
||||
if ( code >= static_cast<Uint32>( KEY_F1 ) && code <= static_cast<Uint32>( KEY_F12 ) )
|
||||
return static_cast<Scancode>( SCANCODE_F1 + code - static_cast<Uint32>( KEY_F1 ) );
|
||||
if ( code >= 57376 && code <= 57387 )
|
||||
return static_cast<Scancode>( SCANCODE_F13 + code - 57376 );
|
||||
if ( code >= 57400 && code <= 57408 )
|
||||
return static_cast<Scancode>( SCANCODE_KP_1 + code - 57400 );
|
||||
switch ( code ) {
|
||||
case 9:
|
||||
return SCANCODE_TAB;
|
||||
@@ -252,19 +259,97 @@ Scancode decodeScancode( Uint32 code ) {
|
||||
return SCANCODE_ESCAPE;
|
||||
case 32:
|
||||
return SCANCODE_SPACE;
|
||||
case 127:
|
||||
return SCANCODE_BACKSPACE;
|
||||
case '-':
|
||||
return SCANCODE_MINUS;
|
||||
case '=':
|
||||
return SCANCODE_EQUALS;
|
||||
case '[':
|
||||
return SCANCODE_LEFTBRACKET;
|
||||
case ']':
|
||||
return SCANCODE_RIGHTBRACKET;
|
||||
case '\\':
|
||||
return SCANCODE_BACKSLASH;
|
||||
case ';':
|
||||
return SCANCODE_SEMICOLON;
|
||||
case '\'':
|
||||
return SCANCODE_APOSTROPHE;
|
||||
case '`':
|
||||
return SCANCODE_GRAVE;
|
||||
case ',':
|
||||
return SCANCODE_COMMA;
|
||||
case '.':
|
||||
return SCANCODE_PERIOD;
|
||||
case '/':
|
||||
return SCANCODE_SLASH;
|
||||
case 57358:
|
||||
return SCANCODE_CAPSLOCK;
|
||||
case 57359:
|
||||
return SCANCODE_SCROLLLOCK;
|
||||
case 57360:
|
||||
return SCANCODE_NUMLOCKCLEAR;
|
||||
case 57361:
|
||||
return SCANCODE_PRINTSCREEN;
|
||||
case 57362:
|
||||
return SCANCODE_PAUSE;
|
||||
case 57363:
|
||||
return SCANCODE_MENU;
|
||||
case 57399:
|
||||
return SCANCODE_KP_0;
|
||||
case 57409:
|
||||
return SCANCODE_KP_PERIOD;
|
||||
case 57410:
|
||||
return SCANCODE_KP_DIVIDE;
|
||||
case 57411:
|
||||
return SCANCODE_KP_MULTIPLY;
|
||||
case 57412:
|
||||
return SCANCODE_KP_MINUS;
|
||||
case 57413:
|
||||
return SCANCODE_KP_PLUS;
|
||||
case 57414:
|
||||
return SCANCODE_KP_ENTER;
|
||||
case 57415:
|
||||
return SCANCODE_KP_EQUALS;
|
||||
case 57416:
|
||||
return SCANCODE_KP_COMMA;
|
||||
case 57441:
|
||||
return SCANCODE_LSHIFT;
|
||||
case 57442:
|
||||
return SCANCODE_LCTRL;
|
||||
case 57443:
|
||||
return SCANCODE_LALT;
|
||||
case 57444:
|
||||
return SCANCODE_LGUI;
|
||||
case 57447:
|
||||
return SCANCODE_RSHIFT;
|
||||
case 57448:
|
||||
return SCANCODE_RCTRL;
|
||||
case 57449:
|
||||
return SCANCODE_RALT;
|
||||
case 57450:
|
||||
return SCANCODE_RGUI;
|
||||
default:
|
||||
return SCANCODE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueueKey( Window& window, Uint32 code, Uint32 mods, Uint32 kind, Uint32 text ) {
|
||||
Keycode decodeKeycode( Uint32 code, Scancode scancode, bool kittyBackspace ) {
|
||||
// Kitty identifies Backspace with its terminal encoding (DEL), while eepp represents the
|
||||
// logical key as BS. Keep protocol encoding out of the neutral InputEvent keycode.
|
||||
if ( kittyBackspace && code == 127 )
|
||||
return KEY_BACKSPACE;
|
||||
return code >= 57344 && scancode != SCANCODE_UNKNOWN
|
||||
? static_cast<Keycode>( SCANCODE_TO_KEYCODE( scancode ) )
|
||||
: static_cast<Keycode>( code );
|
||||
}
|
||||
|
||||
void enqueueKey( Window& window, Uint32 code, Uint32 mods, Uint32 kind, Uint32 text,
|
||||
bool kittyBackspace = false ) {
|
||||
const Scancode scancode = decodeScancode( code, kittyBackspace );
|
||||
InputEvent event( kind == 3 ? InputEvent::KeyUp : InputEvent::KeyDown );
|
||||
event.key = { 0,
|
||||
static_cast<Uint8>( kind == 3 ? 0 : 1 ),
|
||||
static_cast<Uint8>( kind == 2 ),
|
||||
{ decodeScancode( code ), static_cast<Keycode>( code ), mods, text } };
|
||||
{ scancode, decodeKeycode( code, scancode, kittyBackspace ), mods, text } };
|
||||
window.getInput()->enqueueEvent( event );
|
||||
if ( text && kind != 3 ) {
|
||||
InputEvent input( InputEvent::TextInput );
|
||||
@@ -342,6 +427,15 @@ void TerminalRuntime::readInput() {
|
||||
pending.append( data, count );
|
||||
}
|
||||
#endif
|
||||
// Kitty graphics acknowledgements are APC strings. They are not input events, and leaving
|
||||
// an unrecognized ESC _ prefix at the front would block all subsequent keyboard and mouse
|
||||
// input.
|
||||
while ( pending.size() >= 2 && pending[0] == '\033' && pending[1] == '_' ) {
|
||||
const size_t end = pending.find( "\033\\", 2 );
|
||||
if ( end == std::string::npos )
|
||||
break;
|
||||
pending.erase( 0, end + 2 );
|
||||
}
|
||||
while ( pending.size() >= 3 && pending[0] == '\033' && pending[1] == '[' ) {
|
||||
size_t end = 2;
|
||||
while ( end < pending.size() && !( pending[end] >= '@' && pending[end] <= '~' ) )
|
||||
@@ -408,7 +502,22 @@ void TerminalRuntime::readInput() {
|
||||
if ( separator != std::string::npos ) {
|
||||
code = static_cast<unsigned int>( std::strtoul( params.c_str(), nullptr, 10 ) );
|
||||
std::sscanf( params.c_str() + separator + 1, "%u:%u;%u", &mods, &kind, &text );
|
||||
enqueueKey( *mWindow, code, decodeModifiers( mods ), kind, text );
|
||||
enqueueKey( *mWindow, code, decodeModifiers( mods ), kind, text, code == 127 );
|
||||
const size_t textSeparator = params.find( ';', separator + 1 );
|
||||
if ( kind != 3 && textSeparator != std::string::npos ) {
|
||||
size_t next = params.find( ':', textSeparator + 1 );
|
||||
while ( next != std::string::npos ) {
|
||||
const Uint32 codepoint = static_cast<Uint32>(
|
||||
std::strtoul( params.c_str() + next + 1, nullptr, 10 ) );
|
||||
if ( codepoint ) {
|
||||
InputEvent input( InputEvent::TextInput );
|
||||
input.text = { 0,
|
||||
static_cast<String::StringBaseType>( codepoint ) };
|
||||
mWindow->getInput()->enqueueEvent( input );
|
||||
}
|
||||
next = params.find( ':', next + 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( final == 'A' || final == 'B' || final == 'C' || final == 'D' ||
|
||||
final == 'H' || final == 'F' || final == 'P' || final == 'Q' ||
|
||||
|
||||
@@ -71,7 +71,7 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
4 ) );
|
||||
|
||||
Log::instance()->setLogLevelThreshold( LogLevel::Debug );
|
||||
Log::instance()->setLogToStdOut( true );
|
||||
Log::instance()->setLogToStdOut( !Runtime::isOffscreen() );
|
||||
Log::instance()->setLiveWrite( true );
|
||||
|
||||
Http::setDefaultUserAgent( "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like "
|
||||
|
||||
@@ -86,7 +86,7 @@ void EETest::init() {
|
||||
EE = Engine::instance();
|
||||
|
||||
Log::instance()->setLiveWrite( true );
|
||||
Log::instance()->setLogToStdOut( true );
|
||||
Log::instance()->setLogToStdOut( !Runtime::isOffscreen() );
|
||||
|
||||
mTranslator.loadFromString( "<resources language='en'>"
|
||||
" <string name='app_name'>eepp</string>"
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -1234,7 +1234,7 @@ void App::init( const Float& pixelDensityConf, const bool& useAppTheme, const st
|
||||
pixelDensity = pixelDensityConf;
|
||||
|
||||
Log::instance()->setLiveWrite( true );
|
||||
Log::instance()->setLogToStdOut( true );
|
||||
Log::instance()->setLogToStdOut( !Runtime::isOffscreen() );
|
||||
|
||||
mResPath = Sys::getProcessPath();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user