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