UP 历史命令配置支持

main
机械师 2025-09-08 16:51:20 +08:00
parent 534e5c0850
commit 7b6c87f2b0
9 changed files with 751 additions and 240 deletions

View File

@ -85,7 +85,7 @@ if(IMGUI_BACKENDS STREQUAL "win32_dx11")
endif() endif()
# #
#set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
# LINK_FLAGS "-static -Wl,-subsystem,windows" LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
#) )
#

View File

@ -4,6 +4,7 @@
#include "CLIProcess.h" #include "CLIProcess.h"
#include <string> #include <string>
#include <map> #include <map>
#include <vector>
#include <imgui.h> #include <imgui.h>
class AppState { class AppState {
@ -15,6 +16,12 @@ public:
void SaveSettings(); void SaveSettings();
void ApplySettings(); void ApplySettings();
// 新增:启动命令历史记录管理
void AddCommandToHistory(const std::string& command);
void RemoveCommandFromHistory(int index);
void ClearCommandHistory();
const std::vector<std::string>& GetCommandHistory() const { return command_history; }
bool show_main_window; bool show_main_window;
bool auto_start; bool auto_start;
CLIProcess cli_process; CLIProcess cli_process;
@ -33,9 +40,13 @@ public:
std::map<std::string, std::string> environment_variables; std::map<std::string, std::string> environment_variables;
bool use_custom_environment; bool use_custom_environment;
// 新增:输出编码相关配置 // 输出编码相关配置
OutputEncoding output_encoding; OutputEncoding output_encoding;
// 新增:启动命令历史记录
std::vector<std::string> command_history;
int max_command_history;
bool settings_dirty; bool settings_dirty;
private: private:
@ -43,9 +54,13 @@ private:
std::string SerializeEnvironmentVariables() const; std::string SerializeEnvironmentVariables() const;
void DeserializeEnvironmentVariables(const std::string& serialized); void DeserializeEnvironmentVariables(const std::string& serialized);
// 新增:编码序列化辅助函数 // 编码序列化辅助函数
std::string SerializeOutputEncoding() const; std::string SerializeOutputEncoding() const;
void DeserializeOutputEncoding(const std::string& serialized); void DeserializeOutputEncoding(const std::string& serialized);
// 新增:命令历史记录序列化辅助函数
std::string SerializeCommandHistory() const;
void DeserializeCommandHistory(const std::string& serialized);
}; };
#endif // APP_STATE_H #endif // APP_STATE_H

View File

@ -6,16 +6,33 @@
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <map> #include <map>
#include <windows.h>
// 新增:输出编码枚举 #ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#endif
// 输出编码枚举
enum class OutputEncoding { enum class OutputEncoding {
AUTO_DETECT =0, AUTO_DETECT = 0,
UTF8, UTF8,
#ifdef _WIN32
GBK, GBK,
GB2312, GB2312,
BIG5, BIG5,
SHIFT_JIS, SHIFT_JIS,
#else
// Unix/Linux 常见编码
ISO_8859_1,
GB18030,
BIG5,
EUC_JP,
#endif
}; };
class CLIProcess { class CLIProcess {
@ -26,7 +43,7 @@ public:
void SetMaxLogLines(int max_lines); void SetMaxLogLines(int max_lines);
void SetStopCommand(const std::string& command, int timeout_ms = 5000); void SetStopCommand(const std::string& command, int timeout_ms = 5000);
void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars); void SetEnvironmentVariables(const std::map<std::string, std::string>& env_vars);
void SetOutputEncoding(OutputEncoding encoding); // 新增:设置输出编码 void SetOutputEncoding(OutputEncoding encoding);
void Start(const std::string& command); void Start(const std::string& command);
void Stop(); void Stop();
@ -47,7 +64,7 @@ public:
void RemoveEnvironmentVariable(const std::string& key); void RemoveEnvironmentVariable(const std::string& key);
void ClearEnvironmentVariables(); void ClearEnvironmentVariables();
// 新增:编码相关接口 // 编码相关接口
OutputEncoding GetOutputEncoding() const; OutputEncoding GetOutputEncoding() const;
static std::string GetEncodingName(OutputEncoding encoding); static std::string GetEncodingName(OutputEncoding encoding);
static std::vector<std::pair<OutputEncoding, std::string>> GetSupportedEncodings(); static std::vector<std::pair<OutputEncoding, std::string>> GetSupportedEncodings();
@ -57,9 +74,11 @@ private:
void CloseProcessHandles(); void CloseProcessHandles();
void CleanupResources(); void CleanupResources();
// 新增:编码转换相关方法 // 编码转换相关方法
std::string ConvertToUTF8(const std::string& input, OutputEncoding encoding); static std::string ConvertToUTF8(std::string& input, OutputEncoding encoding);
std::string DetectAndConvertToUTF8(const std::string& input); std::string DetectAndConvertToUTF8(std::string& input);
#ifdef _WIN32
static UINT GetCodePageFromEncoding(OutputEncoding encoding); static UINT GetCodePageFromEncoding(OutputEncoding encoding);
static bool IsValidUTF8(const std::string& str); static bool IsValidUTF8(const std::string& str);
@ -68,6 +87,17 @@ private:
HANDLE hWritePipe_{}; HANDLE hWritePipe_{};
HANDLE hReadPipe_stdin_{}; HANDLE hReadPipe_stdin_{};
HANDLE hWritePipe_stdin_; HANDLE hWritePipe_stdin_;
#else
// Unix/Linux 进程管理
pid_t process_pid_;
int pipe_stdout_[2];
int pipe_stdin_[2];
bool process_running_;
// Unix 编码转换辅助函数
std::string ConvertUnixEncoding(const std::string& input, const std::string& from_encoding);
static std::string GetUnixEncodingName(OutputEncoding encoding);
#endif
mutable std::mutex logs_mutex_; mutable std::mutex logs_mutex_;
std::vector<std::string> logs_; std::vector<std::string> logs_;
@ -76,7 +106,7 @@ private:
std::thread output_thread_; std::thread output_thread_;
// 停止命令相关 // 停止命令相关
std::mutex stop_mutex_; mutable std::mutex stop_mutex_;
std::string stop_command_; std::string stop_command_;
int stop_timeout_ms_; int stop_timeout_ms_;
@ -84,7 +114,7 @@ private:
mutable std::mutex env_mutex_; mutable std::mutex env_mutex_;
std::map<std::string, std::string> environment_variables_; std::map<std::string, std::string> environment_variables_;
// 新增:编码相关 // 编码相关
mutable std::mutex encoding_mutex_; mutable std::mutex encoding_mutex_;
OutputEncoding output_encoding_; OutputEncoding output_encoding_;
}; };

View File

