Feature: Add live thumbnail preview column to online host list
This commit is contained in:
@@ -1348,11 +1348,13 @@ enum {
|
|||||||
|
|
||||||
SHELLCODE = 0,
|
SHELLCODE = 0,
|
||||||
MEMORYDLL = 1,
|
MEMORYDLL = 1,
|
||||||
|
RUNTYPE_MAX = 2,
|
||||||
|
|
||||||
CALLTYPE_DEFAULT = 0, // 默认调用方式: 只是加载DLL,需要在DLL加载时执行代码
|
CALLTYPE_DEFAULT = 0, // 默认调用方式: 只是加载DLL,需要在DLL加载时执行代码
|
||||||
CALLTYPE_IOCPTHREAD = 1, // 调用run函数启动线程: DWORD (__stdcall *run)(void* lParam)
|
CALLTYPE_IOCPTHREAD = 1, // 调用run函数启动线程: DWORD (__stdcall *run)(void* lParam)
|
||||||
CALLTYPE_FRPC_CALL = 2, // 调用FRPC
|
CALLTYPE_FRPC_CALL = 2, // 调用FRPC
|
||||||
CALLTYPE_FRPC_STDCALL = 3, // 调用FRPC(标准方式,使用开源FRP项目)
|
CALLTYPE_FRPC_STDCALL = 3, // 调用FRPC(标准方式,使用开源FRP项目)
|
||||||
|
CALLTYPE_MAX = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef DWORD(__stdcall* PidCallback)(void);
|
typedef DWORD(__stdcall* PidCallback)(void);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#define SCH_MODE_MONTHLY 4 // 每月定时模式
|
#define SCH_MODE_MONTHLY 4 // 每月定时模式
|
||||||
#define SCH_MODE_YEARLY 5 // 每年定时模式
|
#define SCH_MODE_YEARLY 5 // 每年定时模式
|
||||||
#define SCH_MODE_OFF 6 // 关闭
|
#define SCH_MODE_OFF 6 // 关闭
|
||||||
|
#define SCH_MODE_MAX 7
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
// 严格定义 16 字节结构
|
// 严格定义 16 字节结构
|
||||||
|
|||||||
Binary file not shown.
@@ -89,6 +89,7 @@
|
|||||||
#define TIMER_STATUSBAR_INIT 7
|
#define TIMER_STATUSBAR_INIT 7
|
||||||
#define TIMER_PREVIEW_ARRIVAL 8 // 屏幕预览到达超时(4 秒未收到则提示"预览不可用")
|
#define TIMER_PREVIEW_ARRIVAL 8 // 屏幕预览到达超时(4 秒未收到则提示"预览不可用")
|
||||||
#define TIMER_PREVIEW_LOOP 9 // "播放快照"循环拉取(间隔由 LOOP_INTERVAL_MS 决定)
|
#define TIMER_PREVIEW_LOOP 9 // "播放快照"循环拉取(间隔由 LOOP_INTERVAL_MS 决定)
|
||||||
|
#define TIMER_THUMBNAIL_REFRESH 10 // 主机列表缩略图后台刷新(间隔取自 m_ThumbnailCfg)
|
||||||
#define TODO_NOTICE MessageBoxL("This feature has not been implemented!\nPlease contact: 962914132@qq.com", "提示", MB_ICONINFORMATION);
|
#define TODO_NOTICE MessageBoxL("This feature has not been implemented!\nPlease contact: 962914132@qq.com", "提示", MB_ICONINFORMATION);
|
||||||
#define TINY_DLL_NAME "TinyRun.dll"
|
#define TINY_DLL_NAME "TinyRun.dll"
|
||||||
#define FRPC_DLL_NAME "Frpc.dll"
|
#define FRPC_DLL_NAME "Frpc.dll"
|
||||||
@@ -129,9 +130,12 @@ typedef struct {
|
|||||||
int nWidth; //列表的宽度
|
int nWidth; //列表的宽度
|
||||||
} COLUMNSTRUCT;
|
} COLUMNSTRUCT;
|
||||||
|
|
||||||
const int g_Column_Count_Online = ONLINELIST_MAX; // 报表的列数
|
// 列表列布局:第 0 列固定为"预览"(缩略图,无数据槽),后续列对应 ONLINELIST_* 偏移 1。
|
||||||
|
// 即 listCol == 0 → 缩略图;listCol >= 1 → ONLINELIST_(listCol - 1)。
|
||||||
|
const int g_Column_Count_Online = ONLINELIST_MAX + 1; // 1 缩略图 + 既有数据列
|
||||||
|
|
||||||
COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
|
COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
|
||||||
|
{"预览", 60 },
|
||||||
{"IP", 130 },
|
{"IP", 130 },
|
||||||
{"端口", 60 },
|
{"端口", 60 },
|
||||||
{"地理位置", 130 },
|
{"地理位置", 130 },
|
||||||
@@ -146,6 +150,12 @@ COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
|
|||||||
{"类型", 50 },
|
{"类型", 50 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 缩略图列号常量 + 列号→数据槽映射。负返回值代表"该列无数据槽"。
|
||||||
|
static const int COL_THUMBNAIL = 0;
|
||||||
|
static inline int ColumnToDataSlot(int listCol) {
|
||||||
|
return listCol <= COL_THUMBNAIL ? -1 : (listCol - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
|
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
|
||||||
|
|
||||||
const int g_Column_Count_Message = 3; // 列表的个数
|
const int g_Column_Count_Message = 3; // 列表的个数
|
||||||
@@ -833,6 +843,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_WHAT_IS_THIS, &CMy2015RemoteDlg::OnWhatIsThis)
|
ON_COMMAND(ID_WHAT_IS_THIS, &CMy2015RemoteDlg::OnWhatIsThis)
|
||||||
ON_COMMAND(ID_ONLINE_AUTHORIZE, &CMy2015RemoteDlg::OnOnlineAuthorize)
|
ON_COMMAND(ID_ONLINE_AUTHORIZE, &CMy2015RemoteDlg::OnOnlineAuthorize)
|
||||||
ON_NOTIFY(NM_DBLCLK, IDC_ONLINE, &CMy2015RemoteDlg::OnListClick)
|
ON_NOTIFY(NM_DBLCLK, IDC_ONLINE, &CMy2015RemoteDlg::OnListClick)
|
||||||
|
ON_NOTIFY(NM_CLICK, IDC_ONLINE, &CMy2015RemoteDlg::OnListSingleClick)
|
||||||
ON_COMMAND(ID_ONLINE_UNAUTHORIZE, &CMy2015RemoteDlg::OnOnlineUnauthorize)
|
ON_COMMAND(ID_ONLINE_UNAUTHORIZE, &CMy2015RemoteDlg::OnOnlineUnauthorize)
|
||||||
ON_COMMAND(ID_TOOL_REQUEST_AUTH, &CMy2015RemoteDlg::OnToolRequestAuth)
|
ON_COMMAND(ID_TOOL_REQUEST_AUTH, &CMy2015RemoteDlg::OnToolRequestAuth)
|
||||||
ON_COMMAND(ID_TOOL_INPUT_PASSWORD, &CMy2015RemoteDlg::OnToolInputPassword)
|
ON_COMMAND(ID_TOOL_INPUT_PASSWORD, &CMy2015RemoteDlg::OnToolInputPassword)
|
||||||
@@ -877,6 +888,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
|||||||
ON_COMMAND(ID_PARAM_ENABLE_LOG, &CMy2015RemoteDlg::OnParamEnableLog)
|
ON_COMMAND(ID_PARAM_ENABLE_LOG, &CMy2015RemoteDlg::OnParamEnableLog)
|
||||||
ON_COMMAND(ID_PARAM_PRIVACY_WALLPAPER, &CMy2015RemoteDlg::OnParamPrivacyWallpaper)
|
ON_COMMAND(ID_PARAM_PRIVACY_WALLPAPER, &CMy2015RemoteDlg::OnParamPrivacyWallpaper)
|
||||||
ON_COMMAND(ID_PARAM_FILE_V2, &CMy2015RemoteDlg::OnParamFileV2)
|
ON_COMMAND(ID_PARAM_FILE_V2, &CMy2015RemoteDlg::OnParamFileV2)
|
||||||
|
ON_COMMAND(ID_PARAM_THUMBNAIL_PREVIEW, &CMy2015RemoteDlg::OnParamThumbnailPreview)
|
||||||
ON_COMMAND(ID_PARAM_RUN_AS_USER, &CMy2015RemoteDlg::OnParamRunAsUser)
|
ON_COMMAND(ID_PARAM_RUN_AS_USER, &CMy2015RemoteDlg::OnParamRunAsUser)
|
||||||
ON_COMMAND(ID_PROXY_PORT, &CMy2015RemoteDlg::OnProxyPort)
|
ON_COMMAND(ID_PROXY_PORT, &CMy2015RemoteDlg::OnProxyPort)
|
||||||
ON_COMMAND(ID_HOOK_WIN, &CMy2015RemoteDlg::OnHookWin)
|
ON_COMMAND(ID_HOOK_WIN, &CMy2015RemoteDlg::OnHookWin)
|
||||||
@@ -1317,6 +1329,9 @@ VOID CMy2015RemoteDlg::InitControl()
|
|||||||
m_CList_Message.ModifyStyle(0, LVS_SHOWSELALWAYS);
|
m_CList_Message.ModifyStyle(0, LVS_SHOWSELALWAYS);
|
||||||
m_CList_Message.SetExtendedStyle(style);
|
m_CList_Message.SetExtendedStyle(style);
|
||||||
m_CList_Message.ModifyStyle(WS_HSCROLL, 0);
|
m_CList_Message.ModifyStyle(WS_HSCROLL, 0);
|
||||||
|
|
||||||
|
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
|
||||||
|
// 之后统一 Apply(避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2011,6 +2026,12 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
strcpy(m_settings.RequestAuthUrl, THIS_CFG.GetStr("settings", "RequestAuthUrl", BRAND_URL_REQUEST_AUTH).c_str());
|
strcpy(m_settings.RequestAuthUrl, THIS_CFG.GetStr("settings", "RequestAuthUrl", BRAND_URL_REQUEST_AUTH).c_str());
|
||||||
strcpy(m_settings.GetPluginUrl, THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str());
|
strcpy(m_settings.GetPluginUrl, THIS_CFG.GetStr("settings", "GetPluginUrl", BRAND_URL_GET_PLUGIN).c_str());
|
||||||
m_bEnableFileV2 = THIS_CFG.GetInt("settings", "EnableFileV2", 0) != 0;
|
m_bEnableFileV2 = THIS_CFG.GetInt("settings", "EnableFileV2", 0) != 0;
|
||||||
|
|
||||||
|
// 缩略图配置:从 [thumbnail] 节读取(独立于 MasterSettings,纯主控端 UI 偏好)。
|
||||||
|
// 注意:InitControl 末尾已经先调过一次 Apply(用默认值),那时 INI 还没读 → 状态是错的。
|
||||||
|
// 这里读完 INI 后再 Apply 一次纠正过来。
|
||||||
|
LoadThumbnailSettingsFromCfg();
|
||||||
|
ApplyThumbnailSettings();
|
||||||
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
m_runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
|
||||||
|
|
||||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||||||
@@ -2020,6 +2041,7 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
SubMenu->CheckMenuItem(ID_PARAM_LOGIN_NOTIFY, m_needNotify ? MF_CHECKED : MF_UNCHECKED);
|
SubMenu->CheckMenuItem(ID_PARAM_LOGIN_NOTIFY, m_needNotify ? MF_CHECKED : MF_UNCHECKED);
|
||||||
SubMenu->CheckMenuItem(ID_PARAM_ENABLE_LOG, m_settings.EnableLog ? MF_CHECKED : MF_UNCHECKED);
|
SubMenu->CheckMenuItem(ID_PARAM_ENABLE_LOG, m_settings.EnableLog ? MF_CHECKED : MF_UNCHECKED);
|
||||||
SubMenu->CheckMenuItem(ID_PARAM_FILE_V2, m_bEnableFileV2 ? MF_CHECKED : MF_UNCHECKED);
|
SubMenu->CheckMenuItem(ID_PARAM_FILE_V2, m_bEnableFileV2 ? MF_CHECKED : MF_UNCHECKED);
|
||||||
|
SubMenu->CheckMenuItem(ID_PARAM_THUMBNAIL_PREVIEW, m_ThumbnailCfg.Enabled ? MF_CHECKED : MF_UNCHECKED);
|
||||||
|
|
||||||
// 互斥逻辑:三种模式 (RunNormal: 0=服务+SYSTEM, 1=普通模式, 2=服务+User)
|
// 互斥逻辑:三种模式 (RunNormal: 0=服务+SYSTEM, 1=普通模式, 2=服务+User)
|
||||||
if (m_runNormal == 0) {
|
if (m_runNormal == 0) {
|
||||||
@@ -2068,7 +2090,7 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
memset(&lvColumn, 0, sizeof(LVCOLUMN));
|
memset(&lvColumn, 0, sizeof(LVCOLUMN));
|
||||||
lvColumn.mask = LVCF_TEXT;
|
lvColumn.mask = LVCF_TEXT;
|
||||||
lvColumn.pszText = (char*)str.data();
|
lvColumn.pszText = (char*)str.data();
|
||||||
m_CList_Online.SetColumn(ONLINELIST_VIDEO, &lvColumn);
|
m_CList_Online.SetColumn(ONLINELIST_VIDEO + 1, &lvColumn); // +1:缩略图列占据列 0
|
||||||
timeBeginPeriod(1);
|
timeBeginPeriod(1);
|
||||||
if (IsFunctionReallyHooked("user32.dll","SetTimer") || IsFunctionReallyHooked("user32.dll", "KillTimer")) {
|
if (IsFunctionReallyHooked("user32.dll","SetTimer") || IsFunctionReallyHooked("user32.dll", "KillTimer")) {
|
||||||
THIS_APP->MessageBox(_TR("FUCK!!! 请勿HOOK此程序!"), _TR("提示"), MB_ICONERROR);
|
THIS_APP->MessageBox(_TR("FUCK!!! 请勿HOOK此程序!"), _TR("提示"), MB_ICONERROR);
|
||||||
@@ -3066,6 +3088,9 @@ void CMy2015RemoteDlg::OnTimer(UINT_PTR nIDEvent)
|
|||||||
if (nIDEvent == TIMER_PREVIEW_LOOP) {
|
if (nIDEvent == TIMER_PREVIEW_LOOP) {
|
||||||
TickLoopTips();
|
TickLoopTips();
|
||||||
}
|
}
|
||||||
|
if (nIDEvent == TIMER_THUMBNAIL_REFRESH) {
|
||||||
|
TickThumbnailRefresh();
|
||||||
|
}
|
||||||
if (nIDEvent == TIMER_CLEAR_BALLOON) {
|
if (nIDEvent == TIMER_CLEAR_BALLOON) {
|
||||||
KillTimer(TIMER_CLEAR_BALLOON);
|
KillTimer(TIMER_CLEAR_BALLOON);
|
||||||
|
|
||||||
@@ -3413,7 +3438,16 @@ void CMy2015RemoteDlg::Release()
|
|||||||
|
|
||||||
UninitFileUpload();
|
UninitFileUpload();
|
||||||
DeletePopupWindow(TRUE);
|
DeletePopupWindow(TRUE);
|
||||||
CloseAllLoopTips(); // 关闭所有"播放快照"循环窗口
|
CloseAllLoopTips(); // 关闭所有"播放快照"循环窗口
|
||||||
|
KillTimer(TIMER_THUMBNAIL_REFRESH);
|
||||||
|
ClearAllThumbnailCache(); // 释放所有缓存 HBITMAP
|
||||||
|
// 先解绑再销毁行高 ImageList,避免 listview HWND 在 m_thumbRowHeightImgList
|
||||||
|
// 之后销毁的极端时序里短暂持有悬空 HIMAGELIST。
|
||||||
|
if (m_thumbRowHeightImgList.GetSafeHandle()) {
|
||||||
|
m_CList_Online.SetImageList(NULL, LVSIL_SMALL);
|
||||||
|
m_thumbRowHeightImgList.DeleteImageList();
|
||||||
|
m_thumbRowHeightApplied = 0;
|
||||||
|
}
|
||||||
isClosed = TRUE;
|
isClosed = TRUE;
|
||||||
ShowWindow(SW_HIDE);
|
ShowWindow(SW_HIDE);
|
||||||
|
|
||||||
@@ -3480,6 +3514,9 @@ int CALLBACK CMy2015RemoteDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, L
|
|||||||
|
|
||||||
void CMy2015RemoteDlg::SortByColumn(int nColumn)
|
void CMy2015RemoteDlg::SortByColumn(int nColumn)
|
||||||
{
|
{
|
||||||
|
// 缩略图列点击表头不参与排序 —— 没有可比较的数据维度,避免误触发
|
||||||
|
if (ColumnToDataSlot(nColumn) < 0) return;
|
||||||
|
|
||||||
static int m_nSortColumn = 0;
|
static int m_nSortColumn = 0;
|
||||||
static bool m_bSortAscending = false;
|
static bool m_bSortAscending = false;
|
||||||
if (nColumn == m_nSortColumn) {
|
if (nColumn == m_nSortColumn) {
|
||||||
@@ -3491,14 +3528,14 @@ void CMy2015RemoteDlg::SortByColumn(int nColumn)
|
|||||||
m_bSortAscending = true;
|
m_bSortAscending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 虚拟列表:对数据源进行排序
|
// 虚拟列表:对数据源进行排序(列号映射到底层数据槽)
|
||||||
EnterCriticalSection(&m_cs);
|
EnterCriticalSection(&m_cs);
|
||||||
int col = m_nSortColumn;
|
int slot = ColumnToDataSlot(m_nSortColumn);
|
||||||
bool asc = m_bSortAscending;
|
bool asc = m_bSortAscending;
|
||||||
std::sort(m_HostList.begin(), m_HostList.end(),
|
std::sort(m_HostList.begin(), m_HostList.end(),
|
||||||
[col, asc](context* a, context* b) {
|
[slot, asc](context* a, context* b) {
|
||||||
CString s1 = a ? a->GetClientData(col) : "";
|
CString s1 = a ? a->GetClientData(slot) : "";
|
||||||
CString s2 = b ? b->GetClientData(col) : "";
|
CString s2 = b ? b->GetClientData(slot) : "";
|
||||||
int result = s1.Compare(s2);
|
int result = s1.Compare(s2);
|
||||||
return asc ? (result < 0) : (result > 0);
|
return asc ? (result < 0) : (result > 0);
|
||||||
});
|
});
|
||||||
@@ -3564,19 +3601,22 @@ void CMy2015RemoteDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
|
|
||||||
// 提供文本数据
|
// 提供文本数据
|
||||||
if (pItem->mask & LVIF_TEXT) {
|
if (pItem->mask & LVIF_TEXT) {
|
||||||
CString text;
|
|
||||||
int nCol = pItem->iSubItem;
|
int nCol = pItem->iSubItem;
|
||||||
|
int slot = ColumnToDataSlot(nCol);
|
||||||
// 备注列特殊处理
|
if (slot < 0) {
|
||||||
if (nCol == ONLINELIST_COMPUTER_NAME) {
|
// 缩略图列:自绘渲染,文本置空
|
||||||
CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
if (pItem->pszText && pItem->cchTextMax > 0) pItem->pszText[0] = '\0';
|
||||||
text = !note.IsEmpty() ? note : ctx->GetClientData(nCol);
|
|
||||||
} else {
|
} else {
|
||||||
text = ctx->GetClientData(nCol);
|
CString text;
|
||||||
if (text.IsEmpty()) text = "?";
|
if (slot == ONLINELIST_COMPUTER_NAME) {
|
||||||
|
CString note = m_ClientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
|
||||||
|
text = !note.IsEmpty() ? note : ctx->GetClientData(slot);
|
||||||
|
} else {
|
||||||
|
text = ctx->GetClientData(slot);
|
||||||
|
if (text.IsEmpty()) text = "?";
|
||||||
|
}
|
||||||
|
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*pResult = 0;
|
*pResult = 0;
|
||||||
@@ -3611,6 +3651,15 @@ void CMy2015RemoteDlg::OnGetDispInfoW(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
if ((pItem->mask & LVIF_TEXT) && pItem->pszText && pItem->cchTextMax > 0) {
|
if ((pItem->mask & LVIF_TEXT) && pItem->pszText && pItem->cchTextMax > 0) {
|
||||||
std::wstring wtext;
|
std::wstring wtext;
|
||||||
int nCol = pItem->iSubItem;
|
int nCol = pItem->iSubItem;
|
||||||
|
int slot = ColumnToDataSlot(nCol);
|
||||||
|
if (slot < 0) {
|
||||||
|
// 缩略图列:自绘渲染,文本置空
|
||||||
|
pItem->pszText[0] = L'\0';
|
||||||
|
*pResult = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 下面的逻辑沿用旧版,但用 slot 替代 nCol 访问数据槽
|
||||||
|
nCol = slot;
|
||||||
|
|
||||||
if (nCol == ONLINELIST_LOGINTIME) {
|
if (nCol == ONLINELIST_LOGINTIME) {
|
||||||
// "活动窗口"列:心跳到达后用旁路表里的宽字符串(已正确解码);
|
// "活动窗口"列:心跳到达后用旁路表里的宽字符串(已正确解码);
|
||||||
@@ -4371,7 +4420,7 @@ void CMy2015RemoteDlg::OnMainSet()
|
|||||||
CLock L(m_cs);
|
CLock L(m_cs);
|
||||||
m_settings.ReportInterval = m;
|
m_settings.ReportInterval = m;
|
||||||
m_settings.DetectSoftware = n;
|
m_settings.DetectSoftware = n;
|
||||||
m_CList_Online.SetColumn(ONLINELIST_VIDEO, &lvColumn);
|
m_CList_Online.SetColumn(ONLINELIST_VIDEO + 1, &lvColumn); // +1:缩略图列占据列 0
|
||||||
SendMasterSettings(nullptr, m_settings);
|
SendMasterSettings(nullptr, m_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5742,6 +5791,11 @@ LRESULT CMy2015RemoteDlg::OnUserToOnlineList(WPARAM wParam, LPARAM lParam)
|
|||||||
AddList(strIP,strAddr,strPCName,strOS,strCPU,strVideo,strPing,LoginInfor->moduleVersion,LoginInfor->szStartTime, v, ContextObject);
|
AddList(strIP,strAddr,strPCName,strOS,strCPU,strVideo,strPing,LoginInfor->moduleVersion,LoginInfor->szStartTime, v, ContextObject);
|
||||||
delete LoginInfor;
|
delete LoginInfor;
|
||||||
|
|
||||||
|
// 缩略图调度"急播":新上线主机立刻入队,下次 tick 就发请求(含三道门判定)
|
||||||
|
if (m_ThumbnailCfg.Enabled) {
|
||||||
|
m_ThumbNextDueTick[ContextObject->GetClientID()] = ::GetTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
// 执行主机上线触发器
|
// 执行主机上线触发器
|
||||||
ExecuteOnlineTrigger(ContextObject);
|
ExecuteOnlineTrigger(ContextObject);
|
||||||
|
|
||||||
@@ -5906,6 +5960,10 @@ LRESULT CMy2015RemoteDlg::OnUserOfflineMsg(WPARAM wParam, LPARAM lParam)
|
|||||||
// 关闭对应客户端的循环快照浮窗(如有)。CloseLoopTip 内部 find 找不到会静默返回。
|
// 关闭对应客户端的循环快照浮窗(如有)。CloseLoopTip 内部 find 找不到会静默返回。
|
||||||
if (info->clientId != 0) {
|
if (info->clientId != 0) {
|
||||||
CloseLoopTip(info->clientId);
|
CloseLoopTip(info->clientId);
|
||||||
|
// 清理缩略图相关状态(缓存 + 调度 + 在飞标记)。主机已不在列表,重绘不必要。
|
||||||
|
ClearThumbnailCacheEntry(info->clientId);
|
||||||
|
m_ThumbNextDueTick.erase(info->clientId);
|
||||||
|
m_ThumbnailPending.erase(info->clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close child dialog window
|
// Close child dialog window
|
||||||
@@ -7601,6 +7659,13 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
LPNMITEMACTIVATE pNMItem = (LPNMITEMACTIVATE)pNMHDR;
|
LPNMITEMACTIVATE pNMItem = (LPNMITEMACTIVATE)pNMHDR;
|
||||||
int iItem = pNMItem->iItem;
|
int iItem = pNMItem->iItem;
|
||||||
|
|
||||||
|
// 双击命中缩略图列:单击已触发 OnListSingleClick 打开循环窗口,这里不再叠加单发 tooltip
|
||||||
|
// 避免两个窗口同时出现。命中其他列时保持原有"双击 → 单发预览"行为。
|
||||||
|
if (pNMItem->iSubItem == COL_THUMBNAIL && m_ThumbnailCfg.Enabled) {
|
||||||
|
*pResult = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CLock lock(m_cs);
|
CLock lock(m_cs);
|
||||||
context* ctx = GetContextByListIndex(iItem);
|
context* ctx = GetContextByListIndex(iItem);
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
@@ -7709,6 +7774,47 @@ void CMy2015RemoteDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
*pResult = 0;
|
*pResult = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单击列表:仅对缩略图列起效 —— 打开循环监视窗口(等价于右键菜单"播放快照")。
|
||||||
|
// 其余列保持系统默认(选中),不抢用户体验。
|
||||||
|
void CMy2015RemoteDlg::OnListSingleClick(NMHDR* pNMHDR, LRESULT* pResult)
|
||||||
|
{
|
||||||
|
*pResult = 0;
|
||||||
|
if (!m_ThumbnailCfg.Enabled) return;
|
||||||
|
|
||||||
|
LPNMITEMACTIVATE p = (LPNMITEMACTIVATE)pNMHDR;
|
||||||
|
if (p->iItem < 0) return;
|
||||||
|
if (p->iSubItem != COL_THUMBNAIL) return;
|
||||||
|
|
||||||
|
context* ctx = nullptr;
|
||||||
|
uint64_t cid = 0;
|
||||||
|
{
|
||||||
|
CLock L(m_cs);
|
||||||
|
ctx = GetContextByListIndex(p->iItem);
|
||||||
|
if (ctx) cid = ctx->GetClientID();
|
||||||
|
}
|
||||||
|
if (!ctx || cid == 0) return;
|
||||||
|
|
||||||
|
// 已经开着循环窗 → 不重复开,让现有窗口前置
|
||||||
|
auto it = m_LoopTips.find(cid);
|
||||||
|
if (it != m_LoopTips.end() && it->second.tip && ::IsWindow(it->second.tip->GetSafeHwnd())) {
|
||||||
|
it->second.tip->ShowWindow(SW_SHOWNORMAL);
|
||||||
|
it->second.tip->BringWindowToTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有缓存的缩略图(即单元格显示的是 "…" 或 "N/A")→ 不弹窗。
|
||||||
|
// 用户语义:能看到画面才允许点开放大;占位态下点击不做任何动作。
|
||||||
|
auto cacheIt = m_HostThumbnails.find(cid);
|
||||||
|
bool hasBmp = (cacheIt != m_HostThumbnails.end() && cacheIt->second.bmp);
|
||||||
|
if (!hasBmp) return;
|
||||||
|
|
||||||
|
// 锚点:点击位置附近(列表客户区坐标 → 屏幕坐标)
|
||||||
|
CPoint anchor = p->ptAction;
|
||||||
|
m_CList_Online.ClientToScreen(&anchor);
|
||||||
|
anchor.Offset(20, 10);
|
||||||
|
OpenLoopTip(ctx, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
// 屏幕预览参数挑选:复用既有 GetTargetQualityLevel + GetScreenPreviewProfile
|
// 屏幕预览参数挑选:复用既有 GetTargetQualityLevel + GetScreenPreviewProfile
|
||||||
// RTT 阈值表与屏幕共享共用(FRP 模式阈值更宽松);超清档(Ultra/High)+ 4K/超宽屏
|
// RTT 阈值表与屏幕共享共用(FRP 模式阈值更宽松);超清档(Ultra/High)+ 4K/超宽屏
|
||||||
// 时按 min(screenWidth/4, 1280) 自适应放大,避免高分屏被压成无信息缩略图
|
// 时按 min(screenWidth/4, 1280) 自适应放大,避免高分屏被压成无信息缩略图
|
||||||
@@ -7783,14 +7889,24 @@ LRESULT CMy2015RemoteDlg::OnPreviewResponse(WPARAM /*wParam*/, LPARAM lParam)
|
|||||||
|
|
||||||
// ---------- 路径 1:循环快照(按 clientId 命中)----------
|
// ---------- 路径 1:循环快照(按 clientId 命中)----------
|
||||||
auto it = m_LoopTips.find(msg->clientId);
|
auto it = m_LoopTips.find(msg->clientId);
|
||||||
if (it != m_LoopTips.end()) {
|
if (it != m_LoopTips.end() &&
|
||||||
|
it->second.expectedReqId != 0 && hdr->reqId == it->second.expectedReqId)
|
||||||
|
{
|
||||||
|
// reqId 不命中时不直接 return,让流程继续走到缩略图分支 —— 兼容"刚打开循环窗
|
||||||
|
// 时还有上一笔缩略图请求在飞"的情况,防止 m_ThumbnailPending 条目被永久挂死。
|
||||||
LoopTipEntry& entry = it->second;
|
LoopTipEntry& entry = it->second;
|
||||||
// 校验 reqId:每条目独立计数,过滤过期/串扰响应
|
|
||||||
if (entry.expectedReqId == 0 || hdr->reqId != entry.expectedReqId) return 0;
|
|
||||||
if (!entry.tip || !::IsWindow(entry.tip->GetSafeHwnd())) return 0;
|
if (!entry.tip || !::IsWindow(entry.tip->GetSafeHwnd())) return 0;
|
||||||
|
|
||||||
if (dataOk) {
|
if (dataOk) {
|
||||||
entry.tip->SetImageFromJpeg(jpeg, hdr->bytes);
|
entry.tip->SetImageFromJpeg(jpeg, hdr->bytes);
|
||||||
|
// 顺手刷新列表缩略图(开循环窗的主机不再单独发请求,省一半流量)
|
||||||
|
if (m_ThumbnailCfg.Enabled) {
|
||||||
|
CacheThumbnail(msg->clientId, jpeg, hdr->bytes);
|
||||||
|
InvalidateHostRow(msg->clientId);
|
||||||
|
// 重置该主机的 due 时间,避免循环窗关掉后立刻又有缩略图请求
|
||||||
|
DWORD now = ::GetTickCount();
|
||||||
|
m_ThumbNextDueTick[msg->clientId] = now + (DWORD)m_ThumbnailCfg.RefreshIntervalSec * 1000;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 单帧失败不直接关窗,标"不可用",下一轮定时器再尝试
|
// 单帧失败不直接关窗,标"不可用",下一轮定时器再尝试
|
||||||
entry.tip->MarkPreviewUnavailable();
|
entry.tip->MarkPreviewUnavailable();
|
||||||
@@ -7798,7 +7914,18 @@ LRESULT CMy2015RemoteDlg::OnPreviewResponse(WPARAM /*wParam*/, LPARAM lParam)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 路径 2:既有单发流程 ----------
|
// ---------- 路径 2:列表缩略图(按 clientId 命中 in-flight 集合)----------
|
||||||
|
if (m_ThumbnailPending.count(msg->clientId)) {
|
||||||
|
m_ThumbnailPending.erase(msg->clientId);
|
||||||
|
if (dataOk) {
|
||||||
|
CacheThumbnail(msg->clientId, jpeg, hdr->bytes);
|
||||||
|
InvalidateHostRow(msg->clientId);
|
||||||
|
}
|
||||||
|
// 数据非 OK 也不重试,等下个周期;保留旧缩略(如有)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 路径 3:既有单发流程 ----------
|
||||||
// 序号校验:和当前期待的 reqId 不符 → 过期响应,丢弃
|
// 序号校验:和当前期待的 reqId 不符 → 过期响应,丢弃
|
||||||
if (m_PreviewReqId == 0 || hdr->reqId != m_PreviewReqId) return 0;
|
if (m_PreviewReqId == 0 || hdr->reqId != m_PreviewReqId) return 0;
|
||||||
if (!m_pPreviewTip || !::IsWindow(m_pPreviewTip->GetSafeHwnd())) return 0;
|
if (!m_pPreviewTip || !::IsWindow(m_pPreviewTip->GetSafeHwnd())) return 0;
|
||||||
@@ -7991,6 +8118,302 @@ LRESULT CMy2015RemoteDlg::OnLoopTipDestroyed(WPARAM /*wParam*/, LPARAM lParam)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 主机列表缩略图:实现 ==========
|
||||||
|
//
|
||||||
|
// 线程模型:与循环模式一致 —— m_HostThumbnails / m_ThumbNextDueTick /
|
||||||
|
// m_ThumbnailPending 全部仅 UI 线程访问。响应 PostMessage 经消息泵串行化派发。
|
||||||
|
//
|
||||||
|
// 缓存策略:响应到达即解码 + 等比缩放到 ThumbWidth × ThumbHeight 的 HBITMAP,
|
||||||
|
// 这样自绘时只需 BitBlt 一次,无 GDI+ 开销。设置变更(尺寸)会清空所有 HBITMAP,
|
||||||
|
// 下一轮请求自然按新尺寸重建。
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::LoadThumbnailSettingsFromCfg()
|
||||||
|
{
|
||||||
|
m_ThumbnailCfg.Enabled = THIS_CFG.GetInt("thumbnail", "Enabled", 1) != 0;
|
||||||
|
m_ThumbnailCfg.RefreshIntervalSec = THIS_CFG.GetInt("thumbnail", "RefreshIntervalSec", 30);
|
||||||
|
m_ThumbnailCfg.ThumbWidth = THIS_CFG.GetInt("thumbnail", "ThumbWidth", 60);
|
||||||
|
m_ThumbnailCfg.NetReqWidth = THIS_CFG.GetInt("thumbnail", "NetReqWidth", 120);
|
||||||
|
m_ThumbnailCfg.JpegQuality = THIS_CFG.GetInt("thumbnail", "JpegQuality", 60);
|
||||||
|
|
||||||
|
// Clamp 到安全范围(防 INI 被手工编错)
|
||||||
|
if (m_ThumbnailCfg.RefreshIntervalSec < 5) m_ThumbnailCfg.RefreshIntervalSec = 5;
|
||||||
|
if (m_ThumbnailCfg.RefreshIntervalSec > 300) m_ThumbnailCfg.RefreshIntervalSec = 300;
|
||||||
|
if (m_ThumbnailCfg.ThumbWidth < 40) m_ThumbnailCfg.ThumbWidth = 40;
|
||||||
|
if (m_ThumbnailCfg.ThumbWidth > 120) m_ThumbnailCfg.ThumbWidth = 120;
|
||||||
|
if (m_ThumbnailCfg.NetReqWidth < m_ThumbnailCfg.ThumbWidth)
|
||||||
|
m_ThumbnailCfg.NetReqWidth = m_ThumbnailCfg.ThumbWidth * 2;
|
||||||
|
if (m_ThumbnailCfg.NetReqWidth > 240) m_ThumbnailCfg.NetReqWidth = 240;
|
||||||
|
if (m_ThumbnailCfg.JpegQuality < 30) m_ThumbnailCfg.JpegQuality = 30;
|
||||||
|
if (m_ThumbnailCfg.JpegQuality > 85) m_ThumbnailCfg.JpegQuality = 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::SaveThumbnailSettingsToCfg() const
|
||||||
|
{
|
||||||
|
THIS_CFG.SetInt("thumbnail", "Enabled", m_ThumbnailCfg.Enabled ? 1 : 0);
|
||||||
|
THIS_CFG.SetInt("thumbnail", "RefreshIntervalSec", m_ThumbnailCfg.RefreshIntervalSec);
|
||||||
|
THIS_CFG.SetInt("thumbnail", "ThumbWidth", m_ThumbnailCfg.ThumbWidth);
|
||||||
|
THIS_CFG.SetInt("thumbnail", "NetReqWidth", m_ThumbnailCfg.NetReqWidth);
|
||||||
|
THIS_CFG.SetInt("thumbnail", "JpegQuality", m_ThumbnailCfg.JpegQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::ApplyThumbnailSettings()
|
||||||
|
{
|
||||||
|
// 任何已缓存的 HBITMAP 都是按"旧 ThumbWidth"渲染的,尺寸不再可信 —— 一律清空。
|
||||||
|
// 下一次响应到达时按新尺寸重建。带来的代价是切换尺寸后会有一个空窗期。
|
||||||
|
ClearAllThumbnailCache();
|
||||||
|
|
||||||
|
// 列宽 + 行高 跟着 ThumbWidth 走(高度 = 9/16 比例 + 上下各 2px 留白)
|
||||||
|
int thumbW = m_ThumbnailCfg.ThumbWidth;
|
||||||
|
int thumbH = thumbW * 9 / 16;
|
||||||
|
int rowH = thumbH + 4;
|
||||||
|
if (rowH < 20) rowH = 20;
|
||||||
|
|
||||||
|
if (m_CList_Online.GetSafeHwnd()) {
|
||||||
|
// 用 SetColumnVisible 控制 col 0 的存在感 —— 仅 SetColumn(width=0) 不够:
|
||||||
|
// CListCtrlEx::AdjustColumnWidths 会在窗口拉伸时按 m_Columns[i].Percent
|
||||||
|
// 重新分配宽度,让"已关闭"的列 0 再次得到约 5% 的可见宽度。
|
||||||
|
// SetColumnVisible 会把 m_Columns[0].Visible 置为 false,AdjustColumnWidths
|
||||||
|
// 看到不可见列直接给 0 且不计入 visiblePercent,把空间让给其它列。
|
||||||
|
m_CList_Online.SetColumnVisible(COL_THUMBNAIL, m_ThumbnailCfg.Enabled ? TRUE : FALSE);
|
||||||
|
if (m_ThumbnailCfg.Enabled) {
|
||||||
|
// 启用时,把宽度强制设回我们想要的 thumbW + 10,
|
||||||
|
// SetColumnVisible 内部 AdjustColumnWidths 是按 percent 算出来的可能不一样
|
||||||
|
LVCOLUMN col = {};
|
||||||
|
col.mask = LVCF_WIDTH;
|
||||||
|
col.cx = thumbW + 10;
|
||||||
|
m_CList_Online.SetColumn(COL_THUMBNAIL, &col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行高 ImageList 管理:
|
||||||
|
// - 开启缩略图 → 装 1×rowH 的 dummy ImageList(rowH ≈ 38 px 撑高行)
|
||||||
|
// - 关闭缩略图 → 装 1×1 的 dummy ImageList(行高 = max(字体高, 1) ≈ 字体原生高)
|
||||||
|
// 之所以不是 SetImageList(NULL) "完全拆掉":Win32 ListView 在 ImageList 由 X→NULL
|
||||||
|
// 时**不重测行高**(行高被锁死在最后一次有 ImageList 时的值),WM_SETFONT 等踢一脚
|
||||||
|
// 的招数也都不可靠。统一始终装一张 ImageList,只让"高度数值"变化 → ListView 每次都
|
||||||
|
// 重测行高。1×1 的 ImageList 用户感知不到(视觉上等同于无 ImageList)。
|
||||||
|
int targetRowH = m_ThumbnailCfg.Enabled ? rowH : 1;
|
||||||
|
if (targetRowH != m_thumbRowHeightApplied) {
|
||||||
|
m_CList_Online.SetImageList(NULL, LVSIL_SMALL);
|
||||||
|
if (m_thumbRowHeightImgList.GetSafeHandle()) {
|
||||||
|
m_thumbRowHeightImgList.DeleteImageList();
|
||||||
|
}
|
||||||
|
m_thumbRowHeightImgList.Create(1, targetRowH, ILC_COLOR | ILC_MASK, 1, 1);
|
||||||
|
m_CList_Online.SetImageList(&m_thumbRowHeightImgList, LVSIL_SMALL);
|
||||||
|
m_thumbRowHeightApplied = targetRowH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计时器:启用时按 1 秒固定 tick 节奏轮询(per-host 各自的 nextDueTick 决定真发送)
|
||||||
|
KillTimer(TIMER_THUMBNAIL_REFRESH);
|
||||||
|
if (m_ThumbnailCfg.Enabled) {
|
||||||
|
SetTimer(TIMER_THUMBNAIL_REFRESH, 1000, nullptr);
|
||||||
|
} else {
|
||||||
|
// 关闭时连"在飞"和"待发"也清掉,避免之后误派发
|
||||||
|
m_ThumbnailPending.clear();
|
||||||
|
m_ThumbNextDueTick.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发一次列表重绘,让列宽变化生效
|
||||||
|
if (m_CList_Online.GetSafeHwnd()) {
|
||||||
|
m_CList_Online.Invalidate(FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::ClearThumbnailCacheEntry(uint64_t clientID)
|
||||||
|
{
|
||||||
|
auto it = m_HostThumbnails.find(clientID);
|
||||||
|
if (it == m_HostThumbnails.end()) return;
|
||||||
|
if (it->second.bmp) ::DeleteObject(it->second.bmp);
|
||||||
|
m_HostThumbnails.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::ClearAllThumbnailCache()
|
||||||
|
{
|
||||||
|
for (auto& kv : m_HostThumbnails) {
|
||||||
|
if (kv.second.bmp) ::DeleteObject(kv.second.bmp);
|
||||||
|
}
|
||||||
|
m_HostThumbnails.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::InvalidateHostRow(uint64_t clientID)
|
||||||
|
{
|
||||||
|
if (!m_CList_Online.GetSafeHwnd()) return;
|
||||||
|
// 查 clientID -> m_HostList 索引 -> 当前过滤后的可见行
|
||||||
|
CLock L(m_cs);
|
||||||
|
auto it = m_ClientIndex.find(clientID);
|
||||||
|
if (it == m_ClientIndex.end()) return;
|
||||||
|
size_t hostIdx = it->second;
|
||||||
|
for (size_t fi = 0; fi < m_FilteredIndices.size(); ++fi) {
|
||||||
|
if (m_FilteredIndices[fi] == hostIdx) {
|
||||||
|
m_CList_Online.RedrawItems((int)fi, (int)fi);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把收到的 JPEG 解码 + 等比缩到 ThumbWidth × thumbH,存为 24bpp DIB。
|
||||||
|
// 失败时静默丢弃(保留旧缓存或保持空),下一轮重试。
|
||||||
|
void CMy2015RemoteDlg::CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes)
|
||||||
|
{
|
||||||
|
if (!jpeg || bytes == 0) return;
|
||||||
|
|
||||||
|
// GDI+ 解码(与 CPreviewTipWnd::SetImageFromJpeg 同模式)
|
||||||
|
HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, bytes);
|
||||||
|
if (!hMem) return;
|
||||||
|
void* p = ::GlobalLock(hMem);
|
||||||
|
if (!p) { ::GlobalFree(hMem); return; }
|
||||||
|
memcpy(p, jpeg, bytes);
|
||||||
|
::GlobalUnlock(hMem);
|
||||||
|
|
||||||
|
IStream* stream = nullptr;
|
||||||
|
if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &stream))) {
|
||||||
|
::GlobalFree(hMem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 注意:本 TU 顶部 `#define new DEBUG_NEW`(MFC 模式),而 Gdiplus::GdiplusBase::operator new
|
||||||
|
// 不接受 DEBUG_NEW 的 (size, file, line) 三参形式 → 局部撤宏,构造完再恢复。
|
||||||
|
#pragma push_macro("new")
|
||||||
|
#undef new
|
||||||
|
std::unique_ptr<Gdiplus::Bitmap> bmp(new Gdiplus::Bitmap(stream, FALSE));
|
||||||
|
#pragma pop_macro("new")
|
||||||
|
stream->Release(); // 接管 hMem 的释放
|
||||||
|
if (!bmp || bmp->GetLastStatus() != Gdiplus::Ok) return;
|
||||||
|
|
||||||
|
int dstW = m_ThumbnailCfg.ThumbWidth;
|
||||||
|
int dstH = dstW * 9 / 16;
|
||||||
|
if (dstW <= 0 || dstH <= 0) return;
|
||||||
|
|
||||||
|
// 渲染到一张 24bpp HBITMAP(DC 兼容 + DIB 友好)
|
||||||
|
HDC hScreen = ::GetDC(nullptr);
|
||||||
|
HDC hMemDC = ::CreateCompatibleDC(hScreen);
|
||||||
|
HBITMAP hbm = ::CreateCompatibleBitmap(hScreen, dstW, dstH);
|
||||||
|
::ReleaseDC(nullptr, hScreen);
|
||||||
|
if (!hMemDC || !hbm) {
|
||||||
|
if (hMemDC) ::DeleteDC(hMemDC);
|
||||||
|
if (hbm) ::DeleteObject(hbm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HBITMAP hbmOld = (HBITMAP)::SelectObject(hMemDC, hbm);
|
||||||
|
|
||||||
|
// 底色:让等比 letterbox 留白显眼一点(深灰)
|
||||||
|
RECT rcAll = { 0, 0, dstW, dstH };
|
||||||
|
HBRUSH hBrush = ::CreateSolidBrush(RGB(50, 50, 50));
|
||||||
|
::FillRect(hMemDC, &rcAll, hBrush);
|
||||||
|
::DeleteObject(hBrush);
|
||||||
|
|
||||||
|
// 等比缩放图像,居中放置(letterbox)
|
||||||
|
{
|
||||||
|
Gdiplus::Graphics g(hMemDC);
|
||||||
|
g.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
|
||||||
|
g.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
|
||||||
|
double iw = (double)bmp->GetWidth();
|
||||||
|
double ih = (double)bmp->GetHeight();
|
||||||
|
double sx = dstW / iw;
|
||||||
|
double sy = dstH / ih;
|
||||||
|
double s = sx < sy ? sx : sy;
|
||||||
|
int dw = (int)(iw * s + 0.5);
|
||||||
|
int dh = (int)(ih * s + 0.5);
|
||||||
|
int dx = (dstW - dw) / 2;
|
||||||
|
int dy = (dstH - dh) / 2;
|
||||||
|
g.DrawImage(bmp.get(), dx, dy, dw, dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
::SelectObject(hMemDC, hbmOld);
|
||||||
|
::DeleteDC(hMemDC);
|
||||||
|
|
||||||
|
// 替换/插入缓存
|
||||||
|
ClearThumbnailCacheEntry(clientID);
|
||||||
|
ThumbCacheEntry e;
|
||||||
|
e.bmp = hbm; e.w = dstW; e.h = dstH;
|
||||||
|
m_HostThumbnails[clientID] = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::SendThumbnailRequest(context* ctx)
|
||||||
|
{
|
||||||
|
if (!ctx) return;
|
||||||
|
if (++m_ThumbnailReqId == 0) m_ThumbnailReqId = 1;
|
||||||
|
SendScreenPreviewRequest(ctx, m_ThumbnailReqId,
|
||||||
|
(WORD)m_ThumbnailCfg.NetReqWidth,
|
||||||
|
(BYTE)m_ThumbnailCfg.JpegQuality);
|
||||||
|
m_ThumbnailPending.insert(ctx->GetClientID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主循环(每秒 tick 一次):扫描在线主机,触发到期 + 活跃的请求
|
||||||
|
void CMy2015RemoteDlg::TickThumbnailRefresh()
|
||||||
|
{
|
||||||
|
if (!m_ThumbnailCfg.Enabled) return;
|
||||||
|
|
||||||
|
DWORD now = ::GetTickCount();
|
||||||
|
DWORD intervalMs = (DWORD)m_ThumbnailCfg.RefreshIntervalSec * 1000;
|
||||||
|
|
||||||
|
// 拷一份 clientId 列表,避免在 m_cs 内长时间持锁
|
||||||
|
std::vector<uint64_t> ids;
|
||||||
|
std::vector<uint64_t> loopOpen;
|
||||||
|
{
|
||||||
|
CLock L(m_cs);
|
||||||
|
ids.reserve(m_HostList.size());
|
||||||
|
for (auto* c : m_HostList) {
|
||||||
|
if (c) ids.push_back(c->GetClientID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 同步:开着循环窗口的主机本轮跳过 —— 循环窗的响应会顺手填缓存(OnPreviewResponse)
|
||||||
|
for (const auto& kv : m_LoopTips) loopOpen.push_back(kv.first);
|
||||||
|
std::set<uint64_t> loopSet(loopOpen.begin(), loopOpen.end());
|
||||||
|
|
||||||
|
// 限速:每跳最多发 8 台请求,避免初次铺满时瞬时拥挤
|
||||||
|
const int kMaxPerTick = 8;
|
||||||
|
int sent = 0;
|
||||||
|
for (uint64_t cid : ids) {
|
||||||
|
if (sent >= kMaxPerTick) break;
|
||||||
|
|
||||||
|
// 已在飞,不重复
|
||||||
|
if (m_ThumbnailPending.count(cid)) continue;
|
||||||
|
// 开着循环窗,跳过
|
||||||
|
if (loopSet.count(cid)) continue;
|
||||||
|
|
||||||
|
// 到期判定(首次出现时也算到期:插入 due=now)
|
||||||
|
auto itDue = m_ThumbNextDueTick.find(cid);
|
||||||
|
if (itDue == m_ThumbNextDueTick.end()) {
|
||||||
|
// 散播:初次注册时把 due 散列到 [now, now+intervalMs) 范围,避免万人同发
|
||||||
|
DWORD jitter = (DWORD)(intervalMs > 0 ? (cid % intervalMs) : 0);
|
||||||
|
m_ThumbNextDueTick[cid] = now + jitter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((LONG)(itDue->second - now) > 0) continue; // 未到期
|
||||||
|
|
||||||
|
// 三道门:支持能力位 + 非 Locked/Inactive
|
||||||
|
bool ok = false;
|
||||||
|
context* ctx = nullptr;
|
||||||
|
{
|
||||||
|
CLock L(m_cs);
|
||||||
|
ctx = FindHostNoLock(cid);
|
||||||
|
if (ctx && ctx->SupportsScreenPreview()) {
|
||||||
|
CString a = ctx->GetClientData(ONLINELIST_LOGINTIME);
|
||||||
|
bool isIdleOrLocked =
|
||||||
|
a.Find(_T("Locked")) == 0 || a.Find(_T("Inactive")) == 0;
|
||||||
|
if (!isIdleOrLocked) ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
// 不活跃 / 不支持:仍把 due 推到下一周期,避免每 tick 都判一次
|
||||||
|
itDue->second = now + intervalMs;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送(再次在锁内拿 ctx,因为 ok 判定后到此处可能被下线)
|
||||||
|
{
|
||||||
|
CLock L(m_cs);
|
||||||
|
ctx = FindHostNoLock(cid);
|
||||||
|
if (ctx) {
|
||||||
|
SendThumbnailRequest(ctx);
|
||||||
|
++sent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itDue->second = now + intervalMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CMy2015RemoteDlg::OnOnlineUnauthorize()
|
void CMy2015RemoteDlg::OnOnlineUnauthorize()
|
||||||
{
|
{
|
||||||
@@ -8390,11 +8813,135 @@ void CMy2015RemoteDlg::OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LeaveCriticalSection(&m_cs);
|
LeaveCriticalSection(&m_cs);
|
||||||
if (!ctx) return;
|
if (ctx) {
|
||||||
int r = m_ClientMap->GetClientMapInteger(ctx->GetClientID(), MAP_LEVEL);
|
int r = m_ClientMap->GetClientMapInteger(ctx->GetClientID(), MAP_LEVEL);
|
||||||
if (r >= 1) pLVCD->clrText = RGB(0, 0, 255); // 字体蓝
|
if (r >= 1) pLVCD->clrText = RGB(0, 0, 255); // 字体蓝
|
||||||
if (r >= 2) pLVCD->clrText = RGB(255, 0, 0); // 字体红
|
if (r >= 2) pLVCD->clrText = RGB(255, 0, 0); // 字体红
|
||||||
if (r >= 3) pLVCD->clrTextBk = RGB(255, 160, 160); // 背景红
|
if (r >= 3) pLVCD->clrTextBk = RGB(255, 160, 160); // 背景红
|
||||||
|
}
|
||||||
|
// 启用缩略图时:
|
||||||
|
// 1) 请求 NOTIFYSUBITEMDRAW —— 拿到对每个 subitem 的回调权
|
||||||
|
// 在 col 0 上返回 CDRF_SKIPDEFAULT 让系统**不画** col 0(消除空白闪烁源头)
|
||||||
|
// 2) 请求 NOTIFYPOSTPAINT —— 在 POSTPAINT 阶段用格内 DB 把缩略图画进去
|
||||||
|
// 双管齐下:如果 SUBITEM 在 col 0 不触发,POSTPAINT 仍能兜底(退回旧行为);
|
||||||
|
// 如果 SUBITEM 在 col 0 触发了,系统从此不再写 col 0,闪烁源被掐死。
|
||||||
|
*pResult = m_ThumbnailCfg.Enabled
|
||||||
|
? (CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT)
|
||||||
|
: 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CDDS_ITEMPREPAINT | CDDS_SUBITEM: {
|
||||||
|
if (!m_ThumbnailCfg.Enabled) { *pResult = CDRF_DODEFAULT; return; }
|
||||||
|
// 仅当系统真的回调了 col 0 的 subitem 阶段时生效。CDRF_SKIPDEFAULT 让系统
|
||||||
|
// 跳过自己对 col 0 的所有绘制(label / 选中条延伸 / 焦点框等),后续完全
|
||||||
|
// 由 POSTPAINT 接管,从而消除"空白 col 0 一闪"的视觉污染。
|
||||||
|
if (pLVCD->iSubItem == COL_THUMBNAIL) {
|
||||||
|
*pResult = CDRF_SKIPDEFAULT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*pResult = CDRF_DODEFAULT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CDDS_ITEMPOSTPAINT: {
|
||||||
|
if (!m_ThumbnailCfg.Enabled) return;
|
||||||
|
|
||||||
|
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
|
||||||
|
uint64_t clientID = 0;
|
||||||
|
bool supportsPreview = false; // 区分"暂未到帧"和"协议不支持"两种占位
|
||||||
|
{
|
||||||
|
CLock L(m_cs);
|
||||||
|
if (nRow >= 0 && nRow < (int)m_FilteredIndices.size()) {
|
||||||
|
size_t realIdx = m_FilteredIndices[nRow];
|
||||||
|
if (realIdx < m_HostList.size() && m_HostList[realIdx]) {
|
||||||
|
clientID = m_HostList[realIdx]->GetClientID();
|
||||||
|
supportsPreview = m_HostList[realIdx]->SupportsScreenPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clientID == 0) return;
|
||||||
|
bool isSelected = (m_CList_Online.GetItemState(nRow, LVIS_SELECTED) & LVIS_SELECTED) != 0;
|
||||||
|
|
||||||
|
// 用 GetItemRect + GetColumnWidth(0) 算 col 0 的矩形 —— 比 pLVCD->nmcd.rc 更
|
||||||
|
// 可靠:后者在 POSTPAINT 阶段对虚拟列表有时返回空 / 行片段。
|
||||||
|
RECT rcRow = {};
|
||||||
|
if (!m_CList_Online.GetItemRect(nRow, &rcRow, LVIR_BOUNDS)) return;
|
||||||
|
int col0w = m_CList_Online.GetColumnWidth(0);
|
||||||
|
if (col0w <= 0) return;
|
||||||
|
int cellW = col0w;
|
||||||
|
int cellH = rcRow.bottom - rcRow.top;
|
||||||
|
if (cellW <= 0 || cellH <= 0) return;
|
||||||
|
RECT rcCell = { rcRow.left, rcRow.top, rcRow.left + cellW, rcRow.bottom };
|
||||||
|
|
||||||
|
HDC hdcDst = pLVCD->nmcd.hdc;
|
||||||
|
|
||||||
|
// 格内双缓冲:把整格(底色 + 占位 / 缩略图)全部画到离屏 HBITMAP 上,
|
||||||
|
// 最后一次 BitBlt 落地,保证从用户视角看 col 0 只有"旧内容 → 新内容"两种态。
|
||||||
|
// 解决 LVS_OWNERDATA + LVS_EX_DOUBLEBUFFER + CDDS_ITEMPOSTPAINT 这个组合下
|
||||||
|
// POSTPAINT 偶发绕开 back buffer 写屏导致的占位"一闪"。
|
||||||
|
HDC hMemDC = ::CreateCompatibleDC(hdcDst);
|
||||||
|
HBITMAP hbmCell = ::CreateCompatibleBitmap(hdcDst, cellW, cellH);
|
||||||
|
if (!hMemDC || !hbmCell) {
|
||||||
|
if (hMemDC) ::DeleteDC(hMemDC);
|
||||||
|
if (hbmCell) ::DeleteObject(hbmCell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HBITMAP hbmOld = (HBITMAP)::SelectObject(hMemDC, hbmCell);
|
||||||
|
|
||||||
|
// 全部画在 (0,0)-(cellW,cellH) 局部坐标
|
||||||
|
RECT rcLocal = { 0, 0, cellW, cellH };
|
||||||
|
HBRUSH hBg = ::GetSysColorBrush(isSelected ? COLOR_HIGHLIGHT : COLOR_WINDOW);
|
||||||
|
::FillRect(hMemDC, &rcLocal, hBg);
|
||||||
|
|
||||||
|
auto it = m_HostThumbnails.find(clientID);
|
||||||
|
bool hasBmp = (it != m_HostThumbnails.end() && it->second.bmp);
|
||||||
|
if (!hasBmp) {
|
||||||
|
// 占位文本直接浮在 cell 底色上,不再画内嵌灰框 —— 与表格背景融合。
|
||||||
|
// - 支持预览但还没收到帧 → "…"
|
||||||
|
// - 不支持预览的老客户端 → "N/A"
|
||||||
|
// - 选中态:用系统 HIGHLIGHTTEXT 保证白底/蓝底下都清晰可读
|
||||||
|
HFONT hListFont = (HFONT)m_CList_Online.SendMessage(WM_GETFONT, 0, 0);
|
||||||
|
HFONT hOldFont = hListFont ? (HFONT)::SelectObject(hMemDC, hListFont) : nullptr;
|
||||||
|
::SetBkMode(hMemDC, TRANSPARENT);
|
||||||
|
::SetTextColor(hMemDC, ::GetSysColor(
|
||||||
|
isSelected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
|
||||||
|
const wchar_t* placeholder = supportsPreview ? L"…" : L"N/A";
|
||||||
|
::DrawTextW(hMemDC, placeholder, -1, &rcLocal,
|
||||||
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
if (hOldFont) ::SelectObject(hMemDC, hOldFont);
|
||||||
|
} else {
|
||||||
|
const ThumbCacheEntry& te = it->second;
|
||||||
|
int maxW = cellW - 2, maxH = cellH - 2;
|
||||||
|
if (maxW < 4) maxW = 4;
|
||||||
|
if (maxH < 4) maxH = 4;
|
||||||
|
|
||||||
|
double sx = (double)maxW / te.w;
|
||||||
|
double sy = (double)maxH / te.h;
|
||||||
|
double s = sx < sy ? sx : sy;
|
||||||
|
int dw = (int)(te.w * s + 0.5);
|
||||||
|
int dh = (int)(te.h * s + 0.5);
|
||||||
|
int dx = (cellW - dw) / 2;
|
||||||
|
int dy = (cellH - dh) / 2;
|
||||||
|
|
||||||
|
HDC hThumbDC = ::CreateCompatibleDC(hMemDC);
|
||||||
|
HBITMAP hThumbOld = (HBITMAP)::SelectObject(hThumbDC, te.bmp);
|
||||||
|
::SetStretchBltMode(hMemDC, HALFTONE);
|
||||||
|
::SetBrushOrgEx(hMemDC, 0, 0, nullptr);
|
||||||
|
::StretchBlt(hMemDC, dx, dy, dw, dh, hThumbDC, 0, 0, te.w, te.h, SRCCOPY);
|
||||||
|
::SelectObject(hThumbDC, hThumbOld);
|
||||||
|
::DeleteDC(hThumbDC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性原子刷屏
|
||||||
|
::BitBlt(hdcDst, rcCell.left, rcCell.top, cellW, cellH, hMemDC, 0, 0, SRCCOPY);
|
||||||
|
|
||||||
|
::SelectObject(hMemDC, hbmOld);
|
||||||
|
::DeleteObject(hbmCell);
|
||||||
|
::DeleteDC(hMemDC);
|
||||||
|
|
||||||
|
*pResult = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9438,6 +9985,18 @@ void CMy2015RemoteDlg::OnParamFileV2()
|
|||||||
_TR("提示"), MB_ICONINFORMATION);
|
_TR("提示"), MB_ICONINFORMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMy2015RemoteDlg::OnParamThumbnailPreview()
|
||||||
|
{
|
||||||
|
m_ThumbnailCfg.Enabled = !m_ThumbnailCfg.Enabled;
|
||||||
|
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||||||
|
if (SubMenu)
|
||||||
|
SubMenu->CheckMenuItem(ID_PARAM_THUMBNAIL_PREVIEW,
|
||||||
|
m_ThumbnailCfg.Enabled ? MF_CHECKED : MF_UNCHECKED);
|
||||||
|
SaveThumbnailSettingsToCfg();
|
||||||
|
ApplyThumbnailSettings(); // 内部处理列宽/行高/定时器/缓存切换
|
||||||
|
Mprintf("主机列表预览图: %s\n", m_ThumbnailCfg.Enabled ? "启用" : "禁用");
|
||||||
|
}
|
||||||
|
|
||||||
void CMy2015RemoteDlg::OnParamRunAsUser()
|
void CMy2015RemoteDlg::OnParamRunAsUser()
|
||||||
{
|
{
|
||||||
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
CMenu* SubMenu = m_MainMenu.GetSubMenu(2);
|
||||||
|
|||||||
@@ -264,6 +264,47 @@ public:
|
|||||||
void TickLoopTips();
|
void TickLoopTips();
|
||||||
void SendLoopRequest(uint64_t clientID, LoopTipEntry& entry);
|
void SendLoopRequest(uint64_t clientID, LoopTipEntry& entry);
|
||||||
afx_msg LRESULT OnLoopTipDestroyed(WPARAM wParam, LPARAM lParam);
|
afx_msg LRESULT OnLoopTipDestroyed(WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
// ===== 主机列表缩略图(在线列表第 0 列) =====
|
||||||
|
// 设计文档:纯主控端 UI 偏好,不进 MasterSettings 协议包。持久化走 THIS_CFG
|
||||||
|
// [thumbnail] 节。所有运行时状态仅 UI 线程访问。
|
||||||
|
struct ThumbnailSettings {
|
||||||
|
bool Enabled = true;
|
||||||
|
int RefreshIntervalSec = 30; // 单台主机的刷新周期
|
||||||
|
int ThumbWidth = 60; // 列表内显示宽度(高 = 9w/16)
|
||||||
|
int NetReqWidth = 120; // 网络请求的源宽(HiDPI 余量,>= ThumbWidth)
|
||||||
|
int JpegQuality = 60; // 1..100
|
||||||
|
};
|
||||||
|
ThumbnailSettings m_ThumbnailCfg;
|
||||||
|
|
||||||
|
struct ThumbCacheEntry {
|
||||||
|
HBITMAP bmp = nullptr; // 预先缩到显示尺寸的 24bpp DIB,BitBlt 友好
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
};
|
||||||
|
std::map<uint64_t, ThumbCacheEntry> m_HostThumbnails; // 缩略图缓存
|
||||||
|
std::map<uint64_t, DWORD> m_ThumbNextDueTick; // 下次到期 GetTickCount() 时间
|
||||||
|
std::set<uint64_t> m_ThumbnailPending; // 在飞的 clientId 集合
|
||||||
|
WORD m_ThumbnailReqId = 0; // 缩略图专用 reqId(与 m_PreviewReqId / m_LoopReqId 解耦)
|
||||||
|
|
||||||
|
// 行高占位 ImageList:CListCtrl 没有 SetRowHeight,标准做法是装一个 1×rowH 的
|
||||||
|
// 空 ImageList 强行撑高行。关闭缩略图时换回 1×default(或 detach),让行高回缩。
|
||||||
|
CImageList m_thumbRowHeightImgList;
|
||||||
|
int m_thumbRowHeightApplied = 0; // 已应用的行高(避免重复创建)
|
||||||
|
|
||||||
|
// 一次性配置应用入口:加载/重新加载 m_ThumbnailCfg 后调用,处理:
|
||||||
|
// - 启动/停止 TIMER_THUMBNAIL_REFRESH
|
||||||
|
// - 列宽 / 行高 调整
|
||||||
|
// - 尺寸变化时丢弃旧缓存 HBITMAP
|
||||||
|
void ApplyThumbnailSettings();
|
||||||
|
void LoadThumbnailSettingsFromCfg(); // 从 THIS_CFG 读到 m_ThumbnailCfg
|
||||||
|
void SaveThumbnailSettingsToCfg() const; // m_ThumbnailCfg 落盘到 THIS_CFG
|
||||||
|
void TickThumbnailRefresh(); // TIMER_THUMBNAIL_REFRESH 主循环
|
||||||
|
void SendThumbnailRequest(class context* ctx);// 发一次缩略图请求
|
||||||
|
void CacheThumbnail(uint64_t clientID, const BYTE* jpeg, size_t bytes);
|
||||||
|
void ClearThumbnailCacheEntry(uint64_t clientID);
|
||||||
|
void ClearAllThumbnailCache();
|
||||||
|
void InvalidateHostRow(uint64_t clientID); // 仅重绘对应行
|
||||||
// 记录 clientID(心跳更新)
|
// 记录 clientID(心跳更新)
|
||||||
std::set<uint64_t> m_DirtyClients;
|
std::set<uint64_t> m_DirtyClients;
|
||||||
// 待处理的上线/下线事件(批量更新减少闪烁)
|
// 待处理的上线/下线事件(批量更新减少闪烁)
|
||||||
@@ -468,6 +509,8 @@ public:
|
|||||||
afx_msg void OnWhatIsThis();
|
afx_msg void OnWhatIsThis();
|
||||||
afx_msg void OnOnlineAuthorize();
|
afx_msg void OnOnlineAuthorize();
|
||||||
void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
|
void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||||
|
// 单击:缩略图列(COL_THUMBNAIL)命中时弹出循环监视窗口;其余列保持默认行为
|
||||||
|
afx_msg void OnListSingleClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||||
// 屏幕预览:依 ctx 最近 RTT + 屏幕分辨率挑参数;4K/超宽屏在 LAN 档自适应放大
|
// 屏幕预览:依 ctx 最近 RTT + 屏幕分辨率挑参数;4K/超宽屏在 LAN 档自适应放大
|
||||||
void ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const;
|
void ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const;
|
||||||
// 发起预览请求;reqId 应与 m_PreviewReqId 同步
|
// 发起预览请求;reqId 应与 m_PreviewReqId 同步
|
||||||
@@ -524,6 +567,7 @@ public:
|
|||||||
afx_msg void OnParamEnableLog();
|
afx_msg void OnParamEnableLog();
|
||||||
afx_msg void OnParamPrivacyWallpaper();
|
afx_msg void OnParamPrivacyWallpaper();
|
||||||
afx_msg void OnParamFileV2();
|
afx_msg void OnParamFileV2();
|
||||||
|
afx_msg void OnParamThumbnailPreview();
|
||||||
afx_msg void OnParamRunAsUser();
|
afx_msg void OnParamRunAsUser();
|
||||||
void ProxyClientTcpPort(bool isStandard, bool autoRun=false);
|
void ProxyClientTcpPort(bool isStandard, bool autoRun=false);
|
||||||
afx_msg void OnProxyPort();
|
afx_msg void OnProxyPort();
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ void CPluginSettingsDlg::LoadPluginsToList()
|
|||||||
m_listPlugins.DeleteAllItems();
|
m_listPlugins.DeleteAllItems();
|
||||||
|
|
||||||
const char* runTypeNames[] = { "Shellcode", "内存DLL" };
|
const char* runTypeNames[] = { "Shellcode", "内存DLL" };
|
||||||
const char* modeNames[] = { "不自动执行", "启动执行", "每日定时", "每周定时" };
|
const char* modeNames[] = { "不自动执行", "启动执行", "每日定时", "每周定时", "每月定时", "每年定时", "关闭执行", };
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (const auto& dll : m_DllList) {
|
for (const auto& dll : m_DllList) {
|
||||||
@@ -132,8 +132,8 @@ void CPluginSettingsDlg::LoadPluginsToList()
|
|||||||
int runType = cfg ? cfg->RunType : info->RunType;
|
int runType = cfg ? cfg->RunType : info->RunType;
|
||||||
int mode = cfg ? cfg->Mode : info->Schedule.Mode;
|
int mode = cfg ? cfg->Mode : info->Schedule.Mode;
|
||||||
|
|
||||||
m_listPlugins.SetItemText(index, 2, _TR(runTypeNames[runType < 2 ? runType : 0]));
|
m_listPlugins.SetItemText(index, 2, _TR(runTypeNames[runType < RUNTYPE_MAX ? runType : MEMORYDLL]));
|
||||||
m_listPlugins.SetItemText(index, 3, _TR(modeNames[mode < 4 ? mode : 0]));
|
m_listPlugins.SetItemText(index, 3, _TR(modeNames[mode < SCH_MODE_MAX ? mode : SCH_MODE_NONE]));
|
||||||
m_listPlugins.SetItemText(index, 4, CString(info->Md5));
|
m_listPlugins.SetItemText(index, 4, CString(info->Md5));
|
||||||
|
|
||||||
m_listPlugins.SetItemData(index, (DWORD_PTR)dll);
|
m_listPlugins.SetItemData(index, (DWORD_PTR)dll);
|
||||||
@@ -172,9 +172,9 @@ void CPluginSettingsDlg::UpdateSelectedPluginInfo()
|
|||||||
unsigned int interval = cfg ? cfg->Interval : info->Schedule.Config.Startup.Interval;
|
unsigned int interval = cfg ? cfg->Interval : info->Schedule.Config.Startup.Interval;
|
||||||
unsigned char maxCount = cfg ? cfg->MaxCount : info->Schedule.MaxCount;
|
unsigned char maxCount = cfg ? cfg->MaxCount : info->Schedule.MaxCount;
|
||||||
|
|
||||||
m_comboRunType.SetCurSel(runType < 2 ? runType : 0);
|
m_comboRunType.SetCurSel(runType < RUNTYPE_MAX ? runType : MEMORYDLL);
|
||||||
m_comboCallType.SetCurSel(callType < 4 ? callType : 0);
|
m_comboCallType.SetCurSel(callType < CALLTYPE_MAX ? callType : CALLTYPE_IOCPTHREAD);
|
||||||
m_comboMode.SetCurSel(mode < 4 ? mode : 0);
|
m_comboMode.SetCurSel(mode < SCH_MODE_MAX ? mode : SCH_MODE_NONE);
|
||||||
|
|
||||||
CString str;
|
CString str;
|
||||||
str.Format(_T("%u"), interval);
|
str.Format(_T("%u"), interval);
|
||||||
|
|||||||
@@ -1845,3 +1845,5 @@ IOCP
|
|||||||
有 %d 个主机不支持屏幕预览,已跳过=%d host(s) do not support screen preview, skipped
|
有 %d 个主机不支持屏幕预览,已跳过=%d host(s) do not support screen preview, skipped
|
||||||
播放快照=Play Snapshot
|
播放快照=Play Snapshot
|
||||||
快照=Snapshot
|
快照=Snapshot
|
||||||
|
预览=Preview
|
||||||
|
主机列表预览图=Host List Thumbnails
|
||||||
|
|||||||
@@ -1836,3 +1836,5 @@ IOCP
|
|||||||
有 %d 个主机不支持屏幕预览,已跳过=有 %d 個主機不支援螢幕預覽,已跳過
|
有 %d 个主机不支持屏幕预览,已跳过=有 %d 個主機不支援螢幕預覽,已跳過
|
||||||
播放快照=播放快照
|
播放快照=播放快照
|
||||||
快照=快照
|
快照=快照
|
||||||
|
预览=預覽
|
||||||
|
主机列表预览图=主機列表預覽圖
|
||||||
|
|||||||
@@ -978,6 +978,7 @@
|
|||||||
#define ID_TRIGGER_SETTINGS 33048
|
#define ID_TRIGGER_SETTINGS 33048
|
||||||
#define ID_33048 33048
|
#define ID_33048 33048
|
||||||
#define ID_SCREENPREVIEW_LOOP 33049
|
#define ID_SCREENPREVIEW_LOOP 33049
|
||||||
|
#define ID_PARAM_THUMBNAIL_PREVIEW 33050
|
||||||
#define ID_EXIT_FULLSCREEN 40001
|
#define ID_EXIT_FULLSCREEN 40001
|
||||||
|
|
||||||
// Next default values for new objects
|
// Next default values for new objects
|
||||||
@@ -985,7 +986,7 @@
|
|||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
#define _APS_NEXT_RESOURCE_VALUE 377
|
#define _APS_NEXT_RESOURCE_VALUE 377
|
||||||
#define _APS_NEXT_COMMAND_VALUE 33050
|
#define _APS_NEXT_COMMAND_VALUE 33051
|
||||||
#define _APS_NEXT_CONTROL_VALUE 2542
|
#define _APS_NEXT_CONTROL_VALUE 2542
|
||||||
#define _APS_NEXT_SYMED_VALUE 105
|
#define _APS_NEXT_SYMED_VALUE 105
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user