UP 历史命令配置支持
parent
534e5c0850
commit
7b6c87f2b0
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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_;
|
||||
};
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
CloseHandle(hWriteTmp);
|
||||
CloseHandle(hReadTmp_stdin);
|
||||
|
||||
if (!bSuccess) {
|
||||
CloseHandle(hReadTmp);
|
||||
CloseHandle(hWriteTmp_stdin);
|
||||
return;
|
||||
}
|
||||
|
||||
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()) {
|
||||
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()) + ")");
|
||||
}
|
||||
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_;
|
||||
|
||||
// 检查是否设置了停止命令
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::lock_guard<std::mutex> lock(stop_mutex_);
|
||||
if (!stop_command_.empty() && IsRunning()) {
|
||||
useStopCommand = true;
|
||||
stopCmd = stop_command_;
|
||||
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;
|
||||
}
|
||||
|
@ -508,9 +534,18 @@ bool CLIProcess::SendCommand(const std::string& 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;
|
||||
#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
|
||||
}
|
||||
|
|
|
@ -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))) {
|
||||
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))) {
|
||||
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() {
|
||||
|
@ -831,3 +964,11 @@ void Manager::Shutdown() {
|
|||
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// macOS 特定的辅助函数声明
|
||||
extern "C" {
|
||||
void* GetMacAppDelegate();
|
||||
void* GetMacTrayIcon();
|
||||
}
|
||||
#endif
|
|
@ -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;
|
||||
|
||||
|
@ -132,3 +166,61 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM 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
|
Loading…
Reference in New Issue