副标题:无需 root、不依赖窗口系统,纯 Qt 方案实现高兼容性触摸事件模拟

| 事件类型 | 类名 | 触发条件 | QWidget 默认响应 |
|---|---|---|---|
| 鼠标事件 | QMouseEvent | 单点点击/移动 | ✅ 自动映射(推荐) |
| 触摸事件 | QTouchEvent | 多点触控设备 | ❌ 需显式启用 |

// touch_simulator.h#pragma once#include<QWidget>#include<QSerialPort>class TouchSimulator : public QWidget {Q_OBJECTpublic:explicitTouchSimulator(QWidget *parent = nullptr);private slots:voidonSerialDataReady();private:voidsimulateClick(int x, int y);QSerialPort m_serial;};
// touch_simulator.cpp#include"touch_simulator.h"#include<QApplication>#include<QMouseEvent>#include<QDebug>TouchSimulator::TouchSimulator(QWidget *parent): QWidget(parent){// 全屏显示(eglfs/linuxfb 常见配置)showFullScreen();// 初始化串口(根据实际设备修改)m_serial.setPortName("ttyS2");m_serial.setBaudRate(QSerialPort::Baud115200);if (m_serial.open(QIODevice::ReadOnly)) {connect(&m_serial, &QSerialPort::readyRead, this, &TouchSimulator::onSerialDataReady);}}voidTouchSimulator::onSerialDataReady(){QByteArray data = m_serial.readAll();qDebug() << "Raw data:" << data;// 协议示例: "TCH:320,240\n"if (data.startsWith("TCH:")) {QString line = QString::fromLatin1(data).trimmed();QStringList parts = line.mid(4).split(',');if (parts.size() == 2) {bool ok1, ok2;int x = parts[0].toInt(&ok1);int y = parts[1].toInt(&ok2);if (ok1 && ok2) {simulateClick(x, y);}}}}voidTouchSimulator::simulateClick(int x, int y){// 在 eglfs/linuxfb 全屏模式下,x/y 即为窗口局部坐标QPoint pos(x, y);// 鼠标按下QMouseEvent press(QEvent::MouseButtonPress, pos, Qt::LeftButton,Qt::LeftButton, Qt::NoModifier);QApplication::sendEvent(this, &press);// 鼠标释放(形成一次点击)QMouseEvent release(QEvent::MouseButtonRelease, pos, Qt::LeftButton,Qt::LeftButton, Qt::NoModifier);QApplication::sendEvent(this, &release);}
💡 关键提示:若你的应用不是全屏,或坐标是屏幕全局坐标,需调用
mapFromGlobal()转换。
// 启用触摸事件setFocusPolicy(Qt::StrongFocus);setAttribute(Qt::WA_AcceptTouchEvents);// 发送触摸事件voidsendTouchEvent(int x, int y){static int id = 0;QTouchEvent::TouchPoint point;point.setId(id++);point.setPos(QPointF(x, y));point.setPressure(1.0);QList<QTouchEvent::TouchPoint> points{point};QTouchEvent begin(QEvent::TouchBegin, nullptr, Qt::NoModifier, points);QTouchEvent end(QEvent::TouchEnd, nullptr, Qt::NoModifier, {});QApplication::sendEvent(this, &begin);QApplication::sendEvent(this, &end);}
📌 结论:除非你明确需要多点触控逻辑,否则优先使用方案 1。
#include<linux/uinput.h>#include<fcntl.h>#include<unistd.h>int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);ioctl(fd, UI_SET_EVBIT, EV_KEY);ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);ioctl(fd, UI_SET_EVBIT, EV_ABS);ioctl(fd, UI_SET_ABSBIT, ABS_X);ioctl(fd, UI_SET_ABSBIT, ABS_Y);struct uinput_setup usetup = {};usetup.id.bustype = BUS_USB;strcpy(usetup.name, "Virtual Touchscreen");usetup.absinfo[0].maximum = 800; // 屏幕宽度usetup.absinfo[1].maximum = 480; // 屏幕高度ioctl(fd, UI_DEV_SETUP, &usetup);ioctl(fd, UI_DEV_CREATE);// 发送坐标write_event(fd, EV_ABS, ABS_X, x);write_event(fd, EV_ABS, ABS_Y, y);write_event(fd, EV_KEY, BTN_TOUCH, 1); // 按下write_event(fd, EV_SYN, SYN_REPORT, 0);// ... 释放


float scaleX = static_cast<float>(width()) / externalMaxX;float scaleY = static_cast<float>(height()) / externalMaxY;int qtX = x * scaleX;int qtY = y * scaleY;
if (m_lastPos == pos && !m_hasReleased) return;qDebug() 打印接收到的坐标QLabel 显示 (x,y) 位置mousePressEvent 中验证事件是否到达
记住:Qt 的强大之处,在于它允许你在应用层优雅地解决系统级问题。善用
QApplication::sendEvent(),你就能让任何数据流变成指尖的魔法。
更多精彩推荐:


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 数据库使用全方位指南