Feature: screen preview thumbnail on host double-click
Server sends COMMAND_SCREEN_PREVIEW_REQ when user double-clicks an active (non-Locked/Inactive) host that advertises CLIENT_CAP_SCREEN_PREVIEW. Client BitBlts primary screen, encodes to JPEG via GDI+ and replies. The existing STATIC tooltip is replaced with a self-drawn CPreviewTipWnd showing the thumbnail above the host info text, with wide-character rendering so the popup also works on non-Chinese servers. - Quality tiers reuse QualityProfile pattern: PreviewProfile + 6 levels driven by GetTargetQualityLevel (FRP-aware), with 4K/ultrawide auto upscale on Ultra/High tiers up to min(screenWidth/4, 1280). - Client limits to 1 in-flight capture via atomic counter to defend against flood/DoS; Send2Server is already mutex-serialized. - Server validates responses by reqId only (single in-flight tip); 4s arrival timeout marks "preview unavailable" without blocking the text fallback path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -125,9 +125,10 @@ inline int isValid_10s()
|
||||
#define DLL_VERSION __DATE__ // DLL版本
|
||||
|
||||
// 客户端能力位
|
||||
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
|
||||
#define CLIENT_CAP_UTF8 0x0002 // 协议字符串字段统一使用 UTF-8 编码(活动窗口、窗口列表、键盘记录中的窗口标题等)
|
||||
// 无此位 = 老客户端,按系统 ANSI(默认 CP936)解读
|
||||
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
|
||||
#define CLIENT_CAP_UTF8 0x0002 // 协议字符串字段统一使用 UTF-8 编码(活动窗口、窗口列表、键盘记录中的窗口标题等)
|
||||
// 无此位 = 老客户端,按系统 ANSI(默认 CP936)解读
|
||||
#define CLIENT_CAP_SCREEN_PREVIEW 0x0004 // 支持屏幕预览(双击在线主机时弹缩略图)
|
||||
|
||||
#define TALK_DLG_MAXLEN 1024 // 最大输入字符长度
|
||||
|
||||
@@ -334,6 +335,8 @@ enum {
|
||||
CMD_PEER_TO_PEER = 244, // P2P通信
|
||||
TOKEN_CLIENTID = 245,
|
||||
TOKEN_CONN_AUTH = 246, // 子连接身份校验包(客户端首发,服务端回 ConnAuthAck)
|
||||
COMMAND_SCREEN_PREVIEW_REQ = 247, // 屏幕预览请求(服务端→客户端)
|
||||
TOKEN_SCREEN_PREVIEW_RSP = 248, // 屏幕预览响应(客户端→服务端)
|
||||
};
|
||||
|
||||
// 子连接校验:HMAC 签名 (clientID || timestamp || nonce),服务端通过校验后把 clientID
|
||||
@@ -374,6 +377,47 @@ struct ConnAuthAck {
|
||||
static_assert(sizeof(ConnAuthPacket) == 512, "ConnAuthPacket must be exactly 512 bytes");
|
||||
static_assert(sizeof(ConnAuthAck) == 256, "ConnAuthAck must be exactly 256 bytes");
|
||||
|
||||
// 屏幕预览:服务端按双击在线主机触发,向客户端要一张缩略图(JPEG),与浮窗一起显示。
|
||||
// 服务端依 ctx 最近心跳 Ping + RES_RESOLUTION 决定 maxWidth/quality 后下发;客户端
|
||||
// 主屏抓图 → 等比缩放 → JPEG 编码 → 回响应。format 字段 v1 锁 0=JPEG,预留 PNG/WebP。
|
||||
#pragma pack(push, 1)
|
||||
struct ScreenPreviewReq {
|
||||
uint8_t cmd; // = COMMAND_SCREEN_PREVIEW_REQ
|
||||
uint16_t reqId; // 请求序号,用于丢弃过期响应
|
||||
uint16_t maxWidth; // 服务端期望的目标宽度(客户端等比缩放,不强制)
|
||||
uint8_t jpegQuality; // 1..100
|
||||
uint16_t reserved;
|
||||
}; // 总 8 字节
|
||||
|
||||
enum ScreenPreviewStatus {
|
||||
SCREEN_PREVIEW_OK = 0,
|
||||
SCREEN_PREVIEW_CAPTURE_FAILED = 1, // 抓屏失败
|
||||
SCREEN_PREVIEW_ENCODE_FAILED = 2, // 编码失败
|
||||
SCREEN_PREVIEW_NOT_SUPPORTED = 3, // 平台不支持
|
||||
};
|
||||
|
||||
enum ScreenPreviewFormat {
|
||||
SCREEN_PREVIEW_FMT_JPEG = 0,
|
||||
SCREEN_PREVIEW_FMT_PNG = 1, // 预留
|
||||
SCREEN_PREVIEW_FMT_WEBP = 2, // 预留
|
||||
};
|
||||
|
||||
struct ScreenPreviewRspHeader {
|
||||
uint8_t token; // = TOKEN_SCREEN_PREVIEW_RSP
|
||||
uint16_t reqId; // 回显请求序号
|
||||
uint8_t status; // ScreenPreviewStatus
|
||||
uint8_t format; // ScreenPreviewFormat(v1 仅 JPEG)
|
||||
uint16_t width; // 实际编码图宽
|
||||
uint16_t height; // 实际编码图高
|
||||
uint32_t bytes; // 图像字节数(紧随其后)
|
||||
uint8_t reserved[3];
|
||||
// 后接 data[bytes]
|
||||
}; // 头部 16 字节
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(ScreenPreviewReq) == 8, "ScreenPreviewReq must be 8 bytes");
|
||||
static_assert(sizeof(ScreenPreviewRspHeader) == 16, "ScreenPreviewRspHeader must be 16 bytes");
|
||||
|
||||
enum ConnAuthStatus {
|
||||
CONN_AUTH_OK = 0,
|
||||
CONN_AUTH_BAD_SIZE = 1, // 包长度不对
|
||||
@@ -970,7 +1014,7 @@ typedef struct LOGIN_INFOR {
|
||||
{
|
||||
memset(this, 0, sizeof(LOGIN_INFOR));
|
||||
bToken = TOKEN_LOGIN;
|
||||
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2 | CLIENT_CAP_UTF8);
|
||||
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2 | CLIENT_CAP_UTF8 | CLIENT_CAP_SCREEN_PREVIEW);
|
||||
}
|
||||
LOGIN_INFOR& Speed(unsigned long speed)
|
||||
{
|
||||
@@ -1143,6 +1187,29 @@ inline const QualityProfile& GetQualityProfile(int level) {
|
||||
return g_QualityProfiles[level];
|
||||
}
|
||||
|
||||
// 屏幕预览质量配置(与 QualityLevel 共用 RTT 阈值表,但参数维度不同:缩略图只关心
|
||||
// 编码尺寸 + JPEG 质量,没有 FPS / 算法等运动视频参数)
|
||||
struct PreviewProfile {
|
||||
int maxWidth; // 期望编码宽度(客户端会等比缩放,禁止放大)
|
||||
int jpegQuality; // JPEG 质量 1..100
|
||||
};
|
||||
|
||||
inline const PreviewProfile& GetScreenPreviewProfile(int level) {
|
||||
static const PreviewProfile g_PreviewProfiles[QUALITY_COUNT] = {
|
||||
{ 1024, 85 }, // Ultra: 超清 (LAN/同省,4K 源屏可进一步放大到 1280)
|
||||
{ 800, 80 }, // High: 高清 (跨省直连)
|
||||
{ 640, 75 }, // Good: 标清 (同国/邻国)
|
||||
{ 480, 70 }, // Medium: 常规 (大陆间)
|
||||
{ 384, 60 }, // Low: 低清 (跨洲)
|
||||
{ 256, 50 }, // Minimal: 最低 (极差网络/卫星链路)
|
||||
};
|
||||
if (level < 0 || level >= QUALITY_COUNT) {
|
||||
static const PreviewProfile fallback = { 480, 70 };
|
||||
return fallback;
|
||||
}
|
||||
return g_PreviewProfiles[level];
|
||||
}
|
||||
|
||||
// 根据RTT获取目标质量等级 (控制端使用)
|
||||
inline int GetTargetQualityLevel(int rtt, int usingFRP) {
|
||||
// 根据模式应用不同 RTT阈值 (毫秒)
|
||||
|
||||
Reference in New Issue
Block a user