Win32软件开发: 按键精灵 鼠标模拟器(VibraClick)

邵献
2023-12-01

1. 简介

玩游戏的时候难免会遇到一些游戏的 "折磨"。例如:我们要使用道具的时候,可这个道具居然没有批量使用!!!

那行吧,我们就来动手做一个按键精灵解放我们的双手。

PS:目前只做了鼠标按键版的,如果有需要键盘的,可以私信或下方留言,后续看需补充吧~

2. 那我们就开始吧~

①. 首先是Win32的框架(这里我就直接套用过来了,不懂可以看下我之前的文章哈~)

//++++++++++++++++++++++++++++++++++
// 宏定义
//----------------------------------
#ifndef UNICODE
#define UNICODE  // 使用UNICODE编码,如果在编译器设置了使用UNICODE字符集此处可免
#endif
#ifndef _UNICODE
#define _UNICODE // 使用UNICODE编码,如果在编译器设置了使用UNICODE字符集此处可免
#endif

//++++++++++++++++++++++++++++++++++
// 头文件
//----------------------------------
#include <windows.h>                                    // Win32程序最重要的头文件
#include <tchar.h>                                      // 兼容字符集头文件

#include "VibraClick.h"                                 // 鼠标模拟器头文件

//++++++++++++++++++++++++++++++++++
// 全局变量
//----------------------------------
TCHAR g_lpszClassName[] = _T("VibraClick");             // 窗口类的名称
TCHAR g_lpszWindowName[] = _T("VibraClick");            // 窗口的名称,(也就是窗口的标题)

VibraClick g_vibraClick;                                // 鼠标模拟器

//++++++++++++++++++++++++++++++++++
// 函数声明
//----------------------------------
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);   // 窗口消息处理过程
VOID VibraClick_Init(HWND);	                        // 初始化软件

//++++++++++++++++++++++++++++++++++
// 游戏主函数
//----------------------------------
INT APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, INT nCmdShow)
{
	/* 1.设计一个窗口类 */
        ...

	/* 2.注册窗口类 */
        ...

	/* 3.创建窗口, 并居中显示 */
	...

	/* 4.更新显示窗口 */
	...

	/* 初始化 */
	VibraClick_Init(hWnd);

	/* 5.消息循环 */
	...

	return msg.wParam;
}

//++++++++++++++++++++++++++++++++++
// 窗口消息处理过程
//----------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_HOTKEY:
		g_vibraClick.OnHotKey(wParam);
		break;
	case WM_DESTROY:
		// 窗口销毁,释放资源
		::PostQuitMessage(0);
		break;
	default:
		return ::DefWindowProc(hWnd, message, wParam, lParam);
	}

	return ((LRESULT)0);
}

VOID VibraClick_Init(HWND hWnd)
{
	g_vibraClick.Init(hWnd);
}

简单讲解上面内容:

1. 定义了一个按键模拟器类(VibraClick) 的变量 g_vibraClick

2. 初始化这个按键模拟器

3. 由于注册了热键 CTRL + S 和 CTRL + R 进行录制和运行,所以消息处理了WM_HOTKEY

②. VibraClick(按键模拟器类)

VibraClick.h



#pragma once

#ifndef __VIBRA_CLICK_H__
#define __VIBRA_CLICK_H__

#include <Windows.h>
#include <vector>

// 参照 INPUT 类
struct MouseRecInput {
	DWORD type;
	MOUSEINPUT mi;
};

// 定时器回调
VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT nIDEvent, DWORD dwTime);
// 鼠标Hook消息处理
LRESULT CALLBACK MouseMessageProc(INT nCode, WPARAM wParam, LPARAM lParam);
class VibraClick
{
	using MouseRecInputVector = std::vector<MouseRecInput *>;
public:
	VibraClick();
	~VibraClick();

public:
	void Init(HWND hWnd);				// 初始化
	void StartMouseRec();				// 开始录制鼠标操作
	void StopMouseRec();				// 停止录制鼠标操作
	void StartRunMouseRec();			// 开始运行鼠标录制的内容
	void StopRunMouseRec();				// 停止运行鼠标录制的内容
	void CleanMouseRecInput();			// 清除鼠标记录内容
	void OnHotKey(WPARAM nHotKeyId);	        // 热键处理

public:
	static VibraClick *GetInstance() { return _inst; }
	bool IsStartMouseRec() { return m_IsStartMouseRec; }
	bool IsStartRunMouseRec() { return m_IsStartRunMouseRec; }
	HHOOK GetHHMouseHook() { return m_hhMouseHook; }
	MouseRecInputVector &GetMouseRecInputVector() { return m_vecMouseRecInput; }
	int GetMouseRecIndex() { return m_MouseRecIndex; }
	void SetMouseRecIndex(int value) { m_MouseRecIndex = value; }

protected:
	static VibraClick *_inst;	        // 实例自己
	HWND m_hWnd;                            // 窗口句柄

	MouseRecInputVector m_vecMouseRecInput;    // 鼠标操作数据
	bool m_IsStartMouseRec;                    // 是否开始记录鼠标操作
	bool m_IsStartRunMouseRec;                 // 是否开始鼠标操作
	int m_MouseRecIndex;                       // 鼠标操作索引
	UINT_PTR m_RunTimerId;                     // 运行的定时器Id

	ATOM m_VibraClick_StartRec;                // 开始记录热键id
	ATOM m_VibraClick_RunRec;                  // 开始运行记录热键id

	// 鼠标的Hook
	HHOOK m_hhMouseHook;

};

#endif // !__VIBRA_CLICK_H__

INPUT 结构体(WinUser.h头文件中)

typedef struct tagINPUT {
    DWORD   type;

    union
    {
        MOUSEINPUT      mi;
        KEYBDINPUT      ki;
        HARDWAREINPUT   hi;
    } DUMMYUNIONNAME;
} INPUT, *PINPUT, FAR* LPINPUT;

简单讲解上面的内容:

1. MouseRecInput结构体,具体参照 INPUT 结构体。因为MOUSEINPUT是在共用体内,所以对其进行一个扩展

2. TimerProc是一个定时器的处理,用在运行按键模拟的时候

3. MouseMessageProc这个一个鼠标的录制Hook处理

4. 类的方法和成员变量都有对应的注释就不详细说明了哈~(不懂的话在下方留言吧,到时候再进行补充)

VibraClick.cpp


#include "VibraClick.h"
#include <tchar.h>

VibraClick *VibraClick::_inst = nullptr;
VibraClick::VibraClick()
{
	// 初始化一些变量
	m_IsStartMouseRec = false;
	m_IsStartRunMouseRec = false;
	m_MouseRecIndex = 0;
	m_RunTimerId = -1;

	m_hhMouseHook = NULL;
	m_hWnd = NULL;
	_inst = this;
}

VibraClick::~VibraClick()
{
	// 清除鼠标记录内容
	CleanMouseRecInput();

	StopMouseRec();
	StopRunMouseRec();

	// 反注册热键
	UnregisterHotKey(this->m_hWnd, m_VibraClick_StartRec);	// Ctrl + S
	UnregisterHotKey(this->m_hWnd, m_VibraClick_RunRec);	// Ctrl + R
}

void VibraClick::Init(HWND hWnd)
{
	m_hWnd = hWnd;

	m_VibraClick_StartRec = GlobalAddAtom(_T("VibraClick_StartRec")) - 0xC000;
	m_VibraClick_RunRec = GlobalAddAtom(_T("VibraClick_RunRec")) - 0xC000;

	RegisterHotKey(this->m_hWnd, m_VibraClick_StartRec, MOD_CONTROL, 'S');	// Ctrl + S
	RegisterHotKey(this->m_hWnd, m_VibraClick_RunRec, MOD_CONTROL, 'R');	// Ctrl + R
}

void VibraClick::StartMouseRec()
{
	// 清除鼠标记录内容
	CleanMouseRecInput();
	// 设置当前为录制鼠标操作状态
	m_IsStartMouseRec = true;
	// 开始鼠标Hook(全局鼠标钩子)
	m_hhMouseHook = SetWindowsHookEx(WH_MOUSE_LL, &MouseMessageProc, GetModuleHandle(NULL), NULL);
}

void VibraClick::StopMouseRec()
{
	// 取消设置当前为录制鼠标操作状态
	m_IsStartMouseRec = false;
	// 释放鼠标Hook
	if (m_hhMouseHook != NULL)
		UnhookWindowsHookEx(m_hhMouseHook);
}

void VibraClick::StartRunMouseRec()
{
	// 开始模拟鼠标操作
	m_IsStartRunMouseRec = true;
	// 设置运行索引
	m_MouseRecIndex = 0;
	// 开启定时器
	m_RunTimerId = SetTimer(m_hWnd, 999, 10, &TimerProc);
}

void VibraClick::StopRunMouseRec()
{
	// 停止运行鼠标记录的内容
	m_IsStartRunMouseRec = false;
	// 关闭定时器
	if (m_RunTimerId != -1)
	{
		KillTimer(m_hWnd, m_RunTimerId);
		m_RunTimerId = -1;
	}
}

void VibraClick::CleanMouseRecInput()
{
	// 清除记录的内容
	for (auto input : m_vecMouseRecInput)
	{
		delete input;
		input = nullptr;
	}
	// 释放vector占用内存
	MouseRecInputVector tmp;
	m_vecMouseRecInput.swap(tmp);
	// 设置运行索引
	m_MouseRecIndex = 0;
}

void VibraClick::OnHotKey(WPARAM nHotKeyId)
{
	if (nHotKeyId == m_VibraClick_StartRec)
	{
		if (m_IsStartMouseRec)
			StopMouseRec();
		else
			StartMouseRec();
	}
	else if (nHotKeyId == m_VibraClick_RunRec)
	{
		if (m_IsStartRunMouseRec)
			StopRunMouseRec();
		else
			StartRunMouseRec();
	}
}

VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT nIDEvent, DWORD dwTime)
{
	switch (nIDEvent)
	{
		case 999:
		{
			// 取余方式进行循环运行
			int MouseRecIndex = VibraClick::GetInstance()->GetMouseRecIndex();
			MouseRecIndex %= VibraClick::GetInstance()->GetMouseRecInputVector().size();
			// 读取当前索引的鼠标模拟消息
			auto pMHD = VibraClick::GetInstance()->GetMouseRecInputVector()[MouseRecIndex++];
			// 通过INPUT进行模拟操作
			INPUT Input;
			Input.type = pMHD->type;
			memcpy((void *)&Input.mi, (void *)&pMHD->mi, sizeof(MOUSEINPUT));
			// 发送模拟消息
			SendInput(1, &Input, sizeof(INPUT));
			VibraClick::GetInstance()->SetMouseRecIndex(MouseRecIndex);
		}
		break;
	}
}

LRESULT CALLBACK MouseMessageProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
	PMSLLHOOKSTRUCT pStruct = (PMSLLHOOKSTRUCT)lParam;
	// LLMHF_INJECTED标志着: 事件是否被注入,通过SendInput后会触发这个标志,也就是模拟的处理消息则不记录了
	// nCode 表示有关Hook的消息
	/*
	 * Hook Codes
	 *
	 * #define HC_ACTION           0
	 * #define HC_GETNEXT          1
	 * #define HC_SKIP             2
	 * #define HC_NOREMOVE         3
	 * #define HC_NOREM            HC_NOREMOVE
	 * #define HC_SYSMODALON       4
	 * #define HC_SYSMODALOFF      5
	*/
	if (nCode < 0 || pStruct->flags & LLMHF_INJECTED)
	{
		return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);
	}

	// 是否开启录制鼠标操作
	if (!VibraClick::GetInstance()->IsStartMouseRec())
		return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);

	// 判断是否为鼠标数据, 当前只是列举一部分的鼠标消息,有需要可以自己加哈~
	if (
		wParam == WM_LBUTTONDOWN ||
		wParam == WM_LBUTTONUP ||
		wParam == WM_RBUTTONDOWN ||
		wParam == WM_RBUTTONUP ||
		wParam == WM_MBUTTONDOWN ||
		wParam == WM_MBUTTONUP ||
		wParam == WM_MOUSEMOVE)
	{
		MouseRecInput *Input = new MouseRecInput;
		// 现在固定为鼠标的模拟输入
		Input->type = INPUT_MOUSE;				
		// 设置输入的数据
		Input->mi.dx = pStruct->pt.x;
		Input->mi.dy = pStruct->pt.y;
		Input->mi.mouseData = pStruct->mouseData;
		Input->mi.time = pStruct->time;
		Input->mi.dwExtraInfo = pStruct->dwExtraInfo;
		switch (wParam)
		{
			case WM_LBUTTONDOWN:
				Input->mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
				break;
			case WM_LBUTTONUP:
				Input->mi.dwFlags = MOUSEEVENTF_LEFTUP;
				break;
			case WM_RBUTTONDOWN:
				Input->mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
				break;
			case WM_RBUTTONUP:
				Input->mi.dwFlags = MOUSEEVENTF_RIGHTUP;
				break;
			case WM_MBUTTONDOWN:
				Input->mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN;
				break;
			case WM_MBUTTONUP:
				Input->mi.dwFlags = MOUSEEVENTF_MIDDLEUP;
				break;
			case WM_MOUSEMOVE:
			{
				int cx_screen = ::GetSystemMetrics(SM_CXSCREEN);
				int cy_screen = ::GetSystemMetrics(SM_CYSCREEN);
				Input->mi.dx = pStruct->pt.x * 65536 / cx_screen;
				Input->mi.dy = pStruct->pt.y * 65536 / cy_screen;
				Input->mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
			}
			break;
		}
		VibraClick::GetInstance()->GetMouseRecInputVector().push_back(Input);
	}
	return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);
}

简单讲解上面的内容:

1. 对静态的_inst进行一个初始化,做这个主要是在Hook和定时器中进行使用到这个按键模拟器

2. 初始化的时候进行保存窗口的句柄,然后进行注册热键和定时器的使用

3. 讲一下这个HOOK的MOUSEMOVE,鼠标点击的x和y坐标需要转换到绝对位置,屏幕的全屏范围是 0~65535,所以需要用当前的电脑分辨率进行转换到绝对的位置

最后PS:这个按键模拟器没有按钮,所以目前只能靠热键进行模拟

分别是:

CTRL + S 开启和关闭录制

CTRL + R 运行模拟

后续有需求可以下方留言,到时候在补充吧~~~

源码:已上传到Github了哦,有兴趣的读者可以去下载了~

 类似资料: