Fix 40+ bugs from audit passes 9-12
- PNG chunk parsing overflow protection with checked arithmetic - Font directory traversal bounded with global result limit - find_unique_path TOCTOU race fixed with create_new + marker byte - Watch mode "processed" dir exclusion narrowed to prevent false skips - Metadata copy now checks format support before little_exif calls - Clipboard temp files cleaned up on app exit - Atomic writes for file manager integration scripts - BMP format support added to encoder and convert step - Regex DoS protection with DFA size limit - Watermark NaN/negative scale guard - Selective EXIF stripping for privacy/custom metadata modes - CLI watch mode: file stability checks, per-file history saves - High contrast toggle preserves and restores original theme - Image list deduplication uses O(1) HashSet lookups - Saturation/trim/padding overflow guards in adjustments
This commit is contained in:
@@ -782,7 +782,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
.child(&scrolled)
|
||||
.build();
|
||||
|
||||
// On page map: refresh preview and show/hide per-format rows
|
||||
// On page map: refresh thumbnail strip, preview, and show/hide per-format rows
|
||||
{
|
||||
let up = update_preview.clone();
|
||||
let jc = state.job_config.clone();
|
||||
@@ -793,7 +793,71 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
let wer = webp_effort_row;
|
||||
let ar = avif_row;
|
||||
let asr = avif_speed_row;
|
||||
let lf = state.loaded_files.clone();
|
||||
let tb = thumb_box.clone();
|
||||
let ts = thumb_scrolled.clone();
|
||||
let pidx = preview_index.clone();
|
||||
let up2 = update_preview.clone();
|
||||
page.connect_map(move |_| {
|
||||
// Rebuild thumbnail strip from current file list
|
||||
while let Some(child) = tb.first_child() {
|
||||
tb.remove(&child);
|
||||
}
|
||||
let files = lf.borrow();
|
||||
let max_thumbs = files.len().min(10);
|
||||
for i in 0..max_thumbs {
|
||||
let pic = gtk::Picture::builder()
|
||||
.content_fit(gtk::ContentFit::Cover)
|
||||
.width_request(50)
|
||||
.height_request(50)
|
||||
.build();
|
||||
pic.set_filename(Some(&files[i]));
|
||||
let frame = gtk::Frame::builder()
|
||||
.child(&pic)
|
||||
.build();
|
||||
if i == *pidx.borrow() {
|
||||
frame.add_css_class("accent");
|
||||
}
|
||||
let pidx_c = pidx.clone();
|
||||
let up_c = up2.clone();
|
||||
let tb_c = tb.clone();
|
||||
let current_idx = i;
|
||||
let btn = gtk::Button::builder()
|
||||
.child(&frame)
|
||||
.has_frame(false)
|
||||
.tooltip_text(files[i].file_name().and_then(|n| n.to_str()).unwrap_or("image"))
|
||||
.build();
|
||||
btn.connect_clicked(move |_| {
|
||||
*pidx_c.borrow_mut() = current_idx;
|
||||
up_c(true);
|
||||
let mut c = tb_c.first_child();
|
||||
let mut j = 0usize;
|
||||
while let Some(w) = c {
|
||||
if let Some(b) = w.downcast_ref::<gtk::Button>() {
|
||||
if let Some(f) = b.child().and_then(|c| c.downcast::<gtk::Frame>().ok()) {
|
||||
if j == current_idx {
|
||||
f.add_css_class("accent");
|
||||
} else {
|
||||
f.remove_css_class("accent");
|
||||
}
|
||||
}
|
||||
}
|
||||
c = w.next_sibling();
|
||||
j += 1;
|
||||
}
|
||||
});
|
||||
tb.append(&btn);
|
||||
}
|
||||
ts.set_visible(max_thumbs > 1);
|
||||
// Clamp preview index if files were removed
|
||||
{
|
||||
let mut idx = pidx.borrow_mut();
|
||||
if *idx >= files.len() && !files.is_empty() {
|
||||
*idx = 0;
|
||||
}
|
||||
}
|
||||
drop(files);
|
||||
|
||||
up(true);
|
||||
|
||||
let cfg = jc.borrow();
|
||||
@@ -814,7 +878,7 @@ pub fn build_compress_page(state: &AppState) -> adw::NavigationPage {
|
||||
Some(ImageFormat::Png) => has_png = true,
|
||||
Some(ImageFormat::WebP) => has_webp = true,
|
||||
Some(ImageFormat::Avif) => has_avif = true,
|
||||
Some(ImageFormat::Gif) | Some(ImageFormat::Tiff) => {}
|
||||
Some(ImageFormat::Gif) | Some(ImageFormat::Tiff) | Some(ImageFormat::Bmp) => {}
|
||||
}
|
||||
|
||||
for (_, &choice_idx) in &cfg.format_mappings {
|
||||
|
||||
Reference in New Issue
Block a user