Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

280
docs/CustomCursor_Design.md Normal file
View File

@@ -0,0 +1,280 @@
# 自定义光标支持设计方案
**状态:已实现**
## 概述
扩展远程桌面的光标支持,使其能够显示应用程序自定义光标和动画光标,而不仅仅是 16 种标准系统光标。
## 现有实现
### 数据流
```
客户端 (ScreenCapture.h):
getCurrentCursorIndex() → 0-15 或 -1
帧数据: [算法(1)] [光标位置(8)] [光标索引(1)] [图像数据...]
服务端 (ScreenSpyDlg.cpp):
cursorIndex == -1 → 回退到标准箭头
cursorIndex 0-15 → 使用 m_CursorInfo.getCursorHandle(index)
```
### 限制
- 仅支持 16 种标准系统光标 (IDC_ARROW, IDC_HAND, IDC_IBEAM 等)
- 自定义光标显示为箭头
- 动画光标不支持
## 设计方案
### 方案概述
采用**独立命令传输 + 哈希缓存 + 节流机制**
1. 新增 `CMD_CURSOR_IMAGE` 命令传输光标位图
2. 使用位图哈希避免重复传输
3. 节流机制防止动画光标过载
4. 光标索引 `-2` 表示使用已缓存的自定义光标
### 光标索引定义
| 索引值 | 含义 |
|-------|------|
| 0-15 | 标准系统光标(现有) |
| -1 (255) | 不支持,显示箭头(现有,向后兼容) |
| -2 (254) | 使用缓存的自定义光标(新增) |
### CMD_CURSOR_IMAGE 数据格式
```
字节偏移 大小 字段
────────────────────────────
0 1 CMD_CURSOR_IMAGE (命令字节)
1 4 hash (位图哈希,用于去重)
5 2 hotspotX (热点X坐标)
7 2 hotspotY (热点Y坐标)
9 1 width (光标宽度最大255)
10 1 height (光标高度最大255)
11 N BGRA位图数据 (N = width * height * 4)
典型大小: 32x32光标 = 11 + 4096 = 4107 字节
```
### 客户端逻辑 (ScreenCapture.h / ScreenManager.cpp)
```cpp
// 新增状态变量
DWORD m_lastCursorHash = 0; // 上次发送的光标哈希
DWORD m_lastCursorSendTime = 0; // 上次发送时间
const DWORD CURSOR_THROTTLE = 50; // 最小发送间隔 (ms)
// 每帧处理逻辑
int cursorIndex = getCurrentCursorIndex();
if (cursorIndex == -1) {
// 非标准光标,获取位图
CursorBitmapInfo info;
if (GetCurrentCursorBitmap(&info)) {
DWORD hash = CalculateBitmapHash(info);
DWORD now = GetTickCount();
// 哈希变化且超过节流时间 → 发送光标图像
if (hash != m_lastCursorHash &&
(now - m_lastCursorSendTime) > CURSOR_THROTTLE) {
SendCursorImage(info, hash);
m_lastCursorHash = hash;
m_lastCursorSendTime = now;
}
cursorIndex = -2; // 使用自定义光标
}
}
// 帧数据中写入 cursorIndex
```
### 服务端逻辑 (ScreenSpyDlg.cpp)
```cpp
// 新增成员变量
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
DWORD m_customCursorHash = 0; // 当前光标哈希
// 处理 CMD_CURSOR_IMAGE
case CMD_CURSOR_IMAGE: {
DWORD hash = *(DWORD*)(buffer + 1);
if (hash != m_customCursorHash) {
WORD hotX = *(WORD*)(buffer + 5);
WORD hotY = *(WORD*)(buffer + 7);
BYTE width = buffer[9];
BYTE height = buffer[10];
LPBYTE bitmapData = buffer + 11;
// 销毁旧光标
if (m_hCustomCursor) {
DestroyCursor(m_hCustomCursor);
}
// 创建新光标
m_hCustomCursor = CreateCursorFromBitmap(
hotX, hotY, width, height, bitmapData);
m_customCursorHash = hash;
}
break;
}
// 处理帧数据中的光标索引
BYTE cursorIndex = buffer[2 + sizeof(POINT)];
HCURSOR cursor;
if (cursorIndex == 254) { // -2
cursor = m_hCustomCursor ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (cursorIndex == 255) { // -1
cursor = LoadCursor(NULL, IDC_ARROW);
} else {
cursor = m_CursorInfo.getCursorHandle(cursorIndex);
}
```
### 获取光标位图实现
```cpp
struct CursorBitmapInfo {
WORD hotspotX;
WORD hotspotY;
BYTE width;
BYTE height;
std::vector<BYTE> bgraData; // width * height * 4
};
bool GetCurrentCursorBitmap(CursorBitmapInfo* info) {
CURSORINFO ci = { sizeof(CURSORINFO) };
if (!GetCursorInfo(&ci) || ci.flags != CURSOR_SHOWING)
return false;
ICONINFO iconInfo;
if (!GetIconInfo(ci.hCursor, &iconInfo))
return false;
info->hotspotX = (WORD)iconInfo.xHotspot;
info->hotspotY = (WORD)iconInfo.yHotspot;
BITMAP bm;
GetObject(iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask,
sizeof(BITMAP), &bm);
info->width = (BYTE)min(bm.bmWidth, 255);
info->height = (BYTE)min(bm.bmHeight, 255);
// 获取 BGRA 位图数据
// ... (使用 GetDIBits)
// 清理
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
return true;
}
```
### 哈希计算
```cpp
DWORD CalculateBitmapHash(const CursorBitmapInfo& info) {
// 使用 FNV-1a 或简单的累加哈希
DWORD hash = 2166136261; // FNV offset basis
const BYTE* data = info.bgraData.data();
size_t len = info.bgraData.size();
for (size_t i = 0; i < len; i += 16) { // 每16字节采样一次加速
hash ^= data[i];
hash *= 16777619; // FNV prime
}
return hash;
}
```
## 数据流图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端 │
├─────────────────────────────────────────────────────────────────┤
│ getCurrentCursorIndex() │
│ │ │
│ ├─ 0-15 ──→ 帧数据: cursorIndex = 0-15 │
│ │ │
│ └─ -1 ──→ GetCurrentCursorBitmap() │
│ │ │
│ ├─ 计算哈希 │
│ │ │
│ ├─ 哈希变化 && 超过节流? │
│ │ │ │
│ │ ├─ Yes ──→ 发送 CMD_CURSOR_IMAGE │
│ │ │ 更新 lastHash, lastTime │
│ │ │ │
│ │ └─ No ──→ 跳过发送 │
│ │ │
│ └─ 帧数据: cursorIndex = -2 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 服务端 │
├─────────────────────────────────────────────────────────────────┤
│ 收到 CMD_CURSOR_IMAGE: │
│ └─ hash != m_customCursorHash? │
│ ├─ Yes ──→ CreateCursorFromBitmap() → m_hCustomCursor │
│ └─ No ──→ 忽略 (已缓存) │
│ │
│ 收到帧数据: │
│ └─ cursorIndex: │
│ ├─ 0-15 ──→ m_CursorInfo.getCursorHandle(index) │
│ ├─ -1 ──→ LoadCursor(IDC_ARROW) (向后兼容) │
│ └─ -2 ──→ m_hCustomCursor │
└─────────────────────────────────────────────────────────────────┘
```
## 动画光标支持
本方案自然支持动画光标:
| 机制 | 说明 |
|-----|------|
| **自动检测** | 动画每帧位图哈希不同,自动触发传输 |
| **节流保护** | 50ms 节流 ≈ 最高 20fps防止过载 |
| **带宽控制** | 32x32 动画 @ 10fps ≈ 40 KB/s可接受 |
## 修改文件清单
| 文件 | 修改内容 |
|-----|---------|
| `common/commands.h` | 新增 `CMD_CURSOR_IMAGE = 75` |
| `client/CursorInfo.h` | 新增 `GetCurrentCursorBitmap()`, `CalculateBitmapHash()` |
| `client/ScreenCapture.h` | 发送逻辑:检测、节流、发送 CMD_CURSOR_IMAGE |
| `client/ScreenManager.cpp` | 处理 CMD_CURSOR_IMAGE如果服务端发给客户端需要 |
| `server/2015Remote/ScreenSpyDlg.h` | 新增 `m_hCustomCursor`, `m_customCursorHash` |
| `server/2015Remote/ScreenSpyDlg.cpp` | 处理 CMD_CURSOR_IMAGE更新光标显示逻辑 |
## 向后兼容性
- 旧客户端:继续发送 `-1`,服务端显示箭头(现有行为)
- 新客户端 + 旧服务端:发送 `-2` 和 CMD_CURSOR_IMAGE旧服务端忽略新命令`-2` 当作 `-1` 处理
- 新客户端 + 新服务端:完整自定义光标支持
## 可调参数
```cpp
// client/ScreenCapture.h
const DWORD CURSOR_THROTTLE = 50; // 节流间隔 (ms)
// 50ms → 最高 20fps动画流畅
// 100ms → 最高 10fps省带宽
const BYTE MAX_CURSOR_SIZE = 64; // 最大光标尺寸
// 超过此尺寸的光标将被缩放或回退到标准光标
```
## 测试用例
1. **标准光标** - 箭头、手形、I形等应使用索引 0-15
2. **自定义静态光标** - 应用程序自定义光标,只传输一次
3. **动画光标** - Windows 忙碌动画,应持续更新(受节流限制)
4. **快速切换** - 频繁切换光标,节流应生效
5. **大尺寸光标** - 超过 64x64 的光标,应正确处理或回退