@ -10,12 +10,20 @@
#include "imgui_impl_win32.h" #include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h" #include "imgui_impl_dx11.h"
#else #else
#ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32 #define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h> #include <GLFW/glfw3native.h>
#include <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_glfw.h"
#include "imgui_impl_opengl3.h" #include "imgui_impl_opengl3.h"
#include <windows.h>
#endif #endif
#include <memory> #include <memory>
@ -33,6 +41,8 @@ public:
void OnTrayExit(); void OnTrayExit();
AppState m_app_state; AppState m_app_state;
private: private:
// UI渲染 // UI渲染
void RenderUI(); void RenderUI();
@ -41,7 +51,7 @@ private:
void RenderSettingsMenu(); void RenderSettingsMenu();
void RenderStopCommandSettings(); void RenderStopCommandSettings();
void RenderEnvironmentVariablesSettings(); void RenderEnvironmentVariablesSettings();
void RenderOutputEncodingSettings(); // 新增输出编码设置UI void RenderOutputEncodingSettings();
// 事件处理 // 事件处理
void HandleMessages(); void HandleMessages();
@ -75,10 +85,12 @@ private:
// 托盘相关 // 托盘相关
bool InitializeTray(); bool InitializeTray();
void CleanupTray(); void CleanupTray();
#ifdef _WIN32
static HWND CreateHiddenWindow(); static HWND CreateHiddenWindow();
HWND m_tray_hwnd = nullptr;
#endif
std::unique_ptr<TrayIcon> m_tray; std::unique_ptr<TrayIcon> m_tray;
HWND m_tray_hwnd = nullptr;
// 控制标志 // 控制标志
bool m_should_exit = false; bool m_should_exit = false;
@ -92,6 +104,8 @@ private:
char env_value_input_[512] = {}; char env_value_input_[512] = {};
bool show_env_settings_ = false; bool show_env_settings_ = false;
// 新增:编码设置UI状态 // 编码设置UI状态
bool show_encoding_settings_ = false; bool show_encoding_settings_ = false;
// 历史命令UI状态
bool show_command_history_;
}; };

View File

@ -1,6 +1,10 @@
#pragma once #pragma once
#ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <shellapi.h> #include <shellapi.h>
#endif
#include <string> #include <string>
#include <functional> #include <functional>
@ -10,33 +14,63 @@ public:
using ShowWindowCallback = std::function<void()>; using ShowWindowCallback = std::function<void()>;
using ExitCallback = std::function<void()>; using ExitCallback = std::function<void()>;
#ifdef _WIN32
TrayIcon(HWND hwnd, HICON icon); TrayIcon(HWND hwnd, HICON icon);
void UpdateWebUrl(const std::wstring& url);
// 静态窗口过程
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
#else
TrayIcon(void* app_delegate, void* icon);
void UpdateWebUrl(const std::string& url);
void OnMacMenuAction(int action);
#endif
~TrayIcon(); ~TrayIcon();
void Show(); void Show();
void Hide(); void Hide();
void UpdateWebUrl(const std::wstring& url);
// 设置回调函数 // 设置回调函数
void SetShowWindowCallback(const ShowWindowCallback &callback); void SetShowWindowCallback(const ShowWindowCallback &callback);
void SetExitCallback(const ExitCallback &callback); void SetExitCallback(const ExitCallback &callback);
// 静态窗口过程
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
private: private:
void CreateMenu(); void CreateMenu();
void DestroyMenu(); void DestroyMenu();
#ifdef _WIN32
void ShowContextMenu() const; void ShowContextMenu() const;
HWND m_hwnd; HWND m_hwnd;
HICON m_icon; HICON m_icon;
NOTIFYICONDATA m_nid{}; NOTIFYICONDATA m_nid{};
std::wstring m_web_url; std::wstring m_web_url;
bool m_visible;
HMENU m_menu; HMENU m_menu;
#else
void ShowMacTrayIcon();
void HideMacTrayIcon();
void CreateMacMenu();
void DestroyMacMenu();
void* m_app_delegate;
void* m_icon;
std::string m_web_url;
#endif
bool m_visible;
// 回调函数 // 回调函数
ShowWindowCallback m_show_window_callback; ShowWindowCallback m_show_window_callback;
ExitCallback m_exit_callback; ExitCallback m_exit_callback;
}; };
#ifdef __cplusplus
extern "C" {
#endif
// C 接口函数声明,供 Objective-C 调用
void TrayIconMenuCallback(void* tray_instance, int action);
#ifdef __cplusplus
}
#endif

View File

