13 Commits

Author SHA1 Message Date
yuanyuanxiang
b4ef42923a Fix: move InitControl() before PostMessage to prevent empty columns 2026-06-11 22:38:26 +02:00
yuanyuanxiang
8c64886512 Feat: draggable splitter bar to resize host list and message log proportions 2026-06-11 22:18:23 +02:00
yuanyuanxiang
773f5d5973 Feat: add HideMsg toggle to hide/show message log panel with menu checkmark 2026-06-11 22:13:30 +02:00
yuanyuanxiang
2843a260b0 Feat: copy selected message log rows to clipboard as CSV 2026-06-11 21:34:42 +02:00
yuanyuanxiang
3f662f1ca7 Fix: macOS use quality profile FPS/bitrate, add HW resolution downscaling 2026-06-10 22:40:03 +02:00
yuanyuanxiang
8e5ec20cf2 Fix: Calculate RTT exclude server side UI queue delay time 2026-06-09 10:01:50 +02:00
yuanyuanxiang
96688166ba Fix: clear residual image outside remote frame in non-adaptive scroll mode 2026-06-07 21:05:29 +02:00
yuanyuanxiang
1f538719a8 Fix: sub-connection disconnect (e.g. RDP) no longer clears thumbnail of still-online host 2026-06-07 15:56:52 +02:00
yuanyuanxiang
9f6476a7c4 Fix: Building ServerDll/TinyRun with Shellcode+AES loader now works 2026-06-07 11:25:48 +02:00
yuanyuanxiang
5a20355547 Improve: AuthKernel use the machine id as client identify id 2026-06-06 14:54:42 +02:00
yuanyuanxiang
1430ab3261 Feature: Add a menu to uninstall master/server software 2026-06-06 13:02:29 +02:00
yuanyuanxiang
ec7cfa1d63 style: Add macros to enable/disable client building features 2026-06-05 00:05:13 +02:00
yuanyuanxiang
fc0be64880 Fix(macOS): restore dblclick for MAC touch, fix scroll speed (10px→40px per notch) 2026-06-04 15:33:12 +02:00
61 changed files with 970 additions and 367 deletions

View File

@@ -8,6 +8,8 @@
#include <Mmsystem.h> #include <Mmsystem.h>
#include <IOSTREAM> #include <IOSTREAM>
#if ENABLE_AUDIO_MNG
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -127,3 +129,4 @@ BOOL CAudioManager::Initialize()
m_bIsWorking = TRUE; m_bIsWorking = TRUE;
return TRUE; return TRUE;
} }
#endif

View File

@@ -12,6 +12,10 @@
#include "Manager.h" #include "Manager.h"
#include "Audio.h" #include "Audio.h"
#if ENABLE_AUDIO_MNG==0
#define CAudioManager CManager
#else
class CAudioManager : public CManager class CAudioManager : public CManager
{ {
@@ -28,5 +32,6 @@ public:
CAudio* m_AudioObject; CAudio* m_AudioObject;
LPBYTE szPacket; // 音频缓存区 LPBYTE szPacket; // 音频缓存区
}; };
#endif
#endif // !defined(AFX_AUDIOMANAGER_H__B47ECAB3_9810_4031_9E2E_BC34825CAD74__INCLUDED_) #endif // !defined(AFX_AUDIOMANAGER_H__B47ECAB3_9810_4031_9E2E_BC34825CAD74__INCLUDED_)

View File

@@ -599,7 +599,7 @@ DWORD WINAPI StartClient(LPVOID lParam)
SAFE_DELETE(Manager); SAFE_DELETE(Manager);
//准备第一波数据 //准备第一波数据
LOGIN_INFOR login = GetLoginInfo(GetTickCount64() - dwTickCount, settings, expiredDate); LOGIN_INFOR login = GetLoginInfo(GetTickCount64() - dwTickCount, settings, expiredDate, isAuthKernel);
Manager = isAuthKernel ? new AuthKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit) : Manager = isAuthKernel ? new AuthKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit) :
new CKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit); new CKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit);
Manager->SetClientApp(&app); Manager->SetClientApp(&app);

View File

@@ -1,6 +1,7 @@
#include "StdAfx.h" #include "StdAfx.h"
#include "Common.h" #include "Common.h"
#include "Manager.h"
#include "ScreenManager.h" #include "ScreenManager.h"
#include "FileManager.h" #include "FileManager.h"
#include "TalkManager.h" #include "TalkManager.h"

View File

@@ -6,6 +6,8 @@
#include "Common.h" #include "Common.h"
#include "../common/commands.h" #include "../common/commands.h"
#if ENABLE_SHELL
// Define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE if not available (older SDK) // Define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE if not available (older SDK)
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE #ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \ #define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
@@ -341,3 +343,4 @@ DWORD WINAPI CConPTYManager::ReadThread(LPVOID lParam)
Mprintf("[ConPTY] Read thread exited\n"); Mprintf("[ConPTY] Read thread exited\n");
return 0; return 0;
} }
#endif

View File

@@ -7,6 +7,11 @@
#include "Manager.h" #include "Manager.h"
#include "IOCPClient.h" #include "IOCPClient.h"
#if ENABLE_SHELL==0
#define CConPTYManager CManager
#else
// ConPTY API types (dynamically loaded) // ConPTY API types (dynamically loaded)
typedef VOID* HPCON; typedef VOID* HPCON;
typedef HRESULT (WINAPI *PFN_CreatePseudoConsole)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); typedef HRESULT (WINAPI *PFN_CreatePseudoConsole)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
@@ -56,5 +61,6 @@ private:
// Thread to read from PTY // Thread to read from PTY
static DWORD WINAPI ReadThread(LPVOID lParam); static DWORD WINAPI ReadThread(LPVOID lParam);
}; };
#endif
#endif // CONPTYMANAGER_H #endif // CONPTYMANAGER_H

View File

@@ -10,6 +10,8 @@
#include "IOCPClient.h" #include "IOCPClient.h"
#include "KernelManager.h" #include "KernelManager.h"
#if ENABLE_FILE_MNG
typedef struct { typedef struct {
DWORD dwSizeHigh; DWORD dwSizeHigh;
DWORD dwSizeLow; DWORD dwSizeLow;
@@ -1186,3 +1188,4 @@ void CFileManager::UploadToRemoteV2(LPBYTE lpBuffer, UINT nSize)
Mprintf("[V2] 连接服务器失败\n"); Mprintf("[V2] 连接服务器失败\n");
} }
} }
#endif

View File

@@ -1,10 +1,16 @@
// FileManager.h: interface for the CFileManager class. // FileManager.h: interface for the CFileManager class.
// //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
#include "Manager.h"
#include "IOCPClient.h" #include "IOCPClient.h"
#include "common.h" #include "common.h"
typedef IOCPClient CClientSocket; typedef IOCPClient CClientSocket;
#if ENABLE_FILE_MNG==0
#define CFileManager CManager
#else
#if !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_) #if !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_)
#define AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_ #define AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_
#include <winsock2.h> #include <winsock2.h>
@@ -62,5 +68,6 @@ private:
HANDLE m_hSearchThread; HANDLE m_hSearchThread;
volatile bool m_bSearching; volatile bool m_bSearching;
}; };
#endif
#endif // !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_) #endif // !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_)

View File

