aboutsummaryrefslogtreecommitdiff
path: root/src/ghostty
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-15 03:52:42 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-15 03:53:37 +0300
commit7c636e97f4568583651de3bc8c8550a3bab54743 (patch)
treed189e60904307bdfe8d3cfc89428fd3e43f88a90 /src/ghostty
parent67d5e7391f70cc7ecfe99eaf23132d12152340de (diff)
fix: refresh and reuse kitty graphics placements
Diffstat (limited to 'src/ghostty')
-rw-r--r--src/ghostty/mod.rs45
1 files changed, 44 insertions, 1 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index b69dfdb7..a0ff3226 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -148,6 +148,10 @@ pub const MODE_SYNCHRONIZED_OUTPUT: u16 = 2026;
const KITTY_IMAGE_STORAGE_LIMIT_BYTES: u64 = 64 * 1024 * 1024;
const APC_MAX_BYTES: usize = 16 * 1024 * 1024;
const APC_MAX_BYTES_KITTY: usize = 16 * 1024 * 1024;
+// Kitty image fingerprints are used as a display cache key, not a
+// cryptographic identity. Sampling keeps redraws cheap for multi-megabyte
+// images while still distinguishing normal screenshots/photos/diagrams.
+const KITTY_FINGERPRINT_SAMPLE_BYTES: usize = 4096;
static INSTALL_PNG_DECODER: Once = Once::new();
@@ -1188,10 +1192,23 @@ fn kitty_image_fingerprint(
}
let data = unsafe { slice::from_raw_parts(ptr_out, len) };
- data.hash(&mut hasher);
+ hash_kitty_image_data_sample(&mut hasher, data);
hasher.finish()
}
+fn hash_kitty_image_data_sample(hasher: &mut impl Hasher, data: &[u8]) {
+ let sample = KITTY_FINGERPRINT_SAMPLE_BYTES;
+ if data.len() <= sample * 3 {
+ data.hash(hasher);
+ return;
+ }
+
+ data[..sample].hash(hasher);
+ let middle_start = (data.len() / 2).saturating_sub(sample / 2);
+ data[middle_start..middle_start + sample].hash(hasher);
+ data[data.len() - sample..].hash(hasher);
+}
+
fn grid_ref_hyperlink_uri(grid_ref: &ffi::GhosttyGridRef) -> Result<Option<String>, Error> {
let mut required = 0usize;
let result =
@@ -1863,6 +1880,32 @@ mod tests {
}
#[test]
+ fn kitty_image_fingerprint_samples_large_payloads() {
+ let mut data = vec![1u8; KITTY_FINGERPRINT_SAMPLE_BYTES * 4];
+ let original =
+ kitty_image_fingerprint(data.as_ptr(), data.len(), 100, 50, KittyImageFormat::Png);
+
+ data[KITTY_FINGERPRINT_SAMPLE_BYTES / 2] = 2;
+ let changed_prefix =
+ kitty_image_fingerprint(data.as_ptr(), data.len(), 100, 50, KittyImageFormat::Png);
+ assert_ne!(original, changed_prefix);
+
+ data[KITTY_FINGERPRINT_SAMPLE_BYTES / 2] = 1;
+ let middle = data.len() / 2;
+ data[middle] = 3;
+ let changed_middle =
+ kitty_image_fingerprint(data.as_ptr(), data.len(), 100, 50, KittyImageFormat::Png);
+ assert_ne!(original, changed_middle);
+
+ data[middle] = 1;
+ let last = data.len() - 1;
+ data[last] = 4;
+ let changed_suffix =
+ kitty_image_fingerprint(data.as_ptr(), data.len(), 100, 50, KittyImageFormat::Png);
+ assert_ne!(original, changed_suffix);
+ }
+
+ #[test]
fn build_info_contract_matches_expected_vendored_features() {
let _simd = build_info_bool(ffi::GhosttyBuildInfo_GHOSTTY_BUILD_INFO_SIMD);
let _tmux_control_mode =