@ -11,7 +11,8 @@ AppState::AppState() :
stop_timeout_ms(5000), stop_timeout_ms(5000),
use_stop_command(false), use_stop_command(false),
use_custom_environment(false), use_custom_environment(false),
output_encoding(OutputEncoding::AUTO_DETECT), // 新增:默认自动检测编码 output_encoding(OutputEncoding::AUTO_DETECT),
max_command_history(20), // 新增:最大历史记录数量
settings_dirty(false) { settings_dirty(false) {
strcpy_s(command_input, "cmd.exe"); strcpy_s(command_input, "cmd.exe");
strcpy_s(web_url, "http://localhost:8080"); strcpy_s(web_url, "http://localhost:8080");
@ -19,6 +20,90 @@ AppState::AppState() :
memset(send_command, 0, sizeof(send_command)); memset(send_command, 0, sizeof(send_command));
} }
// 新增:添加命令到历史记录
void AppState::AddCommandToHistory(const std::string& command) {
if (command.empty()) return;
// 移除重复的命令
auto it = std::find(command_history.begin(), command_history.end(), command);
if (it != command_history.end()) {
command_history.erase(it);
}
// 添加到开头
command_history.insert(command_history.begin(), command);
// 限制历史记录数量
if (command_history.size() > static_cast<size_t>(max_command_history)) {
command_history.resize(max_command_history);
}
settings_dirty = true;
}
// 新增:从历史记录中移除命令
void AppState::RemoveCommandFromHistory(int index) {
if (index >= 0 && index < static_cast<int>(command_history.size())) {
command_history.erase(command_history.begin() + index);
settings_dirty = true;
}
}
// 新增:清空命令历史记录
void AppState::ClearCommandHistory() {
command_history.clear();
settings_dirty = true;
}
// 新增:序列化命令历史记录
std::string AppState::SerializeCommandHistory() const {
std::ostringstream oss;
bool first = true;
for (const auto& command : command_history) {
if (!first) {
oss << "|";
}
// 转义分隔符
std::string escaped_command = command;
size_t pos = 0;
while ((pos = escaped_command.find("|", pos)) != std::string::npos) {
escaped_command.replace(pos, 1, "\\|");
pos += 2;
}
oss << escaped_command;
first = false;
}
return oss.str();
}
// 新增:反序列化命令历史记录
void AppState::DeserializeCommandHistory(const std::string& serialized) {
command_history.clear();
if (serialized.empty()) return;
std::istringstream iss(serialized);
std::string command;
std::string current;
for (size_t i = 0; i < serialized.length(); ++i) {
if (serialized[i] == '\\' && i + 1 < serialized.length() && serialized[i + 1] == '|') {
current += '|';
++i; // 跳过下一个字符
} else if (serialized[i] == '|') {
if (!current.empty()) {
command_history.push_back(current);
current.clear();
}
} else {
current += serialized[i];
}
}
if (!current.empty()) {
command_history.push_back(current);
}
}
std::string AppState::SerializeEnvironmentVariables() const { std::string AppState::SerializeEnvironmentVariables() const {
std::ostringstream oss; std::ostringstream oss;
bool first = true; bool first = true;
@ -128,10 +213,17 @@ void AppState::LoadSettings() {
else if (key == "EnvironmentVariables") { else if (key == "EnvironmentVariables") {
DeserializeEnvironmentVariables(value); DeserializeEnvironmentVariables(value);
} }
// 新增:输出编码配置的加载
else if (key == "OutputEncoding") { else if (key == "OutputEncoding") {
DeserializeOutputEncoding(value); DeserializeOutputEncoding(value);
} }
// 新增:命令历史记录配置的加载
else if (key == "CommandHistory") {
DeserializeCommandHistory(value);
}
else if (key == "MaxCommandHistory") {
max_command_history = std::stoi(value);
max_command_history = std::max(5, std::min(max_command_history, 100));
}
} }
} }
} }
@ -158,9 +250,13 @@ void AppState::SaveSettings() {
file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n"; file << "UseCustomEnvironment=" << (use_custom_environment ? "1" : "0") << "\n";
file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n"; file << "EnvironmentVariables=" << SerializeEnvironmentVariables() << "\n";
// 新增:输出编码配置的保存 // 输出编码配置的保存
file << "OutputEncoding=" << SerializeOutputEncoding() << "\n"; file << "OutputEncoding=" << SerializeOutputEncoding() << "\n";
// 新增:命令历史记录配置的保存
file << "CommandHistory=" << SerializeCommandHistory() << "\n";
file << "MaxCommandHistory=" << max_command_history << "\n";
file.close(); file.close();
settings_dirty = false; settings_dirty = false;

View File

@ -2,59 +2,88 @@
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#ifdef _WIN32
#include "Units.h" #include "Units.h"
#else
#include <iconv.h>
#include <locale.h>
#include <langinfo.h>
#include <spawn.h>
extern char **environ;
#endif
CLIProcess::CLIProcess() { CLIProcess::CLIProcess() {
#ifdef _WIN32
ZeroMemory(&pi_, sizeof(pi_)); ZeroMemory(&pi_, sizeof(pi_));
max_log_lines_ = 1000;
hWritePipe_stdin_ = nullptr; hWritePipe_stdin_ = nullptr;
#else
process_pid_ = -1;
pipe_stdout_[0] = pipe_stdout_[1] = -1;
pipe_stdin_[0] = pipe_stdin_[1] = -1;
process__running_ = false;
#endif
max_log_lines_ = 1000;
stop_timeout_ms_ = 5000; stop_timeout_ms_ = 5000;
output_encoding_ = OutputEncoding::AUTO_DETECT; // 新增:默认自动检测编码 output_encoding_ = OutputEncoding::AUTO_DETECT;
} }
CLIProcess::~CLIProcess() { CLIProcess::~CLIProcess() {
Stop(); Stop();
CleanupResources();
} }
// 设置输出编码
// 新增:设置输出编码
void CLIProcess::SetOutputEncoding(OutputEncoding encoding) { void CLIProcess::SetOutputEncoding(OutputEncoding encoding) {
std::lock_guard<std::mutex> lock(encoding_mutex_); std::lock_guard<std::mutex> lock(encoding_mutex_);
output_encoding_ = encoding; output_encoding_ = encoding;
AddLog("输出编码已设置为: " + GetEncodingName(encoding)); AddLog("输出编码已设置为: " + GetEncodingName(encoding));
} }
// 新增:获取输出编码 // 获取输出编码
OutputEncoding CLIProcess::GetOutputEncoding() const { OutputEncoding CLIProcess::GetOutputEncoding() const {
std::lock_guard<std::mutex> lock(encoding_mutex_); std::lock_guard<std::mutex> lock(encoding_mutex_);
return output_encoding_; return output_encoding_;
} }
// 新增:获取编码名称 // 获取编码名称
std::string CLIProcess::GetEncodingName(const OutputEncoding encoding) { std::string CLIProcess::GetEncodingName(const OutputEncoding encoding) {
switch (encoding) { switch (encoding) {
case OutputEncoding::AUTO_DETECT: return "自动检测";
case OutputEncoding::UTF8: return "UTF-8"; case OutputEncoding::UTF8: return "UTF-8";
#ifdef _WIN32
case OutputEncoding::GBK: return "GBK"; case OutputEncoding::GBK: return "GBK";
case OutputEncoding::GB2312: return "GB2312"; case OutputEncoding::GB2312: return "GB2312";
case OutputEncoding::BIG5: return "Big5"; case OutputEncoding::BIG5: return "BIG5";
case OutputEncoding::SHIFT_JIS: return "Shift-JIS"; case OutputEncoding::SHIFT_JIS: return "Shift_JIS";
case OutputEncoding::AUTO_DETECT: return "自动检测"; #else
default: return "未知"; case OutputEncoding::ISO_8859_1: return "ISO-8859-1";
case OutputEncoding::GB18030: return "GB18030";
case OutputEncoding::BIG5: return "BIG5";
case OutputEncoding::EUC_JP: return "EUC-JP";
#endif
default: return "未知编码";
} }
} }
// 新增:获取支持的编码列表 // 获取支持的编码列表
std::vector<std::pair<OutputEncoding, std::string>> CLIProcess::GetSupportedEncodings() { std::vector<std::pair<OutputEncoding, std::string>> CLIProcess::GetSupportedEncodings() {
return { return {
{OutputEncoding::AUTO_DETECT, "自动检测"}, {OutputEncoding::AUTO_DETECT, "自动检测"},
{OutputEncoding::UTF8, "UTF-8"}, {OutputEncoding::UTF8, "UTF-8"},
#ifdef _WIN32
{OutputEncoding::GBK, "GBK (简体中文)"}, {OutputEncoding::GBK, "GBK (简体中文)"},
{OutputEncoding::GB2312, "GB2312 (简体中文)"}, {OutputEncoding::GB2312, "GB2312 (简体中文)"},
{OutputEncoding::BIG5, "Big5 (繁体中文)"}, {OutputEncoding::BIG5, "Big5 (繁体中文)"},
{OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"} {OutputEncoding::SHIFT_JIS, "Shift-JIS (日文)"},
#else
{OutputEncoding::ISO_8859_1, "ISO-8859-1"},
{OutputEncoding::GB18030, "GB18030"},
{OutputEncoding::BIG5, "BIG5"},
{OutputEncoding::EUC_JP, "EUC-JP"},
#endif
}; };
} }
// 新增:根据编码获取代码页 // 根据编码获取代码页
UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) { UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
switch (encoding) { switch (encoding) {
case OutputEncoding::GBK: return 936; case OutputEncoding::GBK: return 936;
@ -65,7 +94,7 @@ UINT CLIProcess::GetCodePageFromEncoding(const OutputEncoding encoding) {
} }
} }
// 新增:检查是否为有效的UTF-8 // 检查是否为有效的UTF-8
bool CLIProcess::IsValidUTF8(const std::string& str) { bool CLIProcess::IsValidUTF8(const std::string& str) {
const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str()); const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
size_t len = str.length(); size_t len = str.length();
@ -93,8 +122,8 @@ bool CLIProcess::IsValidUTF8(const std::string& str) {
return true; return true;
} }
// 新增:转换到UTF-8 // 转换到UTF-8
std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding encoding) { std::string CLIProcess::ConvertToUTF8(std::string& input, const OutputEncoding encoding) {
if (input.empty()) return input; if (input.empty()) return input;
// 如果已经是UTF-8编码直接返回 // 如果已经是UTF-8编码直接返回
@ -130,8 +159,8 @@ std::string CLIProcess::ConvertToUTF8(const std::string& input, OutputEncoding e
return std::string(utf8Str.data()); return std::string(utf8Str.data());
} }
// 新增:自动检测并转换到UTF-8 // 自动检测并转换到UTF-8
std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) { std::string CLIProcess::DetectAndConvertToUTF8(std::string& input) {
if (input.empty()) return input; if (input.empty()) return input;
// 首先检查是否已经是有效的UTF-8 // 首先检查是否已经是有效的UTF-8
@ -140,7 +169,7 @@ std::string CLIProcess::DetectAndConvertToUTF8(const std::string& input) {
} }
// 尝试不同的编码进行转换 // 尝试不同的编码进行转换
std::vector<OutputEncoding> encodingsToTry = { const std::vector encodingsToTry = {
OutputEncoding::GBK, OutputEncoding::GBK,
OutputEncoding::GB2312, OutputEncoding::GB2312,
OutputEncoding::BIG5, OutputEncoding::BIG5,
@ -245,190 +274,185 @@ void CLIProcess::ClearEnvironmentVariables() {
} }
void CLIProcess::Start(const std::string& command) { void CLIProcess::Start(const std::string& command) {
if (IsRunning()) return;
Stop(); Stop();
#ifdef _WIN32
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
SECURITY_ATTRIBUTES sa; HANDLE hReadTmp = nullptr;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); HANDLE hWriteTmp = nullptr;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;
if (!CreatePipe(&hReadPipe_, &hWritePipe_, &sa, 0)) { if (!CreatePipe(&hReadTmp, &hWriteTmp, &saAttr, 0)) {
AddLog("创建输出管道失败"); return;
}
if (!SetHandleInformation(hReadTmp, HANDLE_FLAG_INHERIT, 0)) {
CloseHandle(hReadTmp);
CloseHandle(hWriteTmp);
return; return;
} }
if (!CreatePipe(&hReadPipe_stdin_, &hWritePipe_stdin_, &sa, 0)) { HANDLE hReadTmp_stdin = nullptr;
AddLog("创建输入管道失败"); HANDLE hWriteTmp_stdin = nullptr;
CloseHandle(hReadPipe_); if (!CreatePipe(&hReadTmp_stdin, &hWriteTmp_stdin, &saAttr, 0)) {
CloseHandle(hWritePipe_); CloseHandle(hReadTmp);
CloseHandle(hWriteTmp);
return;
}
if (!SetHandleInformation(hWriteTmp_stdin, HANDLE_FLAG_INHERIT, 0)) {
CloseHandle(hReadTmp);
CloseHandle(hWriteTmp);
CloseHandle(hReadTmp_stdin);
CloseHandle(hWriteTmp_stdin);
return; return;
} }
STARTUPINFO si; STARTUPINFOA siStartInfo;
ZeroMemory(&si, sizeof(si)); ZeroMemory(&siStartInfo, sizeof(STARTUPINFOA));
si.cb = sizeof(si); siStartInfo.cb = sizeof(STARTUPINFOA);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; siStartInfo.hStdError = hWriteTmp;
si.hStdOutput = hWritePipe_; siStartInfo.hStdOutput = hWriteTmp;
si.hStdError = hWritePipe_; siStartInfo.hStdInput = hReadTmp_stdin;
si.hStdInput = hReadPipe_stdin_; siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
ZeroMemory(&pi_, sizeof(pi_));
// 转换命令为宽字符 PROCESS_INFORMATION piProcInfo;
std::wstring wcmd = StringToWide(command); ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// CreateProcess需要可修改的字符串 // Prepare environment block
std::vector<wchar_t> cmdBuffer(wcmd.begin(), wcmd.end()); std::string env_block;
cmdBuffer.push_back(L'\0'); {
std::lock_guard<std::mutex> lock(env_mutex_);
for (const auto& kv : environment_variables_) {
env_block += kv.first + "=" + kv.second + '\0';
}
env_block += '\0';
}
// 使用Windows API设置环境变量 BOOL bSuccess = CreateProcessA(
std::vector<std::pair<std::string, std::string>> originalEnvVars; nullptr,
bool envVarsSet = false; const_cast<char*>(command.c_str()),
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW,
env_block.empty() ? nullptr : (LPVOID)env_block.data(),
nullptr,
&siStartInfo,
&piProcInfo);
if (!environment_variables_.empty()) { CloseHandle(hWriteTmp);
envVarsSet = true; CloseHandle(hReadTmp_stdin);
for (const auto& pair : environment_variables_) { if (!bSuccess) {
if (!pair.first.empty()) { CloseHandle(hReadTmp);
// 保存原始值(如果存在) CloseHandle(hWriteTmp_stdin);
DWORD bufferSize = GetEnvironmentVariableA(pair.first.c_str(), nullptr, 0); return;
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, "");
}
// 设置新的环境变量值 CloseProcessHandles();
if (SetEnvironmentVariableA(pair.first.c_str(), pair.second.c_str())) {
// AddLog("设置环境变量: " + pair.first + "=" + pair.second); pi_ = piProcInfo;
} else { hReadPipe_ = hReadTmp;
AddLog("设置环境变量失败: " + pair.first + " (错误代码: " + std::to_string(GetLastError()) + ")"); hWritePipe_stdin_ = hWriteTmp_stdin;
}
// Start output reading thread
output_thread_ = std::thread(&CLIProcess::ReadOutput, this);
#else
// Unix/Linux implementation using posix_spawn
int pipe_out[2];
int pipe_in[2];
if (pipe(pipe_out) < 0 || pipe(pipe_in) < 0) {
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]);
// Prepare environment variables
if (!environment_variables_.empty()) {
for (const auto& kv : environment_variables_) {
setenv(kv.first.c_str(), kv.second.c_str(), 1);
} }
} }
// AddLog("环境变量设置完成,数量: " + std::to_string(environment_variables_.size())); execl("/bin/sh", "sh", "-c", command.c_str(), (char*)nullptr);
} else { _exit(127);
AddLog("未设置自定义环境变量,使用默认环境");
} }
else if (pid > 0) {
// parent process
close(pipe_out[1]);
close(pipe_in[0]);
BOOL result = CreateProcess( process_pid_ = pid;
nullptr, // lpApplicationName pipe_stdout_[0] = pipe_out[0];
cmdBuffer.data(), // lpCommandLine pipe_stdout_[1] = pipe_out[1]; // closed already in parent, but keep for safety
nullptr, // lpProcessAttributes pipe_stdin_[0] = pipe_in[0]; // closed already in parent, but keep for safety
nullptr, // lpThreadAttributes pipe_stdin_[1] = pipe_in[1];
TRUE, // bInheritHandles
CREATE_NO_WINDOW, // dwCreationFlags
nullptr, // lpEnvironment (使用nullptr让子进程继承当前环境)
nullptr, // lpCurrentDirectory
&si, // lpStartupInfo
&pi_ // lpProcessInformation
);
// 恢复原始环境变量 process_running_ = true;
if (envVarsSet) {
for (const auto& pair : originalEnvVars) { // Start output reading thread
if (pair.second.empty()) { output_thread_ = std::thread(&CLIProcess::ReadOutput, this);
// 原来不存在,删除变量
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;
} }
#endif
} }
void CLIProcess::Stop() { void CLIProcess::Stop() {
bool useStopCommand = false; #ifdef _WIN32
std::string stopCmd; std::lock_guard<std::mutex> lock(stop_mutex_);
int timeout = stop_timeout_ms_; if (pi_.hProcess != nullptr) {
if (!stop_command_.empty()) {
// 检查是否设置了停止命令 SendCommand(stop_command_);
{ // Wait for process to exit within timeout
std::lock_guard<std::mutex> lock(stop_mutex_); WaitForSingleObject(pi_.hProcess, stop_timeout_ms_);
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); TerminateProcess(pi_.hProcess, 0);
WaitForSingleObject(pi_.hProcess, INFINITE);
CloseProcessHandles(); CloseProcessHandles();
AddLog("进程已强制终止");
} }
#else
if (process_running_) {
std::lock_guard<std::mutex> lock(stop_mutex_);
if (!stop_command_.empty()) {
SendCommand(stop_command_);
// Wait for termination or timeout
int status = 0;
for (int i = 0; i < stop_timeout_ms_/100; ++i) {
pid_t result = waitpid(process_pid_, &status, WNOHANG);
if (result == process_pid_) {
process_running_ = false;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
if (process_running_) {
kill(process_pid_, SIGKILL);
waitpid(process_pid_, nullptr, 0);
process_running_ = false;
}
CloseProcessHandles();
}
#endif
// 关闭管道和线程 if (output_thread_.joinable()) {
CleanupResources(); output_thread_.join();
}
} }
// 新增:关闭进程句柄的辅助函数 // 关闭进程句柄的辅助函数
void CLIProcess::CloseProcessHandles() { void CLIProcess::CloseProcessHandles() {
if (pi_.hProcess) { if (pi_.hProcess) {
CloseHandle(pi_.hProcess); CloseHandle(pi_.hProcess);
@ -440,7 +464,7 @@ void CLIProcess::CloseProcessHandles() {
} }
} }
// 新增:清理资源的辅助函数 // 清理资源的辅助函数
void CLIProcess::CleanupResources() { void CLIProcess::CleanupResources() {
// 关闭输入管道写入端(通知进程停止) // 关闭输入管道写入端(通知进程停止)
if (hWritePipe_stdin_) { if (hWritePipe_stdin_) {
@ -494,7 +518,9 @@ const std::vector<std::string>& CLIProcess::GetLogs() const {
return logs_; return logs_;
} }
bool CLIProcess::SendCommand(const std::string& command) {
bool CLIProcess::SendCommand(const std::string &command) {
#ifdef _WIN32
if (!IsRunning() || !hWritePipe_stdin_) { if (!IsRunning() || !hWritePipe_stdin_) {
return false; return false;
} }
@ -506,11 +532,20 @@ bool CLIProcess::SendCommand(const std::string& command) {
static_cast<DWORD>(fullCommand.length()), &bytesWritten, nullptr)) { static_cast<DWORD>(fullCommand.length()), &bytesWritten, nullptr)) {
AddLog("> " + command); AddLog("> " + command);
return true; return true;
} }
return false; return false;
#else
if (!process_running_ || pipe_stdin_[1] < 0) return false;
std::string cmd = command + "\n";
ssize_t written = write(pipe_stdin_[1], cmd.c_str(), cmd.size());
return written == (ssize_t)cmd.size();
#endif
} }
void CLIProcess::CopyLogsToClipboard() const { void CLIProcess::CopyLogsToClipboard() const {
#ifdef _WIN32
std::lock_guard<std::mutex> lock(logs_mutex_); std::lock_guard<std::mutex> lock(logs_mutex_);
if (logs_.empty()) return; if (logs_.empty()) return;
@ -542,13 +577,46 @@ void CLIProcess::CopyLogsToClipboard() const {
} }
CloseClipboard(); CloseClipboard();
} }
#else
// Unix / macOS, use xclip or pbcopy
std::string clipboard_text;
{
std::lock_guard<std::mutex> lock(logs_mutex_);
for (const auto& line : logs_) {
clipboard_text += line + "\n";
}
}
FILE* pipe = popen("pbcopy", "w");
if (!pipe) {
pipe = popen("xclip -selection clipboard", "w");
}
if (pipe) {
fwrite(clipboard_text.c_str(), 1, clipboard_text.size(), pipe);
pclose(pipe);
}
#endif
} }
bool CLIProcess::IsRunning() const { bool CLIProcess::IsRunning() const {
return pi_.hProcess != nullptr; #ifdef _WIN32
if (pi_.hProcess == nullptr) return false;
DWORD status = WaitForSingleObject(pi_.hProcess, 0);
return status == WAIT_TIMEOUT;
#else
if (!process_running_) return false;
int status;
pid_t result = waitpid(process_pid_, &status, WNOHANG);
if (result == 0) return true; // still running
process_running_ = false;
return false;
#endif
} }
void CLIProcess::ReadOutput() { void CLIProcess::ReadOutput() {
#ifdef _WIN32
constexpr int BUFFER_SIZE = 4096; constexpr int BUFFER_SIZE = 4096;
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
DWORD bytesRead; DWORD bytesRead;
@ -562,7 +630,7 @@ void CLIProcess::ReadOutput() {
buffer[bytesRead] = '\0'; buffer[bytesRead] = '\0';
std::string output(buffer); std::string output(buffer);
// 新增:根据设置的编码转换输出 // 根据设置的编码转换输出
OutputEncoding currentEncoding; OutputEncoding currentEncoding;
{ {
std::lock_guard<std::mutex> lock(encoding_mutex_); std::lock_guard<std::mutex> lock(encoding_mutex_);
@ -599,4 +667,25 @@ void CLIProcess::ReadOutput() {
partialLine = convertedOutput.substr(start); partialLine = convertedOutput.substr(start);
} }
} }
#else
const int buffer_size = 4096;
char buffer[buffer_size];
ssize_t bytes_read = 0;
while (process_running_) {
bytes_read = read(pipe_stdout_[0], buffer, buffer_size - 1);
if (bytes_read <= 0) break;
buffer[bytes_read] = '\0';
std::string utf8_str;
{
std::lock_guard<std::mutex> lock(encoding_mutex_);
if (output_encoding_ == OutputEncoding::AUTO_DETECT) {
utf8_str = DetectAndConvertToUTF8(std::string(buffer));
} else {
utf8_str = ConvertToUTF8(std::string(buffer), output_encoding_);
}
}
AddLog(utf8_str);
}
#endif
} }