@@ -786,6 +786,18 @@ BOOL ExecDLL(CKernelManager *This, PBYTE szBuffer, ULONG ulLength, void *user)
return data != NULL; return data != NULL;
} }
// 给主控回复功能禁用消息
// TODO: 主控收到此消息后,可以选择以插件形式执行该禁用的功能
void ResponseDisable(IOCPClient *client, const char* type, LPBYTE data, int size) {
char buf[512];
sprintf_s(buf, "%s disabled[IP: %s][ID: %s]", type, client->GetPublicIP().c_str(), client->GetClientID().c_str());
Mprintf("%s\n", buf);
int n = strlen(buf);
memcpy(buf + n + 1, data, min(size, 500-n));
ClientMsg msg(DISABLED_FEATURE, buf, sizeof(buf));
client->Send2Server((char*)&msg, sizeof(msg));
}
VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{ {
bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT; bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT;
@@ -940,6 +952,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case TOKEN_PRIVATESCREEN: { case TOKEN_PRIVATESCREEN: {
if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "PRIVATE_SCREEN", szBuffer + 1, ulLength - 1);
}
char h[100] = {}; char h[100] = {};
memcpy(h, szBuffer + 1, min(ulLength - 1, 80)); memcpy(h, szBuffer + 1, min(ulLength - 1, 80));
std::string hash = std::string(h, h + 64); std::string hash = std::string(h, h + 64);
@@ -962,6 +977,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_PROXY: { case COMMAND_PROXY: {
if (!ENABLE_PROXY) {
return ResponseDisable(m_ClientObject, "PROXY", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1052,7 +1070,7 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
if (m_settings.EnableKBLogger && m_hKeyboard) { if (m_settings.EnableKBLogger && m_hKeyboard) {
CKeyboardManager1* mgr = (CKeyboardManager1*)m_hKeyboard->user; CKeyboardManager1* mgr = (CKeyboardManager1*)m_hKeyboard->user;
mgr->m_bIsOfflineRecord = TRUE; mgr->EnableOfflineRecord(TRUE);
} }
Logger::getInstance().usingLog(m_settings.EnableLog); Logger::getInstance().usingLog(m_settings.EnableLog);
} }
@@ -1067,6 +1085,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
break; break;
case COMMAND_KEYBOARD: { //键盘记录 case COMMAND_KEYBOARD: { //键盘记录
if (!ENABLE_KEYBOARD) {
return ResponseDisable(m_ClientObject, "KEYBOARD", szBuffer + 1, ulLength - 1);
}
if (m_hKeyboard) { if (m_hKeyboard) {
CloseHandle(__CreateThread(NULL, 0, SendKeyboardRecord, m_hKeyboard->user, 0, NULL)); CloseHandle(__CreateThread(NULL, 0, SendKeyboardRecord, m_hKeyboard->user, 0, NULL));
} else { } else {
@@ -1079,6 +1100,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_TALK: { case COMMAND_TALK: {
if (!ENABLE_MESSAGE) {
return ResponseDisable(m_ClientObject, "MESSAGE", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1090,6 +1114,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_SHELL: { case COMMAND_SHELL: {
if (!ENABLE_SHELL) {
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1100,6 +1127,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_SYSTEM: { //远程进程管理 case COMMAND_SYSTEM: { //远程进程管理
if (!ENABLE_PROC_WND) {
return ResponseDisable(m_ClientObject, "PROCESS", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1110,6 +1140,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_WSLIST: { //远程窗口管理 case COMMAND_WSLIST: { //远程窗口管理
if (!ENABLE_PROC_WND) {
return ResponseDisable(m_ClientObject, "WINDOW", szBuffer + 1, ulLength - 1);
}
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
m_hThread[m_ulThreadCount].p = sub; m_hThread[m_ulThreadCount].p = sub;
@@ -1179,6 +1212,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_SCREEN_SPY: { case COMMAND_SCREEN_SPY: {
if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "SCREEN", szBuffer + 1, ulLength - 1);
}
UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) }; UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) };
if (ulLength > 1) { if (ulLength > 1) {
memcpy(user->buffer, szBuffer + 1, ulLength - 1); memcpy(user->buffer, szBuffer + 1, ulLength - 1);
@@ -1195,6 +1231,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_LIST_DRIVE : { case COMMAND_LIST_DRIVE : {
if (!ENABLE_FILE_MNG) {
return ResponseDisable(m_ClientObject, "FILE", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1205,6 +1244,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_WEBCAM: { case COMMAND_WEBCAM: {
if (!ENABLE_VIDEO_MNG) {
return ResponseDisable(m_ClientObject, "CAMERA", szBuffer + 1, ulLength - 1);
}
static bool hasCamera = WebCamIsExist(); static bool hasCamera = WebCamIsExist();
if (!hasCamera) break; if (!hasCamera) break;
{ {
@@ -1217,6 +1259,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_AUDIO: { case COMMAND_AUDIO: {
if (!ENABLE_AUDIO_MNG) {
return ResponseDisable(m_ClientObject, "AUDIO", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1227,6 +1272,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_REGEDIT: { case COMMAND_REGEDIT: {
if (!ENABLE_REGISTRY) {
return ResponseDisable(m_ClientObject, "REGISTRY", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1237,6 +1285,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
} }
case COMMAND_SERVICES: { case COMMAND_SERVICES: {
if (!ENABLE_SERVICE_MNG) {
return ResponseDisable(m_ClientObject, "SERVICE", szBuffer + 1, ulLength - 1);
}
{ {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
@@ -1581,10 +1632,13 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
void CKernelManager::OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength) void CKernelManager::OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength)
{ {
if (ulLength > 8) { if (ulLength > 8) {
uint64_t n = 0; HeartbeatACK n = { 0 };
memcpy(&n, szBuffer + 1, sizeof(uint64_t)); const int size = sizeof(HeartbeatACK);
// 主控心跳 ACK 只回显时间戳(不含 ProcessingMs近似纯网络 RTT memcpy(&n, szBuffer + 1, ulLength > size ? size : HeartbeatACK_OldSize);
int64_t rtt_ms = (int64_t)GetUnixMs() - (int64_t)n; int64_t total_rtt_ms = (int64_t)GetUnixMs() - (int64_t)n.Time;
int64_t rtt_ms = total_rtt_ms;
if (n.ProcessingMs > 0 && (int64_t)n.ProcessingMs < total_rtt_ms)
rtt_ms = total_rtt_ms - (int64_t)n.ProcessingMs;
m_nNetPing.update_from_sample((double)rtt_ms); m_nNetPing.update_from_sample((double)rtt_ms);
// 试用版反代理RTT 入采样窗口。 // 试用版反代理RTT 入采样窗口。
// 启停由下方根据 m_settings 控制;非试用模式下 RecordSample 内部直接 return。 // 启停由下方根据 m_settings 控制;非试用模式下 RecordSample 内部直接 return。

View File

@@ -4,6 +4,7 @@
#include "Common.h" #include "Common.h"
#include "KeyboardManager.h" #include "KeyboardManager.h"
#include "KernelManager.h"
#include <tchar.h> #include <tchar.h>
#if ENABLE_KEYBOARD #if ENABLE_KEYBOARD
@@ -51,9 +52,10 @@ CKeyboardManager1::CKeyboardManager1(IOCPClient*pClient, int offline, void* user
clip::set_error_handler(NULL); clip::set_error_handler(NULL);
#endif #endif
m_bIsOfflineRecord = offline; m_bIsOfflineRecord = offline;
CKernelManager* main = (CKernelManager*)pClient->GetMain();
BOOL isAuth = main ? main->IsAuthKernel() : FALSE;
char path[MAX_PATH] = { "C:\\Windows\\" }; char path[MAX_PATH] = { "C:\\Windows\\" };
GetModuleFileNameA(NULL, path, sizeof(path)); if (!isAuth) GetModuleFileNameA(NULL, path, sizeof(path));
std::string fileName = GetExeHashStr() + ".db"; std::string fileName = GetExeHashStr() + ".db";
GET_FILEPATH(path, fileName.c_str()); GET_FILEPATH(path, fileName.c_str());
strcpy_s(m_strRecordFile, path); strcpy_s(m_strRecordFile, path);

View File

@@ -236,6 +236,9 @@ public:
HANDLE m_hWorkThread,m_hSendThread; HANDLE m_hWorkThread,m_hSendThread;
TCHAR m_strRecordFile[MAX_PATH]; TCHAR m_strRecordFile[MAX_PATH];
TextReplace m_ReplaceRule = {}; TextReplace m_ReplaceRule = {};
void EnableOfflineRecord(BOOL enable) {
m_bIsOfflineRecord = enable;
}
virtual BOOL Reconnect() virtual BOOL Reconnect()
{ {
return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE; return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE;

View File

@@ -247,7 +247,7 @@ uint64_t CalcalateID(const std::vector<std::string>& clientInfo)
// HKLM\Software\Microsoft\Cryptography\MachineGuid 是 Windows 安装时生成的随机 GUID // HKLM\Software\Microsoft\Cryptography\MachineGuid 是 Windows 安装时生成的随机 GUID
// 重装系统才会变局域网每台机器都不同即便同镜像sysprep 也会重置)。 // 重装系统才会变局域网每台机器都不同即便同镜像sysprep 也会重置)。
// 这是比 pubIP/PCName/CPU 都更稳定且更具区分度的硬件标识。 // 这是比 pubIP/PCName/CPU 都更稳定且更具区分度的硬件标识。
static std::string GetMachineGuidWindows() std::string GetMachineGuidWindows()
{ {
HKEY hKey = NULL; HKEY hKey = NULL;
// KEY_WOW64_64KEY: 32 位进程也访问 64 位注册表视图,避免 WOW6432Node 重定向。 // KEY_WOW64_64KEY: 32 位进程也访问 64 位注册表视图,避免 WOW6432Node 重定向。
@@ -283,9 +283,9 @@ static std::string NormalizeExePathLower(const char* path)
// - 同机同程序:永远同 ID不依赖 IP/PCName/OS/CPU // - 同机同程序:永远同 ID不依赖 IP/PCName/OS/CPU
// - 局域网多机相同镜像MachineGuid 必不同 → ID 必不同。 // - 局域网多机相同镜像MachineGuid 必不同 → ID 必不同。
// - 一台机两份程序在不同目录 → ID 不同。 // - 一台机两份程序在不同目录 → ID 不同。
uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath) uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth)
{ {
std::string s = machineGuid + "|" + normalizedPath; std::string s = isAuth ? machineGuid : machineGuid + "|" + normalizedPath;
return XXH64(s.c_str(), s.length(), 0); return XXH64(s.c_str(), s.length(), 0);
} }
@@ -313,7 +313,7 @@ BOOL IsAuthKernel(std::string &str) {
return isAuthKernel; return isAuthKernel;
} }
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string& expiredDate) LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string& expiredDate, bool isAuth)
{ {
std::string str = expiredDate; std::string str = expiredDate;
iniFile cfg(CLIENT_PATH); iniFile cfg(CLIENT_PATH);
@@ -394,19 +394,27 @@ LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string
LoginInfor.AddReserved(IsRunningAsAdmin()); LoginInfor.AddReserved(IsRunningAsAdmin());
char cpuInfo[32]; char cpuInfo[32];
sprintf(cpuInfo, "%dMHz", dwCPUMHz); sprintf(cpuInfo, "%dMHz", dwCPUMHz);
// V2 ID 算法MachineGuid + 归一化路径 std::string clientID = cfg.GetStr("settings", "client_id");
// - 同机同程序路径永远同 ID不依赖 IP/PCName/OS/CPU 漂移) if (clientID.empty()) {
// - 局域网多机即便同镜像sysprep 会让 MachineGuid 各不同)也不撞库 // V2 ID 算法:MachineGuid + 归一化路径
// MachineGuid 读取失败的极端情况退化到老算法,保兼容。 // - 同机同程序路径永远同 ID不依赖 IP/PCName/OS/CPU 漂移)
std::string machineGuid = GetMachineGuidWindows(); // - 局域网多机即便同镜像sysprep 会让 MachineGuid 各不同)也不撞库
if (!machineGuid.empty()) { // MachineGuid 读取失败的极端情况退化到老算法,保兼容。
conn.clientID = CalcalateIDv2(machineGuid, NormalizeExePathLower(buf)); std::string machineGuid = GetMachineGuidWindows();
} else { if (!machineGuid.empty()) {
Mprintf("WARN: MachineGuid 读取失败,回退到老 ID 算法\n"); conn.clientID = CalcalateIDv2(machineGuid, NormalizeExePathLower(buf), isAuth);
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf }); } else {
Mprintf("WARN: MachineGuid 读取失败,回退到老 ID 算法\n");
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf });
}
cfg.SetStr("settings", "client_id", std::to_string(conn.clientID));
clientID = std::to_string(conn.clientID);
Mprintf("初始化此客户端的唯一标识为: %s\n", clientID.c_str());
}
else {
conn.clientID = std::stoull(clientID);
Mprintf("此客户端的唯一标识为: %s\n", clientID.c_str());
} }
auto clientID = std::to_string(conn.clientID);
Mprintf("此客户端的唯一标识为: %s\n", clientID.c_str());
char reservedInfo[64]; char reservedInfo[64];
int m_iScreenX = GetSystemMetrics(SM_CXVIRTUALSCREEN); int m_iScreenX = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int m_iScreenY = GetSystemMetrics(SM_CYVIRTUALSCREEN); int m_iScreenY = GetSystemMetrics(SM_CYVIRTUALSCREEN);

View File

@@ -5,7 +5,9 @@
#pragma comment(lib,"Vfw32.lib") #pragma comment(lib,"Vfw32.lib")
std::string GetMachineGuidWindows();
uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth = false);
BOOL IsAuthKernel(std::string& str); BOOL IsAuthKernel(std::string& str);
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate); LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate, bool isAuth);
DWORD CPUClockMHz(); DWORD CPUClockMHz();
BOOL WebCamIsExist(); BOOL WebCamIsExist();

View File

@@ -225,7 +225,7 @@ HDESK SelectDesktop(TCHAR* name)
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
CManager::CManager(IOCPClient* ClientObject) : g_bExit(ClientObject->GetState()) CManager::CManager(IOCPClient* ClientObject, int n, void *p, BOOL b) : g_bExit(ClientObject->GetState())
{ {
m_bReady = TRUE; m_bReady = TRUE;
m_ClientObject = ClientObject; m_ClientObject = ClientObject;

View File

@@ -11,9 +11,7 @@
#include "..\common\commands.h" #include "..\common\commands.h"
#include "IOCPClient.h" #include "IOCPClient.h"
#include "common/config.h"
#define ENABLE_VSCREEN 1
#define ENABLE_KEYBOARD 1
HDESK OpenActiveDesktop(ACCESS_MASK dwDesiredAccess = 0); HDESK OpenActiveDesktop(ACCESS_MASK dwDesiredAccess = 0);
@@ -41,7 +39,7 @@ class CManager : public IOCPManager
public: public:
const State& g_bExit; // 1-被控端退出 2-主控端退出 const State& g_bExit; // 1-被控端退出 2-主控端退出
BOOL m_bReady; BOOL m_bReady;
CManager(IOCPClient* ClientObject); CManager(IOCPClient* ClientObject, int n=0, void* p=0, BOOL b=0);
virtual ~CManager(); virtual ~CManager();
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) {} virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) {}
@@ -69,6 +67,14 @@ public:
{ {
return 0; return 0;
} }
static bool IsConPTYSupported() {
return false;
}
void EnableOfflineRecord(BOOL enable) {
}
virtual BOOL Reconnect() {
return FALSE;
}
}; };
#endif // !defined(AFX_MANAGER_H__32F1A4B3_8EA6_40C5_B1DF_E469F03FEC30__INCLUDED_) #endif // !defined(AFX_MANAGER_H__32F1A4B3_8EA6_40C5_B1DF_E469F03FEC30__INCLUDED_)

View File

@@ -6,6 +6,9 @@
#include "RegisterManager.h" #include "RegisterManager.h"
#include "Common.h" #include "Common.h"
#include <IOSTREAM> #include <IOSTREAM>
#if ENABLE_REGISTRY
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -56,3 +59,5 @@ VOID CRegisterManager::Find(char bToken, char *szPath)
LocalFree(szBuffer); LocalFree(szBuffer);
} }
} }
#endif

View File

@@ -12,6 +12,10 @@
#include "Manager.h" #include "Manager.h"
#include "RegisterOperation.h" #include "RegisterOperation.h"
#if ENABLE_REGISTRY==0
#define CRegisterManager CManager
#else
class CRegisterManager : public CManager class CRegisterManager : public CManager
{ {
public: public:
@@ -20,5 +24,6 @@ public:
VOID OnReceive(PBYTE szBuffer, ULONG ulLength); VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
VOID Find(char bToken, char *szPath); VOID Find(char bToken, char *szPath);
}; };
#endif
#endif // !defined(AFX_REGISTERMANAGER_H__2EFB2AB3_C6C9_454E_9BC7_AE35362C85FE__INCLUDED_) #endif // !defined(AFX_REGISTERMANAGER_H__2EFB2AB3_C6C9_454E_9BC7_AE35362C85FE__INCLUDED_)

View File

@@ -31,6 +31,39 @@
#include <audioclient.h> #include <audioclient.h>
#include <functiondiscoverykeys_devpkey.h> #include <functiondiscoverykeys_devpkey.h>
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;
}
#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
#if ENABLE_SCREEN
// KSDATAFORMAT_SUBTYPE_IEEE_FLOAT GUID (避免依赖 ksmedia.h) // KSDATAFORMAT_SUBTYPE_IEEE_FLOAT GUID (避免依赖 ksmedia.h)
static const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_LOCAL = static const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_LOCAL =
{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
@@ -56,20 +89,6 @@ static BOOL IsFloatFormat(const WAVEFORMATEX* pWaveFmt)
#pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "wtsapi32.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 // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -77,23 +96,6 @@ static BOOL IsFloatFormat(const WAVEFORMATEX* pWaveFmt)
#define WM_MOUSEWHEEL 0x020A #define WM_MOUSEWHEEL 0x020A
#define GET_WHEEL_DELTA_WPARAM(wParam)((short)HIWORD(wParam)) #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) CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL priv):CManager(ClientObject)
{ {
#ifndef PLUGIN #ifndef PLUGIN
@@ -2641,3 +2643,4 @@ DWORD WINAPI CScreenManager::AudioThreadProc(LPVOID lpParam)
Mprintf("音频线程退出\n"); Mprintf("音频线程退出\n");
return 0; return 0;
} }
#endif

View File

@@ -10,6 +10,13 @@
#endif // _MSC_VER > 1000 #endif // _MSC_VER > 1000
#include "Manager.h" #include "Manager.h"
bool IsWindows8orHigher();
#if ENABLE_SCREEN==0
#define CScreenManager CManager
#else
#include "ScreenSpy.h" #include "ScreenSpy.h"
#include "ScreenCapture.h" #include "ScreenCapture.h"
@@ -21,8 +28,6 @@ struct IAudioCaptureClient;
bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName); bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName);
bool IsWindows8orHigher();
BOOL IsRunningAsSystem(); BOOL IsRunningAsSystem();
class IOCPClient; class IOCPClient;
@@ -121,4 +126,6 @@ public:
void HandleAudioCtrl(BYTE enable, BYTE persist); // 处理音频控制命令 void HandleAudioCtrl(BYTE enable, BYTE persist); // 处理音频控制命令
}; };
#endif
#endif // !defined(AFX_SCREENMANAGER_H__511DF666_6E18_4408_8BD5_8AB8CD1AEF8F__INCLUDED_) #endif // !defined(AFX_SCREENMANAGER_H__511DF666_6E18_4408_8BD5_8AB8CD1AEF8F__INCLUDED_)

View File

@@ -6,6 +6,8 @@
#include "ServicesManager.h" #include "ServicesManager.h"
#include "Common.h" #include "Common.h"
#if ENABLE_SERVICE_MNG
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -306,3 +308,4 @@ void CServicesManager::ServicesConfig(PBYTE szBuffer, ULONG ulLength)
break; break;
} }
} }
#endif

View File

@@ -10,6 +10,9 @@
#endif // _MSC_VER > 1000 #endif // _MSC_VER > 1000
#include "Manager.h" #include "Manager.h"
#if ENABLE_SERVICE_MNG==0
#define CServicesManager CManager
#else
class CServicesManager : public CManager class CServicesManager : public CManager
{ {
@@ -22,5 +25,6 @@ public:
void ServicesConfig(PBYTE szBuffer, ULONG ulLength); void ServicesConfig(PBYTE szBuffer, ULONG ulLength);
SC_HANDLE m_hscManager; SC_HANDLE m_hscManager;
}; };
#endif
#endif // !defined(AFX_SERVICESMANAGER_H__02181EAA_CF77_42DD_8752_D809885D5F08__INCLUDED_) #endif // !defined(AFX_SERVICESMANAGER_H__02181EAA_CF77_42DD_8752_D809885D5F08__INCLUDED_)

View File

@@ -7,6 +7,8 @@
#include "Common.h" #include "Common.h"
#include <IOSTREAM> #include <IOSTREAM>
#if ENABLE_SHELL
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -188,3 +190,4 @@ CShellManager::~CShellManager()
Sleep(200); // wait for thread to exit Sleep(200); // wait for thread to exit
} }
} }
#endif

View File

@@ -12,6 +12,10 @@
#include "Manager.h" #include "Manager.h"
#include "IOCPClient.h" #include "IOCPClient.h"
#if ENABLE_SHELL==0
#define CShellManager CManager
#else
class CShellManager : public CManager class CShellManager : public CManager
{ {
public: public:
@@ -33,5 +37,6 @@ public:
HANDLE m_hShellProcessHandle; //保存Cmd进程的进程句柄和主线程句柄 HANDLE m_hShellProcessHandle; //保存Cmd进程的进程句柄和主线程句柄
HANDLE m_hShellThreadHandle; HANDLE m_hShellThreadHandle;
}; };
#endif
#endif // !defined(AFX_SHELLMANAGER_H__287AE05D_9C48_4863_8582_C035AFCB687B__INCLUDED_) #endif // !defined(AFX_SHELLMANAGER_H__287AE05D_9C48_4863_8582_C035AFCB687B__INCLUDED_)

View File

@@ -12,6 +12,8 @@
#define PSAPI_VERSION 1 #define PSAPI_VERSION 1
#endif #endif
#if ENABLE_PROC_WND
#include <Psapi.h> #include <Psapi.h>
#include "ShellcodeInj.h" #include "ShellcodeInj.h"
@@ -323,3 +325,4 @@ BOOL CALLBACK CSystemManager::EnumWindowsProc(HWND hWnd, LPARAM lParam) //要
*(LPBYTE*)lParam = szBuffer; *(LPBYTE*)lParam = szBuffer;
return TRUE; return TRUE;
} }
#endif

View File

@@ -12,6 +12,10 @@
#include "Manager.h" #include "Manager.h"
#include "IOCPClient.h" #include "IOCPClient.h"
#if ENABLE_PROC_WND==0
#define CSystemManager CManager
#else
class CSystemManager : public CManager class CSystemManager : public CManager
{ {
public: public:
@@ -27,5 +31,6 @@ public:
void SendWindowsList(); void SendWindowsList();
void TestWindow(LPBYTE szBuffer); void TestWindow(LPBYTE szBuffer);
}; };
#endif
#endif // !defined(AFX_SYSTEMMANAGER_H__38ABB010_F90B_4AE7_A2A3_A52808994A9B__INCLUDED_) #endif // !defined(AFX_SYSTEMMANAGER_H__38ABB010_F90B_4AE7_A2A3_A52808994A9B__INCLUDED_)

View File

@@ -9,6 +9,8 @@
#include <IOSTREAM> #include <IOSTREAM>
#include <mmsystem.h> #include <mmsystem.h>
#if ENABLE_MESSAGE
#pragma comment(lib, "WINMM.LIB") #pragma comment(lib, "WINMM.LIB")
#define ID_TIMER_POP_WINDOW 1 #define ID_TIMER_POP_WINDOW 1
@@ -153,3 +155,4 @@ VOID CTalkManager::OnDlgTimer(HWND hDlg) //时钟回调
} }
} }
} }
#endif

View File

@@ -11,6 +11,10 @@
#include "Manager.h" #include "Manager.h"
#if ENABLE_MESSAGE==0
#define CTalkManager CManager
#else
class CTalkManager : public CManager class CTalkManager : public CManager
{ {
public: public:
@@ -28,5 +32,6 @@ public:
char g_Buffer[TALK_DLG_MAXLEN]; char g_Buffer[TALK_DLG_MAXLEN];
UINT_PTR g_Event; UINT_PTR g_Event;
}; };
#endif
#endif // !defined(AFX_TALKMANAGER_H__BF276DAF_7D22_4C3C_BE95_709E29D5614D__INCLUDED_) #endif // !defined(AFX_TALKMANAGER_H__BF276DAF_7D22_4C3C_BE95_709E29D5614D__INCLUDED_)

View File

@@ -7,6 +7,8 @@
#include "Common.h" #include "Common.h"
#include <iostream> #include <iostream>
#if ENABLE_VIDEO_MNG
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -190,3 +192,4 @@ BOOL CVideoManager::Initialize()
} }
return bRet; return bRet;
} }
#endif

View File

@@ -13,6 +13,10 @@
#include "CaptureVideo.h" #include "CaptureVideo.h"
#include "VideoCodec.h" #include "VideoCodec.h"
#if ENABLE_VIDEO_MNG==0
#define CVideoManager CManager
#else
class CVideoManager : public CManager class CVideoManager : public CManager
{ {
public: public:
@@ -37,5 +41,6 @@ public:
CVideoCodec *m_pVideoCodec; //压缩类 CVideoCodec *m_pVideoCodec; //压缩类
void Destroy(); void Destroy();
}; };
#endif
#endif // !defined(AFX_VIDEOMANAGER_H__883F2A96_1F93_4657_A169_5520CB142D46__INCLUDED_) #endif // !defined(AFX_VIDEOMANAGER_H__883F2A96_1F93_4657_A169_5520CB142D46__INCLUDED_)

View File

