Compare commits

...

3 Commits

Author SHA1 Message Date
JiXieShi deecdbeeab 新增: 加入托盘图标、通知消息 2025-09-12 20:51:47 +08:00
JiXieShi 57cac6a451 新增: 添加自定义主题设置相关函数 2025-09-12 19:04:20 +08:00
JiXieShi 0a57dae39c 📝 修改: 更新README 2025-09-12 18:02:41 +08:00
12 changed files with 702 additions and 141 deletions
+2 -1
View File
@@ -31,6 +31,7 @@ file(GLOB SRC
${IMGUI_DIR}/misc/cpp/*.cpp ${IMGUI_DIR}/misc/cpp/*.cpp
app/src/*.* app/src/*.*
main.cpp main.cpp
) )
@@ -63,7 +64,7 @@ if(IMGUI_BACKENDS STREQUAL "win32_dx11")
endif() endif()
# generate binary # generate binary
add_executable(${PROJECT_NAME} WIN32 ${SRC} ${PLATFORM_SRC}) add_executable(${PROJECT_NAME} WIN32 ${SRC} ${PLATFORM_SRC} logo.rc)
if(IMGUI_BACKENDS STREQUAL "glfw_opengl") if(IMGUI_BACKENDS STREQUAL "glfw_opengl")
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
+28 -2
View File
@@ -5,7 +5,26 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector> #include <vector>
#include <imgui.h>
#include "imgui.h"
struct LogColors {
ImVec4 error_color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
ImVec4 warn_color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f);
ImVec4 info_color = ImVec4(0.4f, 1.0f, 0.4f, 1.0f);
ImVec4 debug_color = ImVec4(0.6f, 0.6f, 1.0f, 1.0f);
ImVec4 trace_color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
void ResetToDefaults() {
error_color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
warn_color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f);
info_color = ImVec4(0.4f, 1.0f, 0.4f, 1.0f);
debug_color = ImVec4(0.6f, 0.6f, 1.0f, 1.0f);
trace_color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
}
};
class AppState { class AppState {
public: public:
@@ -50,8 +69,12 @@ public:
std::vector<std::string> command_history; std::vector<std::string> command_history;
int max_command_history; int max_command_history;
LogColors log_colors;
bool use_custom_log_colors = false;
bool use_ansi_colors = true;
bool settings_dirty; bool settings_dirty;
private: private:
// 环境变量序列化辅助函数 // 环境变量序列化辅助函数
std::string SerializeEnvironmentVariables() const; std::string SerializeEnvironmentVariables() const;
@@ -60,8 +83,11 @@ private:
// 编码序列化辅助函数 // 编码序列化辅助函数
std::string SerializeOutputEncoding() const; std::string SerializeOutputEncoding() const;
void DeserializeOutputEncoding(const std::string& serialized); void DeserializeOutputEncoding(const std::string& serialized);
// 日志颜色序列化辅助
std::string SerializeLogColors() const;
void DeserializeLogColors(const std::string &serialized);
// 新增:命令历史记录序列化辅助函数 // 命令历史记录序列化辅助函数
std::string SerializeCommandHistory() const; std::string SerializeCommandHistory() const;
void DeserializeCommandHistory(const std::string& serialized); void DeserializeCommandHistory(const std::string& serialized);
}; };
+16
View File
@@ -37,6 +37,7 @@
#include "AppState.h" #include "AppState.h"
#include "TrayIcon.h" #include "TrayIcon.h"
class Manager { class Manager {
public: public:
// 构造函数和析构函数 // 构造函数和析构函数
@@ -69,10 +70,14 @@ private:
void RenderUI(); // 渲染主UI void RenderUI(); // 渲染主UI
void RenderMenuBar(); // 渲染菜单栏 void RenderMenuBar(); // 渲染菜单栏
void RenderMainContent(); // 渲染主内容区域 void RenderMainContent(); // 渲染主内容区域
void RenderSettingsMenu(); // 渲染设置菜单 void RenderSettingsMenu(); // 渲染设置菜单
void RenderStopCommandSettings(); // 渲染停止命令设置 void RenderStopCommandSettings(); // 渲染停止命令设置
void RenderEnvironmentVariablesSettings(); // 渲染环境变量设置 void RenderEnvironmentVariablesSettings(); // 渲染环境变量设置
void RenderOutputEncodingSettings(); // 渲染输出编码设置 void RenderOutputEncodingSettings(); // 渲染输出编码设置
void RenderColorThemeSettings();
void RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth); // 渲染控制面板 void RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth); // 渲染控制面板
void RenderCommandPanel(float buttonWidth, float inputWidth); // 渲染命令面板 void RenderCommandPanel(float buttonWidth, float inputWidth); // 渲染命令面板
void RenderLogPanel(); // 渲染日志面板 void RenderLogPanel(); // 渲染日志面板
@@ -99,6 +104,11 @@ private:
void UpdateDPIScale(); // 更新DPI缩放 void UpdateDPIScale(); // 更新DPI缩放
void ReloadFonts() const; // 重新加载字体 void ReloadFonts() const; // 重新加载字体
void SaveCurrentTheme();
void LoadSavedTheme();
ImVec4 GetCustomLogLevelColor(const std::string &log);
// 平台相关初始化方法 // 平台相关初始化方法
#ifdef USE_WIN32_BACKEND #ifdef USE_WIN32_BACKEND
bool InitializeWin32(); // 初始化Win32 bool InitializeWin32(); // 初始化Win32
@@ -167,4 +177,10 @@ private:
bool show_env_settings_ = false; // 是否显示环境变量设置 bool show_env_settings_ = false; // 是否显示环境变量设置
bool show_encoding_settings_ = false; // 是否显示编码设置 bool show_encoding_settings_ = false; // 是否显示编码设置
bool show_command_history_ = false; // 是否显示命令历史 bool show_command_history_ = false; // 是否显示命令历史
bool m_show_theme_save_success = true;
float m_theme_save_success_timer = 3.0f;
float m_theme_load_success_timer = 0.0f;
bool m_show_theme_load_success = false;
}; };
+10 -4
View File
@@ -35,12 +35,18 @@ public:
// 设置回调函数 // 设置回调函数
void SetShowWindowCallback(const ShowWindowCallback &callback); void SetShowWindowCallback(const ShowWindowCallback &callback);
void SetExitCallback(const ExitCallback &callback); void SetExitCallback(const ExitCallback &callback);
enum class NotifyAction {
Notify_NONE,
Notify_INFO,
Notify_WARNING,
Notify_ERROR,
Notify_USER
};
#ifdef _WIN32 #ifdef _WIN32
void ShowWindowsNotification(const std::wstring& title, const std::wstring& message); void ShowNotification(const std::wstring &title, const std::wstring &message, NotifyAction notify= NotifyAction::Notify_INFO) const;
#elif __APPLE__
void ShowMacNotification(const std::string& title, const std::string& message);
#else #else
void ShowLinuxNotification(const std::string& title, const std::string& message); void ShowNotification(const std::string& title, const std::string& message, NotifyAction notify= NotifyAction::Notify_INFO);
#endif #endif
private: private:
void CreateMenu(); void CreateMenu();
+87
View File
@@ -160,6 +160,82 @@ void AppState::DeserializeOutputEncoding(const std::string& serialized) {
} }
} }
std::string AppState::SerializeLogColors() const {
std::ostringstream oss;
// 按顺序保存每个颜色的RGBA值
oss << log_colors.error_color.x << ","
<< log_colors.error_color.y << ","
<< log_colors.error_color.z << ","
<< log_colors.error_color.w << "|";
oss << log_colors.warn_color.x << ","
<< log_colors.warn_color.y << ","
<< log_colors.warn_color.z << ","
<< log_colors.warn_color.w << "|";
oss << log_colors.info_color.x << ","
<< log_colors.info_color.y << ","
<< log_colors.info_color.z << ","
<< log_colors.info_color.w << "|";
oss << log_colors.debug_color.x << ","
<< log_colors.debug_color.y << ","
<< log_colors.debug_color.z << ","
<< log_colors.debug_color.w << "|";
oss << log_colors.trace_color.x << ","
<< log_colors.trace_color.y << ","
<< log_colors.trace_color.z << ","
<< log_colors.trace_color.w;
return oss.str();
}
// 新增:反序列化日志颜色
void AppState::DeserializeLogColors(const std::string& serialized) {
if (serialized.empty()) {
log_colors.ResetToDefaults();
return;
}
std::istringstream iss(serialized);
std::string colorStr;
int colorIndex = 0;
while (std::getline(iss, colorStr, '|') && colorIndex < 5) {
std::istringstream colorIss(colorStr);
std::string component;
float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
int i = 0;
while (std::getline(colorIss, component, ',') && i < 4) {
try {
rgba[i++] = std::stof(component);
} catch (const std::exception&) {
// 如果解析失败,使用默认值
}
}
ImVec4 color(rgba[0], rgba[1], rgba[2], rgba[3]);
switch (colorIndex) {
case 0: log_colors.error_color = color; break;
case 1: log_colors.warn_color = color; break;
case 2: log_colors.info_color = color; break;
case 3: log_colors.debug_color = color; break;
case 4: log_colors.trace_color = color; break;
}
colorIndex++;
}
// 如果没有足够的颜色数据,重置为默认值
if (colorIndex < 5) {
log_colors.ResetToDefaults();
}
}
void AppState::LoadSettings() { void AppState::LoadSettings() {
std::ifstream file("climanager_settings.ini"); std::ifstream file("climanager_settings.ini");
if (!file.is_open()) return; if (!file.is_open()) return;
@@ -234,6 +310,14 @@ void AppState::LoadSettings() {
else if (key == "MaxCommandHistory") { else if (key == "MaxCommandHistory") {
max_command_history = std::stoi(value); max_command_history = std::stoi(value);
max_command_history = std::max(5, std::min(max_command_history, 100)); max_command_history = std::max(5, std::min(max_command_history, 100));
}else if (key == "UseCustomLogColors") {
use_custom_log_colors = (value == "1");
}
else if (key == "UseAnsiColors") {
use_ansi_colors = (value == "1");
}
else if (key == "LogColors") {
DeserializeLogColors(value);
} }
} }
} }
@@ -271,6 +355,9 @@ void AppState::SaveSettings() {
file << "CommandHistory=" << SerializeCommandHistory() << "\n"; file << "CommandHistory=" << SerializeCommandHistory() << "\n";
file << "MaxCommandHistory=" << max_command_history << "\n"; file << "MaxCommandHistory=" << max_command_history << "\n";
file << "UseCustomLogColors=" << (use_custom_log_colors ? "1" : "0") << "\n";
file << "UseAnsiColors=" << (use_ansi_colors ? "1" : "0") << "\n";
file << "LogColors=" << SerializeLogColors() << "\n";
file.close(); file.close();
settings_dirty = false; settings_dirty = false;
-1
View File
@@ -503,7 +503,6 @@ void CLIProcess::Start(const std::string& command) {
if (result) { if (result) {
AddLog("进程已启动: " + command + " PID: " + std::to_string(pi_.dwProcessId)); AddLog("进程已启动: " + command + " PID: " + std::to_string(pi_.dwProcessId));
CloseHandle(hWritePipe_); CloseHandle(hWritePipe_);
CloseHandle(hReadPipe_stdin_); CloseHandle(hReadPipe_stdin_);
hWritePipe_ = nullptr; hWritePipe_ = nullptr;
+314 -15
View File
@@ -6,6 +6,7 @@
#include "imgui_internal.h" #include "imgui_internal.h"
#include "resource.h"
#include "Units.h" #include "Units.h"
Manager::Manager() = default; Manager::Manager() = default;
@@ -31,7 +32,7 @@ bool Manager::Initialize() {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
// io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
io.ConfigViewportsNoAutoMerge = true; io.ConfigViewportsNoAutoMerge = true;
io.IniFilename = "imgui.ini"; io.IniFilename = "imgui.ini";
@@ -113,6 +114,7 @@ bool Manager::Initialize() {
m_app_state.ApplySettings(); m_app_state.ApplySettings();
m_app_state.SaveSettings(); m_app_state.SaveSettings();
LoadSavedTheme();
#ifdef _WIN32 #ifdef _WIN32
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url)); m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
@@ -125,6 +127,7 @@ bool Manager::Initialize() {
m_app_state.cli_process.Start(m_app_state.command_input); m_app_state.cli_process.Start(m_app_state.command_input);
m_app_state.show_main_window = false; m_app_state.show_main_window = false;
HideMainWindow(); HideMainWindow();
m_tray->ShowNotification(L"CLI自启动",StringToWide(m_app_state.command_input) + L"\n当前状态:" + (m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止"));
} }
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_app_state.cli_process.GetPid()); m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_app_state.cli_process.GetPid());
m_initialized = true; m_initialized = true;
@@ -302,9 +305,10 @@ void Manager::RenderMenuBar() {
// 添加主题菜单 // 添加主题菜单
if (ImGui::BeginMenu("主题")) { if (ImGui::BeginMenu("主题")) {
if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); } // if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); }
if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); } // if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); }
if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); } // if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); }
RenderColorThemeSettings();
ImGui::EndMenu(); ImGui::EndMenu();
} }
@@ -485,9 +489,11 @@ void Manager::RenderStatusMessages() {
} }
ImGui::End(); ImGui::End();
m_save_success_timer -= ImGui::GetIO().DeltaTime; m_save_success_timer -= ImGui::GetIO().DeltaTime;
if (m_save_success_timer <= 0.0f) { if (m_save_success_timer <= 0.0f) {
m_show_save_success = false; m_show_save_success = false;
m_tray->ShowNotification(L"CLI_Manager",L"当前布局保存成功!");
} }
} }
@@ -501,13 +507,54 @@ void Manager::RenderStatusMessages() {
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已加载");
} }
ImGui::End(); ImGui::End();
m_load_success_timer -= ImGui::GetIO().DeltaTime; m_load_success_timer -= ImGui::GetIO().DeltaTime;
if (m_load_success_timer <= 0.0f) { if (m_load_success_timer <= 0.0f) {
m_show_load_success = false; m_show_load_success = false;
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已加载");
}
}
if (m_show_theme_save_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("ThemeSaveSuccess", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "主题已保存");
}
ImGui::End();
m_theme_save_success_timer -= ImGui::GetIO().DeltaTime;
if (m_theme_save_success_timer <= 0.0f) {
m_show_theme_save_success = false;
m_tray->ShowNotification(L"CLI_Manager",L"当前主题保存成功!");
}
}
// 渲染主题加载成功提示
if (m_show_theme_load_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("ThemeLoadSuccess", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "主题已加载");
}
ImGui::End();
m_theme_load_success_timer -= ImGui::GetIO().DeltaTime;
if (m_theme_load_success_timer <= 0.0f) {
m_show_theme_load_success = false;
} }
} }
} }
@@ -585,6 +632,7 @@ void Manager::RenderSettingsMenu() {
RenderStopCommandSettings(); RenderStopCommandSettings();
RenderEnvironmentVariablesSettings(); RenderEnvironmentVariablesSettings();
RenderOutputEncodingSettings(); RenderOutputEncodingSettings();
} }
void Manager::RenderStopCommandSettings() { void Manager::RenderStopCommandSettings() {
@@ -773,8 +821,9 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
m_app_state.AddCommandToHistory(m_app_state.command_input); m_app_state.AddCommandToHistory(m_app_state.command_input);
if (strlen(m_app_state.working_directory) > 0) { if (strlen(m_app_state.working_directory) > 0) {
m_app_state.cli_process.SetWorkingDirectory(m_app_state.working_directory); m_app_state.cli_process.SetWorkingDirectory(m_app_state.working_directory);
}else { } else {
strncpy_s(m_app_state.working_directory,m_app_state.cli_process.GetWorkingDirectory().c_str(),sizeof(m_app_state.working_directory)-1); strncpy_s(m_app_state.working_directory, m_app_state.cli_process.GetWorkingDirectory().c_str(),
sizeof(m_app_state.working_directory) - 1);
} }
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid()); m_app_state.cli_process.GetPid());
@@ -793,6 +842,7 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
m_app_state.AddCommandToHistory(m_app_state.command_input); m_app_state.AddCommandToHistory(m_app_state.command_input);
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid()); m_app_state.cli_process.GetPid());
m_tray->ShowNotification(L"CLI_Manager",m_app_state.cli_process.IsRunning() ? L"重启成功!" : L"重启失败!");
} }
} }
@@ -800,9 +850,9 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
// 状态显示 // 状态显示
ImGui::SeparatorText("运行状态"); ImGui::SeparatorText("运行状态");
ImVec4 statusColor = m_app_state.cli_process.IsRunning() ? ImVec4 statusColor = m_app_state.cli_process.IsRunning()
ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f)
ImVec4(1.0f, 0.0f, 0.0f, 1.0f); : ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
ImGui::TextColored(statusColor, "状态: %s", ImGui::TextColored(statusColor, "状态: %s",
m_app_state.cli_process.IsRunning() ? "运行中" : "已停止"); m_app_state.cli_process.IsRunning() ? "运行中" : "已停止");
} }
@@ -825,7 +875,7 @@ void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) {
// 显示发送状态 // 显示发送状态
if (!m_app_state.cli_process.IsRunning()) { if (!m_app_state.cli_process.IsRunning()) {
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "提示: 程序未运行,无法发送命令"); ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 0.6f), "提示: 程序未运行,无法发送命令");
} }
} }
@@ -875,12 +925,18 @@ void Manager::RenderLogPanel() {
const std::string &log = logs[i]; const std::string &log = logs[i];
if (m_app_state.enable_colored_logs) { if (m_app_state.enable_colored_logs) {
if (m_app_state.use_ansi_colors) {
// 使用ANSI颜色转义序列解析
RenderColoredLogLine(log); RenderColoredLogLine(log);
} else { } else {
// 简单的日志级别颜色区分 // 仅使用日志级别颜色区分
ImVec4 textColor = GetLogLevelColor(log); ImVec4 textColor = GetCustomLogLevelColor(log);
ImGui::TextColored(textColor, "%s", log.c_str()); ImGui::TextColored(textColor, "%s", log.c_str());
} }
} else {
// 不启用彩色显示,使用默认白色
ImGui::TextUnformatted(log.c_str());
}
} }
} }
@@ -1047,7 +1103,12 @@ bool Manager::InitializeTray() {
return false; return false;
} }
HICON trayIcon = LoadIcon(NULL, IDI_APPLICATION); // 加载自定义图标
HICON trayIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
if (!trayIcon) {
// 如果加载失败,使用默认图标
trayIcon = LoadIcon(NULL, IDI_APPLICATION);
}
m_tray = std::make_unique<TrayIcon>(m_tray_hwnd, trayIcon); m_tray = std::make_unique<TrayIcon>(m_tray_hwnd, trayIcon);
// 设置托盘窗口的用户数据,指向TrayIcon实例 // 设置托盘窗口的用户数据,指向TrayIcon实例
@@ -1303,7 +1364,7 @@ bool Manager::InitializeGLFW() {
int windowWidth = static_cast<int>(screenWidth * 0.8); int windowWidth = static_cast<int>(screenWidth * 0.8);
int windowHeight = static_cast<int>(screenHeight * 0.8); int windowHeight = static_cast<int>(screenHeight * 0.8);
// glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏 // glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏
m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr); m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr);
if (!m_window) if (!m_window)
@@ -1502,3 +1563,241 @@ extern "C" {
void* GetMacTrayIcon(); void* GetMacTrayIcon();
} }
#endif #endif
void Manager::RenderColorThemeSettings() {
// 预设主题
if (ImGui::MenuItem("暗黑(Dark)")) {
ImGui::StyleColorsDark();
}
if (ImGui::MenuItem("明亮(Light)")) {
ImGui::StyleColorsLight();
}
if (ImGui::MenuItem("经典(Classic)")) {
ImGui::StyleColorsClassic();
}
ImGui::Separator();
ImGui::Text("颜色主题设置");
if (ImGui::Checkbox("自定义日志颜色", &m_app_state.use_custom_log_colors)) {
m_app_state.settings_dirty = true;
}
if (m_app_state.use_custom_log_colors) {
ImGui::Indent();
ImGui::Text("日志级别颜色:");
// 编辑错误日志颜色
ImVec4 errorColor = ImVec4(
m_app_state.log_colors.error_color.x,
m_app_state.log_colors.error_color.y,
m_app_state.log_colors.error_color.z,
1.0f
);
if (ImGui::ColorEdit3("错误日志", (float *) &errorColor)) {
m_app_state.log_colors.error_color = errorColor;
m_app_state.settings_dirty = true;
}
// 编辑警告日志颜色
ImVec4 warnColor = ImVec4(
m_app_state.log_colors.warn_color.x,
m_app_state.log_colors.warn_color.y,
m_app_state.log_colors.warn_color.z,
1.0f
);
if (ImGui::ColorEdit3("警告日志", (float *) &warnColor)) {
m_app_state.log_colors.warn_color = warnColor;
m_app_state.settings_dirty = true;
}
// 编辑信息日志颜色
ImVec4 infoColor = ImVec4(
m_app_state.log_colors.info_color.x,
m_app_state.log_colors.info_color.y,
m_app_state.log_colors.info_color.z,
1.0f
);
if (ImGui::ColorEdit3("信息日志", (float *) &infoColor)) {
m_app_state.log_colors.info_color = infoColor;
m_app_state.settings_dirty = true;
}
// 编辑调试日志颜色
ImVec4 debugColor = ImVec4(
m_app_state.log_colors.debug_color.x,
m_app_state.log_colors.debug_color.y,
m_app_state.log_colors.debug_color.z,
1.0f
);
if (ImGui::ColorEdit3("调试日志", (float *) &debugColor)) {
m_app_state.log_colors.debug_color = debugColor;
m_app_state.settings_dirty = true;
}
// 重置为默认颜色按钮
if (ImGui::Button("重置为默认颜色")) {
m_app_state.log_colors.ResetToDefaults();
m_app_state.settings_dirty = true;
}
ImGui::Unindent();
}
// ANSI颜色设置
ImGui::Separator();
if (ImGui::Checkbox("使用ANSI颜色转义", &m_app_state.use_ansi_colors)) {
m_app_state.settings_dirty = true;
}
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted("启用后,日志中的ANSI颜色转义序列将被解析并显示为彩色文本。");
ImGui::TextUnformatted("例如: \\033[31m红色文本\\033[0m");
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
// 颜色样式设置
ImGui::Text("全局颜色样式:");
static int currentTheme = 0; // 默认暗色主题
const char *themes[] = {"暗黑 (Dark)", "明亮 (Light)", "经典 (Classic)", "自定义 (Custom)"};
if (ImGui::Combo("应用主题", &currentTheme, themes, IM_ARRAYSIZE(themes))) {
switch (currentTheme) {
case 0: ImGui::StyleColorsDark();
break;
case 1: ImGui::StyleColorsLight();
break;
case 2: ImGui::StyleColorsClassic();
break;
case 3: /* 应用自定义主题 */ break;
}
}
// 自定义主题编辑器(仅在选择"自定义"主题时显示)
if (currentTheme == 3) {
if (ImGui::TreeNode("自定义主题编辑器")) {
ImGuiStyle &style = ImGui::GetStyle();
// 添加主题颜色编辑器
for (int i = 0; i < ImGuiCol_COUNT; i++) {
const char *name = ImGui::GetStyleColorName(i);
ImGui::ColorEdit4(name, (float *) &style.Colors[i]);
}
// 添加样式参数编辑器
ImGui::SliderFloat("窗口圆角", &style.WindowRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("子窗口圆角", &style.ChildRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("框架圆角", &style.FrameRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("弹出窗口圆角", &style.PopupRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("滚动条圆角", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("抓取圆角", &style.GrabRounding, 0.0f, 12.0f, "%.0f");
ImGui::SliderFloat("标签圆角", &style.TabRounding, 0.0f, 12.0f, "%.0f");
ImGui::Separator();
// 保存/加载主题按钮
if (ImGui::Button("保存当前主题")) {
SaveCurrentTheme();
}
ImGui::SameLine();
if (ImGui::Button("加载保存的主题")) {
LoadSavedTheme();
}
ImGui::TreePop();
}
}
}
// 保存当前主题设置
void Manager::SaveCurrentTheme() {
std::ofstream file("theme.ini", std::ios::binary);
if (file.is_open()) {
ImGuiStyle &style = ImGui::GetStyle();
// 保存主题颜色
for (int i = 0; i < ImGuiCol_COUNT; i++) {
file.write(reinterpret_cast<const char *>(&style.Colors[i]), sizeof(ImVec4));
}
// 保存样式参数
file.write(reinterpret_cast<const char *>(&style.WindowRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.ChildRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.FrameRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.PopupRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.ScrollbarRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.GrabRounding), sizeof(float));
file.write(reinterpret_cast<const char *>(&style.TabRounding), sizeof(float));
file.close();
// 显示保存成功提示
m_show_theme_save_success = true;
m_theme_save_success_timer = 3.0f; // 3秒后消失
}
}
// 加载保存的主题设置
void Manager::LoadSavedTheme() {
std::ifstream file("theme.ini", std::ios::binary);
if (file.is_open()) {
ImGuiStyle &style = ImGui::GetStyle();
// 加载主题颜色
for (int i = 0; i < ImGuiCol_COUNT; i++) {
file.read(reinterpret_cast<char *>(&style.Colors[i]), sizeof(ImVec4));
}
// 加载样式参数
file.read(reinterpret_cast<char *>(&style.WindowRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.ChildRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.FrameRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.PopupRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.ScrollbarRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.GrabRounding), sizeof(float));
file.read(reinterpret_cast<char *>(&style.TabRounding), sizeof(float));
file.close();
// 显示加载成功提示
m_show_theme_load_success = true;
m_theme_load_success_timer = 3.0f; // 3秒后消失
}
}
ImVec4 Manager::GetCustomLogLevelColor(const std::string &log) {
if (!m_app_state.use_custom_log_colors) {
// 使用默认颜色
return GetLogLevelColor(log);
}
// 使用自定义颜色
if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos ||
log.find("[ERROR]") != std::string::npos || log.find("error") != std::string::npos) {
return m_app_state.log_colors.error_color;
} else if (log.find("警告") != std::string::npos || log.find("[W]") != std::string::npos ||
log.find("[WARN]") != std::string::npos || log.find("warning") != std::string::npos) {
return m_app_state.log_colors.warn_color;
} else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos ||
log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) {
return m_app_state.log_colors.info_color;
} else if (log.find("调试") != std::string::npos || log.find("[D]") != std::string::npos ||
log.find("[DEBUG]") != std::string::npos || log.find("debug") != std::string::npos) {
return m_app_state.log_colors.debug_color;
} else if (log.find("跟踪") != std::string::npos || log.find("[T]") != std::string::npos ||
log.find("[TRACE]") != std::string::npos || log.find("trace") != std::string::npos) {
return m_app_state.log_colors.trace_color;
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
+4 -4
View File
@@ -77,16 +77,16 @@ void TrayIcon::SetExitCallback(const ExitCallback &callback) {
} }
#ifdef _WIN32 #ifdef _WIN32
void TrayIcon::ShowWindowsNotification(const std::wstring &title, const std::wstring &message) { void TrayIcon::ShowNotification(const std::wstring &title, const std::wstring &message, NotifyAction notify) const {
NOTIFYICONDATA nid = m_nid; NOTIFYICONDATA nid = m_nid;
nid.uFlags |= NIF_INFO; nid.uFlags |= NIF_INFO;
wcsncpy_s(nid.szInfoTitle, title.c_str(), _TRUNCATE); wcsncpy_s(nid.szInfoTitle, title.c_str(), _TRUNCATE);
wcsncpy_s(nid.szInfo, message.c_str(), _TRUNCATE); wcsncpy_s(nid.szInfo, message.c_str(), _TRUNCATE);
nid.dwInfoFlags = NIIF_INFO; // 信息图标,可选 NIIF_WARNING, NIIF_ERROR nid.dwInfoFlags = static_cast<DWORD>(notify); // 信息图标,可选 NIIF_WARNING, NIIF_ERROR
Shell_NotifyIcon(NIM_MODIFY, &nid); Shell_NotifyIcon(NIM_MODIFY, &nid);
} }
#elif __APPLE__ #elif __APPLE__
void TrayIcon::ShowMacNotification(const std::string &title, const std::string &message) void TrayIcon::ShowNotification(const std::string &title, const std::string &message)
{ {
// 通过 AppleScript 或 Objective-C 桥接 // 通过 AppleScript 或 Objective-C 桥接
std::string script = "display notification \"" + message + "\" with title \"" + title + "\""; std::string script = "display notification \"" + message + "\" with title \"" + title + "\"";
@@ -94,7 +94,7 @@ void TrayIcon::ShowMacNotification(const std::string &title, const std::string &
system(cmd.c_str()); system(cmd.c_str());
} }
#else #else
void TrayIcon::ShowLinuxNotification(const std::string &title, const std::string &message) void TrayIcon::ShowNotification(const std::string &title, const std::string &message)
{ {
// 使用 notify-send 命令 // 使用 notify-send 命令
std::string cmd = "notify-send \"" + title + "\" \"" + message + "\""; std::string cmd = "notify-send \"" + title + "\" \"" + message + "\"";
+54 -8
View File
@@ -6,7 +6,7 @@
#include <windows.h> #include <windows.h>
#include <sstream> #include <sstream>
std::wstring StringToWide(const std::string& str) { std::wstring StringToWide(const std::string &str) {
if (str.empty()) return L""; if (str.empty()) return L"";
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
std::wstring wstr(size, 0); std::wstring wstr(size, 0);
@@ -14,7 +14,7 @@ std::wstring StringToWide(const std::string& str) {
return wstr; return wstr;
} }
std::string WideToString(const std::wstring& wstr) { std::string WideToString(const std::wstring &wstr) {
if (wstr.empty()) return ""; if (wstr.empty()) return "";
int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
std::string str(size, 0); std::string str(size, 0);
@@ -44,9 +44,8 @@ void SetAutoStart(bool enable) {
if (enable) { if (enable) {
RegSetValueEx(hKey, keyName, 0, REG_SZ, RegSetValueEx(hKey, keyName, 0, REG_SZ,
(BYTE*)exePath, (wcslen(exePath) + 1) * sizeof(WCHAR)); (BYTE *) exePath, (wcslen(exePath) + 1) * sizeof(WCHAR));
} } else {
else {
RegDeleteValue(hKey, keyName); RegDeleteValue(hKey, keyName);
} }
RegCloseKey(hKey); RegCloseKey(hKey);
@@ -79,6 +78,7 @@ bool IsAutoStartEnabled() {
return exists; return exists;
} }
// 日志颜色处理函数
ImVec4 GetLogLevelColor(const std::string &log) { ImVec4 GetLogLevelColor(const std::string &log) {
// 简单的日志级别颜色区分 // 简单的日志级别颜色区分
if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos || if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos ||
@@ -90,11 +90,17 @@ ImVec4 GetLogLevelColor(const std::string &log) {
} else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos || } else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos ||
log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) { log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) {
return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色 return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色
} else if (log.find("调试") != std::string::npos || log.find("[D]") != std::string::npos ||
log.find("[DEBUG]") != std::string::npos || log.find("debug") != std::string::npos) {
return ImVec4(0.6f, 0.6f, 1.0f, 1.0f); // 蓝色
} else if (log.find("跟踪") != std::string::npos || log.find("[T]") != std::string::npos ||
log.find("[TRACE]") != std::string::npos || log.find("trace") != std::string::npos) {
return ImVec4(0.8f, 0.8f, 0.8f, 1.0f); // 灰色
} }
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色 return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
} }
// ANSI颜色处理增强版本
void RenderColoredLogLine(const std::string &log) { void RenderColoredLogLine(const std::string &log) {
auto segments = ParseAnsiColorCodes(log); auto segments = ParseAnsiColorCodes(log);
@@ -263,10 +269,49 @@ ParseAnsiColorCode(const std::string &code, const ImVec4 &currentColor, bool cur
newColor = GetAnsiColor(15, false); newColor = GetAnsiColor(15, false);
break; // 亮白色 break; // 亮白色
// 背景色暂时忽略 (40-47, 100-107) // 增加对背景色的支持 (40-47, 100-107)
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
// 背景色暂时不处理,因为ImGui不容易实现背景色
break;
default: default:
// 处理256色和RGB色彩(38;5;n 和 38;2;r;g;b) // 处理256色和RGB色彩(38;5;n 和 38;2;r;g;b)
// 这里可以根据需要扩展 if (colorCode == 38 || colorCode == 48) {
// 38=前景,48=背景
// 检查是否是256色模式或RGB模式
if (codes.size() > 1) {
if (codes[1] == 5 && codes.size() > 2) {
// 256色模式
// 暂时不实现所有256色,仅处理基本的16色
int colorIndex = codes[2];
if (colorIndex >= 0 && colorIndex < 16) {
newColor = GetAnsiColor(colorIndex, newBold);
}
} else if (codes[1] == 2 && codes.size() > 4) {
// RGB模式
// 处理RGB值
float r = static_cast<float>(codes[2]) / 255.0f;
float g = static_cast<float>(codes[3]) / 255.0f;
float b = static_cast<float>(codes[4]) / 255.0f;
newColor = ImVec4(r, g, b, 1.0f);
}
}
}
break; break;
} }
} }
@@ -313,3 +358,4 @@ ImVec4 GetAnsiColor(int colorIndex, bool bright) {
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色 return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+2
View File
@@ -0,0 +1,2 @@
#include "app/inc/resource.h"
IDI_ICON1 ICON DISCARDABLE "logo.ico"
+81 -2
View File
@@ -1,6 +1,43 @@
# 通用Cli Manager工具 # 通用CLI管理工具
基于IMGUI实现的支持开机自启动、环境变量、自动编码识别转换托盘持久化 一个基于IMGUI实现的CLI管理工具,具备开机自启动、环境变量管理、自动编码识别转换托盘持久化等功能。
## 主要功能
### 命令管理
- **命令历史记录**:自动记录执行过的命令,支持命令去重和历史记录数量限制
- **命令执行**:便捷地向CLI发送命令并获取执行结果
### 环境变量管理
- 查看当前环境变量
- 添加/删除/清空环境变量
- 为每个CLI进程设置独立的环境变量
### 编码支持
- 自动识别命令输出编码
- 支持多种编码格式转换
- UTF-8编码检测和转换
### 工作目录管理
- 获取和设置工作目录
- 支持从命令中提取目录路径
- 路径有效性验证
### 系统集成
- 系统托盘集成,支持最小化到托盘
- 开机自启动选项
- 剪贴板集成,支持日志复制
### 其他特性
- 多线程安全的数据访问
- Web服务快捷打开
- 直观的图形界面
## 程序效果 ## 程序效果
@@ -9,3 +46,45 @@
![主界面](./img/img2.png) ![主界面](./img/img2.png)
![托盘显示](./img/img3.png) ![托盘显示](./img/img3.png)
## 技术实现
- 基于IMGUI构建用户界面
- 使用C++标准库实现核心功能
- 在Windows平台上提供完整的进程管理
- 线程安全设计,适用于多线程环境
## 系统要求
- Windows操作系统
- 支持现代C++编译器
## 使用指南
### 命令执行
1. 在命令输入框中输入需要执行的命令
2. 点击执行按钮或按Enter键发送命令
3. 命令输出将实时显示在日志区域
### 环境变量配置
1. 在环境变量面板中查看当前变量
2. 使用添加/删除按钮管理环境变量
3. 所有修改将即时应用到当前CLI进程
### 工作目录设置
1. 使用工作目录下拉框选择或手动输入目录路径
2. 系统会自动验证目录有效性
3. 可从命令中自动提取目录路径
### 托盘功能
1. 最小化应用时将自动缩小到系统托盘
2. 右键托盘图标可访问快捷菜单
3. 支持通过托盘快速恢复窗口或退出应用
## 开发者信息
本项目是一个开源工具,欢迎贡献代码或提出改进建议。