Home · All Classes · Grouped Classes · Annotated · Functions

Tutorial: Implementing a Device Plug-in

Introduction

The primary purpose of tutorial describes how to write keyboard and touchscreen/mouse input plug-ins for a device. Qtopia Core includes keyboard and mouse handlers for some common devices. The source code for these drivers is located in the <qt-root-dir>/src/gui/embedded directory. These handlers may be appropriate for your device. Additional plug-in examples can be found under the Device Profiles for various devices, see <qtopia-root-dir>/devices/<device>/src/plugins/qtopiacore.

This tutorial assumes that you will be using and have set up a device profile for your device.

The following directories are of particular interest:

DirectoryDescription
mkspecscontains the qmake configuration file used to build for the device
srccontains the source code for the plug-in drivers for keyboard and touchscreen

This tutorial assumes that debugging using qLog() is enabled. To activate debugging:

This functionality is available in both debug and release builds.

The steps taken to implement a new plug-in depend on whether you are implementing a keyboard plug-in or a touchscreen/mouse plug-in. This tutorial provides the steps for both.

Keyboard

To create a keyboard plug-in we need to create two classes. The first class will be a subclass of QWSKeyboardHandler and will implement the actual keyboard handler. The second class will be a subclass of QKbdDriverPlugin and links our keyboard handler with the Qtopia Core keyboard input system.

In this tutorial we will be implementing a keyboard plug-in for the Linux event interface. The full source for this plug-in is available in the <qtopia-root-dir>/devices/example/src/plugins/qtopiacore/kbddrivers/example directory. When creating your own keyboard plug-ins your source code should be located in the <qtopia-root-dir>/devices/<device profile>/src/plugins/qtopiacore/kbddrivers/<driver name> directory.

Keyboard Handler

The class definition for our keyboard handler is (examplekbdhandler.h):

    class ExampleKbdHandler : public QObject, public QWSKeyboardHandler
    {
        Q_OBJECT
    public:
        ExampleKbdHandler(const QString &device = QString("/dev/input/event0"));
        ~ExampleKbdHandler();

    private:
        QSocketNotifier *m_notify;
        int  kbdFd;
        bool shift;

    private Q_SLOTS:
        void readKbdData();
    };

The implementation consists of the class constructor and destructor and the readKbdData() slot.

The constructor opens the keyboard device, /dev/input/event0. A QSocketNotifier is used to get asynchronous notification on the availability of data from the device. The readKbdData() slot is connected to the QSocketNotifier::activated() signal and will be called whenever data is available to be read from the device.

    ExampleKbdHandler::ExampleKbdHandler(const QString &device)
    {
        qLog(Input) << "Loaded Example keyboard plugin!";
        setObjectName("Example Keypad Handler");
        kbdFd = ::open(device.toLocal8Bit().constData(), O_RDONLY, 0);
        if (kbdFd >= 0) {
            qLog(Input) << "Opened" << device << "as keyboard input";
            m_notify = new QSocketNotifier(kbdFd, QSocketNotifier::Read, this);
            connect(m_notify, SIGNAL(activated(int)), this, SLOT(readKbdData()));
        } else {
            qWarning("Cannot open %s for keyboard input (%s)",
                     device.toLocal8Bit().constData(), strerror(errno));
            return;
        }
        shift = false;
    }

In the destructor we close the device.

    ExampleKbdHandler::~ExampleKbdHandler()
    {
        if (kbdFd >= 0)
            ::close(kbdFd);
    }

The ExampleInput structure defines the format of the data read from the device. The Linux event interface uses 16 byte packets.

    struct ExampleInput {
        unsigned int   dummy1;
        unsigned int   dummy2;
        unsigned short type;
        unsigned short code;
        unsigned int   value;
    };

Whenever data is available to be read from the device the readKbdData() slot will be invoked by the QSocketNotifier. readKbdData() reads and processes a single packet from the device.

    void ExampleKbdHandler::readKbdData()
    {
        ExampleInput event;

        int n = read(kbdFd, &event, sizeof(ExampleInput));
        if (n != 16) {
            qLog(Input) << "keypressed: n=" << n;
            return;
        }

        qLog(Input) << "keypressed: type=" << event.type
                    << "code=" << event.code
                    << "value=" << event.value
                    << ((event.value != 0) ? "(Down)" : "(Up)");

A switch statement is used to convert events into Qt::Key and Unicode mappings.

        Qt::KeyboardModifiers modifiers = Qt::NoModifier;
        int unicode = 0xffff;
        int key_code = 0;

        switch (event.code) {
        case 0x110:
            key_code = Qt::Key_Context1;
            unicode  = 0xffff;
            break;
        case 0x111:
            key_code = Qt::Key_Back;
            unicode  = 0xffff;
            break;
        case 0xA9:
            key_code = Qt::Key_Call;
            unicode  = 0xffff;
            break;
        ...
        }
        if (shift) modifiers |= Qt::ShiftModifier;

QWSKeyboardHandler::processKeyEvent() is called to process the key event.

        processKeyEvent(unicode, key_code, modifiers, event.value!=0, false);
    }

