이 글은 KDAB에 소개된 글과 예제소스코드를 기반으로 작성되었다.
가상키보드는 실제 물리 키보드가 없는 환경에서 입력장치로 유용하게 사용될 수 있다.
Qt는 이미 훌륭한 Virtual keyboard를 제공하고 있지만 이 글에서는 dbus, QPA기반에서 키입력 인터페이스를 사용하는 방법과 가상키보드 구현 기본 개념을 살펴본다.
가상키보드를 구현하는 방법에는 크게 두가지 방법이 있다. in-process로 구현하거나 또는 하나의 독립 프로세스로(out-of-process) 동작하도록 구현하는 것이다.
in-process로 구현하는 접근 방식의 장점은 키보드와 응용프로그램간 즉, 프로세스 간 통신이 필요 없기 때문에 구현이 매우 쉽다는 것이다. 단점은 모든 응용프로그램이 가상키보드의 인스턴스를 가지고 있어야 한다는 것이다. 이 문제는 모든 인스턴스 (가시성, 활동 등)의 상태를 응용프로그램간 동기화를 어렵게 한다는 것을 의미한다.
반면 out-of-process 방식은 다른 모든 응용프로그램간에 가상 키보드의 단일 인스턴스(singleton)를 공유 할 수 있으므로 여러 가상키보드 인스턴스간에 동기화 문제가 발생하지 않는다는 것이다. 다만 이를 위해서는 전역 가상키보드 인스턴스를 사용하려는 모든 응용 프로그램 사이의 프로세스 간 통신 메커니즘이 필요하다.
이 글에서는 기존 리눅스 Input Method 시스템인 IBus에서 이미 작동하는 것으로 입증되었기 때문에 아웃 프로세스 (out-of-process) 방식을 사용하고 dbus를 IPC메커니즘으로 사용한다.
dbus에 관해서 더 자세한 내용은 다음 글을 참고하길 바란다. https://makersweb.net/linux/2027
#include <QApplication>
#include <QDBusConnection>
#include "keyboard.h"
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    if (!QDBusConnection::sessionBus().registerService("com.kdab.inputmethod")) {
        qFatal("Unable to register at DBus");
        return 1;
    }
    Keyboard keyboard;
    if (!QDBusConnection::sessionBus().registerObject("/VirtualKeyboard", &keyboard, QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllSlots)) {
        qFatal("Unable to register object at DBus");
        return 1;
    }
    return app.exec();
}#ifndef KEYBOARD_H
#define KEYBOARD_H
#include <QWidget>
class Keyboard : public QWidget
{
    Q_OBJECT
public:
    explicit Keyboard(QWidget *parent = Q_NULLPTR);
public slots:
    void showKeyboard(int globalX, int globalY);
    void hideKeyboard();
    bool keyboardVisible() const;
signals:
    void specialKeyClicked(int key);
    void keyClicked(const QString &text);
private slots:
    void buttonClicked(int key);
};
#endif
Keyboard::Keyboard(QWidget *parent)
    : QWidget(parent)
{
    setWindowFlags(Qt::WindowDoesNotAcceptFocus | Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::BypassWindowManagerHint);
    QGridLayout *gridLayout = new QGridLayout(this);
    QSignalMapper *mapper = new QSignalMapper(this);
    connect(mapper, SIGNAL(mapped(int)), SLOT(buttonClicked(int)));
    int row = 0;
    int column = 0;
    for (int i = 0; i < layoutSize; ++i) {
        if (keyboardLayout[i].key == NEXT_ROW_MARKER) {
            row++;
            column = 0;
            continue;
        }
        QPushButton *button = new QPushButton;
        button->setFixedWidth(40);
        button->setText(QString::fromLatin1(keyboardLayout[i].label));
        mapper->setMapping(button, keyboardLayout[i].key);
        connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
        gridLayout->addWidget(button, row, column);
        column++;
    }
}void Keyboard::buttonClicked(int key)
{
    if ((key == Qt::Key_Enter) || (key == Qt::Key_Backspace))
        emit specialKeyClicked(key);
    else
        emit keyClicked(keyToCharacter(key));
}static QString keyToCharacter(int key)
{
    for (int i = 0; i < layoutSize; ++i) {
        if (keyboardLayout[i].key == key)
            return QString::fromLatin1(keyboardLayout[i].label);
    }
    return QString();
}class QVkImPlatformInputContextPlugin : public QPlatformInputContextPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QPlatformInputContextFactoryInterface_iid FILE "vkim.json")
public:
    QVkImPlatformInputContext *create(const QString&, const QStringList&) Q_DECL_OVERRIDE;
};
QVkImPlatformInputContext *QVkImPlatformInputContextPlugin::create(const QString& system, const QStringList& paramList)
{
    Q_UNUSED(paramList);
    if (system == QLatin1String("vkim")) {
        return new QVkImPlatformInputContext;
    }
    return 0;
}{
    "Keys": [ "vkim" ]
}QVkImPlatformInputContext::QVkImPlatformInputContext()
    : m_focusObject(0)
{
    m_keyboardInterface = new QDBusInterface("com.kdab.inputmethod", "/VirtualKeyboard", "local.server.Keyboard", QDBusConnection::sessionBus(), this);
    connect(m_keyboardInterface, SIGNAL(keyClicked(QString)), SLOT(keyboardKeyClicked(QString)));
    connect(m_keyboardInterface, SIGNAL(specialKeyClicked(int)), SLOT(keyboardSpecialKeyClicked(int)));
}void QVkImPlatformInputContext::keyboardKeyClicked(const QString &characters)
{
    if (!m_focusObject)
        return;
    QInputMethodEvent event;
    event.setCommitString(characters);
    QGuiApplication::sendEvent(m_focusObject, &event);
} 
이제 입력 필드를 가진 간단한 응용프로그램을 만든다.

 
응용프로그램을 실행하고 입력 필드를 클릭하면 키보드가 나타나고 키를 클릭하면 글자가 입력된다.

아래는 예제소스코드 저장소이다.
https://github.com/KDAB/virtual-keyboard-demo








