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 <QtCore/QMetaMethod>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
#include <qpa/qplatformintegration.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -794,4 +799,30 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco
|
||||
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
|
||||
|
@ -64,7 +64,9 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon)
|
||||
|
||||
class QEvent;
|
||||
class QKeyEvent;
|
||||
class QPlatformInputContext;
|
||||
|
||||
class QXkbCommon
|
||||
{
|
||||
@ -99,6 +101,8 @@ public:
|
||||
return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9;
|
||||
}
|
||||
|
||||
static void setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context);
|
||||
|
||||
struct XKBStateDeleter {
|
||||
void operator()(struct xkb_state *state) const { return xkb_state_unref(state); }
|
||||
};
|
||||
|
@ -3,18 +3,14 @@ TARGET = composeplatforminputcontextplugin
|
||||
QT += core-private gui-private
|
||||
|
||||
SOURCES += $$PWD/qcomposeplatforminputcontextmain.cpp \
|
||||
$$PWD/qcomposeplatforminputcontext.cpp \
|
||||
$$PWD/generator/qtablegenerator.cpp \
|
||||
$$PWD/qcomposeplatforminputcontext.cpp
|
||||
|
||||
HEADERS += $$PWD/qcomposeplatforminputcontext.h \
|
||||
$$PWD/generator/qtablegenerator.h \
|
||||
HEADERS += $$PWD/qcomposeplatforminputcontext.h
|
||||
|
||||
QMAKE_USE_PRIVATE += xkbcommon
|
||||
|
||||
include($$OUT_PWD/../../../gui/qtgui-config.pri)
|
||||
|
||||
DEFINES += X11_PREFIX='\\"$$QMAKE_X11_PREFIX\\"'
|
||||
|
||||
OTHER_FILES += $$PWD/compose.json
|
||||
|
||||
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/
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
@ -36,131 +36,100 @@
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qcomposeplatforminputcontext.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
//#define DEBUG_COMPOSING
|
||||
|
||||
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
|
||||
};
|
||||
Q_LOGGING_CATEGORY(lcXkbCompose, "qt.xkb.compose")
|
||||
|
||||
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)
|
||||
{
|
||||
const QKeyEvent *keyEvent = (const QKeyEvent *)event;
|
||||
// should pass only the key presses
|
||||
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)
|
||||
auto keyEvent = static_cast<const QKeyEvent *>(event);
|
||||
if (keyEvent->type() != QEvent::KeyPress)
|
||||
return false;
|
||||
|
||||
int keyval = keyEvent->key();
|
||||
int keysym = 0;
|
||||
|
||||
if (ignoreKey(keyval))
|
||||
if (!inputMethodAccepted())
|
||||
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;
|
||||
|
||||
keysym = keyEvent->nativeVirtualKey();
|
||||
xkb_compose_state_feed(m_composeState, keyEvent->nativeVirtualKey());
|
||||
|
||||
int nCompose = 0;
|
||||
while (nCompose < QT_KEYSEQUENCE_MAX_LEN && m_composeBuffer[nCompose] != 0)
|
||||
nCompose++;
|
||||
|
||||
if (nCompose == QT_KEYSEQUENCE_MAX_LEN) {
|
||||
reset();
|
||||
nCompose = 0;
|
||||
}
|
||||
|
||||
m_composeBuffer[nCompose] = keysym;
|
||||
// check sequence
|
||||
if (checkComposeTable())
|
||||
switch (xkb_compose_state_get_status(m_composeState)) {
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
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
|
||||
@ -175,7 +144,8 @@ void QComposeInputContext::setFocusObject(QObject *object)
|
||||
|
||||
void QComposeInputContext::reset()
|
||||
{
|
||||
clearComposeBuffer();
|
||||
if (m_composeState)
|
||||
xkb_compose_state_reset(m_composeState);
|
||||
}
|
||||
|
||||
void QComposeInputContext::update(Qt::InputMethodQueries q)
|
||||
@ -183,125 +153,4 @@ void QComposeInputContext::update(Qt::InputMethodQueries 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
@ -36,24 +36,24 @@
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||
#define QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
|
||||
#include <QtCore/QList>
|
||||
|
||||
#include "generator/qtablegenerator.h"
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcXkbCompose)
|
||||
|
||||
class QEvent;
|
||||
|
||||
class QComposeInputContext : public QPlatformInputContext
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QComposeInputContext();
|
||||
~QComposeInputContext();
|
||||
@ -62,21 +62,22 @@ public:
|
||||
void setFocusObject(QObject *object) override;
|
||||
void reset() override;
|
||||
void update(Qt::InputMethodQueries) 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:
|
||||
void clearComposeBuffer();
|
||||
bool ignoreKey(int keyval) const;
|
||||
bool composeKey(int keyval) const;
|
||||
bool checkComposeTable();
|
||||
void commitText(uint character) const;
|
||||
void ensureInitialized();
|
||||
|
||||
private:
|
||||
QObject *m_focusObject;
|
||||
QVector<QComposeTableElement> m_composeTable;
|
||||
uint m_composeBuffer[QT_KEYSEQUENCE_MAX_LEN];
|
||||
TableGenerator::TableState m_tableState;
|
||||
bool m_compositionTableInitialized;
|
||||
bool m_initialized = false;
|
||||
xkb_context *m_context = nullptr;
|
||||
xkb_compose_table *m_composeTable = nullptr;
|
||||
xkb_compose_state *m_composeState = nullptr;
|
||||
QObject *m_focusObject = nullptr;
|
||||
struct xkb_context *m_XkbContext = nullptr;
|
||||
};
|
||||
|
||||
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/
|
||||
**
|
||||
** 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
|
||||
|| system.compare(system, QLatin1String("xim"), Qt::CaseInsensitive) == 0)
|
||||
return new QComposeInputContext;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -357,6 +357,8 @@ void QXcbIntegration::initialize()
|
||||
m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
|
||||
if (!m_inputContext && icStr != defaultInputContext && icStr != QLatin1String("none"))
|
||||
m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
|
||||
|
||||
defaultConnection()->keyboard()->initialize();
|
||||
}
|
||||
|
||||
void QXcbIntegration::moveToScreen(QWindow *window, int screen)
|
||||
|
@ -565,6 +565,12 @@ QXcbKeyboard::~QXcbKeyboard()
|
||||
xcb_key_symbols_free(m_key_symbols);
|
||||
}
|
||||
|
||||
void QXcbKeyboard::initialize()
|
||||
{
|
||||
auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
|
||||
QXkbCommon::setXkbContext(inputContext, m_xkbContext.get());
|
||||
}
|
||||
|
||||
void QXcbKeyboard::selectEvents()
|
||||
{
|
||||
#if QT_CONFIG(xkb)
|
||||
|
@ -63,6 +63,7 @@ public:
|
||||
|
||||
~QXcbKeyboard();
|
||||
|
||||
void initialize();
|
||||
void selectEvents();
|
||||
|
||||
void handleKeyPressEvent(const xcb_key_press_event_t *event);
|
||||
|
@ -67,3 +67,8 @@ winrt|!qtHaveModule(gui)|!qtConfig(accessibility): SUBDIRS -= qaccessibility
|
||||
|
||||
android: SUBDIRS += \
|
||||
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…
Reference in New Issue
Block a user