From 7c636e97f4568583651de3bc8c8550a3bab54743 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Fri, 15 May 2026 03:52:42 +0300 Subject: fix: refresh and reuse kitty graphics placements --- src/ghostty/mod.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'src/ghostty') 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, Error> { let mut required = 0usize; let result = @@ -1862,6 +1879,32 @@ mod tests { out } + #[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); -- cgit v1.2.3