9 Commits
Author SHA1 Message Date
yuanyuanxianganddeepseek-v4-pro ca6f76565c Change: Hide client chat dialog on close instead of terminating
Closing the client-side chat dialog (via the "end chat" button or the title-bar
X) now only hides the window and keeps the session alive, instead of destroying
it. Closing the chat stays server-initiated: the next message from the server
reopens the hidden dialog via the existing SW_RESTORE path in OnReceive, and the
dialog is only truly destroyed on COMMAND_CHAT_CLOSE or connection close.

Co-Authored-By: deepseek-v4-pro
2026-10-10 14:39:34 +02:00
yuanyuanxianganddeepseek-v4-pro d15cd77a28 Fix: Decode geographic location as UTF-8 across client and server
Geographic location (city, country) from the geo APIs is UTF-8, but the client
and several server paths converted or stored it in the system ANSI code page,
turning umlauts ("ö"/"ä") into "?" or GBK mojibake ("B枚blingen"). Keep the
value UTF-8 end to end: the client caches it as UTF-8 and re-queries once after
an upgrade (keyed by __DATE__, the app's version convention), while the server
stores and displays it as UTF-8 in the online list, the activity log, the
client-info export, JSON/email payloads and search.

Also add a confirmation prompt before opening a remote chat from the toolbar,
with German, English and Traditional-Chinese translations.

Co-Authored-By: deepseek-v4-pro
2026-10-10 13:58:38 +02:00
yuanyuanxianganddeepseek-v4-pro 1a9312d2d1 Fix: Decode remote file-list names/paths from GBK/UTF-8 on non-Chinese systems
The Windows ghost client lists directories with FindFirstFileA, so remote
file names arrive as GBK/936 bytes. Both file-manager dialogs wrote them
back through ANSI InsertItem/SetItemText, which re-converts through CP_ACP
and garbles Chinese names when the operator machine runs a non-Chinese ACP.

Decode the name/path from the client encoding (GBK for Windows, UTF-8 for
LNX/MAC, chosen by RES_CLIENT_TYPE) and write the text via LVM_SETITEMTEXTW,
keeping the icon through an empty InsertItem - the same pattern used for the
process list, window list and client-list path.

Co-Authored-By: deepseek-v4-pro
2026-10-09 16:23:23 +02:00
yuanyuanxianganddeepseek-v4-pro f3a8406050 Fix: Decode client file path correctly in client list and info popup
The client reports its own exe path as UTF-8 in RES_FILE_PATH. Two
display sites re-decoded those bytes with the wrong code page and turned
Chinese paths into mojibake on non-Chinese servers:

- The client-list "程序路径" column wrote ProgramPath through the ANSI
  SetItemText path, so the UTF-8 bytes were misread as the system ACP.
  Decode UTF-8 to wide and write with LVM_SETITEMTEXTW instead (same
  pattern as the window/process lists).

- The double-click info popup normalized the client RES_* fields to the
  system ACP and rendered strText with CP_ACP -> wide. The app's internal
  ANSI convention is fixed GBK/936 (/execution-charset:.936 plus
  setlocale(".936")), so on non-Chinese systems the path collapsed to '?'.
  Normalize to 936 and decode 936 instead, which also fixes the popup's
  GBK labels and username in the same pass.

Co-Authored-By: deepseek-v4-pro
2026-10-09 15:00:39 +02:00
yuanyuanxianganddeepseek-v4-pro ff6b290580 Fix: Decode process list names/paths as GBK on Windows
Process management decoded the client's process name/path bytes with
GetClientEncoding, which returns UTF-8 for CLIENT_CAP_UTF8 clients. But
the Windows client builds the process list with the ANSI A-interface
(Process32FirstA / QueryFullProcessImageNameA), which emits the client's
system code page (GBK on Chinese Windows) regardless of the UTF-8
capability -- unlike window titles, which were migrated to the
W-interface plus UTF-8. Decoding GBK as UTF-8 garbled Chinese process
names and paths.

Add GetProcessListEncoding(): 936 for Windows clients, UTF-8 for
LNX/MAC, matching McpServer::BuildListProcesses, and use it in
ShowProcessList (which still writes via LVM_SETITEMTEXTW).

Also raise the IP history cap to 1000 and rename the experimental
"隐私屏幕" menu item to "虚拟屏幕".

Co-Authored-By: deepseek-v4-pro
2026-10-09 14:05:52 +02:00
yuanyuanxianganddeepseek-v4-pro da0ec983ba Feature: Show live transfer speed in file-transfer dialog title
Add a smoothed bytes-per-second readout to the CDlgFileSend title,
refreshed about every 200 ms during transfer (e.g. "发送文件 - 5.3M/s").
The base title is captured on the first progress frame and the speed
suffix applied through Unicode APIs (GetWindowTextW / SetWindowTextW),
so the whole title stays correct on non-Chinese systems where the ANSI
message path would misdecode GBK as the system ACP.

Also add the missing "接收文件 (V2)" translation (en_US "Receive File (V2)",
zh_TW "接收檔案 (V2)"), so the V2 receive title no longer falls back to
raw Chinese on non-Chinese systems.

Co-Authored-By: deepseek-v4-pro
2026-10-08 22:15:31 +02:00
yuanyuanxianganddeepseek-v4-pro 6220335f63 Fix: Decode GBK dialog/toolbar text on non-Chinese systems
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
2026-10-08 21:53:53 +02:00
yuanyuanxianganddeepseek-v4-pro c7f7b7b29b Fix: Expand env vars in V2 file receive path
V2 upload to a remote directory picked via the quick-icon shortcuts
(Desktop / Downloads / Home) landed files in a literal "%USERPROFILE%"
folder instead of the real profile. The shortcuts set m_Remote_Path to an
unexpanded "%USERPROFILE%\..." template (FileManagerDlg.cpp
OnRemoteDesktop/Downloads/Home), and the V2 receive path handed that
filename straight to RecvFileChunkV2 (prebuilt FileUpload lib), which writes
it verbatim. V1 upload already expanded env vars in COMMAND_FILE_SIZE
(client/FileManager.cpp ExpandPath), so this is a V2-only regression.

Expand the filename's environment variables in CKernelManager::OnReceive's
COMMAND_SEND_FILE_V2 branch before RecvFileChunkV2, rebuilding the packet
only when the expanded length differs; other packet types pass through
unchanged.

Co-Authored-By: deepseek-v4-pro
2026-10-08 17:56:18 +02:00
yuanyuanxianganddeepseek-v4-pro 59fead7b5f Fix: Deliver file-transfer speed via MasterSettings (was CMD_FILE_SPEED)
The file-transfer speed dialog previously sent a separate CMD_FILE_SPEED
(5 bytes) back-to-back before COMMAND_SCREEN_SPY / COMMAND_LIST_DRIVE when
opening remote desktop or file manager. The IOCP send path batches two
consecutive Send2Client calls into one WSASend of the accumulated
OutCompressedBuffer, so the client received the [FILE_SPEED][SCREEN_SPY]
command stream twice - the root cause of two remote-desktop dialogs popping
up on a single click (introduced in commit 76decec).

Fold the two values into MasterSettings (TransferChunkKb / TransferDurationMs,
uint16 each, before Reserved, sizeof unchanged at 1000) so they reach every
client on login and are broadcast to online clients when the dialog saves.
The client persists them into [transfer] on CMD_MASTERSETTING, guarded by
TransferChunkKb != 0 so older masters keep local defaults. CMD_FILE_SPEED(252)
and its client handler remain for backward compatibility, but the master no
longer sends it.

Update the design doc, the three GBK lang files' prompt entry, and the READMEs.

Co-Authored-By: deepseek-v4-pro
2026-10-07 21:19:04 +02:00
32 changed files with 646 additions and 126 deletions

No files matched your search

+1 -1
View File
@@ -390,7 +390,7 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
- **主机管理**:迁移 CMachineManager(进程 / 窗口 / 网络 / 软件 / IE 历史 / 收藏夹 / 服务 / 任务 / hosts 枚举 + 进程注入),COMMAND_MACHINE_MANAGE 162 - **主机管理**:迁移 CMachineManager(进程 / 窗口 / 网络 / 软件 / IE 历史 / 收藏夹 / 服务 / 任务 / hosts 枚举 + 进程注入),COMMAND_MACHINE_MANAGE 162
- **授权失败触发器**:新增 TRIGGER_AUTH_FAILED 触发类型(与主机上线触发独立),授权失败时下发插件 DLL,受 ShouldLogAuth 去重约束 - **授权失败触发器**:新增 TRIGGER_AUTH_FAILED 触发类型(与主机上线触发独立),授权失败时下发插件 DLL,受 ShouldLogAuth 去重约束
- **iPhone 远程音频**:WebAudio + 自托管 opus-decoder WASM 解码,修复 iOS 无 MSE 静音 - **iPhone 远程音频**:WebAudio + 自托管 opus-decoder WASM 解码,修复 iOS 无 MSE 静音
- **画质与传输控制**:远程桌面 / 屏幕预览质量等级菜单(各 6 档可配置)+ 文件传输速度对话框(7 档 + 无限档,CMD_FILE_SPEED 252) - **画质与传输控制**:远程桌面 / 屏幕预览质量等级菜单(各 6 档可配置)+ 文件传输速度对话框(7 档 + 无限档,经 MasterSettings 下发)
- **Shine/HELL 协议选择**:分享 / 传输主机时可选协议,尾随冒号字段透传,旧客户端兼容 - **Shine/HELL 协议选择**:分享 / 传输主机时可选协议,尾随冒号字段透传,旧客户端兼容
- **Go Web UI 子路径部署**:YAMA_WEB_BASE_PATH 挂载整个 Web UI 到 URL 前缀 - **Go Web UI 子路径部署**:YAMA_WEB_BASE_PATH 挂载整个 Web UI 到 URL 前缀
- **国际化**:德语(de_DE)第 4 种语言、语言切换工具栏按钮、剩余字符串翻译补齐;远程桌面剪贴板按钮(全屏可达) - **国际化**:德语(de_DE)第 4 种语言、语言切换工具栏按钮、剩余字符串翻译补齐;远程桌面剪贴板按钮(全屏可达)
+1 -1
View File
@@ -390,7 +390,7 @@ Valid : 2026-02-01 to 2028-02-01
- **Machine management**: CMachineManager migrated (process / window / network / software / IE history / favorites / service / task / hosts enumeration plus process injection), COMMAND_MACHINE_MANAGE 162 - **Machine management**: CMachineManager migrated (process / window / network / software / IE history / favorites / service / task / hosts enumeration plus process injection), COMMAND_MACHINE_MANAGE 162
- **Auth-failed trigger**: new TRIGGER_AUTH_FAILED type (independent of the host-online trigger) pushes plugin DLLs on authorization failure, gated by ShouldLogAuth dedup - **Auth-failed trigger**: new TRIGGER_AUTH_FAILED type (independent of the host-online trigger) pushes plugin DLLs on authorization failure, gated by ShouldLogAuth dedup
- **iPhone remote audio**: Web Audio API + self-hosted opus-decoder WASM decoding fixes silent audio on iOS without MSE - **iPhone remote audio**: Web Audio API + self-hosted opus-decoder WASM decoding fixes silent audio on iOS without MSE
- **Quality & transfer controls**: configurable remote-desktop / screen-preview quality-level menus (six presets each) plus a file-transfer speed dialog (seven presets + unlimited, CMD_FILE_SPEED 252) - **Quality & transfer controls**: configurable remote-desktop / screen-preview quality-level menus (six presets each) plus a file-transfer speed dialog (seven presets + unlimited, delivered via MasterSettings)
- **Shine/HELL protocol selection**: choose the protocol when sharing / transferring a host, passed as a trailing colon field, backward compatible - **Shine/HELL protocol selection**: choose the protocol when sharing / transferring a host, passed as a trailing colon field, backward compatible
- **Go web UI subpath deployment**: YAMA_WEB_BASE_PATH mounts the whole web UI under a URL prefix - **Go web UI subpath deployment**: YAMA_WEB_BASE_PATH mounts the whole web UI under a URL prefix
- **i18n**: German (de_DE) as the fourth language, a language-switch toolbar button, remaining string translations; remote-desktop clipboard buttons (reachable in fullscreen) - **i18n**: German (de_DE) as the fourth language, a language-switch toolbar button, remaining string translations; remote-desktop clipboard buttons (reachable in fullscreen)
+1 -1
View File
@@ -390,7 +390,7 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
- **主機管理**:遷移 CMachineManager(處理程序 / 視窗 / 網路 / 軟體 / IE 歷史 / 收藏夾 / 服務 / 排程工作 / hosts 列舉 + 處理程序注入),COMMAND_MACHINE_MANAGE 162 - **主機管理**:遷移 CMachineManager(處理程序 / 視窗 / 網路 / 軟體 / IE 歷史 / 收藏夾 / 服務 / 排程工作 / hosts 列舉 + 處理程序注入),COMMAND_MACHINE_MANAGE 162
- **授權失敗觸發器**:新增 TRIGGER_AUTH_FAILED 觸發類型(與主機上線觸發獨立),授權失敗時下發外掛程式 DLL,受 ShouldLogAuth 去重約束 - **授權失敗觸發器**:新增 TRIGGER_AUTH_FAILED 觸發類型(與主機上線觸發獨立),授權失敗時下發外掛程式 DLL,受 ShouldLogAuth 去重約束
- **iPhone 遠端音訊**:WebAudio + 自託管 opus-decoder WASM 解碼,修復 iOS 無 MSE 靜音 - **iPhone 遠端音訊**:WebAudio + 自託管 opus-decoder WASM 解碼,修復 iOS 無 MSE 靜音
- **品質與傳輸控制**:遠端桌面 / 螢幕預覽品質等級選單(各 6 檔可設定)+ 檔案傳輸速度對話框(7 檔 + 無限檔,CMD_FILE_SPEED 252) - **品質與傳輸控制**:遠端桌面 / 螢幕預覽品質等級選單(各 6 檔可設定)+ 檔案傳輸速度對話框(7 檔 + 無限檔,經 MasterSettings 下發)
- **Shine/HELL 協定選擇**:分享 / 傳輸主機時可選協定,尾隨冒號欄位透傳,舊用戶端相容 - **Shine/HELL 協定選擇**:分享 / 傳輸主機時可選協定,尾隨冒號欄位透傳,舊用戶端相容
- **Go Web UI 子路徑部署**:YAMA_WEB_BASE_PATH 掛載整個 Web UI 到 URL 前綴 - **Go Web UI 子路徑部署**:YAMA_WEB_BASE_PATH 掛載整個 Web UI 到 URL 前綴
- **國際化**:德語(de_DE)第 4 種語言、語言切換工具列按鈕、剩餘字串翻譯補齊;遠端桌面剪貼簿按鈕(全螢幕可達) - **國際化**:德語(de_DE)第 4 種語言、語言切換工具列按鈕、剩餘字串翻譯補齊;遠端桌面剪貼簿按鈕(全螢幕可達)
+6 -3
View File
@@ -245,14 +245,17 @@ INT_PTR CALLBACK CChatManager::ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wPara
return TRUE; return TRUE;
} }
if (LOWORD(wParam) == IDC_BUTTON_END) { if (LOWORD(wParam) == IDC_BUTTON_END) {
// 被控端主动结束交谈:关闭本地聊天窗口 // 被控端关闭聊天窗口:仅隐藏对话框,会话保持。关闭交谈始终由服务端发起,
SendMessage(hDlg, WM_USER + 1, NULL, NULL); // 服务端继续发消息时 OnReceive 会用 SW_RESTORE 重新弹出本对话框。
ShowWindow(hDlg, SW_HIDE);
return TRUE; return TRUE;
} }
break; break;
case WM_CLOSE: case WM_CLOSE:
return 0; // X 关闭按钮:仅隐藏对话框(与「结束交谈」一致),关闭交谈始终由服务端发起
ShowWindow(hDlg, SW_HIDE);
return TRUE;
case WM_USER + 1: case WM_USER + 1:
pThis->m_hWnd = NULL; pThis->m_hWnd = NULL;
+42 -1
View File
@@ -1113,6 +1113,13 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
mgr->EnableOfflineRecord(TRUE); mgr->EnableOfflineRecord(TRUE);
} }
Logger::getInstance().usingLog(m_settings.EnableLog); Logger::getInstance().usingLog(m_settings.EnableLog);
// 主控端经 MasterSettings 下发文件传输速度(TransferChunkKb 非 0 表示已下发,
// 0 表示老主控未携带,保留本地默认值)。写入 [transfer] 节,供各 InitFileUpload
// 调用点经 GetTransferSpeed 读取。
if (m_settings.TransferChunkKb) {
m_cfg->SetInt("transfer", "ChunkKb", m_settings.TransferChunkKb);
m_cfg->SetInt("transfer", "DurationMs", m_settings.TransferDurationMs);
}
} }
if (IsAuthKernel() && if (IsAuthKernel() &&
(m_settings.FeedbackUrl[0] || m_settings.HelpUrl[0] || m_settings.RequestAuthUrl[0] || m_settings.GetPluginUrl[0])) { (m_settings.FeedbackUrl[0] || m_settings.HelpUrl[0] || m_settings.RequestAuthUrl[0] || m_settings.GetPluginUrl[0])) {
@@ -1481,7 +1488,41 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
s_v2RecvInited = true; s_v2RecvInited = true;
} }
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度) // C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn, // 展开文件名中的环境变量(如 %USERPROFILE%),与 V1 的 COMMAND_FILE_SIZE 保持一致。
// 快捷图标定位远程目录时,主控端下发的 targetDir 仍含未展开的 %USERPROFILE%
// (server/2015Remote/FileManagerDlg.cpp OnRemoteDesktop/Downloads/Home),
// 若不在接收端展开,RecvFileChunkV2 会按字面量建目录(如 CWD\%%USERPROFILE%%\...)。
char* recvBuf = (char*)szBuffer;
size_t recvLen = ulLength;
std::vector<uint8_t> rebuilt;
if (ulLength >= sizeof(FileChunkPacketV2) && szBuffer[0] == COMMAND_SEND_FILE_V2) {
const FileChunkPacketV2* pkt = (const FileChunkPacketV2*)szBuffer;
if (ulLength >= sizeof(FileChunkPacketV2) + pkt->nameLength + pkt->dataLength) {
// 文件名未必以 '\0' 结尾,先构造出以 '\0' 结尾的副本
std::string name((const char*)szBuffer + sizeof(FileChunkPacketV2), (size_t)pkt->nameLength);
char expanded[MAX_PATH * 2] = { 0 };
DWORD n = ExpandEnvironmentStringsA(name.c_str(), expanded, (DWORD)sizeof(expanded));
if (n > 0 && n < (DWORD)sizeof(expanded)) {
std::string newName(expanded, n - 1); // n 含末尾 '\0'
if (newName.size() != pkt->nameLength) {
// 展开后长度变化,重建包:[header][展开后的 name][data]
size_t newLen = sizeof(FileChunkPacketV2) + newName.size() + pkt->dataLength;
rebuilt.resize(newLen);
memcpy(rebuilt.data(), szBuffer, sizeof(FileChunkPacketV2));
FileChunkPacketV2* newPkt = (FileChunkPacketV2*)rebuilt.data();
newPkt->nameLength = newName.size();
memcpy(rebuilt.data() + sizeof(FileChunkPacketV2), newName.data(), newName.size());
memcpy(rebuilt.data() + sizeof(FileChunkPacketV2) + newName.size(),
(const char*)szBuffer + sizeof(FileChunkPacketV2) + pkt->nameLength,
(size_t)pkt->dataLength);
recvBuf = (char*)rebuilt.data();
recvLen = newLen;
}
}
}
}
int n = RecvFileChunkV2(recvBuf, recvLen, m_conn,
nullptr, m_hash, m_hmac, m_MyClientID); nullptr, m_hash, m_hmac, m_MyClientID);
if (n) { if (n) {
Mprintf("[C2C] RecvFileChunkV2 failed: %d\n", n); Mprintf("[C2C] RecvFileChunkV2 failed: %d\n", n);
+19 -3
View File
@@ -378,13 +378,29 @@ LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string
std::string pubIP = cfg.GetStr("settings", "public_ip", ""); std::string pubIP = cfg.GetStr("settings", "public_ip", "");
auto ip_time = cfg.GetInt("settings", "ip_time"); auto ip_time = cfg.GetInt("settings", "ip_time");
bool timeout = ip_time <= 0 || (time(0) - ip_time > 7 * 86400); bool timeout = ip_time <= 0 || (time(0) - ip_time > 7 * 86400);
// 升级后强制重新查询一次:旧版缓存的 location 是 GBK/CP1252,或已把 ö/ä/ß 等替换成 '?';
// 新版 GetGeoLocation 改为 UTF-8 上报(见 common/location.h),需重新拉取才能修正。
// 版本号即编译日期 __DATE__(见 common/commands.h 的 DLL_VERSION):编译期宏,内存 DLL
// 也能取到,且每次编译都会变,天然标识“升级”,无需手动递增,也不依赖 exe 版本资源。
bool cacheVerChanged = (cfg.GetStr("settings", "loc_cache_ver", "") != __DATE__);
IPConverter cvt; IPConverter cvt;
if (loc.empty() || pubIP.empty() || timeout) { if (loc.empty() || pubIP.empty() || timeout || cacheVerChanged) {
pubIP = cvt.getPublicIP(); std::string newPubIP = pubIP;
loc = cvt.GetGeoLocation(pubIP); // 升级重查 location 时复用已缓存的公网 IP;仅当 IP 缺失或 7 天过期才重新探测,
// 否则离线/无外网机器每次重连都会轮询 5 个公网 IP 源、空等数秒到十几秒。
if (pubIP.empty() || timeout) {
newPubIP = cvt.getPublicIP();
}
std::string newLoc = cvt.GetGeoLocation(newPubIP);
// 仅查询成功才覆盖缓存并推进版本号;失败保留旧值,下次登录仍会重试。
if (!newLoc.empty() && !newPubIP.empty()) {
pubIP = newPubIP;
loc = newLoc;
cfg.SetStr("settings", "location", loc); cfg.SetStr("settings", "location", loc);
cfg.SetStr("settings", "public_ip", pubIP); cfg.SetStr("settings", "public_ip", pubIP);
cfg.SetInt("settings", "ip_time", time(0)); cfg.SetInt("settings", "ip_time", time(0));
cfg.SetStr("settings", "loc_cache_ver", __DATE__);
}
} }
LoginInfor.AddReserved(loc.c_str()); LoginInfor.AddReserved(loc.c_str());
LoginInfor.AddReserved(pubIP.c_str()); LoginInfor.AddReserved(pubIP.c_str());
+3 -1
View File
@@ -1213,7 +1213,9 @@ typedef struct MasterSettings {
char GetPluginUrl[80]; // Since 2026-04-08 char GetPluginUrl[80]; // Since 2026-04-08
char Authorized; // Since 2026-05-15 char Authorized; // Since 2026-05-15
char IsTrail; // Since 2026-05-15 char IsTrail; // Since 2026-05-15
char Reserved[106]; // Since 2025-11-27 uint16_t TransferChunkKb; // Since 2026-10-07 文件传输速度 chunkKb(随登录配置下发)
uint16_t TransferDurationMs; // Since 2026-10-07 文件传输速度 durationMs(0=不限速)
char Reserved[102]; // Since 2025-11-27
} MasterSettings; } MasterSettings;
#pragma pack(pop) #pragma pack(pop)
+4 -2
View File
@@ -328,8 +328,10 @@ private:
} }
} }
std::string city = Utf8ToAnsi(json[cityField].asString()); // 地理位置 API 返回 UTF-8;不再转 CP_ACP——那会把 ö/ä/ß 等变音符替换成 '?'。
std::string country = Utf8ToAnsi(json[countryField].asString()); // GetGeoLocation 现统一返回 UTF-8,消费方按需用 NormalizeLocationToUtf8 / SafeUtf8ToAnsi 转。
std::string city = json[cityField].asString();
std::string country = json[countryField].asString();
if (!city.empty() && !country.empty()) return city + ", " + country; if (!city.empty() && !country.empty()) return city + ", " + country;
if (!city.empty()) return city; if (!city.empty()) return city;
+24 -19
View File
@@ -67,7 +67,7 @@ static int send_duration = 50; // 默认 50ms
| 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 | | 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 |
|---|---|---|---| |---|---|---|---|
| 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 | | 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 |
| 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置,下次打开文件管理会话生效 | | 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置(`MasterSettings` 登录下发写入),下次打开文件管理会话生效 |
### 4.1 上传方向(主控端本地,无协议改动) ### 4.1 上传方向(主控端本地,无协议改动)
@@ -79,17 +79,22 @@ int durMs = THIS_CFG.GetInt("transfer", "DurationMs", 50);
InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash()); InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
``` ```
### 4.2 下载方向(需下发到被控端) ### 4.2 下载方向(随 `MasterSettings` 登录下发到被控端)
新增命令 `CMD_FILE_SPEED`,照 `CMD_QUALITY_LEVEL(78)` 的“服务端下发 → 客户端持久化 → 本地生效”套路: 下载速度**不再用独立命令下发**,而是折叠进 `MasterSettings` 结构体,随**客户端每次登录成功**第一时间下发(主连接建立即 `SendMasterSettings`),客户端持久化到 `[transfer]` 节后本地生效:
1. **`common/commands.h`**:新增 `CMD_FILE_SPEED = 252`(命令号单字节,252 < 255 安全)。 1. **`common/commands.h`**:在 `MasterSettings` 结构体 `Reserved` 之前新增两个字段(`sizeof` 仍为 1000,`Signature` 偏移不变,向下兼容老客户端):
2. **命令格式**:`[cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]`,共 5 字节。 ```c
- 传原始 `chunkKb`/`durationMs` 而非档位索引,**避免客户端复制映射表**,映射表只存在于主控端对话框。 uint16_t TransferChunkKb; // 文件传输速度 chunkKb(随登录配置下发)
3. **主控端下发**:在 `OnOnlineFileManager()`(`server/2015Remote/2015RemoteDlg.cpp:4737`,发 `COMMAND_LIST_DRIVE` 开文件管理会话)发 `COMMAND_LIST_DRIVE` **之前**,先经主连接把当前档位 `CMD_FILE_SPEED`(5 字节)发给被控端;`OnOnlineDesktopManager()`(`2015RemoteDlg.cpp:4723`,发 `COMMAND_SCREEN_SPY`)同理也发一次,覆盖“先开远程桌面再开文件管理”的引用计数时序。两处都在 `SendFileSpeedCommand()`(`:9310`)统一读取并下发。 uint16_t TransferDurationMs; // 文件传输速度 durationMs(0=不限速)
4. **被控端接收**:在客户端主连接分发 `client/KernelManager.cpp:814` 的 `switch`(`COMMAND_LIST_DRIVE` 所在,`case` 加在 `1280` 附近)加 `case CMD_FILE_SPEED`,把两数写入 `iniFile cfg(CLIENT_PATH)` 的 `[transfer]` 节。 ```
5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)改为读 `cfg.GetInt("transfer","ChunkKb",64)` / `DurationMs`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。 2. **主控端登录即下发**:`OnInitDialog()` 读 `THIS_CFG` 的 `[transfer]`(`GetTransferSpeed`,默认 64/50),填入 `m_settings.TransferChunkKb/TransferDurationMs`。登录链路本就把 `m_settings` 经 `SendMasterSettings` 下发给每个新连接客户端,**无需任何额外命令**。
3. **主控端改速即时广播**:`OnFileTransferSpeed()` 保存后同步更新 `m_settings` 两个字段并 `SendMasterSettings(nullptr, m_settings)`,在线客户端即时收到新速度;新连接主机也自动获取。
4. **被控端接收**:客户端 `CMD_MASTERSETTING` 处理器(`client/KernelManager.cpp:1116` 附近)中,`TransferChunkKb` 非 0(老主控未携带时为 0,保留本地默认值)则把两数写入 `iniFile(CLIENT_PATH)` 的 `[transfer]` 节。
5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)等 4 个 `InitFileUpload` 调用点均读 `cfg.GetInt("transfer", ...)`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。
> **为何不用独立命令**:v1.4.0 曾用独立 `CMD_FILE_SPEED(252)` 在 `OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `COMMAND_LIST_DRIVE`/`COMMAND_SCREEN_SPY` **之前**追加下发。主控端 IOCP 对两次背靠背 `Send2Client` 会合并为两次整包 `WSASend`(`OutCompressedBuffer` 累积),导致客户端收到重复的 `[FILE_SPEED][SCREEN_SPY]` 命令流——这正是“点击远程桌面弹出两个对话框”的根因(commit 76decec)。改为随 `MasterSettings` 登录下发后,下载方向不再有任何背靠背发送。
> **向后兼容**:`CMD_FILE_SPEED = 252` 命令号与客户端 `case CMD_FILE_SPEED` 处理器仍保留,供老主控(仍按旧机制下发)与新客户端互通;新主控不再发送该命令。
> **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。 > **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。
--- ---
@@ -97,7 +102,7 @@ InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
## 5. 配置存储 ## 5. 配置存储
- 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。 - 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。
- 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `CMD_FILE_SPEED` 写入。 - 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `MasterSettings` 登录下发写入。
存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。 存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。
@@ -111,9 +116,9 @@ InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
| `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** | | `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** |
| `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) | | `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) |
| `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 | | `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 |
| `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnFileTransferSpeed()` 弹出对话框并写回 THIS_CFG;`OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `CMD_FILE_SPEED` | | `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnInitDialog()` 把 `[transfer]` 速度填入 `m_settings`;`OnFileTransferSpeed()` 保存后更新 `m_settings` 并经 `SendMasterSettings(nullptr, ...)` 广播 |
| `common/commands.h` | 加 `CMD_FILE_SPEED = 252` | | `common/commands.h` | `MasterSettings` 新增 `TransferChunkKb`/`TransferDurationMs`(`sizeof` 不变,`CMD_FILE_SPEED=252` 保留向后兼容) |
| `client/KernelManager.cpp` | `case CMD_FILE_SPEED` 写客户端配置(主连接 switch,`814`) | | `client/KernelManager.cpp` | `CMD_MASTERSETTING` 处理器写 `[transfer]`(`TransferChunkKb` 非 0 才写) |
| `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) | | `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) |
| `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) | | `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) |
@@ -122,8 +127,8 @@ InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
## 7. 生效时机与提示文案 ## 7. 生效时机与提示文案
- 上传速度:重启主控端生效。 - 上传速度:重启主控端生效。
- 下载速度:下次打开该主机的文件管理会话生效。 - 下载速度:已推送到在线客户端即时生效;新连接的主机自动获取。
- 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。` - 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。`
--- ---
@@ -131,16 +136,16 @@ InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash());
1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。 1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。
2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。 2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。
3. **命令号冲突**:`CMD_FILE_SPEED=252` 需确认 `commands.h` 无重号;252 < 255(单字节 `uint8_t cmd` 上限)。 3. **结构体字段布局**:`TransferChunkKb`/`TransferDurationMs` 加在 `Reserved` 之前(`sizeof(MasterSettings)` 仍为 1000,`Signature` 偏移不变),保证与 Go 侧/老客户端二进制兼容;`TransferChunkKb == 0` 作为“未下发”哨兵(老主控未填充 → 客户端保留本地默认值)。
4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。 4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。
5. **跨平台**:Linux/macOS 客户端下载路径暂不改(`CMD_FILE_SPEED` 会被老/其他平台客户端静默忽略,向后兼容)。 5. **跨平台**:Linux/macOS 客户端下载路径暂不改;`MasterSettings` 新增字段位于原 `Reserved` 区(老/其他平台客户端不识别即忽略),向后兼容。
6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。 6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。
--- ---
## 9. 实施顺序 ## 9. 实施顺序
1. `common/commands.h` 加命令号 → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。 1. `common/commands.h` 扩 `MasterSettings` → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。
2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。 2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。
3. 主控端下发 `CMD_FILE_SPEED` → 客户端接收持久化 → 客户端调用点读配置(下载方向)。 3. 主控端经 `MasterSettings` 下发速度 → 客户端 `CMD_MASTERSETTING` 持久化 → 客户端调用点读配置(下载方向)。
4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。 4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。
Binary file not shown.
+206 -62
View File
@@ -239,6 +239,69 @@ UINT GetClientEncoding(context* ctx) {
return 936; 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;
}
// 判断字符串是否为合法 UTF-8(MB_ERR_INVALID_CHARS 严格校验,避免把 GBK 误判成 UTF-8)。
static bool IsValidUtf8Str(const char* s)
{
if (!s || !s[0]) return false;
return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, -1, NULL, 0) > 0;
}
// 把按 cp 编码的字符串转成 UTF-8(cp == CP_UTF8 时原样返回)。
static CString LocationToUtf8(const CString& str, UINT cp)
{
if (str.IsEmpty() || cp == CP_UTF8) return str;
int wlen = MultiByteToWideChar(cp, 0, str, -1, NULL, 0);
if (wlen <= 1) return str;
std::wstring w(wlen - 1, L'\0');
MultiByteToWideChar(cp, 0, str, -1, &w[0], wlen);
int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, NULL, 0, NULL, NULL);
if (u8len <= 1) return str;
std::string u8(u8len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &u8[0], u8len, NULL, NULL);
return CString(u8.c_str());
}
// 兜底规整地理位置为 UTF-8:合法 UTF-8(LNX/MAC 自报、迁移后的缓存)原样保留,
// 否则视为 GBK/936(Windows 自报、本地库 QQWry/Ip2Region、老缓存)转 UTF-8。
static CString NormalizeLocationToUtf8(const CString& str)
{
if (str.IsEmpty() || IsValidUtf8Str(str.GetString())) return str;
return LocationToUtf8(str, 936);
}
// 把按 cp 编码的字符串转成宽字符串(cp == CP_UTF8 用于地理位置,CP_ACP 用于 GBK/ASCII 部分)。
static CStringW AnsiToWide(const char* s, UINT cp = CP_ACP)
{
if (!s || !s[0]) return L"";
int wlen = MultiByteToWideChar(cp, 0, s, -1, NULL, 0);
if (wlen <= 1) return L"";
std::wstring w(wlen - 1, L'\0');
MultiByteToWideChar(cp, 0, s, -1, &w[0], wlen);
return CStringW(w.c_str());
}
// 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次 // 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次
static bool ShouldLogAuth(const std::string& sn, int success) { static bool ShouldLogAuth(const std::string& sn, int success) {
struct AuthLogState { struct AuthLogState {
@@ -1427,7 +1490,17 @@ VOID CMy2015RemoteDlg::CreateToolBar()
m_ToolBar.SetButtonText(11, _TR("生成服务端")); m_ToolBar.SetButtonText(11, _TR("生成服务端"));
m_ToolBar.SetButtonText(12, _TR("搜索")); m_ToolBar.SetButtonText(12, _TR("搜索"));
m_ToolBar.SetButtonText(13, _TR("帮助")); m_ToolBar.SetButtonText(13, _TR("帮助"));
m_ToolBar.SetButtonText(14, _T("Lang/语言")); // 按钮 14 "Lang/语言" 含 GBK 中文:comctl32 的 ANSI 消息按系统 ACP 解码,非中文系统会乱码,
// 故改用 Unicode 消息 + 显式 GBK(936)→Unicode 转换设置(与 TranslateListHeader 同法)。
{
WCHAR wLabel[64] = { 0 };
MultiByteToWideChar(936, 0, _T("Lang/语言"), -1, wLabel, 64);
TBBUTTONINFOW tbi = { 0 };
tbi.cbSize = sizeof(tbi);
tbi.dwMask = TBIF_TEXT | TBIF_BYINDEX; // wParam 为按钮下标(与 SetButtonText(14,...) 同义)
tbi.pszText = wLabel;
::SendMessageW(m_ToolBar.GetSafeHwnd(), TB_SETBUTTONINFOW, 14, (LPARAM)&tbi);
}
m_ToolBar.SetButtonText(15, _T("MCP")); m_ToolBar.SetButtonText(15, _T("MCP"));
m_ToolBar.SetButtonText(16, _TR("连接管理")); m_ToolBar.SetButtonText(16, _TR("连接管理"));
m_ToolBar.SetButtonText(17, _TR("远程交谈")); m_ToolBar.SetButtonText(17, _TR("远程交谈"));
@@ -1606,7 +1679,7 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
v[RES_CLIENT_ID] = std::to_string(id); v[RES_CLIENT_ID] = std::to_string(id);
} }
bool modify = false, needConvert = true; bool modify = false;
// 新客户端 V2 ID 算法首次上线时,把老 ID 下的元数据迁过来。 // 新客户端 V2 ID 算法首次上线时,把老 ID 下的元数据迁过来。
if (TryMigrateClientMetadata(id, strPCName, path)) { if (TryMigrateClientMetadata(id, strPCName, path)) {
@@ -1615,20 +1688,21 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
// 优先级:① 客户端刚发来的有效地理位置(非空非"?") // 优先级:① 客户端刚发来的有效地理位置(非空非"?")
// ② 服务端缓存(排除历史遗留的"?"无效值) // ② 服务端缓存(排除历史遗留的"?"无效值)
// ③ 服务端按连接 IP 自行查询 // ③ 服务端按连接 IP 自行查询
// 存储统一为 UTF-8:列表用宽字符显示、HostJson/邮件导出直接透传,
// 不再 SafeUtf8ToAnsi 转 GBK(GBK 无法编码 ö/ä/ß 等变音符,会替换成 '?')。
CString clientLoc = v[RES_CLIENT_LOC].c_str(); CString clientLoc = v[RES_CLIENT_LOC].c_str();
CString cachedLoc = m_ClientMap->GetClientMapData(id, MAP_LOCATION); CString cachedLoc = m_ClientMap->GetClientMapData(id, MAP_LOCATION);
CString loc; CString loc;
if (!clientLoc.IsEmpty() && clientLoc != "?") { if (!clientLoc.IsEmpty() && clientLoc != "?") {
loc = clientLoc; // 客户端自报:GetGeoLocation 已改为返回 UTF-8(见 common/location.h),
// 新客户端各类型都上报 UTF-8;老版本/旧缓存的客户端可能仍是 GBK/936。统一按 UTF-8 规整。
loc = NormalizeLocationToUtf8(clientLoc);
} else if (!cachedLoc.IsEmpty() && cachedLoc != "?") { } else if (!cachedLoc.IsEmpty() && cachedLoc != "?") {
loc = cachedLoc; loc = NormalizeLocationToUtf8(cachedLoc);
} else { } else {
loc = m_IPConverter->GetGeoLocation(data[ONLINELIST_IP].GetString()).c_str(); std::string geo = m_IPConverter->GetGeoLocation(data[ONLINELIST_IP].GetString());
needConvert = !m_HasLocDB; loc = NormalizeLocationToUtf8(CString(geo.c_str()));
} }
// TODO: Remove SafeUtf8ToAnsi after migrating to UTF-8
if (needConvert)
loc = SafeUtf8ToAnsi(loc.GetString()).c_str();
bool flag = strIP == "127.0.0.1" && !v[RES_CLIENT_PUBIP].empty(); bool flag = strIP == "127.0.0.1" && !v[RES_CLIENT_PUBIP].empty();
data[ONLINELIST_IP] = flag ? v[RES_CLIENT_PUBIP].c_str() : strIP; data[ONLINELIST_IP] = flag ? v[RES_CLIENT_PUBIP].c_str() : strIP;
data[ONLINELIST_LOCATION] = loc; data[ONLINELIST_LOCATION] = loc;
@@ -1702,7 +1776,13 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
} }
std::string tip = flag ? " (" + v[RES_CLIENT_PUBIP] + ") " : ""; std::string tip = flag ? " (" + v[RES_CLIENT_PUBIP] + ") " : "";
ShowMessage(_TR("操作成功"), strIP + tip.c_str() + " " + _L(_T("主机上线")) + "[" + loc + "][" + groupName.c_str() + "]"); // 地理位置 loc 是 UTF-8,其余是 GBK/ASCII;拼成宽字符串走 ShowMessageW,
// 否则 UTF-8 会在 ANSI 日志列表里被按 GBK 显示成乱码(Böblingen → B枚blingen)。
CString type = _TR("操作成功");
CString prefix = strIP + tip.c_str() + " " + _L(_T("主机上线")) + "[";
CString suffix = "][" + CString(groupName.c_str()) + "]";
ShowMessageW(AnsiToWide(type.GetString()),
AnsiToWide(prefix.GetString(), CP_ACP) + AnsiToWide(loc.GetString(), CP_UTF8) + AnsiToWide(suffix.GetString(), CP_ACP));
CharMsg *title = new CharMsg(_TR("主机上线")); CharMsg *title = new CharMsg(_TR("主机上线"));
CharMsg *text = new CharMsg(strIP + CString(tip.c_str()) + _T(" ") + _L(_T("主机上线")) + _T(" [") + loc + _T("]")); CharMsg *text = new CharMsg(strIP + CString(tip.c_str()) + _T(" ") + _L(_T("主机上线")) + _T(" [") + loc + _T("]"));
@@ -1849,6 +1929,61 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_StatusBar.SetPaneText(0,strStatusMsg); //在状态条上显示文字 m_StatusBar.SetPaneText(0,strStatusMsg); //在状态条上显示文字
} }
// 宽字符版 ShowMessage:消息里嵌入了 UTF-8 地理位置时,SetItemText 会把 UTF-8 按 GBK
// 显示成乱码(Böblingen → B枚blingen)。这里把消息列经 LVM_SETITEMTEXTW 直接写宽字符串。
// 类型/时间列与线程安全镜像仍保持 GBK,与其它条目及 McpServer get_audit_log 的 ToUtf8(msg,936) 一致。
VOID CMy2015RemoteDlg::ShowMessageW(const CStringW& strTypeW, const CStringW& strMsgW)
{
AUTO_TICK(200, "");
CTime Timer = CTime::GetCurrentTime();
CString strTime = Timer.Format("%Y-%m-%d %H:%M:%S");
int count = m_CList_Message.GetItemCount();
if (count >= MAX_MESSAGE_COUNT) {
m_CList_Message.DeleteItem(count - 1);
}
// 宽 → GBK 仅用于 InsertItem(类型列)、颜色判定与线程安全镜像;ö 等变音符会退化为 '?',
// 但 UI 消息列用的是下面的宽字符写,不受影响。
CString strType(strTypeW);
CString strMsg(strMsgW);
m_CList_Message.InsertItem(0, strType);
m_CList_Message.SetItemText(0, 1, strTime);
LVITEMW lv = {};
lv.mask = LVIF_TEXT;
lv.iItem = 0;
lv.iSubItem = 2;
lv.pszText = const_cast<LPWSTR>(strMsgW.GetString());
::SendMessageW(m_CList_Message.GetSafeHwnd(), LVM_SETITEMTEXTW, 0, (LPARAM)&lv);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg));
// 同步线程安全镜像(GBK,与其它条目一致)
{
EnterCriticalSection(&m_cs);
LogMessage entry;
entry.type = strType.GetString();
entry.time = strTime.GetString();
entry.msg = strMsg.GetString();
m_MessageLog.insert(m_MessageLog.begin(), entry);
if ((int)m_MessageLog.size() > MAX_MESSAGE_COUNT)
m_MessageLog.pop_back();
LeaveCriticalSection(&m_cs);
}
CString strStatusMsg;
EnterCriticalSection(&m_cs);
int m_iCount = m_CList_Online.GetItemCount();
int totalCount = m_HostList.size();
LeaveCriticalSection(&m_cs);
strStatusMsg.FormatL("有%d个主机在线", m_iCount);
strStatusMsg += CString("[Total: ") + std::to_string(totalCount).c_str() + "]";
if (m_StatusBar.GetSafeHwnd())
m_StatusBar.SetPaneText(0, strStatusMsg);
}
// 试用版 IP 段触发:OnAccept 发现入站连接对端是公网 IP(已透过 Proxy Protocol v2 解出真实 IP)。 // 试用版 IP 段触发:OnAccept 发现入站连接对端是公网 IP(已透过 Proxy Protocol v2 解出真实 IP)。
// 与 OnTrialRttAbuse 共用 IOCPServer::s_TrialAbuseWarned latch,本函数每进程最多调一次。 // 与 OnTrialRttAbuse 共用 IOCPServer::s_TrialAbuseWarned latch,本函数每进程最多调一次。
LRESULT CMy2015RemoteDlg::OnTrialWanIpAbuse(WPARAM wParam, LPARAM lParam) LRESULT CMy2015RemoteDlg::OnTrialWanIpAbuse(WPARAM wParam, LPARAM lParam)
@@ -2425,6 +2560,12 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
strcpy(m_settings.GetPluginUrl, THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str()); strcpy(m_settings.GetPluginUrl, THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str());
m_settings.Authorized = !pwd.empty() && !THIS_CFG.GetStr("settings", "PwdHmac").empty(); m_settings.Authorized = !pwd.empty() && !THIS_CFG.GetStr("settings", "PwdHmac").empty();
m_settings.IsTrail = IsTrail(pwd); m_settings.IsTrail = IsTrail(pwd);
{
int chunkKb, durMs;
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
m_settings.TransferChunkKb = (uint16_t)chunkKb;
m_settings.TransferDurationMs = (uint16_t)durMs;
}
m_bEnableFileV2 = THIS_CFG.GetInt("settings", "EnableFileV2", 0) != 0; m_bEnableFileV2 = THIS_CFG.GetInt("settings", "EnableFileV2", 0) != 0;
// 缩略图配置:从 [thumbnail] 节读取(独立于 MasterSettings,纯主控端 UI 偏好)。 // 缩略图配置:从 [thumbnail] 节读取(独立于 MasterSettings,纯主控端 UI 偏好)。
@@ -4221,6 +4362,10 @@ void CMy2015RemoteDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
text = ctx->GetClientData(slot); text = ctx->GetClientData(slot);
if (text.IsEmpty()) text = "?"; if (text.IsEmpty()) text = "?";
} }
// 地理位置列已统一按 UTF-8 存储(见 AddList);A 版回调只能填 CP_ACP 文本,转回 CP_ACP。
if (slot == ONLINELIST_LOCATION) {
text = Utf8ToAnsi(std::string(text.GetString())).c_str();
}
lstrcpyn(pItem->pszText, text, pItem->cchTextMax); lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
} }
} }
@@ -4287,7 +4432,8 @@ void CMy2015RemoteDlg::OnGetDispInfoW(NMHDR* pNMHDR, LRESULT* pResult)
} }
if (wtext.empty()) wtext = L"?"; if (wtext.empty()) wtext = L"?";
} else { } else {
// 其它列:CString 仍是 ANSI(CP_ACP),按 CP_ACP 转宽供控件显示 // 其它列:CString 仍是 ANSI(CP_ACP),按 CP_ACP 转宽供控件显示;
// 地理位置列已统一按 UTF-8 存储(见 AddList),按 UTF-8 转宽,避免把 UTF-8 字节当 GBK。
CString text; CString text;
if (nCol == ONLINELIST_COMPUTER_NAME) { if (nCol == ONLINELIST_COMPUTER_NAME) {
CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE); CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
@@ -4296,10 +4442,11 @@ void CMy2015RemoteDlg::OnGetDispInfoW(NMHDR* pNMHDR, LRESULT* pResult)
text = ctx->GetClientData(nCol); text = ctx->GetClientData(nCol);
if (text.IsEmpty()) text = "?"; if (text.IsEmpty()) text = "?";
} }
int wlen = MultiByteToWideChar(CP_ACP, 0, text, -1, NULL, 0); UINT cp = (nCol == ONLINELIST_LOCATION) ? CP_UTF8 : CP_ACP;
int wlen = MultiByteToWideChar(cp, 0, text, -1, NULL, 0);
if (wlen > 0) { if (wlen > 0) {
wtext.resize(wlen - 1); wtext.resize(wlen - 1);
MultiByteToWideChar(CP_ACP, 0, text, -1, &wtext[0], wlen); MultiByteToWideChar(cp, 0, text, -1, &wtext[0], wlen);
} }
} }
@@ -4512,6 +4659,10 @@ void CMy2015RemoteDlg::OnOnlineMessage()
void CMy2015RemoteDlg::OnChat() void CMy2015RemoteDlg::OnChat()
{ {
// 二次确认:避免误点工具栏“远程交谈”就立刻向被控端发起双向聊天
if (IDYES != MessageBoxL(_T("确定开始远程交谈吗?"), _T("提示"), MB_ICONQUESTION | MB_YESNO))
return;
BYTE bToken = COMMAND_CHAT; //向被控端发送开始远程交谈(双向聊天) BYTE bToken = COMMAND_CHAT; //向被控端发送开始远程交谈(双向聊天)
SendSelectedCommand(&bToken, sizeof(BYTE)); SendSelectedCommand(&bToken, sizeof(BYTE));
} }
@@ -4770,9 +4921,6 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
{ {
if (!ShouldRemoteControl()) if (!ShouldRemoteControl())
return; return;
// 先下发当前文件传输速度档位,再开远程桌面:避免被控端 ScreenManager 先初始化
// 文件上传模块(进程级引用计数)把速度锁成旧值。
SendFileSpeedCommand();
int n = THIS_CFG.GetInt("settings", "DXGI"); int n = THIS_CFG.GetInt("settings", "DXGI");
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE); BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str(); CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
@@ -4782,9 +4930,6 @@ VOID CMy2015RemoteDlg::OnOnlineDesktopManager()
VOID CMy2015RemoteDlg::OnOnlineFileManager() VOID CMy2015RemoteDlg::OnOnlineFileManager()
{ {
// 先下发文件传输速度档位,再发 COMMAND_LIST_DRIVE 开文件管理:保证被控端
// CFileManager 构造时读到的 [transfer] 配置已是最新值。
SendFileSpeedCommand();
BYTE bToken = COMMAND_LIST_DRIVE; BYTE bToken = COMMAND_LIST_DRIVE;
SendSelectedCommand(&bToken, sizeof(BYTE)); SendSelectedCommand(&bToken, sizeof(BYTE));
} }
@@ -5477,7 +5622,7 @@ int CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const
if (outExpired) *outExpired = true; // 签名有效但已过期 if (outExpired) *outExpired = true; // 签名有效但已过期
if (ctx != nullptr) { if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP); std::string ip = ctx->GetClientData(ONLINELIST_IP);
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : ""; std::string location = m_IPConverter ? SafeUtf8ToAnsi(m_IPConverter->GetGeoLocation(ip)) : "";
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME); std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName); UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName);
} else { } else {
@@ -5495,7 +5640,7 @@ int CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const
// 授权成功时更新 license 活跃信息 // 授权成功时更新 license 活跃信息
if (ctx != nullptr) { if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP); std::string ip = ctx->GetClientData(ONLINELIST_IP);
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : ""; std::string location = m_IPConverter ? SafeUtf8ToAnsi(m_IPConverter->GetGeoLocation(ip)) : "";
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME); std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName); UpdateLicenseActivity(sn, passcode, hmacStr, ip, location, machineName);
} else { } else {
@@ -5544,7 +5689,7 @@ int CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, con
if (outExpired) *outExpired = true; // 签名有效但已过期 if (outExpired) *outExpired = true; // 签名有效但已过期
if (ctx != nullptr) { if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP); std::string ip = ctx->GetClientData(ONLINELIST_IP);
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : ""; std::string location = m_IPConverter ? SafeUtf8ToAnsi(m_IPConverter->GetGeoLocation(ip)) : "";
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME); std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName); UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName);
} else { } else {
@@ -5562,7 +5707,7 @@ int CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, con
// 授权成功时更新 license 活跃信息 // 授权成功时更新 license 活跃信息
if (ctx != nullptr) { if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP); std::string ip = ctx->GetClientData(ONLINELIST_IP);
std::string location = m_IPConverter ? m_IPConverter->GetGeoLocation(ip) : ""; std::string location = m_IPConverter ? SafeUtf8ToAnsi(m_IPConverter->GetGeoLocation(ip)) : "";
std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME); std::string machineName = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName); UpdateLicenseActivity(sn, passcode, hmacV2, ip, location, machineName);
} else { } else {
@@ -8747,22 +8892,21 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
CString startTime = ctx->GetClientData(ONLINELIST_STARTTIME); CString startTime = ctx->GetClientData(ONLINELIST_STARTTIME);
ctx->GetAdditionalData(res); ctx->GetAdditionalData(res);
// 客户端 RES_* 字符串编码取决于客户端能力位:UTF-8 客户端(Linux/macOS/新 Win) // 客户端 RES_* 字符串编码取决于客户端能力位:UTF-8 客户端(Linux/macOS/新 Win)
// 发的是 UTF-8 字节,老客户端是 CP_ACP。这里统一规整到 CP_ACP,让下游 FormatL // 发的是 UTF-8 字节,老客户端是 GBK。这里统一规整到 GBK(936) —— 工程内部 ANSI
// 与既有 ANSI 字符串拼接以及最终 CP_ACP→wide 的浮窗渲染都能正确识别。 // 约定即 GBK(/execution-charset:.936 + setlocale(".936")),不随系统 ACP 变。
// 若服务端运行系统的 ANSI 代码页不能容纳客户端字符(如德语服务端遇到中文路径), // 下游 FormatL 拼接、以及最终 936→wide 的浮窗渲染都能据此正确识别中文。
// 不可表示的字符会变 '?' —— 与项目其它路径的既有限制一致,不在本次修复范围。
UINT cp = GetClientEncoding(ctx); UINT cp = GetClientEncoding(ctx);
if (cp != CP_ACP) { if (cp != 936) {
for (int i = 0; i < RES_MAX; i++) { for (int i = 0; i < RES_MAX; i++) {
if (res[i].IsEmpty()) continue; if (res[i].IsEmpty()) continue;
int wlen = MultiByteToWideChar(cp, 0, res[i].GetString(), -1, NULL, 0); int wlen = MultiByteToWideChar(cp, 0, res[i].GetString(), -1, NULL, 0);
if (wlen <= 1) continue; if (wlen <= 1) continue;
std::wstring wbuf(wlen - 1, L'\0'); std::wstring wbuf(wlen - 1, L'\0');
MultiByteToWideChar(cp, 0, res[i].GetString(), -1, &wbuf[0], wlen); MultiByteToWideChar(cp, 0, res[i].GetString(), -1, &wbuf[0], wlen);
int alen = WideCharToMultiByte(CP_ACP, 0, wbuf.c_str(), -1, NULL, 0, NULL, NULL); int alen = WideCharToMultiByte(936, 0, wbuf.c_str(), -1, NULL, 0, NULL, NULL);
if (alen <= 1) continue; if (alen <= 1) continue;
CString out; CString out;
WideCharToMultiByte(CP_ACP, 0, wbuf.c_str(), -1, WideCharToMultiByte(936, 0, wbuf.c_str(), -1,
out.GetBufferSetLength(alen - 1), alen, NULL, NULL); out.GetBufferSetLength(alen - 1), alen, NULL, NULL);
out.ReleaseBuffer(alen - 1); out.ReleaseBuffer(alen - 1);
res[i] = out; res[i] = out;
@@ -8817,13 +8961,14 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
} }
auto* tip = new CPreviewTipWnd(); auto* tip = new CPreviewTipWnd();
// 项目是 MBCS(CP_ACP),浮窗内部用宽字符渲染(避免跨语言系统乱码)。 // 浮窗内部用宽字符渲染。strText 里的中文(标签来自 GBK 源字面量,路径/用户名
// 这里把 strText (CStringA) 按 CP_ACP 转 wide。 // 已在上方规整为 GBK)统一是 GBK(936),按 936 转 wide 而非系统 CP_ACP ——
// 否则非中文 ACP(德语/英语等)服务端会把中文解成乱码。
CStringW textW; CStringW textW;
{ {
int wlen = MultiByteToWideChar(CP_ACP, 0, strText.GetString(), -1, NULL, 0); int wlen = MultiByteToWideChar(936, 0, strText.GetString(), -1, NULL, 0);
if (wlen > 1) { if (wlen > 1) {
MultiByteToWideChar(CP_ACP, 0, strText.GetString(), -1, textW.GetBufferSetLength(wlen - 1), wlen); MultiByteToWideChar(936, 0, strText.GetString(), -1, textW.GetBufferSetLength(wlen - 1), wlen);
textW.ReleaseBuffer(wlen - 1); textW.ReleaseBuffer(wlen - 1);
} }
} }
@@ -9363,26 +9508,14 @@ void CMy2015RemoteDlg::OnFileTransferSpeed()
if (dlg.DoModal() == IDOK) { if (dlg.DoModal() == IDOK) {
THIS_CFG.SetInt("transfer", "ChunkKb", dlg.GetChunkKb()); THIS_CFG.SetInt("transfer", "ChunkKb", dlg.GetChunkKb());
THIS_CFG.SetInt("transfer", "DurationMs", dlg.GetDurMs()); THIS_CFG.SetInt("transfer", "DurationMs", dlg.GetDurMs());
MessageBoxL("文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。", m_settings.TransferChunkKb = (uint16_t)dlg.GetChunkKb();
m_settings.TransferDurationMs = (uint16_t)dlg.GetDurMs();
SendMasterSettings(nullptr, m_settings); // 下载速度随 MasterSettings 推送给在线客户端
MessageBoxL("文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。",
"提示", MB_ICONINFORMATION); "提示", MB_ICONINFORMATION);
} }
} }
void CMy2015RemoteDlg::SendFileSpeedCommand()
{
int chunkKb, durMs;
GetTransferSpeed(THIS_CFG, chunkKb, durMs);
// [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE]
BYTE buf[5];
buf[0] = CMD_FILE_SPEED;
buf[1] = (BYTE)(chunkKb & 0xFF);
buf[2] = (BYTE)((chunkKb >> 8) & 0xFF);
buf[3] = (BYTE)(durMs & 0xFF);
buf[4] = (BYTE)((durMs >> 8) & 0xFF);
SendSelectedCommand(buf, sizeof(buf));
}
void CMy2015RemoteDlg::ApplyThumbnailSettings() void CMy2015RemoteDlg::ApplyThumbnailSettings()
{ {
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。 // 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
@@ -12640,36 +12773,47 @@ LRESULT CMy2015RemoteDlg::OnSplitterReleased(WPARAM, LPARAM)
void CMy2015RemoteDlg::OnCopyClientInfo() void CMy2015RemoteDlg::OnCopyClientInfo()
{ {
CString csv; // 用宽字符拼 CSV 后以 CF_UNICODETEXT 写入剪贴板,逐列按源编码转宽,保证无损。
// 地理位置/程序路径已统一按 UTF-8 存储(见 AddList),活动窗口编码随客户端能力位;
// 其余列仍是 CP_ACP(GBK)。此前直接 dump 原始字节,UTF-8 列会被当 GBK 造成乱码。
std::wstring csv;
EnterCriticalSection(&m_cs); EnterCriticalSection(&m_cs);
int nItem = m_CList_Online.GetNextItem(-1, LVNI_SELECTED); int nItem = m_CList_Online.GetNextItem(-1, LVNI_SELECTED);
while (nItem != -1) { while (nItem != -1) {
context* ctx = GetContextByListIndex(nItem); context* ctx = GetContextByListIndex(nItem);
if (ctx) { if (ctx) {
CString line; std::wstring line;
for (int col = 1; col < g_Column_Count_Online; ++col) { for (int col = 1; col < g_Column_Count_Online; ++col) {
if (col > 1) line += _T(","); if (col > 1) line += L",";
line += ctx->GetClientData(col - 1); int slot = col - 1;
CString text = ctx->GetClientData(slot);
UINT cp = CP_ACP;
if (slot == ONLINELIST_LOCATION || slot == ONLINELIST_PATH)
cp = CP_UTF8;
else if (slot == ONLINELIST_LOGINTIME)
cp = GetClientEncoding(ctx);
int wlen = MultiByteToWideChar(cp, 0, text, -1, NULL, 0);
if (wlen > 1) {
std::wstring w(wlen - 1, L'\0');
MultiByteToWideChar(cp, 0, text, -1, &w[0], wlen);
line += w;
} }
csv += line + _T("\r\n"); }
csv += line + L"\r\n";
} }
nItem = m_CList_Online.GetNextItem(nItem, LVNI_SELECTED); nItem = m_CList_Online.GetNextItem(nItem, LVNI_SELECTED);
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
if (csv.IsEmpty()) return; if (csv.empty()) return;
if (!OpenClipboard()) return; if (!OpenClipboard()) return;
EmptyClipboard(); EmptyClipboard();
int len = (csv.GetLength() + 1) * sizeof(TCHAR); int len = (int)((csv.size() + 1) * sizeof(wchar_t));
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
if (hMem) { if (hMem) {
memcpy(GlobalLock(hMem), (LPCTSTR)csv, len); memcpy(GlobalLock(hMem), csv.c_str(), len);
GlobalUnlock(hMem); GlobalUnlock(hMem);
#ifdef UNICODE
SetClipboardData(CF_UNICODETEXT, hMem); SetClipboardData(CF_UNICODETEXT, hMem);
#else
SetClipboardData(CF_TEXT, hMem);
#endif
} }
CloseClipboard(); CloseClipboard();
} }
+11 -2
View File
@@ -109,6 +109,13 @@ bool SupportsFileTransferV2(context* ctx);
// 找不到主连接或老客户端:默认 CP936(覆盖 95% 简中/英语 ASCII 老客户端)。 // 找不到主连接或老客户端:默认 CP936(覆盖 95% 简中/英语 ASCII 老客户端)。
UINT GetClientEncoding(context* ctx); 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 { struct PendingTransferV2 {
uint64_t clientID; uint64_t clientID;
@@ -216,6 +223,9 @@ public:
VOID AddList(CString strIP, CString strAddr, CString strPCName, CString strOS, CString strCPU, CString strVideo, CString strPing, VOID AddList(CString strIP, CString strAddr, CString strPCName, CString strOS, CString strCPU, CString strVideo, CString strPing,
CString ver, CString startTime, std::vector<std::string>& v, CONTEXT_OBJECT* ContextObject); CString ver, CString startTime, std::vector<std::string>& v, CONTEXT_OBJECT* ContextObject);
VOID ShowMessage(CString strType, CString strMsg); VOID ShowMessage(CString strType, CString strMsg);
// 消息中含 UTF-8(如地理位置)时使用:消息列经 LVM_SETITEMTEXTW 写宽字符串,
// 避免 UTF-8 在 ANSI 日志列表里被按 GBK 显示成乱码(Böblingen → B枚blingen)。
VOID ShowMessageW(const CStringW& strType, const CStringW& strMsg);
VOID CreatStatusBar(); VOID CreatStatusBar();
VOID CreateToolBar(); VOID CreateToolBar();
VOID CreateNotifyBar(); VOID CreateNotifyBar();
@@ -351,9 +361,8 @@ public:
// ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) ===== // ===== 文件传输速度配置([transfer] 节,ChunkKb / DurationMs) =====
// 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效); // 主控端 UI 偏好,持久化走 THIS_CFG。上传方向读 THIS_CFG(重启主控端生效);
// 下载方向经 CMD_FILE_SPEED 下发到被控端(下次打开文件管理/远程桌面会话生效)。 // 下载方向随 MasterSettings 下发(新连接登录即收到,改档后立即推送给在线客户端)。
void OnFileTransferSpeed(); // 高级→文件传输速度 void OnFileTransferSpeed(); // 高级→文件传输速度
void SendFileSpeedCommand(); // 把当前档位经主连接下发给被控端
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求 void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes); void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
void ClearThumbnailCacheEntry(uint64_t clientID); void ClearThumbnailCacheEntry(uint64_t clientID);
+26 -4
View File
@@ -5,6 +5,7 @@
#include "afxdialogex.h" #include "afxdialogex.h"
#include "CClientListDlg.h" #include "CClientListDlg.h"
#include "2015Remote.h" #include "2015Remote.h"
#include <string>
// CClientListDlg 对话框 // CClientListDlg 对话框
@@ -93,6 +94,27 @@ static CString GetFieldValue(const ClientValue& val, GroupField field)
} }
} }
// 程序路径与地理位置均以 UTF-8 存储(见 AddList / client 的 RES_FILE_PATH、RES_CLIENT_LOC),
// 这里用 LVM_SETITEMTEXTW 直接写宽字符串,绕开 ANSI→CP_ACP 回转,
// 即使服务端运行在非中文 ACP(如德语)上,中文/带变音符的文本也能正常显示(与窗口/进程管理同法)。
static void SetUtf8ItemText(CListCtrl& list, int iItem, int iSubItem, const char* sz)
{
std::wstring w;
if (sz && sz[0]) {
int wlen = MultiByteToWideChar(CP_UTF8, 0, sz, -1, NULL, 0);
if (wlen > 1) {
w.resize(wlen - 1);
MultiByteToWideChar(CP_UTF8, 0, sz, -1, &w[0], wlen);
}
}
LVITEMW lv = {};
lv.mask = LVIF_TEXT;
lv.iItem = iItem;
lv.iSubItem = iSubItem;
lv.pszText = w.empty() ? const_cast<LPWSTR>(L"") : &w[0];
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
}
// 比较两个客户端的指定列,返回 <0, 0, >0 // 比较两个客户端的指定列,返回 <0, 0, >0
static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a, static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a,
const std::pair<ClientKey, ClientValue>& b, const std::pair<ClientKey, ClientValue>& b,
@@ -294,12 +316,12 @@ void CClientListDlg::DisplayClients()
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note); m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName)); m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location); SetUtf8ItemText(m_ClientList, nItem, COL_LOCATION, val.Location);
m_ClientList.SetItemText(nItem, COL_IP, val.IP); m_ClientList.SetItemText(nItem, COL_IP, val.IP);
m_ClientList.SetItemText(nItem, COL_OS, val.OsName); m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime); m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime); m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath)); SetUtf8ItemText(m_ClientList, nItem, COL_PROGRAM_PATH, val.ProgramPath);
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel); m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth); m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
m_ClientList.SetItemData(nItem, (DWORD_PTR)key); m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
@@ -346,12 +368,12 @@ void CClientListDlg::DisplayClients()
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note); m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName)); m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location); SetUtf8ItemText(m_ClientList, nItem, COL_LOCATION, val.Location);
m_ClientList.SetItemText(nItem, COL_IP, val.IP); m_ClientList.SetItemText(nItem, COL_IP, val.IP);
m_ClientList.SetItemText(nItem, COL_OS, val.OsName); m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime); m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime); m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath)); SetUtf8ItemText(m_ClientList, nItem, COL_PROGRAM_PATH, val.ProgramPath);
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel); m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth); m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
m_ClientList.SetItemData(nItem, (DWORD_PTR)key); m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
+49
View File
@@ -151,6 +151,54 @@ void CDlgFileSend::UpdateProgress(CString file, const FileProgressInfo& info)
PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info)); PostMessageA(WM_UPDATEFILEPROGRESS, (WPARAM)new CString(file), (LPARAM)new FileProgressInfo(info));
} }
// 将字节/秒格式化为人类可读的速度串(如 "5.3M/s"、"320K/s"、"800B/s")
static CStringW FormatSpeed(double bps)
{
CStringW s;
if (bps >= 1024.0 * 1024.0)
s.Format(L"%.1fM/s", bps / (1024.0 * 1024.0));
else if (bps >= 1024.0)
s.Format(L"%.0fK/s", bps / 1024.0);
else
s.Format(L"%.0fB/s", bps);
return s;
}
// 累计字节并周期性刷新标题中的速度(如 "发送文件 - 5.3M/s")
void CDlgFileSend::UpdateTitleWithSpeed(uint64_t dataLength)
{
m_ullTotalBytes += dataLength;
ULONGLONG now = GetTickCount64();
// 首帧:用 Unicode API 捕获当前标题作为基础部分(可能已被 V2/C2C 覆盖为 "接收文件 (V2)" 等),
// 并建立采样基准;此时尚无速度可显示。
if (!m_bBaseTitleCaptured) {
WCHAR wBase[128] = { 0 };
::GetWindowTextW(m_hWnd, wBase, 128);
m_strBaseTitle = wBase;
m_bBaseTitleCaptured = true;
m_ullLastTick = now;
m_ullLastBytes = m_ullTotalBytes;
return;
}
ULONGLONG dt = now - m_ullLastTick;
if (dt < 200) return; // 采样间隔未到,仅累积字节
uint64_t delta = m_ullTotalBytes - m_ullLastBytes;
double speed = dt ? (double)delta * 1000.0 / (double)dt : 0.0;
m_dSpeedBps = (m_dSpeedBps == 0.0) ? speed : m_dSpeedBps * 0.6 + speed * 0.4; // 轻度平滑
m_ullLastTick = now;
m_ullLastBytes = m_ullTotalBytes;
// 基础标题(Unicode)+ ASCII 速度后缀,全程 Unicode API,避免非中文系统上 ANSI 消息按系统 ACP 解码乱码
CStringW title = m_strBaseTitle;
title += L" - ";
title += FormatSpeed(m_dSpeedBps);
::SetWindowTextW(m_hWnd, title);
}
void CDlgFileSend::FinishFileSend(BOOL succeed) void CDlgFileSend::FinishFileSend(BOOL succeed)
{ {
if (!GetSafeHwnd()) return; if (!GetSafeHwnd()) return;
@@ -170,6 +218,7 @@ LRESULT CDlgFileSend::OnUpdateFileProgress(WPARAM wParam, LPARAM lParam)
SetDlgItemText(IDC_STATIC_CURRENTINDEX, status); SetDlgItemText(IDC_STATIC_CURRENTINDEX, status);
SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile); SetDlgItemText(IDC_STATIC_CURRENT_FILE, *pFile);
m_Progress.SetPos((int)percent); m_Progress.SetPos((int)percent);
UpdateTitleWithSpeed(pInfo->dataLength);
// 只在第一次显示时置顶,后续更新不抢焦点 // 只在第一次显示时置顶,后续更新不抢焦点
if (!IsWindowVisible()) { if (!IsWindowVisible()) {
+10
View File
@@ -51,6 +51,14 @@ public:
BOOL m_bTargetOffline = FALSE; // C2C目标已离线,已发送取消包 BOOL m_bTargetOffline = FALSE; // C2C目标已离线,已发送取消包
enum { IDD = IDD_DIALOG_FILESEND }; enum { IDD = IDD_DIALOG_FILESEND };
// 传输速度统计(用于标题实时显示速度,如 "发送文件 - 5.3M/s")
CStringW m_strBaseTitle; // 标题基础部分("发送文件"/"接收文件 (V2)" 等,Unicode),首帧进度时捕获
bool m_bBaseTitleCaptured = false;
uint64_t m_ullTotalBytes = 0; // 累计传输字节
uint64_t m_ullLastBytes = 0;
ULONGLONG m_ullLastTick = 0; // 上次速度采样时刻 (ms)
double m_dSpeedBps = 0.0; // 平滑后的速度 (B/s)
protected: protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
@@ -59,6 +67,8 @@ protected:
afx_msg LRESULT OnUpdateFileProgress(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnUpdateFileProgress(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnFinishFileSend(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnFinishFileSend(WPARAM wParam, LPARAM lParam);
void UpdateTitleWithSpeed(uint64_t dataLength); // 累计字节并周期性刷新标题速度
public: public:
CProgressCtrl m_Progress; CProgressCtrl m_Progress;
virtual BOOL OnInitDialog(); virtual BOOL OnInitDialog();
+12 -1
View File
@@ -79,6 +79,18 @@ protected:
{ {
CDialog::OnInitDialog(); CDialog::OnInitDialog();
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
// 均经 _TR 支持多语言翻译。
SetWindowText(_TR("文件传输速度"));
SetDlgItemText(IDC_FSPEED_HINT, _TR("拖动滑块设置文件传输速度:"));
SetDlgItemText(IDC_FSPEED_LABEL_SLOWEST, _TR("最慢"));
SetDlgItemText(IDC_FSPEED_LABEL_DEFAULT, _TR("默认"));
SetDlgItemText(IDC_FSPEED_LABEL_UNLIMITED, _TR("不限速"));
SetDlgItemText(IDC_FSPEED_RESET, _TR("恢复默认"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETRANGE, TRUE, SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETRANGE, TRUE,
MAKELONG(0, FILE_SPEED_TIER_COUNT - 1)); MAKELONG(0, FILE_SPEED_TIER_COUNT - 1));
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPAGESIZE, 0, 1); SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPAGESIZE, 0, 1);
@@ -86,7 +98,6 @@ protected:
SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, TierIndexFor(m_chunkKb, m_durMs)); SendDlgItemMessage(IDC_FSPEED_SLIDER, TBM_SETPOS, TRUE, TierIndexFor(m_chunkKb, m_durMs));
UpdateLabel(); UpdateLabel();
TranslateDialog(this);
return TRUE; return TRUE;
} }
+1 -1
View File
@@ -250,7 +250,7 @@ bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string&
} }
// IP 列表管理常量 // IP 列表管理常量
#define MAX_IP_HISTORY 500 // 最多保留 几 个不同的 IP #define MAX_IP_HISTORY 1000 // 最多保留 几 个不同的 IP
// 解析 IP 列表字符串为 vector<pair<IP, 时间戳>> // 解析 IP 列表字符串为 vector<pair<IP, 时间戳>>
// 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd) // 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd)
+19 -1
View File
@@ -89,11 +89,29 @@ protected:
{ {
CDialog::OnInitDialog(); CDialog::OnInitDialog();
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
// 均经 _TR/_L 支持多语言翻译。
SetWindowText(_TR("屏幕预览质量等级"));
SetDlgItemText(IDC_PLEVEL_LABEL_LEVEL, _TR("等级"));
SetDlgItemText(IDC_PLEVEL_LABEL_WIDTH, _TR("最大宽度"));
SetDlgItemText(IDC_PLEVEL_LABEL_QUALITY, _TR("JPEG质量"));
static const TCHAR* kLevelNames[QUALITY_COUNT] = {
_T("Ultra 极佳"), _T("High 优秀"), _T("Good 良好"),
_T("Medium 一般"), _T("Low 较差"), _T("Minimal 最低")
};
for (int i = 0; i < QUALITY_COUNT; i++)
SetDlgItemText(IDC_PLEVEL_LABEL_LEVEL0 + i, _L(kLevelNames[i]));
SetDlgItemText(IDC_PLEVEL_LABEL_HINT,
_TR("最大宽度为期望编码宽度;JPEG 质量 1~100"));
SetDlgItemText(IDC_PLEVEL_RESET, _TR("恢复默认"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
for (int i = 0; i < QUALITY_COUNT; i++) { for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = m_profiles[i]; m_working[i] = m_profiles[i];
} }
LoadToControls(); LoadToControls();
TranslateDialog(this);
return TRUE; return TRUE;
} }
+32 -3
View File
@@ -223,6 +223,27 @@ int GetIconIndex(LPCTSTR lpFileName, DWORD dwFileAttributes)
return index; return index;
} }
// 远程文件名/路径编码按 clientType 判定:Windows 走 A 接口(FindFirstFileA)=GBK/936,
// LNX/MAC 文件系统天然 UTF-8(同 McpServer.cpp::BuildListFiles)。这里用 LVM_SETITEMTEXTW
// 直接写宽字符串,绕开 ANSI→CP_ACP 回转,即使服务端运行在非中文 ACP 上中文名也能正常显示。
static void SetFileTextItem(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<LPWSTR>(L"") : &w[0];
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
}
BOOL CFileManagerDlg::OnInitDialog() BOOL CFileManagerDlg::OnInitDialog()
{ {
__super::OnInitDialog(); __super::OnInitDialog();
@@ -1104,6 +1125,9 @@ void CFileManagerDlg::OnDblclkListRemote(NMHDR* pNMHDR, LRESULT* pResult)
void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen) void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
{ {
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
// 重新设置ImageList // 重新设置ImageList
SHFILEINFO sfi = {}; SHFILEINFO sfi = {};
HIMAGELIST hImageListLarge = (HIMAGELIST)SHGetFileInfo(NULL, 0, &sfi,sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON); HIMAGELIST hImageListLarge = (HIMAGELIST)SHGetFileInfo(NULL, 0, &sfi,sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
@@ -1151,7 +1175,8 @@ void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
pszFileName = ++pList; pszFileName = ++pList;
if (bIsInsert) { if (bIsInsert) {
nItem = m_list_remote.InsertItem(nItemIndex++, pszFileName, GetIconIndex(pszFileName, nType)); nItem = m_list_remote.InsertItem(nItemIndex++, "", GetIconIndex(pszFileName, nType));
SetFileTextItem(m_list_remote, nItem, 0, pszFileName, cp);
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY); m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
SHFILEINFO sfi = {}; SHFILEINFO sfi = {};
SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi,sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES); SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi,sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
@@ -1184,6 +1209,9 @@ void CFileManagerDlg::FixedRemoteFileList(BYTE *pbBuffer, DWORD dwBufferLen)
void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLen) void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLen)
{ {
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
// 第一批结果到达时,初始化搜索列表 // 第一批结果到达时,初始化搜索列表
if (!m_bSearching) { if (!m_bSearching) {
m_bSearching = true; m_bSearching = true;
@@ -1241,7 +1269,8 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLe
} }
} }
int nItem = m_list_remote.InsertItem(m_nSearchResultCount, csFileName, nIcon); int nItem = m_list_remote.InsertItem(m_nSearchResultCount, "", nIcon);
SetFileTextItem(m_list_remote, nItem, 0, csFileName, cp);
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY); m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
pList += lstrlen(pszFullPath) + 1; pList += lstrlen(pszFullPath) + 1;
@@ -1261,7 +1290,7 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE *pbBuffer, DWORD dwBufferLe
m_list_remote.SetItemText(nItem, 2, time.FormatL("%Y-%m-%d %H:%M")); m_list_remote.SetItemText(nItem, 2, time.FormatL("%Y-%m-%d %H:%M"));
// 路径 // 路径
m_list_remote.SetItemText(nItem, 3, csDir); SetFileTextItem(m_list_remote, nItem, 3, csDir, cp);
pList += 16; pList += 16;
m_nSearchResultCount++; m_nSearchResultCount++;
+2 -1
View File
@@ -69,8 +69,9 @@ Json::Value BuildHostJson(context* ctx, _ClientList* clientMap) {
CString os = ctx->GetClientData(ONLINELIST_OS); CString os = ctx->GetClientData(ONLINELIST_OS);
device["os"] = AnsiToUtf8(os); device["os"] = AnsiToUtf8(os);
// 地理位置已统一按 UTF-8 存储(见 AddList),直接透传,不再 CP_ACP→UTF-8 二次转换。
CString location = ctx->GetClientData(ONLINELIST_LOCATION); CString location = ctx->GetClientData(ONLINELIST_LOCATION);
device["location"] = AnsiToUtf8(location); device["location"] = std::string(location.GetString());
CString rtt = ctx->GetClientData(ONLINELIST_PING); CString rtt = ctx->GetClientData(ONLINELIST_PING);
device["rtt"] = AnsiToUtf8(rtt); device["rtt"] = AnsiToUtf8(rtt);
+13 -4
View File
@@ -723,11 +723,11 @@ protected:
AppendData(&dlgTemplate, sizeof(DLGTEMPLATE)); AppendData(&dlgTemplate, sizeof(DLGTEMPLATE));
AppendWord(0); // 菜单 AppendWord(0); // 菜单
AppendWord(0); // 窗口类 AppendWord(0); // 窗口类
AppendString(_T("切换语言 / Switch Language")); AppendString(_T("切换语言"));
AlignToDword(); AlignToDword();
// 静态文本 // 静态文本
AddControl(0x0082, 15, 15, 50, 12, (WORD)-1, AddControl(0x0082, 15, 15, 50, 12, 1002,
SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:")); SS_LEFT | WS_CHILD | WS_VISIBLE, _T("语言:"));
// ComboBox // ComboBox
@@ -796,8 +796,17 @@ protected:
{ {
CDialog::OnInitDialog(); CDialog::OnInitDialog();
// 翻译对话框控件(标题、标签、按钮) // 模板为动态构造的 Unicode 模板,MBCS 下被 ANSI 创建路径误读,标题/标签/按钮文字会乱码,
TranslateDialog(this); // 故在此显式设置(走线程 locale GBK,与其它对话框同法),不再对已乱码文本调用 TranslateDialog。
// 标题含中文:非中文系统上 ANSI 消息按系统 ACP 解码会乱码,用 _TR 取翻译(中文→切换语言,
// 英语→Switch Language)后,显式 GBK(936)→Unicode 转换再用 Unicode 消息设置(与工具栏同法)。
CString strTitle = _TR("切换语言");
WCHAR wTitle[64] = { 0 };
MultiByteToWideChar(936, 0, strTitle, -1, wTitle, 64);
::SetWindowTextW(m_hWnd, wTitle);
SetDlgItemText(1002, _TR("语言:"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
m_comboLang.SubclassDlgItem(1001, this); m_comboLang.SubclassDlgItem(1001, this);
+2 -1
View File
@@ -205,7 +205,8 @@ void NotifyManager::BuildHostOnlineEmail(context* ctx, const std::string& matche
// Get host info // Get host info
std::string computerName = CT2A(ctx->GetClientData(ONLINELIST_COMPUTER_NAME), CP_UTF8); std::string computerName = CT2A(ctx->GetClientData(ONLINELIST_COMPUTER_NAME), CP_UTF8);
std::string ip = CT2A(ctx->GetClientData(ONLINELIST_IP), CP_UTF8); std::string ip = CT2A(ctx->GetClientData(ONLINELIST_IP), CP_UTF8);
std::string location = CT2A(ctx->GetClientData(ONLINELIST_LOCATION), CP_UTF8); // 地理位置已统一按 UTF-8 存储(见 AddList),直接透传,不再 CP_ACP→UTF-8 二次转换。
std::string location = ctx->GetClientData(ONLINELIST_LOCATION).GetString();
std::string os = CT2A(ctx->GetClientData(ONLINELIST_OS), CP_UTF8); std::string os = CT2A(ctx->GetClientData(ONLINELIST_OS), CP_UTF8);
std::string version = CT2A(ctx->GetClientData(ONLINELIST_VERSION), CP_UTF8); std::string version = CT2A(ctx->GetClientData(ONLINELIST_VERSION), CP_UTF8);
+4
View File
@@ -102,6 +102,10 @@ void CPluginSettingsDlg::InitListCtrl()
GetDlgItem(IDC_STATIC_PLUGIN_INTERVAL)->SetWindowText(_TR("间隔(秒):")); GetDlgItem(IDC_STATIC_PLUGIN_INTERVAL)->SetWindowText(_TR("间隔(秒):"));
GetDlgItem(IDC_STATIC_PLUGIN_COUNTER)->SetWindowText(_TR("最大次数:")); GetDlgItem(IDC_STATIC_PLUGIN_COUNTER)->SetWindowText(_TR("最大次数:"));
SetWindowText(_TR("插件设置")); SetWindowText(_TR("插件设置"));
// 按钮文字来自对话框模板,非中文系统上会被系统 ACP 转成 "??",须显式设置(参照 NotifySettingsDlg)
SetDlgItemText(IDC_BTN_SAVE, _TR("保存"));
SetDlgItemText(IDOK, _TR("关闭"));
} }
void CPluginSettingsDlg::LoadPluginsToList() void CPluginSettingsDlg::LoadPluginsToList()
+21 -1
View File
@@ -124,11 +124,31 @@ protected:
{ {
CDialog::OnInitDialog(); CDialog::OnInitDialog();
// 非中文系统上 MBCS 对话框模板的中文会被系统 ACP 转成 "?",TranslateDialog
// 拿到的是已乱码文本无法还原,故在此显式设置(参照 NotifySettingsDlg)。
// 均经 _TR/_L 支持多语言翻译。
SetWindowText(_TR("远程桌面质量等级"));
SetDlgItemText(IDC_QLEVEL_LABEL_LEVEL, _TR("等级"));
SetDlgItemText(IDC_QLEVEL_LABEL_FPS, _TR("帧率(FPS)"));
SetDlgItemText(IDC_QLEVEL_LABEL_WIDTH, _TR("最大宽度"));
SetDlgItemText(IDC_QLEVEL_LABEL_ALGO, _TR("编码算法"));
SetDlgItemText(IDC_QLEVEL_LABEL_BITRATE, _TR("码率(kbps)"));
static const TCHAR* kLevelNames[QUALITY_COUNT] = {
_T("Ultra 极佳"), _T("High 优秀"), _T("Good 良好"),
_T("Medium 一般"), _T("Low 较差"), _T("Minimal 最低")
};
for (int i = 0; i < QUALITY_COUNT; i++)
SetDlgItemText(IDC_QLEVEL_LABEL_LEVEL0 + i, _L(kLevelNames[i]));
SetDlgItemText(IDC_QLEVEL_LABEL_HINT,
_TR("最大宽度 0 表示原始分辨率;DIFF/RGB565 适合局域网,H264 适合跨网"));
SetDlgItemText(IDC_QLEVEL_RESET, _TR("恢复默认"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
for (int i = 0; i < QUALITY_COUNT; i++) { for (int i = 0; i < QUALITY_COUNT; i++) {
m_working[i] = m_profiles[i]; m_working[i] = m_profiles[i];
} }
LoadToControls(); LoadToControls();
TranslateDialog(this);
return TRUE; return TRUE;
} }
+6
View File
@@ -277,6 +277,12 @@ void CSearchBarDlg::DoSearch()
colText = ctx->GetClientData(col); colText = ctx->GetClientData(col);
} }
// 地理位置列已统一按 UTF-8 存储(见 AddList),而搜索输入是 CP_ACP;
// 转回 CP_ACP 再比对,否则按中文位置搜索会因编码不一致而失配。
if (col == ONLINELIST_LOCATION) {
colText = CString(Utf8ToAnsi(std::string(colText.GetString())).c_str());
}
colText.MakeLower(); colText.MakeLower();
if (colText.Find(searchText) >= 0) { if (colText.Find(searchText) >= 0) {
matched = true; matched = true;
+28 -3
View File
@@ -232,6 +232,26 @@ void CSystemDlg::ShowWindowsList(void)
ApplySort(); // 应用记忆的排序偏好 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<LPWSTR>(L"") : &w[0];
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
}
void CSystemDlg::ShowProcessList(void) void CSystemDlg::ShowProcessList(void)
{ {
// GetMyBuffer(1) 深拷贝数据,跳过 TOKEN 字节 // GetMyBuffer(1) 深拷贝数据,跳过 TOKEN 字节
@@ -248,6 +268,10 @@ void CSystemDlg::ShowProcessList(void)
const char *szProcessFullPath; const char *szProcessFullPath;
DWORD dwOffset = 0; DWORD dwOffset = 0;
CString str; CString str;
// 进程名/路径编码:Windows 客户端 A 接口生成,恒为 GBK/936;仅 LNX/MAC 为 UTF-8。
// 不能用 GetClientEncoding(它对新客户端返回 CP_UTF8,会把 GBK 进程名解成乱码)。
UINT cp = GetProcessListEncoding(m_ContextObject);
DeleteAllItems(); DeleteAllItems();
//遍历发送来的每一个字符别忘了他的数据结构啊 Id+进程名+0+完整名+0 //遍历发送来的每一个字符别忘了他的数据结构啊 Id+进程名+0+完整名+0
int i; int i;
@@ -258,10 +282,11 @@ void CSystemDlg::ShowProcessList(void)
szProcessFullPath = szExeFile + lstrlen(szExeFile) + 1; //完整名就是进程名之后的啦 szProcessFullPath = szExeFile + lstrlen(szExeFile) + 1; //完整名就是进程名之后的啦
//他的数据结构的构建很巧妙 //他的数据结构的构建很巧妙
m_ControlList.InsertItem(i, arr[0].c_str()); //将得到的数据加入到列表当中 m_ControlList.InsertItem(i, ""); // 占位,首列随后用 Unicode 写入
str.FormatL("%5u", *PID); str.FormatL("%5u", *PID);
m_ControlList.SetItemText(i, 1, str); m_ControlList.SetItemText(i, 1, str); // PID (ASCII)
m_ControlList.SetItemText(i, 2, szProcessFullPath); 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()); m_ControlList.SetItemText(i, 3, arr[1].empty() ? "N/A" : arr[1].c_str());
// ItemData 为进程ID;Data[3] 需与第 4 列"架构"一致,否则按架构排序拿到空串而无意义 // 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() }; auto data = new ItemData{ *PID, {arr[0].c_str(), str, szProcessFullPath, arr[1].empty() ? "N/A" : arr[1].c_str()}, arr[1].c_str() };
+7
View File
@@ -134,6 +134,13 @@ void CTriggerSettingsDlg::InitControls()
SetWindowText(_TR("触发器设置")); SetWindowText(_TR("触发器设置"));
GetDlgItem(IDC_STATIC_TRIGGER_TYPE)->SetWindowText(_TR("触发类型:")); GetDlgItem(IDC_STATIC_TRIGGER_TYPE)->SetWindowText(_TR("触发类型:"));
GetDlgItem(IDC_STATIC_TRIGGER_ACTION)->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() void CTriggerSettingsDlg::LoadPluginsToList()
+48 -6
View File
@@ -7,6 +7,7 @@
#include "InputDlg.h" #include "InputDlg.h"
#include <windows.h> #include <windows.h>
#include <Shlwapi.h> #include <Shlwapi.h>
#include <string>
#ifdef _DEBUG #ifdef _DEBUG
#define new DEBUG_NEW #define new DEBUG_NEW
@@ -157,6 +158,15 @@ BOOL CFileManagerDlg::OnInitDialog()
SetDlgItemText(IDC_STATIC_FILE_WINOS_SEARCH, _TR("搜索文件名:")); SetDlgItemText(IDC_STATIC_FILE_WINOS_SEARCH, _TR("搜索文件名:"));
SetDlgItemText(IDC_STATIC_FILE_WINOS_PATH, _TR("路径:")); SetDlgItemText(IDC_STATIC_FILE_WINOS_PATH, _TR("路径:"));
// 多语言翻译 - 按钮/复选框(模板文字在非中文系统会被系统 ACP 转成 "?",须显式设置)
SetDlgItemText(IDC_CK_SUBFORDLE, _TR("包含子文件夹"));
SetDlgItemText(IDC_BTN_SEARCH, _TR("开始搜索"));
SetDlgItemText(ID_SEARCH_STOP, _TR("停止搜索"));
SetDlgItemText(ID_SEARCH_RESULT, _TR("搜索栏"));
SetDlgItemText(IDT_REMOTE_PREV, _TR("上一层"));
SetDlgItemText(IDT_REMOTE_VIEW, _TR("切换显示"));
SetDlgItemText(IDT_REMOTE_STOP, _TR("取消"));
RECT rect; RECT rect;
GetClientRect(&rect); GetClientRect(&rect);
@@ -809,8 +819,32 @@ void CFileManagerDlg::OnclickListSearch(NMHDR* pNMHDR, LRESULT* pResult)
int GetIconIndex(LPCTSTR lpFileName, DWORD dwFileAttributes); int GetIconIndex(LPCTSTR lpFileName, DWORD dwFileAttributes);
// 远程文件名/路径编码按 clientType 判定:Windows 走 A 接口(FindFirstFileA)=GBK/936,
// LNX/MAC 文件系统天然 UTF-8(同 McpServer.cpp::BuildListFiles)。这里用 LVM_SETITEMTEXTW
// 直接写宽字符串,绕开 ANSI→CP_ACP 回转,即使服务端运行在非中文 ACP 上中文名也能正常显示。
static void SetFileTextItem(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<LPWSTR>(L"") : &w[0];
::SendMessageW(list.GetSafeHwnd(), LVM_SETITEMTEXTW, (WPARAM)iItem, (LPARAM)&lv);
}
void CFileManagerDlg::FixedRemoteFileList(BYTE* pbBuffer, DWORD dwBufferLen) void CFileManagerDlg::FixedRemoteFileList(BYTE* pbBuffer, DWORD dwBufferLen)
{ {
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
// 重建标题 // 重建标题
m_list_remote.DeleteAllItems(); m_list_remote.DeleteAllItems();
int nItemIndex = 0; int nItemIndex = 0;
@@ -846,7 +880,8 @@ void CFileManagerDlg::FixedRemoteFileList(BYTE* pbBuffer, DWORD dwBufferLen)
if (bIsInsert) { if (bIsInsert) {
int iicon = GetIconIndex(pszFileName, nType); int iicon = GetIconIndex(pszFileName, nType);
nItem = m_list_remote.InsertItem(nItemIndex++, pszFileName, iicon); nItem = m_list_remote.InsertItem(nItemIndex++, _T(""), iicon);
SetFileTextItem(m_list_remote, nItem, 0, pszFileName, cp);
m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY); m_list_remote.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
SHFILEINFO sfi = {}; SHFILEINFO sfi = {};
SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi, sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES); SHGetFileInfo(pszFileName, FILE_ATTRIBUTE_NORMAL | nType, &sfi, sizeof(SHFILEINFO), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
@@ -2412,6 +2447,9 @@ void CFileManagerDlg::SearchEnd()
void CFileManagerDlg::FixedRemoteSearchFileList(BYTE* pbBuffer, DWORD dwBufferLen) void CFileManagerDlg::FixedRemoteSearchFileList(BYTE* pbBuffer, DWORD dwBufferLen)
{ {
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
byte* pList = pbBuffer + 1; byte* pList = pbBuffer + 1;
int nItemIndex = 0; int nItemIndex = 0;
for (byte* pBase = pList; (int)(pList - pBase) < (int)(dwBufferLen - 1);) { for (byte* pBase = pList; (int)(pList - pBase) < (int)(dwBufferLen - 1);) {
@@ -2430,7 +2468,8 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE* pbBuffer, DWORD dwBufferLe
int nPos = csFilePath.ReverseFind(_T('\\')); int nPos = csFilePath.ReverseFind(_T('\\'));
csFileFullName = csFilePath.Right(csFilePath.GetLength() - nPos - 1);// 获取文件全名,包括文件名和扩展名 csFileFullName = csFilePath.Right(csFilePath.GetLength() - nPos - 1);// 获取文件全名,包括文件名和扩展名
nItem = m_list_remote_search.InsertItem(nItemIndex++, csFileFullName, GetIconIndex(pszFileName, nType)); nItem = m_list_remote_search.InsertItem(nItemIndex++, _T(""), GetIconIndex(pszFileName, nType));
SetFileTextItem(m_list_remote_search, nItem, 0, csFileFullName, cp);
m_list_remote_search.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY); m_list_remote_search.SetItemData(nItem, nType == FILE_ATTRIBUTE_DIRECTORY);
pList += (lstrlen(pszFileName) + 1) * sizeof(TCHAR); pList += (lstrlen(pszFileName) + 1) * sizeof(TCHAR);
@@ -2446,7 +2485,7 @@ void CFileManagerDlg::FixedRemoteSearchFileList(BYTE* pbBuffer, DWORD dwBufferLe
m_list_remote_search.SetItemText(nItem, 2, time.FormatL(_T("%Y-%m-%d %H:%M"))); m_list_remote_search.SetItemText(nItem, 2, time.FormatL(_T("%Y-%m-%d %H:%M")));
PathRemoveFileSpec(pszFileName); //去除文件名获取文件路径 PathRemoveFileSpec(pszFileName); //去除文件名获取文件路径
m_list_remote_search.SetItemText(nItem, 3, pszFileName); SetFileTextItem(m_list_remote_search, nItem, 3, pszFileName, cp);
pList += 16; pList += 16;
} }
@@ -2482,6 +2521,9 @@ void CFileManagerDlg::ShowSearchPlugList()
char* Filename; char* Filename;
char* Pathname; char* Pathname;
CString clientType = m_ContextObject->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == _T("LNX") || clientType == _T("MAC")) ? CP_UTF8 : 936;
DWORD dwOffset = 0; DWORD dwOffset = 0;
m_list_remote_search.DeleteAllItems(); m_list_remote_search.DeleteAllItems();
int i = 0; int i = 0;
@@ -2489,13 +2531,13 @@ void CFileManagerDlg::ShowSearchPlugList()
Filename = lpBuffer + dwOffset; Filename = lpBuffer + dwOffset;
Pathname = Filename + strlen(Filename) + 1; Pathname = Filename + strlen(Filename) + 1;
CString CS_tem; CString CS_tem;
CS_tem = Filename; m_list_remote_search.InsertItem(i, _T(""), 0);
m_list_remote_search.InsertItem(i, CS_tem, 0); SetFileTextItem(m_list_remote_search, i, 0, Filename, cp);
CS_tem = Pathname; CS_tem = Pathname;
int index = CS_tem.ReverseFind('\\'); int index = CS_tem.ReverseFind('\\');
if (index >= 0) if (index >= 0)
CS_tem = CS_tem.Left(index); CS_tem = CS_tem.Left(index);
m_list_remote_search.SetItemText(i,3, CS_tem); SetFileTextItem(m_list_remote_search, i, 3, CS_tem, cp);
dwOffset += strlen(Filename) + strlen(Pathname) + 2; dwOffset += strlen(Filename) + strlen(Pathname) + 2;
} }
m_list_remote_search.EnableWindow(TRUE); m_list_remote_search.EnableWindow(TRUE);
+2 -1
View File
@@ -306,6 +306,7 @@ Web
; ============================================ ; ============================================
发送消息=Nachricht senden 发送消息=Nachricht senden
结束交谈=Chat beenden 结束交谈=Chat beenden
确定开始远程交谈吗?=Remote-Chat starten?
锁定屏幕\n屏蔽功能键=Bildschirm sperren\nTasten blockieren 锁定屏幕\n屏蔽功能键=Bildschirm sperren\nTasten blockieren
解除锁定=Entsperren 解除锁定=Entsperren
@@ -2067,4 +2068,4 @@ JPEG
不限速=Unbegrenzt 不限速=Unbegrenzt
%s · 约 %.2f MB/s=%s · ca. %.2f MB/s %s · 约 %.2f MB/s=%s · ca. %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · Unbegrenzt (durch Netzwerkbandbreite begrenzt) %s · 不受限速(受网络带宽约束)=%s · Unbegrenzt (durch Netzwerkbandbreite begrenzt)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=Uebertragungsgeschwindigkeit gespeichert. Upload-Geschwindigkeit wird nach Neustart des Masters wirksam; Download-Geschwindigkeit beim naechsten Oeffnen des Dateimanagers oder des Remotedesktops. 文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。=Uebertragungsgeschwindigkeit gespeichert. Upload-Geschwindigkeit wird nach Neustart des Masters wirksam; Download-Geschwindigkeit wurde an Online-Clients gesendet, neu verbundene Hosts erhalten sie automatisch.
+6 -1
View File
@@ -99,6 +99,8 @@ RTT=RTT
即时消息=Message 即时消息=Message
虚拟屏幕=Virtual Screen 虚拟屏幕=Virtual Screen
隐私屏幕=Privacy Screen 隐私屏幕=Privacy Screen
实验功能=Experimental Features
远程画板=Remote Drawing Board
代理服务=Proxy Service 代理服务=Proxy Service
注入设置=Inject Config 注入设置=Inject Config
创建计划任务=Scheduled Task 创建计划任务=Scheduled Task
@@ -121,6 +123,7 @@ RTT=RTT
; ============================================ ; ============================================
; Language Selection Dialog ; Language Selection Dialog
切换语言=Switch Language
; ============================================ ; ============================================
选择语言 / Select Language=Select Language 选择语言 / Select Language=Select Language
语言:=Language: 语言:=Language:
@@ -306,6 +309,7 @@ Web
; ============================================ ; ============================================
发送消息=Send Msg 发送消息=Send Msg
结束交谈=End Chat 结束交谈=End Chat
确定开始远程交谈吗?=Start remote chat?
锁定屏幕\n屏蔽功能键=Lock Screen\nBlock Keys 锁定屏幕\n屏蔽功能键=Lock Screen\nBlock Keys
解除锁定=Unlock 解除锁定=Unlock
@@ -1397,6 +1401,7 @@ RDP
C2C 文件传输=C2C File Transfer C2C 文件传输=C2C File Transfer
续传文件 (V2)=Resume File Upload (V2) 续传文件 (V2)=Resume File Upload (V2)
发送文件 (V2)=Send File (V2) 发送文件 (V2)=Send File (V2)
接收文件 (V2)=Receive File (V2)
发送文件(%d/%d): %.2f%%=Send Files(%d/%d): %.2f%% 发送文件(%d/%d): %.2f%%=Send Files(%d/%d): %.2f%%
接收文件(%d/%d): %.2f%%=Recv Files(%d/%d): %.2f%% 接收文件(%d/%d): %.2f%%=Recv Files(%d/%d): %.2f%%
[远程右键复制, 本地 Ctrl+V 粘贴]= [Remote Right-click Copy, Local Ctrl+V Paste] [远程右键复制, 本地 Ctrl+V 粘贴]= [Remote Right-click Copy, Local Ctrl+V Paste]
@@ -2068,7 +2073,7 @@ JPEG
不限速=Unlimited 不限速=Unlimited
%s · 约 %.2f MB/s=%s · about %.2f MB/s %s · 约 %.2f MB/s=%s · about %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · Unlimited (limited by network bandwidth) %s · 不受限速(受网络带宽约束)=%s · Unlimited (limited by network bandwidth)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=Transfer speed saved. Upload speed takes effect after restarting the master; download speed takes effect the next time you open File Manager or Remote Desktop. 文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。=Transfer speed saved. Upload speed takes effect after restarting the master; download speed has been pushed to online clients, and newly connected hosts will get it automatically.
连接管理=Connections 连接管理=Connections
%s - 连接管理=%s - Connections %s - 连接管理=%s - Connections
+6 -1
View File
@@ -99,6 +99,8 @@ RTT=RTT
即时消息=即時訊息 即时消息=即時訊息
虚拟屏幕=虛擬桌面 虚拟屏幕=虛擬桌面
隐私屏幕=隱私螢幕 隐私屏幕=隱私螢幕
实验功能=實驗功能
远程画板=遠端繪圖板
代理服务=代理服務 代理服务=代理服務
注入设置=注入設定 注入设置=注入設定
创建计划任务=建立排程工作 创建计划任务=建立排程工作
@@ -121,6 +123,7 @@ RTT=RTT
; ============================================ ; ============================================
; Language Selection Dialog ; Language Selection Dialog
切換語言=切換語言
; ============================================ ; ============================================
选择语言 / Select Language=選擇語言 选择语言 / Select Language=選擇語言
语言:=語言: 语言:=語言:
@@ -306,6 +309,7 @@ Web
; ============================================ ; ============================================
发送消息=傳送訊息 发送消息=傳送訊息
结束交谈=結束交談 结束交谈=結束交談
确定开始远程交谈吗?=確定開始遠程交談嗎?
锁定屏幕\n屏蔽功能键=鎖定螢幕\n屏蔽功能鍵 锁定屏幕\n屏蔽功能键=鎖定螢幕\n屏蔽功能鍵
解除锁定=解除鎖定 解除锁定=解除鎖定
@@ -1393,6 +1397,7 @@ RDP
C2C 文件传输=C2C 檔案傳輸 C2C 文件传输=C2C 檔案傳輸
续传文件 (V2)=續傳檔案 (V2) 续传文件 (V2)=續傳檔案 (V2)
发送文件 (V2)=傳送檔案 (V2) 发送文件 (V2)=傳送檔案 (V2)
接收文件 (V2)=接收檔案 (V2)
发送文件(%d/%d): %.2f%%=发送文件(%d/%d): %.2f%% 发送文件(%d/%d): %.2f%%=发送文件(%d/%d): %.2f%%
接收文件(%d/%d): %.2f%%=接收文件(%d/%d): %.2f%% 接收文件(%d/%d): %.2f%%=接收文件(%d/%d): %.2f%%
[远程右键复制, 本地 Ctrl+V 粘贴]= [遠端右鍵複製, 本地 Ctrl+V 貼上] [远程右键复制, 本地 Ctrl+V 粘贴]= [遠端右鍵複製, 本地 Ctrl+V 貼上]
@@ -2059,7 +2064,7 @@ JPEG
不限速=不限速 不限速=不限速
%s · 约 %.2f MB/s=%s · 約 %.2f MB/s %s · 约 %.2f MB/s=%s · 約 %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制) %s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度下次開啟檔案管理或遠端桌面後生效。 文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度已推送到線上用戶端,新連線的主機也會自動取得。
连接管理=連線管理 连接管理=連線管理
%s - 连接管理=%s - 連線管理 %s - 连接管理=%s - 連線管理
+34 -1
View File
@@ -1077,13 +1077,46 @@
#define IDC_FSPEED_LABEL 2591 #define IDC_FSPEED_LABEL 2591
#define IDC_FSPEED_RESET 2592 #define IDC_FSPEED_RESET 2592
// 高级菜单三个对话框的静态文本(显式在 OnInitDialog 设置中文,修复非中文系统乱码)
#define IDC_QLEVEL_LABEL_LEVEL 2593
#define IDC_QLEVEL_LABEL_FPS 2594
#define IDC_QLEVEL_LABEL_WIDTH 2595
#define IDC_QLEVEL_LABEL_ALGO 2596
#define IDC_QLEVEL_LABEL_BITRATE 2597
#define IDC_QLEVEL_LABEL_LEVEL0 2598 // 2598~2603:Ultra..Minimal 六档
#define IDC_QLEVEL_LABEL_LEVEL1 2599
#define IDC_QLEVEL_LABEL_LEVEL2 2600
#define IDC_QLEVEL_LABEL_LEVEL3 2601
#define IDC_QLEVEL_LABEL_LEVEL4 2602
#define IDC_QLEVEL_LABEL_LEVEL5 2603
#define IDC_QLEVEL_LABEL_HINT 2604
#define IDC_PLEVEL_LABEL_LEVEL 2605
#define IDC_PLEVEL_LABEL_WIDTH 2606
#define IDC_PLEVEL_LABEL_QUALITY 2607
#define IDC_PLEVEL_LABEL_LEVEL0 2608 // 2608~2613:Ultra..Minimal 六档
#define IDC_PLEVEL_LABEL_LEVEL1 2609
#define IDC_PLEVEL_LABEL_LEVEL2 2610
#define IDC_PLEVEL_LABEL_LEVEL3 2611
#define IDC_PLEVEL_LABEL_LEVEL4 2612
#define IDC_PLEVEL_LABEL_LEVEL5 2613
#define IDC_PLEVEL_LABEL_HINT 2614
#define IDC_FSPEED_HINT 2615
#define IDC_FSPEED_LABEL_SLOWEST 2616
#define IDC_FSPEED_LABEL_DEFAULT 2617
#define IDC_FSPEED_LABEL_UNLIMITED 2618
// 触发器设置对话框的分组框(显式在 OnInitDialog 设置中文,修复非中文系统乱码)
#define IDC_STATIC_TRIGGER_LIST 2619
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 410 #define _APS_NEXT_RESOURCE_VALUE 410
#define _APS_NEXT_COMMAND_VALUE 33093 #define _APS_NEXT_COMMAND_VALUE 33093
#define _APS_NEXT_CONTROL_VALUE 2593 #define _APS_NEXT_CONTROL_VALUE 2620
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif
#endif #endif