| st-kitty-graphics-20240922-a0274bc.diff - sites - public wiki contents of suckl… | |
| git clone git://git.suckless.org/sites | |
| Log | |
| Files | |
| Refs | |
| --- | |
| st-kitty-graphics-20240922-a0274bc.diff (235999B) | |
| --- | |
| 1 From 25d9cca81ce48141de7f6a823b006dddaafd9de8 Mon Sep 17 00:00:00 2001 | |
| 2 From: Sergei Grechanik <[email protected]> | |
| 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:autohi… | |
| 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 perce… | |
| 74 + * 50 means center, 0 means flush left/top, 100 means flush right/botto… | |
| 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 fr… | |
| 113 +/// of image removal. | |
| 114 +double graphics_excess_tolerance_ratio = 0.05; | |
| 115 +/// The minimum delay between redraws caused by animations, in millisec… | |
| 116 +unsigned graphics_animation_min_delay = 20; | |
| 117 + | |
| 118 /* | |
| 119 * Force mouse select/shortcuts while mask is active (when MODE_MOUSE i… | |
| 120 * Note that if you want to use ShiftMask with selmasks, set this to an… | |
| 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 … | |
| 135 + { TERMMOD, Button3, previewimage, {.s = "feh"} }, | |
| 136 + { TERMMOD, Button2, showimageinfo, {}, … | |
| 137 { XK_ANY_MOD, Button2, selpaste, {.i = 0}, … | |
| 138 { ShiftMask, Button4, ttysend, {.s = "\033[5;… | |
| 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 argumen… | |
| 150 { XK_ANY_MOD, XK_Break, sendbreak, {.i = … | |
| 151 @@ -201,6 +233,10 @@ static Shortcut shortcuts[] = { | |
| 152 { TERMMOD, XK_Y, selpaste, {.i = … | |
| 153 { ShiftMask, XK_Insert, selpaste, {.i = … | |
| 154 { TERMMOD, XK_Num_Lock, numlock, {.i = … | |
| 155 + { TERMMOD, XK_F1, togglegrdebug, {.i = … | |
| 156 + { TERMMOD, XK_F6, dumpgrstate, {.i = … | |
| 157 + { TERMMOD, XK_F7, unloadimages, {.i = … | |
| 158 + { TERMMOD, XK_F8, toggleimages, {.i = … | |
| 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 <[email protected]> | |
| 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 di… | |
| 246 +/// and absolute times (as milliseconds since an arbitrary point in tim… | |
| 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 aspec… | |
| 253 + SCALE_MODE_FILL = 1, | |
| 254 + /// Preserve aspect ratio and fit to width or to height so that… | |
| 255 + /// whole image is visible. | |
| 256 + SCALE_MODE_CONTAIN = 2, | |
| 257 + /// Do not scale. The image may be cropped if the box is too sm… | |
| 258 + SCALE_MODE_NONE = 3, | |
| 259 + /// Do not scale, unless the box is too small, in which case th… | |
| 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 do… | |
| 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 cach… | |
| 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… | |
| 316 +// by actions 'a=t', 'a=T`. Each image has an id (image id aka clie… | |
| 317 +// specified by 'i='). An image may have multiple frames (ImageFram… | |
| 318 +// placements (ImagePlacement). | |
| 319 +// | |
| 320 +// - ImageFrame: represents a single frame of an image, usually creat… | |
| 321 +// the action 'a=f' (and the first frame is created with the image … | |
| 322 +// Each frame has an index and also: | |
| 323 +// - a file containing the frame data (considered to be "on disk", … | |
| 324 +// it's probably in tmpfs), | |
| 325 +// - an imlib object containing the fully composed frame (i.e. the … | |
| 326 +// data from the file composed onto the background frame or color… | |
| 327 +// not ready for display yet, because it needs to be scaled and u… | |
| 328 +// to the X server. | |
| 329 +// | |
| 330 +// - ImagePlacement: represents a placement of an image, created by '… | |
| 331 +// 'a=T'. Each placement has an id (placement id, specified by 'p='… | |
| 332 +// each placement has an array of pixmaps: one for each frame of th… | |
| 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 Im… | |
| 336 +// objects (allocated on the heap). | |
| 337 +// | |
| 338 +// Placements are stored in the `placements` hash table of each Image o… | |
| 339 +// mapping placement ids to ImagePlacement objects (also allocated on t… | |
| 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 store… | |
| 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 ini… | |
| 359 + int index; | |
| 360 + /// The last time when the frame was displayed or otherwise tou… | |
| 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 in… | |
| 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 '… | |
| 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 … | |
| 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 i… | |
| 387 + char blend; | |
| 388 + /// The file corresponding to the on-disk cache, used when uplo… | |
| 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 … | |
| 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 nonzer… | |
| 399 + uint32_t image_id; | |
| 400 + /// The client id specified in the query command (`a=q`). This … | |
| 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 t… | |
| 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 tou… | |
| 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 wh… | |
| 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 cu… | |
| 421 + /// frame (in ms since initialization). | |
| 422 + Milliseconds current_frame_time; | |
| 423 + /// The absolute time of the last redraw (in ms since initializ… | |
| 424 + /// Used to check whether it's the first time we draw the image… | |
| 425 + /// current redraw cycle. | |
| 426 + Milliseconds last_redraw; | |
| 427 + /// The absolute time of the next redraw (in ms since initializ… | |
| 428 + /// 0 means no redraw is scheduled. | |
| 429 + Milliseconds next_redraw; | |
| 430 + /// The unscaled pixel width and height of the image. Usually i… | |
| 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 c… | |
| 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… | |
| 452 + Milliseconds atime; | |
| 453 + /// The 1-based index of the protected pixmap. We protect a pix… | |
| 454 + /// gr_load_pixmap to avoid unloading it right after it was loa… | |
| 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 imag… | |
| 465 + int src_pix_width, src_pix_height; | |
| 466 + /// The image appropriately scaled and uploaded to the X server… | |
| 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 res… | |
| 473 + uint16_t scaled_cw, scaled_ch; | |
| 474 + /// If true, do not move the cursor when displaying this placem… | |
| 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 a… | |
| 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); … | |
| 504 + ImageFrame *framevar = \ | |
| 505 + __i == 0 ? &(image).first_frame \ | |
| 506 + : &kv_A((image).frames_beyond_the_first, __i - … | |
| 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_fir… | |
| 518 + Pixmap pixmapvar = \ | |
| 519 + __i == 0 ? (placement).first_pixmap \ | |
| 520 + : kv_A((placement).pixmaps_beyond_the_first, __… | |
| 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 … | |
| 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… | |
| 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 animat… | |
| 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__); } wh… | |
| 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… | |
| 622 +/// `gr_last_uploaded_frame_index` instead since the last frame may be … | |
| 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 i… | |
| 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 f… | |
| 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_SU… | |
| 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… | |
| 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 … | |
| 665 + return 0; | |
| 666 +} | |
| 667 + | |
| 668 +/// Sets the pixmap for the frame with the given index. The index is 1-… | |
| 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… | |
| 680 + for (size_t i = old_size; i < index - 1; i++) | |
| 681 + kv_A(placement->pixmaps_beyond_the_first, i) = … | |
| 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 can… | |
| 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 N… | |
| 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_n… | |
| 712 + img->image_id); | |
| 713 + return newest_img; | |
| 714 +} | |
| 715 + | |
| 716 +/// Finds the placement corresponding to the id. If the placement id is… | |
| 717 +/// returns some default placement. | |
| 718 +static ImagePlacement *gr_find_placement(Image *img, uint32_t placement… | |
| 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_plac… | |
| 726 + if (dflt) | |
| 727 + return dflt; | |
| 728 + // If there is no default placement, return the first o… | |
| 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_i… | |
| 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` shoul… | |
| 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 no… | |
| 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_he… | |
| 764 +} | |
| 765 + | |
| 766 +/// Returns the (estimation) of the RAM size used by a single frame pix… | |
| 767 +static unsigned gr_placement_single_frame_ram_size(ImagePlacement *plac… | |
| 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 rig… | |
| 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 obje… | |
| 785 +/// If the on-disk file of the frame is preserved, it can be reloaded l… | |
| 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 / 102… | |
| 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 p… | |
| 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(pla… | |
| 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 /… | |
| 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… | |
| 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-r… | |
| 859 +/// object (if it exists) is not deleted, placements are not unloaded e… | |
| 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 / 10… | |
| 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… | |
| 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… | |
| 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… | |
| 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 re… | |
| 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 alr… | |
| 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) ==… | |
| 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 fi… | |
| 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_fir… | |
| 1015 + memset(frame, 0, sizeof(ImageFrame)); | |
| 1016 + frame->index = kv_size(img->frames_beyond_the_first) + … | |
| 1017 + } | |
| 1018 + frame->image = img; | |
| 1019 + gr_touch_frame(frame); | |
| 1020 + GR_LOG("Appending frame %d to image %u\n", frame->index, img->i… | |
| 1021 + return frame; | |
| 1022 +} | |
| 1023 + | |
| 1024 +/// Creates a new placement with the given id. If a placement with that… | |
| 1025 +/// already exists, it is deleted first. If the provided id is 0, gener… | |
| 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(im… | |
| 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'… | |
| 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 imag… | |
| 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… | |
| 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_pi… | |
| 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 … | |
| 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,… | |
| 1106 + // logical thing is to find the minimum size of the | |
| 1107 + // non-specified dimension that allows the image to fit… | |
| 1108 + // specified dimension. | |
| 1109 + if (placement->cols == 0) { | |
| 1110 + placement->cols = ceil_div( | |
| 1111 + placement->src_pix_width * placement->r… | |
| 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 * curr… | |
| 1120 + placement->src_pix_width * cur… | |
| 1121 + return; | |
| 1122 + } | |
| 1123 + } else { | |
| 1124 + // Otherwise we stretch the image or preserve the origi… | |
| 1125 + // In both cases we compute the best number of columns … | |
| 1126 + // pixel size and cell size. | |
| 1127 + // TODO: In the case of stretching it's not the most lo… | |
| 1128 + // thing to do, may need to revisit in the future. | |
| 1129 + // Currently we switch to SCALE_MODE_CONTAIN when… | |
| 1130 + // of the dimensions is specified, so this case s… | |
| 1131 + // happen in practice. | |
| 1132 + if (!placement->cols) | |
| 1133 + placement->cols = | |
| 1134 + ceil_div(placement->src_pix_width, curr… | |
| 1135 + if (!placement->rows) | |
| 1136 + placement->rows = | |
| 1137 + ceil_div(placement->src_pix_height, cur… | |
| 1138 + } | |
| 1139 +} | |
| 1140 + | |
| 1141 +/// Adjusts the current frame index if enough time has passed since the… | |
| 1142 +/// of the current frame. Also computes the time of the next redraw of … | |
| 1143 +/// image (`img->next_redraw`). The current time is passed as an argume… | |
| 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 change… | |
| 1157 + img->next_redraw = 0; | |
| 1158 + return; | |
| 1159 + } | |
| 1160 + int last_uploaded_frame_index = gr_last_uploaded_frame_index(im… | |
| 1161 + // If we are loading and we reached the last frame, show the la… | |
| 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 change… | |
| 1165 + // frames are added). | |
| 1166 + img->next_redraw = 0; | |
| 1167 + return; | |
| 1168 + } | |
| 1169 + | |
| 1170 + // Check how many milliseconds passed since the current frame w… | |
| 1171 + int passed_ms = now - img->current_frame_time; | |
| 1172 + // If the animation is looping and too much time has passes, we… | |
| 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_fram… | |
| 1183 + if (!frame) { | |
| 1184 + // The frame doesn't exist, go to the first fra… | |
| 1185 + img->current_frame = 1; | |
| 1186 + img->current_frame_time = now; | |
| 1187 + img->next_redraw = now + MAX(1, img->first_fram… | |
| 1188 + return; | |
| 1189 + } | |
| 1190 + if (frame->gap >= 0 && passed_ms < frame->gap) { | |
| 1191 + // Not enough time has passed, we are still in … | |
| 1192 + // frame, and it's not a gapless frame. | |
| 1193 + img->next_redraw = | |
| 1194 + img->current_frame_time + MAX(1, frame-… | |
| 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 loa… | |
| 1201 + // remain on it. | |
| 1202 + if (img->animation_state == ANIMATION_STATE_LOA… | |
| 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 re… | |
| 1215 + // next frame yet. This may happen if too much … | |
| 1216 + // passed since the last redraw or all the fram… | |
| 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… | |
| 1225 + return; | |
| 1226 + } | |
| 1227 + // Adjust the start time of the frame. The next redraw … | |
| 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_at… | |
| 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 sc… | |
| 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-… | |
| 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 access… | |
| 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 *fram… | |
| 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 anim… | |
| 1369 + // Score it above `now` to prefer unloading non-active … | |
| 1370 + // Randomize the score because it's not very clear in w… | |
| 1371 + // order we want to unload them: reloading a frame may … | |
| 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 ani… | |
| 1392 + // Score it above `now` to prefer unloading non-active … | |
| 1393 + // Also assign higher scores to frames that are closer … | |
| 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_f… | |
| 1401 + // If the pixmap is much larger than the imlib image, p… | |
| 1402 + // unload the pixmap by adding up to -1000 to the score… | |
| 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)… | |
| 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, fra… | |
| 1426 + int frameidx = frame->index; | |
| 1427 + kh_foreach_value(img->placements, placement, { | |
| 1428 + if (!gr_get_frame_pixmap(placement, fra… | |
| 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_rat… | |
| 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_placem… | |
| 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_cou… | |
| 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 corr… | |
| 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_cac… | |
| 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_si… | |
| 1498 + changed = 1; | |
| 1499 + int frames_begin = 0; | |
| 1500 + GR_LOG("Too much ram: %ld KiB\n", images_ram_size / 102… | |
| 1501 + objects_sorted = gr_get_unloadable_objects_sorted_by_sc… | |
| 1502 + for (int i = 0; i < kv_size(objects_sorted); i++) { | |
| 1503 + if (images_ram_size <= graphics_max_total_ram_s… | |
| 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 … | |
| 1539 +/// image data `to`. The format may be 24 (RGB) or 32 (RGBA), and it's … | |
| 1540 +/// to imlib2's representation, which is 0xAARRGGBB (having BGRA memory… | |
| 1541 +/// on little-endian architectures). | |
| 1542 +static inline void gr_copy_pixels(DATA32 *to, unsigned char *from, int … | |
| 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 | 0xFF0000… | |
| 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 *fil… | |
| 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, fi… | |
| 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_p… | |
| 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, tr… | |
| 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.av… | |
| 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, … | |
| 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,… | |
| 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) / pi… | |
| 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_pixel… | |
| 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… | |
| 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_o… | |
| 1657 + copied_bytes; | |
| 1658 + memmove(decompressed_chunk, | |
| 1659 + decompressed_chunk + copied_bytes, left… | |
| 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 reache… | |
| 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),… | |
| 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_h… | |
| 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: … | |
| 1687 + frame->image->image_id, frame->index, total_pix… | |
| 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 … | |
| 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,… | |
| 1722 + total_pixel… | |
| 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 iml… | |
| 1737 +/// is fully composed on top of the background frame. If the frame is a… | |
| 1738 +/// loaded, does nothing. Loading may fail, in which case the status of… | |
| 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 th… | |
| 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 fr… | |
| 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\… | |
| 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 recu… | |
| 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… | |
| 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, filena… | |
| 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: %… | |
| 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 t… | |
| 1821 + // information about the original image size, unless it's alrea… | |
| 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… | |
| 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_widt… | |
| 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… | |
| 1880 + frame->image->image_id, frame->index, | |
| 1881 + images_ram_size / 1024, gr_frame_current_ram_size(frame)… | |
| 1882 +} | |
| 1883 + | |
| 1884 +/// Premultiplies the alpha channel of the image data. The data is an a… | |
| 1885 +/// pixels such that each pixel is a 32-bit integer in the format 0xAAR… | |
| 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 / 2… | |
| 1895 + unsigned char r = ((pixel >> 16) & 0xFF) * a / … | |
| 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 co… | |
| 1902 +/// image data correctly scaled and fit to the box defined by the numbe… | |
| 1903 +/// rows/columns of the image placement and the provided cell dimension… | |
| 1904 +/// pixels. If the placement is already loaded, it will be reloaded onl… | |
| 1905 +/// cell dimensions have changed. | |
| 1906 +Pixmap gr_load_pixmap(ImagePlacement *placement, int frameidx, int cw, … | |
| 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 immed… | |
| 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_siz… | |
| 1949 + fprintf(stderr, | |
| 1950 + "error: placement %u/%u would be too big to loa… | |
| 1951 + "%d x 4 > %u\n", | |
| 1952 + img->image_id, placement->placement_id, scaled_… | |
| 1953 + scaled_h, graphics_max_single_image_ram_size); | |
| 1954 + return 0; | |
| 1955 + } | |
| 1956 + Imlib_Image scaled_image = imlib_create_image(scaled_w, scaled_… | |
| 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 ima… | |
| 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 backgroun… | |
| 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, sr… | |
| 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_smal… | |
| 1992 + imlib_blend_image_onto_image(frame->imlib_object, 1, sr… | |
| 1993 + src_y, src_w, src_h, 0, 0,… | |
| 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,… | |
| 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, sr… | |
| 2020 + src_y, src_w, src_h, dest_… | |
| 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, &vis… | |
| 2038 + XImage *ximage = XCreateImage(disp, visinfo.visual, 32, ZPixmap… | |
| 2039 + (char *)data, scaled_w, scaled_h,… | |
| 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 … | |
| 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 (+… | |
| 2057 + "KiB)\n", | |
| 2058 + frame->image->image_id, placement->placement_id, frame->… | |
| 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 m… | |
| 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_di… | |
| 2078 + if (!mkdtemp(cache_dir)) { | |
| 2079 + fprintf(stderr, | |
| 2080 + "error: could not create temporary dir from tem… | |
| 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 ne… | |
| 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 … | |
| 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 f… | |
| 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", second… | |
| 2156 + else if (seconds < 60) | |
| 2157 + snprintf(result, sizeof(result), "%d sec ago", (int)sec… | |
| 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 a… | |
| 2163 + (int)(seconds / 3600), (int)(seconds) % 3600 /… | |
| 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_durat… | |
| 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_… | |
| 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_pi… | |
| 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 … | |
| 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` spa… | |
| 2252 +static void gr_dump_placement_info(FILE *file, ImagePlacement *placemen… | |
| 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_i… | |
| 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_mod… | |
| 2264 + fprintf_ind(file, ind, "size: %u cols x %u rows\n", placement->… | |
| 2265 + placement->rows); | |
| 2266 + fprintf_ind(file, ind, "cell size: %ux%u\n", placement->scaled_… | |
| 2267 + placement->scaled_ch); | |
| 2268 + fprintf_ind(file, ind, "ram per frame: %u KiB\n", | |
| 2269 + gr_placement_single_frame_ram_size(placement) / 102… | |
| 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` spac… | |
| 2275 +static void gr_dump_placement_pixmaps(FILE *file, ImagePlacement *place… | |
| 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", frameid… | |
| 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 ====… | |
| 2292 + fprintf_ind(file, ind, | |
| 2293 + "sizeof(Image) = %lu sizeof(ImageFrame) = %lu " | |
| 2294 + "sizeof(ImagePlacement) = %lu\n", | |
| 2295 + sizeof(Image), sizeof(ImageFrame), sizeof(ImagePlacemen… | |
| 2296 + fprintf_ind(file, ind, "Image count: %u\n", kh_size(images)); | |
| 2297 + fprintf_ind(file, ind, "Placement count: %u\n", total_placement… | |
| 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… | |
| 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_c… | |
| 2332 + } | |
| 2333 + if (img->total_duration != total_duration_computed) { | |
| 2334 + fprintf_ind(file, ind, | |
| 2335 + " ERROR: total_duration is %d, but c… | |
| 2336 + "value is %d\n", | |
| 2337 + img->total_duration, total_duration_com… | |
| 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… | |
| 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 va… | |
| 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 v… | |
| 2361 + "is %ld\n", | |
| 2362 + images_disk_size, images_disk_size_computed); | |
| 2363 + } | |
| 2364 + fprintf_ind(file, ind, "=======================================… | |
| 2365 +} | |
| 2366 + | |
| 2367 +/// Executes `command` with the name of the file corresponding to `imag… | |
| 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 t… | |
| 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_SIZ… | |
| 2379 + if (frame->disk_size == 0) { | |
| 2380 + len = snprintf(command, 255, | |
| 2381 + "xmessage 'Image with id=%u is n… | |
| 2382 + "fully copied to %s'", | |
| 2383 + image_id, sanitized_filename(fil… | |
| 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", comman… | |
| 2395 + snprintf(command, 255, "xmessage 'error: command too lo… | |
| 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 tempor… | |
| 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_… | |
| 2409 + char *st_executable) { | |
| 2410 + char filename[MAX_FILENAME_SIZE]; | |
| 2411 + snprintf(filename, sizeof(filename), "%s/info-%u", cache_dir, i… | |
| 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, pla… | |
| 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_coun… | |
| 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… | |
| 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 c… | |
| 2452 +static void gr_displayinfo(Drawable buf, ImageRect *rect, int col1, int… | |
| 2453 + const char *message) { | |
| 2454 + int w_pix = (rect->img_end_col - rect->img_start_col) * rect->c… | |
| 2455 + int h_pix = (rect->img_end_row - rect->img_start_row) * rect->c… | |
| 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)",… | |
| 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)", me… | |
| 2466 + rect->image_id, rect->img_start_col, rect->img… | |
| 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->c… | |
| 2480 + int h_pix = (rect->img_end_row - rect->img_start_row) * rect->c… | |
| 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_… | |
| 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_redra… | |
| 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->place… | |
| 2512 + // If the image does not exist or image display is switched off… | |
| 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, 0xFFFFFFF… | |
| 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 re… | |
| 2525 + // cycle. Update the frame index we are going to displa… | |
| 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; ++ro… | |
| 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, r… | |
| 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, 0xFFFFFFF… | |
| 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->c… | |
| 2557 + int height = (rect->img_end_row - rect->img_start_row) * rect->… | |
| 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 app… | |
| 2572 + // inverting the pixmap is not entirely correct, because the pi… | |
| 2573 + // premultiplied. But the result is good enough to visually ind… | |
| 2574 + // selection. | |
| 2575 + if (rect->reverse) { | |
| 2576 + unsigned pixmap_w = | |
| 2577 + (unsigned)placement->cols * placement->scaled_c… | |
| 2578 + unsigned pixmap_h = | |
| 2579 + (unsigned)placement->rows * placement->scaled_c… | |
| 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 … | |
| 2597 + // the alpha channel of the image because the naive inversion a… | |
| 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, widt… | |
| 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(Image… | |
| 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 l… | |
| 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 th… | |
| 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, … | |
| 2654 + if (row_next_redraw > 0) { | |
| 2655 + int delay = MAX(graphics_animation_min_delay, | |
| 2656 + row_next_redraw - drawing_end_t… | |
| 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_tim… | |
| 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_B… | |
| 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 … | |
| 2678 + "%d cell %dx%d delay %d", | |
| 2679 + debug_mode_str, milliseconds, images_ram_size … | |
| 2680 + images_disk_size / 1024, kh_size(images), curr… | |
| 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 pixma… | |
| 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 place… | |
| 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 plac… | |
| 2701 + int img_start_col, int img_end_col, int img_st… | |
| 2702 + int img_end_row, int x_col, int y_row, int x_p… | |
| 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, … | |
| 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 != c… | |
| 2741 + rect->ch != ch || rect->reverse != reverse) | |
| 2742 + continue; | |
| 2743 + // We only support the case when the new stripe is adde… | |
| 2744 + // bottom of an existing rectangle and they are perfect… | |
| 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, an… | |
| 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 the… | |
| 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_tim… | |
| 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 wi… | |
| 2807 + /// imlib2. If 'f=0', will try to load with imlib2, then fallba… | |
| 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=… | |
| 2817 + /// When 'a=f', this is the size of the frame rectangle when co… | |
| 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 … | |
| 2837 + /// transmission and not the first one for this image. Populate… | |
| 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 placehol… | |
| 2843 + int virtual; | |
| 2844 + /// 'C=', if true, do not move the cursor when displaying this … | |
| 2845 + /// (non-virtual placements only). | |
| 2846 + char do_not_move_cursor; | |
| 2847 + // ------------------------------------------------------------… | |
| 2848 + // Animation-related fields. Their keys often overlap with keys… | |
| 2849 + // commands, so these make sense only if the action is 'a=f' (f… | |
| 2850 + // transmission) or 'a=a' (animation control). | |
| 2851 + // | |
| 2852 + // 'x=' and 'y=', the relative position of the frame image when… | |
| 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 … | |
| 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… | |
| 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, neg… | |
| 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 tim… | |
| 2874 + /// animation will loop. 1 to loop infinitely, N to loop N-1 ti… | |
| 2875 + int loops; | |
| 2876 +} GraphicsCommand; | |
| 2877 + | |
| 2878 +/// Replaces all non-printed characters in `str` with '?' and truncates… | |
| 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, … | |
| 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_resu… | |
| 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 pr… | |
| 2909 + // stderr. | |
| 2910 + fprintf(stderr, | |
| 2911 + "error: No image id or image number or placemen… | |
| 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… | |
| 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, "… | |
| 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->qu… | |
| 2991 + : frame->image->im… | |
| 2992 + fprintf(stderr, "%s id=%u\n", errmsg, id); | |
| 2993 + if (frame->quiet < 2) | |
| 2994 + gr_createresponse(id, frame->image->image_numbe… | |
| 2995 + frame->image->initial_placeme… | |
| 2996 + errmsg); | |
| 2997 + } | |
| 2998 +} | |
| 2999 + | |
| 3000 +/// Loads an image and creates a success/failure response. Returns `fra… | |
| 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 imag… | |
| 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 co… | |
| 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 im… | |
| 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 im… | |
| 3036 + "doesn't match the expected size %u", | |
| 3037 + frame->disk_size, frame->expected_si… | |
| 3038 + break; | |
| 3039 + }; | |
| 3040 +} | |
| 3041 + | |
| 3042 +/// Displays a non-virtual placement. This functions records the inform… | |
| 3043 +/// `graphics_command_result`, the placeholder itself is created by the… | |
| 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_S… | |
| 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… | |
| 3053 + // by the terminal. | |
| 3054 + graphics_command_result.create_placeholder = 1; | |
| 3055 + graphics_command_result.placeholder.image_id = placement->image… | |
| 3056 + graphics_command_result.placeholder.placement_id = placement->p… | |
| 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 specifi… | |
| 3076 +/// the image id and frame index we are currently uploading. | |
| 3077 +static void gr_append_data(ImageFrame *frame, const char *payload, int … | |
| 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_in… | |
| 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 f… | |
| 3095 + "image to append dat… | |
| 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, da… | |
| 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 … | |
| 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_SIZ… | |
| 3128 + FILE *file = fopen(filename, frame->disk_size ? "a" : "… | |
| 3129 + if (!file) { | |
| 3130 + frame->status = STATUS_UPLOADING_ERROR; | |
| 3131 + frame->uploading_failure = ERROR_CANNOT_OPEN_CA… | |
| 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 do… | |
| 3163 + // match the expected size. | |
| 3164 + frame->status = STATUS_UPLOADING_ERROR; | |
| 3165 + frame->uploading_failure = ERROR_UNEXPECTED_SIZ… | |
| 3166 + gr_reportuploaderror(frame); | |
| 3167 + } else { | |
| 3168 + // Make sure to redraw all existing image insta… | |
| 3169 + gr_schedule_image_redraw(frame->image); | |
| 3170 + // Try to load the image into ram and report th… | |
| 3171 + frame = gr_loadimage_and_report(frame); | |
| 3172 + // If there is a non-virtual image placement, w… | |
| 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, place… | |
| 3178 + gr_display_nonvirtual_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 … | |
| 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 imag… | |
| 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 … | |
| 3206 +/// command's action) and initializes its parameters from the command. | |
| 3207 +static ImageFrame *gr_new_image_or_frame_from_command(GraphicsCommand *… | |
| 3208 + if (cmd->format != 0 && cmd->format != 32 && cmd->format != 24 … | |
| 3209 + cmd->compression != 0) { | |
| 3210 + gr_reporterror_cmd(cmd, "EINVAL: compression is support… | |
| 3211 + "for raw pixel data (f=32 or f=… | |
| 3212 + // Even though we report an error, we still create an i… | |
| 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 foun… | |
| 3222 + return NULL; | |
| 3223 + } | |
| 3224 + } else { | |
| 3225 + // Otherwise create a new image object. If the action i… | |
| 3226 + // we'll use random id instead of the one specified in … | |
| 3227 + // command. | |
| 3228 + uint32_t image_id = cmd->action == 'q' ? 0 : cmd->image… | |
| 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 d… | |
| 3257 + // transmission subsequent transmission command won't contain t… | |
| 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 transm… | |
| 3282 + // medium is 'd' (or unspecified), and there is an active direc… | |
| 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… | |
| 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 ima… | |
| 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, NU… | |
| 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… | |
| 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_si… | |
| 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_FI… | |
| 3324 + } else { | |
| 3325 + gr_make_sure_tmpdir_exists(); | |
| 3326 + // Build the filename for the cached copy of th… | |
| 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 fil… | |
| 3331 + // then copy the file to the temporary cache di… | |
| 3332 + // this symlink trick mostly to be able to use … | |
| 3333 + // copying, and avoid escaping file name charac… | |
| 3334 + // calling system at the same time. | |
| 3335 + char tmp_filename_symlink[MAX_FILENAME_SIZE + 4… | |
| 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 + 2… | |
| 3341 + "cp '%s' '%s'", tmp_filename_s… | |
| 3342 + cache_filename); | |
| 3343 + if (len > MAX_FILENAME_SIZE + 255 || | |
| 3344 + symlink(original_filename, tmp_filename_sym… | |
| 3345 + system(command) != 0) { | |
| 3346 + gr_reporterror_cmd(cmd, | |
| 3347 + "EBADF: could not co… | |
| 3348 + "image to the cache … | |
| 3349 + fprintf(stderr, | |
| 3350 + "Could not copy the image " | |
| 3351 + "%s (symlink %s) to %s", | |
| 3352 + sanitized_filename(original_fil… | |
| 3353 + tmp_filename_symlink, cache_fil… | |
| 3354 + frame->status = STATUS_UPLOADING_ERROR; | |
| 3355 + frame->uploading_failure = ERROR_CANNOT… | |
| 3356 + } else { | |
| 3357 + // Get the file size of the copied file. | |
| 3358 + frame->status = STATUS_UPLOADING_SUCCES… | |
| 3359 + frame->disk_size = st.st_size; | |
| 3360 + frame->image->total_disk_size += st.st_… | |
| 3361 + images_disk_size += frame->disk_size; | |
| 3362 + if (frame->expected_size && | |
| 3363 + frame->expected_size != frame->disk… | |
| 3364 + // The file has unexpected size. | |
| 3365 + frame->status = STATUS_UPLOADIN… | |
| 3366 + frame->uploading_failure = | |
| 3367 + ERROR_UNEXPECTED_SIZE; | |
| 3368 + gr_reportuploaderror(frame); | |
| 3369 + } else { | |
| 3370 + // Everything seems fine, try t… | |
| 3371 + // and redraw existing instance… | |
| 3372 + gr_schedule_image_redraw(frame-… | |
| 3373 + frame = gr_loadimage_and_report… | |
| 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 trans… | |
| 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 transm… | |
| 3394 + // command. If we couldn't find the image, something we… | |
| 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 suppor… | |
| 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 … | |
| 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 … | |
| 3434 + // it will be deleted. If the id is zero, a random id will be g… | |
| 3435 + ImagePlacement *placement = gr_new_placement(img, cmd->placemen… | |
| 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… | |
| 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… | |
| 3453 + // contain. | |
| 3454 + placement->scale_mode = SCALE_MODE_CONTAIN; | |
| 3455 + } else { | |
| 3456 + // If none of them are specified, the default is to use… | |
| 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 place… | |
| 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 … | |
| 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 != placeme… | |
| 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 d… | |
| 3496 + // specifier) and there are no more placements. | |
| 3497 + if (del_data->delete_image_if_no_ref && kh_size(img->placements… | |
| 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… | |
| 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 pla… | |
| 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 th… | |
| 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 t… | |
| 3532 + // even if there are no _visible_ placements to delete.… | |
| 3533 + // this because otherwise there is no way to delete an … | |
| 3534 + // with virtual placements in one command. | |
| 3535 + if (!del_data.placement_id && del_data.delete_image_if_… | |
| 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'.… | |
| 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 … | |
| 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 f… | |
| 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: … | |
| 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 expe… | |
| 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 transmis… | |
| 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 t… | |
| 3622 + // as transmit, but the image is discarded, and the id … | |
| 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 o… | |
| 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 … | |
| 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 … | |
| 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 va… | |
| 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 specificati… | |
| 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 compres… | |
| 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… | |
| 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(GraphicsCommandResul… | |
| 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, … | |
| 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 valu… | |
| 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… | |
| 3873 + "key-value p… | |
| 3874 + break; | |
| 3875 + } | |
| 3876 + key_vals[key_vals_count].key_start = ke… | |
| 3877 + key_vals[key_vals_count].val_start = va… | |
| 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 pa… | |
| 3891 + break; | |
| 3892 + } | |
| 3893 + ++c; | |
| 3894 + } | |
| 3895 + | |
| 3896 + // Set the action key ('a=') first because we need it to disamb… | |
| 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 ==… | |
| 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.respons… | |
| 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.q… | |
| 3933 + // taken into account when creating the response, but it's not … | |
| 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, … | |
| 3949 + 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,… | |
| 3951 + 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, … | |
| 3952 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,… | |
| 3953 + 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29,… | |
| 3954 + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,… | |
| 3955 + 48, 49, 50, 51, 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, … | |
| 3957 + 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, … | |
| 3959 + 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, … | |
| 3961 + 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, … | |
| 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 … | |
| 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(… | |
| 3977 + int b = gr_base64_digits[(unsigned char)gr_base64_getc(… | |
| 3978 + int c = gr_base64_digits[(unsigned char)gr_base64_getc(… | |
| 3979 + int d = gr_base64_digits[(unsigned char)gr_base64_getc(… | |
| 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 functi… | |
| 4015 +/// actually draw some rectangles, or it may wait till more rectangles … | |
| 4016 +/// appended. Must be called between `gr_start_drawing` and `gr_finish_… | |
| 4017 +/// - `img_start_col..img_end_col` and `img_start_row..img_end_row` def… | |
| 4018 +/// part of the image to draw (row/col indices are zero-based, ends a… | |
| 4019 +/// excluded). | |
| 4020 +/// - `x_col` and `y_row` are the coordinates of the top-left corner of… | |
| 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 plac… | |
| 4025 + int img_start_col, int img_end_col, int img_st… | |
| 4026 + int img_end_row, int x_col, int y_row, int x_p… | |
| 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 l… | |
| 4031 +/// draw. | |
| 4032 +void gr_finish_drawing(Drawable buf); | |
| 4033 +/// Mark rows containing animations as dirty if it's time to redraw the… | |
| 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… | |
| 4038 +/// at least `len + 1` characters (including '\0'). Returns 1 on succes… | |
| 4039 +/// Additional informations is returned through `graphics_command_resul… | |
| 4040 +int gr_parse_command(char *buf, size_t len); | |
| 4041 + | |
| 4042 +/// Executes `command` with the name of the file corresponding to `imag… | |
| 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 tempor… | |
| 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_… | |
| 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… | |
| 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_… | |
| 4063 + uint32_t placement_id, int … | |
| 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… | |
| 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… | |
| 4096 + /// the response must be sent back anyway). | |
| 4097 + char error; | |
| 4098 + /// Whether the terminal has to create a placeholder for a non-… | |
| 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 grap… | |
| 4126 +protocol with Unicode placeholders. It is very basic, please use someth… | |
| 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 … | |
| 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… | |
| 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… | |
| 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"; t… | |
| 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" … | |
| 4312 + cell_width="${cell_size_ioctl% *}" | |
| 4313 + cell_height="${cell_size_ioctl#* }" | |
| 4314 + if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; t… | |
| 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"; t… | |
| 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" | … | |
| 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 animatio… | |
| 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 va… | |
| 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 wi… | |
| 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 … | |
| 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_CONNECTI… | |
| 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 wil… | |
| 4446 +# repeated for every chunk (if the method is direct), and <command> is … | |
| 4447 +# of the command that specifies the image parameters. <action> and <com… | |
| 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}" "$f… | |
| 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 … | |
| 4468 + # graphics command shouldn't be more than 4096, so we set the s… | |
| 4469 + # encoded chunk to be 3968, slightly less than that. | |
| 4470 + chunk_size=3968 | |
| 4471 + cat "$arg_file" | base64 -w0 | split -b "$chunk_size" - "$chunk… | |
| 4472 + | |
| 4473 + # Issue a command indicating that we want to start data transmi… | |
| 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 ea… | |
| 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-sep… | |
| 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_numbe… | |
| 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},… | |
| 4543 + # Set the delay for the first frame and also play the a… | |
| 4544 + # in loading mode (s=2). | |
| 4545 + gr_command "a=a,v=1,s=2,r=${frame_number},z=${delay},i=… | |
| 4546 + # Print the placeholder after the first frame to reduce… | |
| 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}" "$… | |
| 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 w… | |
| 4561 + # delay to avoid removing files before they are loaded by the t… | |
| 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… | |
| 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 c… | |
| 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 … | |
| 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_diacri… | |
| 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 <[email protected]… | |
| 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, stepp… | |
| 4975 + i*(i+1)/2 guarantees to traverse each bucket. It is better th… | |
| 4976 + hashing on cache performance and is more robust than linear p… | |
| 4977 + | |
| 4978 + In theory, double hashing should be more robust than quadrati… | |
| 4979 + However, my implementation is probably not for large hash tab… | |
| 4980 + the second hash function is closely tied to the first hash fu… | |
| 4981 + which reduce the effectiveness of double hashing. | |
| 4982 + | |
| 4983 + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadrat… | |
| 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 impr… | |
| 4992 + speed for simple keys. Thank Zilong Tan for the suggestion. R… | |
| 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 bett… | |
| 4998 + performance for random input. Double hashing is still the def… | |
| 4999 + is more robust to certain non-random input. | |
| 5000 + | |
| 5001 + * Added Wang's integer hash function (not used by default). Thi… | |
| 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__ &&… | |
| 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)<<… | |
| 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… | |
| 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 ke… | |
| 5130 + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buc… | |
| 5131 + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int… | |
| 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_f… | |
| 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)… | |
| 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_isde… | |
| 5161 + i = (i + (++step)) & mask; \ | |
| 5162 + if (i == last) return h->n_buckets; … | |
| 5163 + } … | |
| 5164 + return __ac_iseither(h->flags, i)? h->n_buckets… | |
| 5165 + } else return 0; … | |
| 5166 + } … | |
| 5167 + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buck… | |
| 5168 + { /* This function uses 0.25*n_buckets bytes of working space i… | |
| 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_H… | |
| 5175 + else { /* hash table size to be changed (shrink… | |
| 5176 + new_flags = (khint32_t*)kmalloc(__ac_fs… | |
| 5177 + if (!new_flags) return -1; … | |
| 5178 + memset(new_flags, 0xaa, __ac_fsize(new_… | |
| 5179 + if (h->n_buckets < new_n_buckets) { … | |
| 5180 + khkey_t *new_keys = (khkey_t*)k… | |
| 5181 + if (!new_keys) { kfree(new_flag… | |
| 5182 + h->keys = new_keys; … | |
| 5183 + if (kh_is_map) { … | |
| 5184 + khval_t *new_vals = (kh… | |
| 5185 + if (!new_vals) { kfree(… | |
| 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… | |
| 5201 + khint_t k, i, step = 0;… | |
| 5202 + k = __hash_func(key); … | |
| 5203 + i = k & new_mask; … | |
| 5204 + while (!__ac_isempty(ne… | |
| 5205 + __ac_set_isempty_false(… | |
| 5206 + if (i < h->n_buckets &&… | |
| 5207 + { khkey_t tmp =… | |
| 5208 + if (kh_is_map) … | |
| 5209 + __ac_set_isdel_… | |
| 5210 + } else { /* write the e… | |
| 5211 + h->keys[i] = ke… | |
| 5212 + if (kh_is_map) … | |
| 5213 + break; … | |
| 5214 + } … | |
| 5215 + } … | |
| 5216 + } … | |
| 5217 + } … | |
| 5218 + if (h->n_buckets > new_n_buckets) { /* shrink t… | |
| 5219 + h->keys = (khkey_t*)krealloc((void *)h-… | |
| 5220 + if (kh_is_map) h->vals = (khval_t*)krea… | |
| 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_… | |
| 5227 + } … | |
| 5228 + return 0; … | |
| 5229 + } … | |
| 5230 + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int … | |
| 5231 + { … | |
| 5232 + khint_t x; … | |
| 5233 + if (h->n_occupied >= h->upper_bound) { /* update the ha… | |
| 5234 + if (h->n_buckets > (h->size<<1)) { … | |
| 5235 + if (kh_resize_##name(h, h->n_buckets - … | |
| 5236 + *ret = -1; return h->n_buckets;… | |
| 5237 + } … | |
| 5238 + } else if (kh_resize_##name(h, h->n_buckets + 1… | |
| 5239 + *ret = -1; return h->n_buckets; … | |
| 5240 + } … | |
| 5241 + } /* TODO: to implement automatically shrinking; resize… | |
| 5242 + { … | |
| 5243 + khint_t k, i, site, last, mask = h->n_buckets -… | |
| 5244 + x = site = h->n_buckets; k = __hash_func(key); … | |
| 5245 + if (__ac_isempty(h->flags, i)) x = i; /* for sp… | |
| 5246 + else { … | |
| 5247 + last = i; \ | |
| 5248 + while (!__ac_isempty(h->flags, i) && (_… | |
| 5249 + if (__ac_isdel(h->flags, i)) si… | |
| 5250 + i = (i + (++step)) & mask; \ | |
| 5251 + if (i == last) { x = site; brea… | |
| 5252 + } … | |
| 5253 + if (x == h->n_buckets) { … | |
| 5254 + if (__ac_isempty(h->flags, i) &… | |
| 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 a… | |
| 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_fu… | |
| 5285 + __KHASH_TYPE(name, khkey_t, khval_t) … | |
| 5286 + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_f… | |
| 5287 + | |
| 5288 +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __ha… | |
| 5289 + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_… | |
| 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… | |
| 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 elem… | |
| 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_h… | |
| 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_in… | |
| 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_int… | |
| 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_… | |
| 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_h… | |
| 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_st… | |
| 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 <[email protected]> | |
| 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, … | |
| 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)… | |
| 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… | |
| 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)… | |
| 5637 + (v).a = (type*)… | |
| 5638 + : 0), ((v).a + ((v).… | |
| 5639 + | |
| 5640 +#define kv_a(type, v, i) (((v).m <= (size_t)(i)? \ | |
| 5641 + ((v).m = (v).n = (i) … | |
| 5642 + (v).a = (type*)reall… | |
| 5643 + : (v).n <= (size_t)(i… | |
| 5644 + : 0), (v).a[(i)]) | |
| 5645 + | |
| 5646 +#endif | |
| 5647 diff --git a/rowcolumn_diacritics_helpers.c b/rowcolumn_diacritics_helpe… | |
| 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) == 0x… | |
| 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, 0x… | |
| 6085 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800,… | |
| 6086 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF,… | |
| 6087 | |
| 6088 +/* Converts a diacritic to a row/column/etc number. The result is 1-bas… | |
| 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_CHA… | |
| 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 - writt… | |
| 6134 + // If buflen changed during the call to twrite,… | |
| 6135 + // new data, and we need to keep processing, ot… | |
| 6136 + // we can exit. This will not loop forever beca… | |
| 6137 + // buffer is limited, and we don't clean it in … | |
| 6138 + // loop, so at some point ttywrite will have to… | |
| 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… | |
| 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 lin… | |
| 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 = defaultb… | |
| 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 placem… | |
| 6210 + // placeholders with space symbols (unlike Unicode plac… | |
| 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_… | |
| 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 poin… | |
| 6240 +/// cursor. Adds empty lines if needed. The placeholder will be marked … | |
| 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 … | |
| 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… | |
| 6270 + // preserved (we never change term.c.x in the loop abov… | |
| 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 … | |
| 6279 + // protocol. If the cursor goes beyond the screen edge,… | |
| 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_… | |
| 6289 + uint32_t placement_id, int … | |
| 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 = tgetimgplacemen… | |
| 6298 + int ret = | |
| 6299 + callback(data, tgetimgid(gp), | |
| 6300 + tgetimgplacementid(gp), | |
| 6301 + tgetimgcol(gp), tgetim… | |
| 6302 + tgetisclassicplacehold… | |
| 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, id… | |
| 6349 + } else if (idx == 0) { | |
| 6350 + term.c.attr.mode &= ~ATTR_UNDER… | |
| 6351 + tsetdecorstyle(&term.c.attr, 0); | |
| 6352 + } else { | |
| 6353 + fprintf(stderr, | |
| 6354 + "erresc: unknown underl… | |
| 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_COLO… | |
| 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 versi… | |
| 6391 + len = snprintf(buf, sizeof(buf), | |
| 6392 + "\033P>|st-graphics(%s)\033\\", … | |
| 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 charact… | |
| 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_… | |
| 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_cu… | |
| 6439 + } | |
| 6440 + if (res->response[0]) | |
| 6441 + ttywrite(res->response, strlen(res->res… | |
| 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… | |
| 6458 + // supported, so we just ignore them, unless it denotes… | |
| 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)… | |
| 6521 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg !=… | |
| 6522 - (a).bg != (b).bg) | |
| 6523 + (a).bg != (b).bg || (a).decor != (b).de… | |
| 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) &= ~(… | |
| 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 th… | |
| 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 & 0x1… | |
| 6586 +static inline uint32_t tgetdecorstyle(Glyph *g) { return (g->decor >> 2… | |
| 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 uns… | |
| 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) & 0x1f… | |
| 6604 +static inline uint32_t tgetimgid4thbyteplus1(Glyph *g) { return (g->u >… | |
| 6605 +static inline uint32_t tgetimgdiacriticcount(Glyph *g) { return (g->u >… | |
| 6606 +static inline uint32_t tgetisclassicplaceholder(Glyph *g) { return (g->… | |
| 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 isclassi… | |
| 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 m… | |
| 6624 +/// significant byte is not specified, it's assumed to be 0 instead of … | |
| 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}%*%{10… | |
| 6662 setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%… | |
| 6663 setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p… | |
| 6664 +# Underline colors | |
| 6665 + Su, | |
| 6666 + Setulc=\E[58:2:%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%… | |
| 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, … | |
| 6727 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, i… | |
| 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=… | |
| 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=… | |
| 6766 + image_id, tgetimgplacementid(&g), tgetimgcol(&g… | |
| 6767 + tgetimgrow(&g)); | |
| 6768 + char stcommand[256] = {0}; | |
| 6769 + size_t len = snprintf(stcommand, sizeof(stcommand), "%s… | |
| 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 l… | |
| 6897 { | |
| 6898 - float winx = borderpx + x * win.cw, winy = borderpx + y * win.c… | |
| 6899 + float winx = win.hborderpx + x * win.cw, winy = win.vborderpx +… | |
| 6900 ushort mode, prevmode = USHRT_MAX; | |
| 6901 Font *font = &dc.font; | |
| 6902 int frcflags = FRC_NORMAL; | |
| 6903 @@ -1267,6 +1352,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, cons… | |
| 6904 if (mode == ATTR_WDUMMY) | |
| 6905 continue; | |
| 6906 | |
| 6907 + /* Draw spaces for image placeholders (images will be d… | |
| 6908 + * separately). */ | |
| 6909 + if (mode & ATTR_IMAGE) | |
| 6910 + rune = ' '; | |
| 6911 + | |
| 6912 /* Determine font for glyph if different from previous … | |
| 6913 if (prevmode != mode) { | |
| 6914 prevmode = mode; | |
| 6915 @@ -1374,11 +1464,61 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, con… | |
| 6916 return numspecs; | |
| 6917 } | |
| 6918 | |
| 6919 +/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `… | |
| 6920 + * is the length of the dash plus the length of the gap. `fraction` is … | |
| 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 - s… | |
| 6932 + thick); | |
| 6933 + } | |
| 6934 +} | |
| 6935 + | |
| 6936 +/* Draws an undercurl. `h` is the total height, including line thicknes… | |
| 6937 +static void | |
| 6938 +xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int… | |
| 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 | GC… | |
| 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 i… | |
| 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 + … | |
| 6961 + {.x = i + wavelen, .y = y + yoffset… | |
| 6962 + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points… | |
| 6963 + CoordModeOrigin); | |
| 6964 + } | |
| 6965 + | |
| 6966 + XFreeGC(xw.dpy, gc); | |
| 6967 +} | |
| 6968 + | |
| 6969 void | |
| 6970 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len,… | |
| 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… | |
| 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 *spec… | |
| 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 : … | |
| 6986 + ((winy + win.ch >= win.vborderpx + win.th)? win… | |
| 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 : … | |
| 6992 + ((winy + win.ch >= win.vborderpx + win.th)? win… | |
| 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 *spec… | |
| 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, &de… | |
| 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 * … | |
| 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 fi… | |
| 7045 + liney -= MAX(0, liney + thick - (winy + win.ch)); | |
| 7046 + if (style == UNDERLINE_DOUBLE) { | |
| 7047 + liney -= MAX(0, liney + doubleh - (winy + win.c… | |
| 7048 + XftDrawRect(xw.draw, &decor, winx, liney, width… | |
| 7049 + XftDrawRect(xw.draw, &decor, winx, | |
| 7050 + liney + doubleh - thick, width, thi… | |
| 7051 + } else if (style == UNDERLINE_DOTTED) { | |
| 7052 + xdrawunderdashed(xw.draw, &decor, winx, liney, … | |
| 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, … | |
| 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, wi… | |
| 7061 + curlh, thick); | |
| 7062 + } else { | |
| 7063 + XftDrawRect(xw.draw, &decor, winx, liney, width… | |
| 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.ascen… | |
| 7073 - width, 1); | |
| 7074 + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascen… | |
| 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 … | |
| 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, in… | |
| 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.… | |
| 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 as… | |
| 7152 + * that they have the same attributes (and thus the same lower 24 bits … | |
| 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 … | |
| 7165 + uint32_t last_id_4thbyteplus1 = 0; | |
| 7166 + // We may need to inherit row/column/4th byte from the previous… | |
| 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… | |
| 7183 + // of the previous cell. Note that `cur_row` may contai… | |
| 7184 + // value imputed earlier, which will be preserved if `l… | |
| 7185 + // is zero (i.e. we don't know the row of the previous … | |
| 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 sa… | |
| 7189 + // row of the previous cell, then assume that the colum… | |
| 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… | |
| 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 … | |
| 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 cel… | |
| 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) … | |
| 7214 + if (last_row != 0) { | |
| 7215 + int x_pix = | |
| 7216 + win.hborderpx + last_start_x * … | |
| 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_st… | |
| 7221 + y1, x_pix, y_pix, win.cw, win.c… | |
| 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 inheritanc… | |
| 7231 + // runs and support the naive implementation of tgetimg… | |
| 7232 + if (!tgetimgrow(g)) | |
| 7233 + tsetimgrow(g, cur_row); | |
| 7234 + // We cannot save this information if there are > 511 c… | |
| 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_… | |
| 7248 + last_row, last_start_x, y1, x_pix, … | |
| 7249 + win.cw, win.ch, base.mode & ATTR_RE… | |
| 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 + … | |
| 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 … | |
| 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_de… | |
| 7316 + : MIN(timeout, | |
| 7317 + graphics_next_redra… | |
| 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 |