From 379c05398e8d7c774737bc2370c49cd84ca04447 Mon Sep 17 00:00:00 2001
From: Takao Fujiwara <tfujiwar@redhat.com>
Date: Fri, 1 Aug 2014 15:59:31 +0900
Subject: [PATCH] Use ibus ProcessKeyEvent async.

ibus-hangul send "CommitText" dbus async API during "ProcessKeyEvent"
dbus API is called. If "ProcessKeyEvent" is sync, "ProcessKeyEvent"
finishes before "CommitText" finishes and the order of Hangul
characters and space is not correct.

Task-number:QTBUG-40541
---
 .../ibus/qibusplatforminputcontext.cpp             |  12 +-
 .../ibus/qibusplatforminputcontext.h               |   4 +-
 src/plugins/platforms/xcb/qxcbkeyboard.cpp         | 165 +++++++++++++++++----
 src/plugins/platforms/xcb/qxcbkeyboard.h           |  18 ++-
 4 files changed, 163 insertions(+), 36 deletions(-)

diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
index 53e9b17..b8e4036 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
@@ -226,24 +226,24 @@ void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint
 }
 
 
-bool
+QDBusPendingReply<bool>
 QIBusPlatformInputContext::x11FilterEvent(uint keyval, uint keycode, uint state, bool press)
 {
     if (!d->valid)
-        return false;
+        return QDBusPendingCall::fromError(QDBusError(QDBusError::InvalidInterface, QString("Invalid")));
 
     if (!inputMethodAccepted())
-        return false;
+        return QDBusPendingCall::fromError(QDBusError(QDBusError::AccessDenied, QString("Invalid")));
 
     if (!press)
-        return false;
+        return QDBusPendingCall::fromError(QDBusError(QDBusError::NoError, QString("Released")));
 
     keycode -= 8; // ###
-    QDBusReply<bool> reply = d->context->ProcessKeyEvent(keyval, keycode, state);
+    QDBusPendingReply<bool> reply = d->context->ProcessKeyEvent(keyval, keycode, state);
 
 //    qDebug() << "x11FilterEvent return" << reply.value();
 
-    return reply.value();
+    return reply;
 }
 
 QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
index 400ef39..e48175a 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
@@ -43,6 +43,8 @@
 
 #include <qpa/qplatforminputcontext.h>
 
+#include <QtDBus/QDBusPendingReply>
+
 QT_BEGIN_NAMESPACE
 
 class QIBusPlatformInputContextPrivate;
@@ -63,7 +65,7 @@ public:
     void commit();
     void update(Qt::InputMethodQueries);
 
-    Q_INVOKABLE bool x11FilterEvent(uint keyval, uint keycode, uint state, bool press);
+    Q_INVOKABLE QDBusPendingReply<bool> x11FilterEvent(uint keyval, uint keycode, uint state, bool press);
 
 public Q_SLOTS:
     void commitText(const QDBusVariant &text);
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
index 4c84b19..7e5e998 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
@@ -50,6 +50,7 @@
 #include <QtCore/QTextCodec>
 #include <QtCore/QMetaMethod>
 #include <QtCore/QDir>
+#include <QtDBus/QDBusPendingReply>
 #include <private/qguiapplication_p.h>
 
 #include <stdio.h>
@@ -584,6 +585,27 @@ static const Qt::KeyboardModifiers ModsTbl[] = {
     Qt::NoModifier                                              // Fall-back to raw Key_*, for non-latin1 kb layouts
 };
 
+class QX11FilterEventWatcher: public QDBusPendingCallWatcher
+{
+public:
+    QWindow *window = 0;
+    QPlatformInputContext *inputContext = 0;
+    QList<QVariant> args;
+
+    QX11FilterEventWatcher(const QDBusPendingCall &call, QObject *parent = 0,
+                           QWindow *window = 0,
+                           QPlatformInputContext *inputContext = 0,
+                           QList<QVariant> args = QList<QVariant>())
+    : QDBusPendingCallWatcher(call, parent)
+    , window(window)
+    , inputContext(inputContext)
+    , args(args)
+    {}
+    ~QX11FilterEventWatcher()
+    {}
+};
+
+
 Qt::KeyboardModifiers QXcbKeyboard::translateModifiers(int s) const
 {
     Qt::KeyboardModifiers ret = 0;
@@ -983,7 +1005,8 @@ int QXcbKeyboard::keysymToQtKey(xcb_keysym_t keysym, Qt::KeyboardModifiers &modi
 }
 
 QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
-    : QXcbObject(connection)
+    : QObject (connection)
+    , QXcbObject(connection)
     , m_autorepeat_code(0)
     , xkb_context(0)
     , xkb_keymap(0)
@@ -1008,6 +1031,7 @@ QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
     }
 #endif
     updateKeymap();
+    m_event_filter_use_sync_mode = getBoolEnv("IBUS_ENABLE_SYNC_MODE", false);
 }
 
 QXcbKeyboard::~QXcbKeyboard()
@@ -1020,6 +1044,23 @@ QXcbKeyboard::~QXcbKeyboard()
     clearXKBConfig();
 }
 
+bool QXcbKeyboard::getBoolEnv(const char *name, bool defval)
+{
+    QByteArray array = qgetenv (name);
+
+    if (array.isEmpty())
+      return defval;
+
+    if (array == "" ||
+        array == "0" ||
+        array == "false" ||
+        array == "False" ||
+        array == "FALSE")
+      return false;
+
+    return true;
+}
+
 void QXcbKeyboard::updateVModMapping()
 {
 #ifndef QT_NO_XKB
@@ -1307,26 +1348,63 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
     xcb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, code);
 
     QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
-    QMetaMethod method;
 
-    if (inputContext) {
-        int methodIndex = inputContext->metaObject()->indexOfMethod("x11FilterEvent(uint,uint,uint,bool)");
-        if (methodIndex != -1)
-            method = inputContext->metaObject()->method(methodIndex);
-    }
+    if (handleKeyEventFilter(window, inputContext, type, code, sym, state, time, false))
+        return;
 
-    if (method.isValid()) {
-        bool retval = false;
-        method.invoke(inputContext, Qt::DirectConnection,
-                      Q_RETURN_ARG(bool, retval),
-                      Q_ARG(uint, sym),
-                      Q_ARG(uint, code),
-                      Q_ARG(uint, state),
-                      Q_ARG(bool, type == QEvent::KeyPress));
-        if (retval)
-            return;
+    handleKeyEventNoFilter(window, inputContext, type, code, sym, state, time, false);
+}
+
+bool QXcbKeyboard::handleKeyEventFilter(QWindow *window, QPlatformInputContext *inputContext,
+                                  QEvent::Type type, xcb_keycode_t code, xcb_keysym_t sym,
+                                  quint16 state, xcb_timestamp_t time,
+                                  bool isAutoRepeat)
+{
+    if (!inputContext)
+        return false;
+
+    int methodIndex = inputContext->metaObject()->indexOfMethod("x11FilterEvent(uint,uint,uint,bool)");
+    if (methodIndex == -1)
+        return false;
+
+    QMetaMethod method = inputContext->metaObject()->method(methodIndex);
+
+    if (!method.isValid())
+        return false;
+
+    QDBusPendingReply<bool> reply;
+    method.invoke(inputContext, Qt::DirectConnection,
+                  Q_RETURN_ARG(QDBusPendingReply<bool>, reply),
+                  Q_ARG(uint, sym),
+                  Q_ARG(uint, code),
+                  Q_ARG(uint, state),
+                  Q_ARG(bool, isAutoRepeat ? true : (type == QEvent::KeyPress)));
+
+    if (m_event_filter_use_sync_mode)
+        return reply.value();
+
+    if (reply.isFinished()) {
+        if (reply.value())
+            return true;
+    } else {
+        QList<QVariant> args;
+        args << QVariant::fromValue((uint) type);
+        args << QVariant::fromValue(code) << QVariant::fromValue(sym) << QVariant::fromValue(state);
+        args << QVariant::fromValue(time) << QVariant::fromValue(isAutoRepeat);
+
+        QX11FilterEventWatcher *watcher = new QX11FilterEventWatcher(reply, this, window, inputContext, args);
+        QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(x11FilterEventFinished(QDBusPendingCallWatcher*)));
+        return true;
     }
 
