该程序为一个基于Windows API的鼠标与键盘自动化辅助工具,支持通过快捷键(如Ctrl+F1~F8)实现自动左/右键点击、滚轮滚动、按W键、空格键连按及鼠标左右缓慢移动。界面透明可调,实时显示状态,使用SendInput模拟输入,具备高精度定时控制,适用于游戏或自动化操作场景。
使用方式:拷贝程序代码,c++编译后即可运行,可以快速点击,智能翻页,也可玩游戏
#ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #define NOMINMAX #include <windows.h> #include <mmsystem.h> // timeBeginPeriod / timeEndPeriod #include <algorithm> #include <sstream> #include <string> #include <cwchar> #pragma comment(lib, "winmm.lib") #ifndef MOD_NOREPEAT #define MOD_NOREPEAT 0x4000 #endif #define TICK_MS 20 #define CLICK_INTERVAL_MS 0 // 左/右键连点间隔 #define SCROLL_INTERVAL_MS 10 // 滚轮间隔 #define KEY_INTERVAL_MS 50 // 空格连按间隔 #define MOVE_SPEED_PX_S 3000 #define WIN_W 440 #define WIN_H 270 #define WIN_ALPHA 120 // 数值越小越透明(0~255) #define HOTKEY_CLICK_L 1 #define HOTKEY_CLICK_R 2 #define HOTKEY_SCROLL 3 #define HOTKEY_KEY_W 4 #define HOTKEY_KEY_SPACE 5 #define HOTKEY_MOVE_R 6 #define HOTKEY_MOVE_L 7 #define HOTKEY_STOP 8 #define TIMER_TICK 1 HWND g_hwnd = nullptr; bool g_autoL = false; // 自动左键 bool g_autoR = false; // 自动右键 bool g_autoScroll = false; // 自动滚轮 bool g_autoW = false; // 自动按 W(按住) bool g_autoSpace = false; // 自动按空格 bool g_moveRight = false; bool g_moveLeft = false; int g_accumL = 0; int g_accumR = 0; int g_accumScroll = 0; int g_accumSpace = 0; int g_moveSpeed = MOVE_SPEED_PX_S; wchar_t g_status[1024] = L""; /* ---------------- 输入模拟 ---------------- */ void SendLeftClick() { INPUT in[2] = {}; in[0].type = INPUT_MOUSE; in[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; in[1].type = INPUT_MOUSE; in[1].mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(2, in, sizeof(INPUT)); } void SendRightClick() { INPUT in[2] = {}; in[0].type = INPUT_MOUSE; in[0].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; in[1].type = INPUT_MOUSE; in[1].mi.dwFlags = MOUSEEVENTF_RIGHTUP; SendInput(2, in, sizeof(INPUT)); } void SendWheel() { INPUT in = {}; in.type = INPUT_MOUSE; in.mi.dwFlags = MOUSEEVENTF_WHEEL; in.mi.mouseData = (DWORD)(-WHEEL_DELTA); // 负值=向下滚;改成 WHEEL_DELTA 就是向上 SendInput(1, &in, sizeof(INPUT)); } // 使用硬件扫描码发送按键,可能穿透部分 DirectInput 程序 void SendKeyTap(WORD vk) { WORD scan = (WORD)MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); INPUT in[2] = {}; in[0].type = INPUT_KEYBOARD; in[0].ki.wVk = 0; in[0].ki.wScan = scan; in[0].ki.dwFlags = KEYEVENTF_SCANCODE; in[1].type = INPUT_KEYBOARD; in[1].ki.wVk = 0; in[1].ki.wScan = scan; in[1].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; SendInput(2, in, sizeof(INPUT)); } /* W 键采用"按住"方式,开关切换时按下/抬起 */ bool g_wHeld = false; void SetWHeld(bool held) { if (held == g_wHeld) return; WORD scan = (WORD)MapVirtualKeyW(0x57, MAPVK_VK_TO_VSC); // 'W' INPUT in = {}; in.type = INPUT_KEYBOARD; in.ki.wVk = 0; in.ki.wScan = scan; in.ki.dwFlags = KEYEVENTF_SCANCODE | (held ? 0 : KEYEVENTF_KEYUP); SendInput(1, &in, sizeof(INPUT)); g_wHeld = held; } void SendMove(int dx) { INPUT in = {}; in.type = INPUT_MOUSE; in.mi.dx = dx; in.mi.dy = 0; in.mi.dwFlags = MOUSEEVENTF_MOVE; SendInput(1, &in, sizeof(INPUT)); } /* ---------------- 状态文本 ---------------- */ void UpdateStatus() { std::wstringstream ss; ss << L"鼠标助手\n" << L"----------------------------------------\n" << L"Ctrl+F1 自动左键点击 " << (g_autoL ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F2 自动右键点击 " << (g_autoR ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F3 自动滚轮滚动 " << (g_autoScroll ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F4 自动按 W 键 " << (g_autoW ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F5 自动按空格 " << (g_autoSpace ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F6 向右缓慢滑动 " << (g_moveRight ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F7 向左缓慢滑动 " << (g_moveLeft ? L"[开]" : L"[关]") << L"\n" << L"Ctrl+F8 全部停止\n" << L"----------------------------------------\n" << L"双击本窗口退出"; std::wstring s = ss.str(); if (s.size() > 1023) s.resize(1023); std::wmemcpy(g_status, s.c_str(), s.size()); g_status[s.size()] = 0; if (g_hwnd) InvalidateRect(g_hwnd, nullptr, TRUE); } /* ---------------- 窗口过程 ---------------- */ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_HOTKEY: switch (wp) { case HOTKEY_CLICK_L: g_autoL = !g_autoL; g_accumL = 0; break; case HOTKEY_CLICK_R: g_autoR = !g_autoR; g_accumR = 0; break; case HOTKEY_SCROLL: g_autoScroll = !g_autoScroll; g_accumScroll = 0; break; case HOTKEY_KEY_W: g_autoW = !g_autoW; SetWHeld(g_autoW); break; case HOTKEY_KEY_SPACE: g_autoSpace = !g_autoSpace; g_accumSpace = 0; break; case HOTKEY_MOVE_R: g_moveRight = !g_moveRight; if (g_moveRight) g_moveLeft = false; break; case HOTKEY_MOVE_L: g_moveLeft = !g_moveLeft; if (g_moveLeft) g_moveRight = false; break; case HOTKEY_STOP: g_autoL = g_autoR = g_autoScroll = g_autoSpace = false; g_moveRight = g_moveLeft = false; g_autoW = false; SetWHeld(false); g_accumL = g_accumR = g_accumScroll = g_accumSpace = 0; break; } UpdateStatus(); return 0; case WM_TIMER: if (wp == TIMER_TICK) { if (g_autoL) { g_accumL += TICK_MS; if (g_accumL >= CLICK_INTERVAL_MS) { g_accumL = 0; SendLeftClick(); } } if (g_autoR) { g_accumR += TICK_MS; if (g_accumR >= CLICK_INTERVAL_MS) { g_accumR = 0; SendRightClick(); } } if (g_autoScroll) { g_accumScroll += TICK_MS; if (g_accumScroll >= SCROLL_INTERVAL_MS) { g_accumScroll = 0; SendWheel(); } } if (g_autoSpace) { g_accumSpace += TICK_MS; if (g_accumSpace >= KEY_INTERVAL_MS) { g_accumSpace = 0; SendKeyTap(VK_SPACE); } } if (g_moveRight || g_moveLeft) { int step = g_moveSpeed * TICK_MS / 1000; if (step < 1) step = 1; if (g_moveLeft) step = -step; POINT p; GetCursorPos(&p); int leftEdge = GetSystemMetrics(SM_XVIRTUALSCREEN); int virtWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); int rightEdge = leftEdge + virtWidth; int nx = p.x + step; if (nx < leftEdge || nx >= rightEdge) { g_moveRight = g_moveLeft = false; UpdateStatus(); } else { SendMove(step); } } } return 0; case WM_NCLBUTTONDBLCLK: // 双击窗口任意位置退出 DestroyWindow(hwnd); return 0; case WM_ERASEBKGND: return 1; case WM_NCHITTEST: return HTCAPTION; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); RECT rc; GetClientRect(hwnd, &rc); HBRUSH bg = CreateSolidBrush(RGB(20, 22, 28)); FillRect(hdc, &rc, bg); DeleteObject(bg); HPEN pen = CreatePen(PS_SOLID, 1, RGB(0, 220, 130)); HGDIOBJ oldPen = SelectObject(hdc, pen); HGDIOBJ oldBrush = SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, 0, 0, rc.right, rc.bottom); SelectObject(hdc, oldPen); SelectObject(hdc, oldBrush); DeleteObject(pen); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, RGB(0, 235, 150)); RECT tr = rc; tr.left += 16; tr.top += 10; tr.right -= 16; tr.bottom -= 10; DrawTextW(hdc, g_status, -1, &tr, DT_LEFT | DT_TOP | DT_NOPREFIX); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: SetWHeld(false); // 退出前务必松开 W,避免卡键 KillTimer(hwnd, TIMER_TICK); UnregisterHotKey(hwnd, HOTKEY_CLICK_L); UnregisterHotKey(hwnd, HOTKEY_CLICK_R); UnregisterHotKey(hwnd, HOTKEY_SCROLL); UnregisterHotKey(hwnd, HOTKEY_KEY_W); UnregisterHotKey(hwnd, HOTKEY_KEY_SPACE); UnregisterHotKey(hwnd, HOTKEY_MOVE_R); UnregisterHotKey(hwnd, HOTKEY_MOVE_L); UnregisterHotKey(hwnd, HOTKEY_STOP); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } /* ---------------- 入口 ---------------- */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { // 提高定时器精度,让连点间隔更准 timeBeginPeriod(1); // 建议:REALTIME_PRIORITY_CLASS 可能导致系统卡顿,推荐改为 HIGH_PRIORITY_CLASS // 如果你确实需要最高优先级,可以保留下面两行,但请自行承担风险。 SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); WNDCLASSW wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"MouseHelperWndClass"; wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hbrBackground = nullptr; if (!RegisterClassW(&wc)) return 0; int x = GetSystemMetrics(SM_CXSCREEN) - WIN_W - 30; int y = 30; g_hwnd = CreateWindowExW( WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, L"MouseHelperWndClass", L"鼠标助手", WS_POPUP, x, y, WIN_W, WIN_H, nullptr, nullptr, hInstance, nullptr); if (!g_hwnd) return 0; SetLayeredWindowAttributes(g_hwnd, 0, WIN_ALPHA, LWA_ALPHA); RegisterHotKey(g_hwnd, HOTKEY_CLICK_L, MOD_CONTROL | MOD_NOREPEAT, VK_F1); RegisterHotKey(g_hwnd, HOTKEY_CLICK_R, MOD_CONTROL | MOD_NOREPEAT, VK_F2); RegisterHotKey(g_hwnd, HOTKEY_SCROLL, MOD_CONTROL | MOD_NOREPEAT, VK_F3); RegisterHotKey(g_hwnd, HOTKEY_KEY_W, MOD_CONTROL | MOD_NOREPEAT, VK_F4); RegisterHotKey(g_hwnd, HOTKEY_KEY_SPACE, MOD_CONTROL | MOD_NOREPEAT, VK_F5); RegisterHotKey(g_hwnd, HOTKEY_MOVE_R, MOD_CONTROL | MOD_NOREPEAT, VK_F6); RegisterHotKey(g_hwnd, HOTKEY_MOVE_L, MOD_CONTROL | MOD_NOREPEAT, VK_F7); RegisterHotKey(g_hwnd, HOTKEY_STOP, MOD_CONTROL | MOD_NOREPEAT, VK_F8); UpdateStatus(); ShowWindow(g_hwnd, SW_SHOW); UpdateWindow(g_hwnd); SetTimer(g_hwnd, TIMER_TICK, TICK_MS, nullptr); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } timeEndPeriod(1); return 0; }一、程序功能概览
这个工具的核心目标很明确:把日常重复的鼠标点击、滚轮滚动和键盘按键交给程序自动完成,同时保持界面轻量、透明、不抢焦点。它基于 Windows 原生 API 实现,不依赖任何第三方库,编译后就是一个独立的小程序。
主要能力包括以下几项:
- 自动左键点击:按
Ctrl+F1开关,适合需要高频点击的场景。 - 自动右键点击:按
Ctrl+F2开关,可用于菜单刷新、右键连点等操作。 - 自动滚轮滚动:按
Ctrl+F3开关,默认向下滚动,适合长页面翻页。 - 自动按 W 键:按
Ctrl+F4开关,采用“按住”方式,而不是反复点按。 - 自动按空格:按
Ctrl+F5开关,按固定间隔连按。 - 鼠标缓慢左右移动:按
Ctrl+F6向右、Ctrl+F7向左,速度可调。 - 一键全部停止:按
Ctrl+F8立即关闭所有自动化功能。
窗口本身是置顶、半透明、无边框的,并且设置了WS_EX_NOACTIVATE,因此点击它不会抢走当前前台程序的焦点,这一点在游戏或全屏应用中尤其重要。
二、环境准备与编译方式
程序只依赖 Windows SDK 自带的头文件和库,使用 Visual Studio 或 MinGW 都可以编译。
2.1 使用 Visual Studio 编译
- 新建一个“Windows 桌面应用程序”或空的 C++ 项目。
- 把代码保存为
main.cpp,添加到项目中。 - 项目属性中确认字符集为“使用 Unicode 字符集”。
- 链接器会自动处理
#pragma comment(lib, "winmm.lib"),无需手动添加。 - 选择 Release / x64 编译,生成可执行文件。
2.2 使用 MinGW 编译
g++ main.cpp -o MouseHelper.exe -municode -lwinmm -static -O2其中-municode用于支持WinMain的宽字符入口,-lwinmm对应timeBeginPeriod所在的库。
提示:如果编译时报错找不到
WinMain,请检查是否使用了 Unicode 入口,或者把int WINAPI WinMain改为int WINAPI wWinMain并配合-municode。
三、核心实现原理
3.1 用 SendInput 模拟输入
所有鼠标和键盘事件都通过SendInput注入到系统输入流中。相比mouse_event和keybd_event,SendInput是更现代、更可靠的接口,可以一次性提交一组事件,保证按下和抬起之间的原子性。
例如左键点击的实现,就是把MOUSEEVENTF_LEFTDOWN和MOUSEEVENTF_LEFTUP两个事件打包后一次发送:
void SendLeftClick() { INPUT in[2] = {}; in[0].type = INPUT_MOUSE; in[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; in[1].type = INPUT_MOUSE; in[1].mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(2, in, sizeof(INPUT)); }3.2 用扫描码发送按键
键盘部分没有直接使用虚拟键码,而是通过MapVirtualKeyW把虚拟键转换成硬件扫描码,再配合KEYEVENTF_SCANCODE发送。这样做的好处是兼容性更好,部分使用 DirectInput 的程序只认扫描码,不认虚拟键码。
void SendKeyTap(WORD vk) { WORD scan = (WORD)MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); INPUT in[2] = {}; in[0].type = INPUT_KEYBOARD; in[0].ki.wScan = scan; in[0].ki.dwFlags = KEYEVENTF_SCANCODE; in[1].type = INPUT_KEYBOARD; in[1].ki.wScan = scan; in[1].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; SendInput(2, in, sizeof(INPUT)); }3.3 定时器驱动的累加式调度
程序只注册了一个 20ms 的系统定时器TIMER_TICK,所有自动化动作都在这一个定时器里按累加时间判断是否触发。这种设计避免了为每个功能单独开线程或定时器,逻辑集中、开销小。
case WM_TIMER: if (wp == TIMER_TICK) { if (g_autoL) { g_accumL += TICK_MS; if (g_accumL >= CLICK_INTERVAL_MS) { g_accumL = 0; SendLeftClick(); } } // 其余功能同理…… } return 0;每个功能维护一个累加器,累加到设定间隔就执行一次并清零。这样即使定时器本身有抖动,长期平均间隔也能保持稳定。
3.4 提高定时精度
Windows 默认的定时器精度大约在 15.6ms 左右,对于高频连点来说不够用。程序在入口处调用了timeBeginPeriod(1),把系统定时器精度提升到 1ms,退出时再调用timeEndPeriod(1)恢复。
timeBeginPeriod(1); // ... 消息循环 ... timeEndPeriod(1);四、界面与交互设计
4.1 透明置顶窗口
窗口创建时使用了几个关键扩展样式:
WS_EX_TOPMOST:始终置顶,不被其他窗口遮挡。WS_EX_LAYERED:支持整体透明度,通过SetLayeredWindowAttributes设置。WS_EX_TOOLWINDOW:不在任务栏显示,减少干扰。WS_EX_NOACTIVATE:点击窗口不激活,不抢前台焦点。
透明度由宏WIN_ALPHA控制,取值 0~255,数值越小越透明,默认 120 是一个比较适中的半透明效果。
4.2 自绘界面
窗口没有使用任何控件,全部内容在WM_PAINT中自绘:先填充深色背景,再画一圈绿色边框,最后用DrawTextW输出状态文本。状态文本由UpdateStatus统一生成,每次开关变化后调用InvalidateRect触发重绘。
4.3 拖动与退出
在WM_NCHITTEST中直接返回HTCAPTION,让整个窗口都可以拖动;在WM_NCLBUTTONDBLCLK中处理双击退出。这样既省去了标题栏,又保留了基本的窗口操作体验。
五、参数调优建议
代码顶部的几个宏决定了实际手感,可以根据使用场景调整:
| 宏名称 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
TICK_MS | 20 | 定时器周期 | 不建议低于 10,否则 CPU 占用上升 |
CLICK_INTERVAL_MS | 0 | 左右键连点间隔 | 0 表示每个 tick 都点,实际约 20ms 一次 |
SCROLL_INTERVAL_MS | 10 | 滚轮间隔 | 翻页太快可调到 30~50 |
KEY_INTERVAL_MS | 50 | 空格连按间隔 | 根据目标程序响应速度调整 |
MOVE_SPEED_PX_S | 3000 | 鼠标移动速度(像素/秒) | 想更慢可降到 500~1000 |
WIN_ALPHA | 120 | 窗口透明度 | 0~255,越小越透明 |
需要注意的是,CLICK_INTERVAL_MS设为 0 时,实际点击频率受定时器周期限制,约为每秒 50 次。如果目标程序对点击频率敏感,建议适当增大该值。
六、注意事项与风险提示
- 进程优先级:代码中使用了
REALTIME_PRIORITY_CLASS,这是最高优先级,可能导致系统响应变慢甚至卡顿。除非确有需要,建议改为HIGH_PRIORITY_CLASS。 - W 键卡键风险:W 键采用按住方式,程序在
WM_DESTROY中会主动松开,但如果进程被强制结束,仍可能出现卡键。建议保留Ctrl+F8作为应急停止。 - 权限问题:如果目标程序以管理员权限运行,本工具也需要以管理员身份启动,否则
SendInput可能被 UIPI 拦截。 - 合规使用:自动化工具应仅用于提升个人工作效率或测试场景,请勿用于违反游戏用户协议、破坏公平性或其他违规用途。
- 杀毒软件误报:模拟输入的 API 组合有时会被安全软件标记,建议自行编译并添加信任。
七、可扩展方向
如果希望在此基础上继续完善,可以考虑以下几个方向:
- 配置文件化:把间隔、速度、透明度等参数写入 ini 或 json,运行时读取,避免每次改代码重新编译。
- 自定义热键:把固定的
Ctrl+F1~F8改为可配置,通过RegisterHotKey动态注册。 - 托盘图标:增加系统托盘菜单,提供更直观的开关入口。
- 录制回放:记录一段鼠标键盘操作序列,再按时间轴回放,适合重复性流程。
- 多套方案切换:为不同应用保存不同的参数组合,一键切换。
整体来看,这个程序虽然代码量不大,但把 Windows 输入模拟、定时调度、分层窗口和自绘界面几个知识点串在了一起,是一个很适合练手的完整小项目。