Kitty Graphics Protocol Discussion #5683
anthonykim1
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Kitty Graphics Protocol — Feature Inventory
Spec: https://sw.kovidgoyal.net/kitty/graphics-protocol/
Coming from: #5619
Key files:
KittyGraphicsHandler.tsKittyGraphicsTypes.tsKittyImageStorage.tsImageStorage.tsImageRenderer.tsflowchart TD A["Shell sends Kitty escape sequence<br/>ESC _ G a=T,f=32,s=2,v=2,i=1;<br/>base64... ESC \\"] A --> B B["ApcParser matches 'G'<br/>→ KittyGraphicsHandler<br/>registered in ImageAddon.ts"] B --> C C["start — reset state"] C --> D D["put — two-phase parsing<br/>1. Scan for semicolon,<br/>buffer keys<br/>2. parseKittyCommand<br/>extracts keys<br/>3. Stream payload into<br/>Base64Decoder"] D --> E E["end — finalize"] E --> F{"More chunks?<br/>m=1"} F -->|"Yes"| G["Park decoder in<br/>_pendingTransmissions<br/>keeps decoder alive<br/>across sequences"] G -.->|"Next sequence"| C F -->|"No"| H{"Action?"} H -->|"a=t transmit"| I["_handleTransmit<br/>KittyImageStorage.storeImage<br/>Blob copies bytes off JS heap"] H -->|"a=T transmit+display"| J["_handleTransmitDisplay<br/>storeImage as Blob<br/>then render"] H -->|"a=q query"| K["_handleQuery<br/>validate format/size<br/>respond OK or EINVAL"] H -->|"a=d delete"| L J --> M["_decodeAndDisplay"] M --> M1{"Image format?"} M1 -->|"f=100 PNG"| M1a["Wrap as image/png Blob<br/>browser decodes natively"] M1 -->|"f=32 RGBA"| M1b["Raw bytes → ImageData<br/>already has alpha"] M1 -->|"f=24 RGB"| M1c["uint32 block interleave<br/>RGB → RGBA<br/>adds alpha=0xFF000000"] M1a --> BMP M1b --> BMP M1c --> BMP BMP["createImageBitmap<br/>GPU-backed render-ready<br/>object for efficient<br/>canvas drawing"] BMP --> M2["Resize via<br/>createImageBitmap<br/>if c/r columns/rows<br/>specified"] M2 --> M3["KittyImageStorage.addImage<br/>→ ImageStorage.addImage<br/>Tiles bitmap into cells,<br/>marks each with<br/>image+tile ID.<br/>Adds scroll marker<br/>for cleanup."] M3 --> M4["KittyImageStorage<br/>bridges Kitty ID<br/>→ internal storage ID<br/>via _kittyIdToStorageId<br/>+ _storageIdToKittyId"] M4 --> M5{"Cursor<br/>movement<br/>C key"} M5 -->|"C=0 default"| M6["Advance cursor<br/>past image"] M5 -->|"C=1"| M7["Restore cursor<br/>with scroll correction"] subgraph LAYER["Layer Selection"] Z1{"z key?"} Z1 -->|"z < 0"| Z2["bottom layer<br/>(behind text)"] Z1 -->|"z >= 0 or absent"| Z3["top layer<br/>(on top of text)"] end M2 --> Z1 Z2 --> M3 Z3 --> M3 subgraph DEL["_handleDelete — selector dispatch"] L1{"d key selector?"} L1 -->|"d=a / d=A"| L2["Abort ALL in-flight decoders<br/>KittyImageStorage.deleteAll()"] L1 -->|"d=i / d=I"| L3["Abort targeted decoder<br/>KittyImageStorage.deleteById()"] L1 -->|"default (no d key)"| L2 L1 -->|"other (c,n,p,…)"| L4["Ignored (not yet supported)"] L2 --> L5["Delete Blob from _images<br/>Delete bitmap from ImageStorage<br/>Clear kittyId↔storageId maps"] L3 --> L5 end L --> L1 subgraph EVICT["Eviction & Cleanup (KittyImageStorage)"] EV1["onImageDeleted callback<br/>fires when ImageStorage<br/>evicts/deletes an image"] EV1 --> EV2["_handleStorageImageDeleted<br/>cleans _kittyIdToStorageId<br/>_storageIdToKittyId<br/>_images"] EV3["storeImage with same ID<br/>deletes old placement<br/>from ImageStorage first"] EV4["_evictUndisplayedImages<br/>when _images exceeds 256<br/>preferentially removes<br/>images without placements"] end I --> RESP M5 --> RESP K --> RESP RESP["_sendResponse<br/>OK or EINVAL to client<br/>q=1 suppresses OK<br/>q=2 suppresses errors"] RESP --> DONE["Client receives response"]Legend: ✅ Done |⚠️ Partial | ❌ TODO | 🚫 Blocked (renderer/architecture limitation)
1. Escape Code Format (APC)
ESC_G<control>;<payload>ESC\registerApcHandler(0x47, ...)inImageAddon.tskey=valuecontrol data parsingparseKittyCommand()inKittyGraphicsTypes.tsBase64DecoderinKittyGraphicsHandler.ts2. Actions (
akey)a=tKittyImageStorage.storeImage(). SendsGi=<id>;OKwheniprovideda=Ta=qa=pa=da=ddispatches to_handleDelete.dkey (delete selector) is fully parsed —d=a/d=Adeletes all,d=i/d=Ideletes by image ID. Default selector is'a'per spec. Cascades to ImageStorage viaKittyImageStorage. 7 unit tests ford/pkey parsing + 14 integration tests for delete dispatcha=fa=aa=c3. Image Formats (
fkey)f=32KittyFormat.RGBAper specf=24f=100Blob(type:'image/png')→createImageBitmap4. Transmission Medium (
tkey)t=dtkey parsed astransmission. Explicitly checks and acceptst=d. 13 integration tests for medium rejectiont=ft=tt=s5. Compression (
okey)o=zDecompressionStream('deflate')with fallback to'deflate-raw'. Applied after base64 decode, before pixel interpretationSkey validation for PNG + compressionSkey." Functionally works without it but conformance gap6. Chunked Transmission (
mkey)m=1/m=0)_pendingTransmissionsmap accumulates chunks via shared decoderIPendingTransmission.totalEncodedSizem+qpending.cmdstores first chunk's command. Subsequent chunks' extra keys are silently ignored._lastPendingKeytracks most recent pending upload for chunks withouti=a=f7. Image Display Layout
KittyImageStorage.addImage()→ImageStorage.addImage()c,rcreateImageBitmap; stretches to fill placement rectangle per specx,y,w,hx,yparsed but unused;w,hnot parsed. NeedscreateImageBitmapcrop pathX,YCCkey parsed. Default (C=0): cursor at (originX + cols, last row) per spec. C=1 restores cursor with scroll correction. 10 integration testszzkey parsed. Negative z → bottom layer (behind text), z≥0 → top layer. Within same layer, z-index stored but render order is insertion order (no per-image z-sort within a canvas). 9 integration tests8. Image IDs & Placement IDs
iKittyImageStorage._imagesmap keyinot specified_nextImageId++inKittyImageStorageIi+Iconflict validated. ButGi=<id>,I=<num>;OKresponse not sent, andI-only commands don't do image number lookupi+Iconflict → EINVALput()and_handleNoPayloadCommand()pparseKittyCommand()asplacementId. Not yet used for multiple placements of same imageKittyImageStoragemanages bidirectional_kittyIdToStorageId/_storageIdToKittyIdmaps.onImageDeletedcallback keeps maps in sync when ImageStorage evicts images. Used by delete, re-transmit, and eviction flowsKittyImageStorage.storeImage()checks for existing_kittyIdToStorageIdentry and calls_storage.deleteImage()on old placement before storing new data. 2 integration tests (re-transmit with a=T, re-transmit with a=t then a=T)9. Delete Commands (
a=d,dkey)a/Adkey parsed viadeleteSelector. Default selector is'a'per spec.KittyImageStorage.deleteAll()clears_images, iterates_kittyIdToStorageIdto delete from ImageStorage, clears maps. Also aborts all in-flight uploads. 4 integration tests (d=a,d=A, storage cleanup, pixel cleanup)i/Id=i/d=Idispatched via switch/case.KittyImageStorage.deleteById()removes from_images, cascades to ImageStorage. Aborts only the targeted in-flight upload. 6 integration tests (d=i,d=I, storage cleanup, pixel cleanup, no-id edge case, targeted abort)i/I + ppkey parsed but placement IDs not fully implementedn/Nc/Cp/Pq/Qr/Rx/Xy/Yz/Zf/FKittyGraphicsHandler.tsand KittyGraphics.test.ts_handleDeletereleases decoders and clears_pendingTransmissionsbefore deleting images.d=a/d=Aaborts all uploads;d=i/d=Iaborts only the targeted upload. 4 integration tests10. Response / Acknowledgement System
ESC_Gi=<id>;OK ESC\)a=qa=tsends OK synchronously after storing blob.a=Tsends OK after async display via.then(). Only responds whencmd.idprovided. Respectsq=1/q=2i+Iconflict, missing dimensions, unsupported transmission medium, unsupported actionq=1(suppress OK)q=2(suppress errors)a=pa=pGi=<id>,p=<pid>;OK)Gi=<id>,I=<num>;OK)11. Unicode Placeholders
U+10EEEEplaceholder characterU=1)12. Relative Placements
P,QH,V13. Animation
a=fcx,y,s,vzY(RGBA)X=1a=a, s, va=a,c=<frame>a=cz=<neg>14. Terminal Interaction
reset()clearsKittyImageStoragemaps and_pendingTransmissions. Wired via DECSTR, RIS,onRequestResetESC[2JonImageDeletedcallback →KittyImageStorage._handleStorageImageDeletedcascades cleanup to_images,_kittyIdToStorageId,_storageIdToKittyId. No stale entrieswipeAlternate()calls_delImg()which firesonImageDeleted→KittyImageStorage._handleStorageImageDeletedcleans kitty maps. Fully cascadedESC[14t/ESC[16t)15. Memory & Storage
KittyImageStorageenforces_maxStoredImages = 256cap.onImageDeletedcallback keeps kitty maps in sync when ImageStorage evicts. 2 integration tests (memory limit eviction, scrollback eviction)IKittyImageData.dataisBlobkittySizeLimitoption enforced during streaming decodeKittyImageStorage._evictUndisplayedImages()preferentially removes images that have no_kittyIdToStorageIdentry (i.e., not displayed)KittyImageStorage._imagesAND renderedImageBitmaplives inImageStorage. Two separate stores but now coordinated viaonImageDeletedcallback. Still two copies of data — post-MVP to unify16. Control Data Keys — Parse Status
Click to expand full key table
at,T,q,dhandled.p,f,a,cnot handledfttransmission.t=daccepted;t=f,t=t,t=srejected with EINVAL. 13 integration testssvSoz(zlib/deflate)miIpplacementIdinparseKittyCommand(). Not yet used for multiple placementsqxywhXYcrCzzIndex. Negative → bottom layer, ≥0 → top layer. Stored in ImageSpec. 9 integration testsddeleteSelector. Dispatchesa/A(all),i/I(by ID). Default'a'per specPQHVUMVP Checklist (Feb 9 – 20)
Items from the chart above that need work before initial merge. Ordered by priority.
P0 — Clients break without these
Ckey parsed. Default (C=0): cursor at (originX + cols, lastRow). C=1: cursor restored. Fixed proportional resize to spec-correct stretch. 10 integration tests (5 unskipped + 5 updated). 2 unit tests for C key parsing.P1 — Important for correctness
KittyGraphicsHandlerandIIPHandlercreate independent wasmBase64Decoderinstances. Avoid duplicate wasm memory. (PR feedback)Done
KittyImageStoragemanages bidirectional_kittyIdToStorageId/_storageIdToKittyId.onImageDeletedcallback keeps maps in sync when ImageStorage evicts. Delete cascades to ImageStorage.KittyImageStorage.storeImage()checks for existing_kittyIdToStorageIdentry and calls_storage.deleteImage()on old placement before storing new data. 2 integration tests.dkey + delete dispatch —DELETE_SELECTORandPLACEMENT_IDadded toKittyKey.deleteSelectorparsed as string,placementIdparsed as number._handleDeletedispatches via switch/case:d=a/d=A→KittyImageStorage.deleteAll(),d=i/d=I→KittyImageStorage.deleteById(). Default selector is'a'per spec.d=aaborts all in-flight uploads;d=iaborts only targeted upload. 7 unit tests ford/pkey parsing. 14 integration tests for delete dispatch including pixel-level verification. TODOs for lowercase/uppercase distinction.KittyImageStorage.deleteById()calls_storage.deleteImage()for displayed images.deleteImage()public API on ImageStorage — Thin wrapper around_delImg+ marker dispose.a=t/a=T—a=tsends OK in dispatcher after_handleTransmit.a=Tsends OK after async display via.then(). Only whencmd.idprovided. Respectsq=1/q=2._handleTransmitnow defaults toKittyFormat.RGBA(f=32) per spec instead of PNG._handleDeletereleases decoders and clears_pendingTransmissions.d=a/d=Aaborts all;d=i/d=Iaborts only targeted. 4 integration tests.Ckey parsed. Default (C=0): cursor at (originX + cols, lastRow). C=1: cursor restored. Fixed proportional resize to spec-correct stretch. 10 integration tests (5 unskipped + 5 updated). 2 unit tests for C key parsing.ESC[2J— ImageStorage's marker disposal firesonImageDeletedcallback →KittyImageStorage._handleStorageImageDeletedcascades cleanup to_images,_kittyIdToStorageId,_storageIdToKittyId. No more stale entries.wipeAlternate()calls_delImg()which firesonImageDeleted→KittyImageStorage._handleStorageImageDeletedcleans kitty maps. Fully cascaded.KittyImageStorageenforces_maxStoredImages = 256cap with_evictUndisplayedImages().onImageDeletedcallback keeps kitty maps in sync when ImageStorage evicts by pixel limit or scrollback. 2 integration tests (memory limit eviction, scrollback eviction).KittyImageStorage._evictUndisplayedImages()preferentially removes images that have no_kittyIdToStorageIdentry.zkey parsed. Negative z → bottom layer (behind text), z≥0 → top layer.ImageStorage.addImage()acceptslayerandzIndexparams. 9 integration tests including DOM order verification.tkey parsing + rejection —tkey parsed astransmissionviaKittyKey.TRANSMISSION.t=daccepted;t=f/t=t/t=srejected with EINVAL for both transmit and query actions. 13 integration tests.Beta Was this translation helpful? Give feedback.
All reactions