diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index b32effd..329cb53 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 5edac3f..4d90ba1 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -239,6 +239,28 @@ UINT GetClientEncoding(context* ctx) { return 936; } +// 客户端「进程名/路径」编码:Windows 用 A 接口生成,恒为客户端 ANSI(简中=GBK/936), +// 不随 CLIENT_CAP_UTF8 转 UTF-8;LNX/MAC 的 /proc 天然 UTF-8。 +// 不能用 GetClientEncoding——它按能力位返回 CP_UTF8,会误解 Windows 客户端的 GBK 进程名。 +// 与 McpServer::BuildListProcesses 的判定保持一致。 +UINT GetProcessListEncoding(context* ctx) { + if (!ctx) return 936; + CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE); + if (clientType == "LNX" || clientType == "MAC") return CP_UTF8; + // 子连接情形:自身 RES_CLIENT_TYPE 为空 -> 找主连接取 clientType + if (g_2015RemoteDlg) { + context* mainCtx = g_2015RemoteDlg->FindHostByClientID(ctx->GetClientID()); + if (!mainCtx) { + mainCtx = g_2015RemoteDlg->FindHostByIP(ctx->GetPeerName()); + } + if (mainCtx) { + CString mainType = mainCtx->GetAdditionalData(RES_CLIENT_TYPE); + if (mainType == "LNX" || mainType == "MAC") return CP_UTF8; + } + } + return 936; +} + // 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次 static bool ShouldLogAuth(const std::string& sn, int success) { struct AuthLogState { diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 037c440..e811092 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -109,6 +109,13 @@ bool SupportsFileTransferV2(context* ctx); // 找不到主连接或老客户端:默认 CP936(覆盖 95% 简中/英语 ASCII 老客户端)。 UINT GetClientEncoding(context* ctx); +// 获取客户端「进程名/路径」编码 (CP_UTF8 或 936)。 +// 与 GetClientEncoding 不同:Windows 客户端的进程名/路径由 A 接口 +// (Process32FirstA / QueryFullProcessImageNameA 等)生成,恒为客户端系统 +// ANSI(简中=GBK/936),不随 CLIENT_CAP_UTF8 转 UTF-8;仅 LNX/MAC 走 UTF-8。 +// 与 McpServer::BuildListProcesses 的判定保持一致。 +UINT GetProcessListEncoding(context* ctx); + // 服务端待续传的传输信息 struct PendingTransferV2 { uint64_t clientID; diff --git a/server/2015Remote/CPasswordDlg.cpp b/server/2015Remote/CPasswordDlg.cpp index 5217d01..d3a5775 100644 --- a/server/2015Remote/CPasswordDlg.cpp +++ b/server/2015Remote/CPasswordDlg.cpp @@ -250,7 +250,7 @@ bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& } // IP 列表管理常量 -#define MAX_IP_HISTORY 500 // 最多保留 几 个不同的 IP +#define MAX_IP_HISTORY 1000 // 最多保留 几 个不同的 IP // 解析 IP 列表字符串为 vector> // 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd) diff --git a/server/2015Remote/SystemDlg.cpp b/server/2015Remote/SystemDlg.cpp index 93c480d..61c5a3e 100644 --- a/server/2015Remote/SystemDlg.cpp +++ b/server/2015Remote/SystemDlg.cpp @@ -232,6 +232,26 @@ void CSystemDlg::ShowWindowsList(void) ApplySort(); // 应用记忆的排序偏好 } +// 按客户端编码 (936 或 UTF-8) 解码字节串,并用 LVM_SETITEMTEXTW 写入列表单元格, +// 绕开 ANSI→CP_ACP 回转(非中文 ACP 服务端上中文也能正常显示,与窗口管理同法)。 +static void SetClientTextItem(CListCtrl& list, int iItem, int iSubItem, const char* sz, UINT cp) +{ + std::wstring w; + if (sz && sz[0]) { + int wlen = MultiByteToWideChar(cp, 0, sz, -1, NULL, 0); + if (wlen > 1) { + w.resize(wlen - 1); + MultiByteToWideChar(cp, 0, sz, -1, &w[0], wlen); + } + } + LVITEMW lv = {}; + lv.mask = LVIF_TEXT; + lv.iItem = iItem; + lv.iSubItem = iSubItem; + lv.pszText = w.empty() ? const_cast(L"") : &w[0]; + ::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv); +} + void CSystemDlg::ShowProcessList(void) { // GetMyBuffer(1) 深拷贝数据,跳过 TOKEN 字节 @@ -248,6 +268,10 @@ void CSystemDlg::ShowProcessList(void) const char *szProcessFullPath; DWORD dwOffset = 0; CString str; + // 进程名/路径编码:Windows 客户端 A 接口生成,恒为 GBK/936;仅 LNX/MAC 为 UTF-8。 + // 不能用 GetClientEncoding(它对新客户端返回 CP_UTF8,会把 GBK 进程名解成乱码)。 + UINT cp = GetProcessListEncoding(m_ContextObject); + DeleteAllItems(); //遍历发送来的每一个字符别忘了他的数据结构啊 Id+进程名+0+完整名+0 int i; @@ -258,10 +282,11 @@ void CSystemDlg::ShowProcessList(void) szProcessFullPath = szExeFile + lstrlen(szExeFile) + 1; //完整名就是进程名之后的啦 //他的数据结构的构建很巧妙 - m_ControlList.InsertItem(i, arr[0].c_str()); //将得到的数据加入到列表当中 + m_ControlList.InsertItem(i, ""); // 占位,首列随后用 Unicode 写入 str.FormatL("%5u", *PID); - m_ControlList.SetItemText(i, 1, str); - m_ControlList.SetItemText(i, 2, szProcessFullPath); + m_ControlList.SetItemText(i, 1, str); // PID (ASCII) + SetClientTextItem(m_ControlList, i, 0, arr[0].c_str(), cp); // 进程名(按客户端编码) + SetClientTextItem(m_ControlList, i, 2, szProcessFullPath, cp); // 程序路径(按客户端编码) m_ControlList.SetItemText(i, 3, arr[1].empty() ? "N/A" : arr[1].c_str()); // ItemData 为进程ID;Data[3] 需与第 4 列"架构"一致,否则按架构排序拿到空串而无意义 auto data = new ItemData{ *PID, {arr[0].c_str(), str, szProcessFullPath, arr[1].empty() ? "N/A" : arr[1].c_str()}, arr[1].c_str() };