- 桌面应用
【免费下载链接】czkawka
Multi functional app to find duplicates, empty folders, similar images etc.
Czkawka 是一个多功能的重复文件、空目录、相似图片等清理工具,而czkawka_gui是它基于 GTK4 的官方图形前端,负责把 czkawka_core 的扫描引擎以可视化的方式呈现给用户。本文以仓库内 czkawka_gui/CLAUDE.md 架构指南为主线,结合src/下的真实实现逐层拆解其源码布局、中央数据结构GuiData、扫描线程模型、结果填充管线、语言切换与配置持久化机制,读完即可对"如何给 GTK4 应用接入 Rust 扫描引擎"这一经典模式形成完整的实战认知。
项目定位与维护状态
czkawka_gui是 Czkawka 项目三大程序之一(其余为 CLI 工具 czkawka_cli 与新一代 Slint 前端 krokiet),当前版本为12.0.1(见 Cargo.toml)。
需要特别注意的是它的维护状态:
Status: Maintenance mode only——不再开发新功能,仅接受与
czkawka_coreAPI 变更保持兼容的 Bug 修复。
这意味着它的架构相对稳定,非常适合作为"GTK4 + Rust + 扫描引擎"的参考实现来阅读;而新功能开发的工作重心已经转移到 Krokiet 前端(仓库中 krokiet 目录),czkawka_gui甚至会在首次启动时弹出一则 Krokiet 迁移提示对话框(见下文 main.rs 的启动流程)。
整体架构概览
从 CLAUDE.md 的 Overview 可以提炼出三条架构主线:
- GTK4 前端:UI 使用 XML 描述,XML 文件由Cambalache(GTK UI 设计器)编辑,工程源文件是 ui/czkawka.cmb,编译时通过
include_str!直接嵌入二进制。 - Notebook(标签页)接口:
czkawka_core中所有扫描工具都通过一个 11 个标签页的 notebook 暴露给用户——每个标签页对应一个工具(重复文件、空文件夹、大文件、空文件、临时文件、相似图片、相似视频、同音乐、失效符号链接、损坏文件、错误扩展名)。 - 核心/前端分离:扫描逻辑完全位于
czkawka_core,前端只负责参数收集、线程调度、进度展示与结果渲染。
值得注意的是,czkawka_core的工具总数与 GUI 标签数并不相等。在 main.rs 中有这样一行常量:
pub const CZKAWKA_GTK_TOOL_NUMBER: usize = TOOLS_NUMBER - 3; // Missing exif, video optimizer, bad names tools即 GTK 前端故意少暴露了 3 个工具:EXIF 移除、视频优化、坏文件名——它们只在 CLI(czkawka_cli)和 Krokiet 前端中提供。这是阅读源码时容易踩坑的一个细节。
源码布局逐层解读
src/顶层:应用生命周期
| 文件 | 职责 | 关键实现 |
|---|---|---|
| main.rs | GTK Application 搭建、GuiData构造 | 入口与回调接线 |
| initialize_gui.rs | 初始控件状态 | 启动时复位各开关 |
| compute_results.rs | 扫描结果 → GTK ListStore 行 | 结果填充管线 |
| saving_loading.rs | JSON 设置读写 | SettingsJson+ serde |
| language_functions.rs | 语言常量 | LANGUAGES_ALL |
| localizer_gui.rs | flg!宏、Fluent 加载 | 国际化 |
| notebook_enums.rs | 标签页索引枚举 | NotebookMainEnum |
| notebook_info.rs | 每个标签页的列/按钮定义 | NOTEBOOKS_INFO |
| help_combo_box.rs | 下拉框选项数组 | 哈希算法、检查方式等 |
| help_functions.rs | 行颜色常量、按钮管理 | HEADER_ROW_COLOR等 |
| opening_selecting_records.rs | 双击打开文件/文件夹 | 行选择辅助 |
| taskbar_progress.rs | Windows 任务栏进度 | 真实实现 |
| taskbar_progress_dummy.rs | 非 Windows 平台的空实现 | no-op |
| taskbar_progress_win.rs | Windows 任务栏 API 调用 | WinAPI |
| gtk_traits.rs | 自定义 GTK trait 扩展 | 便捷方法 |
应用启动流程(main.rs)
main.rs 的启动顺序很有参考价值:
- 注册图片解码钩子
register_image_decoding_hooks(); - 调用
set_config_cache_path("Czkawka", "Czkawka")确定配置/缓存目录; - Krokiet 提示逻辑:检查缓存目录下是否存在
krokiet_info_dialog_seen2.txt标记文件,不存在则创建;若配置/缓存路径未通过环境变量覆盖、标记文件不存在且未设置CZKAWKA_DONT_ANNOY_ME环境变量,则在 UI 构建完成后弹出 Krokiet 迁移提示对话框; - 注册
connect_command_line,处理 CLI 参数(由czkawka_core的process_cli_args解析,支持--included-directories等参数直接指定扫描范围); build_ui()中依次完成:构建GuiData→ 初始化控件 → 复位配置 → 读取系统语言 → 从 JSON 加载配置 → 设置线程数 → 逐一接线全部回调。
启动时先加载系统语言、再加载配置文件的顺序(load_system_language在load_configuration之前)在源码注释中明确标注:这是为了保证系统语言优先、配置文件中的语言设置随后生效。
ui/目录:Cambalache 生成的 XML
ui/下的.ui文件全部由 Cambalache 从 czkawka.cmb 工程文件导出,运行时通过gtk4::Builder::from_string(include_str!("../../ui/main_window.ui"))加载(见 gui_data.rs)。
| 文件 | 内容 |
|---|---|
| main_window.ui | 约 65 KB 的主窗口完整布局(含全部标签页) |
| settings.ui | 设置对话框 |
| compare_images.ui | 图片对比面板 |
| popover_select.ui | 选择过滤器 popover |
| popover_sort.ui | 排序 popover |
| popover_right_click.ui | 右键上下文菜单 popover |
| about_dialog.ui | 关于窗口 |
| progress.ui | 扫描进度对话框 |
Cambalache 是这些 XML 的"唯一事实来源"(source of truth),这意味着想改布局应该回到.cmb工程文件编辑再导出,而不是手改.ui。
GuiData:连接所有 UI 组件的中央结构体
GuiData是前端最核心的数据结构,定义在 gui_structs/gui_data.rs。它把主窗口、两个 notebook、底部按钮、进度窗口、设置、头部菜单、图片对比面板、about 对话框、popover、文本入口、错误视图、文件对话框、任务栏状态与停止标志全部聚合在一起,通过 clone/reference 传给每一个回调:
#[derive(Clone)] pub struct GuiData { pub window_main: gtk4::Window, pub main_notebook: GuiMainNotebook, // 11 个工具标签页 pub upper_notebook: GuiUpperNotebook, // 目录配置(包含/排除) pub popovers_select: GuiSelectPopovers, // 选择过滤器 pub popovers_sort: GuiSortPopovers, // 排序过滤器 pub bottom_buttons: GuiBottomButtons, // 操作按钮(搜索、删除等) pub progress_window: GuiProgressDialog, // 扫描进度窗口 pub about: GuiAbout, // 关于对话框 pub settings: GuiSettings, // 设置对话框 pub header: GuiHeader, // 菜单栏 pub compare_images: GuiCompareImages, // 并排图片差异对比 pub file_dialog_include_exclude_folder_selection: FileChooserNative, pub file_dialog_move_to_folder: FileChooserNative, pub taskbar_state: Rc<RefCell<TaskbarProgress>>, pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, HashMap<BottomButtonsEnum, bool>>>>, pub entry_info: gtk4::Entry, // 状态提示输入框 pub text_view_errors: gtk4::TextView, // 错误信息文本 pub scrolled_window_errors: gtk4::ScrolledWindow, pub stop_flag: Arc<AtomicBool>, // 向扫描线程发送停止信号 }GuiData::new_with_application()(gui_data.rs)的构造要点:
- Builder 一次性加载整个
main_window.ui,各子结构(GuiUpperNotebook、GuiMainNotebook、GuiBottomButtons等)通过create_from_builder(&builder)从同一 Builder 中取出自己的控件引用; - 原生文件对话框必须常驻内存:两个
FileChooserNative在构造时创建、select_multiple分别设为true(目录选择)与false(移动目标目录),源码注释解释了"原生对话框必须一直存在,与普通对话框相反"这一 GTK4 行为; - 按钮状态映射
shared_buttons:默认每个标签页只显示搜索按钮,其余按钮(删除、保存、硬链接、符号链接、移动、对比、排序)在对应工具找到结果后才激活; stop_flag是Arc<AtomicBool>,用于向工作线程发送取消信号(详见下一节)。
GuiData还承担语言刷新职责:update_language()会依次调用所有子结构的update_language(),用flg!宏重设每个控件标签。
扫描线程架构:Search 按钮背后的完整链路
扫描是前端最复杂的交互流程,CLAUDE.md 给出了抽象流程图,下面结合 connect_button_search.rs 还原每个环节的真实代码。
抽象流程
用户点击 Search → connect_button_search.rs → 禁用 UI、显示进度窗口 → 生成工作线程: - 从 GTK 控件读取设置 - 创建工具结构体(如 DuplicateFinder) - 调用 tool.search(stop_flag, progress_sender) - 通过 result_sender 发送 Message::Duplicates(tool) → 主线程:接收进度更新 → 刷新进度条 → 收到结果后: - compute_results.rs 处理工具数据 → 追加到 ListStore - 重新启用 UI、隐藏进度两条 channel 的分工
Sender<Message>:一次性结果通道,携带携带完整数据的工具枚举(Message::Duplicates(DuplicateFinder)等,定义见 helpers/enums.rs);Sender<ProgressData>:核心引擎实时推送的进度数据通道,驱动进度窗口的进度条。
两个通道都在 main.rs 用crossbeam_channel::unbounded()创建,然后分别交给connect_button_search(发送端)和connect_compute_results/connect_progress_window(接收端)。
工具分发与参数收集
点击事件处理器先检查"是否已把全部包含目录标记为参考目录"(check_if_list_store_column_have_all_same_values),然后按当前标签页分发到 11 个独立搜索函数(connect_button_search.rs):
match current_data.get_current_page() { NotebookMainEnum::Duplicate => duplicate_search(&gui_data, loaded_commons, stop_flag, result_sender, &grid_progress, progress_sender), NotebookMainEnum::EmptyFiles => empty_files_search(...), NotebookMainEnum::EmptyDirectories => empty_dirs_search(...), NotebookMainEnum::BigFiles => big_files_search(...), NotebookMainEnum::Temporary => temporary_files_search(...), NotebookMainEnum::SimilarImages => similar_image_search(...), NotebookMainEnum::SimilarVideos => similar_video_search(...), NotebookMainEnum::SameMusic => same_music_search(...), NotebookMainEnum::Symlinks => bad_symlinks_search(...), NotebookMainEnum::BrokenFiles => broken_files_search(...), NotebookMainEnum::BadExtensions => bad_extensions_search(...), }公共参数统一由LoadedCommonItems::load_items()从 GTK 控件读取,包括:包含/排除/参考目录、递归搜索、排除项(逗号分隔)、允许/排除扩展名、隐藏硬链接、使用缓存、同时保存 JSON、最小缓存文件大小、最小/最大文件大小、是否忽略其他文件系统。随后通过泛型函数set_common_settings<T: CommonData>(&mut tool, &loaded_commons)(connect_button_search.rs)批量写入工具结构体——这是czkawka_core的CommonDatatrait 统一抽象的价值所在。
以重复文件工具为例,完整线程调用链(connect_button_search.rs):
thread::Builder::new() .stack_size(DEFAULT_THREAD_SIZE) .spawn(move || { let params = DuplicateFinderParameters::new( check_method, hash_type, use_prehash_cache, loaded_commons.minimal_cache_file_size, minimal_prehash_cache_file_size, case_sensitive_name_comparison, ); let mut tool = DuplicateFinder::new(params); set_common_settings(&mut tool, &loaded_commons); tool.set_delete_outdated_cache(delete_outdated_cache); tool.search(&stop_flag, Some(&progress_data_sender)); result_sender.send(Message::Duplicates(tool)).expect("Failed to send Duplicates message"); }) .expect("Failed to spawn DuplicateFinder thread");要点:
- 每个工具都在独立线程中运行
search(&stop_flag, Some(&progress_sender)),其中 stop_flag 与 progress_sender 都是跨线程共享的; - 每个工具各有自己的参数结构体(如
SimilarImagesParameters、SimilarVideosParameters、SameMusicParameters),从对应下拉框/滑杆读取;部分高级参数(如相似视频的check_audio_content)在 GTK 前端中未实现,直接以硬编码常量传入; - 扫描期间主 UI(两个 notebook、设置按钮、信息按钮)全部
set_sensitive(false)锁定,进度条复位为 0,错误视图被清空; - 点击 Search 时
stop_flag.store(false)先清除旧标志;取消扫描由 connect_button_stop.rs 置为true。
结果填充管线:compute_results.rs
结果回传由 compute_results.rs 负责。它在glib::spawn_future_local中开启一个异步循环,每 300ms 用try_recv()轮询结果通道(compute_results.rs),收到Message后按类型调用对应的compute_*函数。
核心流程(以 DuplicateFinder 为例)
- 收到
Message::Duplicates(DuplicateFinder); handle_stopped_search()检查是否被用户中止(若是则提示"已停止");- 从
get_information()提取重复文件数、组数、可回收空间,构造flg!翻译消息写入entry_info; - 从工具中取出排序好的分组数据(按
CheckingMethod分别取自get_files_sorted_by_hash/get_files_sorted_by_size/get_files_sorted_by_names/get_files_sorted_by_size_name,参考目录模式则用_referenced变体); - 每个分组先插入一个header 行(着色为
HEADER_ROW_COLOR),再为每个文件插入main 行(MAIN_ROW_COLOR); append_row_to_list_store()把单个单元格写入gtk4::ListStore;finalize_compute()将工具对象存入SubView.shared_model_enum,供后续选择/排序回调复用(compute_results.rs)。
行颜色与列定义
行颜色定义在 help_functions.rs:
pub const MAIN_ROW_COLOR: &str = "#222222"; pub const HEADER_ROW_COLOR: &str = "#111111"; pub const TEXT_COLOR: &str = "#ffffff";每个工具的列索引是编译期枚举(helpers/enums.rs),例如重复文件有 11 列:ActivatableSelectButton、SelectionButton、Size、SizeAsBytes、Name、Path、Modification、ModificationAsSecs、Color、IsHeader、TextColor。每个标签页的完整列 schema(列类型、底部可用按钮、TreeView 名称)集中定义在 notebook_info.rs 的NOTEBOOKS_INFO静态数组中,前端其余模块都从这里读取元信息,做到了"一份定义、多处复用"。
并行排序与参考目录
结果在渲染前使用rayon 并行排序:vector.par_sort_unstable_by(|a, b| split_path_compare(a.get_path(), b.get_path()))(compute_results.rs),超过 2 个元素才排序,少于则原样返回,避免无谓开销。
参考目录模式下渲染逻辑略有不同:第一个文件作为基准行(header 样式)直接显示完整信息,其余文件作为对照行,且相似图片会按相似度差值(difference)升序排列;选择函数也会切换为select_function_always_true以保证基准文件始终可选。
语言切换机制
前端支持多语言,语言常量表定义在 language_functions.rs:LANGUAGES_ALL: &[Language]中每个条目包含combo_box_text(下拉框显示名)与short_text(BCP-47 代码)。文档标注为 26 项,当前源码中实际列出了 27 个条目,覆盖英文、法文、意大利文、波兰文、俄文、乌克兰文、韩文、捷克文、德文、日文、葡萄牙文(含巴西)、简体中文、繁体中文、西班牙文、挪威文、瑞典文、阿拉伯文、保加利亚文、希腊文、荷兰文、罗马尼亚文、土耳其文、波斯文、印地文、印尼文、越南文等。
语言切换的三步流程(由 connect_change_language.rs 驱动):
- 从选中下拉项查
short_text; - 同时调用
localizer.select([lang_id])切换czkawka_core与czkawka_gui两套 Fluent 本地化器(核心引擎的提示文本也会随之翻译); - 调用
gui_data.update_language(),向所有子结构传播,各子结构通过flg!("key")宏更新控件标签。
应用启动时会调用load_system_language()尝试匹配系统默认语言。
设置持久化:JSON 配置的读写细节
存储路径
设置以 JSON 保存,文件名为czkawka_gui_config.json(saving_loading.rs)。实际路径由czkawka_core的get_config_cache_path()决定——默认位于用户配置目录(Linux 下即~/.config/Czkawka/czkawka_gui_config.json),并支持通过CZK_CONFIG/CZK_CACHE环境变量覆盖(main.rs 启动时调用的set_config_cache_path("Czkawka", "Czkawka"))。
SettingsJson 结构
配置模型SettingsJson(saving_loading.rs)用 serde 序列化,每个字段都带#[serde(default = "...")]默认函数——这样某个字段缺失或损坏不会导致整个反序列化失败,这是长期运行工具非常实用的健壮性设计。字段覆盖五类内容:
- 目录配置:
included_directories、reference_directories、excluded_directories(Unix 默认排除/proc、/dev、/sys、/snap,Windows 默认排除C:\Windows); - 工具参数:重复文件的检查方式/哈希类型/大小写敏感/预哈希缓存、相似图片的哈希大小/缩放算法/相似度、相似视频的相似度容差、大文件数量(默认 50)、坏文件检查类型(PDF/音频/图片/压缩包/视频)、音乐相似度维度等;
- UI 状态:是否显示底部文本面板、是否显示图片预览、语言偏好;
- 行为开关:退出保存/启动加载、删除前确认(文件/整组/链接)、删除到回收站、隐藏硬链接、使用缓存、同时保存 JSON 缓存等;
- 通用默认值:最小文件大小默认
"16384"(16 KB)、最大文件大小默认"999999999999"、最小缓存文件大小默认"257144"字节。
保存发生在窗口关闭时(connect_close_request,见 main.rs),且使用非默认 CLI 参数启动时不保存,避免把临时扫描参数写回配置。
关键依赖与可选 Feature
CLAUDE.md 的依赖表与 Cargo.toml 完全对应,整理如下:
| Crate | 版本 | 用途 |
|---|---|---|
gtk4 | 0.11 | GTK4 GUI 框架(v4_6feature) |
gdk4 | 0.11 | 绘制/渲染(配合 gdk-pixbuf 显示图片) |
glib | 0.22 | 事件循环、spawn_future_local |
i18n-embed+rust-embed | 0.16 / 8.5 | Fluent 翻译嵌入与加载 |
open | 5.3 | 调用系统文件管理器 |
image | 0.25 | 预览缩略图(jpeg/png) |
resvg | 0.47 | SVG 图标缩放 |
rayon | 1.10 | 结果并行排序 |
crossbeam-channel | 0.5 | 结果 + 进度通道 |
czkawka_core | 12.0.1 | 扫描引擎(path 依赖) |
serde/serde_json | 1.x | 配置序列化 |
humansize | 2.1 | 人类可读文件大小格式化 |
chrono | 0.4 | 时间戳格式化 |
fun_time | 0.3 | 耗时统计日志(#[fun_time]) |
可选 features(转发给czkawka_core):heif(HEIF 图片)、libraw(RAW 格式)、libavif(AVIF 图片)、xdg_portal_trash(Linux Flatpak 环境下通过 xdg-portal 使用回收站——普通回收站访问在 Flatpak 中总是失败,此选项更慢但错误提示更友好,其他系统上为 no-op)。
注释规范与协作约定
最后一条约定关乎代码风格:注释短而克制——遵循仓库根 AGENTS.md 的准则,只在代码行为无法从阅读本身推断时才写注释(例如非显然的约束、workaround、跨模块耦合),绝不复述代码已表达的内容。这一约定解释了为什么czkawka_gui源码中注释密度很低、但每一条注释(如"原生对话框必须常驻内存")都直指一个真实的坑。
小结:一张图看懂 czkawka_gui
- UI 层:Cambalache 导出 XML →
gtk4::Builder加载 →GuiData聚合全部控件; - 交互层:
connect_*回调统一以GuiData为上下文,负责参数收集与按钮/进度/语言管理; - 并发层:每个工具一个独立线程,
stop_flag(Arc<AtomicBool>)控制取消,progress_sender推送实时进度,result_sender一次性回传携带完整数据的工具对象; - 渲染层:
compute_results.rs在 GLib 主循环中轮询结果通道,按分组插入 header/main 行,rayon 并行排序,最后把工具对象存入shared_model_enum供后续选择、排序、删除、移动、硬链接/符号链接、保存(CSV/JSON)、图片对比等操作复用。
对想要理解"如何为 Rust 扫描引擎编写 GTK4 前端"的开发者而言,czkawka_gui这套"中央结构体 + 双通道线程模型 + ListStore 渲染管线"的模式,是一份结构清晰、可直接借鉴的完整参考。
- 桌面应用
【免费下载链接】czkawka
Multi functional app to find duplicates, empty folders, similar images etc.
相关推荐
RunCat 365完整指南:10分钟修好任务栏猫咪卡顿与启动报错
RunCat 365完整指南:10分钟修好任务栏猫咪卡顿与启动报错 RunCat 365 是一款住在 Windows 任务栏里的轻量级系统资源监视器:一只小猫在
桌面应用CleanRL 快速上手:手把手跑通第一个 PPO 强化学习实验
CleanRL 快速上手:手把手跑通第一个 PPO 强化学习实验 CleanRL 是一个单文件风格的深度强化学习(DRL,即智能体通过与环境交互来学习决策策略的
桌面应用CloudNativePG PostgreSQL 大版本升级完全指南:从 Minor 滚动更新到离线 In-Place 就地升级
CloudNativePG PostgreSQL 大版本升级完全指南:从 Minor 滚动更新到离线 In Place 就地升级 本文以 CloudNative
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考