st-kitty-graphics-20251230-0.9.3.diff (262745B)
1 From 16663a03b6d7522d171b9717151040f9fad67f9f Mon Sep 17 00:00:00 2001 2 From: Sergei Grechanik <sergei.grechanik@gmail.com> 3 Date: Sat, 10 Jan 2026 16:09:05 -0800 4 Subject: [PATCH] Kitty graphics protocol support eb8d6cb 2025-12-30 5 6 This patch implements the kitty graphics protocol in st. 7 See https://github.com/sergei-grechanik/st-graphics 8 Created by squashing the graphics branch, the most recent 9 commit is eb8d6cbc964a864e6391c4288aebe4ebe74bb5c7 (2025-12-30). 10 11 Squashed on top of 6e970474743d57a5d8b054c41fd3bff2bc895742 (0.9.3) 12 13 Note that the following files were excluded from the squash: 14 .clang-format 15 README.md 16 generate-rowcolumn-helpers.py 17 rowcolumn-diacritics.txt 18 rowcolumn_diacritics.sh 19 20 Main changes since 2025-02-22: 21 * Restore evicted images from original files when available. 22 * Fix an issue that caused too many files to be opened during 23 concurrent direct uploads. 24 * Optimize scaled pixmap generation (don't store pad pixels; upscale 25 with XRender). 26 27 Main changes since 2024-09-22: 28 * Restore text under classic placements on deletion. 29 * Support uploading via shared memory (t=s). 30 * Bug fixes. 31 --- 32 Makefile | 7 +- 33 config.def.h | 46 +- 34 config.mk | 5 +- 35 graphics.c | 4393 ++++++++++++++++++++++++++++++++ 36 graphics.h | 112 + 37 icat-mini.sh | 875 +++++++ 38 khash.h | 627 +++++ 39 kvec.h | 90 + 40 rowcolumn_diacritics_helpers.c | 391 +++ 41 st.c | 303 ++- 42 st.h | 84 +- 43 st.info | 6 + 44 win.h | 3 + 45 x.c | 411 ++- 46 14 files changed, 7295 insertions(+), 58 deletions(-) 47 create mode 100644 graphics.c 48 create mode 100644 graphics.h 49 create mode 100755 icat-mini.sh 50 create mode 100644 khash.h 51 create mode 100644 kvec.h 52 create mode 100644 rowcolumn_diacritics_helpers.c 53 54 diff --git a/Makefile b/Makefile 55 index 15db421..413e7ab 100644 56 --- a/Makefile 57 +++ b/Makefile 58 @@ -4,7 +4,7 @@ 59 60 include config.mk 61 62 -SRC = st.c x.c 63 +SRC = st.c x.c rowcolumn_diacritics_helpers.c graphics.c 64 OBJ = $(SRC:.c=.o) 65 66 all: st 67 @@ -15,8 +15,9 @@ config.h: 68 .c.o: 69 $(CC) $(STCFLAGS) -c $< 70 71 -st.o: config.h st.h win.h 72 -x.o: arg.h config.h st.h win.h 73 +st.o: config.h st.h win.h graphics.h 74 +x.o: arg.h config.h st.h win.h graphics.h 75 +graphics.c: graphics.h khash.h kvec.h st.h 76 77 $(OBJ): config.h config.mk 78 79 diff --git a/config.def.h b/config.def.h 80 index 2cd740a..4aadbbc 100644 81 --- a/config.def.h 82 +++ b/config.def.h 83 @@ -8,6 +8,13 @@ 84 static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; 85 static int borderpx = 2; 86 87 +/* How to align the content in the window when the size of the terminal 88 + * doesn't perfectly match the size of the window. The values are percentages. 89 + * 50 means center, 0 means flush left/top, 100 means flush right/bottom. 90 + */ 91 +static int anysize_halign = 50; 92 +static int anysize_valign = 50; 93 + 94 /* 95 * What program is execed by st depends of these precedence rules: 96 * 1: program passed with -e 97 @@ -23,7 +30,8 @@ char *scroll = NULL; 98 char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 99 100 /* identification sequence returned in DA and DECID */ 101 -char *vtiden = "\033[?6c"; 102 +/* By default, use the same one as kitty. */ 103 +char *vtiden = "\033[?62c"; 104 105 /* Kerning / character bounding-box multipliers */ 106 static float cwscale = 1.0; 107 @@ -163,6 +171,28 @@ static unsigned int mousebg = 0; 108 */ 109 static unsigned int defaultattr = 11; 110 111 +/* 112 + * Graphics configuration 113 + */ 114 + 115 +/// The template for the cache directory. 116 +const char graphics_cache_dir_template[] = "/tmp/st-images-XXXXXX"; 117 +/// The max size of a single image file, in bytes. 118 +unsigned graphics_max_single_image_file_size = 20 * 1024 * 1024; 119 +/// The max size of the cache, in bytes. 120 +unsigned graphics_total_file_cache_size = 300 * 1024 * 1024; 121 +/// The max ram size of an image or placement, in bytes. 122 +unsigned graphics_max_single_image_ram_size = 100 * 1024 * 1024; 123 +/// The max total size of all images loaded into RAM. 124 +unsigned graphics_max_total_ram_size = 300 * 1024 * 1024; 125 +/// The max total number of image placements and images. 126 +unsigned graphics_max_total_placements = 4096; 127 +/// The ratio by which limits can be exceeded. This is to reduce the frequency 128 +/// of image removal. 129 +double graphics_excess_tolerance_ratio = 0.05; 130 +/// The minimum delay between redraws caused by animations, in milliseconds. 131 +unsigned graphics_animation_min_delay = 20; 132 + 133 /* 134 * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 135 * Note that if you want to use ShiftMask with selmasks, set this to an other 136 @@ -170,12 +200,18 @@ static unsigned int defaultattr = 11; 137 */ 138 static uint forcemousemod = ShiftMask; 139 140 +/* Internal keyboard shortcuts. */ 141 +#define MODKEY Mod1Mask 142 +#define TERMMOD (ControlMask|ShiftMask) 143 + 144 /* 145 * Internal mouse shortcuts. 146 * Beware that overloading Button1 will disable the selection. 147 */ 148 static MouseShortcut mshortcuts[] = { 149 /* mask button function argument release */ 150 + { TERMMOD, Button3, previewimage, {.s = "feh"} }, 151 + { TERMMOD, Button2, showimageinfo, {}, 1 }, 152 { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 153 { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, 154 { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 155 @@ -183,10 +219,6 @@ static MouseShortcut mshortcuts[] = { 156 { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 157 }; 158 159 -/* Internal keyboard shortcuts. */ 160 -#define MODKEY Mod1Mask 161 -#define TERMMOD (ControlMask|ShiftMask) 162 - 163 static Shortcut shortcuts[] = { 164 /* mask keysym function argument */ 165 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 166 @@ -201,6 +233,10 @@ static Shortcut shortcuts[] = { 167 { TERMMOD, XK_Y, selpaste, {.i = 0} }, 168 { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 169 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 170 + { TERMMOD, XK_F1, togglegrdebug, {.i = 0} }, 171 + { TERMMOD, XK_F6, dumpgrstate, {.i = 0} }, 172 + { TERMMOD, XK_F7, unloadimages, {.i = 0} }, 173 + { TERMMOD, XK_F8, toggleimages, {.i = 0} }, 174 }; 175 176 /* 177 diff --git a/config.mk b/config.mk 178 index 2fc854e..7b22243 100644 179 --- a/config.mk 180 +++ b/config.mk 181 @@ -14,9 +14,12 @@ PKG_CONFIG = pkg-config 182 183 # includes and libs 184 INCS = -I$(X11INC) \ 185 + `$(PKG_CONFIG) --cflags imlib2` \ 186 `$(PKG_CONFIG) --cflags fontconfig` \ 187 `$(PKG_CONFIG) --cflags freetype2` 188 -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ 189 +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ 190 + `$(PKG_CONFIG) --libs imlib2` \ 191 + `$(PKG_CONFIG) --libs zlib` \ 192 `$(PKG_CONFIG) --libs fontconfig` \ 193 `$(PKG_CONFIG) --libs freetype2` 194 195 diff --git a/graphics.c b/graphics.c 196 new file mode 100644 197 index 0000000..801f5c9 198 --- /dev/null 199 +++ b/graphics.c 200 @@ -0,0 +1,4393 @@ 201 +/* The MIT License 202 + 203 + Copyright (c) 2021-2024 Sergei Grechanik <sergei.grechanik@gmail.com> 204 + 205 + Permission is hereby granted, free of charge, to any person obtaining 206 + a copy of this software and associated documentation files (the 207 + "Software"), to deal in the Software without restriction, including 208 + without limitation the rights to use, copy, modify, merge, publish, 209 + distribute, sublicense, and/or sell copies of the Software, and to 210 + permit persons to whom the Software is furnished to do so, subject to 211 + the following conditions: 212 + 213 + The above copyright notice and this permission notice shall be 214 + included in all copies or substantial portions of the Software. 215 + 216 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 217 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 218 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 219 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 220 + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 221 + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 222 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 223 + SOFTWARE. 224 +*/ 225 + 226 +//////////////////////////////////////////////////////////////////////////////// 227 +// 228 +// This file implements a subset of the kitty graphics protocol. 229 +// 230 +//////////////////////////////////////////////////////////////////////////////// 231 + 232 +// A workaround for mac os to enable mkdtemp. 233 +#ifdef __APPLE__ 234 +#define _DARWIN_C_SOURCE 235 +#endif 236 + 237 +#define _POSIX_C_SOURCE 200809L 238 + 239 +#include <zlib.h> 240 +#include <Imlib2.h> 241 +#include <X11/Xlib.h> 242 +#include <X11/extensions/Xrender.h> 243 + 244 +#include <assert.h> 245 +#include <ctype.h> 246 +#include <errno.h> 247 +#include <fcntl.h> 248 +#include <math.h> 249 +#include <spawn.h> 250 +#include <stdarg.h> 251 +#include <stdio.h> 252 +#include <stdlib.h> 253 +#include <string.h> 254 +#include <sys/mman.h> 255 +#include <sys/stat.h> 256 +#include <time.h> 257 +#include <unistd.h> 258 + 259 +#include "khash.h" 260 +#include "kvec.h" 261 + 262 +#include "st.h" 263 +#include "graphics.h" 264 + 265 +extern char **environ; 266 + 267 +#define MAX_FILENAME_SIZE 256 268 +#define MAX_INFO_LEN 256 269 +#define MAX_IMAGE_RECTS 20 270 + 271 +/// The type used in this file to represent time. Used both for time differences 272 +/// and absolute times (as milliseconds since an arbitrary point in time, see 273 +/// `initialization_time`). 274 +typedef int64_t Milliseconds; 275 + 276 +enum ScaleMode { 277 + SCALE_MODE_UNSET = 0, 278 + /// Stretch or shrink the image to fill the box, ignoring aspect ratio. 279 + SCALE_MODE_FILL = 1, 280 + /// Preserve aspect ratio and fit to width or to height so that the 281 + /// whole image is visible. 282 + SCALE_MODE_CONTAIN = 2, 283 + /// Do not scale. The image may be cropped if the box is too small. 284 + SCALE_MODE_NONE = 3, 285 + /// Do not scale, unless the box is too small, in which case the image 286 + /// will be shrunk like with `SCALE_MODE_CONTAIN`. 287 + SCALE_MODE_NONE_OR_CONTAIN = 4, 288 +}; 289 + 290 +enum AnimationState { 291 + ANIMATION_STATE_UNSET = 0, 292 + /// The animation is stopped. Display the current frame, but don't 293 + /// advance to the next one. 294 + ANIMATION_STATE_STOPPED = 1, 295 + /// Run the animation to then end, then wait for the next frame. 296 + ANIMATION_STATE_LOADING = 2, 297 + /// Run the animation in a loop. 298 + ANIMATION_STATE_LOOPING = 3, 299 +}; 300 + 301 +/// The status of an image. Each image uploaded to the terminal is cached on 302 +/// disk, then it is loaded to ram when needed. 303 +enum ImageStatus { 304 + STATUS_UNINITIALIZED = 0, 305 + STATUS_UPLOADING = 1, 306 + STATUS_UPLOADING_ERROR = 2, 307 + STATUS_UPLOADING_SUCCESS = 3, 308 + STATUS_RAM_LOADING_ERROR = 4, 309 + STATUS_RAM_LOADING_SUCCESS = 6, 310 +}; 311 + 312 +const char *image_status_strings[6] = { 313 + "STATUS_UNINITIALIZED", 314 + "STATUS_UPLOADING", 315 + "STATUS_UPLOADING_ERROR", 316 + "STATUS_UPLOADING_SUCCESS", 317 + "STATUS_RAM_LOADING_ERROR", 318 + "STATUS_RAM_LOADING_SUCCESS", 319 +}; 320 + 321 +enum ImageUploadingFailure { 322 + ERROR_OVER_SIZE_LIMIT = 1, 323 + ERROR_CANNOT_OPEN_CACHED_FILE = 2, 324 + ERROR_UNEXPECTED_SIZE = 3, 325 + ERROR_CANNOT_COPY_FILE = 4, 326 + ERROR_CANNOT_OPEN_SHM = 5, 327 + ERROR_MTIME_MISMATCH = 3, 328 +}; 329 + 330 +const char *image_uploading_failure_strings[7] = { 331 + "NO_ERROR", 332 + "ERROR_OVER_SIZE_LIMIT", 333 + "ERROR_CANNOT_OPEN_CACHED_FILE", 334 + "ERROR_UNEXPECTED_SIZE", 335 + "ERROR_CANNOT_COPY_FILE", 336 + "ERROR_CANNOT_OPEN_SHM", 337 + "ERROR_MTIME_MISMATCH", 338 +}; 339 + 340 +//////////////////////////////////////////////////////////////////////////////// 341 +// 342 +// We use the following structures to represent images and placements: 343 +// 344 +// - Image: this is the main structure representing an image, usually created 345 +// by actions 'a=t', 'a=T`. Each image has an id (image id aka client id, 346 +// specified by 'i='). An image may have multiple frames (ImageFrame) and 347 +// placements (ImagePlacement). 348 +// 349 +// - ImageFrame: represents a single frame of an image, usually created by 350 +// the action 'a=f' (and the first frame is created with the image itself). 351 +// Each frame has an index and also: 352 +// - a file containing the frame data (considered to be "on disk", although 353 +// it's probably in tmpfs), 354 +// - an imlib object containing the fully composed frame (i.e. the frame 355 +// data from the file composed onto the background frame or color). It is 356 +// not ready for display yet, because it needs to be scaled and uploaded 357 +// to the X server. 358 +// 359 +// - ImagePlacement: represents a placement of an image, created by 'a=p' and 360 +// 'a=T'. Each placement has an id (placement id, specified by 'p='). Also 361 +// each placement has an array of pixmaps: one for each frame of the image. 362 +// Each pixmap is a scaled and uploaded image ready to be displayed. 363 +// 364 +// Images are store in the `images` hash table, mapping image ids to Image 365 +// objects (allocated on the heap). 366 +// 367 +// Placements are stored in the `placements` hash table of each Image object, 368 +// mapping placement ids to ImagePlacement objects (also allocated on the heap). 369 +// 370 +// ImageFrames are stored in the `first_frame` field and in the 371 +// `frames_beyond_the_first` array of each Image object. They are stored by 372 +// value, so ImageFrame pointer may be invalidated when frames are 373 +// added/deleted, be careful. 374 +// 375 +//////////////////////////////////////////////////////////////////////////////// 376 + 377 +struct Image; 378 +struct ImageFrame; 379 +struct ImagePlacement; 380 + 381 +KHASH_MAP_INIT_INT(id2image, struct Image *) 382 +KHASH_MAP_INIT_INT(id2placement, struct ImagePlacement *) 383 + 384 +/// A transformation to apply to a pixmap before drawing it. 385 +typedef struct PixmapTransformation { 386 + /// The width and height of the pixmap. 387 + int pixmap_w, pixmap_h; 388 + /// The width and height of the transformed pixmap. 389 + int dst_w, dst_h; 390 + /// The offset relative to the top-left corner of the box of cells where 391 + /// the transformed pixmap is drawn. 392 + int dst_x, dst_y; 393 +} PixmapTransformation; 394 + 395 +typedef struct ImageFrame { 396 + /// The image this frame belongs to. 397 + struct Image *image; 398 + /// The 1-based index of the frame. Zero if the frame isn't initialized. 399 + int index; 400 + /// The last time when the frame was displayed or otherwise touched. 401 + Milliseconds atime; 402 + /// The background color of the frame in the 0xRRGGBBAA format. 403 + uint32_t background_color; 404 + /// The index of the background frame. Zero to use the color instead. 405 + int background_frame_index; 406 + /// The duration of the frame in milliseconds. 407 + int gap; 408 + /// The expected size of the frame image file (specified with 'S='), 409 + /// used to check if uploading succeeded. 410 + unsigned expected_size; 411 + /// Format specification (see the `f=` key). 412 + int format; 413 + /// Pixel width and height of the non-composed (on-disk) frame data. May 414 + /// differ from the image (i.e. first frame) dimensions. 415 + int data_pix_width, data_pix_height; 416 + /// The offset of the frame relative to the first frame. 417 + int x, y; 418 + /// Compression mode (see the `o=` key). 419 + char compression; 420 + /// The status (see `ImageStatus`). 421 + char status; 422 + /// Whether loading into ram is in progress. This is used to avoid 423 + /// cyclic dependencies between frames. 424 + char ram_loading_in_progress; 425 + /// The reason of uploading failure (see `ImageUploadingFailure`). 426 + char uploading_failure; 427 + /// Whether failures and successes should be reported ('q='). 428 + char quiet; 429 + /// Whether to blend the frame with the background or replace it. 430 + char blend; 431 + /// The original file name used with file transmission. Malloced. 432 + char *original_filename; 433 + /// The modification time of the original file used with file 434 + /// transmission. 435 + time_t original_file_mtime; 436 + /// The file corresponding to the on-disk cache, used when uploading. 437 + FILE *open_file; 438 + /// The size of the corresponding file cached on disk. 439 + unsigned disk_size; 440 + /// The imlib object containing the fully composed frame. It's not 441 + /// scaled for screen display yet. 442 + Imlib_Image imlib_object; 443 +} ImageFrame; 444 + 445 +typedef struct Image { 446 + /// The client id (the one specified with 'i='). Must be nonzero. 447 + uint32_t image_id; 448 + /// The client id specified in the query command (`a=q`). This one must 449 + /// be used to create the response if it's non-zero. 450 + uint32_t query_id; 451 + /// The number specified in the transmission command (`I=`). If 452 + /// non-zero, it may be used to identify the image instead of the 453 + /// image_id, and it also should be mentioned in responses. 454 + uint32_t image_number; 455 + /// The last time when the image was displayed or otherwise touched. 456 + Milliseconds atime; 457 + /// The total duration of the animation in milliseconds. 458 + int total_duration; 459 + /// The total size of cached image files for all frames. 460 + int total_disk_size; 461 + /// The global index of the creation command. Used to decide which image 462 + /// is newer if they have the same image number. 463 + uint64_t global_command_index; 464 + /// The 1-based index of the currently displayed frame. 465 + int current_frame; 466 + /// The state of the animation, see `AnimationState`. 467 + char animation_state; 468 + /// The absolute time that is assumed to be the start of the current 469 + /// frame (in ms since initialization). 470 + Milliseconds current_frame_time; 471 + /// The absolute time of the last redraw (in ms since initialization). 472 + /// Used to check whether it's the first time we draw the image in the 473 + /// current redraw cycle. 474 + Milliseconds last_redraw; 475 + /// The absolute time of the next redraw (in ms since initialization). 476 + /// 0 means no redraw is scheduled. 477 + Milliseconds next_redraw; 478 + /// The unscaled pixel width and height of the image. Usually inherited 479 + /// from the first frame. 480 + int pix_width, pix_height; 481 + /// The first frame. 482 + ImageFrame first_frame; 483 + /// The array of frames beyond the first one. 484 + kvec_t(ImageFrame) frames_beyond_the_first; 485 + /// Image placements. 486 + khash_t(id2placement) *placements; 487 + /// The default placement. 488 + uint32_t default_placement; 489 + /// The initial placement id, specified with the transmission command, 490 + /// used to report success or failure. 491 + uint32_t initial_placement_id; 492 +} Image; 493 + 494 +typedef struct ImagePlacement { 495 + /// The image this placement belongs to. 496 + Image *image; 497 + /// The id of the placement. Must be nonzero. 498 + uint32_t placement_id; 499 + /// The last time when the placement was displayed or otherwise touched. 500 + Milliseconds atime; 501 + /// The 1-based index of the protected pixmap. We protect a pixmap in 502 + /// gr_load_pixmap to avoid unloading it right after it was loaded. 503 + int protected_frame; 504 + /// Whether the placement is used only for Unicode placeholders. 505 + char virtual; 506 + /// The scaling mode (see `ScaleMode`). 507 + char scale_mode; 508 + /// Height and width in cells. 509 + uint16_t rows, cols; 510 + /// Top-left corner of the source rectangle ('x=' and 'y='). 511 + int src_pix_x, src_pix_y; 512 + /// Height and width of the source rectangle (zero if full image). 513 + int src_pix_width, src_pix_height; 514 + /// The image appropriately scaled and uploaded to the X server. This 515 + /// pixmap is premultiplied by alpha. 516 + Pixmap first_pixmap; 517 + /// The array of pixmaps beyond the first one. 518 + kvec_t(Pixmap) pixmaps_beyond_the_first; 519 + /// The dimensions of the cell used to scale the image. If cell 520 + /// dimensions are changed (font change), the image will be rescaled. 521 + uint16_t scaled_cw, scaled_ch; 522 + /// The transformation to apply to the pixmap before drawing it. 523 + PixmapTransformation pixmap_transformation; 524 + /// If true, do not move the cursor when displaying this placement 525 + /// (non-virtual placements only). 526 + char do_not_move_cursor; 527 + /// The text underneath this placement, valid only for classic 528 + /// placements. On deletion, the text is restored. This is a malloced 529 + /// array of rows*cols Glyphs. 530 + Glyph *text_underneath; 531 +} ImagePlacement; 532 + 533 +/// A rectangular piece of an image to be drawn. 534 +typedef struct { 535 + uint32_t image_id; 536 + uint32_t placement_id; 537 + /// The position of the rectangle in pixels. 538 + int screen_x_pix, screen_y_pix; 539 + /// The starting row on the screen. 540 + int screen_y_row; 541 + /// The part of the whole image to be drawn, in cells. Starts are 542 + /// zero-based, ends are exclusive. 543 + int img_start_col, img_end_col, img_start_row, img_end_row; 544 + /// The current cell width and height in pixels. 545 + int cw, ch; 546 + /// Whether colors should be inverted. 547 + int reverse; 548 +} ImageRect; 549 + 550 +/// Executes `code` for each frame of an image. Example: 551 +/// 552 +/// foreach_frame(image, frame, { 553 +/// printf("Frame %d\n", frame->index); 554 +/// }); 555 +/// 556 +#define foreach_frame(image, framevar, code) { size_t __i; \ 557 + for (__i = 0; __i <= kv_size((image).frames_beyond_the_first); ++__i) { \ 558 + ImageFrame *framevar = \ 559 + __i == 0 ? &(image).first_frame \ 560 + : &kv_A((image).frames_beyond_the_first, __i - 1); \ 561 + code; \ 562 + } } 563 + 564 +/// Executes `code` for each pixmap of a placement. Example: 565 +/// 566 +/// foreach_pixmap(placement, pixmap, { 567 +/// ... 568 +/// }); 569 +/// 570 +#define foreach_pixmap(placement, pixmapvar, code) { size_t __i; \ 571 + for (__i = 0; __i <= kv_size((placement).pixmaps_beyond_the_first); ++__i) { \ 572 + Pixmap pixmapvar = \ 573 + __i == 0 ? (placement).first_pixmap \ 574 + : kv_A((placement).pixmaps_beyond_the_first, __i - 1); \ 575 + code; \ 576 + } } 577 + 578 + 579 +static Image *gr_find_image(uint32_t image_id); 580 +static void gr_get_frame_filename(ImageFrame *frame, char *out, size_t max_len); 581 +static void gr_delete_image(Image *img); 582 +static void gr_erase_placement(ImagePlacement *placement); 583 +static void gr_check_limits(); 584 +static void gr_try_restore_imagefile(ImageFrame *frame); 585 +static char *gr_base64dec(const char *src, size_t *size); 586 +static void sanitize_str(char *str, size_t max_len); 587 +static const char *sanitized_filename(const char *str); 588 + 589 +/// The array of image rectangles to draw. It is reset each frame. 590 +static ImageRect image_rects[MAX_IMAGE_RECTS] = {{0}}; 591 +/// The known images (including the ones being uploaded). 592 +static khash_t(id2image) *images = NULL; 593 +/// The total number of placements in all images. 594 +static unsigned total_placement_count = 0; 595 +/// The total size of all image files stored in the on-disk cache. 596 +static int64_t images_disk_size = 0; 597 +/// The total size of all images and placements loaded into ram. 598 +static int64_t images_ram_size = 0; 599 +/// The id of the last loaded image. 600 +static uint32_t last_image_id = 0; 601 +/// Current cell width and heigh in pixels. 602 +static int current_cw = 0, current_ch = 0; 603 +/// The id of the currently uploaded image (when using direct uploading). 604 +static uint32_t current_upload_image_id = 0; 605 +/// The index of the frame currently being uploaded. 606 +static int current_upload_frame_index = 0; 607 +/// The time when the graphics module was initialized. 608 +static struct timespec initialization_time = {0}; 609 +/// The time when the current frame drawing started, used for debugging fps and 610 +/// to calculate the current frame for animations. 611 +static Milliseconds drawing_start_time; 612 +/// The global index of the current command. 613 +static uint64_t global_command_counter = 0; 614 +/// The next redraw times for each row of the terminal. Used for animations. 615 +/// 0 means no redraw is scheduled. 616 +static kvec_t(Milliseconds) next_redraw_times = {0, 0, NULL}; 617 +/// The number of files loaded in the current redraw cycle or command execution. 618 +static int debug_loaded_files_counter = 0; 619 +/// The number of pixmaps loaded in the current redraw cycle or command execution. 620 +static int debug_loaded_pixmaps_counter = 0; 621 + 622 +/// The directory where the cache files are stored. 623 +static char cache_dir[MAX_FILENAME_SIZE - 16]; 624 + 625 +/// The table used for color inversion. 626 +static unsigned char reverse_table[256]; 627 + 628 +// Declared in the header. 629 +GraphicsDebugMode graphics_debug_mode = GRAPHICS_DEBUG_NONE; 630 +char graphics_display_images = 1; 631 +GraphicsCommandResult graphics_command_result = {0}; 632 +int graphics_next_redraw_delay = INT_MAX; 633 + 634 +// Defined in config.h 635 +extern const char graphics_cache_dir_template[]; 636 +extern unsigned graphics_max_single_image_file_size; 637 +extern unsigned graphics_total_file_cache_size; 638 +extern unsigned graphics_max_single_image_ram_size; 639 +extern unsigned graphics_max_total_ram_size; 640 +extern unsigned graphics_max_total_placements; 641 +extern double graphics_excess_tolerance_ratio; 642 +extern unsigned graphics_animation_min_delay; 643 + 644 +// Constants that are not important enough to expose in the config. 645 + 646 +/// The time after which an interrupted (with another command) direct 647 +/// transmission cannot be resumed. 648 +static Milliseconds graphics_direct_transmission_timeout_ms = 2000; 649 + 650 +//////////////////////////////////////////////////////////////////////////////// 651 +// Basic helpers. 652 +//////////////////////////////////////////////////////////////////////////////// 653 + 654 +#define MIN(a, b) ((a) < (b) ? (a) : (b)) 655 +#define MAX(a, b) ((a) < (b) ? (b) : (a)) 656 + 657 +/// Returns the difference between `end` and `start` in milliseconds. 658 +static int64_t gr_timediff_ms(const struct timespec *end, 659 + const struct timespec *start) { 660 + return (end->tv_sec - start->tv_sec) * 1000 + 661 + (end->tv_nsec - start->tv_nsec) / 1000000; 662 +} 663 + 664 +/// Returns the current time in milliseconds since the initialization. 665 +static Milliseconds gr_now_ms() { 666 + struct timespec now; 667 + clock_gettime(CLOCK_MONOTONIC, &now); 668 + return gr_timediff_ms(&now, &initialization_time); 669 +} 670 + 671 +//////////////////////////////////////////////////////////////////////////////// 672 +// Logging. 673 +//////////////////////////////////////////////////////////////////////////////// 674 + 675 +#define GR_LOG(...) \ 676 + do { if(graphics_debug_mode) fprintf(stderr, __VA_ARGS__); } while(0) 677 + 678 +//////////////////////////////////////////////////////////////////////////////// 679 +// Basic image management functions (create, delete, find, etc). 680 +//////////////////////////////////////////////////////////////////////////////// 681 + 682 +/// Returns the 1-based index of the last frame. Note that you may want to use 683 +/// `gr_last_uploaded_frame_index` instead since the last frame may be not 684 +/// fully uploaded yet. 685 +static inline int gr_last_frame_index(Image *img) { 686 + return kv_size(img->frames_beyond_the_first) + 1; 687 +} 688 + 689 +/// Returns the frame with the given index. Returns NULL if the index is out of 690 +/// bounds. The index is 1-based. 691 +static ImageFrame *gr_get_frame(Image *img, int index) { 692 + if (!img) 693 + return NULL; 694 + if (index == 1) 695 + return &img->first_frame; 696 + if (2 <= index && index <= gr_last_frame_index(img)) 697 + return &kv_A(img->frames_beyond_the_first, index - 2); 698 + return NULL; 699 +} 700 + 701 +/// Returns the last frame of the image. Returns NULL if `img` is NULL. 702 +static ImageFrame *gr_get_last_frame(Image *img) { 703 + if (!img) 704 + return NULL; 705 + return gr_get_frame(img, gr_last_frame_index(img)); 706 +} 707 + 708 +/// Returns the 1-based index of the last frame or the second-to-last frame if 709 +/// the last frame is not fully uploaded yet. 710 +static inline int gr_last_uploaded_frame_index(Image *img) { 711 + int last_index = gr_last_frame_index(img); 712 + if (last_index > 1 && 713 + gr_get_frame(img, last_index)->status < STATUS_UPLOADING_SUCCESS) 714 + return last_index - 1; 715 + return last_index; 716 +} 717 + 718 +/// Returns the pixmap for the frame with the given index. Returns 0 if the 719 +/// index is out of bounds. The index is 1-based. 720 +static Pixmap gr_get_frame_pixmap(ImagePlacement *placement, int index) { 721 + if (index == 1) 722 + return placement->first_pixmap; 723 + if (2 <= index && 724 + index <= kv_size(placement->pixmaps_beyond_the_first) + 1) 725 + return kv_A(placement->pixmaps_beyond_the_first, index - 2); 726 + return 0; 727 +} 728 + 729 +/// Sets the pixmap for the frame with the given index. The index is 1-based. 730 +/// The array of pixmaps is resized if needed. 731 +static void gr_set_frame_pixmap(ImagePlacement *placement, int index, 732 + Pixmap pixmap) { 733 + if (index == 1) { 734 + placement->first_pixmap = pixmap; 735 + return; 736 + } 737 + // Resize the array if needed. 738 + size_t old_size = kv_size(placement->pixmaps_beyond_the_first); 739 + if (old_size < index - 1) { 740 + kv_a(Pixmap, placement->pixmaps_beyond_the_first, index - 2); 741 + for (size_t i = old_size; i < index - 1; i++) 742 + kv_A(placement->pixmaps_beyond_the_first, i) = 0; 743 + } 744 + kv_A(placement->pixmaps_beyond_the_first, index - 2) = pixmap; 745 +} 746 + 747 +/// Finds the image corresponding to the client id. Returns NULL if cannot find. 748 +static Image *gr_find_image(uint32_t image_id) { 749 + khiter_t k = kh_get(id2image, images, image_id); 750 + if (k == kh_end(images)) 751 + return NULL; 752 + Image *res = kh_value(images, k); 753 + return res; 754 +} 755 + 756 +/// Finds the newest image corresponding to the image number. Returns NULL if 757 +/// cannot find. 758 +static Image *gr_find_image_by_number(uint32_t image_number) { 759 + if (image_number == 0) 760 + return NULL; 761 + Image *newest_img = NULL; 762 + Image *img = NULL; 763 + kh_foreach_value(images, img, { 764 + if (img->image_number == image_number && 765 + (!newest_img || newest_img->global_command_index < 766 + img->global_command_index)) 767 + newest_img = img; 768 + }); 769 + if (!newest_img) 770 + GR_LOG("Image number %u not found\n", image_number); 771 + else 772 + GR_LOG("Found image number %u, its id is %u\n", image_number, 773 + img->image_id); 774 + return newest_img; 775 +} 776 + 777 +/// Finds the placement corresponding to the id. If the placement id is 0, 778 +/// returns some default placement. 779 +static ImagePlacement *gr_find_placement(Image *img, uint32_t placement_id) { 780 + if (!img) 781 + return NULL; 782 + if (placement_id == 0) { 783 + // Try to get the default placement. 784 + ImagePlacement *dflt = NULL; 785 + if (img->default_placement != 0) 786 + dflt = gr_find_placement(img, img->default_placement); 787 + if (dflt) 788 + return dflt; 789 + // If there is no default placement, return the first one and 790 + // set it as the default. 791 + kh_foreach_value(img->placements, dflt, { 792 + img->default_placement = dflt->placement_id; 793 + return dflt; 794 + }); 795 + // If there are no placements, return NULL. 796 + return NULL; 797 + } 798 + khiter_t k = kh_get(id2placement, img->placements, placement_id); 799 + if (k == kh_end(img->placements)) 800 + return NULL; 801 + ImagePlacement *res = kh_value(img->placements, k); 802 + return res; 803 +} 804 + 805 +/// Finds the placement by image id and placement id. 806 +static ImagePlacement *gr_find_image_and_placement(uint32_t image_id, 807 + uint32_t placement_id) { 808 + return gr_find_placement(gr_find_image(image_id), placement_id); 809 +} 810 + 811 +/// Returns a pointer to the glyph under the classic placement with `image_id` 812 +/// and `placement_id` at `col` and `row` (1-based). May return NULL if the 813 +/// underneath text is unknown. 814 +Glyph *gr_get_glyph_underneath_image(uint32_t image_id, uint32_t placement_id, 815 + int col, int row) { 816 + ImagePlacement *placement = 817 + gr_find_image_and_placement(image_id, placement_id); 818 + if (!placement || !placement->text_underneath) 819 + return NULL; 820 + col--; 821 + row--; 822 + if (col < 0 || col >= placement->cols || row < 0 || 823 + row >= placement->rows) 824 + return NULL; 825 + return &placement->text_underneath[row * placement->cols + col]; 826 +} 827 + 828 +/// Writes the name of the on-disk cache file to `out`. `max_len` should be the 829 +/// size of `out`. The name will be something like 830 +/// "/tmp/st-images-xxx/img-ID-FRAME". 831 +static void gr_get_frame_filename(ImageFrame *frame, char *out, 832 + size_t max_len) { 833 + snprintf(out, max_len, "%s/img-%.3u-%.3u", cache_dir, 834 + frame->image->image_id, frame->index); 835 +} 836 + 837 +/// Returns the (estimation) of the RAM size used by the frame right now. 838 +static unsigned gr_frame_current_ram_size(ImageFrame *frame) { 839 + if (!frame->imlib_object) 840 + return 0; 841 + return (unsigned)frame->image->pix_width * frame->image->pix_height * 4; 842 +} 843 + 844 +/// Returns the (estimation) of the RAM size used by a single frame pixmap. 845 +static unsigned gr_placement_single_frame_ram_size(ImagePlacement *placement) { 846 + return (unsigned)placement->pixmap_transformation.pixmap_w * 847 + placement->pixmap_transformation.pixmap_h * 4; 848 +} 849 + 850 +/// Returns the (estimation) of the RAM size used by the placemenet right now. 851 +static unsigned gr_placement_current_ram_size(ImagePlacement *placement) { 852 + unsigned single_frame_size = 853 + gr_placement_single_frame_ram_size(placement); 854 + unsigned result = 0; 855 + foreach_pixmap(*placement, pixmap, { 856 + if (pixmap) 857 + result += single_frame_size; 858 + }); 859 + return result; 860 +} 861 + 862 +/// Unload the frame from RAM (i.e. delete the corresponding imlib object). 863 +/// If the on-disk file of the frame is preserved, it can be reloaded later. 864 +static void gr_unload_frame(ImageFrame *frame) { 865 + if (!frame->imlib_object) 866 + return; 867 + 868 + unsigned frame_ram_size = gr_frame_current_ram_size(frame); 869 + images_ram_size -= frame_ram_size; 870 + 871 + imlib_context_set_image(frame->imlib_object); 872 + imlib_free_image_and_decache(); 873 + frame->imlib_object = NULL; 874 + 875 + GR_LOG("After unloading image %u frame %u (atime %ld ms ago) " 876 + "ram: %ld KiB (- %u KiB)\n", 877 + frame->image->image_id, frame->index, 878 + drawing_start_time - frame->atime, images_ram_size / 1024, 879 + frame_ram_size / 1024); 880 +} 881 + 882 +/// Unload all frames of the image. 883 +static void gr_unload_all_frames(Image *img) { 884 + foreach_frame(*img, frame, { 885 + gr_unload_frame(frame); 886 + }); 887 +} 888 + 889 +/// Unload the placement from RAM (i.e. free all of the corresponding pixmaps). 890 +/// If the on-disk files or imlib objects of the corresponding image are 891 +/// preserved, the placement can be reloaded later. 892 +static void gr_unload_placement(ImagePlacement *placement) { 893 + unsigned placement_ram_size = gr_placement_current_ram_size(placement); 894 + images_ram_size -= placement_ram_size; 895 + 896 + Display *disp = imlib_context_get_display(); 897 + foreach_pixmap(*placement, pixmap, { 898 + if (pixmap) 899 + XFreePixmap(disp, pixmap); 900 + }); 901 + 902 + placement->first_pixmap = 0; 903 + placement->pixmaps_beyond_the_first.n = 0; 904 + placement->scaled_ch = placement->scaled_cw = 0; 905 + 906 + GR_LOG("After unloading placement %u/%u (atime %ld ms ago) " 907 + "ram: %ld KiB (- %u KiB)\n", 908 + placement->image->image_id, placement->placement_id, 909 + drawing_start_time - placement->atime, images_ram_size / 1024, 910 + placement_ram_size / 1024); 911 +} 912 + 913 +/// Unload a single pixmap of the placement from RAM. 914 +static void gr_unload_pixmap(ImagePlacement *placement, int frameidx) { 915 + Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); 916 + if (!pixmap) 917 + return; 918 + 919 + Display *disp = imlib_context_get_display(); 920 + XFreePixmap(disp, pixmap); 921 + gr_set_frame_pixmap(placement, frameidx, 0); 922 + images_ram_size -= gr_placement_single_frame_ram_size(placement); 923 + 924 + GR_LOG("After unloading pixmap %ld of " 925 + "placement %u/%u (atime %ld ms ago) " 926 + "frame %u (atime %ld ms ago) " 927 + "ram: %ld KiB (- %u KiB)\n", 928 + pixmap, placement->image->image_id, placement->placement_id, 929 + drawing_start_time - placement->atime, frameidx, 930 + drawing_start_time - 931 + gr_get_frame(placement->image, frameidx)->atime, 932 + images_ram_size / 1024, 933 + gr_placement_single_frame_ram_size(placement) / 1024); 934 +} 935 + 936 +/// Closes the on-disk cache file of the frame `frame`. 937 +static void gr_close_disk_cache_file(ImageFrame *frame) { 938 + if (frame && frame->open_file) { 939 + fclose(frame->open_file); 940 + frame->open_file = NULL; 941 + } 942 +} 943 + 944 +/// Deletes the on-disk cache file corresponding to the frame. The in-ram image 945 +/// object (if it exists) is not deleted, placements are not unloaded either. 946 +static void gr_delete_imagefile(ImageFrame *frame) { 947 + // It may still be being loaded. Close the file in this case. 948 + gr_close_disk_cache_file(frame); 949 + 950 + if (frame->disk_size == 0) 951 + return; 952 + 953 + char filename[MAX_FILENAME_SIZE]; 954 + gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 955 + remove(filename); 956 + 957 + unsigned disk_size = frame->disk_size; 958 + images_disk_size -= disk_size; 959 + frame->image->total_disk_size -= disk_size; 960 + frame->disk_size = 0; 961 + 962 + GR_LOG("After deleting image file %u frame %u (atime %ld ms ago) " 963 + "disk: %ld KiB (- %u KiB)\n", 964 + frame->image->image_id, frame->index, 965 + drawing_start_time - frame->atime, images_disk_size / 1024, 966 + disk_size / 1024); 967 +} 968 + 969 +/// Deletes all on-disk cache files of the image (for each frame). 970 +static void gr_delete_imagefiles(Image *img) { 971 + foreach_frame(*img, frame, { 972 + gr_delete_imagefile(frame); 973 + }); 974 +} 975 + 976 +/// Deletes the given placement: unloads, frees the object, erases it from the 977 +/// screen in the classic case, but doesn't change the `placements` hash table. 978 +static void gr_delete_placement_keep_id(ImagePlacement *placement) { 979 + if (!placement) 980 + return; 981 + GR_LOG("Deleting placement %u/%u\n", placement->image->image_id, 982 + placement->placement_id); 983 + // Erase the placement from the screen if it's classic and there is some 984 + // saved text underneath. 985 + if (placement->text_underneath && !placement->virtual) 986 + gr_erase_placement(placement); 987 + gr_unload_placement(placement); 988 + kv_destroy(placement->pixmaps_beyond_the_first); 989 + free(placement->text_underneath); 990 + free(placement); 991 + total_placement_count--; 992 +} 993 + 994 +/// Deletes all placements of `img`. 995 +static void gr_delete_all_placements(Image *img) { 996 + ImagePlacement *placement = NULL; 997 + kh_foreach_value(img->placements, placement, { 998 + gr_delete_placement_keep_id(placement); 999 + }); 1000 + kh_clear(id2placement, img->placements); 1001 +} 1002 + 1003 +/// Deletes the given image: unloads, deletes the file, frees the Image object, 1004 +/// but doesn't change the `images` hash table. 1005 +static void gr_delete_image_keep_id(Image *img) { 1006 + if (!img) 1007 + return; 1008 + GR_LOG("Deleting image %u\n", img->image_id); 1009 + foreach_frame(*img, frame, { 1010 + gr_delete_imagefile(frame); 1011 + gr_unload_frame(frame); 1012 + if (frame->original_filename) 1013 + free(frame->original_filename); 1014 + }); 1015 + kv_destroy(img->frames_beyond_the_first); 1016 + gr_delete_all_placements(img); 1017 + kh_destroy(id2placement, img->placements); 1018 + free(img); 1019 +} 1020 + 1021 +/// Deletes the given image: unloads, deletes the file, frees the Image object, 1022 +/// and also removes it from `images`. 1023 +static void gr_delete_image(Image *img) { 1024 + if (!img) 1025 + return; 1026 + uint32_t id = img->image_id; 1027 + gr_delete_image_keep_id(img); 1028 + khiter_t k = kh_get(id2image, images, id); 1029 + kh_del(id2image, images, k); 1030 +} 1031 + 1032 +/// Deletes the given placement: unloads, frees the object, erases from the 1033 +/// screen (in the classic case), and also removes it from `placements`. 1034 +static void gr_delete_placement(ImagePlacement *placement) { 1035 + if (!placement) 1036 + return; 1037 + uint32_t id = placement->placement_id; 1038 + Image *img = placement->image; 1039 + gr_delete_placement_keep_id(placement); 1040 + khiter_t k = kh_get(id2placement, img->placements, id); 1041 + kh_del(id2placement, img->placements, k); 1042 +} 1043 + 1044 +/// Deletes all images and clears `images`. 1045 +static void gr_delete_all_images() { 1046 + Image *img = NULL; 1047 + kh_foreach_value(images, img, { 1048 + gr_delete_image_keep_id(img); 1049 + }); 1050 + kh_clear(id2image, images); 1051 +} 1052 + 1053 +/// Update the atime of the image. 1054 +static void gr_touch_image(Image *img) { 1055 + img->atime = gr_now_ms(); 1056 +} 1057 + 1058 +/// Update the atime of the frame. 1059 +static void gr_touch_frame(ImageFrame *frame) { 1060 + frame->image->atime = frame->atime = gr_now_ms(); 1061 +} 1062 + 1063 +/// Update the atime of the placement. Touches the images too. 1064 +static void gr_touch_placement(ImagePlacement *placement) { 1065 + placement->image->atime = placement->atime = gr_now_ms(); 1066 +} 1067 + 1068 +/// Creates a new image with the given id. If an image with that id already 1069 +/// exists, it is deleted first. If the provided id is 0, generates a 1070 +/// random id. 1071 +static Image *gr_new_image(uint32_t id) { 1072 + if (id == 0) { 1073 + do { 1074 + id = rand(); 1075 + // Avoid IDs that don't need full 32 bits. 1076 + } while ((id & 0xFF000000) == 0 || (id & 0x00FFFF00) == 0 || 1077 + gr_find_image(id)); 1078 + GR_LOG("Generated random image id %u\n", id); 1079 + } 1080 + Image *img = gr_find_image(id); 1081 + gr_delete_image_keep_id(img); 1082 + GR_LOG("Creating image %u\n", id); 1083 + img = malloc(sizeof(Image)); 1084 + memset(img, 0, sizeof(Image)); 1085 + img->placements = kh_init(id2placement); 1086 + int ret; 1087 + khiter_t k = kh_put(id2image, images, id, &ret); 1088 + kh_value(images, k) = img; 1089 + img->image_id = id; 1090 + gr_touch_image(img); 1091 + img->global_command_index = global_command_counter; 1092 + return img; 1093 +} 1094 + 1095 +/// Creates a new frame at the end of the frame array. It may be the first frame 1096 +/// if there are no frames yet. 1097 +static ImageFrame *gr_append_new_frame(Image *img) { 1098 + ImageFrame *frame = NULL; 1099 + if (img->first_frame.index == 0 && 1100 + kv_size(img->frames_beyond_the_first) == 0) { 1101 + frame = &img->first_frame; 1102 + frame->index = 1; 1103 + } else { 1104 + frame = kv_pushp(ImageFrame, img->frames_beyond_the_first); 1105 + memset(frame, 0, sizeof(ImageFrame)); 1106 + frame->index = kv_size(img->frames_beyond_the_first) + 1; 1107 + } 1108 + frame->image = img; 1109 + gr_touch_frame(frame); 1110 + GR_LOG("Appending frame %d to image %u\n", frame->index, img->image_id); 1111 + return frame; 1112 +} 1113 + 1114 +/// Creates a new placement with the given id. If a placement with that id 1115 +/// already exists, it is deleted first. If the provided id is 0, generates a 1116 +/// random id. 1117 +static ImagePlacement *gr_new_placement(Image *img, uint32_t id) { 1118 + if (id == 0) { 1119 + do { 1120 + // Currently we support only 24-bit IDs. 1121 + id = rand() & 0xFFFFFF; 1122 + // Avoid IDs that need only one byte. 1123 + } while ((id & 0x00FFFF00) == 0 || gr_find_placement(img, id)); 1124 + } 1125 + ImagePlacement *placement = gr_find_placement(img, id); 1126 + gr_delete_placement_keep_id(placement); 1127 + GR_LOG("Creating placement %u/%u\n", img->image_id, id); 1128 + placement = malloc(sizeof(ImagePlacement)); 1129 + memset(placement, 0, sizeof(ImagePlacement)); 1130 + total_placement_count++; 1131 + int ret; 1132 + khiter_t k = kh_put(id2placement, img->placements, id, &ret); 1133 + kh_value(img->placements, k) = placement; 1134 + placement->image = img; 1135 + placement->placement_id = id; 1136 + gr_touch_placement(placement); 1137 + if (img->default_placement == 0) 1138 + img->default_placement = id; 1139 + return placement; 1140 +} 1141 + 1142 +static int64_t ceil_div(int64_t a, int64_t b) { 1143 + return (a + b - 1) / b; 1144 +} 1145 + 1146 +/// Computes the best number of rows and columns for a placement if it's not 1147 +/// specified, and also adjusts the source rectangle size. 1148 +static void gr_infer_placement_size_maybe(ImagePlacement *placement) { 1149 + // The size of the image. 1150 + int image_pix_width = placement->image->pix_width; 1151 + int image_pix_height = placement->image->pix_height; 1152 + // Negative values are not allowed. Quietly set them to 0. 1153 + if (placement->src_pix_x < 0) 1154 + placement->src_pix_x = 0; 1155 + if (placement->src_pix_y < 0) 1156 + placement->src_pix_y = 0; 1157 + if (placement->src_pix_width < 0) 1158 + placement->src_pix_width = 0; 1159 + if (placement->src_pix_height < 0) 1160 + placement->src_pix_height = 0; 1161 + // If the source rectangle is outside the image, truncate it. 1162 + if (placement->src_pix_x > image_pix_width) 1163 + placement->src_pix_x = image_pix_width; 1164 + if (placement->src_pix_y > image_pix_height) 1165 + placement->src_pix_y = image_pix_height; 1166 + // If the source rectangle is not specified, use the whole image. If 1167 + // it's partially outside the image, truncate it. 1168 + if (placement->src_pix_width == 0 || 1169 + placement->src_pix_x + placement->src_pix_width > image_pix_width) 1170 + placement->src_pix_width = 1171 + image_pix_width - placement->src_pix_x; 1172 + if (placement->src_pix_height == 0 || 1173 + placement->src_pix_y + placement->src_pix_height > image_pix_height) 1174 + placement->src_pix_height = 1175 + image_pix_height - placement->src_pix_y; 1176 + 1177 + if (placement->cols != 0 && placement->rows != 0) 1178 + return; 1179 + if (placement->src_pix_width == 0 || placement->src_pix_height == 0) 1180 + return; 1181 + if (current_cw == 0 || current_ch == 0) 1182 + return; 1183 + 1184 + // If no size is specified, use the image size. 1185 + if (placement->cols == 0 && placement->rows == 0) { 1186 + placement->cols = 1187 + ceil_div(placement->src_pix_width, current_cw); 1188 + placement->rows = 1189 + ceil_div(placement->src_pix_height, current_ch); 1190 + return; 1191 + } 1192 + 1193 + // Some applications specify only one of the dimensions. 1194 + if (placement->scale_mode == SCALE_MODE_CONTAIN) { 1195 + // If we preserve aspect ratio and fit to width/height, the most 1196 + // logical thing is to find the minimum size of the 1197 + // non-specified dimension that allows the image to fit the 1198 + // specified dimension. 1199 + if (placement->cols == 0) { 1200 + placement->cols = ceil_div( 1201 + placement->src_pix_width * placement->rows * 1202 + current_ch, 1203 + placement->src_pix_height * current_cw); 1204 + return; 1205 + } 1206 + if (placement->rows == 0) { 1207 + placement->rows = 1208 + ceil_div(placement->src_pix_height * 1209 + placement->cols * current_cw, 1210 + placement->src_pix_width * current_ch); 1211 + return; 1212 + } 1213 + } else { 1214 + // Otherwise we stretch the image or preserve the original size. 1215 + // In both cases we compute the best number of columns from the 1216 + // pixel size and cell size. 1217 + // TODO: In the case of stretching it's not the most logical 1218 + // thing to do, may need to revisit in the future. 1219 + // Currently we switch to SCALE_MODE_CONTAIN when only one 1220 + // of the dimensions is specified, so this case shouldn't 1221 + // happen in practice. 1222 + if (!placement->cols) 1223 + placement->cols = 1224 + ceil_div(placement->src_pix_width, current_cw); 1225 + if (!placement->rows) 1226 + placement->rows = 1227 + ceil_div(placement->src_pix_height, current_ch); 1228 + } 1229 +} 1230 + 1231 +/// Adjusts the current frame index if enough time has passed since the display 1232 +/// of the current frame. Also computes the time of the next redraw of this 1233 +/// image (`img->next_redraw`). The current time is passed as an argument so 1234 +/// that all animations are in sync. 1235 +static void gr_update_frame_index(Image *img, Milliseconds now) { 1236 + if (img->current_frame == 0) { 1237 + img->current_frame_time = now; 1238 + img->current_frame = 1; 1239 + img->next_redraw = now + MAX(1, img->first_frame.gap); 1240 + return; 1241 + } 1242 + // If the animation is stopped, show the current frame. 1243 + if (!img->animation_state || 1244 + img->animation_state == ANIMATION_STATE_STOPPED || 1245 + img->animation_state == ANIMATION_STATE_UNSET) { 1246 + // The next redraw is never (unless the state is changed). 1247 + img->next_redraw = 0; 1248 + return; 1249 + } 1250 + int last_uploaded_frame_index = gr_last_uploaded_frame_index(img); 1251 + // If we are loading and we reached the last frame, show the last frame. 1252 + if (img->animation_state == ANIMATION_STATE_LOADING && 1253 + img->current_frame == last_uploaded_frame_index) { 1254 + // The next redraw is never (unless the state is changed or 1255 + // frames are added). 1256 + img->next_redraw = 0; 1257 + return; 1258 + } 1259 + 1260 + // Check how many milliseconds passed since the current frame was shown. 1261 + int passed_ms = now - img->current_frame_time; 1262 + // If the animation is looping and too much time has passes, we can 1263 + // make a shortcut. 1264 + if (img->animation_state == ANIMATION_STATE_LOOPING && 1265 + img->total_duration > 0 && passed_ms >= img->total_duration) { 1266 + passed_ms %= img->total_duration; 1267 + img->current_frame_time = now - passed_ms; 1268 + } 1269 + // Find the next frame. 1270 + int original_frame_index = img->current_frame; 1271 + while (1) { 1272 + ImageFrame *frame = gr_get_frame(img, img->current_frame); 1273 + if (!frame) { 1274 + // The frame doesn't exist, go to the first frame. 1275 + img->current_frame = 1; 1276 + img->current_frame_time = now; 1277 + img->next_redraw = now + MAX(1, img->first_frame.gap); 1278 + return; 1279 + } 1280 + if (frame->gap >= 0 && passed_ms < frame->gap) { 1281 + // Not enough time has passed, we are still in the same 1282 + // frame, and it's not a gapless frame. 1283 + img->next_redraw = 1284 + img->current_frame_time + MAX(1, frame->gap); 1285 + return; 1286 + } 1287 + // Otherwise go to the next frame. 1288 + passed_ms -= MAX(0, frame->gap); 1289 + if (img->current_frame >= last_uploaded_frame_index) { 1290 + // It's the last frame, if the animation is loading, 1291 + // remain on it. 1292 + if (img->animation_state == ANIMATION_STATE_LOADING) { 1293 + img->next_redraw = 0; 1294 + return; 1295 + } 1296 + // Otherwise the animation is looping. 1297 + img->current_frame = 1; 1298 + // TODO: Support finite number of loops. 1299 + } else { 1300 + img->current_frame++; 1301 + } 1302 + // Make sure we don't get stuck in an infinite loop. 1303 + if (img->current_frame == original_frame_index) { 1304 + // We looped through all frames, but haven't reached the 1305 + // next frame yet. This may happen if too much time has 1306 + // passed since the last redraw or all the frames are 1307 + // gapless. Just move on to the next frame. 1308 + img->current_frame++; 1309 + if (img->current_frame > 1310 + last_uploaded_frame_index) 1311 + img->current_frame = 1; 1312 + img->current_frame_time = now; 1313 + img->next_redraw = now + MAX( 1314 + 1, gr_get_frame(img, img->current_frame)->gap); 1315 + return; 1316 + } 1317 + // Adjust the start time of the frame. The next redraw time will 1318 + // be set in the next iteration. 1319 + img->current_frame_time += MAX(0, frame->gap); 1320 + } 1321 +} 1322 + 1323 +//////////////////////////////////////////////////////////////////////////////// 1324 +// Unloading and deleting images to save resources. 1325 +//////////////////////////////////////////////////////////////////////////////// 1326 + 1327 +/// Returns whether the original file of the frame is still available (exists, 1328 +/// is a regular file, has the same size and mtime). 1329 +static int gr_is_original_file_still_available(ImageFrame *frame) { 1330 + if (!frame->original_filename) 1331 + return 0; 1332 + struct stat st; 1333 + if (stat(frame->original_filename, &st) != 0) 1334 + return 0; 1335 + if (!S_ISREG(st.st_mode)) 1336 + return 0; 1337 + if (st.st_size == 0 || st.st_size > graphics_max_single_image_file_size) 1338 + return 0; 1339 + if (frame->expected_size && st.st_size != frame->expected_size) 1340 + return 0; 1341 + if (st.st_mtime != frame->original_file_mtime) 1342 + return 0; 1343 + return 1; 1344 +} 1345 + 1346 +/// A helper to compare frames by atime for qsort. 1347 +static int gr_cmp_frames_by_atime(const void *a, const void *b) { 1348 + ImageFrame *frame_a = *(ImageFrame *const *)a; 1349 + ImageFrame *frame_b = *(ImageFrame *const *)b; 1350 + if (frame_a->atime == frame_b->atime) 1351 + return frame_a->image->global_command_index - 1352 + frame_b->image->global_command_index; 1353 + return frame_a->atime - frame_b->atime; 1354 +} 1355 + 1356 +/// A helper to compare images by atime for qsort. 1357 +static int gr_cmp_images_by_atime(const void *a, const void *b) { 1358 + Image *img_a = *(Image *const *)a; 1359 + Image *img_b = *(Image *const *)b; 1360 + if (img_a->atime == img_b->atime) 1361 + return img_a->global_command_index - 1362 + img_b->global_command_index; 1363 + return img_a->atime - img_b->atime; 1364 +} 1365 + 1366 +/// A helper to compare placements by atime for qsort. 1367 +static int gr_cmp_placements_by_atime(const void *a, const void *b) { 1368 + ImagePlacement *p_a = *(ImagePlacement **)a; 1369 + ImagePlacement *p_b = *(ImagePlacement **)b; 1370 + if (p_a->atime == p_b->atime) 1371 + return p_a->image->global_command_index - 1372 + p_b->image->global_command_index; 1373 + return p_a->atime - p_b->atime; 1374 +} 1375 + 1376 +typedef kvec_t(Image *) ImageVec; 1377 +typedef kvec_t(ImagePlacement *) ImagePlacementVec; 1378 +typedef kvec_t(ImageFrame *) ImageFrameVec; 1379 + 1380 +/// Returns an array of pointers to all images sorted by atime. 1381 +static ImageVec gr_get_images_sorted_by_atime() { 1382 + ImageVec vec; 1383 + kv_init(vec); 1384 + if (kh_size(images) == 0) 1385 + return vec; 1386 + kv_resize(Image *, vec, kh_size(images)); 1387 + Image *img = NULL; 1388 + kh_foreach_value(images, img, { kv_push(Image *, vec, img); }); 1389 + qsort(vec.a, kv_size(vec), sizeof(Image *), gr_cmp_images_by_atime); 1390 + return vec; 1391 +} 1392 + 1393 +/// Returns an array of pointers to all placements sorted by atime. 1394 +static ImagePlacementVec gr_get_placements_sorted_by_atime() { 1395 + ImagePlacementVec vec; 1396 + kv_init(vec); 1397 + if (total_placement_count == 0) 1398 + return vec; 1399 + kv_resize(ImagePlacement *, vec, total_placement_count); 1400 + Image *img = NULL; 1401 + ImagePlacement *placement = NULL; 1402 + kh_foreach_value(images, img, { 1403 + kh_foreach_value(img->placements, placement, { 1404 + kv_push(ImagePlacement *, vec, placement); 1405 + }); 1406 + }); 1407 + qsort(vec.a, kv_size(vec), sizeof(ImagePlacement *), 1408 + gr_cmp_placements_by_atime); 1409 + return vec; 1410 +} 1411 + 1412 +/// Returns an array of pointers to all frames sorted by atime. 1413 +static ImageFrameVec gr_get_frames_sorted_by_atime() { 1414 + ImageFrameVec frames; 1415 + kv_init(frames); 1416 + Image *img = NULL; 1417 + kh_foreach_value(images, img, { 1418 + foreach_frame(*img, frame, { 1419 + kv_push(ImageFrame *, frames, frame); 1420 + }); 1421 + }); 1422 + qsort(frames.a, kv_size(frames), sizeof(ImageFrame *), 1423 + gr_cmp_frames_by_atime); 1424 + return frames; 1425 +} 1426 + 1427 +/// An object that can be unloaded from RAM. 1428 +typedef struct { 1429 + /// Some score, probably based on access time. The lower the score, the 1430 + /// more likely that the object should be unloaded. 1431 + int64_t score; 1432 + union { 1433 + ImagePlacement *placement; 1434 + ImageFrame *frame; 1435 + }; 1436 + /// If zero, the object is the imlib object of `frame`, if non-zero, 1437 + /// the object is a pixmap of `frameidx`-th frame of `placement`. 1438 + int frameidx; 1439 +} UnloadableObject; 1440 + 1441 +typedef kvec_t(UnloadableObject) UnloadableObjectVec; 1442 + 1443 +/// A helper to compare unloadable objects by score for qsort. 1444 +static int gr_cmp_unloadable_objects(const void *a, const void *b) { 1445 + UnloadableObject *obj_a = (UnloadableObject *)a; 1446 + UnloadableObject *obj_b = (UnloadableObject *)b; 1447 + return obj_a->score - obj_b->score; 1448 +} 1449 + 1450 +/// Unloads an unloadable object from RAM. 1451 +static void gr_unload_object(UnloadableObject *obj) { 1452 + if (obj->frameidx) { 1453 + if (obj->placement->protected_frame == obj->frameidx) 1454 + return; 1455 + gr_unload_pixmap(obj->placement, obj->frameidx); 1456 + } else { 1457 + gr_unload_frame(obj->frame); 1458 + } 1459 +} 1460 + 1461 +/// Returns the recency threshold for an image. Frames that were accessed within 1462 +/// this threshold from now are considered recent and may be handled 1463 +/// differently because we may need them again very soon. 1464 +static Milliseconds gr_recency_threshold(Image *img) { 1465 + return img->total_duration * 2 + 1000; 1466 +} 1467 + 1468 +/// Creates an unloadable object for the imlib object of a frame. 1469 +static UnloadableObject gr_unloadable_object_for_frame(Milliseconds now, 1470 + ImageFrame *frame) { 1471 + UnloadableObject obj = {0}; 1472 + obj.frameidx = 0; 1473 + obj.frame = frame; 1474 + Milliseconds atime = frame->atime; 1475 + obj.score = atime; 1476 + if (atime >= now - gr_recency_threshold(frame->image)) { 1477 + // This is a recent frame, probably from an active animation. 1478 + // Score it above `now` to prefer unloading non-active frames. 1479 + // Randomize the score because it's not very clear in which 1480 + // order we want to unload them: reloading a frame may require 1481 + // reloading other frames. 1482 + obj.score = now + 1000 + rand() % 1000; 1483 + } 1484 + return obj; 1485 +} 1486 + 1487 +/// Creates an unloadable object for a pixmap. 1488 +static UnloadableObject 1489 +gr_unloadable_object_for_pixmap(Milliseconds now, ImageFrame *frame, 1490 + ImagePlacement *placement) { 1491 + UnloadableObject obj = {0}; 1492 + obj.frameidx = frame->index; 1493 + obj.placement = placement; 1494 + obj.score = placement->atime; 1495 + // Since we don't store pixmap atimes, use the 1496 + // oldest atime of the frame and the placement. 1497 + Milliseconds atime = MIN(placement->atime, frame->atime); 1498 + obj.score = atime; 1499 + if (atime >= now - gr_recency_threshold(frame->image)) { 1500 + // This is a recent pixmap, probably from an active animation. 1501 + // Score it above `now` to prefer unloading non-active frames. 1502 + // Also assign higher scores to frames that are closer to the 1503 + // current frame (more likely to be used soon). 1504 + int num_frames = gr_last_frame_index(frame->image); 1505 + int dist = frame->index - frame->image->current_frame; 1506 + if (dist < 0) 1507 + dist += num_frames; 1508 + obj.score = 1509 + now + 1000 + (num_frames - dist) * 1000 / num_frames; 1510 + // If the pixmap is much larger than the imlib image, prefer to 1511 + // unload the pixmap by adding up to -1000 to the score. If the 1512 + // imlib image is larger, add up to +1000. 1513 + float imlib_size = gr_frame_current_ram_size(frame); 1514 + float pixmap_size = 1515 + gr_placement_single_frame_ram_size(placement); 1516 + obj.score += 1517 + 2000 * (imlib_size / (imlib_size + pixmap_size) - 0.5); 1518 + } 1519 + return obj; 1520 +} 1521 + 1522 +/// Returns an array of unloadable objects sorted by score. 1523 +static UnloadableObjectVec 1524 +gr_get_unloadable_objects_sorted_by_score(Milliseconds now) { 1525 + UnloadableObjectVec objects; 1526 + kv_init(objects); 1527 + Image *img = NULL; 1528 + ImagePlacement *placement = NULL; 1529 + kh_foreach_value(images, img, { 1530 + foreach_frame(*img, frame, { 1531 + if (frame->imlib_object) { 1532 + kv_push(UnloadableObject, objects, 1533 + gr_unloadable_object_for_frame(now, 1534 + frame)); 1535 + } 1536 + int frameidx = frame->index; 1537 + kh_foreach_value(img->placements, placement, { 1538 + if (!gr_get_frame_pixmap(placement, frameidx)) 1539 + continue; 1540 + kv_push(UnloadableObject, objects, 1541 + gr_unloadable_object_for_pixmap( 1542 + now, frame, placement)); 1543 + }); 1544 + }); 1545 + }); 1546 + qsort(objects.a, kv_size(objects), sizeof(UnloadableObject), 1547 + gr_cmp_unloadable_objects); 1548 + return objects; 1549 +} 1550 + 1551 +/// Returns the limit adjusted by the excess tolerance ratio. 1552 +static inline unsigned apply_tolerance(unsigned limit) { 1553 + return limit + (unsigned)(limit * graphics_excess_tolerance_ratio); 1554 +} 1555 + 1556 +/// Checks RAM and disk cache limits and deletes/unloads some images. 1557 +static void gr_check_limits() { 1558 + Milliseconds now = gr_now_ms(); 1559 + ImageVec images_sorted = {0}; 1560 + ImagePlacementVec placements_sorted = {0}; 1561 + ImageFrameVec frames_sorted = {0}; 1562 + UnloadableObjectVec objects_sorted = {0}; 1563 + int images_begin = 0; 1564 + int placements_begin = 0; 1565 + char changed = 0; 1566 + // First reduce the number of images if there are too many. 1567 + if (kh_size(images) > apply_tolerance(graphics_max_total_placements)) { 1568 + GR_LOG("Too many images: %d\n", kh_size(images)); 1569 + changed = 1; 1570 + images_sorted = gr_get_images_sorted_by_atime(); 1571 + int to_delete = kv_size(images_sorted) - 1572 + graphics_max_total_placements; 1573 + for (; images_begin < to_delete; images_begin++) 1574 + gr_delete_image(images_sorted.a[images_begin]); 1575 + } 1576 + // Then reduce the number of placements if there are too many. 1577 + if (total_placement_count > 1578 + apply_tolerance(graphics_max_total_placements)) { 1579 + GR_LOG("Too many placements: %d\n", total_placement_count); 1580 + changed = 1; 1581 + placements_sorted = gr_get_placements_sorted_by_atime(); 1582 + int to_delete = kv_size(placements_sorted) - 1583 + graphics_max_total_placements; 1584 + for (; placements_begin < to_delete; placements_begin++) { 1585 + ImagePlacement *placement = 1586 + placements_sorted.a[placements_begin]; 1587 + if (placement->protected_frame) 1588 + break; 1589 + gr_delete_placement(placement); 1590 + } 1591 + } 1592 + // Then reduce the size of the image file cache. The files correspond to 1593 + // image frames. 1594 + if (images_disk_size > 1595 + apply_tolerance(graphics_total_file_cache_size)) { 1596 + GR_LOG("Too big disk cache: %ld KiB\n", 1597 + images_disk_size / 1024); 1598 + changed = 1; 1599 + frames_sorted = gr_get_frames_sorted_by_atime(); 1600 + for (int i = 0; i < kv_size(frames_sorted); i++) { 1601 + if (images_disk_size <= graphics_total_file_cache_size) 1602 + break; 1603 + gr_delete_imagefile(kv_A(frames_sorted, i)); 1604 + } 1605 + } 1606 + // Then unload images from RAM. 1607 + if (images_ram_size > apply_tolerance(graphics_max_total_ram_size)) { 1608 + changed = 1; 1609 + int frames_begin = 0; 1610 + GR_LOG("Too much ram: %ld KiB\n", images_ram_size / 1024); 1611 + objects_sorted = gr_get_unloadable_objects_sorted_by_score(now); 1612 + for (int i = 0; i < kv_size(objects_sorted); i++) { 1613 + if (images_ram_size <= graphics_max_total_ram_size) 1614 + break; 1615 + gr_unload_object(&kv_A(objects_sorted, i)); 1616 + } 1617 + } 1618 + if (changed) { 1619 + Milliseconds end = gr_now_ms(); 1620 + GR_LOG("After cleaning: ram: %ld KiB disk: %ld KiB " 1621 + "img count: %d placement count: %d Took %ld ms\n", 1622 + images_ram_size / 1024, images_disk_size / 1024, 1623 + kh_size(images), total_placement_count, end - now); 1624 + } 1625 + kv_destroy(images_sorted); 1626 + kv_destroy(placements_sorted); 1627 + kv_destroy(frames_sorted); 1628 + kv_destroy(objects_sorted); 1629 +} 1630 + 1631 +/// Unloads all images by user request. 1632 +void gr_unload_images_to_reduce_ram() { 1633 + Image *img = NULL; 1634 + ImagePlacement *placement = NULL; 1635 + kh_foreach_value(images, img, { 1636 + kh_foreach_value(img->placements, placement, { 1637 + if (placement->protected_frame) 1638 + continue; 1639 + gr_unload_placement(placement); 1640 + }); 1641 + gr_unload_all_frames(img); 1642 + }); 1643 +} 1644 + 1645 +//////////////////////////////////////////////////////////////////////////////// 1646 +// Image loading. 1647 +//////////////////////////////////////////////////////////////////////////////// 1648 + 1649 +/// Copies `num_pixels` pixels (not bytes!) from a buffer `from` to an imlib2 1650 +/// image data `to`. The format may be 24 (RGB) or 32 (RGBA), and it's converted 1651 +/// to imlib2's representation, which is 0xAARRGGBB (having BGRA memory layout 1652 +/// on little-endian architectures). 1653 +static inline void gr_copy_pixels(DATA32 *to, unsigned char *from, int format, 1654 + size_t num_pixels) { 1655 + size_t pixel_size = format == 24 ? 3 : 4; 1656 + if (format == 32) { 1657 + for (unsigned i = 0; i < num_pixels; ++i) { 1658 + unsigned byte_i = i * pixel_size; 1659 + to[i] = ((DATA32)from[byte_i + 2]) | 1660 + ((DATA32)from[byte_i + 1]) << 8 | 1661 + ((DATA32)from[byte_i]) << 16 | 1662 + ((DATA32)from[byte_i + 3]) << 24; 1663 + } 1664 + } else { 1665 + for (unsigned i = 0; i < num_pixels; ++i) { 1666 + unsigned byte_i = i * pixel_size; 1667 + to[i] = ((DATA32)from[byte_i + 2]) | 1668 + ((DATA32)from[byte_i + 1]) << 8 | 1669 + ((DATA32)from[byte_i]) << 16 | 0xFF000000; 1670 + } 1671 + } 1672 +} 1673 + 1674 +/// Loads uncompressed RGB or RGBA image data from a file. 1675 +static void gr_load_raw_pixel_data_uncompressed(DATA32 *data, FILE *file, 1676 + int format, 1677 + size_t total_pixels) { 1678 + unsigned char chunk[BUFSIZ]; 1679 + size_t pixel_size = format == 24 ? 3 : 4; 1680 + size_t chunk_size_pix = BUFSIZ / 4; 1681 + size_t chunk_size_bytes = chunk_size_pix * pixel_size; 1682 + size_t bytes = total_pixels * pixel_size; 1683 + for (size_t chunk_start_pix = 0; chunk_start_pix < total_pixels; 1684 + chunk_start_pix += chunk_size_pix) { 1685 + size_t read_size = fread(chunk, 1, chunk_size_bytes, file); 1686 + size_t read_pixels = read_size / pixel_size; 1687 + if (chunk_start_pix + read_pixels > total_pixels) 1688 + read_pixels = total_pixels - chunk_start_pix; 1689 + gr_copy_pixels(data + chunk_start_pix, chunk, format, 1690 + read_pixels); 1691 + } 1692 +} 1693 + 1694 +#define COMPRESSED_CHUNK_SIZE BUFSIZ 1695 +#define DECOMPRESSED_CHUNK_SIZE (BUFSIZ * 4) 1696 + 1697 +/// Loads compressed RGB or RGBA image data from a file. 1698 +static int gr_load_raw_pixel_data_compressed(DATA32 *data, FILE *file, 1699 + int format, size_t total_pixels) { 1700 + size_t pixel_size = format == 24 ? 3 : 4; 1701 + unsigned char compressed_chunk[COMPRESSED_CHUNK_SIZE]; 1702 + unsigned char decompressed_chunk[DECOMPRESSED_CHUNK_SIZE]; 1703 + 1704 + z_stream strm; 1705 + strm.zalloc = Z_NULL; 1706 + strm.zfree = Z_NULL; 1707 + strm.opaque = Z_NULL; 1708 + strm.next_out = decompressed_chunk; 1709 + strm.avail_out = DECOMPRESSED_CHUNK_SIZE; 1710 + strm.avail_in = 0; 1711 + strm.next_in = Z_NULL; 1712 + int ret = inflateInit(&strm); 1713 + if (ret != Z_OK) 1714 + return 1; 1715 + 1716 + int error = 0; 1717 + int progress = 0; 1718 + size_t total_copied_pixels = 0; 1719 + while (1) { 1720 + // If we don't have enough data in the input buffer, try to read 1721 + // from the file. 1722 + if (strm.avail_in <= COMPRESSED_CHUNK_SIZE / 4) { 1723 + // Move the existing data to the beginning. 1724 + memmove(compressed_chunk, strm.next_in, strm.avail_in); 1725 + strm.next_in = compressed_chunk; 1726 + // Read more data. 1727 + size_t bytes_read = fread( 1728 + compressed_chunk + strm.avail_in, 1, 1729 + COMPRESSED_CHUNK_SIZE - strm.avail_in, file); 1730 + strm.avail_in += bytes_read; 1731 + if (bytes_read != 0) 1732 + progress = 1; 1733 + } 1734 + 1735 + // Try to inflate the data. 1736 + int ret = inflate(&strm, Z_SYNC_FLUSH); 1737 + if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) { 1738 + error = 1; 1739 + fprintf(stderr, 1740 + "error: could not decompress the image, error " 1741 + "%s\n", 1742 + ret == Z_MEM_ERROR ? "Z_MEM_ERROR" 1743 + : "Z_DATA_ERROR"); 1744 + break; 1745 + } 1746 + 1747 + // Copy the data from the output buffer to the image. 1748 + size_t full_pixels = 1749 + (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) / pixel_size; 1750 + // Make sure we don't overflow the image. 1751 + if (full_pixels > total_pixels - total_copied_pixels) 1752 + full_pixels = total_pixels - total_copied_pixels; 1753 + if (full_pixels > 0) { 1754 + // Copy pixels. 1755 + gr_copy_pixels(data, decompressed_chunk, format, 1756 + full_pixels); 1757 + data += full_pixels; 1758 + total_copied_pixels += full_pixels; 1759 + if (total_copied_pixels >= total_pixels) { 1760 + // We filled the whole image, there may be some 1761 + // data left, but we just truncate it. 1762 + break; 1763 + } 1764 + // Move the remaining data to the beginning. 1765 + size_t copied_bytes = full_pixels * pixel_size; 1766 + size_t leftover = 1767 + (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) - 1768 + copied_bytes; 1769 + memmove(decompressed_chunk, 1770 + decompressed_chunk + copied_bytes, leftover); 1771 + strm.next_out -= copied_bytes; 1772 + strm.avail_out += copied_bytes; 1773 + progress = 1; 1774 + } 1775 + 1776 + // If we haven't made any progress, then we have reached the end 1777 + // of both the file and the inflated data. 1778 + if (!progress) 1779 + break; 1780 + progress = 0; 1781 + } 1782 + 1783 + inflateEnd(&strm); 1784 + return error; 1785 +} 1786 + 1787 +#undef COMPRESSED_CHUNK_SIZE 1788 +#undef DECOMPRESSED_CHUNK_SIZE 1789 + 1790 +/// Load the image from a file containing raw pixel data (RGB or RGBA), the data 1791 +/// may be compressed. 1792 +static Imlib_Image gr_load_raw_pixel_data(ImageFrame *frame, 1793 + const char *filename) { 1794 + size_t total_pixels = frame->data_pix_width * frame->data_pix_height; 1795 + if (total_pixels * 4 > graphics_max_single_image_ram_size) { 1796 + fprintf(stderr, 1797 + "error: image %u frame %u is too big too load: %zu > %u\n", 1798 + frame->image->image_id, frame->index, total_pixels * 4, 1799 + graphics_max_single_image_ram_size); 1800 + return NULL; 1801 + } 1802 + 1803 + FILE* file = fopen(filename, "rb"); 1804 + if (!file) { 1805 + fprintf(stderr, 1806 + "error: could not open image file: %s\n", 1807 + sanitized_filename(filename)); 1808 + return NULL; 1809 + } 1810 + 1811 + Imlib_Image image = imlib_create_image(frame->data_pix_width, 1812 + frame->data_pix_height); 1813 + if (!image) { 1814 + fprintf(stderr, 1815 + "error: could not create an image of size %d x %d\n", 1816 + frame->data_pix_width, frame->data_pix_height); 1817 + fclose(file); 1818 + return NULL; 1819 + } 1820 + 1821 + imlib_context_set_image(image); 1822 + imlib_image_set_has_alpha(1); 1823 + DATA32* data = imlib_image_get_data(); 1824 + 1825 + if (frame->compression == 0) { 1826 + gr_load_raw_pixel_data_uncompressed(data, file, frame->format, 1827 + total_pixels); 1828 + } else { 1829 + int ret = gr_load_raw_pixel_data_compressed( 1830 + data, file, frame->format, total_pixels); 1831 + if (ret != 0) { 1832 + imlib_image_put_back_data(data); 1833 + imlib_free_image(); 1834 + fclose(file); 1835 + return NULL; 1836 + } 1837 + } 1838 + 1839 + fclose(file); 1840 + imlib_image_put_back_data(data); 1841 + return image; 1842 +} 1843 + 1844 +/// Loads the unscaled frame into RAM as an imlib object. The frame imlib object 1845 +/// is fully composed on top of the background frame. If the frame is already 1846 +/// loaded, does nothing. Loading may fail, in which case the status of the 1847 +/// frame will be set to STATUS_RAM_LOADING_ERROR. 1848 +static void gr_load_imlib_object(ImageFrame *frame) { 1849 + if (frame->imlib_object) 1850 + return; 1851 + 1852 + // If the image is uninitialized or uploading has failed, or the file 1853 + // has been deleted, we cannot load the image. 1854 + if (frame->status < STATUS_UPLOADING_SUCCESS) 1855 + return; 1856 + if (frame->disk_size == 0) { 1857 + // In some cases the original image file may still be available, 1858 + // try to restore it. 1859 + gr_try_restore_imagefile(frame); 1860 + } 1861 + if (frame->disk_size == 0) { 1862 + if (frame->status != STATUS_RAM_LOADING_ERROR && 1863 + frame->status >= STATUS_UPLOADING_SUCCESS) { 1864 + fprintf(stderr, 1865 + "error: cached image was deleted: %u frame %u\n", 1866 + frame->image->image_id, frame->index); 1867 + } 1868 + frame->status = STATUS_RAM_LOADING_ERROR; 1869 + return; 1870 + } 1871 + 1872 + // Prevent recursive dependences between frames. 1873 + if (frame->ram_loading_in_progress) { 1874 + if (frame->status != STATUS_RAM_LOADING_ERROR) { 1875 + fprintf(stderr, 1876 + "error: recursive loading of image %u frame " 1877 + "%u\n", 1878 + frame->image->image_id, frame->index); 1879 + } 1880 + frame->status = STATUS_RAM_LOADING_ERROR; 1881 + return; 1882 + } 1883 + frame->ram_loading_in_progress = 1; 1884 + 1885 + // Load the background frame if needed. Hopefully it's not recursive. 1886 + ImageFrame *bg_frame = NULL; 1887 + if (frame->background_frame_index) { 1888 + bg_frame = gr_get_frame(frame->image, 1889 + frame->background_frame_index); 1890 + if (!bg_frame) { 1891 + if (frame->status != STATUS_RAM_LOADING_ERROR) { 1892 + fprintf(stderr, 1893 + "error: could not find background " 1894 + "frame %d for image %u frame %d\n", 1895 + frame->background_frame_index, 1896 + frame->image->image_id, frame->index); 1897 + frame->status = STATUS_RAM_LOADING_ERROR; 1898 + } 1899 + return; 1900 + } 1901 + gr_load_imlib_object(bg_frame); 1902 + if (!bg_frame->imlib_object) { 1903 + if (frame->status != STATUS_RAM_LOADING_ERROR) { 1904 + fprintf(stderr, 1905 + "error: could not load background " 1906 + "frame %d for image %u frame %d\n", 1907 + frame->background_frame_index, 1908 + frame->image->image_id, frame->index); 1909 + } 1910 + frame->status = STATUS_RAM_LOADING_ERROR; 1911 + return; 1912 + } 1913 + } 1914 + 1915 + frame->ram_loading_in_progress = 0; 1916 + 1917 + // We exclude background frames from the time to load the frame. 1918 + Milliseconds loading_start = gr_now_ms(); 1919 + 1920 + // Load the frame data image. 1921 + Imlib_Image frame_data_image = NULL; 1922 + char filename[MAX_FILENAME_SIZE]; 1923 + gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 1924 + GR_LOG("Loading image: %s\n", sanitized_filename(filename)); 1925 + if (frame->format == 100) 1926 + frame_data_image = imlib_load_image(filename); 1927 + if (frame->format == 32 || frame->format == 24) 1928 + frame_data_image = gr_load_raw_pixel_data(frame, filename); 1929 + debug_loaded_files_counter++; 1930 + 1931 + if (!frame_data_image) { 1932 + if (frame->status != STATUS_RAM_LOADING_ERROR) { 1933 + fprintf(stderr, "error: could not load image: %s\n", 1934 + sanitized_filename(filename)); 1935 + } 1936 + frame->status = STATUS_RAM_LOADING_ERROR; 1937 + return; 1938 + } 1939 + 1940 + imlib_context_set_image(frame_data_image); 1941 + int frame_data_width = imlib_image_get_width(); 1942 + int frame_data_height = imlib_image_get_height(); 1943 + 1944 + // Check that the size of the image we are loading does not exceed the 1945 + // limit. 1946 + if (frame_data_width * frame_data_height * 4 > 1947 + graphics_max_single_image_ram_size) { 1948 + if (frame->status != STATUS_RAM_LOADING_ERROR) { 1949 + fprintf(stderr, 1950 + "error: image %u frame %u is too big too load: " 1951 + "%d x %d * 4 = %d > %u\n", 1952 + frame->image->image_id, frame->index, 1953 + frame_data_width, frame_data_height, 1954 + frame_data_width * frame_data_height * 4, 1955 + graphics_max_single_image_ram_size); 1956 + } 1957 + imlib_free_image(); 1958 + frame->status = STATUS_RAM_LOADING_ERROR; 1959 + return; 1960 + } 1961 + 1962 + GR_LOG("Successfully loaded, size %d x %d\n", frame_data_width, 1963 + frame_data_height); 1964 + // If imlib loading succeeded, and it is the first frame, set the 1965 + // information about the original image size, unless it's already set. 1966 + if (frame->index == 1 && frame->image->pix_width == 0 && 1967 + frame->image->pix_height == 0) { 1968 + frame->image->pix_width = frame_data_width; 1969 + frame->image->pix_height = frame_data_height; 1970 + } 1971 + 1972 + int image_width = frame->image->pix_width; 1973 + int image_height = frame->image->pix_height; 1974 + 1975 + // Compose the image with the background color or frame. 1976 + if (frame->background_color != 0 || bg_frame || 1977 + image_width != frame_data_width || 1978 + image_height != frame_data_height) { 1979 + GR_LOG("Composing the frame bg = 0x%08X, bgframe = %d\n", 1980 + frame->background_color, frame->background_frame_index); 1981 + Imlib_Image composed_image = imlib_create_image( 1982 + image_width, image_height); 1983 + imlib_context_set_image(composed_image); 1984 + imlib_image_set_has_alpha(1); 1985 + imlib_context_set_anti_alias(0); 1986 + 1987 + // Start with the background frame or color. 1988 + imlib_context_set_blend(0); 1989 + if (bg_frame && bg_frame->imlib_object) { 1990 + imlib_blend_image_onto_image( 1991 + bg_frame->imlib_object, 1, 0, 0, 1992 + image_width, image_height, 0, 0, 1993 + image_width, image_height); 1994 + } else { 1995 + int r = (frame->background_color >> 24) & 0xFF; 1996 + int g = (frame->background_color >> 16) & 0xFF; 1997 + int b = (frame->background_color >> 8) & 0xFF; 1998 + int a = frame->background_color & 0xFF; 1999 + imlib_context_set_color(r, g, b, a); 2000 + imlib_image_fill_rectangle(0, 0, image_width, 2001 + image_height); 2002 + } 2003 + 2004 + // Blend the frame data image onto the background. 2005 + imlib_context_set_blend(1); 2006 + imlib_blend_image_onto_image( 2007 + frame_data_image, 1, 0, 0, frame->data_pix_width, 2008 + frame->data_pix_height, frame->x, frame->y, 2009 + frame->data_pix_width, frame->data_pix_height); 2010 + 2011 + // Free the frame data image. 2012 + imlib_context_set_image(frame_data_image); 2013 + imlib_free_image(); 2014 + 2015 + frame_data_image = composed_image; 2016 + } 2017 + 2018 + frame->imlib_object = frame_data_image; 2019 + 2020 + images_ram_size += gr_frame_current_ram_size(frame); 2021 + frame->status = STATUS_RAM_LOADING_SUCCESS; 2022 + 2023 + Milliseconds loading_end = gr_now_ms(); 2024 + GR_LOG("After loading image %u frame %d ram: %ld KiB (+ %u KiB) Took " 2025 + "%ld ms\n", 2026 + frame->image->image_id, frame->index, images_ram_size / 1024, 2027 + gr_frame_current_ram_size(frame) / 1024, 2028 + loading_end - loading_start); 2029 +} 2030 + 2031 +/// Premultiplies the alpha channel of the image data. The data is an array of 2032 +/// pixels such that each pixel is a 32-bit integer in the format 0xAARRGGBB. 2033 +static void gr_premultiply_alpha(DATA32 *data, size_t num_pixels) { 2034 + for (size_t i = 0; i < num_pixels; ++i) { 2035 + DATA32 pixel = data[i]; 2036 + unsigned char a = pixel >> 24; 2037 + if (a == 0) { 2038 + data[i] = 0; 2039 + } else if (a != 255) { 2040 + unsigned char b = (pixel & 0xFF) * a / 255; 2041 + unsigned char g = ((pixel >> 8) & 0xFF) * a / 255; 2042 + unsigned char r = ((pixel >> 16) & 0xFF) * a / 255; 2043 + data[i] = (a << 24) | (r << 16) | (g << 8) | b; 2044 + } 2045 + } 2046 +} 2047 + 2048 +/// Computes the pixmap transformation, which is essentially the destination 2049 +/// rectangle for drawing an image placement in a box of size `cols*cw` x 2050 +/// `rows*ch`. The destination rectangle depends on the parameters of the 2051 +/// placement, like scaling mode. The computed transformation is stored in the 2052 +/// placement. 2053 +void gr_compute_pixmap_transformation(ImagePlacement *placement) { 2054 + // Infer the placement size if needed. 2055 + gr_infer_placement_size_maybe(placement); 2056 + 2057 + // The size of the box in which the image is placed. 2058 + int box_w = (int)placement->cols * placement->scaled_cw; 2059 + int box_h = (int)placement->rows * placement->scaled_ch; 2060 + 2061 + int src_w = placement->src_pix_width; 2062 + int src_h = placement->src_pix_height; 2063 + 2064 + // Whether the box is too small to use the true size of the image. 2065 + char box_too_small = box_w < src_w || box_h < src_h; 2066 + char mode = placement->scale_mode; 2067 + 2068 + PixmapTransformation *tr = &placement->pixmap_transformation; 2069 + 2070 + if (src_w <= 0 || src_h <= 0) { 2071 + tr->dst_x = tr->dst_y = tr->dst_w = tr->dst_h = 0; 2072 + } else if (mode == SCALE_MODE_FILL) { 2073 + tr->dst_x = tr->dst_y = 0; 2074 + tr->dst_w = box_w; 2075 + tr->dst_h = box_h; 2076 + } else if (mode == SCALE_MODE_NONE || 2077 + (mode == SCALE_MODE_NONE_OR_CONTAIN && !box_too_small)) { 2078 + tr->dst_x = tr->dst_y = 0; 2079 + tr->dst_w = src_w; 2080 + tr->dst_h = src_h; 2081 + } else { 2082 + if (mode != SCALE_MODE_CONTAIN && 2083 + mode != SCALE_MODE_NONE_OR_CONTAIN) { 2084 + fprintf(stderr, 2085 + "warning: unknown scale mode %u, using " 2086 + "'contain' instead\n", 2087 + mode); 2088 + } 2089 + if (box_w * src_h > src_w * box_h) { 2090 + // If the box is wider than the original image, fit to 2091 + // height. 2092 + tr->dst_h = box_h; 2093 + tr->dst_y = 0; 2094 + tr->dst_w = src_w * box_h / src_h; 2095 + tr->dst_x = (box_w - tr->dst_w) / 2; 2096 + } else { 2097 + // Otherwise, fit to width. 2098 + tr->dst_w = box_w; 2099 + tr->dst_x = 0; 2100 + tr->dst_h = src_h * box_w / src_w; 2101 + tr->dst_y = (box_h - tr->dst_h) / 2; 2102 + } 2103 + } 2104 + 2105 + // Make sure that the size of the destination image is non-zero. 2106 + tr->dst_w = MAX(1, tr->dst_w); 2107 + tr->dst_h = MAX(1, tr->dst_h); 2108 + 2109 + // Normally we want the pixmap to be exactly the size of the destination 2110 + // rectangle. 2111 + tr->pixmap_w = tr->dst_w; 2112 + tr->pixmap_h = tr->dst_h; 2113 + // However, if the pixmap would be larger than the source image, use the 2114 + // source image size. The upscaling will be done by XRender then. 2115 + if (tr->pixmap_w * tr->pixmap_h > src_w * src_h) { 2116 + tr->pixmap_w = MAX(1, src_w); 2117 + tr->pixmap_h = MAX(1, src_h); 2118 + } 2119 + 2120 + // If the pixmap would be over the limit, scale it down. 2121 + if (tr->pixmap_w * tr->pixmap_h * 4 > 2122 + graphics_max_single_image_ram_size) { 2123 + double scale = sqrt((double)graphics_max_single_image_ram_size / 2124 + (tr->pixmap_w * tr->pixmap_h * 4)); 2125 + tr->pixmap_w = MAX(1, (int)(tr->pixmap_w * scale)); 2126 + tr->pixmap_h = MAX(1, (int)(tr->pixmap_h * scale)); 2127 + } 2128 +} 2129 + 2130 +/// Creates and returns a scaled imlib image for the image placement. The 2131 +/// original imlib object must be loaded and the placement size must be inferred 2132 +/// by the caller (by calling `gr_compute_pixmap_transformation`). The caller is 2133 +/// responsible for freeing the returned image. 2134 +Imlib_Image gr_create_scaled_image_object(ImagePlacement *placement, 2135 + ImageFrame *frame) { 2136 + // The source rectangle (inside the original image). 2137 + int src_x = placement->src_pix_x; 2138 + int src_y = placement->src_pix_y; 2139 + int src_w = placement->src_pix_width; 2140 + int src_h = placement->src_pix_height; 2141 + // The pixmap dimensions. 2142 + int pixmap_w = placement->pixmap_transformation.pixmap_w; 2143 + int pixmap_h = placement->pixmap_transformation.pixmap_h; 2144 + 2145 + if (pixmap_w * pixmap_h * 4 > graphics_max_single_image_ram_size) { 2146 + fprintf(stderr, 2147 + "error: placement %u/%u would be too big to load: %d x " 2148 + "%d x 4 > %u\n", 2149 + placement->image->image_id, placement->placement_id, 2150 + pixmap_w, pixmap_h, graphics_max_single_image_ram_size); 2151 + return 0; 2152 + } 2153 + 2154 + if (pixmap_w == 0 || pixmap_h == 0) 2155 + fprintf(stderr, "warning: image of zero size\n"); 2156 + 2157 + imlib_context_set_image(frame->imlib_object); 2158 + imlib_context_set_anti_alias(1); 2159 + imlib_context_set_blend(1); 2160 + 2161 + return imlib_create_cropped_scaled_image(src_x, src_y, src_w, src_h, 2162 + pixmap_w, pixmap_h); 2163 +} 2164 + 2165 +/// Creates a pixmap for the frame of an image placement. The pixmap contains 2166 +/// the image data correctly scaled and fit to the box defined by the number of 2167 +/// rows/columns of the image placement and the provided cell dimensions in 2168 +/// pixels. If the placement is already loaded, it will be reloaded only if the 2169 +/// cell dimensions have changed. 2170 +Pixmap gr_load_pixmap(ImagePlacement *placement, int frameidx, int cw, int ch) { 2171 + Milliseconds loading_start = gr_now_ms(); 2172 + Image *img = placement->image; 2173 + ImageFrame *frame = gr_get_frame(img, frameidx); 2174 + 2175 + // Update the atime unconditionally. 2176 + gr_touch_placement(placement); 2177 + if (frame) 2178 + gr_touch_frame(frame); 2179 + 2180 + // If cw or ch are different, unload all the pixmaps and recompute the 2181 + // pixmap geometry and transformation (shared for all pixmaps). 2182 + if (placement->scaled_cw != cw || placement->scaled_ch != ch) { 2183 + gr_unload_placement(placement); 2184 + placement->scaled_cw = cw; 2185 + placement->scaled_ch = ch; 2186 + gr_compute_pixmap_transformation(placement); 2187 + } 2188 + 2189 + // If it's already loaded, do nothing. 2190 + Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); 2191 + if (pixmap) 2192 + return pixmap; 2193 + 2194 + GR_LOG("Loading placement: %u/%u frame %u\n", img->image_id, 2195 + placement->placement_id, frameidx); 2196 + 2197 + if (placement->pixmap_transformation.pixmap_w == 0 || 2198 + placement->pixmap_transformation.pixmap_h == 0) { 2199 + GR_LOG("Not loading because the pixmap size is zero\n"); 2200 + return 0; 2201 + } 2202 + 2203 + // Load the imlib object for the frame. 2204 + if (!frame) { 2205 + fprintf(stderr, 2206 + "error: could not find frame %u for image %u\n", 2207 + frameidx, img->image_id); 2208 + return 0; 2209 + } 2210 + gr_load_imlib_object(frame); 2211 + if (!frame->imlib_object) 2212 + return 0; 2213 + 2214 + // Create the scaled image. This is temporary, we will upload to the X 2215 + // server, and then delete immediately. 2216 + Imlib_Image scaled_image = 2217 + gr_create_scaled_image_object(placement, frame); 2218 + if (!scaled_image) 2219 + return 0; 2220 + imlib_context_set_image(scaled_image); 2221 + int pixmap_w = imlib_image_get_width(); 2222 + int pixmap_h = imlib_image_get_height(); 2223 + 2224 + // XRender needs the alpha channel premultiplied. 2225 + DATA32 *data = imlib_image_get_data(); 2226 + gr_premultiply_alpha(data, pixmap_w * pixmap_h); 2227 + 2228 + // Upload the image to the X server. 2229 + Display *disp = imlib_context_get_display(); 2230 + Visual *vis = imlib_context_get_visual(); 2231 + Colormap cmap = imlib_context_get_colormap(); 2232 + Drawable drawable = imlib_context_get_drawable(); 2233 + if (!drawable) 2234 + drawable = DefaultRootWindow(disp); 2235 + pixmap = XCreatePixmap(disp, drawable, pixmap_w, pixmap_h, 32); 2236 + XVisualInfo visinfo = {0}; 2237 + Status visual_found = XMatchVisualInfo(disp, DefaultScreen(disp), 32, 2238 + TrueColor, &visinfo) || 2239 + XMatchVisualInfo(disp, DefaultScreen(disp), 24, 2240 + TrueColor, &visinfo); 2241 + if (!visual_found) { 2242 + fprintf(stderr, 2243 + "error: could not find 32-bit TrueColor visual\n"); 2244 + // Proceed anyway. 2245 + visinfo.visual = NULL; 2246 + } 2247 + XImage *ximage = XCreateImage(disp, visinfo.visual, 32, ZPixmap, 0, 2248 + (char *)data, pixmap_w, pixmap_h, 32, 0); 2249 + if (!ximage) { 2250 + fprintf(stderr, "error: could not create XImage\n"); 2251 + imlib_image_put_back_data(data); 2252 + imlib_free_image(); 2253 + return 0; 2254 + } 2255 + GC gc = XCreateGC(disp, pixmap, 0, NULL); 2256 + XPutImage(disp, pixmap, gc, ximage, 0, 0, 0, 0, pixmap_w, pixmap_h); 2257 + XFreeGC(disp, gc); 2258 + // XDestroyImage will free the data as well, but it is managed by imlib, 2259 + // so set it to NULL. 2260 + ximage->data = NULL; 2261 + XDestroyImage(ximage); 2262 + imlib_image_put_back_data(data); 2263 + imlib_free_image(); 2264 + 2265 + // Assign the pixmap to the frame and increase the ram size. 2266 + gr_set_frame_pixmap(placement, frameidx, pixmap); 2267 + images_ram_size += gr_placement_single_frame_ram_size(placement); 2268 + debug_loaded_pixmaps_counter++; 2269 + 2270 + Milliseconds loading_end = gr_now_ms(); 2271 + GR_LOG("After loading placement %u/%u frame %d ram: %ld KiB (+ %u " 2272 + "KiB) Took %ld ms\n", 2273 + frame->image->image_id, placement->placement_id, frame->index, 2274 + images_ram_size / 1024, 2275 + gr_placement_single_frame_ram_size(placement) / 1024, 2276 + loading_end - loading_start); 2277 + 2278 + // Free up ram if needed, but keep the pixmap we've loaded no matter 2279 + // what. 2280 + placement->protected_frame = frameidx; 2281 + gr_check_limits(); 2282 + placement->protected_frame = 0; 2283 + 2284 + return pixmap; 2285 +} 2286 + 2287 +//////////////////////////////////////////////////////////////////////////////// 2288 +// Initialization and deinitialization. 2289 +//////////////////////////////////////////////////////////////////////////////// 2290 + 2291 +/// Creates a temporary directory. 2292 +static int gr_create_cache_dir() { 2293 + strncpy(cache_dir, graphics_cache_dir_template, sizeof(cache_dir)); 2294 + if (!mkdtemp(cache_dir)) { 2295 + fprintf(stderr, 2296 + "error: could not create temporary dir from template " 2297 + "%s\n", 2298 + sanitized_filename(cache_dir)); 2299 + return 0; 2300 + } 2301 + fprintf(stderr, "Graphics cache directory: %s\n", cache_dir); 2302 + return 1; 2303 +} 2304 + 2305 +/// Checks whether `tmp_dir` exists and recreates it if it doesn't. 2306 +static void gr_make_sure_tmpdir_exists() { 2307 + struct stat st; 2308 + if (stat(cache_dir, &st) == 0 && S_ISDIR(st.st_mode)) 2309 + return; 2310 + fprintf(stderr, 2311 + "error: %s is not a directory, will need to create a new " 2312 + "graphics cache directory\n", 2313 + sanitized_filename(cache_dir)); 2314 + gr_create_cache_dir(); 2315 +} 2316 + 2317 +/// Initialize the graphics module. 2318 +void gr_init(Display *disp, Visual *vis, Colormap cm) { 2319 + // Set the initialization time. 2320 + clock_gettime(CLOCK_MONOTONIC, &initialization_time); 2321 + 2322 + // Create the temporary dir. 2323 + if (!gr_create_cache_dir()) 2324 + abort(); 2325 + 2326 + // Initialize imlib. 2327 + imlib_context_set_display(disp); 2328 + imlib_context_set_visual(vis); 2329 + imlib_context_set_colormap(cm); 2330 + imlib_context_set_anti_alias(1); 2331 + imlib_context_set_blend(1); 2332 + // Imlib2 checks only the file name when caching, which is not enough 2333 + // for us since we reuse file names. Disable caching. 2334 + imlib_set_cache_size(0); 2335 + 2336 + // Prepare for color inversion. 2337 + for (size_t i = 0; i < 256; ++i) 2338 + reverse_table[i] = 255 - i; 2339 + 2340 + // Create data structures. 2341 + images = kh_init(id2image); 2342 + kv_init(next_redraw_times); 2343 + 2344 + atexit(gr_deinit); 2345 +} 2346 + 2347 +/// Deinitialize the graphics module. 2348 +void gr_deinit() { 2349 + // Remove the cache dir. 2350 + remove(cache_dir); 2351 + kv_destroy(next_redraw_times); 2352 + if (images) { 2353 + // Delete all images. 2354 + gr_delete_all_images(); 2355 + // Destroy the data structures. 2356 + kh_destroy(id2image, images); 2357 + images = NULL; 2358 + } 2359 +} 2360 + 2361 +//////////////////////////////////////////////////////////////////////////////// 2362 +// Dumping, debugging, and image preview. 2363 +//////////////////////////////////////////////////////////////////////////////// 2364 + 2365 +/// Returns a string containing a time difference in a human-readable format. 2366 +/// Uses a static buffer, so be careful. 2367 +static const char *gr_ago(Milliseconds diff) { 2368 + static char result[32]; 2369 + double seconds = (double)diff / 1000.0; 2370 + if (seconds < 1) 2371 + snprintf(result, sizeof(result), "%.2f sec ago", seconds); 2372 + else if (seconds < 60) 2373 + snprintf(result, sizeof(result), "%d sec ago", (int)seconds); 2374 + else if (seconds < 3600) 2375 + snprintf(result, sizeof(result), "%d min %d sec ago", 2376 + (int)(seconds / 60), (int)(seconds) % 60); 2377 + else { 2378 + snprintf(result, sizeof(result), "%d hr %d min %d sec ago", 2379 + (int)(seconds / 3600), (int)(seconds) % 3600 / 60, 2380 + (int)(seconds) % 60); 2381 + } 2382 + return result; 2383 +} 2384 + 2385 +/// Prints to `file` with an indentation of `ind` spaces. 2386 +static void fprintf_ind(FILE *file, int ind, const char *format, ...) { 2387 + fprintf(file, "%*s", ind, ""); 2388 + va_list args; 2389 + va_start(args, format); 2390 + vfprintf(file, format, args); 2391 + va_end(args); 2392 +} 2393 + 2394 +/// Dumps the image info to `file` with an indentation of `ind` spaces. 2395 +static void gr_dump_image_info(FILE *file, Image *img, int ind) { 2396 + if (!img) { 2397 + fprintf_ind(file, ind, "Image is NULL\n"); 2398 + return; 2399 + } 2400 + Milliseconds now = gr_now_ms(); 2401 + fprintf_ind(file, ind, "Image %u\n", img->image_id); 2402 + ind += 4; 2403 + fprintf_ind(file, ind, "number: %u\n", img->image_number); 2404 + fprintf_ind(file, ind, "global command index: %lu\n", 2405 + img->global_command_index); 2406 + fprintf_ind(file, ind, "accessed: %ld %s\n", img->atime, 2407 + gr_ago(now - img->atime)); 2408 + fprintf_ind(file, ind, "pix size: %ux%u\n", img->pix_width, 2409 + img->pix_height); 2410 + fprintf_ind(file, ind, "cur frame start time: %ld %s\n", 2411 + img->current_frame_time, 2412 + gr_ago(now - img->current_frame_time)); 2413 + if (img->next_redraw) 2414 + fprintf_ind(file, ind, "next redraw: %ld in %ld ms\n", 2415 + img->next_redraw, img->next_redraw - now); 2416 + fprintf_ind(file, ind, "total disk size: %u KiB\n", 2417 + img->total_disk_size / 1024); 2418 + fprintf_ind(file, ind, "total duration: %d\n", img->total_duration); 2419 + fprintf_ind(file, ind, "frames: %d\n", gr_last_frame_index(img)); 2420 + fprintf_ind(file, ind, "cur frame: %d\n", img->current_frame); 2421 + fprintf_ind(file, ind, "animation state: %d\n", img->animation_state); 2422 + fprintf_ind(file, ind, "default_placement: %u\n", 2423 + img->default_placement); 2424 +} 2425 + 2426 +/// Dumps the frame info to `file` with an indentation of `ind` spaces. 2427 +static void gr_dump_frame_info(FILE *file, ImageFrame *frame, int ind) { 2428 + if (!frame) { 2429 + fprintf_ind(file, ind, "Frame is NULL\n"); 2430 + return; 2431 + } 2432 + Milliseconds now = gr_now_ms(); 2433 + fprintf_ind(file, ind, "Frame %d\n", frame->index); 2434 + ind += 4; 2435 + if (frame->index == 0) { 2436 + fprintf_ind(file, ind, "NOT INITIALIZED\n"); 2437 + return; 2438 + } 2439 + if (frame->original_filename) { 2440 + fprintf_ind(file, ind, "original filename (sanitized): %s\n", 2441 + sanitized_filename(frame->original_filename)); 2442 + fprintf_ind(file, ind, "original file %s\n", 2443 + gr_is_original_file_still_available(frame) 2444 + ? "is still available" 2445 + : "is NOT available anymore"); 2446 + } 2447 + if (frame->uploading_failure) 2448 + fprintf_ind(file, ind, "uploading failure: %s\n", 2449 + image_uploading_failure_strings 2450 + [frame->uploading_failure]); 2451 + fprintf_ind(file, ind, "gap: %d\n", frame->gap); 2452 + fprintf_ind(file, ind, "accessed: %ld %s\n", frame->atime, 2453 + gr_ago(now - frame->atime)); 2454 + fprintf_ind(file, ind, "data pix size: %ux%u\n", frame->data_pix_width, 2455 + frame->data_pix_height); 2456 + char filename[MAX_FILENAME_SIZE]; 2457 + gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 2458 + if (access(filename, F_OK) != -1) 2459 + fprintf_ind(file, ind, "file: %s\n", 2460 + sanitized_filename(filename)); 2461 + else 2462 + fprintf_ind(file, ind, "not on disk\n"); 2463 + fprintf_ind(file, ind, "disk size: %u KiB\n", frame->disk_size / 1024); 2464 + if (frame->imlib_object) { 2465 + unsigned ram_size = gr_frame_current_ram_size(frame); 2466 + fprintf_ind(file, ind, 2467 + "loaded into ram, size: %d " 2468 + "KiB\n", 2469 + ram_size / 1024); 2470 + } else { 2471 + fprintf_ind(file, ind, "not loaded into ram\n"); 2472 + } 2473 +} 2474 + 2475 +/// Dumps the placement info to `file` with an indentation of `ind` spaces. 2476 +static void gr_dump_placement_info(FILE *file, ImagePlacement *placement, 2477 + int ind) { 2478 + if (!placement) { 2479 + fprintf_ind(file, ind, "Placement is NULL\n"); 2480 + return; 2481 + } 2482 + Milliseconds now = gr_now_ms(); 2483 + fprintf_ind(file, ind, "Placement %u\n", placement->placement_id); 2484 + ind += 4; 2485 + fprintf_ind(file, ind, "accessed: %ld %s\n", placement->atime, 2486 + gr_ago(now - placement->atime)); 2487 + fprintf_ind(file, ind, "scale_mode: %u\n", placement->scale_mode); 2488 + fprintf_ind(file, ind, "size: %u cols x %u rows\n", placement->cols, 2489 + placement->rows); 2490 + fprintf_ind(file, ind, "cell size: %ux%u\n", placement->scaled_cw, 2491 + placement->scaled_ch); 2492 + PixmapTransformation *tr = &placement->pixmap_transformation; 2493 + fprintf_ind(file, ind, "pixmap size: %ux%u\n", tr->pixmap_w, 2494 + tr->pixmap_h); 2495 + fprintf_ind(file, ind, "dst size: %ux%u offset: (%d, %d)\n", tr->dst_w, 2496 + tr->dst_h, tr->dst_x, tr->dst_y); 2497 + fprintf_ind(file, ind, "ram per frame: %u KiB\n", 2498 + gr_placement_single_frame_ram_size(placement) / 1024); 2499 + unsigned ram_size = gr_placement_current_ram_size(placement); 2500 + fprintf_ind(file, ind, "ram size: %d KiB\n", ram_size / 1024); 2501 +} 2502 + 2503 +/// Dumps placement pixmaps to `file` with an indentation of `ind` spaces. 2504 +static void gr_dump_placement_pixmaps(FILE *file, ImagePlacement *placement, 2505 + int ind) { 2506 + if (!placement) 2507 + return; 2508 + int frameidx = 1; 2509 + foreach_pixmap(*placement, pixmap, { 2510 + fprintf_ind(file, ind, "Frame %d pixmap %lu\n", frameidx, 2511 + pixmap); 2512 + ++frameidx; 2513 + }); 2514 +} 2515 + 2516 +/// Dumps the internal state (images and placements) to stderr. 2517 +void gr_dump_state() { 2518 + FILE *file = stderr; 2519 + int ind = 0; 2520 + fprintf_ind(file, ind, "======= Graphics module state dump =======\n"); 2521 + fprintf_ind(file, ind, 2522 + "sizeof(Image) = %lu sizeof(ImageFrame) = %lu " 2523 + "sizeof(ImagePlacement) = %lu\n", 2524 + sizeof(Image), sizeof(ImageFrame), sizeof(ImagePlacement)); 2525 + fprintf_ind(file, ind, "Image count: %u\n", kh_size(images)); 2526 + fprintf_ind(file, ind, "Placement count: %u\n", total_placement_count); 2527 + fprintf_ind(file, ind, "Estimated RAM usage: %ld KiB\n", 2528 + images_ram_size / 1024); 2529 + fprintf_ind(file, ind, "Estimated Disk usage: %ld KiB\n", 2530 + images_disk_size / 1024); 2531 + 2532 + Milliseconds now = gr_now_ms(); 2533 + 2534 + int64_t images_ram_size_computed = 0; 2535 + int64_t images_disk_size_computed = 0; 2536 + 2537 + Image *img = NULL; 2538 + ImagePlacement *placement = NULL; 2539 + kh_foreach_value(images, img, { 2540 + fprintf_ind(file, ind, "----------------\n"); 2541 + gr_dump_image_info(file, img, 0); 2542 + int64_t total_disk_size_computed = 0; 2543 + int total_duration_computed = 0; 2544 + foreach_frame(*img, frame, { 2545 + gr_dump_frame_info(file, frame, 4); 2546 + if (frame->image != img) 2547 + fprintf_ind(file, 8, 2548 + "ERROR: WRONG IMAGE POINTER\n"); 2549 + total_duration_computed += frame->gap; 2550 + images_disk_size_computed += frame->disk_size; 2551 + total_disk_size_computed += frame->disk_size; 2552 + if (frame->imlib_object) 2553 + images_ram_size_computed += 2554 + gr_frame_current_ram_size(frame); 2555 + }); 2556 + if (img->total_disk_size != total_disk_size_computed) { 2557 + fprintf_ind(file, ind, 2558 + " ERROR: total_disk_size is %u, but " 2559 + "computed value is %ld\n", 2560 + img->total_disk_size, total_disk_size_computed); 2561 + } 2562 + if (img->total_duration != total_duration_computed) { 2563 + fprintf_ind(file, ind, 2564 + " ERROR: total_duration is %d, but computed " 2565 + "value is %d\n", 2566 + img->total_duration, total_duration_computed); 2567 + } 2568 + kh_foreach_value(img->placements, placement, { 2569 + gr_dump_placement_info(file, placement, 4); 2570 + if (placement->image != img) 2571 + fprintf_ind(file, 8, 2572 + "ERROR: WRONG IMAGE POINTER\n"); 2573 + fprintf_ind(file, 8, 2574 + "Pixmaps:\n"); 2575 + gr_dump_placement_pixmaps(file, placement, 12); 2576 + unsigned ram_size = 2577 + gr_placement_current_ram_size(placement); 2578 + images_ram_size_computed += ram_size; 2579 + }); 2580 + }); 2581 + if (images_ram_size != images_ram_size_computed) { 2582 + fprintf_ind(file, ind, 2583 + "ERROR: images_ram_size is %ld, but computed value " 2584 + "is %ld\n", 2585 + images_ram_size, images_ram_size_computed); 2586 + } 2587 + if (images_disk_size != images_disk_size_computed) { 2588 + fprintf_ind(file, ind, 2589 + "ERROR: images_disk_size is %ld, but computed value " 2590 + "is %ld\n", 2591 + images_disk_size, images_disk_size_computed); 2592 + } 2593 + fprintf_ind(file, ind, "===========================================\n"); 2594 +} 2595 + 2596 +/// Executes `command` with the name of the file corresponding to `image_id` as 2597 +/// the argument. Executes xmessage with an error message on failure. 2598 +// TODO: Currently we do this for the first frame only. Not sure what to do with 2599 +// animations. 2600 +void gr_preview_image(uint32_t image_id, const char *exec) { 2601 + char command[256]; 2602 + size_t len; 2603 + Image *img = gr_find_image(image_id); 2604 + if (img) { 2605 + ImageFrame *frame = &img->first_frame; 2606 + char filename[MAX_FILENAME_SIZE]; 2607 + gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 2608 + if (frame->disk_size == 0) { 2609 + len = snprintf(command, 255, 2610 + "xmessage 'Image with id=%u is not " 2611 + "fully copied to %s'", 2612 + image_id, sanitized_filename(filename)); 2613 + } else { 2614 + len = snprintf(command, 255, "%s %s &", exec, 2615 + sanitized_filename(filename)); 2616 + } 2617 + } else { 2618 + len = snprintf(command, 255, 2619 + "xmessage 'Cannot find image with id=%u'", 2620 + image_id); 2621 + } 2622 + if (len > 255) { 2623 + fprintf(stderr, "error: command too long: %s\n", command); 2624 + snprintf(command, 255, "xmessage 'error: command too long'"); 2625 + } 2626 + if (system(command) != 0) { 2627 + fprintf(stderr, "error: could not execute command %s\n", 2628 + command); 2629 + } 2630 +} 2631 + 2632 +/// Executes `<st> -e less <file>` where <file> is the name of a temporary file 2633 +/// containing the information about an image and placement, and <st> is 2634 +/// specified with `st_executable`. 2635 +void gr_show_image_info(uint32_t image_id, uint32_t placement_id, 2636 + uint32_t imgcol, uint32_t imgrow, 2637 + char is_classic_placeholder, int32_t diacritic_count, 2638 + char *st_executable) { 2639 + char filename[MAX_FILENAME_SIZE]; 2640 + snprintf(filename, sizeof(filename), "%s/info-%u", cache_dir, image_id); 2641 + FILE *file = fopen(filename, "w"); 2642 + if (!file) { 2643 + perror("fopen"); 2644 + return; 2645 + } 2646 + // Basic information about the cell. 2647 + fprintf(file, "image_id = %u = 0x%08X\n", image_id, image_id); 2648 + fprintf(file, "placement_id = %u = 0x%08X\n", placement_id, placement_id); 2649 + fprintf(file, "column = %d, row = %d\n", imgcol, imgrow); 2650 + fprintf(file, "classic/unicode placeholder = %s\n", 2651 + is_classic_placeholder ? "classic" : "unicode"); 2652 + fprintf(file, "original diacritic count = %d\n", diacritic_count); 2653 + // Information about the image and the placement. 2654 + Image *img = gr_find_image(image_id); 2655 + ImagePlacement *placement = gr_find_placement(img, placement_id); 2656 + gr_dump_image_info(file, img, 0); 2657 + gr_dump_placement_info(file, placement, 0); 2658 + // The text underneath this particular cell. 2659 + if (placement && placement->text_underneath && imgcol >= 1 && 2660 + imgrow >= 1 && imgcol <= placement->cols && 2661 + imgrow <= placement->rows) { 2662 + fprintf(file, "Glyph underneath:\n"); 2663 + Glyph *glyph = 2664 + &placement->text_underneath[(imgrow - 1) * 2665 + placement->cols + 2666 + imgcol - 1]; 2667 + fprintf(file, " rune = 0x%08X\n", glyph->u); 2668 + fprintf(file, " bg = 0x%08X\n", glyph->bg); 2669 + fprintf(file, " fg = 0x%08X\n", glyph->fg); 2670 + fprintf(file, " decor = 0x%08X\n", glyph->decor); 2671 + fprintf(file, " mode = 0x%08X\n", glyph->mode); 2672 + } 2673 + if (img) { 2674 + fprintf(file, "Frames:\n"); 2675 + foreach_frame(*img, frame, { 2676 + gr_dump_frame_info(file, frame, 4); 2677 + }); 2678 + } 2679 + if (placement) { 2680 + fprintf(file, "Placement pixmaps:\n"); 2681 + gr_dump_placement_pixmaps(file, placement, 4); 2682 + } 2683 + fclose(file); 2684 + char *argv[] = {st_executable, "-e", "less", filename, NULL}; 2685 + if (posix_spawnp(NULL, st_executable, NULL, NULL, argv, environ) != 0) { 2686 + perror("posix_spawnp"); 2687 + return; 2688 + } 2689 +} 2690 + 2691 +//////////////////////////////////////////////////////////////////////////////// 2692 +// Appending and displaying image rectangles. 2693 +//////////////////////////////////////////////////////////////////////////////// 2694 + 2695 +/// Displays debug information in the rectangle using colors col1 and col2. 2696 +static void gr_displayinfo(Drawable buf, ImageRect *rect, int col1, int col2, 2697 + const char *message) { 2698 + int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; 2699 + int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; 2700 + Display *disp = imlib_context_get_display(); 2701 + GC gc = XCreateGC(disp, buf, 0, NULL); 2702 + char info[MAX_INFO_LEN]; 2703 + if (rect->placement_id) 2704 + snprintf(info, MAX_INFO_LEN, "%s%u/%u [%d:%d)x[%d:%d)", message, 2705 + rect->image_id, rect->placement_id, 2706 + rect->img_start_col, rect->img_end_col, 2707 + rect->img_start_row, rect->img_end_row); 2708 + else 2709 + snprintf(info, MAX_INFO_LEN, "%s%u [%d:%d)x[%d:%d)", message, 2710 + rect->image_id, rect->img_start_col, rect->img_end_col, 2711 + rect->img_start_row, rect->img_end_row); 2712 + XSetForeground(disp, gc, col1); 2713 + XDrawString(disp, buf, gc, rect->screen_x_pix + 4, 2714 + rect->screen_y_pix + h_pix - 3, info, strlen(info)); 2715 + XSetForeground(disp, gc, col2); 2716 + XDrawString(disp, buf, gc, rect->screen_x_pix + 2, 2717 + rect->screen_y_pix + h_pix - 5, info, strlen(info)); 2718 + XFreeGC(disp, gc); 2719 +} 2720 + 2721 +/// Draws a rectangle (bounding box) for debugging. 2722 +static void gr_showrect(Drawable buf, ImageRect *rect) { 2723 + int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; 2724 + int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; 2725 + Display *disp = imlib_context_get_display(); 2726 + GC gc = XCreateGC(disp, buf, 0, NULL); 2727 + XSetForeground(disp, gc, 0xFF00FF00); 2728 + XDrawRectangle(disp, buf, gc, rect->screen_x_pix, rect->screen_y_pix, 2729 + w_pix - 1, h_pix - 1); 2730 + XSetForeground(disp, gc, 0xFFFF0000); 2731 + XDrawRectangle(disp, buf, gc, rect->screen_x_pix + 1, 2732 + rect->screen_y_pix + 1, w_pix - 3, h_pix - 3); 2733 + XFreeGC(disp, gc); 2734 +} 2735 + 2736 +/// Updates the next redraw time for the given row. Resizes the 2737 +/// next_redraw_times array if needed. 2738 +static void gr_update_next_redraw_time(int row, Milliseconds next_redraw) { 2739 + if (next_redraw == 0) 2740 + return; 2741 + if (row >= kv_size(next_redraw_times)) { 2742 + size_t old_size = kv_size(next_redraw_times); 2743 + kv_a(Milliseconds, next_redraw_times, row); 2744 + for (size_t i = old_size; i <= row; ++i) 2745 + kv_A(next_redraw_times, i) = 0; 2746 + } 2747 + Milliseconds old_value = kv_A(next_redraw_times, row); 2748 + if (old_value == 0 || old_value > next_redraw) 2749 + kv_A(next_redraw_times, row) = next_redraw; 2750 +} 2751 + 2752 +/// Draws the given part of an image. 2753 +static void gr_drawimagerect(Drawable buf, ImageRect *rect) { 2754 + ImagePlacement *placement = 2755 + gr_find_image_and_placement(rect->image_id, rect->placement_id); 2756 + // If the image does not exist or image display is switched off, draw 2757 + // the bounding box. 2758 + if (!placement || !graphics_display_images) { 2759 + gr_showrect(buf, rect); 2760 + if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 2761 + gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2762 + return; 2763 + } 2764 + 2765 + Image *img = placement->image; 2766 + 2767 + if (img->last_redraw < drawing_start_time) { 2768 + // This is the first time we draw this image in this redraw 2769 + // cycle. Update the frame index we are going to display. Note 2770 + // that currently all image placements are synchronized. 2771 + int old_frame = img->current_frame; 2772 + gr_update_frame_index(img, drawing_start_time); 2773 + img->last_redraw = drawing_start_time; 2774 + } 2775 + 2776 + // Adjust next redraw times for the rows of this image rect. 2777 + if (img->next_redraw) { 2778 + for (int row = rect->screen_y_row; 2779 + row <= rect->screen_y_row + rect->img_end_row - 2780 + rect->img_start_row - 1; ++row) { 2781 + gr_update_next_redraw_time( 2782 + row, img->next_redraw); 2783 + } 2784 + } 2785 + 2786 + // Load the frame. 2787 + Pixmap pixmap = gr_load_pixmap(placement, img->current_frame, rect->cw, 2788 + rect->ch); 2789 + 2790 + // If the image couldn't be loaded, display the bounding box. 2791 + if (!pixmap) { 2792 + gr_showrect(buf, rect); 2793 + if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 2794 + gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2795 + return; 2796 + } 2797 + 2798 + // The coordinates and size (in pixels) of the src rectangle inside the 2799 + // box of cells (not the pixmap). 2800 + int src_x = rect->img_start_col * rect->cw; 2801 + int src_y = rect->img_start_row * rect->ch; 2802 + int src_w = (rect->img_end_col - rect->img_start_col) * rect->cw; 2803 + int src_h = (rect->img_end_row - rect->img_start_row) * rect->ch; 2804 + // The coordinates of the dst rectangle inside the window. The size is 2805 + // the same as the src size in the box (src_w, src_h). 2806 + int window_x = rect->screen_x_pix; 2807 + int window_y = rect->screen_y_pix; 2808 + 2809 + Display *disp = imlib_context_get_display(); 2810 + Visual *vis = imlib_context_get_visual(); 2811 + 2812 + // Create an xrender picture for the window. 2813 + XRenderPictFormat *win_format = 2814 + XRenderFindVisualFormat(disp, vis); 2815 + Picture window_pic = 2816 + XRenderCreatePicture(disp, buf, win_format, 0, NULL); 2817 + 2818 + // If needed, invert the image pixmap. Note that this naive approach of 2819 + // inverting the pixmap is not entirely correct, because the pixmap is 2820 + // premultiplied. But the result is good enough to visually indicate 2821 + // selection. 2822 + if (rect->reverse) { 2823 + unsigned pixmap_w = placement->pixmap_transformation.pixmap_w; 2824 + unsigned pixmap_h = placement->pixmap_transformation.pixmap_h; 2825 + Pixmap invpixmap = 2826 + XCreatePixmap(disp, buf, pixmap_w, pixmap_h, 32); 2827 + XGCValues gcv = {.function = GXcopyInverted}; 2828 + GC gc = XCreateGC(disp, invpixmap, GCFunction, &gcv); 2829 + XCopyArea(disp, pixmap, invpixmap, gc, 0, 0, pixmap_w, 2830 + pixmap_h, 0, 0); 2831 + XFreeGC(disp, gc); 2832 + pixmap = invpixmap; 2833 + } 2834 + 2835 + // Create a picture for the image pixmap. 2836 + XRenderPictFormat *pic_format = 2837 + XRenderFindStandardFormat(disp, PictStandardARGB32); 2838 + // We use RepeatPad to avoid bilinear filtering halo. 2839 + XRenderPictureAttributes attrs = {0}; 2840 + attrs.repeat = RepeatPad; 2841 + Picture pixmap_pic = XRenderCreatePicture(disp, pixmap, pic_format, 2842 + CPRepeat, &attrs); 2843 + 2844 + // Since the pixmap may be of different size than the destination box of 2845 + // cells, we must apply a transformation to it. 2846 + // The XTransform structure describes a matrix to transform the 2847 + // destination picture coordinates (i.e. in the box) to the source 2848 + // coordinates (i.e. in the pixmap). We apply only scaling (translation 2849 + // will be applied to the src coordinates directly): 2850 + // 2851 + // pixmap_x = picture_x * pixmap_w / dst_w 2852 + // pixmap_y = picture_y * pixmap_h / dst_h 2853 + // 2854 + // Where dst_w, dst_h, pixmap_w, pixmap_h are from the placement's 2855 + // pixmap_transformation structure. 2856 + PixmapTransformation *tr = &placement->pixmap_transformation; 2857 + double xs = (double)tr->pixmap_w / MAX(tr->dst_w, 1); 2858 + double ys = (double)tr->pixmap_h / MAX(tr->dst_h, 1); 2859 + // clang-format off 2860 + XTransform xform = {{ 2861 + { XDoubleToFixed(xs), XDoubleToFixed( 0), XDoubleToFixed( 0) }, 2862 + { XDoubleToFixed( 0), XDoubleToFixed(ys), XDoubleToFixed( 0) }, 2863 + { XDoubleToFixed( 0), XDoubleToFixed( 0), XDoubleToFixed( 1) } 2864 + }}; 2865 + // clang-format on 2866 + XRenderSetPictureTransform(disp, pixmap_pic, &xform); 2867 + XRenderSetPictureFilter(disp, pixmap_pic, FilterBilinear, NULL, 0); 2868 + 2869 + // Do the translation: modify the src coordinates to make them 2870 + // coordinates into the picture rather than into the box. 2871 + src_x -= tr->dst_x; 2872 + src_y -= tr->dst_y; 2873 + // At this point src_x, src_y, src_w, src_h are coordinates of the 2874 + // source rectangle inside the picture (scaled pixmap). 2875 + 2876 + // Now do clipping. If src coordinates are negative, adjust the dst 2877 + // coordinates (into the window) and the width/height. We do clipping 2878 + // instead of using the values as is to avoid rendering the pad area 2879 + // outside the pixmap. 2880 + if (src_x < 0) { 2881 + window_x += -src_x; 2882 + src_w -= -src_x; 2883 + src_x = 0; 2884 + } 2885 + if (src_y < 0) { 2886 + window_y += -src_y; 2887 + src_h -= -src_y; 2888 + src_y = 0; 2889 + } 2890 + 2891 + // Adjust width and height if the src rectangle exceeds the picture. 2892 + src_w = MIN(src_w, tr->dst_w - src_x); 2893 + src_h = MIN(src_h, tr->dst_h - src_y); 2894 + 2895 + // Composite the image onto the window. In the reverse mode we ignore 2896 + // the alpha channel of the image because the naive inversion above 2897 + // seems to invert the alpha channel as well. 2898 + int pictop = rect->reverse ? PictOpSrc : PictOpOver; 2899 + if (src_w > 0 && src_h > 0) 2900 + XRenderComposite(disp, pictop, pixmap_pic, 0, window_pic, src_x, 2901 + src_y, src_x, src_y, window_x, window_y, src_w, 2902 + src_h); 2903 + 2904 + // Free resources 2905 + XRenderFreePicture(disp, pixmap_pic); 2906 + XRenderFreePicture(disp, window_pic); 2907 + if (rect->reverse) 2908 + XFreePixmap(disp, pixmap); 2909 + 2910 + // In debug mode always draw bounding boxes and print info. 2911 + if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) { 2912 + gr_showrect(buf, rect); 2913 + gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2914 + } 2915 +} 2916 + 2917 +/// Removes the given image rectangle. 2918 +static void gr_freerect(ImageRect *rect) { memset(rect, 0, sizeof(ImageRect)); } 2919 + 2920 +/// Returns the bottom coordinate of the rect. 2921 +static int gr_getrectbottom(ImageRect *rect) { 2922 + return rect->screen_y_pix + 2923 + (rect->img_end_row - rect->img_start_row) * rect->ch; 2924 +} 2925 + 2926 +/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. 2927 +void gr_start_drawing(Drawable buf, int cw, int ch) { 2928 + current_cw = cw; 2929 + current_ch = ch; 2930 + debug_loaded_files_counter = 0; 2931 + debug_loaded_pixmaps_counter = 0; 2932 + drawing_start_time = gr_now_ms(); 2933 + imlib_context_set_drawable(buf); 2934 +} 2935 + 2936 +/// Finish image drawing. This functions will draw all the rectangles left to 2937 +/// draw. 2938 +void gr_finish_drawing(Drawable buf) { 2939 + // Draw and then delete all known image rectangles. 2940 + for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 2941 + ImageRect *rect = &image_rects[i]; 2942 + if (!rect->image_id) 2943 + continue; 2944 + gr_drawimagerect(buf, rect); 2945 + gr_freerect(rect); 2946 + } 2947 + 2948 + // Compute the delay until the next redraw as the minimum of the next 2949 + // redraw delays for all rows. 2950 + Milliseconds drawing_end_time = gr_now_ms(); 2951 + graphics_next_redraw_delay = INT_MAX; 2952 + for (int row = 0; row < kv_size(next_redraw_times); ++row) { 2953 + Milliseconds row_next_redraw = kv_A(next_redraw_times, row); 2954 + if (row_next_redraw > 0) { 2955 + int delay = MAX(graphics_animation_min_delay, 2956 + row_next_redraw - drawing_end_time); 2957 + graphics_next_redraw_delay = 2958 + MIN(graphics_next_redraw_delay, delay); 2959 + } 2960 + } 2961 + 2962 + // In debug mode display additional info. 2963 + if (graphics_debug_mode) { 2964 + int milliseconds = drawing_end_time - drawing_start_time; 2965 + 2966 + Display *disp = imlib_context_get_display(); 2967 + GC gc = XCreateGC(disp, buf, 0, NULL); 2968 + const char *debug_mode_str = 2969 + graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES 2970 + ? "(boxes shown) " 2971 + : ""; 2972 + int redraw_delay = graphics_next_redraw_delay == INT_MAX 2973 + ? -1 2974 + : graphics_next_redraw_delay; 2975 + char info[MAX_INFO_LEN]; 2976 + snprintf(info, MAX_INFO_LEN, 2977 + "%sRender time: %d ms ram %ld K disk %ld K count " 2978 + "%d cell %dx%d delay %d", 2979 + debug_mode_str, milliseconds, images_ram_size / 1024, 2980 + images_disk_size / 1024, kh_size(images), current_cw, 2981 + current_ch, redraw_delay); 2982 + XSetForeground(disp, gc, 0xFF000000); 2983 + XFillRectangle(disp, buf, gc, 0, 0, 600, 16); 2984 + XSetForeground(disp, gc, 0xFFFFFFFF); 2985 + XDrawString(disp, buf, gc, 0, 14, info, strlen(info)); 2986 + XFreeGC(disp, gc); 2987 + 2988 + if (milliseconds > 0) { 2989 + fprintf(stderr, "%s (loaded %d files, %d pixmaps)\n", 2990 + info, debug_loaded_files_counter, 2991 + debug_loaded_pixmaps_counter); 2992 + } 2993 + } 2994 + 2995 + // Check the limits in case we have used too much ram for placements. 2996 + gr_check_limits(); 2997 +} 2998 + 2999 +// Add an image rectangle to the list of rectangles to draw. 3000 +void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, 3001 + int img_start_col, int img_end_col, int img_start_row, 3002 + int img_end_row, int x_col, int y_row, int x_pix, 3003 + int y_pix, int cw, int ch, int reverse) { 3004 + current_cw = cw; 3005 + current_ch = ch; 3006 + 3007 + ImageRect new_rect; 3008 + new_rect.image_id = image_id; 3009 + new_rect.placement_id = placement_id; 3010 + new_rect.img_start_col = img_start_col; 3011 + new_rect.img_end_col = img_end_col; 3012 + new_rect.img_start_row = img_start_row; 3013 + new_rect.img_end_row = img_end_row; 3014 + new_rect.screen_y_row = y_row; 3015 + new_rect.screen_x_pix = x_pix; 3016 + new_rect.screen_y_pix = y_pix; 3017 + new_rect.ch = ch; 3018 + new_rect.cw = cw; 3019 + new_rect.reverse = reverse; 3020 + 3021 + // Display some red text in debug mode. 3022 + if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 3023 + gr_displayinfo(buf, &new_rect, 0xFF000000, 0xFFFF0000, "? "); 3024 + 3025 + // If it's the empty image (image_id=0) or an empty rectangle, do 3026 + // nothing. 3027 + if (image_id == 0 || img_end_col - img_start_col <= 0 || 3028 + img_end_row - img_start_row <= 0) 3029 + return; 3030 + // Try to find a rect to merge with. 3031 + ImageRect *free_rect = NULL; 3032 + for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 3033 + ImageRect *rect = &image_rects[i]; 3034 + if (rect->image_id == 0) { 3035 + if (!free_rect) 3036 + free_rect = rect; 3037 + continue; 3038 + } 3039 + if (rect->image_id != image_id || 3040 + rect->placement_id != placement_id || rect->cw != cw || 3041 + rect->ch != ch || rect->reverse != reverse) 3042 + continue; 3043 + // We only support the case when the new stripe is added to the 3044 + // bottom of an existing rectangle and they are perfectly 3045 + // aligned. 3046 + if (rect->img_end_row == img_start_row && 3047 + gr_getrectbottom(rect) == y_pix) { 3048 + if (rect->img_start_col == img_start_col && 3049 + rect->img_end_col == img_end_col && 3050 + rect->screen_x_pix == x_pix) { 3051 + rect->img_end_row = img_end_row; 3052 + return; 3053 + } 3054 + } 3055 + } 3056 + // If we haven't merged the new rect with any existing rect, and there 3057 + // is no free rect, we have to render one of the existing rects. 3058 + if (!free_rect) { 3059 + for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 3060 + ImageRect *rect = &image_rects[i]; 3061 + if (!free_rect || gr_getrectbottom(free_rect) > 3062 + gr_getrectbottom(rect)) 3063 + free_rect = rect; 3064 + } 3065 + gr_drawimagerect(buf, free_rect); 3066 + gr_freerect(free_rect); 3067 + } 3068 + // Start a new rectangle in `free_rect`. 3069 + *free_rect = new_rect; 3070 +} 3071 + 3072 +/// Mark rows containing animations as dirty if it's time to redraw them. Must 3073 +/// be called right after `gr_start_drawing`. 3074 +void gr_mark_dirty_animations(int *dirty, int rows) { 3075 + if (rows < kv_size(next_redraw_times)) 3076 + kv_size(next_redraw_times) = rows; 3077 + if (rows * 2 < kv_max(next_redraw_times)) 3078 + kv_resize(Milliseconds, next_redraw_times, rows); 3079 + for (int i = 0; i < MIN(rows, kv_size(next_redraw_times)); ++i) { 3080 + if (dirty[i]) { 3081 + kv_A(next_redraw_times, i) = 0; 3082 + continue; 3083 + } 3084 + Milliseconds next_update = kv_A(next_redraw_times, i); 3085 + if (next_update > 0 && next_update <= drawing_start_time) { 3086 + dirty[i] = 1; 3087 + kv_A(next_redraw_times, i) = 0; 3088 + } 3089 + } 3090 +} 3091 + 3092 +//////////////////////////////////////////////////////////////////////////////// 3093 +// Command parsing and handling. 3094 +//////////////////////////////////////////////////////////////////////////////// 3095 + 3096 +/// A parsed kitty graphics protocol command. 3097 +typedef struct { 3098 + /// The command itself, without the 'G'. 3099 + char *command; 3100 + /// The payload (after ';'). 3101 + char *payload; 3102 + /// 'a=', may be 't', 'q', 'f', 'T', 'p', 'd', 'a'. 3103 + char action; 3104 + /// 'q=', 1 to suppress OK response, 2 to suppress errors too. 3105 + int quiet; 3106 + /// 'f=', use 24 or 32 for raw pixel data, 100 to autodetect with 3107 + /// imlib2. If 'f=0', will try to load with imlib2, then fallback to 3108 + /// 32-bit pixel data. 3109 + int format; 3110 + /// 'o=', may be 'z' for RFC 1950 ZLIB. 3111 + int compression; 3112 + /// 't=', may be 'f', 't' or 'd'. 3113 + char transmission_medium; 3114 + /// 'd=' 3115 + char delete_specifier; 3116 + /// 's=', 'v=', if 'a=t' or 'a=T', used only when 'f=24' or 'f=32'. 3117 + /// When 'a=f', this is the size of the frame rectangle when composed on 3118 + /// top of another frame. 3119 + int frame_pix_width, frame_pix_height; 3120 + /// 'x=', 'y=' - top-left corner of the source rectangle. 3121 + int src_pix_x, src_pix_y; 3122 + /// 'w=', 'h=' - width and height of the source rectangle. 3123 + int src_pix_width, src_pix_height; 3124 + /// 'r=', 'c=' 3125 + int rows, columns; 3126 + /// 'i=' 3127 + uint32_t image_id; 3128 + /// 'I=' 3129 + uint32_t image_number; 3130 + /// 'p=' 3131 + uint32_t placement_id; 3132 + /// 'm=', may be 0 or 1. 3133 + int more; 3134 + /// True if turns out that this command is a continuation of a data 3135 + /// transmission and not the first one for this image. Populated by 3136 + /// `gr_handle_transmit_command`. 3137 + char is_direct_transmission_continuation; 3138 + /// 'S=', used to check the size of uploaded data. 3139 + int size; 3140 + /// The offset of the frame image data in the shared memory ('O='). 3141 + unsigned offset; 3142 + /// 'U=', whether it's a virtual placement for Unicode placeholders. 3143 + int virtual; 3144 + /// 'C=', if true, do not move the cursor when displaying this placement 3145 + /// (non-virtual placements only). 3146 + char do_not_move_cursor; 3147 + // --------------------------------------------------------------------- 3148 + // Animation-related fields. Their keys often overlap with keys of other 3149 + // commands, so these make sense only if the action is 'a=f' (frame 3150 + // transmission) or 'a=a' (animation control). 3151 + // 3152 + // 'x=' and 'y=', the relative position of the frame image when it's 3153 + // composed on top of another frame. 3154 + int frame_dst_pix_x, frame_dst_pix_y; 3155 + /// 'X=', 'X=1' to replace colors instead of alpha blending on top of 3156 + /// the background color or frame. 3157 + char replace_instead_of_blending; 3158 + /// 'Y=', the background color in the 0xRRGGBBAA format (still 3159 + /// transmitted as a decimal number). 3160 + uint32_t background_color; 3161 + /// (Only for 'a=f'). 'c=', the 1-based index of the background frame. 3162 + int background_frame; 3163 + /// (Only for 'a=a'). 'c=', sets the index of the current frame. 3164 + int current_frame; 3165 + /// 'r=', the 1-based index of the frame to edit. 3166 + int edit_frame; 3167 + /// 'z=', the duration of the frame. Zero if not specified, negative if 3168 + /// the frame is gapless (i.e. skipped). 3169 + int gap; 3170 + /// (Only for 'a=a'). 's=', if non-zero, sets the state of the 3171 + /// animation, 1 to stop, 2 to run in loading mode, 3 to loop. 3172 + int animation_state; 3173 + /// (Only for 'a=a'). 'v=', if non-zero, sets the number of times the 3174 + /// animation will loop. 1 to loop infinitely, N to loop N-1 times. 3175 + int loops; 3176 +} GraphicsCommand; 3177 + 3178 +/// Replaces all non-printed characters in `str` with '?' and truncates the 3179 +/// string to `max_size`, maybe inserting ellipsis at the end. 3180 +static void sanitize_str(char *str, size_t max_size) { 3181 + assert(max_size >= 4); 3182 + for (size_t i = 0; i < max_size; ++i) { 3183 + unsigned c = str[i]; 3184 + if (c == '\0') 3185 + return; 3186 + if (c >= 128 || !isprint(c)) 3187 + str[i] = '?'; 3188 + } 3189 + str[max_size - 1] = '\0'; 3190 + str[max_size - 2] = '.'; 3191 + str[max_size - 3] = '.'; 3192 + str[max_size - 4] = '.'; 3193 +} 3194 + 3195 +/// A non-destructive version of `sanitize_str`. Uses a static buffer, so be 3196 +/// careful. 3197 +static const char *sanitized_filename(const char *str) { 3198 + static char buf[MAX_FILENAME_SIZE]; 3199 + strncpy(buf, str, sizeof(buf)); 3200 + sanitize_str(buf, sizeof(buf)); 3201 + return buf; 3202 +} 3203 + 3204 +/// Creates a response to the current command in `graphics_command_result`. 3205 +static void gr_createresponse(uint32_t image_id, uint32_t image_number, 3206 + uint32_t placement_id, const char *msg) { 3207 + if (!image_id && !image_number && !placement_id) { 3208 + // Nobody expects the response in this case, so just print it to 3209 + // stderr. 3210 + fprintf(stderr, 3211 + "error: No image id or image number or placement_id, " 3212 + "but still there is a response: %s\n", 3213 + msg); 3214 + return; 3215 + } 3216 + char *buf = graphics_command_result.response; 3217 + size_t maxlen = MAX_GRAPHICS_RESPONSE_LEN; 3218 + size_t written; 3219 + written = snprintf(buf, maxlen, "\033_G"); 3220 + buf += written; 3221 + maxlen -= written; 3222 + if (image_id) { 3223 + written = snprintf(buf, maxlen, "i=%u,", image_id); 3224 + buf += written; 3225 + maxlen -= written; 3226 + } 3227 + if (image_number) { 3228 + written = snprintf(buf, maxlen, "I=%u,", image_number); 3229 + buf += written; 3230 + maxlen -= written; 3231 + } 3232 + if (placement_id) { 3233 + written = snprintf(buf, maxlen, "p=%u,", placement_id); 3234 + buf += written; 3235 + maxlen -= written; 3236 + } 3237 + buf[-1] = ';'; 3238 + written = snprintf(buf, maxlen, "%s\033\\", msg); 3239 + buf += written; 3240 + maxlen -= written; 3241 + buf[-2] = '\033'; 3242 + buf[-1] = '\\'; 3243 +} 3244 + 3245 +/// Creates the 'OK' response to the current command, unless suppressed or a 3246 +/// non-final data transmission. 3247 +static void gr_reportsuccess_cmd(GraphicsCommand *cmd) { 3248 + if (cmd->quiet < 1 && !cmd->more) 3249 + gr_createresponse(cmd->image_id, cmd->image_number, 3250 + cmd->placement_id, "OK"); 3251 +} 3252 + 3253 +/// Creates the 'OK' response to the current command (unless suppressed). 3254 +static void gr_reportsuccess_frame(ImageFrame *frame) { 3255 + uint32_t id = frame->image->query_id ? frame->image->query_id 3256 + : frame->image->image_id; 3257 + if (frame->quiet < 1) 3258 + gr_createresponse(id, frame->image->image_number, 3259 + frame->image->initial_placement_id, "OK"); 3260 +} 3261 + 3262 +/// Creates an error response to the current command (unless suppressed). 3263 +static void gr_reporterror_cmd(GraphicsCommand *cmd, const char *format, ...) { 3264 + char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; 3265 + graphics_command_result.error = 1; 3266 + va_list args; 3267 + va_start(args, format); 3268 + vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); 3269 + va_end(args); 3270 + 3271 + fprintf(stderr, "%s in command: %s\n", errmsg, cmd->command); 3272 + if (cmd->quiet < 2) 3273 + gr_createresponse(cmd->image_id, cmd->image_number, 3274 + cmd->placement_id, errmsg); 3275 +} 3276 + 3277 +/// Creates an error response to the current command (unless suppressed). 3278 +static void gr_reporterror_frame(ImageFrame *frame, const char *format, ...) { 3279 + char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; 3280 + graphics_command_result.error = 1; 3281 + va_list args; 3282 + va_start(args, format); 3283 + vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); 3284 + va_end(args); 3285 + 3286 + if (!frame) { 3287 + fprintf(stderr, "%s\n", errmsg); 3288 + gr_createresponse(0, 0, 0, errmsg); 3289 + } else { 3290 + uint32_t id = frame->image->query_id ? frame->image->query_id 3291 + : frame->image->image_id; 3292 + fprintf(stderr, "%s id=%u\n", errmsg, id); 3293 + if (frame->quiet < 2) 3294 + gr_createresponse(id, frame->image->image_number, 3295 + frame->image->initial_placement_id, 3296 + errmsg); 3297 + } 3298 +} 3299 + 3300 +/// Loads an image and creates a success/failure response. Returns `frame`, or 3301 +/// NULL if it's a query action and the image was deleted. 3302 +static ImageFrame *gr_loadimage_and_report(ImageFrame *frame) { 3303 + gr_load_imlib_object(frame); 3304 + if (!frame->imlib_object) { 3305 + gr_reporterror_frame(frame, "EBADF: could not load image"); 3306 + } else { 3307 + gr_reportsuccess_frame(frame); 3308 + } 3309 + // If it was a query action, discard the image. 3310 + if (frame->image->query_id) { 3311 + gr_delete_image(frame->image); 3312 + return NULL; 3313 + } 3314 + return frame; 3315 +} 3316 + 3317 +/// Creates an appropriate uploading failure response to the current command. 3318 +static void gr_reportuploaderror(ImageFrame *frame) { 3319 + switch (frame->uploading_failure) { 3320 + case 0: 3321 + return; 3322 + case ERROR_CANNOT_OPEN_CACHED_FILE: 3323 + gr_reporterror_frame(frame, 3324 + "EIO: could not create a file for image"); 3325 + break; 3326 + case ERROR_OVER_SIZE_LIMIT: 3327 + gr_reporterror_frame( 3328 + frame, 3329 + "EFBIG: the size of the uploaded image exceeded " 3330 + "the image size limit %u", 3331 + graphics_max_single_image_file_size); 3332 + break; 3333 + case ERROR_UNEXPECTED_SIZE: 3334 + gr_reporterror_frame(frame, 3335 + "EINVAL: the size of the uploaded image %u " 3336 + "doesn't match the expected size %u", 3337 + frame->disk_size, frame->expected_size); 3338 + break; 3339 + }; 3340 +} 3341 + 3342 +/// Displays a non-virtual placement. This functions records the information in 3343 +/// `graphics_command_result`, the placeholder itself is created by the terminal 3344 +/// after handling the current command in the graphics module. 3345 +static void gr_display_nonvirtual_placement(ImagePlacement *placement) { 3346 + if (placement->virtual) 3347 + return; 3348 + if (placement->image->first_frame.status < STATUS_RAM_LOADING_SUCCESS) 3349 + return; 3350 + // Infer the placement size if needed. 3351 + gr_infer_placement_size_maybe(placement); 3352 + // Populate the information about the placeholder which will be created 3353 + // by the terminal. 3354 + graphics_command_result.create_placeholder = 1; 3355 + graphics_command_result.placeholder.image_id = placement->image->image_id; 3356 + graphics_command_result.placeholder.placement_id = placement->placement_id; 3357 + graphics_command_result.placeholder.columns = placement->cols; 3358 + graphics_command_result.placeholder.rows = placement->rows; 3359 + graphics_command_result.placeholder.do_not_move_cursor = 3360 + placement->do_not_move_cursor; 3361 + placement->text_underneath = 3362 + calloc(placement->rows * placement->cols, sizeof(Glyph)); 3363 + graphics_command_result.placeholder.text_underneath = 3364 + placement->text_underneath; 3365 + GR_LOG("Creating a placeholder for %u/%u %d x %d\n", 3366 + placement->image->image_id, placement->placement_id, 3367 + placement->cols, placement->rows); 3368 +} 3369 + 3370 +/// Marks the rows that are occupied by the image as dirty. 3371 +static void gr_schedule_image_redraw(Image *img) { 3372 + if (!img) 3373 + return; 3374 + gr_schedule_image_redraw_by_id(img->image_id); 3375 +} 3376 + 3377 +/// Closes the file currently being uploaded. This doesn't necessarily finish 3378 +/// the upload since the file may be reopened. 3379 +static void gr_close_current_upload_file() { 3380 + Image *img = gr_find_image(current_upload_image_id); 3381 + ImageFrame *frame = gr_get_frame(img, current_upload_frame_index); 3382 + gr_close_disk_cache_file(frame); 3383 +} 3384 + 3385 +/// Sets the current image and frame being uploaded. Closes the previous upload 3386 +/// file if it's changed. If `frame` is NULL, clears the current upload 3387 +/// image/frame. 3388 +static void gr_set_current_upload_frame(ImageFrame *frame) { 3389 + if (frame) { 3390 + if (current_upload_image_id != frame->image->image_id || 3391 + current_upload_frame_index != frame->index) { 3392 + gr_close_current_upload_file(); 3393 + } 3394 + current_upload_image_id = frame->image->image_id; 3395 + current_upload_frame_index = frame->index; 3396 + GR_LOG("Set current_upload_image_id = %u, " 3397 + "current_upload_frame_index = %u\n", 3398 + current_upload_image_id, current_upload_frame_index); 3399 + } else { 3400 + gr_close_current_upload_file(); 3401 + current_upload_image_id = 0; 3402 + current_upload_frame_index = 0; 3403 + GR_LOG("Set current_upload_image_id = 0\n"); 3404 + } 3405 +} 3406 + 3407 +/// Returns whether direct transmission continuation is allowed for the given 3408 +/// command and frame. 3409 +static int gr_transmission_continuation_is_allowed(GraphicsCommand *cmd, 3410 + ImageFrame *frame) { 3411 + if (!frame || frame->status != STATUS_UPLOADING) 3412 + return 0; 3413 + 3414 + // If it's the same image and frame as the current upload, allow it. 3415 + if (current_upload_image_id == frame->image->image_id && 3416 + current_upload_frame_index == frame->index) 3417 + return 1; 3418 + 3419 + // Otherwise it's a continuation of an interrupted upload. The kitty 3420 + // graphics protocol doesn't allow interleaving of direct transmission 3421 + // with other commands, so interrupted uploads must be aborted. However, 3422 + // we still allow it as an extension, because it's useful for 3423 + // protocol-unaware multiplexer. We check that there are no 3424 + // contradictions, and the time since the last upload activity is small. 3425 + 3426 + if (cmd->size && cmd->size != frame->expected_size) { 3427 + fprintf(stderr, "warning: Not resuming interrupted upload " 3428 + "because of expected size mismatch\n"); 3429 + return 0; 3430 + } 3431 + if (cmd->format && cmd->format != frame->format) { 3432 + fprintf(stderr, "warning: Not resuming interrupted upload " 3433 + "because of format mismatch\n"); 3434 + return 0; 3435 + } 3436 + if (cmd->compression && cmd->compression != frame->compression) { 3437 + fprintf(stderr, "warning: Not resuming interrupted upload " 3438 + "because of compression mismatch\n"); 3439 + return 0; 3440 + } 3441 + if ((cmd->frame_pix_width && 3442 + cmd->frame_pix_width != frame->data_pix_width) || 3443 + (cmd->frame_pix_height && 3444 + cmd->frame_pix_height != frame->data_pix_height) || 3445 + (cmd->background_color && 3446 + cmd->background_color != frame->background_color) || 3447 + (cmd->background_frame && 3448 + cmd->background_frame != frame->background_frame_index) || 3449 + (cmd->gap && cmd->gap != frame->gap) || 3450 + (cmd->replace_instead_of_blending && 3451 + cmd->replace_instead_of_blending != !frame->blend)) { 3452 + fprintf(stderr, "warning: Not resuming interrupted upload " 3453 + "because of frame parameters mismatch\n"); 3454 + return 0; 3455 + } 3456 + 3457 + Milliseconds now = gr_now_ms(); 3458 + if (now - frame->atime > graphics_direct_transmission_timeout_ms) { 3459 + fprintf(stderr, "warning: Not resuming interrupted upload " 3460 + "because of time out\n"); 3461 + return 0; 3462 + } 3463 + 3464 + return 1; 3465 +} 3466 + 3467 +/// Appends `data` to the on-disk cache file of the frame `frame`. Creates the 3468 +/// file if it doesn't exist. Updates `frame->disk_size` and the total disk 3469 +/// size. Returns 1 on success and 0 on failure. 3470 +static int gr_append_raw_data_to_file(ImageFrame *frame, const char *data, 3471 + size_t data_size) { 3472 + // If there is no open file corresponding to the image, create it. 3473 + if (!frame->open_file) { 3474 + gr_make_sure_tmpdir_exists(); 3475 + char filename[MAX_FILENAME_SIZE]; 3476 + gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 3477 + FILE *file = fopen(filename, frame->disk_size ? "a" : "w"); 3478 + if (!file) 3479 + return 0; 3480 + frame->open_file = file; 3481 + } 3482 + 3483 + // Write data to the file and update disk size variables. 3484 + fwrite(data, 1, data_size, frame->open_file); 3485 + frame->disk_size += data_size; 3486 + frame->image->total_disk_size += data_size; 3487 + images_disk_size += data_size; 3488 + gr_touch_frame(frame); 3489 + return 1; 3490 +} 3491 + 3492 +/// Appends data from `payload` to the frame `frame` when using direct 3493 +/// transmission. Note that we report errors only for the final command 3494 +/// (`!more`) to avoid spamming the client. If the frame is not specified, use 3495 +/// the image id and frame index we are currently uploading. 3496 +static void gr_append_data(ImageFrame *frame, const char *payload, int more) { 3497 + gr_set_current_upload_frame(frame); 3498 + 3499 + if (frame->status != STATUS_UPLOADING) { 3500 + if (!more) 3501 + gr_reportuploaderror(frame); 3502 + gr_set_current_upload_frame(NULL); 3503 + return; 3504 + } 3505 + 3506 + // Decode the data. 3507 + size_t data_size = 0; 3508 + char *data = gr_base64dec(payload, &data_size); 3509 + 3510 + GR_LOG("appending %u + %zu = %zu bytes\n", frame->disk_size, data_size, 3511 + frame->disk_size + data_size); 3512 + 3513 + // Do not append this data if the image exceeds the size limit. 3514 + if (frame->disk_size + data_size > 3515 + graphics_max_single_image_file_size || 3516 + frame->expected_size > graphics_max_single_image_file_size) { 3517 + free(data); 3518 + gr_delete_imagefile(frame); 3519 + frame->uploading_failure = ERROR_OVER_SIZE_LIMIT; 3520 + if (!more) 3521 + gr_reportuploaderror(frame); 3522 + gr_set_current_upload_frame(NULL); 3523 + return; 3524 + } 3525 + 3526 + // Append the data to the file. 3527 + if (!gr_append_raw_data_to_file(frame, data, data_size)) { 3528 + frame->status = STATUS_UPLOADING_ERROR; 3529 + frame->uploading_failure = ERROR_CANNOT_OPEN_CACHED_FILE; 3530 + if (!more) 3531 + gr_reportuploaderror(frame); 3532 + gr_set_current_upload_frame(NULL); 3533 + return; 3534 + } 3535 + free(data); 3536 + 3537 + if (!more) { 3538 + gr_set_current_upload_frame(NULL); 3539 + frame->status = STATUS_UPLOADING_SUCCESS; 3540 + uint32_t placement_id = frame->image->default_placement; 3541 + if (frame->expected_size && 3542 + frame->expected_size != frame->disk_size) { 3543 + // Report failure if the uploaded image size doesn't 3544 + // match the expected size. 3545 + frame->status = STATUS_UPLOADING_ERROR; 3546 + frame->uploading_failure = ERROR_UNEXPECTED_SIZE; 3547 + gr_reportuploaderror(frame); 3548 + } else { 3549 + // Make sure to redraw all existing image instances. 3550 + gr_schedule_image_redraw(frame->image); 3551 + // Try to load the image into ram and report the result. 3552 + frame = gr_loadimage_and_report(frame); 3553 + // If there is a non-virtual image placement, we may 3554 + // need to display it. 3555 + if (frame && frame->index == 1) { 3556 + Image *img = frame->image; 3557 + ImagePlacement *placement = NULL; 3558 + kh_foreach_value(img->placements, placement, { 3559 + gr_display_nonvirtual_placement(placement); 3560 + }); 3561 + } 3562 + } 3563 + } 3564 + 3565 + // Check whether we need to delete old images. 3566 + gr_check_limits(); 3567 +} 3568 + 3569 +/// Finds the image either by id or by number specified in the command. 3570 +static Image *gr_find_image_for_command(GraphicsCommand *cmd) { 3571 + if (cmd->image_id) 3572 + return gr_find_image(cmd->image_id); 3573 + Image *img = NULL; 3574 + // If the image number is not specified, we can't find the image, unless 3575 + // it's a put command, in which case we will try the last image. 3576 + if (cmd->image_number == 0 && cmd->action == 'p') 3577 + img = gr_find_image(last_image_id); 3578 + else 3579 + img = gr_find_image_by_number(cmd->image_number); 3580 + return img; 3581 +} 3582 + 3583 +/// Creates a new image or a new frame in an existing image (depending on the 3584 +/// command's action) and initializes its parameters from the command. 3585 +static ImageFrame *gr_new_image_or_frame_from_command(GraphicsCommand *cmd) { 3586 + if (cmd->format != 0 && cmd->format != 32 && cmd->format != 24 && 3587 + cmd->compression != 0) { 3588 + gr_reporterror_cmd(cmd, "EINVAL: compression is supported only " 3589 + "for raw pixel data (f=32 or f=24)"); 3590 + // Even though we report an error, we still create an image. 3591 + } 3592 + 3593 + Image *img = NULL; 3594 + if (cmd->action == 'f') { 3595 + // If it's a frame transmission action, there must be an 3596 + // existing image. 3597 + img = gr_find_image_for_command(cmd); 3598 + if (img) { 3599 + cmd->image_id = img->image_id; 3600 + } else { 3601 + gr_reporterror_cmd(cmd, "ENOENT: image not found"); 3602 + return NULL; 3603 + } 3604 + } else { 3605 + // Otherwise create a new image object. If the action is `q`, 3606 + // we'll use random id instead of the one specified in the 3607 + // command. 3608 + uint32_t image_id = cmd->action == 'q' ? 0 : cmd->image_id; 3609 + img = gr_new_image(image_id); 3610 + if (!img) 3611 + return NULL; 3612 + if (cmd->action == 'q') 3613 + img->query_id = cmd->image_id; 3614 + else if (!cmd->image_id) 3615 + cmd->image_id = img->image_id; 3616 + // Set the image number. 3617 + img->image_number = cmd->image_number; 3618 + } 3619 + 3620 + ImageFrame *frame = gr_append_new_frame(img); 3621 + // Initialize the frame. 3622 + frame->expected_size = cmd->size; 3623 + // The default format is 32. 3624 + frame->format = cmd->format ? cmd->format : 32; 3625 + frame->compression = cmd->compression; 3626 + frame->background_color = cmd->background_color; 3627 + frame->background_frame_index = cmd->background_frame; 3628 + frame->gap = cmd->gap; 3629 + img->total_duration += frame->gap; 3630 + frame->blend = !cmd->replace_instead_of_blending; 3631 + frame->data_pix_width = cmd->frame_pix_width; 3632 + frame->data_pix_height = cmd->frame_pix_height; 3633 + if (cmd->action == 'f') { 3634 + frame->x = cmd->frame_dst_pix_x; 3635 + frame->y = cmd->frame_dst_pix_y; 3636 + } 3637 + // If the expected size is not specified, we can infer it from the pixel 3638 + // width and height if the format is 24 or 32 and there is no 3639 + // compression. This is required for the shared memory transmission. 3640 + if (!frame->expected_size && !frame->compression && 3641 + (frame->format == 24 || frame->format == 32)) { 3642 + frame->expected_size = frame->data_pix_width * 3643 + frame->data_pix_height * 3644 + (frame->format / 8); 3645 + } 3646 + // We save the quietness information in the frame because for direct 3647 + // transmission subsequent transmission command won't contain this info. 3648 + frame->quiet = cmd->quiet; 3649 + return frame; 3650 +} 3651 + 3652 +/// Removes a file if it actually looks like a temporary file. 3653 +static void gr_delete_tmp_file(const char *filename) { 3654 + if (strstr(filename, "tty-graphics-protocol") == NULL) 3655 + return; 3656 + if (strstr(filename, "/tmp/") != filename) { 3657 + const char *tmpdir = getenv("TMPDIR"); 3658 + if (!tmpdir || !tmpdir[0] || 3659 + strstr(filename, tmpdir) != filename) 3660 + return; 3661 + } 3662 + unlink(filename); 3663 +} 3664 + 3665 +/// Copy the image file `frame->original_filename` to the cache directory. This 3666 +/// is done when the image is transmitted via file transfer, or when we have 3667 +/// evicted the image from the disk cache and need to restore it. 3668 +/// If `cmd` is not NULL, it's used to report errors, otherwise errors are only 3669 +/// printed to stderr. 3670 +static void gr_copy_imagefile(ImageFrame *frame, GraphicsCommand *cmd) { 3671 + GR_LOG("Copying image %s\n", 3672 + sanitized_filename(frame->original_filename)); 3673 + // Stat the file and check that it's a regular file and not too big. 3674 + struct stat st; 3675 + int stat_res = stat(frame->original_filename, &st); 3676 + 3677 + const char *stat_error = NULL; 3678 + if (stat_res) 3679 + stat_error = strerror(errno); 3680 + else if (!S_ISREG(st.st_mode)) 3681 + stat_error = "Not a regular file"; 3682 + else if (st.st_size == 0) 3683 + stat_error = "The size of the file is zero"; 3684 + else if (st.st_size > graphics_max_single_image_file_size) 3685 + stat_error = "The file is too large"; 3686 + if (stat_error) { 3687 + fprintf(stderr, "Could not load the file %s: %s\n", 3688 + sanitized_filename(frame->original_filename), 3689 + stat_error); 3690 + if (cmd) 3691 + gr_reporterror_cmd(cmd, "EBADF: %s", stat_error); 3692 + frame->status = STATUS_UPLOADING_ERROR; 3693 + frame->uploading_failure = ERROR_CANNOT_COPY_FILE; 3694 + return; 3695 + } 3696 + 3697 + // Check the expected size if specified. 3698 + if (frame->expected_size && frame->expected_size != st.st_size) { 3699 + fprintf(stderr, 3700 + "Could not load, the size doesn't match: %s expected " 3701 + "%u vs actual %ld\n", 3702 + sanitized_filename(frame->original_filename), 3703 + frame->expected_size, st.st_size); 3704 + // The file has unexpected size. 3705 + frame->status = STATUS_UPLOADING_ERROR; 3706 + frame->uploading_failure = ERROR_UNEXPECTED_SIZE; 3707 + if (cmd) 3708 + gr_reportuploaderror(frame); 3709 + return; 3710 + } 3711 + 3712 + // If we know the original modification time, we are trying to restore 3713 + // the evicted image file. Check that the modification time matches. 3714 + if (frame->original_file_mtime && 3715 + frame->original_file_mtime != st.st_mtime) { 3716 + fprintf(stderr, "Could not load, the mtime doesn't match: %s\n", 3717 + sanitized_filename(frame->original_filename)); 3718 + frame->status = STATUS_UPLOADING_ERROR; 3719 + frame->uploading_failure = ERROR_MTIME_MISMATCH; 3720 + if (cmd) 3721 + gr_reportuploaderror(frame); 3722 + return; 3723 + } 3724 + 3725 + frame->original_file_mtime = st.st_mtime; 3726 + 3727 + gr_make_sure_tmpdir_exists(); 3728 + // Build the filename for the cached copy of the file. 3729 + char cache_filename[MAX_FILENAME_SIZE]; 3730 + gr_get_frame_filename(frame, cache_filename, MAX_FILENAME_SIZE); 3731 + // We will create a symlink to the original file, and 3732 + // then copy the file to the temporary cache dir. We do 3733 + // this symlink trick mostly to be able to use cp for 3734 + // copying, and avoid escaping file name characters when 3735 + // calling system at the same time. 3736 + char tmp_filename_symlink[MAX_FILENAME_SIZE + 4] = {0}; 3737 + strcat(tmp_filename_symlink, cache_filename); 3738 + strcat(tmp_filename_symlink, ".sym"); 3739 + char command[MAX_FILENAME_SIZE + 256]; 3740 + size_t len = snprintf(command, MAX_FILENAME_SIZE + 255, "cp '%s' '%s'", 3741 + tmp_filename_symlink, cache_filename); 3742 + 3743 + if (len > MAX_FILENAME_SIZE + 255 || 3744 + symlink(frame->original_filename, tmp_filename_symlink) || 3745 + system(command) != 0) { 3746 + fprintf(stderr, 3747 + "Could not copy the image " 3748 + "%s (symlink %s) to %s", 3749 + sanitized_filename(frame->original_filename), 3750 + tmp_filename_symlink, cache_filename); 3751 + if (cmd) 3752 + gr_reporterror_cmd(cmd, "EBADF: could not copy the " 3753 + "image to the cache dir"); 3754 + frame->status = STATUS_UPLOADING_ERROR; 3755 + frame->uploading_failure = ERROR_CANNOT_COPY_FILE; 3756 + // Delete the symlink. 3757 + unlink(tmp_filename_symlink); 3758 + return; 3759 + } 3760 + 3761 + // Delete the symlink. 3762 + unlink(tmp_filename_symlink); 3763 + // Set the status and update disk size variables. 3764 + frame->status = STATUS_UPLOADING_SUCCESS; 3765 + frame->disk_size = st.st_size; 3766 + frame->image->total_disk_size += st.st_size; 3767 + images_disk_size += frame->disk_size; 3768 +} 3769 + 3770 +/// Tries to restore the image file for `frame` if the original file is still 3771 +/// available. 3772 +static void gr_try_restore_imagefile(ImageFrame *frame) { 3773 + if (frame->disk_size != 0) 3774 + return; 3775 + if (gr_is_original_file_still_available(frame)) 3776 + gr_copy_imagefile(frame, NULL); 3777 +} 3778 + 3779 +/// Handles a data transmission command. 3780 +static ImageFrame *gr_handle_transmit_command(GraphicsCommand *cmd) { 3781 + // The default is direct transmission. 3782 + if (!cmd->transmission_medium) 3783 + cmd->transmission_medium = 'd'; 3784 + 3785 + // If neither id, nor image number is specified, and the transmission 3786 + // medium is 'd' (or unspecified), and there is an active direct upload, 3787 + // this is a continuation of the upload. 3788 + if (current_upload_image_id != 0 && cmd->image_id == 0 && 3789 + cmd->image_number == 0 && cmd->transmission_medium == 'd') { 3790 + cmd->image_id = current_upload_image_id; 3791 + GR_LOG("No images id is specified, continuing uploading %u\n", 3792 + cmd->image_id); 3793 + } 3794 + 3795 + ImageFrame *frame = NULL; 3796 + if (cmd->transmission_medium == 'f' || 3797 + cmd->transmission_medium == 't') { 3798 + // File transmission. 3799 + // Create a new image or a new frame of an existing image. 3800 + frame = gr_new_image_or_frame_from_command(cmd); 3801 + if (!frame) 3802 + return NULL; 3803 + last_image_id = frame->image->image_id; 3804 + // Decode the filename. 3805 + frame->original_filename = gr_base64dec(cmd->payload, NULL); 3806 + // Copy the file to the cache directory. 3807 + gr_copy_imagefile(frame, cmd); 3808 + if (frame->status == STATUS_UPLOADING_SUCCESS) { 3809 + // Everything seems fine, try to load and redraw 3810 + // existing instances. 3811 + gr_schedule_image_redraw(frame->image); 3812 + frame = gr_loadimage_and_report(frame); 3813 + } 3814 + // Delete the original file if it's temporary. 3815 + if (cmd->transmission_medium == 't') 3816 + gr_delete_tmp_file(frame->original_filename); 3817 + gr_check_limits(); 3818 + } else if (cmd->transmission_medium == 'd') { 3819 + // Direct transmission (default if 't' is not specified). 3820 + frame = gr_get_last_frame(gr_find_image_for_command(cmd)); 3821 + if (gr_transmission_continuation_is_allowed(cmd, frame)) { 3822 + // This is a continuation of the previous transmission. 3823 + cmd->is_direct_transmission_continuation = 1; 3824 + cmd->image_id = frame->image->image_id; 3825 + gr_append_data(frame, cmd->payload, cmd->more); 3826 + return frame; 3827 + } 3828 + // Otherwise create a new image or frame structure. 3829 + frame = gr_new_image_or_frame_from_command(cmd); 3830 + if (!frame) 3831 + return NULL; 3832 + last_image_id = frame->image->image_id; 3833 + frame->status = STATUS_UPLOADING; 3834 + // Start appending data. 3835 + gr_append_data(frame, cmd->payload, cmd->more); 3836 + } else if (cmd->transmission_medium == 's') { 3837 + // Shared memory transmission. 3838 + // Create a new image or a new frame of an existing image. 3839 + frame = gr_new_image_or_frame_from_command(cmd); 3840 + if (!frame) 3841 + return NULL; 3842 + last_image_id = frame->image->image_id; 3843 + // Check that we know the size. 3844 + if (!frame->expected_size) { 3845 + frame->status = STATUS_UPLOADING_ERROR; 3846 + frame->uploading_failure = ERROR_UNEXPECTED_SIZE; 3847 + gr_reporterror_cmd( 3848 + cmd, "EINVAL: the size of the image is not " 3849 + "specified and cannot be inferred"); 3850 + return frame; 3851 + } 3852 + // Check the data size limit. 3853 + if (frame->expected_size > graphics_max_single_image_file_size) { 3854 + frame->uploading_failure = ERROR_OVER_SIZE_LIMIT; 3855 + gr_reportuploaderror(frame); 3856 + return frame; 3857 + } 3858 + // Decode the filename. 3859 + char *original_filename = gr_base64dec(cmd->payload, NULL); 3860 + GR_LOG("Loading image from shared memory %s\n", 3861 + sanitized_filename(original_filename)); 3862 + // Open the shared memory object. 3863 + int fd = shm_open(original_filename, O_RDONLY, 0); 3864 + if (fd == -1) { 3865 + gr_reporterror_cmd(cmd, "EBADF: shm_open: %s", 3866 + strerror(errno)); 3867 + frame->status = STATUS_UPLOADING_ERROR; 3868 + frame->uploading_failure = ERROR_CANNOT_OPEN_SHM; 3869 + fprintf(stderr, "shm_open failed for %s\n", 3870 + sanitized_filename(original_filename)); 3871 + shm_unlink(original_filename); 3872 + free(original_filename); 3873 + return frame; 3874 + } 3875 + shm_unlink(original_filename); 3876 + free(original_filename); 3877 + // The offset we pass to mmap must be a multiple of the page 3878 + // size. If it's not, adjust it and the size. 3879 + size_t page_size = sysconf(_SC_PAGESIZE); 3880 + if (page_size == -1) 3881 + page_size = 1; 3882 + size_t offset = cmd->offset - (cmd->offset % page_size); 3883 + size_t size = frame->expected_size + (cmd->offset - offset); 3884 + // Map the shared memory object. 3885 + void *data = 3886 + mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset); 3887 + if (data == MAP_FAILED) { 3888 + gr_reporterror_cmd(cmd, "EBADF: mmap: %s", 3889 + strerror(errno)); 3890 + frame->status = STATUS_UPLOADING_ERROR; 3891 + frame->uploading_failure = ERROR_CANNOT_OPEN_SHM; 3892 + fprintf(stderr, 3893 + "mmap failed for size = %ld, offset = %ld\n", 3894 + size, offset); 3895 + close(fd); 3896 + return frame; 3897 + } 3898 + close(fd); 3899 + // Append the data to the cache file. 3900 + if (gr_append_raw_data_to_file(frame, 3901 + data + (cmd->offset - offset), 3902 + frame->expected_size)) { 3903 + frame->status = STATUS_UPLOADING_SUCCESS; 3904 + } else { 3905 + frame->status = STATUS_UPLOADING_ERROR; 3906 + frame->uploading_failure = 3907 + ERROR_CANNOT_OPEN_CACHED_FILE; 3908 + gr_reportuploaderror(frame); 3909 + } 3910 + // Close the cache file. 3911 + gr_close_disk_cache_file(frame); 3912 + // Unmap the data 3913 + if (munmap(data, size) != 0) 3914 + fprintf(stderr, "munmap failed: %s\n", strerror(errno)); 3915 + // Try to load and redraw existing instances. 3916 + gr_schedule_image_redraw(frame->image); 3917 + frame = gr_loadimage_and_report(frame); 3918 + gr_check_limits(); 3919 + } else { 3920 + gr_reporterror_cmd( 3921 + cmd, 3922 + "EINVAL: transmission medium '%c' is not supported", 3923 + cmd->transmission_medium); 3924 + return NULL; 3925 + } 3926 + 3927 + return frame; 3928 +} 3929 + 3930 +/// Handles the 'put' command by creating a placement. 3931 +static void gr_handle_put_command(GraphicsCommand *cmd) { 3932 + if (cmd->image_id == 0 && cmd->image_number == 0) { 3933 + gr_reporterror_cmd(cmd, 3934 + "EINVAL: neither image id nor image number " 3935 + "are specified or both are zero"); 3936 + return; 3937 + } 3938 + 3939 + // Find the image with the id or number. 3940 + Image *img = gr_find_image_for_command(cmd); 3941 + if (img) { 3942 + cmd->image_id = img->image_id; 3943 + } else { 3944 + gr_reporterror_cmd(cmd, "ENOENT: image not found"); 3945 + return; 3946 + } 3947 + 3948 + // Create a placement. If a placement with the same id already exists, 3949 + // it will be deleted. If the id is zero, a random id will be generated. 3950 + ImagePlacement *placement = gr_new_placement(img, cmd->placement_id); 3951 + placement->virtual = cmd->virtual; 3952 + placement->src_pix_x = cmd->src_pix_x; 3953 + placement->src_pix_y = cmd->src_pix_y; 3954 + placement->src_pix_width = cmd->src_pix_width; 3955 + placement->src_pix_height = cmd->src_pix_height; 3956 + placement->cols = cmd->columns; 3957 + placement->rows = cmd->rows; 3958 + placement->do_not_move_cursor = cmd->do_not_move_cursor; 3959 + 3960 + if (placement->virtual) { 3961 + placement->scale_mode = SCALE_MODE_CONTAIN; 3962 + } else if (placement->cols && placement->rows) { 3963 + // For classic placements the default is to stretch the image if 3964 + // both cols and rows are specified. 3965 + placement->scale_mode = SCALE_MODE_FILL; 3966 + } else if (placement->cols || placement->rows) { 3967 + // But if only one of them is specified, the default is to 3968 + // contain. 3969 + placement->scale_mode = SCALE_MODE_CONTAIN; 3970 + } else { 3971 + // If none of them are specified, the default is to use the 3972 + // original size. 3973 + placement->scale_mode = SCALE_MODE_NONE; 3974 + } 3975 + 3976 + // Display the placement unless it's virtual. 3977 + gr_display_nonvirtual_placement(placement); 3978 + 3979 + // Report success. 3980 + gr_reportsuccess_cmd(cmd); 3981 +} 3982 + 3983 +/// Information about what to delete. 3984 +typedef struct DeletionData { 3985 + uint32_t image_id; 3986 + uint32_t placement_id; 3987 + /// Visible placement found during screen traversal that need to be 3988 + /// deleted. Each placement must occur only once in this vector. 3989 + ImagePlacementVec placements_to_delete; 3990 +} DeletionData; 3991 + 3992 +/// The callback called for each cell to perform deletion. 3993 +static int gr_deletion_callback(void *data, Glyph *gp) { 3994 + DeletionData *del_data = data; 3995 + // Leave unicode placeholders alone. 3996 + if (!tgetisclassicplaceholder(gp)) 3997 + return 0; 3998 + uint32_t image_id = tgetimgid(gp); 3999 + uint32_t placement_id = tgetimgplacementid(gp); 4000 + if (del_data->image_id && del_data->image_id != image_id) 4001 + return 0; 4002 + if (del_data->placement_id && del_data->placement_id != placement_id) 4003 + return 0; 4004 + 4005 + ImagePlacement *placement = NULL; 4006 + 4007 + // Record the placement to delete. We will actually delete it later. 4008 + for (int i = 0; i < kv_size(del_data->placements_to_delete); ++i) { 4009 + ImagePlacement *cand = kv_A(del_data->placements_to_delete, i); 4010 + if (cand->image->image_id == image_id && 4011 + cand->placement_id == placement_id) { 4012 + placement = cand; 4013 + break; 4014 + } 4015 + } 4016 + if (!placement) { 4017 + placement = gr_find_image_and_placement(image_id, placement_id); 4018 + if (placement) { 4019 + kv_push(ImagePlacement *, 4020 + del_data->placements_to_delete, placement); 4021 + } 4022 + } 4023 + 4024 + // Restore the text underneath the placement if possible. 4025 + if (placement && placement->text_underneath) { 4026 + int row = tgetimgrow(gp) - 1; 4027 + int col = tgetimgcol(gp) - 1; 4028 + if (col >= 0 && row >= 0 && row < placement->rows && 4029 + col < placement->cols) { 4030 + *gp = placement->text_underneath[row * placement->cols + 4031 + col]; 4032 + return 1; 4033 + } 4034 + } 4035 + 4036 + // Otherwise just erase the cell. 4037 + gp->mode = 0; 4038 + gp->u = ' '; 4039 + return 1; 4040 +} 4041 + 4042 +/// Handles the delete command. 4043 +static void gr_handle_delete_command(GraphicsCommand *cmd) { 4044 + DeletionData del_data = {0}; 4045 + char delete_image_if_no_ref = isupper(cmd->delete_specifier) != 0; 4046 + char d = tolower(cmd->delete_specifier); 4047 + 4048 + if (d == 'n') { 4049 + d = 'i'; 4050 + Image *img = gr_find_image_by_number(cmd->image_number); 4051 + if (!img) 4052 + return; 4053 + del_data.image_id = img->image_id; 4054 + } 4055 + 4056 + kv_init(del_data.placements_to_delete); 4057 + 4058 + if (!d || d == 'a') { 4059 + // Delete all visible placements. 4060 + gr_for_each_image_cell(gr_deletion_callback, &del_data); 4061 + } else if (d == 'i') { 4062 + // Delete the specified image by image id and maybe placement 4063 + // id. 4064 + if (!del_data.image_id) 4065 + del_data.image_id = cmd->image_id; 4066 + if (!del_data.image_id) { 4067 + fprintf(stderr, 4068 + "ERROR: image id is not specified in the " 4069 + "delete command\n"); 4070 + kv_destroy(del_data.placements_to_delete); 4071 + return; 4072 + } 4073 + del_data.placement_id = cmd->placement_id; 4074 + gr_for_each_image_cell(gr_deletion_callback, &del_data); 4075 + } else { 4076 + fprintf(stderr, 4077 + "WARNING: unsupported value of the d key: '%c'. The " 4078 + "command is ignored.\n", 4079 + cmd->delete_specifier); 4080 + } 4081 + 4082 + // Delete the placements we have collected and maybe images too. 4083 + for (int i = 0; i < kv_size(del_data.placements_to_delete); ++i) { 4084 + ImagePlacement *placement = 4085 + kv_A(del_data.placements_to_delete, i); 4086 + // Delete the text underneath the placement and set it to NULL 4087 + // to avoid erasing it from the screen again. 4088 + free(placement->text_underneath); 4089 + placement->text_underneath = NULL; 4090 + Image *img = placement->image; 4091 + gr_delete_placement(placement); 4092 + // Delete the image if image deletion is requested (uppercase 4093 + // delete specifier) and there are no more placements. 4094 + if (delete_image_if_no_ref && kh_size(img->placements) == 0) 4095 + gr_delete_image(img); 4096 + } 4097 + 4098 + // NOTE: It's not very clear whether we should delete the image 4099 + // even if there are no _visible_ placements to delete. We do 4100 + // this because otherwise there is no way to delete an image 4101 + // with virtual placements in one command. 4102 + if (d == 'i' && !del_data.placement_id && delete_image_if_no_ref) 4103 + gr_delete_image(gr_find_image(cmd->image_id)); 4104 + 4105 + kv_destroy(del_data.placements_to_delete); 4106 +} 4107 + 4108 +/// Clears the cells occupied by the placement. This is normally done when 4109 +/// implicitly deleting a classic placement. 4110 +static void gr_erase_placement(ImagePlacement *placement) { 4111 + DeletionData del_data = {0}; 4112 + del_data.image_id = placement->image->image_id; 4113 + del_data.placement_id = placement->placement_id; 4114 + kv_init(del_data.placements_to_delete); 4115 + gr_for_each_image_cell(gr_deletion_callback, &del_data); 4116 + // Delete the text underneath the placement and set it to NULL 4117 + // to avoid erasing it from the screen again. 4118 + free(placement->text_underneath); 4119 + placement->text_underneath = NULL; 4120 + kv_destroy(del_data.placements_to_delete); 4121 +} 4122 + 4123 +static void gr_handle_animation_control_command(GraphicsCommand *cmd) { 4124 + if (cmd->image_id == 0 && cmd->image_number == 0) { 4125 + gr_reporterror_cmd(cmd, 4126 + "EINVAL: neither image id nor image number " 4127 + "are specified or both are zero"); 4128 + return; 4129 + } 4130 + 4131 + // Find the image with the id or number. 4132 + Image *img = gr_find_image_for_command(cmd); 4133 + if (img) { 4134 + cmd->image_id = img->image_id; 4135 + } else { 4136 + gr_reporterror_cmd(cmd, "ENOENT: image not found"); 4137 + return; 4138 + } 4139 + 4140 + // Find the frame to edit, if requested. 4141 + ImageFrame *frame = NULL; 4142 + if (cmd->edit_frame) 4143 + frame = gr_get_frame(img, cmd->edit_frame); 4144 + if (cmd->edit_frame || cmd->gap) { 4145 + if (!frame) { 4146 + gr_reporterror_cmd(cmd, "ENOENT: frame %d not found", 4147 + cmd->edit_frame); 4148 + return; 4149 + } 4150 + if (cmd->gap) { 4151 + img->total_duration -= frame->gap; 4152 + frame->gap = cmd->gap; 4153 + img->total_duration += frame->gap; 4154 + } 4155 + } 4156 + 4157 + // Set animation-related parameters of the image. 4158 + if (cmd->current_frame) 4159 + img->current_frame = cmd->current_frame; 4160 + if (cmd->animation_state) { 4161 + if (cmd->animation_state == 1) { 4162 + img->animation_state = ANIMATION_STATE_STOPPED; 4163 + } else if (cmd->animation_state == 2) { 4164 + img->animation_state = ANIMATION_STATE_LOADING; 4165 + } else if (cmd->animation_state == 3) { 4166 + img->animation_state = ANIMATION_STATE_LOOPING; 4167 + } else { 4168 + gr_reporterror_cmd( 4169 + cmd, "EINVAL: invalid animation state: %d", 4170 + cmd->animation_state); 4171 + } 4172 + } 4173 + // TODO: Set the number of loops to cmd->loops 4174 + 4175 + // Make sure we redraw all instances of the image. 4176 + gr_schedule_image_redraw(img); 4177 +} 4178 + 4179 +/// Handles a command. 4180 +static void gr_handle_command(GraphicsCommand *cmd) { 4181 + if (!cmd->image_id && !cmd->image_number) { 4182 + // If there is no image id or image number, nobody expects a 4183 + // response, so set quiet to 2. 4184 + cmd->quiet = 2; 4185 + } 4186 + 4187 + int was_transmission = 0; 4188 + ImageFrame *frame = NULL; 4189 + 4190 + switch (cmd->action) { 4191 + case 0: 4192 + // If no action is specified, it is data transmission. 4193 + case 't': 4194 + case 'q': 4195 + case 'f': 4196 + was_transmission = 1; 4197 + // Transmit data. 'q' means query, which is basically the same 4198 + // as transmit, but the image is discarded, and the id is fake. 4199 + // 'f' appends a frame to an existing image. 4200 + gr_handle_transmit_command(cmd); 4201 + break; 4202 + case 'p': 4203 + // Display (put) the image. 4204 + gr_handle_put_command(cmd); 4205 + break; 4206 + case 'T': 4207 + was_transmission = 1; 4208 + // Transmit and display. 4209 + frame = gr_handle_transmit_command(cmd); 4210 + if (frame && !cmd->is_direct_transmission_continuation) { 4211 + gr_handle_put_command(cmd); 4212 + if (cmd->placement_id) 4213 + frame->image->initial_placement_id = 4214 + cmd->placement_id; 4215 + } 4216 + break; 4217 + case 'd': 4218 + gr_handle_delete_command(cmd); 4219 + break; 4220 + case 'a': 4221 + gr_handle_animation_control_command(cmd); 4222 + break; 4223 + default: 4224 + gr_reporterror_cmd(cmd, "EINVAL: unsupported action: %c", 4225 + cmd->action); 4226 + break; 4227 + } 4228 + 4229 + if (!was_transmission || 4230 + (cmd->transmission_medium && cmd->transmission_medium != 'd')) { 4231 + // If it wasn't a transmission command, or if the transmission 4232 + // wasn't direct, clear the current upload frame and close the 4233 + // file. (If it was a direct transmission, the current upload 4234 + // was handled inside `gr_append_data`.) 4235 + gr_set_current_upload_frame(NULL); 4236 + } 4237 +} 4238 + 4239 +/// A partially parsed key-value pair. 4240 +typedef struct KeyAndValue { 4241 + char *key_start; 4242 + char *val_start; 4243 + unsigned key_len, val_len; 4244 +} KeyAndValue; 4245 + 4246 +/// Parses the value of a key and assigns it to the appropriate field of `cmd`. 4247 +static void gr_set_keyvalue(GraphicsCommand *cmd, KeyAndValue *kv) { 4248 + char *key_start = kv->key_start; 4249 + char *key_end = key_start + kv->key_len; 4250 + char *value_start = kv->val_start; 4251 + char *value_end = value_start + kv->val_len; 4252 + // Currently all keys are one-character. 4253 + if (key_end - key_start != 1) { 4254 + gr_reporterror_cmd(cmd, "EINVAL: unknown key of length %ld: %s", 4255 + key_end - key_start, key_start); 4256 + return; 4257 + } 4258 + long num = 0; 4259 + if (*key_start == 'a' || *key_start == 't' || *key_start == 'd' || 4260 + *key_start == 'o') { 4261 + // Some keys have one-character values. 4262 + if (value_end - value_start != 1) { 4263 + gr_reporterror_cmd( 4264 + cmd, 4265 + "EINVAL: value of 'a', 't' or 'd' must be a " 4266 + "single char: %s", 4267 + key_start); 4268 + return; 4269 + } 4270 + } else { 4271 + // All the other keys have integer values. 4272 + char *num_end = NULL; 4273 + num = strtol(value_start, &num_end, 10); 4274 + if (num_end != value_end) { 4275 + gr_reporterror_cmd( 4276 + cmd, "EINVAL: could not parse number value: %s", 4277 + key_start); 4278 + return; 4279 + } 4280 + } 4281 + switch (*key_start) { 4282 + case 'a': 4283 + cmd->action = *value_start; 4284 + break; 4285 + case 't': 4286 + cmd->transmission_medium = *value_start; 4287 + break; 4288 + case 'd': 4289 + cmd->delete_specifier = *value_start; 4290 + break; 4291 + case 'q': 4292 + cmd->quiet = num; 4293 + break; 4294 + case 'f': 4295 + cmd->format = num; 4296 + if (num != 0 && num != 24 && num != 32 && num != 100) { 4297 + gr_reporterror_cmd( 4298 + cmd, 4299 + "EINVAL: unsupported format specification: %s", 4300 + key_start); 4301 + } 4302 + break; 4303 + case 'o': 4304 + cmd->compression = *value_start; 4305 + if (cmd->compression != 'z') { 4306 + gr_reporterror_cmd(cmd, 4307 + "EINVAL: unsupported compression " 4308 + "specification: %s", 4309 + key_start); 4310 + } 4311 + break; 4312 + case 's': 4313 + if (cmd->action == 'a') 4314 + cmd->animation_state = num; 4315 + else 4316 + cmd->frame_pix_width = num; 4317 + break; 4318 + case 'v': 4319 + if (cmd->action == 'a') 4320 + cmd->loops = num; 4321 + else 4322 + cmd->frame_pix_height = num; 4323 + break; 4324 + case 'i': 4325 + cmd->image_id = num; 4326 + break; 4327 + case 'I': 4328 + cmd->image_number = num; 4329 + break; 4330 + case 'p': 4331 + cmd->placement_id = num; 4332 + break; 4333 + case 'x': 4334 + cmd->src_pix_x = num; 4335 + cmd->frame_dst_pix_x = num; 4336 + break; 4337 + case 'y': 4338 + if (cmd->action == 'f') 4339 + cmd->frame_dst_pix_y = num; 4340 + else 4341 + cmd->src_pix_y = num; 4342 + break; 4343 + case 'w': 4344 + cmd->src_pix_width = num; 4345 + break; 4346 + case 'h': 4347 + cmd->src_pix_height = num; 4348 + break; 4349 + case 'c': 4350 + if (cmd->action == 'f') 4351 + cmd->background_frame = num; 4352 + else if (cmd->action == 'a') 4353 + cmd->current_frame = num; 4354 + else 4355 + cmd->columns = num; 4356 + break; 4357 + case 'r': 4358 + if (cmd->action == 'f' || cmd->action == 'a') 4359 + cmd->edit_frame = num; 4360 + else 4361 + cmd->rows = num; 4362 + break; 4363 + case 'm': 4364 + cmd->more = num; 4365 + break; 4366 + case 'S': 4367 + cmd->size = num; 4368 + break; 4369 + case 'O': 4370 + cmd->offset = num; 4371 + break; 4372 + case 'U': 4373 + cmd->virtual = num; 4374 + break; 4375 + case 'X': 4376 + if (cmd->action == 'f') 4377 + cmd->replace_instead_of_blending = num; 4378 + else 4379 + break; /*ignore*/ 4380 + break; 4381 + case 'Y': 4382 + if (cmd->action == 'f') 4383 + cmd->background_color = num; 4384 + else 4385 + break; /*ignore*/ 4386 + break; 4387 + case 'z': 4388 + if (cmd->action == 'f' || cmd->action == 'a') 4389 + cmd->gap = num; 4390 + else 4391 + break; /*ignore*/ 4392 + break; 4393 + case 'C': 4394 + cmd->do_not_move_cursor = num; 4395 + break; 4396 + default: 4397 + gr_reporterror_cmd(cmd, "EINVAL: unsupported key: %s", 4398 + key_start); 4399 + return; 4400 + } 4401 +} 4402 + 4403 +/// Parse and execute a graphics command. `buf` must start with 'G' and contain 4404 +/// at least `len + 1` characters. Returns 1 on success. 4405 +int gr_parse_command(char *buf, size_t len) { 4406 + if (buf[0] != 'G') 4407 + return 0; 4408 + 4409 + Milliseconds command_start_time = gr_now_ms(); 4410 + debug_loaded_files_counter = 0; 4411 + debug_loaded_pixmaps_counter = 0; 4412 + global_command_counter++; 4413 + GR_LOG("### Command %lu: %.80s\n", global_command_counter, buf); 4414 + 4415 + memset(&graphics_command_result, 0, sizeof(GraphicsCommandResult)); 4416 + 4417 + // Eat the 'G'. 4418 + ++buf; 4419 + --len; 4420 + 4421 + GraphicsCommand cmd = {.command = buf}; 4422 + // The state of parsing. 'k' to parse key, 'v' to parse value, 'p' to 4423 + // parse the payload. 4424 + char state = 'k'; 4425 + // An array of partially parsed key-value pairs. 4426 + KeyAndValue key_vals[32]; 4427 + unsigned key_vals_count = 0; 4428 + char *key_start = buf; 4429 + char *key_end = NULL; 4430 + char *val_start = NULL; 4431 + char *val_end = NULL; 4432 + char *c = buf; 4433 + while (c - buf < len + 1) { 4434 + if (state == 'k') { 4435 + switch (*c) { 4436 + case ',': 4437 + case ';': 4438 + case '\0': 4439 + state = *c == ',' ? 'k' : 'p'; 4440 + key_end = c; 4441 + gr_reporterror_cmd( 4442 + &cmd, "EINVAL: key without value: %s ", 4443 + key_start); 4444 + break; 4445 + case '=': 4446 + key_end = c; 4447 + state = 'v'; 4448 + val_start = c + 1; 4449 + break; 4450 + default: 4451 + break; 4452 + } 4453 + } else if (state == 'v') { 4454 + switch (*c) { 4455 + case ',': 4456 + case ';': 4457 + case '\0': 4458 + state = *c == ',' ? 'k' : 'p'; 4459 + val_end = c; 4460 + if (key_vals_count >= 4461 + sizeof(key_vals) / sizeof(*key_vals)) { 4462 + gr_reporterror_cmd(&cmd, 4463 + "EINVAL: too many " 4464 + "key-value pairs"); 4465 + break; 4466 + } 4467 + key_vals[key_vals_count].key_start = key_start; 4468 + key_vals[key_vals_count].val_start = val_start; 4469 + key_vals[key_vals_count].key_len = 4470 + key_end - key_start; 4471 + key_vals[key_vals_count].val_len = 4472 + val_end - val_start; 4473 + ++key_vals_count; 4474 + key_start = c + 1; 4475 + break; 4476 + default: 4477 + break; 4478 + } 4479 + } else if (state == 'p') { 4480 + cmd.payload = c; 4481 + // break out of the loop, we don't check the payload 4482 + break; 4483 + } 4484 + ++c; 4485 + } 4486 + 4487 + // Set the action key ('a=') first because we need it to disambiguate 4488 + // some keys. Also set 'i=' and 'I=' for better error reporting. 4489 + for (unsigned i = 0; i < key_vals_count; ++i) { 4490 + if (key_vals[i].key_len == 1) { 4491 + char *start = key_vals[i].key_start; 4492 + if (*start == 'a' || *start == 'i' || *start == 'I') { 4493 + gr_set_keyvalue(&cmd, &key_vals[i]); 4494 + break; 4495 + } 4496 + } 4497 + } 4498 + // Set the rest of the keys. 4499 + for (unsigned i = 0; i < key_vals_count; ++i) 4500 + gr_set_keyvalue(&cmd, &key_vals[i]); 4501 + 4502 + if (!cmd.payload) 4503 + cmd.payload = buf + len; 4504 + 4505 + if (cmd.payload && cmd.payload[0]) 4506 + GR_LOG(" payload size: %ld\n", strlen(cmd.payload)); 4507 + 4508 + if (!graphics_command_result.error) 4509 + gr_handle_command(&cmd); 4510 + 4511 + if (graphics_debug_mode) { 4512 + fprintf(stderr, "Response: "); 4513 + for (const char *resp = graphics_command_result.response; 4514 + *resp != '\0'; ++resp) { 4515 + if (isprint(*resp)) 4516 + fprintf(stderr, "%c", *resp); 4517 + else 4518 + fprintf(stderr, "(0x%x)", *resp); 4519 + } 4520 + fprintf(stderr, "\n"); 4521 + } 4522 + 4523 + // Make sure that we suppress response if needed. Usually cmd.quiet is 4524 + // taken into account when creating the response, but it's not very 4525 + // reliable in the current implementation. 4526 + if (cmd.quiet) { 4527 + if (!graphics_command_result.error || cmd.quiet >= 2) 4528 + graphics_command_result.response[0] = '\0'; 4529 + } 4530 + 4531 + Milliseconds command_end_time = gr_now_ms(); 4532 + GR_LOG("Command %lu took %ld ms (loaded %d files, %d pixmaps)\n\n", 4533 + global_command_counter, command_end_time - command_start_time, 4534 + debug_loaded_files_counter, debug_loaded_pixmaps_counter); 4535 + 4536 + return 1; 4537 +} 4538 + 4539 +//////////////////////////////////////////////////////////////////////////////// 4540 +// base64 decoding part is basically copied from st.c 4541 +//////////////////////////////////////////////////////////////////////////////// 4542 + 4543 +static const char gr_base64_digits[] = { 4544 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4545 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4546 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 4547 + 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 4548 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 4549 + 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 4550 + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 4551 + 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4552 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4553 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4554 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4555 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4556 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4557 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4558 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 4559 + 4560 +static char gr_base64_getc(const char **src) { 4561 + while (**src && !isprint(**src)) 4562 + (*src)++; 4563 + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 4564 +} 4565 + 4566 +char *gr_base64dec(const char *src, size_t *size) { 4567 + size_t in_len = strlen(src); 4568 + char *result, *dst; 4569 + 4570 + result = dst = malloc((in_len + 3) / 4 * 3 + 1); 4571 + while (*src) { 4572 + int a = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 4573 + int b = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 4574 + int c = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 4575 + int d = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 4576 + 4577 + if (a == -1 || b == -1) 4578 + break; 4579 + 4580 + *dst++ = (a << 2) | ((b & 0x30) >> 4); 4581 + if (c == -1) 4582 + break; 4583 + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 4584 + if (d == -1) 4585 + break; 4586 + *dst++ = ((c & 0x03) << 6) | d; 4587 + } 4588 + *dst = '\0'; 4589 + if (size) { 4590 + *size = dst - result; 4591 + } 4592 + return result; 4593 +} 4594 diff --git a/graphics.h b/graphics.h 4595 new file mode 100644 4596 index 0000000..f9a649a 4597 --- /dev/null 4598 +++ b/graphics.h 4599 @@ -0,0 +1,112 @@ 4600 + 4601 +#include <stdint.h> 4602 +#include <sys/types.h> 4603 +#include <X11/Xlib.h> 4604 + 4605 +/// Initialize the graphics module. 4606 +void gr_init(Display *disp, Visual *vis, Colormap cm); 4607 +/// Deinitialize the graphics module. 4608 +void gr_deinit(); 4609 + 4610 +/// Add an image rectangle to a list if rectangles to draw. This function may 4611 +/// actually draw some rectangles, or it may wait till more rectangles are 4612 +/// appended. Must be called between `gr_start_drawing` and `gr_finish_drawing`. 4613 +/// - `img_start_col..img_end_col` and `img_start_row..img_end_row` define the 4614 +/// part of the image to draw (row/col indices are zero-based, ends are 4615 +/// excluded). 4616 +/// - `x_col` and `y_row` are the coordinates of the top-left corner of the 4617 +/// image in the terminal grid. 4618 +/// - `x_pix` and `y_pix` are the same but in pixels. 4619 +/// - `reverse` indicates whether colors should be inverted. 4620 +void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, 4621 + int img_start_col, int img_end_col, int img_start_row, 4622 + int img_end_row, int x_col, int y_row, int x_pix, 4623 + int y_pix, int cw, int ch, int reverse); 4624 +/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. 4625 +void gr_start_drawing(Drawable buf, int cw, int ch); 4626 +/// Finish image drawing. This functions will draw all the rectangles left to 4627 +/// draw. 4628 +void gr_finish_drawing(Drawable buf); 4629 +/// Mark rows containing animations as dirty if it's time to redraw them. Must 4630 +/// be called right after `gr_start_drawing`. 4631 +void gr_mark_dirty_animations(int *dirty, int rows); 4632 + 4633 +/// Parse and execute a graphics command. `buf` must start with 'G' and contain 4634 +/// at least `len + 1` characters (including '\0'). Returns 1 on success. 4635 +/// Additional informations is returned through `graphics_command_result`. 4636 +int gr_parse_command(char *buf, size_t len); 4637 + 4638 +/// Executes `command` with the name of the file corresponding to `image_id` as 4639 +/// the argument. Executes xmessage with an error message on failure. 4640 +void gr_preview_image(uint32_t image_id, const char *command); 4641 + 4642 +/// Executes `<st> -e less <file>` where <file> is the name of a temporary file 4643 +/// containing the information about an image and placement, and <st> is 4644 +/// specified with `st_executable`. 4645 +void gr_show_image_info(uint32_t image_id, uint32_t placement_id, 4646 + uint32_t imgcol, uint32_t imgrow, 4647 + char is_classic_placeholder, int32_t diacritic_count, 4648 + char *st_executable); 4649 + 4650 +/// Dumps the internal state (images and placements) to stderr. 4651 +void gr_dump_state(); 4652 + 4653 +/// Unloads images to reduce RAM usage. 4654 +void gr_unload_images_to_reduce_ram(); 4655 + 4656 +/// Executes `callback` for each image cell. The callback should return 1 if it 4657 +/// changed the glyph. This function is implemented in `st.c`. 4658 +void gr_for_each_image_cell(int (*callback)(void *data, Glyph *gp), 4659 + void *data); 4660 + 4661 +/// Marks all the rows containing the image with `image_id` as dirty. 4662 +void gr_schedule_image_redraw_by_id(uint32_t image_id); 4663 + 4664 +/// Returns a pointer to the glyph under the classic placement with `image_id` 4665 +/// and `placement_id` at `col` and `row` (1-based). May return NULL if the 4666 +/// underneath text is unknown. 4667 +Glyph *gr_get_glyph_underneath_image(uint32_t image_id, uint32_t placement_id, 4668 + int col, int row); 4669 + 4670 +typedef enum { 4671 + GRAPHICS_DEBUG_NONE = 0, 4672 + GRAPHICS_DEBUG_LOG = 1, 4673 + GRAPHICS_DEBUG_LOG_AND_BOXES = 2, 4674 +} GraphicsDebugMode; 4675 + 4676 +/// Print additional information, draw bounding bounding boxes, etc. 4677 +extern GraphicsDebugMode graphics_debug_mode; 4678 + 4679 +/// Whether to display images or just draw bounding boxes. 4680 +extern char graphics_display_images; 4681 + 4682 +/// The time in milliseconds until the next redraw to update animations. 4683 +/// INT_MAX means no redraw is needed. Populated by `gr_finish_drawing`. 4684 +extern int graphics_next_redraw_delay; 4685 + 4686 +#define MAX_GRAPHICS_RESPONSE_LEN 256 4687 + 4688 +/// A structure representing the result of a graphics command. 4689 +typedef struct { 4690 + /// Indicates if the terminal needs to be redrawn. 4691 + char redraw; 4692 + /// The response of the command that should be sent back to the client 4693 + /// (may be empty if the quiet flag is set). 4694 + char response[MAX_GRAPHICS_RESPONSE_LEN]; 4695 + /// Whether there was an error executing this command (not very useful, 4696 + /// the response must be sent back anyway). 4697 + char error; 4698 + /// Whether the terminal has to create a placeholder for a non-virtual 4699 + /// placement. 4700 + char create_placeholder; 4701 + /// The placeholder that needs to be created. 4702 + struct { 4703 + uint32_t rows, columns; 4704 + uint32_t image_id, placement_id; 4705 + char do_not_move_cursor; 4706 + Glyph *text_underneath; 4707 + } placeholder; 4708 +} GraphicsCommandResult; 4709 + 4710 +/// The result of a graphics command. 4711 +extern GraphicsCommandResult graphics_command_result; 4712 diff --git a/icat-mini.sh b/icat-mini.sh 4713 new file mode 100755 4714 index 0000000..8f240e9 4715 --- /dev/null 4716 +++ b/icat-mini.sh 4717 @@ -0,0 +1,875 @@ 4718 +#!/bin/sh 4719 + 4720 +# vim: shiftwidth=4 4721 + 4722 +script_name="$(basename "$0")" 4723 + 4724 +short_help="Usage: $script_name [OPTIONS] <image_file> 4725 + 4726 +This is a script to display images in the terminal using the kitty graphics 4727 +protocol with Unicode placeholders. It is very basic, please use something else 4728 +if you have alternatives. 4729 + 4730 +Options: 4731 + -h Show this help. 4732 + -s SCALE The scale of the image, may be floating point. 4733 + -c N, --cols N The number of columns. 4734 + -r N, --rows N The number of rows. 4735 + --max-cols N The maximum number of columns. 4736 + --max-rows N The maximum number of rows. 4737 + --cell-size WxH The cell size in pixels. 4738 + -m METHOD The uploading method, may be 'file', 'direct' or 'auto'. 4739 + --speed SPEED The multiplier for the animation speed (float). 4740 +" 4741 + 4742 +# Exit the script on keyboard interrupt 4743 +trap "echo 'icat-mini was interrupted' >&2; exit 1" INT 4744 + 4745 +cols="" 4746 +rows="" 4747 +file="" 4748 +command_tty="" 4749 +response_tty="" 4750 +uploading_method="auto" 4751 +cell_size="" 4752 +scale=1 4753 +max_cols="" 4754 +max_rows="" 4755 +speed="" 4756 + 4757 +# Parse the command line. 4758 +while [ $# -gt 0 ]; do 4759 + case "$1" in 4760 + -c|--columns|--cols) 4761 + cols="$2" 4762 + shift 2 4763 + ;; 4764 + -r|--rows|-l|--lines) 4765 + rows="$2" 4766 + shift 2 4767 + ;; 4768 + -s|--scale) 4769 + scale="$2" 4770 + shift 2 4771 + ;; 4772 + -h|--help) 4773 + echo "$short_help" 4774 + exit 0 4775 + ;; 4776 + -m|--upload-method|--uploading-method) 4777 + uploading_method="$2" 4778 + shift 2 4779 + ;; 4780 + --cell-size) 4781 + cell_size="$2" 4782 + shift 2 4783 + ;; 4784 + --max-cols) 4785 + max_cols="$2" 4786 + shift 2 4787 + ;; 4788 + --max-rows) 4789 + max_rows="$2" 4790 + shift 2 4791 + ;; 4792 + --speed) 4793 + speed="$2" 4794 + shift 2 4795 + ;; 4796 + --) 4797 + file="$2" 4798 + shift 2 4799 + ;; 4800 + -*) 4801 + echo "Unknown option: $1" >&2 4802 + exit 1 4803 + ;; 4804 + *) 4805 + if [ -n "$file" ]; then 4806 + echo "Multiple image files are not supported: $file and $1" >&2 4807 + exit 1 4808 + fi 4809 + file="$1" 4810 + shift 4811 + ;; 4812 + esac 4813 +done 4814 + 4815 +file="$(realpath "$file")" 4816 + 4817 +##################################################################### 4818 +# Detect imagemagick 4819 +##################################################################### 4820 + 4821 +# If there is the 'magick' command, use it instead of separate 'convert' and 4822 +# 'identify' commands. 4823 +if command -v magick > /dev/null; then 4824 + identify="magick identify" 4825 + convert="magick" 4826 +else 4827 + identify="identify" 4828 + convert="convert" 4829 +fi 4830 + 4831 +##################################################################### 4832 +# Detect tmux 4833 +##################################################################### 4834 + 4835 +# Check if we are inside tmux. 4836 +inside_tmux="" 4837 +if [ -n "$TMUX" ]; then 4838 + inside_tmux=1 4839 +fi 4840 + 4841 +if [ -z "$command_tty" ] && [ -n "$inside_tmux" ]; then 4842 + # Get the pty of the current tmux pane. 4843 + command_tty="$(tmux display-message -t "$TMUX_PANE" -p "#{pane_tty}")" 4844 + if [ ! -e "$command_tty" ]; then 4845 + command_tty="" 4846 + fi 4847 +fi 4848 + 4849 +##################################################################### 4850 +# Adjust the terminal state 4851 +##################################################################### 4852 + 4853 +if [ -z "$command_tty" ]; then 4854 + command_tty="/dev/tty" 4855 +fi 4856 +if [ -z "$response_tty" ]; then 4857 + response_tty="/dev/tty" 4858 +fi 4859 + 4860 +stty_orig="$(stty -g < "$response_tty")" 4861 +stty -echo < "$response_tty" 4862 +# Disable ctrl-z. Pressing ctrl-z during image uploading may cause some 4863 +# horrible issues otherwise. 4864 +stty susp undef < "$response_tty" 4865 +stty -icanon < "$response_tty" 4866 + 4867 +restore_echo() { 4868 + [ -n "$stty_orig" ] || return 4869 + stty $stty_orig < "$response_tty" 4870 +} 4871 + 4872 +trap restore_echo EXIT TERM 4873 + 4874 +##################################################################### 4875 +# Compute the number of rows and columns 4876 +##################################################################### 4877 + 4878 +is_pos_int() { 4879 + if [ -z "$1" ]; then 4880 + return 1 # false 4881 + fi 4882 + if [ -z "$(printf '%s' "$1" | tr -d '[:digit:]')" ]; then 4883 + if [ "$1" -gt 0 ]; then 4884 + return 0 # true 4885 + fi 4886 + fi 4887 + return 1 # false 4888 +} 4889 + 4890 +if [ -n "$cols" ] || [ -n "$rows" ]; then 4891 + if [ -n "$max_cols" ] || [ -n "$max_rows" ]; then 4892 + echo "You can't specify both max-cols/rows and cols/rows" >&2 4893 + exit 1 4894 + fi 4895 +fi 4896 + 4897 +# Get the max number of cols and rows. 4898 +[ -n "$max_cols" ] || max_cols="$(tput cols)" 4899 +[ -n "$max_rows" ] || max_rows="$(tput lines)" 4900 +if [ "$max_rows" -gt 255 ]; then 4901 + max_rows=255 4902 +fi 4903 + 4904 +python_ioctl_command="import array, fcntl, termios 4905 +buf = array.array('H', [0, 0, 0, 0]) 4906 +fcntl.ioctl(0, termios.TIOCGWINSZ, buf) 4907 +print(int(buf[2]/buf[1]), int(buf[3]/buf[0]))" 4908 + 4909 +# Get the cell size in pixels if either cols or rows are not specified. 4910 +if [ -z "$cols" ] || [ -z "$rows" ]; then 4911 + cell_width="" 4912 + cell_height="" 4913 + # If the cell size is specified, use it. 4914 + if [ -n "$cell_size" ]; then 4915 + cell_width="${cell_size%x*}" 4916 + cell_height="${cell_size#*x}" 4917 + if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then 4918 + echo "Invalid cell size: $cell_size" >&2 4919 + exit 1 4920 + fi 4921 + fi 4922 + # Otherwise try to use TIOCGWINSZ ioctl via python. 4923 + if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then 4924 + cell_size_ioctl="$(python3 -c "$python_ioctl_command" < "$command_tty" 2> /dev/null)" 4925 + cell_width="${cell_size_ioctl% *}" 4926 + cell_height="${cell_size_ioctl#* }" 4927 + if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then 4928 + cell_width="" 4929 + cell_height="" 4930 + fi 4931 + fi 4932 + # If it didn't work, try to use csi XTWINOPS. 4933 + if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then 4934 + if [ -n "$inside_tmux" ]; then 4935 + printf '\ePtmux;\e\e[16t\e\\' >> "$command_tty" 4936 + else 4937 + printf '\e[16t' >> "$command_tty" 4938 + fi 4939 + # The expected response will look like ^[[6;<height>;<width>t 4940 + term_response="" 4941 + while true; do 4942 + char=$(dd bs=1 count=1 <"$response_tty" 2>/dev/null) 4943 + if [ "$char" = "t" ]; then 4944 + break 4945 + fi 4946 + term_response="$term_response$char" 4947 + done 4948 + cell_height="$(printf '%s' "$term_response" | cut -d ';' -f 2)" 4949 + cell_width="$(printf '%s' "$term_response" | cut -d ';' -f 3)" 4950 + if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then 4951 + cell_width=8 4952 + cell_height=16 4953 + fi 4954 + fi 4955 +fi 4956 + 4957 +# Compute a formula with bc and round to the nearest integer. 4958 +bc_round() { 4959 + LC_NUMERIC=C printf '%.0f' "$(printf '%s\n' "scale=2;($1) + 0.5" | bc)" 4960 +} 4961 + 4962 +# Compute the number of rows and columns of the image. 4963 +if [ -z "$cols" ] || [ -z "$rows" ]; then 4964 + # Get the size of the image and its resolution. If it's an animation, use 4965 + # the first frame. 4966 + format_output="$($identify -format '%w %h\n' "$file" | head -1)" 4967 + img_width="$(printf '%s' "$format_output" | cut -d ' ' -f 1)" 4968 + img_height="$(printf '%s' "$format_output" | cut -d ' ' -f 2)" 4969 + if ! is_pos_int "$img_width" || ! is_pos_int "$img_height"; then 4970 + echo "Couldn't get image size from identify: $format_output" >&2 4971 + echo >&2 4972 + exit 1 4973 + fi 4974 + opt_cols_expr="(${scale}*${img_width}/${cell_width})" 4975 + opt_rows_expr="(${scale}*${img_height}/${cell_height})" 4976 + if [ -z "$cols" ] && [ -z "$rows" ]; then 4977 + # If columns and rows are not specified, compute the optimal values 4978 + # using the information about rows and columns per inch. 4979 + cols="$(bc_round "$opt_cols_expr")" 4980 + rows="$(bc_round "$opt_rows_expr")" 4981 + # Make sure that automatically computed rows and columns are within some 4982 + # sane limits 4983 + if [ "$cols" -gt "$max_cols" ]; then 4984 + rows="$(bc_round "$rows * $max_cols / $cols")" 4985 + cols="$max_cols" 4986 + fi 4987 + if [ "$rows" -gt "$max_rows" ]; then 4988 + cols="$(bc_round "$cols * $max_rows / $rows")" 4989 + rows="$max_rows" 4990 + fi 4991 + elif [ -z "$cols" ]; then 4992 + # If only one dimension is specified, compute the other one to match the 4993 + # aspect ratio as close as possible. 4994 + cols="$(bc_round "${opt_cols_expr}*${rows}/${opt_rows_expr}")" 4995 + elif [ -z "$rows" ]; then 4996 + rows="$(bc_round "${opt_rows_expr}*${cols}/${opt_cols_expr}")" 4997 + fi 4998 + 4999 + if [ "$cols" -lt 1 ]; then 5000 + cols=1 5001 + fi 5002 + if [ "$rows" -lt 1 ]; then 5003 + rows=1 5004 + fi 5005 +fi 5006 + 5007 +##################################################################### 5008 +# Generate an image id 5009 +##################################################################### 5010 + 5011 +image_id="" 5012 +while [ -z "$image_id" ]; do 5013 + image_id="$(shuf -i 16777217-4294967295 -n 1)" 5014 + # Check that the id requires 24-bit fg colors. 5015 + if [ "$(expr \( "$image_id" / 256 \) % 65536)" -eq 0 ]; then 5016 + image_id="" 5017 + fi 5018 +done 5019 + 5020 +##################################################################### 5021 +# Uploading the image 5022 +##################################################################### 5023 + 5024 +# Choose the uploading method 5025 +if [ "$uploading_method" = "auto" ]; then 5026 + if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ] || [ -n "$SSH_CONNECTION" ]; then 5027 + uploading_method="direct" 5028 + else 5029 + uploading_method="file" 5030 + fi 5031 +fi 5032 + 5033 +# Functions to emit the start and the end of a graphics command. 5034 +if [ -n "$inside_tmux" ]; then 5035 + # If we are in tmux we have to wrap the command in Ptmux. 5036 + graphics_command_start='\ePtmux;\e\e_G' 5037 + graphics_command_end='\e\e\\\e\\' 5038 +else 5039 + graphics_command_start='\e_G' 5040 + graphics_command_end='\e\\' 5041 +fi 5042 + 5043 +# Send a graphics command with the correct start and end 5044 +gr_command() { 5045 + printf "${graphics_command_start}%s${graphics_command_end}" "$1" >> "$command_tty" 5046 +} 5047 + 5048 +# Compute the size of a data chunk for direct transmission. 5049 +if [ "$uploading_method" = "direct" ]; then 5050 + # Get the value of PIPE_BUF. 5051 + pipe_buf="$(getconf PIPE_BUF "$command_tty" 2> /dev/null)" 5052 + if is_pos_int "$pipe_buf"; then 5053 + # Make sure it's between 512 and 4096. 5054 + if [ "$(expr "$pipe_buf" \< 512)" -eq 1 ]; then 5055 + pipe_buf=512 5056 + elif [ "$(expr "$pipe_buf" \> 4096)" -eq 1 ]; then 5057 + pipe_buf=4096 5058 + fi 5059 + else 5060 + pipe_buf=512 5061 + fi 5062 + 5063 + # The size of each graphics command shouldn't be more than PIPE_BUF, so we 5064 + # set the size of an encoded chunk to be PIPE_BUF - 128 to leave some space 5065 + # for the command. 5066 + chunk_size="$(expr "$pipe_buf" - 128)" 5067 +fi 5068 + 5069 +# Check if the image format is supported. 5070 +is_format_supported() { 5071 + arg_format="$1" 5072 + if [ "$arg_format" = "PNG" ]; then 5073 + return 0 5074 + elif [ "$arg_format" = "JPEG" ]; then 5075 + if [ -z "$inside_tmux" ]; then 5076 + actual_term="$TERM" 5077 + else 5078 + # Get the actual current terminal name from tmux. 5079 + actual_term="$(tmux display-message -p "#{client_termname}")" 5080 + fi 5081 + # st is known to support JPEG. 5082 + case "$actual_term" in 5083 + st | *-st | st-* | *-st-*) 5084 + return 0 5085 + ;; 5086 + esac 5087 + return 1 5088 + else 5089 + return 1 5090 + fi 5091 +} 5092 + 5093 +# Send an uploading command. Usage: gr_upload <action> <command> <file> 5094 +# Where <action> is a part of command that specifies the action, it will be 5095 +# repeated for every chunk (if the method is direct), and <command> is the rest 5096 +# of the command that specifies the image parameters. <action> and <command> 5097 +# must not include the transmission method or ';'. 5098 +# Example: 5099 +# gr_upload "a=T,q=2" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" 5100 +gr_upload() { 5101 + arg_action="$1" 5102 + arg_command="$2" 5103 + arg_file="$3" 5104 + if [ "$uploading_method" = "file" ]; then 5105 + # base64-encode the filename 5106 + encoded_filename=$(printf '%s' "$arg_file" | base64 -w0) 5107 + # If the file name contains 'tty-graphics-protocol', assume it's 5108 + # temporary and use t=t. 5109 + medium="t=f" 5110 + case "$arg_file" in 5111 + *tty-graphics-protocol*) 5112 + medium="t=t" 5113 + ;; 5114 + *) 5115 + medium="t=f" 5116 + ;; 5117 + esac 5118 + gr_command "${arg_action},${arg_command},${medium};${encoded_filename}" 5119 + fi 5120 + if [ "$uploading_method" = "direct" ]; then 5121 + # Create a temporary directory to store the chunked image. 5122 + chunkdir="$(mktemp -d)" 5123 + if [ ! "$chunkdir" ] || [ ! -d "$chunkdir" ]; then 5124 + echo "Can't create a temp dir" >&2 5125 + exit 1 5126 + fi 5127 + # base64-encode the file and split it into chunks. The size of each 5128 + # graphics command shouldn't be more than 4096, so we set the size of an 5129 + # encoded chunk to be 3968, slightly less than that. 5130 + chunk_size=3968 5131 + cat "$arg_file" | base64 -w0 | split -b "$chunk_size" - "$chunkdir/chunk_" 5132 + 5133 + # Issue a command indicating that we want to start data transmission for 5134 + # a new image. 5135 + gr_command "${arg_action},${arg_command},t=d,m=1" 5136 + 5137 + # Transmit chunks. 5138 + for chunk in "$chunkdir/chunk_"*; do 5139 + gr_command "${arg_action},i=${image_id},m=1;$(cat "$chunk")" 5140 + rm "$chunk" 5141 + done 5142 + 5143 + # Tell the terminal that we are done. 5144 + gr_command "${arg_action},i=$image_id,m=0" 5145 + 5146 + # Remove the temporary directory. 5147 + rmdir "$chunkdir" 5148 + fi 5149 +} 5150 + 5151 +delayed_frame_dir_cleanup() { 5152 + arg_frame_dir="$1" 5153 + sleep 2 5154 + if [ -n "$arg_frame_dir" ]; then 5155 + for frame in "$arg_frame_dir"/frame_*.png; do 5156 + rm "$frame" 5157 + done 5158 + rmdir "$arg_frame_dir" 5159 + fi 5160 +} 5161 + 5162 +upload_image_and_print_placeholder() { 5163 + # Check if the file is an animation. 5164 + format_output=$($identify -format '%n %m\n' "$file" | head -n 1) 5165 + frame_count="$(printf '%s' "$format_output" | cut -d ' ' -f 1)" 5166 + image_format="$(printf '%s' "$format_output" | cut -d ' ' -f 2)" 5167 + 5168 + if [ "$frame_count" -gt 1 ]; then 5169 + # The file is an animation, decompose into frames and upload each frame. 5170 + frame_dir="$(mktemp -d)" 5171 + frame_dir="$HOME/temp/frames${frame_dir}" 5172 + mkdir -p "$frame_dir" 5173 + if [ ! "$frame_dir" ] || [ ! -d "$frame_dir" ]; then 5174 + echo "Can't create a temp dir for frames" >&2 5175 + exit 1 5176 + fi 5177 + 5178 + # Decompose the animation into separate frames. 5179 + $convert "$file" -coalesce "$frame_dir/frame_%06d.png" 5180 + 5181 + # Get all frame delays at once, in centiseconds, as a space-separated 5182 + # string. 5183 + delays=$($identify -format "%T " "$file") 5184 + 5185 + frame_number=1 5186 + for frame in "$frame_dir"/frame_*.png; do 5187 + # Read the delay for the current frame and convert it from 5188 + # centiseconds to milliseconds. 5189 + delay=$(printf '%s' "$delays" | cut -d ' ' -f "$frame_number") 5190 + delay=$((delay * 10)) 5191 + # If the delay is 0, set it to 100ms. 5192 + if [ "$delay" -eq 0 ]; then 5193 + delay=100 5194 + fi 5195 + 5196 + if [ -n "$speed" ]; then 5197 + delay=$(bc_round "$delay / $speed") 5198 + fi 5199 + 5200 + if [ "$frame_number" -eq 1 ]; then 5201 + # Abort the previous transmission, just in case. 5202 + gr_command "q=2,a=t,i=${image_id},m=0" 5203 + # Upload the first frame with a=T 5204 + gr_upload "q=2,a=T" "f=100,U=1,i=${image_id},c=${cols},r=${rows}" "$frame" 5205 + # Set the delay for the first frame and also play the animation 5206 + # in loading mode (s=2). 5207 + gr_command "a=a,v=1,s=2,r=${frame_number},z=${delay},i=${image_id}" 5208 + # Print the placeholder after the first frame to reduce the wait 5209 + # time. 5210 + print_placeholder 5211 + else 5212 + # Upload subsequent frames with a=f 5213 + gr_upload "q=2,a=f" "f=100,i=${image_id},z=${delay}" "$frame" 5214 + fi 5215 + 5216 + frame_number=$((frame_number + 1)) 5217 + done 5218 + 5219 + # Play the animation in loop mode (s=3). 5220 + gr_command "a=a,v=1,s=3,i=${image_id}" 5221 + 5222 + # Remove the temporary directory, but do it in the background with a 5223 + # delay to avoid removing files before they are loaded by the terminal. 5224 + delayed_frame_dir_cleanup "$frame_dir" 2> /dev/null & 5225 + elif is_format_supported "$image_format"; then 5226 + # The file is not an animation and has a supported format, upload it 5227 + # directly. 5228 + gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" 5229 + # Print the placeholder 5230 + print_placeholder 5231 + else 5232 + # The format is not supported, try to convert it to png. 5233 + temp_file="$(mktemp --tmpdir "icat-mini-tty-graphics-protocol-XXXXX.png")" 5234 + if ! $convert "$file" "$temp_file"; then 5235 + echo "Failed to convert the image to PNG" >&2 5236 + exit 1 5237 + fi 5238 + # Upload the converted image. 5239 + gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$temp_file" 5240 + # Print the placeholder 5241 + print_placeholder 5242 + fi 5243 +} 5244 + 5245 +##################################################################### 5246 +# Printing the image placeholder 5247 +##################################################################### 5248 + 5249 +print_placeholder() { 5250 + # Each line starts with the escape sequence to set the foreground color to 5251 + # the image id. 5252 + blue="$(expr "$image_id" % 256 )" 5253 + green="$(expr \( "$image_id" / 256 \) % 256 )" 5254 + red="$(expr \( "$image_id" / 65536 \) % 256 )" 5255 + line_start="$(printf "\e[38;2;%d;%d;%dm" "$red" "$green" "$blue")" 5256 + line_end="$(printf "\e[39;m")" 5257 + 5258 + id4th="$(expr \( "$image_id" / 16777216 \) % 256 )" 5259 + eval "id_diacritic=\$d${id4th}" 5260 + 5261 + # Reset the brush state, mostly to reset the underline color. 5262 + printf "\e[0m" 5263 + 5264 + # Fill the output with characters representing the image 5265 + for y in $(seq 0 "$(expr "$rows" - 1)"); do 5266 + eval "row_diacritic=\$d${y}" 5267 + line="$line_start" 5268 + for x in $(seq 0 "$(expr "$cols" - 1)"); do 5269 + eval "col_diacritic=\$d${x}" 5270 + # Note that when $x is out of bounds, the column diacritic will 5271 + # be empty, meaning that the column should be guessed by the 5272 + # terminal. 5273 + if [ "$x" -ge "$num_diacritics" ]; then 5274 + line="${line}${placeholder}${row_diacritic}" 5275 + else 5276 + line="${line}${placeholder}${row_diacritic}${col_diacritic}${id_diacritic}" 5277 + fi 5278 + done 5279 + line="${line}${line_end}" 5280 + printf "%s\n" "$line" 5281 + done 5282 + 5283 + printf "\e[0m" 5284 +} 5285 + 5286 +d0="̅" 5287 +d1="̍" 5288 +d2="̎" 5289 +d3="̐" 5290 +d4="̒" 5291 +d5="̽" 5292 +d6="̾" 5293 +d7="̿" 5294 +d8="͆" 5295 +d9="͊" 5296 +d10="͋" 5297 +d11="͌" 5298 +d12="͐" 5299 +d13="͑" 5300 +d14="͒" 5301 +d15="͗" 5302 +d16="͛" 5303 +d17="ͣ" 5304 +d18="ͤ" 5305 +d19="ͥ" 5306 +d20="ͦ" 5307 +d21="ͧ" 5308 +d22="ͨ" 5309 +d23="ͩ" 5310 +d24="ͪ" 5311 +d25="ͫ" 5312 +d26="ͬ" 5313 +d27="ͭ" 5314 +d28="ͮ" 5315 +d29="ͯ" 5316 +d30="҃" 5317 +d31="҄" 5318 +d32="҅" 5319 +d33="҆" 5320 +d34="҇" 5321 +d35="֒" 5322 +d36="֓" 5323 +d37="֔" 5324 +d38="֕" 5325 +d39="֗" 5326 +d40="֘" 5327 +d41="֙" 5328 +d42="֜" 5329 +d43="֝" 5330 +d44="֞" 5331 +d45="֟" 5332 +d46="֠" 5333 +d47="֡" 5334 +d48="֨" 5335 +d49="֩" 5336 +d50="֫" 5337 +d51="֬" 5338 +d52="֯" 5339 +d53="ׄ" 5340 +d54="ؐ" 5341 +d55="ؑ" 5342 +d56="ؒ" 5343 +d57="ؓ" 5344 +d58="ؔ" 5345 +d59="ؕ" 5346 +d60="ؖ" 5347 +d61="ؗ" 5348 +d62="ٗ" 5349 +d63="٘" 5350 +d64="ٙ" 5351 +d65="ٚ" 5352 +d66="ٛ" 5353 +d67="ٝ" 5354 +d68="ٞ" 5355 +d69="ۖ" 5356 +d70="ۗ" 5357 +d71="ۘ" 5358 +d72="ۙ" 5359 +d73="ۚ" 5360 +d74="ۛ" 5361 +d75="ۜ" 5362 +d76="۟" 5363 +d77="۠" 5364 +d78="ۡ" 5365 +d79="ۢ" 5366 +d80="ۤ" 5367 +d81="ۧ" 5368 +d82="ۨ" 5369 +d83="۫" 5370 +d84="۬" 5371 +d85="ܰ" 5372 +d86="ܲ" 5373 +d87="ܳ" 5374 +d88="ܵ" 5375 +d89="ܶ" 5376 +d90="ܺ" 5377 +d91="ܽ" 5378 +d92="ܿ" 5379 +d93="݀" 5380 +d94="݁" 5381 +d95="݃" 5382 +d96="݅" 5383 +d97="݇" 5384 +d98="݉" 5385 +d99="݊" 5386 +d100="߫" 5387 +d101="߬" 5388 +d102="߭" 5389 +d103="߮" 5390 +d104="߯" 5391 +d105="߰" 5392 +d106="߱" 5393 +d107="߳" 5394 +d108="ࠖ" 5395 +d109="ࠗ" 5396 +d110="࠘" 5397 +d111="࠙" 5398 +d112="ࠛ" 5399 +d113="ࠜ" 5400 +d114="ࠝ" 5401 +d115="ࠞ" 5402 +d116="ࠟ" 5403 +d117="ࠠ" 5404 +d118="ࠡ" 5405 +d119="ࠢ" 5406 +d120="ࠣ" 5407 +d121="ࠥ" 5408 +d122="ࠦ" 5409 +d123="ࠧ" 5410 +d124="ࠩ" 5411 +d125="ࠪ" 5412 +d126="ࠫ" 5413 +d127="ࠬ" 5414 +d128="࠭" 5415 +d129="॑" 5416 +d130="॓" 5417 +d131="॔" 5418 +d132="ྂ" 5419 +d133="ྃ" 5420 +d134="྆" 5421 +d135="྇" 5422 +d136="፝" 5423 +d137="፞" 5424 +d138="፟" 5425 +d139="៝" 5426 +d140="᤺" 5427 +d141="ᨗ" 5428 +d142="᩵" 5429 +d143="᩶" 5430 +d144="᩷" 5431 +d145="᩸" 5432 +d146="᩹" 5433 +d147="᩺" 5434 +d148="᩻" 5435 +d149="᩼" 5436 +d150="᭫" 5437 +d151="᭭" 5438 +d152="᭮" 5439 +d153="᭯" 5440 +d154="᭰" 5441 +d155="᭱" 5442 +d156="᭲" 5443 +d157="᭳" 5444 +d158="᳐" 5445 +d159="᳑" 5446 +d160="᳒" 5447 +d161="᳚" 5448 +d162="᳛" 5449 +d163="᳠" 5450 +d164="᷀" 5451 +d165="᷁" 5452 +d166="᷃" 5453 +d167="᷄" 5454 +d168="᷅" 5455 +d169="᷆" 5456 +d170="᷇" 5457 +d171="᷈" 5458 +d172="᷉" 5459 +d173="᷋" 5460 +d174="᷌" 5461 +d175="᷑" 5462 +d176="᷒" 5463 +d177="ᷓ" 5464 +d178="ᷔ" 5465 +d179="ᷕ" 5466 +d180="ᷖ" 5467 +d181="ᷗ" 5468 +d182="ᷘ" 5469 +d183="ᷙ" 5470 +d184="ᷚ" 5471 +d185="ᷛ" 5472 +d186="ᷜ" 5473 +d187="ᷝ" 5474 +d188="ᷞ" 5475 +d189="ᷟ" 5476 +d190="ᷠ" 5477 +d191="ᷡ" 5478 +d192="ᷢ" 5479 +d193="ᷣ" 5480 +d194="ᷤ" 5481 +d195="ᷥ" 5482 +d196="ᷦ" 5483 +d197="᷾" 5484 +d198="⃐" 5485 +d199="⃑" 5486 +d200="⃔" 5487 +d201="⃕" 5488 +d202="⃖" 5489 +d203="⃗" 5490 +d204="⃛" 5491 +d205="⃜" 5492 +d206="⃡" 5493 +d207="⃧" 5494 +d208="⃩" 5495 +d209="⃰" 5496 +d210="⳯" 5497 +d211="⳰" 5498 +d212="⳱" 5499 +d213="ⷠ" 5500 +d214="ⷡ" 5501 +d215="ⷢ" 5502 +d216="ⷣ" 5503 +d217="ⷤ" 5504 +d218="ⷥ" 5505 +d219="ⷦ" 5506 +d220="ⷧ" 5507 +d221="ⷨ" 5508 +d222="ⷩ" 5509 +d223="ⷪ" 5510 +d224="ⷫ" 5511 +d225="ⷬ" 5512 +d226="ⷭ" 5513 +d227="ⷮ" 5514 +d228="ⷯ" 5515 +d229="ⷰ" 5516 +d230="ⷱ" 5517 +d231="ⷲ" 5518 +d232="ⷳ" 5519 +d233="ⷴ" 5520 +d234="ⷵ" 5521 +d235="ⷶ" 5522 +d236="ⷷ" 5523 +d237="ⷸ" 5524 +d238="ⷹ" 5525 +d239="ⷺ" 5526 +d240="ⷻ" 5527 +d241="ⷼ" 5528 +d242="ⷽ" 5529 +d243="ⷾ" 5530 +d244="ⷿ" 5531 +d245="꙯" 5532 +d246="꙼" 5533 +d247="꙽" 5534 +d248="꛰" 5535 +d249="꛱" 5536 +d250="꣠" 5537 +d251="꣡" 5538 +d252="꣢" 5539 +d253="꣣" 5540 +d254="꣤" 5541 +d255="꣥" 5542 +d256="꣦" 5543 +d257="꣧" 5544 +d258="꣨" 5545 +d259="꣩" 5546 +d260="꣪" 5547 +d261="꣫" 5548 +d262="꣬" 5549 +d263="꣭" 5550 +d264="꣮" 5551 +d265="꣯" 5552 +d266="꣰" 5553 +d267="꣱" 5554 +d268="ꪰ" 5555 +d269="ꪲ" 5556 +d270="ꪳ" 5557 +d271="ꪷ" 5558 +d272="ꪸ" 5559 +d273="ꪾ" 5560 +d274="꪿" 5561 +d275="꫁" 5562 +d276="︠" 5563 +d277="︡" 5564 +d278="︢" 5565 +d279="︣" 5566 +d280="︤" 5567 +d281="︥" 5568 +d282="︦" 5569 +d283="𐨏" 5570 +d284="𐨸" 5571 +d285="𝆅" 5572 +d286="𝆆" 5573 +d287="𝆇" 5574 +d288="𝆈" 5575 +d289="𝆉" 5576 +d290="𝆪" 5577 +d291="𝆫" 5578 +d292="𝆬" 5579 +d293="𝆭" 5580 +d294="𝉂" 5581 +d295="𝉃" 5582 +d296="𝉄" 5583 + 5584 +num_diacritics="297" 5585 + 5586 +placeholder="" 5587 + 5588 +##################################################################### 5589 +# Upload the image and print the placeholder 5590 +##################################################################### 5591 + 5592 +upload_image_and_print_placeholder 5593 diff --git a/khash.h b/khash.h 5594 new file mode 100644 5595 index 0000000..f75f347 5596 --- /dev/null 5597 +++ b/khash.h 5598 @@ -0,0 +1,627 @@ 5599 +/* The MIT License 5600 + 5601 + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> 5602 + 5603 + Permission is hereby granted, free of charge, to any person obtaining 5604 + a copy of this software and associated documentation files (the 5605 + "Software"), to deal in the Software without restriction, including 5606 + without limitation the rights to use, copy, modify, merge, publish, 5607 + distribute, sublicense, and/or sell copies of the Software, and to 5608 + permit persons to whom the Software is furnished to do so, subject to 5609 + the following conditions: 5610 + 5611 + The above copyright notice and this permission notice shall be 5612 + included in all copies or substantial portions of the Software. 5613 + 5614 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 5615 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 5616 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 5617 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 5618 + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 5619 + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 5620 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 5621 + SOFTWARE. 5622 +*/ 5623 + 5624 +/* 5625 + An example: 5626 + 5627 +#include "khash.h" 5628 +KHASH_MAP_INIT_INT(32, char) 5629 +int main() { 5630 + int ret, is_missing; 5631 + khiter_t k; 5632 + khash_t(32) *h = kh_init(32); 5633 + k = kh_put(32, h, 5, &ret); 5634 + kh_value(h, k) = 10; 5635 + k = kh_get(32, h, 10); 5636 + is_missing = (k == kh_end(h)); 5637 + k = kh_get(32, h, 5); 5638 + kh_del(32, h, k); 5639 + for (k = kh_begin(h); k != kh_end(h); ++k) 5640 + if (kh_exist(h, k)) kh_value(h, k) = 1; 5641 + kh_destroy(32, h); 5642 + return 0; 5643 +} 5644 +*/ 5645 + 5646 +/* 5647 + 2013-05-02 (0.2.8): 5648 + 5649 + * Use quadratic probing. When the capacity is power of 2, stepping function 5650 + i*(i+1)/2 guarantees to traverse each bucket. It is better than double 5651 + hashing on cache performance and is more robust than linear probing. 5652 + 5653 + In theory, double hashing should be more robust than quadratic probing. 5654 + However, my implementation is probably not for large hash tables, because 5655 + the second hash function is closely tied to the first hash function, 5656 + which reduce the effectiveness of double hashing. 5657 + 5658 + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php 5659 + 5660 + 2011-12-29 (0.2.7): 5661 + 5662 + * Minor code clean up; no actual effect. 5663 + 5664 + 2011-09-16 (0.2.6): 5665 + 5666 + * The capacity is a power of 2. This seems to dramatically improve the 5667 + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: 5668 + 5669 + - http://code.google.com/p/ulib/ 5670 + - http://nothings.org/computer/judy/ 5671 + 5672 + * Allow to optionally use linear probing which usually has better 5673 + performance for random input. Double hashing is still the default as it 5674 + is more robust to certain non-random input. 5675 + 5676 + * Added Wang's integer hash function (not used by default). This hash 5677 + function is more robust to certain non-random input. 5678 + 5679 + 2011-02-14 (0.2.5): 5680 + 5681 + * Allow to declare global functions. 5682 + 5683 + 2009-09-26 (0.2.4): 5684 + 5685 + * Improve portability 5686 + 5687 + 2008-09-19 (0.2.3): 5688 + 5689 + * Corrected the example 5690 + * Improved interfaces 5691 + 5692 + 2008-09-11 (0.2.2): 5693 + 5694 + * Improved speed a little in kh_put() 5695 + 5696 + 2008-09-10 (0.2.1): 5697 + 5698 + * Added kh_clear() 5699 + * Fixed a compiling error 5700 + 5701 + 2008-09-02 (0.2.0): 5702 + 5703 + * Changed to token concatenation which increases flexibility. 5704 + 5705 + 2008-08-31 (0.1.2): 5706 + 5707 + * Fixed a bug in kh_get(), which has not been tested previously. 5708 + 5709 + 2008-08-31 (0.1.1): 5710 + 5711 + * Added destructor 5712 +*/ 5713 + 5714 + 5715 +#ifndef __AC_KHASH_H 5716 +#define __AC_KHASH_H 5717 + 5718 +/*! 5719 + @header 5720 + 5721 + Generic hash table library. 5722 + */ 5723 + 5724 +#define AC_VERSION_KHASH_H "0.2.8" 5725 + 5726 +#include <stdlib.h> 5727 +#include <string.h> 5728 +#include <limits.h> 5729 + 5730 +/* compiler specific configuration */ 5731 + 5732 +#if UINT_MAX == 0xffffffffu 5733 +typedef unsigned int khint32_t; 5734 +#elif ULONG_MAX == 0xffffffffu 5735 +typedef unsigned long khint32_t; 5736 +#endif 5737 + 5738 +#if ULONG_MAX == ULLONG_MAX 5739 +typedef unsigned long khint64_t; 5740 +#else 5741 +typedef unsigned long long khint64_t; 5742 +#endif 5743 + 5744 +#ifndef kh_inline 5745 +#ifdef _MSC_VER 5746 +#define kh_inline __inline 5747 +#else 5748 +#define kh_inline inline 5749 +#endif 5750 +#endif /* kh_inline */ 5751 + 5752 +#ifndef klib_unused 5753 +#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) 5754 +#define klib_unused __attribute__ ((__unused__)) 5755 +#else 5756 +#define klib_unused 5757 +#endif 5758 +#endif /* klib_unused */ 5759 + 5760 +typedef khint32_t khint_t; 5761 +typedef khint_t khiter_t; 5762 + 5763 +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) 5764 +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) 5765 +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) 5766 +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) 5767 +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) 5768 +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) 5769 +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) 5770 + 5771 +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) 5772 + 5773 +#ifndef kroundup32 5774 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) 5775 +#endif 5776 + 5777 +#ifndef kcalloc 5778 +#define kcalloc(N,Z) calloc(N,Z) 5779 +#endif 5780 +#ifndef kmalloc 5781 +#define kmalloc(Z) malloc(Z) 5782 +#endif 5783 +#ifndef krealloc 5784 +#define krealloc(P,Z) realloc(P,Z) 5785 +#endif 5786 +#ifndef kfree 5787 +#define kfree(P) free(P) 5788 +#endif 5789 + 5790 +static const double __ac_HASH_UPPER = 0.77; 5791 + 5792 +#define __KHASH_TYPE(name, khkey_t, khval_t) \ 5793 + typedef struct kh_##name##_s { \ 5794 + khint_t n_buckets, size, n_occupied, upper_bound; \ 5795 + khint32_t *flags; \ 5796 + khkey_t *keys; \ 5797 + khval_t *vals; \ 5798 + } kh_##name##_t; 5799 + 5800 +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ 5801 + extern kh_##name##_t *kh_init_##name(void); \ 5802 + extern void kh_destroy_##name(kh_##name##_t *h); \ 5803 + extern void kh_clear_##name(kh_##name##_t *h); \ 5804 + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ 5805 + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ 5806 + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ 5807 + extern void kh_del_##name(kh_##name##_t *h, khint_t x); 5808 + 5809 +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 5810 + SCOPE kh_##name##_t *kh_init_##name(void) { \ 5811 + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ 5812 + } \ 5813 + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ 5814 + { \ 5815 + if (h) { \ 5816 + kfree((void *)h->keys); kfree(h->flags); \ 5817 + kfree((void *)h->vals); \ 5818 + kfree(h); \ 5819 + } \ 5820 + } \ 5821 + SCOPE void kh_clear_##name(kh_##name##_t *h) \ 5822 + { \ 5823 + if (h && h->flags) { \ 5824 + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ 5825 + h->size = h->n_occupied = 0; \ 5826 + } \ 5827 + } \ 5828 + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ 5829 + { \ 5830 + if (h->n_buckets) { \ 5831 + khint_t k, i, last, mask, step = 0; \ 5832 + mask = h->n_buckets - 1; \ 5833 + k = __hash_func(key); i = k & mask; \ 5834 + last = i; \ 5835 + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ 5836 + i = (i + (++step)) & mask; \ 5837 + if (i == last) return h->n_buckets; \ 5838 + } \ 5839 + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ 5840 + } else return 0; \ 5841 + } \ 5842 + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ 5843 + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ 5844 + khint32_t *new_flags = 0; \ 5845 + khint_t j = 1; \ 5846 + { \ 5847 + kroundup32(new_n_buckets); \ 5848 + if (new_n_buckets < 4) new_n_buckets = 4; \ 5849 + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ 5850 + else { /* hash table size to be changed (shrink or expand); rehash */ \ 5851 + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ 5852 + if (!new_flags) return -1; \ 5853 + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ 5854 + if (h->n_buckets < new_n_buckets) { /* expand */ \ 5855 + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ 5856 + if (!new_keys) { kfree(new_flags); return -1; } \ 5857 + h->keys = new_keys; \ 5858 + if (kh_is_map) { \ 5859 + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ 5860 + if (!new_vals) { kfree(new_flags); return -1; } \ 5861 + h->vals = new_vals; \ 5862 + } \ 5863 + } /* otherwise shrink */ \ 5864 + } \ 5865 + } \ 5866 + if (j) { /* rehashing is needed */ \ 5867 + for (j = 0; j != h->n_buckets; ++j) { \ 5868 + if (__ac_iseither(h->flags, j) == 0) { \ 5869 + khkey_t key = h->keys[j]; \ 5870 + khval_t val; \ 5871 + khint_t new_mask; \ 5872 + new_mask = new_n_buckets - 1; \ 5873 + if (kh_is_map) val = h->vals[j]; \ 5874 + __ac_set_isdel_true(h->flags, j); \ 5875 + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ 5876 + khint_t k, i, step = 0; \ 5877 + k = __hash_func(key); \ 5878 + i = k & new_mask; \ 5879 + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ 5880 + __ac_set_isempty_false(new_flags, i); \ 5881 + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ 5882 + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ 5883 + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ 5884 + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ 5885 + } else { /* write the element and jump out of the loop */ \ 5886 + h->keys[i] = key; \ 5887 + if (kh_is_map) h->vals[i] = val; \ 5888 + break; \ 5889 + } \ 5890 + } \ 5891 + } \ 5892 + } \ 5893 + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ 5894 + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ 5895 + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ 5896 + } \ 5897 + kfree(h->flags); /* free the working space */ \ 5898 + h->flags = new_flags; \ 5899 + h->n_buckets = new_n_buckets; \ 5900 + h->n_occupied = h->size; \ 5901 + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ 5902 + } \ 5903 + return 0; \ 5904 + } \ 5905 + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ 5906 + { \ 5907 + khint_t x; \ 5908 + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ 5909 + if (h->n_buckets > (h->size<<1)) { \ 5910 + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ 5911 + *ret = -1; return h->n_buckets; \ 5912 + } \ 5913 + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ 5914 + *ret = -1; return h->n_buckets; \ 5915 + } \ 5916 + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ 5917 + { \ 5918 + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ 5919 + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ 5920 + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ 5921 + else { \ 5922 + last = i; \ 5923 + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ 5924 + if (__ac_isdel(h->flags, i)) site = i; \ 5925 + i = (i + (++step)) & mask; \ 5926 + if (i == last) { x = site; break; } \ 5927 + } \ 5928 + if (x == h->n_buckets) { \ 5929 + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ 5930 + else x = i; \ 5931 + } \ 5932 + } \ 5933 + } \ 5934 + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ 5935 + h->keys[x] = key; \ 5936 + __ac_set_isboth_false(h->flags, x); \ 5937 + ++h->size; ++h->n_occupied; \ 5938 + *ret = 1; \ 5939 + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ 5940 + h->keys[x] = key; \ 5941 + __ac_set_isboth_false(h->flags, x); \ 5942 + ++h->size; \ 5943 + *ret = 2; \ 5944 + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ 5945 + return x; \ 5946 + } \ 5947 + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ 5948 + { \ 5949 + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ 5950 + __ac_set_isdel_true(h->flags, x); \ 5951 + --h->size; \ 5952 + } \ 5953 + } 5954 + 5955 +#define KHASH_DECLARE(name, khkey_t, khval_t) \ 5956 + __KHASH_TYPE(name, khkey_t, khval_t) \ 5957 + __KHASH_PROTOTYPES(name, khkey_t, khval_t) 5958 + 5959 +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 5960 + __KHASH_TYPE(name, khkey_t, khval_t) \ 5961 + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) 5962 + 5963 +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ 5964 + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) 5965 + 5966 +/* --- BEGIN OF HASH FUNCTIONS --- */ 5967 + 5968 +/*! @function 5969 + @abstract Integer hash function 5970 + @param key The integer [khint32_t] 5971 + @return The hash value [khint_t] 5972 + */ 5973 +#define kh_int_hash_func(key) (khint32_t)(key) 5974 +/*! @function 5975 + @abstract Integer comparison function 5976 + */ 5977 +#define kh_int_hash_equal(a, b) ((a) == (b)) 5978 +/*! @function 5979 + @abstract 64-bit integer hash function 5980 + @param key The integer [khint64_t] 5981 + @return The hash value [khint_t] 5982 + */ 5983 +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) 5984 +/*! @function 5985 + @abstract 64-bit integer comparison function 5986 + */ 5987 +#define kh_int64_hash_equal(a, b) ((a) == (b)) 5988 +/*! @function 5989 + @abstract const char* hash function 5990 + @param s Pointer to a null terminated string 5991 + @return The hash value 5992 + */ 5993 +static kh_inline khint_t __ac_X31_hash_string(const char *s) 5994 +{ 5995 + khint_t h = (khint_t)*s; 5996 + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; 5997 + return h; 5998 +} 5999 +/*! @function 6000 + @abstract Another interface to const char* hash function 6001 + @param key Pointer to a null terminated string [const char*] 6002 + @return The hash value [khint_t] 6003 + */ 6004 +#define kh_str_hash_func(key) __ac_X31_hash_string(key) 6005 +/*! @function 6006 + @abstract Const char* comparison function 6007 + */ 6008 +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) 6009 + 6010 +static kh_inline khint_t __ac_Wang_hash(khint_t key) 6011 +{ 6012 + key += ~(key << 15); 6013 + key ^= (key >> 10); 6014 + key += (key << 3); 6015 + key ^= (key >> 6); 6016 + key += ~(key << 11); 6017 + key ^= (key >> 16); 6018 + return key; 6019 +} 6020 +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) 6021 + 6022 +/* --- END OF HASH FUNCTIONS --- */ 6023 + 6024 +/* Other convenient macros... */ 6025 + 6026 +/*! 6027 + @abstract Type of the hash table. 6028 + @param name Name of the hash table [symbol] 6029 + */ 6030 +#define khash_t(name) kh_##name##_t 6031 + 6032 +/*! @function 6033 + @abstract Initiate a hash table. 6034 + @param name Name of the hash table [symbol] 6035 + @return Pointer to the hash table [khash_t(name)*] 6036 + */ 6037 +#define kh_init(name) kh_init_##name() 6038 + 6039 +/*! @function 6040 + @abstract Destroy a hash table. 6041 + @param name Name of the hash table [symbol] 6042 + @param h Pointer to the hash table [khash_t(name)*] 6043 + */ 6044 +#define kh_destroy(name, h) kh_destroy_##name(h) 6045 + 6046 +/*! @function 6047 + @abstract Reset a hash table without deallocating memory. 6048 + @param name Name of the hash table [symbol] 6049 + @param h Pointer to the hash table [khash_t(name)*] 6050 + */ 6051 +#define kh_clear(name, h) kh_clear_##name(h) 6052 + 6053 +/*! @function 6054 + @abstract Resize a hash table. 6055 + @param name Name of the hash table [symbol] 6056 + @param h Pointer to the hash table [khash_t(name)*] 6057 + @param s New size [khint_t] 6058 + */ 6059 +#define kh_resize(name, h, s) kh_resize_##name(h, s) 6060 + 6061 +/*! @function 6062 + @abstract Insert a key to the hash table. 6063 + @param name Name of the hash table [symbol] 6064 + @param h Pointer to the hash table [khash_t(name)*] 6065 + @param k Key [type of keys] 6066 + @param r Extra return code: -1 if the operation failed; 6067 + 0 if the key is present in the hash table; 6068 + 1 if the bucket is empty (never used); 2 if the element in 6069 + the bucket has been deleted [int*] 6070 + @return Iterator to the inserted element [khint_t] 6071 + */ 6072 +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) 6073 + 6074 +/*! @function 6075 + @abstract Retrieve a key from the hash table. 6076 + @param name Name of the hash table [symbol] 6077 + @param h Pointer to the hash table [khash_t(name)*] 6078 + @param k Key [type of keys] 6079 + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] 6080 + */ 6081 +#define kh_get(name, h, k) kh_get_##name(h, k) 6082 + 6083 +/*! @function 6084 + @abstract Remove a key from the hash table. 6085 + @param name Name of the hash table [symbol] 6086 + @param h Pointer to the hash table [khash_t(name)*] 6087 + @param k Iterator to the element to be deleted [khint_t] 6088 + */ 6089 +#define kh_del(name, h, k) kh_del_##name(h, k) 6090 + 6091 +/*! @function 6092 + @abstract Test whether a bucket contains data. 6093 + @param h Pointer to the hash table [khash_t(name)*] 6094 + @param x Iterator to the bucket [khint_t] 6095 + @return 1 if containing data; 0 otherwise [int] 6096 + */ 6097 +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) 6098 + 6099 +/*! @function 6100 + @abstract Get key given an iterator 6101 + @param h Pointer to the hash table [khash_t(name)*] 6102 + @param x Iterator to the bucket [khint_t] 6103 + @return Key [type of keys] 6104 + */ 6105 +#define kh_key(h, x) ((h)->keys[x]) 6106 + 6107 +/*! @function 6108 + @abstract Get value given an iterator 6109 + @param h Pointer to the hash table [khash_t(name)*] 6110 + @param x Iterator to the bucket [khint_t] 6111 + @return Value [type of values] 6112 + @discussion For hash sets, calling this results in segfault. 6113 + */ 6114 +#define kh_val(h, x) ((h)->vals[x]) 6115 + 6116 +/*! @function 6117 + @abstract Alias of kh_val() 6118 + */ 6119 +#define kh_value(h, x) ((h)->vals[x]) 6120 + 6121 +/*! @function 6122 + @abstract Get the start iterator 6123 + @param h Pointer to the hash table [khash_t(name)*] 6124 + @return The start iterator [khint_t] 6125 + */ 6126 +#define kh_begin(h) (khint_t)(0) 6127 + 6128 +/*! @function 6129 + @abstract Get the end iterator 6130 + @param h Pointer to the hash table [khash_t(name)*] 6131 + @return The end iterator [khint_t] 6132 + */ 6133 +#define kh_end(h) ((h)->n_buckets) 6134 + 6135 +/*! @function 6136 + @abstract Get the number of elements in the hash table 6137 + @param h Pointer to the hash table [khash_t(name)*] 6138 + @return Number of elements in the hash table [khint_t] 6139 + */ 6140 +#define kh_size(h) ((h)->size) 6141 + 6142 +/*! @function 6143 + @abstract Get the number of buckets in the hash table 6144 + @param h Pointer to the hash table [khash_t(name)*] 6145 + @return Number of buckets in the hash table [khint_t] 6146 + */ 6147 +#define kh_n_buckets(h) ((h)->n_buckets) 6148 + 6149 +/*! @function 6150 + @abstract Iterate over the entries in the hash table 6151 + @param h Pointer to the hash table [khash_t(name)*] 6152 + @param kvar Variable to which key will be assigned 6153 + @param vvar Variable to which value will be assigned 6154 + @param code Block of code to execute 6155 + */ 6156 +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ 6157 + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ 6158 + if (!kh_exist(h,__i)) continue; \ 6159 + (kvar) = kh_key(h,__i); \ 6160 + (vvar) = kh_val(h,__i); \ 6161 + code; \ 6162 + } } 6163 + 6164 +/*! @function 6165 + @abstract Iterate over the values in the hash table 6166 + @param h Pointer to the hash table [khash_t(name)*] 6167 + @param vvar Variable to which value will be assigned 6168 + @param code Block of code to execute 6169 + */ 6170 +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ 6171 + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ 6172 + if (!kh_exist(h,__i)) continue; \ 6173 + (vvar) = kh_val(h,__i); \ 6174 + code; \ 6175 + } } 6176 + 6177 +/* More convenient interfaces */ 6178 + 6179 +/*! @function 6180 + @abstract Instantiate a hash set containing integer keys 6181 + @param name Name of the hash table [symbol] 6182 + */ 6183 +#define KHASH_SET_INIT_INT(name) \ 6184 + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) 6185 + 6186 +/*! @function 6187 + @abstract Instantiate a hash map containing integer keys 6188 + @param name Name of the hash table [symbol] 6189 + @param khval_t Type of values [type] 6190 + */ 6191 +#define KHASH_MAP_INIT_INT(name, khval_t) \ 6192 + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) 6193 + 6194 +/*! @function 6195 + @abstract Instantiate a hash set containing 64-bit integer keys 6196 + @param name Name of the hash table [symbol] 6197 + */ 6198 +#define KHASH_SET_INIT_INT64(name) \ 6199 + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) 6200 + 6201 +/*! @function 6202 + @abstract Instantiate a hash map containing 64-bit integer keys 6203 + @param name Name of the hash table [symbol] 6204 + @param khval_t Type of values [type] 6205 + */ 6206 +#define KHASH_MAP_INIT_INT64(name, khval_t) \ 6207 + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) 6208 + 6209 +typedef const char *kh_cstr_t; 6210 +/*! @function 6211 + @abstract Instantiate a hash map containing const char* keys 6212 + @param name Name of the hash table [symbol] 6213 + */ 6214 +#define KHASH_SET_INIT_STR(name) \ 6215 + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) 6216 + 6217 +/*! @function 6218 + @abstract Instantiate a hash map containing const char* keys 6219 + @param name Name of the hash table [symbol] 6220 + @param khval_t Type of values [type] 6221 + */ 6222 +#define KHASH_MAP_INIT_STR(name, khval_t) \ 6223 + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) 6224 + 6225 +#endif /* __AC_KHASH_H */ 6226 diff --git a/kvec.h b/kvec.h 6227 new file mode 100644 6228 index 0000000..10f1c5b 6229 --- /dev/null 6230 +++ b/kvec.h 6231 @@ -0,0 +1,90 @@ 6232 +/* The MIT License 6233 + 6234 + Copyright (c) 2008, by Attractive Chaos <attractor@live.co.uk> 6235 + 6236 + Permission is hereby granted, free of charge, to any person obtaining 6237 + a copy of this software and associated documentation files (the 6238 + "Software"), to deal in the Software without restriction, including 6239 + without limitation the rights to use, copy, modify, merge, publish, 6240 + distribute, sublicense, and/or sell copies of the Software, and to 6241 + permit persons to whom the Software is furnished to do so, subject to 6242 + the following conditions: 6243 + 6244 + The above copyright notice and this permission notice shall be 6245 + included in all copies or substantial portions of the Software. 6246 + 6247 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 6248 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 6249 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 6250 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 6251 + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 6252 + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 6253 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 6254 + SOFTWARE. 6255 +*/ 6256 + 6257 +/* 6258 + An example: 6259 + 6260 +#include "kvec.h" 6261 +int main() { 6262 + kvec_t(int) array; 6263 + kv_init(array); 6264 + kv_push(int, array, 10); // append 6265 + kv_a(int, array, 20) = 5; // dynamic 6266 + kv_A(array, 20) = 4; // static 6267 + kv_destroy(array); 6268 + return 0; 6269 +} 6270 +*/ 6271 + 6272 +/* 6273 + 2008-09-22 (0.1.0): 6274 + 6275 + * The initial version. 6276 + 6277 +*/ 6278 + 6279 +#ifndef AC_KVEC_H 6280 +#define AC_KVEC_H 6281 + 6282 +#include <stdlib.h> 6283 + 6284 +#define kv_roundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) 6285 + 6286 +#define kvec_t(type) struct { size_t n, m; type *a; } 6287 +#define kv_init(v) ((v).n = (v).m = 0, (v).a = 0) 6288 +#define kv_destroy(v) free((v).a) 6289 +#define kv_A(v, i) ((v).a[(i)]) 6290 +#define kv_pop(v) ((v).a[--(v).n]) 6291 +#define kv_size(v) ((v).n) 6292 +#define kv_max(v) ((v).m) 6293 + 6294 +#define kv_resize(type, v, s) ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m)) 6295 + 6296 +#define kv_copy(type, v1, v0) do { \ 6297 + if ((v1).m < (v0).n) kv_resize(type, v1, (v0).n); \ 6298 + (v1).n = (v0).n; \ 6299 + memcpy((v1).a, (v0).a, sizeof(type) * (v0).n); \ 6300 + } while (0) \ 6301 + 6302 +#define kv_push(type, v, x) do { \ 6303 + if ((v).n == (v).m) { \ 6304 + (v).m = (v).m? (v).m<<1 : 2; \ 6305 + (v).a = (type*)realloc((v).a, sizeof(type) * (v).m); \ 6306 + } \ 6307 + (v).a[(v).n++] = (x); \ 6308 + } while (0) 6309 + 6310 +#define kv_pushp(type, v) ((((v).n == (v).m)? \ 6311 + ((v).m = ((v).m? (v).m<<1 : 2), \ 6312 + (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ 6313 + : 0), ((v).a + ((v).n++))) 6314 + 6315 +#define kv_a(type, v, i) (((v).m <= (size_t)(i)? \ 6316 + ((v).m = (v).n = (i) + 1, kv_roundup32((v).m), \ 6317 + (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ 6318 + : (v).n <= (size_t)(i)? (v).n = (i) + 1 \ 6319 + : 0), (v).a[(i)]) 6320 + 6321 +#endif 6322 diff --git a/rowcolumn_diacritics_helpers.c b/rowcolumn_diacritics_helpers.c 6323 new file mode 100644 6324 index 0000000..829c0fc 6325 --- /dev/null 6326 +++ b/rowcolumn_diacritics_helpers.c 6327 @@ -0,0 +1,391 @@ 6328 +#include <stdint.h> 6329 + 6330 +uint16_t diacritic_to_num(uint32_t code) 6331 +{ 6332 + switch (code) { 6333 + case 0x305: 6334 + return code - 0x305 + 1; 6335 + case 0x30d: 6336 + case 0x30e: 6337 + return code - 0x30d + 2; 6338 + case 0x310: 6339 + return code - 0x310 + 4; 6340 + case 0x312: 6341 + return code - 0x312 + 5; 6342 + case 0x33d: 6343 + case 0x33e: 6344 + case 0x33f: 6345 + return code - 0x33d + 6; 6346 + case 0x346: 6347 + return code - 0x346 + 9; 6348 + case 0x34a: 6349 + case 0x34b: 6350 + case 0x34c: 6351 + return code - 0x34a + 10; 6352 + case 0x350: 6353 + case 0x351: 6354 + case 0x352: 6355 + return code - 0x350 + 13; 6356 + case 0x357: 6357 + return code - 0x357 + 16; 6358 + case 0x35b: 6359 + return code - 0x35b + 17; 6360 + case 0x363: 6361 + case 0x364: 6362 + case 0x365: 6363 + case 0x366: 6364 + case 0x367: 6365 + case 0x368: 6366 + case 0x369: 6367 + case 0x36a: 6368 + case 0x36b: 6369 + case 0x36c: 6370 + case 0x36d: 6371 + case 0x36e: 6372 + case 0x36f: 6373 + return code - 0x363 + 18; 6374 + case 0x483: 6375 + case 0x484: 6376 + case 0x485: 6377 + case 0x486: 6378 + case 0x487: 6379 + return code - 0x483 + 31; 6380 + case 0x592: 6381 + case 0x593: 6382 + case 0x594: 6383 + case 0x595: 6384 + return code - 0x592 + 36; 6385 + case 0x597: 6386 + case 0x598: 6387 + case 0x599: 6388 + return code - 0x597 + 40; 6389 + case 0x59c: 6390 + case 0x59d: 6391 + case 0x59e: 6392 + case 0x59f: 6393 + case 0x5a0: 6394 + case 0x5a1: 6395 + return code - 0x59c + 43; 6396 + case 0x5a8: 6397 + case 0x5a9: 6398 + return code - 0x5a8 + 49; 6399 + case 0x5ab: 6400 + case 0x5ac: 6401 + return code - 0x5ab + 51; 6402 + case 0x5af: 6403 + return code - 0x5af + 53; 6404 + case 0x5c4: 6405 + return code - 0x5c4 + 54; 6406 + case 0x610: 6407 + case 0x611: 6408 + case 0x612: 6409 + case 0x613: 6410 + case 0x614: 6411 + case 0x615: 6412 + case 0x616: 6413 + case 0x617: 6414 + return code - 0x610 + 55; 6415 + case 0x657: 6416 + case 0x658: 6417 + case 0x659: 6418 + case 0x65a: 6419 + case 0x65b: 6420 + return code - 0x657 + 63; 6421 + case 0x65d: 6422 + case 0x65e: 6423 + return code - 0x65d + 68; 6424 + case 0x6d6: 6425 + case 0x6d7: 6426 + case 0x6d8: 6427 + case 0x6d9: 6428 + case 0x6da: 6429 + case 0x6db: 6430 + case 0x6dc: 6431 + return code - 0x6d6 + 70; 6432 + case 0x6df: 6433 + case 0x6e0: 6434 + case 0x6e1: 6435 + case 0x6e2: 6436 + return code - 0x6df + 77; 6437 + case 0x6e4: 6438 + return code - 0x6e4 + 81; 6439 + case 0x6e7: 6440 + case 0x6e8: 6441 + return code - 0x6e7 + 82; 6442 + case 0x6eb: 6443 + case 0x6ec: 6444 + return code - 0x6eb + 84; 6445 + case 0x730: 6446 + return code - 0x730 + 86; 6447 + case 0x732: 6448 + case 0x733: 6449 + return code - 0x732 + 87; 6450 + case 0x735: 6451 + case 0x736: 6452 + return code - 0x735 + 89; 6453 + case 0x73a: 6454 + return code - 0x73a + 91; 6455 + case 0x73d: 6456 + return code - 0x73d + 92; 6457 + case 0x73f: 6458 + case 0x740: 6459 + case 0x741: 6460 + return code - 0x73f + 93; 6461 + case 0x743: 6462 + return code - 0x743 + 96; 6463 + case 0x745: 6464 + return code - 0x745 + 97; 6465 + case 0x747: 6466 + return code - 0x747 + 98; 6467 + case 0x749: 6468 + case 0x74a: 6469 + return code - 0x749 + 99; 6470 + case 0x7eb: 6471 + case 0x7ec: 6472 + case 0x7ed: 6473 + case 0x7ee: 6474 + case 0x7ef: 6475 + case 0x7f0: 6476 + case 0x7f1: 6477 + return code - 0x7eb + 101; 6478 + case 0x7f3: 6479 + return code - 0x7f3 + 108; 6480 + case 0x816: 6481 + case 0x817: 6482 + case 0x818: 6483 + case 0x819: 6484 + return code - 0x816 + 109; 6485 + case 0x81b: 6486 + case 0x81c: 6487 + case 0x81d: 6488 + case 0x81e: 6489 + case 0x81f: 6490 + case 0x820: 6491 + case 0x821: 6492 + case 0x822: 6493 + case 0x823: 6494 + return code - 0x81b + 113; 6495 + case 0x825: 6496 + case 0x826: 6497 + case 0x827: 6498 + return code - 0x825 + 122; 6499 + case 0x829: 6500 + case 0x82a: 6501 + case 0x82b: 6502 + case 0x82c: 6503 + case 0x82d: 6504 + return code - 0x829 + 125; 6505 + case 0x951: 6506 + return code - 0x951 + 130; 6507 + case 0x953: 6508 + case 0x954: 6509 + return code - 0x953 + 131; 6510 + case 0xf82: 6511 + case 0xf83: 6512 + return code - 0xf82 + 133; 6513 + case 0xf86: 6514 + case 0xf87: 6515 + return code - 0xf86 + 135; 6516 + case 0x135d: 6517 + case 0x135e: 6518 + case 0x135f: 6519 + return code - 0x135d + 137; 6520 + case 0x17dd: 6521 + return code - 0x17dd + 140; 6522 + case 0x193a: 6523 + return code - 0x193a + 141; 6524 + case 0x1a17: 6525 + return code - 0x1a17 + 142; 6526 + case 0x1a75: 6527 + case 0x1a76: 6528 + case 0x1a77: 6529 + case 0x1a78: 6530 + case 0x1a79: 6531 + case 0x1a7a: 6532 + case 0x1a7b: 6533 + case 0x1a7c: 6534 + return code - 0x1a75 + 143; 6535 + case 0x1b6b: 6536 + return code - 0x1b6b + 151; 6537 + case 0x1b6d: 6538 + case 0x1b6e: 6539 + case 0x1b6f: 6540 + case 0x1b70: 6541 + case 0x1b71: 6542 + case 0x1b72: 6543 + case 0x1b73: 6544 + return code - 0x1b6d + 152; 6545 + case 0x1cd0: 6546 + case 0x1cd1: 6547 + case 0x1cd2: 6548 + return code - 0x1cd0 + 159; 6549 + case 0x1cda: 6550 + case 0x1cdb: 6551 + return code - 0x1cda + 162; 6552 + case 0x1ce0: 6553 + return code - 0x1ce0 + 164; 6554 + case 0x1dc0: 6555 + case 0x1dc1: 6556 + return code - 0x1dc0 + 165; 6557 + case 0x1dc3: 6558 + case 0x1dc4: 6559 + case 0x1dc5: 6560 + case 0x1dc6: 6561 + case 0x1dc7: 6562 + case 0x1dc8: 6563 + case 0x1dc9: 6564 + return code - 0x1dc3 + 167; 6565 + case 0x1dcb: 6566 + case 0x1dcc: 6567 + return code - 0x1dcb + 174; 6568 + case 0x1dd1: 6569 + case 0x1dd2: 6570 + case 0x1dd3: 6571 + case 0x1dd4: 6572 + case 0x1dd5: 6573 + case 0x1dd6: 6574 + case 0x1dd7: 6575 + case 0x1dd8: 6576 + case 0x1dd9: 6577 + case 0x1dda: 6578 + case 0x1ddb: 6579 + case 0x1ddc: 6580 + case 0x1ddd: 6581 + case 0x1dde: 6582 + case 0x1ddf: 6583 + case 0x1de0: 6584 + case 0x1de1: 6585 + case 0x1de2: 6586 + case 0x1de3: 6587 + case 0x1de4: 6588 + case 0x1de5: 6589 + case 0x1de6: 6590 + return code - 0x1dd1 + 176; 6591 + case 0x1dfe: 6592 + return code - 0x1dfe + 198; 6593 + case 0x20d0: 6594 + case 0x20d1: 6595 + return code - 0x20d0 + 199; 6596 + case 0x20d4: 6597 + case 0x20d5: 6598 + case 0x20d6: 6599 + case 0x20d7: 6600 + return code - 0x20d4 + 201; 6601 + case 0x20db: 6602 + case 0x20dc: 6603 + return code - 0x20db + 205; 6604 + case 0x20e1: 6605 + return code - 0x20e1 + 207; 6606 + case 0x20e7: 6607 + return code - 0x20e7 + 208; 6608 + case 0x20e9: 6609 + return code - 0x20e9 + 209; 6610 + case 0x20f0: 6611 + return code - 0x20f0 + 210; 6612 + case 0x2cef: 6613 + case 0x2cf0: 6614 + case 0x2cf1: 6615 + return code - 0x2cef + 211; 6616 + case 0x2de0: 6617 + case 0x2de1: 6618 + case 0x2de2: 6619 + case 0x2de3: 6620 + case 0x2de4: 6621 + case 0x2de5: 6622 + case 0x2de6: 6623 + case 0x2de7: 6624 + case 0x2de8: 6625 + case 0x2de9: 6626 + case 0x2dea: 6627 + case 0x2deb: 6628 + case 0x2dec: 6629 + case 0x2ded: 6630 + case 0x2dee: 6631 + case 0x2def: 6632 + case 0x2df0: 6633 + case 0x2df1: 6634 + case 0x2df2: 6635 + case 0x2df3: 6636 + case 0x2df4: 6637 + case 0x2df5: 6638 + case 0x2df6: 6639 + case 0x2df7: 6640 + case 0x2df8: 6641 + case 0x2df9: 6642 + case 0x2dfa: 6643 + case 0x2dfb: 6644 + case 0x2dfc: 6645 + case 0x2dfd: 6646 + case 0x2dfe: 6647 + case 0x2dff: 6648 + return code - 0x2de0 + 214; 6649 + case 0xa66f: 6650 + return code - 0xa66f + 246; 6651 + case 0xa67c: 6652 + case 0xa67d: 6653 + return code - 0xa67c + 247; 6654 + case 0xa6f0: 6655 + case 0xa6f1: 6656 + return code - 0xa6f0 + 249; 6657 + case 0xa8e0: 6658 + case 0xa8e1: 6659 + case 0xa8e2: 6660 + case 0xa8e3: 6661 + case 0xa8e4: 6662 + case 0xa8e5: 6663 + case 0xa8e6: 6664 + case 0xa8e7: 6665 + case 0xa8e8: 6666 + case 0xa8e9: 6667 + case 0xa8ea: 6668 + case 0xa8eb: 6669 + case 0xa8ec: 6670 + case 0xa8ed: 6671 + case 0xa8ee: 6672 + case 0xa8ef: 6673 + case 0xa8f0: 6674 + case 0xa8f1: 6675 + return code - 0xa8e0 + 251; 6676 + case 0xaab0: 6677 + return code - 0xaab0 + 269; 6678 + case 0xaab2: 6679 + case 0xaab3: 6680 + return code - 0xaab2 + 270; 6681 + case 0xaab7: 6682 + case 0xaab8: 6683 + return code - 0xaab7 + 272; 6684 + case 0xaabe: 6685 + case 0xaabf: 6686 + return code - 0xaabe + 274; 6687 + case 0xaac1: 6688 + return code - 0xaac1 + 276; 6689 + case 0xfe20: 6690 + case 0xfe21: 6691 + case 0xfe22: 6692 + case 0xfe23: 6693 + case 0xfe24: 6694 + case 0xfe25: 6695 + case 0xfe26: 6696 + return code - 0xfe20 + 277; 6697 + case 0x10a0f: 6698 + return code - 0x10a0f + 284; 6699 + case 0x10a38: 6700 + return code - 0x10a38 + 285; 6701 + case 0x1d185: 6702 + case 0x1d186: 6703 + case 0x1d187: 6704 + case 0x1d188: 6705 + case 0x1d189: 6706 + return code - 0x1d185 + 286; 6707 + case 0x1d1aa: 6708 + case 0x1d1ab: 6709 + case 0x1d1ac: 6710 + case 0x1d1ad: 6711 + return code - 0x1d1aa + 291; 6712 + case 0x1d242: 6713 + case 0x1d243: 6714 + case 0x1d244: 6715 + return code - 0x1d242 + 295; 6716 + } 6717 + return 0; 6718 +} 6719 diff --git a/st.c b/st.c 6720 index 8e57991..9824ea6 100644 6721 --- a/st.c 6722 +++ b/st.c 6723 @@ -19,6 +19,7 @@ 6724 6725 #include "st.h" 6726 #include "win.h" 6727 +#include "graphics.h" 6728 6729 #if defined(__linux) 6730 #include <pty.h> 6731 @@ -36,6 +37,10 @@ 6732 #define STR_BUF_SIZ ESC_BUF_SIZ 6733 #define STR_ARG_SIZ ESC_ARG_SIZ 6734 6735 +/* PUA character used as an image placeholder */ 6736 +#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE 6737 +#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE 6738 + 6739 /* macros */ 6740 #define IS_SET(flag) ((term.mode & (flag)) != 0) 6741 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 6742 @@ -113,6 +118,8 @@ typedef struct { 6743 typedef struct { 6744 int row; /* nb row */ 6745 int col; /* nb col */ 6746 + int pixw; /* width of the text area in pixels */ 6747 + int pixh; /* height of the text area in pixels */ 6748 Line *line; /* screen */ 6749 Line *alt; /* alternate screen */ 6750 int *dirty; /* dirtyness of lines */ 6751 @@ -213,7 +220,6 @@ static Rune utf8decodebyte(char, size_t *); 6752 static char utf8encodebyte(Rune, size_t); 6753 static size_t utf8validate(Rune *, size_t); 6754 6755 -static char *base64dec(const char *); 6756 static char base64dec_getc(const char **); 6757 6758 static ssize_t xwrite(int, const char *, size_t); 6759 @@ -232,6 +238,10 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 6760 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 6761 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 6762 6763 +/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0 6764 + * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */ 6765 +uint16_t diacritic_to_num(uint32_t code); 6766 + 6767 ssize_t 6768 xwrite(int fd, const char *s, size_t len) 6769 { 6770 @@ -616,6 +626,12 @@ getsel(void) 6771 if (gp->mode & ATTR_WDUMMY) 6772 continue; 6773 6774 + if (gp->mode & ATTR_IMAGE) { 6775 + // TODO: Copy diacritics as well 6776 + ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr); 6777 + continue; 6778 + } 6779 + 6780 ptr += utf8encode(gp->u, ptr); 6781 } 6782 6783 @@ -715,11 +731,14 @@ sigchld(int a) 6784 int stat; 6785 pid_t p; 6786 6787 - if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 6788 + if ((p = waitpid(-1, &stat, WNOHANG)) < 0) 6789 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 6790 6791 - if (pid != p) 6792 + if (pid != p) { 6793 + /* reinstall sigchld handler */ 6794 + signal(SIGCHLD, sigchld); 6795 return; 6796 + } 6797 6798 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 6799 die("child exited with status %d\n", WEXITSTATUS(stat)); 6800 @@ -819,7 +838,11 @@ ttyread(void) 6801 { 6802 static char buf[BUFSIZ]; 6803 static int buflen = 0; 6804 - int ret, written; 6805 + static int already_processing = 0; 6806 + int ret, written = 0; 6807 + 6808 + if (buflen >= LEN(buf)) 6809 + return 0; 6810 6811 /* append read bytes to unprocessed bytes */ 6812 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 6813 @@ -831,7 +854,24 @@ ttyread(void) 6814 die("couldn't read from shell: %s\n", strerror(errno)); 6815 default: 6816 buflen += ret; 6817 - written = twrite(buf, buflen, 0); 6818 + if (already_processing) { 6819 + /* Avoid recursive call to twrite() */ 6820 + return ret; 6821 + } 6822 + already_processing = 1; 6823 + while (1) { 6824 + int buflen_before_processing = buflen; 6825 + written += twrite(buf + written, buflen - written, 0); 6826 + // If buflen changed during the call to twrite, there is 6827 + // new data, and we need to keep processing, otherwise 6828 + // we can exit. This will not loop forever because the 6829 + // buffer is limited, and we don't clean it in this 6830 + // loop, so at some point ttywrite will have to drop 6831 + // some data. 6832 + if (buflen_before_processing == buflen) 6833 + break; 6834 + } 6835 + already_processing = 0; 6836 buflen -= written; 6837 /* keep any incomplete UTF-8 byte sequence for the next call */ 6838 if (buflen > 0) 6839 @@ -874,6 +914,7 @@ ttywriteraw(const char *s, size_t n) 6840 fd_set wfd, rfd; 6841 ssize_t r; 6842 size_t lim = 256; 6843 + int retries_left = 100; 6844 6845 /* 6846 * Remember that we are using a pty, which might be a modem line. 6847 @@ -882,6 +923,9 @@ ttywriteraw(const char *s, size_t n) 6848 * FIXME: Migrate the world to Plan 9. 6849 */ 6850 while (n > 0) { 6851 + if (retries_left-- <= 0) 6852 + goto too_many_retries; 6853 + 6854 FD_ZERO(&wfd); 6855 FD_ZERO(&rfd); 6856 FD_SET(cmdfd, &wfd); 6857 @@ -923,11 +967,16 @@ ttywriteraw(const char *s, size_t n) 6858 6859 write_error: 6860 die("write error on tty: %s\n", strerror(errno)); 6861 +too_many_retries: 6862 + fprintf(stderr, "Could not write %zu bytes to tty\n", n); 6863 } 6864 6865 void 6866 ttyresize(int tw, int th) 6867 { 6868 + term.pixw = tw; 6869 + term.pixh = th; 6870 + 6871 struct winsize w; 6872 6873 w.ws_row = term.row; 6874 @@ -1015,7 +1064,8 @@ treset(void) 6875 term.c = (TCursor){{ 6876 .mode = ATTR_NULL, 6877 .fg = defaultfg, 6878 - .bg = defaultbg 6879 + .bg = defaultbg, 6880 + .decor = DECOR_DEFAULT_COLOR 6881 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 6882 6883 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 6884 @@ -1038,7 +1088,9 @@ treset(void) 6885 void 6886 tnew(int col, int row) 6887 { 6888 - term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 6889 + term = (Term){.c = {.attr = {.fg = defaultfg, 6890 + .bg = defaultbg, 6891 + .decor = DECOR_DEFAULT_COLOR}}}; 6892 tresize(col, row); 6893 treset(); 6894 } 6895 @@ -1215,9 +1267,24 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) 6896 term.line[y][x-1].mode &= ~ATTR_WIDE; 6897 } 6898 6899 + if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE && 6900 + tgetisclassicplaceholder(&term.line[y][x])) { 6901 + // This is a workaround: don't overwrite classic placement 6902 + // placeholders with space symbols (unlike Unicode placeholders 6903 + // which must be overwritten by anything). 6904 + term.line[y][x].bg = attr->bg; 6905 + term.dirty[y] = 1; 6906 + return; 6907 + } 6908 + 6909 term.dirty[y] = 1; 6910 term.line[y][x] = *attr; 6911 term.line[y][x].u = u; 6912 + 6913 + if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) { 6914 + term.line[y][x].u = 0; 6915 + term.line[y][x].mode |= ATTR_IMAGE; 6916 + } 6917 } 6918 6919 void 6920 @@ -1244,12 +1311,110 @@ tclearregion(int x1, int y1, int x2, int y2) 6921 selclear(); 6922 gp->fg = term.c.attr.fg; 6923 gp->bg = term.c.attr.bg; 6924 + gp->decor = term.c.attr.decor; 6925 gp->mode = 0; 6926 gp->u = ' '; 6927 } 6928 } 6929 } 6930 6931 +/// Fills a rectangle area with an image placeholder. The starting point is the 6932 +/// cursor. Adds empty lines if needed. The placeholder will be marked as 6933 +/// classic. 6934 +void tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, int cols, 6935 + int rows, char do_not_move_cursor, 6936 + Glyph *text_underneath) { 6937 + for (int row = 0; row < rows; ++row) { 6938 + int y = term.c.y; 6939 + term.dirty[y] = 1; 6940 + for (int col = 0; col < cols; ++col) { 6941 + int x = term.c.x + col; 6942 + if (x >= term.col) 6943 + break; 6944 + Glyph *gp = &term.line[y][x]; 6945 + if (selected(x, y)) 6946 + selclear(); 6947 + if (text_underneath) { 6948 + Glyph *to_save = gp; 6949 + // If there is already a classic placeholder, 6950 + // use the text underneath it. This will leave 6951 + // holes in images, but at least we are 6952 + // guaranteed to restore the original text. 6953 + if (gp->mode & ATTR_IMAGE && 6954 + tgetisclassicplaceholder(gp)) { 6955 + Glyph *under = 6956 + gr_get_glyph_underneath_image( 6957 + tgetimgid(gp), 6958 + tgetimgplacementid(gp), 6959 + tgetimgcol(gp), 6960 + tgetimgrow(gp)); 6961 + if (under) 6962 + to_save = under; 6963 + } 6964 + text_underneath[cols * row + col] = *to_save; 6965 + } 6966 + gp->mode = ATTR_IMAGE; 6967 + gp->u = 0; 6968 + tsetimgrow(gp, row + 1); 6969 + tsetimgcol(gp, col + 1); 6970 + tsetimgid(gp, image_id); 6971 + tsetimgplacementid(gp, placement_id); 6972 + tsetimgdiacriticcount(gp, 3); 6973 + tsetisclassicplaceholder(gp, 1); 6974 + } 6975 + // If moving the cursor is not allowed and this is the last line 6976 + // of the terminal, we are done. 6977 + if (do_not_move_cursor && y == term.row - 1) 6978 + break; 6979 + // Move the cursor down, maybe creating a new line. The x is 6980 + // preserved (we never change term.c.x in the loop above). 6981 + if (row != rows - 1) 6982 + tnewline(/*first_col=*/0); 6983 + } 6984 + if (do_not_move_cursor) { 6985 + // Return the cursor to the original position. 6986 + tmoveto(term.c.x, term.c.y - rows + 1); 6987 + } else { 6988 + // Move the cursor beyond the last column, as required by the 6989 + // protocol. If the cursor goes beyond the screen edge, insert a 6990 + // newline to match the behavior of kitty. 6991 + if (term.c.x + cols >= term.col) 6992 + tnewline(/*first_col=*/1); 6993 + else 6994 + tmoveto(term.c.x + cols, term.c.y); 6995 + } 6996 +} 6997 + 6998 +void gr_for_each_image_cell(int (*callback)(void *data, Glyph *gp), 6999 + void *data) { 7000 + for (int row = 0; row < term.row; ++row) { 7001 + for (int col = 0; col < term.col; ++col) { 7002 + Glyph *gp = &term.line[row][col]; 7003 + if (gp->mode & ATTR_IMAGE) { 7004 + if (callback(data, gp)) 7005 + term.dirty[row] = 1; 7006 + } 7007 + } 7008 + } 7009 +} 7010 + 7011 +void gr_schedule_image_redraw_by_id(uint32_t image_id) { 7012 + for (int row = 0; row < term.row; ++row) { 7013 + if (term.dirty[row]) 7014 + continue; 7015 + for (int col = 0; col < term.col; ++col) { 7016 + Glyph *gp = &term.line[row][col]; 7017 + if (gp->mode & ATTR_IMAGE) { 7018 + uint32_t cell_image_id = tgetimgid(gp); 7019 + if (cell_image_id == image_id) { 7020 + term.dirty[row] = 1; 7021 + break; 7022 + } 7023 + } 7024 + } 7025 + } 7026 +} 7027 + 7028 void 7029 tdeletechar(int n) 7030 { 7031 @@ -1368,6 +1533,7 @@ tsetattr(const int *attr, int l) 7032 ATTR_STRUCK ); 7033 term.c.attr.fg = defaultfg; 7034 term.c.attr.bg = defaultbg; 7035 + term.c.attr.decor = DECOR_DEFAULT_COLOR; 7036 break; 7037 case 1: 7038 term.c.attr.mode |= ATTR_BOLD; 7039 @@ -1380,6 +1546,20 @@ tsetattr(const int *attr, int l) 7040 break; 7041 case 4: 7042 term.c.attr.mode |= ATTR_UNDERLINE; 7043 + if (i + 1 < l) { 7044 + idx = attr[++i]; 7045 + if (BETWEEN(idx, 1, 5)) { 7046 + tsetdecorstyle(&term.c.attr, idx); 7047 + } else if (idx == 0) { 7048 + term.c.attr.mode &= ~ATTR_UNDERLINE; 7049 + tsetdecorstyle(&term.c.attr, 0); 7050 + } else { 7051 + fprintf(stderr, 7052 + "erresc: unknown underline " 7053 + "style %d\n", 7054 + idx); 7055 + } 7056 + } 7057 break; 7058 case 5: /* slow blink */ 7059 /* FALLTHROUGH */ 7060 @@ -1403,6 +1583,7 @@ tsetattr(const int *attr, int l) 7061 break; 7062 case 24: 7063 term.c.attr.mode &= ~ATTR_UNDERLINE; 7064 + tsetdecorstyle(&term.c.attr, 0); 7065 break; 7066 case 25: 7067 term.c.attr.mode &= ~ATTR_BLINK; 7068 @@ -1431,10 +1612,11 @@ tsetattr(const int *attr, int l) 7069 term.c.attr.bg = defaultbg; 7070 break; 7071 case 58: 7072 - /* This starts a sequence to change the color of 7073 - * "underline" pixels. We don't support that and 7074 - * instead eat up a following "5;n" or "2;r;g;b". */ 7075 - tdefcolor(attr, &i, l); 7076 + if ((idx = tdefcolor(attr, &i, l)) >= 0) 7077 + tsetdecorcolor(&term.c.attr, idx); 7078 + break; 7079 + case 59: 7080 + tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR); 7081 break; 7082 default: 7083 if (BETWEEN(attr[i], 30, 37)) { 7084 @@ -1823,6 +2005,39 @@ csihandle(void) 7085 goto unknown; 7086 } 7087 break; 7088 + case '>': 7089 + switch (csiescseq.mode[1]) { 7090 + case 'q': /* XTVERSION -- Print terminal name and version */ 7091 + len = snprintf(buf, sizeof(buf), 7092 + "\033P>|st-graphics(%s)\033\\", VERSION); 7093 + ttywrite(buf, len, 0); 7094 + break; 7095 + default: 7096 + goto unknown; 7097 + } 7098 + break; 7099 + case 't': /* XTWINOPS -- Window manipulation */ 7100 + switch (csiescseq.arg[0]) { 7101 + case 14: /* Report text area size in pixels. */ 7102 + len = snprintf(buf, sizeof(buf), "\033[4;%i;%it", 7103 + term.pixh, term.pixw); 7104 + ttywrite(buf, len, 0); 7105 + break; 7106 + case 16: /* Report character cell size in pixels. */ 7107 + len = snprintf(buf, sizeof(buf), "\033[6;%i;%it", 7108 + term.pixh / term.row, 7109 + term.pixw / term.col); 7110 + ttywrite(buf, len, 0); 7111 + break; 7112 + case 18: /* Report the size of the text area in characters. */ 7113 + len = snprintf(buf, sizeof(buf), "\033[8;%i;%it", 7114 + term.row, term.col); 7115 + ttywrite(buf, len, 0); 7116 + break; 7117 + default: 7118 + goto unknown; 7119 + } 7120 + break; 7121 } 7122 } 7123 7124 @@ -1985,8 +2200,27 @@ strhandle(void) 7125 case 'k': /* old title set compatibility */ 7126 xsettitle(strescseq.args[0]); 7127 return; 7128 - case 'P': /* DCS -- Device Control String */ 7129 case '_': /* APC -- Application Program Command */ 7130 + if (gr_parse_command(strescseq.buf, strescseq.len)) { 7131 + GraphicsCommandResult *res = &graphics_command_result; 7132 + if (res->create_placeholder) { 7133 + tcreateimgplaceholder( 7134 + res->placeholder.image_id, 7135 + res->placeholder.placement_id, 7136 + res->placeholder.columns, 7137 + res->placeholder.rows, 7138 + res->placeholder.do_not_move_cursor, 7139 + res->placeholder.text_underneath); 7140 + } 7141 + if (res->response[0]) 7142 + ttywrite(res->response, strlen(res->response), 7143 + 0); 7144 + if (res->redraw) 7145 + tfulldirt(); 7146 + return; 7147 + } 7148 + return; 7149 + case 'P': /* DCS -- Device Control String */ 7150 case '^': /* PM -- Privacy Message */ 7151 return; 7152 } 7153 @@ -2492,6 +2726,41 @@ check_control_code: 7154 if (selected(term.c.x, term.c.y)) 7155 selclear(); 7156 7157 + // wcwidth is broken on some systems, set the width to 0 if it's a known 7158 + // diacritic used for images. 7159 + uint16_t num = diacritic_to_num(u); 7160 + if (num != 0) 7161 + width = 0; 7162 + // Set the width to 1 if it's an image placeholder character. 7163 + if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) 7164 + width = 1; 7165 + 7166 + if (width == 0) { 7167 + // It's probably a combining char. Combining characters are not 7168 + // supported, so we just ignore them, unless it denotes the row and 7169 + // column of an image character. 7170 + if (term.c.y <= 0 && term.c.x <= 0) 7171 + return; 7172 + else if (term.c.x == 0) 7173 + gp = &term.line[term.c.y-1][term.col-1]; 7174 + else if (term.c.state & CURSOR_WRAPNEXT) 7175 + gp = &term.line[term.c.y][term.c.x]; 7176 + else 7177 + gp = &term.line[term.c.y][term.c.x-1]; 7178 + if (num && (gp->mode & ATTR_IMAGE)) { 7179 + unsigned diaccount = tgetimgdiacriticcount(gp); 7180 + if (diaccount == 0) 7181 + tsetimgrow(gp, num); 7182 + else if (diaccount == 1) 7183 + tsetimgcol(gp, num); 7184 + else if (diaccount == 2) 7185 + tsetimg4thbyteplus1(gp, num); 7186 + tsetimgdiacriticcount(gp, diaccount + 1); 7187 + } 7188 + term.lastc = u; 7189 + return; 7190 + } 7191 + 7192 gp = &term.line[term.c.y][term.c.x]; 7193 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 7194 gp->mode |= ATTR_WRAP; 7195 @@ -2658,6 +2927,8 @@ drawregion(int x1, int y1, int x2, int y2) 7196 { 7197 int y; 7198 7199 + xstartimagedraw(term.dirty, term.row); 7200 + 7201 for (y = y1; y < y2; y++) { 7202 if (!term.dirty[y]) 7203 continue; 7204 @@ -2665,6 +2936,8 @@ drawregion(int x1, int y1, int x2, int y2) 7205 term.dirty[y] = 0; 7206 xdrawline(term.line[y], x1, y, x2); 7207 } 7208 + 7209 + xfinishimagedraw(); 7210 } 7211 7212 void 7213 @@ -2699,3 +2972,9 @@ redraw(void) 7214 tfulldirt(); 7215 draw(); 7216 } 7217 + 7218 +Glyph 7219 +getglyphat(int col, int row) 7220 +{ 7221 + return term.line[row][col]; 7222 +} 7223 diff --git a/st.h b/st.h 7224 index fd3b0d8..c5dd731 100644 7225 --- a/st.h 7226 +++ b/st.h 7227 @@ -12,7 +12,7 @@ 7228 #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 7229 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 7230 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 7231 - (a).bg != (b).bg) 7232 + (a).bg != (b).bg || (a).decor != (b).decor) 7233 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 7234 (t1.tv_nsec-t2.tv_nsec)/1E6) 7235 #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 7236 @@ -20,6 +20,10 @@ 7237 #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 7238 #define IS_TRUECOL(x) (1 << 24 & (x)) 7239 7240 +// This decor color indicates that the fg color should be used. Note that it's 7241 +// not a 24-bit color because the 25-th bit is not set. 7242 +#define DECOR_DEFAULT_COLOR 0x0ffffff 7243 + 7244 enum glyph_attribute { 7245 ATTR_NULL = 0, 7246 ATTR_BOLD = 1 << 0, 7247 @@ -34,6 +38,7 @@ enum glyph_attribute { 7248 ATTR_WIDE = 1 << 9, 7249 ATTR_WDUMMY = 1 << 10, 7250 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 7251 + ATTR_IMAGE = 1 << 14, 7252 }; 7253 7254 enum selection_mode { 7255 @@ -52,6 +57,14 @@ enum selection_snap { 7256 SNAP_LINE = 2 7257 }; 7258 7259 +enum underline_style { 7260 + UNDERLINE_STRAIGHT = 1, 7261 + UNDERLINE_DOUBLE = 2, 7262 + UNDERLINE_CURLY = 3, 7263 + UNDERLINE_DOTTED = 4, 7264 + UNDERLINE_DASHED = 5, 7265 +}; 7266 + 7267 typedef unsigned char uchar; 7268 typedef unsigned int uint; 7269 typedef unsigned long ulong; 7270 @@ -65,6 +78,7 @@ typedef struct { 7271 ushort mode; /* attribute flags */ 7272 uint32_t fg; /* foreground */ 7273 uint32_t bg; /* background */ 7274 + uint32_t decor; /* decoration (like underline) */ 7275 } Glyph; 7276 7277 typedef Glyph *Line; 7278 @@ -105,6 +119,8 @@ void selextend(int, int, int, int); 7279 int selected(int, int); 7280 char *getsel(void); 7281 7282 +Glyph getglyphat(int, int); 7283 + 7284 size_t utf8encode(Rune, char *); 7285 7286 void *xmalloc(size_t); 7287 @@ -124,3 +140,69 @@ extern unsigned int tabspaces; 7288 extern unsigned int defaultfg; 7289 extern unsigned int defaultbg; 7290 extern unsigned int defaultcs; 7291 + 7292 +// Accessors to decoration properties stored in `decor`. 7293 +// The 25-th bit is used to indicate if it's a 24-bit color. 7294 +static inline uint32_t tgetdecorcolor(Glyph *g) { return g->decor & 0x1ffffff; } 7295 +static inline uint32_t tgetdecorstyle(Glyph *g) { return (g->decor >> 25) & 0x7; } 7296 +static inline void tsetdecorcolor(Glyph *g, uint32_t color) { 7297 + g->decor = (g->decor & ~0x1ffffff) | (color & 0x1ffffff); 7298 +} 7299 +static inline void tsetdecorstyle(Glyph *g, uint32_t style) { 7300 + g->decor = (g->decor & ~(0x7 << 25)) | ((style & 0x7) << 25); 7301 +} 7302 + 7303 + 7304 +// Some accessors to image placeholder properties stored in `u`: 7305 +// - row (1-base) - 9 bits 7306 +// - column (1-base) - 9 bits 7307 +// - most significant byte of the image id plus 1 - 9 bits (0 means unspecified, 7308 +// don't forget to subtract 1). 7309 +// - the original number of diacritics (0, 1, 2, or 3) - 2 bits 7310 +// - whether this is a classic (1) or Unicode (0) placeholder - 1 bit 7311 +static inline uint32_t tgetimgrow(Glyph *g) { return g->u & 0x1ff; } 7312 +static inline uint32_t tgetimgcol(Glyph *g) { return (g->u >> 9) & 0x1ff; } 7313 +static inline uint32_t tgetimgid4thbyteplus1(Glyph *g) { return (g->u >> 18) & 0x1ff; } 7314 +static inline uint32_t tgetimgdiacriticcount(Glyph *g) { return (g->u >> 27) & 0x3; } 7315 +static inline uint32_t tgetisclassicplaceholder(Glyph *g) { return (g->u >> 29) & 0x1; } 7316 +static inline void tsetimgrow(Glyph *g, uint32_t row) { 7317 + g->u = (g->u & ~0x1ff) | (row & 0x1ff); 7318 +} 7319 +static inline void tsetimgcol(Glyph *g, uint32_t col) { 7320 + g->u = (g->u & ~(0x1ff << 9)) | ((col & 0x1ff) << 9); 7321 +} 7322 +static inline void tsetimg4thbyteplus1(Glyph *g, uint32_t byteplus1) { 7323 + g->u = (g->u & ~(0x1ff << 18)) | ((byteplus1 & 0x1ff) << 18); 7324 +} 7325 +static inline void tsetimgdiacriticcount(Glyph *g, uint32_t count) { 7326 + g->u = (g->u & ~(0x3 << 27)) | ((count & 0x3) << 27); 7327 +} 7328 +static inline void tsetisclassicplaceholder(Glyph *g, uint32_t isclassic) { 7329 + g->u = (g->u & ~(0x1 << 29)) | ((isclassic & 0x1) << 29); 7330 +} 7331 + 7332 +/// Returns the full image id. This is a naive implementation, if the most 7333 +/// significant byte is not specified, it's assumed to be 0 instead of inferring 7334 +/// it from the cells to the left. 7335 +static inline uint32_t tgetimgid(Glyph *g) { 7336 + uint32_t msb = tgetimgid4thbyteplus1(g); 7337 + if (msb != 0) 7338 + --msb; 7339 + return (msb << 24) | (g->fg & 0xFFFFFF); 7340 +} 7341 + 7342 +/// Sets the full image id. 7343 +static inline void tsetimgid(Glyph *g, uint32_t id) { 7344 + g->fg = (id & 0xFFFFFF) | (1 << 24); 7345 + tsetimg4thbyteplus1(g, ((id >> 24) & 0xFF) + 1); 7346 +} 7347 + 7348 +static inline uint32_t tgetimgplacementid(Glyph *g) { 7349 + if (tgetdecorcolor(g) == DECOR_DEFAULT_COLOR) 7350 + return 0; 7351 + return g->decor & 0xFFFFFF; 7352 +} 7353 + 7354 +static inline void tsetimgplacementid(Glyph *g, uint32_t id) { 7355 + g->decor = (id & 0xFFFFFF) | (1 << 24); 7356 +} 7357 diff --git a/st.info b/st.info 7358 index efab2cf..ded76c1 100644 7359 --- a/st.info 7360 +++ b/st.info 7361 @@ -195,6 +195,7 @@ st-mono| simpleterm monocolor, 7362 Ms=\E]52;%p1%s;%p2%s\007, 7363 Se=\E[2 q, 7364 Ss=\E[%p1%d q, 7365 + Smulx=\E[4:%p1%dm, 7366 7367 st| simpleterm, 7368 use=st-mono, 7369 @@ -215,6 +216,11 @@ st-256color| simpleterm with 256 colors, 7370 initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 7371 setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 7372 setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 7373 +# Underline colors 7374 + Su, 7375 + Setulc=\E[58:2:%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m, 7376 + Setulc1=\E[58:5:%p1%dm, 7377 + ol=\E[59m, 7378 7379 st-meta| simpleterm with meta key, 7380 use=st, 7381 diff --git a/win.h b/win.h 7382 index 6de960d..31b3fff 100644 7383 --- a/win.h 7384 +++ b/win.h 7385 @@ -39,3 +39,6 @@ void xsetpointermotion(int); 7386 void xsetsel(char *); 7387 int xstartdraw(void); 7388 void xximspot(int, int); 7389 + 7390 +void xstartimagedraw(int *dirty, int rows); 7391 +void xfinishimagedraw(); 7392 diff --git a/x.c b/x.c 7393 index d73152b..6f1bf8c 100644 7394 --- a/x.c 7395 +++ b/x.c 7396 @@ -4,6 +4,8 @@ 7397 #include <limits.h> 7398 #include <locale.h> 7399 #include <signal.h> 7400 +#include <stdio.h> 7401 +#include <stdlib.h> 7402 #include <sys/select.h> 7403 #include <time.h> 7404 #include <unistd.h> 7405 @@ -19,6 +21,7 @@ char *argv0; 7406 #include "arg.h" 7407 #include "st.h" 7408 #include "win.h" 7409 +#include "graphics.h" 7410 7411 /* types used in config.h */ 7412 typedef struct { 7413 @@ -59,6 +62,12 @@ static void zoom(const Arg *); 7414 static void zoomabs(const Arg *); 7415 static void zoomreset(const Arg *); 7416 static void ttysend(const Arg *); 7417 +static void previewimage(const Arg *); 7418 +static void showimageinfo(const Arg *); 7419 +static void togglegrdebug(const Arg *); 7420 +static void dumpgrstate(const Arg *); 7421 +static void unloadimages(const Arg *); 7422 +static void toggleimages(const Arg *); 7423 7424 /* config.h for applying patches and the configuration. */ 7425 #include "config.h" 7426 @@ -81,6 +90,7 @@ typedef XftGlyphFontSpec GlyphFontSpec; 7427 typedef struct { 7428 int tw, th; /* tty width and height */ 7429 int w, h; /* window width and height */ 7430 + int hborderpx, vborderpx; 7431 int ch; /* char height */ 7432 int cw; /* char width */ 7433 int mode; /* window state/mode flags */ 7434 @@ -144,6 +154,8 @@ static inline ushort sixd_to_16bit(int); 7435 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 7436 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 7437 static void xdrawglyph(Glyph, int, int); 7438 +static void xdrawimages(Glyph, Line, int x1, int y1, int x2); 7439 +static void xdrawoneimagecell(Glyph, int x, int y); 7440 static void xclear(int, int, int, int); 7441 static int xgeommasktogravity(int); 7442 static int ximopen(Display *); 7443 @@ -220,6 +232,7 @@ static DC dc; 7444 static XWindow xw; 7445 static XSelection xsel; 7446 static TermWindow win; 7447 +static unsigned int mouse_col = 0, mouse_row = 0; 7448 7449 /* Font Ring Cache */ 7450 enum { 7451 @@ -328,10 +341,72 @@ ttysend(const Arg *arg) 7452 ttywrite(arg->s, strlen(arg->s), 1); 7453 } 7454 7455 +void 7456 +previewimage(const Arg *arg) 7457 +{ 7458 + Glyph g = getglyphat(mouse_col, mouse_row); 7459 + if (g.mode & ATTR_IMAGE) { 7460 + uint32_t image_id = tgetimgid(&g); 7461 + fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", 7462 + image_id, tgetimgplacementid(&g), tgetimgcol(&g), 7463 + tgetimgrow(&g)); 7464 + gr_preview_image(image_id, arg->s); 7465 + } 7466 +} 7467 + 7468 +void 7469 +showimageinfo(const Arg *arg) 7470 +{ 7471 + Glyph g = getglyphat(mouse_col, mouse_row); 7472 + if (g.mode & ATTR_IMAGE) { 7473 + uint32_t image_id = tgetimgid(&g); 7474 + fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", 7475 + image_id, tgetimgplacementid(&g), tgetimgcol(&g), 7476 + tgetimgrow(&g)); 7477 + char stcommand[256] = {0}; 7478 + size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0); 7479 + if (len > sizeof(stcommand) - 1) { 7480 + fprintf(stderr, "Executable name too long: %s\n", 7481 + argv0); 7482 + return; 7483 + } 7484 + gr_show_image_info(image_id, tgetimgplacementid(&g), 7485 + tgetimgcol(&g), tgetimgrow(&g), 7486 + tgetisclassicplaceholder(&g), 7487 + tgetimgdiacriticcount(&g), argv0); 7488 + } 7489 +} 7490 + 7491 +void 7492 +togglegrdebug(const Arg *arg) 7493 +{ 7494 + graphics_debug_mode = (graphics_debug_mode + 1) % 3; 7495 + redraw(); 7496 +} 7497 + 7498 +void 7499 +dumpgrstate(const Arg *arg) 7500 +{ 7501 + gr_dump_state(); 7502 +} 7503 + 7504 +void 7505 +unloadimages(const Arg *arg) 7506 +{ 7507 + gr_unload_images_to_reduce_ram(); 7508 +} 7509 + 7510 +void 7511 +toggleimages(const Arg *arg) 7512 +{ 7513 + graphics_display_images = !graphics_display_images; 7514 + redraw(); 7515 +} 7516 + 7517 int 7518 evcol(XEvent *e) 7519 { 7520 - int x = e->xbutton.x - borderpx; 7521 + int x = e->xbutton.x - win.hborderpx; 7522 LIMIT(x, 0, win.tw - 1); 7523 return x / win.cw; 7524 } 7525 @@ -339,7 +414,7 @@ evcol(XEvent *e) 7526 int 7527 evrow(XEvent *e) 7528 { 7529 - int y = e->xbutton.y - borderpx; 7530 + int y = e->xbutton.y - win.vborderpx; 7531 LIMIT(y, 0, win.th - 1); 7532 return y / win.ch; 7533 } 7534 @@ -452,6 +527,9 @@ mouseaction(XEvent *e, uint release) 7535 /* ignore Button<N>mask for Button<N> - it's set on release */ 7536 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 7537 7538 + mouse_col = evcol(e); 7539 + mouse_row = evrow(e); 7540 + 7541 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 7542 if (ms->release == release && 7543 ms->button == e->xbutton.button && 7544 @@ -739,6 +817,9 @@ cresize(int width, int height) 7545 col = MAX(1, col); 7546 row = MAX(1, row); 7547 7548 + win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100; 7549 + win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100; 7550 + 7551 tresize(col, row); 7552 xresize(col, row); 7553 ttyresize(win.tw, win.th); 7554 @@ -869,8 +950,8 @@ xhints(void) 7555 sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 7556 sizeh->height = win.h; 7557 sizeh->width = win.w; 7558 - sizeh->height_inc = win.ch; 7559 - sizeh->width_inc = win.cw; 7560 + sizeh->height_inc = 1; 7561 + sizeh->width_inc = 1; 7562 sizeh->base_height = 2 * borderpx; 7563 sizeh->base_width = 2 * borderpx; 7564 sizeh->min_height = win.ch + 2 * borderpx; 7565 @@ -1014,7 +1095,8 @@ xloadfonts(const char *fontstr, double fontsize) 7566 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 7567 usedfontsize = 12; 7568 } 7569 - defaultfontsize = usedfontsize; 7570 + if (defaultfontsize <= 0) 7571 + defaultfontsize = usedfontsize; 7572 } 7573 7574 if (xloadfont(&dc.font, pattern)) 7575 @@ -1024,7 +1106,7 @@ xloadfonts(const char *fontstr, double fontsize) 7576 FcPatternGetDouble(dc.font.match->pattern, 7577 FC_PIXEL_SIZE, 0, &fontval); 7578 usedfontsize = fontval; 7579 - if (fontsize == 0) 7580 + if (defaultfontsize <= 0 && fontsize == 0) 7581 defaultfontsize = fontval; 7582 } 7583 7584 @@ -1152,8 +1234,8 @@ xinit(int cols, int rows) 7585 xloadcols(); 7586 7587 /* adjust fixed window geometry */ 7588 - win.w = 2 * borderpx + cols * win.cw; 7589 - win.h = 2 * borderpx + rows * win.ch; 7590 + win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; 7591 + win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; 7592 if (xw.gm & XNegative) 7593 xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 7594 if (xw.gm & YNegative) 7595 @@ -1240,12 +1322,15 @@ xinit(int cols, int rows) 7596 xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 7597 if (xsel.xtarget == None) 7598 xsel.xtarget = XA_STRING; 7599 + 7600 + // Initialize the graphics (image display) module. 7601 + gr_init(xw.dpy, xw.vis, xw.cmap); 7602 } 7603 7604 int 7605 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 7606 { 7607 - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 7608 + float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; 7609 ushort mode, prevmode = USHRT_MAX; 7610 Font *font = &dc.font; 7611 int frcflags = FRC_NORMAL; 7612 @@ -1267,6 +1352,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 7613 if (mode == ATTR_WDUMMY) 7614 continue; 7615 7616 + /* Draw spaces for image placeholders (images will be drawn 7617 + * separately). */ 7618 + if (mode & ATTR_IMAGE) 7619 + rune = ' '; 7620 + 7621 /* Determine font for glyph if different from previous glyph. */ 7622 if (prevmode != mode) { 7623 prevmode = mode; 7624 @@ -1374,11 +1464,61 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 7625 return numspecs; 7626 } 7627 7628 +/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen` 7629 + * is the length of the dash plus the length of the gap. `fraction` is the 7630 + * fraction of the dash length compared to `wavelen`. */ 7631 +static void 7632 +xdrawunderdashed(Draw draw, Color *color, int x, int y, int w, 7633 + int wavelen, float fraction, int thick) 7634 +{ 7635 + int dashw = MAX(1, fraction * wavelen); 7636 + for (int i = x - x % wavelen; i < x + w; i += wavelen) { 7637 + int startx = MAX(i, x); 7638 + int endx = MIN(i + dashw, x + w); 7639 + if (startx < endx) 7640 + XftDrawRect(xw.draw, color, startx, y, endx - startx, 7641 + thick); 7642 + } 7643 +} 7644 + 7645 +/* Draws an undercurl. `h` is the total height, including line thickness. */ 7646 +static void 7647 +xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick) 7648 +{ 7649 + XGCValues gcvals = {.foreground = color->pixel, 7650 + .line_width = thick, 7651 + .line_style = LineSolid, 7652 + .cap_style = CapRound}; 7653 + GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), 7654 + GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, 7655 + &gcvals); 7656 + 7657 + XRectangle clip = {.x = x, .y = y, .width = w, .height = h}; 7658 + XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted); 7659 + 7660 + int yoffset = thick / 2; 7661 + int segh = MAX(1, h - thick); 7662 + /* Make sure every segment is at a 45 degree angle, otherwise it doesn't 7663 + * look good without antialiasing. */ 7664 + int segw = segh; 7665 + int wavelen = MAX(1, segw * 2); 7666 + 7667 + for (int i = x - (x % wavelen); i < x + w; i += wavelen) { 7668 + XPoint points[3] = {{.x = i, .y = y + yoffset}, 7669 + {.x = i + segw, .y = y + yoffset + segh}, 7670 + {.x = i + wavelen, .y = y + yoffset}}; 7671 + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3, 7672 + CoordModeOrigin); 7673 + } 7674 + 7675 + XFreeGC(xw.dpy, gc); 7676 +} 7677 + 7678 void 7679 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 7680 { 7681 int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 7682 - int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 7683 + int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, 7684 width = charlen * win.cw; 7685 Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 7686 XRenderColor colfg, colbg; 7687 @@ -1468,17 +1608,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 7688 7689 /* Intelligent cleaning up of the borders. */ 7690 if (x == 0) { 7691 - xclear(0, (y == 0)? 0 : winy, borderpx, 7692 + xclear(0, (y == 0)? 0 : winy, win.hborderpx, 7693 winy + win.ch + 7694 - ((winy + win.ch >= borderpx + win.th)? win.h : 0)); 7695 + ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); 7696 } 7697 - if (winx + width >= borderpx + win.tw) { 7698 + if (winx + width >= win.hborderpx + win.tw) { 7699 xclear(winx + width, (y == 0)? 0 : winy, win.w, 7700 - ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); 7701 + ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); 7702 } 7703 if (y == 0) 7704 - xclear(winx, 0, winx + width, borderpx); 7705 - if (winy + win.ch >= borderpx + win.th) 7706 + xclear(winx, 0, winx + width, win.vborderpx); 7707 + if (winy + win.ch >= win.vborderpx + win.th) 7708 xclear(winx, winy + win.ch, winx + width, win.h); 7709 7710 /* Clean up the region we want to draw to. */ 7711 @@ -1491,18 +1631,68 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 7712 r.width = width; 7713 XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 7714 7715 - /* Render the glyphs. */ 7716 - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 7717 - 7718 - /* Render underline and strikethrough. */ 7719 + /* Decoration color. */ 7720 + Color decor; 7721 + uint32_t decorcolor = tgetdecorcolor(&base); 7722 + if (decorcolor == DECOR_DEFAULT_COLOR) { 7723 + decor = *fg; 7724 + } else if (IS_TRUECOL(decorcolor)) { 7725 + colfg.alpha = 0xffff; 7726 + colfg.red = TRUERED(decorcolor); 7727 + colfg.green = TRUEGREEN(decorcolor); 7728 + colfg.blue = TRUEBLUE(decorcolor); 7729 + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor); 7730 + } else { 7731 + decor = dc.col[decorcolor]; 7732 + } 7733 + decor.color.alpha = 0xffff; 7734 + decor.pixel |= 0xff << 24; 7735 + 7736 + /* Float thickness, used as a base to compute other values. */ 7737 + float fthick = dc.font.height / 18.0; 7738 + /* Integer thickness in pixels. Must not be 0. */ 7739 + int thick = MAX(1, roundf(fthick)); 7740 + /* The default gap between the baseline and a single underline. */ 7741 + int gap = roundf(fthick * 2); 7742 + /* The total thickness of a double underline. */ 7743 + int doubleh = thick * 2 + ceilf(fthick * 0.5); 7744 + /* The total thickness of an undercurl. */ 7745 + int curlh = thick * 2 + roundf(fthick * 0.75); 7746 + 7747 + /* Render the underline before the glyphs. */ 7748 if (base.mode & ATTR_UNDERLINE) { 7749 - XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, 7750 - width, 1); 7751 + uint32_t style = tgetdecorstyle(&base); 7752 + int liney = winy + dc.font.ascent + gap; 7753 + /* Adjust liney to guarantee that a single underline fits. */ 7754 + liney -= MAX(0, liney + thick - (winy + win.ch)); 7755 + if (style == UNDERLINE_DOUBLE) { 7756 + liney -= MAX(0, liney + doubleh - (winy + win.ch)); 7757 + XftDrawRect(xw.draw, &decor, winx, liney, width, thick); 7758 + XftDrawRect(xw.draw, &decor, winx, 7759 + liney + doubleh - thick, width, thick); 7760 + } else if (style == UNDERLINE_DOTTED) { 7761 + xdrawunderdashed(xw.draw, &decor, winx, liney, width, 7762 + thick * 2, 0.5, thick); 7763 + } else if (style == UNDERLINE_DASHED) { 7764 + int wavelen = MAX(2, win.cw * 0.9); 7765 + xdrawunderdashed(xw.draw, &decor, winx, liney, width, 7766 + wavelen, 0.65, thick); 7767 + } else if (style == UNDERLINE_CURLY) { 7768 + liney -= MAX(0, liney + curlh - (winy + win.ch)); 7769 + xdrawundercurl(xw.draw, &decor, winx, liney, width, 7770 + curlh, thick); 7771 + } else { 7772 + XftDrawRect(xw.draw, &decor, winx, liney, width, thick); 7773 + } 7774 } 7775 7776 + /* Render the glyphs. */ 7777 + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 7778 + 7779 + /* Render strikethrough. Alway use the fg color. */ 7780 if (base.mode & ATTR_STRUCK) { 7781 - XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, 7782 - width, 1); 7783 + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, 7784 + width, thick); 7785 } 7786 7787 /* Reset clip to none. */ 7788 @@ -1517,6 +1707,11 @@ xdrawglyph(Glyph g, int x, int y) 7789 7790 numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 7791 xdrawglyphfontspecs(&spec, g, numspecs, x, y); 7792 + if (g.mode & ATTR_IMAGE) { 7793 + gr_start_drawing(xw.buf, win.cw, win.ch); 7794 + xdrawoneimagecell(g, x, y); 7795 + gr_finish_drawing(xw.buf); 7796 + } 7797 } 7798 7799 void 7800 @@ -1532,6 +1727,10 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 7801 if (IS_SET(MODE_HIDE)) 7802 return; 7803 7804 + // If it's an image, just draw a ballot box for simplicity. 7805 + if (g.mode & ATTR_IMAGE) 7806 + g.u = 0x2610; 7807 + 7808 /* 7809 * Select the right color for the right mode. 7810 */ 7811 @@ -1572,39 +1771,167 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 7812 case 3: /* Blinking Underline */ 7813 case 4: /* Steady Underline */ 7814 XftDrawRect(xw.draw, &drawcol, 7815 - borderpx + cx * win.cw, 7816 - borderpx + (cy + 1) * win.ch - \ 7817 + win.hborderpx + cx * win.cw, 7818 + win.vborderpx + (cy + 1) * win.ch - \ 7819 cursorthickness, 7820 win.cw, cursorthickness); 7821 break; 7822 case 5: /* Blinking bar */ 7823 case 6: /* Steady bar */ 7824 XftDrawRect(xw.draw, &drawcol, 7825 - borderpx + cx * win.cw, 7826 - borderpx + cy * win.ch, 7827 + win.hborderpx + cx * win.cw, 7828 + win.vborderpx + cy * win.ch, 7829 cursorthickness, win.ch); 7830 break; 7831 } 7832 } else { 7833 XftDrawRect(xw.draw, &drawcol, 7834 - borderpx + cx * win.cw, 7835 - borderpx + cy * win.ch, 7836 + win.hborderpx + cx * win.cw, 7837 + win.vborderpx + cy * win.ch, 7838 win.cw - 1, 1); 7839 XftDrawRect(xw.draw, &drawcol, 7840 - borderpx + cx * win.cw, 7841 - borderpx + cy * win.ch, 7842 + win.hborderpx + cx * win.cw, 7843 + win.vborderpx + cy * win.ch, 7844 1, win.ch - 1); 7845 XftDrawRect(xw.draw, &drawcol, 7846 - borderpx + (cx + 1) * win.cw - 1, 7847 - borderpx + cy * win.ch, 7848 + win.hborderpx + (cx + 1) * win.cw - 1, 7849 + win.vborderpx + cy * win.ch, 7850 1, win.ch - 1); 7851 XftDrawRect(xw.draw, &drawcol, 7852 - borderpx + cx * win.cw, 7853 - borderpx + (cy + 1) * win.ch - 1, 7854 + win.hborderpx + cx * win.cw, 7855 + win.vborderpx + (cy + 1) * win.ch - 1, 7856 win.cw, 1); 7857 } 7858 } 7859 7860 +/* Draw (or queue for drawing) image cells between columns x1 and x2 assuming 7861 + * that they have the same attributes (and thus the same lower 24 bits of the 7862 + * image ID and the same placement ID). */ 7863 +void 7864 +xdrawimages(Glyph base, Line line, int x1, int y1, int x2) { 7865 + int y_pix = win.vborderpx + y1 * win.ch; 7866 + uint32_t image_id_24bits = base.fg & 0xFFFFFF; 7867 + uint32_t placement_id = tgetimgplacementid(&base); 7868 + // Columns and rows are 1-based, 0 means unspecified. 7869 + int last_col = 0; 7870 + int last_row = 0; 7871 + int last_start_col = 0; 7872 + int last_start_x = x1; 7873 + // The most significant byte is also 1-base, subtract 1 before use. 7874 + uint32_t last_id_4thbyteplus1 = 0; 7875 + // We may need to inherit row/column/4th byte from the previous cell. 7876 + Glyph *prev = &line[x1 - 1]; 7877 + if (x1 > 0 && (prev->mode & ATTR_IMAGE) && 7878 + (prev->fg & 0xFFFFFF) == image_id_24bits && 7879 + prev->decor == base.decor) { 7880 + last_row = tgetimgrow(prev); 7881 + last_col = tgetimgcol(prev); 7882 + last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev); 7883 + last_start_col = last_col + 1; 7884 + } 7885 + for (int x = x1; x < x2; ++x) { 7886 + Glyph *g = &line[x]; 7887 + uint32_t cur_row = tgetimgrow(g); 7888 + uint32_t cur_col = tgetimgcol(g); 7889 + uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g); 7890 + uint32_t num_diacritics = tgetimgdiacriticcount(g); 7891 + // If the row is not specified, assume it's the same as the row 7892 + // of the previous cell. Note that `cur_row` may contain a 7893 + // value imputed earlier, which will be preserved if `last_row` 7894 + // is zero (i.e. we don't know the row of the previous cell). 7895 + if (last_row && (num_diacritics == 0 || !cur_row)) 7896 + cur_row = last_row; 7897 + // If the column is not specified and the row is the same as the 7898 + // row of the previous cell, then assume that the column is the 7899 + // next one. 7900 + if (last_col && (num_diacritics <= 1 || !cur_col) && 7901 + cur_row == last_row) 7902 + cur_col = last_col + 1; 7903 + // If the additional id byte is not specified and the 7904 + // coordinates are consecutive, assume the byte is also the 7905 + // same. 7906 + if (last_id_4thbyteplus1 && 7907 + (num_diacritics <= 2 || !cur_id_4thbyteplus1) && 7908 + cur_row == last_row && cur_col == last_col + 1) 7909 + cur_id_4thbyteplus1 = last_id_4thbyteplus1; 7910 + // If we couldn't infer row and column, start from the top left 7911 + // corner. 7912 + if (cur_row == 0) 7913 + cur_row = 1; 7914 + if (cur_col == 0) 7915 + cur_col = 1; 7916 + // If this cell breaks a contiguous stripe of image cells, draw 7917 + // that line and start a new one. 7918 + if (cur_col != last_col + 1 || cur_row != last_row || 7919 + cur_id_4thbyteplus1 != last_id_4thbyteplus1) { 7920 + uint32_t image_id = image_id_24bits; 7921 + if (last_id_4thbyteplus1) 7922 + image_id |= (last_id_4thbyteplus1 - 1) << 24; 7923 + if (last_row != 0) { 7924 + int x_pix = 7925 + win.hborderpx + last_start_x * win.cw; 7926 + gr_append_imagerect( 7927 + xw.buf, image_id, placement_id, 7928 + last_start_col - 1, last_col, 7929 + last_row - 1, last_row, last_start_x, 7930 + y1, x_pix, y_pix, win.cw, win.ch, 7931 + base.mode & ATTR_REVERSE); 7932 + } 7933 + last_start_col = cur_col; 7934 + last_start_x = x; 7935 + } 7936 + last_row = cur_row; 7937 + last_col = cur_col; 7938 + last_id_4thbyteplus1 = cur_id_4thbyteplus1; 7939 + // Populate the missing glyph data to enable inheritance between 7940 + // runs and support the naive implementation of tgetimgid. 7941 + if (!tgetimgrow(g)) 7942 + tsetimgrow(g, cur_row); 7943 + // We cannot save this information if there are > 511 cols. 7944 + if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0) 7945 + tsetimgcol(g, cur_col); 7946 + if (!tgetimgid4thbyteplus1(g)) 7947 + tsetimg4thbyteplus1(g, cur_id_4thbyteplus1); 7948 + } 7949 + uint32_t image_id = image_id_24bits; 7950 + if (last_id_4thbyteplus1) 7951 + image_id |= (last_id_4thbyteplus1 - 1) << 24; 7952 + // Draw the last contiguous stripe. 7953 + if (last_row != 0) { 7954 + int x_pix = win.hborderpx + last_start_x * win.cw; 7955 + gr_append_imagerect(xw.buf, image_id, placement_id, 7956 + last_start_col - 1, last_col, last_row - 1, 7957 + last_row, last_start_x, y1, x_pix, y_pix, 7958 + win.cw, win.ch, base.mode & ATTR_REVERSE); 7959 + } 7960 +} 7961 + 7962 +/* Draw just one image cell without inheriting attributes from the left. */ 7963 +void xdrawoneimagecell(Glyph g, int x, int y) { 7964 + if (!(g.mode & ATTR_IMAGE)) 7965 + return; 7966 + int x_pix = win.hborderpx + x * win.cw; 7967 + int y_pix = win.vborderpx + y * win.ch; 7968 + uint32_t row = tgetimgrow(&g) - 1; 7969 + uint32_t col = tgetimgcol(&g) - 1; 7970 + uint32_t placement_id = tgetimgplacementid(&g); 7971 + uint32_t image_id = tgetimgid(&g); 7972 + gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row, 7973 + row + 1, x, y, x_pix, y_pix, win.cw, win.ch, 7974 + g.mode & ATTR_REVERSE); 7975 +} 7976 + 7977 +/* Prepare for image drawing. */ 7978 +void xstartimagedraw(int *dirty, int rows) { 7979 + gr_start_drawing(xw.buf, win.cw, win.ch); 7980 + gr_mark_dirty_animations(dirty, rows); 7981 +} 7982 + 7983 +/* Draw all queued image cells. */ 7984 +void xfinishimagedraw() { 7985 + gr_finish_drawing(xw.buf); 7986 +} 7987 + 7988 void 7989 xsetenv(void) 7990 { 7991 @@ -1671,6 +1998,8 @@ xdrawline(Line line, int x1, int y1, int x2) 7992 new.mode ^= ATTR_REVERSE; 7993 if (i > 0 && ATTRCMP(base, new)) { 7994 xdrawglyphfontspecs(specs, base, i, ox, y1); 7995 + if (base.mode & ATTR_IMAGE) 7996 + xdrawimages(base, line, ox, y1, x); 7997 specs += i; 7998 numspecs -= i; 7999 i = 0; 8000 @@ -1683,6 +2012,8 @@ xdrawline(Line line, int x1, int y1, int x2) 8001 } 8002 if (i > 0) 8003 xdrawglyphfontspecs(specs, base, i, ox, y1); 8004 + if (i > 0 && base.mode & ATTR_IMAGE) 8005 + xdrawimages(base, line, ox, y1, x); 8006 } 8007 8008 void 8009 @@ -1907,6 +2238,7 @@ cmessage(XEvent *e) 8010 } 8011 } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 8012 ttyhangup(); 8013 + gr_deinit(); 8014 exit(0); 8015 } 8016 } 8017 @@ -1957,6 +2289,13 @@ run(void) 8018 if (XPending(xw.dpy)) 8019 timeout = 0; /* existing events might not set xfd */ 8020 8021 + /* Decrease the timeout if there are active animations. */ 8022 + if (graphics_next_redraw_delay != INT_MAX && 8023 + IS_SET(MODE_VISIBLE)) 8024 + timeout = timeout < 0 ? graphics_next_redraw_delay 8025 + : MIN(timeout, 8026 + graphics_next_redraw_delay); 8027 + 8028 seltv.tv_sec = timeout / 1E3; 8029 seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); 8030 tv = timeout >= 0 ? &seltv : NULL; 8031 -- 8032 2.43.0 8033