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()
#
#set_target_properties(${PROJECT_NAME} PROPERTIES
# LINK_FLAGS "-static -Wl,-subsystem,windows"
#)
#
set_target_properties(${PROJECT_NAME} PROPERTIES
LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@
#include <cstdio>
#include <algorithm>
#include "Units.h"
#include "Units.h"
Manager::Manager() = default;
@ -45,25 +45,56 @@ bool Manager::Initialize() {
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);
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
// 加载中文字体
// 加载字体
#ifdef _WIN32
ImFont* font = io.Fonts->AddFontFromFileTTF(
"C:/Windows/Fonts/msyh.ttc",
18.0f,
14.0f,
nullptr,
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;
@ -72,7 +103,12 @@ bool Manager::Initialize() {
m_app_state.LoadSettings();
m_app_state.auto_start = IsAutoStartEnabled();
m_app_state.ApplySettings();
#ifdef _WIN32
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) {
@ -196,19 +232,36 @@ void Manager::RenderSettingsMenu() {
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::Text("Web设置");
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));
#else
m_tray->UpdateWebUrl(m_app_state.web_url);
#endif
m_app_state.settings_dirty = true;
}
RenderStopCommandSettings();
RenderEnvironmentVariablesSettings();
RenderOutputEncodingSettings(); // 新增:渲染编码设置
RenderOutputEncodingSettings();
}
void Manager::RenderStopCommandSettings() {
ImGui::Separator();
ImGui::Text("停止命令设置");
@ -233,10 +286,8 @@ void Manager::RenderStopCommandSettings() {
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("说明:禁用时将直接强制终止程序。");
ImGui::EndDisabled();
}
}
@ -360,29 +411,97 @@ void Manager::RenderOutputEncodingSettings() {
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;
// 启动命令输入
float inputWidth = ImGui::GetContentRegionAvail().x * 0.5f; // 调整输入框宽度为50%
// 启动命令输入区域
ImGui::BeginGroup();
ImGui::Text("启动命令");
// 命令输入框和历史记录按钮
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;
}
// 控制按钮
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();
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();
if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
@ -390,7 +509,10 @@ void Manager::RenderMainContent() {
}
ImGui::SameLine();
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();
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
bool Manager::InitializeTray() {
#ifdef _WIN32
m_tray_hwnd = CreateHiddenWindow();
if (!m_tray_hwnd) {
return false;
@ -536,6 +659,16 @@ bool Manager::InitializeTray() {
HICON trayIcon = LoadIcon(NULL, IDI_APPLICATION);
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]() {
OnTrayShowWindow();
@ -546,17 +679,14 @@ bool Manager::InitializeTray() {
});
m_tray->Show();
// 设置托盘窗口的用户数据指向TrayIcon实例
SetWindowLongPtr(m_tray_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(m_tray.get()));
return true;
}
#ifdef _WIN32
HWND Manager::CreateHiddenWindow() {
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = TrayIcon::WindowProc; // 使用TrayIcon的窗口过程
wc.lpfnWndProc = TrayIcon::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"CLIManagerTrayWindow";
@ -575,6 +705,7 @@ HWND Manager::CreateHiddenWindow() {
NULL
);
}
#endif
void Manager::HandleMessages() {
#ifdef USE_WIN32_BACKEND
@ -584,7 +715,6 @@ void Manager::HandleMessages() {
m_should_exit = true;
}
else if (msg.message == WM_CLOSE) {
// 主窗口关闭时隐藏到托盘,而不是退出
if (msg.hwnd == m_hwnd) {
HideMainWindow();
continue;
@ -598,15 +728,16 @@ void Manager::HandleMessages() {
}
#else
// GLFW后端的消息处理
#ifdef _WIN32
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
m_should_exit = true;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
#endif
glfwPollEvents();
if (glfwWindowShouldClose(m_window)) {
@ -800,11 +931,13 @@ void Manager::GlfwErrorCallback(int error, const char* description) {
void Manager::CleanupTray() {
m_tray.reset();
#ifdef _WIN32
if (m_tray_hwnd) {
DestroyWindow(m_tray_hwnd);
m_tray_hwnd = nullptr;
}
UnregisterClass(L"CLIManagerTrayWindow", GetModuleHandle(nullptr));
#endif
}
void Manager::Shutdown() {
@ -830,4 +963,12 @@ void Manager::Shutdown() {
#endif
m_initialized = false;
}
}
#ifdef __APPLE__
// macOS 特定的辅助函数声明
extern "C" {
void* GetMacAppDelegate();
void* GetMacTrayIcon();
}
#endif

View File

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