UP 彩色日志 动态界面 立即回收

main
机械师 2025-09-08 19:33:32 +08:00
parent 7b6c87f2b0
commit 0ab3543aa4
6 changed files with 1367 additions and 210 deletions

View File

@ -28,6 +28,7 @@ public:
char command_input[256]{};
char send_command[256]{};
bool auto_scroll_logs;
bool enable_colored_logs;
int max_log_lines;
char web_url[256]{};

View File

@ -45,6 +45,10 @@ public:
void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars);
void SetOutputEncoding(OutputEncoding encoding);
// 新增:工作目录设置
void SetWorkingDirectory(const std::string& working_dir);
std::string GetWorkingDirectory() const;
void Start(const std::string& command);
void Stop();
void Restart(const std::string& command);
@ -69,6 +73,11 @@ public:
static std::string GetEncodingName(OutputEncoding encoding);
static std::vector<std::pair<OutputEncoding, std::string>> GetSupportedEncodings();
// 新增:工作目录相关静态方法
static std::string ExtractDirectoryFromCommand(const std::string& command);
static std::string GetAbsolutePath(const std::string& path);
static bool DirectoryExists(const std::string& path);
private:
void ReadOutput();
void CloseProcessHandles();
@ -117,6 +126,11 @@ private:
// 编码相关
mutable std::mutex encoding_mutex_;
OutputEncoding output_encoding_;
// 新增:工作目录相关
mutable std::mutex working_dir_mutex_;
std::string working_directory_;
bool use_auto_working_dir_;
};
#endif // CLIPROCESS_H

View File

