From fcc3a412fb3e3737a3e50c4c2837711016e7a51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 10 Sep 2026 01:42:51 -0300 Subject: [PATCH] fix: restrict persistent Kitty updates to eterm by default Enable persistent root-image and damage updates automatically only when TERM=eterm, since Kitty a=f root-image edits are not consistently supported by other terminal emulators. Use reliable anonymous full-frame transfers elsewhere while preserving the environment variables as explicit overrides. Document the compatibility rationale and the dependency of damage updates on persistent images. --- .../window/terminal/kittyframepresenter.hpp | 13 +- src/eepp/window/kittyframepresenter.cpp | 15 +- src/thirdparty/chipmunk/cpSpace.c | 146 +++++++++--------- 3 files changed, 94 insertions(+), 80 deletions(-) diff --git a/include/eepp/window/terminal/kittyframepresenter.hpp b/include/eepp/window/terminal/kittyframepresenter.hpp index 8ea269df7..e652c128e 100644 --- a/include/eepp/window/terminal/kittyframepresenter.hpp +++ b/include/eepp/window/terminal/kittyframepresenter.hpp @@ -12,9 +12,12 @@ namespace EE { namespace Window { * * Presentation policy can be configured before initialization with * `EEPP_TERMINAL_DAMAGE_UPDATES`, `EEPP_TERMINAL_PERSISTENT_UPDATES`, and - * `EEPP_TERMINAL_ZLIB_LEVEL`. Damage and persistent updates default to enabled. The zlib level - * defaults to 1, accepts levels 1 through 9, and can be set to 0 to disable compression. Disabling - * persistent updates also disables damage updates because rectangles require a persistent image. + * `EEPP_TERMINAL_ZLIB_LEVEL`. Persistent and damage updates default to enabled only when + * `TERM=eterm`; other terminals default to anonymous full-frame transfers because support for the + * Kitty root-image animation update commands is inconsistent. Either default can be explicitly + * overridden with its corresponding environment variable. The zlib level defaults to 1, accepts + * levels 1 through 9, and can be set to 0 to disable compression. Disabling persistent updates also + * disables damage updates because rectangle updates require a persistent image. */ class KittyFramePresenter final : public FramePresenter { public: @@ -52,8 +55,8 @@ class KittyFramePresenter final : public FramePresenter { int mZlibCompressionLevel{ 1 }; bool mRunning{ false }; bool mHasPending{ false }; - bool mDamageUpdatesEnabled{ true }; - bool mPersistentUpdatesEnabled{ true }; + bool mDamageUpdatesEnabled{ false }; + bool mPersistentUpdatesEnabled{ false }; /** Consumes the bounded newest-frame queue until shutdown. */ void run(); /** Encodes and writes one RGB24 frame using chunked Kitty direct transmission. */ diff --git a/src/eepp/window/kittyframepresenter.cpp b/src/eepp/window/kittyframepresenter.cpp index 1c09e9eef..263250e84 100644 --- a/src/eepp/window/kittyframepresenter.cpp +++ b/src/eepp/window/kittyframepresenter.cpp @@ -36,6 +36,14 @@ bool environmentFlag( const char* name, bool defaultValue ) { return defaultValue; } +bool supportsPersistentRootImageUpdates() { + const char* term = std::getenv( "TERM" ); + // Root-image animation-frame edits (Kitty a=f,r=1) are not implemented consistently by + // terminal emulators. eterm supports the exact update path used by this presenter; use the + // universally reliable anonymous full-frame path elsewhere unless explicitly overridden. + return term && String::iequals( term, "eterm" ); +} + int environmentCompressionLevel() { const char* value = std::getenv( "EEPP_TERMINAL_ZLIB_LEVEL" ); if ( !value || !value[0] ) @@ -128,9 +136,12 @@ KittyFramePresenter::~KittyFramePresenter() { bool KittyFramePresenter::initialize( Window& window ) { if ( !TerminalRuntime::instance().initialize() ) return false; - mPersistentUpdatesEnabled = environmentFlag( "EEPP_TERMINAL_PERSISTENT_UPDATES", true ); + const bool persistentUpdatesDefault = supportsPersistentRootImageUpdates(); + mPersistentUpdatesEnabled = + environmentFlag( "EEPP_TERMINAL_PERSISTENT_UPDATES", persistentUpdatesDefault ); mDamageUpdatesEnabled = - mPersistentUpdatesEnabled && environmentFlag( "EEPP_TERMINAL_DAMAGE_UPDATES", true ); + mPersistentUpdatesEnabled && + environmentFlag( "EEPP_TERMINAL_DAMAGE_UPDATES", persistentUpdatesDefault ); mZlibCompressionLevel = environmentCompressionLevel(); TerminalRuntime::instance().attach( window ); mRunning = true; diff --git a/src/thirdparty/chipmunk/cpSpace.c b/src/thirdparty/chipmunk/cpSpace.c index 149a0d0ed..11d557aac 100644 --- a/src/thirdparty/chipmunk/cpSpace.c +++ b/src/thirdparty/chipmunk/cpSpace.c @@ -1,15 +1,15 @@ /* Copyright (c) 2007 Scott Lembcke - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -18,7 +18,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + #include #include @@ -32,7 +32,7 @@ arbiterSetEql(cpShape **shapes, cpArbiter *arb) { cpShape *a = shapes[0]; cpShape *b = shapes[1]; - + return ((a == arb->a && b == arb->b) || (b == arb->a && a == arb->b)); } @@ -51,7 +51,7 @@ handlerSetTrans(cpCollisionHandler *handler, void *unused) { cpCollisionHandler *copy = (cpCollisionHandler *)cpcalloc(1, sizeof(cpCollisionHandler)); (*copy) = (*handler); - + return copy; } @@ -82,56 +82,56 @@ cpSpaceInit(cpSpace *space) #ifndef NDEBUG static cpBool done = cpFalse; if(!done){ - printf("Initializing cpSpace - Chipmunk v%s (Debug Enabled)\n", cpVersionString); - printf("Compile with -DNDEBUG defined to disable debug mode and runtime assertion checks\n"); + // printf("Initializing cpSpace - Chipmunk v%s (Debug Enabled)\n", cpVersionString); + // printf("Compile with -DNDEBUG defined to disable debug mode and runtime assertion checks\n"); done = cpTrue; } #endif space->iterations = 10; - + space->gravity = cpvzero; space->damping = 1.0f; - + space->collisionSlop = 0.1f; space->collisionBias = cpfpow(1.0f - 0.1f, 60.0f); space->collisionPersistence = 3; - + space->locked = 0; space->stamp = 0; space->staticShapes = cpBBTreeNew((cpSpatialIndexBBFunc)cpShapeGetBB, NULL); space->activeShapes = cpBBTreeNew((cpSpatialIndexBBFunc)cpShapeGetBB, space->staticShapes); cpBBTreeSetVelocityFunc(space->activeShapes, (cpBBTreeVelocityFunc)shapeVelocityFunc); - + space->allocatedBuffers = cpArrayNew(0); - + space->bodies = cpArrayNew(0); space->sleepingComponents = cpArrayNew(0); space->rousedBodies = cpArrayNew(0); - + space->sleepTimeThreshold = INFINITY; space->idleSpeedThreshold = 0.0f; space->enableContactGraph = cpFalse; - + space->arbiters = cpArrayNew(0); space->pooledArbiters = cpArrayNew(0); - + space->contactBuffersHead = NULL; space->cachedArbiters = cpHashSetNew(0, (cpHashSetEqlFunc)arbiterSetEql); - + space->constraints = cpArrayNew(0); - + space->defaultHandler = cpDefaultCollisionHandler; space->collisionHandlers = cpHashSetNew(0, (cpHashSetEqlFunc)handlerSetEql); cpHashSetSetDefaultValue(space->collisionHandlers, &cpDefaultCollisionHandler); - + space->postStepCallbacks = cpArrayNew(0); space->skipPostStep = cpFalse; - + cpBodyInitStatic(&space->_staticBody); space->staticBody = &space->_staticBody; - + return space; } @@ -150,31 +150,31 @@ void cpSpaceDestroy(cpSpace *space) { cpSpaceEachBody(space, (cpSpaceBodyIteratorFunc)cpBodyActivate2, NULL); - + cpSpatialIndexFree(space->staticShapes); cpSpatialIndexFree(space->activeShapes); - + cpArrayFree(space->bodies); cpArrayFree(space->sleepingComponents); cpArrayFree(space->rousedBodies); - + cpArrayFree(space->constraints); - + cpHashSetFree(space->cachedArbiters); - + cpArrayFree(space->arbiters); cpArrayFree(space->pooledArbiters); - + if(space->allocatedBuffers){ cpArrayFreeEach(space->allocatedBuffers, cpfree); cpArrayFree(space->allocatedBuffers); } - + if(space->postStepCallbacks){ cpArrayFreeEach(space->postStepCallbacks, cpfree); cpArrayFree(space->postStepCallbacks); } - + if(space->collisionHandlers) cpHashSetEach(space->collisionHandlers, freeWrap, NULL); cpHashSetFree(space->collisionHandlers); } @@ -207,10 +207,10 @@ cpSpaceAddCollisionHandler( void *data ){ cpAssertSpaceUnlocked(space); - + // Remove any old function so the new one will get added. cpSpaceRemoveCollisionHandler(space, a, b); - + cpCollisionHandler handler = { a, b, begin ? begin : alwaysCollide, @@ -219,7 +219,7 @@ cpSpaceAddCollisionHandler( separate ? separate : nothing, data }; - + cpHashSetInsert(space->collisionHandlers, CP_HASH_PAIR(a, b), &handler, NULL, (cpHashSetTransFunc)handlerSetTrans); } @@ -227,7 +227,7 @@ void cpSpaceRemoveCollisionHandler(cpSpace *space, cpCollisionType a, cpCollisionType b) { cpAssertSpaceUnlocked(space); - + struct { cpCollisionType a, b; } ids = {a, b}; cpCollisionHandler *old_handler = (cpCollisionHandler *) cpHashSetRemove(space->collisionHandlers, CP_HASH_PAIR(a, b), &ids); cpfree(old_handler); @@ -243,7 +243,7 @@ cpSpaceSetDefaultCollisionHandler( void *data ){ cpAssertSpaceUnlocked(space); - + cpCollisionHandler handler = { 0, 0, begin ? begin : alwaysCollide, @@ -252,7 +252,7 @@ cpSpaceSetDefaultCollisionHandler( separate ? separate : nothing, data }; - + space->defaultHandler = handler; cpHashSetSetDefaultValue(space->collisionHandlers, &space->defaultHandler); } @@ -263,18 +263,18 @@ cpSpaceAddShape(cpSpace *space, cpShape *shape) { cpBody *body = shape->body; if(cpBodyIsStatic(body)) return cpSpaceAddStaticShape(space, shape); - + cpAssertHard(shape->space != space, "You have already added this shape to this space. You must not add it a second time."); cpAssertHard(!shape->space, "You have already added this shape to another space. You cannot add it to a second."); cpAssertSpaceUnlocked(space); - + cpBodyActivate(body); cpBodyAddShape(body, shape); - + cpShapeUpdate(shape, body->p, body->rot); cpSpatialIndexInsert(space->activeShapes, shape, shape->hashid); shape->space = space; - + return shape; } @@ -285,13 +285,13 @@ cpSpaceAddStaticShape(cpSpace *space, cpShape *shape) cpAssertHard(!shape->space, "You have already added this shape to another space. You cannot add it to a second."); cpAssertHard(cpBodyIsRogue(shape->body), "You are adding a static shape to a dynamic body. Did you mean to attach it to a static or rogue body? See the documentation for more information."); cpAssertSpaceUnlocked(space); - + cpBody *body = shape->body; cpBodyAddShape(body, shape); cpShapeUpdate(shape, body->p, body->rot); cpSpatialIndexInsert(space->staticShapes, shape, shape->hashid); shape->space = space; - + return shape; } @@ -302,10 +302,10 @@ cpSpaceAddBody(cpSpace *space, cpBody *body) cpAssertHard(body->space != space, "You have already added this body to this space. You must not add it a second time."); cpAssertHard(!body->space, "You have already added this body to another space. You cannot add it to a second."); cpAssertSpaceUnlocked(space); - + cpArrayPush(space->bodies, body); body->space = space; - + return body; } @@ -316,17 +316,17 @@ cpSpaceAddConstraint(cpSpace *space, cpConstraint *constraint) cpAssertHard(!constraint->space, "You have already added this constraint to another space. You cannot add it to a second."); cpAssertHard(constraint->a && constraint->b, "Constraint is attached to a NULL body."); cpAssertSpaceUnlocked(space); - + cpBodyActivate(constraint->a); cpBodyActivate(constraint->b); cpArrayPush(space->constraints, constraint); - + // Push onto the heads of the bodies' constraint lists cpBody *a = constraint->a, *b = constraint->b; constraint->next_a = a->constraintList; a->constraintList = constraint; constraint->next_b = b->constraintList; b->constraintList = constraint; constraint->space = space; - + return constraint; } @@ -341,8 +341,8 @@ cachedArbitersFilter(cpArbiter *arb, struct arbiterFilterContext *context) { cpShape *shape = context->shape; cpBody *body = context->body; - - + + // Match on the filter shape, or if it's NULL the filter body if( (body == arb->body_a && (shape == arb->a || shape == NULL)) || @@ -350,14 +350,14 @@ cachedArbitersFilter(cpArbiter *arb, struct arbiterFilterContext *context) ){ // Call separate when removing shapes. if(shape && arb->state != cpArbiterStateCached) cpArbiterCallSeparate(arb, context->space); - + cpArbiterUnthread(arb); cpArrayDeleteObj(context->space->arbiters, arb); cpArrayPush(context->space->pooledArbiters, arb); - + return cpFalse; } - + return cpTrue; } @@ -379,7 +379,7 @@ cpSpaceRemoveShape(cpSpace *space, cpShape *shape) } else { cpAssertHard(cpSpaceContainsShape(space, shape), "Cannot remove a shape that was not added to the space. (Removed twice maybe?)"); cpAssertSpaceUnlocked(space); - + cpBodyActivate(body); cpBodyRemoveShape(body, shape); cpSpaceFilterArbiters(space, body, shape); @@ -393,7 +393,7 @@ cpSpaceRemoveStaticShape(cpSpace *space, cpShape *shape) { cpAssertHard(cpSpaceContainsShape(space, shape), "Cannot remove a static or sleeping shape that was not added to the space. (Removed twice maybe?)"); cpAssertSpaceUnlocked(space); - + cpBody *body = shape->body; if(cpBodyIsStatic(body)) cpBodyActivateStatic(body, shape); cpBodyRemoveShape(body, shape); @@ -407,7 +407,7 @@ cpSpaceRemoveBody(cpSpace *space, cpBody *body) { cpAssertHard(cpSpaceContainsBody(space, body), "Cannot remove a body that was not added to the space. (Removed twice maybe?)"); cpAssertSpaceUnlocked(space); - + cpBodyActivate(body); // cpSpaceFilterArbiters(space, body, NULL); cpArrayDeleteObj(space->bodies, body); @@ -419,11 +419,11 @@ cpSpaceRemoveConstraint(cpSpace *space, cpConstraint *constraint) { cpAssertHard(cpSpaceContainsConstraint(space, constraint), "Cannot remove a constraint that was not added to the space. (Removed twice maybe?)"); cpAssertSpaceUnlocked(space); - + cpBodyActivate(constraint->a); cpBodyActivate(constraint->b); cpArrayDeleteObj(space->constraints, constraint); - + cpBodyRemoveConstraint(constraint->a, constraint); cpBodyRemoveConstraint(constraint->b, constraint); constraint->space = NULL; @@ -452,13 +452,13 @@ cpSpaceConvertBodyToStatic(cpSpace *space, cpBody *body) cpAssertHard(!cpBodyIsStatic(body), "Body is already static."); cpAssertHard(cpBodyIsRogue(body), "Remove the body from the space before calling this function."); cpAssertSpaceUnlocked(space); - + cpBodySetMass(body, INFINITY); cpBodySetMoment(body, INFINITY); - + cpBodySetVel(body, cpvzero); cpBodySetAngVel(body, 0.0f); - + body->node.idleTime = INFINITY; CP_BODY_FOREACH_SHAPE(body, shape){ cpSpatialIndexRemove(space->activeShapes, shape, shape->hashid); @@ -471,12 +471,12 @@ cpSpaceConvertBodyToDynamic(cpSpace *space, cpBody *body, cpFloat m, cpFloat i) { cpAssertHard(cpBodyIsStatic(body), "Body is already dynamic."); cpAssertSpaceUnlocked(space); - + cpBodyActivateStatic(body, NULL); - + cpBodySetMass(body, m); cpBodySetMoment(body, i); - + body->node.idleTime = 0.0f; CP_BODY_FOREACH_SHAPE(body, shape){ cpSpatialIndexRemove(space->staticShapes, shape, shape->hashid); @@ -491,15 +491,15 @@ cpSpaceEachBody(cpSpace *space, cpSpaceBodyIteratorFunc func, void *data) { cpSpaceLock(space); { cpArray *bodies = space->bodies; - + for(int i=0; inum; i++){ func((cpBody *)bodies->arr[i], data); } - + cpArray *components = space->sleepingComponents; for(int i=0; inum; i++){ cpBody *root = (cpBody *)components->arr[i]; - + cpBody *body = root; while(body){ cpBody *next = body->node.next; @@ -536,7 +536,7 @@ cpSpaceEachConstraint(cpSpace *space, cpSpaceConstraintIteratorFunc func, void * { cpSpaceLock(space); { cpArray *constraints = space->constraints; - + for(int i=0; inum; i++){ func((cpConstraint *)constraints->arr[i], data); } @@ -552,11 +552,11 @@ updateBBCache(cpShape *shape, void *unused) cpShapeUpdate(shape, body->p, body->rot); } -void +void cpSpaceReindexStatic(cpSpace *space) { cpAssertHard(!space->locked, "You cannot manually reindex objects while the space is locked. Wait until the current query or step is complete."); - + cpSpatialIndexEach(space->staticShapes, (cpSpatialIndexIteratorFunc)&updateBBCache, NULL); cpSpatialIndexReindex(space->staticShapes); } @@ -565,10 +565,10 @@ void cpSpaceReindexShape(cpSpace *space, cpShape *shape) { cpAssertHard(!space->locked, "You cannot manually reindex objects while the space is locked. Wait until the current query or step is complete."); - + cpBody *body = shape->body; cpShapeUpdate(shape, body->p, body->rot); - + // attempt to rehash the shape in both hashes cpSpatialIndexReindexObject(space->activeShapes, shape, shape->hashid); cpSpatialIndexReindexObject(space->staticShapes, shape, shape->hashid); @@ -592,13 +592,13 @@ cpSpaceUseSpatialHash(cpSpace *space, cpFloat dim, int count) { cpSpatialIndex *staticShapes = cpSpaceHashNew(dim, count, (cpSpatialIndexBBFunc)cpShapeGetBB, NULL); cpSpatialIndex *activeShapes = cpSpaceHashNew(dim, count, (cpSpatialIndexBBFunc)cpShapeGetBB, staticShapes); - + cpSpatialIndexEach(space->staticShapes, (cpSpatialIndexIteratorFunc)copyShapes, staticShapes); cpSpatialIndexEach(space->activeShapes, (cpSpatialIndexIteratorFunc)copyShapes, activeShapes); - + cpSpatialIndexFree(space->staticShapes); cpSpatialIndexFree(space->activeShapes); - + space->staticShapes = staticShapes; space->activeShapes = activeShapes; }