UP 历史命令配置支持
parent
534e5c0850
commit
7b6c87f2b0
|
@ -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"
|
||||||
#)
|
)
|
||||||
#
|
|
||||||
|
|
|
@ -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
|
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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_;
|
||||||
};
|
};
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue