MBCS dialog templates are decoded with the system ANSI code page, so on
non-Chinese systems the GBK template strings turn into "?" before
TranslateDialog can read them. Set those strings explicitly in OnInitDialog
(PluginSettings save/close, the three Advanced dialogs, the file-manager
search buttons/checkboxes, and Trigger Settings) so they are re-decoded
with the thread locale. Also fix the language-selection dialog and the
toolbar "Lang/语言" button: the language dialog's template is built as
WCHAR strings but created through the ANSI path, so its title, label, and
buttons are set explicitly in OnInitDialog (AppendString stays on CP_ACP),
with the title looked up via _TR("切换语言") and set through SetWindowTextW
after an explicit GBK conversion; new en_US ("Switch Language") and zh_TW
("切換語言") translations accompany it. The toolbar button is set through
the Unicode TB_SETBUTTONINFOW message after an explicit GBK conversion. Add
en_US/zh_TW translations for the experimental-features context menu items.
Co-Authored-By: deepseek-v4-pro
524 lines
15 KiB
C++
524 lines
15 KiB
C++
#include "stdafx.h"
|
|
#include "TriggerSettingsDlg.h"
|
|
#include "2015RemoteDlg.h"
|
|
#include "jsoncpp/json.h"
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#ifndef _WIN64
|
|
#ifdef _DEBUG
|
|
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
|
|
#else
|
|
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
|
|
#endif
|
|
#else
|
|
#ifdef _DEBUG
|
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
|
|
#else
|
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
|
|
#endif
|
|
#endif
|
|
|
|
// GBK (CP_ACP) -> UTF-8 编码转换
|
|
static std::string GbkToUtf8(const std::string& gbkStr)
|
|
{
|
|
if (gbkStr.empty()) return "";
|
|
|
|
// GBK -> WideChar
|
|
int wideLen = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, NULL, 0);
|
|
if (wideLen <= 0) return gbkStr;
|
|
|
|
std::wstring wideStr(wideLen, 0);
|
|
MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, &wideStr[0], wideLen);
|
|
|
|
// WideChar -> UTF-8
|
|
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, NULL, 0, NULL, NULL);
|
|
if (utf8Len <= 0) return gbkStr;
|
|
|
|
std::string utf8Str(utf8Len, 0);
|
|
WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, &utf8Str[0], utf8Len, NULL, NULL);
|
|
|
|
// 移除末尾的 null 字符
|
|
if (!utf8Str.empty() && utf8Str.back() == '\0') {
|
|
utf8Str.pop_back();
|
|
}
|
|
return utf8Str;
|
|
}
|
|
|
|
// UTF-8 -> GBK (CP_ACP) 编码转换
|
|
static std::string Utf8ToGbk(const std::string& utf8Str)
|
|
{
|
|
if (utf8Str.empty()) return "";
|
|
|
|
// UTF-8 -> WideChar
|
|
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
|
|
if (wideLen <= 0) return utf8Str;
|
|
|
|
std::wstring wideStr(wideLen, 0);
|
|
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &wideStr[0], wideLen);
|
|
|
|
// WideChar -> GBK
|
|
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, NULL, 0, NULL, NULL);
|
|
if (gbkLen <= 0) return utf8Str;
|
|
|
|
std::string gbkStr(gbkLen, 0);
|
|
WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, &gbkStr[0], gbkLen, NULL, NULL);
|
|
|
|
// 移除末尾的 null 字符
|
|
if (!gbkStr.empty() && gbkStr.back() == '\0') {
|
|
gbkStr.pop_back();
|
|
}
|
|
return gbkStr;
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(CTriggerSettingsDlg, CDialogLangEx)
|
|
ON_BN_CLICKED(IDC_BTN_SAVE, &CTriggerSettingsDlg::OnBnClickedBtnSave)
|
|
ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd)
|
|
ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove)
|
|
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers)
|
|
ON_CBN_SELCHANGE(IDC_COMBO_TRIGGER_TYPE, &CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType)
|
|
END_MESSAGE_MAP()
|
|
|
|
CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector<DllInfo*>& dllList, CWnd* pParent)
|
|
: CDialogLangEx(IDD_DIALOG_TRIGGER_SETTINGS, pParent)
|
|
, m_DllList(dllList)
|
|
{
|
|
}
|
|
|
|
CTriggerSettingsDlg::~CTriggerSettingsDlg()
|
|
{
|
|
}
|
|
|
|
void CTriggerSettingsDlg::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
CDialogLangEx::DoDataExchange(pDX);
|
|
DDX_Control(pDX, IDC_COMBO_TRIGGER_TYPE, m_comboTriggerType);
|
|
DDX_Control(pDX, IDC_LIST_TRIGGER_PLUGINS, m_listPlugins);
|
|
DDX_Control(pDX, IDC_LIST_TRIGGERS, m_listTriggers);
|
|
}
|
|
|
|
BOOL CTriggerSettingsDlg::OnInitDialog()
|
|
{
|
|
CDialogLangEx::OnInitDialog();
|
|
|
|
InitControls();
|
|
|
|
// 加载配置
|
|
m_Configs = LoadTriggerConfigs();
|
|
|
|
// 加载插件列表
|
|
LoadPluginsToList();
|
|
|
|
// 加载已配置的触发器
|
|
LoadTriggersToList();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CTriggerSettingsDlg::InitControls()
|
|
{
|
|
// 初始化触发类型下拉框
|
|
m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线"));
|
|
m_comboTriggerType.InsertString(TRIGGER_AUTH_FAILED, _TR("授权失败"));
|
|
m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE);
|
|
|
|
// 初始化插件列表
|
|
m_listPlugins.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_CHECKBOXES);
|
|
m_listPlugins.InsertColumn(0, _TR("插件名称"), LVCFMT_LEFT, 160);
|
|
|
|
// 初始化已配置触发器列表
|
|
m_listTriggers.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
|
|
m_listTriggers.InsertColumn(0, _TR("触发器"), LVCFMT_LEFT, 160);
|
|
|
|
// 设置静态文本
|
|
SetWindowText(_TR("触发器设置"));
|
|
GetDlgItem(IDC_STATIC_TRIGGER_TYPE)->SetWindowText(_TR("触发类型:"));
|
|
GetDlgItem(IDC_STATIC_TRIGGER_ACTION)->SetWindowText(_TR("执行动作:"));
|
|
|
|
// 按钮/分组框文字来自对话框模板,非中文系统上会被系统 ACP 转成 "?",须显式设置
|
|
SetDlgItemText(IDC_STATIC_TRIGGER_LIST, _TR("已配置的触发器"));
|
|
SetDlgItemText(IDC_BTN_TRIGGER_ADD, _TR("添加 >>"));
|
|
SetDlgItemText(IDC_BTN_TRIGGER_REMOVE, _TR("<< 移除"));
|
|
SetDlgItemText(IDC_BTN_SAVE, _TR("保存(&S)"));
|
|
SetDlgItemText(IDOK, _TR("关闭"));
|
|
}
|
|
|
|
void CTriggerSettingsDlg::LoadPluginsToList()
|
|
{
|
|
m_listPlugins.DeleteAllItems();
|
|
|
|
int index = 0;
|
|
for (const auto& dll : m_DllList) {
|
|
if (!dll || !dll->Data) continue;
|
|
|
|
m_listPlugins.InsertItem(index, CString(dll->Name.c_str()));
|
|
m_listPlugins.SetItemData(index, (DWORD_PTR)dll);
|
|
index++;
|
|
}
|
|
|
|
// 回显当前选中触发类型已勾选的插件
|
|
ReflectPluginsForType((TriggerType)m_comboTriggerType.GetCurSel());
|
|
}
|
|
|
|
void CTriggerSettingsDlg::LoadTriggersToList()
|
|
{
|
|
m_listTriggers.DeleteAllItems();
|
|
|
|
const char* typeNames[] = { "主机上线", "授权失败" };
|
|
|
|
int index = 0;
|
|
for (const auto& cfg : m_Configs) {
|
|
if (!cfg.PluginNames.empty()) {
|
|
// 显示触发器类型和插件数量
|
|
CString text;
|
|
text.Format(_T("%s (%d)"), _TR(typeNames[cfg.Type]), (int)cfg.PluginNames.size());
|
|
m_listTriggers.InsertItem(index, text);
|
|
m_listTriggers.SetItemData(index, cfg.Type);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd()
|
|
{
|
|
// 获取选中的触发类型
|
|
int triggerType = m_comboTriggerType.GetCurSel();
|
|
if (triggerType < 0) return;
|
|
|
|
// 获取勾选的插件(直接从 DllInfo 获取名称,避免编码转换问题)
|
|
std::vector<std::string> selectedPlugins;
|
|
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
if (m_listPlugins.GetCheck(i)) {
|
|
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(i));
|
|
if (dll) {
|
|
selectedPlugins.push_back(dll->Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selectedPlugins.empty()) {
|
|
MessageBoxL(_TR("请先选择至少一个插件"), _TR("提示"), MB_ICONINFORMATION);
|
|
return;
|
|
}
|
|
|
|
// 查找或创建该类型的触发器
|
|
TriggerConfig* cfg = nullptr;
|
|
for (auto& c : m_Configs) {
|
|
if (c.Type == (TriggerType)triggerType) {
|
|
cfg = &c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!cfg) {
|
|
TriggerConfig newCfg;
|
|
newCfg.Type = (TriggerType)triggerType;
|
|
m_Configs.push_back(newCfg);
|
|
cfg = &m_Configs.back();
|
|
}
|
|
|
|
cfg->PluginNames = selectedPlugins;
|
|
|
|
// 刷新显示
|
|
LoadTriggersToList();
|
|
}
|
|
|
|
void CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove()
|
|
{
|
|
POSITION pos = m_listTriggers.GetFirstSelectedItemPosition();
|
|
if (!pos) return;
|
|
|
|
int nItem = m_listTriggers.GetNextSelectedItem(pos);
|
|
TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem);
|
|
|
|
// 从配置中移除
|
|
for (auto it = m_Configs.begin(); it != m_Configs.end(); ++it) {
|
|
if (it->Type == type) {
|
|
m_Configs.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 取消插件列表的勾选
|
|
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
m_listPlugins.SetCheck(i, FALSE);
|
|
}
|
|
|
|
// 刷新显示
|
|
LoadTriggersToList();
|
|
}
|
|
|
|
void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
|
|
*pResult = 0;
|
|
|
|
// 只处理选中状态变化
|
|
if (!(pNMLV->uNewState & LVIS_SELECTED)) return;
|
|
|
|
int nItem = pNMLV->iItem;
|
|
if (nItem < 0) return;
|
|
|
|
TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem);
|
|
|
|
// 回显该触发器配置的插件,并同步下拉框选择
|
|
ReflectPluginsForType(type);
|
|
m_comboTriggerType.SetCurSel(type);
|
|
}
|
|
|
|
void CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType()
|
|
{
|
|
int sel = m_comboTriggerType.GetCurSel();
|
|
if (sel >= 0) {
|
|
ReflectPluginsForType((TriggerType)sel);
|
|
}
|
|
}
|
|
|
|
void CTriggerSettingsDlg::ReflectPluginsForType(TriggerType type)
|
|
{
|
|
// 查找对应的触发器配置
|
|
TriggerConfig* cfg = nullptr;
|
|
for (auto& c : m_Configs) {
|
|
if (c.Type == type) {
|
|
cfg = &c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 先取消所有勾选
|
|
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
m_listPlugins.SetCheck(i, FALSE);
|
|
}
|
|
|
|
// 勾选该触发器配置的插件
|
|
if (cfg) {
|
|
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(i));
|
|
if (!dll) continue;
|
|
for (const auto& pluginName : cfg->PluginNames) {
|
|
if (pluginName == dll->Name) {
|
|
m_listPlugins.SetCheck(i, TRUE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTriggerSettingsDlg::OnBnClickedBtnSave()
|
|
{
|
|
// 先从界面收集当前选择
|
|
int triggerType = m_comboTriggerType.GetCurSel();
|
|
if (triggerType >= 0) {
|
|
std::vector<std::string> selectedPlugins;
|
|
for (int i = 0; i < m_listPlugins.GetItemCount(); i++) {
|
|
if (m_listPlugins.GetCheck(i)) {
|
|
DllInfo* dll = reinterpret_cast<DllInfo*>(m_listPlugins.GetItemData(i));
|
|
if (dll) {
|
|
selectedPlugins.push_back(dll->Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 更新或创建触发器配置
|
|
TriggerConfig* cfg = nullptr;
|
|
for (auto& c : m_Configs) {
|
|
if (c.Type == (TriggerType)triggerType) {
|
|
cfg = &c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!selectedPlugins.empty()) {
|
|
if (!cfg) {
|
|
TriggerConfig newCfg;
|
|
newCfg.Type = (TriggerType)triggerType;
|
|
m_Configs.push_back(newCfg);
|
|
cfg = &m_Configs.back();
|
|
}
|
|
cfg->PluginNames = selectedPlugins;
|
|
} else if (cfg) {
|
|
// 如果没有选中插件,删除该触发器
|
|
for (auto it = m_Configs.begin(); it != m_Configs.end(); ++it) {
|
|
if (it->Type == (TriggerType)triggerType) {
|
|
m_Configs.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SaveTriggerConfigs(m_Configs);
|
|
LoadTriggersToList();
|
|
|
|
MessageBoxL(_TR("配置已保存"), _TR("提示"), MB_ICONINFORMATION);
|
|
}
|
|
|
|
std::string CTriggerSettingsDlg::GetTriggerConfigPath()
|
|
{
|
|
std::string dbPath = GetDbPath();
|
|
size_t pos = dbPath.find_last_of("\\/");
|
|
std::string dir = (pos != std::string::npos) ? dbPath.substr(0, pos + 1) : "";
|
|
return dir + "triggers.json";
|
|
}
|
|
|
|
std::vector<TriggerConfig> CTriggerSettingsDlg::LoadTriggerConfigs()
|
|
{
|
|
std::vector<TriggerConfig> configs;
|
|
std::string path = GetTriggerConfigPath();
|
|
|
|
std::ifstream file(path);
|
|
if (!file.is_open()) {
|
|
return configs;
|
|
}
|
|
|
|
Json::Value root;
|
|
Json::CharReaderBuilder builder;
|
|
std::string errors;
|
|
|
|
if (!Json::parseFromStream(builder, file, &root, &errors)) {
|
|
return configs;
|
|
}
|
|
|
|
if (!root.isArray()) {
|
|
return configs;
|
|
}
|
|
|
|
for (const auto& item : root) {
|
|
TriggerConfig cfg;
|
|
cfg.Type = (TriggerType)item.get("type", TRIGGER_HOST_ONLINE).asInt();
|
|
|
|
const Json::Value& plugins = item["plugins"];
|
|
if (plugins.isArray()) {
|
|
for (const auto& p : plugins) {
|
|
cfg.PluginNames.push_back(Utf8ToGbk(p.asString())); // UTF-8 -> GBK
|
|
}
|
|
}
|
|
|
|
if (!cfg.PluginNames.empty()) {
|
|
configs.push_back(cfg);
|
|
}
|
|
}
|
|
|
|
return configs;
|
|
}
|
|
|
|
void CTriggerSettingsDlg::SaveTriggerConfigs(const std::vector<TriggerConfig>& configs)
|
|
{
|
|
std::string path = GetTriggerConfigPath();
|
|
|
|
Json::Value root(Json::arrayValue);
|
|
|
|
for (const auto& cfg : configs) {
|
|
Json::Value item;
|
|
item["type"] = cfg.Type;
|
|
|
|
Json::Value plugins(Json::arrayValue);
|
|
for (const auto& name : cfg.PluginNames) {
|
|
plugins.append(GbkToUtf8(name)); // GBK -> UTF-8
|
|
}
|
|
item["plugins"] = plugins;
|
|
|
|
root.append(item);
|
|
}
|
|
|
|
std::ofstream file(path);
|
|
if (file.is_open()) {
|
|
Json::StreamWriterBuilder builder;
|
|
builder["indentation"] = " ";
|
|
builder["emitUTF8"] = true; // 输出可读的 UTF-8 字符,而非 \uXXXX
|
|
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
|
writer->write(root, &file);
|
|
file.close(); // 确保文件完全写入并关闭
|
|
}
|
|
|
|
// 通知 TriggerManager 重新加载缓存
|
|
TriggerManager::Instance().Reload();
|
|
}
|
|
|
|
// ============================================
|
|
// TriggerManager 实现
|
|
// ============================================
|
|
|
|
TriggerManager::TriggerManager() : m_bLoaded(false)
|
|
{
|
|
InitializeCriticalSection(&m_cs);
|
|
}
|
|
|
|
TriggerManager::~TriggerManager()
|
|
{
|
|
DeleteCriticalSection(&m_cs);
|
|
}
|
|
|
|
void TriggerManager::LoadFromDisk()
|
|
{
|
|
// 不加锁,由调用者保证线程安全
|
|
m_OnlinePlugins.clear();
|
|
m_AuthFailedPlugins.clear();
|
|
|
|
auto configs = CTriggerSettingsDlg::LoadTriggerConfigs();
|
|
for (const auto& cfg : configs) {
|
|
if (cfg.Type == TRIGGER_HOST_ONLINE) {
|
|
for (const auto& name : cfg.PluginNames) {
|
|
m_OnlinePlugins.insert(name);
|
|
}
|
|
} else if (cfg.Type == TRIGGER_AUTH_FAILED) {
|
|
for (const auto& name : cfg.PluginNames) {
|
|
m_AuthFailedPlugins.insert(name);
|
|
}
|
|
}
|
|
}
|
|
m_bLoaded = true;
|
|
}
|
|
|
|
void TriggerManager::Reload()
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
LoadFromDisk();
|
|
LeaveCriticalSection(&m_cs);
|
|
}
|
|
|
|
bool TriggerManager::HasOnlineTrigger()
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
if (!m_bLoaded) {
|
|
LoadFromDisk();
|
|
}
|
|
bool has = !m_OnlinePlugins.empty();
|
|
LeaveCriticalSection(&m_cs);
|
|
return has;
|
|
}
|
|
|
|
std::set<std::string> TriggerManager::GetOnlinePlugins()
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
if (!m_bLoaded) {
|
|
LoadFromDisk();
|
|
}
|
|
std::set<std::string> result = m_OnlinePlugins; // 复制一份返回
|
|
LeaveCriticalSection(&m_cs);
|
|
return result;
|
|
}
|
|
|
|
bool TriggerManager::HasAuthFailedTrigger()
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
if (!m_bLoaded) {
|
|
LoadFromDisk();
|
|
}
|
|
bool has = !m_AuthFailedPlugins.empty();
|
|
LeaveCriticalSection(&m_cs);
|
|
return has;
|
|
}
|
|
|
|
std::set<std::string> TriggerManager::GetAuthFailedPlugins()
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
if (!m_bLoaded) {
|
|
LoadFromDisk();
|
|
}
|
|
std::set<std::string> result = m_AuthFailedPlugins; // 复制一份返回
|
|
LeaveCriticalSection(&m_cs);
|
|
return result;
|
|
}
|