Compare commits
8 Commits
534e5c0850
...
286bd74835
Author | SHA1 | Date |
---|---|---|
|
286bd74835 | |
|
eb3a5aa939 | |
|
b8227c46d3 | |
|
b7dc9495ac | |
|
59c791c5a7 | |
|
d2113117cc | |
|
0ab3543aa4 | |
|
7b6c87f2b0 |
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.31)
|
cmake_minimum_required(VERSION 3.26)
|
||||||
project(CLI_Manager)
|
project(CLI_Manager)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
@ -12,7 +12,7 @@ set(IMGUI_BACKENDS "glfw_opengl")
|
||||||
|
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
#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)
|
add_definitions(-DUNICODE -D_UNICODE)
|
||||||
|
|
||||||
|
@ -85,7 +85,6 @@ if(IMGUI_BACKENDS STREQUAL "win32_dx11")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 设置链接选项
|
# 设置链接选项
|
||||||
#set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
# LINK_FLAGS "-static -Wl,-subsystem,windows"
|
LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
|
||||||
#)
|
)
|
||||||
#
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "CLIProcess.h"
|
#include "CLIProcess.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
class AppState {
|
class AppState {
|
||||||
|
@ -15,12 +16,21 @@ public:
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
void ApplySettings();
|
void ApplySettings();
|
||||||
|
|
||||||
|
// 启动命令历史记录管理
|
||||||
|
void AddCommandToHistory(const std::string& command);
|
||||||
|
void RemoveCommandFromHistory(int index);
|
||||||
|
void ClearCommandHistory();
|
||||||
|
const std::vector<std::string>& GetCommandHistory() const { return command_history; }
|
||||||
|
|
||||||
bool show_main_window;
|
bool show_main_window;
|
||||||
bool auto_start;
|
bool auto_start;
|
||||||
CLIProcess cli_process;
|
CLIProcess cli_process;
|
||||||
char command_input[256]{};
|
char command_input[256]{};
|
||||||
char send_command[256]{};
|
char send_command[256]{};
|
||||||
|
char working_directory[256]{};
|
||||||
|
bool auto_working_dir;
|
||||||
bool auto_scroll_logs;
|
bool auto_scroll_logs;
|
||||||
|
bool enable_colored_logs;
|
||||||
int max_log_lines;
|
int max_log_lines;
|
||||||
char web_url[256]{};
|
char web_url[256]{};
|
||||||
|
|
||||||
|
@ -33,9 +43,13 @@ public:
|
||||||
std::map<std::string, std::string> environment_variables;
|
std::map<std::string, std::string> environment_variables;
|
||||||
bool use_custom_environment;
|
bool use_custom_environment;
|
||||||
|
|
||||||
// 新增:输出编码相关配置
|
// 输出编码相关配置
|
||||||
OutputEncoding output_encoding;
|
OutputEncoding output_encoding;
|
||||||
|
|
||||||
|
// 新增:启动命令历史记录
|
||||||
|
std::vector<std::string> command_history;
|
||||||
|
int max_command_history;
|
||||||
|
|
||||||
bool settings_dirty;
|
bool settings_dirty;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -43,9 +57,13 @@ private:
|
||||||
std::string SerializeEnvironmentVariables() const;
|
std::string SerializeEnvironmentVariables() const;
|
||||||
void DeserializeEnvironmentVariables(const std::string& serialized);
|
void DeserializeEnvironmentVariables(const std::string& serialized);
|
||||||
|
|
||||||
// 新增:编码序列化辅助函数
|
// 编码序列化辅助函数
|
||||||
std::string SerializeOutputEncoding() const;
|
std::string SerializeOutputEncoding() const;
|
||||||
void DeserializeOutputEncoding(const std::string& serialized);
|
void DeserializeOutputEncoding(const std::string& serialized);
|
||||||
|
|
||||||
|
// 新增:命令历史记录序列化辅助函数
|
||||||
|
std::string SerializeCommandHistory() const;
|
||||||
|
void DeserializeCommandHistory(const std::string& serialized);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APP_STATE_H
|
#endif // APP_STATE_H
|
|
@ -6,16 +6,33 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
// 新增:输出编码枚举
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 输出编码枚举
|
||||||
enum class OutputEncoding {
|
enum class OutputEncoding {
|
||||||
AUTO_DETECT =0,
|
AUTO_DETECT = 0,
|
||||||
UTF8,
|
UTF8,
|
||||||
|
#ifdef _WIN32
|
||||||
GBK,
|
GBK,
|
||||||
GB2312,
|
GB2312,
|
||||||
BIG5,
|
BIG5,
|
||||||
SHIFT_JIS,
|
SHIFT_JIS,
|
||||||
|
#else
|
||||||
|
// Unix/Linux 常见编码
|
||||||
|
ISO_8859_1,
|
||||||
|
GB18030,
|
||||||
|
BIG5,
|
||||||
|
EUC_JP,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class CLIProcess {
|
class CLIProcess {
|
||||||
|
@ -26,12 +43,19 @@ public:
|
||||||
void SetMaxLogLines(int max_lines);
|
void SetMaxLogLines(int max_lines);
|
||||||
void SetStopCommand(const std::string& command, int timeout_ms = 5000);
|
void SetStopCommand(const std::string& command, int timeout_ms = 5000);
|
||||||
void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars);
|
void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars);
|
||||||
void SetOutputEncoding(OutputEncoding encoding); // 新增:设置输出编码
|
void SetOutputEncoding(OutputEncoding encoding);
|
||||||
|
void SetAutoWorkingDir(bool auto_dir);
|
||||||
|
|
||||||
|
// 工作目录设置
|
||||||
|
void SetWorkingDirectory(const std::string& working_dir);
|
||||||
|
std::string GetWorkingDirectory() const;
|
||||||
|
|
||||||
void Start(const std::string& command);
|
void Start(const std::string& command);
|
||||||
void Stop();
|
void Stop();
|
||||||
void Restart(const std::string& command);
|
void Restart(const std::string& command);
|
||||||
|
|
||||||
|
std::wstring GetPid() const;
|
||||||
|
|
||||||
void ClearLogs();
|
void ClearLogs();
|
||||||
void AddLog(const std::string& log);
|
void AddLog(const std::string& log);
|
||||||
const std::vector<std::string>& GetLogs() const;
|
const std::vector<std::string>& GetLogs() const;
|
||||||
|
@ -47,19 +71,26 @@ public:
|
||||||
void RemoveEnvironmentVariable(const std::string& key);
|
void RemoveEnvironmentVariable(const std::string& key);
|
||||||
void ClearEnvironmentVariables();
|
void ClearEnvironmentVariables();
|
||||||
|
|
||||||
// 新增:编码相关接口
|
// 编码相关接口
|
||||||
OutputEncoding GetOutputEncoding() const;
|
OutputEncoding GetOutputEncoding() const;
|
||||||
static std::string GetEncodingName(OutputEncoding encoding);
|
static std::string GetEncodingName(OutputEncoding encoding);
|
||||||
static std::vector<std::pair<OutputEncoding, std::string>> GetSupportedEncodings();
|
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:
|
private:
|
||||||
void ReadOutput();
|
void ReadOutput();
|
||||||
void CloseProcessHandles();
|
void CloseProcessHandles();
|
||||||
void CleanupResources();
|
void CleanupResources();
|
||||||
|
|
||||||
// 新增:编码转换相关方法
|
// 编码转换相关方法
|
||||||
std::string ConvertToUTF8(const std::string& input, OutputEncoding encoding);
|
static std::string ConvertToUTF8(std::string& input, OutputEncoding encoding);
|
||||||
std::string DetectAndConvertToUTF8(const std::string& input);
|
std::string DetectAndConvertToUTF8(std::string& input);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
static UINT GetCodePageFromEncoding(OutputEncoding encoding);
|
static UINT GetCodePageFromEncoding(OutputEncoding encoding);
|
||||||
static bool IsValidUTF8(const std::string& str);
|
static bool IsValidUTF8(const std::string& str);
|
||||||
|
|
||||||
|
@ -68,6 +99,17 @@ private:
|
||||||
HANDLE hWritePipe_{};
|
HANDLE hWritePipe_{};
|
||||||
HANDLE hReadPipe_stdin_{};
|
HANDLE hReadPipe_stdin_{};
|
||||||
HANDLE hWritePipe_stdin_;
|
HANDLE hWritePipe_stdin_;
|
||||||
|
#else
|
||||||
|
// Unix/Linux 进程管理
|
||||||
|
pid_t process_pid_;
|
||||||
|
int pipe_stdout_[2];
|
||||||
|
int pipe_stdin_[2];
|
||||||
|
bool process_running_;
|
||||||
|
|
||||||
|
// Unix 编码转换辅助函数
|
||||||
|
std::string ConvertUnixEncoding(const std::string& input, const std::string& from_encoding);
|
||||||
|
static std::string GetUnixEncodingName(OutputEncoding encoding);
|
||||||
|
#endif
|
||||||
|
|
||||||
mutable std::mutex logs_mutex_;
|
mutable std::mutex logs_mutex_;
|
||||||
std::vector<std::string> logs_;
|
std::vector<std::string> logs_;
|
||||||
|
@ -76,7 +118,7 @@ private:
|
||||||
std::thread output_thread_;
|
std::thread output_thread_;
|
||||||
|
|
||||||
// 停止命令相关
|
// 停止命令相关
|
||||||
std::mutex stop_mutex_;
|
mutable std::mutex stop_mutex_;
|
||||||
std::string stop_command_;
|
std::string stop_command_;
|
||||||
int stop_timeout_ms_;
|
int stop_timeout_ms_;
|
||||||
|
|
||||||
|
@ -84,9 +126,14 @@ private:
|
||||||
mutable std::mutex env_mutex_;
|
mutable std::mutex env_mutex_;
|
||||||
std::map<std::string, std::string> environment_variables_;
|
std::map<std::string, std::string> environment_variables_;
|
||||||
|
|
||||||
// 新增:编码相关
|
// 编码相关
|
||||||
mutable std::mutex encoding_mutex_;
|
mutable std::mutex encoding_mutex_;
|
||||||
OutputEncoding output_encoding_;
|
OutputEncoding output_encoding_;
|
||||||
|
|
||||||
|
// 工作目录相关
|
||||||
|
mutable std::mutex working_dir_mutex_;
|
||||||
|
std::string working_directory_;
|
||||||
|
bool use_auto_working_dir_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CLIPROCESS_H
|
#endif // CLIPROCESS_H
|
|
@ -1,97 +1,170 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "imgui.h"
|
// 系统头文件
|
||||||
#include "AppState.h"
|
#include <memory>
|
||||||
#include "TrayIcon.h"
|
|
||||||
|
|
||||||
|
// 第三方库头文件
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
// 平台相关头文件
|
||||||
#ifdef USE_WIN32_BACKEND
|
#ifdef USE_WIN32_BACKEND
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include "imgui_impl_win32.h"
|
#include "imgui_impl_win32.h"
|
||||||
#include "imgui_impl_dx11.h"
|
#include "imgui_impl_dx11.h"
|
||||||
#else
|
#else
|
||||||
|
#ifdef _WIN32
|
||||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||||
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <GLFW/glfw3native.h>
|
#include <GLFW/glfw3native.h>
|
||||||
#include "imgui_impl_glfw.h"
|
|
||||||
#include "imgui_impl_opengl3.h"
|
|
||||||
#include <windows.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
|
#endif
|
||||||
|
|
||||||
#include <memory>
|
#include "imgui_impl_glfw.h"
|
||||||
|
#include "imgui_impl_opengl3.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 项目头文件
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "TrayIcon.h"
|
||||||
|
|
||||||
class Manager {
|
class Manager {
|
||||||
public:
|
public:
|
||||||
|
// 构造函数和析构函数
|
||||||
Manager();
|
Manager();
|
||||||
|
|
||||||
~Manager();
|
~Manager();
|
||||||
|
|
||||||
bool Initialize();
|
// 核心生命周期管理
|
||||||
void Run();
|
bool Initialize(); // 初始化应用程序
|
||||||
void Shutdown();
|
void Run(); // 运行主循环
|
||||||
|
void Shutdown(); // 关闭应用程序
|
||||||
|
|
||||||
void OnTrayShowWindow();
|
// 托盘事件回调
|
||||||
void OnTrayExit();
|
void OnTrayShowWindow(); // 托盘显示窗口事件
|
||||||
|
void OnTrayExit(); // 托盘退出事件
|
||||||
|
|
||||||
|
// 公共成员变量
|
||||||
|
AppState m_app_state; // 应用程序状态
|
||||||
|
|
||||||
AppState m_app_state;
|
|
||||||
private:
|
private:
|
||||||
// UI渲染
|
// 枚举类型定义
|
||||||
void RenderUI();
|
enum class LayoutPreset {
|
||||||
void RenderMenuBar();
|
Classic, // 经典布局
|
||||||
void RenderMainContent();
|
Development, // 开发布局
|
||||||
void RenderSettingsMenu();
|
Monitoring // 监控布局
|
||||||
void RenderStopCommandSettings();
|
};
|
||||||
void RenderEnvironmentVariablesSettings();
|
|
||||||
void RenderOutputEncodingSettings(); // 新增:输出编码设置UI
|
|
||||||
|
|
||||||
// 事件处理
|
|
||||||
void HandleMessages();
|
|
||||||
bool ShouldExit() const;
|
|
||||||
void ShowMainWindow();
|
|
||||||
void HideMainWindow();
|
|
||||||
|
|
||||||
// 平台相关初始化
|
// 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(); // 加载已保存布局
|
||||||
|
|
||||||
|
// 事件处理相关方法
|
||||||
|
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
|
#ifdef USE_WIN32_BACKEND
|
||||||
bool InitializeWin32();
|
bool InitializeWin32(); // 初始化Win32
|
||||||
bool InitializeDirectX11();
|
bool InitializeDirectX11(); // 初始化DirectX11
|
||||||
void CleanupWin32();
|
void CleanupWin32(); // 清理Win32
|
||||||
void CleanupDirectX11();
|
void CleanupDirectX11(); // 清理DirectX11
|
||||||
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
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;
|
|
||||||
#else
|
#else
|
||||||
bool InitializeGLFW();
|
|
||||||
void CleanupGLFW();
|
|
||||||
static void GlfwErrorCallback(int error, const char* description);
|
|
||||||
|
|
||||||
GLFWwindow* m_window = nullptr;
|
bool InitializeGLFW(); // 初始化GLFW
|
||||||
const char* m_glsl_version = nullptr;
|
void CleanupGLFW(); // 清理GLFW
|
||||||
|
static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 托盘相关
|
// 托盘相关方法
|
||||||
bool InitializeTray();
|
bool InitializeTray(); // 初始化托盘
|
||||||
void CleanupTray();
|
void CleanupTray(); // 清理托盘
|
||||||
static HWND CreateHiddenWindow();
|
#ifdef _WIN32
|
||||||
|
|
||||||
std::unique_ptr<TrayIcon> m_tray;
|
static HWND CreateHiddenWindow(); // 创建隐藏窗口
|
||||||
HWND m_tray_hwnd = nullptr;
|
#endif
|
||||||
|
|
||||||
|
// 平台相关成员变量
|
||||||
|
#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_should_exit = false; // 是否应该退出
|
||||||
bool m_initialized = 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缩放
|
||||||
|
|
||||||
// DPI缩放因子
|
// 布局相关成员变量
|
||||||
float m_dpi_scale = 1.0f;
|
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; // 是否重置布局
|
||||||
|
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状态
|
// UI状态相关成员变量
|
||||||
char env_key_input_[256] = {};
|
char env_key_input_[256] = {}; // 环境变量键输入缓冲区
|
||||||
char env_value_input_[512] = {};
|
char env_value_input_[512] = {}; // 环境变量值输入缓冲区
|
||||||
bool show_env_settings_ = false;
|
bool show_env_settings_ = false; // 是否显示环境变量设置
|
||||||
|
bool show_encoding_settings_ = false; // 是否显示编码设置
|
||||||
// 新增:编码设置UI状态
|
bool show_command_history_ = false; // 是否显示命令历史
|
||||||
bool show_encoding_settings_ = false;
|
|
||||||
};
|
};
|
|
@ -1,6 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
@ -10,33 +14,73 @@ public:
|
||||||
using ShowWindowCallback = std::function<void()>;
|
using ShowWindowCallback = std::function<void()>;
|
||||||
using ExitCallback = std::function<void()>;
|
using ExitCallback = std::function<void()>;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
TrayIcon(HWND hwnd, HICON icon);
|
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
|
||||||
|
TrayIcon(void* app_delegate, void* icon);
|
||||||
|
void UpdateWebUrl(const std::string& url);
|
||||||
|
void OnMacMenuAction(int action);
|
||||||
|
#endif
|
||||||
|
|
||||||
~TrayIcon();
|
~TrayIcon();
|
||||||
|
|
||||||
void Show();
|
void Show();
|
||||||
void Hide();
|
void Hide();
|
||||||
void UpdateWebUrl(const std::wstring& url);
|
|
||||||
|
|
||||||
// 设置回调函数
|
// 设置回调函数
|
||||||
void SetShowWindowCallback(const ShowWindowCallback &callback);
|
void SetShowWindowCallback(const ShowWindowCallback &callback);
|
||||||
void SetExitCallback(const ExitCallback &callback);
|
void SetExitCallback(const ExitCallback &callback);
|
||||||
|
#ifdef _WIN32
|
||||||
// 静态窗口过程
|
void ShowWindowsNotification(const std::wstring& title, const std::wstring& message);
|
||||||
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
#elif __APPLE__
|
||||||
|
void ShowMacNotification(const std::string& title, const std::string& message);
|
||||||
|
#else
|
||||||
|
void ShowLinuxNotification(const std::string& title, const std::string& message);
|
||||||
|
#endif
|
||||||
private:
|
private:
|
||||||
void CreateMenu();
|
void CreateMenu();
|
||||||
void DestroyMenu();
|
void DestroyMenu();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
void ShowContextMenu() const;
|
void ShowContextMenu() const;
|
||||||
|
|
||||||
HWND m_hwnd;
|
HWND m_hwnd;
|
||||||
HICON m_icon;
|
HICON m_icon;
|
||||||
NOTIFYICONDATA m_nid{};
|
NOTIFYICONDATA m_nid{};
|
||||||
std::wstring m_web_url;
|
std::wstring m_web_url;
|
||||||
bool m_visible;
|
std::wstring m_status;
|
||||||
|
std::wstring m_pid;
|
||||||
HMENU m_menu;
|
HMENU m_menu;
|
||||||
|
#else
|
||||||
|
void ShowMacTrayIcon();
|
||||||
|
void HideMacTrayIcon();
|
||||||
|
void CreateMacMenu();
|
||||||
|
void DestroyMacMenu();
|
||||||
|
|
||||||
|
void* m_app_delegate;
|
||||||
|
void* m_icon;
|
||||||
|
std::string m_web_url;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool m_visible;
|
||||||
|
|
||||||
// 回调函数
|
// 回调函数
|
||||||
ShowWindowCallback m_show_window_callback;
|
ShowWindowCallback m_show_window_callback;
|
||||||
ExitCallback m_exit_callback;
|
ExitCallback m_exit_callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// C 接口函数声明,供 Objective-C 调用
|
||||||
|
void TrayIconMenuCallback(void* tray_instance, int action);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
#ifndef UNITS_H
|
#ifndef UNITS_H
|
||||||
#define UNITS_H
|
#define UNITS_H
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
std::wstring StringToWide(const std::string& str);
|
std::wstring StringToWide(const std::string& str);
|
||||||
std::string WideToString(const std::wstring& wstr);
|
std::string WideToString(const std::wstring& wstr);
|
||||||
void SetAutoStart(bool enable);
|
void SetAutoStart(bool enable);
|
||||||
bool IsAutoStartEnabled();
|
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 ¤tColor, bool currentBold); // 解析单个ANSI颜色代码
|
||||||
|
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
|
||||||
|
|
||||||
#endif //UNITS_H
|
#endif //UNITS_H
|
||||||
|
|
|
@ -7,11 +7,14 @@ AppState::AppState() :
|
||||||
show_main_window(true),
|
show_main_window(true),
|
||||||
auto_start(false),
|
auto_start(false),
|
||||||
auto_scroll_logs(true),
|
auto_scroll_logs(true),
|
||||||
|
auto_working_dir(true),
|
||||||
|
enable_colored_logs(true),
|
||||||
max_log_lines(1000),
|
max_log_lines(1000),
|
||||||
stop_timeout_ms(5000),
|
stop_timeout_ms(5000),
|
||||||
use_stop_command(false),
|
use_stop_command(false),
|
||||||
use_custom_environment(false),
|
use_custom_environment(false),
|
||||||
output_encoding(OutputEncoding::AUTO_DETECT), // 新增:默认自动检测编码
|
output_encoding(OutputEncoding::AUTO_DETECT),
|
||||||
|
max_command_history(20), // 新增:最大历史记录数量
|
||||||
settings_dirty(false) {
|
settings_dirty(false) {
|
||||||
strcpy_s(command_input, "cmd.exe");
|
strcpy_s(command_input, "cmd.exe");
|
||||||
strcpy_s(web_url, "http://localhost:8080");
|
strcpy_s(web_url, "http://localhost:8080");
|
||||||
|
@ -19,6 +22,90 @@ AppState::AppState() :
|
||||||
memset(send_command, 0, sizeof(send_command));
|
memset(send_command, 0, sizeof(send_command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:添加命令到历史记录
|
||||||
|
void AppState::AddCommandToHistory(const std::string& command) {
|
||||||
|
if (command.empty()) return;
|
||||||
|
|
||||||
|
// 移除重复的命令
|
||||||
|
auto it = std::find(command_history.begin(), command_history.end(), command);
|
||||||
|
if (it != command_history.end()) {
|
||||||
|
command_history.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到开头
|
||||||
|
command_history.insert(command_history.begin(), command);
|
||||||
|
|
||||||
|
// 限制历史记录数量
|
||||||
|
if (command_history.size() > static_cast<size_t>(max_command_history)) {
|
||||||
|
command_history.resize(max_command_history);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:从历史记录中移除命令
|
||||||
|
void AppState::RemoveCommandFromHistory(int index) {
|
||||||
|
if (index >= 0 && index < static_cast<int>(command_history.size())) {
|
||||||
|
command_history.erase(command_history.begin() + index);
|
||||||
|
settings_dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:清空命令历史记录
|
||||||
|
void AppState::ClearCommandHistory() {
|
||||||
|
command_history.clear();
|
||||||
|
settings_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:序列化命令历史记录
|
||||||
|
std::string AppState::SerializeCommandHistory() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& command : command_history) {
|
||||||
|
if (!first) {
|
||||||
|
oss << "|";
|
||||||
|
}
|
||||||
|
// 转义分隔符
|
||||||
|
std::string escaped_command = command;
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = escaped_command.find("|", pos)) != std::string::npos) {
|
||||||
|
escaped_command.replace(pos, 1, "\\|");
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
oss << escaped_command;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化命令历史记录
|
||||||
|
void AppState::DeserializeCommandHistory(const std::string& serialized) {
|
||||||
|
command_history.clear();
|
||||||
|
if (serialized.empty()) return;
|
||||||
|
|
||||||
|
std::istringstream iss(serialized);
|
||||||
|
std::string command;
|
||||||
|
std::string current;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < serialized.length(); ++i) {
|
||||||
|
if (serialized[i] == '\\' && i + 1 < serialized.length() && serialized[i + 1] == '|') {
|
||||||
|
current += '|';
|
||||||
|
++i; // 跳过下一个字符
|
||||||
|
} else if (serialized[i] == '|') {
|
||||||
|
if (!current.empty()) {
|
||||||
|
command_history.push_back(current);
|
||||||
|
current.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current += serialized[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current.empty()) {
|
||||||
|
command_history.push_back(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string AppState::SerializeEnvironmentVariables() const {
|
std::string AppState::SerializeEnvironmentVariables() const {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
@ -99,6 +186,9 @@ void AppState::LoadSettings() {
|
||||||
if (key == "CommandInput") {
|
if (key == "CommandInput") {
|
||||||
strncpy_s(command_input, value.c_str(), sizeof(command_input) - 1);
|
strncpy_s(command_input, value.c_str(), sizeof(command_input) - 1);
|
||||||
}
|
}
|
||||||
|
else if (key == "WorkingDirectory") {
|
||||||
|
strncpy_s(working_directory, value.c_str(), sizeof(working_directory) - 1);
|
||||||
|
}
|
||||||
else if (key == "MaxLogLines") {
|
else if (key == "MaxLogLines") {
|
||||||
max_log_lines = std::stoi(value);
|
max_log_lines = std::stoi(value);
|
||||||
max_log_lines = std::max(100, std::min(max_log_lines, 10000));
|
max_log_lines = std::max(100, std::min(max_log_lines, 10000));
|
||||||
|
@ -106,9 +196,15 @@ void AppState::LoadSettings() {
|
||||||
else if (key == "AutoScrollLogs") {
|
else if (key == "AutoScrollLogs") {
|
||||||
auto_scroll_logs = (value == "1");
|
auto_scroll_logs = (value == "1");
|
||||||
}
|
}
|
||||||
|
else if (key == "EnableColoredLogs") {
|
||||||
|
enable_colored_logs = (value == "1");
|
||||||
|
}
|
||||||
else if (key == "AutoStart") {
|
else if (key == "AutoStart") {
|
||||||
auto_start = (value == "1");
|
auto_start = (value == "1");
|
||||||
}
|
}
|
||||||
|
else if (key == "AutoWorkDirectory") {
|
||||||
|
auto_working_dir = (value == "1");
|
||||||
|
}
|
||||||
else if (key == "WebUrl") {
|
else if (key == "WebUrl") {
|
||||||
strncpy_s(web_url, value.c_str(), sizeof(web_url) - 1);
|
strncpy_s(web_url, value.c_str(), sizeof(web_url) - 1);
|
||||||
}
|
}
|
||||||
|
@ -128,10 +224,17 @@ void AppState::LoadSettings() {
|
||||||
else if (key == "EnvironmentVariables") {
|
else if (key == "EnvironmentVariables") {
|
||||||
DeserializeEnvironmentVariables(value);
|
DeserializeEnvironmentVariables(value);
|
||||||
}
|
}
|
||||||
// 新增:输出编码配置的加载
|
|
||||||
else if (key == "OutputEncoding") {
|
else if (key == "OutputEncoding") {
|
||||||
DeserializeOutputEncoding(value);
|
DeserializeOutputEncoding(value);
|
||||||
}
|
}
|
||||||
|
// 新增:命令历史记录配置的加载
|
||||||
|
else if (key == "CommandHistory") {
|
||||||
|
DeserializeCommandHistory(value);
|
||||||
|
}
|
||||||
|
else if (key == "MaxCommandHistory") {
|
||||||
|
max_command_history = std::stoi(value);
|
||||||
|
max_command_history = std::max(5, std::min(max_command_history, 100));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,9 +247,12 @@ void AppState::SaveSettings() {
|
||||||
|
|
||||||
file << "[Settings]\n";
|
file << "[Settings]\n";
|
||||||
file << "CommandInput=" << command_input << "\n";
|
file << "CommandInput=" << command_input << "\n";
|
||||||
|
file << "WorkingDirectory=" << working_directory << "\n";
|
||||||
file << "MaxLogLines=" << max_log_lines << "\n";
|
file << "MaxLogLines=" << max_log_lines << "\n";
|
||||||
file << "AutoScrollLogs=" << (auto_scroll_logs ? "1" : "0") << "\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 << "AutoStart=" << (auto_start ? "1" : "0") << "\n";
|
||||||
|
file << "AutoWorkDirectory=" << (auto_working_dir ? "1" : "0") << "\n";
|
||||||
file << "WebUrl=" << web_url << "\n";
|
file << "WebUrl=" << web_url << "\n";
|
||||||
|
|
||||||
// 停止命令相关配置的保存
|
// 停止命令相关配置的保存
|
||||||
|
@ -158,9 +264,13 @@ void AppState::SaveSettings() {
|
||||||
file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n";
|
file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n";
|
||||||
file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n";
|
file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n";
|
||||||
|
|
||||||
// 新增:输出编码配置的保存
|
// 输出编码配置的保存
|
||||||
file << "OutputEncoding=" << SerializeOutputEncoding() << "\n";
|
file << "OutputEncoding=" << SerializeOutputEncoding() << "\n";
|
||||||
|
|
||||||
|
// 新增:命令历史记录配置的保存
|
||||||
|
file << "CommandHistory=" << SerializeCommandHistory() << "\n";
|
||||||
|
file << "MaxCommandHistory=" << max_command_history << "\n";
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
settings_dirty = false;
|
settings_dirty = false;
|
||||||
|
@ -183,6 +293,11 @@ void AppState::ApplySettings() {
|
||||||
cli_process.SetEnvironmentVariables({});
|
cli_process.SetEnvironmentVariables({});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:应用输出编码设置
|
if (strlen(working_directory)>0) {
|
||||||
|
cli_process.SetWorkingDirectory(working_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
cli_process.SetAutoWorkingDir(auto_working_dir);
|
||||||
|
// 应用输出编码设置
|
||||||
cli_process.SetOutputEncoding(output_encoding);
|
cli_process.SetOutputEncoding(output_encoding);
|
||||||
}
|
}
|
|
@ -1,60 +1,197 @@
|
||||||
#include "CLIProcess.h"
|
#include "CLIProcess.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include "Units.h"
|
#include "Units.h"
|
||||||
|
#else
|
||||||
|
#include <iconv.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <langinfo.h>
|
||||||
|
#include <spawn.h>
|
||||||
|
extern char **environ;
|
||||||
|
#endif
|
||||||
|
|
||||||
CLIProcess::CLIProcess() {
|
CLIProcess::CLIProcess() {
|
||||||
|
#ifdef _WIN32
|
||||||
ZeroMemory(&pi_, sizeof(pi_));
|
ZeroMemory(&pi_, sizeof(pi_));
|
||||||
max_log_lines_ = 1000;
|
|
||||||
hWritePipe_stdin_ = nullptr;
|
hWritePipe_stdin_ = nullptr;
|
||||||
|
#else
|
||||||
|
process_pid_ = -1;
|
||||||
|
pipe_stdout_[0] = pipe_stdout_[1] = -1;
|
||||||
|
pipe_stdin_[0] = pipe_stdin_[1] = -1;
|
||||||
|
process_running_ = false;
|
||||||
|
#endif
|
||||||
|
max_log_lines_ = 1000;
|
||||||
stop_timeout_ms_ = 5000;
|
stop_timeout_ms_ = 5000;
|
||||||
output_encoding_ = OutputEncoding::AUTO_DETECT; // 新增:默认自动检测编码
|
output_encoding_ = OutputEncoding::AUTO_DETECT;
|
||||||
|
use_auto_working_dir_ = true; // 自动工作目录
|
||||||
}
|
}
|
||||||
|
|
||||||
CLIProcess::~CLIProcess() {
|
CLIProcess::~CLIProcess() {
|
||||||
Stop();
|
Stop();
|
||||||
|
CleanupResources();
|
||||||
|
}
|
||||||
|
void CLIProcess::SetAutoWorkingDir(const bool auto_dir) {
|
||||||
|
use_auto_working_dir_ = auto_dir;
|
||||||
|
}
|
||||||
|
// 设置工作目录
|
||||||
|
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) {
|
void CLIProcess::SetOutputEncoding(OutputEncoding encoding) {
|
||||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||||
output_encoding_ = encoding;
|
output_encoding_ = encoding;
|
||||||
AddLog("输出编码已设置为: " + GetEncodingName(encoding));
|
AddLog("输出编码已设置为: " + GetEncodingName(encoding));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:获取输出编码
|
// 获取输出编码
|
||||||
OutputEncoding CLIProcess::GetOutputEncoding() const {
|
OutputEncoding CLIProcess::GetOutputEncoding() const {
|
||||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||||
return output_encoding_;
|
return output_encoding_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:获取编码名称
|
// 获取编码名称
|
||||||
std::string CLIProcess::GetEncodingName(const OutputEncoding encoding) {
|
std::string CLIProcess::GetEncodingName(const OutputEncoding encoding) {
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
|
case OutputEncoding::AUTO_DETECT: return "自动检测";
|
||||||
case OutputEncoding::UTF8: return "UTF-8";
|
case OutputEncoding::UTF8: return "UTF-8";
|
||||||
|
#ifdef _WIN32
|
||||||
case OutputEncoding::GBK: return "GBK";
|
case OutputEncoding::GBK: return "GBK";
|
||||||
case OutputEncoding::GB2312: return "GB2312";
|
case OutputEncoding::GB2312: return "GB2312";
|
||||||
case OutputEncoding::BIG5: return "Big5";
|
case OutputEncoding::BIG5: return "BIG5";
|
||||||
case OutputEncoding::SHIFT_JIS: return "Shift-JIS";
|
case OutputEncoding::SHIFT_JIS: return "Shift_JIS";
|
||||||
case OutputEncoding::AUTO_DETECT: return "自动检测";
|
#else
|
||||||
default: return "未知";
|
case OutputEncoding::ISO_8859_1: return "ISO-8859-1";
|
||||||
|
case OutputEncoding::GB18030: return "GB18030";
|
||||||
|
case OutputEncoding::BIG5: return "BIG5";
|
||||||
|
case OutputEncoding::EUC_JP: return "EUC-JP";
|
||||||
|
#endif
|
||||||
|
default: return "未知编码";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:获取支持的编码列表
|
// 获取支持的编码列表
|
||||||
std::vector<std::pair<OutputEncoding, std::string>> CLIProcess::GetSupportedEncodings() {
|
std::vector<std::pair<OutputEncoding, std::string>> CLIProcess::GetSupportedEncodings() {
|
||||||
return {
|
return {
|
||||||
{OutputEncoding::AUTO_DETECT, "自动检测"},
|
{OutputEncoding::AUTO_DETECT, "自动检测"},
|
||||||
{OutputEncoding::UTF8, "UTF-8"},
|
{OutputEncoding::UTF8, "UTF-8"},
|
||||||
|
#ifdef _WIN32
|
||||||
{OutputEncoding::GBK, "GBK (简体中文)"},
|
{OutputEncoding::GBK, "GBK (简体中文)"},
|
||||||
{OutputEncoding::GB2312, "GB2312 (简体中文)"},
|
{OutputEncoding::GB2312, "GB2312 (简体中文)"},
|
||||||
{OutputEncoding::BIG5, "Big5 (繁体中文)"},
|
{OutputEncoding::BIG5, "Big5 (繁体中文)"},
|
||||||
{OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"}
|
{OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"},
|
||||||
|
#else
|
||||||
|
{OutputEncoding::ISO_8859_1, "ISO-8859-1"},
|
||||||
|
{OutputEncoding::GB18030, "GB18030"},
|
||||||
|
{OutputEncoding::BIG5, "BIG5"},
|
||||||
|
{OutputEncoding::EUC_JP, "EUC-JP"},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:根据编码获取代码页
|
// 根据编码获取代码页
|
||||||
UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
|
UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case OutputEncoding::GBK: return 936;
|
case OutputEncoding::GBK: return 936;
|
||||||
|
@ -65,7 +202,7 @@ UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:检查是否为有效的UTF-8
|
// 检查是否为有效的UTF-8
|
||||||
bool CLIProcess::IsValidUTF8(const std::string& str) {
|
bool CLIProcess::IsValidUTF8(const std::string& str) {
|
||||||
const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
|
const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
|
||||||
size_t len = str.length();
|
size_t len = str.length();
|
||||||
|
@ -93,8 +230,8 @@ bool CLIProcess::IsValidUTF8(const std::string& str) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:转换到UTF-8
|
// 转换到UTF-8
|
||||||
std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding encoding) {
|
std::string CLIProcess::ConvertToUTF8(std::string& input, const OutputEncoding encoding) {
|
||||||
if (input.empty()) return input;
|
if (input.empty()) return input;
|
||||||
|
|
||||||
// 如果已经是UTF-8编码,直接返回
|
// 如果已经是UTF-8编码,直接返回
|
||||||
|
@ -130,8 +267,8 @@ std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding e
|
||||||
return std::string(utf8Str.data());
|
return std::string(utf8Str.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:自动检测并转换到UTF-8
|
// 自动检测并转换到UTF-8
|
||||||
std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) {
|
std::string CLIProcess::DetectAndConvertToUTF8(std::string& input) {
|
||||||
if (input.empty()) return input;
|
if (input.empty()) return input;
|
||||||
|
|
||||||
// 首先检查是否已经是有效的UTF-8
|
// 首先检查是否已经是有效的UTF-8
|
||||||
|
@ -140,7 +277,7 @@ std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试不同的编码进行转换
|
// 尝试不同的编码进行转换
|
||||||
std::vector<OutputEncoding> encodingsToTry = {
|
const std::vector encodingsToTry = {
|
||||||
OutputEncoding::GBK,
|
OutputEncoding::GBK,
|
||||||
OutputEncoding::GB2312,
|
OutputEncoding::GB2312,
|
||||||
OutputEncoding::BIG5,
|
OutputEncoding::BIG5,
|
||||||
|
@ -199,7 +336,7 @@ void CLIProcess::SetEnvironmentVariables(const std::map<std::string, std::string
|
||||||
if (!environment_variables_.empty()) {
|
if (!environment_variables_.empty()) {
|
||||||
// AddLog("已设置 " + std::to_string(environment_variables_.size()) + " 个有效环境变量");
|
// AddLog("已设置 " + std::to_string(environment_variables_.size()) + " 个有效环境变量");
|
||||||
for (const auto& pair : environment_variables_) {
|
for (const auto& pair : environment_variables_) {
|
||||||
AddLog(" " + pair.first + "=" + pair.second);
|
// AddLog(" " + pair.first + "=" + pair.second);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// AddLog("已清空所有自定义环境变量");
|
// AddLog("已清空所有自定义环境变量");
|
||||||
|
@ -245,9 +382,27 @@ void CLIProcess::ClearEnvironmentVariables() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CLIProcess::Start(const std::string& command) {
|
void CLIProcess::Start(const std::string& command) {
|
||||||
if (IsRunning()) return;
|
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
|
// 确定工作目录
|
||||||
|
std::wstring working_dir;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(working_dir_mutex_);
|
||||||
|
if (use_auto_working_dir_) {
|
||||||
|
working_dir = StringToWide(ExtractDirectoryFromCommand(command));
|
||||||
|
if (working_dir.empty()) {
|
||||||
|
working_dir = StringToWide(std::filesystem::current_path().string());
|
||||||
|
}
|
||||||
|
// AddLog("自动检测工作目录: " + working_dir);
|
||||||
|
} else {
|
||||||
|
working_dir = StringToWide(working_directory_);
|
||||||
|
if (!working_dir.empty()) {
|
||||||
|
// AddLog("使用指定工作目录: " + working_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
SECURITY_ATTRIBUTES sa;
|
SECURITY_ATTRIBUTES sa;
|
||||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
sa.bInheritHandle = TRUE;
|
sa.bInheritHandle = TRUE;
|
||||||
|
@ -289,7 +444,7 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
if (!environment_variables_.empty()) {
|
if (!environment_variables_.empty()) {
|
||||||
envVarsSet = true;
|
envVarsSet = true;
|
||||||
|
|
||||||
for (const auto& pair : environment_variables_) {
|
for (const auto &pair: environment_variables_) {
|
||||||
if (!pair.first.empty()) {
|
if (!pair.first.empty()) {
|
||||||
// 保存原始值(如果存在)
|
// 保存原始值(如果存在)
|
||||||
DWORD bufferSize = GetEnvironmentVariableA(pair.first.c_str(), nullptr, 0);
|
DWORD bufferSize = GetEnvironmentVariableA(pair.first.c_str(), nullptr, 0);
|
||||||
|
@ -317,7 +472,7 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
|
|
||||||
// AddLog("环境变量设置完成,数量: " + std::to_string(environment_variables_.size()));
|
// AddLog("环境变量设置完成,数量: " + std::to_string(environment_variables_.size()));
|
||||||
} else {
|
} else {
|
||||||
AddLog("未设置自定义环境变量,使用默认环境");
|
// AddLog("未设置自定义环境变量,使用默认环境 PWD:" + WideToString(working_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL result = CreateProcess(
|
BOOL result = CreateProcess(
|
||||||
|
@ -328,14 +483,14 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
TRUE, // bInheritHandles
|
TRUE, // bInheritHandles
|
||||||
CREATE_NO_WINDOW, // dwCreationFlags
|
CREATE_NO_WINDOW, // dwCreationFlags
|
||||||
nullptr, // lpEnvironment (使用nullptr让子进程继承当前环境)
|
nullptr, // lpEnvironment (使用nullptr让子进程继承当前环境)
|
||||||
nullptr, // lpCurrentDirectory
|
working_dir.empty() ? nullptr : working_dir.data(), // lpCurrentDirectory
|
||||||
&si, // lpStartupInfo
|
&si, // lpStartupInfo
|
||||||
&pi_ // lpProcessInformation
|
&pi_ // lpProcessInformation
|
||||||
);
|
);
|
||||||
|
|
||||||
// 恢复原始环境变量
|
// 恢复原始环境变量
|
||||||
if (envVarsSet) {
|
if (envVarsSet) {
|
||||||
for (const auto& pair : originalEnvVars) {
|
for (const auto &pair: originalEnvVars) {
|
||||||
if (pair.second.empty()) {
|
if (pair.second.empty()) {
|
||||||
// 原来不存在,删除变量
|
// 原来不存在,删除变量
|
||||||
SetEnvironmentVariableA(pair.first.c_str(), nullptr);
|
SetEnvironmentVariableA(pair.first.c_str(), nullptr);
|
||||||
|
@ -347,7 +502,7 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
AddLog("进程已启动: " + command);
|
AddLog("进程已启动: " + command + " PID: " + std::to_string(pi_.dwProcessId));
|
||||||
|
|
||||||
CloseHandle(hWritePipe_);
|
CloseHandle(hWritePipe_);
|
||||||
CloseHandle(hReadPipe_stdin_);
|
CloseHandle(hReadPipe_stdin_);
|
||||||
|
@ -357,8 +512,7 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
output_thread_ = std::thread([this]() {
|
output_thread_ = std::thread([this]() {
|
||||||
ReadOutput();
|
ReadOutput();
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
DWORD err = GetLastError();
|
DWORD err = GetLastError();
|
||||||
|
|
||||||
// 获取详细的错误信息
|
// 获取详细的错误信息
|
||||||
|
@ -366,7 +520,7 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
size_t size = FormatMessageW(
|
size_t size = FormatMessageW(
|
||||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
(LPWSTR)&messageBuffer, 0, nullptr);
|
(LPWSTR) &messageBuffer, 0, nullptr);
|
||||||
|
|
||||||
std::string errorMsg = "CreateProcess 失败 (错误代码: " + std::to_string(err) + ")";
|
std::string errorMsg = "CreateProcess 失败 (错误代码: " + std::to_string(err) + ")";
|
||||||
if (messageBuffer) {
|
if (messageBuffer) {
|
||||||
|
@ -384,51 +538,128 @@ void CLIProcess::Start(const std::string& command) {
|
||||||
CloseHandle(hWritePipe_stdin_);
|
CloseHandle(hWritePipe_stdin_);
|
||||||
hReadPipe_ = hWritePipe_ = hReadPipe_stdin_ = hWritePipe_stdin_ = nullptr;
|
hReadPipe_ = hWritePipe_ = hReadPipe_stdin_ = hWritePipe_stdin_ = nullptr;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
// child process
|
||||||
|
close(pipe_out[0]);
|
||||||
|
dup2(pipe_out[1], STDOUT_FILENO);
|
||||||
|
dup2(pipe_out[1], STDERR_FILENO);
|
||||||
|
close(pipe_out[1]);
|
||||||
|
|
||||||
|
close(pipe_in[1]);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execl("/bin/sh", "sh", "-c", command.c_str(), (char*)nullptr);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
else if (pid > 0) {
|
||||||
|
// parent process
|
||||||
|
close(pipe_out[1]);
|
||||||
|
close(pipe_in[0]);
|
||||||
|
|
||||||
|
process_pid_ = pid;
|
||||||
|
pipe_stdout_[0] = pipe_out[0];
|
||||||
|
pipe_stdout_[1] = pipe_out[1]; // closed already in parent, but keep for safety
|
||||||
|
pipe_stdin_[0] = pipe_in[0]; // closed already in parent, but keep for safety
|
||||||
|
pipe_stdin_[1] = pipe_in[1];
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
void CLIProcess::Stop() {
|
void CLIProcess::Stop() {
|
||||||
bool useStopCommand = false;
|
#ifdef _WIN32
|
||||||
std::string stopCmd;
|
|
||||||
int timeout = stop_timeout_ms_;
|
|
||||||
|
|
||||||
// 检查是否设置了停止命令
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(stop_mutex_);
|
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||||
if (!stop_command_.empty() && IsRunning()) {
|
if (pi_.hProcess != nullptr) {
|
||||||
useStopCommand = true;
|
if (!stop_command_.empty()) {
|
||||||
stopCmd = stop_command_;
|
SendCommand(stop_command_);
|
||||||
|
// Wait for process to exit within timeout
|
||||||
|
WaitForSingleObject(pi_.hProcess, stop_timeout_ms_);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (useStopCommand) {
|
|
||||||
AddLog("尝试发送停止命令: " + stopCmd);
|
|
||||||
if (SendCommand(stopCmd)) {
|
|
||||||
// 等待进程正常退出
|
|
||||||
DWORD waitResult = WaitForSingleObject(pi_.hProcess, timeout);
|
|
||||||
if (waitResult == WAIT_OBJECT_0) {
|
|
||||||
// 进程已正常退出
|
|
||||||
CloseProcessHandles();
|
|
||||||
AddLog("进程已通过停止命令正常退出");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AddLog("停止命令超时,将强制终止进程");
|
|
||||||
} else {
|
|
||||||
AddLog("发送停止命令失败,将强制终止进程");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 强制终止进程
|
|
||||||
if (pi_.hProcess) {
|
|
||||||
TerminateProcess(pi_.hProcess, 0);
|
TerminateProcess(pi_.hProcess, 0);
|
||||||
|
WaitForSingleObject(pi_.hProcess, INFINITE);
|
||||||
CloseProcessHandles();
|
CloseProcessHandles();
|
||||||
AddLog("进程已强制终止");
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (process_running_) {
|
||||||
|
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||||
|
if (!stop_command_.empty()) {
|
||||||
|
SendCommand(stop_command_);
|
||||||
|
// Wait for termination or timeout
|
||||||
|
int status = 0;
|
||||||
|
for (int i = 0; i < stop_timeout_ms_/100; ++i) {
|
||||||
|
pid_t result = waitpid(process_pid_, &status, WNOHANG);
|
||||||
|
if (result == process_pid_) {
|
||||||
|
process_running_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process_running_) {
|
||||||
|
kill(process_pid_, SIGKILL);
|
||||||
|
waitpid(process_pid_, nullptr, 0);
|
||||||
|
process_running_ = false;
|
||||||
|
}
|
||||||
|
CloseProcessHandles();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// 关闭管道和线程
|
if (output_thread_.joinable()) {
|
||||||
CleanupResources();
|
output_thread_.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:关闭进程句柄的辅助函数
|
// 关闭进程句柄的辅助函数
|
||||||
void CLIProcess::CloseProcessHandles() {
|
void CLIProcess::CloseProcessHandles() {
|
||||||
if (pi_.hProcess) {
|
if (pi_.hProcess) {
|
||||||
CloseHandle(pi_.hProcess);
|
CloseHandle(pi_.hProcess);
|
||||||
|
@ -440,7 +671,7 @@ void CLIProcess::CloseProcessHandles() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:清理资源的辅助函数
|
// 清理资源的辅助函数
|
||||||
void CLIProcess::CleanupResources() {
|
void CLIProcess::CleanupResources() {
|
||||||
// 关闭输入管道写入端(通知进程停止)
|
// 关闭输入管道写入端(通知进程停止)
|
||||||
if (hWritePipe_stdin_) {
|
if (hWritePipe_stdin_) {
|
||||||
|
@ -494,7 +725,9 @@ const std::vector<std::string>& CLIProcess::GetLogs() const {
|
||||||
return logs_;
|
return logs_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CLIProcess::SendCommand(const std::string& command) {
|
|
||||||
|
bool CLIProcess::SendCommand(const std::string &command) {
|
||||||
|
#ifdef _WIN32
|
||||||
if (!IsRunning() || !hWritePipe_stdin_) {
|
if (!IsRunning() || !hWritePipe_stdin_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -508,9 +741,18 @@ bool CLIProcess::SendCommand(const std::string& command) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
#else
|
||||||
|
if (!process_running_ || pipe_stdin_[1] < 0) return false;
|
||||||
|
|
||||||
|
std::string cmd = command + "\n";
|
||||||
|
ssize_t written = write(pipe_stdin_[1], cmd.c_str(), cmd.size());
|
||||||
|
return written == (ssize_t)cmd.size();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CLIProcess::CopyLogsToClipboard() const {
|
void CLIProcess::CopyLogsToClipboard() const {
|
||||||
|
#ifdef _WIN32
|
||||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||||
if (logs_.empty()) return;
|
if (logs_.empty()) return;
|
||||||
|
|
||||||
|
@ -542,13 +784,46 @@ void CLIProcess::CopyLogsToClipboard() const {
|
||||||
}
|
}
|
||||||
CloseClipboard();
|
CloseClipboard();
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Unix / macOS, use xclip or pbcopy
|
||||||
|
std::string clipboard_text;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||||
|
for (const auto& line : logs_) {
|
||||||
|
clipboard_text += line + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FILE* pipe = popen("pbcopy", "w");
|
||||||
|
if (!pipe) {
|
||||||
|
pipe = popen("xclip -selection clipboard", "w");
|
||||||
|
}
|
||||||
|
if (pipe) {
|
||||||
|
fwrite(clipboard_text.c_str(), 1, clipboard_text.size(), pipe);
|
||||||
|
pclose(pipe);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CLIProcess::IsRunning() const {
|
bool CLIProcess::IsRunning() const {
|
||||||
return pi_.hProcess != nullptr;
|
#ifdef _WIN32
|
||||||
|
if (pi_.hProcess == nullptr) return false;
|
||||||
|
|
||||||
|
DWORD status = WaitForSingleObject(pi_.hProcess, 0);
|
||||||
|
return status == WAIT_TIMEOUT;
|
||||||
|
#else
|
||||||
|
if (!process_running_) return false;
|
||||||
|
|
||||||
|
int status;
|
||||||
|
pid_t result = waitpid(process_pid_, &status, WNOHANG);
|
||||||
|
if (result == 0) return true; // still running
|
||||||
|
process_running_ = false;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CLIProcess::ReadOutput() {
|
void CLIProcess::ReadOutput() {
|
||||||
|
#ifdef _WIN32
|
||||||
constexpr int BUFFER_SIZE = 4096;
|
constexpr int BUFFER_SIZE = 4096;
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[BUFFER_SIZE];
|
||||||
DWORD bytesRead;
|
DWORD bytesRead;
|
||||||
|
@ -562,7 +837,7 @@ void CLIProcess::ReadOutput() {
|
||||||
buffer[bytesRead] = '\0';
|
buffer[bytesRead] = '\0';
|
||||||
std::string output(buffer);
|
std::string output(buffer);
|
||||||
|
|
||||||
// 新增:根据设置的编码转换输出
|
// 根据设置的编码转换输出
|
||||||
OutputEncoding currentEncoding;
|
OutputEncoding currentEncoding;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||||
|
@ -599,4 +874,30 @@ void CLIProcess::ReadOutput() {
|
||||||
partialLine = convertedOutput.substr(start);
|
partialLine = convertedOutput.substr(start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
const int buffer_size = 4096;
|
||||||
|
char buffer[buffer_size];
|
||||||
|
ssize_t bytes_read = 0;
|
||||||
|
while (process_running_) {
|
||||||
|
bytes_read = read(pipe_stdout_[0], buffer, buffer_size - 1);
|
||||||
|
if (bytes_read <= 0) break;
|
||||||
|
buffer[bytes_read] = '\0';
|
||||||
|
|
||||||
|
std::string utf8_str;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||||
|
if (output_encoding_ == OutputEncoding::AUTO_DETECT) {
|
||||||
|
utf8_str = DetectAndConvertToUTF8(std::string(buffer));
|
||||||
|
} else {
|
||||||
|
utf8_str = ConvertToUTF8(std::string(buffer), output_encoding_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddLog(utf8_str);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring CLIProcess::GetPid() const {
|
||||||
|
if (pi_.hProcess == nullptr) return L"";
|
||||||
|
return StringToWide(std::to_string(pi_.dwProcessId));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
||||||
#include "TrayIcon.h"
|
#include "TrayIcon.h"
|
||||||
#include "Units.h"
|
#include "Units.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
TrayIcon::TrayIcon(HWND hwnd, HICON icon)
|
TrayIcon::TrayIcon(HWND hwnd, HICON icon)
|
||||||
: m_hwnd(hwnd), m_icon(icon), m_visible(false), m_menu(nullptr) {
|
: m_hwnd(hwnd), m_icon(icon), m_visible(false), m_menu(nullptr) {
|
||||||
|
|
||||||
ZeroMemory(&m_nid, sizeof(m_nid));
|
ZeroMemory(&m_nid, sizeof(m_nid));
|
||||||
m_nid.cbSize = sizeof(m_nid);
|
m_nid.cbSize = sizeof(m_nid);
|
||||||
m_nid.hWnd = m_hwnd;
|
m_nid.hWnd = m_hwnd;
|
||||||
|
@ -16,6 +16,13 @@ TrayIcon::TrayIcon(HWND hwnd, HICON icon)
|
||||||
|
|
||||||
CreateMenu();
|
CreateMenu();
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
TrayIcon::TrayIcon(void* app_delegate, void* icon)
|
||||||
|
: m_app_delegate(app_delegate), m_icon(icon), m_visible(false) {
|
||||||
|
m_web_url = "http://localhost:8080"; // 默认URL
|
||||||
|
CreateMenu();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TrayIcon::~TrayIcon() {
|
TrayIcon::~TrayIcon() {
|
||||||
Hide();
|
Hide();
|
||||||
|
@ -24,24 +31,42 @@ TrayIcon::~TrayIcon() {
|
||||||
|
|
||||||
void TrayIcon::Show() {
|
void TrayIcon::Show() {
|
||||||
if (!m_visible) {
|
if (!m_visible) {
|
||||||
|
#ifdef _WIN32
|
||||||
Shell_NotifyIcon(NIM_ADD, &m_nid);
|
Shell_NotifyIcon(NIM_ADD, &m_nid);
|
||||||
|
#else
|
||||||
|
ShowMacTrayIcon();
|
||||||
|
#endif
|
||||||
m_visible = true;
|
m_visible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrayIcon::Hide() {
|
void TrayIcon::Hide() {
|
||||||
if (m_visible) {
|
if (m_visible) {
|
||||||
|
#ifdef _WIN32
|
||||||
Shell_NotifyIcon(NIM_DELETE, &m_nid);
|
Shell_NotifyIcon(NIM_DELETE, &m_nid);
|
||||||
|
#else
|
||||||
|
HideMacTrayIcon();
|
||||||
|
#endif
|
||||||
m_visible = false;
|
m_visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrayIcon::UpdateWebUrl(const std::wstring& url) {
|
#ifdef _WIN32
|
||||||
|
void TrayIcon::UpdateWebUrl(const std::wstring &url) {
|
||||||
m_web_url = url;
|
m_web_url = url;
|
||||||
// 重新创建菜单以更新Web URL显示
|
// 重新创建菜单以更新Web URL显示
|
||||||
DestroyMenu();
|
DestroyMenu();
|
||||||
CreateMenu();
|
CreateMenu();
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
void TrayIcon::UpdateWebUrl(const std::string &url)
|
||||||
|
{
|
||||||
|
m_web_url = url;
|
||||||
|
// 重新创建菜单以更新Web URL显示
|
||||||
|
DestroyMenu();
|
||||||
|
CreateMenu();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) {
|
void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) {
|
||||||
m_show_window_callback = callback;
|
m_show_window_callback = callback;
|
||||||
|
@ -51,15 +76,46 @@ void TrayIcon::SetExitCallback(const ExitCallback &callback) {
|
||||||
m_exit_callback = callback;
|
m_exit_callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
void TrayIcon::ShowWindowsNotification(const std::wstring &title, const std::wstring &message) {
|
||||||
|
NOTIFYICONDATA nid = m_nid;
|
||||||
|
nid.uFlags |= NIF_INFO;
|
||||||
|
wcsncpy_s(nid.szInfoTitle, title.c_str(), _TRUNCATE);
|
||||||
|
wcsncpy_s(nid.szInfo, message.c_str(), _TRUNCATE);
|
||||||
|
nid.dwInfoFlags = NIIF_INFO; // 信息图标,可选 NIIF_WARNING, NIIF_ERROR
|
||||||
|
Shell_NotifyIcon(NIM_MODIFY, &nid);
|
||||||
|
}
|
||||||
|
#elif __APPLE__
|
||||||
|
void TrayIcon::ShowMacNotification(const std::string &title, const std::string &message)
|
||||||
|
{
|
||||||
|
// 通过 AppleScript 或 Objective-C 桥接
|
||||||
|
std::string script = "display notification \"" + message + "\" with title \"" + title + "\"";
|
||||||
|
std::string cmd = "osascript -e '" + script + "'";
|
||||||
|
system(cmd.c_str());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void TrayIcon::ShowLinuxNotification(const std::string &title, const std::string &message)
|
||||||
|
{
|
||||||
|
// 使用 notify-send 命令
|
||||||
|
std::string cmd = "notify-send \"" + title + "\" \"" + message + "\"";
|
||||||
|
system(cmd.c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void TrayIcon::CreateMenu() {
|
void TrayIcon::CreateMenu() {
|
||||||
|
#ifdef _WIN32
|
||||||
if (m_menu) {
|
if (m_menu) {
|
||||||
DestroyMenu();
|
DestroyMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_menu = CreatePopupMenu();
|
m_menu = CreatePopupMenu();
|
||||||
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
|
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
|
||||||
AppendMenu(m_menu, MF_SEPARATOR, 0, NULL);
|
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地址菜单项(如果有设置)
|
// 添加Web地址菜单项(如果有设置)
|
||||||
if (!m_web_url.empty() && m_web_url != L"") {
|
if (!m_web_url.empty() && m_web_url != L"") {
|
||||||
std::wstring webText = L"打开Web页面: " + m_web_url;
|
std::wstring webText = L"打开Web页面: " + m_web_url;
|
||||||
|
@ -68,15 +124,23 @@ void TrayIcon::CreateMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendMenu(m_menu, MF_STRING, 1003, L"退出");
|
AppendMenu(m_menu, MF_STRING, 1003, L"退出");
|
||||||
|
#else
|
||||||
|
CreateMacMenu();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrayIcon::DestroyMenu() {
|
void TrayIcon::DestroyMenu() {
|
||||||
|
#ifdef _WIN32
|
||||||
if (m_menu) {
|
if (m_menu) {
|
||||||
::DestroyMenu(m_menu);
|
::DestroyMenu(m_menu);
|
||||||
m_menu = nullptr;
|
m_menu = nullptr;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
DestroyMacMenu();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
void TrayIcon::ShowContextMenu() const {
|
void TrayIcon::ShowContextMenu() const {
|
||||||
if (!m_menu) return;
|
if (!m_menu) return;
|
||||||
|
|
||||||
|
@ -132,3 +196,82 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
|
||||||
}
|
}
|
||||||
return 0;
|
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()
|
||||||
|
{
|
||||||
|
// 通过 Objective-C 接口显示托盘图标
|
||||||
|
ShowMacTrayIconImpl(m_app_delegate, m_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayIcon::HideMacTrayIcon()
|
||||||
|
{
|
||||||
|
// 通过 Objective-C 接口隐藏托盘图标
|
||||||
|
HideMacTrayIconImpl(m_app_delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayIcon::CreateMacMenu()
|
||||||
|
{
|
||||||
|
// 通过 Objective-C 接口创建菜单
|
||||||
|
CreateMacMenuImpl(m_app_delegate, m_web_url.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayIcon::DestroyMacMenu()
|
||||||
|
{
|
||||||
|
// 通过 Objective-C 接口销毁菜单
|
||||||
|
DestroyMacMenuImpl(m_app_delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayIcon::OnMacMenuAction(int action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case 1001: // 显示主窗口
|
||||||
|
if (m_show_window_callback)
|
||||||
|
{
|
||||||
|
m_show_window_callback();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1002: // 打开Web页面
|
||||||
|
if (!m_web_url.empty())
|
||||||
|
{
|
||||||
|
OpenWebPageMac(m_web_url.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1003: // 退出
|
||||||
|
if (m_exit_callback)
|
||||||
|
{
|
||||||
|
m_exit_callback();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C 接口函数,供 Objective-C 调用
|
||||||
|
extern "C" void TrayIconMenuCallback(void *tray_instance, int action)
|
||||||
|
{
|
||||||
|
if (tray_instance)
|
||||||
|
{
|
||||||
|
static_cast<TrayIcon *>(tray_instance)->OnMacMenuAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部声明的 Objective-C 接口函数
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
void ShowMacTrayIconImpl(void *app_delegate, void *icon);
|
||||||
|
void HideMacTrayIconImpl(void *app_delegate);
|
||||||
|
void CreateMacMenuImpl(void *app_delegate, const char *web_url);
|
||||||
|
void DestroyMacMenuImpl(void *app_delegate);
|
||||||
|
void OpenWebPageMac(const char *url);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#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);
|
||||||
|
@ -30,12 +32,22 @@ void SetAutoStart(bool enable) {
|
||||||
WCHAR exePath[MAX_PATH];
|
WCHAR exePath[MAX_PATH];
|
||||||
GetModuleFileName(NULL, exePath, MAX_PATH);
|
GetModuleFileName(NULL, exePath, MAX_PATH);
|
||||||
|
|
||||||
|
// 计算路径的简单哈希值
|
||||||
|
size_t hash = 0;
|
||||||
|
for (size_t i = 0; i < wcslen(exePath); i++) {
|
||||||
|
hash = (hash * 31) + exePath[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建包含哈希的键名
|
||||||
|
WCHAR keyName[32];
|
||||||
|
swprintf(keyName, 32, L"CLIManager_%08X", static_cast<unsigned int>(hash));
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
RegSetValueEx(hKey, L"CLIManager", 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, L"CLIManager");
|
RegDeleteValue(hKey, keyName);
|
||||||
}
|
}
|
||||||
RegCloseKey(hKey);
|
RegCloseKey(hKey);
|
||||||
}
|
}
|
||||||
|
@ -47,9 +59,257 @@ bool IsAutoStartEnabled() {
|
||||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
WCHAR exePath[MAX_PATH];
|
||||||
|
GetModuleFileName(NULL, exePath, MAX_PATH);
|
||||||
|
|
||||||
|
// 计算相同的哈希值
|
||||||
|
size_t hash = 0;
|
||||||
|
for (size_t i = 0; i < wcslen(exePath); i++) {
|
||||||
|
hash = (hash * 31) + exePath[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建相同的键名用于查询
|
||||||
|
WCHAR keyName[32];
|
||||||
|
swprintf(keyName, 32, L"CLIManager_%08X", static_cast<unsigned int>(hash));
|
||||||
|
|
||||||
DWORD type, size = 0;
|
DWORD type, size = 0;
|
||||||
bool exists = (RegQueryValueEx(hKey, L"CLIManager", NULL, &type, NULL, &size) == ERROR_SUCCESS);
|
bool exists = (RegQueryValueEx(hKey, keyName, NULL, &type, NULL, &size) == ERROR_SUCCESS);
|
||||||
|
|
||||||
RegCloseKey(hKey);
|
RegCloseKey(hKey);
|
||||||
return exists;
|
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 ¤tColor, 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); // 默认白色
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue