Go bindings for libghostty-vt.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3 from mitchellh/push-ynozqtxqtoqp

kitty_graphics: add batch info and render info bindings

authored by

Mitchell Hashimoto and committed by
GitHub
4611ac67 d7dbb854

+413 -1
+1 -1
CMakeLists.txt
··· 4 4 include(FetchContent) 5 5 FetchContent_Declare(ghostty 6 6 GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git 7 - GIT_TAG c34901dddbc36b63f33bbb1a47d62f6911584d65 7 + GIT_TAG 7421b4b13f87e101d4bbcedd4da84886ceae4e7b 8 8 ) 9 9 FetchContent_MakeAvailable(ghostty)
+226
kitty_graphics.go
··· 12 12 GhosttySelection s = GHOSTTY_INIT_SIZED(GhosttySelection); 13 13 return s; 14 14 } 15 + 16 + // Helper to create a properly initialized GhosttyKittyGraphicsImageInfo (sized struct). 17 + static inline GhosttyKittyGraphicsImageInfo init_kitty_image_info() { 18 + GhosttyKittyGraphicsImageInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsImageInfo); 19 + return info; 20 + } 21 + 22 + // Helper to create a properly initialized GhosttyKittyGraphicsPlacementInfo (sized struct). 23 + static inline GhosttyKittyGraphicsPlacementInfo init_kitty_placement_info() { 24 + GhosttyKittyGraphicsPlacementInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementInfo); 25 + return info; 26 + } 27 + 28 + // Helper to create a properly initialized GhosttyKittyGraphicsPlacementRenderInfo (sized struct). 29 + static inline GhosttyKittyGraphicsPlacementRenderInfo init_kitty_placement_render_info() { 30 + GhosttyKittyGraphicsPlacementRenderInfo info = GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo); 31 + return info; 32 + } 15 33 */ 16 34 import "C" 17 35 ··· 127 145 } 128 146 } 129 147 148 + // KittyGraphicsImageInfo contains all image metadata in a single struct. 149 + // This is more efficient than querying each field individually since it 150 + // requires only one cgo call. 151 + // 152 + // C: GhosttyKittyGraphicsImageInfo 153 + type KittyGraphicsImageInfo struct { 154 + // ID is the image ID. 155 + ID uint32 156 + 157 + // Number is the image number. 158 + Number uint32 159 + 160 + // Width is the image width in pixels. 161 + Width uint32 162 + 163 + // Height is the image height in pixels. 164 + Height uint32 165 + 166 + // Format is the pixel format of the image. 167 + Format KittyImageFormat 168 + 169 + // Compression is the compression of the image. 170 + Compression KittyImageCompression 171 + 172 + // Data is a borrowed slice of the raw pixel data. Only valid 173 + // until the next mutating terminal call. 174 + Data []byte 175 + } 176 + 177 + // KittyGraphicsPlacementInfo contains all placement metadata in a single 178 + // struct. This is more efficient than querying each field individually 179 + // since it requires only one cgo call. 180 + // 181 + // C: GhosttyKittyGraphicsPlacementInfo 182 + type KittyGraphicsPlacementInfo struct { 183 + // ImageID is the image ID this placement belongs to. 184 + ImageID uint32 185 + 186 + // PlacementID is the placement ID. 187 + PlacementID uint32 188 + 189 + // IsVirtual indicates whether this is a virtual placement (unicode placeholder). 190 + IsVirtual bool 191 + 192 + // XOffset is the pixel offset from the left edge of the cell. 193 + XOffset uint32 194 + 195 + // YOffset is the pixel offset from the top edge of the cell. 196 + YOffset uint32 197 + 198 + // SourceX is the source rectangle x origin in pixels. 199 + SourceX uint32 200 + 201 + // SourceY is the source rectangle y origin in pixels. 202 + SourceY uint32 203 + 204 + // SourceWidth is the source rectangle width in pixels (0 = full image width). 205 + SourceWidth uint32 206 + 207 + // SourceHeight is the source rectangle height in pixels (0 = full image height). 208 + SourceHeight uint32 209 + 210 + // Columns is the number of columns this placement occupies. 211 + Columns uint32 212 + 213 + // Rows is the number of rows this placement occupies. 214 + Rows uint32 215 + 216 + // Z is the z-index for this placement. 217 + Z int32 218 + } 219 + 220 + // KittyGraphicsPlacementRenderInfo contains all rendering geometry for a 221 + // placement in a single struct. Combines pixel size, grid size, viewport 222 + // position, and source rectangle into one cgo call. 223 + // 224 + // C: GhosttyKittyGraphicsPlacementRenderInfo 225 + type KittyGraphicsPlacementRenderInfo struct { 226 + // PixelWidth is the rendered width in pixels. 227 + PixelWidth uint32 228 + 229 + // PixelHeight is the rendered height in pixels. 230 + PixelHeight uint32 231 + 232 + // GridCols is the number of grid columns the placement occupies. 233 + GridCols uint32 234 + 235 + // GridRows is the number of grid rows the placement occupies. 236 + GridRows uint32 237 + 238 + // ViewportCol is the viewport-relative column (may be negative 239 + // for partially visible placements). 240 + ViewportCol int32 241 + 242 + // ViewportRow is the viewport-relative row (may be negative 243 + // for partially visible placements). 244 + ViewportRow int32 245 + 246 + // ViewportVisible is false when the placement is fully off-screen 247 + // or is a virtual placement. When false, ViewportCol and ViewportRow 248 + // may contain meaningless values. 249 + ViewportVisible bool 250 + 251 + // SourceX is the resolved source rectangle x origin in pixels. 252 + SourceX uint32 253 + 254 + // SourceY is the resolved source rectangle y origin in pixels. 255 + SourceY uint32 256 + 257 + // SourceWidth is the resolved source rectangle width in pixels. 258 + SourceWidth uint32 259 + 260 + // SourceHeight is the resolved source rectangle height in pixels. 261 + SourceHeight uint32 262 + } 263 + 130 264 // PlacementIterator populates the given iterator with placement data 131 265 // from this storage. The iterator must have been created with 132 266 // NewKittyGraphicsPlacementIterator. ··· 224 358 return 0, err 225 359 } 226 360 return KittyImageCompression(v), nil 361 + } 362 + 363 + // Info returns all image metadata in a single call. This is more 364 + // efficient than calling ID, Number, Width, Height, Format, 365 + // Compression, and Data individually. 366 + func (img *KittyGraphicsImage) Info() (*KittyGraphicsImageInfo, error) { 367 + ci := C.init_kitty_image_info() 368 + if err := resultError(C.ghostty_kitty_graphics_image_get( 369 + img.ptr, 370 + C.GHOSTTY_KITTY_IMAGE_DATA_INFO, 371 + unsafe.Pointer(&ci), 372 + )); err != nil { 373 + return nil, err 374 + } 375 + 376 + info := &KittyGraphicsImageInfo{ 377 + ID: uint32(ci.id), 378 + Number: uint32(ci.number), 379 + Width: uint32(ci.width), 380 + Height: uint32(ci.height), 381 + Format: KittyImageFormat(ci.format), 382 + Compression: KittyImageCompression(ci.compression), 383 + } 384 + 385 + if ci.data_ptr != nil && ci.data_len > 0 { 386 + info.Data = unsafe.Slice((*byte)(unsafe.Pointer(ci.data_ptr)), int(ci.data_len)) 387 + } 388 + 389 + return info, nil 227 390 } 228 391 229 392 // Data returns a borrowed slice of the raw pixel data. The slice is ··· 447 610 return 0, err 448 611 } 449 612 return int32(v), nil 613 + } 614 + 615 + // Info returns all placement metadata in a single call. This is more 616 + // efficient than calling ImageID, PlacementID, IsVirtual, XOffset, 617 + // YOffset, SourceX, SourceY, SourceWidth, SourceHeight, Columns, 618 + // Rows, and Z individually. 619 + func (it *KittyGraphicsPlacementIterator) Info() (*KittyGraphicsPlacementInfo, error) { 620 + ci := C.init_kitty_placement_info() 621 + if err := resultError(C.ghostty_kitty_graphics_placement_get( 622 + it.ptr, 623 + C.GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO, 624 + unsafe.Pointer(&ci), 625 + )); err != nil { 626 + return nil, err 627 + } 628 + 629 + return &KittyGraphicsPlacementInfo{ 630 + ImageID: uint32(ci.image_id), 631 + PlacementID: uint32(ci.placement_id), 632 + IsVirtual: bool(ci.is_virtual), 633 + XOffset: uint32(ci.x_offset), 634 + YOffset: uint32(ci.y_offset), 635 + SourceX: uint32(ci.source_x), 636 + SourceY: uint32(ci.source_y), 637 + SourceWidth: uint32(ci.source_width), 638 + SourceHeight: uint32(ci.source_height), 639 + Columns: uint32(ci.columns), 640 + Rows: uint32(ci.rows), 641 + Z: int32(ci.z), 642 + }, nil 643 + } 644 + 645 + // RenderInfo returns all rendering geometry for the current placement 646 + // in a single call. This combines the results of PixelSize, GridSize, 647 + // ViewportPos, and SourceRect into one cgo call. 648 + // 649 + // When ViewportVisible is false, the placement is fully off-screen or 650 + // is a virtual placement; ViewportCol and ViewportRow may contain 651 + // meaningless values in that case. 652 + func (it *KittyGraphicsPlacementIterator) RenderInfo(img *KittyGraphicsImage, t *Terminal) (*KittyGraphicsPlacementRenderInfo, error) { 653 + ci := C.init_kitty_placement_render_info() 654 + if err := resultError(C.ghostty_kitty_graphics_placement_render_info( 655 + it.ptr, 656 + img.ptr, 657 + t.ptr, 658 + &ci, 659 + )); err != nil { 660 + return nil, err 661 + } 662 + 663 + return &KittyGraphicsPlacementRenderInfo{ 664 + PixelWidth: uint32(ci.pixel_width), 665 + PixelHeight: uint32(ci.pixel_height), 666 + GridCols: uint32(ci.grid_cols), 667 + GridRows: uint32(ci.grid_rows), 668 + ViewportCol: int32(ci.viewport_col), 669 + ViewportRow: int32(ci.viewport_row), 670 + ViewportVisible: bool(ci.viewport_visible), 671 + SourceX: uint32(ci.source_x), 672 + SourceY: uint32(ci.source_y), 673 + SourceWidth: uint32(ci.source_width), 674 + SourceHeight: uint32(ci.source_height), 675 + }, nil 450 676 } 451 677 452 678 // Rect computes the grid rectangle occupied by the current placement.
+186
kitty_graphics_test.go
··· 321 321 } 322 322 } 323 323 324 + func TestKittyGraphicsImageInfo(t *testing.T) { 325 + term := newKittyTerminal(t) 326 + defer term.Close() 327 + 328 + sendKittyImage(t, term) 329 + 330 + kg, err := term.KittyGraphics() 331 + if err != nil { 332 + t.Fatal(err) 333 + } 334 + 335 + iter, err := NewKittyGraphicsPlacementIterator() 336 + if err != nil { 337 + t.Fatal(err) 338 + } 339 + defer iter.Close() 340 + 341 + if err := kg.PlacementIterator(iter); err != nil { 342 + t.Fatal(err) 343 + } 344 + 345 + if !iter.Next() { 346 + t.Fatal("expected at least one placement") 347 + } 348 + 349 + imageID, err := iter.ImageID() 350 + if err != nil { 351 + t.Fatal(err) 352 + } 353 + 354 + img := kg.Image(imageID) 355 + if img == nil { 356 + t.Fatal("expected non-nil image") 357 + } 358 + 359 + info, err := img.Info() 360 + if err != nil { 361 + t.Fatal(err) 362 + } 363 + 364 + if info.ID != imageID { 365 + t.Fatalf("expected image ID %d, got %d", imageID, info.ID) 366 + } 367 + if info.Width != 1 || info.Height != 1 { 368 + t.Fatalf("expected 1x1 image, got %dx%d", info.Width, info.Height) 369 + } 370 + if info.Format != KittyImageFormatRGBA { 371 + t.Fatalf("expected RGBA format, got %d", info.Format) 372 + } 373 + if info.Compression != KittyImageCompressionNone { 374 + t.Fatalf("expected no compression, got %d", info.Compression) 375 + } 376 + if len(info.Data) != 4 { 377 + t.Fatalf("expected 4 bytes of pixel data, got %d", len(info.Data)) 378 + } 379 + } 380 + 381 + func TestKittyGraphicsPlacementInfo(t *testing.T) { 382 + term := newKittyTerminal(t) 383 + defer term.Close() 384 + 385 + sendKittyImage(t, term) 386 + 387 + kg, err := term.KittyGraphics() 388 + if err != nil { 389 + t.Fatal(err) 390 + } 391 + 392 + iter, err := NewKittyGraphicsPlacementIterator() 393 + if err != nil { 394 + t.Fatal(err) 395 + } 396 + defer iter.Close() 397 + 398 + if err := kg.PlacementIterator(iter); err != nil { 399 + t.Fatal(err) 400 + } 401 + 402 + if !iter.Next() { 403 + t.Fatal("expected at least one placement") 404 + } 405 + 406 + info, err := iter.Info() 407 + if err != nil { 408 + t.Fatal(err) 409 + } 410 + 411 + // Verify the info matches individual getters. 412 + imageID, err := iter.ImageID() 413 + if err != nil { 414 + t.Fatal(err) 415 + } 416 + if info.ImageID != imageID { 417 + t.Fatalf("expected image ID %d, got %d", imageID, info.ImageID) 418 + } 419 + 420 + isVirtual, err := iter.IsVirtual() 421 + if err != nil { 422 + t.Fatal(err) 423 + } 424 + if info.IsVirtual != isVirtual { 425 + t.Fatalf("expected IsVirtual=%v, got %v", isVirtual, info.IsVirtual) 426 + } 427 + 428 + z, err := iter.Z() 429 + if err != nil { 430 + t.Fatal(err) 431 + } 432 + if info.Z != z { 433 + t.Fatalf("expected Z=%d, got %d", z, info.Z) 434 + } 435 + } 436 + 437 + func TestKittyGraphicsPlacementRenderInfo(t *testing.T) { 438 + term := newKittyTerminal(t) 439 + defer term.Close() 440 + 441 + sendKittyImage(t, term) 442 + 443 + kg, err := term.KittyGraphics() 444 + if err != nil { 445 + t.Fatal(err) 446 + } 447 + 448 + iter, err := NewKittyGraphicsPlacementIterator() 449 + if err != nil { 450 + t.Fatal(err) 451 + } 452 + defer iter.Close() 453 + 454 + if err := kg.PlacementIterator(iter); err != nil { 455 + t.Fatal(err) 456 + } 457 + 458 + if !iter.Next() { 459 + t.Fatal("expected at least one placement") 460 + } 461 + 462 + imageID, err := iter.ImageID() 463 + if err != nil { 464 + t.Fatal(err) 465 + } 466 + img := kg.Image(imageID) 467 + if img == nil { 468 + t.Fatal("expected image lookup to succeed") 469 + } 470 + 471 + ri, err := iter.RenderInfo(img, term) 472 + if err != nil { 473 + t.Fatal(err) 474 + } 475 + 476 + // Verify render info matches individual calls. 477 + pw, ph, err := iter.PixelSize(img, term) 478 + if err != nil { 479 + t.Fatal(err) 480 + } 481 + if ri.PixelWidth != pw || ri.PixelHeight != ph { 482 + t.Fatalf("pixel size mismatch: RenderInfo=%dx%d, PixelSize=%dx%d", 483 + ri.PixelWidth, ri.PixelHeight, pw, ph) 484 + } 485 + 486 + gc, gr, err := iter.GridSize(img, term) 487 + if err != nil { 488 + t.Fatal(err) 489 + } 490 + if ri.GridCols != gc || ri.GridRows != gr { 491 + t.Fatalf("grid size mismatch: RenderInfo=%dx%d, GridSize=%dx%d", 492 + ri.GridCols, ri.GridRows, gc, gr) 493 + } 494 + 495 + _, _, sw, sh, err := iter.SourceRect(img) 496 + if err != nil { 497 + t.Fatal(err) 498 + } 499 + if ri.SourceWidth != sw || ri.SourceHeight != sh { 500 + t.Fatalf("source rect size mismatch: RenderInfo=%dx%d, SourceRect=%dx%d", 501 + ri.SourceWidth, ri.SourceHeight, sw, sh) 502 + } 503 + 504 + // A freshly placed image should be viewport-visible. 505 + if !ri.ViewportVisible { 506 + t.Fatal("expected placement to be viewport-visible") 507 + } 508 + } 509 + 324 510 func TestKittyGraphicsPlacementLayerFilter(t *testing.T) { 325 511 term := newKittyTerminal(t) 326 512 defer term.Close()