QPA のお勉強(1)

QPA とは?

Qt Platform Abstraction (通称 QPA) は Qt の GUI まわりのプラットフォームの抽象化レイヤーです。Qt 4.8 で導入され、Qt 5.0 で機能を追加しすべてのプラットフォームで使われるようになりました。

windows, cocoa, xcb, wayland, eglfs, qnx など、実際に使用されているプラグインの他、directfb, kms, linuxfb, openwfd のように試験的に実装されているプラグインもあります。

QPA の勉強を兼ねてなにか面白いプラグインを作りたいなと思ってきたのですが、なかなかいいアイデアも思い浮かばなかったので、今回は proxy プラグインを作ってみました。

QPA プラグインのソースコード

Qt 内の QPA プラグインのソースの位置は qtbase/src/plugins/platforms/ になります。

今回は、最小限のサンプルである minimal を参考にして proxy プラグインを作成しました。

QPA プラグインを作成する

proxy ディレクトリを作成し、proxy.pro, proxy.json, main.cpp と qproxyintegration.{h,cpp} を作成します。

proxy.pro

TARGET = qproxy

PLUGIN_TYPE = platforms
PLUGIN_CLASS_NAME = QProxyIntegrationPlugin
load(qt_plugin)

QT += core-private gui-private platformsupport-private

SOURCES =   main.cpp \             qproxyintegration.cpp
HEADERS =   qproxyintegration.h

OTHER_FILES += proxy.json

Qt 5 でのプラグインのプロジェクトファイルの典型的な内容になっています。

main.cpp

/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia.  For licensing terms and ** conditions see http://qt.digia.com/licensing.  For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights.  These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/


#include <qpa/qplatformintegrationplugin.h>
#include "qproxyintegration.h"

QT_BEGIN_NAMESPACE

class QProxyIntegrationPlugin : public QPlatformIntegrationPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPA.QPlatformIntegrationFactoryInterface.5.1" FILE "proxy.json")
public:
    QPlatformIntegration *create(const QString&, const QStringList&);
};

QPlatformIntegration *QProxyIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
    Q_UNUSED(paramList);
    if (system.toLower() == "proxy")
        return new QProxyIntegration(paramList);

    return 0;
}

QT_END_NAMESPACE

#include "main.moc"

こちらも Qt 5 でのプラグインのソースファイルの典型的な内容です。

proxy.json

{
    "Keys": [ "proxy" ]
}

Qt 4 では Q_EXPORT_PLUGIN2 マクロを使用してプラグインの登録を行っていましたが、 Qt 5 では、プラグイン自体をロードしなくてもメタデータが取得できるよう プラグインの仕組みが書き換えられました。これにより、プラグインのメタデータは上記のように json ファイルに記述するように変わっています。

qproxyintegration.h

/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia.  For licensing terms and ** conditions see http://qt.digia.com/licensing.  For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights.  These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/

#ifndef QPLATFORMINTEGRATION_PROXY_H
#define QPLATFORMINTEGRATION_PROXY_H

#include <qpa/qplatformintegration.h>

QT_BEGIN_NAMESPACE

class QProxyIntegration : public QPlatformIntegration
{
public:
    QProxyIntegration(const QStringList &paramList);
    ~QProxyIntegration();

    virtual bool hasCapability(QPlatformIntegration::Capability cap) const;

    virtual QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const;
    virtual QPlatformWindow *createPlatformWindow(QWindow *window) const;
    virtual QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const;
#ifndef QT_NO_OPENGL
    virtual QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const;
#endif
    virtual QPlatformSharedGraphicsCache *createPlatformSharedGraphicsCache(const char *cacheId) const;
    virtual QPaintEngine *createImagePaintEngine(QPaintDevice *paintDevice) const;

// Event dispatcher:
    virtual QAbstractEventDispatcher *guiThreadEventDispatcher() const;

//Deeper window system integrations
    virtual QPlatformFontDatabase *fontDatabase() const;
#ifndef QT_NO_CLIPBOARD
    virtual QPlatformClipboard *clipboard() const;
#endif
#ifndef QT_NO_DRAGANDDROP
    virtual QPlatformDrag *drag() const;
#endif
    virtual QPlatformInputContext *inputContext() const;
#ifndef QT_NO_ACCESSIBILITY
    virtual QPlatformAccessibility *accessibility() const;
#endif

    // Access native handles. The window handle is already available from Wid;
    virtual QPlatformNativeInterface *nativeInterface() const;

    virtual QPlatformServices *services() const;

    virtual QVariant styleHint(StyleHint hint) const;

    virtual Qt::KeyboardModifiers queryKeyboardModifiers() const;
    virtual QList<int> possibleKeys(const QKeyEvent *) const;

    virtual QStringList themeNames() const;
    virtual QPlatformTheme *createPlatformTheme(const QString &name) const;

private:
    QPlatformIntegration *mPlatformIntegration;
};

QT_END_NAMESPACE

#endif

QPlatformIntegration のすべての仮想関数をオーバーライドし、参照元の QPlatformIntegration のポインタをメンバ変数として持つようにしています。

qproxyintegration.cpp

/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia.  For licensing terms and ** conditions see http://qt.digia.com/licensing.  For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights.  These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file.  Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/

#include "qproxyintegration.h"
#include <qpa/qplatformintegrationfactory_p.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>

QT_BEGIN_NAMESPACE

