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 |