Simple rotary encoder driver for Qt4 Embedded on Linux

  • Tutorial


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 when moving, but the scroll wheel will not. Because the linuxinput driver does not handle events of type REL_WHEEL , which the encoder generates, but only REL_X and REL_Y , which are responsible for changing coordinates.

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


Here is a piece of linuxinput driver code that handles events from the input of 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);
  }
}


We solve problems


We have three options:

  • modify linuxinput driver

  • modify the kernel driver so that it generates events that are understandable to the l inuxinput driver

  • write your input device driver for Qt4


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

Writing a driver


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

Let's start with the QWSMouseHandler descendant class :

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 you can see from the header file, we need to implement as many as three functions: suspend () , resume () , readMouseData () . Well, a constructor with a destructor.

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

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 descriptor of the input device, attached a QSocketNotifier to it , and hung up our handler on its activated (int) signal .

The destructor for this class is quite simple - its task is to check whether the descriptor 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 on the QSocketNotifier .

So we got directly to the data processor.

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 state of the encoder. Those. this driver cannot be used for the mouse as it is, since there is no processing for changes in the coordinates of the mouse itself - nothing but the scroll wheel will 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 tell the QMouseDriverFactory class that it is a driver called rotaryencoderdriver . Well, create () methods , of course.

Battle check


Now that we have the driver, we need to somehow explain to the Qt4 library what exactly it needs to be used for a specific device. There is a special environment variable for this - QWS_MOUSE_PROTO . It serves to indicate Qt4 which driver and from which device to take data on the movement of the mouse. Suppose our encoder is / dev / input / rotary0 , therefore, to make it 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: a video has been added for clarity

Also popular now: