sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

st-kitty-graphics-20251230-0.9.3.diff (262745B)


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