12.2 实现键盘模拟按键

本节将向读者介绍如何使用键盘鼠标操控模拟技术,键盘鼠标操控模拟技术是一种非常实用的技术,可以自动化执行一些重复性的任务,提高工作效率,在Windows系统下,通过使用各种键盘鼠标控制函数实现动态捕捉和模拟特定功能的操作。

键盘鼠标的模拟是实现自动化的必备流程,通常我们可以使用keybd_event()实现对键盘的击键模拟,使用SetCursorPos()实现对鼠标的模拟,使用两者的配合读者可以很容易的实现对键盘鼠标的控制,本节将依次封装实现,模拟键盘鼠标控制功能,读者可根据自己的实际需求选用不同的函数片段。

12.2.1 模拟键盘按键

模拟按键的核心功能是通过调用keybd_event()函数实现的,如下是这段代码的完整实现,首先MySetKeyBig()函数该函数用于设置键盘状态是否为大小写,用户可以传入一个状态值来设置当前输入法大小写模式,MyAnalogKey()函数用于实现模拟键盘按键,该函数接收一个英文字符串,并自动实现击键操作,代码实现并不复杂,读者可自行测试功能。

#include <windows.h>
#include <iostream>

using namespace std;

// 设置键盘大小写状态 为TRUE则切换大写状态,否则切换小写状态
VOID MySetKeyBig(BOOL big = FALSE)
{
// 判断键盘CapsLock键是否开启状态,开启状态则为大写,否则为小写
if (GetKeyState(VK_CAPITAL))
{
// 如果当前键盘状态为大写,要求改小写,则模拟按键CapsLock切换状态
if (!big)
{
keybd_event(VK_CAPITAL, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event(VK_CAPITAL, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}

std::cout << "[键盘状态] " << "切换大写" << std::endl;
}
else
{
// 如果当前键盘状态为小写,要求改大写,则模拟按键CapsLock切换状态
if (big)
{
keybd_event(VK_CAPITAL, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event(VK_CAPITAL, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}

std::cout << "[键盘状态] " << "切换小写" << std::endl;
}
}

// 模拟键盘按键
VOID MyAnalogKey(char* str)
{
int iLen = 0;
char* tmp = NULL;
INPUT* keys = NULL;
BOOL bOldState = FALSE;

// 保存此操作前的键盘状态
bOldState = (BOOL)GetKeyState(VK_CAPITAL);
iLen = lstrlen(str);
tmp = (char*)malloc(iLen);
memmove(tmp, str, iLen);
for (int i = 0; i < iLen; i++)
{
// 某些符号非直属键盘按键,这里只过滤转换两个,以后用到其它字符自行添加过滤
if (tmp[i] == ' ')
{
// 产生一个击键消息步骤:按下->抬起
keybd_event(VK_SPACE, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event(VK_SPACE, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}
else if (tmp[i] == ',')
{
keybd_event(VK_OEM_COMMA, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event(VK_OEM_COMMA, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}
else if (tmp[i] >= 'a' && tmp[i] <= 'z')
{
// 根据字符大小写切换键盘大小写状态
MySetKeyBig(0);
// keybd_event只识别大写
keybd_event((BYTE)tmp[i] - 32, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event((BYTE)tmp[i] - 32, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}
else if ((tmp[i] >= 'A' && tmp[i] <= 'Z') || (tmp[i] >= '0' && tmp[i] <= '9'))
{
MySetKeyBig(1);
keybd_event((BYTE)tmp[i], NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event((BYTE)tmp[i], NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}
else
{
keybd_event((BYTE)tmp[i] + 64, NULL, KEYEVENTF_EXTENDEDKEY | 0, NULL);
keybd_event((BYTE)tmp[i] + 64, NULL, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, NULL);
}
Sleep(50);
}
// 恢复此操作之前的键盘状态
MySetKeyBig(bOldState);
free(tmp);
}

int main(int argc, char *argv[])
{
Sleep(5000);
MyAnalogKey((char*)"Love life , Love LyShark WelCome LyShark Cpp Home ...");

system("pause");
return 0;
}

读者可自行编译并运行上述代码片段,将光标移动到记事本中,等待五秒钟,则会依次敲击如下所示的键盘按键;

12.2.2 设置窗体最大化

如下代码实现了设置一个窗体置顶并将该窗体最大化显示的效果,该代码实现原理是通过使用EnumWindows函数传递一个回调函数,实现对特定窗体的枚举,当找到对应窗体句柄后则将该窗体句柄传递给global_hwnd全局句柄中,当获取到Google浏览器句柄之后则通过GetSystemMetrics函数得到当前全屏窗体的像素比,通过调用SetWindowPos可将一个窗体设置为置顶显示,最后可调用SendMessage函数向特定窗体句柄发送最大化消息,使其填充满整个屏幕,代码如下所示;

#include <iostream>
#include <windows.h>

using namespace std;

HWND global_hwnd = 0;

// 将字符串逆序
char * Reverse(char str[])
{
int n = strlen(str);
int i;
char temp;
for (i = 0; i < (n / 2); i++)
{
temp = str[i];
str[i] = str[n - i - 1];
str[n - i - 1] = temp;
}
return str;
}

// 窗体枚举回调函数
BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam)
{
char WindowName[MAXBYTE] = { 0 };
DWORD ThreadId, ProcessId = 0;

GetWindowText(hwnd, WindowName, sizeof(WindowName));
ThreadId = GetWindowThreadProcessId(hwnd, &ProcessId);
printf("句柄: %-9d --> 线程ID: %-6d --> 进程ID: %-6d --> 名称: %s \n", hwnd, ThreadId, ProcessId, WindowName);

// 首先逆序输出字符串,然后比较前13个字符
if (strncmp(Reverse(WindowName), "emorhC elgooG", 13) == 0)
{
global_hwnd = hwnd;
}
return TRUE;
}

int main(int argc, char* argv[])
{
// 枚举Google浏览器句柄
EnumWindows(lpEnumFunc, 0);
std::cout << "浏览器句柄: " << global_hwnd << std::endl;

if (global_hwnd != 0)
{
// 获取系统屏幕宽度与高度 (像素)
int cx = GetSystemMetrics(SM_CXSCREEN);
int cy = GetSystemMetrics(SM_CYSCREEN);
std::cout << "屏幕X: " << cx << " 屏幕Y: " << cy << std::endl;

// 传入指定的HWND句柄
HWND hForeWnd = GetForegroundWindow();
DWORD dwCurID = GetCurrentThreadId();
DWORD dwForeID = GetWindowThreadProcessId(hForeWnd, NULL);
AttachThreadInput(dwCurID, dwForeID, TRUE);

// 将该窗体呼出到顶层
ShowWindow(global_hwnd, SW_SHOWNORMAL);
SetWindowPos(global_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetWindowPos(global_hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetForegroundWindow(global_hwnd);
AttachThreadInput(dwCurID, dwForeID, FALSE);

// 发送最大化消息
SendMessage(global_hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0); // 最大化
// SendMessage(global_hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); // 最小化
// SendMessage(global_hwnd, WM_SYSCOMMAND, SC_CLOSE, 0); // 关闭
}

system("pause");
return 0;
}

读者可自行编译并运行上述程序,此时会将谷歌浏览器全屏并置顶显示,输出信息如下图所示;

12.2.3 读取与设置剪辑板

读取与设置剪辑版可用于对数据的拷贝与设置,调用setClipbar函数并传入一段字符串可实现将传入字符串拷贝到剪辑版的功能,使用getClipBoardValue则可实现读取剪辑版中的内容到程序内。

#include <iostream>
#include <windows.h>
#include <time.h>

// 将字符串写入到剪切板
BOOL setClipbar(const char* data)
{
int contentSize = strlen(data) + 1;
HGLOBAL hMemory; LPTSTR lpMemory;

// 打开剪切板
if (!OpenClipboard(NULL))
{
return FALSE;
}
// 清空剪切板
if (!EmptyClipboard())
{
return FALSE;
}
// 对剪切板分配内存
if (!(hMemory = GlobalAlloc(GMEM_MOVEABLE, contentSize)))
{
return FALSE;
}
// 锁定内存区域
if (!(lpMemory = (LPTSTR)GlobalLock(hMemory)))
{
return FALSE;
}

// 复制数据到内存区域,解除内存锁定
memcpy_s(lpMemory, contentSize, data, contentSize);
GlobalUnlock(hMemory);

// 设置剪切板数据
if (!SetClipboardData(CF_TEXT, hMemory))
{
return FALSE;
}

// std::cout << "已复制: " << data << " 长度: " << contentSize << std::endl;
return CloseClipboard();
}

// 获取剪切板内容
char* getClipBoardValue()
{
// 初始化
char* url, *pData;
size_t length;

// 打开剪切板
if (!OpenClipboard(NULL))
{
return FALSE;
}

// 获取剪切板内的数据
HANDLE hData = GetClipboardData(CF_TEXT);
if (hData == NULL)
{
return FALSE;
}

// 获取数据长度
length = GlobalSize(hData);
url = (char*)malloc(length + 1);

// 将数据转换为字符
pData = (char*)GlobalLock(hData);
strcpy_s(url, length, pData);

// 清理工作
url[length] = 0;
GlobalUnlock(hData);
CloseClipboard();

// 返回结果并释放内存
char* result = _strdup(url);
free(url);
return result;
}

int main(int argc, char *argv[])
{
Sleep(5000);

for (size_t i = 0; i < 10; i++)
{
// 填充字符串
char MyData[256] = { 0 };
sprintf(MyData, "hello lyshark --> %d", i);

// 设置到剪辑版
BOOL set_flag = setClipbar(MyData);
if (set_flag == TRUE)
{
// std::cout << "[*] 已设置" << std::endl;

// 获取剪辑版
char *data = getClipBoardValue();
std::cout << "[剪辑版数据] = " << data << std::endl;
}
}

system("pause");
return 0;
}

运行上述程序,依次实现填充字符串并设置到剪辑版,最后再通过getClipBoardValue函数从剪辑版内读出数据,如下图所示;