@ -1,111 +1,171 @@
#pragma once
// 系统头文件
#include <memory>
// 第三方库头文件
#include "imgui.h"
// 平台相关头文件
#ifdef USE_WIN32_BACKEND
#include <d3d11.h>
#include <windows.h>
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#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>
#include <GLFW/glfw3native.h>
#else
#include <GLFW/glfw3.h>
#endif
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#endif
// 项目头文件
#include "AppState.h"
#include "TrayIcon.h"
#ifdef USE_WIN32_BACKEND
#include <d3d11.h>
#include <windows.h>
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#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>
#include <GLFW/glfw3native.h>
#else
#include <GLFW/glfw3.h>
#endif
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#endif
#include <memory>
class Manager {
public:
// 构造函数和析构函数
Manager();
~Manager();
bool Initialize();
void Run();
void Shutdown();
// 核心生命周期管理
bool Initialize(); // 初始化应用程序
void Run(); // 运行主循环
void Shutdown(); // 关闭应用程序
void OnTrayShowWindow();
void OnTrayExit();
AppState m_app_state;
// 托盘事件回调
void OnTrayShowWindow(); // 托盘显示窗口事件
void OnTrayExit(); // 托盘退出事件
// 公共成员变量
AppState m_app_state; // 应用程序状态
private:
// UI渲染
void RenderUI();
void RenderMenuBar();
void RenderMainContent();
void RenderSettingsMenu();
void RenderStopCommandSettings();
void RenderEnvironmentVariablesSettings();
void RenderOutputEncodingSettings();
// 枚举类型定义
enum class LayoutPreset {
Classic, // 经典布局
Development, // 开发布局
Monitoring // 监控布局
};
// 事件处理
void HandleMessages();
bool ShouldExit() const;
void ShowMainWindow();
void HideMainWindow();
// 结构体定义
struct ColoredTextSegment {
std::string text; // 文本内容
ImVec4 color; // 文本颜色
};
// 平台相关初始化
// UI渲染相关方法
void RenderUI(); // 渲染主UI
void RenderMenuBar(); // 渲染菜单栏
void RenderMainContent(); // 渲染主内容区域
void RenderSettingsMenu(); // 渲染设置菜单
void RenderStopCommandSettings(); // 渲染停止命令设置
void RenderEnvironmentVariablesSettings(); // 渲染环境变量设置
void RenderOutputEncodingSettings(); // 渲染输出编码设置
void RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth); // 渲染控制面板
void RenderCommandPanel(float buttonWidth, float inputWidth); // 渲染命令面板
void RenderLogPanel(); // 渲染日志面板
void RenderCommandHistory(); // 渲染命令历史
void RenderStatusMessages(); // 渲染状态消息
// 布局管理相关方法
void SetupDefaultDockingLayout(ImGuiID dockspace_id); // 设置默认停靠布局
void RenderLayoutMenu(); // 渲染布局菜单
static void ApplyPresetLayout(LayoutPreset preset); // 应用预设布局
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; // 检查是否应该退出
static void ContentScaleCallback(GLFWwindow *window, float xscale, float yscale); // 内容缩放回调
// 窗口管理相关方法
void ShowMainWindow(); // 显示主窗口
void HideMainWindow(); // 隐藏主窗口
// DPI相关方法
void UpdateDPIScale(); // 更新DPI缩放
void ReloadFonts() const; // 重新加载字体
// 平台相关初始化方法
#ifdef USE_WIN32_BACKEND
bool InitializeWin32();
bool InitializeDirectX11();
void CleanupWin32();
void CleanupDirectX11();
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
HWND m_hwnd = nullptr;
WNDCLASSEX m_wc = {};
ID3D11Device* m_pd3dDevice = nullptr;
ID3D11DeviceContext* m_pd3dDeviceContext = nullptr;
IDXGISwapChain* m_pSwapChain = nullptr;
ID3D11RenderTargetView* m_mainRenderTargetView = nullptr;
bool InitializeWin32(); // 初始化Win32
bool InitializeDirectX11(); // 初始化DirectX11
void CleanupWin32(); // 清理Win32
void CleanupDirectX11(); // 清理DirectX11
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程
#else
bool InitializeGLFW();
void CleanupGLFW();
static void GlfwErrorCallback(int error, const char* description);
GLFWwindow* m_window = nullptr;
const char* m_glsl_version = nullptr;
bool InitializeGLFW(); // 初始化GLFW
void CleanupGLFW(); // 清理GLFW
static void GlfwErrorCallback(int error, const char* description); // GLFW错误回调
#endif
// 托盘相关
bool InitializeTray();
void CleanupTray();
// 托盘相关方法
bool InitializeTray(); // 初始化托盘
void CleanupTray(); // 清理托盘
#ifdef _WIN32
static HWND CreateHiddenWindow();
HWND m_tray_hwnd = nullptr;
static HWND CreateHiddenWindow(); // 创建隐藏窗口
#endif
std::unique_ptr<TrayIcon> m_tray;
// 平台相关成员变量
#ifdef USE_WIN32_BACKEND
HWND m_hwnd = nullptr; // 窗口句柄
WNDCLASSEX m_wc = {}; // 窗口类
ID3D11Device* m_pd3dDevice = nullptr; // D3D11设备
ID3D11DeviceContext* m_pd3dDeviceContext = nullptr; // D3D11设备上下文
IDXGISwapChain* m_pSwapChain = nullptr; // 交换链
ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; // 主渲染目标视图
#else
GLFWwindow* m_window = nullptr; // GLFW窗口
const char* m_glsl_version = nullptr; // GLSL版本
#endif
// 托盘相关成员变量
std::unique_ptr<TrayIcon> m_tray; // 托盘图标
#ifdef _WIN32
HWND m_tray_hwnd = nullptr; // 托盘窗口句柄
#endif
// 控制标志
bool m_should_exit = false;
bool m_initialized = false;
bool m_should_exit = false; // 是否应该退出
bool m_initialized = false; // 是否已初始化
// DPI缩放因子
float m_dpi_scale = 1.0f;
// DPI缩放相关
float m_dpi_scale = 1.0f; // 当前DPI缩放
float m_last_dpi_scale = 1.0f; // 上次DPI缩放
// 环境变量UI状态
char env_key_input_[256] = {};
char env_value_input_[512] = {};
bool show_env_settings_ = false;
// 布局相关成员变量
bool m_apply_preset_layout = false; // 是否需要应用预设布局
LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局
bool m_reset_layout = false; // 是否重置布局
bool m_show_save_success = false; // 是否显示保存成功消息
bool m_show_load_success = false; // 是否显示加载成功消息
float m_save_success_timer = 0.0f; // 保存成功消息计时器
float m_load_success_timer = 0.0f; // 加载成功消息计时器
// 编码设置UI状态
bool show_encoding_settings_ = false;
// 历史命令UI状态
bool show_command_history_;
// UI状态相关成员变量
char env_key_input_[256] = {}; // 环境变量键输入缓冲区
char env_value_input_[512] = {}; // 环境变量值输入缓冲区
bool show_env_settings_ = false; // 是否显示环境变量设置
bool show_encoding_settings_ = false; // 是否显示编码设置
bool show_command_history_ = false; // 是否显示命令历史
};

