summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforminputcontexts
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforminputcontexts')
-rw-r--r--src/plugins/platforminputcontexts/compose/compose.pro2
-rw-r--r--src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp235
-rw-r--r--src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h4
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp124
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h38
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibustypes.cpp6
6 files changed, 361 insertions, 48 deletions
diff --git a/src/plugins/platforminputcontexts/compose/compose.pro b/src/plugins/platforminputcontexts/compose/compose.pro
index a9da36c473..a4b5280e64 100644
--- a/src/plugins/platforminputcontexts/compose/compose.pro
+++ b/src/plugins/platforminputcontexts/compose/compose.pro
@@ -5,7 +5,7 @@ PLUGIN_EXTENDS = -
PLUGIN_CLASS_NAME = QComposePlatformInputContextPlugin
load(qt_plugin)
-QT += gui-private
+QT += core-private gui-private
DEFINES += X11_PREFIX='\\"$$QMAKE_X11_PREFIX\\"'
diff --git a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp
index 65020eb848..ad9877eb25 100644
--- a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp
+++ b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.cpp
@@ -36,8 +36,12 @@
#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>
@@ -48,12 +52,191 @@
#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)
+{
+ QComposeCacheFileHeader info;
+ info.reserved = 0;
+ info.fileSize = 0;
+ const QByteArray pathBytes = QFile::encodeName(path);
+ QT_STATBUF st;
+ if (QT_STAT(pathBytes.data(), &st) != 0)
+ return info;
+ info.lastModified = st.st_mtime;
+ info.fileSize = st.st_size;
+ 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();
+ QSaveFile outputFile(filePath);
+
+ 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;
+ return outputFile.commit();
+}
+
TableGenerator::TableGenerator() : m_state(NoErrors),
m_systemComposeDir(QString())
{
initPossibleLocations();
- findComposeFile();
- orderComposeTable();
+ 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
@@ -76,53 +259,39 @@ void TableGenerator::initPossibleLocations()
m_possibleLocations.append(QStringLiteral(X11_PREFIX "/lib/X11/locale"));
}
-void TableGenerator::findComposeFile()
+QString 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);
+ QString path(qgetenv("XCOMPOSEFILE"));
+ if (path.endsWith(QLatin1String("Compose")))
+ return path;
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
+ if (cleanState()) {
+ QString path = qgetenv("HOME") + QStringLiteral("/.XCompose");
+ if (QFile(path).exists())
+ return path;
}
+
// check for the system provided compose files
- if (!found && cleanState()) {
+ 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
- found = processFile(systemComposeDir() + QLatin1Char('/') + table);
-#ifdef DEBUG_GENERATOR
- if (found)
- qDebug() << "Using Compose file from: " <<
- systemComposeDir() + QLatin1Char('/') + table;
-#endif
+ else {
+ QString path = QDir(systemComposeDir()).filePath(table);
+ if (QFile(path).exists())
+ return path;
+ }
}
}
-
- if (found && m_composeTable.isEmpty())
- m_state = EmptyTable;
-
- if (!found)
- m_state = MissingComposeFile;
+ return QString();
}
QString TableGenerator::composeTableForLocale()
diff --git a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h
index 468da4cad1..8ad081bea5 100644
--- a/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h
+++ b/src/plugins/platforminputcontexts/compose/generator/qtablegenerator.h
@@ -43,6 +43,8 @@
//#define DEBUG_GENERATOR
+/* Whenever QComposeTableElement gets modified supportedCacheVersion
+ from qtablegenerator.cpp must be bumped. */
struct QComposeTableElement {
uint keys[QT_KEYSEQUENCE_MAX_LEN];
uint value;
@@ -107,7 +109,7 @@ protected:
void parseKeySequence(char *line);
void parseIncludeInstruction(QString line);
- void findComposeFile();
+ QString findComposeFile();
bool findSystemComposeDir();
QString systemComposeDir();
QString composeTableForLocale();
diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
index 673942b5d9..27fca7c6ed 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
@@ -39,6 +39,10 @@
#include <qwindow.h>
#include <qevent.h>
+#include <qpa/qplatformcursor.h>
+#include <qpa/qplatformscreen.h>
+#include <qpa/qwindowsysteminterface.h>
+
#include "qibusproxy.h"
#include "qibusinputcontextproxy.h"
#include "qibustypes.h"
@@ -48,8 +52,14 @@
#include <QtDBus>
+#ifndef IBUS_RELEASE_MASK
+#define IBUS_RELEASE_MASK (1 << 30)
+#endif
+
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(qtQpaInputMethods, "qt.qpa.input.methods")
+
enum { debug = 0 };
class QIBusPlatformInputContextPrivate
@@ -86,6 +96,13 @@ QIBusPlatformInputContext::QIBusPlatformInputContext ()
}
QInputMethod *p = qApp->inputMethod();
connect(p, SIGNAL(cursorRectangleChanged()), this, SLOT(cursorRectChanged()));
+ m_eventFilterUseSynchronousMode = false;
+ if (qEnvironmentVariableIsSet("IBUS_ENABLE_SYNC_MODE")) {
+ bool ok;
+ int enableSync = qgetenv("IBUS_ENABLE_SYNC_MODE").toInt(&ok);
+ if (ok && enableSync == 1)
+ m_eventFilterUseSynchronousMode = true;
+ }
}
QIBusPlatformInputContext::~QIBusPlatformInputContext (void)
@@ -272,8 +289,7 @@ void QIBusPlatformInputContext::deleteSurroundingText(int offset, uint n_chars)
QCoreApplication::sendEvent(input, &event);
}
-bool
-QIBusPlatformInputContext::x11FilterEvent(uint keyval, uint keycode, uint state, bool press)
+bool QIBusPlatformInputContext::filterEvent(const QEvent *event)
{
if (!d->valid)
return false;
@@ -281,15 +297,105 @@ QIBusPlatformInputContext::x11FilterEvent(uint keyval, uint keycode, uint state,
if (!inputMethodAccepted())
return false;
- if (!press)
- return false;
+ const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
+ quint32 sym = keyEvent->nativeVirtualKey();
+ quint32 code = keyEvent->nativeScanCode();
+ quint32 state = keyEvent->nativeModifiers();
+ quint32 ibusState = state;
+
+ if (keyEvent->type() != QEvent::KeyPress)
+ ibusState |= IBUS_RELEASE_MASK;
+
+ QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(sym, code - 8, ibusState);
+
+ if (m_eventFilterUseSynchronousMode || reply.isFinished()) {
+ bool retval = reply.value();
+ qCDebug(qtQpaInputMethods) << "filterEvent return" << code << sym << state << retval;
+ return retval;
+ }
+
+ Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
+ const int qtcode = keyEvent->key();
+
+ // From QKeyEvent::modifiers()
+ switch (qtcode) {
+ case Qt::Key_Shift:
+ modifiers ^= Qt::ShiftModifier;
+ break;
+ case Qt::Key_Control:
+ modifiers ^= Qt::ControlModifier;
+ break;
+ case Qt::Key_Alt:
+ modifiers ^= Qt::AltModifier;
+ break;
+ case Qt::Key_Meta:
+ modifiers ^= Qt::MetaModifier;
+ break;
+ case Qt::Key_AltGr:
+ modifiers ^= Qt::GroupSwitchModifier;
+ break;
+ }
+
+ QVariantList args;
+ args << QVariant::fromValue(keyEvent->timestamp());
+ args << QVariant::fromValue(static_cast<uint>(keyEvent->type()));
+ args << QVariant::fromValue(qtcode);
+ args << QVariant::fromValue(code) << QVariant::fromValue(sym) << QVariant::fromValue(state);
+ args << QVariant::fromValue(keyEvent->text());
+ args << QVariant::fromValue(keyEvent->isAutoRepeat());
- keycode -= 8; // ###
- QDBusReply<bool> reply = d->context->ProcessKeyEvent(keyval, keycode, state);
+ QIBusFilterEventWatcher *watcher = new QIBusFilterEventWatcher(reply, this, QGuiApplication::focusWindow(), modifiers, args);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &QIBusPlatformInputContext::filterEventFinished);
-// qDebug() << "x11FilterEvent return" << reply.value();
+ return true;
+}
+
+void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *call)
+{
+ QIBusFilterEventWatcher *watcher = (QIBusFilterEventWatcher *) call;
+ QDBusPendingReply<bool> reply = *call;
+
+ if (reply.isError()) {
+ call->deleteLater();
+ return;
+ }
- return reply.value();
+ // Use watcher's window instead of the current focused window
+ // since there is a time lag until filterEventFinished() returns.
+ QWindow *window = watcher->window();
+
+ if (!window) {
+ call->deleteLater();
+ return;
+ }
+
+ Qt::KeyboardModifiers modifiers = watcher->modifiers();
+ QVariantList args = watcher->arguments();
+ const ulong time = static_cast<const ulong>(args.at(0).toUInt());
+ const QEvent::Type type = static_cast<const QEvent::Type>(args.at(1).toUInt());
+ const int qtcode = args.at(2).toInt();
+ const quint32 code = args.at(3).toUInt();
+ const quint32 sym = args.at(4).toUInt();
+ const quint32 state = args.at(5).toUInt();
+ const QString string = args.at(6).toString();
+ const bool isAutoRepeat = args.at(7).toBool();
+
+ // copied from QXcbKeyboard::handleKeyEvent()
+ bool retval = reply.value();
+ qCDebug(qtQpaInputMethods) << "filterEventFinished return" << code << sym << state << retval;
+ if (!retval) {
+ if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu
+ && window != NULL) {
+ const QPoint globalPos = window->screen()->handle()->cursor()->pos();
+ const QPoint pos = window->mapFromGlobal(globalPos);
+ QWindowSystemInterface::handleContextMenuEvent(window, false, pos,
+ globalPos, modifiers);
+ }
+ QWindowSystemInterface::handleExtendedKeyEvent(window, time, type, qtcode, modifiers,
+ code, sym, state, string, isAutoRepeat);
+
+ }
+ call->deleteLater();
}
QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
@@ -340,7 +446,7 @@ QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
QDBusConnection *QIBusPlatformInputContextPrivate::createConnection()
{
- QByteArray display(getenv("DISPLAY"));
+ QByteArray display(qgetenv("DISPLAY"));
QByteArray host = "unix";
QByteArray displayNumber = "0";
diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
index 816da8d377..127db7df8b 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
@@ -35,11 +35,44 @@
#include <qpa/qplatforminputcontext.h>
+#include <QtCore/qpointer.h>
+#include <QtDBus/qdbuspendingreply.h>
+#include <QLoggingCategory>
+#include <QWindow>
+
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(qtQpaInputMethods)
+
class QIBusPlatformInputContextPrivate;
class QDBusVariant;
+class QIBusFilterEventWatcher: public QDBusPendingCallWatcher
+{
+public:
+ explicit QIBusFilterEventWatcher(const QDBusPendingCall &call,
+ QObject *parent = 0,
+ QWindow *window = 0,
+ const Qt::KeyboardModifiers modifiers = 0,
+ const QVariantList arguments = QVariantList())
+ : QDBusPendingCallWatcher(call, parent)
+ , m_window(window)
+ , m_modifiers(modifiers)
+ , m_arguments(arguments)
+ {}
+ ~QIBusFilterEventWatcher()
+ {}
+
+ inline QWindow *window() const { return m_window; }
+ inline const Qt::KeyboardModifiers modifiers() const { return m_modifiers; }
+ inline const QVariantList arguments() const { return m_arguments; }
+
+private:
+ QPointer<QWindow> m_window;
+ const Qt::KeyboardModifiers m_modifiers;
+ const QVariantList m_arguments;
+};
+
class QIBusPlatformInputContext : public QPlatformInputContext
{
Q_OBJECT
@@ -54,8 +87,7 @@ public:
void reset() Q_DECL_OVERRIDE;
void commit() Q_DECL_OVERRIDE;
void update(Qt::InputMethodQueries) Q_DECL_OVERRIDE;
-
- Q_INVOKABLE bool x11FilterEvent(uint keyval, uint keycode, uint state, bool press);
+ bool filterEvent(const QEvent *event) Q_DECL_OVERRIDE;
public Q_SLOTS:
void commitText(const QDBusVariant &text);
@@ -63,9 +95,11 @@ public Q_SLOTS:
void cursorRectChanged();
void deleteSurroundingText(int offset, uint n_chars);
void surroundingTextRequired();
+ void filterEventFinished(QDBusPendingCallWatcher *call);
private:
QIBusPlatformInputContextPrivate *d;
+ bool m_eventFilterUseSynchronousMode;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforminputcontexts/ibus/qibustypes.cpp b/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
index d6be6cbb31..f88e5ca5a3 100644
--- a/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
@@ -225,9 +225,10 @@ const QDBusArgument &operator>>(const QDBusArgument &arg, QIBusAttributeList &at
QList<QInputMethodEvent::Attribute> QIBusAttributeList::imAttributes() const
{
QHash<QPair<int, int>, QTextCharFormat> rangeAttrs;
+ const int numAttributes = attributes.size();
// Merge text fomats for identical ranges into a single QTextFormat.
- for (int i = 0; i < attributes.size(); ++i) {
+ for (int i = 0; i < numAttributes; ++i) {
const QIBusAttribute &attr = attributes.at(i);
const QTextCharFormat &format = attr.format();
@@ -239,8 +240,9 @@ QList<QInputMethodEvent::Attribute> QIBusAttributeList::imAttributes() const
// Assemble list in original attribute order.
QList<QInputMethodEvent::Attribute> imAttrs;
+ imAttrs.reserve(numAttributes);
- for (int i = 0; i < attributes.size(); ++i) {
+ for (int i = 0; i < numAttributes; ++i) {
const QIBusAttribute &attr = attributes.at(i);
const QTextFormat &format = attr.format();