📜 ⬆️ ⬇️

A simple rotary encoder driver for Qt4 Embedded under Linux



It so happened that in Qt4 Embedded , which we use on our Bercut-MMT device, there is no support for input devices such as an encoder. Those. If you attach a mouse to the device, the coordinates will be processed while moving, but the scroll wheel will not. Because the linuxinput driver does not handle events with the REL_WHEEL type, which is generated by the encoder, but only REL_X and REL_Y , which are responsible for changing the coordinates.

Who cares how to solve this problem - welcome under cat.

')
Here is a piece of linuxinput driver code that handles events from the input to the Linux kernel subsystem:

for (int i = 0; i < n; ++i) { struct ::input_event *data = &buffer[i]; bool unknown = false; if (data->type == EV_ABS) { if (data->code == ABS_X) { m_x = data->value; } else if (data->code == ABS_Y) { m_y = data->value; } else { unknown = true; } } else if (data->type == EV_REL) { if (data->code == REL_X) { m_x += data->value; } else if (data->code == REL_Y) { m_y += data->value; } else { unknown = true; } } else if (data->type == EV_KEY && data->code == BTN_TOUCH) { m_buttons = data->value ? Qt::LeftButton : 0; } else if (data->type == EV_KEY) { int button = 0; switch (data->code) { case BTN_LEFT: button = Qt::LeftButton; break; case BTN_MIDDLE: button = Qt::MidButton; break; case BTN_RIGHT: button = Qt::RightButton; break; } if (data->value) m_buttons |= button; else m_buttons &= ~button; } else if (data->type == EV_SYN && data->code == SYN_REPORT) { QPoint pos(m_x, m_y); pos = m_handler->transform(pos); m_handler->limitToScreen(pos); m_handler->mouseChanged(pos, m_buttons); } else if (data->type == EV_MSC && data->code == MSC_SCAN) { // kernel encountered an unmapped key - just ignore it continue; } else { unknown = true; } if (unknown) { qWarning("unknown mouse event type=%x, code=%x, value=%x", data->type, data->code, data->value); } } 


Solve problems


We have three options:





The third option is the most correct one. We will consider it.

We write driver


To create your driver, you need to write two classes - a QWSMouseHandler successor and a QWSMousePlugin successor. The task of the first is to work directly with the input device, the task of the second is to explain the QMouseDriverFactory that the driver with the name % drivername% should use our implementation of the QWSMouseHandler successor.

Let's start with the descendant class QWSMouseHandler :

 class RotaryEncoderHandler: public QObject, public QWSMouseHandler { Q_OBJECT public: RotaryEncoderHandler( const QString &device = QString("/dev/input/rotary_encoder" ) ); ~RotaryEncoderHandler( ); void suspend( ); void resume ( ); private: QSocketNotifier *m_notify; int deviceFd; int m_wheel; private slots: void readMouseData( ); }; 


As can be seen from the header file - we need to implement as many as three functions: suspend () , resume () , readMouseData () . Well, the designer with the destructor.

Constructor - as an argument, the device name comes to us - / dev / input / event3 , for example. Further, our task is to open the file descriptor of the device with the specified name and transfer it to the mercy in QSocketNotifier . QSocketNotifier is such a beast that listens to the file descriptor and emits the activated (int) signal to any of its gestures.

 RotaryEncoderHandler::RotaryEncoderHandler( const QString &device ): QWSMouseHandler( device ) ,deviceFd( 0 ) ,m_wheel( 0 ) { setObjectName("Rotary Encoder Handler"); deviceFd = ::open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY); if( deviceFd > 0 ){ qDebug() << "Opened" << device << "as rotary encoder device"; m_notify = new QSocketNotifier( deviceFd, QSocketNotifier::Read, this); connect( m_notify, SIGNAL( activated(int)), this, SLOT( readMouseData())); } else { qWarning("Cannot open %s: %s", device.toLocal8Bit().constData(), strerror( errno ) ); return; } } 


Those. we opened the input device handle, attached a QSocketNotifier to it , and with its activated (int) signal hung our handler.

The destructor of this class is quite simple - its task is to check whether the handle of the input device is open and, if so, close it.

The suspend () / resume () methods should stop / start processing data from the input device. This is done by simply calling the setEnabled (bool) method of QSocketNotifier .

So we got to the data handler directly.

 void RotaryEncoderHandler::readMouseData( ) { struct ::input_event buffer[32]; int n = 0; forever { n = ::read(deviceFd, reinterpret_cast(buffer) + n, sizeof(buffer) - n); if (n == 0) { qWarning("Got EOF from the input device."); return; } else if (n < 0 && (errno != EINTR && errno != EAGAIN)) { qWarning("Could not read from input device: %s", strerror(errno)); return; } else if (n % sizeof(buffer[0]) == 0) { break; } } n /= sizeof(buffer[0]); for (int i = 0; i < n; ++i) { struct ::input_event *data = &buffer[i]; bool unknown = false; if (data->type == EV_REL) { if (data->code == REL_WHEEL) { m_wheel = data->value; } else { unknown = true; } } else if (data->type == EV_SYN && data->code == SYN_REPORT) { mouseChanged(pos(), Qt::NoButton, m_wheel); } else if (data->type == EV_MSC && data->code == MSC_SCAN) { // kernel encountered an unmapped key - just ignore it continue; } else { unknown = true; } if (unknown) { qWarning("unknown mouse event type=%x, code=%x, value=%x", data->type, data->code, data->value); } } } 


It strongly resembles a similar method from the linuxinput driver, but unlike it, it transmits only events with changes in the encoder status. Those. this driver cannot be used as it is for the mouse, as it lacks the processing of changes in the coordinates of the mouse itself - nothing but the scroll wheel will not work.

Now let's see what the driver class is:

 class RotaryEncoderDriverPlugin : public QMouseDriverPlugin { Q_OBJECT public: RotaryEncoderDriverPlugin( QObject *parent = 0 ); ~RotaryEncoderDriverPlugin(); QWSMouseHandler* create(const QString& driver); QWSMouseHandler* create(const QString& driver, const QString& device); QStringList keys()const; }; 


Not very big, right? Here is its implementation:

 Q_EXPORT_PLUGIN2(rotaryencoderdriver, RotaryEncoderDriverPlugin) RotaryEncoderDriverPlugin::RotaryEncoderDriverPlugin( QObject *parent ): QMouseDriverPlugin( parent ) { } RotaryEncoderDriverPlugin::~RotaryEncoderDriverPlugin() { } QStringList RotaryEncoderDriverPlugin::keys() const { return QStringList() <<"rotaryencoderdriver"; } QWSMouseHandler* RotaryEncoderDriverPlugin::create( const QString& driver, const QString& device ) { if( driver.toLower() == "rotaryencoderdriver" ){ return new RotaryEncoderHandler( device ); } return 0; } QWSMouseHandler* RotaryEncoderDriverPlugin::create( const QString& driver ) { if( driver.toLower() == "rotaryencoderdriver" ){ return new RotaryEncoderHandler( ); } return 0; } 


As you can see from the code, the whole task of the driver is to inform the QMouseDriverFactory class that this is a driver with the name rotaryencoderdriver . Well, create () methods, of course.

Battle check


Now that we have a driver, we need to somehow explain the Qt4 library what it should use for a particular device. For this there is a special environment variable - QWS_MOUSE_PROTO . It serves to tell Qt4 what driver and from which device to take data on mouse movement. Suppose that our encoder is / dev / input / rotary0 , therefore in order for it to work, you need to set the variable as QWS_MOUSE_PROTO = "rotaryencoderdriver: / dev / input / rotary0" .

We catch events from the encoder


To work with encoder events, we need to implement an event filter in our application:

 bool ClassName::eventFilter(QObject *o, QEvent *e) { if ( o ) { if ( e->type() == QEvent::Wheel) { QWheelEvent* we = static_cast< QWheelEvent* >( e ); /*       */ return true; } /*     Object*/ return QObject::eventFilter( o, e ); } 


useful links




Update: video added for clarity

Source: https://habr.com/ru/post/266075/


All Articles