+    return false;
+}
+
+void QXcbKeyboard::handleKeyEventNoFilter(QWindow *window, QPlatformInputContext *inputContext,
+                                  QEvent::Type type, xcb_keycode_t code, xcb_keysym_t sym,
+                                  quint16 state, xcb_timestamp_t time, bool isAutoRepeat)
+{
+    // It is crucial the order of xkb_state_key_get_one_sym & xkb_state_update_key operations is not reversed!
     Qt::KeyboardModifiers modifiers = translateModifiers(state);
 
     QString string = lookupString(xkb_state, code);
@@ -1334,7 +1412,20 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
     string.truncate(count);
 
     int qtcode = keysymToQtKey(sym, modifiers, string);
-    bool isAutoRepeat = false;
+
+    bool filtered = false;
+    if (isAutoRepeat) {
+        if (inputContext) {
+            QKeyEvent event(QEvent::KeyPress, qtcode, modifiers, code, sym, state, string, isAutoRepeat, count);
+            event.setTimestamp(time);
+            filtered = inputContext->filterEvent(&event);
+        }
+        if (!filtered)
+            QWindowSystemInterface::handleExtendedKeyEvent(window, time, QEvent::KeyPress, qtcode, modifiers,
+                                                           code, sym, state, string, isAutoRepeat);
+
+        return;
+    }
 
     if (type == QEvent::KeyPress) {
         if (m_autorepeat_code == code) {
@@ -1352,7 +1443,6 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
         m_autorepeat_code = isAutoRepeat ? code : 0;
     }
 
-    bool filtered = false;
     if (inputContext) {
         QKeyEvent event(type, qtcode, modifiers, code, sym, state, string, isAutoRepeat, count);
         event.setTimestamp(time);
@@ -1371,15 +1461,7 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
 
     if (isAutoRepeat && type == QEvent::KeyRelease) {
         // since we removed it from the event queue using checkEvent we need to send the key press here
-        filtered = false;
-        if (method.isValid()) {
-            method.invoke(inputContext, Qt::DirectConnection,
-                          Q_RETURN_ARG(bool, filtered),
-                          Q_ARG(uint, sym),
-                          Q_ARG(uint, code),
-                          Q_ARG(uint, state),
-                          Q_ARG(bool, true));
-        }
+        filtered = handleKeyEventFilter(window, inputContext, type, code, sym, state, time, true);
 
         if (!filtered && inputContext) {
             QKeyEvent event(QEvent::KeyPress, qtcode, modifiers, code, sym, state, string, isAutoRepeat, count);
@@ -1392,6 +1474,33 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
     }
 }
 
+void QXcbKeyboard::x11FilterEventFinished(QDBusPendingCallWatcher * call)
+{
+    QX11FilterEventWatcher *watcher = (QX11FilterEventWatcher *) call;
+    QDBusPendingReply<bool> reply = *call;
+
+    if (reply.isError())
+        return;
+    if (reply.value())
+        return;
+
+    QWindow *window = watcher->window;
+    QPlatformInputContext *inputContext = watcher->inputContext;
+    QList<QVariant> args = watcher->args;
+    QEvent::Type type = (QEvent::Type) args[0].toUInt();
+    xcb_keycode_t code = args[1].toUInt();
+    xcb_keysym_t sym = args[2].toUInt();
+    quint16 state = args[3].toUInt();
+    xcb_timestamp_t time = args[4].toUInt();
+    bool isAutoRepeat = args[5].toUInt();
+
+    //qDebug() << "x11FilterEventFinished" << sym << code << state << type;
+
+    handleKeyEventNoFilter(window, inputContext, type, code, sym, state, time, isAutoRepeat);
+
+    call->deleteLater();
+}
+
 QString QXcbKeyboard::lookupString(struct xkb_state *state, xcb_keycode_t code) const
 {
     QByteArray chars;
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h
index 11b7429..c027064 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.h
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.h
@@ -51,14 +51,19 @@
 #include <xkbcommon/xkbcommon-x11.h>
 #endif
 
+#include <qpa/qplatforminputcontext.h>
+
 #include <QEvent>
+#include <QObject>
+#include <QtDBus/QDBusPendingCallWatcher>
 
 QT_BEGIN_NAMESPACE
 
 class QWindow;
 
-class QXcbKeyboard : public QXcbObject
+class QXcbKeyboard : public QObject, public QXcbObject
 {
+    Q_OBJECT
 public:
     QXcbKeyboard(QXcbConnection *connection);
 
@@ -82,8 +87,17 @@ public:
     void updateXKBState(xcb_xkb_state_notify_event_t *state);
 #endif
 
+public Q_SLOTS:
+    void x11FilterEventFinished(QDBusPendingCallWatcher * call);
+
 protected:
     void handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycode_t code, quint16 state, xcb_timestamp_t time);
+    bool handleKeyEventFilter(QWindow *window, QPlatformInputContext *inputContext,
+                              QEvent::Type type, xcb_keycode_t code, xcb_keysym_t sym,
+                              quint16 state, xcb_timestamp_t time, bool isAutoRepeat);
+    void handleKeyEventNoFilter(QWindow *window, QPlatformInputContext *inputContext,
+                               QEvent::Type type, xcb_keycode_t code, xcb_keysym_t sym,
+                               quint16 state, xcb_timestamp_t time, bool isAutoRepeat);
 
     void resolveMaskConflicts();
     QString lookupString(struct xkb_state *state, xcb_keycode_t code) const;
@@ -98,6 +112,7 @@ protected:
     // when XKEYBOARD is present on the X server
     void updateVModMapping();
     void updateVModToRModMapping();
+    bool getBoolEnv(const char *name, bool defval);
 
 private:
     bool m_config;
@@ -136,6 +151,7 @@ private:
     _mod_masks vmod_masks;
     int core_device_id;
 #endif
+    bool m_event_filter_use_sync_mode;
 };
 
 QT_END_NAMESPACE
-- 
2.0.0

