Qt之 读取 /dev/input/event*设备 并解析

项目背景

某项目中,需要实现屏幕的自动翻转,即通过读取gsensor实时上报的坐标数据,来做一些逻辑判断。这方面 android已经早已实现,网上搜下即可。安卓主要是通过方向和加速度的判断来实现翻转。手机翻转可参考 https://blog.csdn.net/yun_hen/article/details/78799172
而本项目的翻转策略跟手机不同。
本文简单实现gsensor的数据读取,具体的自动翻转策略需要自己实现。

输入设备应用编程

什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。

对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的X、Y轴位置信息以及触摸屏当前处于按下还是松开状态。

本章将会讨论如下主题内容。

  1. 什么是输入设备;
  2. 如何读取输入设备的数据;
  3. 如何解析从输入设备中获取到的数据;
  4. 按键、触摸屏设备如何解析数据。

什么是输入设备

先来了解什么是输入设备(也称为input设备),常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。

input子系统

由上面的介绍可知,输入设备种类非常多,每种设备上报的数据又不一样,那么Linux系统如何管理呢?Linux系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是input子系统。驱动开发人员基于input子系统开发输入设备的驱动程序,input子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。

基于input子系统注册成功的输入设备,都会在/dev/input目录下生成对应的设备节点(设备文件),通过读取这些设备节点可以获取输入设备上报的数据。

读取数据的流程

如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:

  1. 应用程序打开/dev/input/event0设备文件;
  2. 应用程序发起读操作(譬如调用read),如果没有数据可读则会进入休眠(阻塞I/O情况下);
  3. 当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
  4. 应用程序对读取到的数据进行解析。

当无数据可读时,程序会进入休眠状态(也就是阻塞),譬如应用程序读触摸屏,当前我们并没有去触摸触摸屏,所以自然无数据可读;当我们用手指触摸触摸屏时,此时就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态,当然我们这里说的是在阻塞式I/O方式下,当有数据可读时才会被唤醒。

应用程序如何解析数据

首先我们要知道,应用程序打开输入设备的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢?其实每一次read操作获取的都是一个struct input_event结构体类型数据,该结构体定义在<linux/input.h>头文件中,它的定义如下:

struct input_event结构体
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

结构体中的time成员变量是一个struct timeval类型的变量,该结构体在前面给大家介绍过,每个输入事件struct input_event中都包含了其发生的时间,就通过这个struct timeval类型的变量time来描述。时间参数通常不是很重要,而其它3个成员变量type、code、value更为重要。

type:type用于描述发生了哪类事件,Linux系统所支持的输入事件类型如下所示:

/*
 * Event types
 */

#define EV_SYN			0x00		//同步事件,用于分隔事件
#define EV_KEY			0x01		//按键事件
#define EV_REL			0x02		//相对位移事件(譬如鼠标)
#define EV_ABS			0x03		//绝对位移事件(譬如触摸屏)
#define EV_MSC			0x04		//其它杂类
#define EV_SW			0x05
#define EV_LED			0x11
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS		0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

以上这些宏定义也是在<linux/input.h>头文件中,所以在应用程序当中需要包含该头文件;一种输入设备通常可以产生多种不同类型的事件,譬如鼠标点击按键(左键、右键,或鼠标上的其它按键)时会上报按键事件,移动鼠标时则会上报相对位移事件。

code:code表示该类事件下的哪一个具体的事件,每一种事件类型都有对应的一系列事件。不同的事件类型所包含的事件是不同的,譬如对于按键事件来说,一个键盘上有很多的按键,譬如字母A、B、C、D或者数字1、2、3、4等,这每一个不同的按键都会对应一个具体的事件,如下所示:

#define KEY_RESERVED		0
#define KEY_ESC			1			//ESC键
#define KEY_1			2			//数字1键
#define KEY_2			3			//数字2键
#define KEY_TAB			15			//TAB键
#define KEY_Q			16			//字母Q键
#define KEY_W			17			//字母W键
#define KEY_E			18			//字母E键
#define KEY_R			19			//字母R键
……

相对位移事件的code值

#define REL_X			0x00	//X轴
#define REL_Y			0x01	//Y轴
#define REL_Z			0x02	//Z轴
#define REL_RX			0x03
#define REL_RY			0x04
#define REL_RZ			0x05
#define REL_HWHEEL		0x06
#define REL_DIAL		0x07
#define REL_WHEEL		0x08
#define REL_MISC		0x09
#define REL_MAX			0x0f
#define REL_CNT			(REL_MAX+1)

绝对位移事件的code值

譬如对于触摸屏设备来说,一个触摸点有X轴坐标和Y轴坐标,甚至一个触摸点还有压力值等这些信息,这每一种信息都会对应一个具体的事件,也就是code值。