Keyboard Plug-in

The class definition of the keyboard driver plug-in (examplekbddriverplugin.h):

    #include <QtGui/QWSKeyboardHandlerFactoryInterface>

    class ExampleKbdDriverPlugin : public QKbdDriverPlugin {
        Q_OBJECT
    public:
        ExampleKbdDriverPlugin( QObject *parent  = 0 );
        ~ExampleKbdDriverPlugin();

        QWSKeyboardHandler* create(const QString& driver, const QString& device);
        QWSKeyboardHandler* create(const QString& driver);
        QStringList keys() const;
    };

The keys function returns the list of drivers that this plug-in provides. Our plug-in only provides the "examplekbdhandler" driver.

    QStringList ExampleKbdDriverPlugin::keys() const
    {
        return QStringList() << "examplekbdhandler";
    }

The create functions are responsible for returning an instance of the keyboard driver. We do this only if the driver parameter matches our key.

The device parameter is the driver specific options specified in the QWS_KEYBOARD environment variable. For our driver we use this to specify the device name. If no device name is specified the driver defaults to /dev/input/event0.

    QWSKeyboardHandler* ExampleKbdDriverPlugin::create(const QString& driver,
                                                       const QString& device)
    {
        if (driver.toLower() == "examplekbdhandler") {
            qLog(Input) << "Before call ExampleKbdHandler()";
            return new ExampleKbdHandler(device);
        }
        return 0;
    }

    QWSKeyboardHandler* ExampleKbdDriverPlugin::create(const QString& driver)
    {
        if (driver.toLower() == "examplekbdhandler") {
            qLog(Input) << "Before call ExampleKbdHandler()";
            return new ExampleKbdHandler();
        }
        return 0;
    }

Finally export the plug-in using the QTOPIA_EXPORT_QT_PLUGIN macro.

    QTOPIA_EXPORT_QT_PLUGIN(ExampleKbdDriverPlugin)

Building and testing the plug-in

The qmake project file for the plug-in (example.pro):

    qtopia_project(embedded qtopia core plugin)
    TARGET = examplekbdhandler

    CONFIG+=no_tr

    HEADERS = examplekbddriverplugin.h examplekbdhandler.h
    SOURCES = examplekbddriverplugin.cpp examplekbdhandler.cpp

Ensure that the plug-in will be built by adding the following line to the <qtopia-root-dir>/devices/<device profile>/src/projects.pri file.

    PROJECTS *= plugins/qtopiacore/kbddrivers/example

Build and install Qtopia.

Note: When creating a plug-in it is not necessary to rebuild all of Qtopia to enable it. Simply rebuild the plug-in and copy the files to the image/Qtopia directory and restart Qtopia.

Set the QWS_KEYBOARD environment variable

    export QWS_KEYBOARD=examplekbdhandler

or if the keyboard device is different from the default, for example /dev/input/event1:

    export QWS_KEYBOARD=examplekbdhandler:/dev/input/event1

Start Qtopia.

Touchscreen/Mouse

To create a touchscreen/mouse plug-in we need to create two classes. The first class will be a subclass of QWSCalibratedMouseHandler and will implement the actual touchscreen handler. The second class will be a subclass of QMouseDriverPlugin and links our touchscreen handler with the Qtopia Core mouse input system.

In this tutorial we will be implementing a mouse plug-in for the Linux event interface. The full source for this plug-in is available in the <qtopia-root-dir/devices/example/src/plugins/qtopiacore/mousedrivers/example directory. When creating your own mouse plug-ins your source code should be located in the <qtopia-root-dir>/devices/<device profile>/src/plugins/qtopiacore/mousedrivers/<driver name> directory.

As touchscreen drivers may output noisy samples, we will be implementing basic filtering of the input data in our handler.

Mouse Handler

The class definition for our mouse handler contains numerous data members which are used by the filtering code. The class definition is (examplemousehandler.h):

    #define TS_SAMPLES   5

    class ExampleMouseHandler : public QObject, public QWSCalibratedMouseHandler {
        Q_OBJECT
    public:
        ExampleMouseHandler(const QString &device = QString("/dev/input/event1"));
        ~ExampleMouseHandler();

        void suspend();
        void resume();

    private:
        int  nX, nY;
        int  sx[TS_SAMPLES+3], sy[TS_SAMPLES+3];
        int  index_x1, index_x2, index_y1, index_y2, min_x, min_y;
        int  mouseIdx;
        static const int mouseBufSize = 2048;
        uchar mouseBuf[mouseBufSize];
        QPoint oldmouse;

        QSocketNotifier *m_notify;
        int  mouseFd;

    private Q_SLOTS:
        void readMouseData();
    };

