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