sites

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

st-kitty-graphics-20240922-a0274bc.diff (235999B)


      1 From 25d9cca81ce48141de7f6a823b006dddaafd9de8 Mon Sep 17 00:00:00 2001
      2 From: Sergei Grechanik <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