You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Linux系统中使用Qt读取设备文件?

嘿,刚好我之前做过Qt和Linux设备文件交互的项目,结合你的游戏手柄需求,给你梳理下Qt里的典型实现方法,再把你提到的基于@rodrigo思路的实现拆解说明下~

Qt与Linux设备文件交互的典型方法

Qt处理Linux设备文件(比如/dev/input/js0这类字符设备),核心是避免阻塞GUI主线程,常用的有两种方案:

方案1:QSocketNotifier + QFile(推荐非阻塞方式)

这种方式利用Linux的文件描述符监听机制,让Qt在设备有数据可读时主动通知我们,完全不会卡UI,非常适合GUI程序:

  • 步骤拆解:
    1. 用QFile打开目标设备文件,注意要以只读模式打开,并且检查打开是否成功(权限问题很常见)。
    2. 通过QFile::handle()获取设备的文件描述符(fd)。
    3. 创建QSocketNotifier,监听这个fd的可读事件(QSocketNotifier::Read),并绑定槽函数。
    4. 当设备有数据时,槽函数会被触发,此时我们再读取数据并处理。

方案2:QThread + 阻塞读取(适合习惯C风格循环的场景)

如果更熟悉C语言里的阻塞read循环,可以把读取逻辑放到子线程中,避免阻塞主线程的GUI渲染:

  • 步骤拆解:
    1. 自定义一个继承QThread的类,在run()方法里用C标准库的open()打开设备文件。
    2. 在run()里做持续的阻塞read()循环,每次读取到数据后,通过Qt信号把数据发送到主线程。
    3. 主线程绑定这些信号,更新UI显示。
    4. 程序退出或需要停止读取时,调用QThread::requestInterruption()终止循环,并关闭设备文件。

基于@rodrigo思路的Joystick实现示例

下面是完整的头文件和源文件实现,用的是上面推荐的非阻塞方案:

joystick.h

#ifndef JOYSTICK_H
#define JOYSTICK_H

#include <QObject>
#include <QFile>
#include <QSocketNotifier>
#include <linux/joystick.h>

class Joystick : public QObject
{
    Q_OBJECT
public:
    explicit Joystick(const QString &devicePath = "/dev/input/js0", QObject *parent = nullptr);
    ~Joystick();

    // 判断是否成功连接到手柄设备
    bool isConnected() const;

signals:
    // 轴移动信号:axis是轴编号,value是轴的取值(范围-32768到32767)
    void axisMoved(int axis, qint16 value);
    // 按键按下信号:button是按键编号
    void buttonPressed(int button);
    // 按键释放信号:button是按键编号
    void buttonReleased(int button);

private slots:
    // 读取设备数据的槽函数
    void readData();

private:
    QFile m_deviceFile;
    QSocketNotifier *m_notifier;
    bool m_connected; // 标记设备是否连接成功
};

#endif // JOYSTICK_H

joystick.cpp

#include "joystick.h"
#include <QDebug>

Joystick::Joystick(const QString &devicePath, QObject *parent)
    : QObject(parent), m_connected(false)
{
    m_deviceFile.setFileName(devicePath);
    // 以只读方式打开设备文件
    if (m_deviceFile.open(QIODevice::ReadOnly)) {
        int fd = m_deviceFile.handle();
        // 创建SocketNotifier监听可读事件
        m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
        connect(m_notifier, &QSocketNotifier::activated, this, &Joystick::readData);
        m_connected = true;
        qDebug() << "成功连接到手柄设备:" << devicePath;
    } else {
        qDebug() << "打开手柄设备失败:" << devicePath << "错误信息:" << m_deviceFile.errorString();
    }
}

Joystick::~Joystick()
{
    // 清理资源
    if (m_notifier) {
        m_notifier->setEnabled(false);
        delete m_notifier;
    }
    if (m_deviceFile.isOpen()) {
        m_deviceFile.close();
    }
}

bool Joystick::isConnected() const
{
    return m_connected;
}

void Joystick::readData()
{
    js_event event;
    // 读取手柄事件结构体
    qint64 bytesRead = m_deviceFile.read(reinterpret_cast<char*>(&event), sizeof(event));
    if (bytesRead == sizeof(event)) {
        // 过滤初始化事件(手柄刚连接时会发送一批初始化事件,不需要处理)
        event.type &= ~JS_EVENT_INIT;
        switch (event.type) {
        case JS_EVENT_AXIS:
            emit axisMoved(event.number, event.value);
            break;
        case JS_EVENT_BUTTON:
            if (event.value == 1) {
                emit buttonPressed(event.number);
            } else if (event.value == 0) {
                emit buttonReleased(event.number);
            }
            break;
        default:
            break;
        }
    } else {
        qDebug() << "读取手柄事件失败,读取字节数:" << bytesRead;
        // 出现错误时停止监听,关闭设备
        m_notifier->setEnabled(false);
        m_deviceFile.close();
        m_connected = false;
    }
}

一些实用注意事项
  • 权限问题:/dev/input/js0默认需要用户属于input组才能读取,你可以用sudo usermod -aG input 你的用户名给用户添加权限,或者编写udev规则自动设置设备权限。
  • 设备重连处理:如果手柄拔掉再插上,你可以定时检查设备文件是否存在,或者用udev监听设备插拔事件,实现自动重连。
  • 线程方案补充:如果想用线程方式,run()方法的核心逻辑大概是这样:
void JoystickThread::run()
{
    int fd = open("/dev/input/js0", O_RDONLY);
    if (fd == -1) {
        emit errorOccurred("打开手柄设备失败");
        return;
    }
    js_event event;
    while (!isInterruptionRequested()) {
        ssize_t bytes = read(fd, &event, sizeof(event));
        if (bytes == sizeof(event)) {
            event.type &= ~JS_EVENT_INIT;
            switch (event.type) {
            case JS_EVENT_AXIS:
                emit axisMoved(event.number, event.value);
                break;
            case JS_EVENT_BUTTON:
                if (event.value == 1) {
                    emit buttonPressed(event.number);
                } else if (event.value == 0) {
                    emit buttonReleased(event.number);
                }
                break;
            }
        } else {
            emit errorOccurred("读取手柄数据失败");
            break;
        }
    }
    close(fd);
}

内容的提问来源于stack exchange,提问作者Alex44

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:43:10