main
commit
2840c9a378
|
@ -0,0 +1,91 @@
|
|||
cmake_minimum_required(VERSION 3.31)
|
||||
project(CLI_Manager)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(IMGUI_DIR $ENV{LIB_HOME}/imgui-docking)
|
||||
set(GLFW_DIR $ENV{LIB_HOME}/glfw)
|
||||
|
||||
set(IMGUI_BACKENDS "glfw_opengl")
|
||||
#set(IMGUI_BACKENDS "win32_dx11")
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
|
||||
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
|
||||
# add header path
|
||||
include_directories(
|
||||
${IMGUI_DIR}
|
||||
${IMGUI_DIR}/backends
|
||||
${IMGUI_DIR}/misc/cpp
|
||||
app/inc
|
||||
)
|
||||
|
||||
# set common source
|
||||
file(GLOB SRC
|
||||
${IMGUI_DIR}/*.cpp
|
||||
${IMGUI_DIR}/misc/cpp/*.cpp
|
||||
app/src/*.*
|
||||
main.cpp
|
||||
)
|
||||
|
||||
|
||||
if(IMGUI_BACKENDS STREQUAL "glfw_opengl")
|
||||
add_definitions(-DIMGUI_IMPL_OPENGL_LOADER_GL3W)
|
||||
|
||||
include_directories(
|
||||
${GLFW_DIR}/include
|
||||
${IMGUI_DIR}/examples/libs/gl3w # for GL/gl3w.h
|
||||
)
|
||||
|
||||
file(GLOB PLATFORM_SRC
|
||||
${IMGUI_DIR}/examples/libs/gl3w/GL/gl3w.*
|
||||
${IMGUI_DIR}/backends/imgui_impl_glfw.*
|
||||
${IMGUI_DIR}/backends/imgui_impl_opengl3.*
|
||||
)
|
||||
|
||||
link_directories(
|
||||
${GLFW_DIR}/build/src
|
||||
)
|
||||
endif()
|
||||
|
||||
# 链接库和选项
|
||||
if(IMGUI_BACKENDS STREQUAL "win32_dx11")
|
||||
add_definitions(-DUSE_WIN32_BACKEND)
|
||||
file(GLOB PLATFORM_SRC
|
||||
${IMGUI_DIR}/backends/imgui_impl_win32.*
|
||||
${IMGUI_DIR}/backends/imgui_impl_dx11.*
|
||||
)
|
||||
endif()
|
||||
|
||||
# generate binary
|
||||
add_executable(${PROJECT_NAME} WIN32 ${SRC} ${PLATFORM_SRC})
|
||||
|
||||
if(IMGUI_BACKENDS STREQUAL "glfw_opengl")
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
glfw3.a
|
||||
opengl32
|
||||
)
|
||||
endif()
|
||||
# 链接库和选项
|
||||
if(IMGUI_BACKENDS STREQUAL "win32_dx11")
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
d3d11
|
||||
dxgi
|
||||
user32
|
||||
gdi32
|
||||
ole32
|
||||
dwmapi
|
||||
d3dcompiler
|
||||
)
|
||||
endif()
|
||||
|
||||
# 设置链接选项
|
||||
#set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
# LINK_FLAGS "-static -Wl,-subsystem,windows"
|
||||
#)
|
||||
#
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef APP_STATE_H
|
||||
#define APP_STATE_H
|
||||
|
||||
#include "CLIProcess.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <imgui.h>
|
||||
|
||||
class AppState {
|
||||
public:
|
||||
AppState();
|
||||
~AppState() = default;
|
||||
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
void ApplySettings();
|
||||
|
||||
bool show_main_window;
|
||||
bool auto_start;
|
||||
CLIProcess cli_process;
|
||||
char command_input[256]{};
|
||||
char send_command[256]{};
|
||||
bool auto_scroll_logs;
|
||||
int max_log_lines;
|
||||
char web_url[256]{};
|
||||
|
||||
// 停止命令相关配置
|
||||
char stop_command[256]{};
|
||||
int stop_timeout_ms;
|
||||
bool use_stop_command;
|
||||
|
||||
// 环境变量相关配置
|
||||
std::map<std::string, std::string> environment_variables;
|
||||
bool use_custom_environment;
|
||||
|
||||
// 新增:输出编码相关配置
|
||||
OutputEncoding output_encoding;
|
||||
|
||||
bool settings_dirty;
|
||||
|
||||
private:
|
||||
// 环境变量序列化辅助函数
|
||||
std::string SerializeEnvironmentVariables() const;
|
||||
void DeserializeEnvironmentVariables(const std::string& serialized);
|
||||
|
||||
// 新增:编码序列化辅助函数
|
||||
std::string SerializeOutputEncoding() const;
|
||||
void DeserializeOutputEncoding(const std::string& serialized);
|
||||
};
|
||||
|
||||
#endif // APP_STATE_H
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef CLIPROCESS_H
|
||||
#define CLIPROCESS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <windows.h>
|
||||
|
||||
// 新增:输出编码枚举
|
||||
enum class OutputEncoding {
|
||||
UTF8 = 0,
|
||||
GBK,
|
||||
GB2312,
|
||||
BIG5,
|
||||
SHIFT_JIS,
|
||||
AUTO_DETECT
|
||||
};
|
||||
|
||||
class CLIProcess {
|
||||
public:
|
||||
CLIProcess();
|
||||
~CLIProcess();
|
||||
|
||||
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 Start(const std::string& command);
|
||||
void Stop();
|
||||
void Restart(const std::string& command);
|
||||
|
||||
void ClearLogs();
|
||||
void AddLog(const std::string& log);
|
||||
const std::vector<std::string>& GetLogs() const;
|
||||
|
||||
bool SendCommand(const std::string& command);
|
||||
void CopyLogsToClipboard() const;
|
||||
|
||||
bool IsRunning() const;
|
||||
|
||||
// 环境变量管理接口
|
||||
const std::map<std::string, std::string>& GetEnvironmentVariables() const;
|
||||
void AddEnvironmentVariable(const std::string& key, const std::string& value);
|
||||
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();
|
||||
|
||||
private:
|
||||
void ReadOutput();
|
||||
void CloseProcessHandles();
|
||||
void CleanupResources();
|
||||
|
||||
// 新增:编码转换相关方法
|
||||
std::string ConvertToUTF8(const std::string& input, OutputEncoding encoding);
|
||||
std::string DetectAndConvertToUTF8(const std::string& input);
|
||||
UINT GetCodePageFromEncoding(OutputEncoding encoding);
|
||||
bool IsValidUTF8(const std::string& str);
|
||||
|
||||
PROCESS_INFORMATION pi_{};
|
||||
HANDLE hReadPipe_{};
|
||||
HANDLE hWritePipe_{};
|
||||
HANDLE hReadPipe_stdin_{};
|
||||
HANDLE hWritePipe_stdin_;
|
||||
|
||||
mutable std::mutex logs_mutex_;
|
||||
std::vector<std::string> logs_;
|
||||
int max_log_lines_;
|
||||
|
||||
std::thread output_thread_;
|
||||
|
||||
// 停止命令相关
|
||||
std::mutex stop_mutex_;
|
||||
std::string stop_command_;
|
||||
int stop_timeout_ms_;
|
||||
|
||||
// 环境变量相关
|
||||
mutable std::mutex env_mutex_;
|
||||
std::map<std::string, std::string> environment_variables_;
|
||||
|
||||
// 新增:编码相关
|
||||
mutable std::mutex encoding_mutex_;
|
||||
OutputEncoding output_encoding_;
|
||||
};
|
||||
|
||||
#endif // CLIPROCESS_H
|
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
#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
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GLFW/glfw3native.h>
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Manager {
|
||||
public:
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
bool Initialize();
|
||||
void Run();
|
||||
void Shutdown();
|
||||
|
||||
void OnTrayShowWindow();
|
||||
void OnTrayExit();
|
||||
|
||||
AppState m_app_state;
|
||||
private:
|
||||
// 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();
|
||||
|
||||
// 平台相关初始化
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
bool InitializeWin32();
|
||||
bool InitializeDirectX11();
|
||||
void CleanupWin32();
|
||||
void CleanupDirectX11();
|
||||
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
WNDCLASSEX m_wc = {};
|
||||
ID3D11Device* m_pd3dDevice = nullptr;
|
||||
ID3D11DeviceContext* m_pd3dDeviceContext = nullptr;
|
||||
IDXGISwapChain* m_pSwapChain = nullptr;
|
||||
ID3D11RenderTargetView* m_mainRenderTargetView = nullptr;
|
||||
#else
|
||||
bool InitializeGLFW();
|
||||
void CleanupGLFW();
|
||||
static void GlfwErrorCallback(int error, const char* description);
|
||||
|
||||
GLFWwindow* m_window = nullptr;
|
||||
const char* m_glsl_version = nullptr;
|
||||
#endif
|
||||
|
||||
// 托盘相关
|
||||
bool InitializeTray();
|
||||
void CleanupTray();
|
||||
static HWND CreateHiddenWindow();
|
||||
|
||||
std::unique_ptr<TrayIcon> m_tray;
|
||||
HWND m_tray_hwnd = nullptr;
|
||||
|
||||
// 控制标志
|
||||
bool m_should_exit = false;
|
||||
bool m_initialized = false;
|
||||
|
||||
// DPI缩放因子
|
||||
float m_dpi_scale = 1.0f;
|
||||
|
||||
// 环境变量UI状态
|
||||
char env_key_input_[256] = {};
|
||||
char env_value_input_[512] = {};
|
||||
bool show_env_settings_ = false;
|
||||
|
||||
// 新增:编码设置UI状态
|
||||
bool show_encoding_settings_ = false;
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class TrayIcon {
|
||||
public:
|
||||
// 回调函数类型定义
|
||||
using ShowWindowCallback = std::function<void()>;
|
||||
using ExitCallback = std::function<void()>;
|
||||
|
||||
TrayIcon(HWND hwnd, HICON icon);
|
||||
~TrayIcon();
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
void UpdateWebUrl(const std::wstring& url);
|
||||
|
||||
// 设置回调函数
|
||||
void SetShowWindowCallback(const ShowWindowCallback &callback);
|
||||
void SetExitCallback(const ExitCallback &callback);
|
||||
|
||||
// 静态窗口过程
|
||||
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
private:
|
||||
void CreateMenu();
|
||||
void DestroyMenu();
|
||||
void ShowContextMenu() const;
|
||||
|
||||
HWND m_hwnd;
|
||||
HICON m_icon;
|
||||
NOTIFYICONDATA m_nid{};
|
||||
std::wstring m_web_url;
|
||||
bool m_visible;
|
||||
HMENU m_menu;
|
||||
|
||||
// 回调函数
|
||||
ShowWindowCallback m_show_window_callback;
|
||||
ExitCallback m_exit_callback;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef UNITS_H
|
||||
#define UNITS_H
|
||||
#include <string>
|
||||
|
||||
std::wstring StringToWide(const std::string& str);
|
||||
std::string WideToString(const std::wstring& wstr);
|
||||
void SetAutoStart(bool enable);
|
||||
bool IsAutoStartEnabled();
|
||||
|
||||
|
||||
#endif //UNITS_H
|
|
@ -0,0 +1,188 @@
|
|||
#include "AppState.h"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
AppState::AppState() :
|
||||
show_main_window(true),
|
||||
auto_start(false),
|
||||
auto_scroll_logs(true),
|
||||
max_log_lines(1000),
|
||||
stop_timeout_ms(5000),
|
||||
use_stop_command(false),
|
||||
use_custom_environment(false),
|
||||
output_encoding(OutputEncoding::AUTO_DETECT), // 新增:默认自动检测编码
|
||||
settings_dirty(false) {
|
||||
strcpy_s(command_input, "cmd.exe");
|
||||
strcpy_s(web_url, "http://localhost:8080");
|
||||
strcpy_s(stop_command, "exit");
|
||||
memset(send_command, 0, sizeof(send_command));
|
||||
}
|
||||
|
||||
std::string AppState::SerializeEnvironmentVariables() const {
|
||||
std::ostringstream oss;
|
||||
bool first = true;
|
||||
for (const auto& pair : environment_variables) {
|
||||
if (!first) {
|
||||
oss << "|";
|
||||
}
|
||||
oss << pair.first << "=" << pair.second;
|
||||
first = false;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void AppState::DeserializeEnvironmentVariables(const std::string& serialized) {
|
||||
environment_variables.clear();
|
||||
if (serialized.empty()) return;
|
||||
|
||||
std::istringstream iss(serialized);
|
||||
std::string pair;
|
||||
|
||||
while (std::getline(iss, pair, '|')) {
|
||||
size_t equalPos = pair.find('=');
|
||||
if (equalPos != std::string::npos && equalPos > 0) {
|
||||
std::string key = pair.substr(0, equalPos);
|
||||
std::string value = pair.substr(equalPos + 1);
|
||||
environment_variables[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:序列化输出编码
|
||||
std::string AppState::SerializeOutputEncoding() const {
|
||||
return std::to_string(static_cast<int>(output_encoding));
|
||||
}
|
||||
|
||||
// 新增:反序列化输出编码
|
||||
void AppState::DeserializeOutputEncoding(const std::string& serialized) {
|
||||
if (serialized.empty()) {
|
||||
output_encoding = OutputEncoding::AUTO_DETECT;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int encodingValue = std::stoi(serialized);
|
||||
if (encodingValue >= 0 && encodingValue <= static_cast<int>(OutputEncoding::AUTO_DETECT)) {
|
||||
output_encoding = static_cast<OutputEncoding>(encodingValue);
|
||||
} else {
|
||||
output_encoding = OutputEncoding::AUTO_DETECT;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
output_encoding = OutputEncoding::AUTO_DETECT;
|
||||
}
|
||||
}
|
||||
|
||||
void AppState::LoadSettings() {
|
||||
std::ifstream file("climanager_settings.ini");
|
||||
if (!file.is_open()) return;
|
||||
|
||||
std::string line;
|
||||
std::string section;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty() && line[line.size() - 1] == '\r') {
|
||||
line.erase(line.size() - 1);
|
||||
}
|
||||
|
||||
if (line.empty()) continue;
|
||||
|
||||
if (line[0] == '[' && line[line.size() - 1] == ']') {
|
||||
section = line.substr(1, line.size() - 2);
|
||||
}
|
||||
else if (section == "Settings") {
|
||||
size_t pos = line.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
|
||||
if (key == "CommandInput") {
|
||||
strncpy_s(command_input, value.c_str(), sizeof(command_input) - 1);
|
||||
}
|
||||
else if (key == "MaxLogLines") {
|
||||
max_log_lines = std::stoi(value);
|
||||
max_log_lines = std::max(100, std::min(max_log_lines, 10000));
|
||||
}
|
||||
else if (key == "AutoScrollLogs") {
|
||||
auto_scroll_logs = (value == "1");
|
||||
}
|
||||
else if (key == "AutoStart") {
|
||||
auto_start = (value == "1");
|
||||
}
|
||||
else if (key == "WebUrl") {
|
||||
strncpy_s(web_url, value.c_str(), sizeof(web_url) - 1);
|
||||
}
|
||||
else if (key == "StopCommand") {
|
||||
strncpy_s(stop_command, value.c_str(), sizeof(stop_command) - 1);
|
||||
}
|
||||
else if (key == "StopTimeoutMs") {
|
||||
stop_timeout_ms = std::stoi(value);
|
||||
stop_timeout_ms = std::max(1000, std::min(stop_timeout_ms, 60000));
|
||||
}
|
||||
else if (key == "UseStopCommand") {
|
||||
use_stop_command = (value == "1");
|
||||
}
|
||||
else if (key == "UseCustomEnvironment") {
|
||||
use_custom_environment = (value == "1");
|
||||
}
|
||||
else if (key == "EnvironmentVariables") {
|
||||
DeserializeEnvironmentVariables(value);
|
||||
}
|
||||
// 新增:输出编码配置的加载
|
||||
else if (key == "OutputEncoding") {
|
||||
DeserializeOutputEncoding(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void AppState::SaveSettings() {
|
||||
std::ofstream file("climanager_settings.ini");
|
||||
if (!file.is_open()) return;
|
||||
|
||||
file << "[Settings]\n";
|
||||
file << "CommandInput=" << command_input << "\n";
|
||||
file << "MaxLogLines=" << max_log_lines << "\n";
|
||||
file << "AutoScrollLogs=" << (auto_scroll_logs ? "1" : "0") << "\n";
|
||||
file << "AutoStart=" << (auto_start ? "1" : "0") << "\n";
|
||||
file << "WebUrl=" << web_url << "\n";
|
||||
|
||||
// 停止命令相关配置的保存
|
||||
file << "StopCommand=" << stop_command << "\n";
|
||||
file << "StopTimeoutMs=" << stop_timeout_ms << "\n";
|
||||
file << "UseStopCommand=" << (use_stop_command ? "1" : "0") << "\n";
|
||||
|
||||
// 环境变量相关配置的保存
|
||||
file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n";
|
||||
file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n";
|
||||
|
||||
// 新增:输出编码配置的保存
|
||||
file << "OutputEncoding=" << SerializeOutputEncoding() << "\n";
|
||||
|
||||
file.close();
|
||||
|
||||
settings_dirty = false;
|
||||
}
|
||||
|
||||
void AppState::ApplySettings() {
|
||||
cli_process.SetMaxLogLines(max_log_lines);
|
||||
|
||||
// 应用停止命令设置
|
||||
if (use_stop_command && strlen(stop_command) > 0) {
|
||||
cli_process.SetStopCommand(stop_command, stop_timeout_ms);
|
||||
} else {
|
||||
cli_process.SetStopCommand("", 0);
|
||||
}
|
||||
|
||||
// 应用环境变量设置
|
||||
if (use_custom_environment) {
|
||||
cli_process.SetEnvironmentVariables(environment_variables);
|
||||
} else {
|
||||
cli_process.SetEnvironmentVariables({});
|
||||
}
|
||||
|
||||
// 新增:应用输出编码设置
|
||||
cli_process.SetOutputEncoding(output_encoding);
|
||||
}
|
|
@ -0,0 +1,603 @@
|
|||
#include "CLIProcess.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Units.h"
|
||||
|
||||
CLIProcess::CLIProcess() {
|
||||
ZeroMemory(&pi_, sizeof(pi_));
|
||||
max_log_lines_ = 1000;
|
||||
hWritePipe_stdin_ = nullptr;
|
||||
stop_timeout_ms_ = 5000;
|
||||
output_encoding_ = OutputEncoding::AUTO_DETECT; // 新增:默认自动检测编码
|
||||
}
|
||||
|
||||
CLIProcess::~CLIProcess() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
// 新增:设置输出编码
|
||||
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(OutputEncoding encoding) {
|
||||
switch (encoding) {
|
||||
case OutputEncoding::UTF8: return "UTF-8";
|
||||
case OutputEncoding::GBK: return "GBK";
|
||||
case OutputEncoding::GB2312: return "GB2312";
|
||||
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"},
|
||||
{OutputEncoding::GBK, "GBK (简体中文)"},
|
||||
{OutputEncoding::GB2312, "GB2312 (简体中文)"},
|
||||
{OutputEncoding::BIG5, "Big5 (繁体中文)"},
|
||||
{OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"}
|
||||
};
|
||||
}
|
||||
|
||||
// 新增:根据编码获取代码页
|
||||
UINT CLIProcess::GetCodePageFromEncoding(OutputEncoding encoding) {
|
||||
switch (encoding) {
|
||||
case OutputEncoding::UTF8: return CP_UTF8;
|
||||
case OutputEncoding::GBK: return 936;
|
||||
case OutputEncoding::GB2312: return 20936;
|
||||
case OutputEncoding::BIG5: return 950;
|
||||
case OutputEncoding::SHIFT_JIS: return 932;
|
||||
default: return CP_ACP; // 系统默认代码页
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:检查是否为有效的UTF-8
|
||||
bool CLIProcess::IsValidUTF8(const std::string& str) {
|
||||
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
|
||||
size_t len = str.length();
|
||||
|
||||
for (size_t i = 0; i < len; ) {
|
||||
if (bytes[i] <= 0x7F) {
|
||||
// ASCII字符
|
||||
i++;
|
||||
} else if ((bytes[i] & 0xE0) == 0xC0) {
|
||||
// 2字节UTF-8序列
|
||||
if (i + 1 >= len || (bytes[i + 1] & 0xC0) != 0x80) return false;
|
||||
i += 2;
|
||||
} else if ((bytes[i] & 0xF0) == 0xE0) {
|
||||
// 3字节UTF-8序列
|
||||
if (i + 2 >= len || (bytes[i + 1] & 0xC0) != 0x80 || (bytes[i + 2] & 0xC0) != 0x80) return false;
|
||||
i += 3;
|
||||
} else if ((bytes[i] & 0xF8) == 0xF0) {
|
||||
// 4字节UTF-8序列
|
||||
if (i + 3 >= len || (bytes[i + 1] & 0xC0) != 0x80 || (bytes[i + 2] & 0xC0) != 0x80 || (bytes[i + 3] & 0xC0) != 0x80) return false;
|
||||
i += 4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 新增:转换到UTF-8
|
||||
std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding encoding) {
|
||||
if (input.empty()) return input;
|
||||
|
||||
// 如果已经是UTF-8编码,直接返回
|
||||
if (encoding == OutputEncoding::UTF8) {
|
||||
return input;
|
||||
}
|
||||
|
||||
UINT codePage = GetCodePageFromEncoding(encoding);
|
||||
|
||||
// 先转换为宽字符
|
||||
int wideSize = MultiByteToWideChar(codePage, 0, input.c_str(), -1, nullptr, 0);
|
||||
if (wideSize <= 0) {
|
||||
// 转换失败,返回原始字符串
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<wchar_t> wideStr(wideSize);
|
||||
if (MultiByteToWideChar(codePage, 0, input.c_str(), -1, wideStr.data(), wideSize) <= 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// 再从宽字符转换为UTF-8
|
||||
int utf8Size = WideCharToMultiByte(CP_UTF8, 0, wideStr.data(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (utf8Size <= 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<char> utf8Str(utf8Size);
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, wideStr.data(), -1, utf8Str.data(), utf8Size, nullptr, nullptr) <= 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return std::string(utf8Str.data());
|
||||
}
|
||||
|
||||
// 新增:自动检测并转换到UTF-8
|
||||
std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) {
|
||||
if (input.empty()) return input;
|
||||
|
||||
// 首先检查是否已经是有效的UTF-8
|
||||
if (IsValidUTF8(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// 尝试不同的编码进行转换
|
||||
std::vector<OutputEncoding> encodingsToTry = {
|
||||
OutputEncoding::GBK,
|
||||
OutputEncoding::GB2312,
|
||||
OutputEncoding::BIG5,
|
||||
OutputEncoding::SHIFT_JIS
|
||||
};
|
||||
|
||||
for (OutputEncoding encoding : encodingsToTry) {
|
||||
std::string converted = ConvertToUTF8(input, encoding);
|
||||
if (converted != input && IsValidUTF8(converted)) {
|
||||
// 转换成功且结果是有效的UTF-8
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有编码都失败,尝试使用系统默认代码页
|
||||
return ConvertToUTF8(input, OutputEncoding::GBK); // 默认使用GBK
|
||||
}
|
||||
void CLIProcess::SetStopCommand(const std::string& command, int timeout_ms) {
|
||||
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||
stop_command_ = command;
|
||||
stop_timeout_ms_ = (timeout_ms > 0) ? timeout_ms : 5000;
|
||||
|
||||
if (!command.empty()) {
|
||||
AddLog("已设置停止命令: " + command + " (超时: " + std::to_string(timeout_ms) + "ms)");
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
void CLIProcess::SetMaxLogLines(int max_lines) {
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
max_log_lines_ = max_lines;
|
||||
if (logs_.size() > max_log_lines_) {
|
||||
logs_.erase(logs_.begin(), logs_.end() - max_log_lines_);
|
||||
}
|
||||
}
|
||||
|
||||
void CLIProcess::SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars) {
|
||||
std::lock_guard<std::mutex> lock(env_mutex_);
|
||||
environment_variables_.clear();
|
||||
|
||||
// 验证所有环境变量
|
||||
for (const auto& pair : env_vars) {
|
||||
if (pair.first.empty()) {
|
||||
AddLog("警告: 跳过空的环境变量名");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pair.first.find('=') != std::string::npos || pair.first.find('\0') != std::string::npos) {
|
||||
AddLog("警告: 跳过包含无效字符的环境变量: " + pair.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
environment_variables_[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
if (!environment_variables_.empty()) {
|
||||
// AddLog("已设置 " + std::to_string(environment_variables_.size()) + " 个有效环境变量");
|
||||
for (const auto& pair : environment_variables_) {
|
||||
AddLog(" " + pair.first + "=" + pair.second);
|
||||
}
|
||||
} else {
|
||||
// AddLog("已清空所有自定义环境变量");
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::string, std::string>& CLIProcess::GetEnvironmentVariables() const {
|
||||
std::lock_guard<std::mutex> lock(env_mutex_);
|
||||
return environment_variables_;
|
||||
}
|
||||
|
||||
void CLIProcess::AddEnvironmentVariable(const std::string& key, const std::string& value) {
|
||||
std::lock_guard<std::mutex> lock(env_mutex_);
|
||||
|
||||
// 验证环境变量名
|
||||
if (key.empty()) {
|
||||
AddLog("错误: 环境变量名不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否包含无效字符
|
||||
if (key.find('=') != std::string::npos || key.find('\0') != std::string::npos) {
|
||||
AddLog("错误: 环境变量名包含无效字符: " + key);
|
||||
return;
|
||||
}
|
||||
|
||||
environment_variables_[key] = value;
|
||||
// AddLog("添加环境变量: " + key + "=" + value);
|
||||
}
|
||||
void CLIProcess::RemoveEnvironmentVariable(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(env_mutex_);
|
||||
auto it = environment_variables_.find(key);
|
||||
if (it != environment_variables_.end()) {
|
||||
environment_variables_.erase(it);
|
||||
// AddLog("移除环境变量: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
void CLIProcess::ClearEnvironmentVariables() {
|
||||
std::lock_guard<std::mutex> lock(env_mutex_);
|
||||
environment_variables_.clear();
|
||||
// AddLog("已清空所有自定义环境变量");
|
||||
}
|
||||
|
||||
void CLIProcess::Start(const std::string& command) {
|
||||
if (IsRunning()) return;
|
||||
Stop();
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
if (!CreatePipe(&hReadPipe_, &hWritePipe_, &sa, 0)) {
|
||||
AddLog("创建输出管道失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CreatePipe(&hReadPipe_stdin_, &hWritePipe_stdin_, &sa, 0)) {
|
||||
AddLog("创建输入管道失败");
|
||||
CloseHandle(hReadPipe_);
|
||||
CloseHandle(hWritePipe_);
|
||||
return;
|
||||
}
|
||||
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
si.hStdOutput = hWritePipe_;
|
||||
si.hStdError = hWritePipe_;
|
||||
si.hStdInput = hReadPipe_stdin_;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
ZeroMemory(&pi_, sizeof(pi_));
|
||||
|
||||
// 转换命令为宽字符
|
||||
std::wstring wcmd = StringToWide(command);
|
||||
|
||||
// CreateProcess需要可修改的字符串
|
||||
std::vector<wchar_t> cmdBuffer(wcmd.begin(), wcmd.end());
|
||||
cmdBuffer.push_back(L'\0');
|
||||
|
||||
// 使用Windows API设置环境变量
|
||||
std::vector<std::pair<std::string, std::string>> originalEnvVars;
|
||||
bool envVarsSet = false;
|
||||
|
||||
if (!environment_variables_.empty()) {
|
||||
envVarsSet = true;
|
||||
|
||||
for (const auto& pair : environment_variables_) {
|
||||
if (!pair.first.empty()) {
|
||||
// 保存原始值(如果存在)
|
||||
DWORD bufferSize = GetEnvironmentVariableA(pair.first.c_str(), nullptr, 0);
|
||||
if (bufferSize > 0) {
|
||||
// 变量存在,保存原始值
|
||||
std::vector<char> buffer(bufferSize);
|
||||
if (GetEnvironmentVariableA(pair.first.c_str(), buffer.data(), bufferSize) > 0) {
|
||||
originalEnvVars.emplace_back(pair.first, std::string(buffer.data()));
|
||||
} else {
|
||||
originalEnvVars.emplace_back(pair.first, "");
|
||||
}
|
||||
} else {
|
||||
// 变量不存在,标记为新变量(使用空字符串表示原来不存在)
|
||||
originalEnvVars.emplace_back(pair.first, "");
|
||||
}
|
||||
|
||||
// 设置新的环境变量值
|
||||
if (SetEnvironmentVariableA(pair.first.c_str(), pair.second.c_str())) {
|
||||
// AddLog("设置环境变量: " + pair.first + "=" + pair.second);
|
||||
} else {
|
||||
AddLog("设置环境变量失败: " + pair.first + " (错误代码: " + std::to_string(GetLastError()) + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddLog("环境变量设置完成,数量: " + std::to_string(environment_variables_.size()));
|
||||
} else {
|
||||
AddLog("未设置自定义环境变量,使用默认环境");
|
||||
}
|
||||
|
||||
BOOL result = CreateProcess(
|
||||
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) {
|
||||
if (pair.second.empty()) {
|
||||
// 原来不存在,删除变量
|
||||
SetEnvironmentVariableA(pair.first.c_str(), nullptr);
|
||||
} else {
|
||||
// 恢复原始值
|
||||
SetEnvironmentVariableA(pair.first.c_str(), pair.second.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
AddLog("进程已启动: " + command);
|
||||
|
||||
CloseHandle(hWritePipe_);
|
||||
CloseHandle(hReadPipe_stdin_);
|
||||
hWritePipe_ = nullptr;
|
||||
hReadPipe_stdin_ = nullptr;
|
||||
|
||||
output_thread_ = std::thread([this]() {
|
||||
ReadOutput();
|
||||
});
|
||||
}
|
||||
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);
|
||||
|
||||
std::string errorMsg = "CreateProcess 失败 (错误代码: " + std::to_string(err) + ")";
|
||||
if (messageBuffer) {
|
||||
std::wstring wErrorMsg(messageBuffer);
|
||||
errorMsg += " - " + WideToString(wErrorMsg);
|
||||
LocalFree(messageBuffer);
|
||||
}
|
||||
|
||||
AddLog(errorMsg);
|
||||
|
||||
// 清理资源
|
||||
CloseHandle(hReadPipe_);
|
||||
CloseHandle(hWritePipe_);
|
||||
CloseHandle(hReadPipe_stdin_);
|
||||
CloseHandle(hWritePipe_stdin_);
|
||||
hReadPipe_ = hWritePipe_ = hReadPipe_stdin_ = hWritePipe_stdin_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CLIProcess::Stop() {
|
||||
bool useStopCommand = false;
|
||||
std::string stopCmd;
|
||||
int timeout = stop_timeout_ms_;
|
||||
|
||||
// 检查是否设置了停止命令
|
||||
{
|
||||
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);
|
||||
pi_.hProcess = nullptr;
|
||||
}
|
||||
if (pi_.hThread) {
|
||||
CloseHandle(pi_.hThread);
|
||||
pi_.hThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:清理资源的辅助函数
|
||||
void CLIProcess::CleanupResources() {
|
||||
// 关闭输入管道写入端(通知进程停止)
|
||||
if (hWritePipe_stdin_) {
|
||||
CloseHandle(hWritePipe_stdin_);
|
||||
hWritePipe_stdin_ = nullptr;
|
||||
}
|
||||
|
||||
// 等待输出线程结束
|
||||
if (output_thread_.joinable()) {
|
||||
output_thread_.join();
|
||||
}
|
||||
|
||||
// 关闭输出管道读取端
|
||||
if (hReadPipe_) {
|
||||
CloseHandle(hReadPipe_);
|
||||
hReadPipe_ = nullptr;
|
||||
}
|
||||
|
||||
// 确保所有句柄都已关闭
|
||||
if (hWritePipe_) {
|
||||
CloseHandle(hWritePipe_);
|
||||
hWritePipe_ = nullptr;
|
||||
}
|
||||
|
||||
if (hReadPipe_stdin_) {
|
||||
CloseHandle(hReadPipe_stdin_);
|
||||
hReadPipe_stdin_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CLIProcess::Restart(const std::string& command) {
|
||||
Stop();
|
||||
Start(command);
|
||||
}
|
||||
|
||||
void CLIProcess::ClearLogs() {
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
logs_.clear();
|
||||
}
|
||||
|
||||
void CLIProcess::AddLog(const std::string& log) {
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
logs_.push_back(log);
|
||||
if (logs_.size() > max_log_lines_) {
|
||||
logs_.erase(logs_.begin(), logs_.begin() + (logs_.size() - max_log_lines_));
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& CLIProcess::GetLogs() const {
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
return logs_;
|
||||
}
|
||||
|
||||
bool CLIProcess::SendCommand(const std::string& command) {
|
||||
if (!IsRunning() || !hWritePipe_stdin_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytesWritten;
|
||||
std::string fullCommand = command + "\n";
|
||||
|
||||
if (WriteFile(hWritePipe_stdin_, fullCommand.c_str(),
|
||||
static_cast<DWORD>(fullCommand.length()), &bytesWritten, nullptr)) {
|
||||
AddLog("> " + command);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CLIProcess::CopyLogsToClipboard() const {
|
||||
std::lock_guard<std::mutex> lock(logs_mutex_);
|
||||
if (logs_.empty()) return;
|
||||
|
||||
// 构建完整的日志字符串(使用\r\n确保跨平台兼容)
|
||||
std::wstring allLogs;
|
||||
for (const auto& log : logs_) {
|
||||
allLogs += StringToWide(log) ;
|
||||
allLogs.resize(allLogs.size() - sizeof(wchar_t));
|
||||
allLogs += L"\n";
|
||||
}
|
||||
|
||||
if (OpenClipboard(nullptr)) {
|
||||
EmptyClipboard();
|
||||
|
||||
// 计算正确的内存大小(包括终止空字符)
|
||||
const size_t dataSize = (allLogs.length() + 1) * sizeof(wchar_t);
|
||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, dataSize);
|
||||
|
||||
if (hMem) {
|
||||
wchar_t* pMem = static_cast<wchar_t*>(GlobalLock(hMem));
|
||||
if (pMem) {
|
||||
// 安全复制宽字符串数据
|
||||
wcscpy_s(pMem, allLogs.length() + 1, allLogs.c_str());
|
||||
GlobalUnlock(hMem);
|
||||
SetClipboardData(CF_UNICODETEXT, hMem);
|
||||
} else {
|
||||
GlobalFree(hMem); // 锁定失败时释放内存
|
||||
}
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
bool CLIProcess::IsRunning() const {
|
||||
return pi_.hProcess != nullptr;
|
||||
}
|
||||
|
||||
void CLIProcess::ReadOutput() {
|
||||
constexpr int BUFFER_SIZE = 4096;
|
||||
char buffer[BUFFER_SIZE];
|
||||
DWORD bytesRead;
|
||||
std::string partialLine;
|
||||
|
||||
while (true) {
|
||||
if (!ReadFile(hReadPipe_, buffer, BUFFER_SIZE - 1, &bytesRead, nullptr) || bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[bytesRead] = '\0';
|
||||
std::string output(buffer);
|
||||
|
||||
// 新增:根据设置的编码转换输出
|
||||
OutputEncoding currentEncoding;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(encoding_mutex_);
|
||||
currentEncoding = output_encoding_;
|
||||
}
|
||||
|
||||
std::string convertedOutput;
|
||||
if (currentEncoding == OutputEncoding::AUTO_DETECT) {
|
||||
convertedOutput = DetectAndConvertToUTF8(output);
|
||||
} else {
|
||||
convertedOutput = ConvertToUTF8(output, currentEncoding);
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = convertedOutput.find('\n');
|
||||
|
||||
while (end != std::string::npos) {
|
||||
std::string line = partialLine + convertedOutput.substr(start, end - start);
|
||||
partialLine.clear();
|
||||
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
if (!line.empty()) {
|
||||
AddLog(line);
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
end = convertedOutput.find('\n', start);
|
||||
}
|
||||
|
||||
if (start < convertedOutput.size()) {
|
||||
partialLine = convertedOutput.substr(start);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,838 @@
|
|||
#include "Manager.h"
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Units.h"
|
||||
|
||||
|
||||
Manager::Manager() = default;
|
||||
|
||||
Manager::~Manager() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Manager::Initialize() {
|
||||
if (m_initialized) return true;
|
||||
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
if (!InitializeWin32()) return false;
|
||||
if (!InitializeDirectX11()) return false;
|
||||
#else
|
||||
if (!InitializeGLFW()) return false;
|
||||
#endif
|
||||
|
||||
// 初始化ImGui
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigViewportsNoAutoMerge = true;
|
||||
io.IniFilename = "imgui.ini";
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||
style.WindowRounding = 0.0f;
|
||||
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
|
||||
}
|
||||
|
||||
// 设置样式
|
||||
style.WindowPadding = ImVec2(15, 15);
|
||||
style.FramePadding = ImVec2(5, 5);
|
||||
style.ItemSpacing = ImVec2(10, 8);
|
||||
style.ItemInnerSpacing = ImVec2(8, 6);
|
||||
style.IndentSpacing = 25.0f;
|
||||
style.ScrollbarSize = 15.0f;
|
||||
style.GrabMinSize = 10.0f;
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
ImGui_ImplWin32_Init(m_hwnd);
|
||||
ImGui_ImplDX11_Init(m_pd3dDevice, m_pd3dDeviceContext);
|
||||
// ImGui_ImplWin32_EnableDpiAwareness();
|
||||
// m_dpi_scale=ImGui_ImplWin32_GetDpiScaleForHwnd(m_hwnd);
|
||||
// style.ScaleAllSizes(m_dpi_scale);
|
||||
#else
|
||||
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
|
||||
ImGui_ImplOpenGL3_Init(m_glsl_version);
|
||||
#endif
|
||||
|
||||
// 加载中文字体
|
||||
ImFont* font = io.Fonts->AddFontFromFileTTF(
|
||||
"C:/Windows/Fonts/msyh.ttc",
|
||||
18.0f,
|
||||
nullptr,
|
||||
io.Fonts->GetGlyphRangesChineseFull()
|
||||
);
|
||||
IM_ASSERT(font != nullptr);
|
||||
|
||||
// 初始化托盘
|
||||
if (!InitializeTray()) return false;
|
||||
|
||||
// 初始化应用状态
|
||||
m_app_state.LoadSettings();
|
||||
m_app_state.auto_start = IsAutoStartEnabled();
|
||||
m_app_state.ApplySettings();
|
||||
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
|
||||
|
||||
// 如果开启了开机自启动且有启动命令,则自动启动子进程
|
||||
if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) {
|
||||
m_app_state.cli_process.Start(m_app_state.command_input);
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Manager::Run() {
|
||||
if (!m_initialized) return;
|
||||
|
||||
while (!ShouldExit()) {
|
||||
HandleMessages();
|
||||
|
||||
if (m_should_exit) break;
|
||||
|
||||
if (m_app_state.settings_dirty) {
|
||||
m_app_state.SaveSettings();
|
||||
}
|
||||
|
||||
if (m_app_state.show_main_window) {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
// Win32 渲染循环
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
#else
|
||||
// GLFW 渲染循环
|
||||
if (glfwWindowShouldClose(m_window)) {
|
||||
HideMainWindow();
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
#endif
|
||||
|
||||
ImGui::NewFrame();
|
||||
RenderUI();
|
||||
ImGui::Render();
|
||||
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
float clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
|
||||
m_pd3dDeviceContext->OMSetRenderTargets(1, &m_mainRenderTargetView, nullptr);
|
||||
m_pd3dDeviceContext->ClearRenderTargetView(m_mainRenderTargetView, clearColor);
|
||||
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
||||
m_pSwapChain->Present(1, 0);
|
||||
#else
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(m_window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.00f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
glfwSwapBuffers(m_window);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
WaitMessage();
|
||||
#else
|
||||
glfwWaitEvents();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (m_app_state.settings_dirty) {
|
||||
m_app_state.SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::RenderUI() {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
int display_w = rect.right - rect.left;
|
||||
int display_h = rect.bottom - rect.top;
|
||||
#else
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(m_window, &display_w, &display_h);
|
||||
#endif
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(display_w, display_h));
|
||||
|
||||
ImGui::Begin("CLI程序管理工具", &m_app_state.show_main_window,
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_MenuBar);
|
||||
|
||||
RenderMenuBar();
|
||||
RenderMainContent();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Manager::RenderMenuBar() {
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
if (ImGui::BeginMenu("设置")) {
|
||||
RenderSettingsMenu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::RenderSettingsMenu() {
|
||||
if (ImGui::MenuItem("开机自启动", nullptr, m_app_state.auto_start)) {
|
||||
m_app_state.auto_start = !m_app_state.auto_start;
|
||||
SetAutoStart(m_app_state.auto_start);
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("日志设置");
|
||||
if (ImGui::InputInt("最大日志行数", &m_app_state.max_log_lines, 100, 500)) {
|
||||
m_app_state.max_log_lines = std::max(100, std::min(m_app_state.max_log_lines, 10000));
|
||||
m_app_state.cli_process.SetMaxLogLines(m_app_state.max_log_lines);
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Web设置");
|
||||
if (ImGui::InputText("Web地址", m_app_state.web_url, IM_ARRAYSIZE(m_app_state.web_url))) {
|
||||
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
|
||||
RenderStopCommandSettings();
|
||||
RenderEnvironmentVariablesSettings();
|
||||
RenderOutputEncodingSettings(); // 新增:渲染编码设置
|
||||
}
|
||||
|
||||
|
||||
void Manager::RenderStopCommandSettings() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("停止命令设置");
|
||||
|
||||
if (ImGui::Checkbox("启用优雅停止命令", &m_app_state.use_stop_command)) {
|
||||
m_app_state.settings_dirty = true;
|
||||
m_app_state.ApplySettings();
|
||||
}
|
||||
|
||||
if (m_app_state.use_stop_command) {
|
||||
if (ImGui::InputText("停止命令", m_app_state.stop_command, IM_ARRAYSIZE(m_app_state.stop_command))) {
|
||||
m_app_state.settings_dirty = true;
|
||||
m_app_state.ApplySettings();
|
||||
}
|
||||
|
||||
if (ImGui::InputInt("超时时间(毫秒)", &m_app_state.stop_timeout_ms, 1000, 5000)) {
|
||||
m_app_state.stop_timeout_ms = std::max(1000, std::min(m_app_state.stop_timeout_ms, 60000));
|
||||
m_app_state.settings_dirty = true;
|
||||
m_app_state.ApplySettings();
|
||||
}
|
||||
|
||||
ImGui::TextWrapped("说明:启用后,停止程序时会先发送指定命令,等待程序优雅退出。超时后将强制终止。");
|
||||
} else {
|
||||
ImGui::BeginDisabled(true);
|
||||
ImGui::InputText("停止命令", m_app_state.stop_command, IM_ARRAYSIZE(m_app_state.stop_command));
|
||||
ImGui::InputInt("超时时间(毫秒)", &m_app_state.stop_timeout_ms);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::TextWrapped("说明:禁用时将直接强制终止程序。");
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::RenderEnvironmentVariablesSettings() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("环境变量设置");
|
||||
|
||||
if (ImGui::Checkbox("使用自定义环境变量", &m_app_state.use_custom_environment)) {
|
||||
m_app_state.settings_dirty = true;
|
||||
m_app_state.ApplySettings();
|
||||
}
|
||||
|
||||
if (m_app_state.use_custom_environment) {
|
||||
ImGui::Indent();
|
||||
|
||||
// 添加新环境变量
|
||||
ImGui::Text("添加环境变量:");
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
ImGui::InputText("变量名", env_key_input_, IM_ARRAYSIZE(env_key_input_));
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(300.0f);
|
||||
ImGui::InputText("变量值", env_value_input_, IM_ARRAYSIZE(env_value_input_));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("添加") && strlen(env_key_input_) > 0) {
|
||||
m_app_state.environment_variables[env_key_input_] = env_value_input_;
|
||||
m_app_state.cli_process.AddEnvironmentVariable(env_key_input_, env_value_input_);
|
||||
memset(env_key_input_, 0, sizeof(env_key_input_));
|
||||
memset(env_value_input_, 0, sizeof(env_value_input_));
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// 显示当前环境变量列表
|
||||
if (!m_app_state.environment_variables.empty()) {
|
||||
ImGui::Text("当前环境变量 (%d个):", static_cast<int>(m_app_state.environment_variables.size()));
|
||||
|
||||
if (ImGui::BeginChild("EnvVarsList", ImVec2(0, 150), true)) {
|
||||
std::vector<std::string> keysToRemove;
|
||||
|
||||
for (const auto& pair : m_app_state.environment_variables) {
|
||||
ImGui::PushID(pair.first.c_str());
|
||||
|
||||
// 显示环境变量
|
||||
ImGui::Text("%s = %s", pair.first.c_str(), pair.second.c_str());
|
||||
ImGui::SameLine();
|
||||
|
||||
// 删除按钮
|
||||
if (ImGui::SmallButton("删除")) {
|
||||
keysToRemove.push_back(pair.first);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// 删除标记的环境变量
|
||||
for (const auto& key : keysToRemove) {
|
||||
m_app_state.environment_variables.erase(key);
|
||||
m_app_state.cli_process.RemoveEnvironmentVariable(key);
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
// 清空所有环境变量按钮
|
||||
if (ImGui::Button("清空所有环境变量")) {
|
||||
m_app_state.environment_variables.clear();
|
||||
m_app_state.cli_process.ClearEnvironmentVariables();
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "暂无自定义环境变量");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("说明:启用后,CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并,同名变量会被覆盖。");
|
||||
|
||||
ImGui::Unindent();
|
||||
} else {
|
||||
ImGui::BeginDisabled(true);
|
||||
ImGui::InputText("变量名", env_key_input_, IM_ARRAYSIZE(env_key_input_));
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("变量值", env_value_input_, IM_ARRAYSIZE(env_value_input_));
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("添加");
|
||||
ImGui::EndDisabled();
|
||||
ImGui::TextWrapped("说明:禁用时将使用系统默认环境变量启动程序。");
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::RenderOutputEncodingSettings() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("输出编码设置");
|
||||
|
||||
// 获取支持的编码列表
|
||||
auto supportedEncodings = CLIProcess::GetSupportedEncodings();
|
||||
|
||||
// 当前选择的编码索引
|
||||
int currentEncodingIndex = static_cast<int>(m_app_state.output_encoding);
|
||||
|
||||
// 创建编码名称数组用于Combo
|
||||
std::vector<const char*> encodingNames;
|
||||
for (const auto& encoding : supportedEncodings) {
|
||||
encodingNames.push_back(encoding.second.c_str());
|
||||
}
|
||||
|
||||
if (ImGui::Combo("输出编码", ¤tEncodingIndex, encodingNames.data(), static_cast<int>(encodingNames.size()))) {
|
||||
if (currentEncodingIndex >= 0 && currentEncodingIndex < static_cast<int>(supportedEncodings.size())) {
|
||||
m_app_state.output_encoding = supportedEncodings[currentEncodingIndex].first;
|
||||
m_app_state.cli_process.SetOutputEncoding(m_app_state.output_encoding);
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示当前编码信息
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "当前: %s",
|
||||
CLIProcess::GetEncodingName(m_app_state.output_encoding).c_str());
|
||||
|
||||
// 编码说明
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("说明:");
|
||||
ImGui::BulletText("自动检测:程序会尝试自动识别输出编码并转换为UTF-8显示");
|
||||
ImGui::BulletText("UTF-8:适用于现代程序和国际化应用");
|
||||
ImGui::BulletText("GBK/GB2312:适用于中文Windows系统的程序");
|
||||
ImGui::BulletText("Big5:适用于繁体中文程序");
|
||||
ImGui::BulletText("Shift-JIS:适用于日文程序");
|
||||
|
||||
// // 测试按钮
|
||||
// if (ImGui::Button("测试编码转换")) {
|
||||
// std::string testText = "测试中编码转换显示:中文,English, 日本語, 한국어";
|
||||
// m_app_state.cli_process.TestOutputEncoding(testText);
|
||||
// }
|
||||
}
|
||||
|
||||
void Manager::RenderMainContent() {
|
||||
float buttonWidth = 80.0f * m_dpi_scale;
|
||||
float buttonHeight = 40.0f * m_dpi_scale;
|
||||
float inputWidth = ImGui::GetContentRegionAvail().x * 0.6f;
|
||||
|
||||
// 启动命令输入
|
||||
ImGui::SetNextItemWidth(inputWidth);
|
||||
if (ImGui::InputText("启动命令", m_app_state.command_input, IM_ARRAYSIZE(m_app_state.command_input))) {
|
||||
m_app_state.settings_dirty = true;
|
||||
}
|
||||
|
||||
// 控制按钮
|
||||
ImGui::BeginGroup();
|
||||
if (ImGui::Button("启动", ImVec2(buttonWidth, buttonHeight))) {
|
||||
m_app_state.cli_process.Start(m_app_state.command_input);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
|
||||
m_app_state.cli_process.Stop();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
|
||||
m_app_state.cli_process.Restart(m_app_state.command_input);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("清理日志", ImVec2(100.0f * m_dpi_scale, buttonHeight))) {
|
||||
m_app_state.cli_process.ClearLogs();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::Text("状态: %s", m_app_state.cli_process.IsRunning() ? "运行中" : "已停止");
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("发送命令到CLI程序");
|
||||
|
||||
// 命令发送
|
||||
ImGui::BeginGroup();
|
||||
ImGui::SetNextItemWidth(inputWidth);
|
||||
bool sendCommandPressed = ImGui::InputText("##命令输入", m_app_state.send_command, IM_ARRAYSIZE(m_app_state.send_command),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("发送", ImVec2(buttonWidth, 0)) || sendCommandPressed) {
|
||||
if (m_app_state.cli_process.IsRunning() && strlen(m_app_state.send_command) > 0) {
|
||||
m_app_state.cli_process.SendCommand(m_app_state.send_command);
|
||||
memset(m_app_state.send_command, 0, sizeof(m_app_state.send_command));
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// 日志控制
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Text("程序日志");
|
||||
|
||||
float logControlButtonWidth = 100.0f * m_dpi_scale;
|
||||
ImGui::SameLine();
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - (350.0f * m_dpi_scale));
|
||||
if (ImGui::Button("复制日志", ImVec2(logControlButtonWidth, 0))) {
|
||||
m_app_state.cli_process.CopyLogsToClipboard();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("自动滚动", &m_app_state.auto_scroll_logs);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("行数: %d/%d",
|
||||
static_cast<int>(m_app_state.cli_process.GetLogs().size()),
|
||||
m_app_state.max_log_lines);
|
||||
ImGui::EndGroup();
|
||||
|
||||
float logHeight = ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y;
|
||||
ImGui::BeginChild("Logs", ImVec2(0, logHeight), true, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
const auto& logs = m_app_state.cli_process.GetLogs();
|
||||
for (const auto& log : logs) {
|
||||
ImGui::TextUnformatted(log.c_str());
|
||||
}
|
||||
|
||||
if (m_app_state.auto_scroll_logs && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void Manager::OnTrayShowWindow() {
|
||||
m_app_state.show_main_window = true;
|
||||
ShowMainWindow();
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
SetForegroundWindow(m_hwnd);
|
||||
#else
|
||||
glfwRestoreWindow(m_window);
|
||||
glfwFocusWindow(m_window);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Manager::OnTrayExit() {
|
||||
m_should_exit = true;
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
void Manager::ShowMainWindow() {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
ShowWindow(m_hwnd, SW_RESTORE);
|
||||
SetForegroundWindow(m_hwnd);
|
||||
#else
|
||||
glfwShowWindow(m_window);
|
||||
glfwRestoreWindow(m_window);
|
||||
glfwFocusWindow(m_window);
|
||||
#endif
|
||||
m_app_state.show_main_window = true;
|
||||
}
|
||||
|
||||
void Manager::HideMainWindow() {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
#else
|
||||
glfwHideWindow(m_window);
|
||||
#endif
|
||||
m_app_state.show_main_window = false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
LRESULT WINAPI Manager::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
Manager* manager = nullptr;
|
||||
if (msg == WM_NCCREATE) {
|
||||
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
manager = reinterpret_cast<Manager*>(cs->lpCreateParams);
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(manager));
|
||||
} else {
|
||||
manager = reinterpret_cast<Manager*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
if (manager && ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
|
||||
return true;
|
||||
|
||||
switch (msg) {
|
||||
case WM_CLOSE:
|
||||
// 主窗口关闭时隐藏到托盘
|
||||
if (manager) {
|
||||
manager->HideMainWindow();
|
||||
}
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
case WM_SIZE:
|
||||
if (wParam == SIZE_MINIMIZED && manager) {
|
||||
// 最小化时隐藏到托盘
|
||||
manager->HideMainWindow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Manager::InitializeTray() {
|
||||
m_tray_hwnd = CreateHiddenWindow();
|
||||
if (!m_tray_hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HICON trayIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||
m_tray = std::make_unique<TrayIcon>(m_tray_hwnd, trayIcon);
|
||||
|
||||
// 设置回调函数
|
||||
m_tray->SetShowWindowCallback([this]() {
|
||||
OnTrayShowWindow();
|
||||
});
|
||||
|
||||
m_tray->SetExitCallback([this]() {
|
||||
OnTrayExit();
|
||||
});
|
||||
|
||||
m_tray->Show();
|
||||
|
||||
// 设置托盘窗口的用户数据,指向TrayIcon实例
|
||||
SetWindowLongPtr(m_tray_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(m_tray.get()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HWND Manager::CreateHiddenWindow() {
|
||||
WNDCLASSEX wc = {0};
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.lpfnWndProc = TrayIcon::WindowProc; // 使用TrayIcon的窗口过程
|
||||
wc.hInstance = GetModuleHandle(NULL);
|
||||
wc.lpszClassName = L"CLIManagerTrayWindow";
|
||||
|
||||
if (!RegisterClassEx(&wc)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return CreateWindowEx(
|
||||
0,
|
||||
wc.lpszClassName,
|
||||
L"CLI Manager Tray Window",
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
NULL, NULL,
|
||||
wc.hInstance,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
void Manager::HandleMessages() {
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) {
|
||||
m_should_exit = true;
|
||||
}
|
||||
else if (msg.message == WM_CLOSE) {
|
||||
// 主窗口关闭时隐藏到托盘,而不是退出
|
||||
if (msg.hwnd == m_hwnd) {
|
||||
HideMainWindow();
|
||||
continue;
|
||||
} else {
|
||||
m_should_exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
#else
|
||||
// GLFW后端的消息处理
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) {
|
||||
m_should_exit = true;
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
glfwPollEvents();
|
||||
if (glfwWindowShouldClose(m_window)) {
|
||||
HideMainWindow();
|
||||
glfwSetWindowShouldClose(m_window, GLFW_FALSE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Manager::ShouldExit() const {
|
||||
return m_should_exit;
|
||||
}
|
||||
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
bool Manager::InitializeWin32() {
|
||||
m_wc = {};
|
||||
m_wc.cbSize = sizeof(WNDCLASSEX);
|
||||
m_wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
m_wc.lpfnWndProc = WndProc;
|
||||
m_wc.hInstance = GetModuleHandle(NULL);
|
||||
m_wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
m_wc.lpszClassName = L"CLIManagerWin32Class";
|
||||
|
||||
if (!RegisterClassEx(&m_wc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = CreateWindowEx(
|
||||
0,
|
||||
m_wc.lpszClassName,
|
||||
L"CLI程序管理工具",
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
1280,
|
||||
800,
|
||||
NULL,
|
||||
NULL,
|
||||
m_wc.hInstance,
|
||||
this // 将 this 指针传递给窗口创建数据
|
||||
);
|
||||
|
||||
if (!m_hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(m_hwnd, SW_SHOWDEFAULT);
|
||||
UpdateWindow(m_hwnd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::InitializeDirectX11() {
|
||||
DXGI_SWAP_CHAIN_DESC sd = {};
|
||||
sd.BufferCount = 2;
|
||||
sd.BufferDesc.Width = 0; // 自动适配窗口大小
|
||||
sd.BufferDesc.Height = 0;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
sd.OutputWindow = m_hwnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
sd.SampleDesc.Quality = 0;
|
||||
sd.Windowed = TRUE;
|
||||
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
UINT createDeviceFlags = 0;
|
||||
#ifdef _DEBUG
|
||||
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
const D3D_FEATURE_LEVEL featureLevelArray[2] = {
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
};
|
||||
|
||||
if (D3D11CreateDeviceAndSwapChain(
|
||||
NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags,
|
||||
featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &m_pSwapChain,
|
||||
&m_pd3dDevice, &featureLevel, &m_pd3dDeviceContext) != S_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D11Texture2D* pBackBuffer;
|
||||
if (m_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)) != S_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &m_mainRenderTargetView) != S_OK) {
|
||||
pBackBuffer->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
pBackBuffer->Release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Manager::CleanupWin32() {
|
||||
if (m_hwnd) {
|
||||
DestroyWindow(m_hwnd);
|
||||
m_hwnd = nullptr;
|
||||
}
|
||||
UnregisterClass(m_wc.lpszClassName, m_wc.hInstance);
|
||||
}
|
||||
|
||||
void Manager::CleanupDirectX11() {
|
||||
if (m_mainRenderTargetView) {
|
||||
m_mainRenderTargetView->Release();
|
||||
m_mainRenderTargetView = nullptr;
|
||||
}
|
||||
if (m_pSwapChain) {
|
||||
m_pSwapChain->Release();
|
||||
m_pSwapChain = nullptr;
|
||||
}
|
||||
if (m_pd3dDeviceContext) {
|
||||
m_pd3dDeviceContext->Release();
|
||||
m_pd3dDeviceContext = nullptr;
|
||||
}
|
||||
if (m_pd3dDevice) {
|
||||
m_pd3dDevice->Release();
|
||||
m_pd3dDevice = nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
bool Manager::InitializeGLFW() {
|
||||
glfwSetErrorCallback(GlfwErrorCallback);
|
||||
if (!glfwInit())
|
||||
return false;
|
||||
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
m_glsl_version = "#version 100";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||
#elif defined(IMGUI_IMPL_OPENGL_ES3)
|
||||
m_glsl_version = "#version 300 es";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||
#elif defined(__APPLE__)
|
||||
m_glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
#else
|
||||
m_glsl_version = "#version 130";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
#endif
|
||||
|
||||
int screenWidth, screenHeight;
|
||||
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
|
||||
screenWidth = mode->width;
|
||||
screenHeight = mode->height;
|
||||
|
||||
int windowWidth = static_cast<int>(screenWidth * 0.8);
|
||||
int windowHeight = static_cast<int>(screenHeight * 0.8);
|
||||
|
||||
m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr);
|
||||
if (!m_window)
|
||||
return false;
|
||||
|
||||
glfwSetWindowPos(m_window,
|
||||
(screenWidth - windowWidth) / 2,
|
||||
(screenHeight - windowHeight) / 2);
|
||||
|
||||
glfwMakeContextCurrent(m_window);
|
||||
glfwSwapInterval(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Manager::CleanupGLFW() {
|
||||
if (m_window) {
|
||||
glfwDestroyWindow(m_window);
|
||||
m_window = nullptr;
|
||||
}
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
void Manager::GlfwErrorCallback(int error, const char* description) {
|
||||
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Manager::CleanupTray() {
|
||||
m_tray.reset();
|
||||
if (m_tray_hwnd) {
|
||||
DestroyWindow(m_tray_hwnd);
|
||||
m_tray_hwnd = nullptr;
|
||||
}
|
||||
UnregisterClass(L"CLIManagerTrayWindow", GetModuleHandle(nullptr));
|
||||
}
|
||||
|
||||
void Manager::Shutdown() {
|
||||
if (!m_initialized) return;
|
||||
|
||||
if (m_app_state.settings_dirty) {
|
||||
m_app_state.SaveSettings();
|
||||
}
|
||||
|
||||
CleanupTray();
|
||||
|
||||
#ifdef USE_WIN32_BACKEND
|
||||
ImGui_ImplDX11_Shutdown();
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
CleanupDirectX11();
|
||||
CleanupWin32();
|
||||
#else
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
CleanupGLFW();
|
||||
#endif
|
||||
|
||||
m_initialized = false;
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
#include "TrayIcon.h"
|
||||
#include "Units.h"
|
||||
|
||||
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;
|
||||
m_nid.uID = 1;
|
||||
m_nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
|
||||
m_nid.uCallbackMessage = WM_APP + 1;
|
||||
m_nid.hIcon = m_icon;
|
||||
wcscpy_s(m_nid.szTip, L"CLI程序管理工具");
|
||||
m_web_url = L"http://localhost:8080"; // 默认URL
|
||||
|
||||
CreateMenu();
|
||||
}
|
||||
|
||||
TrayIcon::~TrayIcon() {
|
||||
Hide();
|
||||
DestroyMenu();
|
||||
}
|
||||
|
||||
void TrayIcon::Show() {
|
||||
if (!m_visible) {
|
||||
Shell_NotifyIcon(NIM_ADD, &m_nid);
|
||||
m_visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TrayIcon::Hide() {
|
||||
if (m_visible) {
|
||||
Shell_NotifyIcon(NIM_DELETE, &m_nid);
|
||||
m_visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TrayIcon::UpdateWebUrl(const std::wstring& url) {
|
||||
m_web_url = url;
|
||||
// 重新创建菜单以更新Web URL显示
|
||||
DestroyMenu();
|
||||
CreateMenu();
|
||||
}
|
||||
|
||||
void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) {
|
||||
m_show_window_callback = callback;
|
||||
}
|
||||
|
||||
void TrayIcon::SetExitCallback(const ExitCallback &callback) {
|
||||
m_exit_callback = callback;
|
||||
}
|
||||
|
||||
void TrayIcon::CreateMenu() {
|
||||
if (m_menu) {
|
||||
DestroyMenu();
|
||||
}
|
||||
|
||||
m_menu = CreatePopupMenu();
|
||||
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
|
||||
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;
|
||||
AppendMenu(m_menu, MF_STRING, 1002, webText.c_str());
|
||||
AppendMenu(m_menu, MF_SEPARATOR, 0, NULL);
|
||||
}
|
||||
|
||||
AppendMenu(m_menu, MF_STRING, 1003, L"退出");
|
||||
}
|
||||
|
||||
void TrayIcon::DestroyMenu() {
|
||||
if (m_menu) {
|
||||
::DestroyMenu(m_menu);
|
||||
m_menu = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TrayIcon::ShowContextMenu() const {
|
||||
if (!m_menu) return;
|
||||
|
||||
POINT pt;
|
||||
GetCursorPos(&pt);
|
||||
SetForegroundWindow(m_hwnd);
|
||||
TrackPopupMenu(m_menu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hwnd, NULL);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
auto* tray = reinterpret_cast<TrayIcon*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
|
||||
switch (msg) {
|
||||
case WM_APP + 1: // 托盘图标消息
|
||||
switch (LOWORD(lParam)) {
|
||||
case WM_LBUTTONDBLCLK:
|
||||
if (tray && tray->m_show_window_callback) {
|
||||
tray->m_show_window_callback();
|
||||
}
|
||||
break;
|
||||
case WM_RBUTTONUP:
|
||||
if (tray) {
|
||||
tray->ShowContextMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
if (tray) {
|
||||
switch (LOWORD(wParam)) {
|
||||
case 1001: // 显示主窗口
|
||||
if (tray->m_show_window_callback) {
|
||||
tray->m_show_window_callback();
|
||||
}
|
||||
break;
|
||||
case 1002: // 打开Web页面
|
||||
if (!tray->m_web_url.empty()) {
|
||||
ShellExecute(NULL, L"open", tray->m_web_url.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||||
}
|
||||
break;
|
||||
case 1003: // 退出
|
||||
if (tray->m_exit_callback) {
|
||||
tray->m_exit_callback();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#include "Units.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
std::wstring StringToWide(const std::string& str) {
|
||||
if (str.empty()) return L"";
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
||||
std::wstring wstr(size, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], size);
|
||||
return wstr;
|
||||
}
|
||||
|
||||
std::string WideToString(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return "";
|
||||
int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
std::string str(size, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], size, nullptr, nullptr);
|
||||
return str;
|
||||
}
|
||||
|
||||
void SetAutoStart(bool enable) {
|
||||
HKEY hKey;
|
||||
LPCWSTR path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_WRITE, &hKey) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
WCHAR exePath[MAX_PATH];
|
||||
GetModuleFileName(NULL, exePath, MAX_PATH);
|
||||
|
||||
if (enable) {
|
||||
RegSetValueEx(hKey, L"CLIManager", 0, REG_SZ,
|
||||
(BYTE*)exePath, (wcslen(exePath) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
else {
|
||||
RegDeleteValue(hKey, L"CLIManager");
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
bool IsAutoStartEnabled() {
|
||||
HKEY hKey;
|
||||
LPCWSTR path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
DWORD type, size = 0;
|
||||
bool exists = (RegQueryValueEx(hKey, L"CLIManager", NULL, &type, NULL, &size) == ERROR_SUCCESS);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return exists;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,11 @@
|
|||
#include "Manager.h"
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
|
||||
Manager manager;
|
||||
if (!manager.Initialize()) {
|
||||
return 1;
|
||||
}
|
||||
manager.Run();
|
||||
manager.Shutdown();
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue