fljx fljx - 9 months ago 61
C++ Question

Detect the source of a keyboard/barcode scanner event

I need to read several barcode scanners and bind the read data according to its source.

In other words, my application needs to know where from the keystrokes came to be able to take the correct actions like update the UI and send commands to dedicated external hardware.

How can I "route" the inputs of different keyboards/scanners to specific events in my app OR retrieve information that allow my app to find out where from the input came?
(I start from the point that a barcode scanner is just a keyboard to the system.)

I know I can "open" the specific "device" to read raw data from it, but it is not the same as haveing a "keyboard event" in my app.
(Consider also that my app is written in Qt, but I don't really need to be tied to it.)

Thanks.

EDIT:
I'd better say that it must run on Linux. No windows nor .NET and no Embedded Linux too.
I also plan to code it in C++/Qt, but am open to other frameworks.
Sorry for the miss.

Answer Source

Actually it is a solution.

I stil have no working application to show, but the concept is using XI2. I am going to digest XI2 Recipes and try to bind it to QApplication::x11EventFilter().

As illustrated in the XI2 Recipes, Part 3, I can determine the source of an event with the field sourceid present in XIButtonClassInfo, XIKeyClassInfo and XIValuatorClassInfo.

Recipes, Part 4 shows how to print information about the source of an event (in void print_deviceevent(XIDeviceEvent* event)). Sounds easy this way.

(Even having no working solution yet, I decided to post an answer so that it can help anyone else having the same problem. As soon as I make progress I'll edit my own answer with better reports.)


EDIT:

As promised, here is a working snippet that prints out the source of keyboard events:

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

#include <QX11Info>
#include <X11/extensions/XInput2.h>


// XI2 Event types.
static const char   *_xi2_event_names[] =
{
    "Reserved 0",
    "XI_DeviceChanged",
    "XI_KeyPress",
    "XI_KeyRelease",
    "XI_ButtonPress",
    "XI_ButtonRelease",
    "XI_Motion",
    "XI_Enter",
    "XI_Leave",
    "XI_FocusIn",
    "XI_FocusOut",
    "XI_HierarchyChanged",
    "XI_PropertyEvent",
    "XI_RawKeyPress",
    "XI_RawKeyRelease",
    "XI_RawButtonPress",
    "XI_RawButtonRelease",
    "XI_RawMotion"
};

#include <QMainWindow>

QXI2Application::QXI2Application( int &argc, char **argv, int qt_version )
    : QApplication( argc, argv, qt_version )
{
    int event, error;

    _display = QX11Info::display( );

    if ( !XQueryExtension( _display, "XInputExtension", &xi_opcode, &event, &error ) )
        qDebug( ) << "X Input extension not available.\n";

    // We support XI 2.0.
    int major = 2;
    int minor = 0;

    int rc = XIQueryVersion( _display, &major, &minor );
    if ( rc == BadRequest )
        qDebug( ) << "No XI2 support. Server supports version " << major << "." << minor << " only.\n";
    else if ( rc != Success )
        qDebug( ) << "Internal Error! This is a bug in Xlib.\n";
    else
        qDebug( ) << "XI2 supported. Server provides version " << major << "." << minor;
}

void    QXI2Application::setMainWindow( QMainWindow *wnd )
{
    XIEventMask evmasks[ 1 ];
    unsigned char mask1[ ( XI_LASTEVENT + 7 ) / 8 ];

    memset( mask1, 0, sizeof( mask1 ) );

    // Select for key events from all master devices.
    XISetMask( mask1, XI_KeyPress );
    XISetMask( mask1, XI_KeyRelease );

    evmasks[ 0 ].deviceid = XIAllMasterDevices;
    evmasks[ 0 ].mask_len = sizeof( mask1 );
    evmasks[ 0 ].mask = mask1;

    XISelectEvents( _display, wnd->winId( ), evmasks, 1 );
    XFlush( _display );
}

bool QXI2Application::x11EventFilter( XEvent *event )
{
    XGenericEventCookie *cookie = &event->xcookie;

    if ( event->type != GenericEvent
         || cookie->extension != xi_opcode
         || !XGetEventData( _display, cookie ) )
    {
        return  false;
    }

    qDebug( ) << "cookie->evtype = " << cookie->evtype << " ("
            << _xi2_event_names[ cookie->evtype < XI_LASTEVENT ? cookie->evtype : XI_LASTEVENT ] << ")";

    switch( cookie->evtype )
    {
    case XI_KeyPress:
        {
            qDebug( ) << "\tXI_KeyPress";

            XIDeviceEvent *dev_ev = ( XIDeviceEvent * )event->xcookie.data;
            qDebug( ) << "\tdev_ev->deviceid = " << dev_ev->deviceid;
            qDebug( ) << "\tdev_ev->sourceid = " << dev_ev->sourceid;
            break;
        }
    case XI_KeyRelease:
        {
            qDebug( ) << "\tXI_KeyRelease";

            XIDeviceEvent *dev_ev = ( XIDeviceEvent * )event->xcookie.data;
            qDebug( ) << "\tdev_ev->deviceid = " << dev_ev->deviceid;
            qDebug( ) << "\tdev_ev->sourceid = " << dev_ev->sourceid;
            break;
        }
    default:
        qDebug( ) << "\tcookie->evtype = " << cookie->evtype;
        break;
    }

    XFreeEventData( _display, cookie );

    return false;
}

It outputs something like (commented):

-------------------------------------------
XI2 supported. Server provides version  2 . 0

-------------------------------------------

[Keyboard]    ↳ Dell Dell USB Keyboard                    id=8    [slave  keyboard (3)]

cookie->evtype =  2  ( XI_KeyPress )
    XI_KeyPress
    dev_ev->deviceid =  3
    dev_ev->sourceid =  8
cookie->evtype =  3  ( XI_KeyRelease )
    XI_KeyRelease
    dev_ev->deviceid =  3
    dev_ev->sourceid =  8

-------------------------------------------
[Barcode]   ↳ Cypress-Weikeng USB Adapter               id=10   [slave  keyboard (3)]

cookie->evtype =  2  ( XI_KeyPress )
    XI_KeyPress
    dev_ev->deviceid =  3
    dev_ev->sourceid =  10
cookie->evtype =  3  ( XI_KeyRelease )
    XI_KeyRelease
    dev_ev->deviceid =  3
    dev_ev->sourceid =  10

The output of xinput list is:

# xinput list
⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ Dell Dell USB Optical Mouse               id=9    [slave  pointer  (2)]
⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Power Button                              id=7    [slave  keyboard (3)]
    ↳ Dell Dell USB Keyboard                    id=8    [slave  keyboard (3)]
    ↳ Cypress-Weikeng USB Adapter               id=10   [slave  keyboard (3)]

This ultra simple test shows that although all the events come from master dev_ev->deviceid = 3, the slave is distinguishable by dev_ev->sourceid.

I think now I'll be able to route the incoming events to the respective "clients" based on the dev_ev->sourceid configured on application.