在 ARM Linux 系统中,触摸事件本质上是通过/dev/input/event*设备文件上报的input_event结构体数据。实现思路分为两大核心方向:
1.用户态模拟输入:通过 Linux 输入子系统的 API 直接向 input 设备写入事件
2.QT 内部事件模拟:在 QT 应用内部拦截并转换坐标数据为 QT 事件
下面是三种具体实现方案,从系统级到应用级,覆盖不同场景需求。
二、方案详解与代码实现
方案 1:基于 uinput 内核模块的系统级事件模拟(推荐)
核心原理:利用 Linux 的uinput(user input)内核模块创建虚拟输入设备,直接向内核输入子系统上报标准的触摸事件,系统和 QT 程序都会认为这是真实的硬件触摸事件。
前置条件
1、内核已启用CONFIG_UINPUT配置
2、应用有访问/dev/uinput的权限(通常需要 root 或添加 udev 规则)
3、安装必要头文件:apt-get install linux-libc-dev(x86)/ 对应 ARM 交叉编译库
完整实现代码
#include <fcntl.h>#include <unistd.h>#include <linux/uinput.h>#include <cstring>#include <QCoreApplication>#include <QThread>class UInputTouchSimulator : public QObject{ Q_OBJECTpublic: UInputTouchSimulator(QObject *parent = nullptr) : QObject(parent), uinputFd(-1) { // 初始化uinput设备 initUInputDevice(); } ~UInputTouchSimulator() { if (uinputFd >= 0) { ioctl(uinputFd, UI_DEV_DESTROY); close(uinputFd); } }
// 模拟触摸事件(外部调用此函数传入坐标) void simulateTouch(int x, int y, bool press) { if (uinputFd < 0) return; // 1. 上报X坐标 struct input_event ev; memset(&ev, 0, sizeof(ev)); ev.type = EV_ABS; ev.code = ABS_MT_POSITION_X; ev.value = x; write(uinputFd, &ev, sizeof(ev)); // 2. 上报Y坐标 memset(&ev, 0, sizeof(ev)); ev.type = EV_ABS; ev.code = ABS_MT_POSITION_Y; ev.value = y; write(uinputFd, &ev, sizeof(ev)); // 3. 上报触摸状态(按下/抬起) memset(&ev, 0, sizeof(ev)); ev.type = EV_KEY; ev.code = BTN_TOUCH; ev.value = press ? 1 : 0; write(uinputFd, &ev, sizeof(ev)); // 4. 同步事件 memset(&ev, 0, sizeof(ev)); ev.type = EV_SYN; ev.code = SYN_REPORT; ev.value = 0; write(uinputFd, &ev, sizeof(ev)); }private: int uinputFd; void initUInputDevice() { // 打开uinput设备 uinputFd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (uinputFd < 0) { qCritical() << "打开uinput失败:" << strerror(errno); return; } // 启用需要的事件类型 ioctl(uinputFd, UI_SET_EVBIT, EV_KEY); ioctl(uinputFd, UI_SET_KEYBIT, BTN_TOUCH); ioctl(uinputFd, UI_SET_EVBIT, EV_ABS); ioctl(uinputFd, UI_SET_ABSBIT, ABS_MT_POSITION_X); ioctl(uinputFd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); // 配置设备参数 struct uinput_setup usetup; memset(&usetup, 0, sizeof(usetup)); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x1234; // 自定义厂商ID usetup.id.product = 0x5678; // 自定义产品ID strcpy(usetup.name, "Virtual Touchscreen"); ioctl(uinputFd, UI_DEV_SETUP, &usetup); ioctl(uinputFd, UI_DEV_CREATE); // 等待设备创建完成 QThread::msleep(100); }};// ========== 调用示例 ==========// 在QT Widget程序中使用// #include "uinput_touch_simulator.h"// // // 创建模拟器实例// UInputTouchSimulator *touchSim = new UInputTouchSimulator(this);// // // 接收外部坐标数据后调用(示例:模拟触摸按下(100,200))// touchSim->simulateTouch(100, 200, true);// // // 模拟触摸抬起// touchSim->simulateTouch(100, 200, false);
编译配置(.pro 文件)
QT += core gui widgetsSOURCES += main.cpp \\ uinput_touch_simulator.cppHEADERS += uinput_touch_simulator.h
ARM交叉编译时指定编译器# QMAKE_CC = arm-linux-gnueabihf-gcc# QMAKE_CXX = arm-linux-gnueabihf-g++# 链接必要的库LIBS += -lrt -lpthread
方案 2:QT 内部事件模拟(轻量级)
核心原理:不依赖系统输入子系统,直接在 QT 应用内部构造QMouseEvent或QTouchEvent并通过QCoreApplication::postEvent发送给目标窗口,仅在 QT 应用内部生效。
完整实现代码
#include <QApplication>#include <QWidget>#include <QMouseEvent>#include <QTouchEvent>#include <QTimer>class QtTouchSimulator : public QObject{ Q_OBJECTpublic: QtTouchSimulator(QWidget *targetWidget, QObject *parent = nullptr) : QObject(parent), target(targetWidget) {} // 模拟单点触摸(兼容QT5.12) void simulateMouseTouch(int x, int y, bool press) { if (!target) return; // 构造鼠标事件(模拟触摸) QMouseEvent::Type eventType = press ? QMouseEvent::MouseButtonPress : QMouseEvent::MouseButtonRelease; QMouseEvent *event = new QMouseEvent( eventType, QPoint(x, y), // 全局坐标 target->mapFromGlobal(QPoint(x, y)), // 本地坐标 Qt::LeftButton, // 模拟触摸的左键 Qt::LeftButton, // 按下的按钮 Qt::NoModifier // 无修饰键 ); // 发送事件到目标窗口 QCoreApplication::postEvent(target, event); // 如果是按下,模拟鼠标移动(可选) if (press) { QTimer::singleShot(10, [=]() { QMouseEvent *moveEvent = new QMouseEvent( QMouseEvent::MouseMove, QPoint(x, y), target->mapFromGlobal(QPoint(x, y)), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier ); QCoreApplication::postEvent(target, moveEvent); }); } } // 模拟多点触摸(QT5.12+) void simulateMultiTouch(int id1, int x1, int y1, int id2, int x2, int y2, bool press) { if (!target) return; QList<QTouchEvent::TouchPoint> touchPoints;
// 第一个触摸点 QTouchEvent::TouchPoint tp1; tp1.setId(id1); tp1.setScreenPos(QPointF(x1, y1)); tp1.setPos(target->mapFromGlobal(QPoint(x1, y1))); tp1.setState(press ? QTouchEvent::TouchPointPressed : QTouchEvent::TouchPointReleased); touchPoints.append(tp1); // 第二个触摸点 QTouchEvent::TouchPoint tp2; tp2.setId(id2); tp2.setScreenPos(QPointF(x2, y2)); tp2.setPos(target->mapFromGlobal(QPoint(x2, y2))); tp2.setState(press ? QTouchEvent::TouchPointPressed : QTouchEvent::TouchPointReleased); touchPoints.append(tp2); // 构造触摸事件 QTouchEvent *event = new QTouchEvent( press ? QEvent::TouchBegin : QEvent::TouchEnd, Qt::TouchScreen, Qt::NoModifier, QTouchEvent::TouchScreen, touchPoints ); QCoreApplication::postEvent(target, event); }private: QWidget *target;};// ========== 调用示例 ==========// int main(int argc, char *argv[])// {// QApplication a(argc, argv);// MainWindow w;// w.show();// // // 创建QT内部触摸模拟器,指定目标窗口// QtTouchSimulator simulator(&w);// // // 模拟触摸按下(200,300)// simulator.simulateMouseTouch(200, 300, true);// // // 500ms后模拟触摸抬起// QTimer::singleShot(500, [&]() {// simulator.simulateMouseTouch(200, 300, false);// });// // return a.exec();// }
方案 3:基于 tslib 的触摸事件模拟(适配传统触摸屏)
核心原理:针对使用 tslib 的 ARM Linux 系统,直接操作 tslib 的设备节点写入触摸数据,适用于老旧的触摸屏驱动架构。
前置条件
1、系统已安装 tslib:apt-get install libts-dev(ARM 平台需交叉编译 tslib)
2、配置 TSLIB 环境变量:export TSLIB_TSDEVICE=/dev/input/event0
核心代码实现
#include <tslib.h>#include <QThread>#include <QDebug>class TSLibTouchSimulator {public: TSLibTouchSimulator() { // 打开tslib设备 ts = ts_open(getenv("TSLIB_TSDEVICE"), 1); if (!ts) { qCritical() << "打开tslib设备失败"; return; } // 配置tslib if (ts_config(ts) < 0) { qCritical() << "配置tslib失败"; ts_close(ts); ts = nullptr; } } ~TSLibTouchSimulator() { if (ts) ts_close(ts); }
// 模拟触摸事件 void simulateTouch(int x, int y, bool press) { if (!ts) return; struct ts_sample samp; samp.x = x; samp.y = y; samp.pressure = press ? 100 : 0; // 压力值,非0表示按下 // 写入触摸数据(实际tslib主要用于读取,写入需内核支持) // 替代方案:通过tslib的校准数据转换坐标后,再用方案1上报 qDebug() << "模拟tslib触摸:" << x << "," << y << " 压力:" << samp.pressure; }private: struct tsdev *ts = nullptr;};
三、方案对比与选型建议
|
系统级生效 |
✅ 是(全局事件) |
❌ 仅 QT 应用内 |
❌ 仅适配 tslib 程序 |
|
权限要求 |
✅ 需要 root/udev 权限 |
❌ 普通用户即可 |
❌ 需设备访问权限 |
|
QT 版本兼容性 |
✅ 全版本兼容 |
✅ QT5.12 完美支持 |
✅ 需 tslib 适配 |
|
多点触摸支持 |
✅ 完整支持 |
✅ QT5.12 支持 |
❌ 仅单点 |
|
移植性 |
✅ 跨 ARM Linux 平台 |
✅ 纯 QT 跨平台 |
❌ 依赖 tslib |
|
实现复杂度 |
⭐⭐ 中等 |
⭐ 简单 |
⭐⭐⭐ 较复杂 |
|
适用场景 |
全局触摸模拟、多应用 |
单一 QT 应用 |
老旧触摸屏系统 |
选型建议
1、首选方案 1:如果你需要触摸事件被系统所有应用识别(如 QT 程序 + 系统桌面 + 其他应用),且有权限使用 root/udev 规则。
2、备选方案 2:如果仅需在 QT 应用内部处理触摸事件,无需系统级上报,追求轻量级和快速实现。
3、特殊场景方案 3:仅适用于老旧的 ARM Linux 系统(如嵌入式 Linux 3.x),且系统已集成 tslib。
四、关键优化与注意事项
1. 坐标转换
外部坐标可能与 QT 窗口坐标不一致,需进行转换:
// 屏幕坐标转QT窗口本地坐标QPoint screenToWidget(QWidget *widget, int x, int y) { return widget->mapFromGlobal(QPoint(x, y));}// 物理坐标转屏幕坐标(适配不同分辨率)QPoint physicalToScreen(int physX, int physY, int physWidth, int physHeight, int screenWidth, int screenHeight) { int screenX = (physX * screenWidth) / physWidth; int screenY = (physY * screenHeight) / physHeight; return QPoint(screenX, screenY);}
2. 权限配置(方案 1)
创建 udev 规则文件/etc/udev/rules.d/99-uinput.rules:
SUBSYSTEM=="misc", KERNEL=="uinput", MODE="0666", GROUP="input"
重启 udev:udevadm control –reload-rules && udevadm trigger
3. 事件同步
模拟事件时需保证EV_SYN同步事件的发送,否则内核不会处理事件队列。
总结
1、核心方案选择:优先使用uinput 系统级模拟(方案 1)实现全局触摸事件,其次用QT 内部事件模拟(方案 2)实现应用内触摸。
2、关键技术点:系统级事件依赖uinput模块,QT 内部事件依赖QCoreApplication::postEvent,坐标转换需处理屏幕 / 窗口坐标系差异。
3、权限与兼容性:uinput 方案需要 root 权限,QT 内部方案无需特殊权限,tslib 方案仅适用于老旧嵌入式系统。
通过以上方案,你可以根据实际的 ARM Linux 系统环境和权限要求,选择最适合的触摸事件模拟方式,实现外部坐标数据到系统 / 应用级触摸事件的转换和处理。
更多精彩推荐:
Android开发集
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选从 AIDL 到 HIDL:跨语言 Binder 通信的自动化桥接与零拷贝回调优化全栈指南
C/C++编程精选
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选宏之双刃剑:C/C++ 预处理器宏的威力、陷阱与现代化演进全解
开源工场与工具集
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选nlohmann/json:现代 C++ 开发者的 JSON 神器
MCU内核工坊
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选STM32:嵌入式世界的“瑞士军刀”——深度解析意法半导体32位MCU的架构演进、生态优势与全场景应用
拾光札记簿
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选周末遛娃好去处!黄河之巅畅享亲子欢乐时光
数智星河集
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选被算法盯上的岗位:人工智能优先取代的十大职业深度解析与人类突围路径
Docker 容器
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选Docker 原理及使用注意事项(精要版)
linux开发集
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选零拷贝之王:Linux splice() 全面深度解析与高性能实战指南
青衣染霜华
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选脑机接口:从瘫痪患者的“意念行走”到人类智能的下一次跃迁
QT开发记录-专栏
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选Qt 样式表(QSS)终极指南:打造媲美 Web 的精美原生界面
Web/webassembly技术情报局
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选WebAssembly 全栈透视:从应用开发到底层执行的完整技术链路与核心原理深度解析
数据库开发
青衣霜华渡白鸽,公众号:清荷雅集-墨染优选ARM Linux 下 SQLite3 数据库使用全方位指南