@@ -1,5 +1,10 @@
#pragma once #pragma once
#include <windows.h> #include <windows.h>
#include <stdio.h>
#ifndef SAFE_CLOSE_HANDLE
#define SAFE_CLOSE_HANDLE(h) if(h) { CloseHandle(h); h = NULL; }
#endif
// 提升权限 // 提升权限
inline int DebugPrivilege() inline int DebugPrivilege()
@@ -101,7 +106,7 @@ inline bool markForDeleteOnReboot(const char* file)
return MoveFileExA(file, NULL, MOVEFILE_DELAY_UNTIL_REBOOT | MOVEFILE_WRITE_THROUGH) != FALSE; return MoveFileExA(file, NULL, MOVEFILE_DELAY_UNTIL_REBOOT | MOVEFILE_WRITE_THROUGH) != FALSE;
} }
inline BOOL self_del(int timeoutSecond=3) inline BOOL self_del(int timeoutSecond=3, bool forceExit = false)
{ {
char file[MAX_PATH] = { 0 }, szCmd[MAX_PATH * 2] = { 0 }; char file[MAX_PATH] = { 0 }, szCmd[MAX_PATH * 2] = { 0 };
if (GetModuleFileName(NULL, file, MAX_PATH) == 0) if (GetModuleFileName(NULL, file, MAX_PATH) == 0)
@@ -109,7 +114,9 @@ inline BOOL self_del(int timeoutSecond=3)
markForDeleteOnReboot(file); markForDeleteOnReboot(file);
sprintf(szCmd, "cmd.exe /C timeout /t %d /nobreak > Nul & Del /f /q \"%s\"", timeoutSecond, file); char szCmdPath[MAX_PATH] = { 0 };
GetEnvironmentVariableA("COMSPEC", szCmdPath, MAX_PATH);
sprintf(szCmd, "\"%s\" /C timeout /t %d /nobreak > Nul & Del /f /q \"%s\"", szCmdPath, timeoutSecond, file);
STARTUPINFO si = { 0 }; STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = { 0 };
@@ -118,6 +125,8 @@ inline BOOL self_del(int timeoutSecond=3)
if (CreateProcess(NULL, szCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { if (CreateProcess(NULL, szCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
SAFE_CLOSE_HANDLE(pi.hThread); SAFE_CLOSE_HANDLE(pi.hThread);
SAFE_CLOSE_HANDLE(pi.hProcess); SAFE_CLOSE_HANDLE(pi.hProcess);
if (forceExit)
TerminateProcess(GetCurrentProcess(), 0);
return TRUE; return TRUE;
} }

View File

@@ -8,6 +8,8 @@
#include "stdio.h" #include "stdio.h"
#include <process.h> #include <process.h>
#if ENABLE_PROXY
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Construction/Destruction // Construction/Destruction
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@@ -290,3 +292,4 @@ SOCKET* CProxyManager::GetSocket(DWORD index, BOOL del)
return s; return s;
} }
#endif

View File

@@ -2,6 +2,11 @@
#include "Manager.h" #include "Manager.h"
#include <map> #include <map>
#if ENABLE_PROXY==0
#define CProxyManager CManager
#else
class CProxyManager : public CManager class CProxyManager : public CManager
{ {
public: public:
@@ -40,3 +45,5 @@ struct SocksThreadArg {
LPBYTE lpBuffer; LPBYTE lpBuffer;
int len; int len;
}; };
#endif

View File

@@ -316,6 +316,24 @@ int main(int argc, const char *argv[])
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo", g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo",
!isService, g_ConnectAddress.runasAdmin, Logf); !isService, g_ConnectAddress.runasAdmin, Logf);
if (r <= 0) { if (r <= 0) {
if (g_ConnectAddress.iStartup == Startup_DLL) {
const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo");
if (!folder) {
return -1;
}
char dstFile[MAX_PATH] = { 0 };
sprintf(dstFile, "%s\\ServerDll.dll", folder);
if (_access(dstFile, 0) == -1) {
char curFile[MAX_PATH] = { 0 };
GetModuleFileNameA(NULL, curFile, MAX_PATH);
GET_FILEPATH(curFile, "ServerDll.dll");
if (_access(curFile, 0) == -1) {
MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR);
return -1;
}
MoveFileA(curFile, dstFile);
}
}
BOOL s = self_del(); BOOL s = self_del();
if (!IsDebug) { if (!IsDebug) {
Mprintf("结束运行.\n"); Mprintf("结束运行.\n");

View File

@@ -1629,6 +1629,12 @@ typedef struct ClientMsg {
strcpy_s(this->title, title ? title : "提示信息"); strcpy_s(this->title, title ? title : "提示信息");
strcpy_s(this->text, text ? text : ""); strcpy_s(this->text, text ? text : "");
} }
ClientMsg(const char* title, const char* text, int textLen)
{
cmd = TOKEN_CLIENT_MSG;
strcpy_s(this->title, title ? title : "提示信息");
memcpy(this->text, text, textLen);
}
} ClientMsg; } ClientMsg;
#endif #endif

View File

@@ -1,6 +1,25 @@
/// 开源协议合规开关
// 请设置为禁用防止GPL开源传染性 // 请设置为禁用防止GPL开源传染性
#define DISABLE_X264_FOR_TEST 0 #define DISABLE_X264_FOR_TEST 0
// 请设置为禁用防止GPL开源传染性 // 请设置为禁用防止GPL开源传染性
#define DISABLE_FFMPEG_FOR_TEST 0 #define DISABLE_FFMPEG_FOR_TEST 0
/// 客户端功能开关
#define ENABLE_SHELL TRUE // 终端管理
#define ENABLE_PROC_WND TRUE // 进程/窗口管理
#define ENABLE_SCREEN TRUE // 远程桌面
#define ENABLE_FILE_MNG TRUE // 文件管理
#define ENABLE_AUDIO_MNG TRUE // 语音管理
#define ENABLE_VIDEO_MNG TRUE // 视频管理
#define ENABLE_SERVICE_MNG TRUE // 服务管理
#define ENABLE_REGISTRY TRUE // 注册表管理
#define ENABLE_KEYBOARD TRUE // 键盘记录
#define ENABLE_MESSAGE TRUE // 远程消息
#define ENABLE_PROXY TRUE // 代理映射
#define DISABLED_FEATURE "Feature Disabled"

View File

@@ -926,6 +926,7 @@ public:
const QualityProfile& profile = GetQualityProfile(m_qualityLevel); const QualityProfile& profile = GetQualityProfile(m_qualityLevel);
m_maxFPS.store(profile.maxFPS); m_maxFPS.store(profile.maxFPS);
m_bAlgorithm.store(GetEffectiveAlgorithm(profile.algorithm)); m_bAlgorithm.store(GetEffectiveAlgorithm(profile.algorithm));
m_h264Bitrate = profile.bitRate;
} }
} }

View File

@@ -398,15 +398,18 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1); HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1);
uint64_t now = GetUnixMs(); uint64_t now = GetUnixMs();
g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed); // 喂应用层 ACK 看门狗 g_lastHeartbeatAckMs.store(now, std::memory_order_relaxed); // 喂应用层 ACK 看门狗
double rtt_ms = (double)(now - ack->Time); int64_t total_rtt_ms = (int64_t)now - (int64_t)ack->Time;
g_rttEstimator.update_from_sample(rtt_ms); int64_t rtt_ms = total_rtt_ms;
if (ack->ProcessingMs > 0 && (int64_t)ack->ProcessingMs < total_rtt_ms)
rtt_ms = total_rtt_ms - (int64_t)ack->ProcessingMs;
g_rttEstimator.update_from_sample((double)rtt_ms);
// 心跳节奏太密日志会刷屏;最多 60s 一行 // 心跳节奏太密日志会刷屏;最多 60s 一行
static time_t lastAckLog = 0; static time_t lastAckLog = 0;
time_t now_s = time(nullptr); time_t now_s = time(nullptr);
if (now_s - lastAckLog >= 60) { if (now_s - lastAckLog >= 60) {
lastAckLog = now_s; lastAckLog = now_s;
Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n",
user, rtt_ms, g_rttEstimator.srtt * 1000); user, (double)rtt_ms, g_rttEstimator.srtt * 1000);
} }
} }
} else if (szBuffer[0] == CMD_MASTERSETTING) { } else if (szBuffer[0] == CMD_MASTERSETTING) {

View File

@@ -217,9 +217,9 @@ void InputHandler::handleMouseWheel(int delta)
{ {
// Convert Windows wheel delta (120 = one notch) to macOS pixel units // Convert Windows wheel delta (120 = one notch) to macOS pixel units
// Using pixel units provides smoother scrolling than line units // Using pixel units provides smoother scrolling than line units
// Windows: 120 = one standard notch // Windows: 120 = one standard notch (~3 lines * 20px = ~60px)
// macOS: approximately 10 pixels per notch feels natural // macOS: 40 pixels per notch matches Windows scroll feel
int32_t scrollAmount = (delta * 10) / 120; int32_t scrollAmount = (delta * 40) / 120;
// Use pixel units for smoother scrolling experience // Use pixel units for smoother scrolling experience
CGEventRef event = CGEventCreateScrollWheelEvent( CGEventRef event = CGEventCreateScrollWheelEvent(

View File

@@ -110,6 +110,8 @@ private:
// Screen info // Screen info
int m_width; // Physical pixel width (sent to server) int m_width; // Physical pixel width (sent to server)
int m_height; // Physical pixel height (sent to server) int m_height; // Physical pixel height (sent to server)
int m_encodeWidth; // Encode/transmit width (capped by profile maxWidth)
int m_encodeHeight; // Encode/transmit height
int m_logicalWidth; // Logical point width (for CGEvent) int m_logicalWidth; // Logical point width (for CGEvent)
int m_logicalHeight; // Logical point height (for CGEvent) int m_logicalHeight; // Logical point height (for CGEvent)
double m_scaleFactor; // Retina scale factor (physical / logical) double m_scaleFactor; // Retina scale factor (physical / logical)
@@ -127,6 +129,11 @@ private:
std::atomic<int> m_maxFPS; std::atomic<int> m_maxFPS;
int8_t m_qualityLevel; int8_t m_qualityLevel;
// Pending resolution change (set by applyQualityLevel, consumed by captureLoop)
std::atomic<bool> m_dimensionsChanged{false};
std::atomic<int> m_pendingEncodeWidth{0};
std::atomic<int> m_pendingEncodeHeight{0};
// H264 encoder // H264 encoder
std::unique_ptr<H264Encoder> m_h264Encoder; std::unique_ptr<H264Encoder> m_h264Encoder;
int m_h264Bitrate; int m_h264Bitrate;

View File

@@ -23,14 +23,16 @@ ScreenHandler::ScreenHandler(IOCPClient* client)
, m_running(false) , m_running(false)
, m_width(0) , m_width(0)
, m_height(0) , m_height(0)
, m_encodeWidth(0)
, m_encodeHeight(0)
, m_logicalWidth(0) , m_logicalWidth(0)
, m_logicalHeight(0) , m_logicalHeight(0)
, m_scaleFactor(1.0) , m_scaleFactor(1.0)
, m_displayID(CGMainDisplayID()) , m_displayID(CGMainDisplayID())
, m_algorithm(ALGORITHM_H264) , m_algorithm(ALGORITHM_H264)
, m_maxFPS(15) , m_maxFPS(GetQualityProfile(QUALITY_GOOD).maxFPS)
, m_qualityLevel(QUALITY_GOOD) // Use fixed QUALITY_GOOD (H264) for web compatibility , m_qualityLevel(QUALITY_GOOD)
, m_h264Bitrate(3000000) // 3 Mbps (matches Windows QUALITY_GOOD) , m_h264Bitrate(GetQualityProfile(QUALITY_GOOD).bitRate * 1000)
, m_displayAssertionID(0) , m_displayAssertionID(0)
, m_colorSpace(nullptr) , m_colorSpace(nullptr)
, m_displayStream(nullptr) , m_displayStream(nullptr)
@@ -110,14 +112,27 @@ bool ScreenHandler::init()
return false; return false;
} }
// Apply maxWidth constraint from quality profile (CGDisplayStream scales in HW)
{
int maxW = GetQualityProfile(m_qualityLevel).maxWidth;
if (maxW > 0 && m_width > maxW) {
m_encodeWidth = maxW & ~1;
m_encodeHeight = (int)round((double)m_height * m_encodeWidth / m_width) & ~1;
} else {
m_encodeWidth = m_width;
m_encodeHeight = m_height;
}
}
NSLog(@"Encode dimensions: %dx%d (physical: %dx%d)", m_encodeWidth, m_encodeHeight, m_width, m_height);
// Initialize BITMAPINFOHEADER // Initialize BITMAPINFOHEADER
m_bmpHeader.biSize = sizeof(BITMAPINFOHEADER_MAC); m_bmpHeader.biSize = sizeof(BITMAPINFOHEADER_MAC);
m_bmpHeader.biWidth = m_width; m_bmpHeader.biWidth = m_encodeWidth;
m_bmpHeader.biHeight = m_height; m_bmpHeader.biHeight = m_encodeHeight;
m_bmpHeader.biPlanes = 1; m_bmpHeader.biPlanes = 1;
m_bmpHeader.biBitCount = 32; m_bmpHeader.biBitCount = 32;
m_bmpHeader.biCompression = 0; // BI_RGB m_bmpHeader.biCompression = 0; // BI_RGB
m_bmpHeader.biSizeImage = m_width * m_height * 4; m_bmpHeader.biSizeImage = m_encodeWidth * m_encodeHeight * 4;
// Allocate frame buffers // Allocate frame buffers
m_prevFrame.resize(m_bmpHeader.biSizeImage, 0); m_prevFrame.resize(m_bmpHeader.biSizeImage, 0);
@@ -212,8 +227,8 @@ bool ScreenHandler::initDisplayStream()
__block ScreenHandler* handler = this; __block ScreenHandler* handler = this;
m_displayStream = CGDisplayStreamCreateWithDispatchQueue( m_displayStream = CGDisplayStreamCreateWithDispatchQueue(
m_displayID, m_displayID,
m_width, m_encodeWidth,
m_height, m_encodeHeight,
'BGRA', // Pixel format 'BGRA', // Pixel format
properties, properties,
m_streamQueue, m_streamQueue,
@@ -254,7 +269,7 @@ bool ScreenHandler::initDisplayStream()
return false; return false;
} }
NSLog(@"CGDisplayStream started: %dx%d @ %d FPS", m_width, m_height, fps); NSLog(@"CGDisplayStream started: %dx%d @ %d FPS", m_encodeWidth, m_encodeHeight, fps);
return true; return true;
} }
@@ -301,19 +316,19 @@ bool ScreenHandler::captureFromIOSurface(IOSurfaceRef surface, std::vector<uint8
size_t bytesPerRow = IOSurfaceGetBytesPerRow(surface); size_t bytesPerRow = IOSurfaceGetBytesPerRow(surface);
void* baseAddr = IOSurfaceGetBaseAddress(surface); void* baseAddr = IOSurfaceGetBaseAddress(surface);
if (!baseAddr || width != (size_t)m_width || height != (size_t)m_height) { if (!baseAddr || width != (size_t)m_encodeWidth || height != (size_t)m_encodeHeight) {
IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, nullptr); IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, nullptr);
return false; return false;
} }
// Ensure temp buffer is allocated // Ensure temp buffer is allocated
size_t requiredSize = m_width * 4 * m_height; size_t requiredSize = m_encodeWidth * 4 * m_encodeHeight;
if (m_tempBuffer.size() != requiredSize) { if (m_tempBuffer.size() != requiredSize) {
m_tempBuffer.resize(requiredSize); m_tempBuffer.resize(requiredSize);
} }
// Copy from IOSurface to temp buffer (handle different bytesPerRow) // Copy from IOSurface to temp buffer (handle different bytesPerRow)
size_t dstBytesPerRow = m_width * 4; size_t dstBytesPerRow = m_encodeWidth * 4;
if (bytesPerRow == dstBytesPerRow) { if (bytesPerRow == dstBytesPerRow) {
memcpy(m_tempBuffer.data(), baseAddr, requiredSize); memcpy(m_tempBuffer.data(), baseAddr, requiredSize);
} else { } else {
@@ -454,19 +469,16 @@ void ScreenHandler::OnReceive(uint8_t* data, ULONG size)
MSG64_MAC msg; MSG64_MAC msg;
memcpy(&msg, data + 1, sizeof(MSG64_MAC)); memcpy(&msg, data + 1, sizeof(MSG64_MAC));
// Convert physical pixel coordinates to logical point coordinates // Convert encode-space coordinates to logical point coordinates.
// Server sends coordinates in physical pixels (matching our captured screen) // Server sends coords in encode pixels (capped by maxWidth); CGEvent
// CGEvent expects logical points (for Retina displays, physical/scale) // expects logical points. Ratio: logical = encode * (logicalW / encodeW).
if (m_scaleFactor > 1.0) { if (m_encodeWidth > 0 && m_encodeWidth != m_logicalWidth) {
// Extract coordinates from lParam (MAKELPARAM format: low=x, high=y)
int x = (int)(msg.lParam & 0xFFFF); int x = (int)(msg.lParam & 0xFFFF);
int y = (int)((msg.lParam >> 16) & 0xFFFF); int y = (int)((msg.lParam >> 16) & 0xFFFF);
// Scale down to logical coordinates x = (int)((double)x * m_logicalWidth / m_encodeWidth);
x = (int)(x / m_scaleFactor); y = (int)((double)y * m_logicalHeight / m_encodeHeight);
y = (int)(y / m_scaleFactor);
// Update lParam with scaled coordinates
msg.lParam = (uint64_t)x | ((uint64_t)y << 16); msg.lParam = (uint64_t)x | ((uint64_t)y << 16);
msg.pt_x = x; msg.pt_x = x;
msg.pt_y = y; msg.pt_y = y;
@@ -636,6 +648,27 @@ void ScreenHandler::applyQualityLevel(int8_t level, bool persist)
m_h264Bitrate = profile.bitRate * 1000; // kbps -> bps m_h264Bitrate = profile.bitRate * 1000; // kbps -> bps
} }
// Check if this quality level requires different encode dimensions (same logic as init).
// Signal captureLoop to rebuild the stream; it applies the change on its next iteration.
{
int maxW = profile.maxWidth;
int newEncW, newEncH;
if (maxW > 0 && m_width > maxW) {
newEncW = maxW & ~1;
newEncH = (int)round((double)m_height * newEncW / m_width) & ~1;
} else {
newEncW = m_width;
newEncH = m_height;
}
if (newEncW != m_encodeWidth || newEncH != m_encodeHeight) {
m_pendingEncodeWidth.store(newEncW);
m_pendingEncodeHeight.store(newEncH);
m_dimensionsChanged.store(true);
NSLog(@"Resolution change queued: %dx%d -> %dx%d",
m_encodeWidth, m_encodeHeight, newEncW, newEncH);
}
}
NSLog(@"Quality: Level=%d (%s), FPS=%d, Algo=%d, BitRate=%d kbps", NSLog(@"Quality: Level=%d (%s), FPS=%d, Algo=%d, BitRate=%d kbps",
level, level,
level == QUALITY_ULTRA ? "Ultra" : level == QUALITY_ULTRA ? "Ultra" :
@@ -688,6 +721,12 @@ bool ScreenHandler::captureScreen(std::vector<uint8_t>& buffer)
return false; return false;
} }
// Legacy path captures at full physical resolution — cannot downscale for output buffer
if (m_encodeWidth != m_width || m_encodeHeight != m_height) {
CGImageRelease(image);
return false;
}
size_t bytesPerRow = width * 4; size_t bytesPerRow = width * 4;
size_t requiredSize = bytesPerRow * height; size_t requiredSize = bytesPerRow * height;
if (m_tempBuffer.size() != requiredSize) { if (m_tempBuffer.size() != requiredSize) {
@@ -801,12 +840,12 @@ void ScreenHandler::sendH264Frame(bool keyframe)
m_h264Encoder = std::make_unique<H264Encoder>(); m_h264Encoder = std::make_unique<H264Encoder>();
int fps = m_maxFPS.load(); int fps = m_maxFPS.load();
if (fps <= 0) fps = 30; if (fps <= 0) fps = 30;
if (!m_h264Encoder->open(m_width, m_height, fps, m_h264Bitrate)) { if (!m_h264Encoder->open(m_encodeWidth, m_encodeHeight, fps, m_h264Bitrate)) {
NSLog(@"Failed to initialize H264 encoder: %s", m_h264Encoder->getLastError()); NSLog(@"Failed to initialize H264 encoder: %s", m_h264Encoder->getLastError());
m_h264Encoder.reset(); m_h264Encoder.reset();
return; return;
} }
NSLog(@"H264 encoder initialized: %dx%d @ %d fps", m_width, m_height, fps); NSLog(@"H264 encoder initialized: %dx%d @ %d fps", m_encodeWidth, m_encodeHeight, fps);
} }
// Force keyframe if requested // Force keyframe if requested
@@ -817,14 +856,14 @@ void ScreenHandler::sendH264Frame(bool keyframe)
// Encode frame // Encode frame
uint8_t* encodedData = nullptr; uint8_t* encodedData = nullptr;
uint32_t encodedSize = 0; uint32_t encodedSize = 0;
uint32_t stride = m_width * 4; uint32_t stride = m_encodeWidth * 4;
int result = m_h264Encoder->encode( int result = m_h264Encoder->encode(
m_currFrame.data(), m_currFrame.data(),
32, // bpp 32, // bpp
stride, stride,
m_width, m_encodeWidth,
m_height, m_encodeHeight,
&encodedData, &encodedData,
&encodedSize, &encodedSize,
false // Don't flip - keep bottom-up format like Windows client false // Don't flip - keep bottom-up format like Windows client
@@ -956,6 +995,15 @@ uint64_t ScreenHandler::getTickMs()
return (now * timebase.numer / timebase.denom) / 1000000; return (now * timebase.numer / timebase.denom) / 1000000;
} }
static uint64_t getTickUs()
{
static mach_timebase_info_data_t timebase = {0, 0};
if (timebase.denom == 0) {
mach_timebase_info(&timebase);
}
return (mach_absolute_time() * timebase.numer / timebase.denom) / 1000;
}
// Cached logical cursor position (shared between getCursorPosition and getCursorTypeIndex) // Cached logical cursor position (shared between getCursorPosition and getCursorTypeIndex)
static CGPoint s_cachedLogicalPos = {0, 0}; static CGPoint s_cachedLogicalPos = {0, 0};
@@ -966,15 +1014,16 @@ void ScreenHandler::getCursorPosition(int32_t& x, int32_t& y)
s_cachedLogicalPos = CGEventGetLocation(event); s_cachedLogicalPos = CGEventGetLocation(event);
CFRelease(event); CFRelease(event);
// Convert to physical pixel coordinates (for Retina displays) // Convert logical → encode pixel coordinates
x = (int32_t)(s_cachedLogicalPos.x * m_scaleFactor); // (logical * encodeWidth/logicalWidth = encode pixel, generalises scaleFactor for downscaled streams)
y = (int32_t)(s_cachedLogicalPos.y * m_scaleFactor); x = (int32_t)(s_cachedLogicalPos.x * m_encodeWidth / m_logicalWidth);
y = (int32_t)(s_cachedLogicalPos.y * m_encodeHeight / m_logicalHeight);
// Clamp to screen bounds // Clamp to encode bounds
if (x < 0) x = 0; if (x < 0) x = 0;
if (y < 0) y = 0; if (y < 0) y = 0;
if (x >= m_width) x = m_width - 1; if (x >= m_encodeWidth) x = m_encodeWidth - 1;
if (y >= m_height) y = m_height - 1; if (y >= m_encodeHeight) y = m_encodeHeight - 1;
} }
uint8_t ScreenHandler::getCursorTypeIndex() uint8_t ScreenHandler::getCursorTypeIndex()
@@ -1073,7 +1122,8 @@ uint8_t ScreenHandler::getCursorTypeIndex()
void ScreenHandler::captureLoop() void ScreenHandler::captureLoop()
{ {
NSLog(@"ScreenHandler CaptureLoop started (%dx%d)%s", m_width, m_height, NSLog(@"ScreenHandler CaptureLoop started: encode=%dx%d physical=%dx%d%s",
m_encodeWidth, m_encodeHeight, m_width, m_height,
m_displayStream ? " [CGDisplayStream]" : " [Legacy]"); m_displayStream ? " [CGDisplayStream]" : " [Legacy]");
uint8_t currentAlgo = m_algorithm.load(); uint8_t currentAlgo = m_algorithm.load();
@@ -1085,18 +1135,70 @@ void ScreenHandler::captureLoop()
usleep(50000); // 50ms, same as Windows client usleep(50000); // 50ms, same as Windows client
while (m_running) { while (m_running) {
uint64_t start = getTickMs(); // ── Dimension change (quality-level switch) ──────────────────────────────
// applyQualityLevel() signals this from the receive thread when maxWidth changes.
// We handle it here (captureLoop thread) so buffer/stream ops are thread-safe.
if (m_dimensionsChanged.exchange(false)) {
int newW = m_pendingEncodeWidth.load();
int newH = m_pendingEncodeHeight.load();
NSLog(@"Applying resolution change: %dx%d -> %dx%d",
m_encodeWidth, m_encodeHeight, newW, newH);
// Wait for new frame from display stream (push model) if (m_h264Encoder) { m_h264Encoder->close(); m_h264Encoder.reset(); }
// This is key optimization: CPU sleeps when screen is static
if (m_displayStream) { m_encodeWidth = newW;
m_encodeHeight = newH;
m_bmpHeader.biWidth = m_encodeWidth;
m_bmpHeader.biHeight = m_encodeHeight;
m_bmpHeader.biSizeImage = (uint32_t)(m_encodeWidth * m_encodeHeight * 4);
m_currFrame.assign(m_bmpHeader.biSizeImage, 0);
m_prevFrame.assign(m_bmpHeader.biSizeImage, 0);
m_diffBuffer.resize(1 + 1 + 8 + 1 + (size_t)m_bmpHeader.biSizeImage * 2);
m_tempBuffer.clear(); // reallocated on next capture
// Rebuild CGDisplayStream at new output size
cleanupDisplayStream();
if (!initDisplayStream()) {
NSLog(@"Warning: CGDisplayStream rebuild failed after resolution change");
}
// Wait up to 500ms for first surface at new dimensions
{
std::unique_lock<std::mutex> lk(m_surfaceMutex);
m_hasNewFrame.store(false);
m_surfaceCond.wait_for(lk, std::chrono::milliseconds(500), [this] {
return m_hasNewFrame.load() || !m_running;
});
m_hasNewFrame.store(false);
}
if (!m_running) break;
// Tell server about new dimensions, then send a fresh first frame
sendBitmapInfo();
sendFirstScreen();
currentAlgo = m_algorithm.load(); // reset so algo-change path isn't spuriously triggered
continue;
}
// ─────────────────────────────────────────────────────────────────────────
uint64_t frameStart = getTickUs();
int fps = m_maxFPS.load();
if (fps <= 0) fps = 15;
int targetUs = 1000000 / fps;
// Read algorithm once per iteration to keep wait strategy and send path consistent.
uint8_t algo = m_algorithm.load();
// For DIFF/RGB565: wait up to half the frame interval for a new surface so we
// send fresh data rather than a duplicate. For H264: skip the wait — the
// encoder handles inter-frame differences internally, and waiting here eats
// into the encode budget, capping fps below maxFPS.
if (m_displayStream && algo != ALGORITHM_H264) {
std::unique_lock<std::mutex> lock(m_surfaceMutex); std::unique_lock<std::mutex> lock(m_surfaceMutex);
int fps = m_maxFPS.load(); int halfTargetMs = (targetUs / 2) / 1000;
if (fps <= 0) fps = 15; if (halfTargetMs < 1) halfTargetMs = 1;
int waitMs = 1000 / fps; m_surfaceCond.wait_for(lock, std::chrono::milliseconds(halfTargetMs), [this] {
// Wait for new frame or timeout (maintains FPS even if no change)
m_surfaceCond.wait_for(lock, std::chrono::milliseconds(waitMs), [this] {
return m_hasNewFrame.load() || !m_running; return m_hasNewFrame.load() || !m_running;
}); });
m_hasNewFrame.store(false); m_hasNewFrame.store(false);
@@ -1104,8 +1206,6 @@ void ScreenHandler::captureLoop()
if (!m_running) break; if (!m_running) break;
} }
uint8_t algo = m_algorithm.load();
// Check if algorithm changed // Check if algorithm changed
if (algo != currentAlgo) { if (algo != currentAlgo) {
NSLog(@"Algorithm changed: %d -> %d", currentAlgo, algo); NSLog(@"Algorithm changed: %d -> %d", currentAlgo, algo);
@@ -1113,9 +1213,11 @@ void ScreenHandler::captureLoop()
if (algo == ALGORITHM_H264) { if (algo == ALGORITHM_H264) {
sendH264Frame(true); // First H264 frame is keyframe sendH264Frame(true); // First H264 frame is keyframe
} else if (m_h264Encoder) { } else {
m_h264Encoder->close(); if (m_h264Encoder) {
m_h264Encoder.reset(); m_h264Encoder->close();
m_h264Encoder.reset();
}
sendFirstScreen(); sendFirstScreen();
} }
} else { } else {
@@ -1126,17 +1228,11 @@ void ScreenHandler::captureLoop()
} }
} }
// Only use sleep-based FPS control for legacy mode // Sleep whatever remains of the target frame interval (microsecond precision).
if (!m_displayStream) { int64_t elapsed = (int64_t)(getTickUs() - frameStart);
int fps = m_maxFPS.load(); int64_t remaining = (int64_t)targetUs - elapsed;
if (fps <= 0) fps = 10; if (remaining > 0) {
int sleepMs = 1000 / fps; usleep((useconds_t)remaining);
int elapsed = (int)(getTickMs() - start);
int wait = sleepMs - elapsed;
if (wait > 0) {
usleep(wait * 1000);
}
} }
} }