QProxyIntegration::QProxyIntegration(const QStringList &paramList)
{
    QByteArray platformName;
#ifdef QT_QPA_DEFAULT_PLATFORM_NAME
    platformName = QT_QPA_DEFAULT_PLATFORM_NAME;
#endif
    QByteArray platformNameEnv = qgetenv("QT_QPA_PLATFORM");
    if (!platformNameEnv.isEmpty()) {
        platformName = platformNameEnv;
    }

    // Load the platform integration
    QString platformPluginPath = QLatin1String(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH"));

    // On Mac, look inside the application bundle for the platform plugin.
    // TODO (msorvig): Create proper cross-platform solution for loading
    // deployed platform plugins
#ifdef Q_OS_MAC
    const QString bundlePluginPath = QCoreApplication::applicationDirPath() + QLatin1String("../Plugins/");
    if (platformPluginPath.isEmpty() && QDir(bundlePluginPath).exists()) {
        platformPluginPath = bundlePluginPath;
    }
#endif

    QStringList argv = QCoreApplication::arguments();
    for (int i = 0; i < argv.size(); i++) {
        if (argv.at(i) == QLatin1String("-platformpluginpath")) {
            if (++i == argv.size()) {
                platformPluginPath = argv.at(i);
                break;
            }
        }
    }

    QStringList arguments = paramList;
    if (!arguments.isEmpty()) {
        QStringList keys = QPlatformIntegrationFactory::keys(platformPluginPath);
        if (keys.contains(arguments.first())) {
            platformName = arguments.takeFirst().toLower().toUtf8();
        }
    }

    mPlatformIntegration = QPlatformIntegrationFactory::create(QLatin1String(platformName), arguments, platformPluginPath);
    if (mPlatformIntegration) {

    } else {
        QStringList keys = QPlatformIntegrationFactory::keys(platformPluginPath);
        QString fatalMessage =
            QString::fromLatin1("Failed to load platform plugin \"%1\". Available platforms are: \n").arg(QLatin1String(platformName));
        foreach(const QString &key, keys) {
            fatalMessage.append(key + QLatin1Char('\n'));
        }
        qFatal("%s", fatalMessage.toLocal8Bit().constData());
        return;
    }
}

QProxyIntegration::~QProxyIntegration()
{
    delete mPlatformIntegration;
}

bool QProxyIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
    return mPlatformIntegration->hasCapability(cap);
}

QPlatformPixmap *QProxyIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const
{
    return mPlatformIntegration->createPlatformPixmap(type);
}

QPlatformWindow *QProxyIntegration::createPlatformWindow(QWindow *window) const
{
    return mPlatformIntegration->createPlatformWindow(window);
}

QPlatformBackingStore *QProxyIntegration::createPlatformBackingStore(QWindow *window) const
{
    return mPlatformIntegration->createPlatformBackingStore(window);
}

#ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QProxyIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
    return mPlatformIntegration->createPlatformOpenGLContext(context);
}
#endif

QPlatformSharedGraphicsCache *QProxyIntegration::createPlatformSharedGraphicsCache(const char *cacheId) const
{
    return mPlatformIntegration->createPlatformSharedGraphicsCache(cacheId);
}

QPaintEngine *QProxyIntegration::createImagePaintEngine(QPaintDevice *paintDevice) const
{
    return mPlatformIntegration->createImagePaintEngine(paintDevice);
}

QAbstractEventDispatcher *QProxyIntegration::guiThreadEventDispatcher() const
{
    return mPlatformIntegration->guiThreadEventDispatcher();
}

QPlatformFontDatabase *QProxyIntegration::fontDatabase() const
{
    return mPlatformIntegration->fontDatabase();
}

#ifndef QT_NO_CLIPBOARD
QPlatformClipboard *QProxyIntegration::clipboard() const
{
    return mPlatformIntegration->clipboard();
}
#endif

#ifndef QT_NO_DRAGANDDROP
QPlatformDrag *QProxyIntegration::drag() const
{
    return mPlatformIntegration->drag();
}
#endif

QPlatformInputContext *QProxyIntegration::inputContext() const
{
    return mPlatformIntegration->inputContext();
}

#ifndef QT_NO_ACCESSIBILITY
QPlatformAccessibility *QProxyIntegration::accessibility() const
{
    return mPlatformIntegration->accessibility();
}
#endif

QPlatformNativeInterface *QProxyIntegration::nativeInterface() const
{
    return mPlatformIntegration->nativeInterface();
}

QPlatformServices *QProxyIntegration::services() const
{
    return mPlatformIntegration->services();
}

QVariant QProxyIntegration::styleHint(StyleHint hint) const
{
    return mPlatformIntegration->styleHint(hint);
}

Qt::KeyboardModifiers QProxyIntegration::queryKeyboardModifiers() const
{
    return mPlatformIntegration->queryKeyboardModifiers();
}

QList<int> QProxyIntegration::possibleKeys(const QKeyEvent *e) const
{
    return mPlatformIntegration->possibleKeys(e);
}

QStringList QProxyIntegration::themeNames() const
{
    return mPlatformIntegration->themeNames();
}

QPlatformTheme *QProxyIntegration::createPlatformTheme(const QString &name) const
{
    return mPlatformIntegration->createPlatformTheme(name);
}

QT_END_NAMESPACE

上書きした仮想関数内で、参照元のプラグインの関数を呼び出しています。参照元のプラグインの確定アルゴリズムは、QGuiApplication のコードを参考にしています。

まとめ

今回は、minimal プラグインを参考に、他の(プラットフォームのデフォルトの) QPA プラグインをただ呼ぶだけのプロキシプラグインを作成しました。コピペだらけで特に難しいところはないはずです。

次回はこれをベースになにかを作る予定。

おすすめ