platforminputcontexts: use libxkbcommon compose key API
Our implementation of compose table parser was added on Mar, 2013. libxkbcommon added APIs for the same thing in Oct, 2014 (ver: 0.5.0). After removing RHEL 6.6 from the list of supported platforms we were able to move the minimal required libxkbcommon version to 0.5.0. Now we can use the xkbcommon-compose APIs on all supported platforms. With this patch we can drop nearly 1000 lines of maintenance burden. This patch fixes user reported issues with our implementation. Known issues: - Testing revealed that xkbcommon-compose does not support non-utf8 locales, and that is by design - https://github.com/xkbcommon/libxkbcommon/issues/76 Our implementation did work for those locales too, but it is unclear if anyone actually uses non-utf8 locales. It is a corner case (work-arounds existing) and likely a configuration error on the users' system. - Looking at the release notes for versions above 0.6.1, only one issue that stands out. Compose input does not work on system with tr_TR.UTF-8 locale, fixed in 0.7.1. Compose input works fine when using e.g. en_US.UTF-8 locale with Turkish keyboard layout. Note: With Qt 5.13 we have removed Ubuntu 16.04 and openSUSE 42.3 from CI: Ubuntu 16.04 - 0.5.0 openSUSE 42.3 - 0.6.1 CI for Qt 5.13 has: Ubuntu 18.04 - 0.8.0 RHEL-7.4 - 0.7.1 openSUSE 15.0 - 0.8.1 Currently the minimal required libxkbcommon version in src/gui/configure.json is set to 0.5.0, but we could bump it to 0.7.1 to avoid known issues from above, but that is a decision for a separate patch. [ChangeLog][plugins][platforminputcontexts] Now using libxkbcommon-compose APIs for compose key input, instead of Qt's own implementation. Fixes: QTBUG-42181 Fixes: QTBUG-53663 Fixes: QTBUG-48657 Change-Id: I79aafe2bc601293844066e7e5f5eddd3719c6bba Reviewed-by: Giulio Camuffo <giulio.camuffo@kdab.com> Reviewed-by: Johan Helsing <johan.helsing@qt.io>
This commit is contained in:
parent
a34e81ab8b
commit
2065bc070d
@ -41,7 +41,12 @@
|
|||||||
|
|
||||||
#include <private/qmakearray_p.h>
|
#include <private/qmakearray_p.h>
|
||||||
|
|
||||||
|
#include <QtCore/QMetaMethod>
|
||||||
#include <QtGui/QKeyEvent>
|
#include <QtGui/QKeyEvent>
|
||||||
|
#include <QtGui/private/qguiapplication_p.h>
|
||||||
|
|
||||||
|
#include <qpa/qplatforminputcontext.h>
|
||||||
|
#include <qpa/qplatformintegration.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@ -794,4 +799,30 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QXkbCommon::setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context)
|
||||||
|
{
|
||||||
|
if (!inputContext || !context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *const inputContextClassName = "QComposeInputContext";
|
||||||
|
const char *const normalizedSignature = "setXkbContext(xkb_context*)";
|
||||||
|
|
||||||
|
if (inputContext->objectName() != QLatin1String(inputContextClassName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
static const QMetaMethod setXkbContext = [&]() {
|
||||||
|
int methodIndex = inputContext->metaObject()->indexOfMethod(normalizedSignature);
|
||||||
|
QMetaMethod method = inputContext->metaObject()->method(methodIndex);
|
||||||
|
Q_ASSERT(method.isValid());
|
||||||
|
if (!method.isValid())
|
||||||
|
qCWarning(lcXkbcommon) << normalizedSignature << "not found on" << inputContextClassName;
|
||||||
|
return method;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (!setXkbContext.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setXkbContext.invoke(inputContext, Qt::DirectConnection, Q_ARG(struct xkb_context*, context));
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -64,7 +64,9 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon)
|
Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon)
|
||||||
|
|
||||||
|
class QEvent;
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
|
class QPlatformInputContext;
|
||||||
|
|
||||||
class QXkbCommon
|
class QXkbCommon
|
||||||
{
|
{
|
||||||
@ -99,6 +101,8 @@ public:
|
|||||||
return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9;
|
return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context);
|
||||||
|
|
||||||
struct XKBStateDeleter {
|
struct XKBStateDeleter {
|
||||||
void operator()(struct xkb_state *state) const { return xkb_state_unref(state); }
|
void operator()(struct xkb_state *state) const { return xkb_state_unref(state); }
|
||||||
};
|
};
|
||||||
|
@ -3,18 +3,14 @@ TARGET = composeplatforminputcontextplugin
|
|||||||
QT += core-private gui-private
|
QT += core-private gui-private
|
||||||
|
|
||||||
SOURCES += $$PWD/qcomposeplatforminputcontextmain.cpp \
|
SOURCES += $$PWD/qcomposeplatforminputcontextmain.cpp \
|
||||||
$$PWD/qcomposeplatforminputcontext.cpp \
|
$$PWD/qcomposeplatforminputcontext.cpp
|
||||||
$$PWD/generator/qtablegenerator.cpp \
|
|
||||||
|
|
||||||
HEADERS += $$PWD/qcomposeplatforminputcontext.h \
|
HEADERS += $$PWD/qcomposeplatforminputcontext.h
|
||||||
$$PWD/generator/qtablegenerator.h \
|
|
||||||
|
|
||||||
QMAKE_USE_PRIVATE += xkbcommon
|
QMAKE_USE_PRIVATE += xkbcommon
|
||||||
|
|
||||||
include($$OUT_PWD/../../../gui/qtgui-config.pri)
|
include($$OUT_PWD/../../../gui/qtgui-config.pri)
|
||||||
|
|
||||||
DEFINES += X11_PREFIX='\\"$$QMAKE_X11_PREFIX\\"'
|
|
||||||
|
|
||||||
OTHER_FILES += $$PWD/compose.json
|
OTHER_FILES += $$PWD/compose.json
|
||||||
|
|
||||||
PLUGIN_TYPE = platforminputcontexts
|
PLUGIN_TYPE = platforminputcontexts
|
||||||
|
@ -1,658 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** 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 The Qt Company. For licensing terms
|
|
||||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
||||||
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
||||||
** packaging of this file. Please review the following information to
|
|
||||||
** ensure the GNU Lesser General Public License version 3 requirements
|
|
||||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
||||||
**
|
|
||||||
** GNU General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 2.0 or (at your option) the GNU General
|
|
||||||
** Public license version 3 or any later version approved by the KDE Free
|
|
||||||
** Qt Foundation. The licenses are as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
||||||
** included in the packaging of this file. Please review the following
|
|
||||||
** information to ensure the GNU General Public License requirements will
|
|
||||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
||||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include "qtablegenerator.h"
|
|
||||||
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include <QtCore/QTextCodec>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
#include <QtCore/QDir>
|
|
||||||
#include <QtCore/QStringList>
|
|
||||||
#include <QtCore/QString>
|
|
||||||
#include <QtCore/QSaveFile>
|
|
||||||
#include <QtCore/QStandardPaths>
|
|
||||||
#include <private/qcore_unix_p.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
|
|
||||||
#include <locale.h> // LC_CTYPE
|
|
||||||
#include <string.h> // strchr, strncmp, etc.
|
|
||||||
#include <strings.h> // strncasecmp
|
|
||||||
#include <clocale> // LC_CTYPE
|
|
||||||
|
|
||||||
static const quint32 SupportedCacheVersion = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
In short on how and why the "Compose" file is cached:
|
|
||||||
|
|
||||||
The "Compose" file is large, for en_US it's likely located at:
|
|
||||||
/usr/share/X11/locale/en_US.UTF-8/Compose
|
|
||||||
and it has about 6000 string lines.
|
|
||||||
Q(Gui)Applications parse this file each time they're created. On modern CPUs
|
|
||||||
it incurs a 4-10 ms startup penalty of each Qt gui app, on older CPUs -
|
|
||||||
tens of ms or more.
|
|
||||||
Since the "Compose" file (almost) never changes using a pre-parsed
|
|
||||||
cache file instead of the "Compose" file is a good idea to improve Qt5
|
|
||||||
application startup time by about 5+ ms (or tens of ms on older CPUs).
|
|
||||||
|
|
||||||
The cache file contains the contents of the QComposeCacheFileHeader struct at the
|
|
||||||
beginning followed by the pre-parsed contents of the "Compose" file.
|
|
||||||
|
|
||||||
struct QComposeCacheFileHeader stores
|
|
||||||
(a) The cache version - in the unlikely event that some day one might need
|
|
||||||
to break compatibility.
|
|
||||||
(b) The (cache) file size.
|
|
||||||
(c) The lastModified field tracks if anything changed since the last time
|
|
||||||
the cache file was saved.
|
|
||||||
If anything did change then we read the compose file and save (cache) it
|
|
||||||
in binary/pre-parsed format, which should happen extremely rarely if at all.
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct QComposeCacheFileHeader
|
|
||||||
{
|
|
||||||
quint32 cacheVersion;
|
|
||||||
// The compiler will add 4 padding bytes anyway.
|
|
||||||
// Reserve them explicitly to possibly use in the future.
|
|
||||||
quint32 reserved;
|
|
||||||
quint64 fileSize;
|
|
||||||
qint64 lastModified;
|
|
||||||
};
|
|
||||||
|
|
||||||
// localHostName() copied from qtbase/src/corelib/io/qlockfile_unix.cpp
|
|
||||||
static QByteArray localHostName()
|
|
||||||
{
|
|
||||||
QByteArray hostName(512, Qt::Uninitialized);
|
|
||||||
if (gethostname(hostName.data(), hostName.size()) == -1)
|
|
||||||
return QByteArray();
|
|
||||||
hostName.truncate(strlen(hostName.data()));
|
|
||||||
return hostName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reads metadata about the Compose file. Later used to determine if the
|
|
||||||
compose cache should be updated. The fileSize field will be zero on failure.
|
|
||||||
*/
|
|
||||||
static QComposeCacheFileHeader readFileMetadata(const QString &path)
|
|
||||||
{
|
|
||||||
quint64 fileSize = 0;
|
|
||||||
qint64 lastModified = 0;
|
|
||||||
const QByteArray pathBytes = QFile::encodeName(path);
|
|
||||||
QT_STATBUF st;
|
|
||||||
if (QT_STAT(pathBytes.data(), &st) == 0) {
|
|
||||||
lastModified = st.st_mtime;
|
|
||||||
fileSize = st.st_size;
|
|
||||||
}
|
|
||||||
QComposeCacheFileHeader info = { 0, 0, fileSize, lastModified };
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const QString getCacheFilePath()
|
|
||||||
{
|
|
||||||
QFile machineIdFile("/var/lib/dbus/machine-id");
|
|
||||||
QString machineId;
|
|
||||||
if (machineIdFile.exists()) {
|
|
||||||
if (machineIdFile.open(QIODevice::ReadOnly))
|
|
||||||
machineId = QString::fromLatin1(machineIdFile.readAll().trimmed());
|
|
||||||
}
|
|
||||||
if (machineId.isEmpty())
|
|
||||||
machineId = localHostName();
|
|
||||||
const QString dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
|
|
||||||
|
|
||||||
if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
|
|
||||||
return dirPath + QLatin1String("/qt_compose_cache_big_endian_") + machineId;
|
|
||||||
return dirPath + QLatin1String("/qt_compose_cache_little_endian_") + machineId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns empty vector on failure
|
|
||||||
static QVector<QComposeTableElement> loadCache(const QComposeCacheFileHeader &composeInfo)
|
|
||||||
{
|
|
||||||
QVector<QComposeTableElement> vec;
|
|
||||||
const QString cacheFilePath = getCacheFilePath();
|
|
||||||
QFile inputFile(cacheFilePath);
|
|
||||||
|
|
||||||
if (!inputFile.open(QIODevice::ReadOnly))
|
|
||||||
return vec;
|
|
||||||
QComposeCacheFileHeader cacheInfo;
|
|
||||||
// use a "buffer" variable to make the line after this one more readable.
|
|
||||||
char *buffer = reinterpret_cast<char*>(&cacheInfo);
|
|
||||||
|
|
||||||
if (inputFile.read(buffer, sizeof cacheInfo) != sizeof cacheInfo)
|
|
||||||
return vec;
|
|
||||||
if (cacheInfo.fileSize == 0)
|
|
||||||
return vec;
|
|
||||||
// using "!=" just in case someone replaced with a backup that existed before
|
|
||||||
if (cacheInfo.lastModified != composeInfo.lastModified)
|
|
||||||
return vec;
|
|
||||||
if (cacheInfo.cacheVersion != SupportedCacheVersion)
|
|
||||||
return vec;
|
|
||||||
const QByteArray pathBytes = QFile::encodeName(cacheFilePath);
|
|
||||||
QT_STATBUF st;
|
|
||||||
if (QT_STAT(pathBytes.data(), &st) != 0)
|
|
||||||
return vec;
|
|
||||||
const off_t fileSize = st.st_size;
|
|
||||||
if (fileSize > 1024 * 1024 * 5) {
|
|
||||||
// The cache file size is usually about 150KB, so if its size is over
|
|
||||||
// say 5MB then somebody inflated the file, abort.
|
|
||||||
return vec;
|
|
||||||
}
|
|
||||||
const off_t bufferSize = fileSize - (sizeof cacheInfo);
|
|
||||||
const size_t elemSize = sizeof (struct QComposeTableElement);
|
|
||||||
const int elemCount = bufferSize / elemSize;
|
|
||||||
const QByteArray ba = inputFile.read(bufferSize);
|
|
||||||
const char *data = ba.data();
|
|
||||||
// Since we know the number of the (many) elements and their size in
|
|
||||||
// advance calling vector.reserve(..) seems reasonable.
|
|
||||||
vec.reserve(elemCount);
|
|
||||||
|
|
||||||
for (int i = 0; i < elemCount; i++) {
|
|
||||||
const QComposeTableElement *elem =
|
|
||||||
reinterpret_cast<const QComposeTableElement*>(data + (i * elemSize));
|
|
||||||
vec.push_back(*elem);
|
|
||||||
}
|
|
||||||
return vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true on success, false otherwise.
|
|
||||||
static bool saveCache(const QComposeCacheFileHeader &info, const QVector<QComposeTableElement> &vec)
|
|
||||||
{
|
|
||||||
const QString filePath = getCacheFilePath();
|
|
||||||
#if QT_CONFIG(temporaryfile)
|
|
||||||
QSaveFile outputFile(filePath);
|
|
||||||
#else
|
|
||||||
QFile outputFile(filePath);
|
|
||||||
#endif
|
|
||||||
if (!outputFile.open(QIODevice::WriteOnly))
|
|
||||||
return false;
|
|
||||||
const char *data = reinterpret_cast<const char*>(&info);
|
|
||||||
|
|
||||||
if (outputFile.write(data, sizeof info) != sizeof info)
|
|
||||||
return false;
|
|
||||||
data = reinterpret_cast<const char*>(vec.constData());
|
|
||||||
const qint64 size = vec.size() * (sizeof (struct QComposeTableElement));
|
|
||||||
|
|
||||||
if (outputFile.write(data, size) != size)
|
|
||||||
return false;
|
|
||||||
#if QT_CONFIG(temporaryfile)
|
|
||||||
return outputFile.commit();
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
TableGenerator::TableGenerator() : m_state(NoErrors),
|
|
||||||
m_systemComposeDir(QString())
|
|
||||||
{
|
|
||||||
initPossibleLocations();
|
|
||||||
QString composeFilePath = findComposeFile();
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
// don't use cache when in debug mode.
|
|
||||||
if (!composeFilePath.isEmpty())
|
|
||||||
qDebug() << "Using Compose file from: " << composeFilePath;
|
|
||||||
#else
|
|
||||||
QComposeCacheFileHeader fileInfo = readFileMetadata(composeFilePath);
|
|
||||||
if (fileInfo.fileSize != 0)
|
|
||||||
m_composeTable = loadCache(fileInfo);
|
|
||||||
#endif
|
|
||||||
if (m_composeTable.isEmpty() && cleanState()) {
|
|
||||||
if (composeFilePath.isEmpty()) {
|
|
||||||
m_state = MissingComposeFile;
|
|
||||||
} else {
|
|
||||||
QFile composeFile(composeFilePath);
|
|
||||||
composeFile.open(QIODevice::ReadOnly);
|
|
||||||
parseComposeFile(&composeFile);
|
|
||||||
orderComposeTable();
|
|
||||||
if (m_composeTable.isEmpty()) {
|
|
||||||
m_state = EmptyTable;
|
|
||||||
#ifndef DEBUG_GENERATOR
|
|
||||||
// don't save cache when in debug mode
|
|
||||||
} else {
|
|
||||||
fileInfo.cacheVersion = SupportedCacheVersion;
|
|
||||||
saveCache(fileInfo, m_composeTable);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
printComposeTable();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::initPossibleLocations()
|
|
||||||
{
|
|
||||||
// Compose files come as a part of Xlib library. Xlib doesn't provide
|
|
||||||
// a mechanism how to retrieve the location of these files reliably, since it was
|
|
||||||
// never meant for external software to parse compose tables directly. Best we
|
|
||||||
// can do is to hardcode search paths. To add an extra system path use
|
|
||||||
// the QTCOMPOSE environment variable
|
|
||||||
m_possibleLocations.reserve(7);
|
|
||||||
if (qEnvironmentVariableIsSet("QTCOMPOSE"))
|
|
||||||
m_possibleLocations.append(QString::fromLocal8Bit(qgetenv("QTCOMPOSE")));
|
|
||||||
m_possibleLocations.append(QStringLiteral("/usr/share/X11/locale"));
|
|
||||||
m_possibleLocations.append(QStringLiteral("/usr/local/share/X11/locale"));
|
|
||||||
m_possibleLocations.append(QStringLiteral("/usr/lib/X11/locale"));
|
|
||||||
m_possibleLocations.append(QStringLiteral("/usr/local/lib/X11/locale"));
|
|
||||||
m_possibleLocations.append(QStringLiteral(X11_PREFIX "/share/X11/locale"));
|
|
||||||
m_possibleLocations.append(QStringLiteral(X11_PREFIX "/lib/X11/locale"));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableGenerator::findComposeFile()
|
|
||||||
{
|
|
||||||
// check if XCOMPOSEFILE points to a Compose file
|
|
||||||
if (qEnvironmentVariableIsSet("XCOMPOSEFILE")) {
|
|
||||||
const QString path = QFile::decodeName(qgetenv("XCOMPOSEFILE"));
|
|
||||||
if (QFile::exists(path))
|
|
||||||
return path;
|
|
||||||
else
|
|
||||||
qWarning("$XCOMPOSEFILE doesn't point to an existing file");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if user’s home directory has a file named .XCompose
|
|
||||||
if (cleanState()) {
|
|
||||||
QString path = qgetenv("HOME") + QLatin1String("/.XCompose");
|
|
||||||
if (QFile::exists(path))
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for the system provided compose files
|
|
||||||
if (cleanState()) {
|
|
||||||
QString table = composeTableForLocale();
|
|
||||||
if (cleanState()) {
|
|
||||||
if (table.isEmpty())
|
|
||||||
// no table mappings for the system's locale in the compose.dir
|
|
||||||
m_state = UnsupportedLocale;
|
|
||||||
else {
|
|
||||||
QString path = QDir(systemComposeDir()).filePath(table);
|
|
||||||
if (QFile::exists(path))
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableGenerator::composeTableForLocale()
|
|
||||||
{
|
|
||||||
QByteArray loc = locale().toUpper().toUtf8();
|
|
||||||
QString table = readLocaleMappings(loc);
|
|
||||||
if (table.isEmpty())
|
|
||||||
table = readLocaleMappings(readLocaleAliases(loc));
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableGenerator::findSystemComposeDir()
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
for (int i = 0; i < m_possibleLocations.size(); ++i) {
|
|
||||||
QString path = m_possibleLocations.at(i);
|
|
||||||
if (QFile::exists(path + QLatin1String("/compose.dir"))) {
|
|
||||||
m_systemComposeDir = path;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
// should we ask to report this in the qt bug tracker?
|
|
||||||
m_state = UnknownSystemComposeDir;
|
|
||||||
qWarning("Qt Warning: Could not find a location of the system's Compose files. "
|
|
||||||
"Consider setting the QTCOMPOSE environment variable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableGenerator::systemComposeDir()
|
|
||||||
{
|
|
||||||
if (m_systemComposeDir.isNull()
|
|
||||||
&& !findSystemComposeDir()) {
|
|
||||||
return QLatin1String("$QTCOMPOSE");
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_systemComposeDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableGenerator::locale() const
|
|
||||||
{
|
|
||||||
char *name = setlocale(LC_CTYPE, (char *)0);
|
|
||||||
return QLatin1String(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TableGenerator::readLocaleMappings(const QByteArray &locale)
|
|
||||||
{
|
|
||||||
QString file;
|
|
||||||
if (locale.isEmpty())
|
|
||||||
return file;
|
|
||||||
|
|
||||||
QFile mappings(systemComposeDir() + QLatin1String("/compose.dir"));
|
|
||||||
if (mappings.open(QIODevice::ReadOnly)) {
|
|
||||||
const int localeNameLength = locale.size();
|
|
||||||
const char * const localeData = locale.constData();
|
|
||||||
|
|
||||||
char l[1024];
|
|
||||||
// formating of compose.dir has some inconsistencies
|
|
||||||
while (!mappings.atEnd()) {
|
|
||||||
int read = mappings.readLine(l, sizeof(l));
|
|
||||||
if (read <= 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
char *line = l;
|
|
||||||
if (*line >= 'a' && *line <= 'z') {
|
|
||||||
// file name
|
|
||||||
while (*line && *line != ':' && *line != ' ' && *line != '\t')
|
|
||||||
++line;
|
|
||||||
if (!*line)
|
|
||||||
continue;
|
|
||||||
const char * const composeFileNameEnd = line;
|
|
||||||
*line = '\0';
|
|
||||||
++line;
|
|
||||||
|
|
||||||
// locale name
|
|
||||||
while (*line && (*line == ' ' || *line == '\t'))
|
|
||||||
++line;
|
|
||||||
const char * const lc = line;
|
|
||||||
while (*line && *line != ' ' && *line != '\t' && *line != '\n')
|
|
||||||
++line;
|
|
||||||
*line = '\0';
|
|
||||||
if (localeNameLength == (line - lc) && !strncasecmp(lc, localeData, line - lc)) {
|
|
||||||
file = QString::fromLocal8Bit(l, composeFileNameEnd - l);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mappings.close();
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray TableGenerator::readLocaleAliases(const QByteArray &locale)
|
|
||||||
{
|
|
||||||
QFile aliases(systemComposeDir() + QLatin1String("/locale.alias"));
|
|
||||||
QByteArray fullLocaleName;
|
|
||||||
if (aliases.open(QIODevice::ReadOnly)) {
|
|
||||||
while (!aliases.atEnd()) {
|
|
||||||
char l[1024];
|
|
||||||
int read = aliases.readLine(l, sizeof(l));
|
|
||||||
char *line = l;
|
|
||||||
if (read && ((*line >= 'a' && *line <= 'z') ||
|
|
||||||
(*line >= 'A' && *line <= 'Z'))) {
|
|
||||||
const char *alias = line;
|
|
||||||
while (*line && *line != ':' && *line != ' ' && *line != '\t')
|
|
||||||
++line;
|
|
||||||
if (!*line)
|
|
||||||
continue;
|
|
||||||
*line = 0;
|
|
||||||
if (locale.size() == (line - alias)
|
|
||||||
&& !strncasecmp(alias, locale.constData(), line - alias)) {
|
|
||||||
// found a match for alias, read the real locale name
|
|
||||||
++line;
|
|
||||||
while (*line && (*line == ' ' || *line == '\t'))
|
|
||||||
++line;
|
|
||||||
const char *fullName = line;
|
|
||||||
while (*line && *line != ' ' && *line != '\t' && *line != '\n')
|
|
||||||
++line;
|
|
||||||
*line = 0;
|
|
||||||
fullLocaleName = fullName;
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
qDebug() << "Alias for: " << alias << "is: " << fullLocaleName;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliases.close();
|
|
||||||
}
|
|
||||||
return fullLocaleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TableGenerator::processFile(const QString &composeFileName)
|
|
||||||
{
|
|
||||||
QFile composeFile(composeFileName);
|
|
||||||
if (composeFile.open(QIODevice::ReadOnly)) {
|
|
||||||
parseComposeFile(&composeFile);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
qWarning() << QString(QLatin1String("Qt Warning: Compose file: \"%1\" can't be found"))
|
|
||||||
.arg(composeFile.fileName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TableGenerator::~TableGenerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<QComposeTableElement> TableGenerator::composeTable() const
|
|
||||||
{
|
|
||||||
return m_composeTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::parseComposeFile(QFile *composeFile)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
qDebug() << "TableGenerator::parseComposeFile: " << composeFile->fileName();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char line[1024];
|
|
||||||
while (!composeFile->atEnd()) {
|
|
||||||
composeFile->readLine(line, sizeof(line));
|
|
||||||
if (*line == '<')
|
|
||||||
parseKeySequence(line);
|
|
||||||
else if (!strncmp(line, "include", 7))
|
|
||||||
parseIncludeInstruction(QString::fromLocal8Bit(line));
|
|
||||||
}
|
|
||||||
|
|
||||||
composeFile->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::parseIncludeInstruction(QString line)
|
|
||||||
{
|
|
||||||
// Parse something that looks like:
|
|
||||||
// include "/usr/share/X11/locale/en_US.UTF-8/Compose"
|
|
||||||
QString quote = QStringLiteral("\"");
|
|
||||||
line.remove(0, line.indexOf(quote) + 1);
|
|
||||||
line.chop(line.length() - line.indexOf(quote));
|
|
||||||
|
|
||||||
// expand substitutions if present
|
|
||||||
line.replace(QLatin1String("%H"), QString(qgetenv("HOME")));
|
|
||||||
line.replace(QLatin1String("%L"), systemComposeDir() + QLatin1Char('/') + composeTableForLocale());
|
|
||||||
line.replace(QLatin1String("%S"), systemComposeDir());
|
|
||||||
|
|
||||||
processFile(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
ushort TableGenerator::keysymToUtf8(quint32 sym)
|
|
||||||
{
|
|
||||||
QByteArray chars;
|
|
||||||
int bytes;
|
|
||||||
chars.resize(8);
|
|
||||||
bytes = xkb_keysym_to_utf8(sym, chars.data(), chars.size());
|
|
||||||
if (bytes == -1)
|
|
||||||
qWarning("TableGenerator::keysymToUtf8 - buffer too small");
|
|
||||||
|
|
||||||
chars.resize(bytes-1);
|
|
||||||
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
QTextCodec *codec = QTextCodec::codecForLocale();
|
|
||||||
qDebug() << QString("keysym - 0x%1 : utf8 - %2").arg(QString::number(sym, 16))
|
|
||||||
.arg(codec->toUnicode(chars));
|
|
||||||
#endif
|
|
||||||
return QString::fromUtf8(chars).at(0).unicode();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int fromBase8(const char *s, const char *end)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
while (*s && s != end) {
|
|
||||||
if (*s < '0' || *s > '7')
|
|
||||||
return 0;
|
|
||||||
result *= 8;
|
|
||||||
result += *s - '0';
|
|
||||||
++s;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int fromBase16(const char *s, const char *end)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
while (*s && s != end) {
|
|
||||||
result *= 16;
|
|
||||||
if (*s >= '0' && *s <= '9')
|
|
||||||
result += *s - '0';
|
|
||||||
else if (*s >= 'a' && *s <= 'f')
|
|
||||||
result += *s - 'a' + 10;
|
|
||||||
else if (*s >= 'A' && *s <= 'F')
|
|
||||||
result += *s - 'A' + 10;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
++s;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::parseKeySequence(char *line)
|
|
||||||
{
|
|
||||||
// we are interested in the lines with the following format:
|
|
||||||
// <Multi_key> <numbersign> <S> : "♬" U266c # BEAMED SIXTEENTH NOTE
|
|
||||||
char *keysEnd = strchr(line, ':');
|
|
||||||
if (!keysEnd)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QComposeTableElement elem;
|
|
||||||
// find the composed value - strings may be direct text encoded in the locale
|
|
||||||
// for which the compose file is to be used, or an escaped octal or hexadecimal
|
|
||||||
// character code. Octal codes are specified as "\123" and hexadecimal codes as "\0x123a".
|
|
||||||
char *composeValue = strchr(keysEnd, '"');
|
|
||||||
if (!composeValue)
|
|
||||||
return;
|
|
||||||
++composeValue;
|
|
||||||
|
|
||||||
char *composeValueEnd = strchr(composeValue, '"');
|
|
||||||
if (!composeValueEnd)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// if composed value is a quotation mark adjust the end pointer
|
|
||||||
if (composeValueEnd[1] == '"')
|
|
||||||
++composeValueEnd;
|
|
||||||
|
|
||||||
if (*composeValue == '\\' && composeValue[1] >= '0' && composeValue[1] <= '9') {
|
|
||||||
// handle octal and hex code values
|
|
||||||
char detectBase = composeValue[2];
|
|
||||||
if (detectBase == 'x') {
|
|
||||||
// hexadecimal character code
|
|
||||||
elem.value = keysymToUtf8(fromBase16(composeValue + 3, composeValueEnd));
|
|
||||||
} else {
|
|
||||||
// octal character code
|
|
||||||
elem.value = keysymToUtf8(fromBase8(composeValue + 1, composeValueEnd));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// handle direct text encoded in the locale
|
|
||||||
if (*composeValue == '\\')
|
|
||||||
++composeValue;
|
|
||||||
elem.value = QString::fromLocal8Bit(composeValue, composeValueEnd - composeValue).at(0).unicode();
|
|
||||||
++composeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
// find the comment
|
|
||||||
elem.comment = QString::fromLocal8Bit(composeValueEnd + 1).trimmed();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// find the key sequence and convert to X11 keysym
|
|
||||||
char *k = line;
|
|
||||||
const char *kend = keysEnd;
|
|
||||||
|
|
||||||
for (int i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
|
||||||
// find the next pair of angle brackets and get the contents within
|
|
||||||
while (k < kend && *k != '<')
|
|
||||||
++k;
|
|
||||||
char *sym = ++k;
|
|
||||||
while (k < kend && *k != '>')
|
|
||||||
++k;
|
|
||||||
*k = '\0';
|
|
||||||
if (k < kend) {
|
|
||||||
elem.keys[i] = xkb_keysym_from_name(sym, (xkb_keysym_flags)0);
|
|
||||||
if (elem.keys[i] == XKB_KEY_NoSymbol) {
|
|
||||||
if (!strcmp(sym, "dead_inverted_breve"))
|
|
||||||
elem.keys[i] = XKB_KEY_dead_invertedbreve;
|
|
||||||
else if (!strcmp(sym, "dead_double_grave"))
|
|
||||||
elem.keys[i] = XKB_KEY_dead_doublegrave;
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
else
|
|
||||||
qWarning() << QString("Qt Warning - invalid keysym: %1").arg(sym);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
elem.keys[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_composeTable.append(elem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::printComposeTable() const
|
|
||||||
{
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
# ifndef QT_NO_DEBUG_STREAM
|
|
||||||
if (m_composeTable.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QDebug ds = qDebug() << "output:\n";
|
|
||||||
ds.nospace();
|
|
||||||
const int tableSize = m_composeTable.size();
|
|
||||||
for (int i = 0; i < tableSize; ++i) {
|
|
||||||
const QComposeTableElement &elem = m_composeTable.at(i);
|
|
||||||
ds << "{ {";
|
|
||||||
for (int j = 0; j < QT_KEYSEQUENCE_MAX_LEN; j++) {
|
|
||||||
ds << hex << showbase << elem.keys[j] << ", ";
|
|
||||||
}
|
|
||||||
ds << "}, " << hex << showbase << elem.value << ", \"\" }, // " << elem.comment << " \n";
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void TableGenerator::orderComposeTable()
|
|
||||||
{
|
|
||||||
// Stable-sorting to ensure that the item that appeared before the other in the
|
|
||||||
// original container will still appear first after the sort. This property is
|
|
||||||
// needed to handle the cases when user re-defines already defined key sequence
|
|
||||||
std::stable_sort(m_composeTable.begin(), m_composeTable.end(), ByKeys());
|
|
||||||
}
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** 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 The Qt Company. For licensing terms
|
|
||||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
||||||
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
||||||
** packaging of this file. Please review the following information to
|
|
||||||
** ensure the GNU Lesser General Public License version 3 requirements
|
|
||||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
||||||
**
|
|
||||||
** GNU General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 2.0 or (at your option) the GNU General
|
|
||||||
** Public license version 3 or any later version approved by the KDE Free
|
|
||||||
** Qt Foundation. The licenses are as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
||||||
** included in the packaging of this file. Please review the following
|
|
||||||
** information to ensure the GNU General Public License requirements will
|
|
||||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
||||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef QTABLEGENERATOR_H
|
|
||||||
#define QTABLEGENERATOR_H
|
|
||||||
|
|
||||||
#include <QtCore/QVector>
|
|
||||||
#include <QtCore/QFile>
|
|
||||||
#include <QtCore/QMap>
|
|
||||||
#include <QtCore/QString>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
static Q_CONSTEXPR int QT_KEYSEQUENCE_MAX_LEN = 6;
|
|
||||||
|
|
||||||
//#define DEBUG_GENERATOR
|
|
||||||
|
|
||||||
/* Whenever QComposeTableElement gets modified supportedCacheVersion
|
|
||||||
from qtablegenerator.cpp must be bumped. */
|
|
||||||
struct QComposeTableElement {
|
|
||||||
uint keys[QT_KEYSEQUENCE_MAX_LEN];
|
|
||||||
uint value;
|
|
||||||
#ifdef DEBUG_GENERATOR
|
|
||||||
QString comment;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef DEBUG_GENERATOR
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
Q_DECLARE_TYPEINFO(QComposeTableElement, Q_PRIMITIVE_TYPE);
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ByKeys
|
|
||||||
{
|
|
||||||
using uint_array = uint[QT_KEYSEQUENCE_MAX_LEN];
|
|
||||||
using result_type = bool;
|
|
||||||
|
|
||||||
bool operator()(const uint_array &lhs, const uint_array &rhs) const Q_DECL_NOTHROW
|
|
||||||
{
|
|
||||||
return std::lexicographical_compare(lhs, lhs + QT_KEYSEQUENCE_MAX_LEN,
|
|
||||||
rhs, rhs + QT_KEYSEQUENCE_MAX_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator()(const uint_array &lhs, const QComposeTableElement &rhs) const Q_DECL_NOTHROW
|
|
||||||
{
|
|
||||||
return operator()(lhs, rhs.keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator()(const QComposeTableElement &lhs, const uint_array &rhs) const Q_DECL_NOTHROW
|
|
||||||
{
|
|
||||||
return operator()(lhs.keys, rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator()(const QComposeTableElement &lhs, const QComposeTableElement &rhs) const Q_DECL_NOTHROW
|
|
||||||
{
|
|
||||||
return operator()(lhs.keys, rhs.keys);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TableGenerator
|
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum TableState
|
|
||||||
{
|
|
||||||
UnsupportedLocale,
|
|
||||||
EmptyTable,
|
|
||||||
UnknownSystemComposeDir,
|
|
||||||
MissingComposeFile,
|
|
||||||
NoErrors
|
|
||||||
};
|
|
||||||
|
|
||||||
TableGenerator();
|
|
||||||
~TableGenerator();
|
|
||||||
|
|
||||||
void parseComposeFile(QFile *composeFile);
|
|
||||||
void printComposeTable() const;
|
|
||||||
void orderComposeTable();
|
|
||||||
|
|
||||||
QVector<QComposeTableElement> composeTable() const;
|
|
||||||
TableState tableState() const { return m_state; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool processFile(const QString &composeFileName);
|
|
||||||
void parseKeySequence(char *line);
|
|
||||||
void parseIncludeInstruction(QString line);
|
|
||||||
|
|
||||||
QString findComposeFile();
|
|
||||||
bool findSystemComposeDir();
|
|
||||||
QString systemComposeDir();
|
|
||||||
QString composeTableForLocale();
|
|
||||||
|
|
||||||
ushort keysymToUtf8(quint32 sym);
|
|
||||||
|
|
||||||
QString readLocaleMappings(const QByteArray &locale);
|
|
||||||
QByteArray readLocaleAliases(const QByteArray &locale);
|
|
||||||
void initPossibleLocations();
|
|
||||||
bool cleanState() const { return m_state == NoErrors; }
|
|
||||||
QString locale() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QVector<QComposeTableElement> m_composeTable;
|
|
||||||
TableState m_state;
|
|
||||||
QString m_systemComposeDir;
|
|
||||||
QList<QString> m_possibleLocations;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // QTABLEGENERATOR_H
|
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the plugins of the Qt Toolkit.
|
** This file is part of the plugins of the Qt Toolkit.
|
||||||
@ -36,131 +36,100 @@
|
|||||||
** $QT_END_LICENSE$
|
** $QT_END_LICENSE$
|
||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#include "qcomposeplatforminputcontext.h"
|
#include "qcomposeplatforminputcontext.h"
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtGui/QKeyEvent>
|
#include <QtGui/QKeyEvent>
|
||||||
#include <QtCore/QDebug>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <locale.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
//#define DEBUG_COMPOSING
|
Q_LOGGING_CATEGORY(lcXkbCompose, "qt.xkb.compose")
|
||||||
|
|
||||||
static const int ignoreKeys[] = {
|
|
||||||
Qt::Key_Shift,
|
|
||||||
Qt::Key_Control,
|
|
||||||
Qt::Key_Meta,
|
|
||||||
Qt::Key_Alt,
|
|
||||||
Qt::Key_CapsLock,
|
|
||||||
Qt::Key_Super_L,
|
|
||||||
Qt::Key_Super_R,
|
|
||||||
Qt::Key_Hyper_L,
|
|
||||||
Qt::Key_Hyper_R,
|
|
||||||
Qt::Key_Mode_switch
|
|
||||||
};
|
|
||||||
|
|
||||||
static const int composingKeys[] = {
|
|
||||||
Qt::Key_Multi_key,
|
|
||||||
Qt::Key_Dead_Grave,
|
|
||||||
Qt::Key_Dead_Acute,
|
|
||||||
Qt::Key_Dead_Circumflex,
|
|
||||||
Qt::Key_Dead_Tilde,
|
|
||||||
Qt::Key_Dead_Macron,
|
|
||||||
Qt::Key_Dead_Breve,
|
|
||||||
Qt::Key_Dead_Abovedot,
|
|
||||||
Qt::Key_Dead_Diaeresis,
|
|
||||||
Qt::Key_Dead_Abovering,
|
|
||||||
Qt::Key_Dead_Doubleacute,
|
|
||||||
Qt::Key_Dead_Caron,
|
|
||||||
Qt::Key_Dead_Cedilla,
|
|
||||||
Qt::Key_Dead_Ogonek,
|
|
||||||
Qt::Key_Dead_Iota,
|
|
||||||
Qt::Key_Dead_Voiced_Sound,
|
|
||||||
Qt::Key_Dead_Semivoiced_Sound,
|
|
||||||
Qt::Key_Dead_Belowdot,
|
|
||||||
Qt::Key_Dead_Hook,
|
|
||||||
Qt::Key_Dead_Horn,
|
|
||||||
Qt::Key_Dead_Stroke,
|
|
||||||
Qt::Key_Dead_Abovecomma,
|
|
||||||
Qt::Key_Dead_Abovereversedcomma,
|
|
||||||
Qt::Key_Dead_Doublegrave,
|
|
||||||
Qt::Key_Dead_Belowring,
|
|
||||||
Qt::Key_Dead_Belowmacron,
|
|
||||||
Qt::Key_Dead_Belowcircumflex,
|
|
||||||
Qt::Key_Dead_Belowtilde,
|
|
||||||
Qt::Key_Dead_Belowbreve,
|
|
||||||
Qt::Key_Dead_Belowdiaeresis,
|
|
||||||
Qt::Key_Dead_Invertedbreve,
|
|
||||||
Qt::Key_Dead_Belowcomma,
|
|
||||||
Qt::Key_Dead_Currency,
|
|
||||||
Qt::Key_Dead_a,
|
|
||||||
Qt::Key_Dead_A,
|
|
||||||
Qt::Key_Dead_e,
|
|
||||||
Qt::Key_Dead_E,
|
|
||||||
Qt::Key_Dead_i,
|
|
||||||
Qt::Key_Dead_I,
|
|
||||||
Qt::Key_Dead_o,
|
|
||||||
Qt::Key_Dead_O,
|
|
||||||
Qt::Key_Dead_u,
|
|
||||||
Qt::Key_Dead_U,
|
|
||||||
Qt::Key_Dead_Small_Schwa,
|
|
||||||
Qt::Key_Dead_Capital_Schwa,
|
|
||||||
Qt::Key_Dead_Greek,
|
|
||||||
Qt::Key_Dead_Lowline,
|
|
||||||
Qt::Key_Dead_Aboveverticalline,
|
|
||||||
Qt::Key_Dead_Belowverticalline,
|
|
||||||
Qt::Key_Dead_Longsolidusoverlay
|
|
||||||
};
|
|
||||||
|
|
||||||
QComposeInputContext::QComposeInputContext()
|
QComposeInputContext::QComposeInputContext()
|
||||||
: m_tableState(TableGenerator::EmptyTable)
|
|
||||||
, m_compositionTableInitialized(false)
|
|
||||||
{
|
{
|
||||||
clearComposeBuffer();
|
setObjectName(QStringLiteral("QComposeInputContext"));
|
||||||
|
qCDebug(lcXkbCompose, "using xkb compose input context");
|
||||||
|
}
|
||||||
|
|
||||||
|
QComposeInputContext::~QComposeInputContext()
|
||||||
|
{
|
||||||
|
xkb_compose_state_unref(m_composeState);
|
||||||
|
xkb_compose_table_unref(m_composeTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QComposeInputContext::ensureInitialized()
|
||||||
|
{
|
||||||
|
if (m_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_XkbContext) {
|
||||||
|
qCWarning(lcXkbCompose) << "error: xkb context has not been set on" << metaObject()->className();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_initialized = true;
|
||||||
|
const char *const locale = setlocale(LC_CTYPE, "");
|
||||||
|
qCDebug(lcXkbCompose) << "detected locale (LC_CTYPE):" << locale;
|
||||||
|
|
||||||
|
m_composeTable = xkb_compose_table_new_from_locale(m_XkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||||
|
if (m_composeTable)
|
||||||
|
m_composeState = xkb_compose_state_new(m_composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||||
|
|
||||||
|
if (!m_composeTable) {
|
||||||
|
qCWarning(lcXkbCompose, "failed to create compose table");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_composeState) {
|
||||||
|
qCWarning(lcXkbCompose, "failed to create compose state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QComposeInputContext::filterEvent(const QEvent *event)
|
bool QComposeInputContext::filterEvent(const QEvent *event)
|
||||||
{
|
{
|
||||||
const QKeyEvent *keyEvent = (const QKeyEvent *)event;
|
auto keyEvent = static_cast<const QKeyEvent *>(event);
|
||||||
// should pass only the key presses
|
if (keyEvent->type() != QEvent::KeyPress)
|
||||||
if (keyEvent->type() != QEvent::KeyPress) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there were errors when generating the compose table input
|
|
||||||
// context should not try to filter anything, simply return false
|
|
||||||
if (m_compositionTableInitialized && (m_tableState & TableGenerator::NoErrors) != TableGenerator::NoErrors)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int keyval = keyEvent->key();
|
if (!inputMethodAccepted())
|
||||||
int keysym = 0;
|
|
||||||
|
|
||||||
if (ignoreKey(keyval))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!composeKey(keyval) && keyEvent->text().isEmpty())
|
// lazy initialization - we don't want to do this on an app startup
|
||||||
|
ensureInitialized();
|
||||||
|
|
||||||
|
if (!m_composeTable || !m_composeState)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
keysym = keyEvent->nativeVirtualKey();
|
xkb_compose_state_feed(m_composeState, keyEvent->nativeVirtualKey());
|
||||||
|
|
||||||
int nCompose = 0;
|
switch (xkb_compose_state_get_status(m_composeState)) {
|
||||||
while (nCompose < QT_KEYSEQUENCE_MAX_LEN && m_composeBuffer[nCompose] != 0)
|
case XKB_COMPOSE_COMPOSING:
|
||||||
nCompose++;
|
|
||||||
|
|
||||||
if (nCompose == QT_KEYSEQUENCE_MAX_LEN) {
|
|
||||||
reset();
|
|
||||||
nCompose = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_composeBuffer[nCompose] = keysym;
|
|
||||||
// check sequence
|
|
||||||
if (checkComposeTable())
|
|
||||||
return true;
|
return true;
|
||||||
|
case XKB_COMPOSE_CANCELLED:
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
case XKB_COMPOSE_COMPOSED:
|
||||||
|
{
|
||||||
|
const int size = xkb_compose_state_get_utf8(m_composeState, nullptr, 0);
|
||||||
|
QVarLengthArray<char, 32> buffer(size + 1);
|
||||||
|
xkb_compose_state_get_utf8(m_composeState, buffer.data(), buffer.size());
|
||||||
|
QString composedText = QString::fromUtf8(buffer.constData());
|
||||||
|
|
||||||
return false;
|
QInputMethodEvent event;
|
||||||
|
event.setCommitString(composedText);
|
||||||
|
QCoreApplication::sendEvent(m_focusObject, &event);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case XKB_COMPOSE_NOTHING:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QComposeInputContext::isValid() const
|
bool QComposeInputContext::isValid() const
|
||||||
@ -175,7 +144,8 @@ void QComposeInputContext::setFocusObject(QObject *object)
|
|||||||
|
|
||||||
void QComposeInputContext::reset()
|
void QComposeInputContext::reset()
|
||||||
{
|
{
|
||||||
clearComposeBuffer();
|
if (m_composeState)
|
||||||
|
xkb_compose_state_reset(m_composeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QComposeInputContext::update(Qt::InputMethodQueries q)
|
void QComposeInputContext::update(Qt::InputMethodQueries q)
|
||||||
@ -183,125 +153,4 @@ void QComposeInputContext::update(Qt::InputMethodQueries q)
|
|||||||
QPlatformInputContext::update(q);
|
QPlatformInputContext::update(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isDuplicate(const QComposeTableElement &lhs, const QComposeTableElement &rhs)
|
|
||||||
{
|
|
||||||
return std::equal(lhs.keys, lhs.keys + QT_KEYSEQUENCE_MAX_LEN,
|
|
||||||
QT_MAKE_CHECKED_ARRAY_ITERATOR(rhs.keys, QT_KEYSEQUENCE_MAX_LEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QComposeInputContext::checkComposeTable()
|
|
||||||
{
|
|
||||||
if (!m_compositionTableInitialized) {
|
|
||||||
TableGenerator reader;
|
|
||||||
m_tableState = reader.tableState();
|
|
||||||
|
|
||||||
m_compositionTableInitialized = true;
|
|
||||||
if ((m_tableState & TableGenerator::NoErrors) == TableGenerator::NoErrors) {
|
|
||||||
m_composeTable = reader.composeTable();
|
|
||||||
} else {
|
|
||||||
#ifdef DEBUG_COMPOSING
|
|
||||||
qDebug( "### FAILED_PARSING ###" );
|
|
||||||
#endif
|
|
||||||
// if we have errors, don' try to look things up anyways.
|
|
||||||
reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Q_ASSERT(!m_composeTable.isEmpty());
|
|
||||||
QVector<QComposeTableElement>::const_iterator it =
|
|
||||||
std::lower_bound(m_composeTable.constBegin(), m_composeTable.constEnd(), m_composeBuffer, ByKeys());
|
|
||||||
|
|
||||||
// prevent dereferencing an 'end' iterator, which would result in a crash
|
|
||||||
if (it == m_composeTable.constEnd())
|
|
||||||
it -= 1;
|
|
||||||
|
|
||||||
QComposeTableElement elem = *it;
|
|
||||||
// would be nicer if qLowerBound had API that tells if the item was actually found
|
|
||||||
if (m_composeBuffer[0] != elem.keys[0]) {
|
|
||||||
#ifdef DEBUG_COMPOSING
|
|
||||||
qDebug( "### no match ###" );
|
|
||||||
#endif
|
|
||||||
reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// check if compose buffer is matched
|
|
||||||
for (int i=0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
|
||||||
|
|
||||||
// check if partial match
|
|
||||||
if (m_composeBuffer[i] == 0 && elem.keys[i]) {
|
|
||||||
#ifdef DEBUG_COMPOSING
|
|
||||||
qDebug("### partial match ###");
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_composeBuffer[i] != elem.keys[i]) {
|
|
||||||
#ifdef DEBUG_COMPOSING
|
|
||||||
qDebug("### different entry ###");
|
|
||||||
#endif
|
|
||||||
reset();
|
|
||||||
return i != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_COMPOSING
|
|
||||||
qDebug("### match exactly ###");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// check if the key sequence is overwriten - see the comment in
|
|
||||||
// TableGenerator::orderComposeTable()
|
|
||||||
int next = 1;
|
|
||||||
do {
|
|
||||||
// if we are at the end of the table, then we have nothing to do here
|
|
||||||
if (it + next != m_composeTable.constEnd()) {
|
|
||||||
QComposeTableElement nextElem = *(it + next);
|
|
||||||
if (isDuplicate(elem, nextElem)) {
|
|
||||||
elem = nextElem;
|
|
||||||
next++;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} while (true);
|
|
||||||
|
|
||||||
commitText(elem.value);
|
|
||||||
reset();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QComposeInputContext::commitText(uint character) const
|
|
||||||
{
|
|
||||||
QInputMethodEvent event;
|
|
||||||
event.setCommitString(QChar(character));
|
|
||||||
QCoreApplication::sendEvent(m_focusObject, &event);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QComposeInputContext::ignoreKey(int keyval) const
|
|
||||||
{
|
|
||||||
for (uint i = 0; i < (sizeof(ignoreKeys) / sizeof(ignoreKeys[0])); i++)
|
|
||||||
if (keyval == ignoreKeys[i])
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QComposeInputContext::composeKey(int keyval) const
|
|
||||||
{
|
|
||||||
for (uint i = 0; i < (sizeof(composingKeys) / sizeof(composingKeys[0])); i++)
|
|
||||||
if (keyval == composingKeys[i])
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QComposeInputContext::clearComposeBuffer()
|
|
||||||
{
|
|
||||||
for (uint i=0; i < (sizeof(m_composeBuffer) / sizeof(int)); i++)
|
|
||||||
m_composeBuffer[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QComposeInputContext::~QComposeInputContext() {}
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the plugins of the Qt Toolkit.
|
** This file is part of the plugins of the Qt Toolkit.
|
||||||
@ -36,24 +36,24 @@
|
|||||||
** $QT_END_LICENSE$
|
** $QT_END_LICENSE$
|
||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#ifndef QCOMPOSEPLATFORMINPUTCONTEXT_H
|
#ifndef QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||||
#define QCOMPOSEPLATFORMINPUTCONTEXT_H
|
#define QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||||
|
|
||||||
|
#include <QtCore/QLoggingCategory>
|
||||||
|
|
||||||
#include <qpa/qplatforminputcontext.h>
|
#include <qpa/qplatforminputcontext.h>
|
||||||
|
|
||||||
#include <QtCore/QList>
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
#include "generator/qtablegenerator.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(lcXkbCompose)
|
||||||
|
|
||||||
class QEvent;
|
class QEvent;
|
||||||
|
|
||||||
class QComposeInputContext : public QPlatformInputContext
|
class QComposeInputContext : public QPlatformInputContext
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QComposeInputContext();
|
QComposeInputContext();
|
||||||
~QComposeInputContext();
|
~QComposeInputContext();
|
||||||
@ -62,21 +62,22 @@ public:
|
|||||||
void setFocusObject(QObject *object) override;
|
void setFocusObject(QObject *object) override;
|
||||||
void reset() override;
|
void reset() override;
|
||||||
void update(Qt::InputMethodQueries) override;
|
void update(Qt::InputMethodQueries) override;
|
||||||
|
|
||||||
bool filterEvent(const QEvent *event) override;
|
bool filterEvent(const QEvent *event) override;
|
||||||
|
|
||||||
|
// This invokable is called from QXkbCommon::setXkbContext().
|
||||||
|
Q_INVOKABLE void setXkbContext(struct xkb_context *context) { m_XkbContext = context; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void clearComposeBuffer();
|
void ensureInitialized();
|
||||||
bool ignoreKey(int keyval) const;
|
|
||||||
bool composeKey(int keyval) const;
|
|
||||||
bool checkComposeTable();
|
|
||||||
void commitText(uint character) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QObject *m_focusObject;
|
bool m_initialized = false;
|
||||||
QVector<QComposeTableElement> m_composeTable;
|
xkb_context *m_context = nullptr;
|
||||||
uint m_composeBuffer[QT_KEYSEQUENCE_MAX_LEN];
|
xkb_compose_table *m_composeTable = nullptr;
|
||||||
TableGenerator::TableState m_tableState;
|
xkb_compose_state *m_composeState = nullptr;
|
||||||
bool m_compositionTableInitialized;
|
QObject *m_focusObject = nullptr;
|
||||||
|
struct xkb_context *m_XkbContext = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the plugins of the Qt Toolkit.
|
** This file is part of the plugins of the Qt Toolkit.
|
||||||
@ -61,7 +61,7 @@ QComposeInputContext *QComposePlatformInputContextPlugin::create(const QString &
|
|||||||
if (system.compare(system, QLatin1String("compose"), Qt::CaseInsensitive) == 0
|
if (system.compare(system, QLatin1String("compose"), Qt::CaseInsensitive) == 0
|
||||||
|| system.compare(system, QLatin1String("xim"), Qt::CaseInsensitive) == 0)
|
|| system.compare(system, QLatin1String("xim"), Qt::CaseInsensitive) == 0)
|
||||||
return new QComposeInputContext;
|
return new QComposeInputContext;
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -357,6 +357,8 @@ void QXcbIntegration::initialize()
|
|||||||
m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
|
m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
|
||||||
if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
|
if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
|
||||||
m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
|
m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
|
||||||
|
|
||||||
|
defaultConnection()->keyboard()->initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QXcbIntegration::moveToScreen(QWindow *window, int screen)
|
void QXcbIntegration::moveToScreen(QWindow *window, int screen)
|
||||||
|
@ -565,6 +565,12 @@ QXcbKeyboard::~QXcbKeyboard()
|
|||||||
xcb_key_symbols_free(m_key_symbols);
|
xcb_key_symbols_free(m_key_symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QXcbKeyboard::initialize()
|
||||||
|
{
|
||||||
|
auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
|
||||||
|
QXkbCommon::setXkbContext(inputContext, m_xkbContext.get());
|
||||||
|
}
|
||||||
|
|
||||||
void QXcbKeyboard::selectEvents()
|
void QXcbKeyboard::selectEvents()
|
||||||
{
|
{
|
||||||
#if QT_CONFIG(xkb)
|
#if QT_CONFIG(xkb)
|
||||||
|
@ -63,6 +63,7 @@ public:
|
|||||||
|
|
||||||
~QXcbKeyboard();
|
~QXcbKeyboard();
|
||||||
|
|
||||||
|
void initialize();
|
||||||
void selectEvents();
|
void selectEvents();
|
||||||
|
|
||||||
void handleKeyPressEvent(const xcb_key_press_event_t *event);
|
void handleKeyPressEvent(const xcb_key_press_event_t *event);
|
||||||
|
@ -67,3 +67,8 @@ winrt|!qtHaveModule(gui)|!qtConfig(accessibility): SUBDIRS -= qaccessibility
|
|||||||
|
|
||||||
android: SUBDIRS += \
|
android: SUBDIRS += \
|
||||||
android
|
android
|
||||||
|
|
||||||
|
qtConfig(xkbcommon): {
|
||||||
|
SUBDIRS += \
|
||||||
|
xkbkeyboard
|
||||||
|
}
|
||||||
|
60
tests/auto/other/xkbkeyboard/tst_xkbkeyboard.cpp
Normal file
60
tests/auto/other/xkbkeyboard/tst_xkbkeyboard.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the test suite of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||||
|
** 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 The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <QtCore>
|
||||||
|
#include <QtGui>
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
#include <qpa/qplatforminputcontextfactory_p.h>
|
||||||
|
#include <qpa/qplatforminputcontext.h>
|
||||||
|
|
||||||
|
class tst_XkbKeyboard : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private slots:
|
||||||
|
void verifyComposeInputContextInterface();
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_XkbKeyboard::verifyComposeInputContextInterface()
|
||||||
|
{
|
||||||
|
QPlatformInputContext *inputContext = QPlatformInputContextFactory::create(QStringLiteral("compose"));
|
||||||
|
QVERIFY(inputContext);
|
||||||
|
|
||||||
|
const char *const inputContextClassName = "QComposeInputContext";
|
||||||
|
const char *const normalizedSignature = "setXkbContext(xkb_context*)";
|
||||||
|
|
||||||
|
QVERIFY(inputContext->objectName() == QLatin1String(inputContextClassName));
|
||||||
|
|
||||||
|
int methodIndex = inputContext->metaObject()->indexOfMethod(normalizedSignature);
|
||||||
|
QMetaMethod method = inputContext->metaObject()->method(methodIndex);
|
||||||
|
Q_ASSERT(method.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_XkbKeyboard)
|
||||||
|
#include "tst_xkbkeyboard.moc"
|
||||||
|
|
7
tests/auto/other/xkbkeyboard/xkbkeyboard.pro
Normal file
7
tests/auto/other/xkbkeyboard/xkbkeyboard.pro
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CONFIG += testcase
|
||||||
|
TARGET = tst_xkbkeyboard
|
||||||
|
|
||||||
|
SOURCES += tst_xkbkeyboard.cpp
|
||||||
|
|
||||||
|
QT = core-private gui-private testlib
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user