#include "stdafx.h" #include "McpSettingsDlg.h" #include "LangManager.h" // _TR / MessageBoxL #include "McpServer.h" // GenerateRandomToken #include "2015Remote.h" // THIS_CFG (GetThisCfg) #include #ifdef _DEBUG #define new DEBUG_NEW #endif namespace { // 构造一个无控件的 DLGTEMPLATE:cdit=0,控件由 OnInitDialog 动态创建。 // (与 ZstaPickerDlg.cpp 内同名函数一致) void BuildDialogTemplate(std::vector& out, LPCWSTR caption, short cx, short cy) { out.clear(); auto append = [&](const void* p, size_t n) { const BYTE* b = (const BYTE*)p; out.insert(out.end(), b, b + n); }; auto appendW = [&](WORD v) { out.push_back((BYTE)(v & 0xFF)); out.push_back((BYTE)((v >> 8) & 0xFF)); }; auto appendWStr = [&](LPCWSTR s) { size_t n = wcslen(s); append(s, (n + 1) * sizeof(WCHAR)); }; DLGTEMPLATE dt = { 0 }; dt.style = DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU; dt.dwExtendedStyle = 0; dt.cdit = 0; dt.x = 0; dt.y = 0; dt.cx = cx; dt.cy = cy; append(&dt, sizeof(dt)); appendW(0); // no menu appendW(0); // default dialog class appendWStr(caption); // caption appendW(8); // font point size appendWStr(L"MS Shell Dlg"); // typeface while (out.size() % 4) out.push_back(0); // DWORD align } // 把白名单文本规范化为「逗号分隔、逐项去首尾空白、去空项」的存储格式。 // 换行视作分隔符(与逗号等价),便于多行输入。 std::string NormalizeWhitelist(const std::string& raw) { std::string norm; norm.reserve(raw.size()); for (char ch : raw) norm += (ch == '\r' || ch == '\n') ? ',' : ch; std::vector items; size_t start = 0; while (start < norm.size()) { size_t comma = norm.find(',', start); std::string item = norm.substr(start, comma == std::string::npos ? std::string::npos : comma - start); size_t b = item.find_first_not_of(" \t\r\n"); if (b != std::string::npos) { size_t e = item.find_last_not_of(" \t\r\n"); item = item.substr(b, e - b + 1); } else { item.clear(); } if (!item.empty()) items.push_back(item); if (comma == std::string::npos) break; start = comma + 1; } std::string out; for (size_t i = 0; i < items.size(); ++i) { if (i) out += ","; out += items[i]; } return out; } // 存储格式(逗号分隔)→ 多行展示(每行一条)。 std::string WhitelistForDisplay(const std::string& stored) { std::string disp; disp.reserve(stored.size()); for (char ch : stored) { if (ch == ',') disp += "\r\n"; else disp += ch; } return disp; } } // namespace BEGIN_MESSAGE_MAP(CMcpSettingsDlg, CDialog) END_MESSAGE_MAP() CMcpSettingsDlg::CMcpSettingsDlg(CWnd* parent) : CDialog((LPCTSTR)NULL, parent) { } INT_PTR CMcpSettingsDlg::DoModal() { USES_CONVERSION; CString title = _TR("MCP设置"); BuildDialogTemplate(m_Template, T2CW(title), 320, 360); InitModalIndirect((LPCDLGTEMPLATE)m_Template.data()); return CDialog::DoModal(); } BOOL CMcpSettingsDlg::OnInitDialog() { CDialog::OnInitDialog(); CRect cli; GetClientRect(&cli); // 占位 rect,真正布局在 LayoutControls 里 CRect r0(0, 0, 10, 10); m_btnEnable.Create(_TR("启用 MCP"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, r0, this, IDC_MCP_ENABLE); m_lblPort.Create(_TR("端口"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editPort.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, r0, this, IDC_MCP_PORT); m_lblBind.Create(_TR("绑定地址"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editBind.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, r0, this, IDC_MCP_BIND); m_lblToken.Create(_TR("Token"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editToken.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, r0, this, IDC_MCP_TOKEN); m_btnReadonly.Create(_TR("只读模式(禁命令执行)"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, r0, this, IDC_MCP_READONLY); m_btnTerminal.Create(_TR("启用持久终端(全命令,无白名单)"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, r0, this, IDC_MCP_TERMINAL); m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL, r0, this, IDC_MCP_WHITELIST); m_btnOK.Create(_TR("确定"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, r0, this, IDOK); m_btnCancel.Create(_TR("取消"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, r0, this, IDCANCEL); // 子控件继承对话框字体 (DS_SETFONT) HFONT hFont = (HFONT)::SendMessage(GetSafeHwnd(), WM_GETFONT, 0, 0); if (hFont) { m_btnEnable.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblPort.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editPort.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblBind.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editBind.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblToken.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editToken.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnReadonly.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnCancel.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); } // 回填配置(首次打开且 token 为空时预填随机值,随保存持久化) int enabled = THIS_CFG.GetInt("settings", "McpEnabled", 0); int port = THIS_CFG.GetInt("settings", "McpPort", 6544); std::string bind = THIS_CFG.GetStr("settings", "McpBind", "127.0.0.1"); std::string tok = THIS_CFG.GetStr("settings", "McpToken", ""); if (tok.empty()) tok = GenerateRandomToken(); int readonly = THIS_CFG.GetInt("settings", "McpReadonly", 1); int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0); std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", ""); m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED); m_editPort.SetWindowText(CString(std::to_string(port).c_str())); m_editBind.SetWindowText(CString(bind.c_str())); m_editToken.SetWindowText(CString(tok.c_str())); m_btnReadonly.SetCheck(readonly ? BST_CHECKED : BST_UNCHECKED); m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED); // 白名单存储为逗号分隔,展示为每行一条。 m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str())); LayoutControls(cli.Width(), cli.Height()); return TRUE; } void CMcpSettingsDlg::OnOK() { CString sPort, sBind, sToken, sWhitelist; m_editPort.GetWindowText(sPort); m_editBind.GetWindowText(sBind); m_editToken.GetWindowText(sToken); m_editWhitelist.GetWindowText(sWhitelist); bool enabled = (m_btnEnable.GetCheck() == BST_CHECKED); bool readonly = (m_btnReadonly.GetCheck() == BST_CHECKED); bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED); // 端口校验:1-65535 int port = atoi(CT2A(sPort)); if (port < 1 || port > 65535) { MessageBoxL(_TR("端口需为 1-65535 的数字"), _TR("提示"), MB_ICONWARNING); return; } std::string bind = CT2A(sBind); if (bind.empty()) bind = "127.0.0.1"; std::string token = CT2A(sToken); if (enabled && token.empty()) { MessageBoxL(_TR("Token 不能为空"), _TR("提示"), MB_ICONWARNING); return; } // 落盘 THIS_CFG.SetInt("settings", "McpEnabled", enabled ? 1 : 0); THIS_CFG.SetInt("settings", "McpPort", port); THIS_CFG.SetStr("settings", "McpBind", bind); THIS_CFG.SetStr("settings", "McpToken", token); THIS_CFG.SetInt("settings", "McpReadonly", readonly ? 1 : 0); THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0); std::string whitelist = CT2A(sWhitelist); whitelist = NormalizeWhitelist(whitelist); THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist); // 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示) MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") + _TR("启用/端口/绑定地址/Token/只读/白名单/持久终端的改动需重启程序生效。") + _T("\r\n") + _TR("持久终端仅在只读模式关闭时生效。"), _TR("提示"), MB_ICONINFORMATION); CDialog::OnOK(); } void CMcpSettingsDlg::LayoutControls(int cx, int cy) { const int margin = 14; const int labelW = 72; const int rowH = 24; const int gap = 10; const int btnW = 88; const int btnH = 26; int y = margin; m_btnEnable.MoveWindow(margin, y, cx - margin * 2, 22); y += 30; m_lblPort.MoveWindow(margin, y, labelW, rowH); m_editPort.MoveWindow(margin + labelW, y - 2, 100, rowH); y += rowH + gap; m_lblBind.MoveWindow(margin, y, labelW, rowH); m_editBind.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, rowH); y += rowH + gap; m_lblToken.MoveWindow(margin, y, labelW, rowH); m_editToken.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, rowH); y += rowH + gap; m_btnReadonly.MoveWindow(margin, y, cx - margin * 2, 22); y += 30; m_btnTerminal.MoveWindow(margin, y, cx - margin * 2, 22); y += 30; const int whitelistH = 90; m_lblWhitelist.MoveWindow(margin, y, labelW, rowH); m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH); y += whitelistH + gap; int bottomY = cy - margin - btnH; m_btnCancel.MoveWindow(cx - margin - btnW, bottomY, btnW, btnH); m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, bottomY, btnW, btnH); }