diff --git a/bin/PathEditor.exe b/bin/PathEditor.exe index 68d21bf..da67560 100644 Binary files a/bin/PathEditor.exe and b/bin/PathEditor.exe differ diff --git a/bin/records/backup_20260316_193937.reg b/bin/records/backup_20260316_193937.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_193937.reg differ diff --git a/bin/records/backup_20260316_194700.reg b/bin/records/backup_20260316_194700.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_194700.reg differ diff --git a/bin/records/backup_20260316_195033.reg b/bin/records/backup_20260316_195033.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_195033.reg differ diff --git a/bin/records/backup_20260316_195233.reg b/bin/records/backup_20260316_195233.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_195233.reg differ diff --git a/bin/records/backup_20260316_195238.reg b/bin/records/backup_20260316_195238.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_195238.reg differ diff --git a/bin/records/backup_20260316_195622.reg b/bin/records/backup_20260316_195622.reg new file mode 100644 index 0000000..02da0a9 Binary files /dev/null and b/bin/records/backup_20260316_195622.reg differ diff --git a/include/callbacks.h b/include/callbacks.h index ea7dcc3..f3c9009 100644 --- a/include/callbacks.h +++ b/include/callbacks.h @@ -4,17 +4,27 @@ #include // 按钮回调 -int btn_new_cb(Ihandle* self); -int btn_edit_cb(Ihandle* self); -int btn_browse_cb(Ihandle* self); -int btn_del_cb(Ihandle* self); -int btn_up_cb(Ihandle* self); -int btn_down_cb(Ihandle* self); -int btn_ok_cb(Ihandle* self); -int btn_cancel_cb(Ihandle* self); -int btn_help_cb(Ihandle* self); +int btn_new_cb(Ihandle *self); +int btn_edit_cb(Ihandle *self); +int btn_browse_cb(Ihandle *self); +int btn_del_cb(Ihandle *self); +int btn_up_cb(Ihandle *self); +int btn_down_cb(Ihandle *self); +int btn_clean_cb(Ihandle *self); +int btn_ok_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 \ No newline at end of file diff --git a/include/globals.h b/include/globals.h index ba69969..85e9949 100644 --- a/include/globals.h +++ b/include/globals.h @@ -4,12 +4,15 @@ #include // 注册表路径常量 -#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" // 全局控件句柄声明 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 *btn_new; // 新增按钮句柄 extern Ihandle *btn_edit; // 编辑按钮句柄 @@ -17,8 +20,25 @@ extern Ihandle *btn_browse; // 浏览按钮句柄 extern Ihandle *btn_del; // 删除按钮句柄 extern Ihandle *btn_up; // 上移按钮句柄 extern Ihandle *btn_down; // 下移按钮句柄 +extern Ihandle *btn_clean; // 一键清理按钮句柄 extern Ihandle *btn_ok; // 确认按钮句柄 extern Ihandle *btn_cancel; // 取消按钮句柄 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 \ No newline at end of file diff --git a/include/registry.h b/include/registry.h index ac27727..c42d7bf 100644 --- a/include/registry.h +++ b/include/registry.h @@ -1,10 +1,10 @@ #ifndef REGISTRY_H #define REGISTRY_H -// 从注册表加载PATH到列表控件 -void load_path(); +// 从注册表加载所有PATH到列表控件 +void load_all_paths(); // 将列表控件中的PATH保存回注册表 -void save_path(); +void save_all_paths(); #endif // REGISTRY_H \ No newline at end of file diff --git a/include/utils.h b/include/utils.h index dd775a6..72be326 100644 --- a/include/utils.h +++ b/include/utils.h @@ -3,6 +3,7 @@ #include #include +#include // 宽字符转UTF-8 char* wide_to_utf8(const wchar_t* wstr); @@ -13,7 +14,17 @@ wchar_t* utf8_to_wide(const char* str); // 检查管理员权限 int check_admin(); +// 检查路径是否有效(存在且为目录) +int is_path_valid(const char *path); + // 刷新列表样式(斑马纹) 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 \ No newline at end of file diff --git a/src/callbacks.c b/src/callbacks.c index 63605ef..97f7f0f 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -27,8 +27,8 @@ int show_custom_input_dialog(const char *title, const char *label_text, char *bu text, IupHbox( IupFill(), - IupButton("确定", "1"), // "1" will be returned by IupPopup - IupButton("取消", "0"), // "0" will be returned by IupPopup + IupButton("确定", "1"), // "1" 会被返回 + IupButton("取消", "0"), // "0" 会被返回 NULL), NULL)); @@ -132,6 +132,20 @@ int custom_input_dialog(const char *title, const char *label_text, char *buffer, 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) { @@ -140,13 +154,14 @@ int btn_new_cb(Ihandle *self) { if (strlen(buffer) > 0) { - int count = IupGetInt(list_path, "COUNT"); + Ihandle *current_list = get_current_list(); + int count = IupGetInt(current_list, "COUNT"); count++; - IupSetAttributeId(list_path, "", count, buffer); - IupSetInt(list_path, "COUNT", count); - IupSetInt(list_path, "VALUE", count); + IupSetAttributeId(current_list, "", count, buffer); + IupSetInt(current_list, "COUNT", count); + IupSetInt(current_list, "VALUE", count); - refresh_list_style(); + refresh_single_list_style(current_list); } } return IUP_DEFAULT; @@ -155,11 +170,12 @@ int btn_new_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) return IUP_DEFAULT; - char *current_val = IupGetAttributeId(list_path, "", selected); + char *current_val = IupGetAttributeId(current_list, "", selected); char buffer[4096]; // 假设单个路径不超过4096 if (current_val) { @@ -175,8 +191,8 @@ int btn_edit_cb(Ihandle *self) { if (strlen(buffer) > 0) { - IupSetAttributeId(list_path, "", selected, buffer); - refresh_list_style(); + IupSetAttributeId(current_list, "", selected, buffer); + refresh_single_list_style(current_list); } } return IUP_DEFAULT; @@ -185,11 +201,11 @@ int btn_edit_cb(Ihandle *self) // 双击回调 int list_dblclick_cb(Ihandle *self, int item, char *text) { - // 这里的 item 是点击的行号 + // 这里的 self 就是触发双击的列表控件 if (item > 0) { // 选中该行 - IupSetInt(list_path, "VALUE", item); + IupSetInt(self, "VALUE", item); // 调用编辑逻辑 btn_edit_cb(NULL); } @@ -210,42 +226,100 @@ int btn_browse_cb(Ihandle *self) char *value = IupGetAttribute(filedlg, "VALUE"); if (value) { - int count = IupGetInt(list_path, "COUNT"); + Ihandle *current_list = get_current_list(); + int count = IupGetInt(current_list, "COUNT"); count++; - IupSetAttributeId(list_path, "", count, value); - IupSetInt(list_path, "COUNT", count); - IupSetInt(list_path, "VALUE", count); + IupSetAttributeId(current_list, "", count, value); + IupSetInt(current_list, "COUNT", count); + IupSetInt(current_list, "VALUE", count); - refresh_list_style(); + refresh_single_list_style(current_list); } } IupDestroy(filedlg); 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 selected = IupGetInt(list_path, "VALUE"); + Ihandle *current_list = get_current_list(); + int selected = IupGetInt(current_list, "VALUE"); + if (selected == 0) + { + IupMessage("提示", "请先选择要删除的项"); 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; } // 按钮回调:上移 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) return IUP_DEFAULT; // 已经是第一个或未选中 - char *current = IupGetAttributeId(list_path, "", selected); - char *prev = IupGetAttributeId(list_path, "", selected - 1); + char *current = IupGetAttributeId(current_list, "", selected); + char *prev = IupGetAttributeId(current_list, "", selected - 1); // 交换内容 char buf_curr[4096], buf_prev[4096]; @@ -254,26 +328,27 @@ int btn_up_cb(Ihandle *self) strncpy(buf_prev, prev, 4096); buf_prev[4095] = '\0'; - IupSetAttributeId(list_path, "", selected, buf_prev); - IupSetAttributeId(list_path, "", selected - 1, buf_curr); + IupSetAttributeId(current_list, "", selected, buf_prev); + 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_down_cb(Ihandle *self) { - int selected = IupGetInt(list_path, "VALUE"); - int count = IupGetInt(list_path, "COUNT"); + Ihandle *current_list = get_current_list(); + int selected = IupGetInt(current_list, "VALUE"); + int count = IupGetInt(current_list, "COUNT"); if (selected == 0 || selected >= count) return IUP_DEFAULT; - char *current = IupGetAttributeId(list_path, "", selected); - char *next = IupGetAttributeId(list_path, "", selected + 1); + char *current = IupGetAttributeId(current_list, "", selected); + char *next = IupGetAttributeId(current_list, "", selected + 1); char buf_curr[4096], buf_next[4096]; strncpy(buf_curr, current, 4096); @@ -281,19 +356,164 @@ int btn_down_cb(Ihandle *self) strncpy(buf_next, next, 4096); buf_next[4095] = '\0'; - IupSetAttributeId(list_path, "", selected, buf_next); - IupSetAttributeId(list_path, "", selected + 1, buf_curr); + IupSetAttributeId(current_list, "", selected, buf_next); + 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; } // 按钮回调:确定 int btn_ok_cb(Ihandle *self) { - save_path(); + save_all_paths(); return IUP_DEFAULT; } diff --git a/src/main.c b/src/main.c index 77ef73c..fa1afbf 100644 --- a/src/main.c +++ b/src/main.c @@ -8,10 +8,32 @@ #include "registry.h" #include "callbacks.h" +// 全局变量定义 +StringList raw_sys_paths = {0}; +StringList raw_user_paths = {0}; + // 全局控件定义 -Ihandle *dlg, *list_path, *lbl_status; -Ihandle *btn_new, *btn_edit, *btn_browse, *btn_del, *btn_up, *btn_down; -Ihandle *btn_ok, *btn_cancel, *btn_help; +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; // 搜索框 + +// 辅助函数:创建列表控件 +Ihandle *create_path_list() +{ + Ihandle *list = IupFlatList(); + IupSetAttribute(list, "EXPAND", "YES"); + IupSetAttribute(list, "ITEMPADDING", "5x5"); + IupSetAttribute(list, "BACKCOLOR", "255 255 255"); + 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; +} // 主函数 int main(int argc, char **argv) @@ -22,15 +44,24 @@ int main(int argc, char **argv) IupOpen(&argc, &argv); IupSetGlobal("UTF8MODE", "YES"); - // 创建列表控件 - list_path = IupFlatList(); - IupSetAttribute(list_path, "EXPAND", "YES"); - IupSetAttribute(list_path, "ITEMPADDING", "5x5"); - IupSetAttribute(list_path, "BACKCOLOR", "255 255 255"); - IupSetAttribute(list_path, "BORDER", "YES"); - IupSetAttribute(list_path, "CANFOCUS", "YES"); - IupSetAttribute(list_path, "HLINE", "NO"); // 禁用横线,使用斑马纹 - // IupFlatList 不支持 VISIBLELINES,高度由 EXPAND 和布局决定 + // 创建两个列表控件 + list_sys = create_path_list(); + list_user = create_path_list(); + + // 创建搜索框 + txt_search = IupText(NULL); + IupSetAttribute(txt_search, "EXPAND", "HORIZONTAL"); + IupSetAttribute(txt_search, "CUEBANNER", "输入关键词搜索..."); + IupSetCallback(txt_search, "VALUECHANGED_CB", (Icallback)txt_search_cb); + + // 创建 Tabs + tabs_main = IupTabs( + IupVbox(list_sys, NULL), + IupVbox(list_user, NULL), + NULL); + IupSetAttribute(tabs_main, "TABTITLE0", "系统变量 (System)"); + IupSetAttribute(tabs_main, "TABTITLE1", "用户变量 (User)"); + IupSetAttribute(tabs_main, "TABTYPE", "TOP"); // 创建右侧按钮 btn_new = IupButton("新建(N)", NULL); @@ -39,6 +70,7 @@ int main(int argc, char **argv) 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); @@ -47,9 +79,7 @@ int main(int argc, char **argv) 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); + IupSetCallback(btn_clean, "ACTION", (Icallback)btn_clean_cb); // 设置按钮大小 (宽度和高度都增加约1/4) IupSetAttribute(btn_new, "RASTERSIZE", "100x32"); @@ -58,17 +88,20 @@ int main(int argc, char **argv) IupSetAttribute(btn_del, "RASTERSIZE", "100x32"); IupSetAttribute(btn_up, "RASTERSIZE", "100x32"); IupSetAttribute(btn_down, "RASTERSIZE", "100x32"); + IupSetAttribute(btn_clean, "RASTERSIZE", "100x32"); 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", "5"); IupSetAttribute(vbox_btns, "MARGIN", "0x0"); - // 上部布局:列表 + 按钮 - Ihandle *hbox_main = IupHbox(list_path, vbox_btns, NULL); + // 上部布局:Tabs + 按钮 + Ihandle *hbox_main = IupHbox(tabs_main, vbox_btns, NULL); IupSetAttribute(hbox_main, "GAP", "10"); IupSetAttribute(hbox_main, "MARGIN", "10x10"); @@ -96,7 +129,8 @@ int main(int argc, char **argv) // 总体布局 Ihandle *vbox_all = IupVbox( - IupLabel("系统变量 Path:"), + IupLabel("环境变量编辑器:"), + txt_search, hbox_main, hbox_bottom, NULL); @@ -106,7 +140,7 @@ int main(int argc, char **argv) // 创建对话框 dlg = IupDialog(vbox_all); IupSetAttribute(dlg, "TITLE", "编辑环境变量 (IUP版)"); - IupSetAttribute(dlg, "SIZE", "450x350"); + IupSetAttribute(dlg, "SIZE", "500x400"); // 稍微调大一点 IupSetAttribute(dlg, "MINBOX", "NO"); IupSetAttribute(dlg, "MAXBOX", "NO"); @@ -116,13 +150,23 @@ int main(int argc, char **argv) IupMessage("警告", "程序未以管理员身份运行,您只能查看,无法保存更改!"); IupSetAttribute(dlg, "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); // IUP List APPEND 属性需要在控件 Map 之后才能生效 // IupShowXY 会触发 Map - load_path(); + load_all_paths(); IupMainLoop(); IupClose(); diff --git a/src/registry.c b/src/registry.c index 1b9f6bb..51cd288 100644 --- a/src/registry.c +++ b/src/registry.c @@ -6,17 +6,23 @@ #include #include -// 从注册表加载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; - 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) { - char msg[512]; - snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", REG_PATH, res); - IupMessage("错误", msg); - IupSetAttribute(lbl_status, "TITLE", "状态: 注册表读取失败"); + // 只有 HKLM 失败才提示需要管理员,HKCU 失败可能是其他原因 + if (hKeyRoot == HKEY_LOCAL_MACHINE) + { + char msg[512]; + snprintf(msg, sizeof(msg), "无法打开注册表键 (HKLM)。\n路径: %ls\n错误码: %ld\n\n请尝试右键点击程序 -> '以管理员身份运行'。", regPath, res); + IupMessage("错误", msg); + } return; } @@ -24,172 +30,148 @@ void load_path() res = RegQueryValueExW(hKey, REG_VALUE, NULL, &type, NULL, &size); if (res == ERROR_SUCCESS) { - // 安全分配内存:size 是字节数,多分配 2 个字节给 null 终止符 wchar_t *buffer = (wchar_t *)malloc(size + 2); - if (!buffer) + if (buffer) { - IupMessage("错误", "内存分配失败!"); - RegCloseKey(hKey); - 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) + memset(buffer, 0, size + 2); + if (RegQueryValueExW(hKey, REG_VALUE, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS) { - 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); } - - 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); + free(buffer); } - 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); } -// 保存PATH到注册表 -void save_path() +// 加载所有PATH +void load_all_paths() { - if (!check_admin()) - { - IupMessage("错误", "需要管理员权限才能保存更改!\n请重新以管理员身份运行程序。"); - return; - } + 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); - int count = IupGetInt(list_path, "COUNT"); - if (count == 0) - { - // 警告:清空PATH是很危险的 - if (IupAlarm("警告", "PATH 为空!这可能导致系统命令无法使用。\n确定要保存吗?", "确定", "取消", NULL) != 1) - { - return; - } - } + refresh_list_style(); + IupSetAttribute(lbl_status, "TITLE", "状态: 已加载系统和用户变量"); +} - // 计算所需缓冲区大小 +// 内部辅助函数:保存单个列表 +static int save_single_path(HKEY hKeyRoot, const wchar_t *regPath, Ihandle *list) +{ + int count = IupGetInt(list, "COUNT"); + + // 计算大小 size_t total_len = 0; for (int i = 1; i <= count; i++) { - char *item = IupGetAttributeId(list_path, "", i); + char *item = IupGetAttributeId(list, "", i); if (item) { wchar_t *witem = utf8_to_wide(item); - total_len += wcslen(witem) + 1; // +1 for ';' + total_len += wcslen(witem) + 1; free(witem); } } - total_len += 1; // null terminator + total_len += 1; wchar_t *buffer = (wchar_t *)malloc(total_len * sizeof(wchar_t)); if (!buffer) - { - IupMessage("错误", "内存分配失败 (保存时)!"); - return; - } - buffer[0] = L'\0'; + return 0; + buffer[0] = L'\0'; for (int i = 1; i <= count; i++) { - char *item = IupGetAttributeId(list_path, "", i); + char *item = IupGetAttributeId(list, "", i); if (item) { wchar_t *witem = utf8_to_wide(item); wcscat(buffer, witem); if (i < count) - { wcscat(buffer, L";"); - } free(witem); } } - // 写入注册表 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); if (RegSetValueExW(hKey, REG_VALUE, 0, REG_EXPAND_SZ, (LPBYTE)buffer, size) == ERROR_SUCCESS) { - // 发送系统广播通知环境变量已更改 - 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", "状态: 保存失败"); + success = 1; } RegCloseKey(hKey); } - else - { - IupMessage("错误", "无法打开注册表进行写入。请检查权限!"); - IupSetAttribute(lbl_status, "TITLE", "状态: 打开注册表失败"); - } 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", "状态: 保存失败"); + } } \ No newline at end of file diff --git a/src/utils.c b/src/utils.c index 8514d69..cd8dc28 100644 --- a/src/utils.c +++ b/src/utils.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include // 宽字符转UTF-8 (用于IUP显示) char *wide_to_utf8(const wchar_t *wstr) @@ -39,23 +42,192 @@ int check_admin() 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; - int count = IupGetInt(list_path, "COUNT"); + int count = IupGetInt(list, "COUNT"); + + // 用于查重的简单数组(实际项目可以用哈希表) + // 为了简单,我们只用双重循环检查重复,性能在几百个条目下没问题 + for (int i = 1; i <= count; i++) { - // 奇数行:白色 - // 偶数行:极浅灰色 (245 245 245) + char *item = IupGetAttributeId(list, "", i); + 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) { - IupSetAttributeId(list_path, "ITEMBGCOLOR", i, "245 245 245"); + IupSetAttributeId(list, "ITEMBGCOLOR", i, "245 245 245"); } 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; } \ No newline at end of file