Compare commits
No commits in common. "286bd74835d3fb85f4483d5a21c90e0cdffed575" and "534e5c085014c3849ffb7b0f2188bbf15309ab01" have entirely different histories.
286bd74835
...
534e5c0850
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.26)
|
||||
cmake_minimum_required(VERSION 3.31)
|
||||
project(CLI_Manager)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
@ -12,7 +12,7 @@ set(IMGUI_BACKENDS "glfw_opengl")
|
|||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
|
||||
#set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
|
||||
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
|
@ -85,6 +85,7 @@ if(IMGUI_BACKENDS STREQUAL "win32_dx11")
|
|||
endif()
|
||||
|
||||
# 设置链接选项
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
|
||||
)
|
||||
#set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
# LINK_FLAGS "-static -Wl,-subsystem,windows"
|
||||
#)
|
||||
#
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "CLIProcess.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <imgui.h>
|
||||
|
||||
class AppState {
|
||||
|
@ -16,21 +15,12 @@ public:
|
|||
void SaveSettings();
|
||||
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 auto_start;
|
||||
CLIProcess cli_process;
|
||||
char command_input[256]{};
|
||||
char send_command[256]{};
|
||||
char working_directory[256]{};
|
||||
bool auto_working_dir;
|
||||
bool auto_scroll_logs;
|
||||
bool enable_colored_logs;
|
||||
int max_log_lines;
|
||||
char web_url[256]{};
|
||||
|
||||
|
@ -43,13 +33,9 @@ public:
|
|||
std::map<std::string, std::string> environment_variables;
|
||||
bool use_custom_environment;
|
||||
|
||||
// 输出编码相关配置
|
||||
// 新增:输出编码相关配置
|
||||
OutputEncoding output_encoding;
|
||||
|
||||
// 新增:启动命令历史记录
|
||||
std::vector<std::string> command_history;
|
||||
int max_command_history;
|
||||
|
||||
bool settings_dirty;
|
||||
|
||||
private:
|
||||
|
@ -57,13 +43,9 @@ private:
|
|||
std::string SerializeEnvironmentVariables() const;
|
||||
void DeserializeEnvironmentVariables(const std::string& serialized);
|
||||
|
||||
// 编码序列化辅助函数
|
||||
// 新增:编码序列化辅助函数
|
||||
std::string SerializeOutputEncoding() const;
|
||||
void DeserializeOutputEncoding(const std::string& serialized);
|
||||
|
||||
// 新增:命令历史记录序列化辅助函数
|
||||
std::string SerializeCommandHistory() const;
|
||||
void DeserializeCommandHistory(const std::string& serialized);
|
||||
};
|
||||
|
||||
#endif // APP_STATE_H
|
|
@ -6,33 +6,16 @@
|
|||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
|
||||
#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 {
|
||||
AUTO_DETECT = 0,
|
||||
AUTO_DETECT =0,
|
||||
UTF8,
|
||||
#ifdef _WIN32
|
||||
GBK,
|
||||
GB2312,
|
||||
BIG5,
|
||||
SHIFT_JIS,
|
||||
#else
|
||||
// Unix/Linux 常见编码
|
||||
ISO_8859_1,
|
||||
GB18030,
|
||||
BIG5,
|
||||
EUC_JP,
|
||||
#endif
|
||||
};
|
||||
|
||||
class CLIProcess {
|
||||
|
@ -43,19 +26,12 @@ public:
|
|||
void SetMaxLogLines(int max_lines);
|
||||
void SetStopCommand(const std::string& command, int timeout_ms = 5000);
|
||||
void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars);
|
||||
void SetOutputEncoding(OutputEncoding encoding);
|
||||
void SetAutoWorkingDir(bool auto_dir);
|
||||
|
||||
// 工作目录设置
|
||||
void SetWorkingDirectory(const std::string& working_dir);
|
||||
std::string GetWorkingDirectory() const;
|
||||
void SetOutputEncoding(OutputEncoding encoding); // 新增:设置输出编码
|
||||
|
||||
void Start(const std::string& command);
|
||||
void Stop();
|
||||
void Restart(const std::string& command);
|
||||
|
||||
std::wstring GetPid() const;
|
||||
|
||||
void ClearLogs();
|
||||
void AddLog(const std::string& log);
|
||||
const std::vector<std::string>& GetLogs() const;
|
||||
|
@ -71,26 +47,19 @@ public:
|
|||
void RemoveEnvironmentVariable(const std::string& key);
|
||||
void ClearEnvironmentVariables();
|
||||
|
||||
// 编码相关接口
|
||||
// 新增:编码相关接口
|
||||
OutputEncoding GetOutputEncoding() const;
|
||||
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();
|
||||
void CleanupResources();
|
||||
|
||||
// 编码转换相关方法
|
||||
static std::string ConvertToUTF8(std::string& input, OutputEncoding encoding);
|
||||
std::string DetectAndConvertToUTF8(std::string& input);
|
||||
|
||||
#ifdef _WIN32
|
||||
// 新增:编码转换相关方法
|
||||
std::string ConvertToUTF8(const std::string& input, OutputEncoding encoding);
|
||||
std::string DetectAndConvertToUTF8(const std::string& input);
|
||||
static UINT GetCodePageFromEncoding(OutputEncoding encoding);
|
||||
static bool IsValidUTF8(const std::string& str);
|
||||
|
||||
|
@ -99,17 +68,6 @@ private:
|
|||
HANDLE hWritePipe_{};
|
||||
HANDLE hReadPipe_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_;
|
||||
std::vector<std::string> logs_;
|
||||
|
@ -118,7 +76,7 @@ private:
|
|||
std::thread output_thread_;
|
||||
|
||||
// 停止命令相关
|
||||
mutable std::mutex stop_mutex_;
|
||||
std::mutex stop_mutex_;
|
||||
std::string stop_command_;
|
||||
int stop_timeout_ms_;
|
||||
|
||||
|
@ -126,14 +84,9 @@ private:
|
|||
mutable std::mutex env_mutex_;
|
||||
std::map<std::string, std::string> environment_variables_;
|
||||
|
||||
// 编码相关
|
||||
// 新增:编码相关
|
||||
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
|
||||
#endif // CLIPROCESS_H
|
|
@ -1,170 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
// 系统头文件
|
||||
#include <memory>
|
||||
|
||||
// 第三方库头文件
|
||||
#include "imgui.h"
|
||||
#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"
|
||||
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// 项目头文件
|
||||
#include "AppState.h"
|
||||
#include "TrayIcon.h"
|
||||
#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:
|
||||
// 枚举类型定义
|
||||
enum class LayoutPreset {
|
||||
Classic, // 经典布局
|
||||
Development, // 开发布局
|
||||
Monitoring // 监控布局
|
||||
};
|
||||
// UI渲染
|
||||
void RenderUI();
|
||||
void RenderMenuBar();
|
||||
void RenderMainContent();
|
||||
void RenderSettingsMenu();
|
||||
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
|
||||
bool InitializeWin32(); // 初始化Win32
|
||||
bool InitializeDirectX11(); // 初始化DirectX11
|
||||
void CleanupWin32(); // 清理Win32
|
||||
void CleanupDirectX11(); // 清理DirectX11
|
||||
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程
|
||||
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;
|
||||
#else
|
||||
bool InitializeGLFW();
|
||||
void CleanupGLFW();
|
||||
static void GlfwErrorCallback(int error, const char* description);
|
||||
|
||||
bool InitializeGLFW(); // 初始化GLFW
|
||||
void CleanupGLFW(); // 清理GLFW
|
||||
static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调
|
||||
GLFWwindow* m_window = nullptr;
|
||||
const char* m_glsl_version = nullptr;
|
||||
#endif
|
||||
|
||||
// 托盘相关方法
|
||||
bool InitializeTray(); // 初始化托盘
|
||||
void CleanupTray(); // 清理托盘
|
||||
#ifdef _WIN32
|
||||
// 托盘相关
|
||||
bool InitializeTray();
|
||||
void CleanupTray();
|
||||
static HWND CreateHiddenWindow();
|
||||
|
||||
static HWND CreateHiddenWindow(); // 创建隐藏窗口
|
||||
#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
|
||||
std::unique_ptr<TrayIcon> m_tray;
|
||||
HWND m_tray_hwnd = nullptr;
|
||||
|
||||
// 控制标志
|
||||
bool m_should_exit = false; // 是否应该退出
|
||||
bool m_initialized = false; // 是否已初始化
|
||||
bool m_fullscreen = false;
|
||||
bool m_padding = false;
|
||||
// DPI缩放相关
|
||||
float m_dpi_scale = 1.0f; // 当前DPI缩放
|
||||
float m_last_dpi_scale = 1.0f; // 上次DPI缩放
|
||||
bool m_should_exit = false;
|
||||
bool m_initialized = false;
|
||||
|
||||
// 布局相关成员变量
|
||||
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; // 加载成功消息计时器
|
||||
// DPI缩放因子
|
||||
float m_dpi_scale = 1.0f;
|
||||
|
||||
// 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; // 是否显示命令历史
|
||||
};
|
||||
// 环境变量UI状态
|
||||
char env_key_input_[256] = {};
|
||||
char env_value_input_[512] = {};
|
||||
bool show_env_settings_ = false;
|
||||
|
||||
// 新增:编码设置UI状态
|
||||
bool show_encoding_settings_ = false;
|
||||
};
|
|
@ -1,10 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
|
@ -14,73 +10,33 @@ public:
|
|||
using ShowWindowCallback = std::function<void()>;
|
||||
using ExitCallback = std::function<void()>;
|
||||
|
||||
#ifdef _WIN32
|
||||
TrayIcon(HWND hwnd, HICON icon);
|
||||
void UpdateWebUrl(const std::wstring& url);
|
||||
|
||||
void UpdateStatus(const std::wstring &status, const std::wstring &pid);
|
||||
// 静态窗口过程
|
||||
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
#else
|
||||
TrayIcon(void* app_delegate, void* icon);
|
||||
void UpdateWebUrl(const std::string& url);
|
||||
void OnMacMenuAction(int action);
|
||||
#endif
|
||||
|
||||
~TrayIcon();
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
void UpdateWebUrl(const std::wstring& url);
|
||||
|
||||
// 设置回调函数
|
||||
void SetShowWindowCallback(const ShowWindowCallback &callback);
|
||||
void SetExitCallback(const ExitCallback &callback);
|
||||
#ifdef _WIN32
|
||||
void ShowWindowsNotification(const std::wstring& title, const std::wstring& message);
|
||||
#elif __APPLE__
|
||||
void ShowMacNotification(const std::string& title, const std::string& message);
|
||||
#else
|
||||
void ShowLinuxNotification(const std::string& title, const std::string& message);
|
||||
#endif
|
||||
|
||||
// 静态窗口过程
|
||||
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
private:
|
||||
void CreateMenu();
|
||||
void DestroyMenu();
|
||||
|
||||
#ifdef _WIN32
|
||||
void ShowContextMenu() const;
|
||||
|
||||
HWND m_hwnd;
|
||||
HICON m_icon;
|
||||
NOTIFYICONDATA m_nid{};
|
||||
std::wstring m_web_url;
|
||||
std::wstring m_status;
|
||||
std::wstring m_pid;
|
||||
HMENU m_menu;
|
||||
#else
|
||||
void ShowMacTrayIcon();
|
||||
void HideMacTrayIcon();
|
||||
void CreateMacMenu();
|
||||
void DestroyMacMenu();
|
||||
|
||||
void* m_app_delegate;
|
||||
void* m_icon;
|
||||
std::string m_web_url;
|
||||
#endif
|
||||
|
||||
bool m_visible;
|
||||
HMENU m_menu;
|
||||
|
||||
// 回调函数
|
||||
ShowWindowCallback m_show_window_callback;
|
||||
ExitCallback m_exit_callback;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// C 接口函数声明,供 Objective-C 调用
|
||||
void TrayIconMenuCallback(void* tray_instance, int action);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
};
|
|
@ -1,26 +1,11 @@
|
|||
#ifndef UNITS_H
|
||||
#define UNITS_H
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
#include <vector>
|
||||
|
||||
std::wstring StringToWide(const std::string& str);
|
||||
std::string WideToString(const std::wstring& wstr);
|
||||
void SetAutoStart(bool enable);
|
||||
bool IsAutoStartEnabled();
|
||||
|
||||
// 结构体定义
|
||||
struct ColoredTextSegment {
|
||||
std::string text; // 文本内容
|
||||
ImVec4 color; // 文本颜色
|
||||
};
|
||||
|
||||
// 日志颜色处理方法
|
||||
ImVec4 GetLogLevelColor(const std::string &log); // 获取日志级别颜色
|
||||
void RenderColoredLogLine(const std::string &log); // 渲染彩色日志行
|
||||
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text); // 解析ANSI颜色代码
|
||||
std::pair<ImVec4, bool>
|
||||
ParseAnsiColorCode(const std::string &code, const ImVec4 ¤tColor, bool currentBold); // 解析单个ANSI颜色代码
|
||||
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
|
||||
|
||||
#endif //UNITS_H
|
||||
|
|
|
@ -7,14 +7,11 @@ AppState::AppState() :
|
|||
show_main_window(true),
|
||||
auto_start(false),
|
||||
auto_scroll_logs(true),
|
||||
auto_working_dir(true),
|
||||
enable_colored_logs(true),
|
||||
max_log_lines(1000),
|
||||
stop_timeout_ms(5000),
|
||||
use_stop_command(false),
|
||||
use_custom_environment(false),
|
||||
output_encoding(OutputEncoding::AUTO_DETECT),
|
||||
max_command_history(20), // 新增:最大历史记录数量
|
||||
output_encoding(OutputEncoding::AUTO_DETECT), // 新增:默认自动检测编码
|
||||
settings_dirty(false) {
|
||||
strcpy_s(command_input, "cmd.exe");
|
||||
strcpy_s(web_url, "http://localhost:8080");
|
||||
|
@ -22,90 +19,6 @@ AppState::AppState() :
|
|||
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::ostringstream oss;
|
||||
bool first = true;
|
||||
|
@ -186,9 +99,6 @@ void AppState::LoadSettings() {
|
|||
if (key == "CommandInput") {
|
||||
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") {
|
||||
max_log_lines = std::stoi(value);
|
||||
max_log_lines = std::max(100, std::min(max_log_lines, 10000));
|
||||
|
@ -196,15 +106,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");
|
||||
}
|
||||
else if (key == "AutoWorkDirectory") {
|
||||
auto_working_dir = (value == "1");
|
||||
}
|
||||
else if (key == "WebUrl") {
|
||||
strncpy_s(web_url, value.c_str(), sizeof(web_url) - 1);
|
||||
}
|
||||
|
@ -224,17 +128,10 @@ void AppState::LoadSettings() {
|
|||
else if (key == "EnvironmentVariables") {
|
||||
DeserializeEnvironmentVariables(value);
|
||||
}
|
||||
// 新增:输出编码配置的加载
|
||||
else if (key == "OutputEncoding") {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,12 +144,9 @@ void AppState::SaveSettings() {
|
|||
|
||||
file << "[Settings]\n";
|
||||
file << "CommandInput=" << command_input << "\n";
|
||||
file << "WorkingDirectory=" << working_directory << "\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 << "AutoWorkDirectory=" << (auto_working_dir ? "1" : "0") << "\n";
|
||||
file << "WebUrl=" << web_url << "\n";
|
||||
|
||||
// 停止命令相关配置的保存
|
||||
|
@ -264,13 +158,9 @@ void AppState::SaveSettings() {
|
|||
file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n";
|
||||
file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n";
|
||||
|
||||
// 输出编码配置的保存
|
||||
// 新增:输出编码配置的保存
|
||||
file << "OutputEncoding=" << SerializeOutputEncoding() << "\n";
|
||||
|
||||
// 新增:命令历史记录配置的保存
|
||||
file << "CommandHistory=" << SerializeCommandHistory() << "\n";
|
||||
file << "MaxCommandHistory=" << max_command_history << "\n";
|
||||
|
||||
file.close();
|
||||
|
||||
settings_dirty = false;
|
||||
|
@ -293,11 +183,6 @@ void AppState::ApplySettings() {
|
|||
cli_process.SetEnvironmentVariables({});
|
||||
}
|
||||
|
||||
if (strlen(working_directory)>0) {
|
||||
cli_process.SetWorkingDirectory(working_directory);
|
||||
}
|
||||
|
||||
cli_process.SetAutoWorkingDir(auto_working_dir);
|
||||
// 应用输出编码设置
|
||||
// 新增:应用输出编码设置
|
||||
cli_process.SetOutputEncoding(output_encoding);
|
||||
}
|
||||
}
|
|
@ -1,197 +1,60 @@
|
|||
#include "CLIProcess.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Units.h"
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#include <locale.h>
|
||||
#include <langinfo.h>
|
||||
#include <spawn.h>
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
CLIProcess::CLIProcess() {
|
||||
#ifdef _WIN32
|
||||
ZeroMemory(&pi_, sizeof(pi_));
|
||||
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;
|
||||
hWritePipe_stdin_ = nullptr;
|
||||
stop_timeout_ms_ = 5000;
|
||||
output_encoding_ = OutputEncoding::AUTO_DETECT;
|
||||
use_auto_working_dir_ = true; // 自动工作目录
|
||||
output_encoding_ = OutputEncoding::AUTO_DETECT; // 新增:默认自动检测编码
|
||||
}
|
||||
|
||||
CLIProcess::~CLIProcess() {
|
||||
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) {
|
||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||
output_encoding_ = encoding;
|
||||
AddLog("输出编码已设置为: " + GetEncodingName(encoding));
|
||||
}
|
||||
|
||||
// 获取输出编码
|
||||
// 新增:获取输出编码
|
||||
OutputEncoding CLIProcess::GetOutputEncoding() const {
|
||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||
return output_encoding_;
|
||||
}
|
||||
|
||||
// 获取编码名称
|
||||
// 新增:获取编码名称
|
||||
std::string CLIProcess::GetEncodingName(const OutputEncoding encoding) {
|
||||
switch (encoding) {
|
||||
case OutputEncoding::AUTO_DETECT: return "自动检测";
|
||||
case OutputEncoding::UTF8: return "UTF-8";
|
||||
#ifdef _WIN32
|
||||
case OutputEncoding::GBK: return "GBK";
|
||||
case OutputEncoding::GB2312: return "GB2312";
|
||||
case OutputEncoding::BIG5: return "BIG5";
|
||||
case OutputEncoding::SHIFT_JIS: return "Shift_JIS";
|
||||
#else
|
||||
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 "未知编码";
|
||||
case OutputEncoding::BIG5: return "Big5";
|
||||
case OutputEncoding::SHIFT_JIS: return "Shift-JIS";
|
||||
case OutputEncoding::AUTO_DETECT: return "自动检测";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
// 获取支持的编码列表
|
||||
// 新增:获取支持的编码列表
|
||||
std::vector<std::pair<OutputEncoding, std::string>> CLIProcess::GetSupportedEncodings() {
|
||||
return {
|
||||
{OutputEncoding::AUTO_DETECT, "自动检测"},
|
||||
{OutputEncoding::UTF8, "UTF-8"},
|
||||
#ifdef _WIN32
|
||||
{OutputEncoding::GBK, "GBK (简体中文)"},
|
||||
{OutputEncoding::GB2312, "GB2312 (简体中文)"},
|
||||
{OutputEncoding::BIG5, "Big5 (繁体中文)"},
|
||||
{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
|
||||
{OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"}
|
||||
};
|
||||
}
|
||||
|
||||
// 根据编码获取代码页
|
||||
// 新增:根据编码获取代码页
|
||||
UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
|
||||
switch (encoding) {
|
||||
case OutputEncoding::GBK: return 936;
|
||||
|
@ -202,7 +65,7 @@ UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
|
|||
}
|
||||
}
|
||||
|
||||
// 检查是否为有效的UTF-8
|
||||
// 新增:检查是否为有效的UTF-8
|
||||
bool CLIProcess::IsValidUTF8(const std::string& str) {
|
||||
const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
|
||||
size_t len = str.length();
|
||||
|
@ -230,8 +93,8 @@ bool CLIProcess::IsValidUTF8(const std::string& str) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// 转换到UTF-8
|
||||
std::string CLIProcess::ConvertToUTF8(std::string& input, const OutputEncoding encoding) {
|
||||
// 新增:转换到UTF-8
|
||||
std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding encoding) {
|
||||
if (input.empty()) return input;
|
||||
|
||||
// 如果已经是UTF-8编码,直接返回
|
||||
|
@ -267,8 +130,8 @@ std::string CLIProcess::ConvertToUTF8(std::string& input, const OutputEncoding e
|
|||
return std::string(utf8Str.data());
|
||||
}
|
||||
|
||||
// 自动检测并转换到UTF-8
|
||||
std::string CLIProcess::DetectAndConvertToUTF8(std::string& input) {
|
||||
// 新增:自动检测并转换到UTF-8
|
||||
std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) {
|
||||
if (input.empty()) return input;
|
||||
|
||||
// 首先检查是否已经是有效的UTF-8
|
||||
|
@ -277,7 +140,7 @@ std::string CLIProcess::DetectAndConvertToUTF8(std::string& input) {
|
|||
}
|
||||
|
||||
// 尝试不同的编码进行转换
|
||||
const std::vector encodingsToTry = {
|
||||
std::vector<OutputEncoding> encodingsToTry = {
|
||||
OutputEncoding::GBK,
|
||||
OutputEncoding::GB2312,
|
||||
OutputEncoding::BIG5,
|
||||
|
@ -336,7 +199,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("已清空所有自定义环境变量");
|
||||
|
@ -382,27 +245,9 @@ void CLIProcess::ClearEnvironmentVariables() {
|
|||
}
|
||||
|
||||
void CLIProcess::Start(const std::string& command) {
|
||||
if (IsRunning()) return;
|
||||
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;
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = TRUE;
|
||||
|
@ -444,7 +289,7 @@ void CLIProcess::Start(const std::string& command) {
|
|||
if (!environment_variables_.empty()) {
|
||||
envVarsSet = true;
|
||||
|
||||
for (const auto &pair: environment_variables_) {
|
||||
for (const auto& pair : environment_variables_) {
|
||||
if (!pair.first.empty()) {
|
||||
// 保存原始值(如果存在)
|
||||
DWORD bufferSize = GetEnvironmentVariableA(pair.first.c_str(), nullptr, 0);
|
||||
|
@ -472,25 +317,25 @@ void CLIProcess::Start(const std::string& command) {
|
|||
|
||||
// AddLog("环境变量设置完成,数量: " + std::to_string(environment_variables_.size()));
|
||||
} else {
|
||||
// AddLog("未设置自定义环境变量,使用默认环境 PWD:" + WideToString(working_dir));
|
||||
AddLog("未设置自定义环境变量,使用默认环境");
|
||||
}
|
||||
|
||||
BOOL result = CreateProcess(
|
||||
nullptr, // lpApplicationName
|
||||
cmdBuffer.data(), // lpCommandLine
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
TRUE, // bInheritHandles
|
||||
CREATE_NO_WINDOW, // dwCreationFlags
|
||||
nullptr, // lpEnvironment (使用nullptr让子进程继承当前环境)
|
||||
working_dir.empty() ? nullptr : working_dir.data(), // lpCurrentDirectory
|
||||
&si, // lpStartupInfo
|
||||
&pi_ // lpProcessInformation
|
||||
nullptr, // lpApplicationName
|
||||
cmdBuffer.data(), // lpCommandLine
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
TRUE, // bInheritHandles
|
||||
CREATE_NO_WINDOW, // dwCreationFlags
|
||||
nullptr, // lpEnvironment (使用nullptr让子进程继承当前环境)
|
||||
nullptr, // lpCurrentDirectory
|
||||
&si, // lpStartupInfo
|
||||
&pi_ // lpProcessInformation
|
||||
);
|
||||
|
||||
// 恢复原始环境变量
|
||||
if (envVarsSet) {
|
||||
for (const auto &pair: originalEnvVars) {
|
||||
for (const auto& pair : originalEnvVars) {
|
||||
if (pair.second.empty()) {
|
||||
// 原来不存在,删除变量
|
||||
SetEnvironmentVariableA(pair.first.c_str(), nullptr);
|
||||
|
@ -502,7 +347,7 @@ void CLIProcess::Start(const std::string& command) {
|
|||
}
|
||||
|
||||
if (result) {
|
||||
AddLog("进程已启动: " + command + " PID: " + std::to_string(pi_.dwProcessId));
|
||||
AddLog("进程已启动: " + command);
|
||||
|
||||
CloseHandle(hWritePipe_);
|
||||
CloseHandle(hReadPipe_stdin_);
|
||||
|
@ -512,15 +357,16 @@ void CLIProcess::Start(const std::string& command) {
|
|||
output_thread_ = std::thread([this]() {
|
||||
ReadOutput();
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
DWORD err = GetLastError();
|
||||
|
||||
// 获取详细的错误信息
|
||||
LPWSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPWSTR) &messageBuffer, 0, nullptr);
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPWSTR)&messageBuffer, 0, nullptr);
|
||||
|
||||
std::string errorMsg = "CreateProcess 失败 (错误代码: " + std::to_string(err) + ")";
|
||||
if (messageBuffer) {
|
||||
|
@ -538,128 +384,51 @@ void CLIProcess::Start(const std::string& command) {
|
|||
CloseHandle(hWritePipe_stdin_);
|
||||
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() {
|
||||
#ifdef _WIN32
|
||||
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||
if (pi_.hProcess != nullptr) {
|
||||
if (!stop_command_.empty()) {
|
||||
SendCommand(stop_command_);
|
||||
// Wait for process to exit within timeout
|
||||
WaitForSingleObject(pi_.hProcess, stop_timeout_ms_);
|
||||
}
|
||||
TerminateProcess(pi_.hProcess, 0);
|
||||
WaitForSingleObject(pi_.hProcess, INFINITE);
|
||||
CloseProcessHandles();
|
||||
}
|
||||
#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
|
||||
bool useStopCommand = false;
|
||||
std::string stopCmd;
|
||||
int timeout = stop_timeout_ms_;
|
||||
|
||||
if (output_thread_.joinable()) {
|
||||
output_thread_.join();
|
||||
// 检查是否设置了停止命令
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||
if (!stop_command_.empty() && IsRunning()) {
|
||||
useStopCommand = true;
|
||||
stopCmd = stop_command_;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
CloseProcessHandles();
|
||||
AddLog("进程已强制终止");
|
||||
}
|
||||
|
||||
// 关闭管道和线程
|
||||
CleanupResources();
|
||||
}
|
||||
|
||||
// 关闭进程句柄的辅助函数
|
||||
// 新增:关闭进程句柄的辅助函数
|
||||
void CLIProcess::CloseProcessHandles() {
|
||||
if (pi_.hProcess) {
|
||||
CloseHandle(pi_.hProcess);
|
||||
|
@ -671,7 +440,7 @@ void CLIProcess::CloseProcessHandles() {
|
|||
}
|
||||
}
|
||||
|
||||
// 清理资源的辅助函数
|
||||
// 新增:清理资源的辅助函数
|
||||
void CLIProcess::CleanupResources() {
|
||||
// 关闭输入管道写入端(通知进程停止)
|
||||
if (hWritePipe_stdin_) {
|
||||
|
@ -725,9 +494,7 @@ const std::vector<std::string>& CLIProcess::GetLogs() const {
|
|||
return logs_;
|
||||
}
|
||||
|
||||
|
||||
bool CLIProcess::SendCommand(const std::string &command) {
|
||||
#ifdef _WIN32
|
||||
bool CLIProcess::SendCommand(const std::string& command) {
|
||||
if (!IsRunning() || !hWritePipe_stdin_) {
|
||||
return false;
|
||||
}
|
||||
|
@ -739,20 +506,11 @@ bool CLIProcess::SendCommand(const std::string &command) {
|
|||
static_cast<DWORD>(fullCommand.length()), &bytesWritten, nullptr)) {
|
||||
AddLog("> " + command);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
#ifdef _WIN32
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
if (logs_.empty()) return;
|
||||
|
||||
|
@ -784,46 +542,13 @@ void CLIProcess::CopyLogsToClipboard() const {
|
|||
}
|
||||
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 {
|
||||
#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
|
||||
bool CLIProcess::IsRunning() const {
|
||||
return pi_.hProcess != nullptr;
|
||||
}
|
||||
|
||||
|
||||
void CLIProcess::ReadOutput() {
|
||||
#ifdef _WIN32
|
||||
constexpr int BUFFER_SIZE = 4096;
|
||||
char buffer[BUFFER_SIZE];
|
||||
DWORD bytesRead;
|
||||
|
@ -837,7 +562,7 @@ void CLIProcess::ReadOutput() {
|
|||
buffer[bytesRead] = '\0';
|
||||
std::string output(buffer);
|
||||
|
||||
// 根据设置的编码转换输出
|
||||
// 新增:根据设置的编码转换输出
|
||||
OutputEncoding currentEncoding;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||
|
@ -874,30 +599,4 @@ void CLIProcess::ReadOutput() {
|
|||
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 "Units.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
TrayIcon::TrayIcon(HWND hwnd, HICON icon)
|
||||
: m_hwnd(hwnd), m_icon(icon), m_visible(false), m_menu(nullptr) {
|
||||
|
||||
ZeroMemory(&m_nid, sizeof(m_nid));
|
||||
m_nid.cbSize = sizeof(m_nid);
|
||||
m_nid.hWnd = m_hwnd;
|
||||
|
@ -16,13 +16,6 @@ TrayIcon::TrayIcon(HWND hwnd, HICON icon)
|
|||
|
||||
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() {
|
||||
Hide();
|
||||
|
@ -31,42 +24,24 @@ TrayIcon::~TrayIcon() {
|
|||
|
||||
void TrayIcon::Show() {
|
||||
if (!m_visible) {
|
||||
#ifdef _WIN32
|
||||
Shell_NotifyIcon(NIM_ADD, &m_nid);
|
||||
#else
|
||||
ShowMacTrayIcon();
|
||||
#endif
|
||||
m_visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TrayIcon::Hide() {
|
||||
if (m_visible) {
|
||||
#ifdef _WIN32
|
||||
Shell_NotifyIcon(NIM_DELETE, &m_nid);
|
||||
#else
|
||||
HideMacTrayIcon();
|
||||
#endif
|
||||
m_visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void TrayIcon::UpdateWebUrl(const std::wstring &url) {
|
||||
void TrayIcon::UpdateWebUrl(const std::wstring& url) {
|
||||
m_web_url = url;
|
||||
// 重新创建菜单以更新Web URL显示
|
||||
DestroyMenu();
|
||||
CreateMenu();
|
||||
}
|
||||
#else
|
||||
void TrayIcon::UpdateWebUrl(const std::string &url)
|
||||
{
|
||||
m_web_url = url;
|
||||
// 重新创建菜单以更新Web URL显示
|
||||
DestroyMenu();
|
||||
CreateMenu();
|
||||
}
|
||||
#endif
|
||||
|
||||
void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) {
|
||||
m_show_window_callback = callback;
|
||||
|
@ -76,46 +51,15 @@ void TrayIcon::SetExitCallback(const ExitCallback &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() {
|
||||
#ifdef _WIN32
|
||||
if (m_menu) {
|
||||
DestroyMenu();
|
||||
}
|
||||
|
||||
m_menu = CreatePopupMenu();
|
||||
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
|
||||
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
|
||||
std::wstring statusText = L"状态:" + m_status;
|
||||
AppendMenu(m_menu, MF_INSERT, 0, statusText.c_str());
|
||||
std::wstring pidText = L"PID:" + m_pid;
|
||||
AppendMenu(m_menu, MF_INSERT, 0, pidText.c_str());
|
||||
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenu(m_menu, MF_SEPARATOR, 0, NULL);
|
||||
|
||||
// 添加Web地址菜单项(如果有设置)
|
||||
if (!m_web_url.empty() && m_web_url != L"") {
|
||||
std::wstring webText = L"打开Web页面: " + m_web_url;
|
||||
|
@ -124,23 +68,15 @@ void TrayIcon::CreateMenu() {
|
|||
}
|
||||
|
||||
AppendMenu(m_menu, MF_STRING, 1003, L"退出");
|
||||
#else
|
||||
CreateMacMenu();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrayIcon::DestroyMenu() {
|
||||
#ifdef _WIN32
|
||||
if (m_menu) {
|
||||
::DestroyMenu(m_menu);
|
||||
m_menu = nullptr;
|
||||
}
|
||||
#else
|
||||
DestroyMacMenu();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void TrayIcon::ShowContextMenu() const {
|
||||
if (!m_menu) return;
|
||||
|
||||
|
@ -191,87 +127,8 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
|
|||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TrayIcon::UpdateStatus(const std::wstring &status, const std::wstring &pid) {
|
||||
m_status = status;
|
||||
m_pid = pid;
|
||||
// 重新创建菜单以更新Status显示
|
||||
DestroyMenu();
|
||||
CreateMenu();
|
||||
}
|
||||
|
||||
#else
|
||||
// macOS 特定实现
|
||||
void TrayIcon::ShowMacTrayIcon()
|
||||
{
|
||||
// 通过 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,8 +4,6 @@
|
|||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
#include <sstream>
|
||||
|
||||
std::wstring StringToWide(const std::string& str) {
|
||||
if (str.empty()) return L"";
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
||||
|
@ -32,22 +30,12 @@ void SetAutoStart(bool enable) {
|
|||
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));
|
||||
|
||||
if (enable) {
|
||||
RegSetValueEx(hKey, keyName, 0, REG_SZ,
|
||||
RegSetValueEx(hKey, L"CLIManager", 0, REG_SZ,
|
||||
(BYTE*)exePath, (wcslen(exePath) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
else {
|
||||
RegDeleteValue(hKey, keyName);
|
||||
RegDeleteValue(hKey, L"CLIManager");
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
@ -59,257 +47,9 @@ bool IsAutoStartEnabled() {
|
|||
if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
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;
|
||||
bool exists = (RegQueryValueEx(hKey, keyName, NULL, &type, NULL, &size) == ERROR_SUCCESS);
|
||||
bool exists = (RegQueryValueEx(hKey, L"CLIManager", NULL, &type, NULL, &size) == ERROR_SUCCESS);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return exists;
|
||||
}
|
||||
|
||||
ImVec4 GetLogLevelColor(const std::string &log) {
|
||||
// 简单的日志级别颜色区分
|
||||
if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos ||
|
||||
log.find("[ERROR]") != std::string::npos || log.find("error") != std::string::npos) {
|
||||
return ImVec4(1.0f, 0.4f, 0.4f, 1.0f); // 红色
|
||||
} else if (log.find("警告") != std::string::npos || log.find("[W]") != std::string::npos ||
|
||||
log.find("[WARN]") != std::string::npos || log.find("warning") != std::string::npos) {
|
||||
return ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // 黄色
|
||||
} else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos ||
|
||||
log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) {
|
||||
return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色
|
||||
}
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
|
||||
}
|
||||
|
||||
|
||||
void RenderColoredLogLine(const std::string &log) {
|
||||
auto segments = ParseAnsiColorCodes(log);
|
||||
|
||||
if (segments.empty()) {
|
||||
// 如果没有ANSI代码,使用简单的日志级别颜色
|
||||
ImVec4 textColor = GetLogLevelColor(log);
|
||||
ImGui::TextColored(textColor, "%s", log.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// 渲染带颜色的文本段
|
||||
bool first = true;
|
||||
for (const auto &segment: segments) {
|
||||
if (!first) {
|
||||
ImGui::SameLine(0, 0); // 在同一行继续显示
|
||||
}
|
||||
first = false;
|
||||
|
||||
if (!segment.text.empty()) {
|
||||
ImGui::TextColored(segment.color, "%s", segment.text.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text) {
|
||||
std::vector<ColoredTextSegment> segments;
|
||||
|
||||
if (text.empty()) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
ImVec4 currentColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
|
||||
bool isBold = false;
|
||||
|
||||
while (pos < text.length()) {
|
||||
size_t escapePos = text.find('\033', pos);
|
||||
|
||||
if (escapePos == std::string::npos) {
|
||||
// 没有更多转义序列,添加剩余文本
|
||||
if (pos < text.length()) {
|
||||
segments.push_back({text.substr(pos), currentColor});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 添加转义序列之前的文本
|
||||
if (escapePos > pos) {
|
||||
segments.push_back({text.substr(pos, escapePos - pos), currentColor});
|
||||
}
|
||||
|
||||
// 解析ANSI转义序列
|
||||
size_t codeStart = escapePos + 1;
|
||||
if (codeStart < text.length() && text[codeStart] == '[') {
|
||||
size_t codeEnd = text.find('m', codeStart);
|
||||
if (codeEnd != std::string::npos) {
|
||||
std::string colorCode = text.substr(codeStart + 1, codeEnd - codeStart - 1);
|
||||
auto newColor = ParseAnsiColorCode(colorCode, currentColor, isBold);
|
||||
currentColor = newColor.first;
|
||||
isBold = newColor.second;
|
||||
pos = codeEnd + 1;
|
||||
} else {
|
||||
// 无效的转义序列,跳过
|
||||
pos = codeStart;
|
||||
}
|
||||
} else {
|
||||
// 无效的转义序列,跳过
|
||||
pos = codeStart;
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
std::pair<ImVec4, bool>
|
||||
ParseAnsiColorCode(const std::string &code, const ImVec4 ¤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