View File

@ -191,6 +191,9 @@ void AppState::LoadSettings() {
else if (key == "AutoScrollLogs") {
auto_scroll_logs = (value == "1");
}
else if (key == "EnableColoredLogs") {
enable_colored_logs = (value == "1");
}
else if (key == "AutoStart") {
auto_start = (value == "1");
}
@ -238,6 +241,7 @@ void AppState::SaveSettings() {
file << "CommandInput=" << command_input << "\n";
file << "MaxLogLines=" << max_log_lines << "\n";
file << "AutoScrollLogs=" << (auto_scroll_logs ? "1" : "0") << "\n";
file << "EnableColoredLogs=" << (enable_colored_logs ? "1" : "0") << "\n";
file << "AutoStart=" << (auto_start ? "1" : "0") << "\n";
file << "WebUrl=" << web_url << "\n";

View File

@ -1,6 +1,7 @@
#include "CLIProcess.h"
#include <algorithm>
#include <cstdio>
#include <filesystem>
#ifdef _WIN32
#include "Units.h"
@ -20,17 +21,122 @@ CLIProcess::CLIProcess() {
process_pid_ = -1;
pipe_stdout_[0] = pipe_stdout_[1] = -1;
pipe_stdin_[0] = pipe_stdin_[1] = -1;
process__running_ = false;
process_running_ = false;
#endif
max_log_lines_ = 1000;
stop_timeout_ms_ = 5000;
output_encoding_ = OutputEncoding::AUTO_DETECT;
use_auto_working_dir_ = true; // 新增:默认启用自动工作目录
}
CLIProcess::~CLIProcess() {
Stop();
CleanupResources();
}
// 新增:设置工作目录
void CLIProcess::SetWorkingDirectory(const std::string& working_dir) {
std::lock_guard<std::mutex> lock(working_dir_mutex_);
if (working_dir.empty()) {
use_auto_working_dir_ = true;
working_directory_.clear();
} else {
if (DirectoryExists(working_dir)) {
working_directory_ = GetAbsolutePath(working_dir);
use_auto_working_dir_ = false;
AddLog("工作目录已设置为: " + working_directory_);
} else {
AddLog("警告: 指定的工作目录不存在: " + working_dir);
}
}
}
// 新增:获取当前工作目录设置
std::string CLIProcess::GetWorkingDirectory() const {
std::lock_guard<std::mutex> lock(working_dir_mutex_);
return working_directory_;
}
// 新增:从命令中提取目录路径
std::string CLIProcess::ExtractDirectoryFromCommand(const std::string& command) {
if (command.empty()) return "";
std::string trimmed_command = command;
// 移除前后空格
size_t start = trimmed_command.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
size_t end = trimmed_command.find_last_not_of(" \t\r\n");
trimmed_command = trimmed_command.substr(start, end - start + 1);
std::string executable_path;
// 处理引号包围的路径
if (trimmed_command[0] == '"') {
size_t quote_end = trimmed_command.find('"', 1);
if (quote_end != std::string::npos) {
executable_path = trimmed_command.substr(1, quote_end - 1);
}
} else {
// 找到第一个空格前的部分作为可执行文件路径
size_t space_pos = trimmed_command.find(' ');
if (space_pos != std::string::npos) {
executable_path = trimmed_command.substr(0, space_pos);
} else {
executable_path = trimmed_command;
}
}
if (executable_path.empty()) return "";
// 使用 std::filesystem 来处理路径
try {
std::filesystem::path path(executable_path);
// 如果是相对路径,转换为绝对路径
if (path.is_relative()) {
path = std::filesystem::absolute(path);
}
// 检查文件是否存在
if (std::filesystem::exists(path) && std::filesystem::is_regular_file(path)) {
return path.parent_path().string();
}
// 如果文件不存在,但路径看起来像一个文件路径,返回其父目录
if (path.has_parent_path()) {
auto parent = path.parent_path();
if (std::filesystem::exists(parent) && std::filesystem::is_directory(parent)) {
return parent.string();
}
}
} catch (const std::exception& e) {
// 路径解析失败,返回当前工作目录
return std::filesystem::current_path().string();
}
return "";
}
// 新增:获取绝对路径
std::string CLIProcess::GetAbsolutePath(const std::string& path) {
try {
return std::filesystem::absolute(path).string();
} catch (const std::exception&) {
return path;
}
}
// 新增:检查目录是否存在
bool CLIProcess::DirectoryExists(const std::string& path) {
try {
return std::filesystem::exists(path) && std::filesystem::is_directory(path);
} catch (const std::exception&) {
return false;
}
}
// 设置输出编码
void CLIProcess::SetOutputEncoding(OutputEncoding encoding) {
std::lock_guard<std::mutex> lock(encoding_mutex_);
@ -228,7 +334,7 @@ void CLIProcess::SetEnvironmentVariables(const std::map<std::string, std::string
if (!environment_variables_.empty()) {
// AddLog("已设置 " + std::to_string(environment_variables_.size()) + " 个有效环境变量");
for (const auto& pair : environment_variables_) {
AddLog(" " + pair.first + "=" + pair.second);
// AddLog(" " + pair.first + "=" + pair.second);
}
} else {
// AddLog("已清空所有自定义环境变量");
@ -275,6 +381,25 @@ void CLIProcess::ClearEnvironmentVariables() {
void CLIProcess::Start(const std::string& command) {
Stop();
// 确定工作目录
std::string working_dir;
{
std::lock_guard<std::mutex> lock(working_dir_mutex_);
if (use_auto_working_dir_) {
working_dir = ExtractDirectoryFromCommand(command);
if (working_dir.empty()) {
working_dir = std::filesystem::current_path().string();
}
// AddLog("自动检测工作目录: " + working_dir);
} else {
working_dir = working_directory_;
if (!working_dir.empty()) {
// AddLog("使用指定工作目录: " + working_dir);
}
}
}
#ifdef _WIN32
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
@ -329,24 +454,122 @@ void CLIProcess::Start(const std::string& command) {
env_block += '\0';
}
BOOL bSuccess = CreateProcessA(
nullptr,
const_cast<char*>(command.c_str()),
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW,
env_block.empty() ? nullptr : (LPVOID)env_block.data(),
nullptr,
&siStartInfo,
&piProcInfo);
// 处理工作目录 - 支持Unicode路径
const char* working_dir_ptr = nullptr;
std::wstring working_dir_wide;
if (!working_dir.empty()) {
// 验证工作目录是否存在
if (!DirectoryExists(working_dir)) {
// AddLog("警告: 工作目录不存在,使用当前目录: " + working_dir);
working_dir = std::filesystem::current_path().string();
}
// 转换为绝对路径
working_dir = GetAbsolutePath(working_dir);
working_dir_ptr = working_dir.c_str();
// 如果路径包含非ASCII字符需要使用CreateProcessW
bool hasNonAscii = false;
for (char c : working_dir) {
if (static_cast<unsigned char>(c) > 127) {
hasNonAscii = true;
break;
}
}
if (hasNonAscii) {
// 转换为宽字符用于CreateProcessW
int wideSize = MultiByteToWideChar(CP_UTF8, 0, working_dir.c_str(), -1, nullptr, 0);
if (wideSize > 0) {
working_dir_wide.resize(wideSize);
MultiByteToWideChar(CP_UTF8, 0, working_dir.c_str(), -1, &working_dir_wide[0], wideSize);
}
}
}
BOOL bSuccess = FALSE;
// 如果工作目录包含Unicode字符使用CreateProcessW
if (!working_dir_wide.empty()) {
// 转换命令为宽字符
int cmdWideSize = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, nullptr, 0);
if (cmdWideSize > 0) {
std::wstring command_wide(cmdWideSize, L'\0');
MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, &command_wide[0], cmdWideSize);
// 转换环境变量为宽字符
std::wstring env_block_wide;
if (!env_block.empty()) {
int envWideSize = MultiByteToWideChar(CP_UTF8, 0, env_block.c_str(), static_cast<int>(env_block.size()), nullptr, 0);
if (envWideSize > 0) {
env_block_wide.resize(envWideSize);
MultiByteToWideChar(CP_UTF8, 0, env_block.c_str(), static_cast<int>(env_block.size()), &env_block_wide[0], envWideSize);
}
}
STARTUPINFOW siStartInfoW;
ZeroMemory(&siStartInfoW, sizeof(STARTUPINFOW));
siStartInfoW.cb = sizeof(STARTUPINFOW);
siStartInfoW.hStdError = hWriteTmp;
siStartInfoW.hStdOutput = hWriteTmp;
siStartInfoW.hStdInput = hReadTmp_stdin;
siStartInfoW.dwFlags |= STARTF_USESTDHANDLES;
bSuccess = CreateProcessW(
nullptr,
&command_wide[0],
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
env_block_wide.empty() ? nullptr : (LPVOID)env_block_wide.data(),
working_dir_wide.c_str(),
&siStartInfoW,
&piProcInfo);
}
} else {
// 使用ANSI版本
bSuccess = CreateProcessA(
nullptr,
const_cast<char*>(command.c_str()),
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW,
env_block.empty() ? nullptr : static_cast<LPVOID>(env_block.data()),
working_dir_ptr,
&siStartInfo,
&piProcInfo);
}
CloseHandle(hWriteTmp);
CloseHandle(hReadTmp_stdin);
if (!bSuccess) {
DWORD error = GetLastError();
CloseHandle(hReadTmp);
CloseHandle(hWriteTmp_stdin);
AddLog("启动进程失败,错误代码: " + std::to_string(error));
// 提供更详细的错误信息
switch (error) {
case ERROR_FILE_NOT_FOUND:
AddLog("错误: 找不到指定的文件或程序");
break;
case ERROR_PATH_NOT_FOUND:
AddLog("错误: 找不到指定的路径");
break;
case ERROR_ACCESS_DENIED:
AddLog("错误: 访问被拒绝,可能需要管理员权限");
break;
case ERROR_INVALID_PARAMETER:
AddLog("错误: 无效的参数");
break;
default:
AddLog("错误: 未知错误,请检查命令和路径是否正确");
break;
}
return;
}
@ -356,15 +579,21 @@ void CLIProcess::Start(const std::string& command) {
hReadPipe_ = hReadTmp;
hWritePipe_stdin_ = hWriteTmp_stdin;
AddLog("进程已启动PID: " + std::to_string(piProcInfo.dwProcessId));
if (!working_dir.empty()) {
AddLog("工作目录: " + working_dir);
}
// Start output reading thread
output_thread_ = std::thread(&CLIProcess::ReadOutput, this);
#else
// Unix/Linux implementation using posix_spawn
// Unix/Linux implementation
int pipe_out[2];
int pipe_in[2];
if (pipe(pipe_out) < 0 || pipe(pipe_in) < 0) {
AddLog("创建管道失败: " + std::string(strerror(errno)));
return;
}
@ -380,10 +609,26 @@ void CLIProcess::Start(const std::string& command) {
dup2(pipe_in[0], STDIN_FILENO);
close(pipe_in[0]);
// 设置工作目录
if (!working_dir.empty()) {
if (!DirectoryExists(working_dir)) {
fprintf(stderr, "警告: 工作目录不存在: %s\n", working_dir.c_str());
working_dir = std::filesystem::current_path().string();
}
if (chdir(working_dir.c_str()) != 0) {
fprintf(stderr, "无法切换到工作目录: %s, 错误: %s\n",
working_dir.c_str(), strerror(errno));
}
}
// Prepare environment variables
if (!environment_variables_.empty()) {
for (const auto& kv : environment_variables_) {
setenv(kv.first.c_str(), kv.second.c_str(), 1);
{
std::lock_guard<std::mutex> lock(env_mutex_);
if (!environment_variables_.empty()) {
for (const auto& kv : environment_variables_) {
setenv(kv.first.c_str(), kv.second.c_str(), 1);
}
}
}
@ -403,8 +648,19 @@ void CLIProcess::Start(const std::string& command) {
process_running_ = true;
AddLog("进程已启动PID: " + std::to_string(pid));
if (!working_dir.empty()) {
AddLog("工作目录: " + working_dir);
}
// Start output reading thread
output_thread_ = std::thread(&CLIProcess::ReadOutput, this);
} else {
AddLog("fork失败无法启动进程: " + std::string(strerror(errno)));
close(pipe_out[0]);
close(pipe_out[1]);
close(pipe_in[0]);
close(pipe_in[1]);
}
#endif
}

File diff suppressed because it is too large Load Diff