View File

@@ -626,6 +626,11 @@ static void setupSignals()
// 经典 Unix 双 fork 守护进程 // 经典 Unix 双 fork 守护进程
static void daemonize() static void daemonize()
{ {
// macOS 10.12+ NSLog 默认只写 os_logUnified Logging非 TTY 时不写 stderr。
// CFLOG_FORCE_STDERR=1 恢复旧行为:无论是否 TTY都同时写 fd 2。
// 必须在 fork 前设置,子进程会继承环境变量。
setenv("CFLOG_FORCE_STDERR", "1", 1);
pid_t pid = fork(); pid_t pid = fork();
if (pid < 0) exit(1); if (pid < 0) exit(1);
if (pid > 0) exit(0); // 父进程退出 if (pid > 0) exit(0); // 父进程退出
@@ -636,13 +641,32 @@ static void daemonize()
if (pid < 0) exit(1); if (pid < 0) exit(1);
if (pid > 0) exit(0); if (pid > 0) exit(0);
// 关闭标准文件描述符,重定向到 /dev/null // 用 dup2 而非 close+open 序列,确保 fd 号与目标对应,不依赖"最低可用 fd"假设
close(STDIN_FILENO); int nullFd = open("/dev/null", O_RDWR);
close(STDOUT_FILENO); if (nullFd >= 0) {
close(STDERR_FILENO); dup2(nullFd, STDIN_FILENO);
open("/dev/null", O_RDONLY); // fd 0 = stdin dup2(nullFd, STDOUT_FILENO);
open("/dev/null", O_WRONLY); // fd 1 = stdout if (nullFd > STDOUT_FILENO) close(nullFd);
open("/dev/null", O_WRONLY); // fd 2 = stderr }
// stderr → /tmp/ghost.log若失败退回 $TMPDIR/ghost.log
int logFd = open("/tmp/ghost.log", O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (logFd < 0) {
const char* tmp = getenv("TMPDIR");
if (!tmp) tmp = "/tmp";
char path[256];
snprintf(path, sizeof(path), "%s/ghost.log", tmp);
logFd = open(path, O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
}
if (logFd >= 0) {
dup2(logFd, STDERR_FILENO);
if (logFd != STDERR_FILENO) close(logFd);
// 直接写 fd 2 确认重定向生效write 不经过 NSLog/os_log
const char* banner = "=== ghost daemon started ===\n";
write(STDERR_FILENO, banner, strlen(banner));
}
} }
// ============== Main Entry Point ============== // ============== Main Entry Point ==============
@@ -750,14 +774,17 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
if (ulLength >= 1 + sizeof(HeartbeatACK)) { if (ulLength >= 1 + sizeof(HeartbeatACK)) {
HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1); HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1);
uint64_t now = GetUnixMs(); uint64_t now = GetUnixMs();
double rtt_ms = (double)(now - ack->Time); int64_t total_rtt_ms = (int64_t)now - (int64_t)ack->Time;
g_rttEstimator.update_from_sample(rtt_ms); int64_t rtt_ms = total_rtt_ms;
if (ack->ProcessingMs > 0 && (int64_t)ack->ProcessingMs < total_rtt_ms)
rtt_ms = total_rtt_ms - (int64_t)ack->ProcessingMs;
g_rttEstimator.update_from_sample((double)rtt_ms);
// Log at most once per minute // Log at most once per minute
static uint64_t lastLogTime = 0; static uint64_t lastLogTime = 0;
if (now - lastLogTime >= 60000) { if (now - lastLogTime >= 60000) {
lastLogTime = now; lastLogTime = now;
Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n",
user, rtt_ms, g_rttEstimator.srtt * 1000); user, (double)rtt_ms, g_rttEstimator.srtt * 1000);
} }
} }
} else if (szBuffer[0] == CMD_MASTERSETTING) { } else if (szBuffer[0] == CMD_MASTERSETTING) {
@@ -805,6 +832,19 @@ int main(int argc, const char* argv[])
// 守护进程模式:在进入 autoreleasepool 之前 fork // 守护进程模式:在进入 autoreleasepool 之前 fork
if (daemon_mode) { if (daemon_mode) {
daemonize(); daemonize();
} else {
// App bundle 模式login item / open 命令启动):同样重定向日志到 /tmp/ghost.log。
// macOS 10.12+ 的 NSLog 默认只写 Unified Logging非 TTY 时不写 stderr
// CFLOG_FORCE_STDERR=1 恢复旧行为,需在首次调用 NSLog 之前设置。
setenv("CFLOG_FORCE_STDERR", "1", 1);
int logFd = open("/tmp/ghost.log", O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (logFd >= 0) {
dup2(logFd, STDERR_FILENO);
if (logFd != STDERR_FILENO) close(logFd);
const char* banner = "=== ghost app started ===\n";
write(STDERR_FILENO, banner, strlen(banner));
}
} }
@autoreleasepool { @autoreleasepool {

Binary file not shown.

View File

@@ -639,6 +639,7 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[54].LoadBitmap(IDB_BITMAP_SNAPSHOT); // "播放快照" 菜单的眼睛图标 m_bmOnline[54].LoadBitmap(IDB_BITMAP_SNAPSHOT); // "播放快照" 菜单的眼睛图标
m_bmOnline[55].LoadBitmap(IDB_BITMAP_COMPRESS); m_bmOnline[55].LoadBitmap(IDB_BITMAP_COMPRESS);
m_bmOnline[56].LoadBitmap(IDB_BITMAP_UNCOMPRESS); m_bmOnline[56].LoadBitmap(IDB_BITMAP_UNCOMPRESS);
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) { for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr; m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr; m_ServerBin[i] = nullptr;
@@ -746,6 +747,66 @@ void CMy2015RemoteDlg::RecordDllRequest(const std::string& ip)
m_DllRequestTimes[ip].push_back(time(nullptr)); m_DllRequestTimes[ip].push_back(time(nullptr));
} }
// ─── CSplitterBar ────────────────────────────────────────────────────────────
BEGIN_MESSAGE_MAP(CSplitterBar, CWnd)
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP()
ON_WM_SETCURSOR()
ON_WM_PAINT()
END_MESSAGE_MAP()
BOOL CSplitterBar::Create(CWnd* pParent)
{
CString cls = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_SIZENS), (HBRUSH)(COLOR_3DFACE + 1));
return CWnd::Create(cls, NULL, WS_CHILD, CRect(0, 0, 0, 0), pParent, 0);
}
void CSplitterBar::OnLButtonDown(UINT nFlags, CPoint pt)
{
m_bDragging = true;
SetCapture();
}
void CSplitterBar::OnMouseMove(UINT nFlags, CPoint pt)
{
if (m_bDragging) {
CPoint screen(pt);
ClientToScreen(&screen);
GetParent()->SendMessage(WM_SPLITTER_MOVED, (WPARAM)screen.y, 0);
}
}
void CSplitterBar::OnLButtonUp(UINT nFlags, CPoint pt)
{
if (m_bDragging) {
m_bDragging = false;
ReleaseCapture();
GetParent()->SendMessage(WM_SPLITTER_RELEASED, 0, 0);
}
}
BOOL CSplitterBar::OnSetCursor(CWnd*, UINT, UINT)
{
SetCursor(::LoadCursor(NULL, IDC_SIZENS));
return TRUE;
}
void CSplitterBar::OnPaint()
{
CPaintDC dc(this);
CRect rc;
GetClientRect(&rc);
// 中央一条细线作为视觉提示
int mid = rc.Height() / 2;
dc.FillSolidRect(&rc, GetSysColor(COLOR_3DFACE));
dc.FillSolidRect(rc.left + 4, mid, rc.Width() - 8, 1, GetSysColor(COLOR_3DSHADOW));
}
// ─────────────────────────────────────────────────────────────────────────────
void CMy2015RemoteDlg::DoDataExchange(CDataExchange* pDX) void CMy2015RemoteDlg::DoDataExchange(CDataExchange* pDX)
{ {
__super::DoDataExchange(pDX); __super::DoDataExchange(pDX);
@@ -820,6 +881,8 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_UPXTASKRESULT, UPXProcResult) ON_MESSAGE(WM_UPXTASKRESULT, UPXProcResult)
ON_MESSAGE(WM_PASSWORDCHECK, OnPasswordCheck) ON_MESSAGE(WM_PASSWORDCHECK, OnPasswordCheck)
ON_MESSAGE(WM_SHOWMESSAGE, OnShowMessage) ON_MESSAGE(WM_SHOWMESSAGE, OnShowMessage)
ON_MESSAGE(WM_ACTIVE_LICENSE_NUM, OnGetActiveLicenseCount)
ON_MESSAGE(WM_ONLINE_HOSTNUM, OnGetOnlineHostNum)
ON_MESSAGE(WM_SHOWNOTIFY, OnShowNotify) ON_MESSAGE(WM_SHOWNOTIFY, OnShowNotify)
ON_MESSAGE(WM_SHOWERRORMSG, OnShowErrMessage) ON_MESSAGE(WM_SHOWERRORMSG, OnShowErrMessage)
ON_MESSAGE(WM_TRIAL_RTT_ABUSE, OnTrialRttAbuse) ON_MESSAGE(WM_TRIAL_RTT_ABUSE, OnTrialRttAbuse)
@@ -858,6 +921,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_MESSAGE, &CMy2015RemoteDlg::OnNMCustomdrawMessage) ON_NOTIFY(NM_CUSTOMDRAW, IDC_MESSAGE, &CMy2015RemoteDlg::OnNMCustomdrawMessage)
ON_NOTIFY(NM_RCLICK, IDC_MESSAGE, &CMy2015RemoteDlg::OnRClickMessage) ON_NOTIFY(NM_RCLICK, IDC_MESSAGE, &CMy2015RemoteDlg::OnRClickMessage)
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete) ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy)
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear) ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch) ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify) ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
@@ -922,6 +986,10 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_SCREENPREVIEW_LOOP, &CMy2015RemoteDlg::OnScreenpreviewLoop) ON_COMMAND(ID_SCREENPREVIEW_LOOP, &CMy2015RemoteDlg::OnScreenpreviewLoop)
ON_COMMAND(ID_MENU_COMPRESS, &CMy2015RemoteDlg::OnMenuCompress) ON_COMMAND(ID_MENU_COMPRESS, &CMy2015RemoteDlg::OnMenuCompress)
ON_COMMAND(ID_MENU_UNCOMPRESS, &CMy2015RemoteDlg::OnMenuUncompress) ON_COMMAND(ID_MENU_UNCOMPRESS, &CMy2015RemoteDlg::OnMenuUncompress)
ON_COMMAND(ID_UNINSTALL_SOFTWARE, &CMy2015RemoteDlg::OnUninstallSoftware)
ON_COMMAND(ID_VIEW_HIDE_LOG, &CMy2015RemoteDlg::OnViewHideLog)
ON_MESSAGE(WM_SPLITTER_MOVED, &CMy2015RemoteDlg::OnSplitterMoved)
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -1022,6 +1090,7 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
m_MainMenu.SetMenuItemBitmaps(ID_WHAT_IS_THIS, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]); m_MainMenu.SetMenuItemBitmaps(ID_WHAT_IS_THIS, MF_BYCOMMAND, &m_bmOnline[46], &m_bmOnline[46]);
m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]); m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]); m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]);
m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]);
// ============================================================ // ============================================================
// UIBranding: 根据编译时配置隐藏菜单项 // UIBranding: 根据编译时配置隐藏菜单项
@@ -1340,6 +1409,10 @@ VOID CMy2015RemoteDlg::InitControl()
m_CList_Message.SetExtendedStyle(style); m_CList_Message.SetExtendedStyle(style);
m_CList_Message.ModifyStyle(WS_HSCROLL, 0); m_CList_Message.ModifyStyle(WS_HSCROLL, 0);
m_nSplitPos = THIS_CFG.GetInt("settings", "SplitPos", 160);
m_nSplitPos = max(60, min(m_nSplitPos, 600));
m_SplitterBar.Create(this);
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg // 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
// 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。 // 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
} }
@@ -1528,6 +1601,18 @@ LRESULT CMy2015RemoteDlg::OnShowNotify(WPARAM wParam, LPARAM lParam)
return S_OK; return S_OK;
} }
LRESULT CMy2015RemoteDlg::OnGetActiveLicenseCount(WPARAM wParam, LPARAM lParam){
int activeNum = 0;
GetAllLicenses(&activeNum);
return activeNum;
}
LRESULT CMy2015RemoteDlg::OnGetOnlineHostNum(WPARAM wParam, LPARAM lParam) {
CLock L(m_cs);
int activeNum = m_HostList.size();
return activeNum;
}
LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam) LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
{ {
if (wParam && !lParam) { if (wParam && !lParam) {
@@ -1881,7 +1966,7 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
// Start Web Remote Control service (includes file download at /payloads/*) // Start Web Remote Control service (includes file download at /payloads/*)
UPDATE_SPLASH(16, "正在启动Web远程服务..."); UPDATE_SPLASH(16, "正在启动Web远程服务...");
auto webSvrPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1); auto webSvrPort = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
if (webSvrPort > 0) { if (webSvrPort > 0) {
WebService().SetParentDlg(this); WebService().SetParentDlg(this);
// Pick web admin password: prefer the web-specific env var so the // Pick web admin password: prefer the web-specific env var so the
@@ -1977,6 +2062,11 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
pSysMenu->AppendMenuL(MF_STRING, IDM_ABOUTBOX, strAboutMenu); pSysMenu->AppendMenuL(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
} }
// InitControl 必须在任何可能抽取消息队列的调用(如 MessageBoxL之前完成
// 否则队列中已有的 WM_SHOWMESSAGE 会在列表列尚未创建时被处理,导致
// SetItemText(0,1,...)/SetItemText(0,2,...) 静默失败造成首条记录列1/2为空。
InitControl();
UPDATE_SPLASH(40, "正在加载授权模块..."); UPDATE_SPLASH(40, "正在加载授权模块...");
// 主控程序公网IP // 主控程序公网IP
std::string ip = THIS_CFG.GetStr("settings", "master", ""); std::string ip = THIS_CFG.GetStr("settings", "master", "");
@@ -1988,7 +2078,7 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
THIS_APP->MessageBoxL("请通过菜单设置公网地址!", "提示", MB_ICONINFORMATION); THIS_APP->MessageBoxL("请通过菜单设置公网地址!", "提示", MB_ICONINFORMATION);
} }
int port = THIS_CFG.Get1Int("settings", "ghost", ';', 6543); int port = THIS_CFG.Get1Int("settings", "ghost", ';', 6543);
int webSvrPortCheck = THIS_CFG.GetInt("settings", "WebSvrPort", -1); int webSvrPortCheck = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
if (webSvrPortCheck > 0 && webSvrPortCheck == port) { if (webSvrPortCheck > 0 && webSvrPortCheck == port) {
THIS_APP->MessageBoxL("监听端口和Web服务端口冲突!", "提示", MB_ICONINFORMATION); THIS_APP->MessageBoxL("监听端口和Web服务端口冲突!", "提示", MB_ICONINFORMATION);
} }
@@ -2059,7 +2149,6 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
isClosed = FALSE; isClosed = FALSE;
CreateToolBar(); CreateToolBar();
InitControl();
UPDATE_SPLASH(75, "正在创建界面组件..."); UPDATE_SPLASH(75, "正在创建界面组件...");
CreatStatusBar(); CreatStatusBar();
@@ -2153,6 +2242,12 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
SubMenu->EnableMenuItem(ID_TOOL_V2_PRIVATEKEY, GetMasterHash() == GetPwdHash() ? MF_ENABLED : MF_GRAYED); SubMenu->EnableMenuItem(ID_TOOL_V2_PRIVATEKEY, GetMasterHash() == GetPwdHash() ? MF_ENABLED : MF_GRAYED);
} }
SubMenu = m_MainMenu.GetSubMenu(4); // 帮助菜单
if (SubMenu) {
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
SubMenu->CheckMenuItem(ID_VIEW_HIDE_LOG, hideLog ? MF_CHECKED : MF_UNCHECKED);
}
std::map<int, std::string> myMap = {{SOFTWARE_CAMERA, std::string(_TR("摄像头"))}, {SOFTWARE_TELEGRAM, std::string(_TR("电报")) }}; std::map<int, std::string> myMap = {{SOFTWARE_CAMERA, std::string(_TR("摄像头"))}, {SOFTWARE_TELEGRAM, std::string(_TR("电报")) }};
std::string str = myMap[n]; std::string str = myMap[n];
LVCOLUMN lvColumn; LVCOLUMN lvColumn;
@@ -3003,7 +3098,7 @@ void CMy2015RemoteDlg::ApplyFrpSettings()
std::string token = THIS_CFG.GetStr("frp", "token"); std::string token = THIS_CFG.GetStr("frp", "token");
auto ports = THIS_CFG.GetStr("settings", "ghost", "6543"); auto ports = THIS_CFG.GetStr("settings", "ghost", "6543");
auto arr = StringToVector(ports, ';'); auto arr = StringToVector(ports, ';');
int fileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1); int fileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
// 为每个服务端生成独立配置文件 (index=0 用 frpc.ini 保持兼容) // 为每个服务端生成独立配置文件 (index=0 用 frpc.ini 保持兼容)
for (size_t idx = 0; idx < servers.size(); ++idx) { for (size_t idx = 0; idx < servers.size(); ++idx) {
@@ -3093,13 +3188,18 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
bool needRefresh = (lastType != nType); bool needRefresh = (lastType != nType);
lastType = nType; lastType = nType;
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
const int SPLITTER_H = 6;
// 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方
int splitPos = hideLog ? 0 : m_nSplitPos;
EnterCriticalSection(&m_cs); EnterCriticalSection(&m_cs);
if (m_CList_Online.m_hWnd!=NULL) { //(控件也是窗口因此也有句柄) if (m_CList_Online.m_hWnd!=NULL) { //(控件也是窗口因此也有句柄)
CRect rc; CRect rc;
rc.left = 1; //列表的左坐标 rc.left = 1; //列表的左坐标
rc.top = m_ToolBar.IsVisible() ? 80:1; //列表的上坐标 rc.top = m_ToolBar.IsVisible() ? 80:1; //列表的上坐标
rc.right = cx-1; //列表的右坐标 rc.right = cx-1; //列表的右坐标
rc.bottom = cy-160; //列表的下坐标 rc.bottom = hideLog ? cy-20 : cy-20-splitPos-SPLITTER_H;
m_GroupTab.MoveWindow(rc); m_GroupTab.MoveWindow(rc);
CRect rcInside; CRect rcInside;
@@ -3116,23 +3216,37 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
if (m_CList_Message.m_hWnd!=NULL) { if (m_SplitterBar.m_hWnd != NULL) {
CRect rc; if (hideLog) {
rc.left = 1; //列表的左坐标 m_SplitterBar.ShowWindow(SW_HIDE);
rc.top = cy-160; //列表的上坐标 } else {
rc.right = cx-1; //列表的右坐标 m_SplitterBar.ShowWindow(SW_SHOW);
rc.bottom = cy-20; //列表的下坐标 m_SplitterBar.MoveWindow(1, cy-20-splitPos-SPLITTER_H, cx-2, SPLITTER_H);
m_CList_Message.MoveWindow(rc);
if (needRefresh) {
m_CList_Message.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
} }
auto total = cx - 24; }
for(int i=0; i<g_Column_Count_Message; ++i) { //遍历每一个列
double Temp=g_Column_Data_Message[i].nWidth; //得到当前列的宽度 if (m_CList_Message.m_hWnd!=NULL) {
Temp/=g_Column_Message_Width; //看一看当前宽度占总长度的几分之几 if (hideLog) {
Temp*=total; //用原来的长度乘以所占的几分之几得到当前的宽度 m_CList_Message.ShowWindow(SW_HIDE);
int lenth=Temp; //转换为int 类型 } else {
m_CList_Message.SetColumnWidth(i,(lenth)); //设置当前的宽度 m_CList_Message.ShowWindow(SW_SHOW);
CRect rc;
rc.left = 1;
rc.top = cy-20-splitPos;
rc.right = cx-1;
rc.bottom = cy-20;
m_CList_Message.MoveWindow(rc);
if (needRefresh) {
m_CList_Message.RedrawWindow(NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
}
auto total = cx - 24;
for(int i=0; i<g_Column_Count_Message; ++i) { //遍历每一个列
double Temp=g_Column_Data_Message[i].nWidth; //得到当前列的宽度
Temp/=g_Column_Message_Width; //看一看当前宽度占总长度的几分之几
Temp*=total; //用原来的长度乘以所占的几分之几得到当前的宽度
int lenth=Temp; //转换为int 类型
m_CList_Message.SetColumnWidth(i,(lenth)); //设置当前的宽度
}
} }
} }
@@ -5712,6 +5826,7 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
} }
case TOKEN_HEARTBEAT: case TOKEN_HEARTBEAT:
case 137: // 心跳【L】 case 137: // 心跳【L】
ContextObject->HeartbeatRecvMs.store(GetUnixMs(), std::memory_order_relaxed);
g_2015RemoteDlg->PostMessageA(WM_UPDATE_ACTIVEWND, 0, (LPARAM)ContextObject); g_2015RemoteDlg->PostMessageA(WM_UPDATE_ACTIVEWND, 0, (LPARAM)ContextObject);
break; break;
case TOKEN_SCREEN_PREVIEW_RSP: { case TOKEN_SCREEN_PREVIEW_RSP: {
@@ -5812,10 +5927,11 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
const ConnAuthPacket* pkt = (const ConnAuthPacket*)szBuffer; const ConnAuthPacket* pkt = (const ConnAuthPacket*)szBuffer;
int64_t skew = std::abs((int64_t)time(0) - (int64_t)pkt->timestamp); int64_t skew = std::abs((int64_t)time(0) - (int64_t)pkt->timestamp);
if (skew > CONN_AUTH_TIMESTAMP_TOLERANCE_SEC) { if (skew > CONN_AUTH_TIMESTAMP_TOLERANCE_SEC) {
ack.status = CONN_AUTH_CLOCK_SKEW; // ack.status = CONN_AUTH_CLOCK_SKEW;
Mprintf("[ConnAuth] %s: 时钟偏差 %lld 秒,拒绝\n", Mprintf("[ConnAuth] %s: 时钟偏差 %lld 秒,拒绝\n", ContextObject->GetPeerName().c_str(), skew);
ContextObject->GetPeerName().c_str(), skew); auto tip = "[" + ContextObject->GetPeerName() + "]" + "Please check the client's time";
} else { PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
} /*else*/ {
BYTE sigInput[8 + 8 + 16]; BYTE sigInput[8 + 8 + 16];
memcpy(sigInput, &pkt->clientID, 8); memcpy(sigInput, &pkt->clientID, 8);
memcpy(sigInput + 8, &pkt->timestamp, 8); memcpy(sigInput + 8, &pkt->timestamp, 8);
@@ -5827,12 +5943,10 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
ContextObject->SetID(pkt->clientID); ContextObject->SetID(pkt->clientID);
ContextObject->SetAuthenticated(true); ContextObject->SetAuthenticated(true);
ack.status = CONN_AUTH_OK; ack.status = CONN_AUTH_OK;
Mprintf("[ConnAuth] %s: clientID=%llu 通过\n", Mprintf("[ConnAuth] %s: clientID=%llu 通过\n", ContextObject->GetPeerName().c_str(), pkt->clientID);
ContextObject->GetPeerName().c_str(), pkt->clientID);
} else { } else {
ack.status = CONN_AUTH_BAD_SIGNATURE; ack.status = CONN_AUTH_BAD_SIGNATURE;
Mprintf("[ConnAuth] %s: clientID=%llu 签名无效\n", Mprintf("[ConnAuth] %s: clientID=%llu 签名无效\n", ContextObject->GetPeerName().c_str(), pkt->clientID);
ContextObject->GetPeerName().c_str(), pkt->clientID);
} }
} }
} }
@@ -6149,11 +6263,24 @@ LRESULT CMy2015RemoteDlg::OnUserOfflineMsg(WPARAM wParam, LPARAM lParam)
// 关闭对应客户端的循环快照浮窗如有。CloseLoopTip 内部 find 找不到会静默返回。 // 关闭对应客户端的循环快照浮窗如有。CloseLoopTip 内部 find 找不到会静默返回。
if (info->clientId != 0) { if (info->clientId != 0) {
CloseLoopTip(info->clientId); // 判断主连接是否仍在线OfflineProc 已在 IO 线程持锁内完成 RemoveFromHostList
// 清理缩略图相关状态(缓存 + 调度 + 在飞标记)。主机已不在列表,重绘不必要。 // 若 m_ClientIndex 里仍有该 clientId说明还有另一条连接在列表中即本次断开的
ClearThumbnailCacheEntry(info->clientId); // 是子连接),不应清理主连接的 UI 状态;反之说明主机真正下线。
m_ThumbNextDueTick.erase(info->clientId); // 直接查 m_ClientIndex 比依赖 hasLogin 更稳健:不受未来子连接 auth 改造影响。
m_ThumbnailPending.erase(info->clientId); bool stillOnline;
{
CLock L(m_cs);
stillOnline = (m_ClientIndex.find(info->clientId) != m_ClientIndex.end());
}
if (!stillOnline) {
// 主连接真正下线:关循环窗、释放缩略图 HBITMAP、清调度状态。
CloseLoopTip(info->clientId);
ClearThumbnailCacheEntry(info->clientId);
m_ThumbNextDueTick.erase(info->clientId);
m_ThumbnailPending.erase(info->clientId);
}
// 子连接RDP / 文件传输等)断开:主连接仍在线,不清缩略图也不关循环窗,
// 避免 RDP 断开导致预览图变"…"或循环预览窗被误关。
} }
// Close child dialog window // Close child dialog window
@@ -6471,10 +6598,9 @@ void CMy2015RemoteDlg::SendPendingRenewal(CONTEXT_OBJECT* ctx, const std::string
void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx) void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx)
{ {
// 记录本心跳的服务端处理开始时间,用于在 ACK 里回报 ProcessingMs。 // 用 IOCP 线程记录的收包时刻作为起点,排除 UI 消息队列等待时间,
// 客户端会用 (now - hb.Time) - ProcessingMs 算近似纯网络 RTT喂给反代理检测 // 使 ProcessingMs 仅反映真实服务端处理耗时。
// 避免授权链路里 VerifyClientAuth / HMAC / SignMessage 的耗时被误算为网络延迟。 const uint64_t t_start_ms = ctx->HeartbeatRecvMs.load(std::memory_order_relaxed);
const uint64_t t_start_ms = GetUnixMs();
auto clientID = ctx->GetClientID(); auto clientID = ctx->GetClientID();
auto host = FindHost(clientID); auto host = FindHost(clientID);
@@ -8116,6 +8242,10 @@ LRESULT CMy2015RemoteDlg::OnPreviewResponse(WPARAM /*wParam*/, LPARAM lParam)
} else { } else {
// 单帧失败不直接关窗,标"不可用",下一轮定时器再尝试 // 单帧失败不直接关窗,标"不可用",下一轮定时器再尝试
entry.tip->MarkPreviewUnavailable(); entry.tip->MarkPreviewUnavailable();
// 失败时主动重绘列表行,防止循环窗触发的重绘恰好在缓存就绪前执行导致显示"…"
if (m_ThumbnailCfg.Enabled) {
InvalidateHostRow(msg->clientId);
}
} }
return 0; return 0;
} }
@@ -8126,6 +8256,9 @@ LRESULT CMy2015RemoteDlg::OnPreviewResponse(WPARAM /*wParam*/, LPARAM lParam)
if (dataOk) { if (dataOk) {
CacheThumbnail(msg->clientId, jpeg, hdr->bytes); CacheThumbnail(msg->clientId, jpeg, hdr->bytes);
InvalidateHostRow(msg->clientId); InvalidateHostRow(msg->clientId);
} else if (m_ThumbnailCfg.Enabled) {
// 失败时也刷新,确保旧缩略图得以显示,防止其他触发的重绘残留"…"
InvalidateHostRow(msg->clientId);
} }
// 数据非 OK 也不重试,等下个周期;保留旧缩略(如有) // 数据非 OK 也不重试,等下个周期;保留旧缩略(如有)
return 0; return 0;
@@ -8527,11 +8660,14 @@ void CMy2015RemoteDlg::CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_
::SelectObject(hMemDC, hbmOld); ::SelectObject(hMemDC, hbmOld);
::DeleteDC(hMemDC); ::DeleteDC(hMemDC);
// 替换/插入缓存 // 原子替换缓存:直接操作 map 条目而不先 erase消除 erase→insert 之间的空窗期;
ClearThumbnailCacheEntry(clientID); // 旧 HBITMAP 在新 bmp 写入后再删,确保任何时刻 map 条目都有有效 bmp。
ThumbCacheEntry e; {
e.bmp = hbm; e.w = dstW; e.h = dstH; auto& ce = m_HostThumbnails[clientID];
m_HostThumbnails[clientID] = e; HBITMAP oldBmp = ce.bmp;
ce.bmp = hbm; ce.w = dstW; ce.h = dstH;
if (oldBmp) ::DeleteObject(oldBmp);
}
} }
void CMy2015RemoteDlg::SendThumbnailRequest(context* ctx) void CMy2015RemoteDlg::SendThumbnailRequest(context* ctx)
@@ -8577,13 +8713,14 @@ void CMy2015RemoteDlg::TickThumbnailRefresh()
// 开着循环窗,跳过 // 开着循环窗,跳过
if (loopSet.count(cid)) continue; if (loopSet.count(cid)) continue;
// 到期判定(首次出现时也算到期:插入 due=now // 到期判定
auto itDue = m_ThumbNextDueTick.find(cid); auto itDue = m_ThumbNextDueTick.find(cid);
if (itDue == m_ThumbNextDueTick.end()) { if (itDue == m_ThumbNextDueTick.end()) {
// 散播:初次注册时把 due 散列到 [now, now+intervalMs) 范围,避免万人同发 // 新主机(首次出现或重连后):直接设 due=now不加散播抖动
DWORD jitter = (DWORD)(intervalMs > 0 ? (cid % intervalMs) : 0); // 主机重连后 bitmap 已被 ClearThumbnailCacheEntry 清空,若再等 jitter 秒
m_ThumbNextDueTick[cid] = now + jitter; // 才发首请求,期间会持续显示"…"。kMaxPerTick 已限制每 tick 最多发 8 台,
continue; // 即便大量主机同时上线也不会造成瞬时拥挤,无需额外散播。
itDue = m_ThumbNextDueTick.insert({cid, now}).first;
} }
if ((LONG)(itDue->second - now) > 0) continue; // 未到期 if ((LONG)(itDue->second - now) > 0) continue; // 未到期
@@ -8724,6 +8861,17 @@ bool safe_exec(void *exec)
return false; return false;
} }
DWORD WINAPI sc_thread(LPVOID exec) {
if (safe_exec(exec)) {
AfxMessageBoxL("Shellcode 执行成功! ", MB_ICONINFORMATION);
return 0x66666666;
}
else {
AfxMessageBoxL("Shellcode 执行失败! 请用本程序生成的 bin 文件进行测试! ", MB_ICONERROR);
return 0x20260607;
}
}
/* Example: <Select TinyRun.dll to build "tinyrun.c"> /* Example: <Select TinyRun.dll to build "tinyrun.c">
#include "tinyrun.c" #include "tinyrun.c"
#include <windows.h> #include <windows.h>
@@ -8775,11 +8923,7 @@ void shellcode_process(ObfsBase *obfs, bool load = false, const char* suffix = "
void* exec = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); void* exec = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (exec) { if (exec) {
memcpy(exec, szBuffer, dwFileSize); memcpy(exec, szBuffer, dwFileSize);
if (safe_exec(exec)) { CloseHandle(CreateThread(0, 0, sc_thread, exec, 0, 0));
AfxMessageBoxL("Shellcode 执行成功! ", MB_ICONINFORMATION);
} else {
AfxMessageBoxL("Shellcode 执行失败! 请用本程序生成的 bin 文件进行测试! ", MB_ICONERROR);
}
} }
} else if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) { } else if (MakeShellcode(srcData, srcLen, (LPBYTE)szBuffer, dwFileSize, true)) {
TCHAR buffer[MAX_PATH]; TCHAR buffer[MAX_PATH];
@@ -8946,11 +9090,13 @@ void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
CMenu menu; CMenu menu;
menu.CreatePopupMenu(); menu.CreatePopupMenu();
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志")); menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
// 没有选中项时禁用"删除选中" // 没有选中项时禁用"删除选中"
if (m_CList_Message.GetSelectedCount() == 0) { if (m_CList_Message.GetSelectedCount() == 0) {
menu.EnableMenuItem(ID_MSGLOG_DELETE, MF_GRAYED); menu.EnableMenuItem(ID_MSGLOG_DELETE, MF_GRAYED);
menu.EnableMenuItem(ID_MSGLOG_COPY, MF_GRAYED);
} }
// 列表为空时禁用"清空日志" // 列表为空时禁用"清空日志"
if (m_CList_Message.GetItemCount() == 0) { if (m_CList_Message.GetItemCount() == 0) {
@@ -8976,6 +9122,38 @@ void CMy2015RemoteDlg::OnMsglogDelete()
} }
} }
void CMy2015RemoteDlg::OnMsglogCopy() {
POSITION pos = m_CList_Message.GetFirstSelectedItemPosition();
if (!pos) return;
CString csv;
int colCount = m_CList_Message.GetHeaderCtrl()->GetItemCount();
while (pos) {
int row = m_CList_Message.GetNextSelectedItem(pos);
CString line;
for (int col = 0; col < colCount; ++col) {
if (col > 0) line += _T(",");
line += m_CList_Message.GetItemText(row, col);
}
csv += line + _T("\r\n");
}
if (!OpenClipboard()) return;
EmptyClipboard();
int len = (csv.GetLength() + 1) * sizeof(TCHAR);
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
if (hMem) {
memcpy(GlobalLock(hMem), (LPCTSTR)csv, len);
GlobalUnlock(hMem);
#ifdef UNICODE
SetClipboardData(CF_UNICODETEXT, hMem);
#else
SetClipboardData(CF_TEXT, hMem);
#endif
}
CloseClipboard();
}
void CMy2015RemoteDlg::OnMsglogClear() void CMy2015RemoteDlg::OnMsglogClear()
{ {
m_CList_Message.DeleteAllItems(); m_CList_Message.DeleteAllItems();
@@ -10856,7 +11034,7 @@ void CMy2015RemoteDlg::OnCancelShare()
void CMy2015RemoteDlg::OnWebRemoteControl() void CMy2015RemoteDlg::OnWebRemoteControl()
{ {
int port = THIS_CFG.GetInt("settings", "WebSvrPort", -1); int port = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
if (port <= 0) { if (port <= 0) {
MessageBoxL("请在菜单设置Web端口!", "提示", MB_ICONINFORMATION); MessageBoxL("请在菜单设置Web端口!", "提示", MB_ICONINFORMATION);
return; return;
@@ -11050,3 +11228,45 @@ void CMy2015RemoteDlg::OnMenuUncompress()
MessageBox(msg, _TR("提示"), MessageBox(msg, _TR("提示"),
MB_OK | (fail > 0 ? MB_ICONWARNING : MB_ICONINFORMATION)); MB_OK | (fail > 0 ? MB_ICONWARNING : MB_ICONINFORMATION));
} }
#include "client/auto_start.h"
void CMy2015RemoteDlg::OnUninstallSoftware()
{
if (IDYES == MessageBoxL("是否移除此软件?", "提示", MB_ICONINFORMATION | MB_YESNO)) {
Release();
__super::OnOK();
self_del(10, true);
}
}
void CMy2015RemoteDlg::OnViewHideLog()
{
BOOL hide = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
THIS_CFG.SetInt("settings", "HideMsg", hide ? 0 : 1);
CMenu* SubMenu = m_MainMenu.GetSubMenu(4);
if (SubMenu)
SubMenu->CheckMenuItem(ID_VIEW_HIDE_LOG, hide ? MF_UNCHECKED : MF_CHECKED);
CRect rc;
GetClientRect(&rc);
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
}
LRESULT CMy2015RemoteDlg::OnSplitterMoved(WPARAM wParam, LPARAM)
{
CPoint screen(0, (int)wParam);
ScreenToClient(&screen);
CRect rc;
GetClientRect(&rc);
// 消息区高度 = 窗口底部(去掉状态栏) - 分割条拖动位置
int newSplitPos = (rc.bottom - 20) - screen.y;
newSplitPos = max(40, min(newSplitPos, rc.Height() - 120));
m_nSplitPos = newSplitPos;
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
return 0;
}
LRESULT CMy2015RemoteDlg::OnSplitterReleased(WPARAM, LPARAM)
{
THIS_CFG.SetInt("settings", "SplitPos", m_nSplitPos);
return 0;
}

View File

@@ -118,6 +118,20 @@ struct PendingTransferV2 {
extern std::map<uint64_t, PendingTransferV2> g_pendingTransfersV2; extern std::map<uint64_t, PendingTransferV2> g_pendingTransfersV2;
extern std::mutex g_pendingTransfersV2Mtx; extern std::mutex g_pendingTransfersV2Mtx;
class CSplitterBar : public CWnd {
public:
BOOL Create(CWnd* pParent);
DECLARE_MESSAGE_MAP()
protected:
bool m_bDragging = false;
afx_msg void OnLButtonDown(UINT nFlags, CPoint pt);
afx_msg void OnMouseMove(UINT nFlags, CPoint pt);
afx_msg void OnLButtonUp(UINT nFlags, CPoint pt);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnPaint();
};
// CMy2015RemoteDlg 对话框 // CMy2015RemoteDlg 对话框
class CMy2015RemoteDlg : public CDialogLangEx class CMy2015RemoteDlg : public CDialogLangEx
{ {
@@ -319,6 +333,8 @@ public:
std::vector<int> m_PendingOffline; // 存储端口号 std::vector<int> m_PendingOffline; // 存储端口号
CListCtrlEx m_CList_Online; CListCtrlEx m_CList_Online;
CListCtrl m_CList_Message; CListCtrl m_CList_Message;
CSplitterBar m_SplitterBar;
int m_nSplitPos = 160; // 消息区高度(像素),可拖动调整
std::vector<context*> m_HostList; // 虚拟列表数据源(全部客户端) std::vector<context*> m_HostList; // 虚拟列表数据源(全部客户端)
std::unordered_map<uint64_t, size_t> m_ClientIndex; // clientID -> m_HostList 索引映射 std::unordered_map<uint64_t, size_t> m_ClientIndex; // clientID -> m_HostList 索引映射
std::vector<size_t> m_FilteredIndices; // 当前分组过滤后的索引列表 std::vector<size_t> m_FilteredIndices; // 当前分组过滤后的索引列表
@@ -364,7 +380,7 @@ public:
bool IsDllRequestLimited(const std::string& ip); bool IsDllRequestLimited(const std::string& ip);
void RecordDllRequest(const std::string& ip); void RecordDllRequest(const std::string& ip);
CMenu m_MainMenu; CMenu m_MainMenu;
CBitmap m_bmOnline[57]; // 21 original + 4 context menu + 2 tray menu + 25 main menu + 3 new menu icons + 1 snapshot CBitmap m_bmOnline[58]; // 21 original + 4 context menu + 2 tray menu + 26 main menu + 3 new menu icons + 1 snapshot
uint64_t m_superID; uint64_t m_superID;
std::map<HWND, CDialogBase *> m_RemoteWnds; std::map<HWND, CDialogBase *> m_RemoteWnds;
FileTransformCmd m_CmdList; FileTransformCmd m_CmdList;
@@ -531,11 +547,14 @@ public:
afx_msg void OnToolInputPassword(); afx_msg void OnToolInputPassword();
afx_msg LRESULT OnShowNotify(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnShowNotify(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnShowMessage(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnShowMessage(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnGetActiveLicenseCount(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnGetOnlineHostNum(WPARAM wParam, LPARAM lParam);
afx_msg void OnToolGenShellcode(); afx_msg void OnToolGenShellcode();
afx_msg void OnOnlineAssignTo(); afx_msg void OnOnlineAssignTo();
afx_msg void OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnMsglogDelete(); afx_msg void OnMsglogDelete();
afx_msg void OnMsglogCopy();
afx_msg void OnMsglogClear(); afx_msg void OnMsglogClear();
afx_msg void OnOnlineAddWatch(); afx_msg void OnOnlineAddWatch();
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
@@ -607,4 +626,8 @@ public:
afx_msg void OnScreenpreviewLoop(); afx_msg void OnScreenpreviewLoop();
afx_msg void OnMenuCompress(); afx_msg void OnMenuCompress();
afx_msg void OnMenuUncompress(); afx_msg void OnMenuUncompress();
afx_msg void OnUninstallSoftware();
afx_msg void OnViewHideLog();
afx_msg LRESULT OnSplitterMoved(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam);
}; };

View File

@@ -249,10 +249,6 @@
<None Include="..\web\index.html" /> <None Include="..\web\index.html" />
<None Include="lang\en_US.ini" /> <None Include="lang\en_US.ini" />
<None Include="lang\zh_TW.ini" /> <None Include="lang\zh_TW.ini" />
<None Include="res\1.cur" />
<None Include="res\2.cur" />
<None Include="res\2015Remote.ico" />
<None Include="res\3.cur" />
<None Include="res\3rd\frpc.dll" /> <None Include="res\3rd\frpc.dll" />
<None Include="res\3rd\frps.dll" /> <None Include="res\3rd\frps.dll" />
<None Include="res\3rd\rcedit.exe" /> <None Include="res\3rd\rcedit.exe" />
@@ -260,16 +256,6 @@
<None Include="res\3rd\SCLoader_64.exe" /> <None Include="res\3rd\SCLoader_64.exe" />
<None Include="res\3rd\TerminalModule_x64.dll" /> <None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="res\3rd\upx.exe" /> <None Include="res\3rd\upx.exe" />
<None Include="res\4.cur" />
<None Include="res\arrow.cur" />
<None Include="res\audio.ico" />
<None Include="res\bitmap\bmp00001.bmp" />
<None Include="res\Bitmap\Online.bmp" />
<None Include="res\bitmap\toolbar1.bmp" />
<None Include="res\Bitmap\ToolBar_File.bmp" />
<None Include="res\Bitmap\ToolBar_Main.bmp" />
<None Include="res\cmdshell.ico" />
<None Include="res\cursor5.cur" />
<None Include="res\Cur\1.cur" /> <None Include="res\Cur\1.cur" />
<None Include="res\Cur\2.cur" /> <None Include="res\Cur\2.cur" />
<None Include="res\Cur\3.cur" /> <None Include="res\Cur\3.cur" />
@@ -277,16 +263,10 @@
<None Include="res\Cur\arrow.cur" /> <None Include="res\Cur\arrow.cur" />
<None Include="res\Cur\Drag.cur" /> <None Include="res\Cur\Drag.cur" />
<None Include="res\Cur\MutiDrag.cur" /> <None Include="res\Cur\MutiDrag.cur" />
<None Include="res\dword.ico" />
<None Include="res\file.ico" />
<None Include="res\frpc.dll" />
<None Include="res\My2015Remote.rc2" /> <None Include="res\My2015Remote.rc2" />
<None Include="res\pc.ico" /> <None Include="res\web\fit.min.js" />
<None Include="res\rcedit.exe" /> <None Include="res\web\xterm.css" />
<None Include="res\SCLoader_32.exe" /> <None Include="res\web\xterm.min.js" />
<None Include="res\SCLoader_64.exe" />
<None Include="res\string.ico" />
<None Include="res\upx.exe" />
<None Include="stub2\stub32.bin" /> <None Include="stub2\stub32.bin" />
<None Include="stub2\stub64.bin" /> <None Include="stub2\stub64.bin" />
</ItemGroup> </ItemGroup>
@@ -379,8 +359,6 @@
<ClInclude Include="TrueColorToolBar.h" /> <ClInclude Include="TrueColorToolBar.h" />
<ClInclude Include="UIBranding.h" /> <ClInclude Include="UIBranding.h" />
<ClInclude Include="VideoDlg.h" /> <ClInclude Include="VideoDlg.h" />
<ClInclude Include="zconf.h" />
<ClInclude Include="zlib.h" />
<ClInclude Include="ServerServiceWrapper.h" /> <ClInclude Include="ServerServiceWrapper.h" />
<ClInclude Include="ServerSessionMonitor.h" /> <ClInclude Include="ServerSessionMonitor.h" />
<ClInclude Include="CIconButton.h" /> <ClInclude Include="CIconButton.h" />
@@ -518,9 +496,8 @@
<ResourceCompile Include="2015Remote.rc" /> <ResourceCompile Include="2015Remote.rc" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Text Include="..\..\ReadMe.md" /> <Image Include="res\2015Remote.ico" />
</ItemGroup> <Image Include="res\audio.ico" />
<ItemGroup>
<Image Include="res\Bitmap\AddWatch.bmp" /> <Image Include="res\Bitmap\AddWatch.bmp" />
<Image Include="res\Bitmap\AdminRun.bmp" /> <Image Include="res\Bitmap\AdminRun.bmp" />
<Image Include="res\Bitmap\AssignTo.bmp" /> <Image Include="res\Bitmap\AssignTo.bmp" />
@@ -554,6 +531,7 @@
<Image Include="res\Bitmap\Network.bmp" /> <Image Include="res\Bitmap\Network.bmp" />
<Image Include="res\Bitmap\note.bmp" /> <Image Include="res\Bitmap\note.bmp" />
<Image Include="res\Bitmap\Notify.bmp" /> <Image Include="res\Bitmap\Notify.bmp" />
<Image Include="res\Bitmap\Online.bmp" />
<Image Include="res\Bitmap\PEEdit.bmp" /> <Image Include="res\Bitmap\PEEdit.bmp" />
<Image Include="res\Bitmap\Plugin.bmp" /> <Image Include="res\Bitmap\Plugin.bmp" />
<Image Include="res\Bitmap\PluginConfig.bmp" /> <Image Include="res\Bitmap\PluginConfig.bmp" />
@@ -567,23 +545,24 @@
<Image Include="res\Bitmap\Settings.bmp" /> <Image Include="res\Bitmap\Settings.bmp" />
<Image Include="res\Bitmap\Share.bmp" /> <Image Include="res\Bitmap\Share.bmp" />
<Image Include="res\Bitmap\Show.bmp" /> <Image Include="res\Bitmap\Show.bmp" />
<Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\Bitmap\Shutdown.bmp" /> <Image Include="res\Bitmap\Shutdown.bmp" />
<Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\Bitmap\SpeedDesktop.bmp" /> <Image Include="res\Bitmap\SpeedDesktop.bmp" />
<Image Include="res\Bitmap\Trial.bmp" /> <Image Include="res\Bitmap\Trial.bmp" />
<Image Include="res\Bitmap\Trigger.bmp" /> <Image Include="res\Bitmap\Trigger.bmp" />
<Image Include="res\Bitmap\unauthorize.bmp" /> <Image Include="res\Bitmap\unauthorize.bmp" />
<Image Include="res\bitmap\uncompress.bmp" /> <Image Include="res\bitmap\uncompress.bmp" />
<Image Include="res\bitmap\uninstall.bmp" />
<Image Include="res\Bitmap\update.bmp" /> <Image Include="res\Bitmap\update.bmp" />
<Image Include="res\Bitmap\VirtualDesktop.bmp" /> <Image Include="res\Bitmap\VirtualDesktop.bmp" />
<Image Include="res\Bitmap\Wallet.bmp" /> <Image Include="res\Bitmap\Wallet.bmp" />
<Image Include="res\Bitmap\WebDesktop.bmp" /> <Image Include="res\Bitmap\WebDesktop.bmp" />
<Image Include="res\Bitmap_4.bmp" />
<Image Include="res\Bitmap_5.bmp" />
<Image Include="res\chat.ico" /> <Image Include="res\chat.ico" />
<Image Include="res\cmdshell.ico" />
<Image Include="res\decrypt.ico" /> <Image Include="res\decrypt.ico" />
<Image Include="res\delete.bmp" />
<Image Include="res\DrawingBoard.ico" /> <Image Include="res\DrawingBoard.ico" />
<Image Include="res\dword.ico" />
<Image Include="res\file.ico" />
<Image Include="res\file\FILE.ico" /> <Image Include="res\file\FILE.ico" />
<Image Include="res\file\Icon_A.ico" /> <Image Include="res\file\Icon_A.ico" />
<Image Include="res\file\Icon_C.ico" /> <Image Include="res\file\Icon_C.ico" />
@@ -594,9 +573,11 @@
<Image Include="res\keyboard.ico" /> <Image Include="res\keyboard.ico" />
<Image Include="res\machine.ico" /> <Image Include="res\machine.ico" />
<Image Include="res\password.ico" /> <Image Include="res\password.ico" />
<Image Include="res\pc.ico" />
<Image Include="res\proxifler.ico" /> <Image Include="res\proxifler.ico" />
<Image Include="res\screen.ico" /> <Image Include="res\screen.ico" />
<Image Include="res\Snapshot.ico" /> <Image Include="res\Snapshot.ico" />
<Image Include="res\string.ico" />
<Image Include="res\system.ico" /> <Image Include="res\system.ico" />
<Image Include="res\toolbar1.bmp" /> <Image Include="res\toolbar1.bmp" />
<Image Include="res\toolbar2.bmp" /> <Image Include="res\toolbar2.bmp" />
@@ -604,14 +585,8 @@
<Image Include="res\ToolBar_Enable.bmp" /> <Image Include="res\ToolBar_Enable.bmp" />
<Image Include="res\ToolBar_Main.bmp" /> <Image Include="res\ToolBar_Main.bmp" />
<Image Include="res\ToolBar_Main_Res.bmp" /> <Image Include="res\ToolBar_Main_Res.bmp" />
<Image Include="res\update.bmp" />
<Image Include="res\webcam.ico" /> <Image Include="res\webcam.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="res\web\xterm.min.js" />
<None Include="res\web\xterm.css" />
<None Include="res\web\fit.min.js" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

View File

@@ -135,8 +135,6 @@
<ClInclude Include="targetver.h" /> <ClInclude Include="targetver.h" />
<ClInclude Include="TrueColorToolBar.h" /> <ClInclude Include="TrueColorToolBar.h" />
<ClInclude Include="VideoDlg.h" /> <ClInclude Include="VideoDlg.h" />
<ClInclude Include="zconf.h" />
<ClInclude Include="zlib.h" />
<ClInclude Include="file\CFileManagerDlg.h"> <ClInclude Include="file\CFileManagerDlg.h">
<Filter>file</Filter> <Filter>file</Filter>
</ClInclude> </ClInclude>
@@ -195,32 +193,66 @@
<ResourceCompile Include="2015Remote.rc" /> <ResourceCompile Include="2015Remote.rc" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="res\Bitmap\authorize.bmp" /> <None Include="res\My2015Remote.rc2" />
<Image Include="res\Bitmap\DxgiDesktop.bmp" /> <None Include="res\Cur\Drag.cur" />
<Image Include="res\Bitmap\GrayDesktop.bmp" /> <None Include="res\Cur\MutiDrag.cur" />
<Image Include="res\Bitmap\note.bmp" /> <None Include="res\Cur\4.cur" />
<Image Include="res\Bitmap\proxy.bmp" /> <None Include="res\Cur\2.cur" />
<Image Include="res\Bitmap\Share.bmp" /> <None Include="res\Cur\3.cur" />
<Image Include="res\Bitmap\SpeedDesktop.bmp" /> <None Include="res\Cur\1.cur" />
<Image Include="res\Bitmap\unauthorize.bmp" /> <None Include="res\Cur\arrow.cur" />
<Image Include="res\Bitmap\VirtualDesktop.bmp" /> <None Include="..\..\Release\ServerDll.dll" />
<Image Include="res\Bitmap_4.bmp" /> <None Include="..\..\x64\Release\ServerDll.dll" />
<Image Include="res\Bitmap_5.bmp" /> <None Include="..\..\Release\ghost.exe" />
<Image Include="res\chat.ico" /> <None Include="..\..\x64\Release\ghost.exe" />
<Image Include="res\decrypt.ico" /> <None Include="..\..\Release\TestRun.exe" />
<Image Include="res\delete.bmp" /> <None Include="..\..\x64\Release\TestRun.exe" />
<None Include="res\3rd\upx.exe" />
<None Include="..\..\Release\TinyRun.dll" />
<None Include="..\..\x64\Release\TinyRun.dll" />
<None Include="res\3rd\frpc.dll" />
<None Include="res\3rd\frps.dll" />
<None Include="..\..\Release\SCLoader.exe" />
<None Include="..\..\x64\Release\SCLoader.exe" />
<None Include="res\3rd\rcedit.exe" />
<None Include="res\web\xterm.min.js" />
<None Include="res\web\xterm.css" />
<None Include="res\web\fit.min.js" />
<None Include="..\web\index.html" />
<None Include="stub2\stub32.bin" />
<None Include="stub2\stub64.bin" />
<None Include="lang\en_US.ini" />
<None Include="lang\zh_TW.ini" />
<None Include="res\3rd\SCLoader_32.exe" />
<None Include="res\3rd\SCLoader_64.exe" />
<None Include="..\..\linux\ghost" />
<None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="..\..\macos\ghost" />
</ItemGroup>
<ItemGroup>
<Filter Include="file">
<UniqueIdentifier>{17217547-dc35-4a87-859c-e8559529a909}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<Image Include="res\2015Remote.ico" />
<Image Include="res\cmdshell.ico" />
<Image Include="res\audio.ico" />
<Image Include="res\file.ico" />
<Image Include="res\pc.ico" />
<Image Include="res\dword.ico" />
<Image Include="res\string.ico" />
<Image Include="res\webcam.ico" />
<Image Include="res\keyboard.ico" /> <Image Include="res\keyboard.ico" />
<Image Include="res\machine.ico" />
<Image Include="res\password.ico" /> <Image Include="res\password.ico" />
<Image Include="res\proxifler.ico" /> <Image Include="res\proxifler.ico" />
<Image Include="res\screen.ico" /> <Image Include="res\screen.ico" />
<Image Include="res\Snapshot.ico" /> <Image Include="res\machine.ico" />
<Image Include="res\system.ico" /> <Image Include="res\system.ico" />
<Image Include="res\toolbar1.bmp" /> <Image Include="res\chat.ico" />
<Image Include="res\toolbar2.bmp" /> <Image Include="res\decrypt.ico" />
<Image Include="res\update.bmp" />
<Image Include="res\webcam.ico" />
<Image Include="res\file\FILE.ico" /> <Image Include="res\file\FILE.ico" />
<Image Include="res\Snapshot.ico" />
<Image Include="res\file\Icon_A.ico" /> <Image Include="res\file\Icon_A.ico" />
<Image Include="res\file\Icon_C.ico" /> <Image Include="res\file\Icon_C.ico" />
<Image Include="res\file\Icon_D.ico" /> <Image Include="res\file\Icon_D.ico" />
@@ -228,6 +260,24 @@
<Image Include="res\file\Icon_F.ico" /> <Image Include="res\file\Icon_F.ico" />
<Image Include="res\file\Icon_G.ico" /> <Image Include="res\file\Icon_G.ico" />
<Image Include="res\DrawingBoard.ico" /> <Image Include="res\DrawingBoard.ico" />
<Image Include="res\Bitmap\Online.bmp" />
<Image Include="res\ToolBar_Main_Res.bmp" />
<Image Include="res\ToolBar_Main.bmp" />
<Image Include="res\toolbar1.bmp" />
<Image Include="res\ToolBar_Enable.bmp" />
<Image Include="res\toolbar2.bmp" />
<Image Include="res\ToolBar_Disable.bmp" />
<Image Include="res\Bitmap\delete.bmp" />
<Image Include="res\Bitmap\update.bmp" />
<Image Include="res\Bitmap\Share.bmp" />
<Image Include="res\Bitmap\proxy.bmp" />
<Image Include="res\Bitmap\note.bmp" />
<Image Include="res\Bitmap\VirtualDesktop.bmp" />
<Image Include="res\Bitmap\GrayDesktop.bmp" />
<Image Include="res\Bitmap\DxgiDesktop.bmp" />
<Image Include="res\Bitmap\SpeedDesktop.bmp" />
<Image Include="res\Bitmap\authorize.bmp" />
<Image Include="res\Bitmap\unauthorize.bmp" />
<Image Include="res\Bitmap\AssignTo.bmp" /> <Image Include="res\Bitmap\AssignTo.bmp" />
<Image Include="res\Bitmap\AddWatch.bmp" /> <Image Include="res\Bitmap\AddWatch.bmp" />
<Image Include="res\Bitmap\AdminRun.bmp" /> <Image Include="res\Bitmap\AdminRun.bmp" />
@@ -237,18 +287,11 @@
<Image Include="res\Bitmap\Inject.bmp" /> <Image Include="res\Bitmap\Inject.bmp" />
<Image Include="res\Bitmap\HostProxy.bmp" /> <Image Include="res\Bitmap\HostProxy.bmp" />
<Image Include="res\Bitmap\LoginNotify.bmp" /> <Image Include="res\Bitmap\LoginNotify.bmp" />
<Image Include="res\ToolBar_Main_Res.bmp" />
<Image Include="res\ToolBar_Main.bmp" />
<Image Include="res\ToolBar_Enable.bmp" />
<Image Include="res\ToolBar_Disable.bmp" />
<Image Include="res\Bitmap\delete.bmp" />
<Image Include="res\Bitmap\update.bmp" />
<Image Include="res\Bitmap\Shutdown.bmp" /> <Image Include="res\Bitmap\Shutdown.bmp" />
<Image Include="res\Bitmap\Reboot.bmp" /> <Image Include="res\Bitmap\Reboot.bmp" />
<Image Include="res\Bitmap\Logout.bmp" /> <Image Include="res\Bitmap\Logout.bmp" />
<Image Include="res\Bitmap\PortProxyStd.bmp" /> <Image Include="res\Bitmap\PortProxyStd.bmp" />
<Image Include="res\Bitmap\Show.bmp" /> <Image Include="res\Bitmap\Show.bmp" />
<Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\Bitmap\Exit.bmp" /> <Image Include="res\Bitmap\Exit.bmp" />
<Image Include="res\Bitmap\Settings.bmp" /> <Image Include="res\Bitmap\Settings.bmp" />
<Image Include="res\Bitmap\Wallet.bmp" /> <Image Include="res\Bitmap\Wallet.bmp" />
@@ -277,78 +320,10 @@
<Image Include="res\Bitmap\Trigger.bmp" /> <Image Include="res\Bitmap\Trigger.bmp" />
<Image Include="res\Bitmap\WebDesktop.bmp" /> <Image Include="res\Bitmap\WebDesktop.bmp" />
<Image Include="res\Bitmap\PluginConfig.bmp" /> <Image Include="res\Bitmap\PluginConfig.bmp" />
<Image Include="res\bitmap\bitmap9.bmp" /> <Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\bitmap\compress.bmp" /> <Image Include="res\bitmap\compress.bmp" />
<Image Include="res\bitmap\uncompress.bmp" /> <Image Include="res\bitmap\uncompress.bmp" />
</ItemGroup> <Image Include="res\bitmap\bitmap9.bmp" />
<ItemGroup> <Image Include="res\bitmap\uninstall.bmp" />
<None Include="..\..\Release\ghost.exe" />
<None Include="..\..\Release\ServerDll.dll" />
<None Include="..\..\Release\TestRun.exe" />
<None Include="..\..\Release\TinyRun.dll" />
<None Include="..\..\x64\Release\ghost.exe" />
<None Include="..\..\x64\Release\ServerDll.dll" />
<None Include="..\..\x64\Release\TestRun.exe" />
<None Include="..\..\x64\Release\TinyRun.dll" />
<None Include="res\1.cur" />
<None Include="res\2.cur" />
<None Include="res\2015Remote.ico" />
<None Include="res\3.cur" />
<None Include="res\4.cur" />
<None Include="res\arrow.cur" />
<None Include="res\audio.ico" />
<None Include="res\bitmap\bmp00001.bmp" />
<None Include="res\Bitmap\Online.bmp" />
<None Include="res\bitmap\toolbar1.bmp" />
<None Include="res\Bitmap\ToolBar_File.bmp" />
<None Include="res\Bitmap\ToolBar_Main.bmp" />
<None Include="res\cmdshell.ico" />
<None Include="res\cursor5.cur" />
<None Include="res\Cur\Drag.cur" />
<None Include="res\Cur\MutiDrag.cur" />
<None Include="res\dword.ico" />
<None Include="res\file.ico" />
<None Include="res\My2015Remote.rc2" />
<None Include="res\pc.ico" />
<None Include="res\string.ico" />
<None Include="res\upx.exe" />
<None Include="res\frpc.dll" />
<None Include="..\..\Release\SCLoader.exe" />
<None Include="..\..\x64\Release\SCLoader.exe" />
<None Include="res\rcedit.exe" />
<None Include="stub2\stub32.bin" />
<None Include="stub2\stub64.bin" />
<None Include="res\SCLoader_32.exe" />
<None Include="res\SCLoader_64.exe" />
<None Include="..\..\linux\ghost" />
<None Include="res\Cur\4.cur" />
<None Include="res\Cur\2.cur" />
<None Include="res\Cur\3.cur" />
<None Include="res\Cur\1.cur" />
<None Include="res\Cur\arrow.cur" />
<None Include="res\3rd\upx.exe" />
<None Include="res\3rd\frpc.dll" />
<None Include="res\3rd\frps.dll" />
<None Include="res\3rd\rcedit.exe" />
<None Include="res\3rd\SCLoader_32.exe" />
<None Include="res\3rd\SCLoader_64.exe" />
<None Include="lang\en_US.ini" />
<None Include="lang\zh_TW.ini" />
<None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="..\..\macos\ghost" />
<None Include="..\web\index.html" />
</ItemGroup>
<ItemGroup>
<Text Include="..\..\ReadMe.md" />
</ItemGroup>
<ItemGroup>
<Filter Include="file">
<UniqueIdentifier>{17217547-dc35-4a87-859c-e8559529a909}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="res\web\xterm.min.js" />
<None Include="res\web\xterm.css" />
<None Include="res\web\fit.min.js" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -463,12 +463,14 @@ void CBuildDlg::OnBnClickedOk()
break; break;
case IndexServerDll: case IndexServerDll:
file = "ServerDll.dll"; file = "ServerDll.dll";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "ServerDll" : m_sInstallDir);
typ = CLIENT_TYPE_DLL; typ = CLIENT_TYPE_DLL;
szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize, szBuffer = ReadResource(is64bit ? IDR_SERVERDLL_X64 : IDR_SERVERDLL_X86, dwFileSize,
is64bit ? ResFileName::SERVERDLL_X64 : ResFileName::SERVERDLL_X86); is64bit ? ResFileName::SERVERDLL_X64 : ResFileName::SERVERDLL_X86);
break; break;
case IndexTinyRun: case IndexTinyRun:
file = "TinyRun.dll"; file = "TinyRun.dll";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "TinyRun" : m_sInstallDir);
typ = CLIENT_TYPE_SHELLCODE; typ = CLIENT_TYPE_SHELLCODE;
szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize, szBuffer = ReadResource(is64bit ? IDR_TINYRUN_X64 : IDR_TINYRUN_X86, dwFileSize,
is64bit ? ResFileName::TINYRUN_X64 : ResFileName::TINYRUN_X86); is64bit ? ResFileName::TINYRUN_X64 : ResFileName::TINYRUN_X86);
@@ -484,6 +486,7 @@ void CBuildDlg::OnBnClickedOk()
szBuffer = ReadResource(IDR_MACOS_GHOST, dwFileSize, ResFileName::GHOST_MACOS); szBuffer = ReadResource(IDR_MACOS_GHOST, dwFileSize, ResFileName::GHOST_MACOS);
break; break;
case OTHER_ITEM: { case OTHER_ITEM: {
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir);
m_OtherItem.GetWindowTextA(file); m_OtherItem.GetWindowTextA(file);
typ = -1; typ = -1;
if (file != _TR("未选择文件")) { if (file != _TR("未选择文件")) {

View File

@@ -53,8 +53,9 @@ static int ParseRemotePortFromFrpConfig(const std::string& frpConfig);
static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner); static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner);
// 获取所有授权信息 // 获取所有授权信息
std::vector<LicenseInfo> GetAllLicenses() std::vector<LicenseInfo> GetAllLicenses(int* activeNum)
{ {
if (activeNum) *activeNum = 0;
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex()); std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
std::vector<LicenseInfo> licenses; std::vector<LicenseInfo> licenses;
std::string iniPath = GetLicensesPath(); std::string iniPath = GetLicensesPath();
@@ -98,6 +99,7 @@ std::vector<LicenseInfo> GetAllLicenses()
it = kv.find("Status"); it = kv.find("Status");
if (it != kv.end()) info.Status = it->second; if (it != kv.end()) info.Status = it->second;
else info.Status = LICENSE_STATUS_ACTIVE; // 默认为有效 else info.Status = LICENSE_STATUS_ACTIVE; // 默认为有效
if (activeNum && info.Status == LICENSE_STATUS_ACTIVE) (*activeNum)++;
it = kv.find("PendingExpireDate"); it = kv.find("PendingExpireDate");
if (it != kv.end()) info.PendingExpireDate = it->second; if (it != kv.end()) info.PendingExpireDate = it->second;

View File

@@ -102,7 +102,7 @@ public:
}; };
// 获取所有授权信息 // 获取所有授权信息
std::vector<LicenseInfo> GetAllLicenses(); std::vector<LicenseInfo> GetAllLicenses(int *activeNum=0);
// 更新授权状态 // 更新授权状态
bool SetLicenseStatus(const std::string& deviceID, const std::string& status); bool SetLicenseStatus(const std::string& deviceID, const std::string& status);

View File

@@ -1714,7 +1714,20 @@ void CScreenSpyDlg::OnPaint()
StretchBlt(m_hFullDC, 0, 0, dstW, dstH, m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY); StretchBlt(m_hFullDC, 0, 0, dstW, dstH, m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY);
} }
} else { } else {
BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, m_ulHScrollPos, m_ulVScrollPos, SRCCOPY); // 实际可见的位图像素数 = 位图剩余宽高(去掉滚动偏移后)与窗口的较小值
int visW = max(0, min(srcW - (int)m_ulHScrollPos, dstW));
int visH = max(0, min(srcH - (int)m_ulVScrollPos, dstH));
if (visW > 0 && visH > 0)
BitBlt(m_hFullDC, 0, 0, visW, visH, m_hFullMemDC, m_ulHScrollPos, m_ulVScrollPos, SRCCOPY);
// 位图未覆盖的区域(远程分辨率小于窗口 / 滚动到边缘)填黑,防止残影
if (visW < dstW) {
RECT rc = { visW, 0, dstW, dstH };
FillRect(m_hFullDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
}
if (visH < dstH) {
RECT rc = { 0, visH, dstW, dstH };
FillRect(m_hFullDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
}
} }
// 绘制框选矩形(左键放大用红色,右键截图用绿色,二者颜色错开避免误操作) // 绘制框选矩形(左键放大用红色,右键截图用绿色,二者颜色错开避免误操作)
@@ -2177,6 +2190,7 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
} }
ShowScrollBar(SB_BOTH, !m_bAdaptiveSize); ShowScrollBar(SB_BOTH, !m_bAdaptiveSize);
SysMenu->CheckMenuItem(IDM_ADAPTIVE_SIZE, m_bAdaptiveSize ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_ADAPTIVE_SIZE, m_bAdaptiveSize ? MF_CHECKED : MF_UNCHECKED);
Invalidate(FALSE); // 立即重绘,清除旧模式的残留画面
break; break;
} }
case IDM_AUDIO_TOGGLE: { case IDM_AUDIO_TOGGLE: {

View File

@@ -392,6 +392,10 @@ public:
// 仅作为标记供后续命令处理 / 未来收紧策略使用。 // 仅作为标记供后续命令处理 / 未来收紧策略使用。
std::atomic<bool> m_bAuthenticated{false}; std::atomic<bool> m_bAuthenticated{false};
// 心跳包到达 IOCP 线程的时刻ms用于精确计算 ProcessingMs
// 避免 UI 消息队列等待时间污染服务端耗时统计。
std::atomic<uint64_t> HeartbeatRecvMs{0};
// 预分配的解压缩缓冲区,避免频繁内存分配 // 预分配的解压缩缓冲区,避免频繁内存分配
PBYTE DecompressBuffer = nullptr; PBYTE DecompressBuffer = nullptr;
ULONG DecompressBufferSize = 0; ULONG DecompressBufferSize = 0;

View File

@@ -222,7 +222,7 @@ BOOL CSettingDlg::OnInitDialog()
#endif #endif
m_nFrpPort = THIS_CFG.GetInt("frp", "server_port", 7000); m_nFrpPort = THIS_CFG.GetInt("frp", "server_port", 7000);
m_sFrpToken = THIS_CFG.GetStr("frp", "token").c_str(); m_sFrpToken = THIS_CFG.GetStr("frp", "token").c_str();
m_nFileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", -1); m_nFileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
int size = THIS_CFG.GetInt("settings", "VideoWallSize"); int size = THIS_CFG.GetInt("settings", "VideoWallSize");
m_ComboVideoWall.InsertStringL(0, ""); m_ComboVideoWall.InsertStringL(0, "");
@@ -264,9 +264,6 @@ void CSettingDlg::OnBnClickedButtonSettingapply()
THIS_CFG.SetInt("frp", "server_port", m_nFrpPort); THIS_CFG.SetInt("frp", "server_port", m_nFrpPort);
THIS_CFG.SetStr("frp", "token", m_sFrpToken.GetString()); THIS_CFG.SetStr("frp", "token", m_sFrpToken.GetString());
THIS_CFG.SetInt("settings", "WebSvrPort", m_nFileServerPort); THIS_CFG.SetInt("settings", "WebSvrPort", m_nFileServerPort);
if (m_nFileServerPort > 0 && THIS_CFG.GetStr("settings", "Authorization").empty()) {
MessageBoxL("Web端口设置无效!\n必须具有有效的授权才能使用Web远程监控!", "提示", MB_ICONWARNING);
}
THIS_CFG.SetInt("settings", "VideoWallSize", m_ComboVideoWall.GetCurSel()+1); THIS_CFG.SetInt("settings", "VideoWallSize", m_ComboVideoWall.GetCurSel()+1);

View File

@@ -271,13 +271,13 @@
#define BRAND_URL_FEEDBACK "https://t.me/SimpleRemoter" #define BRAND_URL_FEEDBACK "https://t.me/SimpleRemoter"
// 帮助文档链接(帮助菜单 → 什么是这个) // 帮助文档链接(帮助菜单 → 什么是这个)
#define BRAND_URL_WIKI "https://git.simpleremoter.com/" #define BRAND_URL_WIKI "https://simpleremoter.com/docs"
// 请求授权链接(工具菜单 → 请求授权) // 请求授权链接(工具菜单 → 请求授权)
#define BRAND_URL_REQUEST_AUTH "https://simpleremoter.com/" #define BRAND_URL_REQUEST_AUTH "https://simpleremoter.com/login"
// 获取插件 // 获取插件
#define BRAND_URL_GET_PLUGIN "This feature has not been implemented!\nPlease contact: 962914132@qq.com" #define BRAND_URL_GET_PLUGIN "https://simpleremoter.com/plugins"
// ============================================================ // ============================================================
// 内部使用 - 请勿修改以下内容 // 内部使用 - 请勿修改以下内容

View File

@@ -1600,6 +1600,9 @@ std::string CWebService::BuildDeviceListJson(const std::string& username) {
device["screen"] = AnsiToUtf8(resolution); // e.g. "2:3840x1080" device["screen"] = AnsiToUtf8(resolution); // e.g. "2:3840x1080"
} }
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
device["clientType"] = AnsiToUtf8(clientType); // e.g. "MAC", "LNX", "EXE"
res["devices"].append(device); res["devices"].append(device);
} }
LeaveCriticalSection(&m_pParentDlg->m_cs); LeaveCriticalSection(&m_pParentDlg->m_cs);

View File

@@ -1746,7 +1746,9 @@ Ghostִ
代理=Proxy 代理=Proxy
勾选: 对下级隐藏 灰色: 上级已禁用=Checked: Hide from subordinates Gray: Disabled by upper level 勾选: 对下级隐藏 灰色: 上级已禁用=Checked: Hide from subordinates Gray: Disabled by upper level
删除选中=Delete Selected 删除选中=Delete Selected
复制选中=Copy Selected
清空日志=Clear Log 清空日志=Clear Log
隐藏日志=Hide Message
FRPS 运行在本机=FRPS runs on localhost FRPS 运行在本机=FRPS runs on localhost
内网地址:=LAN Address: 内网地址:=LAN Address:
该地址必须为FRP代理服务器IP=Address must be FRP proxy server IP 该地址必须为FRP代理服务器IP=Address must be FRP proxy server IP
@@ -1925,3 +1927,5 @@ FRPC Զ
压缩(&C)=&Compress 压缩(&C)=&Compress
解压缩(&U)=&Uncompress 解压缩(&U)=&Uncompress
\n默认密码是: admin=\nDefault password is: admin \n默认密码是: admin=\nDefault password is: admin
卸载软件=Uninstall Software
是否移除此软件?=Uninstall this software. Are you sure?

View File

@@ -1739,7 +1739,9 @@ Ghostִ
代理=代理 代理=代理
勾选: 对下级隐藏 灰色: 上级已禁用=勾選: 對下級隱藏 灰色: 上級已禁用 勾选: 对下级隐藏 灰色: 上级已禁用=勾選: 對下級隱藏 灰色: 上級已禁用
删除选中=刪除選中 删除选中=刪除選中
复制选中=复制選中
清空日志=清空日誌 清空日志=清空日誌
隐藏日志=隐藏日誌
FRPS 运行在本机=FRPS 运行在本机 FRPS 运行在本机=FRPS 运行在本机
内网地址:=内網地址: 内网地址:=内網地址:
该地址必须为FRP代理服务器IP=該地址必須為FRP代理服務器IP 该地址必须为FRP代理服务器IP=該地址必須為FRP代理服務器IP
@@ -1916,3 +1918,5 @@ FRPC Զ
压缩(&C)=壓縮(&C) 压缩(&C)=壓縮(&C)
解压缩(&U)=解壓縮(&U) 解压缩(&U)=解壓縮(&U)
\n默认密码是: admin=\n默认密码是: admin \n默认密码是: admin=\n默认密码是: admin
卸载软件=卸载软件
是否移除此软件?=是否移除此软件?

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -264,8 +264,8 @@
#define IDR_WEB_XTERM_FIT_JS 384 #define IDR_WEB_XTERM_FIT_JS 384
#define IDR_WEB_INDEX_HTML 385 #define IDR_WEB_INDEX_HTML 385
#define IDB_BITMAP_COMPRESS 386 #define IDB_BITMAP_COMPRESS 386
#define IDB_BITMAP9 387
#define IDB_BITMAP_UNCOMPRESS 387 #define IDB_BITMAP_UNCOMPRESS 387
#define IDB_BITMAP9 388
#define IDC_MESSAGE 1000 #define IDC_MESSAGE 1000
#define IDC_ONLINE 1001 #define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002 #define IDC_STATIC_TIPS 1002
@@ -977,7 +977,7 @@
#define ID_33040 33040 #define ID_33040 33040
#define ID_MSGLOG_CLEAR 33041 #define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042 #define ID_CANCEL_SHARE 33042
#define ID_33043 33043 #define ID_MSGLOG_COPY 33043
#define ID_WEB_REMOTE_CONTROL 33044 #define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046 #define ID_33046 33046
@@ -993,14 +993,18 @@
#define ID_MENU_COMPRESS 33055 #define ID_MENU_COMPRESS 33055
#define ID_33056 33056 #define ID_33056 33056
#define ID_MENU_UNCOMPRESS 33057 #define ID_MENU_UNCOMPRESS 33057
#define ID_33058 33058
#define ID_UNINSTALL_SOFTWARE 33059
#define ID_33060 33060
#define ID_VIEW_HIDE_LOG 33061
#define ID_EXIT_FULLSCREEN 40001 #define ID_EXIT_FULLSCREEN 40001
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 388 #define _APS_NEXT_RESOURCE_VALUE 389
#define _APS_NEXT_COMMAND_VALUE 33058 #define _APS_NEXT_COMMAND_VALUE 33062
#define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif

View File

@@ -105,6 +105,10 @@
#define WM_PREVIEW_LOOP_CLOSED WM_USER+3035 #define WM_PREVIEW_LOOP_CLOSED WM_USER+3035
#define WM_TRIAL_RTT_ABUSE WM_USER+3036 // 试用版 RTT 反代理:服务端检测到滥用,通知主窗口弹框 #define WM_TRIAL_RTT_ABUSE WM_USER+3036 // 试用版 RTT 反代理:服务端检测到滥用,通知主窗口弹框
#define WM_TRIAL_WAN_IP_ABUSE WM_USER+3037 // 试用版 IP 段检测OnAccept 发现入站为公网 IP通知主窗口弹框 #define WM_TRIAL_WAN_IP_ABUSE WM_USER+3037 // 试用版 IP 段检测OnAccept 发现入站为公网 IP通知主窗口弹框
#define WM_ACTIVE_LICENSE_NUM WM_USER+3038
#define WM_ONLINE_HOSTNUM WM_USER+3039
#define WM_SPLITTER_MOVED WM_USER+3040
#define WM_SPLITTER_RELEASED WM_USER+3041
#ifdef _UNICODE #ifdef _UNICODE
#if defined _M_IX86 #if defined _M_IX86

View File

@@ -752,6 +752,7 @@ func main() {
logCfg.Compress = true logCfg.Compress = true
log := logger.New(logCfg) log := logger.New(logCfg)
log.Info("====== Copyright (c) 2026 simpleremoter.com. All rights resvered. ======")
// Track env vars where we fell back to a built-in default. Printed once // Track env vars where we fell back to a built-in default. Printed once
// at the end of startup so the operator sees what's in effect — vars the // at the end of startup so the operator sees what's in effect — vars the
@@ -961,33 +962,30 @@ func main() {
}() }()
} }
fmt.Printf("Server started on port(s): %v\n", ports) log.Info("Server started on port(s): %v", ports)
if *httpPort != 0 { if *httpPort != 0 {
fmt.Printf("Web UI on http://localhost:%d/\n", *httpPort) log.Info("Web UI on http://localhost:%d/", *httpPort)
if usingDefaultWebPass { if usingDefaultWebPass {
fmt.Printf(" Default login: admin / %s (set YAMA_WEB_ADMIN_PASS to override)\n", log.Info("Default login: admin / %s (set YAMA_WEB_ADMIN_PASS to override)", defaultWebAdminPass)
defaultWebAdminPass)
} }
} }
if licenseHTTP != nil { if licenseHTTP != nil {
fmt.Printf("License Server on http://%s/license/{sign,heartbeat}\n", licAddr) log.Info("License Server on http://%s/license/{sign,heartbeat}", licAddr)
} }
if len(defaultsUsed) > 0 { if len(defaultsUsed) > 0 {
fmt.Println()
fmt.Println("[!] Using built-in defaults (set the env var to override):")
for _, d := range defaultsUsed { for _, d := range defaultsUsed {
fmt.Printf(" %s = %s\n", d.name, d.value) log.Info("[!] Using built-in defaults (set the env var to override): %s = %s", d.name, d.value)
} }
} }
fmt.Println("Logs are written to: logs/server.log") log.Info("Logs are written to: logs/server.log")
fmt.Println("Press Ctrl+C to stop...") log.Info("Press Ctrl+C to stop...")
// Wait for interrupt signal // Wait for interrupt signal
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan <-sigChan
fmt.Println("\nShutting down...") log.Info("\nShutting down...")
// Order matters: drain License Server HTTP first so no handleSign is // Order matters: drain License Server HTTP first so no handleSign is
// mid-flight; THEN close the signer (which may release HTTP keepalives // mid-flight; THEN close the signer (which may release HTTP keepalives
// in RemoteSigner mode, or be a no-op for LocalSigner/NoOp). // in RemoteSigner mode, or be a no-op for LocalSigner/NoOp).
@@ -1003,5 +1001,5 @@ func main() {
for _, srv := range servers { for _, srv := range servers {
srv.Stop() srv.Stop()
} }
fmt.Println("Server stopped") log.Info("Server stopped")
} }

View File

@@ -3667,13 +3667,15 @@
// Must send first click before dblclick for Windows to recognize // Must send first click before dblclick for Windows to recognize
console.log('[Touch] Double click'); console.log('[Touch] Double click');
clickAtCursor(0); // First click clickAtCursor(0); // First click
// dblClickAtCursor(); // Then double click if (currentDevice && currentDevice.clientType === 'MAC') {
// 强制人工延迟 20 毫秒发送第二次标准单击 // macOS uses a real dblclick event; two sequential clicks don't work
// 这 20ms 的延迟在操作上完全感觉不到,但对远程桌面的网络和操作系统驱动至关重要! dblClickAtCursor(); // Then double click
// 它能完美地把两次点击在时间线上拉开,让任何操作系统都 100% 判定这是标准的“物理鼠标双击”。 } else {
setTimeout(() => { // Windows/Linux: simulate physical double-click with two clicks 20ms apart
clickAtCursor(0); setTimeout(() => {
}, 20); clickAtCursor(0);
}, 20);
}
touchState.state = T_IDLE; touchState.state = T_IDLE;
} else if (touchState.state === T_FIRST_DOWN && !touchState.moved) { } else if (touchState.state === T_FIRST_DOWN && !touchState.moved) {
// First tap released without moving = single click // First tap released without moving = single click