This commit is contained in:
Misaki
2026-06-10 00:57:54 +08:00
commit 7551a85abe
2805 changed files with 373529 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# IDEs
/.idea
/example/.idea
.vscode
# Build files
/cmake-build-debug
/cmake-build-release
/example/cmake-build-debug
/build
+68
View File
@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.10)
project(autogui-cpp VERSION 1.0.0 LANGUAGES CXX)
# 基础配置
set(CMAKE_CXX_STANDARD 17) # Cpp17
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 创建库目标
add_library(autogui-cpp STATIC
src/Keyboard.cpp
src/Mouse.cpp
src/Utils.cpp
src/Autogui.cpp
)
# 设置头文件搜索路径
# 使用生成器表达式支持add_subdirectory和install两种使用方式
target_include_directories(autogui-cpp PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include/autogui-cpp>
)
# 平台特定配置
# macOS平台
if(APPLE)
# 查找并链接ApplicationServices框架
# 该框架包含CoreGraphics和Carbon相关API
find_library(APPLICATIONSERVICES_LIBRARY ApplicationServices REQUIRED)
target_link_libraries(autogui-cpp PUBLIC ${APPLICATIONSERVICES_LIBRARY})
# Windows平台
elseif(WIN32)
# 链接User32.lib (提供SendInput, GetCursorPos等API)
target_link_libraries(autogui-cpp PUBLIC User32)
# Linux/Unix平台
elseif(UNIX AND NOT APPLE)
# Linux需要X11后端(Wayland尚未支持)
find_package(X11 REQUIRED)
# 查找Xtst库(XTest扩展)
find_library(X11_Xtst_LIB Xtst)
if(NOT X11_Xtst_LIB)
message(FATAL_ERROR "Xtst library not found. Install libxtst-dev.")
endif()
# 查找Xext库(X扩展)
find_library(X11_Xext_LIB Xext)
if(NOT X11_Xext_LIB)
message(FATAL_ERROR "Xext library not found.")
endif()
# 查找XRandR库(Xrandr扩展)
find_library(X11_Xrandr_LIB Xrandr)
if(NOT X11_Xrandr_LIB)
message(FATAL_ERROR "XRandR library not found. Install libxrandr-dev.")
endif()
# 链接所有必需的X11库
target_link_libraries(autogui-cpp PUBLIC
${X11_LIBRARIES}
${X11_Xtst_LIB}
${X11_Xext_LIB}
${X11_Xrandr_LIB}
)
target_include_directories(autogui-cpp PUBLIC ${X11_INCLUDE_DIR})
endif()
# 集成方式: add_subdirectory
# 在你的项目CMakeLists.txt中:
# add_subdirectory(autogui-cpp)
# target_link_libraries(your_target PRIVATE autogui-cpp)
+118
View File
@@ -0,0 +1,118 @@
# autogui-cpp
![GitHub last commit](https://img.shields.io/github/last-commit/Misakityan/autogui-cpp)
![GitHub issues](https://img.shields.io/github/issues/Misakityan/autogui-cpp)
![GitHub stars](https://img.shields.io/github/stars/Misakityan/autogui-cpp?style=social)
本项目继承自[Robot CPP](https://github.com/developer239/robot-cpp),在此感谢该项目的贡献。
原先的项目仅支持`Windows``Mac OS`,本项目增加了对`Linux X11`的支持,`Linux Wanland`的支持正在开发中
为什么要创建本项目,这是因为我在苦苦寻找`pyautogui`的cpp实现,但很遗憾,我并没有找到这个轮子。
本项目在原项目的基础上,增加了以下文件:
1. `Autogui.cpp/.h`
2. `SimpleAutoGUI.h`
并修复了原项目的bug
1. 模拟键盘输入的时候,无法处理大小写与符号的转换
最终实现了与`pyautogui`类似的功能。
但是,本项目不支持以下功能:
1. 模拟键盘输入只支持`ASCII`字符,如果你需要输入中文,或者其他语言的字符,这可能是一个很大的工程量。
更好的解决方案是将内容copy到剪贴板,然后粘贴到目标位置。
同时,本项目裁剪掉了原项目的部分功能:
1. `Hooks`
2. `Screen`
3. `Record`
主要是因为有着更加完美的上位替代品,例如`Screen`可用`Qt``QScreen`替代,并且支持的更加完美。
注意:原项目并没有说明代码的开源协议,因此关于`Robot-cpp`部分的代码,解释权归属于原作者`
developer239`
本项目被我直接使用在[Yosuga](https://github.com/Misakityan/Yosuga)这个我自己的项目当中。
项目已在以下平台完成测试:
1. Windows 10
2. kUbuntu 24.04
由于我没有Mac PC, 因此并未在mac上进行测试,不过mac部分都是直接使用`Robot-cpp`部分的代码,原作者测试没问题,
大概也是可以使用的。如果遇到bug,尽情提issue即可。
## How to use?
想使用本项目十分容易,在你的项目CMakeLists.txt中:
```cmake
add_subdirectory(autogui-cpp)
target_link_libraries(your_target PRIVATE autogui-cpp) # your_target为你项目的构建目标
# 注意:有时候你还需要根据导入本项目的位置,在autogui-cpp的前面增加一些路径信息
```
之后,在你需要的地方`#include "SimpleAutoGUI.h"`,访问AutoGUI当中已经封装好的函数即可使用。
## some examples
```c++
#include <iostream>
#include "SimpleAutoGUI.h"
int main() {
Robot::Point screenSize = AutoGUI::size();
std::cout << "屏幕尺寸: " << screenSize.x << "x" << screenSize.y << std::endl;
// 测试1: 移动鼠标
std::cout << "移动鼠标到屏幕中心..." << std::endl;
int centerX = screenSize.x / 2;
int centerY = screenSize.y / 2;
AutoGUI::moveTo(centerX, centerY, 2);
// 测试2: 单击
std::cout << "左键单击..." << std::endl;
AutoGUI::click();
AutoGUI::sleep(0.5);
// 测试3: 右键单击
std::cout << "右键单击..." << std::endl;
AutoGUI::rightSingle();
AutoGUI::sleep(0.5);
// 测试4: 双击
std::cout << "左键双击..." << std::endl;
AutoGUI::leftDouble();
AutoGUI::sleep(0.5);
// 测试5: 拖拽
std::cout << "拖拽测试..." << std::endl;
AutoGUI::dragRel(100, 100, 0.5);
AutoGUI::sleep(0.5);
// 测试6: 输入文本
std::cout << "输入文本测试..." << std::endl;
AutoGUI::type("Hello, AutoGUI!", 0.05); // 每个字符间隔50ms
AutoGUI::sleep(0.5);
// 测试7: 快捷键
std::cout << "快捷键测试 (Ctrl+A)..." << std::endl;
AutoGUI::hotkey({AutoGUI::Keys::CTRL, "a"});
AutoGUI::sleep(0.5);
std::cout << "快捷键测试 (Ctrl+C)..." << std::endl;
AutoGUI::hotkey({AutoGUI::Keys::CTRL, "c"});
AutoGUI::sleep(0.5);
// 测试8: 滚动
std::cout << "向上滚动..." << std::endl;
AutoGUI::scroll(5);
AutoGUI::sleep(0.5);
std::cout << "向下滚动..." << std::endl;
AutoGUI::scroll(-5);
AutoGUI::sleep(0.5);
// 测试9: 获取鼠标位置
Robot::Point pos = AutoGUI::position();
std::cout << "当前鼠标位置: (" << pos.x << ", " << pos.y << ")" << std::endl;
return 0;
}
```
+745
View File
@@ -0,0 +1,745 @@
//
// Created by misaki on 2026/1/25.
//
#include "Autogui.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
#include <thread>
#include <cstring>
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
namespace AutoGUI {
// 内部辅助函数
namespace {
// 等待指定毫秒数
void delayMs(const int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
// 将秒转换为毫秒
int secondsToMs(double seconds) { return static_cast<int>(seconds * 1000); }
// 获取当前鼠标位置(内部使用)
Robot::Point getCurrentPosition() { return Robot::Mouse::GetPosition(); }
} // namespace
#if defined(__linux__)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <algorithm>
// 获取所有屏幕信息
std::vector<ScreenInfo> getAllScreens() {
std::vector<ScreenInfo> screens;
Display* display = XOpenDisplay(nullptr);
if (!display) return screens;
XRRScreenResources* resources = XRRGetScreenResources(display, DefaultRootWindow(display));
if (resources) {
for (int i = 0; i < resources->noutput; i++) {
XRROutputInfo* output = XRRGetOutputInfo(display, resources, resources->outputs[i]);
if (output && output->connection == RR_Connected && output->crtc) {
XRRCrtcInfo* crtc = XRRGetCrtcInfo(display, resources, output->crtc);
if (crtc) {
ScreenInfo info;
info.id = i;
info.x = crtc->x;
info.y = crtc->y;
info.width = crtc->width;
info.height = crtc->height;
info.isPrimary = (output->name && strcmp(output->name, "primary") == 0);
// 或者使用 XRRGetOutputPrimary 来判断
screens.push_back(info);
XRRFreeCrtcInfo(crtc);
}
XRRFreeOutputInfo(output);
}
}
XRRFreeScreenResources(resources);
}
XCloseDisplay(display);
return screens;
}
// 获取当前鼠标所在的屏幕
ScreenInfo getCurrentScreen() {
auto screens = getAllScreens();
if (screens.empty()) return {0, 0, 0, 1920, 1080, true};
// 获取当前鼠标位置(虚拟桌面绝对坐标)
Robot::Point mouse = position();
// 找到包含该点的屏幕
for (const auto& screen : screens) {
if (mouse.x >= screen.x && mouse.x < screen.x + screen.width &&
mouse.y >= screen.y && mouse.y < screen.y + screen.height) {
return screen;
}
}
// 默认返回主屏或第一个屏幕
auto it = std::find_if(screens.begin(), screens.end(),
[](const ScreenInfo& s) { return s.isPrimary; });
return (it != screens.end()) ? *it : screens[0];
}
// 相对于当前屏幕移动
void moveToOnCurrentScreen(int x, int y, double duration) {
const ScreenInfo current = getCurrentScreen();
// 转换为虚拟桌面绝对坐标
int absX = current.x + x;
int absY = current.y + y;
moveTo(absX, absY, duration);
}
// 获取当前屏幕尺寸
Robot::Point getScreenSize(int screenId) {
if (screenId == -1) {
ScreenInfo current = getCurrentScreen();
return {current.width, current.height};
}
auto screens = getAllScreens();
for (const auto& s : screens) {
if (s.id == screenId) return {s.width, s.height};
}
return {1920, 1080};
}
#endif
#if defined(_WIN32)
#include <Windows.h>
std::vector<ScreenInfo> getAllScreens() {
std::vector<ScreenInfo> screens;
EnumDisplayMonitors(nullptr, nullptr,
[](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL {
auto* screens = reinterpret_cast<std::vector<ScreenInfo>*>(dwData);
MONITORINFOEX info;
info.cbSize = sizeof(info);
if (GetMonitorInfo(hMonitor, &info)) {
ScreenInfo si;
si.id = screens->size();
si.x = info.rcMonitor.left;
si.y = info.rcMonitor.top;
si.width = info.rcMonitor.right - info.rcMonitor.left;
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
si.isPrimary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
screens->push_back(si);
}
return TRUE;
}, reinterpret_cast<LPARAM>(&screens));
return screens;
}
ScreenInfo getCurrentScreen() {
// 获取当前鼠标位置
POINT pt;
GetCursorPos(&pt);
// 查找包含该点的显示器
HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX info;
info.cbSize = sizeof(info);
GetMonitorInfo(hMonitor, &info);
ScreenInfo si;
si.x = info.rcMonitor.left;
si.y = info.rcMonitor.top;
si.width = info.rcMonitor.right - info.rcMonitor.left;
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
return si;
}
void moveToOnCurrentScreen(int x, int y, double duration) {
ScreenInfo current = getCurrentScreen();
moveTo(current.x + x, current.y + y, duration);
}
#endif
// 实现主要API函数
void moveTo(int x, int y, double duration) {
Robot::Point target{x, y};
if (duration > 0.0) {
Robot::Mouse::MoveSmooth(target);
} else {
Robot::Mouse::Move(target);
}
}
void moveRel(int xOffset, int yOffset, double duration) {
Robot::Point current = getCurrentPosition();
Robot::Point target{current.x + xOffset, current.y + yOffset};
moveTo(target.x, target.y, duration);
}
void click(int x, int y, Button button, int clicks, double interval) {
// 如果提供了坐标,先移动到该位置
if (x >= 0 && y >= 0) {
moveTo(x, y);
delayMs(10);
}
Robot::MouseButton robotButton = toRobotButton(button);
for (int i = 0; i < clicks; i++) {
if (clicks == 2 && i == 0) {
// 双击
Robot::Mouse::DoubleClick(robotButton);
} else {
Robot::Mouse::Click(robotButton);
}
// 如果不是最后一次点击,并且设置了间隔,则等待
if (i < clicks - 1 && interval > 0) {
sleep(interval);
}
}
}
void leftDouble(int x, int y) { click(x, y, Button::LEFT, 2); }
void rightSingle(int x, int y) { click(x, y, Button::RIGHT, 1); }
void middleClick(int x, int y) { click(x, y, Button::MIDDLE, 1); }
void mouseDown(Button button, int x, int y) {
// 如果提供了坐标,先移动到该位置
if (x >= 0 && y >= 0) {
moveTo(x, y);
delayMs(10);
}
Robot::Mouse::ToggleButton(true, toRobotButton(button));
}
void mouseUp(Button button, int x, int y) {
// 如果提供了坐标,先移动到该位置
if (x >= 0 && y >= 0) {
moveTo(x, y);
delayMs(10);
}
Robot::Mouse::ToggleButton(false, toRobotButton(button));
}
void drag(const int x1, const int y1, const int x2, const int y2,
const double duration, const Button button) {
// 参数验证
if (!isValidCoord(x1, y1)) {
throw AutoGUIException("Invalid start coordinates: (" + std::to_string(x1) +
", " + std::to_string(y1) + ")");
}
if (!isValidCoord(x2, y2)) {
throw AutoGUIException("Invalid end coordinates: (" + std::to_string(x2) +
", " + std::to_string(y2) + ")");
}
if (duration < 0) {
throw AutoGUIException("Duration cannot be negative");
}
Robot::MouseButton robotButton = toRobotButton(button);
// 移动到起始位置
Robot::Point start{x1, y1};
Robot::Mouse::Move(start);
delayMs(10); // 短暂延迟确保移动完成
// 按下鼠标按钮
Robot::Mouse::ToggleButton(true, robotButton);
delayMs(10); // 短暂延迟确保按钮按下
// 拖动到目标位置
Robot::Point end{x2, y2};
if (duration > 0.0) {
// 使用平滑拖动
Robot::Mouse::DragSmooth(end);
} else {
// 立即拖动
Robot::Mouse::Drag(end);
}
// 释放鼠标按钮
Robot::Mouse::ToggleButton(false, robotButton);
}
void dragTo(const int x, const int y, const double duration,
const Button button) {
// 获取当前位置
Robot::Point current = getCurrentPosition();
// 如果当前位置就是目标位置,则只做点击操作
if (current.x == x && current.y == y) {
click(x, y, button);
return;
}
// 调用 drag 函数
drag(current.x, current.y, x, y, duration, button);
}
void dragRel(const int xOffset, const int yOffset, const double duration,
const Button button) {
// 获取当前位置
const Robot::Point current = getCurrentPosition();
// 计算目标位置
const int targetX = current.x + xOffset;
const int targetY = current.y + yOffset;
// 如果偏移量为0,则只做点击操作
if (xOffset == 0 && yOffset == 0) {
click(current.x, current.y, button);
return;
}
// 调用 drag 函数
drag(current.x, current.y, targetX, targetY, duration, button);
}
Robot::Point position() { return getCurrentPosition(); }
void scroll(int clicks, int x) {
// 注意:正数向上滚动,负数向下滚动
// 与AutoGUI一致
Robot::Mouse::ScrollBy(clicks, x);
}
void type(const std::string &text, double interval) {
if (interval > 0.0) {
// 有间隔的输入
for (char c : text) {
Robot::Keyboard::Click(c);
if (interval > 0) {
sleep(interval);
}
}
} else {
// 无间隔快速输入
Robot::Keyboard::Type(text);
}
}
void press(const std::string &key) {
std::string lowerKey = toLower(key);
if (lowerKey.length() == 1) {
// 单个字符键
Robot::Keyboard::Click(lowerKey[0]);
} else if (isSpecialKey(lowerKey)) {
// 特殊键
Robot::Keyboard::SpecialKey specialKey = stringToSpecialKey(lowerKey);
Robot::Keyboard::Click(specialKey);
} else {
throw AutoGUIException("Unknown key: " + key);
}
}
void keyDown(const std::string &key) {
std::string lowerKey = toLower(key);
if (lowerKey.length() == 1) {
// 单个字符键
Robot::Keyboard::Press(lowerKey[0]);
} else if (isSpecialKey(lowerKey)) {
// 特殊键
Robot::Keyboard::SpecialKey specialKey = stringToSpecialKey(lowerKey);
Robot::Keyboard::Press(specialKey);
} else {
throw AutoGUIException("Unknown key: " + key);
}
}
void keyUp(const std::string &key) {
std::string lowerKey = toLower(key);
if (lowerKey.length() == 1) {
// 单个字符键
Robot::Keyboard::Release(lowerKey[0]);
} else if (isSpecialKey(lowerKey)) {
// 特殊键
Robot::Keyboard::SpecialKey specialKey = stringToSpecialKey(lowerKey);
Robot::Keyboard::Release(specialKey);
} else {
throw AutoGUIException("Unknown key: " + key);
}
}
void hotkey(const std::initializer_list<std::string> &keys) {
// 按下所有键
for (const auto &key : keys) {
keyDown(key);
}
delayMs(50);
// 释放所有键(按相反顺序)
const std::string *end = keys.end();
const std::string *begin = keys.begin();
for (const std::string *it = end - 1; it >= begin; --it) {
keyUp(*it);
}
}
void hotkey(const std::vector<std::string> &keys) {
// 按下所有键
for (const auto &key : keys) {
keyDown(key);
}
delayMs(50);
// 释放所有键(按相反顺序)
for (auto it = keys.rbegin(); it != keys.rend(); ++it) {
keyUp(*it);
}
}
void sleep(double seconds) {
if (seconds > 0) {
delayMs(secondsToMs(seconds));
}
}
#if defined(_WIN32)
#include <Windows.h>
#elif defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#elif defined(__linux__)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#endif
// 平台特定函数实现
Robot::Point size() {
// 平台特定实现
#ifdef _WIN32
// Windows实现
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
return {width, height};
#elif defined(__APPLE__)
// macOS实现
CGRect mainDisplayBounds = CGDisplayBounds(CGMainDisplayID());
int width = static_cast<int>(CGRectGetWidth(mainDisplayBounds));
int height = static_cast<int>(CGRectGetHeight(mainDisplayBounds));
return {width, height};
#elif defined(__linux__)
Display *display = XOpenDisplay(nullptr);
if (!display) return {1920, 1080};
int width = 0, height = 0;
// 使用 XRRGetScreenResources (兼容 XRandR 1.2+,比 Current 版本兼容性更好)
XRRScreenResources *resources = XRRGetScreenResources(display, DefaultRootWindow(display));
if (resources) {
RROutput target_output = None;
// 条件编译:只在 XRandR 1.3+ 时使用 Primary Output 功能
#if defined(RANDR_MAJOR) && defined(RANDR_MINOR)
#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3)
target_output = XRRGetOutputPrimary(display, DefaultRootWindow(display));
#endif
#endif
// 如果没有获取到主显示器(或版本太低),使用第一个已连接的显示器
if (target_output == None) {
for (int i = 0; i < resources->noutput; i++) {
XRROutputInfo *info = XRRGetOutputInfo(display, resources, resources->outputs[i]);
if (info) {
if (info->connection == 0) {
target_output = resources->outputs[i];
XRRFreeOutputInfo(info);
break;
}
XRRFreeOutputInfo(info);
}
}
}
// 获取选中显示器的分辨率
if (target_output != None) {
XRROutputInfo *output_info = XRRGetOutputInfo(display, resources, target_output);
if (output_info && output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, resources, output_info->crtc);
if (crtc_info) {
width = static_cast<int>(crtc_info->width);
height = static_cast<int>(crtc_info->height);
XRRFreeCrtcInfo(crtc_info);
}
XRRFreeOutputInfo(output_info);
}
}
XRRFreeScreenResources(resources);
}
XCloseDisplay(display);
return (width > 0 && height > 0) ? Robot::Point{width, height} : Robot::Point{1920, 1080};
#else
// 其他平台
return {1920, 1080}; // 默认值
#endif
}
// 辅助函数实现
bool isValidCoord(int x, int y) {
const Robot::Point screenSize = size();
return x >= 0 && x < screenSize.x && y >= 0 && y < screenSize.y;
}
std::string toLower(const std::string &str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
return result;
}
namespace ExtendedFunc {
void dragRect(const int x1, const int y1, const int width, const int height,
double const duration, const Button button) {
// 参数验证
if (width <= 0 || height <= 0) {
throw AutoGUIException("Width and height must be positive");
}
// 计算矩形右下角坐标
const int x2 = x1 + width;
const int y2 = y1 + height;
// 拖动矩形:左上角 -> 右上角 -> 右下角 -> 左下角 -> 左上角
drag(x1, y1, x2, y1, duration/4, button); // 上边
drag(x2, y1, x2, y2, duration/4, button); // 右边
drag(x2, y2, x1, y2, duration/4, button); // 下边
drag(x1, y2, x1, y1, duration/4, button); // 左边
}
void dragCircle(const int centerX, const int centerY, const int radius,
double const duration, const Button button) {
// 参数验证
if (radius <= 0) {
throw AutoGUIException("Radius must be positive");
}
// 计算圆上的点数量(越多越平滑)
const int segments = 36; // 每10度一个点
const double angleStep = 2.0 * M_PI / segments;
// 从3点钟方向开始
double startX = centerX + radius;
double startY = centerY;
// 拖动圆形
for (int i = 1; i <= segments; i++) {
double angle = angleStep * i;
double endX = centerX + radius * cos(angle);
double endY = centerY + radius * sin(angle);
// 拖动到下一个点
drag(static_cast<int>(startX), static_cast<int>(startY),
static_cast<int>(endX), static_cast<int>(endY),
duration / segments, button);
// 更新起点为终点
startX = endX;
startY = endY;
}
}
void dragRandomInArea(const int x1, const int y1, const int x2, const int y2,
const double minDuration, const double maxDuration,
const Button button) {
// 参数验证
if (x1 >= x2 || y1 >= y2) {
throw AutoGUIException("Invalid area coordinates");
}
if (minDuration < 0 || maxDuration < 0 || minDuration > maxDuration) {
throw AutoGUIException("Invalid duration range");
}
// 设置随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distX(x1, x2);
std::uniform_int_distribution<> distY(y1, y2);
std::uniform_real_distribution<> distDuration(minDuration, maxDuration);
// 生成随机起始点和结束点
int startX = distX(gen);
int startY = distY(gen);
int endX = distX(gen);
int endY = distY(gen);
double duration = distDuration(gen);
// 执行随机拖动
drag(startX, startY, endX, endY, duration, button);
}
// 模拟人类打字
void typeHumanLike(const std::string& text, double minDelay, double maxDelay, double errorRate) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> delayDist(minDelay, maxDelay);
std::uniform_real_distribution<> errorDist(0.0, 1.0);
for (char c : text) {
// 有一定概率打错字
if (errorDist(gen) < errorRate) {
// 输入一个随机错误字符
char wrongChar = 'a' + static_cast<char>(errorDist(gen) * 26);
AutoGUI::press(std::string(1, wrongChar));
// 退格删除错误
AutoGUI::press("backspace");
AutoGUI::sleep(0.1);
}
// 输入正确字符
AutoGUI::press(std::string(1, c));
// 随机延迟
double delay = delayDist(gen);
AutoGUI::sleep(delay);
}
}
void typewriteEnter(const std::string& text, double interval) {
AutoGUI::type(text, interval);
AutoGUI::press("enter");
}
void typewriteLines(const std::vector<std::string>& lines, double interval, double lineDelay) {
for (size_t i = 0; i < lines.size(); i++) {
AutoGUI::type(lines[i], interval);
if (i < lines.size() - 1) {
AutoGUI::press("enter");
AutoGUI::sleep(lineDelay);
}
}
}
void typeDateTime(const std::string& format) {
const auto now = std::chrono::system_clock::now();
const auto time = std::chrono::system_clock::to_time_t(now);
const std::tm tm = *std::localtime(&time);
std::stringstream ss;
ss << std::put_time(&tm, format.c_str());
AutoGUI::type(ss.str());
}
/// 键盘快捷键扩展实现
void saveFile() {
AutoGUI::hotkey({"ctrl", "s"});
}
void openFile() {
AutoGUI::hotkey({"ctrl", "o"});
}
void newFile() {
AutoGUI::hotkey({"ctrl", "n"});
}
void copy() {
AutoGUI::hotkey({"ctrl", "c"});
}
void paste() {
AutoGUI::hotkey({"ctrl", "v"});
}
void cut() {
AutoGUI::hotkey({"ctrl", "x"});
}
void selectAll() {
AutoGUI::hotkey({"ctrl", "a"});
}
void undo() {
AutoGUI::hotkey({"ctrl", "z"});
}
void redo() {
#ifdef _WIN32
AutoGUI::hotkey({"ctrl", "y"});
#else
AutoGUI::hotkey({"ctrl", "shift", "z"});
#endif
}
void find() {
AutoGUI::hotkey({"ctrl", "f"});
}
void replace() {
AutoGUI::hotkey({"ctrl", "h"});
}
void print() {
AutoGUI::hotkey({"ctrl", "p"});
}
void closeWindow(bool useAltF4) {
if (useAltF4) {
AutoGUI::hotkey({"alt", "f4"});
} else {
AutoGUI::hotkey({"ctrl", "w"});
}
}
void switchWindow(int times) {
for (int i = 0; i < times; i++) {
AutoGUI::hotkey({"alt", "tab"});
AutoGUI::sleep(0.1);
}
}
void refresh() {
AutoGUI::press("f5");
}
/// 游戏/自动化操作实现
void autoClicker(int x, int y, int count, double interval, AutoGUI::Button button) {
for (int i = 0; i < count; i++) {
AutoGUI::click(x, y, button);
if (i < count - 1) {
AutoGUI::sleep(interval);
}
}
}
void rapidClicker(int x, int y, double duration, double clickRate, AutoGUI::Button button) {
int totalClicks = static_cast<int>(duration * clickRate);
double interval = 1.0 / clickRate;
for (int i = 0; i < totalClicks; i++) {
AutoGUI::click(x, y, button);
AutoGUI::sleep(interval);
}
}
void rapidKeyPress(const std::string& key, double duration, double pressRate) {
int totalPresses = static_cast<int>(duration * pressRate);
double interval = 1.0 / pressRate;
for (int i = 0; i < totalPresses; i++) {
AutoGUI::press(key);
AutoGUI::sleep(interval);
}
}
void rapidHotkey(const std::vector<std::string>& keys, double duration, double pressRate) {
int totalPresses = static_cast<int>(duration * pressRate);
double interval = 1.0 / pressRate;
for (int i = 0; i < totalPresses; i++) {
AutoGUI::hotkey(keys);
AutoGUI::sleep(interval);
}
}
} // namespace ExtendedFunc
} // namespace AutoGUI
+521
View File
@@ -0,0 +1,521 @@
//
// Created by misaki on 2026/1/25.
//
#pragma once
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <map>
#include <initializer_list>
#include "Keyboard.h"
#include "Mouse.h"
#include "types.h"
namespace AutoGUI {
// 错误类型
class AutoGUIException : public std::exception {
private:
std::string message;
public:
explicit AutoGUIException(std::string msg) : message(std::move(msg)) {}
[[nodiscard]] const char* what() const noexcept override {
return message.c_str();
}
};
// 鼠标按钮枚举
enum class Button {
LEFT = 0,
RIGHT = 1,
MIDDLE = 2
};
// 将 Button 转换为 Robot::MouseButton
inline Robot::MouseButton toRobotButton(Button button) {
switch (button) {
case Button::LEFT: return Robot::MouseButton::LEFT_BUTTON;
case Button::RIGHT: return Robot::MouseButton::RIGHT_BUTTON;
case Button::MIDDLE: return Robot::MouseButton::CENTER_BUTTON;
default: return Robot::MouseButton::LEFT_BUTTON;
}
}
// 将 Button 枚举转换为字符串
inline std::string buttonToString(Button button) {
switch (button) {
case Button::LEFT: return "left";
case Button::RIGHT: return "right";
case Button::MIDDLE: return "middle";
default: return "left";
}
}
// 键名字符串到 Keyboard::SpecialKey 的映射
inline Robot::Keyboard::SpecialKey stringToSpecialKey(const std::string& key) {
static std::map<std::string, Robot::Keyboard::SpecialKey> keyMap = {
{"backspace", Robot::Keyboard::BACKSPACE},
{"enter", Robot::Keyboard::ENTER},
{"return", Robot::Keyboard::ENTER},
{"tab", Robot::Keyboard::TAB},
{"escape", Robot::Keyboard::ESCAPE},
{"esc", Robot::Keyboard::ESCAPE},
{"up", Robot::Keyboard::UP},
{"down", Robot::Keyboard::DOWN},
{"right", Robot::Keyboard::RIGHT},
{"left", Robot::Keyboard::LEFT},
{"win", Robot::Keyboard::META},
{"command", Robot::Keyboard::META},
{"cmd", Robot::Keyboard::META},
{"alt", Robot::Keyboard::ALT},
{"ctrl", Robot::Keyboard::CONTROL},
{"control", Robot::Keyboard::CONTROL},
{"shift", Robot::Keyboard::SHIFT},
{"capslock", Robot::Keyboard::CAPSLOCK},
{"f1", Robot::Keyboard::F1},
{"f2", Robot::Keyboard::F2},
{"f3", Robot::Keyboard::F3},
{"f4", Robot::Keyboard::F4},
{"f5", Robot::Keyboard::F5},
{"f6", Robot::Keyboard::F6},
{"f7", Robot::Keyboard::F7},
{"f8", Robot::Keyboard::F8},
{"f9", Robot::Keyboard::F9},
{"f10", Robot::Keyboard::F10},
{"f11", Robot::Keyboard::F11},
{"f12", Robot::Keyboard::F12},
{"space", Robot::Keyboard::BACKSPACE} // 特殊处理空格
};
std::string lower = key;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
auto it = keyMap.find(lower);
if (it != keyMap.end()) {
return it->second;
}
// 如果是单个字符,直接返回(会在其他地方处理)
if (lower.length() == 1) {
// 这是单个字符键,不是特殊键
throw AutoGUIException("Unknown key: " + key);
}
throw AutoGUIException("Unknown key: " + key);
}
// 检查字符串是否是特殊键
inline bool isSpecialKey(const std::string& key) {
static std::vector<std::string> specialKeys = {
"backspace", "enter", "return", "tab", "escape", "esc",
"up", "down", "right", "left", "win", "command", "cmd",
"alt", "ctrl", "control", "shift", "capslock",
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12",
"space"
};
std::string lower = key;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
return std::find(specialKeys.begin(), specialKeys.end(), lower) != specialKeys.end();
}
// 解决多屏幕移动鼠标问题
struct ScreenInfo {
int id; // 屏幕ID
int x, y; // 相对于虚拟桌面原点的偏移
int width, height;// 屏幕尺寸
bool isPrimary; // 是否主屏
};
// 获取所有屏幕信息
std::vector<ScreenInfo> getAllScreens();
// 获取当前鼠标所在的屏幕
ScreenInfo getCurrentScreen();
// 相对于当前屏幕移动鼠标(以当前屏幕左上角为原点)
void moveToOnCurrentScreen(int x, int y, double duration = 0.0);
// 获取指定屏幕的尺寸(替代原来的size(),支持多屏)
Robot::Point getScreenSize(int screenId = -1); // -1表示当前屏幕
// 主要 API 函数
/**
* @brief 移动鼠标到指定位置
* @param x 目标位置的X坐标
* @param y 目标位置的Y坐标
* @param duration 移动持续时间(秒),0表示立即移动
*/
void moveTo(int x, int y, double duration = 0.0);
/**
* @brief 相对移动鼠标
* @param xOffset X方向偏移量
* @param yOffset Y方向偏移量
* @param duration 移动持续时间(秒),0表示立即移动
*/
void moveRel(int xOffset, int yOffset, double duration = 0.0);
/**
* @brief 单击鼠标
* @param x 点击位置的X坐标,-1表示当前位置
* @param y 点击位置的Y坐标,-1表示当前位置
* @param button 鼠标按钮:left, right, middle
* @param clicks 点击次数
* @param interval 多次点击之间的间隔(秒)
*/
void click(int x = -1, int y = -1,
Button button = Button::LEFT,
int clicks = 1,
double interval = 0.0);
/**
* @brief 左键双击
* @param x 双击位置的X坐标,-1表示当前位置
* @param y 双击位置的Y坐标,-1表示当前位置
*/
void leftDouble(int x = -1, int y = -1);
/**
* @brief 右键单击
* @param x 点击位置的X坐标,-1表示当前位置
* @param y 点击位置的Y坐标,-1表示当前位置
*/
void rightSingle(int x = -1, int y = -1);
/**
* @brief 中键单击
* @param x 点击位置的X坐标,-1表示当前位置
* @param y 点击位置的Y坐标,-1表示当前位置
*/
void middleClick(int x = -1, int y = -1);
/**
* @brief 按下鼠标按钮
* @param button 要按下的鼠标按钮
* @param x 按下位置的X坐标,-1表示当前位置
* @param y 按下位置的Y坐标,-1表示当前位置
*/
void mouseDown(Button button = Button::LEFT, int x = -1, int y = -1);
/**
* @brief 释放鼠标按钮
* @param button 要释放的鼠标按钮
* @param x 释放位置的X坐标,-1表示当前位置
* @param y 释放位置的Y坐标,-1表示当前位置
*/
void mouseUp(Button button = Button::LEFT, int x = -1, int y = -1);
/**
* @brief 从指定位置拖动到另一个位置
* @param x1 起始位置的X坐标
* @param y1 起始位置的Y坐标
* @param x2 目标位置的X坐标
* @param y2 目标位置的Y坐标
* @param duration 拖动持续时间(秒),0表示立即拖动
* @param button 拖动时按住的鼠标按钮
*/
void drag(int x1, int y1, int x2, int y2,
double duration = 0.0,
Button button = Button::LEFT);
/**
* @brief 拖拽鼠标到指定位置
* @param x 目标位置的X坐标
* @param y 目标位置的Y坐标
* @param duration 拖拽持续时间(秒),0表示立即拖拽
* @param button 拖拽时按住的鼠标按钮
*/
void dragTo(int x, int y, double duration = 0.0, Button button = Button::LEFT);
/**
* @brief 相对拖拽鼠标
* @param xOffset X方向偏移量
* @param yOffset Y方向偏移量
* @param duration 拖拽持续时间(秒),0表示立即拖拽
* @param button 拖拽时按住的鼠标按钮
*/
void dragRel(int xOffset, int yOffset, double duration = 0.0, Button button = Button::LEFT);
/**
* @brief 获取当前鼠标位置
* @return 包含x,y坐标的Point结构体
*/
Robot::Point position();
/**
* @brief 滚动鼠标滚轮
* @param clicks 滚动量,正数向上滚动,负数向下滚动
* @param x 水平滚动量(Linux/macOS支持)
*/
void scroll(int clicks, int x = 0);
/**
* @brief 输入文本
* @param text 要输入的文本
* @param interval 字符之间的间隔时间(秒),0表示无间隔
* 注意:只支持ASCII字符
*/
void type(const std::string& text, double interval = 0.0);
/**
* @brief 按下并释放一个键
* @param key 键名(如:"a", "enter", "ctrl"等)
*/
void press(const std::string& key);
/**
* @brief 按下键(不释放)
* @param key 键名
*/
void keyDown(const std::string& key);
/**
* @brief 释放键
* @param key 键名
*/
void keyUp(const std::string& key);
/**
* @brief 按下组合键
* @param keys 键名列表,如 {"ctrl", "c"}
*/
void hotkey(const std::initializer_list<std::string>& keys);
/**
* @brief 按下组合键(向量版本)
* @param keys 键名向量
*/
void hotkey(const std::vector<std::string>& keys);
/**
* @brief 睡眠/等待
* @param seconds 等待的秒数
*/
void sleep(double seconds);
/**
* @brief 获取屏幕尺寸
* @return 包含屏幕宽度和高度的Point结构体
* @note 需要平台特定实现
* @note 注意:不提供屏幕管理功能,如果你有多个屏幕,那么返回的size可能是错误的,对于单个屏幕是没有影响的,对于获取屏幕尺寸,更加推荐使用Qt当中的接口
*/
Robot::Point size();
// 辅助函数
/**
* @brief 检查坐标是否有效
* @param x X坐标
* @param y Y坐标
* @return 如果坐标有效返回true
*/
bool isValidCoord(int x, int y);
/**
* @brief 将字符串转换为小写
* @param str 输入字符串
* @return 小写字符串
*/
std::string toLower(const std::string& str);
namespace ExtendedFunc {
/// drag拓展功能
/**
* @brief 从指定位置开始拖动一个矩形区域
* @param x1 矩形左上角X坐标
* @param y1 矩形左上角Y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param duration 拖动持续时间(秒)
* @param button 拖动时按住的鼠标按钮
*/
void dragRect(int x1, int y1, int width, int height,
double duration = 0.5, Button button = Button::LEFT);
/**
* @brief 拖动一个圆形区域(从中心开始到边缘)
* @param centerX 圆心X坐标
* @param centerY 圆心Y坐标
* @param radius 圆半径
* @param duration 拖动持续时间(秒)
* @param button 拖动时按住的鼠标按钮
*/
void dragCircle(int centerX, int centerY, int radius,
double duration = 0.5, Button button = Button::LEFT);
/**
* @brief 在区域内随机拖动(用于测试或自动化)
* @param x1 区域左上角X坐标
* @param y1 区域左上角Y坐标
* @param x2 区域右下角X坐标
* @param y2 区域右下角Y坐标
* @param minDuration 最小拖动持续时间(秒)
* @param maxDuration 最大拖动持续时间(秒)
* @param button 拖动时按住的鼠标按钮
*/
void dragRandomInArea(int x1, int y1, int x2, int y2,
double minDuration = 0.1, double maxDuration = 0.5,
Button button = Button::LEFT);
/// 文字输入扩展功能
/**
* @brief 模拟人类打字(带随机延迟和可能的错误)
* @param text 要输入的文本
* @param minDelay 最小延迟(秒)
* @param maxDelay 最大延迟(秒)
* @param errorRate 错误率(0.0-1.0
*/
void typeHumanLike(const std::string& text, double minDelay = 0.05,
double maxDelay = 0.2, double errorRate = 0.0);
/**
* @brief 输入文本并回车
* @param text 要输入的文本
* @param interval 字符间隔
*/
void typewriteEnter(const std::string& text, double interval = 0.0);
/**
* @brief 输入多行文本
* @param lines 文本行数组
* @param interval 字符间隔
* @param lineDelay 行间延迟
*/
void typewriteLines(const std::vector<std::string>& lines,
double interval = 0.0, double lineDelay = 0.1);
/**
* @brief 输入当前日期时间
* @param format 时间格式(如:"%Y-%m-%d %H:%M:%S"
*/
void typeDateTime(const std::string& format = "%Y-%m-%d %H:%M:%S");
/// 键盘快捷键扩展功能
/**
* @brief 保存文件 (Ctrl+S)
*/
void saveFile();
/**
* @brief 打开文件 (Ctrl+O)
*/
void openFile();
/**
* @brief 新建文件 (Ctrl+N)
*/
void newFile();
/**
* @brief 复制 (Ctrl+C)
*/
void copy();
/**
* @brief 粘贴 (Ctrl+V)
*/
void paste();
/**
* @brief 剪切 (Ctrl+X)
*/
void cut();
/**
* @brief 全选 (Ctrl+A)
*/
void selectAll();
/**
* @brief 撤销 (Ctrl+Z)
*/
void undo();
/**
* @brief 重做 (Ctrl+Y 或 Ctrl+Shift+Z)
*/
void redo();
/**
* @brief 查找 (Ctrl+F)
*/
void find();
/**
* @brief 替换 (Ctrl+H)
*/
void replace();
/**
* @brief 打印 (Ctrl+P)
*/
void print();
/**
* @brief 关闭窗口 (Alt+F4 或 Ctrl+W)
*/
void closeWindow(bool useAltF4 = true);
/**
* @brief 切换窗口 (Alt+Tab)
* @param times 切换次数
*/
void switchWindow(int times = 1);
/**
* @brief 刷新 (F5)
*/
void refresh();
/// 游戏/自动化操作功能拓展
/**
* @brief 自动点击器(连续点击)
* @param x 点击位置X坐标
* @param y 点击位置Y坐标
* @param count 点击次数
* @param interval 点击间隔(秒)
* @param button 鼠标按钮
*/
void autoClicker(int x, int y, int count = 10, double interval = 0.1,
Button button = Button::LEFT);
/**
* @brief 连点器(按住连点)
* @param x 点击位置X坐标
* @param y 点击位置Y坐标
* @param duration 持续时间(秒)
* @param clickRate 点击频率(次/秒)
* @param button 鼠标按钮
*/
void rapidClicker(int x, int y, double duration = 1.0,
double clickRate = 10.0,
Button button = Button::LEFT);
/**
* @brief 按键连发(按住按键)
* @param key 按键名称
* @param duration 持续时间(秒)
* @param pressRate 按键频率(次/秒)
*/
void rapidKeyPress(const std::string& key, double duration = 1.0,
double pressRate = 10.0);
/**
* @brief 组合键连发
* @param keys 按键组合
* @param duration 持续时间
* @param pressRate 按键频率
*/
void rapidHotkey(const std::vector<std::string>& keys, double duration = 1.0,
double pressRate = 5.0);
} // namespace ExtendedFunc
} // namespace AutoGUI
+898
View File
@@ -0,0 +1,898 @@
#ifdef __APPLE__
#include <CoreGraphics/CoreGraphics.h>
#endif
#ifdef __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XTest.h>
#endif
#include <iostream>
#include <random>
#include <map>
#include <cstring>
#include "./Keyboard.h"
#include "./Utils.h"
namespace Robot {
int Keyboard::delay = 1;
const char Keyboard::INVALID_ASCII = static_cast<char>(0xFF);
std::thread Keyboard::keyPressThread;
std::atomic<bool> Keyboard::continueHolding(false);
std::set<char> Keyboard::heldAsciiChars;
std::set<Keyboard::SpecialKey> Keyboard::heldSpecialKeys;
#ifdef __linux__
Display* Keyboard::display = nullptr;
Window Keyboard::rootWindow = 0;
void Keyboard::InitializeX11() {
if (display == nullptr) {
display = XOpenDisplay(nullptr);
if (display == nullptr) {
throw std::runtime_error("Cannot open X11 display");
}
rootWindow = DefaultRootWindow(display);
}
}
void Keyboard::CleanupX11() {
if (display != nullptr) {
XCloseDisplay(display);
display = nullptr;
}
}
#endif
#ifdef __linux__
static bool NeedShiftForKeySym(KeySym keysym) {
// 检查这个键是否需要 Shift 键配合
switch (keysym) {
case XK_exclam: // !
case XK_at: // @
case XK_numbersign: // #
case XK_dollar: // $
case XK_percent: // %
case XK_asciicircum: // ^
case XK_ampersand: // &
case XK_asterisk: // *
case XK_parenleft: // (
case XK_parenright: // )
case XK_underscore: // _
case XK_plus: // +
case XK_braceleft: // {
case XK_braceright: // }
case XK_bar: // |
case XK_colon: // :
case XK_quotedbl: // "
case XK_less: // <
case XK_greater: // >
case XK_question: // ?
case XK_asciitilde: // ~
return true;
// 大写字母需要 Shift 键
case XK_A:
case XK_B:
case XK_C:
case XK_D:
case XK_E:
case XK_F:
case XK_G:
case XK_H:
case XK_I:
case XK_J:
case XK_K:
case XK_L:
case XK_M:
case XK_N:
case XK_O:
case XK_P:
case XK_Q:
case XK_R:
case XK_S:
case XK_T:
case XK_U:
case XK_V:
case XK_W:
case XK_X:
case XK_Y:
case XK_Z:
return true;
default:
return false;
}
}
static bool NeedShiftForChar(const char c) {
// 简单判断:大写字母和特殊符号需要 Shift
if (std::isupper(c)) {
return true;
}
// 特殊符号需要 Shift
const char* shiftChars = "!@#$%^&*()_+{}|:\"<>?~";
return (strchr(shiftChars, c) != nullptr);
}
#endif
void Keyboard::HoldStart(char asciiChar) {
if (heldAsciiChars.empty() && heldSpecialKeys.empty()) {
continueHolding = true;
keyPressThread = std::thread(KeyHoldThread);
}
heldAsciiChars.insert(asciiChar);
}
void Keyboard::HoldStart(SpecialKey specialKey) {
if (heldAsciiChars.empty() && heldSpecialKeys.empty()) {
continueHolding = true;
keyPressThread = std::thread(KeyHoldThread);
}
heldSpecialKeys.insert(specialKey);
}
void Keyboard::HoldStop(char asciiChar) {
heldAsciiChars.erase(asciiChar);
if (heldAsciiChars.empty() && heldSpecialKeys.empty()) {
continueHolding = false;
if (keyPressThread.joinable()) {
keyPressThread.join();
}
}
Release(asciiChar);
}
void Keyboard::HoldStop(SpecialKey specialKey) {
heldSpecialKeys.erase(specialKey);
if (heldAsciiChars.empty() && heldSpecialKeys.empty()) {
continueHolding = false;
if (keyPressThread.joinable()) {
keyPressThread.join();
}
}
Release(specialKey);
}
void Keyboard::KeyHoldThread() {
while (continueHolding) {
for (char asciiChar : heldAsciiChars) {
Press(asciiChar);
}
for (SpecialKey specialKey : heldSpecialKeys) {
Press(specialKey);
}
Robot::delay(50);
}
for (char asciiChar : heldAsciiChars) {
Release(asciiChar);
}
for (SpecialKey specialKey : heldSpecialKeys) {
Release(specialKey);
}
}
void Keyboard::Type(const std::string &query) {
for (const char c : query) {
if (!KeyUtils::IsValidAscii(c)) {
std::cerr << "Warning: Skipping invalid ASCII character: " << static_cast<int>(c) << std::endl;
continue;
}
Click(c);
}
}
void Keyboard::TypeHumanLike(const std::string &query) {
std::normal_distribution<double> distribution(75, 25);
std::random_device rd;
std::mt19937 engine(rd());
for (char c : query) {
if (!KeyUtils::IsValidAscii(c)) {
std::cerr << "Warning: Skipping invalid ASCII character: " << static_cast<int>(c) << std::endl;
continue;
}
Click(c);
Robot::delay((int)distribution(engine));
}
}
void Keyboard::Click(char asciiChar) {
#ifdef __linux__
InitializeX11();
// Linux 特殊处理:对于需要 Shift 的字符,先按下 Shift
bool needShift = NeedShiftForChar(asciiChar);
if (needShift) {
// 获取 Shift 键的键码
KeyCode shiftKeyCode = XKeysymToKeycode(display, XK_Shift_L);
if (shiftKeyCode != 0) {
XTestFakeKeyEvent(display, shiftKeyCode, True, CurrentTime);
XFlush(display);
Robot::delay(delay);
}
}
Press(asciiChar);
Release(asciiChar);
if (needShift) {
// 释放 Shift 键
KeyCode shiftKeyCode = XKeysymToKeycode(display, XK_Shift_L);
if (shiftKeyCode != 0) {
XTestFakeKeyEvent(display, shiftKeyCode, False, CurrentTime);
XFlush(display);
Robot::delay(delay);
}
}
#endif
#ifdef __APPLE__
// macOS实现
bool needShift = KeyUtils::NeedsShift(asciiChar);~
char baseKey = KeyUtils::GetBaseKey(asciiChar);
auto it = asciiToVirtualKeyMap.find(baseKey);
if (it == asciiToVirtualKeyMap.end()) {
std::cerr << "Warning: Character not found in key map: " << asciiChar << std::endl;
return;
}
CGKeyCode keycode = static_cast<CGKeyCode>(it->second);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (needShift) {
// 按下Shift键
CGEventRef shiftDown = CGEventCreateKeyboardEvent(source, kVK_Shift, true);
CGEventPost(kCGHIDEventTap, shiftDown);
CFRelease(shiftDown);
Robot::delay(delay);
}
// 按下并释放目标键
CGEventRef keyDown = CGEventCreateKeyboardEvent(source, keycode, true);
CGEventRef keyUp = CGEventCreateKeyboardEvent(source, keycode, false);
CGEventPost(kCGHIDEventTap, keyDown);
Robot::delay(delay);
CGEventPost(kCGHIDEventTap, keyUp);
Robot::delay(delay);
if (needShift) {
// 释放Shift键
CGEventRef shiftUp = CGEventCreateKeyboardEvent(source, kVK_Shift, false);
CGEventPost(kCGHIDEventTap, shiftUp);
CFRelease(shiftUp);
Robot::delay(delay);
}
CFRelease(keyDown);
CFRelease(keyUp);
CFRelease(source);
#endif
#ifdef _WIN32
// Windows实现
bool needShift = KeyUtils::NeedsShift(asciiChar);
char baseKey = KeyUtils::GetBaseKey(asciiChar);
// 获取基础键的虚拟键码
SHORT vkAndShift = VkKeyScan(baseKey);
if (vkAndShift == -1) {
std::cerr << "Warning: Cannot get virtual key for character: " << asciiChar << std::endl;
return;
}
WORD keycode = static_cast<WORD>(vkAndShift & 0xFF);
std::vector<INPUT> inputs;
if (needShift) {
// Shift键按下
INPUT shiftDown = {0};
shiftDown.type = INPUT_KEYBOARD;
shiftDown.ki.wVk = VK_SHIFT;
inputs.push_back(shiftDown);
}
// 目标键按下
INPUT keyDown = {0};
keyDown.type = INPUT_KEYBOARD;
keyDown.ki.wVk = keycode;
inputs.push_back(keyDown);
// 目标键释放
INPUT keyUp = {0};
keyUp.type = INPUT_KEYBOARD;
keyUp.ki.wVk = keycode;
keyUp.ki.dwFlags = KEYEVENTF_KEYUP;
inputs.push_back(keyUp);
if (needShift) {
// Shift键释放
INPUT shiftUp = {0};
shiftUp.type = INPUT_KEYBOARD;
shiftUp.ki.wVk = VK_SHIFT;
shiftUp.ki.dwFlags = KEYEVENTF_KEYUP;
inputs.push_back(shiftUp);
}
// 发送所有输入
SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
#endif
}
void Keyboard::Click(SpecialKey specialKey) {
Press(specialKey);
Release(specialKey);
}
void Keyboard::Press(char asciiChar) {
KeyCode keycode = AsciiToVirtualKey(asciiChar);
#ifdef __APPLE__
CGEventSourceRef source =
CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateKeyboardEvent(source, keycode, true);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
CFRelease(source);
#endif
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = keycode;
SendInput(1, &input, sizeof(INPUT));
#endif
#ifdef __linux__
InitializeX11();
KeyCode xkeycode = XKeysymToKeycode(display, keycode);
if (xkeycode != 0) {
XTestFakeKeyEvent(display, xkeycode, True, CurrentTime);
XFlush(display);
}
#endif
Robot::delay(delay);
}
void Keyboard::Press(SpecialKey specialKey) {
KeyCode keycode = SpecialKeyToVirtualKey(specialKey);
#ifdef __APPLE__
CGEventRef event = CGEventCreateKeyboardEvent(nullptr, keycode, true);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
#endif
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = keycode;
SendInput(1, &input, sizeof(INPUT));
#endif
#ifdef __linux__
InitializeX11();
KeyCode xkeycode = XKeysymToKeycode(display, keycode);
if (xkeycode != 0) {
XTestFakeKeyEvent(display, xkeycode, True, CurrentTime);
XFlush(display);
}
#endif
Robot::delay(delay);
}
void Keyboard::Release(char asciiChar) {
KeyCode keycode = AsciiToVirtualKey(asciiChar);
#ifdef __APPLE__
CGEventRef event =
CGEventCreateKeyboardEvent(nullptr, (CGKeyCode)keycode, false);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
#endif
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = keycode;
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
#endif
#ifdef __linux__
InitializeX11();
KeyCode xkeycode = XKeysymToKeycode(display, keycode);
if (xkeycode != 0) {
XTestFakeKeyEvent(display, xkeycode, False, CurrentTime);
XFlush(display);
}
#endif
Robot::delay(delay);
}
void Keyboard::Release(SpecialKey specialKey) {
KeyCode keycode = SpecialKeyToVirtualKey(specialKey);
#ifdef __APPLE__
CGEventRef event =
CGEventCreateKeyboardEvent(nullptr, (CGKeyCode)keycode, false);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
#endif
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = keycode;
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
#endif
#ifdef __linux__
InitializeX11();
KeyCode xkeycode = XKeysymToKeycode(display, keycode);
if (xkeycode != 0) {
XTestFakeKeyEvent(display, xkeycode, False, CurrentTime);
XFlush(display);
}
#endif
Robot::delay(delay);
}
KeyCode Keyboard::SpecialKeyToVirtualKey(SpecialKey specialKey) {
return specialKeyToVirtualKeyMap.at(specialKey);
}
KeyCode Keyboard::AsciiToVirtualKey(char asciiChar) {
#ifdef __APPLE__
auto it = asciiToVirtualKeyMap.find(asciiChar);
if (it == asciiToVirtualKeyMap.end()) {
std::cerr
<< "Warning: Character not found in the virtual key map. Ignoring..."
<< std::endl;
return 0xFFFF; // Return an invalid keycode
}
return static_cast<KeyCode>(it->second);
#endif
#ifdef _WIN32
// Windows: VkKeyScan 函数返回一个short,高字节是shift状态,低字节是虚拟键码
SHORT vkAndShift = VkKeyScan(asciiChar);
if (vkAndShift == -1) {
std::cerr
<< "Warning: Character not found in the virtual key map. Ignoring..."
<< std::endl;
return 0xFFFF; // Return an invalid keycode
}
// 返回虚拟键码(低字节)
return static_cast<KeyCode>(vkAndShift & 0xFF);
#endif
#ifdef __linux__
// Convert ASCII to X11 KeySym
// For Linux, we need to handle case sensitivity properly
if (std::isalpha(asciiChar)) {
// For letters, use lowercase KeySym
switch (char lowerChar = std::tolower(asciiChar)) {
case 'a': return XK_a;
case 'b': return XK_b;
case 'c': return XK_c;
case 'd': return XK_d;
case 'e': return XK_e;
case 'f': return XK_f;
case 'g': return XK_g;
case 'h': return XK_h;
case 'i': return XK_i;
case 'j': return XK_j;
case 'k': return XK_k;
case 'l': return XK_l;
case 'm': return XK_m;
case 'n': return XK_n;
case 'o': return XK_o;
case 'p': return XK_p;
case 'q': return XK_q;
case 'r': return XK_r;
case 's': return XK_s;
case 't': return XK_t;
case 'u': return XK_u;
case 'v': return XK_v;
case 'w': return XK_w;
case 'x': return XK_x;
case 'y': return XK_y;
case 'z': return XK_z;
}
}
// For numbers and special characters
switch (asciiChar) {
// Numbers
case '0': return XK_0;
case '1': return XK_1;
case '2': return XK_2;
case '3': return XK_3;
case '4': return XK_4;
case '5': return XK_5;
case '6': return XK_6;
case '7': return XK_7;
case '8': return XK_8;
case '9': return XK_9;
// Special characters that need shift
case '!': return XK_exclam; // Shift+1
case '@': return XK_at; // Shift+2
case '#': return XK_numbersign; // Shift+3
case '$': return XK_dollar; // Shift+4
case '%': return XK_percent; // Shift+5
case '^': return XK_asciicircum; // Shift+6
case '&': return XK_ampersand; // Shift+7
case '*': return XK_asterisk; // Shift+8
case '(': return XK_parenleft; // Shift+9
case ')': return XK_parenright; // Shift+0
case '_': return XK_underscore; // Shift+-
case '+': return XK_plus; // Shift+=
case '{': return XK_braceleft; // Shift+[
case '}': return XK_braceright; // Shift+]
case '|': return XK_bar; // Shift+\
case ':': return XK_colon; // Shift+;
case '"': return XK_quotedbl; // Shift+'
case '<': return XK_less; // Shift+,
case '>': return XK_greater; // Shift+.
case '?': return XK_question; // Shift+/
case '~': return XK_asciitilde; // Shift+`
// Special characters without shift
case ' ': return XK_space;
case '\t': return XK_Tab;
case '\n': return XK_Return;
case '\b': return XK_BackSpace;
case 27: return XK_Escape; // ESC
case '-': return XK_minus;
case '=': return XK_equal;
case '[': return XK_bracketleft;
case ']': return XK_bracketright;
case '\\': return XK_backslash;
case ';': return XK_semicolon;
case '\'': return XK_apostrophe;
case ',': return XK_comma;
case '.': return XK_period;
case '/': return XK_slash;
case '`': return XK_grave;
case ':': return XK_colon;
default:
std::cerr << "Warning: Character " << static_cast<int>(asciiChar)
<< " not mapped to X11 KeySym. Using XK_space instead."
<< std::endl;
return XK_space;
}
#endif
}
#ifdef __APPLE__
std::map<Keyboard::SpecialKey, KeyCode> Keyboard::specialKeyToVirtualKeyMap = {
{Keyboard::BACKSPACE, kVK_Delete},
{Keyboard::ENTER, kVK_Return},
{Keyboard::TAB, kVK_Tab},
{Keyboard::ESCAPE, kVK_Escape},
{Keyboard::UP, kVK_UpArrow},
{Keyboard::DOWN, kVK_DownArrow},
{Keyboard::RIGHT, kVK_RightArrow},
{Keyboard::LEFT, kVK_LeftArrow},
{Keyboard::META, kVK_Command},
{Keyboard::ALT, kVK_Option},
{Keyboard::CONTROL, kVK_Control},
{Keyboard::SHIFT, kVK_Shift},
{Keyboard::CAPSLOCK, kVK_CapsLock},
{Keyboard::F1, kVK_F1},
{Keyboard::F2, kVK_F2},
{Keyboard::F3, kVK_F3},
{Keyboard::F4, kVK_F4},
{Keyboard::F5, kVK_F5},
{Keyboard::F6, kVK_F6},
{Keyboard::F7, kVK_F7},
{Keyboard::F8, kVK_F8},
{Keyboard::F9, kVK_F9},
{Keyboard::F10, kVK_F10},
{Keyboard::F11, kVK_F11},
{Keyboard::F12, kVK_F12}};
#endif
#ifdef __linux__
std::map<Keyboard::SpecialKey, KeyCode> Keyboard::specialKeyToVirtualKeyMap = {
{Keyboard::BACKSPACE, XK_BackSpace},
{Keyboard::ENTER, XK_Return},
{Keyboard::TAB, XK_Tab},
{Keyboard::ESCAPE, XK_Escape},
{Keyboard::UP, XK_Up},
{Keyboard::DOWN, XK_Down},
{Keyboard::RIGHT, XK_Right},
{Keyboard::LEFT, XK_Left},
{Keyboard::META, XK_Super_L}, // Super/Windows key
{Keyboard::ALT, XK_Alt_L},
{Keyboard::CONTROL, XK_Control_L},
{Keyboard::SHIFT, XK_Shift_L},
{Keyboard::CAPSLOCK, XK_Caps_Lock},
{Keyboard::F1, XK_F1},
{Keyboard::F2, XK_F2},
{Keyboard::F3, XK_F3},
{Keyboard::F4, XK_F4},
{Keyboard::F5, XK_F5},
{Keyboard::F6, XK_F6},
{Keyboard::F7, XK_F7},
{Keyboard::F8, XK_F8},
{Keyboard::F9, XK_F9},
{Keyboard::F10, XK_F10},
{Keyboard::F11, XK_F11},
{Keyboard::F12, XK_F12}};
#endif
char Keyboard::VirtualKeyToAscii(KeyCode virtualKey) {
#ifdef __APPLE__
auto map = asciiToVirtualKeyMap;
auto it = std::find_if(
map.begin(),
map.end(),
[virtualKey](const std::pair<char, int> &p) {
return p.second == virtualKey;
}
);
if (it == map.end()) {
return INVALID_ASCII;
}
return it->first;
#endif
#ifdef _WIN32
// Convert the virtual key code to a scan code
UINT scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
// Convert the scan code to the corresponding character
char character = 0;
BYTE keyboardState[256] = {0};
GetKeyboardState(keyboardState);
wchar_t buffer[2];
if (ToUnicode(virtualKey, scanCode, keyboardState, buffer, 2, 0) == 1) {
character = static_cast<char>(buffer[0]);
}
return character;
#endif
#ifdef __linux__
InitializeX11();
// Get the keycode from keysym
KeyCode keycode = XKeysymToKeycode(display, virtualKey);
if (keycode == 0) {
return INVALID_ASCII;
}
// Get the current keyboard state
char keys[32];
XQueryKeymap(display, keys);
// Get the current modifier state
XKeyEvent event;
event.display = display;
event.window = rootWindow;
event.root = rootWindow;
event.subwindow = None;
event.time = CurrentTime;
event.x = event.y = event.x_root = event.y_root = 0;
event.same_screen = True;
event.keycode = keycode;
event.state = 0;
// Try to get the character with different modifier states
KeySym keysym;
XLookupString(&event, nullptr, 0, &keysym, nullptr);
// Convert keysym to ASCII if possible
if (keysym >= 0x20 && keysym <= 0x7E) { // Printable ASCII range
return static_cast<char>(keysym);
}
return INVALID_ASCII;
#endif
}
Keyboard::SpecialKey Keyboard::VirtualKeyToSpecialKey(KeyCode virtualKey) {
#ifdef __APPLE__
switch (virtualKey) {
case 123:
return Keyboard::LEFT;
case 124:
return Keyboard::RIGHT;
case 125:
return Keyboard::DOWN;
case 126:
return Keyboard::UP;
case 36:
return Keyboard::ENTER;
case 48:
return Keyboard::TAB;
case 51:
return Keyboard::BACKSPACE;
case 53:
return Keyboard::ESCAPE;
case 55:
return Keyboard::META;
case 56:
return Keyboard::SHIFT;
case 57:
return Keyboard::CAPSLOCK;
case 58:
return Keyboard::ALT;
case 59:
return Keyboard::CONTROL;
}
#endif
#ifdef _WIN32
switch (virtualKey) {
case VK_LEFT:
return Keyboard::LEFT;
case VK_RIGHT:
return Keyboard::RIGHT;
case VK_DOWN:
return Keyboard::DOWN;
case VK_UP:
return Keyboard::UP;
case VK_RETURN:
return Keyboard::ENTER;
case VK_TAB:
return Keyboard::TAB;
case VK_BACK:
return Keyboard::BACKSPACE;
case VK_ESCAPE:
return Keyboard::ESCAPE;
case VK_LWIN:
case VK_RWIN:
return Keyboard::META;
case VK_SHIFT:
return Keyboard::SHIFT;
case VK_CAPITAL:
return Keyboard::CAPSLOCK;
case VK_MENU:
return Keyboard::ALT;
case VK_CONTROL:
return Keyboard::CONTROL;
}
#endif
#ifdef __linux__
switch (virtualKey) {
case XK_Left:
return Keyboard::LEFT;
case XK_Right:
return Keyboard::RIGHT;
case XK_Down:
return Keyboard::DOWN;
case XK_Up:
return Keyboard::UP;
case XK_Return:
return Keyboard::ENTER;
case XK_Tab:
return Keyboard::TAB;
case XK_BackSpace:
return Keyboard::BACKSPACE;
case XK_Escape:
return Keyboard::ESCAPE;
case XK_Super_L:
case XK_Super_R:
return Keyboard::META;
case XK_Shift_L:
case XK_Shift_R:
return Keyboard::SHIFT;
case XK_Caps_Lock:
return Keyboard::CAPSLOCK;
case XK_Alt_L:
case XK_Alt_R:
return Keyboard::ALT;
case XK_Control_L:
case XK_Control_R:
return Keyboard::CONTROL;
}
#endif
// Default case for all platforms
return static_cast<Keyboard::SpecialKey>(0);
}
#ifdef _WIN32
std::map<Keyboard::SpecialKey, KeyCode> Keyboard::specialKeyToVirtualKeyMap = {
{Keyboard::BACKSPACE, VK_BACK},
{Keyboard::ENTER, VK_RETURN},
{Keyboard::TAB, VK_TAB},
{Keyboard::ESCAPE, VK_ESCAPE},
{Keyboard::UP, VK_UP},
{Keyboard::DOWN, VK_DOWN},
{Keyboard::RIGHT, VK_RIGHT},
{Keyboard::LEFT, VK_LEFT},
{Keyboard::META, VK_LWIN},
{Keyboard::ALT, VK_MENU},
{Keyboard::CONTROL, VK_CONTROL},
{Keyboard::SHIFT, VK_SHIFT},
{Keyboard::CAPSLOCK, VK_CAPITAL},
{Keyboard::F1, VK_F1},
{Keyboard::F2, VK_F2},
{Keyboard::F3, VK_F3},
{Keyboard::F4, VK_F4},
{Keyboard::F5, VK_F5},
{Keyboard::F6, VK_F6},
{Keyboard::F7, VK_F7},
{Keyboard::F8, VK_F8},
{Keyboard::F9, VK_F9},
{Keyboard::F10, VK_F10},
{Keyboard::F11, VK_F11},
{Keyboard::F12, VK_F12}};
#endif
#ifdef __APPLE__
std::map<char, int> Keyboard::asciiToVirtualKeyMap = {
// 数字
{'0', kVK_ANSI_0}, {'1', kVK_ANSI_1}, {'2', kVK_ANSI_2},
{'3', kVK_ANSI_3}, {'4', kVK_ANSI_4}, {'5', kVK_ANSI_5},
{'6', kVK_ANSI_6}, {'7', kVK_ANSI_7}, {'8', kVK_ANSI_8},
{'9', kVK_ANSI_9},
// 字母(只使用小写)
{'a', kVK_ANSI_A}, {'b', kVK_ANSI_B}, {'c', kVK_ANSI_C},
{'d', kVK_ANSI_D}, {'e', kVK_ANSI_E}, {'f', kVK_ANSI_F},
{'g', kVK_ANSI_G}, {'h', kVK_ANSI_H}, {'i', kVK_ANSI_I},
{'j', kVK_ANSI_J}, {'k', kVK_ANSI_K}, {'l', kVK_ANSI_L},
{'m', kVK_ANSI_M}, {'n', kVK_ANSI_N}, {'o', kVK_ANSI_O},
{'p', kVK_ANSI_P}, {'q', kVK_ANSI_Q}, {'r', kVK_ANSI_R},
{'s', kVK_ANSI_S}, {'t', kVK_ANSI_T}, {'u', kVK_ANSI_U},
{'v', kVK_ANSI_V}, {'w', kVK_ANSI_W}, {'x', kVK_ANSI_X},
{'y', kVK_ANSI_Y}, {'z', kVK_ANSI_Z},
// 特殊字符(不需要Shift的)
{' ', kVK_Space},
{'-', kVK_ANSI_Minus},
{'=', kVK_ANSI_Equal},
{'[', kVK_ANSI_LeftBracket},
{']', kVK_ANSI_RightBracket},
{'\\', kVK_ANSI_Backslash},
{';', kVK_ANSI_Semicolon},
{'\'', kVK_ANSI_Quote},
{',', kVK_ANSI_Comma},
{'.', kVK_ANSI_Period},
{'/', kVK_ANSI_Slash},
{'`', kVK_ANSI_Grave},
// 需要Shift的字符(映射到对应数字或符号键)
{'!', kVK_ANSI_1}, // Shift+1
{'@', kVK_ANSI_2}, // Shift+2
{'#', kVK_ANSI_3}, // Shift+3
{'$', kVK_ANSI_4}, // Shift+4
{'%', kVK_ANSI_5}, // Shift+5
{'^', kVK_ANSI_6}, // Shift+6
{'&', kVK_ANSI_7}, // Shift+7
{'*', kVK_ANSI_8}, // Shift+8
{'(', kVK_ANSI_9}, // Shift+9
{')', kVK_ANSI_0}, // Shift+0
{'_', kVK_ANSI_Minus}, // Shift+-
{'+', kVK_ANSI_Equal}, // Shift+=
{'{', kVK_ANSI_LeftBracket}, // Shift+[
{'}', kVK_ANSI_RightBracket}, // Shift+]
{'|', kVK_ANSI_Backslash}, // Shift+\
{':', kVK_ANSI_Semicolon}, // Shift+;
{'"', kVK_ANSI_Quote}, // Shift+'
{'<', kVK_ANSI_Comma}, // Shift+,
{'>', kVK_ANSI_Period}, // Shift+.
{'?', kVK_ANSI_Slash}, // Shift+/
{'~', kVK_ANSI_Grave} // Shift+`
};
#endif
} // namespace Robot
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#include <cctype>
#include <string>
#include <map>
#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef __APPLE__
#import <Carbon/Carbon.h>
#endif
#ifdef __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#endif
#include <atomic>
#include <chrono>
#include <set>
#include <thread>
namespace Robot {
#ifdef __APPLE__
typedef CGKeyCode KeyCode;
#endif
#ifdef _WIN32
typedef WORD KeyCode;
#endif
#ifdef __linux__
typedef KeySym KeyCode;
#endif
class Keyboard {
public:
enum SpecialKey {
BACKSPACE,
ENTER,
TAB,
ESCAPE,
UP,
DOWN,
RIGHT,
LEFT,
META,
ALT,
CONTROL,
SHIFT,
CAPSLOCK,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12
};
static const char INVALID_ASCII;
Keyboard() = delete;
virtual ~Keyboard() = default;
static void Type(const std::string& query);
static void TypeHumanLike(const std::string& query);
static void Click(char asciiChar);
static void Click(SpecialKey specialKey);
static void HoldStart(char asciiChar);
static void HoldStart(SpecialKey specialKey);
static void HoldStop(char asciiChar);
static void HoldStop(SpecialKey specialKey);
static void Press(char asciiChar);
static void Press(SpecialKey specialKey);
static void Release(char asciiChar);
static void Release(SpecialKey specialKey);
static char VirtualKeyToAscii(KeyCode virtualKey);
static SpecialKey VirtualKeyToSpecialKey(KeyCode virtualKey);
private:
static std::thread keyPressThread;
static std::atomic<bool> continueHolding;
static std::set<char> heldAsciiChars;
static std::set<SpecialKey> heldSpecialKeys;
static void KeyHoldThread();
static int delay;
static KeyCode AsciiToVirtualKey(char asciiChar);
static KeyCode SpecialKeyToVirtualKey(SpecialKey specialKey);
static std::map<SpecialKey, KeyCode> specialKeyToVirtualKeyMap;
// Platform-specific implementations
#ifdef __linux__
static Display* display;
static Window rootWindow;
static void InitializeX11();
static void CleanupX11();
#endif
// note: windows alternative doesn't use map
#ifdef __APPLE__
static std::map<char, int> asciiToVirtualKeyMap;
#endif
};
} // namespace Robot
+375
View File
@@ -0,0 +1,375 @@
#include "./Mouse.h"
#include "./Utils.h"
#ifdef _WIN32
#include <Windows.h>
#elif __APPLE__
#include <ApplicationServices/ApplicationServices.h>
#elif __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <iostream>
#include <cstring>
#endif
namespace Robot {
unsigned int Mouse::delay = 16;
bool Mouse::isPressed = false;
MouseButton Mouse::pressedButton = MouseButton::LEFT_BUTTON;
#ifdef __linux__
Display* Mouse::display = nullptr;
Window Mouse::rootWindow = 0;
void Mouse::InitializeX11() {
if (display == nullptr) {
display = XOpenDisplay(nullptr);
if (display == nullptr) {
throw std::runtime_error("Cannot open X11 display");
}
rootWindow = DefaultRootWindow(display);
}
}
void Mouse::CleanupX11() {
if (display != nullptr) {
XCloseDisplay(display);
display = nullptr;
}
}
#endif
#ifdef _WIN32
POINT Mouse::getCurrentPosition() {
POINT winPoint;
GetCursorPos(&winPoint);
return winPoint;
}
#elif __APPLE__
CGPoint Mouse::getCurrentPosition() {
CGEventRef event = CGEventCreate(nullptr);
CGPoint cursor = CGEventGetLocation(event);
CFRelease(event);
return cursor;
}
#elif __linux__
Robot::Point Mouse::getCurrentPosition() {
InitializeX11();
Robot::Point point;
Window root_return, child_return;
int root_x, root_y;
int win_x, win_y;
unsigned int mask_return;
XQueryPointer(display, rootWindow, &root_return, &child_return,
&root_x, &root_y, &win_x, &win_y, &mask_return);
point.x = root_x;
point.y = root_y;
return point;
}
#endif
void Mouse::Move(Robot::Point point) {
#ifdef _WIN32
SetCursorPos(point.x, point.y);
#elif __APPLE__
CGPoint target = CGPointMake(point.x, point.y);
CGEventRef event = CGEventCreateMouseEvent(
nullptr,
kCGEventMouseMoved,
target,
kCGMouseButtonLeft
);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
if (Mouse::isPressed) {
Mouse::MoveWithButtonPressed(point, Mouse::pressedButton);
}
#elif __linux__
InitializeX11();
// Move the mouse using XTest
XTestFakeMotionEvent(display, -1, point.x, point.y, CurrentTime);
XFlush(display);
if (Mouse::isPressed) {
Mouse::MoveWithButtonPressed(point, Mouse::pressedButton);
}
#endif
}
Robot::Point Mouse::GetPosition() {
// TODO: how long exactly should we wait?
Robot::delay(16);
Robot::Point point;
#ifdef _WIN32
POINT cursor = getCurrentPosition();
#elif __APPLE__
CGPoint cursor = getCurrentPosition();
#elif __linux__
Robot::Point cursor = getCurrentPosition();
point.x = cursor.x;
point.y = cursor.y;
#endif
point.x = cursor.x;
point.y = cursor.y;
return point;
}
void Mouse::ToggleButton(bool down, MouseButton button, bool doubleClick) {
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_MOUSE;
input.mi.dwFlags =
(button == MouseButton::LEFT_BUTTON
? (down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP)
: button == MouseButton::RIGHT_BUTTON
? (down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP)
: (down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP));
SendInput(1, &input, sizeof(INPUT));
#elif __APPLE__
CGPoint currentPosition = getCurrentPosition();
CGEventType buttonType;
switch (button) {
case MouseButton::LEFT_BUTTON:
buttonType = down ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
break;
case MouseButton::RIGHT_BUTTON:
buttonType = down ? kCGEventRightMouseDown : kCGEventRightMouseUp;
break;
case MouseButton::CENTER_BUTTON:
buttonType = down ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
break;
}
CGEventRef buttonEvent = CGEventCreateMouseEvent(
nullptr,
buttonType,
currentPosition,
(button == MouseButton::CENTER_BUTTON) ? kCGMouseButtonCenter
: kCGMouseButtonLeft
);
if (doubleClick) {
CGEventSetIntegerValueField(buttonEvent, kCGMouseEventClickState, 2);
}
CGEventPost(kCGHIDEventTap, buttonEvent);
CFRelease(buttonEvent);
#elif __linux__
InitializeX11();
unsigned int buttonCode;
switch (button) {
case MouseButton::LEFT_BUTTON:
buttonCode = 1;
break;
case MouseButton::RIGHT_BUTTON:
buttonCode = 3;
break;
case MouseButton::CENTER_BUTTON:
buttonCode = 2;
break;
default:
buttonCode = 1;
}
if (down) {
XTestFakeButtonEvent(display, buttonCode, True, CurrentTime);
} else {
XTestFakeButtonEvent(display, buttonCode, False, CurrentTime);
}
XFlush(display);
// Handle double click
if (doubleClick && !down) {
Robot::delay(10);
XTestFakeButtonEvent(display, buttonCode, True, CurrentTime);
XTestFakeButtonEvent(display, buttonCode, False, CurrentTime);
XFlush(display);
}
#endif
if (down) {
Mouse::isPressed = true;
Mouse::pressedButton = button;
} else {
Mouse::isPressed = false;
}
}
void Mouse::MoveWithButtonPressed(Robot::Point point, MouseButton button) {
#ifdef _WIN32
// On Windows, just calling Move is enough as it will keep the button state.
Mouse::Move(point);
#elif __APPLE__
CGPoint target = CGPointMake(point.x, point.y);
CGEventType dragEventType;
CGMouseButton cgButton;
switch (button) {
case MouseButton::LEFT_BUTTON:
dragEventType = kCGEventLeftMouseDragged;
cgButton = kCGMouseButtonLeft;
break;
case MouseButton::RIGHT_BUTTON:
dragEventType = kCGEventRightMouseDragged;
cgButton = kCGMouseButtonRight;
break;
case MouseButton::CENTER_BUTTON:
dragEventType = kCGEventOtherMouseDragged;
cgButton = kCGMouseButtonCenter;
break;
}
CGEventRef mouseDragEvent =
CGEventCreateMouseEvent(nullptr, dragEventType, target, cgButton);
CGEventPost(kCGHIDEventTap, mouseDragEvent);
CFRelease(mouseDragEvent);
#elif __linux__
InitializeX11();
// For dragging, we simulate mouse motion with button pressed
unsigned int buttonState = 0;
switch (button) {
case MouseButton::LEFT_BUTTON:
buttonState = Button1Mask;
break;
case MouseButton::RIGHT_BUTTON:
buttonState = Button3Mask;
break;
case MouseButton::CENTER_BUTTON:
buttonState = Button2Mask;
break;
}
// Create a motion event with the appropriate button state
XTestFakeMotionEvent(display, -1, point.x, point.y, CurrentTime);
// Ensure the button is held down
unsigned int buttonCode;
switch (button) {
case MouseButton::LEFT_BUTTON:
buttonCode = 1;
break;
case MouseButton::RIGHT_BUTTON:
buttonCode = 3;
break;
case MouseButton::CENTER_BUTTON:
buttonCode = 2;
break;
}
XFlush(display);
#endif
}
void Mouse::Click(MouseButton button) {
ToggleButton(true, button);
Robot::delay(10); // add a little delay
ToggleButton(false, button);
}
void Mouse::DoubleClick(MouseButton button) {
Click(button);
Robot::delay(80);
Click(button);
}
void Mouse::ScrollBy(int y, int x) {
#ifdef _WIN32
INPUT input = {0};
input.type = INPUT_MOUSE;
input.mi.dwFlags = MOUSEEVENTF_WHEEL;
input.mi.mouseData = static_cast<DWORD>(WHEEL_DELTA * y);
SendInput(1, &input, sizeof(INPUT));
input.mi.dwFlags = MOUSEEVENTF_HWHEEL;
input.mi.mouseData = static_cast<DWORD>(WHEEL_DELTA * x);
SendInput(1, &input, sizeof(INPUT));
#elif __APPLE__
CGEventRef scrollEvent =
CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitPixel, 2, y, x);
CGEventPost(kCGHIDEventTap, scrollEvent);
CFRelease(scrollEvent);
#elif __linux__
InitializeX11();
// Vertical scrolling
if (y != 0) {
unsigned int buttonCode = (y > 0) ? 4 : 5; // 4=up, 5=down
for (int i = 0; i < std::abs(y); i++) {
XTestFakeButtonEvent(display, buttonCode, True, CurrentTime);
XTestFakeButtonEvent(display, buttonCode, False, CurrentTime);
XFlush(display);
Robot::delay(10);
}
}
// Horizontal scrolling
if (x != 0) {
unsigned int buttonCode = (x > 0) ? 7 : 6; // 6=left, 7=right
for (int i = 0; i < std::abs(x); i++) {
XTestFakeButtonEvent(display, buttonCode, True, CurrentTime);
XTestFakeButtonEvent(display, buttonCode, False, CurrentTime);
XFlush(display);
Robot::delay(10);
}
}
#endif
}
void Mouse::Drag(Robot::Point toPoint) {
Robot::Mouse::ToggleButton(true, Robot::MouseButton::LEFT_BUTTON);
Robot::delay(10);
Mouse::Move(toPoint);
Robot::delay(10);
Mouse::ToggleButton(false, MouseButton::LEFT_BUTTON);
}
void Mouse::MoveSmooth(Robot::Point point) {
Robot::Point currentPosition = GetPosition();
int dx = point.x - currentPosition.x;
int dy = point.y - currentPosition.y;
int steps = (std::abs(dx) > std::abs(dy)) ? std::abs(dx) : std::abs(dy);
float deltaX = static_cast<float>(dx) / steps;
float deltaY = static_cast<float>(dy) / steps;
for (int i = 1; i <= steps; i++) {
Robot::Point stepPosition;
stepPosition.x = currentPosition.x + static_cast<int>(deltaX * i);
stepPosition.y = currentPosition.y + static_cast<int>(deltaY * i);
if (Mouse::isPressed) {
MoveWithButtonPressed(stepPosition, Mouse::pressedButton);
} else {
Move(stepPosition);
}
Robot::delay(1);
}
}
void Mouse::DragSmooth(Robot::Point toPoint) {
Robot::Mouse::ToggleButton(true, Robot::MouseButton::LEFT_BUTTON);
Robot::delay(10);
Mouse::MoveSmooth(toPoint);
Robot::delay(10);
Mouse::ToggleButton(false, MouseButton::LEFT_BUTTON);
}
} // namespace Robot
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include "./types.h"
#include <cstddef>
#include <cstdint>
#ifdef _WIN32
#include <Windows.h>
#elif __APPLE__
#include <ApplicationServices/ApplicationServices.h>
#elif __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
namespace Robot {
enum class MouseButton : uint8_t {
LEFT_BUTTON = 0,
RIGHT_BUTTON = 1,
CENTER_BUTTON = 2
};
class Mouse {
public:
static unsigned int delay;
static bool isPressed;
static MouseButton pressedButton;
Mouse() = delete;
static void Move(Robot::Point point);
static void MoveSmooth(Robot::Point point);
static void Drag(Robot::Point toPoint);
static void DragSmooth(Robot::Point toPoint);
static Robot::Point GetPosition();
static void ToggleButton(
bool down, MouseButton button, bool doubleClick = false
);
static void Click(MouseButton button);
static void DoubleClick(MouseButton button);
static void ScrollBy(int y, int x = 0);
private:
static void MoveWithButtonPressed(Robot::Point point, MouseButton button);
// Platform-specific implementations
#ifdef __linux__
static Display* display;
static Window rootWindow;
static void InitializeX11();
static void CleanupX11();
#endif
#ifdef _WIN32
static POINT getCurrentPosition();
#elif __APPLE__
static CGPoint getCurrentPosition();
#elif __linux__
static Robot::Point getCurrentPosition();
#endif
};
} // namespace Robot
+47
View File
@@ -0,0 +1,47 @@
//
// Created by misaki on 2026/1/25.
//
#pragma once
// 包含所有必要头文件
#include "Autogui.h"
// 常用常量
namespace AutoGUI {
// 鼠标按钮常量
const Button LEFT = Button::LEFT;
const Button RIGHT = Button::RIGHT;
const Button MIDDLE = Button::MIDDLE;
// 常用键常量(字符串形式,用于press、keyDown等函数)
namespace Keys {
const std::string BACKSPACE = "backspace";
const std::string ENTER = "enter";
const std::string TAB = "tab";
const std::string ESCAPE = "escape";
const std::string ESC = "esc";
const std::string UP = "up";
const std::string DOWN = "down";
const std::string RIGHT = "right";
const std::string LEFT = "left";
const std::string WIN = "win";
const std::string COMMAND = "command";
const std::string ALT = "alt";
const std::string CTRL = "ctrl";
const std::string SHIFT = "shift";
const std::string CAPSLOCK = "capslock";
const std::string F1 = "f1";
const std::string F2 = "f2";
const std::string F3 = "f3";
const std::string F4 = "f4";
const std::string F5 = "f5";
const std::string F6 = "f6";
const std::string F7 = "f7";
const std::string F8 = "f8";
const std::string F9 = "f9";
const std::string F10 = "f10";
const std::string F11 = "f11";
const std::string F12 = "f12";
const std::string SPACE = "space";
}
}
+9
View File
@@ -0,0 +1,9 @@
#include "./Utils.h"
namespace Robot {
void delay(unsigned int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
} // namespace Robot
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <cctype>
#include <chrono>
#include <cstring>
#include <thread>
namespace Robot {
void delay(unsigned int ms);
namespace KeyUtils {
// 检查字符是否需要Shift键
inline bool NeedsShift(char c) {
// 大写字母需要Shift
if (c >= 'A' && c <= 'Z') {
return true;
}
// 特殊符号需要Shift
const char* shiftChars = "!@#$%^&*()_+{}|:\"<>?~";
return (strchr(shiftChars, c) != nullptr);
}
// 获取字符的基础键(物理键位)
inline char GetBaseKey(char c) {
// 对于字母,返回小写形式
if (c >= 'A' && c <= 'Z') {
return std::tolower(c);
}
// 对于数字,保持不变
if (c >= '0' && c <= '9') {
return c;
}
// 对于需要Shift的符号,返回对应的基础键
switch (c) {
case '!': return '1';
case '@': return '2';
case '#': return '3';
case '$': return '4';
case '%': return '5';
case '^': return '6';
case '&': return '7';
case '*': return '8';
case '(': return '9';
case ')': return '0';
case '_': return '-';
case '+': return '=';
case '{': return '[';
case '}': return ']';
case '|': return '\\';
case ':': return ';';
case '"': return '\'';
case '<': return ',';
case '>': return '.';
case '?': return '/';
case '~': return '`';
default: return c;
}
}
// 检查字符是否是有效的ASCII字符
inline bool IsValidAscii(char c) {
return c >= 32 && c <= 126; // 可打印ASCII字符
}
} // namespace KeyUtils
} // namespace Robot
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <cmath>
namespace Robot {
struct Point {
int x;
int y;
[[nodiscard]] double Distance(Point target) const {
return sqrt(pow(target.x - x, 2) + pow(target.y - y, 2));
}
};
} // namespace Robot