i18n: UTF-8 protocol capability + Unicode rendering on server
This commit is contained in:
@@ -175,6 +175,20 @@ bool SupportsFileTransferV2(context* ctx) {
|
||||
return IsDateGreaterOrEqual(version, FILE_TRANSFER_V2_DATE);
|
||||
}
|
||||
|
||||
// 获取客户端协议字符串编码:优先看自身能力位,若是子连接(CAPABILITIES 为空)
|
||||
// 则通过 peer IP 查主连接。找不到则默认 CP936。
|
||||
UINT GetClientEncoding(context* ctx) {
|
||||
if (!ctx) return 936;
|
||||
// 主连接情形:CAPABILITIES 已由 LOGIN_INFOR 处理流程填好
|
||||
if (ctx->SupportsUtf8()) return CP_UTF8;
|
||||
// 子连接情形:CAPABILITIES 为空 -> 通过 IP 找主连接
|
||||
if (g_2015RemoteDlg) {
|
||||
context* mainCtx = g_2015RemoteDlg->FindHostByIP(ctx->GetPeerName());
|
||||
if (mainCtx && mainCtx->SupportsUtf8()) return CP_UTF8;
|
||||
}
|
||||
return 936;
|
||||
}
|
||||
|
||||
// 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次
|
||||
static bool ShouldLogAuth(const std::string& sn, int success) {
|
||||
struct AuthLogState {
|
||||
@@ -722,6 +736,9 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
||||
ON_WM_CLOSE()
|
||||
ON_NOTIFY(NM_RCLICK, IDC_ONLINE, &CMy2015RemoteDlg::OnNMRClickOnline)
|
||||
ON_NOTIFY(LVN_GETDISPINFO, IDC_ONLINE, &CMy2015RemoteDlg::OnGetDispInfo)
|
||||
// 启用 LVM_SETUNICODEFORMAT 后,列表实际发送的是 LVN_GETDISPINFOW(即便工程是 MBCS)。
|
||||
// MBCS 工程里 LVN_GETDISPINFO == LVN_GETDISPINFOA,两者码值不同,需各自映射。
|
||||
ON_NOTIFY(LVN_GETDISPINFOW, IDC_ONLINE, &CMy2015RemoteDlg::OnGetDispInfoW)
|
||||
ON_NOTIFY(HDN_ITEMCLICK, 0, &CMy2015RemoteDlg::OnHdnItemclickList)
|
||||
ON_COMMAND(ID_ONLINE_MESSAGE, &CMy2015RemoteDlg::OnOnlineMessage)
|
||||
ON_COMMAND(ID_ONLINE_DELETE, &CMy2015RemoteDlg::OnOnlineDelete)
|
||||
@@ -1127,13 +1144,23 @@ VOID CMy2015RemoteDlg::CreatStatusBar()
|
||||
|
||||
VOID CMy2015RemoteDlg::CreateNotifyBar()
|
||||
{
|
||||
// GUID 用于 Windows 10/11 Toast 通知关联托盘图标
|
||||
// {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
|
||||
static const GUID NOTIFY_ICON_GUID =
|
||||
{ 0xA1B2C3D4, 0xE5F6, 0x7890, { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 } };
|
||||
|
||||
m_Nid.uVersion = NOTIFYICON_VERSION_4;
|
||||
m_Nid.cbSize = sizeof(NOTIFYICONDATA); //大小赋值
|
||||
m_Nid.hWnd = m_hWnd; //父窗口 是被定义在父类CWnd类中
|
||||
m_Nid.uID = IDR_MAINFRAME; //icon ID
|
||||
m_Nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; //托盘所拥有的状态
|
||||
m_Nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_GUID; //托盘所拥有的状态
|
||||
m_Nid.uCallbackMessage = UM_ICONNOTIFY; //回调消息
|
||||
m_Nid.hIcon = m_hIcon; //icon 变量
|
||||
m_Nid.guidItem = NOTIFY_ICON_GUID;
|
||||
|
||||
// 先删除可能残留的旧图标(程序异常退出时可能残留)
|
||||
Shell_NotifyIcon(NIM_DELETE, &m_Nid);
|
||||
|
||||
CString strTips = _TR(BRAND_TRAY_TIP); //气泡提示
|
||||
lstrcpyn(m_Nid.szTip, (LPCSTR)strTips, sizeof(m_Nid.szTip) / sizeof(m_Nid.szTip[0]));
|
||||
Shell_NotifyIcon(NIM_ADD, &m_Nid); //显示托盘
|
||||
@@ -1251,6 +1278,9 @@ VOID CMy2015RemoteDlg::InitControl()
|
||||
g_Column_Online_Width+=g_Column_Data_Online[i].nWidth;
|
||||
}
|
||||
m_CList_Online.InitColumns();
|
||||
// 让虚拟列表用 Unicode 通知(LVN_GETDISPINFOW),从而绕开 MBCS 工程里
|
||||
// 列表 → ANSI → CP_ACP 的回转,这样在德语/日语等非中文 ACP 服务端上也能显示中文。
|
||||
m_CList_Online.SendMessage(LVM_SETUNICODEFORMAT, TRUE, 0);
|
||||
m_CList_Online.ModifyStyle(0, LVS_SHOWSELALWAYS); // LVS_OWNERDATA 由 SetVirtualMode 设置
|
||||
m_CList_Online.SetExtendedStyle(style);
|
||||
m_CList_Online.SetParent(&m_GroupTab);
|
||||
@@ -1429,7 +1459,8 @@ LRESULT CMy2015RemoteDlg::OnShowNotify(WPARAM wParam, LPARAM lParam)
|
||||
NOTIFYICONDATA nidCopy = m_Nid;
|
||||
nidCopy.cbSize = sizeof(NOTIFYICONDATA);
|
||||
nidCopy.uFlags |= NIF_INFO;
|
||||
nidCopy.dwInfoFlags = NIIF_INFO;
|
||||
nidCopy.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; // 使用自定义图标
|
||||
nidCopy.hBalloonIcon = m_hIcon; // 设置气球提示图标
|
||||
lstrcpynA(nidCopy.szInfoTitle, title->data, sizeof(nidCopy.szInfoTitle));
|
||||
lstrcpynA(nidCopy.szInfo, text->data, sizeof(nidCopy.szInfo));
|
||||
nidCopy.uTimeout = 3000;
|
||||
@@ -3500,6 +3531,84 @@ void CMy2015RemoteDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
// 虚拟列表数据回调(W 版) - 列表启用 LVM_SETUNICODEFORMAT 后由系统改发此通知。
|
||||
// 工程仍是 MBCS,CString 内部仍按 CP_ACP;这里把要显示的列统一转成宽字符填回,
|
||||
// 让控件直接以 Unicode 渲染,从而在非中文 ACP 服务端上也能正确显示中文。
|
||||
void CMy2015RemoteDlg::OnGetDispInfoW(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
{
|
||||
NMLVDISPINFOW* pDispInfo = reinterpret_cast<NMLVDISPINFOW*>(pNMHDR);
|
||||
LVITEMW* pItem = &pDispInfo->item;
|
||||
int iItem = pItem->iItem;
|
||||
|
||||
CLock lock(m_cs);
|
||||
|
||||
if (iItem < 0 || iItem >= (int)m_FilteredIndices.size()) {
|
||||
*pResult = 0;
|
||||
return;
|
||||
}
|
||||
size_t realIdx = m_FilteredIndices[iItem];
|
||||
if (realIdx >= m_HostList.size()) {
|
||||
*pResult = 0;
|
||||
return;
|
||||
}
|
||||
context* ctx = m_HostList[realIdx];
|
||||
if (!ctx) {
|
||||
*pResult = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((pItem->mask & LVIF_TEXT) && pItem->pszText && pItem->cchTextMax > 0) {
|
||||
std::wstring wtext;
|
||||
int nCol = pItem->iSubItem;
|
||||
|
||||
if (nCol == ONLINELIST_LOGINTIME) {
|
||||
// "活动窗口"列:心跳到达后用旁路表里的宽字符串(已正确解码);
|
||||
// 心跳到达前 m_ActiveWndW 还没条目,回退到 CString —— AddList 给这一列
|
||||
// 填的是 startTime(启动时间,纯 ASCII),按 CP_ACP 转宽显示,行为与
|
||||
// 原 A 版回调一致。
|
||||
auto it = m_ActiveWndW.find(ctx->GetClientID());
|
||||
if (it != m_ActiveWndW.end()) {
|
||||
wtext = it->second;
|
||||
} else {
|
||||
CString text = ctx->GetClientData(nCol);
|
||||
if (!text.IsEmpty()) {
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, text, -1, NULL, 0);
|
||||
if (wlen > 0) {
|
||||
wtext.resize(wlen - 1);
|
||||
MultiByteToWideChar(CP_ACP, 0, text, -1, &wtext[0], wlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wtext.empty()) wtext = L"?";
|
||||
} else {
|
||||
// 其它列:CString 仍是 ANSI(CP_ACP),按 CP_ACP 转宽供控件显示
|
||||
CString text;
|
||||
if (nCol == ONLINELIST_COMPUTER_NAME) {
|
||||
CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
||||
text = !note.IsEmpty() ? note : ctx->GetClientData(nCol);
|
||||
} else {
|
||||
text = ctx->GetClientData(nCol);
|
||||
if (text.IsEmpty()) text = "?";
|
||||
}
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, text, -1, NULL, 0);
|
||||
if (wlen > 0) {
|
||||
wtext.resize(wlen - 1);
|
||||
MultiByteToWideChar(CP_ACP, 0, text, -1, &wtext[0], wlen);
|
||||
}
|
||||
}
|
||||
|
||||
// 安全拷贝到控件提供的缓冲区
|
||||
int copy = (int)wtext.size();
|
||||
if (copy > pItem->cchTextMax - 1) copy = pItem->cchTextMax - 1;
|
||||
if (copy > 0) {
|
||||
wmemcpy(pItem->pszText, wtext.c_str(), copy);
|
||||
}
|
||||
pItem->pszText[copy] = L'\0';
|
||||
}
|
||||
|
||||
*pResult = 0;
|
||||
}
|
||||
|
||||
void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
|
||||
{
|
||||
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
||||
@@ -5525,6 +5634,8 @@ bool CMy2015RemoteDlg::RemoveFromHostList(context* ctx)
|
||||
if (WebService().IsRunning()) {
|
||||
WebService().MarkDeviceOffline(clientID);
|
||||
}
|
||||
// 清理"活动窗口"列的宽字符旁路表
|
||||
m_ActiveWndW.erase(clientID);
|
||||
|
||||
// 方案1:通过索引快速查找(如果索引有效且匹配)
|
||||
auto indexIt = m_ClientIndex.find(clientID);
|
||||
@@ -5942,10 +6053,25 @@ void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx)
|
||||
if (id) {
|
||||
bool changed = false;
|
||||
// 只在数据变化时标记 dirty
|
||||
// 注:hb.ActiveWnd 编码由客户端能力位 CLIENT_CAP_UTF8 决定(登录时已识别)。
|
||||
// CString 这里仍然按字节存原始数据,仅用于 "Locked:"/"Inactive:" 等 ASCII 前缀比较;
|
||||
// 真正显示走 m_ActiveWndW + LVN_GETDISPINFOW。
|
||||
CString oldActiveWnd = ctx->GetClientData(ONLINELIST_LOGINTIME);
|
||||
if (oldActiveWnd != hb.ActiveWnd) {
|
||||
ctx->SetClientData(ONLINELIST_LOGINTIME, hb.ActiveWnd);
|
||||
changed = true;
|
||||
|
||||
// 按客户端声明的编码解码:新客户端 UTF-8,老客户端 CP936(默认兜底)
|
||||
std::wstring wActive;
|
||||
if (hb.ActiveWnd[0]) {
|
||||
UINT cp = GetClientEncoding(host);
|
||||
int wlen = MultiByteToWideChar(cp, 0, hb.ActiveWnd, -1, NULL, 0);
|
||||
if (wlen > 0) {
|
||||
wActive.resize(wlen - 1);
|
||||
MultiByteToWideChar(cp, 0, hb.ActiveWnd, -1, &wActive[0], wlen);
|
||||
}
|
||||
}
|
||||
m_ActiveWndW[clientID] = std::move(wActive);
|
||||
}
|
||||
if (hb.Ping > 0) {
|
||||
CString newPing = std::to_string(hb.Ping).c_str();
|
||||
@@ -5966,19 +6092,16 @@ void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx)
|
||||
// Notify web clients of device update
|
||||
if (WebService().IsRunning()) {
|
||||
std::string rtt = hb.Ping > 0 ? std::to_string(hb.Ping) : "";
|
||||
// Convert ANSI to UTF-8 for web
|
||||
// Web 端要 UTF-8:直接用旁路表里的宽字符再编一次 UTF-8,
|
||||
// 这样无论客户端原本是 UTF-8 还是 GBK,发给 Web 都是 UTF-8。
|
||||
std::string activeWnd;
|
||||
CString csActiveWnd(hb.ActiveWnd);
|
||||
if (!csActiveWnd.IsEmpty()) {
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, csActiveWnd, -1, NULL, 0);
|
||||
if (wlen > 0) {
|
||||
std::wstring wstr(wlen - 1, L'\0');
|
||||
MultiByteToWideChar(CP_ACP, 0, csActiveWnd, -1, &wstr[0], wlen);
|
||||
int u8len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (u8len > 0) {
|
||||
activeWnd.resize(u8len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &activeWnd[0], u8len, NULL, NULL);
|
||||
}
|
||||
auto it = m_ActiveWndW.find(clientID);
|
||||
if (it != m_ActiveWndW.end() && !it->second.empty()) {
|
||||
const std::wstring& w = it->second;
|
||||
int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (u8len > 0) {
|
||||
activeWnd.resize(u8len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &activeWnd[0], u8len, NULL, NULL);
|
||||
}
|
||||
}
|
||||
WebService().NotifyDeviceUpdate(clientID, rtt, activeWnd);
|
||||
|
||||
Reference in New Issue
Block a user