From a89f8dd28fa3e07a3042a34657583b3450f375a2 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Tue, 5 May 2026 15:09:16 +0200 Subject: [PATCH] Feature: Add zoom functionality for remote desktop viewer in non-control mode --- server/2015Remote/ScreenSpyDlg.cpp | 274 ++++++++++++++++++++++++++++- server/2015Remote/ScreenSpyDlg.h | 15 ++ 2 files changed, 286 insertions(+), 3 deletions(-) diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 86862d6..6726bf2 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -505,6 +505,7 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog) ON_MESSAGE(WM_RECVFILEV2_CHUNK, &CScreenSpyDlg::OnRecvFileV2Chunk) ON_MESSAGE(WM_RECVFILEV2_COMPLETE, &CScreenSpyDlg::OnRecvFileV2Complete) ON_WM_DROPFILES() + ON_WM_CAPTURECHANGED() END_MESSAGE_MAP() @@ -1618,9 +1619,18 @@ void CScreenSpyDlg::OnPaint() int srcW = m_BitmapInfor_Full->bmiHeader.biWidth; int srcH = m_BitmapInfor_Full->bmiHeader.biHeight; - if (m_bAdaptiveSize) { - int dstW = m_CRect.Width(); - int dstH = m_CRect.Height(); + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + + // 放大模式渲染 + if (m_bZoomedIn && !m_rcZoomSrc.IsRectEmpty()) { + // 使用放大区域作为源进行StretchBlt + StretchBlt(m_hFullDC, 0, 0, dstW, dstH, + m_hFullMemDC, + m_rcZoomSrc.left, m_rcZoomSrc.top, + m_rcZoomSrc.Width(), m_rcZoomSrc.Height(), + SRCCOPY); + } else if (m_bAdaptiveSize) { // 尺寸相同时用 BitBlt(更快),否则用 StretchBlt if (srcW == dstW && srcH == dstH) { BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, 0, 0, SRCCOPY); @@ -1631,6 +1641,24 @@ void CScreenSpyDlg::OnPaint() BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, m_ulHScrollPos, m_ulVScrollPos, SRCCOPY); } + // 绘制框选矩形 + if (m_bSelectingZoom) { + CRect rcSelect; + rcSelect.left = min(m_ptZoomStart.x, m_ptZoomCurrent.x); + rcSelect.top = min(m_ptZoomStart.y, m_ptZoomCurrent.y); + rcSelect.right = max(m_ptZoomStart.x, m_ptZoomCurrent.x); + rcSelect.bottom = max(m_ptZoomStart.y, m_ptZoomCurrent.y); + + // 使用虚线边框绘制选择框 + HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0)); + HPEN hOldPen = (HPEN)SelectObject(m_hFullDC, hPen); + HBRUSH hOldBrush = (HBRUSH)SelectObject(m_hFullDC, GetStockObject(NULL_BRUSH)); + Rectangle(m_hFullDC, rcSelect.left, rcSelect.top, rcSelect.right, rcSelect.bottom); + SelectObject(m_hFullDC, hOldBrush); + SelectObject(m_hFullDC, hOldPen); + DeleteObject(hPen); + } + if ((m_bIsCtrl && m_Settings.RemoteCursor) || m_bIsTraceCursor) { CPoint ptLocal; GetCursorPos(&ptLocal); @@ -1813,6 +1841,10 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam) switch (nID) { case IDM_CONTROL: { m_bIsCtrl = !m_bIsCtrl; + // 进入控制模式时重置放大状态 + if (m_bIsCtrl && m_bZoomedIn) { + ResetZoom(); + } SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); // 控制模式:禁用本地 IME;查看模式:启用本地 IME @@ -2565,6 +2597,11 @@ void CScreenSpyDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) void CScreenSpyDlg::EnterFullScreen() { + // 进入全屏时重置放大状态 + if (m_bZoomedIn) { + ResetZoom(); + } + if (1) { // 1. 获取对话框当前所在的显示器 HMONITOR hMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST); @@ -2652,6 +2689,11 @@ void CScreenSpyDlg::EnterFullScreen() // 全屏退出成功则返回true bool CScreenSpyDlg::LeaveFullScreen() { + // 退出全屏时重置放大状态 + if (m_bZoomedIn) { + ResetZoom(); + } + if (1) { KillTimer(1); if (m_pToolbar) { @@ -2692,14 +2734,165 @@ bool CScreenSpyDlg::LeaveFullScreen() return false; } +// ========== 局部放大功能辅助函数 ========== + +// 重置放大状态 +void CScreenSpyDlg::ResetZoom() +{ + m_bZoomedIn = false; + m_bSelectingZoom = false; + m_bZoomDragging = false; + m_rcZoomSrc.SetRectEmpty(); + Invalidate(); +} + +// 屏幕坐标转原图坐标(考虑放大状态) +CPoint CScreenSpyDlg::ScreenToImage(CPoint pt) +{ + if (!m_BitmapInfor_Full) return pt; + + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + if (dstW <= 0 || dstH <= 0) return pt; // 防止除零 + + if (m_bZoomedIn && !m_rcZoomSrc.IsRectEmpty()) { + // 放大状态:从显示区域映射到放大区域 + double scaleX = (double)m_rcZoomSrc.Width() / dstW; + double scaleY = (double)m_rcZoomSrc.Height() / dstH; + return CPoint( + (int)(m_rcZoomSrc.left + pt.x * scaleX), + (int)(m_rcZoomSrc.top + pt.y * scaleY) + ); + } else if (m_bAdaptiveSize) { + // 自适应模式:按比例缩放 + return CPoint((int)(pt.x * m_wZoom), (int)(pt.y * m_hZoom)); + } else { + // 滚动模式:加上滚动偏移 + return CPoint(pt.x + m_ulHScrollPos, pt.y + m_ulVScrollPos); + } +} + +// 原图坐标转屏幕坐标(考虑放大状态) +CPoint CScreenSpyDlg::ImageToScreen(CPoint pt) +{ + if (!m_BitmapInfor_Full) return pt; + + int zoomW = m_rcZoomSrc.Width(); + int zoomH = m_rcZoomSrc.Height(); + + if (m_bZoomedIn && zoomW > 0 && zoomH > 0) { + // 放大状态:从放大区域映射到显示区域 + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + double scaleX = (double)dstW / zoomW; + double scaleY = (double)dstH / zoomH; + return CPoint( + (int)((pt.x - m_rcZoomSrc.left) * scaleX), + (int)((pt.y - m_rcZoomSrc.top) * scaleY) + ); + } else if (m_bAdaptiveSize) { + if (m_wZoom > 0 && m_hZoom > 0) { + return CPoint((int)(pt.x / m_wZoom), (int)(pt.y / m_hZoom)); + } + return pt; + } else { + return CPoint(pt.x - m_ulHScrollPos, pt.y - m_ulVScrollPos); + } +} + void CScreenSpyDlg::OnLButtonDown(UINT nFlags, CPoint point) { + // 非控制模式下的放大功能 + if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) { + if (m_bZoomedIn) { + // 放大状态:开始拖拽平移 + m_bZoomDragging = true; + m_ptZoomDragStart = point; // 保存起点用于点击检测 + m_ptZoomDragLast = point; // 用于增量拖拽计算 + SetCapture(); + return; + } else { + // 正常状态:开始框选放大区域 + m_bSelectingZoom = true; + m_ptZoomStart = point; + m_ptZoomCurrent = point; + SetCapture(); + return; + } + } __super::OnLButtonDown(nFlags, point); } void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point) { + // 处理放大功能的鼠标释放 + if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) { + if (m_bSelectingZoom) { + // 完成框选 + ReleaseCapture(); + m_bSelectingZoom = false; + + // 计算选择区域(确保leftbmiHeader.biWidth; + int srcH = m_BitmapInfor_Full->bmiHeader.biHeight; + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + + if (m_bAdaptiveSize) { + m_rcZoomSrc.left = (int)(rcSelect.left * m_wZoom); + m_rcZoomSrc.top = (int)(rcSelect.top * m_hZoom); + m_rcZoomSrc.right = (int)(rcSelect.right * m_wZoom); + m_rcZoomSrc.bottom = (int)(rcSelect.bottom * m_hZoom); + } else { + m_rcZoomSrc.left = rcSelect.left + m_ulHScrollPos; + m_rcZoomSrc.top = rcSelect.top + m_ulVScrollPos; + m_rcZoomSrc.right = rcSelect.right + m_ulHScrollPos; + m_rcZoomSrc.bottom = rcSelect.bottom + m_ulVScrollPos; + } + + // 限制在原图范围内 + m_rcZoomSrc.left = max(0L, min(m_rcZoomSrc.left, (LONG)srcW)); + m_rcZoomSrc.top = max(0L, min(m_rcZoomSrc.top, (LONG)srcH)); + m_rcZoomSrc.right = max(0L, min(m_rcZoomSrc.right, (LONG)srcW)); + m_rcZoomSrc.bottom = max(0L, min(m_rcZoomSrc.bottom, (LONG)srcH)); + + // 进入放大状态 + m_bZoomedIn = true; + Invalidate(); + return; + } + + if (m_bZoomDragging) { + // 完成拖拽 + ReleaseCapture(); + m_bZoomDragging = false; + + // 检查是否为点击(几乎没有移动) + int dx = abs(point.x - m_ptZoomDragStart.x); + int dy = abs(point.y - m_ptZoomDragStart.y); + if (dx < 5 && dy < 5) { + // 点击还原 + ResetZoom(); + } + return; + } + } __super::OnLButtonUp(nFlags, point); } @@ -2725,6 +2918,66 @@ BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) void CScreenSpyDlg::OnMouseMove(UINT nFlags, CPoint point) { + // 处理放大功能的鼠标移动 + if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) { + if (m_bSelectingZoom) { + // 框选中:更新当前点并重绘选择框 + m_ptZoomCurrent = point; + Invalidate(FALSE); // FALSE表示不擦除背景,减少闪烁 + return; + } + + if (m_bZoomDragging) { + // 拖拽平移:计算偏移量并移动放大区域 + int dx = point.x - m_ptZoomDragLast.x; + int dy = point.y - m_ptZoomDragLast.y; + m_ptZoomDragLast = point; // 更新上一点(保持m_ptZoomDragStart不变用于点击检测) + + // 计算缩放比例(添加除零保护) + int srcW = m_BitmapInfor_Full->bmiHeader.biWidth; + int srcH = m_BitmapInfor_Full->bmiHeader.biHeight; + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + int zoomW = m_rcZoomSrc.Width(); + int zoomH = m_rcZoomSrc.Height(); + + if (dstW <= 0 || dstH <= 0 || zoomW <= 0 || zoomH <= 0) { + return; // 防止除零 + } + + double scaleX = (double)zoomW / dstW; + double scaleY = (double)zoomH / dstH; + + // 将屏幕偏移转换为原图偏移(方向相反) + int imgDx = (int)(-dx * scaleX); + int imgDy = (int)(-dy * scaleY); + + // 移动放大区域 + m_rcZoomSrc.OffsetRect(imgDx, imgDy); + + // 限制在原图范围内 + if (m_rcZoomSrc.left < 0) { + m_rcZoomSrc.right -= m_rcZoomSrc.left; + m_rcZoomSrc.left = 0; + } + if (m_rcZoomSrc.top < 0) { + m_rcZoomSrc.bottom -= m_rcZoomSrc.top; + m_rcZoomSrc.top = 0; + } + if (m_rcZoomSrc.right > srcW) { + m_rcZoomSrc.left -= (m_rcZoomSrc.right - srcW); + m_rcZoomSrc.right = srcW; + } + if (m_rcZoomSrc.bottom > srcH) { + m_rcZoomSrc.top -= (m_rcZoomSrc.bottom - srcH); + m_rcZoomSrc.bottom = srcH; + } + + Invalidate(FALSE); + return; + } + } + if (m_Settings.RemoteCursor) { if (m_pToolbar != NULL && ::IsWindow(m_pToolbar->m_hWnd) && m_pToolbar->IsWindowVisible()) { CRect rcToolbar; @@ -2807,11 +3060,26 @@ void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) { m_bIsCtrl = ctrl; + // 进入控制模式时重置放大状态 + if (m_bIsCtrl && m_bZoomedIn) { + ResetZoom(); + } SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); // 控制模式:禁用本地 IME;查看模式:启用本地 IME ImmAssociateContext(m_hWnd, m_bIsCtrl ? NULL : m_hOldIMC); } +void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd) +{ + // 捕获丢失时重置框选/拖拽状态 + if (m_bSelectingZoom || m_bZoomDragging) { + m_bSelectingZoom = false; + m_bZoomDragging = false; + Invalidate(); + } + __super::OnCaptureChanged(pWnd); +} + void CScreenSpyDlg::OnDropFiles(HDROP hDropInfo) { if (m_bIsCtrl && m_bConnected) { diff --git a/server/2015Remote/ScreenSpyDlg.h b/server/2015Remote/ScreenSpyDlg.h index 86a0f88..88259bd 100644 --- a/server/2015Remote/ScreenSpyDlg.h +++ b/server/2015Remote/ScreenSpyDlg.h @@ -223,6 +223,20 @@ public: double m_wZoom=1, m_hZoom=1; bool m_bMouseTracking = false; + // ========== 局部放大功能 ========== + bool m_bZoomedIn = false; // 是否处于放大状态 + CRect m_rcZoomSrc; // 放大区域(原图坐标) + bool m_bSelectingZoom = false; // 是否正在框选 + CPoint m_ptZoomStart; // 框选起点(屏幕坐标) + CPoint m_ptZoomCurrent; // 框选当前点(屏幕坐标) + bool m_bZoomDragging = false; // 是否正在拖拽平移 + CPoint m_ptZoomDragStart; // 拖拽起点(用于点击检测) + CPoint m_ptZoomDragLast; // 拖拽上一点(用于增量计算) + + void ResetZoom(); // 重置放大状态 + CPoint ScreenToImage(CPoint pt); // 屏幕坐标转原图坐标 + CPoint ImageToScreen(CPoint pt); // 原图坐标转屏幕坐标 + CString m_aviFile; CBmpToAvi m_aviStream; @@ -302,6 +316,7 @@ public: afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnMouseLeave(); afx_msg void OnKillFocus(CWnd* pNewWnd); + afx_msg void OnCaptureChanged(CWnd* pWnd); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); afx_msg LRESULT OnDisconnect(WPARAM wParam, LPARAM lParam);