#define ABS_X			0x00		//X轴
#define ABS_Y			0x01		//Y轴
#define ABS_Z			0x02		//Z轴
#define ABS_RX			0x03
#define ABS_RY			0x04
#define ABS_RZ			0x05
#define ABS_THROTTLE		0x06
#define ABS_RUDDER		0x07
#define ABS_WHEEL		0x08
#define ABS_GAS			0x09
#define ABS_BRAKE		0x0a
#define ABS_HAT0X		0x10
#define ABS_HAT0Y		0x11
#define ABS_HAT1X		0x12
#define ABS_HAT1Y		0x13
#define ABS_HAT2X		0x14
#define ABS_HAT2Y		0x15
#define ABS_HAT3X		0x16
#define ABS_HAT3Y		0x17
#define ABS_PRESSURE		0x18
#define ABS_DISTANCE		0x19
#define ABS_TILT_X		0x1a
#define ABS_TILT_Y		0x1b
#define ABS_TOOL_WIDTH		0x1c
......

除了以上列举出来的之外,还有很多,大家可以自己浏览<linux/input.h>头文件(这些宏其实是定义在input-event-codes.h头文件中,该头文件被<linux/input.h>所包含了),关于这些具体的事件,后面再给大家进行介绍。

value:value表示事件值,对value值的解释随着code类型的变化而变化。譬如对于按键事件来说,如果code表示按键1,那么value等于1表示按键1按下,value等于0表示按键1被松开,如果value等于2则表示按键被长按。譬如在绝对位移事件中,如果code表示X轴坐标ABS_X,那么value值就表示触摸点在X轴坐标的位置。所以对value值的解释需要根据不同的code类型而定!

事件之间的分隔、同步

上面我们提到了同步事件EV_SYN,同步事件用于对不同事件进行分隔、实现同步操作。应用程序读取输入设备的数据时,一次read操作只能读取一个struct input_event类型数据,譬如对于触摸屏来说,一个触摸点会上报X轴坐标位置、Y轴坐标位置,还有可能会上报压力值,对于这样情况,那么应用程序需要执行3次read操作才能把一个触摸点上报的数据全部读取到;这还只是单个触摸点的情况,如果触摸屏设备支持多点触摸,同一时间触摸屏上有多个触摸点,那么程序当中需要把所有触摸点的数据读取出来,这样才算完整。

那么应用程序如何知道已经读取到完整数据了呢?其实就是通过同步事件来实现的,驱动程序中将完整数据上报完之后(譬如触摸点的X轴、Y轴以及压力等之类的数据),会上报一个同步事件,以告知应用程序数据已经完整、进行同步。

同步事件类型也包含了多种不同的事件,也就是code值,如下所示:

/*
 * Synchronization events.
 */

#define SYN_REPORT		0
#define SYN_CONFIG		1
#define SYN_MT_REPORT		2
#define SYN_DROPPED		3
#define SYN_MAX			0xf
#define SYN_CNT			(SYN_MAX+1)

上报的同步事件通常是第一个SYN_REPORT(code值),而value值通常为0。对于同步事件,应用程序通常不需要去管是哪个具体的同事事件,只需知道type等于EV_SYN则表示数据完整了。

Qt 读取gsensor数据实现

项目中还需要判断坐标的阈值,方向等。但是数据采集的核心代码如下:

class GsensorHandler : public QObject
{
    Q_OBJECT
public:
    explicit GsensorHandler(QObject *parent = nullptr);
    ~GsensorHandler();

private slots:
    void readSensorData();
signals:
    void sensorData(int x,int y,int z);
private:
    QSocketNotifier * m_notify;
    int m_fd;
    int m_x, m_y=0,m_z;

};
GsensorHandler::GsensorHandler(QObject *parent) : QObject(parent)
{
    QByteArray device = QByteArrayLiteral("/dev/input/event0");

    m_fd = open(device.constData(), O_RDONLY);
    if (m_fd >= 0) {
        qInfo() << "open file success";
        m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
        connect(m_notify, SIGNAL(activated(int)), this, SLOT(readSensorData()));
    } else {
        qErrnoWarning(errno, "Gsensor: Cannot open input device %s", device.constData());
    }

}

GsensorHandler::~GsensorHandler()
{
    if (m_fd >= 0)
        close(m_fd);
}


void GsensorHandler::readSensorData()
{
    struct input_event in_ev = {0};
    for ( ; ; ) {

        /* 循环读取数据 */
        if (sizeof(struct input_event) !=
                read(m_fd, &in_ev, sizeof(struct input_event))) {
            perror("read error");
            exit(-1);
        }

        switch (in_ev.type) {
            case EV_ABS:    //绝对位移事件
            {
                if (ABS_X == in_ev.code)    //X轴
                    m_x = in_ev.value;    //保存X轴坐标位置
                else if (ABS_Y == in_ev.code)//Y轴
                	m_y = in_ev.value;    //保存Y轴坐标位置
                else if (ABS_Z == in_ev.code)//Z轴
                    m_z = in_ev.value;    //保存Z轴坐标位置
                emit sensorData(m_x,m_y,m_z);
            }
        }
    }
}

效果如图:在这里插入图片描述

  • 11
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值