Feature: Linux/macOS server-identity gate via libsign.a
fix remote-cursor flicker on Windows controller
This commit is contained in:
52
client/sign_shim_unix.cpp
Normal file
52
client/sign_shim_unix.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// sign_shim_unix.cpp - Linux/macOS adapter for libsign.a's C interface
|
||||||
|
//
|
||||||
|
// libsign.a 公开 ABI 是 C linkage(避免 std::string 跨编译器/跨 libstdc++
|
||||||
|
// 版本 ABI 风险),但 YAMA 客户端代码(IOCPClient.cpp / KernelManager.cpp /
|
||||||
|
// linux/main.cpp / macos/main.mm)习惯用 std::string 调用 signMessage /
|
||||||
|
// verifyMessage。本文件提供 C++ 适配,让两边契合。
|
||||||
|
//
|
||||||
|
// Windows 不编译这个文件——Windows 直接链接私有 .lib 提供的 std::string 版本。
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// libsign.a 提供的 C 接口
|
||||||
|
extern "C" {
|
||||||
|
int signMessage_c(const char* privateKey, int privateKeyLen,
|
||||||
|
const unsigned char* msg, int msgLen,
|
||||||
|
char* outBuf, int outBufSize);
|
||||||
|
int verifyMessage_c(const char* publicKey, int publicKeyLen,
|
||||||
|
const unsigned char* msg, int msgLen,
|
||||||
|
const char* sigHex, int sigLen);
|
||||||
|
int isVerifyCalled_c(void);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 YAMA common/commands.h 中 BYTE 一致
|
||||||
|
typedef unsigned char BYTE;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 提供 YAMA 既有声明所期望的 C++ 符号
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
std::string signMessage(const std::string& privateKey, BYTE* msg, int len)
|
||||||
|
{
|
||||||
|
char buf[65] = {};
|
||||||
|
int n = signMessage_c(privateKey.c_str(), (int)privateKey.size(),
|
||||||
|
msg, len,
|
||||||
|
buf, sizeof(buf));
|
||||||
|
if (n != 64) return std::string();
|
||||||
|
return std::string(buf, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verifyMessage(const std::string& publicKey, BYTE* msg, int len,
|
||||||
|
const std::string& signature)
|
||||||
|
{
|
||||||
|
return verifyMessage_c(publicKey.c_str(), (int)publicKey.size(),
|
||||||
|
msg, len,
|
||||||
|
signature.data(), (int)signature.size()) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int isVerifyCalled()
|
||||||
|
{
|
||||||
|
return isVerifyCalled_c();
|
||||||
|
}
|
||||||
@@ -1014,7 +1014,14 @@ typedef struct LOGIN_INFOR {
|
|||||||
{
|
{
|
||||||
memset(this, 0, sizeof(LOGIN_INFOR));
|
memset(this, 0, sizeof(LOGIN_INFOR));
|
||||||
bToken = TOKEN_LOGIN;
|
bToken = TOKEN_LOGIN;
|
||||||
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2 | CLIENT_CAP_UTF8 | CLIENT_CAP_SCREEN_PREVIEW);
|
// 能力位:声明客户端实际实现了的功能。SCREEN_PREVIEW 只在 Windows 客户端
|
||||||
|
// 实现(依赖 GDI BitBlt + GDI+ JPEG),Linux/macOS 不声明,避免服务端发请求
|
||||||
|
// 后等 4s 超时显示"预览不可用"。
|
||||||
|
unsigned int caps = CLIENT_CAP_V2 | CLIENT_CAP_UTF8;
|
||||||
|
#ifdef _WIN32
|
||||||
|
caps |= CLIENT_CAP_SCREEN_PREVIEW;
|
||||||
|
#endif
|
||||||
|
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, caps);
|
||||||
}
|
}
|
||||||
LOGIN_INFOR& Speed(unsigned long speed)
|
LOGIN_INFOR& Speed(unsigned long speed)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ set(SOURCES
|
|||||||
main.cpp
|
main.cpp
|
||||||
../client/Buffer.cpp
|
../client/Buffer.cpp
|
||||||
../client/IOCPClient.cpp
|
../client/IOCPClient.cpp
|
||||||
|
../client/sign_shim_unix.cpp
|
||||||
)
|
)
|
||||||
add_executable(ghost ${SOURCES})
|
add_executable(ghost ${SOURCES})
|
||||||
|
|
||||||
@@ -40,6 +41,14 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g")
|
|||||||
message(STATUS "链接库文件: ${CMAKE_SOURCE_DIR}/lib/libzstd.a")
|
message(STATUS "链接库文件: ${CMAKE_SOURCE_DIR}/lib/libzstd.a")
|
||||||
target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libzstd.a")
|
target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libzstd.a")
|
||||||
|
|
||||||
|
# 链接私有签名库(提供 signMessage / verifyMessage,源码不开源)
|
||||||
|
# 该库由 SimplePlugins 仓库本地构建后放置于 lib/,构建机需先准备好
|
||||||
|
target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libsign.a")
|
||||||
|
|
||||||
|
# libsign.a 内部使用 OpenSSL HMAC,需要在最终可执行链接 libcrypto
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
target_link_libraries(ghost PRIVATE OpenSSL::Crypto)
|
||||||
|
|
||||||
# 链接 dl 库(dlopen/dlsym 用于运行时加载 X11)
|
# 链接 dl 库(dlopen/dlsym 用于运行时加载 X11)
|
||||||
target_link_libraries(ghost PRIVATE dl)
|
target_link_libraries(ghost PRIVATE dl)
|
||||||
|
|
||||||
|
|||||||
BIN
linux/lib/libsign.a
Normal file
BIN
linux/lib/libsign.a
Normal file
Binary file not shown.
@@ -46,6 +46,11 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
|
|||||||
// 客户端 ID(V2 文件传输需要)
|
// 客户端 ID(V2 文件传输需要)
|
||||||
uint64_t g_myClientID = 0;
|
uint64_t g_myClientID = 0;
|
||||||
|
|
||||||
|
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
|
||||||
|
std::string g_loginMsg;
|
||||||
|
time_t g_loginTime = 0;
|
||||||
|
bool g_settingsVerified = false;
|
||||||
|
|
||||||
// ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ==============
|
// ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ==============
|
||||||
|
|
||||||
static std::string utf8ToGbk(const std::string& utf8)
|
static std::string utf8ToGbk(const std::string& utf8)
|
||||||
@@ -467,18 +472,38 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
|||||||
uint64_t now = GetUnixMs();
|
uint64_t now = GetUnixMs();
|
||||||
double rtt_ms = (double)(now - ack->Time);
|
double rtt_ms = (double)(now - ack->Time);
|
||||||
g_rttEstimator.update_from_sample(rtt_ms);
|
g_rttEstimator.update_from_sample(rtt_ms);
|
||||||
|
// 心跳节奏太密日志会刷屏;最多 60s 一行
|
||||||
|
static time_t lastAckLog = 0;
|
||||||
|
time_t now_s = time(nullptr);
|
||||||
|
if (now_s - lastAckLog >= 60) {
|
||||||
|
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, rtt_ms, g_rttEstimator.srtt * 1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (szBuffer[0] == CMD_MASTERSETTING) {
|
} else if (szBuffer[0] == CMD_MASTERSETTING) {
|
||||||
int settingSize = ulLength - 1;
|
int settingSize = ulLength - 1;
|
||||||
if (settingSize >= (int)sizeof(int)) { // 至少包含 ReportInterval
|
// 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端
|
||||||
|
if (settingSize < (int)sizeof(MasterSettings)) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
MasterSettings settings = {};
|
MasterSettings settings = {};
|
||||||
memcpy(&settings, szBuffer + 1, settingSize < (int)sizeof(MasterSettings) ? settingSize : sizeof(MasterSettings));
|
memcpy(&settings, szBuffer + 1, sizeof(MasterSettings));
|
||||||
|
|
||||||
|
// 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature
|
||||||
|
// 验证签名。失败 → 静默退出(不打印关键词日志)
|
||||||
|
extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
|
||||||
|
std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature));
|
||||||
|
if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
g_settingsVerified = true;
|
||||||
|
|
||||||
if (settings.ReportInterval > 0)
|
if (settings.ReportInterval > 0)
|
||||||
g_heartbeatInterval = settings.ReportInterval;
|
g_heartbeatInterval = settings.ReportInterval;
|
||||||
Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval);
|
Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval);
|
||||||
}
|
|
||||||
} else if (szBuffer[0] == COMMAND_NEXT) {
|
} else if (szBuffer[0] == COMMAND_NEXT) {
|
||||||
Mprintf("** [%p] Received 'NEXT' command ***\n", user);
|
Mprintf("** [%p] Received 'NEXT' command ***\n", user);
|
||||||
} else if (szBuffer[0] == COMMAND_C2C_TEXT) {
|
} else if (szBuffer[0] == COMMAND_C2C_TEXT) {
|
||||||
@@ -1090,6 +1115,9 @@ int main(int argc, char* argv[])
|
|||||||
logInfo.AddReserved((int)getpid()); // [17] RES_PID
|
logInfo.AddReserved((int)getpid()); // [17] RES_PID
|
||||||
logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE
|
logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE
|
||||||
|
|
||||||
|
// 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID)
|
||||||
|
g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID);
|
||||||
|
|
||||||
// 初始化用户活动检测器(用于心跳包中的 ActiveWnd 字段)
|
// 初始化用户活动检测器(用于心跳包中的 ActiveWnd 字段)
|
||||||
ActivityChecker activityChecker;
|
ActivityChecker activityChecker;
|
||||||
|
|
||||||
@@ -1102,6 +1130,9 @@ int main(int argc, char* argv[])
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进入新连接,重置服务端身份校验状态
|
||||||
|
g_loginTime = time(nullptr);
|
||||||
|
g_settingsVerified = false;
|
||||||
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
|
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
|
||||||
|
|
||||||
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
||||||
@@ -1131,6 +1162,13 @@ int main(int argc, char* argv[])
|
|||||||
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
|
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端
|
||||||
|
if (!g_settingsVerified && g_loginTime > 0 &&
|
||||||
|
time(nullptr) - g_loginTime > 30) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致)
|
// 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致)
|
||||||
std::string activity = utf8ToGbk(activityChecker.Check());
|
std::string activity = utf8ToGbk(activityChecker.Check());
|
||||||
|
|
||||||
@@ -1143,10 +1181,16 @@ int main(int argc, char* argv[])
|
|||||||
buf[0] = TOKEN_HEARTBEAT;
|
buf[0] = TOKEN_HEARTBEAT;
|
||||||
memcpy(buf + 1, &hb, sizeof(Heartbeat));
|
memcpy(buf + 1, &hb, sizeof(Heartbeat));
|
||||||
ClientObject->Send2Server((char*)buf, sizeof(buf));
|
ClientObject->Send2Server((char*)buf, sizeof(buf));
|
||||||
|
// 心跳节奏太密日志会刷屏;最多 60s 一行
|
||||||
|
static time_t lastSendLog = 0;
|
||||||
|
time_t now_s = time(nullptr);
|
||||||
|
if (now_s - lastSendLog >= 60) {
|
||||||
|
lastSendLog = now_s;
|
||||||
Mprintf(">>> Heartbeat sent: Ping=%dms, Interval=%ds, Activity=%s\n",
|
Mprintf(">>> Heartbeat sent: Ping=%dms, Interval=%ds, Activity=%s\n",
|
||||||
hb.Ping, interval, activity.c_str());
|
hb.Ping, interval, activity.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Logger::getInstance().stop();
|
Logger::getInstance().stop();
|
||||||
removePidFile();
|
removePidFile();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ set(SOURCES
|
|||||||
main.mm
|
main.mm
|
||||||
../client/Buffer.cpp
|
../client/Buffer.cpp
|
||||||
../client/IOCPClient.cpp
|
../client/IOCPClient.cpp
|
||||||
|
../client/sign_shim_unix.cpp
|
||||||
ScreenHandler.mm
|
ScreenHandler.mm
|
||||||
InputHandler.mm
|
InputHandler.mm
|
||||||
SystemManager.mm
|
SystemManager.mm
|
||||||
@@ -62,6 +63,11 @@ target_link_libraries(ghost PRIVATE
|
|||||||
${ACCELERATE_FRAMEWORK}
|
${ACCELERATE_FRAMEWORK}
|
||||||
${ICONV_LIBRARY}
|
${ICONV_LIBRARY}
|
||||||
"${CMAKE_SOURCE_DIR}/lib/libzstd.a"
|
"${CMAKE_SOURCE_DIR}/lib/libzstd.a"
|
||||||
|
# 私有签名库(提供 signMessage / verifyMessage,源码不开源)
|
||||||
|
# 该库由 SimplePlugins 仓库本地构建后放置于 lib/,构建机需先准备好
|
||||||
|
# libsign.a 内部使用 macOS CommonCrypto(HMAC-SHA256),CCHmac 在 libSystem
|
||||||
|
# 中已被 Cocoa/CoreFoundation 等链接自动引入,故此处无需额外 framework
|
||||||
|
"${CMAKE_SOURCE_DIR}/lib/libsign.a"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compiler flags
|
# Compiler flags
|
||||||
|
|||||||
BIN
macos/lib/libsign.a
Normal file
BIN
macos/lib/libsign.a
Normal file
Binary file not shown.
@@ -36,6 +36,11 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
|
|||||||
// Client ID (calculated from system info, used by ScreenHandler)
|
// Client ID (calculated from system info, used by ScreenHandler)
|
||||||
uint64_t g_myClientID = 0;
|
uint64_t g_myClientID = 0;
|
||||||
|
|
||||||
|
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
|
||||||
|
std::string g_loginMsg;
|
||||||
|
time_t g_loginTime = 0;
|
||||||
|
bool g_settingsVerified = false;
|
||||||
|
|
||||||
// 远程地址:当前为写死状态,如需调试,请按实际情况修改
|
// 远程地址:当前为写死状态,如需调试,请按实际情况修改
|
||||||
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS };
|
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS };
|
||||||
|
|
||||||
@@ -626,6 +631,9 @@ static void fillLoginInfo(LOGIN_INFOR& info)
|
|||||||
}
|
}
|
||||||
info.AddReserved(std::to_string(g_myClientID).c_str());
|
info.AddReserved(std::to_string(g_myClientID).c_str());
|
||||||
|
|
||||||
|
// 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID)
|
||||||
|
g_loginMsg = std::string(info.szStartTime) + "|" + std::to_string(g_myClientID);
|
||||||
|
|
||||||
NSLog(@"LOGIN_INFOR filled: OS=%s, Host=%s, CPU=%dMHz, PubIP=%s, ClientID=%llu",
|
NSLog(@"LOGIN_INFOR filled: OS=%s, Host=%s, CPU=%dMHz, PubIP=%s, ClientID=%llu",
|
||||||
osVer.c_str(), hostname.c_str(), info.dwCPUMHz, pubIP.c_str(), g_myClientID);
|
osVer.c_str(), hostname.c_str(), info.dwCPUMHz, pubIP.c_str(), g_myClientID);
|
||||||
}
|
}
|
||||||
@@ -849,13 +857,27 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
|||||||
}
|
}
|
||||||
} else if (szBuffer[0] == CMD_MASTERSETTING) {
|
} else if (szBuffer[0] == CMD_MASTERSETTING) {
|
||||||
int settingSize = ulLength - 1;
|
int settingSize = ulLength - 1;
|
||||||
if (settingSize >= (int)sizeof(int)) { // 至少包含 ReportInterval
|
// 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端
|
||||||
|
if (settingSize < (int)sizeof(MasterSettings)) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
MasterSettings settings = {};
|
MasterSettings settings = {};
|
||||||
memcpy(&settings, szBuffer + 1, settingSize < (int)sizeof(MasterSettings) ? settingSize : sizeof(MasterSettings));
|
memcpy(&settings, szBuffer + 1, sizeof(MasterSettings));
|
||||||
|
|
||||||
|
// 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature
|
||||||
|
// 验证签名。失败 → 静默退出(不打印关键词日志)
|
||||||
|
extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
|
||||||
|
std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature));
|
||||||
|
if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
g_settingsVerified = true;
|
||||||
|
|
||||||
if (settings.ReportInterval > 0)
|
if (settings.ReportInterval > 0)
|
||||||
g_heartbeatInterval = settings.ReportInterval;
|
g_heartbeatInterval = settings.ReportInterval;
|
||||||
Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval);
|
Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval);
|
||||||
}
|
|
||||||
} else if (szBuffer[0] == COMMAND_NEXT) {
|
} else if (szBuffer[0] == COMMAND_NEXT) {
|
||||||
Mprintf("** [%p] Received 'NEXT' command ***\n", user);
|
Mprintf("** [%p] Received 'NEXT' command ***\n", user);
|
||||||
} else if (szBuffer[0] == CMD_SET_GROUP) {
|
} else if (szBuffer[0] == CMD_SET_GROUP) {
|
||||||
@@ -981,6 +1003,9 @@ int main(int argc, const char* argv[])
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进入新连接,重置服务端身份校验状态
|
||||||
|
g_loginTime = time(nullptr);
|
||||||
|
g_settingsVerified = false;
|
||||||
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
|
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
|
||||||
|
|
||||||
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
||||||
@@ -1002,6 +1027,13 @@ int main(int argc, const char* argv[])
|
|||||||
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
|
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端
|
||||||
|
if (!g_settingsVerified && g_loginTime > 0 &&
|
||||||
|
time(nullptr) - g_loginTime > 30) {
|
||||||
|
g_bExit = S_CLIENT_EXIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致)
|
// 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致)
|
||||||
std::string activity = getActiveApp();
|
std::string activity = getActiveApp();
|
||||||
|
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog)
|
|||||||
ON_WM_LBUTTONDBLCLK()
|
ON_WM_LBUTTONDBLCLK()
|
||||||
ON_WM_ACTIVATE()
|
ON_WM_ACTIVATE()
|
||||||
ON_WM_TIMER()
|
ON_WM_TIMER()
|
||||||
|
ON_WM_ERASEBKGND()
|
||||||
ON_COMMAND(ID_EXIT_FULLSCREEN, &CScreenSpyDlg::OnExitFullscreen)
|
ON_COMMAND(ID_EXIT_FULLSCREEN, &CScreenSpyDlg::OnExitFullscreen)
|
||||||
ON_COMMAND(ID_SHOW_STATUS_INFO, &CScreenSpyDlg::OnShowStatusInfo)
|
ON_COMMAND(ID_SHOW_STATUS_INFO, &CScreenSpyDlg::OnShowStatusInfo)
|
||||||
ON_COMMAND(ID_HIDE_STATUS_INFO, &CScreenSpyDlg::OnHideStatusInfo)
|
ON_COMMAND(ID_HIDE_STATUS_INFO, &CScreenSpyDlg::OnHideStatusInfo)
|
||||||
@@ -1608,6 +1609,19 @@ bool CScreenSpyDlg::Decode(LPBYTE Buffer, int size)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳过默认背景擦除:随帧重绘时若先 FillRect 灰色再 BitBlt 帧,会在两步之间
|
||||||
|
// 出现"瞬时灰背景",启用远程光标(应用层 DrawIconEx)时尤其明显——光标随每帧重绘,
|
||||||
|
// 灰一闪 → 帧覆盖 → 重画光标,循环看上去就是光标频繁闪烁。
|
||||||
|
// adaptive/zoom 模式下 BitBlt/StretchBlt 覆盖整个客户区,本就不需要先擦;
|
||||||
|
// m_bIsFirst(首帧未到达)仍走默认擦除以避免显示残留内容。
|
||||||
|
BOOL CScreenSpyDlg::OnEraseBkgnd(CDC* pDC)
|
||||||
|
{
|
||||||
|
if (m_bIsFirst) {
|
||||||
|
return __super::OnEraseBkgnd(pDC);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
void CScreenSpyDlg::OnPaint()
|
void CScreenSpyDlg::OnPaint()
|
||||||
{
|
{
|
||||||
if (m_bIsClosed) return;
|
if (m_bIsClosed) return;
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ public:
|
|||||||
virtual BOOL OnInitDialog();
|
virtual BOOL OnInitDialog();
|
||||||
afx_msg void OnClose();
|
afx_msg void OnClose();
|
||||||
afx_msg void OnPaint();
|
afx_msg void OnPaint();
|
||||||
|
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||||
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
|
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
|
||||||
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
|
||||||
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
virtual BOOL PreTranslateMessage(MSG* pMsg);
|
||||||
|
|||||||
Reference in New Issue
Block a user