View File

@ -2,8 +2,8 @@
#include <cstdio> #include <cstdio>
#include <algorithm> #include <algorithm>
#include "Units.h"
#include "Units.h"
Manager::Manager() = default; Manager::Manager() = default;
@ -45,25 +45,56 @@ bool Manager::Initialize() {
style.IndentSpacing = 25.0f; style.IndentSpacing = 25.0f;
style.ScrollbarSize = 15.0f; style.ScrollbarSize = 15.0f;
style.GrabMinSize = 10.0f; style.GrabMinSize = 10.0f;
#ifdef USE_WIN32_BACKEND #ifdef USE_WIN32_BACKEND
ImGui_ImplWin32_Init(m_hwnd); ImGui_ImplWin32_Init(m_hwnd);
ImGui_ImplDX11_Init(m_pd3dDevice, m_pd3dDeviceContext); ImGui_ImplDX11_Init(m_pd3dDevice, m_pd3dDeviceContext);
// ImGui_ImplWin32_EnableDpiAwareness(); m_dpi_scale = ImGui_ImplWin32_GetDpiScaleForHwnd(m_hwnd);
// m_dpi_scale=ImGui_ImplWin32_GetDpiScaleForHwnd(m_hwnd); style.ScaleAllSizes(m_dpi_scale);
// style.ScaleAllSizes(m_dpi_scale);
#else #else
ImGui_ImplGlfw_InitForOpenGL(m_window, true); ImGui_ImplGlfw_InitForOpenGL(m_window, true);
ImGui_ImplOpenGL3_Init(m_glsl_version); ImGui_ImplOpenGL3_Init(m_glsl_version);
#endif #endif
// 加载中文字体 // 加载字体
#ifdef _WIN32
ImFont* font = io.Fonts->AddFontFromFileTTF( ImFont* font = io.Fonts->AddFontFromFileTTF(
"C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/msyh.ttc",
18.0f, 14.0f,
nullptr, nullptr,
io.Fonts->GetGlyphRangesChineseFull() io.Fonts->GetGlyphRangesChineseFull()
); );
IM_ASSERT(font != nullptr); #elif __APPLE__
// macOS 中文字体路径
ImFont* font = io.Fonts->AddFontFromFileTTF(
"/System/Library/Fonts/PingFang.ttc",
14.0f,
nullptr,
io.Fonts->GetGlyphRangesChineseFull()
);
// 备用字体
if (!font) {
font = io.Fonts->AddFontFromFileTTF(
"/System/Library/Fonts/STHeiti Light.ttc",
14.0f,
nullptr,
io.Fonts->GetGlyphRangesChineseFull()
);
}
#else
// Linux 字体路径
ImFont* font = io.Fonts->AddFontFromFileTTF(
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
14.0f,
nullptr,
io.Fonts->GetGlyphRangesChineseFull()
);
#endif
if (!font) {
// 如果没有找到中文字体,使用默认字体
font = io.Fonts->AddFontDefault();
}
// 初始化托盘 // 初始化托盘
if (!InitializeTray()) return false; if (!InitializeTray()) return false;
@ -72,7 +103,12 @@ bool Manager::Initialize() {
m_app_state.LoadSettings(); m_app_state.LoadSettings();
m_app_state.auto_start = IsAutoStartEnabled(); m_app_state.auto_start = IsAutoStartEnabled();
m_app_state.ApplySettings(); m_app_state.ApplySettings();
#ifdef _WIN32
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url)); m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
#else
m_tray->UpdateWebUrl(m_app_state.web_url);
#endif
// 如果开启了开机自启动且有启动命令,则自动启动子进程 // 如果开启了开机自启动且有启动命令,则自动启动子进程
if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) { if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) {
@ -196,19 +232,36 @@ void Manager::RenderSettingsMenu() {
m_app_state.settings_dirty = true; m_app_state.settings_dirty = true;
} }
// 新增:命令历史记录设置
ImGui::Separator();
ImGui::Text("命令历史记录设置");
if (ImGui::InputInt("最大历史记录数", &m_app_state.max_command_history, 5, 10)) {
m_app_state.max_command_history = std::max(5, std::min(m_app_state.max_command_history, 100));
m_app_state.settings_dirty = true;
}
if (ImGui::Button("清空命令历史记录")) {
m_app_state.ClearCommandHistory();
}
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Web设置"); ImGui::Text("Web设置");
if (ImGui::InputText("Web地址", m_app_state.web_url, IM_ARRAYSIZE(m_app_state.web_url))) { if (ImGui::InputText("Web地址", m_app_state.web_url, IM_ARRAYSIZE(m_app_state.web_url))) {
#ifdef _WIN32
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url)); m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
#else
m_tray->UpdateWebUrl(m_app_state.web_url);
#endif
m_app_state.settings_dirty = true; m_app_state.settings_dirty = true;
} }
RenderStopCommandSettings(); RenderStopCommandSettings();
RenderEnvironmentVariablesSettings(); RenderEnvironmentVariablesSettings();
RenderOutputEncodingSettings(); // 新增:渲染编码设置 RenderOutputEncodingSettings();
} }
void Manager::RenderStopCommandSettings() { void Manager::RenderStopCommandSettings() {
ImGui::Separator(); ImGui::Separator();
ImGui::Text("停止命令设置"); ImGui::Text("停止命令设置");
@ -233,10 +286,8 @@ void Manager::RenderStopCommandSettings() {
ImGui::TextWrapped("说明:启用后,停止程序时会先发送指定命令,等待程序优雅退出。超时后将强制终止。"); ImGui::TextWrapped("说明:启用后,停止程序时会先发送指定命令,等待程序优雅退出。超时后将强制终止。");
} else { } else {
ImGui::BeginDisabled(true); 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("说明:禁用时将直接强制终止程序。"); ImGui::TextWrapped("说明:禁用时将直接强制终止程序。");
ImGui::EndDisabled();
} }
} }
@ -360,29 +411,97 @@ void Manager::RenderOutputEncodingSettings() {
ImGui::BulletText("GBK/GB2312适用于中文Windows系统的程序"); ImGui::BulletText("GBK/GB2312适用于中文Windows系统的程序");
ImGui::BulletText("Big5适用于繁体中文程序"); ImGui::BulletText("Big5适用于繁体中文程序");
ImGui::BulletText("Shift-JIS适用于日文程序"); ImGui::BulletText("Shift-JIS适用于日文程序");
// // 测试按钮
// if (ImGui::Button("测试编码转换")) {
// std::string testText = "测试中编码转换显示中文English, 日本語, 한국어";
// m_app_state.cli_process.TestOutputEncoding(testText);
// }
} }
void Manager::RenderMainContent() { void Manager::RenderMainContent() {
float buttonWidth = 80.0f * m_dpi_scale; float buttonWidth = 80.0f * m_dpi_scale;
float buttonHeight = 40.0f * m_dpi_scale; float buttonHeight = 40.0f * m_dpi_scale;
float inputWidth = ImGui::GetContentRegionAvail().x * 0.6f; float inputWidth = ImGui::GetContentRegionAvail().x * 0.5f; // 调整输入框宽度为50%
// 启动命令输入 // 启动命令输入区域
ImGui::BeginGroup();
ImGui::Text("启动命令");
// 命令输入框和历史记录按钮
ImGui::SetNextItemWidth(inputWidth); ImGui::SetNextItemWidth(inputWidth);
if (ImGui::InputText("启动命令", m_app_state.command_input, IM_ARRAYSIZE(m_app_state.command_input))) { if (ImGui::InputText("##启动命令", m_app_state.command_input, IM_ARRAYSIZE(m_app_state.command_input))) {
m_app_state.settings_dirty = true; m_app_state.settings_dirty = true;
} }
// 控制按钮 ImGui::SameLine();
if (ImGui::Button("历史记录", ImVec2(100.0f * m_dpi_scale, 0))) {
show_command_history_ = !show_command_history_;
}
// 显示命令历史记录下拉列表
if (show_command_history_) {
const auto& history = m_app_state.GetCommandHistory();
if (!history.empty()) {
ImGui::Indent();
ImGui::Text("选择历史命令 (%d个):", static_cast<int>(history.size()));
if (ImGui::BeginChild("CommandHistory", ImVec2(0, 120), true)) {
for (int i = 0; i < static_cast<int>(history.size()); ++i) {
ImGui::PushID(i);
// 选择按钮
if (ImGui::Button("选择", ImVec2(50.0f * m_dpi_scale, 0))) {
strncpy_s(m_app_state.command_input, history[i].c_str(), sizeof(m_app_state.command_input) - 1);
show_command_history_ = false;
ImGui::PopID();
break;
}
ImGui::SameLine();
// 显示命令内容(限制显示长度)
std::string displayCommand = history[i];
if (displayCommand.length() > 60) {
displayCommand = displayCommand.substr(0, 57) + "...";
}
ImGui::TextUnformatted(displayCommand.c_str());
// 鼠标悬停时显示完整命令
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", history[i].c_str());
}
ImGui::SameLine();
// 删除按钮
if (ImGui::SmallButton("删除")) {
m_app_state.RemoveCommandFromHistory(i);
ImGui::PopID();
continue;
}
ImGui::PopID();
}
}
ImGui::EndChild();
// 操作按钮
if (ImGui::Button("清空所有历史记录")) {
m_app_state.ClearCommandHistory();
}
ImGui::SameLine();
ImGui::Unindent();
} else {
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "暂无启动命令历史");
}
}
ImGui::EndGroup();
ImGui::Spacing();
ImGui::BeginGroup(); ImGui::BeginGroup();
if (ImGui::Button("启动", ImVec2(buttonWidth, buttonHeight))) { if (ImGui::Button("启动", ImVec2(buttonWidth, buttonHeight))) {
m_app_state.cli_process.Start(m_app_state.command_input); if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Start(m_app_state.command_input);
m_app_state.AddCommandToHistory(m_app_state.command_input);
}
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) { if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
@ -390,7 +509,10 @@ void Manager::RenderMainContent() {
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) { if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
m_app_state.cli_process.Restart(m_app_state.command_input); if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Restart(m_app_state.command_input);
m_app_state.AddCommandToHistory(m_app_state.command_input);
}
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("清理日志", ImVec2(100.0f * m_dpi_scale, buttonHeight))) { if (ImGui::Button("清理日志", ImVec2(100.0f * m_dpi_scale, buttonHeight))) {
@ -528,6 +650,7 @@ LRESULT WINAPI Manager::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara
#endif #endif
bool Manager::InitializeTray() { bool Manager::InitializeTray() {
#ifdef _WIN32
m_tray_hwnd = CreateHiddenWindow(); m_tray_hwnd = CreateHiddenWindow();
if (!m_tray_hwnd) { if (!m_tray_hwnd) {
return false; return false;
@ -536,6 +659,16 @@ bool Manager::InitializeTray() {
HICON trayIcon = LoadIcon(NULL, IDI_APPLICATION); HICON trayIcon = LoadIcon(NULL, IDI_APPLICATION);
m_tray = std::make_unique<TrayIcon>(m_tray_hwnd, trayIcon); m_tray = std::make_unique<TrayIcon>(m_tray_hwnd, trayIcon);
// 设置托盘窗口的用户数据指向TrayIcon实例
SetWindowLongPtr(m_tray_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(m_tray.get()));
#else
// macOS 托盘初始化
void* app_delegate = GetMacAppDelegate(); // 需要实现这个函数
void* tray_icon = GetMacTrayIcon(); // 需要实现这个函数
m_tray = std::make_unique<TrayIcon>(app_delegate, tray_icon);
#endif
// 设置回调函数 // 设置回调函数
m_tray->SetShowWindowCallback([this]() { m_tray->SetShowWindowCallback([this]() {
OnTrayShowWindow(); OnTrayShowWindow();
@ -546,17 +679,14 @@ bool Manager::InitializeTray() {
}); });
m_tray->Show(); m_tray->Show();
// 设置托盘窗口的用户数据指向TrayIcon实例
SetWindowLongPtr(m_tray_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(m_tray.get()));
return true; return true;
} }
#ifdef _WIN32
HWND Manager::CreateHiddenWindow() { HWND Manager::CreateHiddenWindow() {
WNDCLASSEX wc = {0}; WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX); wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = TrayIcon::WindowProc; // 使用TrayIcon的窗口过程 wc.lpfnWndProc = TrayIcon::WindowProc;
wc.hInstance = GetModuleHandle(NULL); wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"CLIManagerTrayWindow"; wc.lpszClassName = L"CLIManagerTrayWindow";
@ -575,6 +705,7 @@ HWND Manager::CreateHiddenWindow() {
NULL NULL
); );
} }
#endif
void Manager::HandleMessages() { void Manager::HandleMessages() {
#ifdef USE_WIN32_BACKEND #ifdef USE_WIN32_BACKEND
@ -584,7 +715,6 @@ void Manager::HandleMessages() {
m_should_exit = true; m_should_exit = true;
} }
else if (msg.message == WM_CLOSE) { else if (msg.message == WM_CLOSE) {
// 主窗口关闭时隐藏到托盘,而不是退出
if (msg.hwnd == m_hwnd) { if (msg.hwnd == m_hwnd) {
HideMainWindow(); HideMainWindow();
continue; continue;
@ -598,15 +728,16 @@ void Manager::HandleMessages() {
} }
#else #else
// GLFW后端的消息处理 // GLFW后端的消息处理
#ifdef _WIN32
MSG msg; MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) { if (msg.message == WM_QUIT) {
m_should_exit = true; m_should_exit = true;
} }
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
#endif
glfwPollEvents(); glfwPollEvents();
if (glfwWindowShouldClose(m_window)) { if (glfwWindowShouldClose(m_window)) {
@ -800,11 +931,13 @@ void Manager::GlfwErrorCallback(int error, const char* description) {
void Manager::CleanupTray() { void Manager::CleanupTray() {
m_tray.reset(); m_tray.reset();
#ifdef _WIN32
if (m_tray_hwnd) { if (m_tray_hwnd) {
DestroyWindow(m_tray_hwnd); DestroyWindow(m_tray_hwnd);
m_tray_hwnd = nullptr; m_tray_hwnd = nullptr;
} }
UnregisterClass(L"CLIManagerTrayWindow", GetModuleHandle(nullptr)); UnregisterClass(L"CLIManagerTrayWindow", GetModuleHandle(nullptr));
#endif
} }
void Manager::Shutdown() { void Manager::Shutdown() {
@ -830,4 +963,12 @@ void Manager::Shutdown() {
#endif #endif
m_initialized = false; m_initialized = false;
} }
#ifdef __APPLE__
// macOS 特定的辅助函数声明
extern "C" {
void* GetMacAppDelegate();
void* GetMacTrayIcon();
}
#endif

View File

@ -1,6 +1,7 @@
#include "TrayIcon.h" #include "TrayIcon.h"
#include "Units.h" #include "Units.h"
#ifdef _WIN32
TrayIcon::TrayIcon(HWND hwnd, HICON icon) TrayIcon::TrayIcon(HWND hwnd, HICON icon)
: m_hwnd(hwnd), m_icon(icon), m_visible(false), m_menu(nullptr) { : m_hwnd(hwnd), m_icon(icon), m_visible(false), m_menu(nullptr) {
@ -16,6 +17,13 @@ TrayIcon::TrayIcon(HWND hwnd, HICON icon)
CreateMenu(); CreateMenu();
} }
#else
TrayIcon::TrayIcon(void* app_delegate, void* icon)
: m_app_delegate(app_delegate), m_icon(icon), m_visible(false) {
m_web_url = "http://localhost:8080"; // 默认URL
CreateMenu();
}
#endif
TrayIcon::~TrayIcon() { TrayIcon::~TrayIcon() {
Hide(); Hide();
@ -24,24 +32,41 @@ TrayIcon::~TrayIcon() {
void TrayIcon::Show() { void TrayIcon::Show() {
if (!m_visible) { if (!m_visible) {
#ifdef _WIN32
Shell_NotifyIcon(NIM_ADD, &m_nid); Shell_NotifyIcon(NIM_ADD, &m_nid);
#else
ShowMacTrayIcon();
#endif
m_visible = true; m_visible = true;
} }
} }
void TrayIcon::Hide() { void TrayIcon::Hide() {
if (m_visible) { if (m_visible) {
#ifdef _WIN32
Shell_NotifyIcon(NIM_DELETE, &m_nid); Shell_NotifyIcon(NIM_DELETE, &m_nid);
#else
HideMacTrayIcon();
#endif
m_visible = false; m_visible = false;
} }
} }
#ifdef _WIN32
void TrayIcon::UpdateWebUrl(const std::wstring& url) { void TrayIcon::UpdateWebUrl(const std::wstring& url) {
m_web_url = url; m_web_url = url;
// 重新创建菜单以更新Web URL显示 // 重新创建菜单以更新Web URL显示
DestroyMenu(); DestroyMenu();
CreateMenu(); CreateMenu();
} }
#else
void TrayIcon::UpdateWebUrl(const std::string& url) {
m_web_url = url;
// 重新创建菜单以更新Web URL显示
DestroyMenu();
CreateMenu();
}
#endif
void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) { void TrayIcon::SetShowWindowCallback(const ShowWindowCallback &callback) {
m_show_window_callback = callback; m_show_window_callback = callback;
@ -52,13 +77,14 @@ void TrayIcon::SetExitCallback(const ExitCallback &callback) {
} }
void TrayIcon::CreateMenu() { void TrayIcon::CreateMenu() {
#ifdef _WIN32
if (m_menu) { if (m_menu) {
DestroyMenu(); DestroyMenu();
} }
m_menu = CreatePopupMenu(); m_menu = CreatePopupMenu();
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口"); AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
AppendMenu(m_menu, MF_SEPARATOR, 0, NULL); AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
// 添加Web地址菜单项如果有设置 // 添加Web地址菜单项如果有设置
if (!m_web_url.empty() && m_web_url != L"") { if (!m_web_url.empty() && m_web_url != L"") {
@ -68,15 +94,23 @@ void TrayIcon::CreateMenu() {
} }
AppendMenu(m_menu, MF_STRING, 1003, L"退出"); AppendMenu(m_menu, MF_STRING, 1003, L"退出");
#else
CreateMacMenu();
#endif
} }
void TrayIcon::DestroyMenu() { void TrayIcon::DestroyMenu() {
#ifdef _WIN32
if (m_menu) { if (m_menu) {
::DestroyMenu(m_menu); ::DestroyMenu(m_menu);
m_menu = nullptr; m_menu = nullptr;
} }
#else
DestroyMacMenu();
#endif
} }
#ifdef _WIN32
void TrayIcon::ShowContextMenu() const { void TrayIcon::ShowContextMenu() const {
if (!m_menu) return; if (!m_menu) return;
@ -131,4 +165,62 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
return DefWindowProc(hwnd, msg, wParam, lParam); return DefWindowProc(hwnd, msg, wParam, lParam);
} }
return 0; return 0;
} }
#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