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.ScreenWidth = cfg.GetInt("settings", "ScreenWidth", 0);
m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0); m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0);
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", 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); m_hWorkThread = __CreateThread(NULL,0, WorkThreadProc,this,0,NULL);
} }
@@ -535,6 +536,13 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ScreenSettings.FullScreen = fullScreen; m_ScreenSettings.FullScreen = fullScreen;
break; 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: { case CMD_MULTITHREAD_COMPRESS: {
int threadNum = szBuffer[1]; int threadNum = szBuffer[1];
m_ClientObject->SetMultiThreadCompress(threadNum); m_ClientObject->SetMultiThreadCompress(threadNum);

View File

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

View File

@@ -37,6 +37,7 @@ enum {
IDM_FPS_UNLIMITED, IDM_FPS_UNLIMITED,
IDM_ORIGINAL_SIZE, IDM_ORIGINAL_SIZE,
IDM_SCREEN_1080P, IDM_SCREEN_1080P,
IDM_REMOTE_CURSOR,
}; };
IMPLEMENT_DYNAMIC(CScreenSpyDlg, CDialog) IMPLEMENT_DYNAMIC(CScreenSpyDlg, CDialog)
@@ -162,6 +163,7 @@ void CScreenSpyDlg::DoDataExchange(CDataExchange* pDX)
BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog) BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog)
ON_WM_CLOSE() ON_WM_CLOSE()
ON_WM_PAINT() ON_WM_PAINT()
ON_WM_SETCURSOR()
ON_WM_SYSCOMMAND() ON_WM_SYSCOMMAND()
ON_WM_HSCROLL() ON_WM_HSCROLL()
ON_WM_VSCROLL() ON_WM_VSCROLL()
@@ -242,6 +244,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->AppendMenu(MF_SEPARATOR); SysMenu->AppendMenu(MF_SEPARATOR);
SysMenu->AppendMenu(MF_STRING, IDM_CONTROL, "控制屏幕(&Y)"); SysMenu->AppendMenu(MF_STRING, IDM_CONTROL, "控制屏幕(&Y)");
SysMenu->AppendMenu(MF_STRING, IDM_FULLSCREEN, "全屏(&F)"); 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_ADAPTIVE_SIZE, "自适应窗口大小(&A)");
SysMenu->AppendMenu(MF_STRING, IDM_TRACE_CURSOR, "跟踪被控端鼠标(&T)"); SysMenu->AppendMenu(MF_STRING, IDM_TRACE_CURSOR, "跟踪被控端鼠标(&T)");
SysMenu->AppendMenu(MF_STRING, IDM_BLOCK_INPUT, "锁定被控端鼠标和键盘(&L)"); 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_STRING, IDM_SCREEN_1080P, "限制为1080P(&4)");
SysMenu->AppendMenu(MF_SEPARATOR); 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; CMenu fpsMenu;
if (fpsMenu.CreatePopupMenu()) { if (fpsMenu.CreatePopupMenu()) {
fpsMenu.AppendMenu(MF_STRING, IDM_FPS_10, "最大帧率FPS:10"); 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]; m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2+sizeof(POINT))[0];
if (bOldCursorIndex != m_bCursorIndex) { if (bOldCursorIndex != m_bCursorIndex) {
bChange = TRUE; 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 #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 #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 #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) : 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); 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) if ((m_bIsCtrl && m_Settings.RemoteCursor) || m_bIsTraceCursor) {
DrawIconEx( CPoint ptLocal;
m_hFullDC, GetCursorPos(&ptLocal);
m_ClientCursorPos.x - m_ulHScrollPos, ScreenToClient(&ptLocal);
m_ClientCursorPos.y - m_ulVScrollPos,
m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex), CRect rcToolbar(0, 0, 0, 0);
0,0, if (m_pToolbar) m_pToolbar->GetWindowRect(&rcToolbar), ScreenToClient(&rcToolbar);
0, // 只有当本地鼠标不在工具栏区域时,才绘制远程位图光标
NULL, if (!rcToolbar.PtInRect(ptLocal)) {
DI_NORMAL | DI_COMPAT
); // 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) { if (!m_bConnected && GetTickCount64() - m_nDisconnectTime>2000) {
DrawTipString("正在重连......", 2); 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) VOID CScreenSpyDlg::DrawTipString(CString strString, int fillMode)
{ {
// fillMode: 0=不填充, 1=全黑, 2=半透明 // fillMode: 0=不填充, 1=全黑, 2=半透明
@@ -716,6 +747,12 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
m_ContextObject->Send2Client(cmd, sizeof(cmd)); m_ContextObject->Send2Client(cmd, sizeof(cmd));
break; 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: { // 快照保存 case IDM_SAVEDIB: { // 快照保存
SaveSnapshot(); SaveSnapshot();
break; break;
@@ -1224,7 +1261,25 @@ BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
void CScreenSpyDlg::OnMouseMove(UINT nFlags, CPoint point) 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; m_bMouseTracking = true;
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); 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(); virtual BOOL OnInitDialog();
afx_msg void OnClose(); afx_msg void OnClose();
afx_msg void OnPaint(); afx_msg void OnPaint();
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
virtual BOOL PreTranslateMessage(MSG* pMsg); virtual BOOL PreTranslateMessage(MSG* pMsg);
void OnLButtonDblClk(UINT nFlags, CPoint point); void OnLButtonDblClk(UINT nFlags, CPoint point);

View File

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

View File

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