15 Commits

Author SHA1 Message Date
Serendipity 6509ef98e4 chore: 清理旧版IUP库文件并更新头文件路径
删除旧的iup-3.31_Win64_dllw6_lib目录下的所有DLL和静态库文件
将IUP头文件从旧目录迁移到新的libs/IUP/include统一路径
更新CMakeLists.txt中的包含路径和库链接配置
简化DLL复制逻辑,只复制核心iup.dll文件
2026-03-19 20:14:06 +08:00
Serendipity c928c271e8 chore: 移除构建安装程序前的DLL复制步骤
不再需要手动复制IUP DLL文件,因为安装脚本已直接引用库目录。
2026-03-19 13:23:03 +08:00
Serendipity 02e702b285 fix(构建): 修复IUP DLL复制命令的路径变量
将CMAKE_SOURCE_DIR更改为CMAKE_CURRENT_SOURCE_DIR以确保在子目录中也能正确找到DLL文件。
移除不必要的条件判断,使复制命令始终执行。
2026-03-19 12:40:01 +08:00
Serendipity af3138c146 build: 重构 CMakeLists.txt 以使用现代 CMake 最佳实践
- 将项目声明更新为包含版本和语言
- 启用 RC 语言以正确处理资源文件
- 使用 target_* 命令替代全局命令(如 include_directories、link_directories)
- 将资源文件直接加入源文件列表,简化构建定义
- 优化 DLL 复制逻辑,使用 file(GLOB) 和 copy_if_different
- 改进编译器选项的条件设置,增强跨编译器兼容性
2026-03-19 12:37:41 +08:00
Serendipity 6e6adf3b85 chore: 迁移构建系统并清理遗留的二进制文件
- 删除 bin/ 目录下遗留的 DLL 和可执行文件,它们现在由 CMake 构建过程自动复制
- 更新 CMakeLists.txt,明确设置 C17 标准并优化编译选项
- 更新 Inno Setup 安装脚本,使其从 build/ 目录获取构建产物
- 更新 main.c 中的编译说明,反映当前基于 CMake 的构建流程
2026-03-19 12:32:54 +08:00
Serendipity e84b33c5ca build: 迁移项目构建系统至 CMake
- 新增 CMakeLists.txt 文件,定义项目构建规则、依赖和编译选项。
- 更新 README.md 文档,推荐使用 CMake 进行构建,并说明新旧构建方式。
- 保留原有的 Makefile 支持以保持向后兼容。
2026-03-19 12:07:01 +08:00
Serendipity ac6b409f3a feat: 为只读模式添加专用应用程序标题
在非管理员权限下运行时,将对话框标题从硬编码字符串改为使用配置文件中定义的 APP_NAME_READONLY 宏。这提高了代码的可维护性和一致性,使标题文本集中管理,便于未来修改。
2026-03-18 22:37:33 +08:00
Serendipity 1bbe95582a refactor: 将应用程序名称提取为配置常量
- 在 config.h 中定义 APP_NAME 常量,提高可维护性
- 将 main.c 中的对话框标题硬编码替换为使用 APP_NAME
2026-03-18 22:33:25 +08:00
Serendipity 3ecf35963d feat(ui): 增加对话框最小尺寸并调整默认大小
- 将对话框默认大小从500x400调整为800x800
- 添加MINSIZE属性确保对话框不可缩小
- 清理ui.c中的多余空白字符
- 在main.c中添加编译和打包说明注释
2026-03-18 22:03:57 +08:00
Serendipity 276d2c5fe3 docs(config.h): 为UI配置常量添加注释说明 2026-03-18 21:09:13 +08:00
Serendipity a9339f9b9f style(config): 统一宏定义格式并更新文档
调整 config.h 中 UI_DLG_SIZE 宏定义的对齐格式以保持代码风格一致。
在 README.md 中新增“架构与二次开发”章节,说明项目的模块化设计和配置管理方式。
2026-03-18 21:06:55 +08:00
Serendipity 7fac2aab35 refactor: 重构代码以提取配置和全局变量
- 将 Windows 消息常量和 UI 配置常量分别提取到 globals.h 和 config.h 头文件中,提高可维护性
- 将全局变量和控件定义从 main.c 移至独立的 globals.c 源文件,实现关注点分离
- 更新 Makefile 以包含新的源文件 globals.c
- 在 ui.c 和 main.c 中引用 config.h,使用配置常量替代硬编码的 UI 参数
2026-03-18 21:01:50 +08:00
Serendipity 7db190306c docs: 更新 README 项目描述与功能亮点
重写项目概述,使其更具吸引力和信息性。新增对目标用户、核心优势(如双视图、智能检测、自动备份)的说明,以更好地向潜在用户展示工具价值。
2026-03-16 20:32:16 +08:00
Serendipity 575fcca5c4 refactor: 提取UI组件到独立模块并改进拖拽支持
- 将列表、按钮等UI创建代码从main.c移至ui.c/ui.h
- 添加Windows UIPI消息过滤以支持管理员模式下的文件拖拽
- 更新Makefile和构建脚本以包含新的UI模块
- 清理旧的备份注册表文件并更新README文档
2026-03-16 20:15:10 +08:00
Serendipity 39d06e20e0 feat: 新增系统/用户变量分离、搜索、拖拽和清理功能
- 将单一列表拆分为系统和用户变量两个标签页
- 新增搜索框支持实时过滤路径
- 支持拖拽文件夹直接添加到列表
- 新增一键清理功能移除无效和重复路径
- 增加注册表备份机制和删除确认
- 优化界面布局和权限提示逻辑
2026-03-16 19:58:41 +08:00
98 changed files with 982 additions and 279 deletions
+72
View File
@@ -0,0 +1,72 @@
cmake_minimum_required(VERSION 3.10)
project(PathEditor VERSION 3.0 LANGUAGES C)
# 启用资源编译器以处理 .rc 文件
enable_language(RC)
# 设置 C 标准
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF) # 禁用特定编译器的扩展(如 gnu17),强制使用标准 C17
# 定义源文件
set(SOURCES
src/main.c
src/utils.c
src/registry.c
src/callbacks.c
src/ui.c
src/globals.c
ico/resources.rc
)
# 创建 GUI 可执行文件(WIN32 属性会自动添加 -mwindows 参数)
add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
# 添加宏定义
target_compile_definitions(${PROJECT_NAME} PRIVATE
_WIN32
UNICODE
_UNICODE
)
# 添加编译选项
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-O2
-fexec-charset=UTF-8
)
endif()
# 设置头文件搜索路径
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/libs/IUP/include
)
# 设置库文件搜索路径
target_link_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/libs/IUP
)
# 链接所需库
target_link_libraries(${PROJECT_NAME} PRIVATE
iup
iupcd
gdi32
comdlg32
comctl32
uuid
ole32
advapi32
)
# 编译完成后,仅将程序实际需要的核心 DLL 文件复制到构建输出目录
set(IUP_REQUIRED_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/libs/IUP/iup.dll")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${IUP_REQUIRED_DLLS}
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
COMMENT "Copying required DLLs to build directory..."
)
+8 -2
View File
@@ -18,9 +18,9 @@ CFLAGS = -Wall -O2 -I$(INCLUDE_DIR) -I$(LOCAL_INCLUDE_DIR) -D_WIN32 -DUNICODE -D
LDFLAGS = -L$(LIB_DIR) -liup -liupcd -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32 -ladvapi32 -mwindows LDFLAGS = -L$(LIB_DIR) -liup -liupcd -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32 -ladvapi32 -mwindows
# Source # Source
SRC = src/main.c src/utils.c src/registry.c src/callbacks.c SRC = src/main.c src/utils.c src/registry.c src/callbacks.c src/ui.c src/globals.c
RES = ico/resources.rc RES = ico/resources.rc
OBJ = $(OBJ_DIR)/main.o $(OBJ_DIR)/utils.o $(OBJ_DIR)/registry.o $(OBJ_DIR)/callbacks.o $(OBJ_DIR)/resources.o OBJ = $(OBJ_DIR)/main.o $(OBJ_DIR)/utils.o $(OBJ_DIR)/registry.o $(OBJ_DIR)/callbacks.o $(OBJ_DIR)/ui.o $(OBJ_DIR)/globals.o $(OBJ_DIR)/resources.o
EXE = $(BIN_DIR)/PathEditor.exe EXE = $(BIN_DIR)/PathEditor.exe
all: $(BIN_DIR) $(OBJ_DIR) $(EXE) all: $(BIN_DIR) $(OBJ_DIR) $(EXE)
@@ -46,6 +46,12 @@ $(OBJ_DIR)/registry.o: src/registry.c
$(OBJ_DIR)/callbacks.o: src/callbacks.c $(OBJ_DIR)/callbacks.o: src/callbacks.c
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/ui.o: src/ui.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/globals.o: src/globals.c
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJ_DIR)/resources.o: ico/resources.rc $(OBJ_DIR)/resources.o: ico/resources.rc
$(WINDRES) -i $< -o $@ $(WINDRES) -i $< -o $@
+56 -15
View File
@@ -1,18 +1,44 @@
# Path Editor (系统环境变量编辑器) # Path Editor (系统环境变量编辑器)
一个简单、轻量级的 Windows 系统环境变量(PATH编辑器,基于 C 语言和 IUP 图形库开发。 * Path Editor 是一个专为 Windows 用户设计的系统环境变量(PATH管理工具。它基于原生 C 语言和 IUP 图形库开发,旨在替代 Windows 自带的简陋编辑界面
* 相比系统自带的编辑器,Path Editor 提供了更加直观的双视图(系统/用户变量)界面、智能的路径有效性检测、自动备份机制以及便捷的拖拽操作,让环境变量的管理变得安全、高效且轻松。无论您是开发者还是系统管理员,它都是您配置开发环境的得力助手。
## ✨ 功能特点 ## ✨ 功能特点
* **可视化编辑**:直观地查看和管理系统 PATH 环境变量。 * **🛡️ 安全第一**
* **安全操作**:必须以管理员身份运行才能保存更改,防止误操作 * **自动备份**:每次保存前自动备份注册表,防止意外
* **只读模式**:非管理员运行时自动切换到只读模式,防止误操作。
* **权限检测**:智能检测当前运行权限。
* **📑 双视图管理**
* 完美支持 **System (系统变量)****User (用户变量)** 的分离查看与编辑。
* 清晰的 Tab 标签页切换。
* **🔴 智能诊断与维护**
* **无效路径高亮**:自动检测路径是否存在,不存在的显示为红色。
* **重复路径高亮**:自动检测重复项,重复的显示为橙色。
* **一键清理**:智能移除所有无效和重复的路径,保持环境整洁。
* **📂 高效交互**
* **拖拽支持**:直接将文件夹拖入窗口即可添加(支持管理员模式下的 UIPI 穿透)。
* **实时搜索**:顶部搜索框支持不区分大小写的实时过滤查找。
* **快捷键**:支持 Delete 键快速删除选中项。
* **便捷管理** * **便捷管理**
* **新建**:添加新路径到列表。 * **新建**:添加新路径到列表。
* 📂 **浏览**:直接从文件资源管理器选择目录添加。 * 📂 **浏览**:直接从文件资源管理器选择目录添加。
* ✏️ **编辑**:修改现有路径。 * ✏️ **编辑**双击或点击按钮修改现有路径。
* 🗑️ **删除**:移除不需要的路径。 * 🗑️ **删除**:移除不需要的路径。
* ⬆️⬇️ **排序**:上移/下移调整路径优先级。 * ⬆️⬇️ **排序**:上移/下移调整路径优先级。
* **轻量级**:原生 C 语言编写,运行速度快,占用资源少。
* **轻量级**:原生 C 语言编写,无臃肿依赖,运行速度极快。
## 🏗️ 架构与二次开发
本项目注重代码的模块化和可维护性,非常适合作为 C 语言桌面程序开发的参考:
* **统一配置中心**:所有的 UI 尺寸、间距、颜色等常量配置均提取在 `include/config.h` 中,只需修改宏定义即可轻松定制属于你的专属界面风格。
* **清晰的全局状态**:全局变量和常量被独立分离在 `src/globals.c` / `include/globals.h` 中管理,使得核心业务逻辑更加整洁。
## 📦 下载与安装 ## 📦 下载与安装
@@ -28,10 +54,13 @@
* Windows 操作系统 * Windows 操作系统
* GCC 编译器 (推荐 MinGW-w64) * GCC 编译器 (推荐 MinGW-w64)
* Make 工具 * CMake 工具 (推荐使用 CMake 构建)
* IUP 库 (已包含在 `libs` 目录下) * IUP 库 (已包含在 `libs` 目录下)
* Inno Setup 6 (仅打包需要)
### 编译步骤 ### 编译步骤 (推荐使用 CMake)
本项目已迁移至 CMake 构建系统,支持生成更标准的构建文件并集成到各大 IDE。
1. 克隆仓库: 1. 克隆仓库:
@@ -40,30 +69,42 @@
cd PathEditor cd PathEditor
``` ```
2. 编译项目 2. 使用 CMake 配置和编译:
```bash ```bash
mingw32-make # 生成构建系统 (以 MinGW 为例)
cmake -B build -G "MinGW Makefiles"
# 编译项目
cmake --build build
``` ```
3. 运行: 3. 运行:
编译成功后,可执行文件位于 `bin/PathEditor.exe`。 编译成功后,可执行文件位于 `build/PathEditor.exe`。
*(注:项目依然保留了传统的 `mingw32-make` 方式,您可以直接在根目录运行 `mingw32-make` 进行编译,产物位于 `bin/` 目录。)*
### 打包 (可选) ### 打包 (可选)
本项目使用 Inno Setup 生成安装包。 本项目使用 Inno Setup 生成安装包。
1. 确保已安装 [Inno Setup 6](https://jrsoftware.org/isdl.php)。 1. 确保已安装 [Inno Setup 6](https://jrsoftware.org/isdl.php)。
2. 编译项目生成 exe 文件 2. 运行根目录下的 `build_installer.bat` 脚本
3. 使用 Inno Setup 编译 `dist/installer.iss` 脚本 3. 生成的安装包将位于 `dist/dist/PathEditorSetup.exe`
## 📝 使用说明 ## 📝 使用说明
1. **启动**:右键点击程序图标,选择“以管理员身份运行”。 1. **启动**:右键点击程序图标,选择“以管理员身份运行”。
2. **查看**:程序启动后会自动加载当前的系统 PATH 变量。 2. **查看**:程序启动后会自动加载当前的系统 PATH 变量。
3. **修改**:使用右侧按钮栏进行添加、删除、移动等操作 * **红色**条目表示路径不存在
4. **保存**:操作完成后,务必点击底部的【确定】按钮保存更改 * **橙色**条目表示路径重复
5. **生效**保存后,某些正在运行的程序可能需要重启才能识别新的环境变量。CMD 或 PowerShell 窗口需要重新打开 3. **搜索**在顶部输入关键词可快速筛选
4. **修改**
* 拖拽文件夹到列表可直接添加。
* 使用右侧按钮栏进行常规操作。
* 点击“一键清理”可自动删除无效和重复项。
5. **保存**:操作完成后,务必点击底部的【确定】按钮保存更改。
6. **生效**:保存后,某些正在运行的程序可能需要重启才能识别新的环境变量。CMD 或 PowerShell 窗口需要重新打开。
## 👤 作者信息 ## 👤 作者信息
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
@echo off
echo Building Installer...
"D:\Program Files (x86)\Inno Setup 6\ISCC.exe" "dist\installer.iss"
if %ERRORLEVEL% NEQ 0 (
echo Installer build failed!
exit /b %ERRORLEVEL%
)
echo Done! Installer is in dist\dist\
pause
+2 -2
View File
@@ -37,8 +37,8 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files] [Files]
Source: "d:\Code\doing_exercises\programs\PathEditor\bin\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "d:\Code\doing_exercises\programs\PathEditor\build\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "d:\Code\doing_exercises\programs\PathEditor\bin\*.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "d:\Code\doing_exercises\programs\PathEditor\build\*.dll"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
+20 -10
View File
@@ -4,17 +4,27 @@
#include <iup.h> #include <iup.h>
// 按钮回调 // 按钮回调
int btn_new_cb(Ihandle* self); int btn_new_cb(Ihandle *self);
int btn_edit_cb(Ihandle* self); int btn_edit_cb(Ihandle *self);
int btn_browse_cb(Ihandle* self); int btn_browse_cb(Ihandle *self);
int btn_del_cb(Ihandle* self); int btn_del_cb(Ihandle *self);
int btn_up_cb(Ihandle* self); int btn_up_cb(Ihandle *self);
int btn_down_cb(Ihandle* self); int btn_down_cb(Ihandle *self);
int btn_ok_cb(Ihandle* self); int btn_clean_cb(Ihandle *self);
int btn_cancel_cb(Ihandle* self); int btn_ok_cb(Ihandle *self);
int btn_help_cb(Ihandle* self); int btn_cancel_cb(Ihandle *self);
int btn_help_cb(Ihandle *self);
// 搜索回调
int txt_search_cb(Ihandle *self);
// 双击回调 // 双击回调
int list_dblclick_cb(Ihandle* self, int item, char* text); int list_dblclick_cb(Ihandle *self, int item, char *text);
// 拖拽回调
int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y);
// 键盘按键回调
int list_k_any_cb(Ihandle *self, int c);
#endif // CALLBACKS_H #endif // CALLBACKS_H
+33
View File
@@ -0,0 +1,33 @@
#ifndef CONFIG_H
#define CONFIG_H
// ============================================================================
// UI的配置常量
// ============================================================================
// 应用程序名称
#define APP_NAME "PathEditor" // 编辑环境变量应用程序名称
#define APP_NAME_READONLY "PathEditor (只读模式)" // 编辑环境变量只读模式标题
// 对话框设置
#define UI_DLG_SIZE "800x800" // 对话框默认大小 (像素)
#define UI_DLG_MINSIZE "800x800" // 对话框最小大小 (像素)
// 列表控件设置
#define UI_LIST_ITEM_PADDING "5x5" // 列表项内边距
#define UI_LIST_BACKCOLOR "255 255 255" // 列表背景颜色
// 按钮设置
#define UI_BTN_RASTERSIZE "100x32" // 按钮默认大小
// 布局间隙和边距
#define UI_VBOX_GAP "5" // 垂直布局项间隙
#define UI_VBOX_MARGIN "0x0" // 垂直布局外边距
#define UI_VBOX_ALL_MARGIN "10x10" // 垂直布局总外边距
#define UI_VBOX_ALL_GAP "5" // 垂直布局总间隙
#define UI_HBOX_GAP "10" // 水平布局项间隙
#define UI_HBOX_MARGIN "10x10" // 水平布局外边距
#define UI_HBOX_ALIGNMENT "ACENTER" // 水平布局对齐方式
#endif // CONFIG_H
+32 -2
View File
@@ -3,13 +3,26 @@
#include <iup.h> #include <iup.h>
// 定义 Windows 消息常量
#ifndef WM_COPYGLOBALDATA
#define WM_COPYGLOBALDATA 0x0049
#endif
// 消息过滤器常量
#ifndef MSGFLT_ADD
#define MSGFLT_ADD 1
#endif
// 注册表路径常量 // 注册表路径常量
#define REG_PATH L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" #define REG_PATH_SYS L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
#define REG_PATH_USER L"Environment"
#define REG_VALUE L"Path" #define REG_VALUE L"Path"
// 全局控件句柄声明 // 全局控件句柄声明
extern Ihandle *dlg; // 主对话框句柄 extern Ihandle *dlg; // 主对话框句柄
extern Ihandle *list_path; // 路径列表控件句柄 extern Ihandle *tabs_main; // 标签页容器
extern Ihandle *list_sys; // 系统变量列表
extern Ihandle *list_user; // 用户变量列表
extern Ihandle *lbl_status; // 状态标签句柄 extern Ihandle *lbl_status; // 状态标签句柄
extern Ihandle *btn_new; // 新增按钮句柄 extern Ihandle *btn_new; // 新增按钮句柄
extern Ihandle *btn_edit; // 编辑按钮句柄 extern Ihandle *btn_edit; // 编辑按钮句柄
@@ -17,8 +30,25 @@ extern Ihandle *btn_browse; // 浏览按钮句柄
extern Ihandle *btn_del; // 删除按钮句柄 extern Ihandle *btn_del; // 删除按钮句柄
extern Ihandle *btn_up; // 上移按钮句柄 extern Ihandle *btn_up; // 上移按钮句柄
extern Ihandle *btn_down; // 下移按钮句柄 extern Ihandle *btn_down; // 下移按钮句柄
extern Ihandle *btn_clean; // 一键清理按钮句柄
extern Ihandle *btn_ok; // 确认按钮句柄 extern Ihandle *btn_ok; // 确认按钮句柄
extern Ihandle *btn_cancel; // 取消按钮句柄 extern Ihandle *btn_cancel; // 取消按钮句柄
extern Ihandle *btn_help; // 帮助按钮句柄 extern Ihandle *btn_help; // 帮助按钮句柄
extern Ihandle *txt_search; // 搜索框
// 简单字符串列表结构,用于搜索缓存
typedef struct {
char **items;
int count;
int capacity;
} StringList;
extern StringList raw_sys_paths;
extern StringList raw_user_paths;
// 缓存操作函数声明
void init_string_list(StringList *list);
void add_string_list(StringList *list, const char *str);
void clear_string_list(StringList *list);
#endif // GLOBALS_H #endif // GLOBALS_H
+3 -3
View File
@@ -1,10 +1,10 @@
#ifndef REGISTRY_H #ifndef REGISTRY_H
#define REGISTRY_H #define REGISTRY_H
// 从注册表加载PATH到列表控件 // 从注册表加载所有PATH到列表控件
void load_path(); void load_all_paths();
// 将列表控件中的PATH保存回注册表 // 将列表控件中的PATH保存回注册表
void save_path(); void save_all_paths();
#endif // REGISTRY_H #endif // REGISTRY_H
+15
View File
@@ -0,0 +1,15 @@
#ifndef UI_H
#define UI_H
#include <iup.h>
// 创建列表控件
Ihandle *create_path_list();
// 创建右侧功能按钮区域
Ihandle *create_main_buttons();
// 创建底部按钮区域
Ihandle *create_bottom_buttons();
#endif // UI_H
+11
View File
@@ -3,6 +3,7 @@
#include <windows.h> #include <windows.h>
#include <wchar.h> #include <wchar.h>
#include <iup.h>
// 宽字符转UTF-8 // 宽字符转UTF-8
char* wide_to_utf8(const wchar_t* wstr); char* wide_to_utf8(const wchar_t* wstr);
@@ -13,7 +14,17 @@ wchar_t* utf8_to_wide(const char* str);
// 检查管理员权限 // 检查管理员权限
int check_admin(); int check_admin();
// 检查路径是否有效(存在且为目录)
int is_path_valid(const char *path);
// 刷新列表样式(斑马纹) // 刷新列表样式(斑马纹)
void refresh_list_style(); void refresh_list_style();
void refresh_single_list_style(Ihandle *list);
// 备份注册表
void backup_registry();
// 不区分大小写的字符串查找
char *stristr(const char *haystack, const char *needle);
#endif // UTILS_H #endif // UTILS_H
View File
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+258 -38
View File
@@ -27,8 +27,8 @@ int show_custom_input_dialog(const char *title, const char *label_text, char *bu
text, text,
IupHbox( IupHbox(
IupFill(), IupFill(),
IupButton("确定", "1"), // "1" will be returned by IupPopup IupButton("确定", "1"), // "1" 会被返回
IupButton("取消", "0"), // "0" will be returned by IupPopup IupButton("取消", "0"), // "0" 会被返回
NULL), NULL),
NULL)); NULL));
@@ -132,6 +132,20 @@ int custom_input_dialog(const char *title, const char *label_text, char *buffer,
return result; return result;
} }
// 辅助函数:获取当前选中的列表
Ihandle *get_current_list()
{
// 获取当前选中的 Tab 索引
// 注意:IupTabs 的 VALUE 属性在某些版本可能返回 handle,某些版本返回 pos
// 这里使用 IupGetInt(tabs_main, "VALUEPOS") 更稳妥
int pos = IupGetInt(tabs_main, "VALUEPOS");
if (pos == 0)
return list_sys;
if (pos == 1)
return list_user;
return list_sys; // 默认
}
// 按钮回调:新建 // 按钮回调:新建
int btn_new_cb(Ihandle *self) int btn_new_cb(Ihandle *self)
{ {
@@ -140,13 +154,14 @@ int btn_new_cb(Ihandle *self)
{ {
if (strlen(buffer) > 0) if (strlen(buffer) > 0)
{ {
int count = IupGetInt(list_path, "COUNT"); Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
count++; count++;
IupSetAttributeId(list_path, "", count, buffer); IupSetAttributeId(current_list, "", count, buffer);
IupSetInt(list_path, "COUNT", count); IupSetInt(current_list, "COUNT", count);
IupSetInt(list_path, "VALUE", count); IupSetInt(current_list, "VALUE", count);
refresh_list_style(); refresh_single_list_style(current_list);
} }
} }
return IUP_DEFAULT; return IUP_DEFAULT;
@@ -155,11 +170,12 @@ int btn_new_cb(Ihandle *self)
// 按钮回调:编辑 // 按钮回调:编辑
int btn_edit_cb(Ihandle *self) int btn_edit_cb(Ihandle *self)
{ {
int selected = IupGetInt(list_path, "VALUE"); Ihandle *current_list = get_current_list();
int selected = IupGetInt(current_list, "VALUE");
if (selected == 0) if (selected == 0)
return IUP_DEFAULT; return IUP_DEFAULT;
char *current_val = IupGetAttributeId(list_path, "", selected); char *current_val = IupGetAttributeId(current_list, "", selected);
char buffer[4096]; // 假设单个路径不超过4096 char buffer[4096]; // 假设单个路径不超过4096
if (current_val) if (current_val)
{ {
@@ -175,8 +191,8 @@ int btn_edit_cb(Ihandle *self)
{ {
if (strlen(buffer) > 0) if (strlen(buffer) > 0)
{ {
IupSetAttributeId(list_path, "", selected, buffer); IupSetAttributeId(current_list, "", selected, buffer);
refresh_list_style(); refresh_single_list_style(current_list);
} }
} }
return IUP_DEFAULT; return IUP_DEFAULT;
@@ -185,11 +201,11 @@ int btn_edit_cb(Ihandle *self)
// 双击回调 // 双击回调
int list_dblclick_cb(Ihandle *self, int item, char *text) int list_dblclick_cb(Ihandle *self, int item, char *text)
{ {
// 这里的 item 是点击的行号 // 这里的 self 就是触发双击的列表控件
if (item > 0) if (item > 0)
{ {
// 选中该行 // 选中该行
IupSetInt(list_path, "VALUE", item); IupSetInt(self, "VALUE", item);
// 调用编辑逻辑 // 调用编辑逻辑
btn_edit_cb(NULL); btn_edit_cb(NULL);
} }
@@ -210,42 +226,100 @@ int btn_browse_cb(Ihandle *self)
char *value = IupGetAttribute(filedlg, "VALUE"); char *value = IupGetAttribute(filedlg, "VALUE");
if (value) if (value)
{ {
int count = IupGetInt(list_path, "COUNT"); Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
count++; count++;
IupSetAttributeId(list_path, "", count, value); IupSetAttributeId(current_list, "", count, value);
IupSetInt(list_path, "COUNT", count); IupSetInt(current_list, "COUNT", count);
IupSetInt(list_path, "VALUE", count); IupSetInt(current_list, "VALUE", count);
refresh_list_style(); refresh_single_list_style(current_list);
} }
} }
IupDestroy(filedlg); IupDestroy(filedlg);
return IUP_DEFAULT; return IUP_DEFAULT;
} }
// 辅助函数:从 raw_data 中删除指定字符串
static void remove_from_raw_data(StringList *list, const char *str)
{
if (!list || !str)
return;
for (int i = 0; i < list->count; i++)
{
if (strcmp(list->items[i], str) == 0)
{
free(list->items[i]);
// 移动后续元素
for (int j = i; j < list->count - 1; j++)
{
list->items[j] = list->items[j + 1];
}
list->count--;
break; // 假设没有重复,只删除第一个匹配
}
}
}
// 按钮回调:删除 // 按钮回调:删除
int btn_del_cb(Ihandle *self) int btn_del_cb(Ihandle *self)
{ {
int selected = IupGetInt(list_path, "VALUE"); Ihandle *current_list = get_current_list();
int selected = IupGetInt(current_list, "VALUE");
if (selected == 0) if (selected == 0)
{
IupMessage("提示", "请先选择要删除的项");
return IUP_DEFAULT; return IUP_DEFAULT;
}
IupSetAttribute(list_path, "REMOVEITEM", "SELECTED"); // 获取当前要删除的内容
char *val = IupGetAttributeId(current_list, "", selected);
// 确认删除
// char msg[1024];
// snprintf(msg, sizeof(msg), "确定要删除以下路径吗?\n\n%s", val ? val : "(空)");
// if (IupAlarm("确认删除", msg, "是", "否", NULL) != 1)
// return IUP_DEFAULT;
// 从 raw_data 缓存中同步删除
int pos = IupGetInt(tabs_main, "VALUEPOS");
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 注意:必须先保存 val 的副本,因为 REMOVEITEM 可能会导致 val 指针失效(如果它是指向列表内部缓冲区的)
char *val_copy = val ? _strdup(val) : NULL;
// 先从界面删除
// IupSetAttribute(current_list, "REMOVEITEM", "SELECTED");
// 改为按索引删除,防止失去焦点导致 SELECTED 失效
IupSetInt(current_list, "REMOVEITEM", selected);
// 再从缓存删除
if (val_copy && raw_data)
{
remove_from_raw_data(raw_data, val_copy);
free(val_copy);
}
// 重新刷新
refresh_single_list_style(current_list);
// 更新状态栏,告知用户删除了什么
IupSetAttribute(lbl_status, "TITLE", "状态: 已删除选中项");
// 重新刷新,因为删除了中间项,后面的奇偶性变了
refresh_list_style();
return IUP_DEFAULT; return IUP_DEFAULT;
} }
// 按钮回调:上移 // 按钮回调:上移
int btn_up_cb(Ihandle *self) int btn_up_cb(Ihandle *self)
{ {
int selected = IupGetInt(list_path, "VALUE"); Ihandle *current_list = get_current_list();
int selected = IupGetInt(current_list, "VALUE");
if (selected <= 1) if (selected <= 1)
return IUP_DEFAULT; // 已经是第一个或未选中 return IUP_DEFAULT; // 已经是第一个或未选中
char *current = IupGetAttributeId(list_path, "", selected); char *current = IupGetAttributeId(current_list, "", selected);
char *prev = IupGetAttributeId(list_path, "", selected - 1); char *prev = IupGetAttributeId(current_list, "", selected - 1);
// 交换内容 // 交换内容
char buf_curr[4096], buf_prev[4096]; char buf_curr[4096], buf_prev[4096];
@@ -254,26 +328,27 @@ int btn_up_cb(Ihandle *self)
strncpy(buf_prev, prev, 4096); strncpy(buf_prev, prev, 4096);
buf_prev[4095] = '\0'; buf_prev[4095] = '\0';
IupSetAttributeId(list_path, "", selected, buf_prev); IupSetAttributeId(current_list, "", selected, buf_prev);
IupSetAttributeId(list_path, "", selected - 1, buf_curr); IupSetAttributeId(current_list, "", selected - 1, buf_curr);
IupSetInt(list_path, "VALUE", selected - 1); IupSetInt(current_list, "VALUE", selected - 1);
// 刷新样式(虽然颜色不需要变,但为了保险) // 刷新样式(虽然颜色不需要变,但为了保险)
refresh_list_style(); refresh_single_list_style(current_list);
return IUP_DEFAULT; return IUP_DEFAULT;
} }
// 按钮回调:下移 // 按钮回调:下移
int btn_down_cb(Ihandle *self) int btn_down_cb(Ihandle *self)
{ {
int selected = IupGetInt(list_path, "VALUE"); Ihandle *current_list = get_current_list();
int count = IupGetInt(list_path, "COUNT"); int selected = IupGetInt(current_list, "VALUE");
int count = IupGetInt(current_list, "COUNT");
if (selected == 0 || selected >= count) if (selected == 0 || selected >= count)
return IUP_DEFAULT; return IUP_DEFAULT;
char *current = IupGetAttributeId(list_path, "", selected); char *current = IupGetAttributeId(current_list, "", selected);
char *next = IupGetAttributeId(list_path, "", selected + 1); char *next = IupGetAttributeId(current_list, "", selected + 1);
char buf_curr[4096], buf_next[4096]; char buf_curr[4096], buf_next[4096];
strncpy(buf_curr, current, 4096); strncpy(buf_curr, current, 4096);
@@ -281,19 +356,164 @@ int btn_down_cb(Ihandle *self)
strncpy(buf_next, next, 4096); strncpy(buf_next, next, 4096);
buf_next[4095] = '\0'; buf_next[4095] = '\0';
IupSetAttributeId(list_path, "", selected, buf_next); IupSetAttributeId(current_list, "", selected, buf_next);
IupSetAttributeId(list_path, "", selected + 1, buf_curr); IupSetAttributeId(current_list, "", selected + 1, buf_curr);
IupSetInt(list_path, "VALUE", selected + 1); IupSetInt(current_list, "VALUE", selected + 1);
refresh_list_style(); refresh_single_list_style(current_list);
return IUP_DEFAULT;
}
// 按钮回调:一键清理
int btn_clean_cb(Ihandle *self)
{
Ihandle *current_list = get_current_list();
int count = IupGetInt(current_list, "COUNT");
if (count == 0)
return IUP_DEFAULT;
// 弹出确认对话框
if (IupAlarm("确认清理", "此操作将移除当前列表中所有【无效路径】和【重复路径】。\n确定要继续吗?", "确定", "取消", NULL) != 1)
{
return IUP_DEFAULT;
}
// 从后往前遍历删除,避免索引错位
for (int i = count; i >= 1; i--)
{
char *item = IupGetAttributeId(current_list, "", i);
if (!item)
continue;
int should_remove = 0;
// 1. 检查有效性
if (!is_path_valid(item))
{
should_remove = 1;
}
else
{
// 2. 检查重复 (检查当前项之前是否出现过)
// 注意:这里需要再次遍历,性能稍低但最稳妥
for (int j = 1; j < i; j++)
{
char *prev_item = IupGetAttributeId(current_list, "", j);
if (prev_item && _stricmp(item, prev_item) == 0)
{
should_remove = 1;
break;
}
}
}
if (should_remove)
{
IupSetAttributeId(current_list, "REMOVEITEM", i, NULL);
}
}
refresh_single_list_style(current_list);
IupMessage("提示", "清理完成!");
return IUP_DEFAULT;
}
// 搜索回调
int txt_search_cb(Ihandle *self)
{
char *filter = IupGetAttribute(self, "VALUE");
if (!filter)
return IUP_DEFAULT;
// 获取当前选中的 Tab 索引
int pos = IupGetInt(tabs_main, "VALUEPOS");
Ihandle *current_list = (pos == 0) ? list_sys : list_user;
StringList *raw_data = (pos == 0) ? &raw_sys_paths : &raw_user_paths;
// 清空列表
IupSetAttribute(current_list, "REMOVEITEM", "ALL");
// 重新填充
int count = 0;
for (int i = 0; i < raw_data->count; i++)
{
// 如果 filter 为空,或包含 filter (不区分大小写)
if (strlen(filter) == 0 || stristr(raw_data->items[i], filter) != NULL)
{
count++;
IupSetAttributeId(current_list, "", count, raw_data->items[i]);
}
}
IupSetInt(current_list, "COUNT", count);
refresh_single_list_style(current_list);
return IUP_DEFAULT;
}
// 拖拽回调
int list_dropfiles_cb(Ihandle *self, const char *filename, int num, int x, int y)
{
// 获取当前列表和原始数据
// 注意:拖拽的目标列表可能是 list_sys 或 list_user,由 self 参数决定
// 但为了确保数据一致性,我们还是重新获取一下
Ihandle *current_list = self;
StringList *raw_data = NULL;
if (self == list_sys)
raw_data = &raw_sys_paths;
else if (self == list_user)
raw_data = &raw_user_paths;
else
return IUP_DEFAULT; // 异常情况
// 检查拖入的是否为目录
DWORD attr = GetFileAttributesA(filename);
if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY))
{
// 如果正在搜索,先清空搜索框
IupSetAttribute(txt_search, "VALUE", "");
// 添加到列表末尾
int count = IupGetInt(current_list, "COUNT");
count++;
IupSetAttributeId(current_list, "", count, filename);
IupSetInt(current_list, "COUNT", count);
IupSetInt(current_list, "VALUE", count); // 选中新添加的项
// 同时添加到原始数据缓存,确保搜索时能搜到
if (raw_data)
{
add_string_list(raw_data, filename);
}
refresh_single_list_style(current_list);
}
else
{
// 如果拖入的不是文件夹,可以在状态栏提示
IupSetAttribute(lbl_status, "TITLE", "提示: 只能拖拽文件夹添加到 PATH");
}
return IUP_DEFAULT;
}
// 键盘按键回调
int list_k_any_cb(Ihandle *self, int c)
{
// 处理 Delete 键
if (c == K_DEL)
{
btn_del_cb(NULL);
return IUP_IGNORE; // 阻止默认处理
}
return IUP_DEFAULT; return IUP_DEFAULT;
} }
// 按钮回调:确定 // 按钮回调:确定
int btn_ok_cb(Ihandle *self) int btn_ok_cb(Ihandle *self)
{ {
save_path(); save_all_paths();
return IUP_DEFAULT; return IUP_DEFAULT;
} }
+14
View File
@@ -0,0 +1,14 @@
#include "globals.h"
#include <windows.h>
#include <iup.h>
// 全局变量定义
StringList raw_sys_paths = {0};
StringList raw_user_paths = {0};
// 全局控件定义
Ihandle *dlg, *tabs_main, *list_sys, *list_user, *lbl_status; // 主窗口、标签页、系统路径列表、用户路径列表、状态标签
Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; // 右侧按钮
Ihandle *btn_ok, *btn_cancel, *btn_help; // 确认取消帮助按钮
Ihandle *btn_clean; // 一键清理按钮
Ihandle *txt_search; // 搜索框
+68 -71
View File
@@ -7,11 +7,16 @@
#include "utils.h" #include "utils.h"
#include "registry.h" #include "registry.h"
#include "callbacks.h" #include "callbacks.h"
#include "ui.h"
#include "config.h"
// 全局控件定义 /*
Ihandle *dlg, *list_path, *lbl_status; !编译命令:
Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; cmake -B build -G "MinGW Makefiles"
Ihandle *btn_ok, *btn_cancel, *btn_help; cmake --build build
!打包命令:
build_installer.bat
*/
// 主函数 // 主函数
int main(int argc, char **argv) int main(int argc, char **argv)
@@ -22,91 +27,73 @@ int main(int argc, char **argv)
IupOpen(&argc, &argv); IupOpen(&argc, &argv);
IupSetGlobal("UTF8MODE", "YES"); IupSetGlobal("UTF8MODE", "YES");
// 创建列表控件 // 在管理员模式下,解决无法拖拽文件到列表框的问题 (UIPI)
list_path = IupFlatList(); // 需要加载 User32.dll 获取 ChangeWindowMessageFilter 函数
IupSetAttribute(list_path, "EXPAND", "YES"); HMODULE hUser32 = LoadLibraryW(L"user32.dll");
IupSetAttribute(list_path, "ITEMPADDING", "5x5"); if (hUser32)
IupSetAttribute(list_path, "BACKCOLOR", "255 255 255"); {
IupSetAttribute(list_path, "BORDER", "YES"); typedef BOOL(WINAPI * ChangeWindowMessageFilterProc)(UINT, DWORD);
IupSetAttribute(list_path, "CANFOCUS", "YES"); ChangeWindowMessageFilterProc pChangeWindowMessageFilter =
IupSetAttribute(list_path, "HLINE", "NO"); // 禁用横线,使用斑马纹 (ChangeWindowMessageFilterProc)GetProcAddress(hUser32, "ChangeWindowMessageFilter");
// IupFlatList 不支持 VISIBLELINES,高度由 EXPAND 和布局决定
// 创建右侧按钮 if (pChangeWindowMessageFilter)
btn_new = IupButton("新建(N)", NULL); {
btn_edit = IupButton("编辑(E)", NULL); pChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
btn_browse = IupButton("浏览(B)...", NULL); pChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD);
btn_del = IupButton("删除(D)", NULL); pChangeWindowMessageFilter(WM_COPYGLOBALDATA, MSGFLT_ADD);
btn_up = IupButton("上移(U)", NULL); }
btn_down = IupButton("下移(O)", NULL); FreeLibrary(hUser32);
}
// 设置按钮回调 // 创建两个列表控件
IupSetCallback(btn_new, "ACTION", (Icallback)btn_new_cb); list_sys = create_path_list();
IupSetCallback(btn_edit, "ACTION", (Icallback)btn_edit_cb); list_user = create_path_list();
IupSetCallback(btn_browse, "ACTION", (Icallback)btn_browse_cb);
IupSetCallback(btn_del, "ACTION", (Icallback)btn_del_cb);
IupSetCallback(btn_up, "ACTION", (Icallback)btn_up_cb);
IupSetCallback(btn_down, "ACTION", (Icallback)btn_down_cb);
// 设置双击回调 // 创建搜索框
IupSetCallback(list_path, "DBLCLICK_CB", (Icallback)list_dblclick_cb); txt_search = IupText(NULL);
IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL");
IupSetAttribute(txt_search, "CUEBANNER", "输入关键词搜索...");
IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb);
// 设置按钮大小 (宽度和高度都增加约1/4) // 创建 Tabs
IupSetAttribute(btn_new, "RASTERSIZE", "100x32"); tabs_main = IupTabs(
IupSetAttribute(btn_edit, "RASTERSIZE", "100x32"); IupVbox(list_sys, NULL),
IupSetAttribute(btn_browse, "RASTERSIZE", "100x32"); IupVbox(list_user, NULL),
IupSetAttribute(btn_del, "RASTERSIZE", "100x32");
IupSetAttribute(btn_up, "RASTERSIZE", "100x32");
IupSetAttribute(btn_down, "RASTERSIZE", "100x32");
Ihandle *vbox_btns = IupVbox(
btn_new, btn_edit, btn_browse, btn_del,
IupFill(), // 间隔
btn_up, btn_down,
NULL); NULL);
IupSetAttribute(vbox_btns, "GAP", "5"); IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System)");
IupSetAttribute(vbox_btns, "MARGIN", "0x0"); IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User)");
IupSetAttribute(tabs_main, "TABTYPE", "TOP");
// 上部布局:列表 + 按钮 // 创建右侧按钮区域
Ihandle *hbox_main = IupHbox(list_path, vbox_btns, NULL); Ihandle *vbox_btns = create_main_buttons();
IupSetAttribute(hbox_main, "GAP", "10");
IupSetAttribute(hbox_main, "MARGIN", "10x10"); // 上部布局:Tabs + 按钮
Ihandle *hbox_main = IupHbox(tabs_main, vbox_btns, NULL);
IupSetAttribute(hbox_main, "GAP", UI_HBOX_GAP);
IupSetAttribute(hbox_main, "MARGIN", UI_HBOX_MARGIN);
// 状态标签 // 状态标签
lbl_status = IupLabel("状态: 就绪"); lbl_status = IupLabel("状态: 就绪");
IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL"); IupSetAttribute(lbl_status, "EXPAND", "HORIZONTAL");
// 底部按钮 // 创建底部按钮区域
btn_ok = IupButton("确定", NULL); Ihandle *hbox_bottom = create_bottom_buttons();
btn_cancel = IupButton("取消", NULL);
btn_help = IupButton("帮助(?)", NULL);
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb);
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb);
IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb);
IupSetAttribute(btn_ok, "RASTERSIZE", "100x32");
IupSetAttribute(btn_cancel, "RASTERSIZE", "100x32");
IupSetAttribute(btn_help, "RASTERSIZE", "100x32");
Ihandle *hbox_bottom = IupHbox(lbl_status, IupFill(), btn_help, btn_ok, btn_cancel, NULL);
IupSetAttribute(hbox_bottom, "GAP", "10");
IupSetAttribute(hbox_bottom, "MARGIN", "10x10");
IupSetAttribute(hbox_bottom, "ALIGNMENT", "ACENTER");
// 总体布局 // 总体布局
Ihandle *vbox_all = IupVbox( Ihandle *vbox_all = IupVbox(
IupLabel("系统变量 Path:"), IupLabel("环境变量编辑器:"),
txt_search,
hbox_main, hbox_main,
hbox_bottom, hbox_bottom,
NULL); NULL);
IupSetAttribute(vbox_all, "MARGIN", "10x10"); IupSetAttribute(vbox_all, "MARGIN", UI_VBOX_ALL_MARGIN);
IupSetAttribute(vbox_all, "GAP", "5"); IupSetAttribute(vbox_all, "GAP", UI_VBOX_ALL_GAP);
// 创建对话框 // 创建对话框
dlg = IupDialog(vbox_all); dlg = IupDialog(vbox_all);
IupSetAttribute(dlg, "TITLE", "编辑环境变量 (IUP版)"); IupSetAttribute(dlg, "TITLE", APP_NAME); // 对话框标题
IupSetAttribute(dlg, "SIZE", "450x350"); IupSetAttribute(dlg, "RASTERSIZE", UI_DLG_SIZE); // 对话框初始大小 (像素)
IupSetAttribute(dlg, "MINSIZE", UI_DLG_MINSIZE); // 对话框最小大小 (像素)
IupSetAttribute(dlg, "MINBOX", "NO"); IupSetAttribute(dlg, "MINBOX", "NO");
IupSetAttribute(dlg, "MAXBOX", "NO"); IupSetAttribute(dlg, "MAXBOX", "NO");
@@ -114,15 +101,25 @@ int main(int argc, char **argv)
if (!check_admin()) if (!check_admin())
{ {
IupMessage("警告", "程序未以管理员身份运行,您只能查看,无法保存更改!"); IupMessage("警告", "程序未以管理员身份运行,您只能查看,无法保存更改!");
IupSetAttribute(dlg, "TITLE", "编辑环境变量 (只读模式)"); IupSetAttribute(dlg, "TITLE", APP_NAME_READONLY); // 对话框标题 (只读模式)
IupSetAttribute(lbl_status, "TITLE", "状态: 只读模式 (权限不足)"); IupSetAttribute(lbl_status, "TITLE", "状态: 只读模式 (权限不足)");
// 禁用修改类按钮
IupSetAttribute(btn_new, "ACTIVE", "NO");
IupSetAttribute(btn_edit, "ACTIVE", "NO");
IupSetAttribute(btn_browse, "ACTIVE", "NO");
IupSetAttribute(btn_del, "ACTIVE", "NO");
IupSetAttribute(btn_up, "ACTIVE", "NO");
IupSetAttribute(btn_down, "ACTIVE", "NO");
IupSetAttribute(btn_clean, "ACTIVE", "NO");
IupSetAttribute(btn_ok, "ACTIVE", "NO");
} }
IupShowXY(dlg, IUP_CENTER, IUP_CENTER); IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
// IUP List APPEND 属性需要在控件 Map 之后才能生效 // IUP List APPEND 属性需要在控件 Map 之后才能生效
// IupShowXY 会触发 Map // IupShowXY 会触发 Map
load_path(); load_all_paths();
IupMainLoop(); IupMainLoop();
IupClose(); IupClose();
+110 -128
View File
@@ -6,17 +6,23 @@
#include <stdlib.h> #include <stdlib.h>
#include <wchar.h> #include <wchar.h>
// 从注册表加载PATH // 内部辅助函数:加载单个列表
void load_path() static void load_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list, StringList *cache)
{ {
// 清空旧缓存
clear_string_list(cache);
HKEY hKey; HKEY hKey;
LONG res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_PATH, 0, KEY_READ, &hKey); LONG res = RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_READ, &hKey);
if (res != ERROR_SUCCESS) if (res != ERROR_SUCCESS)
{ {
char msg[512]; // 只有 HKLM 失败才提示需要管理员,HKCU 失败可能是其他原因
snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", REG_PATH, res); if (hKeyRoot == HKEY_LOCAL_MACHINE)
IupMessage("错误", msg); {
IupSetAttribute(lbl_status, "TITLE", "状态: 注册表读取失败"); char msg[512];
snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", regPath, res);
IupMessage("错误", msg);
}
return; return;
} }
@@ -24,172 +30,148 @@ void load_path()
res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size); res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size);
if (res == ERROR_SUCCESS) if (res == ERROR_SUCCESS)
{ {
// 安全分配内存:size 是字节数,多分配 2 个字节给 null 终止符
wchar_t *buffer = (wchar_t *)malloc(size + 2); wchar_t *buffer = (wchar_t *)malloc(size + 2);
if (!buffer) if (buffer)
{ {
IupMessage("错误", "内存分配失败!"); memset(buffer, 0, size + 2);
RegCloseKey(hKey); if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS)
return;
}
// 初始化内存
memset(buffer, 0, size + 2);
if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS)
{
// 重新实现分割逻辑,避免 wcstok 的兼容性问题
wchar_t *current = buffer;
wchar_t *next_semicolon = NULL;
int count = 0;
// 清空列表
IupSetAttribute(list_path, "REMOVEITEM", "ALL");
// 检查内容是否为空
if (wcslen(buffer) == 0)
{ {
IupMessage("提示", "读取到的 PATH 变量为空!"); wchar_t *current = buffer;
wchar_t *next_semicolon = NULL;
int count = 0;
IupSetAttribute(list, "REMOVEITEM", "ALL");
while (*current)
{
next_semicolon = wcschr(current, L';');
if (next_semicolon)
*next_semicolon = L'\0';
if (wcslen(current) > 0)
{
char *utf8_str = wide_to_utf8(current);
// 添加到列表
count++;
IupSetAttributeId(list, "", count, utf8_str);
// 添加到缓存
add_string_list(cache, utf8_str);
free(utf8_str);
}
if (next_semicolon)
current = next_semicolon + 1;
else
break;
}
IupSetInt(list, "COUNT", count);
IupSetInt(list, "VALUE", 1);
} }
free(buffer);
while (*current)
{
// 查找下一个分号
next_semicolon = wcschr(current, L';');
if (next_semicolon)
{
*next_semicolon = L'\0'; // 临时截断
}
// 跳过空项
if (wcslen(current) > 0)
{
char *utf8_str = wide_to_utf8(current);
count++;
IupSetAttributeId(list_path, "", count, utf8_str);
free(utf8_str);
}
if (next_semicolon)
{
current = next_semicolon + 1;
}
else
{
break;
}
}
IupSetInt(list_path, "COUNT", count); // 显式设置列表项数量
IupSetInt(list_path, "VALUE", 1); // 选中第一项
// 刷新斑马纹样式
refresh_list_style();
char status_msg[100];
sprintf(status_msg, "状态: 已加载 %d 个条目", count);
IupSetAttribute(lbl_status, "TITLE", status_msg);
} }
else
{
IupMessage("错误", "读取 PATH 值内容失败!");
IupSetAttribute(lbl_status, "TITLE", "状态: 读取值内容失败");
}
free(buffer);
}
else
{
char msg[256];
sprintf(msg, "查询 PATH 值大小失败。错误码: %ld", res);
IupMessage("错误", msg);
IupSetAttribute(lbl_status, "TITLE", "状态: 查询值失败");
} }
RegCloseKey(hKey); RegCloseKey(hKey);
} }
// 保存PATH到注册表 // 加载所有PATH
void save_path() void load_all_paths()
{ {
if (!check_admin()) load_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list_sys, &raw_sys_paths);
{ load_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list_user, &raw_user_paths);
IupMessage("错误", "需要管理员权限才能保存更改!\n请重新以管理员身份运行程序。");
return;
}
int count = IupGetInt(list_path, "COUNT"); refresh_list_style();
if (count == 0) IupSetAttribute(lbl_status, "TITLE", "状态: 已加载系统和用户变量");
{ }
// 警告:清空PATH是很危险的
if (IupAlarm("警告", "PATH 为空!这可能导致系统命令无法使用。\n确定要保存吗?", "确定", "取消", NULL) != 1)
{
return;
}
}
// 计算所需缓冲区大小 // 内部辅助函数:保存单个列表
static int save_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list)
{
int count = IupGetInt(list, "COUNT");
// 计算大小
size_t total_len = 0; size_t total_len = 0;
for (int i = 1; i <= count; i++) for (int i = 1; i <= count; i++)
{ {
char *item = IupGetAttributeId(list_path, "", i); char *item = IupGetAttributeId(list, "", i);
if (item) if (item)
{ {
wchar_t *witem = utf8_to_wide(item); wchar_t *witem = utf8_to_wide(item);
total_len += wcslen(witem) + 1; // +1 for ';' total_len += wcslen(witem) + 1;
free(witem); free(witem);
} }
} }
total_len += 1; // null terminator total_len += 1;
wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t)); wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t));
if (!buffer) if (!buffer)
{ return 0;
IupMessage("错误", "内存分配失败 (保存时)");
return;
}
buffer[0] = L'\0';
buffer[0] = L'\0';
for (int i = 1; i <= count; i++) for (int i = 1; i <= count; i++)
{ {
char *item = IupGetAttributeId(list_path, "", i); char *item = IupGetAttributeId(list, "", i);
if (item) if (item)
{ {
wchar_t *witem = utf8_to_wide(item); wchar_t *witem = utf8_to_wide(item);
wcscat(buffer, witem); wcscat(buffer, witem);
if (i < count) if (i < count)
{
wcscat(buffer, L";"); wcscat(buffer, L";");
}
free(witem); free(witem);
} }
} }
// 写入注册表
HKEY hKey; HKEY hKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_PATH, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) int success = 0;
if (RegOpenKeyExW(hKeyRoot, regPath, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
{ {
// 使用 REG_EXPAND_SZ 类型,因为 PATH 可能包含 %SystemRoot%
DWORD size = (wcslen(buffer) + 1) * sizeof(wchar_t); DWORD size = (wcslen(buffer) + 1) * sizeof(wchar_t);
if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS) if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS)
{ {
// 发送系统广播通知环境变量已更改 success = 1;
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
IupMessage("成功", "PATH 环境变量已更新!");
IupSetAttribute(lbl_status, "TITLE", "状态: 保存成功");
}
else
{
IupMessage("错误", "写入注册表失败!");
IupSetAttribute(lbl_status, "TITLE", "状态: 保存失败");
} }
RegCloseKey(hKey); RegCloseKey(hKey);
} }
else
{
IupMessage("错误", "无法打开注册表进行写入。请检查权限!");
IupSetAttribute(lbl_status, "TITLE", "状态: 打开注册表失败");
}
free(buffer); free(buffer);
return success;
}
// 保存所有PATH
void save_all_paths()
{
if (!check_admin())
{
IupMessage("错误", "需要管理员权限才能保存更改!");
return;
}
// 备份
backup_registry();
int sys_ok = save_single_path(HKEY_LOCAL_MACHINE, REG_PATH_SYS, list_sys);
int user_ok = save_single_path(HKEY_CURRENT_USER, REG_PATH_USER, list_user);
if (sys_ok && user_ok)
{
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
IupMessage("成功", "系统和用户 PATH 环境变量均已更新!");
IupSetAttribute(lbl_status, "TITLE", "状态: 全部保存成功");
}
else if (sys_ok)
{
IupMessage("提示", "系统变量保存成功,但用户变量保存失败。");
}
else if (user_ok)
{
IupMessage("提示", "用户变量保存成功,但系统变量保存失败。");
}
else
{
IupMessage("错误", "保存失败!");
IupSetAttribute(lbl_status, "TITLE", "状态: 保存失败");
}
} }
+88
View File
@@ -0,0 +1,88 @@
#include "ui.h"
#include "globals.h"
#include "callbacks.h"
#include "config.h"
#include <stdlib.h>
// 创建列表控件
Ihandle *create_path_list()
{
Ihandle *list = IupFlatList();
IupSetAttribute(list, "EXPAND", "YES");
IupSetAttribute(list, "ITEMPADDING", UI_LIST_ITEM_PADDING);
IupSetAttribute(list, "BACKCOLOR", UI_LIST_BACKCOLOR);
IupSetAttribute(list, "BORDER", "YES");
IupSetAttribute(list, "CANFOCUS", "YES");
IupSetAttribute(list, "HLINE", "NO");
IupSetCallback(list, "DBLCLICK_CB", (Icallback)list_dblclick_cb);
IupSetCallback(list, "DROPFILES_CB", (Icallback)list_dropfiles_cb);
IupSetCallback(list, "K_ANY", (Icallback)list_k_any_cb);
return list;
}
// 创建右侧功能按钮区域
Ihandle *create_main_buttons()
{
// 创建右侧按钮
btn_new = IupButton("新建(N)", NULL);
btn_edit = IupButton("编辑(E)", NULL);
btn_browse = IupButton("浏览(B)...", NULL);
btn_del = IupButton("删除(D)", NULL);
btn_up = IupButton("上移(U)", NULL);
btn_down = IupButton("下移(O)", NULL);
btn_clean = IupButton("一键清理", NULL);
// 设置按钮回调
IupSetCallback(btn_new, "ACTION", (Icallback)btn_new_cb);
IupSetCallback(btn_edit, "ACTION", (Icallback)btn_edit_cb);
IupSetCallback(btn_browse, "ACTION", (Icallback)btn_browse_cb);
IupSetCallback(btn_del, "ACTION", (Icallback)btn_del_cb);
IupSetCallback(btn_up, "ACTION", (Icallback)btn_up_cb);
IupSetCallback(btn_down, "ACTION", (Icallback)btn_down_cb);
IupSetCallback(btn_clean, "ACTION", (Icallback)btn_clean_cb);
// 设置按钮大小
IupSetAttribute(btn_new, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_edit, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_browse, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_del, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_up, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_down, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_clean, "RASTERSIZE", UI_BTN_RASTERSIZE);
Ihandle *vbox_btns = IupVbox(
btn_new, btn_edit, btn_browse, btn_del,
IupFill(), // 间隔
btn_clean, // 放在上移下移之前,或者最下面,这里放在中间偏下
IupFill(),
btn_up, btn_down,
NULL);
IupSetAttribute(vbox_btns, "GAP", UI_VBOX_GAP);
IupSetAttribute(vbox_btns, "MARGIN", UI_VBOX_MARGIN);
return vbox_btns;
}
// 创建底部按钮区域
Ihandle *create_bottom_buttons()
{
// 底部按钮
btn_ok = IupButton("确定", NULL);
btn_cancel = IupButton("取消", NULL);
btn_help = IupButton("帮助(?)", NULL);
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_ok_cb);
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_cb);
IupSetCallback(btn_help, "ACTION", (Icallback)btn_help_cb);
IupSetAttribute(btn_ok, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_cancel, "RASTERSIZE", UI_BTN_RASTERSIZE);
IupSetAttribute(btn_help, "RASTERSIZE", UI_BTN_RASTERSIZE);
Ihandle *hbox_bottom = IupHbox(lbl_status, IupFill(), btn_help, btn_ok, btn_cancel, NULL);
IupSetAttribute(hbox_bottom, "GAP", UI_HBOX_GAP);
IupSetAttribute(hbox_bottom, "MARGIN", UI_HBOX_MARGIN);
IupSetAttribute(hbox_bottom, "ALIGNMENT", UI_HBOX_ALIGNMENT);
return hbox_bottom;
}
+180 -8
View File
@@ -3,6 +3,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <iup.h> #include <iup.h>
#include <time.h>
#include <direct.h>
#include <string.h>
// 宽字符转UTF-8 (用于IUP显示) // 宽字符转UTF-8 (用于IUP显示)
char *wide_to_utf8(const wchar_t *wstr) char *wide_to_utf8(const wchar_t *wstr)
@@ -39,23 +42,192 @@ int check_admin()
return 0; return 0;
} }
// 刷新列表样式(斑马纹) // 检查路径是否存在
void refresh_list_style() static int path_exists(const char *path)
{ {
if (!list_path) // 如果包含 %,说明是变量,无法直接检查存在性,默认视为有效
if (strchr(path, '%'))
return 1;
wchar_t *wpath = utf8_to_wide(path);
if (!wpath)
return 0;
DWORD attr = GetFileAttributesW(wpath);
free(wpath);
if (attr == INVALID_FILE_ATTRIBUTES)
return 0;
return (attr & FILE_ATTRIBUTE_DIRECTORY); // 必须是目录
}
// 检查路径是否存在(公开给外部使用)
int is_path_valid(const char *path)
{
return path_exists(path);
}
// 刷新列表样式(斑马纹 + 有效性检查)
void refresh_single_list_style(Ihandle *list)
{
if (!list)
return; return;
int count = IupGetInt(list_path, "COUNT"); int count = IupGetInt(list, "COUNT");
// 用于查重的简单数组(实际项目可以用哈希表)
// 为了简单,我们只用双重循环检查重复,性能在几百个条目下没问题
for (int i = 1; i <= count; i++) for (int i = 1; i <= count; i++)
{ {
// 奇数行:白色 char *item = IupGetAttributeId(list, "", i);
// 偶数行:极浅灰色 (245 245 245) if (!item)
continue;
// 默认颜色:黑字
char fg_color[32] = "0 0 0";
// 1. 检查有效性
if (!path_exists(item))
{
// 无效路径:红色
sprintf(fg_color, "255 0 0");
}
// 2. 检查重复 (只检查当前项之前的项,如果之前出现过,当前项标橙)
for (int j = 1; j < i; j++)
{
char *prev_item = IupGetAttributeId(list, "", j);
if (prev_item && _stricmp(item, prev_item) == 0) // Windows 路径不区分大小写
{
// 重复路径:橙色
sprintf(fg_color, "255 128 0");
break;
}
}
IupSetAttributeId(list, "ITEMFGCOLOR", i, fg_color);
// 斑马纹背景
if (i % 2 == 0) if (i % 2 == 0)
{ {
IupSetAttributeId(list_path, "ITEMBGCOLOR", i, "245 245 245"); IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245");
} }
else else
{ {
IupSetAttributeId(list_path, "ITEMBGCOLOR", i, "255 255 255"); IupSetAttributeId(list, "ITEMBGCOLOR", i, "255 255 255");
} }
} }
} }
// 刷新所有列表样式
void refresh_list_style()
{
refresh_single_list_style(list_sys);
refresh_single_list_style(list_user);
}
// 备份注册表
void backup_registry()
{
// 创建 records 目录
if (_mkdir("records") == -1)
{
// 目录可能已存在,忽略错误
}
// 获取当前时间
time_t t = time(NULL);
struct tm *tm_info = localtime(&t);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", tm_info);
// 构造备份文件名
char filename[256];
snprintf(filename, sizeof(filename), "records\\backup_%s.reg", time_str);
// 构造 reg export 命令
char full_cmd[1024];
snprintf(full_cmd, sizeof(full_cmd), "reg.exe export \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" \"%s\" /y", filename);
// 使用 CreateProcess 隐藏窗口执行
STARTUPINFOA si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE; // 隐藏窗口
memset(&pi, 0, sizeof(pi));
if (CreateProcessA(NULL, full_cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
// 等待进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
// 初始化字符串列表
void init_string_list(StringList *list)
{
list->items = NULL;
list->count = 0;
list->capacity = 0;
}
// 不区分大小写的字符串查找 (简单实现)
char *stristr(const char *haystack, const char *needle)
{
if (!haystack || !needle)
return NULL;
if (*needle == '\0')
return (char *)haystack;
const char *h = haystack;
const char *n = needle;
const char *h_start = haystack;
while (*h)
{
h_start = h;
n = needle;
while (*h && *n && (tolower((unsigned char)*h) == tolower((unsigned char)*n)))
{
h++;
n++;
}
if (*n == '\0')
return (char *)h_start;
h = h_start + 1;
}
return NULL;
}
// 添加字符串到列表
void add_string_list(StringList *list, const char *str)
{
if (list->count >= list->capacity)
{
list->capacity = (list->capacity == 0) ? 16 : list->capacity * 2;
list->items = (char **)realloc(list->items, list->capacity * sizeof(char *));
}
list->items[list->count] = _strdup(str); // 复制字符串
list->count++;
}
// 清空字符串列表
void clear_string_list(StringList *list)
{
for (int i = 0; i < list->count; i++)
{
free(list->items[i]);
}
free(list->items);
list->items = NULL;
list->count = 0;
list->capacity = 0;
}