Feature: Support using remote cursor in screen control

This commit is contained in:
yuanyuanxiang
2026-01-16 22:06:04 +01:00
parent 3f94505aaf
commit 39e07adb3b
6 changed files with 88 additions and 18 deletions

View File

@@ -100,6 +100,7 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user):CMan
m_ScreenSettings.ScreenWidth = cfg.GetInt("settings", "ScreenWidth", 0);
m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0);
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", 0);
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
m_hWorkThread = __CreateThread(NULL,0, WorkThreadProc,this,0,NULL);
}
@@ -535,6 +536,13 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ScreenSettings.FullScreen = fullScreen;
break;
}
case CMD_REMOTE_CURSOR: {
int remoteCursor = szBuffer[1];
iniFile cfg(CLIENT_PATH);
cfg.SetInt("settings", "RemoteCursor", remoteCursor);
m_ScreenSettings.RemoteCursor = remoteCursor;
break;
}
case CMD_MULTITHREAD_COMPRESS: {
int threadNum = szBuffer[1];
m_ClientObject->SetMultiThreadCompress(threadNum);

View File

@@ -202,6 +202,7 @@ enum {
CMD_UNCOMPRESS_FILES = 73, // 解压文件
CMD_SCREEN_SIZE = 74,
CMD_FULL_SCREEN = 75,
CMD_REMOTE_CURSOR = 76,
// 服务端发出的标识
TOKEN_AUTH = 100, // 要求验证
@@ -922,7 +923,8 @@ typedef struct ScreenSettings {
int ScreenWidth; // 屏幕宽度
int ScreenHeight; // 屏幕高度
int FullScreen; // 全屏模式
char Reserved[76]; // 保留字段
int RemoteCursor; // 使用远程光标
char Reserved[72]; // 保留字段
} ScreenSettings;
#pragma pack(push, 1)

View File

@@ -37,6 +37,7 @@ enum {
IDM_FPS_UNLIMITED,
IDM_ORIGINAL_SIZE,
IDM_SCREEN_1080P,
IDM_REMOTE_CURSOR,
};
IMPLEMENT_DYNAMIC(CScreenSpyDlg, CDialog)
@@ -162,6 +163,7 @@ void CScreenSpyDlg::DoDataExchange(CDataExchange* pDX)
BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog)
ON_WM_CLOSE()
ON_WM_PAINT()
ON_WM_SETCURSOR()
ON_WM_SYSCOMMAND()
ON_WM_HSCROLL()
ON_WM_VSCROLL()
@@ -242,6 +244,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->AppendMenu(MF_SEPARATOR);
SysMenu->AppendMenu(MF_STRING, IDM_CONTROL, "控制屏幕(&Y)");
SysMenu->AppendMenu(MF_STRING, IDM_FULLSCREEN, "全屏(&F)");
SysMenu->AppendMenu(MF_STRING, IDM_REMOTE_CURSOR, "使用远程光标(&C)");
SysMenu->AppendMenu(MF_STRING, IDM_ADAPTIVE_SIZE, "自适应窗口大小(&A)");
SysMenu->AppendMenu(MF_STRING, IDM_TRACE_CURSOR, "跟踪被控端鼠标(&T)");
SysMenu->AppendMenu(MF_STRING, IDM_BLOCK_INPUT, "锁定被控端鼠标和键盘(&L)");
@@ -258,6 +261,9 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->AppendMenu(MF_STRING, IDM_SCREEN_1080P, "限制为1080P(&4)");
SysMenu->AppendMenu(MF_SEPARATOR);
SysMenu->CheckMenuItem(IDM_FULLSCREEN, m_Settings.FullScreen ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED);
CMenu fpsMenu;
if (fpsMenu.CreatePopupMenu()) {
fpsMenu.AppendMenu(MF_STRING, IDM_FPS_10, "最大帧率FPS:10");
@@ -477,12 +483,14 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2+sizeof(POINT))[0];
if (bOldCursorIndex != m_bCursorIndex) {
bChange = TRUE;
if (m_bIsCtrl && !m_bIsTraceCursor)//替换指定窗口所属类的WNDCLASSEX结构
if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构
HCURSOR cursor = m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex);
#ifdef _WIN64
SetClassLongPtrA(m_hWnd, GCLP_HCURSOR, (ULONG_PTR)m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex));
SetClassLongPtrA(m_hWnd, GCLP_HCURSOR, (ULONG_PTR)cursor);
#else
SetClassLongA(m_hWnd, GCL_HCURSOR, (LONG)m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex));
SetClassLongA(m_hWnd, GCL_HCURSOR, (LONG)cursor);
#endif
}
}
// 屏幕是否变化
@@ -606,22 +614,45 @@ void CScreenSpyDlg::OnPaint()
StretchBlt(m_hFullDC, 0, 0, m_CRect.Width(), m_CRect.Height(), m_hFullMemDC, 0, 0, m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight, SRCCOPY) :
BitBlt(m_hFullDC, 0, 0, m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight, m_hFullMemDC, m_ulHScrollPos, m_ulVScrollPos, SRCCOPY);
if (m_bIsTraceCursor)
DrawIconEx(
m_hFullDC,
m_ClientCursorPos.x - m_ulHScrollPos,
m_ClientCursorPos.y - m_ulVScrollPos,
m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex),
0,0,
0,
NULL,
DI_NORMAL | DI_COMPAT
);
if ((m_bIsCtrl && m_Settings.RemoteCursor) || m_bIsTraceCursor) {
CPoint ptLocal;
GetCursorPos(&ptLocal);
ScreenToClient(&ptLocal);
CRect rcToolbar(0, 0, 0, 0);
if (m_pToolbar) m_pToolbar->GetWindowRect(&rcToolbar), ScreenToClient(&rcToolbar);
// 只有当本地鼠标不在工具栏区域时,才绘制远程位图光标
if (!rcToolbar.PtInRect(ptLocal)) {
// 1. 计算缩放位置
int drawX = m_bAdaptiveSize ? (int)(m_ClientCursorPos.x / m_wZoom) : (m_ClientCursorPos.x - m_ulHScrollPos);
int drawY = m_bAdaptiveSize ? (int)(m_ClientCursorPos.y / m_hZoom) : (m_ClientCursorPos.y - m_ulVScrollPos);
// 2. 强制绘制
DrawIconEx(
m_hFullDC,
drawX,
drawY,
m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex),
0, 0, 0, NULL, DI_NORMAL | DI_COMPAT
);
}
}
if (!m_bConnected && GetTickCount64() - m_nDisconnectTime>2000) {
DrawTipString("正在重连......", 2);
}
}
BOOL CScreenSpyDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if ((m_bIsCtrl && m_Settings.RemoteCursor) && nHitTest == HTCLIENT)
{
::SetCursor(NULL); // 只要在客户区,始终隐藏系统光标
return TRUE; // 告诉 Windows 我们处理过了
}
return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
VOID CScreenSpyDlg::DrawTipString(CString strString, int fillMode)
{
// fillMode: 0=不填充, 1=全黑, 2=半透明
@@ -716,6 +747,12 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
m_ContextObject->Send2Client(cmd, sizeof(cmd));
break;
}
case IDM_REMOTE_CURSOR: {
BYTE cmd[4] = { CMD_REMOTE_CURSOR, m_Settings.RemoteCursor = !m_Settings.RemoteCursor };
SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED);
m_ContextObject->Send2Client(cmd, sizeof(cmd));
break;
}
case IDM_SAVEDIB: { // 快照保存
SaveSnapshot();
break;
@@ -1224,7 +1261,25 @@ BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
void CScreenSpyDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bMouseTracking) {
if (m_Settings.RemoteCursor) {
if (m_pToolbar != NULL && ::IsWindow(m_pToolbar->m_hWnd) && m_pToolbar->IsWindowVisible())
{
CRect rcToolbar;
m_pToolbar->GetWindowRect(&rcToolbar);
ScreenToClient(&rcToolbar); // 转换到主窗口坐标系
if (rcToolbar.PtInRect(point))
{
// 如果鼠标在工具栏区域,直接显示本地光标并返回,不发送远程指令
::SetCursor(LoadCursor(NULL, IDC_ARROW));
return;
}
}
if (m_bIsCtrl) {
// 关键:在控制模式下,强制设置光标为空,隐藏本地物理箭头
::SetCursor(NULL);
}
}else if (!m_bMouseTracking) {
m_bMouseTracking = true;
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO));
}

