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

Posted

技术标签:

【中文标题】如何使用 Qt 在 Linux 中读取文件设备?【英文标题】:How to read a file device in Linux using Qt? 【发布时间】:2018-02-11 17:39:07 【问题描述】:

我正在开发一个基于 Qt5 的小型 GUI,它将显示来自 Linux 文件设备的数据流。 为此,我选择了操纵杆输入。使用cat /dev/input/js0 可以在终端上看到传入的流。

使用 C,您可以使用带有阻塞读取的循环读取此设备文件或处理设备信号。但我在 Qt 中不明白这一点。

使用 Qt 与设备文件交互的典型方法是什么?


根据@rodrigo 的回答,这里有一个新的实现:

joystick.h

#ifndef JOYSTICK_H
#define JOYSTICK_H

#include <QObject>
#include <QFile>
#include <QSocketNotifier>

class Joystick
      : public QObject

    Q_OBJECT

    QString fileName = "/dev/input/js0";
    QFile *file;
    QSocketNotifier *notifier;

public:
    explicit Joystick(QObject *parent = nullptr);
    ~Joystick();

signals:

public slots:
    void handle_readNotification(int socket);
;

#endif // JOYSTICK_H

joystick.cpp

#include "joystick.h"

Joystick::Joystick(QObject *parent)
   : QObject(parent)

    file = new QFile();
    file->setFileName(fileName);
    if( !file->exists() )
        qWarning("file does not exist");
        return;
    

    if( !file->open(QFile::ReadOnly) )
        qWarning("can not open file");
        return;
    

    notifier = new QSocketNotifier( file->handle(),
                                    QSocketNotifier::Read,
                                    this);

    connect( notifier,
             &QSocketNotifier::activated,
             this,
             &Joystick::handle_readNotification );

    if( !notifier->isEnabled() )
        qInfo("enable notifier");
        notifier->setEnabled(true);
    
    qInfo("Joystick init ready");


void
Joystick::handle_readNotification(int /*socket*/)

    static quint64 cnt=0;
    qInfo("cnt: %d",cnt++);

    if( !(file->isOpen()) )
        qWarning("file closed");
        return;
    

    char buf[16]; /* tested with different sizes */
    if( file->read(buf,sizeof(buf)) )
        qInfo("read: %s",buf);
    
//  QByteArray ba = file->readAll();
//  qInfo("Data: %s", ba.data());

然后我运行这个,最后一个输出是cnt: 0。看来,readreadAll 调用现在阻塞了。如果我注释掉读取调用,计数器运行得非常快。 here a similiar post这里有错吗?


最终解决方案

感谢罗德里戈!

joystick.h

#ifndef JOYSTICK_H
#define JOYSTICK_H

#include <QObject>
#include <QFile>
#include <QSocketNotifier>

class Joystick
      : public QObject

    Q_OBJECT

    QString fileName = "/dev/input/js0";
    QSocketNotifier *notifier;
    int fd;

public:
    explicit Joystick(QObject *parent = nullptr);
    ~Joystick();

signals:
    void buttonPressed(quint8 number, qint16 value);
    void axisMoved(quint8 number, qint16 value);


public slots:
    void handle_readNotification(int socket);
;

#endif // JOYSTICK_H

joystick.cpp

#include "joystick.h"

#include <fcntl.h>
#include <unistd.h>
#include <linux/joystick.h>

Joystick::Joystick(QObject *parent)
   : QObject(parent)

    auto file = new QFile();
    file->setFileName(fileName);
    if( !file->exists() )
        qWarning("file does not exist");
        return;
    

    fd = open(fileName.toUtf8().data(), O_RDONLY|O_NONBLOCK);
    if( fd==-1 )
        qWarning("can not open file");
        return;
    

    notifier = new QSocketNotifier( fd,
                                    QSocketNotifier::Read,
                                    this);

    connect( notifier,
             &QSocketNotifier::activated,
             this,
             &Joystick::handle_readNotification );


Joystick::~Joystick()

    if( fd>=0 )
        close(fd);
    


void
Joystick::handle_readNotification(int /*socket*/)

    struct js_event buf;
    while( read(fd,&buf,sizeof(buf))>0 )
        switch (buf.type) 
        case JS_EVENT_BUTTON:
            emit buttonPressed(buf.number, buf.value);
            break;
        case JS_EVENT_AXIS:
            emit axisMoved(buf.number, buf.value);
            break;
        
    

【问题讨论】:

你能发一个minimal reproducible example你迄今为止尝试过的东西吗? 【参考方案1】:

这个问题通常可以通过工具包的轮询源来解决。对于 Qt,这是QSocketNotifier。尽管它的名字(历史事故?),它可以用来轮询任何文件描述符,而不仅仅是套接字。

所以你只需打开设备,使用open() 获取文件描述符,然后在其上创建QSocketNotifier,类型为QSocketNotifier::Read。当有要读取的事件时,您将收到activate() 信号。

【讨论】:

QSocketNotifier 通知即使在空文件上也有可读内容。我不太确定 QSocketNotifier 真正使用什么来发出 activate() ... @Alex44:activate() 事件是在内部使用poll()select() 生成的。这意味着您可以在文件描述符上调用read() 而不会阻塞。现在,从空文件读取不会阻塞,你只会得到 0 个字节。但是,如果没有未决事件,则从诸如操纵杆之类的设备中读取...将阻塞。如果您仅在获得activate() 时阅读js0,一切都会正常工作。 如果我没记错这就是我所做的......然后我在handle_ 中调用bytesAvailable(),即使有一些输入,它也会返回零。但为什么?它会阻止所有文件... @Alex44:通常的解决方案是将设备的句柄设置为非阻塞,因此在读取时,而不是在没有数据可用时阻塞,您将获得 EAGAIN。然后,在activate() 中,您循环读取设备,直到获得 EAGAIN,但您永远不会阻塞。可以在不阻塞且不将设备设置为非阻塞的情况下执行此操作,但这很棘手。这种方式更简单、更可靠。 @Alex44:不要将QFile 用于设备。只需调用open() 获取原始FD,如果需要,使用fcntl() 将其设置为非阻塞,然后使用它。这种方式没有黑魔法。啊,完成后别忘了close()它。【参考方案2】:

替代解决方案,如果您仍想使用 QFile 而不是低级读/写函数:

auto file = new QFile(fileName);

...

// IMPORTANT: You must set the Unbuffered flag below
if(!file->open(QFile::ReadOnly | QFile::Unbuffered))

    // handle error


auto fd = file.handle();

auto flags = fcntl(fd, F_GETFL, 0);
if(flags == -1)

    // handle error


flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if(flags == -1)

    // handle error


auto notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);

...

struct js_event buf;
while(file->read(&buf, sizeof(buf)) > 0)

    // process data

【讨论】:

以上是关于如何使用 Qt 在 Linux 中读取文件设备?的主要内容,如果未能解决你的问题,请参考以下文章

linux下如何用c语言调用shell命令

我们如何在Qt工具包(Qt)应用程序中读取已挂载(Raspberry pi)usb中的所有mp3文件

Qt学习之路(58): 进程间交互(QProcess.readAllStandardOutput可以读取控制台的输出)

qt如何在windows上打开usb摄像头?

如何在 linux/Qt 中检测 USB 连接

在 Linux 中使用 Qt 的 Close() 文件描述符