Introducing QComposeInputContext
When switching from Xlib to xcb platform plugin it was agreed that XIM is deprecated. Users should be using QT_IM_MODULE to load input context plugin for a more advance input method framework support. The proposed solution is to parse the compose file directly from Qt. This approach removes the overhead of communication protocols used in Xlib and/or IBUS. TableGenerator class follows [1]. The compose file is searched for in the following order: 1) If the environment variable $XCOMPOSEFILE is set, its value is used as the name of the Compose file. 2) If the user’s home directory has a file named .XCompose, it is used as the Compose file. 3) The system provided compose file is used by mapping the locale to a compose file from the list in /usr/share/X11/locale/compose.dir. Regarding step 3 - TableGenerator searches in hard-coded locations for system-provided compose files. Here I have introcuded a new environment variable QTCOMPOSE which can be used to prepend an extra location to be searched. [1] http://www.x.org/archive/X11R7.7/doc/man/man5/Compose.5.xhtml Task-number: QTBUG-28183 Change-Id: I76dcfd454f3acc23db98192a3673c1ab2af4425f Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
This commit is contained in:
parent
fb9cff6e88
commit
24c10b0b8d
@ -82,8 +82,8 @@ QPlatformInputContext *QPlatformInputContextFactory::create()
|
||||
|
||||
QString icString = QString::fromLatin1(qgetenv("QT_IM_MODULE"));
|
||||
|
||||
if (icString == QStringLiteral("none"))
|
||||
return 0;
|
||||
if (icString == QLatin1String("none"))
|
||||
icString = QStringLiteral("compose");
|
||||
|
||||
ic = create(icString);
|
||||
if (ic && ic->isValid())
|
||||
|
3
src/plugins/platforminputcontexts/compose/compose.json
Normal file
3
src/plugins/platforminputcontexts/compose/compose.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": [ "compose" ]
|
||||
}
|
20
src/plugins/platforminputcontexts/compose/compose.pro
Normal file
20
src/plugins/platforminputcontexts/compose/compose.pro
Normal file
@ -0,0 +1,20 @@
|
||||
TARGET = composeplatforminputcontextplugin
|
||||
|
||||
PLUGIN_TYPE = platforminputcontexts
|
||||
PLUGIN_CLASS_NAME = QComposePlatformInputContextPlugin
|
||||
load(qt_plugin)
|
||||
|
||||
QT += gui-private
|
||||
|
||||
LIBS += $$QMAKE_LIBS_XKBCOMMON
|
||||
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XKBCOMMON
|
||||
|
||||
SOURCES += $$PWD/main.cpp \
|
||||
$$PWD/qcomposeplatforminputcontext.cpp \
|
||||
$$PWD/generator/qtablegenerator.cpp \
|
||||
|
||||
HEADERS += $$PWD/qcomposeplatforminputcontext.h \
|
||||
$$PWD/generator/qtablegenerator.h \
|
||||
$$PWD/xkbcommon_workaround.h \
|
||||
|
||||
OTHER_FILES += $$PWD/compose.json
|
@ -0,0 +1,402 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qtablegenerator.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QTextCodec>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon_workaround.h>
|
||||
|
||||
#include <X11/keysym.h>
|
||||
|
||||
//#define DEBUG_GENERATOR
|
||||
|
||||
TableGenerator::TableGenerator() : m_state(NoErrors),
|
||||
m_systemComposeDir(QString())
|
||||
{
|
||||
initPossibleLocations();
|
||||
findComposeFile();
|
||||
orderComposeTable();
|
||||
#ifdef DEBUG_GENERATOR
|
||||
printComposeTable();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TableGenerator::initPossibleLocations()
|
||||
{
|
||||
// AFAICT there is no way to know the exact location
|
||||
// of the compose files. It depends on how Xlib was configured
|
||||
// on a specific platform. During the "./configure" process
|
||||
// xlib generates a config.h file which contains a bunch of defines,
|
||||
// including XLOCALEDIR which points to the location of the compose file dir.
|
||||
// To add an extra system path use the QTCOMPOSE environment variable
|
||||
if (qEnvironmentVariableIsSet("QTCOMPOSE")) {
|
||||
m_possibleLocations.append(QString(qgetenv("QTCOMPOSE")));
|
||||
}
|
||||
m_possibleLocations.append(QStringLiteral("/usr/share/X11/locale"));
|
||||
m_possibleLocations.append(QStringLiteral("/usr/lib/X11/locale"));
|
||||
}
|
||||
|
||||
void TableGenerator::findComposeFile()
|
||||
{
|
||||
bool found = false;
|
||||
// check if XCOMPOSEFILE points to a Compose file
|
||||
if (qEnvironmentVariableIsSet("XCOMPOSEFILE")) {
|
||||
QString composeFile(qgetenv("XCOMPOSEFILE"));
|
||||
if (composeFile.endsWith(QLatin1String("Compose")))
|
||||
found = processFile(composeFile);
|
||||
else
|
||||
qWarning("Qt Warning: XCOMPOSEFILE doesn't point to a valid Compose file");
|
||||
#ifdef DEBUG_GENERATOR
|
||||
if (found)
|
||||
qDebug() << "Using Compose file from: " << composeFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
// check if user’s home directory has a file named .XCompose
|
||||
if (!found && cleanState()) {
|
||||
QString composeFile = qgetenv("HOME") + QStringLiteral("/.XCompose");
|
||||
if (QFile(composeFile).exists())
|
||||
found = processFile(composeFile);
|
||||
#ifdef DEBUG_GENERATOR
|
||||
if (found)
|
||||
qDebug() << "Using Compose file from: " << composeFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
// check for the system provided compose files
|
||||
if (!found && cleanState()) {
|
||||
readLocaleMappings();
|
||||
|
||||
if (cleanState()) {
|
||||
|
||||
QString table = m_localeToTable.value(locale().toUpper());
|
||||
if (table.isEmpty())
|
||||
// no table mappings for the system's locale in the compose.dir
|
||||
m_state = UnsupportedLocale;
|
||||
else
|
||||
found = processFile(systemComposeDir() + QLatin1String("/") + table);
|
||||
#ifdef DEBUG_GENERATOR
|
||||
if (found)
|
||||
qDebug() << "Using Compose file from: " <<
|
||||
systemComposeDir() + QLatin1String("/") + table;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (found && m_composeTable.isEmpty())
|
||||
m_state = EmptyTable;
|
||||
|
||||
if (!found)
|
||||
m_state = MissingComposeFile;
|
||||
}
|
||||
|
||||
bool TableGenerator::findSystemComposeDir()
|
||||
{
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_possibleLocations.size(); ++i) {
|
||||
QString path = m_possibleLocations.at(i);
|
||||
if (QFile(path + QLatin1String("/compose.dir")).exists()) {
|
||||
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);
|
||||
}
|
||||
|
||||
void TableGenerator::readLocaleMappings()
|
||||
{
|
||||
QFile mappings(systemComposeDir() + QLatin1String("/compose.dir"));
|
||||
if (mappings.exists()) {
|
||||
mappings.open(QIODevice::ReadOnly);
|
||||
QTextStream in(&mappings);
|
||||
// formating of compose.dir has some inconsistencies
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine();
|
||||
if (!line.startsWith("#") && line.size() != 0 &&
|
||||
line.at(0).isLower()) {
|
||||
|
||||
QStringList pair = line.split(QRegExp(QLatin1String("\\s+")));
|
||||
QString table = pair.at(0);
|
||||
if (table.endsWith(QLatin1String(":")))
|
||||
table.remove(table.size() - 1, 1);
|
||||
|
||||
m_localeToTable.insert(pair.at(1).toUpper(), table);
|
||||
}
|
||||
}
|
||||
mappings.close();
|
||||
}
|
||||
}
|
||||
|
||||
bool TableGenerator::processFile(QString composeFileName)
|
||||
{
|
||||
QFile composeFile(composeFileName);
|
||||
if (composeFile.exists()) {
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
||||
QList<QComposeTableElement> TableGenerator::composeTable() const
|
||||
{
|
||||
return m_composeTable;
|
||||
}
|
||||
|
||||
void TableGenerator::parseComposeFile(QFile *composeFile)
|
||||
{
|
||||
#ifdef DEBUG_GENERATOR
|
||||
qDebug() << "TableGenerator::parseComposeFile: " << composeFile->fileName();
|
||||
#endif
|
||||
QTextStream in(composeFile);
|
||||
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine();
|
||||
if (line.startsWith(QLatin1String("<"))) {
|
||||
parseKeySequence(line);
|
||||
} else if (line.startsWith(QLatin1String("include"))) {
|
||||
parseIncludeInstruction(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"), locale());
|
||||
line.replace(QLatin1String("%S"), systemComposeDir());
|
||||
|
||||
processFile(line);
|
||||
}
|
||||
|
||||
ushort TableGenerator::keysymToUtf8(uint32_t sym)
|
||||
{
|
||||
QByteArray chars;
|
||||
int bytes;
|
||||
chars.resize(8);
|
||||
|
||||
if (needWorkaround(sym)) {
|
||||
uint32_t codepoint;
|
||||
if (sym == XKB_KEY_KP_Space)
|
||||
codepoint = XKB_KEY_space & 0x7f;
|
||||
else
|
||||
codepoint = sym & 0x7f;
|
||||
|
||||
bytes = utf32_to_utf8(codepoint, chars.data());
|
||||
} else {
|
||||
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
|
||||
const QChar *ch = QString(chars.data()).unicode();
|
||||
return ch->unicode();
|
||||
}
|
||||
|
||||
uint32_t TableGenerator::stringToKeysym(QString keysymName)
|
||||
{
|
||||
uint32_t keysym;
|
||||
const char *name = keysymName.toLatin1().constData();
|
||||
|
||||
if ((keysym = xkb_keysym_from_name(name, (xkb_keysym_flags)0)) == XKB_KEY_NoSymbol)
|
||||
qWarning() << QString("Qt Warrning - invalid keysym: %1").arg(keysymName);
|
||||
|
||||
return keysym;
|
||||
}
|
||||
|
||||
void TableGenerator::parseKeySequence(QString line)
|
||||
{
|
||||
// we are interested in the lines with the following format:
|
||||
// <Multi_key> <numbersign> <S> : "♬" U266c # BEAMED SIXTEENTH NOTE
|
||||
int keysEnd = line.indexOf(QLatin1String(":"));
|
||||
QString keys = line.left(keysEnd).trimmed();
|
||||
|
||||
// find the key sequence
|
||||
QString regexp = QStringLiteral("<[^>]+>");
|
||||
QRegularExpression reg(regexp);
|
||||
QRegularExpressionMatchIterator i = reg.globalMatch(keys);
|
||||
QStringList keyList;
|
||||
while (i.hasNext()) {
|
||||
QRegularExpressionMatch match = i.next();
|
||||
QString word = match.captured(0);
|
||||
keyList << word;
|
||||
}
|
||||
|
||||
QComposeTableElement elem;
|
||||
QString quote = QStringLiteral("\"");
|
||||
// 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".
|
||||
int composeValueIndex = line.indexOf(quote, keysEnd) + 1;
|
||||
const QChar valueType(line.at(composeValueIndex));
|
||||
|
||||
if (valueType == '\\' && line.at(composeValueIndex + 1).isDigit()) {
|
||||
// handle octal and hex code values
|
||||
QChar detectBase(line.at(composeValueIndex + 2));
|
||||
QString codeValue = line.mid(composeValueIndex + 1, line.lastIndexOf(quote) - composeValueIndex - 1);
|
||||
if (detectBase == 'x') {
|
||||
// hexadecimal character code
|
||||
elem.value = keysymToUtf8(codeValue.toUInt(0, 16));
|
||||
} else {
|
||||
// octal character code
|
||||
QString hexStr = QString::number(codeValue.toUInt(0, 8), 16);
|
||||
elem.value = keysymToUtf8(hexStr.toUInt(0, 16));
|
||||
}
|
||||
} else {
|
||||
// handle direct text encoded in the locale
|
||||
elem.value = valueType.unicode();
|
||||
}
|
||||
|
||||
// find the comment
|
||||
int commnetIndex = line.lastIndexOf(quote) + 1;
|
||||
elem.comment = line.mid(commnetIndex).trimmed();
|
||||
|
||||
// Convert to X11 keysym
|
||||
int count = keyList.length();
|
||||
for (int i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
||||
if (i < count) {
|
||||
QString keysym = keyList.at(i);
|
||||
keysym.remove(keysym.length() - 1, 1);
|
||||
keysym.remove(0, 1);
|
||||
|
||||
if (keysym == QLatin1String("dead_inverted_breve"))
|
||||
keysym = QStringLiteral("dead_invertedbreve");
|
||||
else if (keysym == QLatin1String("dead_double_grave"))
|
||||
keysym = QStringLiteral("dead_doublegrave");
|
||||
|
||||
elem.keys[i] = stringToKeysym(keysym);
|
||||
} else {
|
||||
elem.keys[i] = 0;
|
||||
}
|
||||
}
|
||||
m_composeTable.append(elem);
|
||||
}
|
||||
|
||||
void TableGenerator::printComposeTable() const
|
||||
{
|
||||
if (composeTable().isEmpty())
|
||||
return;
|
||||
|
||||
QString output;
|
||||
QComposeTableElement elem;
|
||||
QString comma = QStringLiteral(",");
|
||||
int tableSize = m_composeTable.size();
|
||||
for (int i = 0; i < tableSize; ++i) {
|
||||
elem = m_composeTable.at(i);
|
||||
output.append(QLatin1String("{ {"));
|
||||
for (int j = 0; j < QT_KEYSEQUENCE_MAX_LEN; j++) {
|
||||
output.append(QString(QLatin1String("0x%1, ")).arg(QString::number(elem.keys[j],16)));
|
||||
}
|
||||
// take care of the trailing comma
|
||||
if (i == tableSize - 1)
|
||||
comma = QStringLiteral("");
|
||||
output.append(QString(QLatin1String("}, 0x%1, \"\" }%2 // %3 \n"))
|
||||
.arg(QString::number(elem.value,16))
|
||||
.arg(comma)
|
||||
.arg(elem.comment));
|
||||
}
|
||||
|
||||
qDebug() << "output: \n" << output;
|
||||
}
|
||||
|
||||
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
|
||||
qStableSort(m_composeTable.begin(), m_composeTable.end(), Compare());
|
||||
}
|
||||
|
@ -0,0 +1,128 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTABLEGENERATOR_H
|
||||
#define QTABLEGENERATOR_H
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
|
||||
#define QT_KEYSEQUENCE_MAX_LEN 6
|
||||
|
||||
struct QComposeTableElement {
|
||||
uint keys[QT_KEYSEQUENCE_MAX_LEN];
|
||||
uint value;
|
||||
QString comment;
|
||||
};
|
||||
|
||||
class Compare
|
||||
{
|
||||
public:
|
||||
bool operator () (const QComposeTableElement &lhs, const uint rhs[QT_KEYSEQUENCE_MAX_LEN])
|
||||
{
|
||||
for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
||||
if (lhs.keys[i] != rhs[i])
|
||||
return (lhs.keys[i] < rhs[i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator () (const QComposeTableElement &lhs, const QComposeTableElement &rhs)
|
||||
{
|
||||
for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
||||
if (lhs.keys[i] != rhs.keys[i])
|
||||
return (lhs.keys[i] < rhs.keys[i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class TableGenerator
|
||||
{
|
||||
|
||||
public:
|
||||
enum TableState
|
||||
{
|
||||
UnsupportedLocale,
|
||||
EmptyTable,
|
||||
UnknownSystemComposeDir,
|
||||
MissingComposeFile,
|
||||
NoErrors
|
||||
};
|
||||
|
||||
TableGenerator();
|
||||
~TableGenerator();
|
||||
|
||||
void parseComposeFile(QFile *composeFile);
|
||||
void printComposeTable() const;
|
||||
void orderComposeTable();
|
||||
|
||||
QList<QComposeTableElement> composeTable() const;
|
||||
TableState tableState() const { return m_state; }
|
||||
|
||||
protected:
|
||||
bool processFile(QString composeFileName);
|
||||
void parseKeySequence(QString line);
|
||||
void parseIncludeInstruction(QString line);
|
||||
|
||||
void findComposeFile();
|
||||
bool findSystemComposeDir();
|
||||
QString systemComposeDir();
|
||||
|
||||
ushort keysymToUtf8(uint32_t sym);
|
||||
uint32_t stringToKeysym(QString keysymName);
|
||||
|
||||
void readLocaleMappings();
|
||||
void initPossibleLocations();
|
||||
bool cleanState() const { return ((m_state & NoErrors) == NoErrors); }
|
||||
QString locale() const;
|
||||
|
||||
private:
|
||||
QList<QComposeTableElement> m_composeTable;
|
||||
QMap<QString, QString> m_localeToTable;
|
||||
TableState m_state;
|
||||
QString m_systemComposeDir;
|
||||
QList<QString> m_possibleLocations;
|
||||
};
|
||||
|
||||
#endif // QTABLEGENERATOR_H
|
70
src/plugins/platforminputcontexts/compose/main.cpp
Normal file
70
src/plugins/platforminputcontexts/compose/main.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <qpa/qplatforminputcontextplugin_p.h>
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include "qcomposeplatforminputcontext.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QComposePlatformInputContextPlugin : public QPlatformInputContextPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPlatformInputContextFactoryInterface" FILE "compose.json")
|
||||
|
||||
public:
|
||||
QComposeInputContext *create(const QString &, const QStringList &);
|
||||
};
|
||||
|
||||
QComposeInputContext *QComposePlatformInputContextPlugin::create(const QString &system, const QStringList ¶mList)
|
||||
{
|
||||
Q_UNUSED(paramList);
|
||||
|
||||
if (system.compare(system, QStringLiteral("compose"), Qt::CaseInsensitive) == 0)
|
||||
return new QComposeInputContext;
|
||||
return 0;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "main.moc"
|
@ -0,0 +1,268 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qcomposeplatforminputcontext.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
QComposeInputContext::QComposeInputContext()
|
||||
{
|
||||
TableGenerator reader;
|
||||
m_tableState = reader.tableState();
|
||||
|
||||
if ((m_tableState & TableGenerator::NoErrors) == TableGenerator::NoErrors) {
|
||||
m_composeTable = reader.composeTable();
|
||||
clearComposeBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool QComposeInputContext::filterEvent(const QEvent *event)
|
||||
{
|
||||
// if there were errors when generating the compose table input
|
||||
// context should not try to filter anything, simply return false
|
||||
if ((m_tableState & TableGenerator::NoErrors) != TableGenerator::NoErrors)
|
||||
return false;
|
||||
|
||||
QKeyEvent *keyEvent = (QKeyEvent *)event;
|
||||
// should pass only the key presses
|
||||
if (keyEvent->type() != QEvent::KeyPress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyval = keyEvent->key();
|
||||
int keysym = 0;
|
||||
|
||||
if (ignoreKey(keyval))
|
||||
return false;
|
||||
|
||||
QString text = keyEvent->text();
|
||||
if (!composeKey(keyval) && text.isEmpty())
|
||||
return false;
|
||||
|
||||
keysym = keyEvent->nativeVirtualKey();
|
||||
|
||||
int nCompose = 0;
|
||||
while (m_composeBuffer[nCompose] != 0 && nCompose < QT_KEYSEQUENCE_MAX_LEN)
|
||||
nCompose++;
|
||||
|
||||
if (nCompose == QT_KEYSEQUENCE_MAX_LEN) {
|
||||
reset();
|
||||
nCompose = 0;
|
||||
}
|
||||
|
||||
m_composeBuffer[nCompose] = keysym;
|
||||
// check sequence
|
||||
if (checkComposeTable())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QComposeInputContext::isValid() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void QComposeInputContext::setFocusObject(QObject *object)
|
||||
{
|
||||
m_focusObject = object;
|
||||
}
|
||||
|
||||
void QComposeInputContext::reset()
|
||||
{
|
||||
clearComposeBuffer();
|
||||
}
|
||||
|
||||
void QComposeInputContext::update(Qt::InputMethodQueries q)
|
||||
{
|
||||
QPlatformInputContext::update(q);
|
||||
}
|
||||
|
||||
static bool isDuplicate(const QComposeTableElement &lhs, const QComposeTableElement &rhs)
|
||||
{
|
||||
for (size_t i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
|
||||
if (lhs.keys[i] != rhs.keys[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QComposeInputContext::checkComposeTable()
|
||||
{
|
||||
QList<QComposeTableElement>::iterator it =
|
||||
qLowerBound(m_composeTable.begin(), m_composeTable.end(), m_composeBuffer, Compare());
|
||||
|
||||
// prevent dereferencing an 'end' iterator, which would result in a crash
|
||||
if (it == m_composeTable.end())
|
||||
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.end()) {
|
||||
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
|
@ -0,0 +1,85 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||
#define QCOMPOSEPLATFORMINPUTCONTEXT_H
|
||||
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
|
||||
#include <QtCore/QList>
|
||||
|
||||
#include "generator/qtablegenerator.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QEvent;
|
||||
|
||||
class QComposeInputContext : public QPlatformInputContext
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QComposeInputContext();
|
||||
~QComposeInputContext();
|
||||
|
||||
bool isValid() const;
|
||||
void setFocusObject(QObject *object);
|
||||
void reset();
|
||||
void update(Qt::InputMethodQueries);
|
||||
bool filterEvent(const QEvent *event);
|
||||
|
||||
protected:
|
||||
void clearComposeBuffer();
|
||||
bool ignoreKey(int keyval) const;
|
||||
bool composeKey(int keyval) const;
|
||||
bool checkComposeTable();
|
||||
void commitText(uint character) const;
|
||||
|
||||
private:
|
||||
QObject *m_focusObject;
|
||||
QList<QComposeTableElement> m_composeTable;
|
||||
uint m_composeBuffer[QT_KEYSEQUENCE_MAX_LEN + 1];
|
||||
TableGenerator::TableState m_tableState;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QCOMPOSEPLATFORMINPUTCONTEXT_H
|
105
src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h
Normal file
105
src/plugins/platforminputcontexts/compose/xkbcommon_workaround.h
Normal file
@ -0,0 +1,105 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the plugins of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef XKBCOMMON_WORKAROUND_H
|
||||
#define XKBCOMMON_WORKAROUND_H
|
||||
|
||||
// Function utf32_to_utf8() is borrowed from the libxkbcommon library,
|
||||
// file keysym-utf.c. The workaround should be removed once the fix from
|
||||
// https://bugs.freedesktop.org/show_bug.cgi?id=56780 gets released.
|
||||
static int utf32_to_utf8(uint32_t unichar, char *buffer)
|
||||
{
|
||||
int count, shift, length;
|
||||
uint8_t head;
|
||||
|
||||
if (unichar <= 0x007f) {
|
||||
buffer[0] = unichar;
|
||||
buffer[1] = '\0';
|
||||
return 2;
|
||||
}
|
||||
else if (unichar <= 0x07FF) {
|
||||
length = 2;
|
||||
head = 0xc0;
|
||||
}
|
||||
else if (unichar <= 0xffff) {
|
||||
length = 3;
|
||||
head = 0xe0;
|
||||
}
|
||||
else if (unichar <= 0x1fffff) {
|
||||
length = 4;
|
||||
head = 0xf0;
|
||||
}
|
||||
else if (unichar <= 0x3ffffff) {
|
||||
length = 5;
|
||||
head = 0xf8;
|
||||
}
|
||||
else {
|
||||
length = 6;
|
||||
head = 0xfc;
|
||||
}
|
||||
|
||||
for (count = length - 1, shift = 0; count > 0; count--, shift += 6)
|
||||
buffer[count] = 0x80 | ((unichar >> shift) & 0x3f);
|
||||
|
||||
buffer[0] = head | ((unichar >> shift) & 0x3f);
|
||||
buffer[length] = '\0';
|
||||
|
||||
return length + 1;
|
||||
}
|
||||
|
||||
static bool needWorkaround(uint32_t sym)
|
||||
{
|
||||
/* patch encoding botch */
|
||||
if (sym == XKB_KEY_KP_Space)
|
||||
return true;
|
||||
|
||||
/* special keysyms */
|
||||
if ((sym >= XKB_KEY_BackSpace && sym <= XKB_KEY_Clear) ||
|
||||
(sym >= XKB_KEY_KP_Multiply && sym <= XKB_KEY_KP_9) ||
|
||||
sym == XKB_KEY_Return || sym == XKB_KEY_Escape ||
|
||||
sym == XKB_KEY_Delete || sym == XKB_KEY_KP_Tab ||
|
||||
sym == XKB_KEY_KP_Enter || sym == XKB_KEY_KP_Equal)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // XKBCOMMON_WORKAROUND_H
|
@ -1,4 +1,10 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
qtHaveModule(dbus) {
|
||||
!mac:!win32:SUBDIRS += ibus maliit
|
||||
}
|
||||
|
||||
unix:!macx:contains(QT_CONFIG, xkbcommon): {
|
||||
SUBDIRS += compose
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user