graphics.c (128226B)
1 /* The MIT License 2 3 Copyright (c) 2021-2024 Sergei Grechanik <sergei.grechanik@gmail.com> 4 5 Permission is hereby granted, free of charge, to any person obtaining 6 a copy of this software and associated documentation files (the 7 "Software"), to deal in the Software without restriction, including 8 without limitation the rights to use, copy, modify, merge, publish, 9 distribute, sublicense, and/or sell copies of the Software, and to 10 permit persons to whom the Software is furnished to do so, subject to 11 the following conditions: 12 13 The above copyright notice and this permission notice shall be 14 included in all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 SOFTWARE. 24 */ 25 26 //////////////////////////////////////////////////////////////////////////////// 27 // 28 // This file implements a subset of the kitty graphics protocol. 29 // 30 //////////////////////////////////////////////////////////////////////////////// 31 32 #define _POSIX_C_SOURCE 200809L 33 34 #include <zlib.h> 35 #include <Imlib2.h> 36 #include <X11/Xlib.h> 37 #include <X11/extensions/Xrender.h> 38 #include <assert.h> 39 #include <ctype.h> 40 #include <spawn.h> 41 #include <stdarg.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <sys/stat.h> 46 #include <time.h> 47 #include <unistd.h> 48 #include <errno.h> 49 50 #include "graphics.h" 51 #include "khash.h" 52 #include "kvec.h" 53 54 extern char **environ; 55 56 #define MAX_FILENAME_SIZE 256 57 #define MAX_INFO_LEN 256 58 #define MAX_IMAGE_RECTS 20 59 60 /// The type used in this file to represent time. Used both for time differences 61 /// and absolute times (as milliseconds since an arbitrary point in time, see 62 /// `initialization_time`). 63 typedef int64_t Milliseconds; 64 65 enum ScaleMode { 66 SCALE_MODE_UNSET = 0, 67 /// Stretch or shrink the image to fill the box, ignoring aspect ratio. 68 SCALE_MODE_FILL = 1, 69 /// Preserve aspect ratio and fit to width or to height so that the 70 /// whole image is visible. 71 SCALE_MODE_CONTAIN = 2, 72 /// Do not scale. The image may be cropped if the box is too small. 73 SCALE_MODE_NONE = 3, 74 /// Do not scale, unless the box is too small, in which case the image 75 /// will be shrunk like with `SCALE_MODE_CONTAIN`. 76 SCALE_MODE_NONE_OR_CONTAIN = 4, 77 }; 78 79 enum AnimationState { 80 ANIMATION_STATE_UNSET = 0, 81 /// The animation is stopped. Display the current frame, but don't 82 /// advance to the next one. 83 ANIMATION_STATE_STOPPED = 1, 84 /// Run the animation to then end, then wait for the next frame. 85 ANIMATION_STATE_LOADING = 2, 86 /// Run the animation in a loop. 87 ANIMATION_STATE_LOOPING = 3, 88 }; 89 90 /// The status of an image. Each image uploaded to the terminal is cached on 91 /// disk, then it is loaded to ram when needed. 92 enum ImageStatus { 93 STATUS_UNINITIALIZED = 0, 94 STATUS_UPLOADING = 1, 95 STATUS_UPLOADING_ERROR = 2, 96 STATUS_UPLOADING_SUCCESS = 3, 97 STATUS_RAM_LOADING_ERROR = 4, 98 STATUS_RAM_LOADING_IN_PROGRESS = 5, 99 STATUS_RAM_LOADING_SUCCESS = 6, 100 }; 101 102 const char *image_status_strings[6] = { 103 "STATUS_UNINITIALIZED", 104 "STATUS_UPLOADING", 105 "STATUS_UPLOADING_ERROR", 106 "STATUS_UPLOADING_SUCCESS", 107 "STATUS_RAM_LOADING_ERROR", 108 "STATUS_RAM_LOADING_SUCCESS", 109 }; 110 111 enum ImageUploadingFailure { 112 ERROR_OVER_SIZE_LIMIT = 1, 113 ERROR_CANNOT_OPEN_CACHED_FILE = 2, 114 ERROR_UNEXPECTED_SIZE = 3, 115 ERROR_CANNOT_COPY_FILE = 4, 116 }; 117 118 const char *image_uploading_failure_strings[5] = { 119 "NO_ERROR", 120 "ERROR_OVER_SIZE_LIMIT", 121 "ERROR_CANNOT_OPEN_CACHED_FILE", 122 "ERROR_UNEXPECTED_SIZE", 123 "ERROR_CANNOT_COPY_FILE", 124 }; 125 126 //////////////////////////////////////////////////////////////////////////////// 127 // 128 // We use the following structures to represent images and placements: 129 // 130 // - Image: this is the main structure representing an image, usually created 131 // by actions 'a=t', 'a=T`. Each image has an id (image id aka client id, 132 // specified by 'i='). An image may have multiple frames (ImageFrame) and 133 // placements (ImagePlacement). 134 // 135 // - ImageFrame: represents a single frame of an image, usually created by 136 // the action 'a=f' (and the first frame is created with the image itself). 137 // Each frame has an index and also: 138 // - a file containing the frame data (considered to be "on disk", although 139 // it's probably in tmpfs), 140 // - an imlib object containing the fully composed frame (i.e. the frame 141 // data from the file composed onto the background frame or color). It is 142 // not ready for display yet, because it needs to be scaled and uploaded 143 // to the X server. 144 // 145 // - ImagePlacement: represents a placement of an image, created by 'a=p' and 146 // 'a=T'. Each placement has an id (placement id, specified by 'p='). Also 147 // each placement has an array of pixmaps: one for each frame of the image. 148 // Each pixmap is a scaled and uploaded image ready to be displayed. 149 // 150 // Images are store in the `images` hash table, mapping image ids to Image 151 // objects (allocated on the heap). 152 // 153 // Placements are stored in the `placements` hash table of each Image object, 154 // mapping placement ids to ImagePlacement objects (also allocated on the heap). 155 // 156 // ImageFrames are stored in the `first_frame` field and in the 157 // `frames_beyond_the_first` array of each Image object. They are stored by 158 // value, so ImageFrame pointer may be invalidated when frames are 159 // added/deleted, be careful. 160 // 161 //////////////////////////////////////////////////////////////////////////////// 162 163 struct Image; 164 struct ImageFrame; 165 struct ImagePlacement; 166 167 KHASH_MAP_INIT_INT(id2image, struct Image *) 168 KHASH_MAP_INIT_INT(id2placement, struct ImagePlacement *) 169 170 typedef struct ImageFrame { 171 /// The image this frame belongs to. 172 struct Image *image; 173 /// The 1-based index of the frame. Zero if the frame isn't initialized. 174 int index; 175 /// The last time when the frame was displayed or otherwise touched. 176 Milliseconds atime; 177 /// The background color of the frame in the 0xRRGGBBAA format. 178 uint32_t background_color; 179 /// The index of the background frame. Zero to use the color instead. 180 int background_frame_index; 181 /// The duration of the frame in milliseconds. 182 int gap; 183 /// The expected size of the frame image file (specified with 'S='), 184 /// used to check if uploading succeeded. 185 unsigned expected_size; 186 /// Format specification (see the `f=` key). 187 int format; 188 /// Pixel width and height of the non-composed (on-disk) frame data. May 189 /// differ from the image (i.e. first frame) dimensions. 190 int data_pix_width, data_pix_height; 191 /// The offset of the frame relative to the first frame. 192 int x, y; 193 /// Compression mode (see the `o=` key). 194 char compression; 195 /// The status (see `ImageStatus`). 196 char status; 197 /// The reason of uploading failure (see `ImageUploadingFailure`). 198 char uploading_failure; 199 /// Whether failures and successes should be reported ('q='). 200 char quiet; 201 /// Whether to blend the frame with the background or replace it. 202 char blend; 203 /// The file corresponding to the on-disk cache, used when uploading. 204 FILE *open_file; 205 /// The size of the corresponding file cached on disk. 206 unsigned disk_size; 207 /// The imlib object containing the fully composed frame. It's not 208 /// scaled for screen display yet. 209 Imlib_Image imlib_object; 210 } ImageFrame; 211 212 typedef struct Image { 213 /// The client id (the one specified with 'i='). Must be nonzero. 214 uint32_t image_id; 215 /// The client id specified in the query command (`a=q`). This one must 216 /// be used to create the response if it's non-zero. 217 uint32_t query_id; 218 /// The number specified in the transmission command (`I=`). If 219 /// non-zero, it may be used to identify the image instead of the 220 /// image_id, and it also should be mentioned in responses. 221 uint32_t image_number; 222 /// The last time when the image was displayed or otherwise touched. 223 Milliseconds atime; 224 /// The total duration of the animation in milliseconds. 225 int total_duration; 226 /// The total size of cached image files for all frames. 227 int total_disk_size; 228 /// The global index of the creation command. Used to decide which image 229 /// is newer if they have the same image number. 230 uint64_t global_command_index; 231 /// The 1-based index of the currently displayed frame. 232 int current_frame; 233 /// The state of the animation, see `AnimationState`. 234 char animation_state; 235 /// The absolute time that is assumed to be the start of the current 236 /// frame (in ms since initialization). 237 Milliseconds current_frame_time; 238 /// The absolute time of the last redraw (in ms since initialization). 239 /// Used to check whether it's the first time we draw the image in the 240 /// current redraw cycle. 241 Milliseconds last_redraw; 242 /// The absolute time of the next redraw (in ms since initialization). 243 /// 0 means no redraw is scheduled. 244 Milliseconds next_redraw; 245 /// The unscaled pixel width and height of the image. Usually inherited 246 /// from the first frame. 247 int pix_width, pix_height; 248 /// The first frame. 249 ImageFrame first_frame; 250 /// The array of frames beyond the first one. 251 kvec_t(ImageFrame) frames_beyond_the_first; 252 /// Image placements. 253 khash_t(id2placement) *placements; 254 /// The default placement. 255 uint32_t default_placement; 256 /// The initial placement id, specified with the transmission command, 257 /// used to report success or failure. 258 uint32_t initial_placement_id; 259 } Image; 260 261 typedef struct ImagePlacement { 262 /// The image this placement belongs to. 263 Image *image; 264 /// The id of the placement. Must be nonzero. 265 uint32_t placement_id; 266 /// The last time when the placement was displayed or otherwise touched. 267 Milliseconds atime; 268 /// The 1-based index of the protected pixmap. We protect a pixmap in 269 /// gr_load_pixmap to avoid unloading it right after it was loaded. 270 int protected_frame; 271 /// Whether the placement is used only for Unicode placeholders. 272 char virtual; 273 /// The scaling mode (see `ScaleMode`). 274 char scale_mode; 275 /// Height and width in cells. 276 uint16_t rows, cols; 277 /// Top-left corner of the source rectangle ('x=' and 'y='). 278 int src_pix_x, src_pix_y; 279 /// Height and width of the source rectangle (zero if full image). 280 int src_pix_width, src_pix_height; 281 /// The image appropriately scaled and uploaded to the X server. This 282 /// pixmap is premultiplied by alpha. 283 Pixmap first_pixmap; 284 /// The array of pixmaps beyond the first one. 285 kvec_t(Pixmap) pixmaps_beyond_the_first; 286 /// The dimensions of the cell used to scale the image. If cell 287 /// dimensions are changed (font change), the image will be rescaled. 288 uint16_t scaled_cw, scaled_ch; 289 /// If true, do not move the cursor when displaying this placement 290 /// (non-virtual placements only). 291 char do_not_move_cursor; 292 } ImagePlacement; 293 294 /// A rectangular piece of an image to be drawn. 295 typedef struct { 296 uint32_t image_id; 297 uint32_t placement_id; 298 /// The position of the rectangle in pixels. 299 int screen_x_pix, screen_y_pix; 300 /// The starting row on the screen. 301 int screen_y_row; 302 /// The part of the whole image to be drawn, in cells. Starts are 303 /// zero-based, ends are exclusive. 304 int img_start_col, img_end_col, img_start_row, img_end_row; 305 /// The current cell width and height in pixels. 306 int cw, ch; 307 /// Whether colors should be inverted. 308 int reverse; 309 } ImageRect; 310 311 /// Executes `code` for each frame of an image. Example: 312 /// 313 /// foreach_frame(image, frame, { 314 /// printf("Frame %d\n", frame->index); 315 /// }); 316 /// 317 #define foreach_frame(image, framevar, code) { size_t __i; \ 318 for (__i = 0; __i <= kv_size((image).frames_beyond_the_first); ++__i) { \ 319 ImageFrame *framevar = \ 320 __i == 0 ? &(image).first_frame \ 321 : &kv_A((image).frames_beyond_the_first, __i - 1); \ 322 code; \ 323 } } 324 325 /// Executes `code` for each pixmap of a placement. Example: 326 /// 327 /// foreach_pixmap(placement, pixmap, { 328 /// ... 329 /// }); 330 /// 331 #define foreach_pixmap(placement, pixmapvar, code) { size_t __i; \ 332 for (__i = 0; __i <= kv_size((placement).pixmaps_beyond_the_first); ++__i) { \ 333 Pixmap pixmapvar = \ 334 __i == 0 ? (placement).first_pixmap \ 335 : kv_A((placement).pixmaps_beyond_the_first, __i - 1); \ 336 code; \ 337 } } 338 339 340 static Image *gr_find_image(uint32_t image_id); 341 static void gr_get_frame_filename(ImageFrame *frame, char *out, size_t max_len); 342 static void gr_delete_image(Image *img); 343 static void gr_check_limits(); 344 static char *gr_base64dec(const char *src, size_t *size); 345 static void sanitize_str(char *str, size_t max_len); 346 static const char *sanitized_filename(const char *str); 347 348 /// The array of image rectangles to draw. It is reset each frame. 349 static ImageRect image_rects[MAX_IMAGE_RECTS] = {{0}}; 350 /// The known images (including the ones being uploaded). 351 static khash_t(id2image) *images = NULL; 352 /// The total number of placements in all images. 353 static unsigned total_placement_count = 0; 354 /// The total size of all image files stored in the on-disk cache. 355 static int64_t images_disk_size = 0; 356 /// The total size of all images and placements loaded into ram. 357 static int64_t images_ram_size = 0; 358 /// The id of the last loaded image. 359 static uint32_t last_image_id = 0; 360 /// Current cell width and heigh in pixels. 361 static int current_cw = 0, current_ch = 0; 362 /// The id of the currently uploaded image (when using direct uploading). 363 static uint32_t current_upload_image_id = 0; 364 /// The index of the frame currently being uploaded. 365 static int current_upload_frame_index = 0; 366 /// The time when the graphics module was initialized. 367 static struct timespec initialization_time = {0}; 368 /// The time when the current frame drawing started, used for debugging fps and 369 /// to calculate the current frame for animations. 370 static Milliseconds drawing_start_time; 371 /// The global index of the current command. 372 static uint64_t global_command_counter = 0; 373 /// The next redraw times for each row of the terminal. Used for animations. 374 /// 0 means no redraw is scheduled. 375 static kvec_t(Milliseconds) next_redraw_times = {0, 0, NULL}; 376 /// The number of files loaded in the current redraw cycle. 377 static int this_redraw_cycle_loaded_files = 0; 378 /// The number of pixmaps loaded in the current redraw cycle. 379 static int this_redraw_cycle_loaded_pixmaps = 0; 380 381 /// The directory where the cache files are stored. 382 static char cache_dir[MAX_FILENAME_SIZE - 16]; 383 384 /// The table used for color inversion. 385 static unsigned char reverse_table[256]; 386 387 // Declared in the header. 388 GraphicsDebugMode graphics_debug_mode = GRAPHICS_DEBUG_NONE; 389 char graphics_display_images = 1; 390 GraphicsCommandResult graphics_command_result = {0}; 391 int graphics_next_redraw_delay = INT_MAX; 392 393 // Defined in config.h 394 extern const char graphics_cache_dir_template[]; 395 extern unsigned graphics_max_single_image_file_size; 396 extern unsigned graphics_total_file_cache_size; 397 extern unsigned graphics_max_single_image_ram_size; 398 extern unsigned graphics_max_total_ram_size; 399 extern unsigned graphics_max_total_placements; 400 extern double graphics_excess_tolerance_ratio; 401 extern unsigned graphics_animation_min_delay; 402 403 404 //////////////////////////////////////////////////////////////////////////////// 405 // Basic helpers. 406 //////////////////////////////////////////////////////////////////////////////// 407 408 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 409 #define MAX(a, b) ((a) < (b) ? (b) : (a)) 410 411 /// Returns the difference between `end` and `start` in milliseconds. 412 static int64_t gr_timediff_ms(const struct timespec *end, 413 const struct timespec *start) { 414 return (end->tv_sec - start->tv_sec) * 1000 + 415 (end->tv_nsec - start->tv_nsec) / 1000000; 416 } 417 418 /// Returns the current time in milliseconds since the initialization. 419 static Milliseconds gr_now_ms() { 420 struct timespec now; 421 clock_gettime(CLOCK_MONOTONIC, &now); 422 return gr_timediff_ms(&now, &initialization_time); 423 } 424 425 //////////////////////////////////////////////////////////////////////////////// 426 // Logging. 427 //////////////////////////////////////////////////////////////////////////////// 428 429 #define GR_LOG(...) \ 430 do { if(graphics_debug_mode) fprintf(stderr, __VA_ARGS__); } while(0) 431 432 //////////////////////////////////////////////////////////////////////////////// 433 // Basic image management functions (create, delete, find, etc). 434 //////////////////////////////////////////////////////////////////////////////// 435 436 /// Returns the 1-based index of the last frame. Note that you may want to use 437 /// `gr_last_uploaded_frame_index` instead since the last frame may be not 438 /// fully uploaded yet. 439 static inline int gr_last_frame_index(Image *img) { 440 return kv_size(img->frames_beyond_the_first) + 1; 441 } 442 443 /// Returns the frame with the given index. Returns NULL if the index is out of 444 /// bounds. The index is 1-based. 445 static ImageFrame *gr_get_frame(Image *img, int index) { 446 if (!img) 447 return NULL; 448 if (index == 1) 449 return &img->first_frame; 450 if (2 <= index && index <= gr_last_frame_index(img)) 451 return &kv_A(img->frames_beyond_the_first, index - 2); 452 return NULL; 453 } 454 455 /// Returns the last frame of the image. Returns NULL if `img` is NULL. 456 static ImageFrame *gr_get_last_frame(Image *img) { 457 if (!img) 458 return NULL; 459 return gr_get_frame(img, gr_last_frame_index(img)); 460 } 461 462 /// Returns the 1-based index of the last frame or the second-to-last frame if 463 /// the last frame is not fully uploaded yet. 464 static inline int gr_last_uploaded_frame_index(Image *img) { 465 int last_index = gr_last_frame_index(img); 466 if (last_index > 1 && 467 gr_get_frame(img, last_index)->status < STATUS_UPLOADING_SUCCESS) 468 return last_index - 1; 469 return last_index; 470 } 471 472 /// Returns the pixmap for the frame with the given index. Returns 0 if the 473 /// index is out of bounds. The index is 1-based. 474 static Pixmap gr_get_frame_pixmap(ImagePlacement *placement, int index) { 475 if (index == 1) 476 return placement->first_pixmap; 477 if (2 <= index && 478 index <= kv_size(placement->pixmaps_beyond_the_first) + 1) 479 return kv_A(placement->pixmaps_beyond_the_first, index - 2); 480 return 0; 481 } 482 483 /// Sets the pixmap for the frame with the given index. The index is 1-based. 484 /// The array of pixmaps is resized if needed. 485 static void gr_set_frame_pixmap(ImagePlacement *placement, int index, 486 Pixmap pixmap) { 487 if (index == 1) { 488 placement->first_pixmap = pixmap; 489 return; 490 } 491 // Resize the array if needed. 492 size_t old_size = kv_size(placement->pixmaps_beyond_the_first); 493 if (old_size < index - 1) { 494 kv_a(Pixmap, placement->pixmaps_beyond_the_first, index - 2); 495 for (size_t i = old_size; i < index - 1; i++) 496 kv_A(placement->pixmaps_beyond_the_first, i) = 0; 497 } 498 kv_A(placement->pixmaps_beyond_the_first, index - 2) = pixmap; 499 } 500 501 /// Finds the image corresponding to the client id. Returns NULL if cannot find. 502 static Image *gr_find_image(uint32_t image_id) { 503 khiter_t k = kh_get(id2image, images, image_id); 504 if (k == kh_end(images)) 505 return NULL; 506 Image *res = kh_value(images, k); 507 return res; 508 } 509 510 /// Finds the newest image corresponding to the image number. Returns NULL if 511 /// cannot find. 512 static Image *gr_find_image_by_number(uint32_t image_number) { 513 if (image_number == 0) 514 return NULL; 515 Image *newest_img = NULL; 516 Image *img = NULL; 517 kh_foreach_value(images, img, { 518 if (img->image_number == image_number && 519 (!newest_img || newest_img->global_command_index < 520 img->global_command_index)) 521 newest_img = img; 522 }); 523 if (!newest_img) 524 GR_LOG("Image number %u not found\n", image_number); 525 else 526 GR_LOG("Found image number %u, its id is %u\n", image_number, 527 img->image_id); 528 return newest_img; 529 } 530 531 /// Finds the placement corresponding to the id. If the placement id is 0, 532 /// returns some default placement. 533 static ImagePlacement *gr_find_placement(Image *img, uint32_t placement_id) { 534 if (!img) 535 return NULL; 536 if (placement_id == 0) { 537 // Try to get the default placement. 538 ImagePlacement *dflt = NULL; 539 if (img->default_placement != 0) 540 dflt = gr_find_placement(img, img->default_placement); 541 if (dflt) 542 return dflt; 543 // If there is no default placement, return the first one and 544 // set it as the default. 545 kh_foreach_value(img->placements, dflt, { 546 img->default_placement = dflt->placement_id; 547 return dflt; 548 }); 549 // If there are no placements, return NULL. 550 return NULL; 551 } 552 khiter_t k = kh_get(id2placement, img->placements, placement_id); 553 if (k == kh_end(img->placements)) 554 return NULL; 555 ImagePlacement *res = kh_value(img->placements, k); 556 return res; 557 } 558 559 /// Finds the placement by image id and placement id. 560 static ImagePlacement *gr_find_image_and_placement(uint32_t image_id, 561 uint32_t placement_id) { 562 return gr_find_placement(gr_find_image(image_id), placement_id); 563 } 564 565 /// Writes the name of the on-disk cache file to `out`. `max_len` should be the 566 /// size of `out`. The name will be something like 567 /// "/tmp/st-images-xxx/img-ID-FRAME". 568 static void gr_get_frame_filename(ImageFrame *frame, char *out, 569 size_t max_len) { 570 snprintf(out, max_len, "%s/img-%.3u-%.3u", cache_dir, 571 frame->image->image_id, frame->index); 572 } 573 574 /// Returns the (estimation) of the RAM size used by the frame right now. 575 static unsigned gr_frame_current_ram_size(ImageFrame *frame) { 576 if (!frame->imlib_object) 577 return 0; 578 return (unsigned)frame->image->pix_width * frame->image->pix_height * 4; 579 } 580 581 /// Returns the (estimation) of the RAM size used by a single frame pixmap. 582 static unsigned gr_placement_single_frame_ram_size(ImagePlacement *placement) { 583 return (unsigned)placement->rows * placement->cols * 584 placement->scaled_ch * placement->scaled_cw * 4; 585 } 586 587 /// Returns the (estimation) of the RAM size used by the placemenet right now. 588 static unsigned gr_placement_current_ram_size(ImagePlacement *placement) { 589 unsigned single_frame_size = 590 gr_placement_single_frame_ram_size(placement); 591 unsigned result = 0; 592 foreach_pixmap(*placement, pixmap, { 593 if (pixmap) 594 result += single_frame_size; 595 }); 596 return result; 597 } 598 599 /// Unload the frame from RAM (i.e. delete the corresponding imlib object). 600 /// If the on-disk file of the frame is preserved, it can be reloaded later. 601 static void gr_unload_frame(ImageFrame *frame) { 602 if (!frame->imlib_object) 603 return; 604 605 unsigned frame_ram_size = gr_frame_current_ram_size(frame); 606 images_ram_size -= frame_ram_size; 607 608 imlib_context_set_image(frame->imlib_object); 609 imlib_free_image_and_decache(); 610 frame->imlib_object = NULL; 611 612 GR_LOG("After unloading image %u frame %u (atime %ld ms ago) " 613 "ram: %ld KiB (- %u KiB)\n", 614 frame->image->image_id, frame->index, 615 drawing_start_time - frame->atime, images_ram_size / 1024, 616 frame_ram_size / 1024); 617 } 618 619 /// Unload all frames of the image. 620 static void gr_unload_all_frames(Image *img) { 621 foreach_frame(*img, frame, { 622 gr_unload_frame(frame); 623 }); 624 } 625 626 /// Unload the placement from RAM (i.e. free all of the corresponding pixmaps). 627 /// If the on-disk files or imlib objects of the corresponding image are 628 /// preserved, the placement can be reloaded later. 629 static void gr_unload_placement(ImagePlacement *placement) { 630 unsigned placement_ram_size = gr_placement_current_ram_size(placement); 631 images_ram_size -= placement_ram_size; 632 633 Display *disp = imlib_context_get_display(); 634 foreach_pixmap(*placement, pixmap, { 635 if (pixmap) 636 XFreePixmap(disp, pixmap); 637 }); 638 639 placement->first_pixmap = 0; 640 placement->pixmaps_beyond_the_first.n = 0; 641 placement->scaled_ch = placement->scaled_cw = 0; 642 643 GR_LOG("After unloading placement %u/%u (atime %ld ms ago) " 644 "ram: %ld KiB (- %u KiB)\n", 645 placement->image->image_id, placement->placement_id, 646 drawing_start_time - placement->atime, images_ram_size / 1024, 647 placement_ram_size / 1024); 648 } 649 650 /// Unload a single pixmap of the placement from RAM. 651 static void gr_unload_pixmap(ImagePlacement *placement, int frameidx) { 652 Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); 653 if (!pixmap) 654 return; 655 656 Display *disp = imlib_context_get_display(); 657 XFreePixmap(disp, pixmap); 658 gr_set_frame_pixmap(placement, frameidx, 0); 659 images_ram_size -= gr_placement_single_frame_ram_size(placement); 660 661 GR_LOG("After unloading pixmap %ld of " 662 "placement %u/%u (atime %ld ms ago) " 663 "frame %u (atime %ld ms ago) " 664 "ram: %ld KiB (- %u KiB)\n", 665 pixmap, placement->image->image_id, placement->placement_id, 666 drawing_start_time - placement->atime, frameidx, 667 drawing_start_time - 668 gr_get_frame(placement->image, frameidx)->atime, 669 images_ram_size / 1024, 670 gr_placement_single_frame_ram_size(placement) / 1024); 671 } 672 673 /// Deletes the on-disk cache file corresponding to the frame. The in-ram image 674 /// object (if it exists) is not deleted, placements are not unloaded either. 675 static void gr_delete_imagefile(ImageFrame *frame) { 676 // It may still be being loaded. Close the file in this case. 677 if (frame->open_file) { 678 fclose(frame->open_file); 679 frame->open_file = NULL; 680 } 681 682 if (frame->disk_size == 0) 683 return; 684 685 char filename[MAX_FILENAME_SIZE]; 686 gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 687 remove(filename); 688 689 unsigned disk_size = frame->disk_size; 690 images_disk_size -= disk_size; 691 frame->image->total_disk_size -= disk_size; 692 frame->disk_size = 0; 693 694 GR_LOG("After deleting image file %u frame %u (atime %ld ms ago) " 695 "disk: %ld KiB (- %u KiB)\n", 696 frame->image->image_id, frame->index, 697 drawing_start_time - frame->atime, images_disk_size / 1024, 698 disk_size / 1024); 699 } 700 701 /// Deletes all on-disk cache files of the image (for each frame). 702 static void gr_delete_imagefiles(Image *img) { 703 foreach_frame(*img, frame, { 704 gr_delete_imagefile(frame); 705 }); 706 } 707 708 /// Deletes the given placement: unloads, frees the object, but doesn't change 709 /// the `placements` hash table. 710 static void gr_delete_placement_keep_id(ImagePlacement *placement) { 711 if (!placement) 712 return; 713 GR_LOG("Deleting placement %u/%u\n", placement->image->image_id, 714 placement->placement_id); 715 gr_unload_placement(placement); 716 kv_destroy(placement->pixmaps_beyond_the_first); 717 free(placement); 718 total_placement_count--; 719 } 720 721 /// Deletes all placements of `img`. 722 static void gr_delete_all_placements(Image *img) { 723 ImagePlacement *placement = NULL; 724 kh_foreach_value(img->placements, placement, { 725 gr_delete_placement_keep_id(placement); 726 }); 727 kh_clear(id2placement, img->placements); 728 } 729 730 /// Deletes the given image: unloads, deletes the file, frees the Image object, 731 /// but doesn't change the `images` hash table. 732 static void gr_delete_image_keep_id(Image *img) { 733 if (!img) 734 return; 735 GR_LOG("Deleting image %u\n", img->image_id); 736 foreach_frame(*img, frame, { 737 gr_delete_imagefile(frame); 738 gr_unload_frame(frame); 739 }); 740 kv_destroy(img->frames_beyond_the_first); 741 gr_delete_all_placements(img); 742 kh_destroy(id2placement, img->placements); 743 free(img); 744 } 745 746 /// Deletes the given image: unloads, deletes the file, frees the Image object, 747 /// and also removes it from `images`. 748 static void gr_delete_image(Image *img) { 749 if (!img) 750 return; 751 uint32_t id = img->image_id; 752 gr_delete_image_keep_id(img); 753 khiter_t k = kh_get(id2image, images, id); 754 kh_del(id2image, images, k); 755 } 756 757 /// Deletes the given placement: unloads, frees the object, and also removes it 758 /// from `placements`. 759 static void gr_delete_placement(ImagePlacement *placement) { 760 if (!placement) 761 return; 762 uint32_t id = placement->placement_id; 763 Image *img = placement->image; 764 gr_delete_placement_keep_id(placement); 765 khiter_t k = kh_get(id2placement, img->placements, id); 766 kh_del(id2placement, img->placements, k); 767 } 768 769 /// Deletes all images and clears `images`. 770 static void gr_delete_all_images() { 771 Image *img = NULL; 772 kh_foreach_value(images, img, { 773 gr_delete_image_keep_id(img); 774 }); 775 kh_clear(id2image, images); 776 } 777 778 /// Update the atime of the image. 779 static void gr_touch_image(Image *img) { 780 img->atime = gr_now_ms(); 781 } 782 783 /// Update the atime of the frame. 784 static void gr_touch_frame(ImageFrame *frame) { 785 frame->image->atime = frame->atime = gr_now_ms(); 786 } 787 788 /// Update the atime of the placement. Touches the images too. 789 static void gr_touch_placement(ImagePlacement *placement) { 790 placement->image->atime = placement->atime = gr_now_ms(); 791 } 792 793 /// Creates a new image with the given id. If an image with that id already 794 /// exists, it is deleted first. If the provided id is 0, generates a 795 /// random id. 796 static Image *gr_new_image(uint32_t id) { 797 if (id == 0) { 798 do { 799 id = rand(); 800 // Avoid IDs that don't need full 32 bits. 801 } while ((id & 0xFF000000) == 0 || (id & 0x00FFFF00) == 0 || 802 gr_find_image(id)); 803 GR_LOG("Generated random image id %u\n", id); 804 } 805 Image *img = gr_find_image(id); 806 gr_delete_image_keep_id(img); 807 GR_LOG("Creating image %u\n", id); 808 img = malloc(sizeof(Image)); 809 memset(img, 0, sizeof(Image)); 810 img->placements = kh_init(id2placement); 811 int ret; 812 khiter_t k = kh_put(id2image, images, id, &ret); 813 kh_value(images, k) = img; 814 img->image_id = id; 815 gr_touch_image(img); 816 img->global_command_index = global_command_counter; 817 return img; 818 } 819 820 /// Creates a new frame at the end of the frame array. It may be the first frame 821 /// if there are no frames yet. 822 static ImageFrame *gr_append_new_frame(Image *img) { 823 ImageFrame *frame = NULL; 824 if (img->first_frame.index == 0 && 825 kv_size(img->frames_beyond_the_first) == 0) { 826 frame = &img->first_frame; 827 frame->index = 1; 828 } else { 829 frame = kv_pushp(ImageFrame, img->frames_beyond_the_first); 830 memset(frame, 0, sizeof(ImageFrame)); 831 frame->index = kv_size(img->frames_beyond_the_first) + 1; 832 } 833 frame->image = img; 834 gr_touch_frame(frame); 835 GR_LOG("Appending frame %d to image %u\n", frame->index, img->image_id); 836 return frame; 837 } 838 839 /// Creates a new placement with the given id. If a placement with that id 840 /// already exists, it is deleted first. If the provided id is 0, generates a 841 /// random id. 842 static ImagePlacement *gr_new_placement(Image *img, uint32_t id) { 843 if (id == 0) { 844 do { 845 // Currently we support only 24-bit IDs. 846 id = rand() & 0xFFFFFF; 847 // Avoid IDs that need only one byte. 848 } while ((id & 0x00FFFF00) == 0 || gr_find_placement(img, id)); 849 } 850 ImagePlacement *placement = gr_find_placement(img, id); 851 gr_delete_placement_keep_id(placement); 852 GR_LOG("Creating placement %u/%u\n", img->image_id, id); 853 placement = malloc(sizeof(ImagePlacement)); 854 memset(placement, 0, sizeof(ImagePlacement)); 855 total_placement_count++; 856 int ret; 857 khiter_t k = kh_put(id2placement, img->placements, id, &ret); 858 kh_value(img->placements, k) = placement; 859 placement->image = img; 860 placement->placement_id = id; 861 gr_touch_placement(placement); 862 if (img->default_placement == 0) 863 img->default_placement = id; 864 return placement; 865 } 866 867 static int64_t ceil_div(int64_t a, int64_t b) { 868 return (a + b - 1) / b; 869 } 870 871 /// Computes the best number of rows and columns for a placement if it's not 872 /// specified, and also adjusts the source rectangle size. 873 static void gr_infer_placement_size_maybe(ImagePlacement *placement) { 874 // The size of the image. 875 int image_pix_width = placement->image->pix_width; 876 int image_pix_height = placement->image->pix_height; 877 // Negative values are not allowed. Quietly set them to 0. 878 if (placement->src_pix_x < 0) 879 placement->src_pix_x = 0; 880 if (placement->src_pix_y < 0) 881 placement->src_pix_y = 0; 882 if (placement->src_pix_width < 0) 883 placement->src_pix_width = 0; 884 if (placement->src_pix_height < 0) 885 placement->src_pix_height = 0; 886 // If the source rectangle is outside the image, truncate it. 887 if (placement->src_pix_x > image_pix_width) 888 placement->src_pix_x = image_pix_width; 889 if (placement->src_pix_y > image_pix_height) 890 placement->src_pix_y = image_pix_height; 891 // If the source rectangle is not specified, use the whole image. If 892 // it's partially outside the image, truncate it. 893 if (placement->src_pix_width == 0 || 894 placement->src_pix_x + placement->src_pix_width > image_pix_width) 895 placement->src_pix_width = 896 image_pix_width - placement->src_pix_x; 897 if (placement->src_pix_height == 0 || 898 placement->src_pix_y + placement->src_pix_height > image_pix_height) 899 placement->src_pix_height = 900 image_pix_height - placement->src_pix_y; 901 902 if (placement->cols != 0 && placement->rows != 0) 903 return; 904 if (placement->src_pix_width == 0 || placement->src_pix_height == 0) 905 return; 906 if (current_cw == 0 || current_ch == 0) 907 return; 908 909 // If no size is specified, use the image size. 910 if (placement->cols == 0 && placement->rows == 0) { 911 placement->cols = 912 ceil_div(placement->src_pix_width, current_cw); 913 placement->rows = 914 ceil_div(placement->src_pix_height, current_ch); 915 return; 916 } 917 918 // Some applications specify only one of the dimensions. 919 if (placement->scale_mode == SCALE_MODE_CONTAIN) { 920 // If we preserve aspect ratio and fit to width/height, the most 921 // logical thing is to find the minimum size of the 922 // non-specified dimension that allows the image to fit the 923 // specified dimension. 924 if (placement->cols == 0) { 925 placement->cols = ceil_div( 926 placement->src_pix_width * placement->rows * 927 current_ch, 928 placement->src_pix_height * current_cw); 929 return; 930 } 931 if (placement->rows == 0) { 932 placement->rows = 933 ceil_div(placement->src_pix_height * 934 placement->cols * current_cw, 935 placement->src_pix_width * current_ch); 936 return; 937 } 938 } else { 939 // Otherwise we stretch the image or preserve the original size. 940 // In both cases we compute the best number of columns from the 941 // pixel size and cell size. 942 // TODO: In the case of stretching it's not the most logical 943 // thing to do, may need to revisit in the future. 944 // Currently we switch to SCALE_MODE_CONTAIN when only one 945 // of the dimensions is specified, so this case shouldn't 946 // happen in practice. 947 if (!placement->cols) 948 placement->cols = 949 ceil_div(placement->src_pix_width, current_cw); 950 if (!placement->rows) 951 placement->rows = 952 ceil_div(placement->src_pix_height, current_ch); 953 } 954 } 955 956 /// Adjusts the current frame index if enough time has passed since the display 957 /// of the current frame. Also computes the time of the next redraw of this 958 /// image (`img->next_redraw`). The current time is passed as an argument so 959 /// that all animations are in sync. 960 static void gr_update_frame_index(Image *img, Milliseconds now) { 961 if (img->current_frame == 0) { 962 img->current_frame_time = now; 963 img->current_frame = 1; 964 img->next_redraw = now + MAX(1, img->first_frame.gap); 965 return; 966 } 967 // If the animation is stopped, show the current frame. 968 if (!img->animation_state || 969 img->animation_state == ANIMATION_STATE_STOPPED || 970 img->animation_state == ANIMATION_STATE_UNSET) { 971 // The next redraw is never (unless the state is changed). 972 img->next_redraw = 0; 973 return; 974 } 975 int last_uploaded_frame_index = gr_last_uploaded_frame_index(img); 976 // If we are loading and we reached the last frame, show the last frame. 977 if (img->animation_state == ANIMATION_STATE_LOADING && 978 img->current_frame == last_uploaded_frame_index) { 979 // The next redraw is never (unless the state is changed or 980 // frames are added). 981 img->next_redraw = 0; 982 return; 983 } 984 985 // Check how many milliseconds passed since the current frame was shown. 986 int passed_ms = now - img->current_frame_time; 987 // If the animation is looping and too much time has passes, we can 988 // make a shortcut. 989 if (img->animation_state == ANIMATION_STATE_LOOPING && 990 img->total_duration > 0 && passed_ms >= img->total_duration) { 991 passed_ms %= img->total_duration; 992 img->current_frame_time = now - passed_ms; 993 } 994 // Find the next frame. 995 int original_frame_index = img->current_frame; 996 while (1) { 997 ImageFrame *frame = gr_get_frame(img, img->current_frame); 998 if (!frame) { 999 // The frame doesn't exist, go to the first frame. 1000 img->current_frame = 1; 1001 img->current_frame_time = now; 1002 img->next_redraw = now + MAX(1, img->first_frame.gap); 1003 return; 1004 } 1005 if (frame->gap >= 0 && passed_ms < frame->gap) { 1006 // Not enough time has passed, we are still in the same 1007 // frame, and it's not a gapless frame. 1008 img->next_redraw = 1009 img->current_frame_time + MAX(1, frame->gap); 1010 return; 1011 } 1012 // Otherwise go to the next frame. 1013 passed_ms -= MAX(0, frame->gap); 1014 if (img->current_frame >= last_uploaded_frame_index) { 1015 // It's the last frame, if the animation is loading, 1016 // remain on it. 1017 if (img->animation_state == ANIMATION_STATE_LOADING) { 1018 img->next_redraw = 0; 1019 return; 1020 } 1021 // Otherwise the animation is looping. 1022 img->current_frame = 1; 1023 // TODO: Support finite number of loops. 1024 } else { 1025 img->current_frame++; 1026 } 1027 // Make sure we don't get stuck in an infinite loop. 1028 if (img->current_frame == original_frame_index) { 1029 // We looped through all frames, but haven't reached the 1030 // next frame yet. This may happen if too much time has 1031 // passed since the last redraw or all the frames are 1032 // gapless. Just move on to the next frame. 1033 img->current_frame++; 1034 if (img->current_frame > 1035 last_uploaded_frame_index) 1036 img->current_frame = 1; 1037 img->current_frame_time = now; 1038 img->next_redraw = now + MAX( 1039 1, gr_get_frame(img, img->current_frame)->gap); 1040 return; 1041 } 1042 // Adjust the start time of the frame. The next redraw time will 1043 // be set in the next iteration. 1044 img->current_frame_time += MAX(0, frame->gap); 1045 } 1046 } 1047 1048 //////////////////////////////////////////////////////////////////////////////// 1049 // Unloading and deleting images to save resources. 1050 //////////////////////////////////////////////////////////////////////////////// 1051 1052 /// A helper to compare frames by atime for qsort. 1053 static int gr_cmp_frames_by_atime(const void *a, const void *b) { 1054 ImageFrame *frame_a = *(ImageFrame *const *)a; 1055 ImageFrame *frame_b = *(ImageFrame *const *)b; 1056 if (frame_a->atime == frame_b->atime) 1057 return frame_a->image->global_command_index - 1058 frame_b->image->global_command_index; 1059 return frame_a->atime - frame_b->atime; 1060 } 1061 1062 /// A helper to compare images by atime for qsort. 1063 static int gr_cmp_images_by_atime(const void *a, const void *b) { 1064 Image *img_a = *(Image *const *)a; 1065 Image *img_b = *(Image *const *)b; 1066 if (img_a->atime == img_b->atime) 1067 return img_a->global_command_index - 1068 img_b->global_command_index; 1069 return img_a->atime - img_b->atime; 1070 } 1071 1072 /// A helper to compare placements by atime for qsort. 1073 static int gr_cmp_placements_by_atime(const void *a, const void *b) { 1074 ImagePlacement *p_a = *(ImagePlacement **)a; 1075 ImagePlacement *p_b = *(ImagePlacement **)b; 1076 if (p_a->atime == p_b->atime) 1077 return p_a->image->global_command_index - 1078 p_b->image->global_command_index; 1079 return p_a->atime - p_b->atime; 1080 } 1081 1082 typedef kvec_t(Image *) ImageVec; 1083 typedef kvec_t(ImagePlacement *) ImagePlacementVec; 1084 typedef kvec_t(ImageFrame *) ImageFrameVec; 1085 1086 /// Returns an array of pointers to all images sorted by atime. 1087 static ImageVec gr_get_images_sorted_by_atime() { 1088 ImageVec vec; 1089 kv_init(vec); 1090 if (kh_size(images) == 0) 1091 return vec; 1092 kv_resize(Image *, vec, kh_size(images)); 1093 Image *img = NULL; 1094 kh_foreach_value(images, img, { kv_push(Image *, vec, img); }); 1095 qsort(vec.a, kv_size(vec), sizeof(Image *), gr_cmp_images_by_atime); 1096 return vec; 1097 } 1098 1099 /// Returns an array of pointers to all placements sorted by atime. 1100 static ImagePlacementVec gr_get_placements_sorted_by_atime() { 1101 ImagePlacementVec vec; 1102 kv_init(vec); 1103 if (total_placement_count == 0) 1104 return vec; 1105 kv_resize(ImagePlacement *, vec, total_placement_count); 1106 Image *img = NULL; 1107 ImagePlacement *placement = NULL; 1108 kh_foreach_value(images, img, { 1109 kh_foreach_value(img->placements, placement, { 1110 kv_push(ImagePlacement *, vec, placement); 1111 }); 1112 }); 1113 qsort(vec.a, kv_size(vec), sizeof(ImagePlacement *), 1114 gr_cmp_placements_by_atime); 1115 return vec; 1116 } 1117 1118 /// Returns an array of pointers to all frames sorted by atime. 1119 static ImageFrameVec gr_get_frames_sorted_by_atime() { 1120 ImageFrameVec frames; 1121 kv_init(frames); 1122 Image *img = NULL; 1123 kh_foreach_value(images, img, { 1124 foreach_frame(*img, frame, { 1125 kv_push(ImageFrame *, frames, frame); 1126 }); 1127 }); 1128 qsort(frames.a, kv_size(frames), sizeof(ImageFrame *), 1129 gr_cmp_frames_by_atime); 1130 return frames; 1131 } 1132 1133 /// An object that can be unloaded from RAM. 1134 typedef struct { 1135 /// Some score, probably based on access time. The lower the score, the 1136 /// more likely that the object should be unloaded. 1137 int64_t score; 1138 union { 1139 ImagePlacement *placement; 1140 ImageFrame *frame; 1141 }; 1142 /// If zero, the object is the imlib object of `frame`, if non-zero, 1143 /// the object is a pixmap of `frameidx`-th frame of `placement`. 1144 int frameidx; 1145 } UnloadableObject; 1146 1147 typedef kvec_t(UnloadableObject) UnloadableObjectVec; 1148 1149 /// A helper to compare unloadable objects by score for qsort. 1150 static int gr_cmp_unloadable_objects(const void *a, const void *b) { 1151 UnloadableObject *obj_a = (UnloadableObject *)a; 1152 UnloadableObject *obj_b = (UnloadableObject *)b; 1153 return obj_a->score - obj_b->score; 1154 } 1155 1156 /// Unloads an unloadable object from RAM. 1157 static void gr_unload_object(UnloadableObject *obj) { 1158 if (obj->frameidx) { 1159 if (obj->placement->protected_frame == obj->frameidx) 1160 return; 1161 gr_unload_pixmap(obj->placement, obj->frameidx); 1162 } else { 1163 gr_unload_frame(obj->frame); 1164 } 1165 } 1166 1167 /// Returns the recency threshold for an image. Frames that were accessed within 1168 /// this threshold from now are considered recent and may be handled 1169 /// differently because we may need them again very soon. 1170 static Milliseconds gr_recency_threshold(Image *img) { 1171 return img->total_duration * 2 + 1000; 1172 } 1173 1174 /// Creates an unloadable object for the imlib object of a frame. 1175 static UnloadableObject gr_unloadable_object_for_frame(Milliseconds now, 1176 ImageFrame *frame) { 1177 UnloadableObject obj = {0}; 1178 obj.frameidx = 0; 1179 obj.frame = frame; 1180 Milliseconds atime = frame->atime; 1181 obj.score = atime; 1182 if (atime >= now - gr_recency_threshold(frame->image)) { 1183 // This is a recent frame, probably from an active animation. 1184 // Score it above `now` to prefer unloading non-active frames. 1185 // Randomize the score because it's not very clear in which 1186 // order we want to unload them: reloading a frame may require 1187 // reloading other frames. 1188 obj.score = now + 1000 + rand() % 1000; 1189 } 1190 return obj; 1191 } 1192 1193 /// Creates an unloadable object for a pixmap. 1194 static UnloadableObject 1195 gr_unloadable_object_for_pixmap(Milliseconds now, ImageFrame *frame, 1196 ImagePlacement *placement) { 1197 UnloadableObject obj = {0}; 1198 obj.frameidx = frame->index; 1199 obj.placement = placement; 1200 obj.score = placement->atime; 1201 // Since we don't store pixmap atimes, use the 1202 // oldest atime of the frame and the placement. 1203 Milliseconds atime = MIN(placement->atime, frame->atime); 1204 obj.score = atime; 1205 if (atime >= now - gr_recency_threshold(frame->image)) { 1206 // This is a recent pixmap, probably from an active animation. 1207 // Score it above `now` to prefer unloading non-active frames. 1208 // Also assign higher scores to frames that are closer to the 1209 // current frame (more likely to be used soon). 1210 int num_frames = gr_last_frame_index(frame->image); 1211 int dist = frame->index - frame->image->current_frame; 1212 if (dist < 0) 1213 dist += num_frames; 1214 obj.score = 1215 now + 1000 + (num_frames - dist) * 1000 / num_frames; 1216 // If the pixmap is much larger than the imlib image, prefer to 1217 // unload the pixmap by adding up to -1000 to the score. If the 1218 // imlib image is larger, add up to +1000. 1219 float imlib_size = gr_frame_current_ram_size(frame); 1220 float pixmap_size = 1221 gr_placement_single_frame_ram_size(placement); 1222 obj.score += 1223 2000 * (imlib_size / (imlib_size + pixmap_size) - 0.5); 1224 } 1225 return obj; 1226 } 1227 1228 /// Returns an array of unloadable objects sorted by score. 1229 static UnloadableObjectVec 1230 gr_get_unloadable_objects_sorted_by_score(Milliseconds now) { 1231 UnloadableObjectVec objects; 1232 kv_init(objects); 1233 Image *img = NULL; 1234 ImagePlacement *placement = NULL; 1235 kh_foreach_value(images, img, { 1236 foreach_frame(*img, frame, { 1237 if (!frame->imlib_object) 1238 continue; 1239 kv_push(UnloadableObject, objects, 1240 gr_unloadable_object_for_frame(now, frame)); 1241 int frameidx = frame->index; 1242 kh_foreach_value(img->placements, placement, { 1243 if (!gr_get_frame_pixmap(placement, frameidx)) 1244 continue; 1245 kv_push(UnloadableObject, objects, 1246 gr_unloadable_object_for_pixmap( 1247 now, frame, placement)); 1248 }); 1249 }); 1250 }); 1251 qsort(objects.a, kv_size(objects), sizeof(UnloadableObject), 1252 gr_cmp_unloadable_objects); 1253 return objects; 1254 } 1255 1256 /// Returns the limit adjusted by the excess tolerance ratio. 1257 static inline unsigned apply_tolerance(unsigned limit) { 1258 return limit + (unsigned)(limit * graphics_excess_tolerance_ratio); 1259 } 1260 1261 /// Checks RAM and disk cache limits and deletes/unloads some images. 1262 static void gr_check_limits() { 1263 Milliseconds now = gr_now_ms(); 1264 ImageVec images_sorted = {0}; 1265 ImagePlacementVec placements_sorted = {0}; 1266 ImageFrameVec frames_sorted = {0}; 1267 UnloadableObjectVec objects_sorted = {0}; 1268 int images_begin = 0; 1269 int placements_begin = 0; 1270 char changed = 0; 1271 // First reduce the number of images if there are too many. 1272 if (kh_size(images) > apply_tolerance(graphics_max_total_placements)) { 1273 GR_LOG("Too many images: %d\n", kh_size(images)); 1274 changed = 1; 1275 images_sorted = gr_get_images_sorted_by_atime(); 1276 int to_delete = kv_size(images_sorted) - 1277 graphics_max_total_placements; 1278 for (; images_begin < to_delete; images_begin++) 1279 gr_delete_image(images_sorted.a[images_begin]); 1280 } 1281 // Then reduce the number of placements if there are too many. 1282 if (total_placement_count > 1283 apply_tolerance(graphics_max_total_placements)) { 1284 GR_LOG("Too many placements: %d\n", total_placement_count); 1285 changed = 1; 1286 placements_sorted = gr_get_placements_sorted_by_atime(); 1287 int to_delete = kv_size(placements_sorted) - 1288 graphics_max_total_placements; 1289 for (; placements_begin < to_delete; placements_begin++) { 1290 ImagePlacement *placement = 1291 placements_sorted.a[placements_begin]; 1292 if (placement->protected_frame) 1293 break; 1294 gr_delete_placement(placement); 1295 } 1296 } 1297 // Then reduce the size of the image file cache. The files correspond to 1298 // image frames. 1299 if (images_disk_size > 1300 apply_tolerance(graphics_total_file_cache_size)) { 1301 GR_LOG("Too big disk cache: %ld KiB\n", 1302 images_disk_size / 1024); 1303 changed = 1; 1304 frames_sorted = gr_get_frames_sorted_by_atime(); 1305 for (int i = 0; i < kv_size(frames_sorted); i++) { 1306 if (images_disk_size <= graphics_total_file_cache_size) 1307 break; 1308 gr_delete_imagefile(kv_A(frames_sorted, i)); 1309 } 1310 } 1311 // Then unload images from RAM. 1312 if (images_ram_size > apply_tolerance(graphics_max_total_ram_size)) { 1313 changed = 1; 1314 int frames_begin = 0; 1315 GR_LOG("Too much ram: %ld KiB\n", images_ram_size / 1024); 1316 objects_sorted = gr_get_unloadable_objects_sorted_by_score(now); 1317 for (int i = 0; i < kv_size(objects_sorted); i++) { 1318 if (images_ram_size <= graphics_max_total_ram_size) 1319 break; 1320 gr_unload_object(&kv_A(objects_sorted, i)); 1321 } 1322 } 1323 if (changed) { 1324 GR_LOG("After cleaning: ram: %ld KiB disk: %ld KiB " 1325 "img count: %d placement count: %d\n", 1326 images_ram_size / 1024, images_disk_size / 1024, 1327 kh_size(images), total_placement_count); 1328 } 1329 kv_destroy(images_sorted); 1330 kv_destroy(placements_sorted); 1331 kv_destroy(frames_sorted); 1332 kv_destroy(objects_sorted); 1333 } 1334 1335 /// Unloads all images by user request. 1336 void gr_unload_images_to_reduce_ram() { 1337 Image *img = NULL; 1338 ImagePlacement *placement = NULL; 1339 kh_foreach_value(images, img, { 1340 kh_foreach_value(img->placements, placement, { 1341 if (placement->protected_frame) 1342 continue; 1343 gr_unload_placement(placement); 1344 }); 1345 gr_unload_all_frames(img); 1346 }); 1347 } 1348 1349 //////////////////////////////////////////////////////////////////////////////// 1350 // Image loading. 1351 //////////////////////////////////////////////////////////////////////////////// 1352 1353 /// Copies `num_pixels` pixels (not bytes!) from a buffer `from` to an imlib2 1354 /// image data `to`. The format may be 24 (RGB) or 32 (RGBA), and it's converted 1355 /// to imlib2's representation, which is 0xAARRGGBB (having BGRA memory layout 1356 /// on little-endian architectures). 1357 static inline void gr_copy_pixels(DATA32 *to, unsigned char *from, int format, 1358 size_t num_pixels) { 1359 size_t pixel_size = format == 24 ? 3 : 4; 1360 if (format == 32) { 1361 for (unsigned i = 0; i < num_pixels; ++i) { 1362 unsigned byte_i = i * pixel_size; 1363 to[i] = ((DATA32)from[byte_i + 2]) | 1364 ((DATA32)from[byte_i + 1]) << 8 | 1365 ((DATA32)from[byte_i]) << 16 | 1366 ((DATA32)from[byte_i + 3]) << 24; 1367 } 1368 } else { 1369 for (unsigned i = 0; i < num_pixels; ++i) { 1370 unsigned byte_i = i * pixel_size; 1371 to[i] = ((DATA32)from[byte_i + 2]) | 1372 ((DATA32)from[byte_i + 1]) << 8 | 1373 ((DATA32)from[byte_i]) << 16 | 0xFF000000; 1374 } 1375 } 1376 } 1377 1378 /// Loads uncompressed RGB or RGBA image data from a file. 1379 static void gr_load_raw_pixel_data_uncompressed(DATA32 *data, FILE *file, 1380 int format, 1381 size_t total_pixels) { 1382 unsigned char chunk[BUFSIZ]; 1383 size_t pixel_size = format == 24 ? 3 : 4; 1384 size_t chunk_size_pix = BUFSIZ / 4; 1385 size_t chunk_size_bytes = chunk_size_pix * pixel_size; 1386 size_t bytes = total_pixels * pixel_size; 1387 for (size_t chunk_start_pix = 0; chunk_start_pix < total_pixels; 1388 chunk_start_pix += chunk_size_pix) { 1389 size_t read_size = fread(chunk, 1, chunk_size_bytes, file); 1390 size_t read_pixels = read_size / pixel_size; 1391 if (chunk_start_pix + read_pixels > total_pixels) 1392 read_pixels = total_pixels - chunk_start_pix; 1393 gr_copy_pixels(data + chunk_start_pix, chunk, format, 1394 read_pixels); 1395 } 1396 } 1397 1398 #define COMPRESSED_CHUNK_SIZE BUFSIZ 1399 #define DECOMPRESSED_CHUNK_SIZE (BUFSIZ * 4) 1400 1401 /// Loads compressed RGB or RGBA image data from a file. 1402 static int gr_load_raw_pixel_data_compressed(DATA32 *data, FILE *file, 1403 int format, size_t total_pixels) { 1404 size_t pixel_size = format == 24 ? 3 : 4; 1405 unsigned char compressed_chunk[COMPRESSED_CHUNK_SIZE]; 1406 unsigned char decompressed_chunk[DECOMPRESSED_CHUNK_SIZE]; 1407 1408 z_stream strm; 1409 strm.zalloc = Z_NULL; 1410 strm.zfree = Z_NULL; 1411 strm.opaque = Z_NULL; 1412 strm.next_out = decompressed_chunk; 1413 strm.avail_out = DECOMPRESSED_CHUNK_SIZE; 1414 strm.avail_in = 0; 1415 strm.next_in = Z_NULL; 1416 int ret = inflateInit(&strm); 1417 if (ret != Z_OK) 1418 return 1; 1419 1420 int error = 0; 1421 int progress = 0; 1422 size_t total_copied_pixels = 0; 1423 while (1) { 1424 // If we don't have enough data in the input buffer, try to read 1425 // from the file. 1426 if (strm.avail_in <= COMPRESSED_CHUNK_SIZE / 4) { 1427 // Move the existing data to the beginning. 1428 memmove(compressed_chunk, strm.next_in, strm.avail_in); 1429 strm.next_in = compressed_chunk; 1430 // Read more data. 1431 size_t bytes_read = fread( 1432 compressed_chunk + strm.avail_in, 1, 1433 COMPRESSED_CHUNK_SIZE - strm.avail_in, file); 1434 strm.avail_in += bytes_read; 1435 if (bytes_read != 0) 1436 progress = 1; 1437 } 1438 1439 // Try to inflate the data. 1440 int ret = inflate(&strm, Z_SYNC_FLUSH); 1441 if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) { 1442 error = 1; 1443 fprintf(stderr, 1444 "error: could not decompress the image, error " 1445 "%s\n", 1446 ret == Z_MEM_ERROR ? "Z_MEM_ERROR" 1447 : "Z_DATA_ERROR"); 1448 break; 1449 } 1450 1451 // Copy the data from the output buffer to the image. 1452 size_t full_pixels = 1453 (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) / pixel_size; 1454 // Make sure we don't overflow the image. 1455 if (full_pixels > total_pixels - total_copied_pixels) 1456 full_pixels = total_pixels - total_copied_pixels; 1457 if (full_pixels > 0) { 1458 // Copy pixels. 1459 gr_copy_pixels(data, decompressed_chunk, format, 1460 full_pixels); 1461 data += full_pixels; 1462 total_copied_pixels += full_pixels; 1463 if (total_copied_pixels >= total_pixels) { 1464 // We filled the whole image, there may be some 1465 // data left, but we just truncate it. 1466 break; 1467 } 1468 // Move the remaining data to the beginning. 1469 size_t copied_bytes = full_pixels * pixel_size; 1470 size_t leftover = 1471 (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) - 1472 copied_bytes; 1473 memmove(decompressed_chunk, 1474 decompressed_chunk + copied_bytes, leftover); 1475 strm.next_out -= copied_bytes; 1476 strm.avail_out += copied_bytes; 1477 progress = 1; 1478 } 1479 1480 // If we haven't made any progress, then we have reached the end 1481 // of both the file and the inflated data. 1482 if (!progress) 1483 break; 1484 progress = 0; 1485 } 1486 1487 inflateEnd(&strm); 1488 return error; 1489 } 1490 1491 #undef COMPRESSED_CHUNK_SIZE 1492 #undef DECOMPRESSED_CHUNK_SIZE 1493 1494 /// Load the image from a file containing raw pixel data (RGB or RGBA), the data 1495 /// may be compressed. 1496 static Imlib_Image gr_load_raw_pixel_data(ImageFrame *frame, 1497 const char *filename) { 1498 size_t total_pixels = frame->data_pix_width * frame->data_pix_height; 1499 if (total_pixels * 4 > graphics_max_single_image_ram_size) { 1500 fprintf(stderr, 1501 "error: image %u frame %u is too big too load: %zu > %u\n", 1502 frame->image->image_id, frame->index, total_pixels * 4, 1503 graphics_max_single_image_ram_size); 1504 return NULL; 1505 } 1506 1507 FILE* file = fopen(filename, "rb"); 1508 if (!file) { 1509 fprintf(stderr, 1510 "error: could not open image file: %s\n", 1511 sanitized_filename(filename)); 1512 return NULL; 1513 } 1514 1515 Imlib_Image image = imlib_create_image(frame->data_pix_width, 1516 frame->data_pix_height); 1517 if (!image) { 1518 fprintf(stderr, 1519 "error: could not create an image of size %d x %d\n", 1520 frame->data_pix_width, frame->data_pix_height); 1521 fclose(file); 1522 return NULL; 1523 } 1524 1525 imlib_context_set_image(image); 1526 imlib_image_set_has_alpha(1); 1527 DATA32* data = imlib_image_get_data(); 1528 1529 // The default format is 32. 1530 int format = frame->format ? frame->format : 32; 1531 1532 if (frame->compression == 0) { 1533 gr_load_raw_pixel_data_uncompressed(data, file, format, 1534 total_pixels); 1535 } else { 1536 int ret = gr_load_raw_pixel_data_compressed(data, file, format, 1537 total_pixels); 1538 if (ret != 0) { 1539 imlib_image_put_back_data(data); 1540 imlib_free_image(); 1541 fclose(file); 1542 return NULL; 1543 } 1544 } 1545 1546 fclose(file); 1547 imlib_image_put_back_data(data); 1548 return image; 1549 } 1550 1551 /// Loads the unscaled frame into RAM as an imlib object. The frame imlib object 1552 /// is fully composed on top of the background frame. If the frame is already 1553 /// loaded, does nothing. Loading may fail, in which case the status of the 1554 /// frame will be set to STATUS_RAM_LOADING_ERROR. 1555 static void gr_load_imlib_object(ImageFrame *frame) { 1556 if (frame->imlib_object) 1557 return; 1558 1559 // If the image is uninitialized or uploading has failed, or the file 1560 // has been deleted, we cannot load the image. 1561 if (frame->status < STATUS_UPLOADING_SUCCESS) 1562 return; 1563 if (frame->disk_size == 0) { 1564 if (frame->status != STATUS_RAM_LOADING_ERROR) { 1565 fprintf(stderr, 1566 "error: cached image was deleted: %u frame %u\n", 1567 frame->image->image_id, frame->index); 1568 } 1569 frame->status = STATUS_RAM_LOADING_ERROR; 1570 return; 1571 } 1572 1573 // Prevent recursive dependences between frames. 1574 if (frame->status == STATUS_RAM_LOADING_IN_PROGRESS) { 1575 fprintf(stderr, 1576 "error: recursive loading of image %u frame %u\n", 1577 frame->image->image_id, frame->index); 1578 frame->status = STATUS_RAM_LOADING_ERROR; 1579 return; 1580 } 1581 frame->status = STATUS_RAM_LOADING_IN_PROGRESS; 1582 1583 // Load the background frame if needed. Hopefully it's not recursive. 1584 ImageFrame *bg_frame = NULL; 1585 if (frame->background_frame_index) { 1586 bg_frame = gr_get_frame(frame->image, 1587 frame->background_frame_index); 1588 if (!bg_frame) { 1589 fprintf(stderr, 1590 "error: could not find background " 1591 "frame %d for image %u frame %d\n", 1592 frame->background_frame_index, 1593 frame->image->image_id, frame->index); 1594 frame->status = STATUS_RAM_LOADING_ERROR; 1595 return; 1596 } 1597 gr_load_imlib_object(bg_frame); 1598 if (!bg_frame->imlib_object) { 1599 fprintf(stderr, 1600 "error: could not load background frame %d for " 1601 "image %u frame %d\n", 1602 frame->background_frame_index, 1603 frame->image->image_id, frame->index); 1604 frame->status = STATUS_RAM_LOADING_ERROR; 1605 return; 1606 } 1607 } 1608 1609 // Load the frame data image. 1610 Imlib_Image frame_data_image = NULL; 1611 char filename[MAX_FILENAME_SIZE]; 1612 gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 1613 GR_LOG("Loading image: %s\n", sanitized_filename(filename)); 1614 if (frame->format == 100 || frame->format == 0) 1615 frame_data_image = imlib_load_image(filename); 1616 if (frame->format == 32 || frame->format == 24 || 1617 (!frame_data_image && frame->format == 0)) 1618 frame_data_image = gr_load_raw_pixel_data(frame, filename); 1619 this_redraw_cycle_loaded_files++; 1620 1621 if (!frame_data_image) { 1622 if (frame->status != STATUS_RAM_LOADING_ERROR) { 1623 fprintf(stderr, "error: could not load image: %s\n", 1624 sanitized_filename(filename)); 1625 } 1626 frame->status = STATUS_RAM_LOADING_ERROR; 1627 return; 1628 } 1629 1630 imlib_context_set_image(frame_data_image); 1631 int frame_data_width = imlib_image_get_width(); 1632 int frame_data_height = imlib_image_get_height(); 1633 GR_LOG("Successfully loaded, size %d x %d\n", frame_data_width, 1634 frame_data_height); 1635 // If imlib loading succeeded, and it is the first frame, set the 1636 // information about the original image size, unless it's already set. 1637 if (frame->index == 1 && frame->image->pix_width == 0 && 1638 frame->image->pix_height == 0) { 1639 frame->image->pix_width = frame_data_width; 1640 frame->image->pix_height = frame_data_height; 1641 } 1642 1643 int image_width = frame->image->pix_width; 1644 int image_height = frame->image->pix_height; 1645 1646 // Compose the image with the background color or frame. 1647 if (frame->background_color != 0 || bg_frame || 1648 image_width != frame_data_width || 1649 image_height != frame_data_height) { 1650 GR_LOG("Composing the frame bg = 0x%08X, bgframe = %d\n", 1651 frame->background_color, frame->background_frame_index); 1652 Imlib_Image composed_image = imlib_create_image( 1653 image_width, image_height); 1654 imlib_context_set_image(composed_image); 1655 imlib_image_set_has_alpha(1); 1656 imlib_context_set_anti_alias(0); 1657 1658 // Start with the background frame or color. 1659 imlib_context_set_blend(0); 1660 if (bg_frame && bg_frame->imlib_object) { 1661 imlib_blend_image_onto_image( 1662 bg_frame->imlib_object, 1, 0, 0, 1663 image_width, image_height, 0, 0, 1664 image_width, image_height); 1665 } else { 1666 int r = (frame->background_color >> 24) & 0xFF; 1667 int g = (frame->background_color >> 16) & 0xFF; 1668 int b = (frame->background_color >> 8) & 0xFF; 1669 int a = frame->background_color & 0xFF; 1670 imlib_context_set_color(r, g, b, a); 1671 imlib_image_fill_rectangle(0, 0, image_width, 1672 image_height); 1673 } 1674 1675 // Blend the frame data image onto the background. 1676 imlib_context_set_blend(1); 1677 imlib_blend_image_onto_image( 1678 frame_data_image, 1, 0, 0, frame->data_pix_width, 1679 frame->data_pix_height, frame->x, frame->y, 1680 frame->data_pix_width, frame->data_pix_height); 1681 1682 // Free the frame data image. 1683 imlib_context_set_image(frame_data_image); 1684 imlib_free_image(); 1685 1686 frame_data_image = composed_image; 1687 } 1688 1689 frame->imlib_object = frame_data_image; 1690 1691 images_ram_size += gr_frame_current_ram_size(frame); 1692 frame->status = STATUS_RAM_LOADING_SUCCESS; 1693 1694 GR_LOG("After loading image %u frame %d ram: %ld KiB (+ %u KiB)\n", 1695 frame->image->image_id, frame->index, 1696 images_ram_size / 1024, gr_frame_current_ram_size(frame) / 1024); 1697 } 1698 1699 /// Premultiplies the alpha channel of the image data. The data is an array of 1700 /// pixels such that each pixel is a 32-bit integer in the format 0xAARRGGBB. 1701 static void gr_premultiply_alpha(DATA32 *data, size_t num_pixels) { 1702 for (size_t i = 0; i < num_pixels; ++i) { 1703 DATA32 pixel = data[i]; 1704 unsigned char a = pixel >> 24; 1705 if (a == 0) { 1706 data[i] = 0; 1707 } else if (a != 255) { 1708 unsigned char b = (pixel & 0xFF) * a / 255; 1709 unsigned char g = ((pixel >> 8) & 0xFF) * a / 255; 1710 unsigned char r = ((pixel >> 16) & 0xFF) * a / 255; 1711 data[i] = (a << 24) | (r << 16) | (g << 8) | b; 1712 } 1713 } 1714 } 1715 1716 /// Creates a pixmap for the frame of an image placement. The pixmap contain the 1717 /// image data correctly scaled and fit to the box defined by the number of 1718 /// rows/columns of the image placement and the provided cell dimensions in 1719 /// pixels. If the placement is already loaded, it will be reloaded only if the 1720 /// cell dimensions have changed. 1721 Pixmap gr_load_pixmap(ImagePlacement *placement, int frameidx, int cw, int ch) { 1722 Image *img = placement->image; 1723 ImageFrame *frame = gr_get_frame(img, frameidx); 1724 1725 // Update the atime uncoditionally. 1726 gr_touch_placement(placement); 1727 if (frame) 1728 gr_touch_frame(frame); 1729 1730 // If cw or ch are different, unload all the pixmaps. 1731 if (placement->scaled_cw != cw || placement->scaled_ch != ch) { 1732 gr_unload_placement(placement); 1733 placement->scaled_cw = cw; 1734 placement->scaled_ch = ch; 1735 } 1736 1737 // If it's already loaded, do nothing. 1738 Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); 1739 if (pixmap) 1740 return pixmap; 1741 1742 GR_LOG("Loading placement: %u/%u frame %u\n", img->image_id, 1743 placement->placement_id, frameidx); 1744 1745 // Load the imlib object for the frame. 1746 if (!frame) { 1747 fprintf(stderr, 1748 "error: could not find frame %u for image %u\n", 1749 frameidx, img->image_id); 1750 return 0; 1751 } 1752 gr_load_imlib_object(frame); 1753 if (!frame->imlib_object) 1754 return 0; 1755 1756 // Infer the placement size if needed. 1757 gr_infer_placement_size_maybe(placement); 1758 1759 // Create the scaled image. This is temporary, we will scale it 1760 // appropriately, upload to the X server, and then delete immediately. 1761 int scaled_w = (int)placement->cols * cw; 1762 int scaled_h = (int)placement->rows * ch; 1763 if (scaled_w * scaled_h * 4 > graphics_max_single_image_ram_size) { 1764 fprintf(stderr, 1765 "error: placement %u/%u would be too big to load: %d x " 1766 "%d x 4 > %u\n", 1767 img->image_id, placement->placement_id, scaled_w, 1768 scaled_h, graphics_max_single_image_ram_size); 1769 return 0; 1770 } 1771 Imlib_Image scaled_image = imlib_create_image(scaled_w, scaled_h); 1772 if (!scaled_image) { 1773 fprintf(stderr, 1774 "error: imlib_create_image(%d, %d) returned " 1775 "null\n", 1776 scaled_w, scaled_h); 1777 return 0; 1778 } 1779 imlib_context_set_image(scaled_image); 1780 imlib_image_set_has_alpha(1); 1781 1782 // First fill the scaled image with the transparent color. 1783 imlib_context_set_blend(0); 1784 imlib_context_set_color(0, 0, 0, 0); 1785 imlib_image_fill_rectangle(0, 0, scaled_w, scaled_h); 1786 imlib_context_set_anti_alias(1); 1787 imlib_context_set_blend(1); 1788 1789 // The source rectangle. 1790 int src_x = placement->src_pix_x; 1791 int src_y = placement->src_pix_y; 1792 int src_w = placement->src_pix_width; 1793 int src_h = placement->src_pix_height; 1794 // Whether the box is too small to use the true size of the image. 1795 char box_too_small = scaled_w < src_w || scaled_h < src_h; 1796 char mode = placement->scale_mode; 1797 1798 // Then blend the original image onto the transparent background. 1799 if (src_w <= 0 || src_h <= 0) { 1800 fprintf(stderr, "warning: image of zero size\n"); 1801 } else if (mode == SCALE_MODE_FILL) { 1802 imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, 1803 src_y, src_w, src_h, 0, 0, 1804 scaled_w, scaled_h); 1805 } else if (mode == SCALE_MODE_NONE || 1806 (mode == SCALE_MODE_NONE_OR_CONTAIN && !box_too_small)) { 1807 imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, 1808 src_y, src_w, src_h, 0, 0, src_w, 1809 src_h); 1810 } else { 1811 if (mode != SCALE_MODE_CONTAIN && 1812 mode != SCALE_MODE_NONE_OR_CONTAIN) { 1813 fprintf(stderr, 1814 "warning: unknown scale mode %u, using " 1815 "'contain' instead\n", 1816 mode); 1817 } 1818 int dest_x, dest_y; 1819 int dest_w, dest_h; 1820 if (scaled_w * src_h > src_w * scaled_h) { 1821 // If the box is wider than the original image, fit to 1822 // height. 1823 dest_h = scaled_h; 1824 dest_y = 0; 1825 dest_w = src_w * scaled_h / src_h; 1826 dest_x = (scaled_w - dest_w) / 2; 1827 } else { 1828 // Otherwise, fit to width. 1829 dest_w = scaled_w; 1830 dest_x = 0; 1831 dest_h = src_h * scaled_w / src_w; 1832 dest_y = (scaled_h - dest_h) / 2; 1833 } 1834 imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, 1835 src_y, src_w, src_h, dest_x, 1836 dest_y, dest_w, dest_h); 1837 } 1838 1839 // XRender needs the alpha channel premultiplied. 1840 DATA32 *data = imlib_image_get_data(); 1841 gr_premultiply_alpha(data, scaled_w * scaled_h); 1842 1843 // Upload the image to the X server. 1844 Display *disp = imlib_context_get_display(); 1845 Visual *vis = imlib_context_get_visual(); 1846 Colormap cmap = imlib_context_get_colormap(); 1847 Drawable drawable = imlib_context_get_drawable(); 1848 if (!drawable) 1849 drawable = DefaultRootWindow(disp); 1850 pixmap = XCreatePixmap(disp, drawable, scaled_w, scaled_h, 32); 1851 XVisualInfo visinfo; 1852 XMatchVisualInfo(disp, DefaultScreen(disp), 32, TrueColor, &visinfo); 1853 XImage *ximage = XCreateImage(disp, visinfo.visual, 32, ZPixmap, 0, 1854 (char *)data, scaled_w, scaled_h, 32, 0); 1855 GC gc = XCreateGC(disp, pixmap, 0, NULL); 1856 XPutImage(disp, pixmap, gc, ximage, 0, 0, 0, 0, scaled_w, 1857 scaled_h); 1858 XFreeGC(disp, gc); 1859 // XDestroyImage will free the data as well, but it is managed by imlib, 1860 // so set it to NULL. 1861 ximage->data = NULL; 1862 XDestroyImage(ximage); 1863 imlib_image_put_back_data(data); 1864 imlib_free_image(); 1865 1866 // Assign the pixmap to the frame and increase the ram size. 1867 gr_set_frame_pixmap(placement, frameidx, pixmap); 1868 images_ram_size += gr_placement_single_frame_ram_size(placement); 1869 this_redraw_cycle_loaded_pixmaps++; 1870 1871 GR_LOG("After loading placement %u/%u frame %d ram: %ld KiB (+ %u " 1872 "KiB)\n", 1873 frame->image->image_id, placement->placement_id, frame->index, 1874 images_ram_size / 1024, 1875 gr_placement_single_frame_ram_size(placement) / 1024); 1876 1877 // Free up ram if needed, but keep the pixmap we've loaded no matter 1878 // what. 1879 placement->protected_frame = frameidx; 1880 gr_check_limits(); 1881 placement->protected_frame = 0; 1882 1883 return pixmap; 1884 } 1885 1886 //////////////////////////////////////////////////////////////////////////////// 1887 // Initialization and deinitialization. 1888 //////////////////////////////////////////////////////////////////////////////// 1889 1890 /// Creates a temporary directory. 1891 static int gr_create_cache_dir() { 1892 strncpy(cache_dir, graphics_cache_dir_template, sizeof(cache_dir)); 1893 if (!mkdtemp(cache_dir)) { 1894 fprintf(stderr, 1895 "error: could not create temporary dir from template " 1896 "%s\n", 1897 sanitized_filename(cache_dir)); 1898 return 0; 1899 } 1900 fprintf(stderr, "Graphics cache directory: %s\n", cache_dir); 1901 return 1; 1902 } 1903 1904 /// Checks whether `tmp_dir` exists and recreates it if it doesn't. 1905 static void gr_make_sure_tmpdir_exists() { 1906 struct stat st; 1907 if (stat(cache_dir, &st) == 0 && S_ISDIR(st.st_mode)) 1908 return; 1909 fprintf(stderr, 1910 "error: %s is not a directory, will need to create a new " 1911 "graphics cache directory\n", 1912 sanitized_filename(cache_dir)); 1913 gr_create_cache_dir(); 1914 } 1915 1916 /// Initialize the graphics module. 1917 void gr_init(Display *disp, Visual *vis, Colormap cm) { 1918 // Set the initialization time. 1919 clock_gettime(CLOCK_MONOTONIC, &initialization_time); 1920 1921 // Create the temporary dir. 1922 if (!gr_create_cache_dir()) 1923 abort(); 1924 1925 // Initialize imlib. 1926 imlib_context_set_display(disp); 1927 imlib_context_set_visual(vis); 1928 imlib_context_set_colormap(cm); 1929 imlib_context_set_anti_alias(1); 1930 imlib_context_set_blend(1); 1931 // Imlib2 checks only the file name when caching, which is not enough 1932 // for us since we reuse file names. Disable caching. 1933 imlib_set_cache_size(0); 1934 1935 // Prepare for color inversion. 1936 for (size_t i = 0; i < 256; ++i) 1937 reverse_table[i] = 255 - i; 1938 1939 // Create data structures. 1940 images = kh_init(id2image); 1941 kv_init(next_redraw_times); 1942 1943 atexit(gr_deinit); 1944 } 1945 1946 /// Deinitialize the graphics module. 1947 void gr_deinit() { 1948 // Remove the cache dir. 1949 remove(cache_dir); 1950 kv_destroy(next_redraw_times); 1951 if (images) { 1952 // Delete all images. 1953 gr_delete_all_images(); 1954 // Destroy the data structures. 1955 kh_destroy(id2image, images); 1956 images = NULL; 1957 } 1958 } 1959 1960 //////////////////////////////////////////////////////////////////////////////// 1961 // Dumping, debugging, and image preview. 1962 //////////////////////////////////////////////////////////////////////////////// 1963 1964 /// Returns a string containing a time difference in a human-readable format. 1965 /// Uses a static buffer, so be careful. 1966 static const char *gr_ago(Milliseconds diff) { 1967 static char result[32]; 1968 double seconds = (double)diff / 1000.0; 1969 if (seconds < 1) 1970 snprintf(result, sizeof(result), "%.2f sec ago", seconds); 1971 else if (seconds < 60) 1972 snprintf(result, sizeof(result), "%d sec ago", (int)seconds); 1973 else if (seconds < 3600) 1974 snprintf(result, sizeof(result), "%d min %d sec ago", 1975 (int)(seconds / 60), (int)(seconds) % 60); 1976 else { 1977 snprintf(result, sizeof(result), "%d hr %d min %d sec ago", 1978 (int)(seconds / 3600), (int)(seconds) % 3600 / 60, 1979 (int)(seconds) % 60); 1980 } 1981 return result; 1982 } 1983 1984 /// Prints to `file` with an indentation of `ind` spaces. 1985 static void fprintf_ind(FILE *file, int ind, const char *format, ...) { 1986 fprintf(file, "%*s", ind, ""); 1987 va_list args; 1988 va_start(args, format); 1989 vfprintf(file, format, args); 1990 va_end(args); 1991 } 1992 1993 /// Dumps the image info to `file` with an indentation of `ind` spaces. 1994 static void gr_dump_image_info(FILE *file, Image *img, int ind) { 1995 if (!img) { 1996 fprintf_ind(file, ind, "Image is NULL\n"); 1997 return; 1998 } 1999 Milliseconds now = gr_now_ms(); 2000 fprintf_ind(file, ind, "Image %u\n", img->image_id); 2001 ind += 4; 2002 fprintf_ind(file, ind, "number: %u\n", img->image_number); 2003 fprintf_ind(file, ind, "global command index: %lu\n", 2004 img->global_command_index); 2005 fprintf_ind(file, ind, "accessed: %ld %s\n", img->atime, 2006 gr_ago(now - img->atime)); 2007 fprintf_ind(file, ind, "pix size: %ux%u\n", img->pix_width, 2008 img->pix_height); 2009 fprintf_ind(file, ind, "cur frame start time: %ld %s\n", 2010 img->current_frame_time, 2011 gr_ago(now - img->current_frame_time)); 2012 if (img->next_redraw) 2013 fprintf_ind(file, ind, "next redraw: %ld in %ld ms\n", 2014 img->next_redraw, img->next_redraw - now); 2015 fprintf_ind(file, ind, "total disk size: %u KiB\n", 2016 img->total_disk_size / 1024); 2017 fprintf_ind(file, ind, "total duration: %d\n", img->total_duration); 2018 fprintf_ind(file, ind, "frames: %d\n", gr_last_frame_index(img)); 2019 fprintf_ind(file, ind, "cur frame: %d\n", img->current_frame); 2020 fprintf_ind(file, ind, "animation state: %d\n", img->animation_state); 2021 fprintf_ind(file, ind, "default_placement: %u\n", 2022 img->default_placement); 2023 } 2024 2025 /// Dumps the frame info to `file` with an indentation of `ind` spaces. 2026 static void gr_dump_frame_info(FILE *file, ImageFrame *frame, int ind) { 2027 if (!frame) { 2028 fprintf_ind(file, ind, "Frame is NULL\n"); 2029 return; 2030 } 2031 Milliseconds now = gr_now_ms(); 2032 fprintf_ind(file, ind, "Frame %d\n", frame->index); 2033 ind += 4; 2034 if (frame->index == 0) { 2035 fprintf_ind(file, ind, "NOT INITIALIZED\n"); 2036 return; 2037 } 2038 if (frame->uploading_failure) 2039 fprintf_ind(file, ind, "uploading failure: %s\n", 2040 image_uploading_failure_strings 2041 [frame->uploading_failure]); 2042 fprintf_ind(file, ind, "gap: %d\n", frame->gap); 2043 fprintf_ind(file, ind, "accessed: %ld %s\n", frame->atime, 2044 gr_ago(now - frame->atime)); 2045 fprintf_ind(file, ind, "data pix size: %ux%u\n", frame->data_pix_width, 2046 frame->data_pix_height); 2047 char filename[MAX_FILENAME_SIZE]; 2048 gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 2049 if (access(filename, F_OK) != -1) 2050 fprintf_ind(file, ind, "file: %s\n", 2051 sanitized_filename(filename)); 2052 else 2053 fprintf_ind(file, ind, "not on disk\n"); 2054 fprintf_ind(file, ind, "disk size: %u KiB\n", frame->disk_size / 1024); 2055 if (frame->imlib_object) { 2056 unsigned ram_size = gr_frame_current_ram_size(frame); 2057 fprintf_ind(file, ind, 2058 "loaded into ram, size: %d " 2059 "KiB\n", 2060 ram_size / 1024); 2061 } else { 2062 fprintf_ind(file, ind, "not loaded into ram\n"); 2063 } 2064 } 2065 2066 /// Dumps the placement info to `file` with an indentation of `ind` spaces. 2067 static void gr_dump_placement_info(FILE *file, ImagePlacement *placement, 2068 int ind) { 2069 if (!placement) { 2070 fprintf_ind(file, ind, "Placement is NULL\n"); 2071 return; 2072 } 2073 Milliseconds now = gr_now_ms(); 2074 fprintf_ind(file, ind, "Placement %u\n", placement->placement_id); 2075 ind += 4; 2076 fprintf_ind(file, ind, "accessed: %ld %s\n", placement->atime, 2077 gr_ago(now - placement->atime)); 2078 fprintf_ind(file, ind, "scale_mode: %u\n", placement->scale_mode); 2079 fprintf_ind(file, ind, "size: %u cols x %u rows\n", placement->cols, 2080 placement->rows); 2081 fprintf_ind(file, ind, "cell size: %ux%u\n", placement->scaled_cw, 2082 placement->scaled_ch); 2083 fprintf_ind(file, ind, "ram per frame: %u KiB\n", 2084 gr_placement_single_frame_ram_size(placement) / 1024); 2085 unsigned ram_size = gr_placement_current_ram_size(placement); 2086 fprintf_ind(file, ind, "ram size: %d KiB\n", ram_size / 1024); 2087 } 2088 2089 /// Dumps placement pixmaps to `file` with an indentation of `ind` spaces. 2090 static void gr_dump_placement_pixmaps(FILE *file, ImagePlacement *placement, 2091 int ind) { 2092 if (!placement) 2093 return; 2094 int frameidx = 1; 2095 foreach_pixmap(*placement, pixmap, { 2096 fprintf_ind(file, ind, "Frame %d pixmap %lu\n", frameidx, 2097 pixmap); 2098 ++frameidx; 2099 }); 2100 } 2101 2102 /// Dumps the internal state (images and placements) to stderr. 2103 void gr_dump_state() { 2104 FILE *file = stderr; 2105 int ind = 0; 2106 fprintf_ind(file, ind, "======= Graphics module state dump =======\n"); 2107 fprintf_ind(file, ind, 2108 "sizeof(Image) = %lu sizeof(ImageFrame) = %lu " 2109 "sizeof(ImagePlacement) = %lu\n", 2110 sizeof(Image), sizeof(ImageFrame), sizeof(ImagePlacement)); 2111 fprintf_ind(file, ind, "Image count: %u\n", kh_size(images)); 2112 fprintf_ind(file, ind, "Placement count: %u\n", total_placement_count); 2113 fprintf_ind(file, ind, "Estimated RAM usage: %ld KiB\n", 2114 images_ram_size / 1024); 2115 fprintf_ind(file, ind, "Estimated Disk usage: %ld KiB\n", 2116 images_disk_size / 1024); 2117 2118 Milliseconds now = gr_now_ms(); 2119 2120 int64_t images_ram_size_computed = 0; 2121 int64_t images_disk_size_computed = 0; 2122 2123 Image *img = NULL; 2124 ImagePlacement *placement = NULL; 2125 kh_foreach_value(images, img, { 2126 fprintf_ind(file, ind, "----------------\n"); 2127 gr_dump_image_info(file, img, 0); 2128 int64_t total_disk_size_computed = 0; 2129 int total_duration_computed = 0; 2130 foreach_frame(*img, frame, { 2131 gr_dump_frame_info(file, frame, 4); 2132 if (frame->image != img) 2133 fprintf_ind(file, 8, 2134 "ERROR: WRONG IMAGE POINTER\n"); 2135 total_duration_computed += frame->gap; 2136 images_disk_size_computed += frame->disk_size; 2137 total_disk_size_computed += frame->disk_size; 2138 if (frame->imlib_object) 2139 images_ram_size_computed += 2140 gr_frame_current_ram_size(frame); 2141 }); 2142 if (img->total_disk_size != total_disk_size_computed) { 2143 fprintf_ind(file, ind, 2144 " ERROR: total_disk_size is %u, but " 2145 "computed value is %ld\n", 2146 img->total_disk_size, total_disk_size_computed); 2147 } 2148 if (img->total_duration != total_duration_computed) { 2149 fprintf_ind(file, ind, 2150 " ERROR: total_duration is %d, but computed " 2151 "value is %d\n", 2152 img->total_duration, total_duration_computed); 2153 } 2154 kh_foreach_value(img->placements, placement, { 2155 gr_dump_placement_info(file, placement, 4); 2156 if (placement->image != img) 2157 fprintf_ind(file, 8, 2158 "ERROR: WRONG IMAGE POINTER\n"); 2159 fprintf_ind(file, 8, 2160 "Pixmaps:\n"); 2161 gr_dump_placement_pixmaps(file, placement, 12); 2162 unsigned ram_size = 2163 gr_placement_current_ram_size(placement); 2164 images_ram_size_computed += ram_size; 2165 }); 2166 }); 2167 if (images_ram_size != images_ram_size_computed) { 2168 fprintf_ind(file, ind, 2169 "ERROR: images_ram_size is %ld, but computed value " 2170 "is %ld\n", 2171 images_ram_size, images_ram_size_computed); 2172 } 2173 if (images_disk_size != images_disk_size_computed) { 2174 fprintf_ind(file, ind, 2175 "ERROR: images_disk_size is %ld, but computed value " 2176 "is %ld\n", 2177 images_disk_size, images_disk_size_computed); 2178 } 2179 fprintf_ind(file, ind, "===========================================\n"); 2180 } 2181 2182 /// Executes `command` with the name of the file corresponding to `image_id` as 2183 /// the argument. Executes xmessage with an error message on failure. 2184 // TODO: Currently we do this for the first frame only. Not sure what to do with 2185 // animations. 2186 void gr_preview_image(uint32_t image_id, const char *exec) { 2187 char command[256]; 2188 size_t len; 2189 Image *img = gr_find_image(image_id); 2190 if (img) { 2191 ImageFrame *frame = &img->first_frame; 2192 char filename[MAX_FILENAME_SIZE]; 2193 gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 2194 if (frame->disk_size == 0) { 2195 len = snprintf(command, 255, 2196 "xmessage 'Image with id=%u is not " 2197 "fully copied to %s'", 2198 image_id, sanitized_filename(filename)); 2199 } else { 2200 len = snprintf(command, 255, "%s %s &", exec, 2201 sanitized_filename(filename)); 2202 } 2203 } else { 2204 len = snprintf(command, 255, 2205 "xmessage 'Cannot find image with id=%u'", 2206 image_id); 2207 } 2208 if (len > 255) { 2209 fprintf(stderr, "error: command too long: %s\n", command); 2210 snprintf(command, 255, "xmessage 'error: command too long'"); 2211 } 2212 if (system(command) != 0) { 2213 fprintf(stderr, "error: could not execute command %s\n", 2214 command); 2215 } 2216 } 2217 2218 /// Executes `<st> -e less <file>` where <file> is the name of a temporary file 2219 /// containing the information about an image and placement, and <st> is 2220 /// specified with `st_executable`. 2221 void gr_show_image_info(uint32_t image_id, uint32_t placement_id, 2222 uint32_t imgcol, uint32_t imgrow, 2223 char is_classic_placeholder, int32_t diacritic_count, 2224 char *st_executable) { 2225 char filename[MAX_FILENAME_SIZE]; 2226 snprintf(filename, sizeof(filename), "%s/info-%u", cache_dir, image_id); 2227 FILE *file = fopen(filename, "w"); 2228 if (!file) { 2229 perror("fopen"); 2230 return; 2231 } 2232 // Basic information about the cell. 2233 fprintf(file, "image_id = %u = 0x%08X\n", image_id, image_id); 2234 fprintf(file, "placement_id = %u = 0x%08X\n", placement_id, placement_id); 2235 fprintf(file, "column = %d, row = %d\n", imgcol, imgrow); 2236 fprintf(file, "classic/unicode placeholder = %s\n", 2237 is_classic_placeholder ? "classic" : "unicode"); 2238 fprintf(file, "original diacritic count = %d\n", diacritic_count); 2239 // Information about the image and the placement. 2240 Image *img = gr_find_image(image_id); 2241 ImagePlacement *placement = gr_find_placement(img, placement_id); 2242 gr_dump_image_info(file, img, 0); 2243 gr_dump_placement_info(file, placement, 0); 2244 if (img) { 2245 fprintf(file, "Frames:\n"); 2246 foreach_frame(*img, frame, { 2247 gr_dump_frame_info(file, frame, 4); 2248 }); 2249 } 2250 if (placement) { 2251 fprintf(file, "Placement pixmaps:\n"); 2252 gr_dump_placement_pixmaps(file, placement, 4); 2253 } 2254 fclose(file); 2255 char *argv[] = {st_executable, "-e", "less", filename, NULL}; 2256 if (posix_spawnp(NULL, st_executable, NULL, NULL, argv, environ) != 0) { 2257 perror("posix_spawnp"); 2258 return; 2259 } 2260 } 2261 2262 //////////////////////////////////////////////////////////////////////////////// 2263 // Appending and displaying image rectangles. 2264 //////////////////////////////////////////////////////////////////////////////// 2265 2266 /// Displays debug information in the rectangle using colors col1 and col2. 2267 static void gr_displayinfo(Drawable buf, ImageRect *rect, int col1, int col2, 2268 const char *message) { 2269 int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; 2270 int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; 2271 Display *disp = imlib_context_get_display(); 2272 GC gc = XCreateGC(disp, buf, 0, NULL); 2273 char info[MAX_INFO_LEN]; 2274 if (rect->placement_id) 2275 snprintf(info, MAX_INFO_LEN, "%s%u/%u [%d:%d)x[%d:%d)", message, 2276 rect->image_id, rect->placement_id, 2277 rect->img_start_col, rect->img_end_col, 2278 rect->img_start_row, rect->img_end_row); 2279 else 2280 snprintf(info, MAX_INFO_LEN, "%s%u [%d:%d)x[%d:%d)", message, 2281 rect->image_id, rect->img_start_col, rect->img_end_col, 2282 rect->img_start_row, rect->img_end_row); 2283 XSetForeground(disp, gc, col1); 2284 XDrawString(disp, buf, gc, rect->screen_x_pix + 4, 2285 rect->screen_y_pix + h_pix - 3, info, strlen(info)); 2286 XSetForeground(disp, gc, col2); 2287 XDrawString(disp, buf, gc, rect->screen_x_pix + 2, 2288 rect->screen_y_pix + h_pix - 5, info, strlen(info)); 2289 XFreeGC(disp, gc); 2290 } 2291 2292 /// Draws a rectangle (bounding box) for debugging. 2293 static void gr_showrect(Drawable buf, ImageRect *rect) { 2294 int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; 2295 int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; 2296 Display *disp = imlib_context_get_display(); 2297 GC gc = XCreateGC(disp, buf, 0, NULL); 2298 XSetForeground(disp, gc, 0xFF00FF00); 2299 XDrawRectangle(disp, buf, gc, rect->screen_x_pix, rect->screen_y_pix, 2300 w_pix - 1, h_pix - 1); 2301 XSetForeground(disp, gc, 0xFFFF0000); 2302 XDrawRectangle(disp, buf, gc, rect->screen_x_pix + 1, 2303 rect->screen_y_pix + 1, w_pix - 3, h_pix - 3); 2304 XFreeGC(disp, gc); 2305 } 2306 2307 /// Updates the next redraw time for the given row. Resizes the 2308 /// next_redraw_times array if needed. 2309 static void gr_update_next_redraw_time(int row, Milliseconds next_redraw) { 2310 if (next_redraw == 0) 2311 return; 2312 if (row >= kv_size(next_redraw_times)) { 2313 size_t old_size = kv_size(next_redraw_times); 2314 kv_a(Milliseconds, next_redraw_times, row); 2315 for (size_t i = old_size; i <= row; ++i) 2316 kv_A(next_redraw_times, i) = 0; 2317 } 2318 Milliseconds old_value = kv_A(next_redraw_times, row); 2319 if (old_value == 0 || old_value > next_redraw) 2320 kv_A(next_redraw_times, row) = next_redraw; 2321 } 2322 2323 /// Draws the given part of an image. 2324 static void gr_drawimagerect(Drawable buf, ImageRect *rect) { 2325 ImagePlacement *placement = 2326 gr_find_image_and_placement(rect->image_id, rect->placement_id); 2327 // If the image does not exist or image display is switched off, draw 2328 // the bounding box. 2329 if (!placement || !graphics_display_images) { 2330 gr_showrect(buf, rect); 2331 if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 2332 gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2333 return; 2334 } 2335 2336 Image *img = placement->image; 2337 2338 if (img->last_redraw < drawing_start_time) { 2339 // This is the first time we draw this image in this redraw 2340 // cycle. Update the frame index we are going to display. Note 2341 // that currently all image placements are synchronized. 2342 int old_frame = img->current_frame; 2343 gr_update_frame_index(img, drawing_start_time); 2344 img->last_redraw = drawing_start_time; 2345 } 2346 2347 // Adjust next redraw times for the rows of this image rect. 2348 if (img->next_redraw) { 2349 for (int row = rect->screen_y_row; 2350 row <= rect->screen_y_row + rect->img_end_row - 2351 rect->img_start_row - 1; ++row) { 2352 gr_update_next_redraw_time( 2353 row, img->next_redraw); 2354 } 2355 } 2356 2357 // Load the frame. 2358 Pixmap pixmap = gr_load_pixmap(placement, img->current_frame, rect->cw, 2359 rect->ch); 2360 2361 // If the image couldn't be loaded, display the bounding box. 2362 if (!pixmap) { 2363 gr_showrect(buf, rect); 2364 if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 2365 gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2366 return; 2367 } 2368 2369 int src_x = rect->img_start_col * rect->cw; 2370 int src_y = rect->img_start_row * rect->ch; 2371 int width = (rect->img_end_col - rect->img_start_col) * rect->cw; 2372 int height = (rect->img_end_row - rect->img_start_row) * rect->ch; 2373 int dst_x = rect->screen_x_pix; 2374 int dst_y = rect->screen_y_pix; 2375 2376 // Display the image. 2377 Display *disp = imlib_context_get_display(); 2378 Visual *vis = imlib_context_get_visual(); 2379 2380 // Create an xrender picture for the window. 2381 XRenderPictFormat *win_format = 2382 XRenderFindVisualFormat(disp, vis); 2383 Picture window_pic = 2384 XRenderCreatePicture(disp, buf, win_format, 0, NULL); 2385 2386 // If needed, invert the image pixmap. Note that this naive approach of 2387 // inverting the pixmap is not entirely correct, because the pixmap is 2388 // premultiplied. But the result is good enough to visually indicate 2389 // selection. 2390 if (rect->reverse) { 2391 unsigned pixmap_w = 2392 (unsigned)placement->cols * placement->scaled_cw; 2393 unsigned pixmap_h = 2394 (unsigned)placement->rows * placement->scaled_ch; 2395 Pixmap invpixmap = 2396 XCreatePixmap(disp, buf, pixmap_w, pixmap_h, 32); 2397 XGCValues gcv = {.function = GXcopyInverted}; 2398 GC gc = XCreateGC(disp, invpixmap, GCFunction, &gcv); 2399 XCopyArea(disp, pixmap, invpixmap, gc, 0, 0, pixmap_w, 2400 pixmap_h, 0, 0); 2401 XFreeGC(disp, gc); 2402 pixmap = invpixmap; 2403 } 2404 2405 // Create a picture for the image pixmap. 2406 XRenderPictFormat *pic_format = 2407 XRenderFindStandardFormat(disp, PictStandardARGB32); 2408 Picture pixmap_pic = 2409 XRenderCreatePicture(disp, pixmap, pic_format, 0, NULL); 2410 2411 // Composite the image onto the window. In the reverse mode we ignore 2412 // the alpha channel of the image because the naive inversion above 2413 // seems to invert the alpha channel as well. 2414 int pictop = rect->reverse ? PictOpSrc : PictOpOver; 2415 XRenderComposite(disp, pictop, pixmap_pic, 0, window_pic, 2416 src_x, src_y, src_x, src_y, dst_x, dst_y, width, 2417 height); 2418 2419 // Free resources 2420 XRenderFreePicture(disp, pixmap_pic); 2421 XRenderFreePicture(disp, window_pic); 2422 if (rect->reverse) 2423 XFreePixmap(disp, pixmap); 2424 2425 // In debug mode always draw bounding boxes and print info. 2426 if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) { 2427 gr_showrect(buf, rect); 2428 gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); 2429 } 2430 } 2431 2432 /// Removes the given image rectangle. 2433 static void gr_freerect(ImageRect *rect) { memset(rect, 0, sizeof(ImageRect)); } 2434 2435 /// Returns the bottom coordinate of the rect. 2436 static int gr_getrectbottom(ImageRect *rect) { 2437 return rect->screen_y_pix + 2438 (rect->img_end_row - rect->img_start_row) * rect->ch; 2439 } 2440 2441 /// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. 2442 void gr_start_drawing(Drawable buf, int cw, int ch) { 2443 current_cw = cw; 2444 current_ch = ch; 2445 this_redraw_cycle_loaded_files = 0; 2446 this_redraw_cycle_loaded_pixmaps = 0; 2447 drawing_start_time = gr_now_ms(); 2448 imlib_context_set_drawable(buf); 2449 } 2450 2451 /// Finish image drawing. This functions will draw all the rectangles left to 2452 /// draw. 2453 void gr_finish_drawing(Drawable buf) { 2454 // Draw and then delete all known image rectangles. 2455 for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 2456 ImageRect *rect = &image_rects[i]; 2457 if (!rect->image_id) 2458 continue; 2459 gr_drawimagerect(buf, rect); 2460 gr_freerect(rect); 2461 } 2462 2463 // Compute the delay until the next redraw as the minimum of the next 2464 // redraw delays for all rows. 2465 Milliseconds drawing_end_time = gr_now_ms(); 2466 graphics_next_redraw_delay = INT_MAX; 2467 for (int row = 0; row < kv_size(next_redraw_times); ++row) { 2468 Milliseconds row_next_redraw = kv_A(next_redraw_times, row); 2469 if (row_next_redraw > 0) { 2470 int delay = MAX(graphics_animation_min_delay, 2471 row_next_redraw - drawing_end_time); 2472 graphics_next_redraw_delay = 2473 MIN(graphics_next_redraw_delay, delay); 2474 } 2475 } 2476 2477 // In debug mode display additional info. 2478 if (graphics_debug_mode) { 2479 int milliseconds = drawing_end_time - drawing_start_time; 2480 2481 Display *disp = imlib_context_get_display(); 2482 GC gc = XCreateGC(disp, buf, 0, NULL); 2483 const char *debug_mode_str = 2484 graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES 2485 ? "(boxes shown) " 2486 : ""; 2487 int redraw_delay = graphics_next_redraw_delay == INT_MAX 2488 ? -1 2489 : graphics_next_redraw_delay; 2490 char info[MAX_INFO_LEN]; 2491 snprintf(info, MAX_INFO_LEN, 2492 "%sRender time: %d ms ram %ld K disk %ld K count " 2493 "%d cell %dx%d delay %d", 2494 debug_mode_str, milliseconds, images_ram_size / 1024, 2495 images_disk_size / 1024, kh_size(images), current_cw, 2496 current_ch, redraw_delay); 2497 XSetForeground(disp, gc, 0xFF000000); 2498 XFillRectangle(disp, buf, gc, 0, 0, 600, 16); 2499 XSetForeground(disp, gc, 0xFFFFFFFF); 2500 XDrawString(disp, buf, gc, 0, 14, info, strlen(info)); 2501 XFreeGC(disp, gc); 2502 2503 if (milliseconds > 0) { 2504 fprintf(stderr, "%s (loaded %d files, %d pixmaps)\n", 2505 info, this_redraw_cycle_loaded_files, 2506 this_redraw_cycle_loaded_pixmaps); 2507 } 2508 } 2509 2510 // Check the limits in case we have used too much ram for placements. 2511 gr_check_limits(); 2512 } 2513 2514 // Add an image rectangle to the list of rectangles to draw. 2515 void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, 2516 int img_start_col, int img_end_col, int img_start_row, 2517 int img_end_row, int x_col, int y_row, int x_pix, 2518 int y_pix, int cw, int ch, int reverse) { 2519 current_cw = cw; 2520 current_ch = ch; 2521 2522 ImageRect new_rect; 2523 new_rect.image_id = image_id; 2524 new_rect.placement_id = placement_id; 2525 new_rect.img_start_col = img_start_col; 2526 new_rect.img_end_col = img_end_col; 2527 new_rect.img_start_row = img_start_row; 2528 new_rect.img_end_row = img_end_row; 2529 new_rect.screen_y_row = y_row; 2530 new_rect.screen_x_pix = x_pix; 2531 new_rect.screen_y_pix = y_pix; 2532 new_rect.ch = ch; 2533 new_rect.cw = cw; 2534 new_rect.reverse = reverse; 2535 2536 // Display some red text in debug mode. 2537 if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) 2538 gr_displayinfo(buf, &new_rect, 0xFF000000, 0xFFFF0000, "? "); 2539 2540 // If it's the empty image (image_id=0) or an empty rectangle, do 2541 // nothing. 2542 if (image_id == 0 || img_end_col - img_start_col <= 0 || 2543 img_end_row - img_start_row <= 0) 2544 return; 2545 // Try to find a rect to merge with. 2546 ImageRect *free_rect = NULL; 2547 for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 2548 ImageRect *rect = &image_rects[i]; 2549 if (rect->image_id == 0) { 2550 if (!free_rect) 2551 free_rect = rect; 2552 continue; 2553 } 2554 if (rect->image_id != image_id || 2555 rect->placement_id != placement_id || rect->cw != cw || 2556 rect->ch != ch || rect->reverse != reverse) 2557 continue; 2558 // We only support the case when the new stripe is added to the 2559 // bottom of an existing rectangle and they are perfectly 2560 // aligned. 2561 if (rect->img_end_row == img_start_row && 2562 gr_getrectbottom(rect) == y_pix) { 2563 if (rect->img_start_col == img_start_col && 2564 rect->img_end_col == img_end_col && 2565 rect->screen_x_pix == x_pix) { 2566 rect->img_end_row = img_end_row; 2567 return; 2568 } 2569 } 2570 } 2571 // If we haven't merged the new rect with any existing rect, and there 2572 // is no free rect, we have to render one of the existing rects. 2573 if (!free_rect) { 2574 for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { 2575 ImageRect *rect = &image_rects[i]; 2576 if (!free_rect || gr_getrectbottom(free_rect) > 2577 gr_getrectbottom(rect)) 2578 free_rect = rect; 2579 } 2580 gr_drawimagerect(buf, free_rect); 2581 gr_freerect(free_rect); 2582 } 2583 // Start a new rectangle in `free_rect`. 2584 *free_rect = new_rect; 2585 } 2586 2587 /// Mark rows containing animations as dirty if it's time to redraw them. Must 2588 /// be called right after `gr_start_drawing`. 2589 void gr_mark_dirty_animations(int *dirty, int rows) { 2590 if (rows < kv_size(next_redraw_times)) 2591 kv_size(next_redraw_times) = rows; 2592 if (rows * 2 < kv_max(next_redraw_times)) 2593 kv_resize(Milliseconds, next_redraw_times, rows); 2594 for (int i = 0; i < MIN(rows, kv_size(next_redraw_times)); ++i) { 2595 if (dirty[i]) { 2596 kv_A(next_redraw_times, i) = 0; 2597 continue; 2598 } 2599 Milliseconds next_update = kv_A(next_redraw_times, i); 2600 if (next_update > 0 && next_update <= drawing_start_time) { 2601 dirty[i] = 1; 2602 kv_A(next_redraw_times, i) = 0; 2603 } 2604 } 2605 } 2606 2607 //////////////////////////////////////////////////////////////////////////////// 2608 // Command parsing and handling. 2609 //////////////////////////////////////////////////////////////////////////////// 2610 2611 /// A parsed kitty graphics protocol command. 2612 typedef struct { 2613 /// The command itself, without the 'G'. 2614 char *command; 2615 /// The payload (after ';'). 2616 char *payload; 2617 /// 'a=', may be 't', 'q', 'f', 'T', 'p', 'd', 'a'. 2618 char action; 2619 /// 'q=', 1 to suppress OK response, 2 to suppress errors too. 2620 int quiet; 2621 /// 'f=', use 24 or 32 for raw pixel data, 100 to autodetect with 2622 /// imlib2. If 'f=0', will try to load with imlib2, then fallback to 2623 /// 32-bit pixel data. 2624 int format; 2625 /// 'o=', may be 'z' for RFC 1950 ZLIB. 2626 int compression; 2627 /// 't=', may be 'f', 't' or 'd'. 2628 char transmission_medium; 2629 /// 'd=' 2630 char delete_specifier; 2631 /// 's=', 'v=', if 'a=t' or 'a=T', used only when 'f=24' or 'f=32'. 2632 /// When 'a=f', this is the size of the frame rectangle when composed on 2633 /// top of another frame. 2634 int frame_pix_width, frame_pix_height; 2635 /// 'x=', 'y=' - top-left corner of the source rectangle. 2636 int src_pix_x, src_pix_y; 2637 /// 'w=', 'h=' - width and height of the source rectangle. 2638 int src_pix_width, src_pix_height; 2639 /// 'r=', 'c=' 2640 int rows, columns; 2641 /// 'i=' 2642 uint32_t image_id; 2643 /// 'I=' 2644 uint32_t image_number; 2645 /// 'p=' 2646 uint32_t placement_id; 2647 /// 'm=', may be 0 or 1. 2648 int more; 2649 /// True if either 'm=0' or 'm=1' is specified. 2650 char is_data_transmission; 2651 /// True if turns out that this command is a continuation of a data 2652 /// transmission and not the first one for this image. Populated by 2653 /// `gr_handle_transmit_command`. 2654 char is_direct_transmission_continuation; 2655 /// 'S=', used to check the size of uploaded data. 2656 int size; 2657 /// 'U=', whether it's a virtual placement for Unicode placeholders. 2658 int virtual; 2659 /// 'C=', if true, do not move the cursor when displaying this placement 2660 /// (non-virtual placements only). 2661 char do_not_move_cursor; 2662 // --------------------------------------------------------------------- 2663 // Animation-related fields. Their keys often overlap with keys of other 2664 // commands, so these make sense only if the action is 'a=f' (frame 2665 // transmission) or 'a=a' (animation control). 2666 // 2667 // 'x=' and 'y=', the relative position of the frame image when it's 2668 // composed on top of another frame. 2669 int frame_dst_pix_x, frame_dst_pix_y; 2670 /// 'X=', 'X=1' to replace colors instead of alpha blending on top of 2671 /// the background color or frame. 2672 char replace_instead_of_blending; 2673 /// 'Y=', the background color in the 0xRRGGBBAA format (still 2674 /// transmitted as a decimal number). 2675 uint32_t background_color; 2676 /// (Only for 'a=f'). 'c=', the 1-based index of the background frame. 2677 int background_frame; 2678 /// (Only for 'a=a'). 'c=', sets the index of the current frame. 2679 int current_frame; 2680 /// 'r=', the 1-based index of the frame to edit. 2681 int edit_frame; 2682 /// 'z=', the duration of the frame. Zero if not specified, negative if 2683 /// the frame is gapless (i.e. skipped). 2684 int gap; 2685 /// (Only for 'a=a'). 's=', if non-zero, sets the state of the 2686 /// animation, 1 to stop, 2 to run in loading mode, 3 to loop. 2687 int animation_state; 2688 /// (Only for 'a=a'). 'v=', if non-zero, sets the number of times the 2689 /// animation will loop. 1 to loop infinitely, N to loop N-1 times. 2690 int loops; 2691 } GraphicsCommand; 2692 2693 /// Replaces all non-printed characters in `str` with '?' and truncates the 2694 /// string to `max_size`, maybe inserting ellipsis at the end. 2695 static void sanitize_str(char *str, size_t max_size) { 2696 assert(max_size >= 4); 2697 for (size_t i = 0; i < max_size; ++i) { 2698 unsigned c = str[i]; 2699 if (c == '\0') 2700 return; 2701 if (c >= 128 || !isprint(c)) 2702 str[i] = '?'; 2703 } 2704 str[max_size - 1] = '\0'; 2705 str[max_size - 2] = '.'; 2706 str[max_size - 3] = '.'; 2707 str[max_size - 4] = '.'; 2708 } 2709 2710 /// A non-destructive version of `sanitize_str`. Uses a static buffer, so be 2711 /// careful. 2712 static const char *sanitized_filename(const char *str) { 2713 static char buf[MAX_FILENAME_SIZE]; 2714 strncpy(buf, str, sizeof(buf)); 2715 sanitize_str(buf, sizeof(buf)); 2716 return buf; 2717 } 2718 2719 /// Creates a response to the current command in `graphics_command_result`. 2720 static void gr_createresponse(uint32_t image_id, uint32_t image_number, 2721 uint32_t placement_id, const char *msg) { 2722 if (!image_id && !image_number && !placement_id) { 2723 // Nobody expects the response in this case, so just print it to 2724 // stderr. 2725 fprintf(stderr, 2726 "error: No image id or image number or placement_id, " 2727 "but still there is a response: %s\n", 2728 msg); 2729 return; 2730 } 2731 char *buf = graphics_command_result.response; 2732 size_t maxlen = MAX_GRAPHICS_RESPONSE_LEN; 2733 size_t written; 2734 written = snprintf(buf, maxlen, "\033_G"); 2735 buf += written; 2736 maxlen -= written; 2737 if (image_id) { 2738 written = snprintf(buf, maxlen, "i=%u,", image_id); 2739 buf += written; 2740 maxlen -= written; 2741 } 2742 if (image_number) { 2743 written = snprintf(buf, maxlen, "I=%u,", image_number); 2744 buf += written; 2745 maxlen -= written; 2746 } 2747 if (placement_id) { 2748 written = snprintf(buf, maxlen, "p=%u,", placement_id); 2749 buf += written; 2750 maxlen -= written; 2751 } 2752 buf[-1] = ';'; 2753 written = snprintf(buf, maxlen, "%s\033\\", msg); 2754 buf += written; 2755 maxlen -= written; 2756 buf[-2] = '\033'; 2757 buf[-1] = '\\'; 2758 } 2759 2760 /// Creates the 'OK' response to the current command, unless suppressed or a 2761 /// non-final data transmission. 2762 static void gr_reportsuccess_cmd(GraphicsCommand *cmd) { 2763 if (cmd->quiet < 1 && !cmd->more) 2764 gr_createresponse(cmd->image_id, cmd->image_number, 2765 cmd->placement_id, "OK"); 2766 } 2767 2768 /// Creates the 'OK' response to the current command (unless suppressed). 2769 static void gr_reportsuccess_frame(ImageFrame *frame) { 2770 uint32_t id = frame->image->query_id ? frame->image->query_id 2771 : frame->image->image_id; 2772 if (frame->quiet < 1) 2773 gr_createresponse(id, frame->image->image_number, 2774 frame->image->initial_placement_id, "OK"); 2775 } 2776 2777 /// Creates an error response to the current command (unless suppressed). 2778 static void gr_reporterror_cmd(GraphicsCommand *cmd, const char *format, ...) { 2779 char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; 2780 graphics_command_result.error = 1; 2781 va_list args; 2782 va_start(args, format); 2783 vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); 2784 va_end(args); 2785 2786 fprintf(stderr, "%s in command: %s\n", errmsg, cmd->command); 2787 if (cmd->quiet < 2) 2788 gr_createresponse(cmd->image_id, cmd->image_number, 2789 cmd->placement_id, errmsg); 2790 } 2791 2792 /// Creates an error response to the current command (unless suppressed). 2793 static void gr_reporterror_frame(ImageFrame *frame, const char *format, ...) { 2794 char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; 2795 graphics_command_result.error = 1; 2796 va_list args; 2797 va_start(args, format); 2798 vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); 2799 va_end(args); 2800 2801 if (!frame) { 2802 fprintf(stderr, "%s\n", errmsg); 2803 gr_createresponse(0, 0, 0, errmsg); 2804 } else { 2805 uint32_t id = frame->image->query_id ? frame->image->query_id 2806 : frame->image->image_id; 2807 fprintf(stderr, "%s id=%u\n", errmsg, id); 2808 if (frame->quiet < 2) 2809 gr_createresponse(id, frame->image->image_number, 2810 frame->image->initial_placement_id, 2811 errmsg); 2812 } 2813 } 2814 2815 /// Loads an image and creates a success/failure response. Returns `frame`, or 2816 /// NULL if it's a query action and the image was deleted. 2817 static ImageFrame *gr_loadimage_and_report(ImageFrame *frame) { 2818 gr_load_imlib_object(frame); 2819 if (!frame->imlib_object) { 2820 gr_reporterror_frame(frame, "EBADF: could not load image"); 2821 } else { 2822 gr_reportsuccess_frame(frame); 2823 } 2824 // If it was a query action, discard the image. 2825 if (frame->image->query_id) { 2826 gr_delete_image(frame->image); 2827 return NULL; 2828 } 2829 return frame; 2830 } 2831 2832 /// Creates an appropriate uploading failure response to the current command. 2833 static void gr_reportuploaderror(ImageFrame *frame) { 2834 switch (frame->uploading_failure) { 2835 case 0: 2836 return; 2837 case ERROR_CANNOT_OPEN_CACHED_FILE: 2838 gr_reporterror_frame(frame, 2839 "EIO: could not create a file for image"); 2840 break; 2841 case ERROR_OVER_SIZE_LIMIT: 2842 gr_reporterror_frame( 2843 frame, 2844 "EFBIG: the size of the uploaded image exceeded " 2845 "the image size limit %u", 2846 graphics_max_single_image_file_size); 2847 break; 2848 case ERROR_UNEXPECTED_SIZE: 2849 gr_reporterror_frame(frame, 2850 "EINVAL: the size of the uploaded image %u " 2851 "doesn't match the expected size %u", 2852 frame->disk_size, frame->expected_size); 2853 break; 2854 }; 2855 } 2856 2857 /// Displays a non-virtual placement. This functions records the information in 2858 /// `graphics_command_result`, the placeholder itself is created by the terminal 2859 /// after handling the current command in the graphics module. 2860 static void gr_display_nonvirtual_placement(ImagePlacement *placement) { 2861 if (placement->virtual) 2862 return; 2863 if (placement->image->first_frame.status < STATUS_RAM_LOADING_SUCCESS) 2864 return; 2865 // Infer the placement size if needed. 2866 gr_infer_placement_size_maybe(placement); 2867 // Populate the information about the placeholder which will be created 2868 // by the terminal. 2869 graphics_command_result.create_placeholder = 1; 2870 graphics_command_result.placeholder.image_id = placement->image->image_id; 2871 graphics_command_result.placeholder.placement_id = placement->placement_id; 2872 graphics_command_result.placeholder.columns = placement->cols; 2873 graphics_command_result.placeholder.rows = placement->rows; 2874 graphics_command_result.placeholder.do_not_move_cursor = 2875 placement->do_not_move_cursor; 2876 GR_LOG("Creating a placeholder for %u/%u %d x %d\n", 2877 placement->image->image_id, placement->placement_id, 2878 placement->cols, placement->rows); 2879 } 2880 2881 /// Marks the rows that are occupied by the image as dirty. 2882 static void gr_schedule_image_redraw(Image *img) { 2883 if (!img) 2884 return; 2885 gr_schedule_image_redraw_by_id(img->image_id); 2886 } 2887 2888 /// Appends data from `payload` to the frame `frame` when using direct 2889 /// transmission. Note that we report errors only for the final command 2890 /// (`!more`) to avoid spamming the client. If the frame is not specified, use 2891 /// the image id and frame index we are currently uploading. 2892 static void gr_append_data(ImageFrame *frame, const char *payload, int more) { 2893 if (!frame) { 2894 Image *img = gr_find_image(current_upload_image_id); 2895 frame = gr_get_frame(img, current_upload_frame_index); 2896 GR_LOG("Appending data to image %u frame %d\n", 2897 current_upload_image_id, current_upload_frame_index); 2898 if (!img) 2899 GR_LOG("ERROR: this image doesn't exist\n"); 2900 if (!frame) 2901 GR_LOG("ERROR: this frame doesn't exist\n"); 2902 } 2903 if (!more) { 2904 current_upload_image_id = 0; 2905 current_upload_frame_index = 0; 2906 } 2907 if (!frame) { 2908 if (!more) 2909 gr_reporterror_frame(NULL, "ENOENT: could not find the " 2910 "image to append data to"); 2911 return; 2912 } 2913 if (frame->status != STATUS_UPLOADING) { 2914 if (!more) 2915 gr_reportuploaderror(frame); 2916 return; 2917 } 2918 2919 // Decode the data. 2920 size_t data_size = 0; 2921 char *data = gr_base64dec(payload, &data_size); 2922 2923 GR_LOG("appending %u + %zu = %zu bytes\n", frame->disk_size, data_size, 2924 frame->disk_size + data_size); 2925 2926 // Do not append this data if the image exceeds the size limit. 2927 if (frame->disk_size + data_size > 2928 graphics_max_single_image_file_size || 2929 frame->expected_size > graphics_max_single_image_file_size) { 2930 free(data); 2931 gr_delete_imagefile(frame); 2932 frame->uploading_failure = ERROR_OVER_SIZE_LIMIT; 2933 if (!more) 2934 gr_reportuploaderror(frame); 2935 return; 2936 } 2937 2938 // If there is no open file corresponding to the image, create it. 2939 if (!frame->open_file) { 2940 gr_make_sure_tmpdir_exists(); 2941 char filename[MAX_FILENAME_SIZE]; 2942 gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); 2943 FILE *file = fopen(filename, frame->disk_size ? "a" : "w"); 2944 if (!file) { 2945 frame->status = STATUS_UPLOADING_ERROR; 2946 frame->uploading_failure = ERROR_CANNOT_OPEN_CACHED_FILE; 2947 if (!more) 2948 gr_reportuploaderror(frame); 2949 return; 2950 } 2951 frame->open_file = file; 2952 } 2953 2954 // Write data to the file and update disk size variables. 2955 fwrite(data, 1, data_size, frame->open_file); 2956 free(data); 2957 frame->disk_size += data_size; 2958 frame->image->total_disk_size += data_size; 2959 images_disk_size += data_size; 2960 gr_touch_frame(frame); 2961 2962 if (more) { 2963 current_upload_image_id = frame->image->image_id; 2964 current_upload_frame_index = frame->index; 2965 } else { 2966 current_upload_image_id = 0; 2967 current_upload_frame_index = 0; 2968 // Close the file. 2969 if (frame->open_file) { 2970 fclose(frame->open_file); 2971 frame->open_file = NULL; 2972 } 2973 frame->status = STATUS_UPLOADING_SUCCESS; 2974 uint32_t placement_id = frame->image->default_placement; 2975 if (frame->expected_size && 2976 frame->expected_size != frame->disk_size) { 2977 // Report failure if the uploaded image size doesn't 2978 // match the expected size. 2979 frame->status = STATUS_UPLOADING_ERROR; 2980 frame->uploading_failure = ERROR_UNEXPECTED_SIZE; 2981 gr_reportuploaderror(frame); 2982 } else { 2983 // Make sure to redraw all existing image instances. 2984 gr_schedule_image_redraw(frame->image); 2985 // Try to load the image into ram and report the result. 2986 frame = gr_loadimage_and_report(frame); 2987 // If there is a non-virtual image placement, we may 2988 // need to display it. 2989 if (frame && frame->index == 1) { 2990 Image *img = frame->image; 2991 ImagePlacement *placement = NULL; 2992 kh_foreach_value(img->placements, placement, { 2993 gr_display_nonvirtual_placement(placement); 2994 }); 2995 } 2996 } 2997 } 2998 2999 // Check whether we need to delete old images. 3000 gr_check_limits(); 3001 } 3002 3003 /// Finds the image either by id or by number specified in the command and sets 3004 /// the image_id of `cmd` if the image was found. 3005 static Image *gr_find_image_for_command(GraphicsCommand *cmd) { 3006 if (cmd->image_id) 3007 return gr_find_image(cmd->image_id); 3008 Image *img = NULL; 3009 // If the image number is not specified, we can't find the image, unless 3010 // it's a put command, in which case we will try the last image. 3011 if (cmd->image_number == 0 && cmd->action == 'p') 3012 img = gr_find_image(last_image_id); 3013 else 3014 img = gr_find_image_by_number(cmd->image_number); 3015 if (img) 3016 cmd->image_id = img->image_id; 3017 return img; 3018 } 3019 3020 /// Creates a new image or a new frame in an existing image (depending on the 3021 /// command's action) and initializes its parameters from the command. 3022 static ImageFrame *gr_new_image_or_frame_from_command(GraphicsCommand *cmd) { 3023 if (cmd->format != 0 && cmd->format != 32 && cmd->format != 24 && 3024 cmd->compression != 0) { 3025 gr_reporterror_cmd(cmd, "EINVAL: compression is supported only " 3026 "for raw pixel data (f=32 or f=24)"); 3027 // Even though we report an error, we still create an image. 3028 } 3029 3030 Image *img = NULL; 3031 if (cmd->action == 'f') { 3032 // If it's a frame transmission action, there must be an 3033 // existing image. 3034 img = gr_find_image_for_command(cmd); 3035 if (!img) { 3036 gr_reporterror_cmd(cmd, "ENOENT: image not found"); 3037 return NULL; 3038 } 3039 } else { 3040 // Otherwise create a new image object. If the action is `q`, 3041 // we'll use random id instead of the one specified in the 3042 // command. 3043 uint32_t image_id = cmd->action == 'q' ? 0 : cmd->image_id; 3044 img = gr_new_image(image_id); 3045 if (!img) 3046 return NULL; 3047 if (cmd->action == 'q') 3048 img->query_id = cmd->image_id; 3049 else if (!cmd->image_id) 3050 cmd->image_id = img->image_id; 3051 // Set the image number. 3052 img->image_number = cmd->image_number; 3053 } 3054 3055 ImageFrame *frame = gr_append_new_frame(img); 3056 // Initialize the frame. 3057 frame->expected_size = cmd->size; 3058 frame->format = cmd->format; 3059 frame->compression = cmd->compression; 3060 frame->background_color = cmd->background_color; 3061 frame->background_frame_index = cmd->background_frame; 3062 frame->gap = cmd->gap; 3063 img->total_duration += frame->gap; 3064 frame->blend = !cmd->replace_instead_of_blending; 3065 frame->data_pix_width = cmd->frame_pix_width; 3066 frame->data_pix_height = cmd->frame_pix_height; 3067 if (cmd->action == 'f') { 3068 frame->x = cmd->frame_dst_pix_x; 3069 frame->y = cmd->frame_dst_pix_y; 3070 } 3071 // We save the quietness information in the frame because for direct 3072 // transmission subsequent transmission command won't contain this info. 3073 frame->quiet = cmd->quiet; 3074 return frame; 3075 } 3076 3077 /// Removes a file if it actually looks like a temporary file. 3078 static void gr_delete_tmp_file(const char *filename) { 3079 if (strstr(filename, "tty-graphics-protocol") == NULL) 3080 return; 3081 if (strstr(filename, "/tmp/") != filename) { 3082 const char *tmpdir = getenv("TMPDIR"); 3083 if (!tmpdir || !tmpdir[0] || 3084 strstr(filename, tmpdir) != filename) 3085 return; 3086 } 3087 unlink(filename); 3088 } 3089 3090 /// Handles a data transmission command. 3091 static ImageFrame *gr_handle_transmit_command(GraphicsCommand *cmd) { 3092 // The default is direct transmission. 3093 if (!cmd->transmission_medium) 3094 cmd->transmission_medium = 'd'; 3095 3096 // If neither id, nor image number is specified, and the transmission 3097 // medium is 'd' (or unspecified), and there is an active direct upload, 3098 // this is a continuation of the upload. 3099 if (current_upload_image_id != 0 && cmd->image_id == 0 && 3100 cmd->image_number == 0 && cmd->transmission_medium == 'd') { 3101 cmd->image_id = current_upload_image_id; 3102 GR_LOG("No images id is specified, continuing uploading %u\n", 3103 cmd->image_id); 3104 } 3105 3106 ImageFrame *frame = NULL; 3107 if (cmd->transmission_medium == 'f' || 3108 cmd->transmission_medium == 't') { 3109 // File transmission. 3110 // Create a new image or a new frame of an existing image. 3111 frame = gr_new_image_or_frame_from_command(cmd); 3112 if (!frame) 3113 return NULL; 3114 last_image_id = frame->image->image_id; 3115 // Decode the filename. 3116 char *original_filename = gr_base64dec(cmd->payload, NULL); 3117 GR_LOG("Copying image %s\n", 3118 sanitized_filename(original_filename)); 3119 // Stat the file and check that it's a regular file and not too 3120 // big. 3121 struct stat st; 3122 int stat_res = stat(original_filename, &st); 3123 const char *stat_error = NULL; 3124 if (stat_res) 3125 stat_error = strerror(errno); 3126 else if (!S_ISREG(st.st_mode)) 3127 stat_error = "Not a regular file"; 3128 else if (st.st_size == 0) 3129 stat_error = "The size of the file is zero"; 3130 else if (st.st_size > graphics_max_single_image_file_size) 3131 stat_error = "The file is too large"; 3132 if (stat_error) { 3133 gr_reporterror_cmd(cmd, 3134 "EBADF: %s", stat_error); 3135 fprintf(stderr, "Could not load the file %s\n", 3136 sanitized_filename(original_filename)); 3137 frame->status = STATUS_UPLOADING_ERROR; 3138 frame->uploading_failure = ERROR_CANNOT_COPY_FILE; 3139 } else { 3140 gr_make_sure_tmpdir_exists(); 3141 // Build the filename for the cached copy of the file. 3142 char cache_filename[MAX_FILENAME_SIZE]; 3143 gr_get_frame_filename(frame, cache_filename, 3144 MAX_FILENAME_SIZE); 3145 // We will create a symlink to the original file, and 3146 // then copy the file to the temporary cache dir. We do 3147 // this symlink trick mostly to be able to use cp for 3148 // copying, and avoid escaping file name characters when 3149 // calling system at the same time. 3150 char tmp_filename_symlink[MAX_FILENAME_SIZE + 4] = {0}; 3151 strcat(tmp_filename_symlink, cache_filename); 3152 strcat(tmp_filename_symlink, ".sym"); 3153 char command[MAX_FILENAME_SIZE + 256]; 3154 size_t len = 3155 snprintf(command, MAX_FILENAME_SIZE + 255, 3156 "cp '%s' '%s'", tmp_filename_symlink, 3157 cache_filename); 3158 if (len > MAX_FILENAME_SIZE + 255 || 3159 symlink(original_filename, tmp_filename_symlink) || 3160 system(command) != 0) { 3161 gr_reporterror_cmd(cmd, 3162 "EBADF: could not copy the " 3163 "image to the cache dir"); 3164 fprintf(stderr, 3165 "Could not copy the image " 3166 "%s (symlink %s) to %s", 3167 sanitized_filename(original_filename), 3168 tmp_filename_symlink, cache_filename); 3169 frame->status = STATUS_UPLOADING_ERROR; 3170 frame->uploading_failure = ERROR_CANNOT_COPY_FILE; 3171 } else { 3172 // Get the file size of the copied file. 3173 frame->status = STATUS_UPLOADING_SUCCESS; 3174 frame->disk_size = st.st_size; 3175 frame->image->total_disk_size += st.st_size; 3176 images_disk_size += frame->disk_size; 3177 if (frame->expected_size && 3178 frame->expected_size != frame->disk_size) { 3179 // The file has unexpected size. 3180 frame->status = STATUS_UPLOADING_ERROR; 3181 frame->uploading_failure = 3182 ERROR_UNEXPECTED_SIZE; 3183 gr_reportuploaderror(frame); 3184 } else { 3185 // Everything seems fine, try to load 3186 // and redraw existing instances. 3187 gr_schedule_image_redraw(frame->image); 3188 frame = gr_loadimage_and_report(frame); 3189 } 3190 } 3191 // Delete the symlink. 3192 unlink(tmp_filename_symlink); 3193 // Delete the original file if it's temporary. 3194 if (cmd->transmission_medium == 't') 3195 gr_delete_tmp_file(original_filename); 3196 } 3197 free(original_filename); 3198 gr_check_limits(); 3199 } else if (cmd->transmission_medium == 'd') { 3200 // Direct transmission (default if 't' is not specified). 3201 frame = gr_get_last_frame(gr_find_image_for_command(cmd)); 3202 if (frame && frame->status == STATUS_UPLOADING) { 3203 // This is a continuation of the previous transmission. 3204 cmd->is_direct_transmission_continuation = 1; 3205 gr_append_data(frame, cmd->payload, cmd->more); 3206 return frame; 3207 } 3208 // If no action is specified, it's not the first transmission 3209 // command. If we couldn't find the image, something went wrong 3210 // and we should just drop this command. 3211 if (cmd->action == 0) 3212 return NULL; 3213 // Otherwise create a new image or frame structure. 3214 frame = gr_new_image_or_frame_from_command(cmd); 3215 if (!frame) 3216 return NULL; 3217 last_image_id = frame->image->image_id; 3218 frame->status = STATUS_UPLOADING; 3219 // Start appending data. 3220 gr_append_data(frame, cmd->payload, cmd->more); 3221 } else { 3222 gr_reporterror_cmd( 3223 cmd, 3224 "EINVAL: transmission medium '%c' is not supported", 3225 cmd->transmission_medium); 3226 return NULL; 3227 } 3228 3229 return frame; 3230 } 3231 3232 /// Handles the 'put' command by creating a placement. 3233 static void gr_handle_put_command(GraphicsCommand *cmd) { 3234 if (cmd->image_id == 0 && cmd->image_number == 0) { 3235 gr_reporterror_cmd(cmd, 3236 "EINVAL: neither image id nor image number " 3237 "are specified or both are zero"); 3238 return; 3239 } 3240 3241 // Find the image with the id or number. 3242 Image *img = gr_find_image_for_command(cmd); 3243 if (!img) { 3244 gr_reporterror_cmd(cmd, "ENOENT: image not found"); 3245 return; 3246 } 3247 3248 // Create a placement. If a placement with the same id already exists, 3249 // it will be deleted. If the id is zero, a random id will be generated. 3250 ImagePlacement *placement = gr_new_placement(img, cmd->placement_id); 3251 placement->virtual = cmd->virtual; 3252 placement->src_pix_x = cmd->src_pix_x; 3253 placement->src_pix_y = cmd->src_pix_y; 3254 placement->src_pix_width = cmd->src_pix_width; 3255 placement->src_pix_height = cmd->src_pix_height; 3256 placement->cols = cmd->columns; 3257 placement->rows = cmd->rows; 3258 placement->do_not_move_cursor = cmd->do_not_move_cursor; 3259 3260 if (placement->virtual) { 3261 placement->scale_mode = SCALE_MODE_CONTAIN; 3262 } else if (placement->cols && placement->rows) { 3263 // For classic placements the default is to stretch the image if 3264 // both cols and rows are specified. 3265 placement->scale_mode = SCALE_MODE_FILL; 3266 } else if (placement->cols || placement->rows) { 3267 // But if only one of them is specified, the default is to 3268 // contain. 3269 placement->scale_mode = SCALE_MODE_CONTAIN; 3270 } else { 3271 // If none of them are specified, the default is to use the 3272 // original size. 3273 placement->scale_mode = SCALE_MODE_NONE; 3274 } 3275 3276 // Display the placement unless it's virtual. 3277 gr_display_nonvirtual_placement(placement); 3278 3279 // Report success. 3280 gr_reportsuccess_cmd(cmd); 3281 } 3282 3283 /// Information about what to delete. 3284 typedef struct DeletionData { 3285 uint32_t image_id; 3286 uint32_t placement_id; 3287 /// If true, delete the image object if there are no more placements. 3288 char delete_image_if_no_ref; 3289 } DeletionData; 3290 3291 /// The callback called for each cell to perform deletion. 3292 static int gr_deletion_callback(void *data, uint32_t image_id, 3293 uint32_t placement_id, int col, 3294 int row, char is_classic) { 3295 DeletionData *del_data = data; 3296 // Leave unicode placeholders alone. 3297 if (!is_classic) 3298 return 0; 3299 if (del_data->image_id && del_data->image_id != image_id) 3300 return 0; 3301 if (del_data->placement_id && del_data->placement_id != placement_id) 3302 return 0; 3303 Image *img = gr_find_image(image_id); 3304 // If the image is already deleted, just erase the placeholder. 3305 if (!img) 3306 return 1; 3307 // Delete the placement. 3308 if (placement_id) 3309 gr_delete_placement(gr_find_placement(img, placement_id)); 3310 // Delete the image if image deletion is requested (uppercase delete 3311 // specifier) and there are no more placements. 3312 if (del_data->delete_image_if_no_ref && kh_size(img->placements) == 0) 3313 gr_delete_image(img); 3314 return 1; 3315 } 3316 3317 /// Handles the delete command. 3318 static void gr_handle_delete_command(GraphicsCommand *cmd) { 3319 DeletionData del_data = {0}; 3320 del_data.delete_image_if_no_ref = isupper(cmd->delete_specifier) != 0; 3321 char d = tolower(cmd->delete_specifier); 3322 3323 if (d == 'n') { 3324 d = 'i'; 3325 Image *img = gr_find_image_by_number(cmd->image_number); 3326 if (!img) 3327 return; 3328 del_data.image_id = img->image_id; 3329 } 3330 3331 if (!d || d == 'a') { 3332 // Delete all visible placements. 3333 gr_for_each_image_cell(gr_deletion_callback, &del_data); 3334 } else if (d == 'i') { 3335 // Delete the specified image by image id and maybe placement 3336 // id. 3337 if (!del_data.image_id) 3338 del_data.image_id = cmd->image_id; 3339 if (!del_data.image_id) { 3340 fprintf(stderr, 3341 "ERROR: image id is not specified in the " 3342 "delete command\n"); 3343 return; 3344 } 3345 del_data.placement_id = cmd->placement_id; 3346 // NOTE: It's not very clear whether we should delete the image 3347 // even if there are no _visible_ placements to delete. We do 3348 // this because otherwise there is no way to delete an image 3349 // with virtual placements in one command. 3350 if (!del_data.placement_id && del_data.delete_image_if_no_ref) 3351 gr_delete_image(gr_find_image(cmd->image_id)); 3352 gr_for_each_image_cell(gr_deletion_callback, &del_data); 3353 } else { 3354 fprintf(stderr, 3355 "WARNING: unsupported value of the d key: '%c'. The " 3356 "command is ignored.\n", 3357 cmd->delete_specifier); 3358 } 3359 } 3360 3361 static void gr_handle_animation_control_command(GraphicsCommand *cmd) { 3362 if (cmd->image_id == 0 && cmd->image_number == 0) { 3363 gr_reporterror_cmd(cmd, 3364 "EINVAL: neither image id nor image number " 3365 "are specified or both are zero"); 3366 return; 3367 } 3368 3369 // Find the image with the id or number. 3370 Image *img = gr_find_image_for_command(cmd); 3371 if (!img) { 3372 gr_reporterror_cmd(cmd, "ENOENT: image not found"); 3373 return; 3374 } 3375 3376 // Find the frame to edit, if requested. 3377 ImageFrame *frame = NULL; 3378 if (cmd->edit_frame) 3379 frame = gr_get_frame(img, cmd->edit_frame); 3380 if (cmd->edit_frame || cmd->gap) { 3381 if (!frame) { 3382 gr_reporterror_cmd(cmd, "ENOENT: frame %d not found", 3383 cmd->edit_frame); 3384 return; 3385 } 3386 if (cmd->gap) { 3387 img->total_duration -= frame->gap; 3388 frame->gap = cmd->gap; 3389 img->total_duration += frame->gap; 3390 } 3391 } 3392 3393 // Set animation-related parameters of the image. 3394 if (cmd->current_frame) 3395 img->current_frame = cmd->current_frame; 3396 if (cmd->animation_state) { 3397 if (cmd->animation_state == 1) { 3398 img->animation_state = ANIMATION_STATE_STOPPED; 3399 } else if (cmd->animation_state == 2) { 3400 img->animation_state = ANIMATION_STATE_LOADING; 3401 } else if (cmd->animation_state == 3) { 3402 img->animation_state = ANIMATION_STATE_LOOPING; 3403 } else { 3404 gr_reporterror_cmd( 3405 cmd, "EINVAL: invalid animation state: %d", 3406 cmd->animation_state); 3407 } 3408 } 3409 // TODO: Set the number of loops to cmd->loops 3410 3411 // Make sure we redraw all instances of the image. 3412 gr_schedule_image_redraw(img); 3413 } 3414 3415 /// Handles a command. 3416 static void gr_handle_command(GraphicsCommand *cmd) { 3417 if (!cmd->image_id && !cmd->image_number) { 3418 // If there is no image id or image number, nobody expects a 3419 // response, so set quiet to 2. 3420 cmd->quiet = 2; 3421 } 3422 ImageFrame *frame = NULL; 3423 switch (cmd->action) { 3424 case 0: 3425 // If no action is specified, it may be a data transmission 3426 // command if 'm=' is specified. 3427 if (cmd->is_data_transmission) { 3428 gr_handle_transmit_command(cmd); 3429 break; 3430 } 3431 gr_reporterror_cmd(cmd, "EINVAL: no action specified"); 3432 break; 3433 case 't': 3434 case 'q': 3435 case 'f': 3436 // Transmit data. 'q' means query, which is basically the same 3437 // as transmit, but the image is discarded, and the id is fake. 3438 // 'f' appends a frame to an existing image. 3439 gr_handle_transmit_command(cmd); 3440 break; 3441 case 'p': 3442 // Display (put) the image. 3443 gr_handle_put_command(cmd); 3444 break; 3445 case 'T': 3446 // Transmit and display. 3447 frame = gr_handle_transmit_command(cmd); 3448 if (frame && !cmd->is_direct_transmission_continuation) { 3449 gr_handle_put_command(cmd); 3450 if (cmd->placement_id) 3451 frame->image->initial_placement_id = 3452 cmd->placement_id; 3453 } 3454 break; 3455 case 'd': 3456 gr_handle_delete_command(cmd); 3457 break; 3458 case 'a': 3459 gr_handle_animation_control_command(cmd); 3460 break; 3461 default: 3462 gr_reporterror_cmd(cmd, "EINVAL: unsupported action: %c", 3463 cmd->action); 3464 return; 3465 } 3466 } 3467 3468 /// A partially parsed key-value pair. 3469 typedef struct KeyAndValue { 3470 char *key_start; 3471 char *val_start; 3472 unsigned key_len, val_len; 3473 } KeyAndValue; 3474 3475 /// Parses the value of a key and assigns it to the appropriate field of `cmd`. 3476 static void gr_set_keyvalue(GraphicsCommand *cmd, KeyAndValue *kv) { 3477 char *key_start = kv->key_start; 3478 char *key_end = key_start + kv->key_len; 3479 char *value_start = kv->val_start; 3480 char *value_end = value_start + kv->val_len; 3481 // Currently all keys are one-character. 3482 if (key_end - key_start != 1) { 3483 gr_reporterror_cmd(cmd, "EINVAL: unknown key of length %ld: %s", 3484 key_end - key_start, key_start); 3485 return; 3486 } 3487 long num = 0; 3488 if (*key_start == 'a' || *key_start == 't' || *key_start == 'd' || 3489 *key_start == 'o') { 3490 // Some keys have one-character values. 3491 if (value_end - value_start != 1) { 3492 gr_reporterror_cmd( 3493 cmd, 3494 "EINVAL: value of 'a', 't' or 'd' must be a " 3495 "single char: %s", 3496 key_start); 3497 return; 3498 } 3499 } else { 3500 // All the other keys have integer values. 3501 char *num_end = NULL; 3502 num = strtol(value_start, &num_end, 10); 3503 if (num_end != value_end) { 3504 gr_reporterror_cmd( 3505 cmd, "EINVAL: could not parse number value: %s", 3506 key_start); 3507 return; 3508 } 3509 } 3510 switch (*key_start) { 3511 case 'a': 3512 cmd->action = *value_start; 3513 break; 3514 case 't': 3515 cmd->transmission_medium = *value_start; 3516 break; 3517 case 'd': 3518 cmd->delete_specifier = *value_start; 3519 break; 3520 case 'q': 3521 cmd->quiet = num; 3522 break; 3523 case 'f': 3524 cmd->format = num; 3525 if (num != 0 && num != 24 && num != 32 && num != 100) { 3526 gr_reporterror_cmd( 3527 cmd, 3528 "EINVAL: unsupported format specification: %s", 3529 key_start); 3530 } 3531 break; 3532 case 'o': 3533 cmd->compression = *value_start; 3534 if (cmd->compression != 'z') { 3535 gr_reporterror_cmd(cmd, 3536 "EINVAL: unsupported compression " 3537 "specification: %s", 3538 key_start); 3539 } 3540 break; 3541 case 's': 3542 if (cmd->action == 'a') 3543 cmd->animation_state = num; 3544 else 3545 cmd->frame_pix_width = num; 3546 break; 3547 case 'v': 3548 if (cmd->action == 'a') 3549 cmd->loops = num; 3550 else 3551 cmd->frame_pix_height = num; 3552 break; 3553 case 'i': 3554 cmd->image_id = num; 3555 break; 3556 case 'I': 3557 cmd->image_number = num; 3558 break; 3559 case 'p': 3560 cmd->placement_id = num; 3561 break; 3562 case 'x': 3563 cmd->src_pix_x = num; 3564 cmd->frame_dst_pix_x = num; 3565 break; 3566 case 'y': 3567 if (cmd->action == 'f') 3568 cmd->frame_dst_pix_y = num; 3569 else 3570 cmd->src_pix_y = num; 3571 break; 3572 case 'w': 3573 cmd->src_pix_width = num; 3574 break; 3575 case 'h': 3576 cmd->src_pix_height = num; 3577 break; 3578 case 'c': 3579 if (cmd->action == 'f') 3580 cmd->background_frame = num; 3581 else if (cmd->action == 'a') 3582 cmd->current_frame = num; 3583 else 3584 cmd->columns = num; 3585 break; 3586 case 'r': 3587 if (cmd->action == 'f' || cmd->action == 'a') 3588 cmd->edit_frame = num; 3589 else 3590 cmd->rows = num; 3591 break; 3592 case 'm': 3593 cmd->is_data_transmission = 1; 3594 cmd->more = num; 3595 break; 3596 case 'S': 3597 cmd->size = num; 3598 break; 3599 case 'U': 3600 cmd->virtual = num; 3601 break; 3602 case 'X': 3603 if (cmd->action == 'f') 3604 cmd->replace_instead_of_blending = num; 3605 else 3606 break; /*ignore*/ 3607 break; 3608 case 'Y': 3609 if (cmd->action == 'f') 3610 cmd->background_color = num; 3611 else 3612 break; /*ignore*/ 3613 break; 3614 case 'z': 3615 if (cmd->action == 'f' || cmd->action == 'a') 3616 cmd->gap = num; 3617 else 3618 break; /*ignore*/ 3619 break; 3620 case 'C': 3621 cmd->do_not_move_cursor = num; 3622 break; 3623 default: 3624 gr_reporterror_cmd(cmd, "EINVAL: unsupported key: %s", 3625 key_start); 3626 return; 3627 } 3628 } 3629 3630 /// Parse and execute a graphics command. `buf` must start with 'G' and contain 3631 /// at least `len + 1` characters. Returns 1 on success. 3632 int gr_parse_command(char *buf, size_t len) { 3633 if (buf[0] != 'G') 3634 return 0; 3635 3636 memset(&graphics_command_result, 0, sizeof(GraphicsCommandResult)); 3637 3638 global_command_counter++; 3639 GR_LOG("### Command %lu: %.80s\n", global_command_counter, buf); 3640 3641 // Eat the 'G'. 3642 ++buf; 3643 --len; 3644 3645 GraphicsCommand cmd = {.command = buf}; 3646 // The state of parsing. 'k' to parse key, 'v' to parse value, 'p' to 3647 // parse the payload. 3648 char state = 'k'; 3649 // An array of partially parsed key-value pairs. 3650 KeyAndValue key_vals[32]; 3651 unsigned key_vals_count = 0; 3652 char *key_start = buf; 3653 char *key_end = NULL; 3654 char *val_start = NULL; 3655 char *val_end = NULL; 3656 char *c = buf; 3657 while (c - buf < len + 1) { 3658 if (state == 'k') { 3659 switch (*c) { 3660 case ',': 3661 case ';': 3662 case '\0': 3663 state = *c == ',' ? 'k' : 'p'; 3664 key_end = c; 3665 gr_reporterror_cmd( 3666 &cmd, "EINVAL: key without value: %s ", 3667 key_start); 3668 break; 3669 case '=': 3670 key_end = c; 3671 state = 'v'; 3672 val_start = c + 1; 3673 break; 3674 default: 3675 break; 3676 } 3677 } else if (state == 'v') { 3678 switch (*c) { 3679 case ',': 3680 case ';': 3681 case '\0': 3682 state = *c == ',' ? 'k' : 'p'; 3683 val_end = c; 3684 if (key_vals_count >= 3685 sizeof(key_vals) / sizeof(*key_vals)) { 3686 gr_reporterror_cmd(&cmd, 3687 "EINVAL: too many " 3688 "key-value pairs"); 3689 break; 3690 } 3691 key_vals[key_vals_count].key_start = key_start; 3692 key_vals[key_vals_count].val_start = val_start; 3693 key_vals[key_vals_count].key_len = 3694 key_end - key_start; 3695 key_vals[key_vals_count].val_len = 3696 val_end - val_start; 3697 ++key_vals_count; 3698 key_start = c + 1; 3699 break; 3700 default: 3701 break; 3702 } 3703 } else if (state == 'p') { 3704 cmd.payload = c; 3705 // break out of the loop, we don't check the payload 3706 break; 3707 } 3708 ++c; 3709 } 3710 3711 // Set the action key ('a=') first because we need it to disambiguate 3712 // some keys. Also set 'i=' and 'I=' for better error reporting. 3713 for (unsigned i = 0; i < key_vals_count; ++i) { 3714 if (key_vals[i].key_len == 1) { 3715 char *start = key_vals[i].key_start; 3716 if (*start == 'a' || *start == 'i' || *start == 'I') { 3717 gr_set_keyvalue(&cmd, &key_vals[i]); 3718 break; 3719 } 3720 } 3721 } 3722 // Set the rest of the keys. 3723 for (unsigned i = 0; i < key_vals_count; ++i) 3724 gr_set_keyvalue(&cmd, &key_vals[i]); 3725 3726 if (!cmd.payload) 3727 cmd.payload = buf + len; 3728 3729 if (cmd.payload && cmd.payload[0]) 3730 GR_LOG(" payload size: %ld\n", strlen(cmd.payload)); 3731 3732 if (!graphics_command_result.error) 3733 gr_handle_command(&cmd); 3734 3735 if (graphics_debug_mode) { 3736 fprintf(stderr, "Response: "); 3737 for (const char *resp = graphics_command_result.response; 3738 *resp != '\0'; ++resp) { 3739 if (isprint(*resp)) 3740 fprintf(stderr, "%c", *resp); 3741 else 3742 fprintf(stderr, "(0x%x)", *resp); 3743 } 3744 fprintf(stderr, "\n"); 3745 } 3746 3747 // Make sure that we suppress response if needed. Usually cmd.quiet is 3748 // taken into account when creating the response, but it's not very 3749 // reliable in the current implementation. 3750 if (cmd.quiet) { 3751 if (!graphics_command_result.error || cmd.quiet >= 2) 3752 graphics_command_result.response[0] = '\0'; 3753 } 3754 3755 return 1; 3756 } 3757 3758 //////////////////////////////////////////////////////////////////////////////// 3759 // base64 decoding part is basically copied from st.c 3760 //////////////////////////////////////////////////////////////////////////////// 3761 3762 static const char gr_base64_digits[] = { 3763 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3764 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3765 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 3766 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3767 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 3768 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 3769 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 3770 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3771 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3772 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3773 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3774 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3775 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3776 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3777 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 3778 3779 static char gr_base64_getc(const char **src) { 3780 while (**src && !isprint(**src)) 3781 (*src)++; 3782 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 3783 } 3784 3785 char *gr_base64dec(const char *src, size_t *size) { 3786 size_t in_len = strlen(src); 3787 char *result, *dst; 3788 3789 result = dst = malloc((in_len + 3) / 4 * 3 + 1); 3790 while (*src) { 3791 int a = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 3792 int b = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 3793 int c = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 3794 int d = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; 3795 3796 if (a == -1 || b == -1) 3797 break; 3798 3799 *dst++ = (a << 2) | ((b & 0x30) >> 4); 3800 if (c == -1) 3801 break; 3802 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 3803 if (d == -1) 3804 break; 3805 *dst++ = ((c & 0x03) << 6) | d; 3806 } 3807 *dst = '\0'; 3808 if (size) { 3809 *size = dst - result; 3810 } 3811 return result; 3812 }