diff --git a/.gitignore b/.gitignore index 0b7ed04..b827d62 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,14 @@ test/build/ docs/MultiLayerLicense_Design.md docs/MultiLayerLicense_Implementation.md docs/_CodeReference.md +linux/CMakeFiles/* +Releases/* +*.log +*.txt +linux/Makefile +linux/cmake_install.cmake +.vs +docs/macOS_Support_Design.md +settings.local.json +*.zip +*.lic diff --git a/server/2015Remote/LangManager.h b/server/2015Remote/LangManager.h index cc7037a..c23bfd4 100644 --- a/server/2015Remote/LangManager.h +++ b/server/2015Remote/LangManager.h @@ -57,9 +57,6 @@ public: } else { m_langDir = langDir; } - - // 确保目录存在 - CreateDirectory(m_langDir, NULL); } // 获取可用的语言列表(包括内嵌语言) diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 6726bf2..6a542a8 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -489,6 +489,8 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog) ON_WM_VSCROLL() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() + ON_WM_RBUTTONDOWN() + ON_WM_RBUTTONUP() ON_WM_MOUSEWHEEL() ON_WM_MOUSEMOVE() ON_WM_MOUSELEAVE() @@ -689,7 +691,7 @@ BOOL CScreenSpyDlg::OnInitDialog() if (m_bIsCtrl) { ImmAssociateContext(m_hWnd, NULL); // 控制模式:禁用 IME } - m_bIsTraceCursor = FALSE; //不是跟踪 + m_bIsTraceCursor = !m_bIsCtrl; // 非控制状态,则跟踪鼠标 m_ClientCursorPos.x = 0; m_ClientCursorPos.y = 0; m_bCursorIndex = 0; @@ -1641,16 +1643,19 @@ 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); + // 绘制框选矩形(左键放大用红色,右键截图用绿色,二者颜色错开避免误操作) + if (m_bSelectingZoom || m_bSelectingShot) { + CPoint ptStart = m_bSelectingZoom ? m_ptZoomStart : m_ptShotStart; + CPoint ptCur = m_bSelectingZoom ? m_ptZoomCurrent : m_ptShotCurrent; + COLORREF clr = m_bSelectingZoom ? RGB(255, 0, 0) : RGB(0, 180, 0); - // 使用虚线边框绘制选择框 - HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0)); + CRect rcSelect; + rcSelect.left = min(ptStart.x, ptCur.x); + rcSelect.top = min(ptStart.y, ptCur.y); + rcSelect.right = max(ptStart.x, ptCur.x); + rcSelect.bottom = max(ptStart.y, ptCur.y); + + HPEN hPen = CreatePen(PS_DASH, 1, clr); 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); @@ -2849,29 +2854,10 @@ void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point) } // 将屏幕坐标转换为原图坐标 - int srcW = m_BitmapInfor_Full->bmiHeader.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; + if (!ScreenRectToImageRect(rcSelect, m_rcZoomSrc)) { + return; } - // 限制在原图范围内 - 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(); @@ -2897,6 +2883,145 @@ void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point) } +void CScreenSpyDlg::OnRButtonDown(UINT nFlags, CPoint point) +{ + // 非控制模式下:右键框选 → 截图保存。控制模式下右键由 PreTranslateMessage 转发给客户端。 + if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full) { + // 与左键互斥:左键正在框选/拖拽时不接管右键,避免冲突 + if (m_bSelectingZoom || m_bZoomDragging) { + return; + } + m_bSelectingShot = true; + m_ptShotStart = point; + m_ptShotCurrent = point; + SetCapture(); + return; + } + __super::OnRButtonDown(nFlags, point); +} + + +void CScreenSpyDlg::OnRButtonUp(UINT nFlags, CPoint point) +{ + if (!m_bIsCtrl && !m_bIsFirst && m_BitmapInfor_Full && m_bSelectingShot) { + ReleaseCapture(); + m_bSelectingShot = false; + + CRect rcSelect; + rcSelect.left = min(m_ptShotStart.x, point.x); + rcSelect.top = min(m_ptShotStart.y, point.y); + rcSelect.right = max(m_ptShotStart.x, point.x); + rcSelect.bottom = max(m_ptShotStart.y, point.y); + + // 太小视为误触(与左键放大同阈值) + if (rcSelect.Width() < 20 || rcSelect.Height() < 20) { + Invalidate(FALSE); + return; + } + + CRect rcImage; + if (ScreenRectToImageRect(rcSelect, rcImage) && + rcImage.Width() > 0 && rcImage.Height() > 0) + { + SaveRegionScreenshot(rcImage); + } + Invalidate(FALSE); // 清掉绿色选框 + return; + } + __super::OnRButtonUp(nFlags, point); +} + + +// 屏幕(窗口)选框 → 原图坐标,考虑放大状态、自适应、滚动 +bool CScreenSpyDlg::ScreenRectToImageRect(const CRect& rcScreen, CRect& rcImage) +{ + if (!m_BitmapInfor_Full) return false; + int srcW = m_BitmapInfor_Full->bmiHeader.biWidth; + int srcH = m_BitmapInfor_Full->bmiHeader.biHeight; + if (srcW <= 0 || srcH <= 0) return false; + + if (m_bZoomedIn && !m_rcZoomSrc.IsRectEmpty()) { + // 放大状态:屏幕坐标 → 当前可视的子区域内的原图坐标 + int dstW = m_CRect.Width(); + int dstH = m_CRect.Height(); + if (dstW <= 0 || dstH <= 0) return false; + double scaleX = (double)m_rcZoomSrc.Width() / dstW; + double scaleY = (double)m_rcZoomSrc.Height() / dstH; + rcImage.left = (int)(m_rcZoomSrc.left + rcScreen.left * scaleX); + rcImage.top = (int)(m_rcZoomSrc.top + rcScreen.top * scaleY); + rcImage.right = (int)(m_rcZoomSrc.left + rcScreen.right * scaleX); + rcImage.bottom = (int)(m_rcZoomSrc.top + rcScreen.bottom * scaleY); + } else if (m_bAdaptiveSize) { + rcImage.left = (int)(rcScreen.left * m_wZoom); + rcImage.top = (int)(rcScreen.top * m_hZoom); + rcImage.right = (int)(rcScreen.right * m_wZoom); + rcImage.bottom = (int)(rcScreen.bottom * m_hZoom); + } else { + rcImage.left = rcScreen.left + m_ulHScrollPos; + rcImage.top = rcScreen.top + m_ulVScrollPos; + rcImage.right = rcScreen.right + m_ulHScrollPos; + rcImage.bottom = rcScreen.bottom + m_ulVScrollPos; + } + + // 限制在原图范围内 + rcImage.left = max(0L, min(rcImage.left, (LONG)srcW)); + rcImage.top = max(0L, min(rcImage.top, (LONG)srcH)); + rcImage.right = max(0L, min(rcImage.right, (LONG)srcW)); + rcImage.bottom = max(0L, min(rcImage.bottom, (LONG)srcH)); + return true; +} + + +// 把原图中 [rcImage] 区域裁出来,写成独立 BMP(24bpp 或 32bpp 由源图决定) +void CScreenSpyDlg::SaveRegionScreenshot(const CRect& rcImage) +{ + if (!m_BitmapInfor_Full || !m_BitmapData_Full) return; + if (rcImage.Width() <= 0 || rcImage.Height() <= 0) return; + + auto path = GetScreenShotPath(this, m_IPAddress, _TR("位图文件(*.bmp)|*.bmp|"), "bmp"); + if (path.empty()) return; + + // 源 DIB 是 BGR 24bpp 或 BGRA 32bpp,bottom-up(biHeight > 0) + const BITMAPINFOHEADER& srcHdr = m_BitmapInfor_Full->bmiHeader; + int bpp = srcHdr.biBitCount; + if (bpp != 24 && bpp != 32) return; // 仅支持当前实际使用的两种位深 + int srcW = srcHdr.biWidth; + int srcH = srcHdr.biHeight; + int srcStride = ((srcW * bpp + 31) / 32) * 4; + + int dstW = rcImage.Width(); + int dstH = rcImage.Height(); + int dstStride = ((dstW * bpp + 31) / 32) * 4; + int dstSize = dstStride * dstH; + + std::vector dstPixels(dstSize, 0); + const BYTE* srcBase = (const BYTE*)m_BitmapData_Full; + + // bottom-up:原图第 y 行(从顶起算)位于 srcBase + (srcH - 1 - y) * srcStride + int byteX = rcImage.left * (bpp / 8); + int copyBytes = dstW * (bpp / 8); + for (int y = 0; y < dstH; ++y) { + int srcRowFromTop = rcImage.top + y; + int srcRowOffset = (srcH - 1 - srcRowFromTop) * srcStride + byteX; + int dstRowOffset = (dstH - 1 - y) * dstStride; + memcpy(&dstPixels[dstRowOffset], &srcBase[srcRowOffset], copyBytes); + } + + // 拼装 BITMAPINFO(裁剪后只需要 BITMAPINFOHEADER;24/32bpp 不需要调色板) + BITMAPINFO dstBmi = {}; + dstBmi.bmiHeader = srcHdr; + dstBmi.bmiHeader.biWidth = dstW; + dstBmi.bmiHeader.biHeight = dstH; + dstBmi.bmiHeader.biSizeImage = dstSize; + dstBmi.bmiHeader.biCompression = BI_RGB; + + if (WriteBitmap(&dstBmi, dstPixels.data(), path)) { + m_strSaveNotice = path; + m_nSaveNoticeTime = GetTickCount64(); + } +} + + BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { // Convert screen coordinates to client coordinates @@ -2926,6 +3051,11 @@ void CScreenSpyDlg::OnMouseMove(UINT nFlags, CPoint point) Invalidate(FALSE); // FALSE表示不擦除背景,减少闪烁 return; } + if (m_bSelectingShot) { + m_ptShotCurrent = point; + Invalidate(FALSE); + return; + } if (m_bZoomDragging) { // 拖拽平移:计算偏移量并移动放大区域 @@ -3060,9 +3190,14 @@ void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) { m_bIsCtrl = ctrl; - // 进入控制模式时重置放大状态 - if (m_bIsCtrl && m_bZoomedIn) { - ResetZoom(); + // 进入控制模式时重置放大状态 + 中止任何正在进行的右键截图框选 + if (m_bIsCtrl) { + if (m_bZoomedIn) ResetZoom(); + if (m_bSelectingShot) { + m_bSelectingShot = false; + if (GetCapture() == this) ReleaseCapture(); + Invalidate(FALSE); + } } SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); // 控制模式:禁用本地 IME;查看模式:启用本地 IME @@ -3072,9 +3207,10 @@ void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd) { // 捕获丢失时重置框选/拖拽状态 - if (m_bSelectingZoom || m_bZoomDragging) { + if (m_bSelectingZoom || m_bZoomDragging || m_bSelectingShot) { m_bSelectingZoom = false; m_bZoomDragging = false; + m_bSelectingShot = false; Invalidate(); } __super::OnCaptureChanged(pWnd); diff --git a/server/2015Remote/ScreenSpyDlg.h b/server/2015Remote/ScreenSpyDlg.h index 88259bd..d5aa5b0 100644 --- a/server/2015Remote/ScreenSpyDlg.h +++ b/server/2015Remote/ScreenSpyDlg.h @@ -233,9 +233,16 @@ public: CPoint m_ptZoomDragStart; // 拖拽起点(用于点击检测) CPoint m_ptZoomDragLast; // 拖拽上一点(用于增量计算) + // ========== 区域截图(右键框选) ========== + bool m_bSelectingShot = false; // 是否正在右键框选截图 + CPoint m_ptShotStart; // 右键框选起点(屏幕坐标) + CPoint m_ptShotCurrent; // 右键框选当前点(屏幕坐标) + void ResetZoom(); // 重置放大状态 CPoint ScreenToImage(CPoint pt); // 屏幕坐标转原图坐标 CPoint ImageToScreen(CPoint pt); // 原图坐标转屏幕坐标 + bool ScreenRectToImageRect(const CRect& rcScreen, CRect& rcImage); // 选框坐标→原图坐标 + void SaveRegionScreenshot(const CRect& rcImage); // 保存裁剪区域为 BMP CString m_aviFile; CBmpToAvi m_aviStream; @@ -312,6 +319,8 @@ public: afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnRButtonDown(UINT nFlags, CPoint point); + afx_msg void OnRButtonUp(UINT nFlags, CPoint point); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnMouseLeave(); diff --git a/server/2015Remote/lang/en_US.ini b/server/2015Remote/lang/en_US.ini index b909595..f3e765a 100644 --- a/server/2015Remote/lang/en_US.ini +++ b/server/2015Remote/lang/en_US.ini @@ -1171,7 +1171,7 @@ WIN32 ѡĿ¼=Language location ʻ(&N)=Internationalization ԰Ŀ¼(&D)=Language Pack Directory -ͨչ˵ָ԰Ŀ¼ֶ֧=Please specify the language pack directory via the "Extensions" menu to enable multi-language support. +ͨ\"չ\"˵ָ԰Ŀ¼ֶ֧=Please specify the language pack directory via the "Extensions" menu to enable multi-language support. ѡ[*.ico]ͼļ!=Please select an [*.ico] icon file or enter a process description! PE ༭=PE Edit PE ༭(&R)=PE Edit(&R) diff --git a/server/2015Remote/lang/zh_TW.ini b/server/2015Remote/lang/zh_TW.ini index ec89467..660bde0 100644 --- a/server/2015Remote/lang/zh_TW.ini +++ b/server/2015Remote/lang/zh_TW.ini @@ -1169,7 +1169,7 @@ WIN32 ѡĿ¼=ՈxĿ ʻ(&N)=H ԰Ŀ¼(&D)=Z԰Ŀ -ͨչ˵ָ԰Ŀ¼ֶ֧=Ո͸^U䡹xָZ԰Ŀ䛣֧ԮZԡ +ͨ\"չ\"˵ָ԰Ŀ¼ֶ֧=Ո͸^U䡹xָZ԰Ŀ䛣֧ԮZԡ ѡ[*.ico]ͼļ!=Ոx[*.ico]Dʾnݔ̎ PE ༭=PE ݋ PE ༭(&R)=PE ݋(&R)