UP 优化托盘界面

main
jixieshi 2025-09-09 00:57:21 +08:00
parent d2113117cc
commit b7dc9495ac
9 changed files with 460 additions and 502 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.31)
cmake_minimum_required(VERSION 3.26)
project(CLI_Manager)
set(CMAKE_CXX_STANDARD 20)
@ -12,7 +12,7 @@ set(IMGUI_BACKENDS "glfw_opengl")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
#set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
add_definitions(-DUNICODE -D_UNICODE)
@ -88,4 +88,3 @@ endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
)

View File

@ -53,6 +53,8 @@ public:
void Stop();
void Restart(const std::string& command);
std::wstring GetPid() const;
void ClearLogs();
void AddLog(const std::string& log);
const std::vector<std::string>& GetLogs() const;

View File

@ -15,9 +15,11 @@
#else
#ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <windows.h>
#elif __APPLE__
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3.h>
@ -25,8 +27,10 @@
#else
#include <GLFW/glfw3.h>
#endif
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#endif
// 项目头文件
@ -37,6 +41,7 @@ class Manager {
public:
// 构造函数和析构函数
Manager();
~Manager();
// 核心生命周期管理
@ -59,11 +64,6 @@ private:
Monitoring // 监控布局
};
// 结构体定义
struct ColoredTextSegment {
std::string text; // 文本内容
ImVec4 color; // 文本颜色
};
// UI渲染相关方法
void RenderUI(); // 渲染主UI
@ -86,13 +86,6 @@ private:
void SaveCurrentLayout(); // 保存当前布局
void LoadSavedLayout(); // 加载已保存布局
// 日志颜色处理方法
ImVec4 GetLogLevelColor(const std::string& log); // 获取日志级别颜色
void RenderColoredLogLine(const std::string& log); // 渲染彩色日志行
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string& text); // 解析ANSI颜色代码
std::pair<ImVec4, bool> ParseAnsiColorCode(const std::string& code, const ImVec4& currentColor, bool currentBold); // 解析单个ANSI颜色代码
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
// 事件处理相关方法
void HandleMessages(); // 处理消息
bool ShouldExit() const; // 检查是否应该退出
@ -114,6 +107,7 @@ private:
void CleanupDirectX11(); // 清理DirectX11
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程
#else
bool InitializeGLFW(); // 初始化GLFW
void CleanupGLFW(); // 清理GLFW
static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调
@ -123,6 +117,7 @@ private:
bool InitializeTray(); // 初始化托盘
void CleanupTray(); // 清理托盘
#ifdef _WIN32
static HWND CreateHiddenWindow(); // 创建隐藏窗口
#endif
@ -148,12 +143,16 @@ private:
// 控制标志
bool m_should_exit = false; // 是否应该退出
bool m_initialized = false; // 是否已初始化
bool m_fullscreen = false;
bool m_padding = false;
// DPI缩放相关
float m_dpi_scale = 1.0f; // 当前DPI缩放
float m_last_dpi_scale = 1.0f; // 上次DPI缩放
// 布局相关成员变量
ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_MenuBar;
bool m_apply_preset_layout = false; // 是否需要应用预设布局
LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局
bool m_reset_layout = false; // 是否重置布局

View File

@ -17,6 +17,8 @@ public:
#ifdef _WIN32
TrayIcon(HWND hwnd, HICON icon);
void UpdateWebUrl(const std::wstring& url);
void UpdateStatus(const std::wstring &status, const std::wstring &pid);
// 静态窗口过程
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
#else
@ -45,6 +47,8 @@ private:
HICON m_icon;
NOTIFYICONDATA m_nid{};
std::wstring m_web_url;
std::wstring m_status;
std::wstring m_pid;
HMENU m_menu;
#else
void ShowMacTrayIcon();

View File

@ -1,11 +1,26 @@
#ifndef UNITS_H
#define UNITS_H
#include <string>
#include <imgui.h>
#include <vector>
std::wstring StringToWide(const std::string& str);
std::string WideToString(const std::wstring& wstr);
void SetAutoStart(bool enable);
bool IsAutoStartEnabled();
// 结构体定义
struct ColoredTextSegment {
std::string text; // 文本内容
ImVec4 color; // 文本颜色
};
// 日志颜色处理方法
ImVec4 GetLogLevelColor(const std::string &log); // 获取日志级别颜色
void RenderColoredLogLine(const std::string &log); // 渲染彩色日志行
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text); // 解析ANSI颜色代码
std::pair<ImVec4, bool>
ParseAnsiColorCode(const std::string &code, const ImVec4 &currentColor, bool currentBold); // 解析单个ANSI颜色代码
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
#endif //UNITS_H

