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