View File

@@ -143,6 +143,7 @@ public:
virtual BOOL OnInitDialog();
afx_msg void OnClose();
afx_msg void OnPaint();
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
virtual BOOL PreTranslateMessage(MSG* pMsg);
void OnLButtonDblClk(UINT nFlags, CPoint point);

View File

@@ -6,9 +6,10 @@
IMPLEMENT_DYNAMIC(CToolbarDlg, CDialogEx)
CToolbarDlg::CToolbarDlg(CWnd* pParent)
CToolbarDlg::CToolbarDlg(CScreenSpyDlg* pParent)
: CDialogEx(IDD_TOOLBAR_DLG, pParent)
{
m_pParent = pParent;
}
CToolbarDlg::~CToolbarDlg()

View File

@@ -1,6 +1,8 @@
#pragma once
#include "Resource.h"
class CScreenSpyDlg;
class CToolbarDlg : public CDialogEx
{
DECLARE_DYNAMIC(CToolbarDlg)
@@ -8,7 +10,8 @@ private:
int m_lastY = 0; // 记录上一次的 Y 坐标
public:
CToolbarDlg(CWnd* pParent = nullptr);
CScreenSpyDlg* m_pParent = nullptr;
CToolbarDlg(CScreenSpyDlg* pParent = nullptr);
virtual ~CToolbarDlg();
enum { IDD = IDD_TOOLBAR_DLG };