View File

@ -894,3 +894,8 @@ while (process_running_) {
}
#endif
}
std::wstring CLIProcess::GetPid() const {
if (pi_.hProcess == nullptr) return L"";
return StringToWide(std::to_string(pi_.dwProcessId));
}

View File

@ -28,8 +28,10 @@ bool Manager::Initialize() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
// io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
io.ConfigViewportsNoAutoMerge = true;
io.IniFilename = "imgui.ini";
@ -112,6 +114,7 @@ bool Manager::Initialize() {
#ifdef _WIN32
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
#else
m_tray->UpdateWebUrl(m_app_state.web_url);
#endif
@ -120,7 +123,7 @@ bool Manager::Initialize() {
if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Start(m_app_state.command_input);
}
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_app_state.cli_process.GetPid());
m_initialized = true;
return true;
}
@ -200,22 +203,41 @@ void Manager::RenderUI() {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(display_w, display_h));
if (m_fullscreen) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
} else {
dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
}
if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
window_flags |= ImGuiWindowFlags_NoBackground;
if (!m_padding)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
if (!m_padding)
ImGui::PopStyleVar();
if (m_fullscreen)
ImGui::PopStyleVar(2); // Submit the DockSpace
ImGui::Begin("CLI程序管理工具", &m_app_state.show_main_window,
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_MenuBar);
window_flags);
RenderMenuBar();
// 创建主要的Docking空间
ImGuiID m_dockspace_id = ImGui::GetID("MainDockSpace");
ImGui::DockSpace( m_dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None);
ImGui::DockSpace(m_dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags | ImGuiDockNodeFlags_NoTabBar);
// 设置默认布局(仅在第一次运行时)
// 设置默认布局(仅在第一次运行时)
SetupDefaultDockingLayout(m_dockspace_id);
ImGuiWindowClass window_class;
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoWindowMenuButton;
ImGui::SetNextWindowClass(&window_class);
RenderMainContent();
ImGui::End();
@ -272,12 +294,41 @@ void Manager::RenderMenuBar() {
RenderLayoutMenu();
ImGui::EndMenu();
}
// 添加主题菜单
if (ImGui::BeginMenu("主题")) {
if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); }
if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); }
if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); }
ImGui::EndMenu();
}
if (ImGui::BeginMenu("选项(Options)")) {
ImGui::MenuItem("全屏(Fullscreen)", nullptr, &m_fullscreen);
ImGui::MenuItem("填充(Padding)", nullptr, &m_padding);
ImGui::Separator();
if (ImGui::MenuItem("标志:不分割(Flag: NoSplit)", "", (dockspace_flags & ImGuiDockNodeFlags_NoSplit) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoSplit; }
if (ImGui::MenuItem("标志:不调整大小(Flag: NoResize)", "",
(dockspace_flags & ImGuiDockNodeFlags_NoResize) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; }
if (ImGui::MenuItem("标志:不停靠在中心节点(Flag: NoDockingInCentralNode)", "",
(dockspace_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingInCentralNode; }
if (ImGui::MenuItem("标志:自动隐藏选项卡栏(Flag: AutoHideTabBar)", "",
(dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) !=
0)) { dockspace_flags = ImGuiDockNodeFlags_AutoHideTabBar; }
if (ImGui::MenuItem("标志:中心节点筛选器(Flag: PassthruCentralNode)", "",
(dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0,
m_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; }
ImGui::Separator();
//不关闭菜单
if (ImGui::MenuItem("关闭(Close)", nullptr, !m_app_state.show_main_window)) {
HideMainWindow();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
}
@ -417,7 +468,8 @@ void Manager::LoadSavedLayout() {
void Manager::RenderStatusMessages() {
// 渲染保存成功提示
if (m_show_save_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("SaveSuccess", nullptr,
@ -436,7 +488,8 @@ void Manager::RenderStatusMessages() {
// 渲染加载成功提示
if (m_show_load_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("LoadSuccess", nullptr,
@ -460,8 +513,8 @@ void Manager::RenderMainContent() {
// 控制面板窗口
if (ImGui::Begin("控制面板", nullptr, ImGuiWindowFlags_NoCollapse)) {
float buttonWidth = 80.0f * m_dpi_scale;
float buttonHeight = 40.0f * m_dpi_scale;
float buttonWidth = 40.0f * m_dpi_scale;
float buttonHeight = 25.0f * m_dpi_scale;
float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f;
RenderControlPanel(buttonWidth, buttonHeight, inputWidth);
}
@ -475,23 +528,13 @@ void Manager::RenderMainContent() {
// 命令发送窗口
if (ImGui::Begin("命令发送", nullptr, ImGuiWindowFlags_NoCollapse)) {
float buttonWidth = 80.0f * m_dpi_scale;
float buttonWidth = 40.0f * m_dpi_scale;
float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f;
RenderCommandPanel(buttonWidth, inputWidth);
}
ImGui::End();
}
// void Manager::RenderMenuBar() {
// if (ImGui::BeginMenuBar()) {
// if (ImGui::BeginMenu("设置")) {
// RenderSettingsMenu();
// ImGui::EndMenu();
// }
// ImGui::EndMenuBar();
// }
// }
void Manager::RenderSettingsMenu() {
if (ImGui::MenuItem("开机自启动", nullptr, m_app_state.auto_start)) {
m_app_state.auto_start = !m_app_state.auto_start;
@ -637,7 +680,8 @@ void Manager::RenderEnvironmentVariablesSettings() {
}
ImGui::Spacing();
ImGui::TextWrapped("说明启用后CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并同名变量会被覆盖。");
ImGui::TextWrapped(
"说明启用后CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并同名变量会被覆盖。");
ImGui::Unindent();
} else {
@ -686,179 +730,6 @@ void Manager::RenderOutputEncodingSettings() {
ImGui::BulletText("Shift-JIS适用于日文程序");
}
// void Manager::RenderMainContent() {
// float buttonWidth = 80.0f * m_dpi_scale;
// float buttonHeight = 40.0f * m_dpi_scale;
// float inputWidth = ImGui::GetContentRegionAvail().x * 0.5f; // 调整输入框宽度为50%
//
// // 启动命令输入区域
// ImGui::BeginGroup();
// ImGui::Text("启动命令");
//
// // 命令输入框和历史记录按钮
// ImGui::SetNextItemWidth(inputWidth);
// if (ImGui::InputText("##启动命令", m_app_state.command_input, IM_ARRAYSIZE(m_app_state.command_input))) {
// m_app_state.settings_dirty = true;
// }
//
// ImGui::SameLine();
// if (ImGui::Button("历史记录", ImVec2(100.0f * m_dpi_scale, 0))) {
// show_command_history_ = !show_command_history_;
// }
//
// // 显示命令历史记录下拉列表
// if (show_command_history_) {
// const auto& history = m_app_state.GetCommandHistory();
//
// if (!history.empty()) {
// ImGui::Indent();
// ImGui::Text("选择历史命令 (%d个):", static_cast<int>(history.size()));
//
// if (ImGui::BeginChild("CommandHistory", ImVec2(0, 120), true)) {
// for (int i = 0; i < static_cast<int>(history.size()); ++i) {
// ImGui::PushID(i);
//
// // 选择按钮
// if (ImGui::Button("选择", ImVec2(50.0f * m_dpi_scale, 0))) {
// strncpy_s(m_app_state.command_input, history[i].c_str(), sizeof(m_app_state.command_input) - 1);
// show_command_history_ = false;
// ImGui::PopID();
// break;
// }
// ImGui::SameLine();
//
// // 显示命令内容(限制显示长度)
// std::string displayCommand = history[i];
// if (displayCommand.length() > 60) {
// displayCommand = displayCommand.substr(0, 57) + "...";
// }
// ImGui::TextUnformatted(displayCommand.c_str());
//
// // 鼠标悬停时显示完整命令
// if (ImGui::IsItemHovered()) {
// ImGui::SetTooltip("%s", history[i].c_str());
// }
//
// ImGui::SameLine();
//
// // 删除按钮
// if (ImGui::SmallButton("删除")) {
// m_app_state.RemoveCommandFromHistory(i);
// ImGui::PopID();
// continue;
// }
//
// ImGui::PopID();
// }
// }
// ImGui::EndChild();
//
// // 操作按钮
// if (ImGui::Button("清空所有历史记录")) {
// m_app_state.ClearCommandHistory();
// }
// ImGui::SameLine();
//
// ImGui::Unindent();
// } else {
// ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "暂无启动命令历史");
// }
// }
// ImGui::EndGroup();
//
// ImGui::Spacing();
//
//
// ImGui::BeginGroup();
// if (ImGui::Button("启动", ImVec2(buttonWidth, buttonHeight))) {
// if (strlen(m_app_state.command_input) > 0) {
// m_app_state.cli_process.Start(m_app_state.command_input);
// m_app_state.AddCommandToHistory(m_app_state.command_input);
// }
// }
// ImGui::SameLine();
// if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
// m_app_state.cli_process.Stop();
// }
// ImGui::SameLine();
// if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
// if (strlen(m_app_state.command_input) > 0) {
// m_app_state.cli_process.Restart(m_app_state.command_input);
// m_app_state.AddCommandToHistory(m_app_state.command_input);
// }
// }
// ImGui::SameLine();
// if (ImGui::Button("清理日志", ImVec2(100.0f * m_dpi_scale, buttonHeight))) {
// m_app_state.cli_process.ClearLogs();
// }
// ImGui::EndGroup();
//
// ImGui::Text("状态: %s", m_app_state.cli_process.IsRunning() ? "运行中" : "已停止");
//
// ImGui::Separator();
// ImGui::Text("发送命令到CLI程序");
//
// // 命令发送
// ImGui::BeginGroup();
// ImGui::SetNextItemWidth(inputWidth);
// bool sendCommandPressed = ImGui::InputText("##命令输入", m_app_state.send_command, IM_ARRAYSIZE(m_app_state.send_command),
// ImGuiInputTextFlags_EnterReturnsTrue);
// ImGui::SameLine();
// if (ImGui::Button("发送", ImVec2(buttonWidth, 0)) || sendCommandPressed) {
// if (m_app_state.cli_process.IsRunning() && strlen(m_app_state.send_command) > 0) {
// m_app_state.cli_process.SendCommand(m_app_state.send_command);
// memset(m_app_state.send_command, 0, sizeof(m_app_state.send_command));
// }
// }
// ImGui::EndGroup();
//
// ImGui::Separator();
//
// // 日志控制
// ImGui::BeginGroup();
// ImGui::Text("程序日志");
//
// float logControlButtonWidth = 100.0f * m_dpi_scale;
// float checkboxWidth = 80.0f * m_dpi_scale;
// float statusTextWidth = 150.0f * m_dpi_scale;
//
// // 计算所有右侧控件的总宽度
// float totalRightControlsWidth = logControlButtonWidth +
// ImGui::GetStyle().ItemSpacing.x +
// checkboxWidth +
// ImGui::GetStyle().ItemSpacing.x +
// statusTextWidth;
//
// ImGui::SameLine();
// ImGui::SameLine(ImGui::GetContentRegionAvail().x - totalRightControlsWidth);
// if (ImGui::Button("复制日志", ImVec2(logControlButtonWidth, 0))) {
// m_app_state.cli_process.CopyLogsToClipboard();
// }
//
// ImGui::SameLine();
// ImGui::Checkbox("自动滚动", &m_app_state.auto_scroll_logs);
//
// ImGui::SameLine();
// ImGui::Text("行数: %d/%d",
// static_cast<int>(m_app_state.cli_process.GetLogs().size()),
// m_app_state.max_log_lines);
// ImGui::EndGroup();
//
// float logHeight = ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y;
// ImGui::BeginChild("Logs", ImVec2(0, logHeight), true, ImGuiWindowFlags_HorizontalScrollbar);
//
// const auto& logs = m_app_state.cli_process.GetLogs();
// for (const auto& log : logs) {
// ImGui::TextUnformatted(log.c_str());
// }
//
// if (m_app_state.auto_scroll_logs && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
// ImGui::SetScrollHereY(1.0f);
// }
//
// ImGui::EndChild();
// }
void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth) {
// 启动命令输入区域
ImGui::SeparatorText("启动命令");
@ -870,7 +741,7 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
}
ImGui::SameLine();
if (ImGui::Button("历史记录", ImVec2(100.0f * m_dpi_scale, 0))) {
if (ImGui::Button("历史记录", ImVec2(80.0f * m_dpi_scale, 0))) {
show_command_history_ = !show_command_history_;
}
@ -887,17 +758,23 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Start(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_app_state.cli_process.GetPid());
}
}
ImGui::SameLine();
if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
m_app_state.cli_process.Stop();
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid());
}
ImGui::SameLine();
if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Restart(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_app_state.cli_process.GetPid());
}
}
@ -937,8 +814,8 @@ void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) {
void Manager::RenderLogPanel() {
// 日志控制工具栏
if (ImGui::BeginTable("LogControls", 3, ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 120.0f * m_dpi_scale);
ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed, 120.0f * m_dpi_scale);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 85.0f * m_dpi_scale);
ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed, 100.0f * m_dpi_scale);
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
@ -997,205 +874,6 @@ void Manager::RenderLogPanel() {
ImGui::EndChild();
}
ImVec4 Manager::GetLogLevelColor(const std::string& 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 ImVec4(1.0f, 0.4f, 0.4f, 1.0f); // 红色
} 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 ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // 黄色
} 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 ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void Manager::RenderColoredLogLine(const std::string& log) {
auto segments = ParseAnsiColorCodes(log);
if (segments.empty()) {
// 如果没有ANSI代码使用简单的日志级别颜色
ImVec4 textColor = GetLogLevelColor(log);
ImGui::TextColored(textColor, "%s", log.c_str());
return;
}
// 渲染带颜色的文本段
bool first = true;
for (const auto& segment : segments) {
if (!first) {
ImGui::SameLine(0, 0); // 在同一行继续显示
}
first = false;
if (!segment.text.empty()) {
ImGui::TextColored(segment.color, "%s", segment.text.c_str());
}
}
}
std::vector<Manager::ColoredTextSegment> Manager::ParseAnsiColorCodes(const std::string& text) {
std::vector<ColoredTextSegment> segments;
if (text.empty()) {
return segments;
}
size_t pos = 0;
ImVec4 currentColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
bool isBold = false;
while (pos < text.length()) {
size_t escapePos = text.find('\033', pos);
if (escapePos == std::string::npos) {
// 没有更多转义序列,添加剩余文本
if (pos < text.length()) {
segments.push_back({text.substr(pos), currentColor});
}
break;
}
// 添加转义序列之前的文本
if (escapePos > pos) {
segments.push_back({text.substr(pos, escapePos - pos), currentColor});
}
// 解析ANSI转义序列
size_t codeStart = escapePos + 1;
if (codeStart < text.length() && text[codeStart] == '[') {
size_t codeEnd = text.find('m', codeStart);
if (codeEnd != std::string::npos) {
std::string colorCode = text.substr(codeStart + 1, codeEnd - codeStart - 1);
auto newColor = ParseAnsiColorCode(colorCode, currentColor, isBold);
currentColor = newColor.first;
isBold = newColor.second;
pos = codeEnd + 1;
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
}
return segments;
}
std::pair<ImVec4, bool> Manager::ParseAnsiColorCode(const std::string& code, const ImVec4& currentColor, bool currentBold) {
ImVec4 newColor = currentColor;
bool newBold = currentBold;
// 分割多个颜色代码(用分号分隔)
std::vector<int> codes;
std::stringstream ss(code);
std::string item;
while (std::getline(ss, item, ';')) {
if (!item.empty()) {
try {
codes.push_back(std::stoi(item));
} catch (const std::exception&) {
// 忽略无效的代码
}
}
}
// 如果没有代码默认为0重置
if (codes.empty()) {
codes.push_back(0);
}
for (int colorCode : codes) {
switch (colorCode) {
case 0: // 重置
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
newBold = false;
break;
case 1: // 粗体/亮色
newBold = true;
break;
case 22: // 正常强度
newBold = false;
break;
// 前景色 (30-37)
case 30: newColor = GetAnsiColor(0, newBold); break; // 黑色
case 31: newColor = GetAnsiColor(1, newBold); break; // 红色
case 32: newColor = GetAnsiColor(2, newBold); break; // 绿色
case 33: newColor = GetAnsiColor(3, newBold); break; // 黄色
case 34: newColor = GetAnsiColor(4, newBold); break; // 蓝色
case 35: newColor = GetAnsiColor(5, newBold); break; // 洋红
case 36: newColor = GetAnsiColor(6, newBold); break; // 青色
case 37: newColor = GetAnsiColor(7, newBold); break; // 白色
case 39: newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break; // 默认前景色
// 亮色前景色 (90-97)
case 90: newColor = GetAnsiColor(8, false); break; // 亮黑色(灰色)
case 91: newColor = GetAnsiColor(9, false); break; // 亮红色
case 92: newColor = GetAnsiColor(10, false); break; // 亮绿色
case 93: newColor = GetAnsiColor(11, false); break; // 亮黄色
case 94: newColor = GetAnsiColor(12, false); break; // 亮蓝色
case 95: newColor = GetAnsiColor(13, false); break; // 亮洋红
case 96: newColor = GetAnsiColor(14, false); break; // 亮青色
case 97: newColor = GetAnsiColor(15, false); break; // 亮白色
// 背景色暂时忽略 (40-47, 100-107)
default:
// 处理256色和RGB色彩38;5;n 和 38;2;r;g;b
// 这里可以根据需要扩展
break;
}
}
return {newColor, newBold};
}
ImVec4 Manager::GetAnsiColor(int colorIndex, bool bright) {
// ANSI标准颜色表
static const ImVec4 ansiColors[16] = {
// 标准颜色 (0-7)
ImVec4(0.0f, 0.0f, 0.0f, 1.0f), // 0: 黑色
ImVec4(0.8f, 0.0f, 0.0f, 1.0f), // 1: 红色
ImVec4(0.0f, 0.8f, 0.0f, 1.0f), // 2: 绿色
ImVec4(0.8f, 0.8f, 0.0f, 1.0f), // 3: 黄色
ImVec4(0.0f, 0.0f, 0.8f, 1.0f), // 4: 蓝色
ImVec4(0.8f, 0.0f, 0.8f, 1.0f), // 5: 洋红
ImVec4(0.0f, 0.8f, 0.8f, 1.0f), // 6: 青色
ImVec4(0.8f, 0.8f, 0.8f, 1.0f), // 7: 白色
// 亮色 (8-15)
ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // 8: 亮黑色(灰色)
ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // 9: 亮红色
ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // 10: 亮绿色
ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // 11: 亮黄色
ImVec4(0.0f, 0.0f, 1.0f, 1.0f), // 12: 亮蓝色
ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // 13: 亮洋红
ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // 14: 亮青色
ImVec4(1.0f, 1.0f, 1.0f, 1.0f), // 15: 亮白色
};
if (colorIndex >= 0 && colorIndex < 16) {
ImVec4 color = ansiColors[colorIndex];
// 如果是粗体且是标准颜色0-7增加亮度
if (bright && colorIndex < 8) {
color.x = std::min(1.0f, color.x + 0.3f);
color.y = std::min(1.0f, color.y + 0.3f);
color.z = std::min(1.0f, color.z + 0.3f);
}
return color;
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void Manager::RenderCommandHistory() {
const auto &history = m_app_state.GetCommandHistory();
@ -1378,6 +1056,7 @@ bool Manager::InitializeTray() {
}
#ifdef _WIN32
HWND Manager::CreateHiddenWindow() {
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
@ -1400,6 +1079,7 @@ HWND Manager::CreateHiddenWindow() {
NULL
);
}
#endif
void Manager::HandleMessages() {
@ -1569,6 +1249,7 @@ void Manager::CleanupDirectX11() {
}
}
#else
bool Manager::InitializeGLFW() {
glfwSetErrorCallback(GlfwErrorCallback);
if (!glfwInit())
@ -1605,6 +1286,8 @@ bool Manager::InitializeGLFW() {
int windowWidth = static_cast<int>(screenWidth * 0.8);
int windowHeight = static_cast<int>(screenHeight * 0.8);
// glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏
m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr);
if (!m_window)
return false;
@ -1631,6 +1314,7 @@ void Manager::CleanupGLFW() {
void Manager::GlfwErrorCallback(int error, const char *description) {
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
#endif
void Manager::CleanupTray() {
@ -1716,7 +1400,8 @@ void Manager::ReloadFonts() const {
// 尝试加载系统字体,失败则使用默认字体
#ifdef _WIN32
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", font_size, &font_config, io.Fonts->GetGlyphRangesChineseFull());
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", font_size, &font_config,
io.Fonts->GetGlyphRangesChineseFull());
#elif __APPLE__
io.Fonts->AddFontFromFileTTF("/System/Library/Fonts/PingFang.ttc", font_size, &font_config, io.Fonts->GetGlyphRangesChineseFull());
#else
@ -1794,7 +1479,6 @@ void Manager::ReloadFonts() const {
}
#ifdef __APPLE__
// macOS 特定的辅助函数声明
extern "C" {

View File

@ -85,7 +85,11 @@ void TrayIcon::CreateMenu() {
m_menu = CreatePopupMenu();
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
std::wstring statusText = L"状态:" + m_status;
AppendMenu(m_menu, MF_INSERT, 0, statusText.c_str());
std::wstring pidText = L"PID:" + m_pid;
AppendMenu(m_menu, MF_INSERT, 0, pidText.c_str());
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
// 添加Web地址菜单项如果有设置
if (!m_web_url.empty() && m_web_url != L"") {
std::wstring webText = L"打开Web页面: " + m_web_url;
@ -166,6 +170,15 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
}
return 0;
}
void TrayIcon::UpdateStatus(const std::wstring &status, const std::wstring &pid) {
m_status = status;
m_pid = pid;
// 重新创建菜单以更新Status显示
DestroyMenu();
CreateMenu();
}
#else
// macOS 特定实现
void TrayIcon::ShowMacTrayIcon() {

View File

@ -4,6 +4,8 @@
#include <mutex>
#include <thread>
#include <windows.h>
#include <sstream>
std::wstring StringToWide(const std::string& str) {
if (str.empty()) return L"";
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
@ -53,3 +55,238 @@ bool IsAutoStartEnabled() {
RegCloseKey(hKey);
return exists;
}
ImVec4 GetLogLevelColor(const std::string &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 ImVec4(1.0f, 0.4f, 0.4f, 1.0f); // 红色
} 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 ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // 黄色
} 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 ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void RenderColoredLogLine(const std::string &log) {
auto segments = ParseAnsiColorCodes(log);
if (segments.empty()) {
// 如果没有ANSI代码使用简单的日志级别颜色
ImVec4 textColor = GetLogLevelColor(log);
ImGui::TextColored(textColor, "%s", log.c_str());
return;
}
// 渲染带颜色的文本段
bool first = true;
for (const auto &segment: segments) {
if (!first) {
ImGui::SameLine(0, 0); // 在同一行继续显示
}
first = false;
if (!segment.text.empty()) {
ImGui::TextColored(segment.color, "%s", segment.text.c_str());
}
}
}
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text) {
std::vector<ColoredTextSegment> segments;
if (text.empty()) {
return segments;
}
size_t pos = 0;
ImVec4 currentColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
bool isBold = false;
while (pos < text.length()) {
size_t escapePos = text.find('\033', pos);
if (escapePos == std::string::npos) {
// 没有更多转义序列,添加剩余文本
if (pos < text.length()) {
segments.push_back({text.substr(pos), currentColor});
}
break;
}
// 添加转义序列之前的文本
if (escapePos > pos) {
segments.push_back({text.substr(pos, escapePos - pos), currentColor});
}
// 解析ANSI转义序列
size_t codeStart = escapePos + 1;
if (codeStart < text.length() && text[codeStart] == '[') {
size_t codeEnd = text.find('m', codeStart);
if (codeEnd != std::string::npos) {
std::string colorCode = text.substr(codeStart + 1, codeEnd - codeStart - 1);
auto newColor = ParseAnsiColorCode(colorCode, currentColor, isBold);
currentColor = newColor.first;
isBold = newColor.second;
pos = codeEnd + 1;
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
}
return segments;
}
std::pair<ImVec4, bool>
ParseAnsiColorCode(const std::string &code, const ImVec4 &currentColor, bool currentBold) {
ImVec4 newColor = currentColor;
bool newBold = currentBold;
// 分割多个颜色代码(用分号分隔)
std::vector<int> codes;
std::stringstream ss(code);
std::string item;
while (std::getline(ss, item, ';')) {
if (!item.empty()) {
try {
codes.push_back(std::stoi(item));
} catch (const std::exception &) {
// 忽略无效的代码
}
}
}
// 如果没有代码默认为0(重置)
if (codes.empty()) {
codes.push_back(0);
}
for (int colorCode: codes) {
switch (colorCode) {
case 0: // 重置
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
newBold = false;
break;
case 1: // 粗体/亮色
newBold = true;
break;
case 22: // 正常强度
newBold = false;
break;
// 前景色 (30-37)
case 30:
newColor = GetAnsiColor(0, newBold);
break; // 黑色
case 31:
newColor = GetAnsiColor(1, newBold);
break; // 红色
case 32:
newColor = GetAnsiColor(2, newBold);
break; // 绿色
case 33:
newColor = GetAnsiColor(3, newBold);
break; // 黄色
case 34:
newColor = GetAnsiColor(4, newBold);
break; // 蓝色
case 35:
newColor = GetAnsiColor(5, newBold);
break; // 洋红
case 36:
newColor = GetAnsiColor(6, newBold);
break; // 青色
case 37:
newColor = GetAnsiColor(7, newBold);
break; // 白色
case 39:
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
break; // 默认前景色
// 亮色前景色 (90-97)
case 90:
newColor = GetAnsiColor(8, false);
break; // 亮黑色(灰色)
case 91:
newColor = GetAnsiColor(9, false);
break; // 亮红色
case 92:
newColor = GetAnsiColor(10, false);
break; // 亮绿色
case 93:
newColor = GetAnsiColor(11, false);
break; // 亮黄色
case 94:
newColor = GetAnsiColor(12, false);
break; // 亮蓝色
case 95:
newColor = GetAnsiColor(13, false);
break; // 亮洋红
case 96:
newColor = GetAnsiColor(14, false);
break; // 亮青色
case 97:
newColor = GetAnsiColor(15, false);
break; // 亮白色
// 背景色暂时忽略 (40-47, 100-107)
default:
// 处理256色和RGB色彩(38;5;n 和 38;2;r;g;b)
// 这里可以根据需要扩展
break;
}
}
return {newColor, newBold};
}
ImVec4 GetAnsiColor(int colorIndex, bool bright) {
// ANSI标准颜色表
static const ImVec4 ansiColors[16] = {
// 标准颜色 (0-7)
ImVec4(0.0f, 0.0f, 0.0f, 1.0f), // 0: 黑色
ImVec4(0.8f, 0.0f, 0.0f, 1.0f), // 1: 红色
ImVec4(0.0f, 0.8f, 0.0f, 1.0f), // 2: 绿色
ImVec4(0.8f, 0.8f, 0.0f, 1.0f), // 3: 黄色
ImVec4(0.0f, 0.0f, 0.8f, 1.0f), // 4: 蓝色
ImVec4(0.8f, 0.0f, 0.8f, 1.0f), // 5: 洋红
ImVec4(0.0f, 0.8f, 0.8f, 1.0f), // 6: 青色
ImVec4(0.8f, 0.8f, 0.8f, 1.0f), // 7: 白色
// 亮色 (8-15)
ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // 8: 亮黑色(灰色)
ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // 9: 亮红色
ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // 10: 亮绿色
ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // 11: 亮黄色
ImVec4(0.0f, 0.0f, 1.0f, 1.0f), // 12: 亮蓝色
ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // 13: 亮洋红
ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // 14: 亮青色
ImVec4(1.0f, 1.0f, 1.0f, 1.0f), // 15: 亮白色
};
if (colorIndex >= 0 && colorIndex < 16) {
ImVec4 color = ansiColors[colorIndex];
// 如果是粗体且是标准颜色(0-7),增加亮度
if (bright && colorIndex < 8) {
color.x = std::min(1.0f, color.x + 0.3f);
color.y = std::min(1.0f, color.y + 0.3f);
color.z = std::min(1.0f, color.z + 0.3f);
}
return color;
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}