diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-15 03:52:42 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-15 03:53:37 +0300 |
| commit | 7c636e97f4568583651de3bc8c8550a3bab54743 (patch) | |
| tree | d189e60904307bdfe8d3cfc89428fd3e43f88a90 /src/ghostty | |
| parent | 67d5e7391f70cc7ecfe99eaf23132d12152340de (diff) | |
fix: refresh and reuse kitty graphics placements
Diffstat (limited to 'src/ghostty')
| -rw-r--r-- | src/ghostty/mod.rs | 45 |
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 = |