The constructor opens the mouse device, /dev/input/event1. A QSocketNotifier is used to get asynchronous notification on the availability of data from the device. The readMouseData() slot is connected tot he QSocketNotifier::activated() signal and will be called whenever data is available to be read from the device.

    ExampleMouseHandler::ExampleMouseHandler(const QString &device)
        : nX(0), nY(0), min_x(INT_MAX), min_y(INT_MAX), mouseIdx(0)
    {
        qLog(Input) << "Loaded Example touchscreen plugin!";
        setObjectName("Example Mouse Handler");
        mouseFd = ::open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY);
        if (mouseFd >= 0) {
            qLog(Input) << "Opened" << device << "as touchscreen input";
            m_notify = new QSocketNotifier(mouseFd, QSocketNotifier::Read, this);
            connect(m_notify, SIGNAL(activated(int)), this, SLOT(readMouseData()));
        } else {
            qWarning("Cannot open %s for touchscreen input (%s)",
                     device.toLocal8Bit().constData(), strerror(errno));
            return;
        }
    }

In the destructor we close the device.

    ExampleMouseHandler::~ExampleMouseHandler()
    {
        if (mouseFd >= 0)
            ::close(mouseFd);
    }

The suspend() and resume() functions are called to stop and start the flow of mouse events to the system. We just enable and disable notifications from QSocketNotifier.

    void ExampleMouseHandler::suspend()
    {
        m_notify->setEnabled( false );
    }

    void ExampleMouseHandler::resume()
    {
        m_notify->setEnabled( true );
    }

The ExampleInput structure defines the format of the data read from the device. The Linux event interface uses 16 byte packets.

    struct ExampleInput {
        unsigned int   dummy1;
        unsigned int   dummy2;
        unsigned short type;
        unsigned short code;
        unsigned int   value;
    };

Whenever data is available to be read from the device the readMouseData() slot will be invoked by the QSocketNotifier. readMouseData() reads and processes all available mouse events.

    void ExampleMouseHandler::readMouseData()
    {
        if (!qt_screen)
            return;

        int n;

        do {
            n = read(mouseFd, mouseBuf+mouseIdx, mouseBufSize-mouseIdx);
            if (n > 0)
                mouseIdx += n;

Each packet read from the device only contains information on a single axis. Process packets until we have TS_SAMPLES complete sample.

            ExampleInput *data;
            int idx = 0;

            while (mouseIdx-idx >= (int)sizeof(ExampleInput)) {
                uchar *mb = mouseBuf+idx;
                data = (ExampleInput*)mb;
                if (data->code == 0) {
                    // x value
                    sx[nX] = data->value;
                    nX++;
                } else if (data->code == 1) {
                    // y value
                    sy[nY] = data->value;
                    nY++;
                }

Our simple filtering algorithm uses the average of the two closest points in the last TS_SAMPLES samples.

                if (nX >= TS_SAMPLES && nY >= TS_SAMPLES) {
                    int ss = (nX < nY) ? nX : nY;

                    for (int i = 0; i < ss - 1; i++) {
                        for (int j = i + 1; j < ss; j++) {
                            int dx = sx[i] - sx[j];
                            if (dx < 0)
                                dx = -dx;
                            int dy = sy[i] - sy[j];
                            if (dy < 0)
                                dy = -dy;
                            if (min_x > dx) {
                                min_x = dx;
                                index_x1 = i;
                                index_x2 = j;
                            }
                            if (min_y > dy) {
                                min_y = dy;
                                index_y1 = i;
                                index_y2 = j;
                            }
                        }
                    }

                    QPoint pos((sx[index_x1] + sx[index_x2])/2,
                               (sy[index_y1] + sy[index_y2])/2);

                    nX = 0;
                    nY = 0;
                    min_x = INT_MAX;
                    min_y = INT_MAX;

The filtered touchscreen sample is transformed from device coordinates to screen coordinates, and a mouseChanged() signal is emitted.

                    oldmouse = transform( pos );
                    if (oldmouse.x() < MOUSE_SAMPLE_MIN || oldmouse.x() > MOUSE_SAMPLE_MAX ||
                        oldmouse.y() < MOUSE_SAMPLE_MIN || oldmouse.y() > MOUSE_SAMPLE_MAX) {
                        qLog(Input) << "*BAD Mouse sample :x="
                                    << oldmouse.x() << ",y=" << oldmouse.y();
                        oldmouse.setX(0);
                        oldmouse.setY(0);
                    } else {
                        qLog(Input) << "Mouse Down:x="
                                    << oldmouse.x() << ",y=" << oldmouse.y();
                        emit mouseChanged(oldmouse, Qt::LeftButton);
                    }
                }

Emit mouseChanged() signal when the stylus is removed from the screen.

                if ((data->code == 24) && (data->value == 0)) {
                    // Removed pen from screen
                    qLog(Input) << "Mouse Up  :x=" << oldmouse.x() << ",y=" << oldmouse.y();
                    emit mouseChanged(oldmouse, 0);
                    nX = 0;
                    nY = 0;

Move pointers to process the next packet.

                }
                idx += sizeof(ExampleInput);
            }
            int surplus = mouseIdx - idx;
            for (int i = 0; i < surplus; i++)
                mouseBuf[i] = mouseBuf[idx+i];
            mouseIdx = surplus;
        } while (n > 0);
    }

