Compare commits
2
Commits
f24e7acc48
..
v1.3.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99510e46d | ||
|
|
b97f086c0c |
No files matched your search
@@ -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 客户端 & 窗口捕获增强 & 安全加固**
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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 用戶端 & 視窗擷取增強 & 安全加固**
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 清空结果
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
// 程序版本号 [建议格式: X.Y.Z]
|
||||
// 影响:关于对话框、标题栏
|
||||
#define BRAND_VERSION "1.3.7"
|
||||
#define BRAND_VERSION "1.3.8"
|
||||
|
||||
// 启动画面名称 [建议大写,更有 Logo 感]
|
||||
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
|
||||
|
||||
@@ -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
|
||||
@@ -1949,3 +1949,10 @@ FRPC Զ
|
||||
客户端日志=客户端日志
|
||||
封禁使用=封禁使用
|
||||
确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗?
|
||||
无结果=无结果
|
||||
严禁=严禁
|
||||
禁止=禁止
|
||||
失败=失败
|
||||
搜索日志=搜索日志
|
||||
隐藏搜索=隐藏搜索
|
||||
请=请
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user