Touchscreen/Mouse Plug-in

The class definition of the mouse driver plug-in (examplemousedriverplugin.h):

    #include <QtGui/QWSMouseHandlerFactoryInterface>

    class ExampleMouseDriverPlugin : public QMouseDriverPlugin {
        Q_OBJECT
    public:
        ExampleMouseDriverPlugin( QObject *parent  = 0 );
        ~ExampleMouseDriverPlugin();

        QWSMouseHandler* create(const QString& driver);
        QWSMouseHandler* create(const QString& driver, const QString& device);
        QStringList keys()const;
    };

The keys function returns the list of drivers that this plug-in provides. Our plug-in only provides the "examplemousehandler" driver.

    QStringList ExampleMouseDriverPlugin::keys() const
    {
        return QStringList() << "examplemousehandler";
    }

The create functions are responsible for returning an instance of the mouse driver. We do this only if the driver parameter matches our key.

The device parameter is the driver specific options specified in the QWS_MOUSE_PROTO environment variable. For our driver we use this to specify the device name. If no device name is specified the driver defaults to /dev/input/event1.

    QWSMouseHandler* ExampleMouseDriverPlugin::create(const QString& driver, const QString& device)
    {
        if ( driver.toLower() == "examplemousehandler" ) {
            qLog(Input) << "Before call ExampleMouseHandler()";
            return new ExampleMouseHandler(device);
        }
        return 0;
    }

    QWSMouseHandler* ExampleMouseDriverPlugin::create(const QString& driver)
    {
        if( driver.toLower() == "examplemousehandler" ) {
            qLog(Input) << "Before call ExampleMouseHandler()";
            return new ExampleMouseHandler();
        }
        return 0;
    }

Finally export the plug-in using the QTOPIA_EXPORT_QT_PLUGIN macro.

    QTOPIA_EXPORT_QT_PLUGIN(ExampleMouseDriverPlugin)

Building and testing the plug-in

The qmake project file for the plug-in (example.pro):

    qtopia_project(embedded qtopia core plugin)
    TARGET = examplemousehandler

    CONFIG+=no_tr

    HEADERS = examplemousedriverplugin.h examplemousehandler.h
    SOURCES = examplemousedriverplugin.cpp examplemousehandler.cpp

Ensure that the plug-in will be built by adding the following line to the <qtopia-root-dir>/devices/<device profile>/src/projects.pri file.

    PROJECTS *= plugins/qtopiacore/mousedrivers/example

Build and install Qtopia.

Note: When creating a plug-in it is not necessary to rebuild all of Qtopia to enable it. Simply rebuild the plug-in and copy the files to the image/Qtopia directory and restart Qtopia.

Set the QWS_MOUSE_PROTO environment variable

    export QWS_MOUSE_PROTO=examplemousehandler

or if the mouse device is different from the default, for example /dev/input/event2:

    export QWS_MOUSE_PROTO=examplemousehandler:/dev/input/event2

Creating a Custom Screen Driver

For some hardware there is a need to make minor changes to how the screen is written to the device. One such example is where there needs to be an ioctl sent to the framebuffer device after each frame update. To see how one might create a QScreenDriverPlugin plugin to do that, see src/plugins/qtopiacore/gfxdrivers/example/ and modify the ExampleScreen::exposeRegion function to send the ioctl required.

To make use of this created screen driver plugin set QWS_DISPLAY, for example:

        export QWS_DISPLAY=examplescreen

Creating network scripts

Qtopia devices use a variety of file systems. This requires customized network scripts which provide the link between Qtopia's network code and the file system. It is their responsibility to start, stop and configure the various network interfaces.

For more details check out $QPEDIR/devices/example/src/devtools/scripts/*-network scripts and the Network Services documentation.

Running Qtopia on the device using qpe.env and qpe.sh

To make it easier to run Qtopia on the device there can be two file qpe.env and qpe.sh:

After customizing src/devtools/startup/qpe.{env,sh} to suit the device and Qtopia is installed into the device it is then possible to:

See also Running Qtopia.


Copyright © 2008 Nokia Trademarks
Qtopia 4.3.3