1 /*
2  * Copyright (C) 2011~2017 by CSSlayer
3  * wengxt@gmail.com
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above Copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above Copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of the authors nor the names of its contributors
17  *    may be used to endorse or promote products derived from this
18  *    software without specific prior written permission.
19  */
20 
21 #include <QDBusConnection>
22 #include <QDebug>
23 #include <QGuiApplication>
24 #include <QInputMethod>
25 #include <QKeyEvent>
26 #include <QPalette>
27 #include <QTextCharFormat>
28 #include <QWindow>
29 #include <qpa/qplatformcursor.h>
30 #include <qpa/qplatformscreen.h>
31 #include <qpa/qwindowsysteminterface.h>
32 
33 #include "qtkey.h"
34 
35 #include "fcitxinputcontextproxy.h"
36 #include "fcitxwatcher.h"
37 #include "qfcitxplatforminputcontext.h"
38 
get_boolean_env(const char * name,bool defval)39 static bool get_boolean_env(const char *name, bool defval) {
40     const char *value = getenv(name);
41 
42     if (value == nullptr)
43         return defval;
44 
45     if (strcmp(value, "") == 0 || strcmp(value, "0") == 0 ||
46         strcmp(value, "false") == 0 || strcmp(value, "False") == 0 ||
47         strcmp(value, "FALSE") == 0)
48         return false;
49 
50     return true;
51 }
52 
get_locale()53 static inline const char *get_locale() {
54     const char *locale = getenv("LC_ALL");
55     if (!locale)
56         locale = getenv("LC_CTYPE");
57     if (!locale)
58         locale = getenv("LANG");
59     if (!locale)
60         locale = "C";
61 
62     return locale;
63 }
64 
objectAcceptsInputMethod()65 static bool objectAcceptsInputMethod() {
66     bool enabled = false;
67     QObject *object = qApp->focusObject();
68     if (object) {
69         QInputMethodQueryEvent query(Qt::ImEnabled);
70         QGuiApplication::sendEvent(object, &query);
71         enabled = query.value(Qt::ImEnabled).toBool();
72     }
73 
74     return enabled;
75 }
76 
_xkb_context_new_helper()77 struct xkb_context *_xkb_context_new_helper() {
78     struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
79     if (context) {
80         xkb_context_set_log_level(context, XKB_LOG_LEVEL_CRITICAL);
81     }
82 
83     return context;
84 }
85 
QFcitxPlatformInputContext()86 QFcitxPlatformInputContext::QFcitxPlatformInputContext()
87     : m_watcher(new FcitxWatcher(
88           QDBusConnection::connectToBus(QDBusConnection::SessionBus,
89                                         "fcitx-platform-input-context"),
90           this)),
91       m_cursorPos(0), m_useSurroundingText(false),
92       m_syncMode(get_boolean_env("FCITX_QT_USE_SYNC", false)), m_destroy(false),
93       m_xkbContext(_xkb_context_new_helper()),
94       m_xkbComposeTable(m_xkbContext ? xkb_compose_table_new_from_locale(
95                                            m_xkbContext.data(), get_locale(),
96                                            XKB_COMPOSE_COMPILE_NO_FLAGS)
97                                      : 0),
98       m_xkbComposeState(m_xkbComposeTable
99                             ? xkb_compose_state_new(m_xkbComposeTable.data(),
100                                                     XKB_COMPOSE_STATE_NO_FLAGS)
101                             : 0) {
102     m_watcher->watch();
103 }
104 
~QFcitxPlatformInputContext()105 QFcitxPlatformInputContext::~QFcitxPlatformInputContext() {
106     m_destroy = true;
107     m_watcher->unwatch();
108     cleanUp();
109     delete m_watcher;
110 }
111 
cleanUp()112 void QFcitxPlatformInputContext::cleanUp() {
113     m_icMap.clear();
114 
115     if (!m_destroy) {
116         commitPreedit();
117     }
118 }
119 
isValid() const120 bool QFcitxPlatformInputContext::isValid() const { return true; }
121 
invokeAction(QInputMethod::Action action,int cursorPosition)122 void QFcitxPlatformInputContext::invokeAction(QInputMethod::Action action,
123                                               int cursorPosition) {
124     if (action == QInputMethod::Click &&
125         (cursorPosition <= 0 || cursorPosition >= m_preedit.length())) {
126         // qDebug() << action << cursorPosition;
127         commitPreedit();
128     }
129 }
130 
commitPreedit(QPointer<QObject> input)131 void QFcitxPlatformInputContext::commitPreedit(QPointer<QObject> input) {
132     if (!input)
133         return;
134     if (m_commitPreedit.length() <= 0)
135         return;
136     QInputMethodEvent e;
137     e.setCommitString(m_commitPreedit);
138     QCoreApplication::sendEvent(input, &e);
139     m_commitPreedit.clear();
140     m_preeditList.clear();
141 }
142 
checkUtf8(const QByteArray & byteArray)143 bool checkUtf8(const QByteArray &byteArray) {
144     QString s = QString::fromUtf8(byteArray);
145     return !s.contains(QChar::ReplacementCharacter);
146 }
147 
reset()148 void QFcitxPlatformInputContext::reset() {
149     commitPreedit();
150     if (FcitxInputContextProxy *proxy = validIC()) {
151         proxy->reset();
152     }
153     if (m_xkbComposeState) {
154         xkb_compose_state_reset(m_xkbComposeState.data());
155     }
156     QPlatformInputContext::reset();
157 }
158 
update(Qt::InputMethodQueries queries)159 void QFcitxPlatformInputContext::update(Qt::InputMethodQueries queries) {
160     // ignore the boring query
161     if (!(queries & (Qt::ImCursorRectangle | Qt::ImHints |
162                      Qt::ImSurroundingText | Qt::ImCursorPosition))) {
163         return;
164     }
165 
166     QWindow *window = qApp->focusWindow();
167     FcitxInputContextProxy *proxy = validICByWindow(window);
168     if (!proxy)
169         return;
170 
171     FcitxQtICData &data = *static_cast<FcitxQtICData *>(
172         proxy->property("icData").value<void *>());
173 
174     QObject *input = qApp->focusObject();
175     if (!input)
176         return;
177 
178     QInputMethodQueryEvent query(queries);
179     QGuiApplication::sendEvent(input, &query);
180 
181     if (queries & Qt::ImCursorRectangle) {
182         cursorRectChanged();
183     }
184 
185     if (queries & Qt::ImHints) {
186         Qt::InputMethodHints hints =
187             Qt::InputMethodHints(query.value(Qt::ImHints).toUInt());
188 
189 #define CHECK_HINTS(_HINTS, _CAPACITY)                                         \
190     if (hints & _HINTS)                                                        \
191         addCapability(data, _CAPACITY);                                        \
192     else                                                                       \
193         removeCapability(data, _CAPACITY);
194 
195         CHECK_HINTS(Qt::ImhHiddenText, CAPACITY_PASSWORD)
196         CHECK_HINTS(Qt::ImhNoAutoUppercase, CAPACITY_NOAUTOUPPERCASE)
197         CHECK_HINTS(Qt::ImhPreferNumbers, CAPACITY_NUMBER)
198         CHECK_HINTS(Qt::ImhPreferUppercase, CAPACITY_UPPERCASE)
199         CHECK_HINTS(Qt::ImhPreferLowercase, CAPACITY_LOWERCASE)
200         CHECK_HINTS(Qt::ImhNoPredictiveText, CAPACITY_NO_SPELLCHECK)
201         CHECK_HINTS(Qt::ImhDigitsOnly, CAPACITY_DIGIT)
202         CHECK_HINTS(Qt::ImhFormattedNumbersOnly, CAPACITY_NUMBER)
203         CHECK_HINTS(Qt::ImhUppercaseOnly, CAPACITY_UPPERCASE)
204         CHECK_HINTS(Qt::ImhLowercaseOnly, CAPACITY_LOWERCASE)
205         CHECK_HINTS(Qt::ImhDialableCharactersOnly, CAPACITY_DIALABLE)
206         CHECK_HINTS(Qt::ImhEmailCharactersOnly, CAPACITY_EMAIL)
207     }
208 
209     bool setSurrounding = false;
210     do {
211         if (!m_useSurroundingText)
212             break;
213         if (!((queries & Qt::ImSurroundingText) &&
214               (queries & Qt::ImCursorPosition)))
215             break;
216         if (data.capability.testFlag(CAPACITY_PASSWORD))
217             break;
218         QVariant var = query.value(Qt::ImSurroundingText);
219         QVariant var1 = query.value(Qt::ImCursorPosition);
220         QVariant var2 = query.value(Qt::ImAnchorPosition);
221         if (!var.isValid() || !var1.isValid())
222             break;
223         QString text = var.toString();
224 /* we don't want to waste too much memory here */
225 #define SURROUNDING_THRESHOLD 4096
226         if (text.length() < SURROUNDING_THRESHOLD) {
227             if (checkUtf8(text.toUtf8())) {
228                 addCapability(data, CAPACITY_SURROUNDING_TEXT);
229 
230                 int cursor = var1.toInt();
231                 int anchor;
232                 if (var2.isValid())
233                     anchor = var2.toInt();
234                 else
235                     anchor = cursor;
236 
237                 // adjust it to real character size
238                 QVector<uint> tempUCS4 = text.left(cursor).toUcs4();
239                 cursor = tempUCS4.size();
240                 tempUCS4 = text.left(anchor).toUcs4();
241                 anchor = tempUCS4.size();
242                 if (data.surroundingText != text) {
243                     data.surroundingText = text;
244                     proxy->setSurroundingText(text, cursor, anchor);
245                 } else {
246                     if (data.surroundingAnchor != anchor ||
247                         data.surroundingCursor != cursor)
248                         proxy->setSurroundingTextPosition(cursor, anchor);
249                 }
250                 data.surroundingCursor = cursor;
251                 data.surroundingAnchor = anchor;
252                 setSurrounding = true;
253             }
254         }
255         if (!setSurrounding) {
256             data.surroundingAnchor = -1;
257             data.surroundingCursor = -1;
258             data.surroundingText = QString();
259             removeCapability(data, CAPACITY_SURROUNDING_TEXT);
260         }
261     } while (0);
262 }
263 
commit()264 void QFcitxPlatformInputContext::commit() { QPlatformInputContext::commit(); }
265 
setFocusObject(QObject * object)266 void QFcitxPlatformInputContext::setFocusObject(QObject *object) {
267     Q_UNUSED(object);
268     FcitxInputContextProxy *proxy = validICByWindow(m_lastWindow);
269     commitPreedit(m_lastObject);
270     if (proxy) {
271         proxy->focusOut();
272     }
273 
274     QWindow *window = qApp->focusWindow();
275     m_lastWindow = window;
276     m_lastObject = object;
277     // Always create IC Data for window.
278     if (window) {
279         proxy = validICByWindow(window);
280         if (!proxy) {
281             createICData(window);
282         }
283     }
284     if (!window || (!inputMethodAccepted() && !objectAcceptsInputMethod())) {
285         m_lastWindow = nullptr;
286         m_lastObject = nullptr;
287         return;
288     }
289     if (proxy) {
290         proxy->focusIn();
291         // We need to delegate this otherwise it may cause self-recursion in
292         // certain application like libreoffice.
293         auto window = m_lastWindow;
294         QMetaObject::invokeMethod(
295             this,
296             [this, window]() {
297                 if (window != m_lastWindow) {
298                     return;
299                 }
300                 if (validICByWindow(window.data())) {
301                     cursorRectChanged();
302                 }
303             },
304             Qt::QueuedConnection);
305     }
306 }
307 
windowDestroyed(QObject * object)308 void QFcitxPlatformInputContext::windowDestroyed(QObject *object) {
309     /* access QWindow is not possible here, so we use our own map to do so */
310     m_icMap.erase(reinterpret_cast<QWindow *>(object));
311     // qDebug() << "Window Destroyed and we destroy IC correctly, horray!";
312 }
313 
cursorRectChanged()314 void QFcitxPlatformInputContext::cursorRectChanged() {
315     QWindow *inputWindow = qApp->focusWindow();
316     if (!inputWindow)
317         return;
318     FcitxInputContextProxy *proxy = validICByWindow(inputWindow);
319     if (!proxy)
320         return;
321 
322     FcitxQtICData &data = *static_cast<FcitxQtICData *>(
323         proxy->property("icData").value<void *>());
324 
325     QRect r = qApp->inputMethod()->cursorRectangle().toRect();
326     if (!r.isValid())
327         return;
328 
329     // not sure if this is necessary but anyway, qt's screen used to be buggy.
330     if (!inputWindow->screen()) {
331         return;
332     }
333 
334     if (data.capability & CAPACITY_RELATIVE_CURSOR_RECT) {
335         auto margins = inputWindow->frameMargins();
336         r.translate(margins.left(), margins.top());
337         if (data.rect != r) {
338             data.rect = r;
339             proxy->setCursorRect(r.x(), r.y(), r.width(), r.height());
340         }
341         return;
342     }
343     qreal scale = inputWindow->devicePixelRatio();
344     auto screenGeometry = inputWindow->screen()->geometry();
345     auto point = inputWindow->mapToGlobal(r.topLeft());
346     auto native =
347         (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft();
348     QRect newRect(native, r.size() * scale);
349 
350     if (data.rect != newRect) {
351         data.rect = newRect;
352         proxy->setCursorRect(newRect.x(), newRect.y(), newRect.width(),
353                              newRect.height());
354     }
355 }
356 
createInputContextFinished()357 void QFcitxPlatformInputContext::createInputContextFinished() {
358     FcitxInputContextProxy *proxy =
359         qobject_cast<FcitxInputContextProxy *>(sender());
360     if (!proxy) {
361         return;
362     }
363     auto w = static_cast<QWindow *>(proxy->property("wid").value<void *>());
364     FcitxQtICData *data =
365         static_cast<FcitxQtICData *>(proxy->property("icData").value<void *>());
366     data->rect = QRect();
367 
368     if (proxy->isValid()) {
369         QWindow *window = qApp->focusWindow();
370         if (window && window == w && inputMethodAccepted() &&
371             objectAcceptsInputMethod()) {
372             cursorRectChanged();
373             proxy->focusIn();
374         }
375     }
376 
377     QFlags<FcitxCapabilityFlags> flag;
378     flag |= CAPACITY_PREEDIT;
379     flag |= CAPACITY_FORMATTED_PREEDIT;
380     flag |= CAPACITY_CLIENT_UNFOCUS_COMMIT;
381     flag |= CAPACITY_GET_IM_INFO_ON_FOCUS;
382     m_useSurroundingText =
383         get_boolean_env("FCITX_QT_ENABLE_SURROUNDING_TEXT", true);
384     if (m_useSurroundingText) {
385         flag |= CAPACITY_SURROUNDING_TEXT;
386     }
387 
388     if (qApp && qApp->platformName() == "wayland") {
389         flag |= CAPACITY_RELATIVE_CURSOR_RECT;
390     }
391 
392     addCapability(*data, flag, true);
393 }
394 
updateCapability(const FcitxQtICData & data)395 void QFcitxPlatformInputContext::updateCapability(const FcitxQtICData &data) {
396     if (!data.proxy || !data.proxy->isValid())
397         return;
398 
399     QDBusPendingReply<void> result =
400         data.proxy->setCapability((uint)data.capability);
401 }
402 
commitString(const QString & str)403 void QFcitxPlatformInputContext::commitString(const QString &str) {
404     m_cursorPos = 0;
405     m_preeditList.clear();
406     m_commitPreedit.clear();
407     QObject *input = qApp->focusObject();
408     if (!input)
409         return;
410 
411     QInputMethodEvent event;
412     event.setCommitString(str);
413     QCoreApplication::sendEvent(input, &event);
414 }
415 
updateFormattedPreedit(const FcitxFormattedPreeditList & preeditList,int cursorPos)416 void QFcitxPlatformInputContext::updateFormattedPreedit(
417     const FcitxFormattedPreeditList &preeditList, int cursorPos) {
418     QObject *input = qApp->focusObject();
419     if (!input)
420         return;
421     if (cursorPos == m_cursorPos && preeditList == m_preeditList)
422         return;
423     m_preeditList = preeditList;
424     m_cursorPos = cursorPos;
425     QString str, commitStr;
426     int pos = 0;
427     QList<QInputMethodEvent::Attribute> attrList;
428 
429     // Fcitx 5's flags support.
430     enum TextFormatFlag : int {
431         TextFormatFlag_Underline = (1 << 3), /**< underline is a flag */
432         TextFormatFlag_HighLight = (1 << 4), /**< highlight the preedit */
433         TextFormatFlag_DontCommit = (1 << 5),
434         TextFormatFlag_Bold = (1 << 6),
435         TextFormatFlag_Strike = (1 << 7),
436         TextFormatFlag_Italic = (1 << 8),
437     };
438 
439     Q_FOREACH (const FcitxFormattedPreedit &preedit, preeditList) {
440         str += preedit.string();
441         if (!(preedit.format() & TextFormatFlag_DontCommit))
442             commitStr += preedit.string();
443         QTextCharFormat format;
444         if (preedit.format() & TextFormatFlag_Underline) {
445             format.setUnderlineStyle(QTextCharFormat::DashUnderline);
446         }
447         if (preedit.format() & TextFormatFlag_Strike) {
448             format.setFontStrikeOut(true);
449         }
450         if (preedit.format() & TextFormatFlag_Bold) {
451             format.setFontWeight(QFont::Bold);
452         }
453         if (preedit.format() & TextFormatFlag_Italic) {
454             format.setFontItalic(true);
455         }
456         if (preedit.format() & TextFormatFlag_HighLight) {
457             QBrush brush;
458             QPalette palette;
459             palette = QGuiApplication::palette();
460             format.setBackground(QBrush(
461                 QColor(palette.color(QPalette::Active, QPalette::Highlight))));
462             format.setForeground(QBrush(QColor(
463                 palette.color(QPalette::Active, QPalette::HighlightedText))));
464         }
465         attrList.append(
466             QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, pos,
467                                          preedit.string().length(), format));
468         pos += preedit.string().length();
469     }
470 
471     QByteArray array = str.toUtf8();
472     array.truncate(cursorPos);
473     cursorPos = QString::fromUtf8(array).length();
474 
475     attrList.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
476                                                  cursorPos, 1, 0));
477     m_preedit = str;
478     m_commitPreedit = commitStr;
479     QInputMethodEvent event(str, attrList);
480     QCoreApplication::sendEvent(input, &event);
481     update(Qt::ImCursorRectangle);
482 }
483 
deleteSurroundingText(int offset,uint _nchar)484 void QFcitxPlatformInputContext::deleteSurroundingText(int offset,
485                                                        uint _nchar) {
486     QObject *input = qApp->focusObject();
487     if (!input)
488         return;
489 
490     QInputMethodEvent event;
491 
492     FcitxInputContextProxy *proxy =
493         qobject_cast<FcitxInputContextProxy *>(sender());
494     if (!proxy) {
495         return;
496     }
497 
498     FcitxQtICData *data =
499         static_cast<FcitxQtICData *>(proxy->property("icData").value<void *>());
500     auto ucsText = data->surroundingText.toStdU32String();
501 
502     int cursor = data->surroundingCursor;
503     // make nchar signed so we are safer
504     int nchar = _nchar;
505     // Qt's reconvert semantics is different from gtk's. It doesn't count the
506     // current
507     // selection. Discard selection from nchar.
508     if (data->surroundingAnchor < data->surroundingCursor) {
509         nchar -= data->surroundingCursor - data->surroundingAnchor;
510         offset += data->surroundingCursor - data->surroundingAnchor;
511         cursor = data->surroundingAnchor;
512     } else if (data->surroundingAnchor > data->surroundingCursor) {
513         nchar -= data->surroundingAnchor - data->surroundingCursor;
514         cursor = data->surroundingCursor;
515     }
516 
517     // validates
518     if (nchar >= 0 && cursor + offset >= 0 &&
519         cursor + offset + nchar <= static_cast<int>(ucsText.size())) {
520         // order matters
521         auto replacedChars = ucsText.substr(cursor + offset, nchar);
522         nchar = QString::fromUcs4(replacedChars.data(), replacedChars.size())
523                     .size();
524 
525         int start, len;
526         if (offset >= 0) {
527             start = cursor;
528             len = offset;
529         } else {
530             start = cursor + offset;
531             len = -offset;
532         }
533 
534         auto prefixedChars = ucsText.substr(start, len);
535         offset = QString::fromUcs4(prefixedChars.data(), prefixedChars.size())
536                      .size() *
537                  (offset >= 0 ? 1 : -1);
538         event.setCommitString("", offset, nchar);
539         QCoreApplication::sendEvent(input, &event);
540     }
541 }
542 
forwardKey(uint keyval,uint state,bool type)543 void QFcitxPlatformInputContext::forwardKey(uint keyval, uint state,
544                                             bool type) {
545     auto proxy = qobject_cast<FcitxInputContextProxy *>(sender());
546     if (!proxy) {
547         return;
548     }
549     FcitxQtICData &data = *static_cast<FcitxQtICData *>(
550         proxy->property("icData").value<void *>());
551     auto w = static_cast<QWindow *>(proxy->property("wid").value<void *>());
552     QObject *input = qApp->focusObject();
553     auto window = qApp->focusWindow();
554     if (input && window && w == window) {
555         std::unique_ptr<QKeyEvent> keyevent{
556             createKeyEvent(keyval, state, type, data.event.get())};
557 
558         forwardEvent(window, *keyevent);
559     }
560 }
561 
updateCurrentIM(const QString & name,const QString & uniqueName,const QString & langCode)562 void QFcitxPlatformInputContext::updateCurrentIM(const QString &name,
563                                                  const QString &uniqueName,
564                                                  const QString &langCode) {
565     Q_UNUSED(name);
566     Q_UNUSED(uniqueName);
567     QLocale newLocale(langCode);
568     if (m_locale != newLocale) {
569         m_locale = newLocale;
570         emitLocaleChanged();
571     }
572 }
573 
locale() const574 QLocale QFcitxPlatformInputContext::locale() const { return m_locale; }
575 
createICData(QWindow * w)576 void QFcitxPlatformInputContext::createICData(QWindow *w) {
577     auto iter = m_icMap.find(w);
578     if (iter == m_icMap.end()) {
579         auto result =
580             m_icMap.emplace(std::piecewise_construct, std::forward_as_tuple(w),
581                             std::forward_as_tuple(m_watcher));
582         connect(w, &QObject::destroyed, this,
583                 &QFcitxPlatformInputContext::windowDestroyed);
584         iter = result.first;
585         auto &data = iter->second;
586 
587         if (QGuiApplication::platformName() == QLatin1String("xcb")) {
588             data.proxy->setDisplay("x11:");
589         } else if (QGuiApplication::platformName() ==
590                    QLatin1String("wayland")) {
591             data.proxy->setDisplay("wayland:");
592         }
593         data.proxy->setProperty("wid",
594                                 QVariant::fromValue(static_cast<void *>(w)));
595         data.proxy->setProperty(
596             "icData", QVariant::fromValue(static_cast<void *>(&data)));
597         connect(data.proxy, &FcitxInputContextProxy::inputContextCreated, this,
598                 &QFcitxPlatformInputContext::createInputContextFinished);
599         connect(data.proxy, &FcitxInputContextProxy::commitString, this,
600                 &QFcitxPlatformInputContext::commitString);
601         connect(data.proxy, &FcitxInputContextProxy::forwardKey, this,
602                 &QFcitxPlatformInputContext::forwardKey);
603         connect(data.proxy, &FcitxInputContextProxy::updateFormattedPreedit,
604                 this, &QFcitxPlatformInputContext::updateFormattedPreedit);
605         connect(data.proxy, &FcitxInputContextProxy::deleteSurroundingText,
606                 this, &QFcitxPlatformInputContext::deleteSurroundingText);
607         connect(data.proxy, &FcitxInputContextProxy::currentIM, this,
608                 &QFcitxPlatformInputContext::updateCurrentIM);
609     }
610 }
611 
createKeyEvent(uint keyval,uint state,bool isRelease,const QKeyEvent * event)612 QKeyEvent *QFcitxPlatformInputContext::createKeyEvent(uint keyval, uint state,
613                                                       bool isRelease,
614                                                       const QKeyEvent *event) {
615     QKeyEvent *newEvent = nullptr;
616     if (event && event->nativeVirtualKey() == keyval &&
617         event->nativeModifiers() == state &&
618         isRelease == (event->type() == QEvent::KeyRelease)) {
619 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
620         newEvent = new QKeyEvent(
621             event->type(), event->key(), event->modifiers(),
622             event->nativeScanCode(), event->nativeVirtualKey(),
623             event->nativeModifiers(), event->text(), event->isAutoRepeat(),
624             event->count(), event->device());
625 #else
626         newEvent = new QKeyEvent(*event);
627 #endif
628     } else {
629         Qt::KeyboardModifiers qstate = Qt::NoModifier;
630 
631         int count = 1;
632         if (state & FcitxKeyState_Alt) {
633             qstate |= Qt::AltModifier;
634             count++;
635         }
636 
637         if (state & FcitxKeyState_Shift) {
638             qstate |= Qt::ShiftModifier;
639             count++;
640         }
641 
642         if (state & FcitxKeyState_Ctrl) {
643             qstate |= Qt::ControlModifier;
644             count++;
645         }
646 
647         char32_t unicode = xkb_keysym_to_utf32(keyval);
648         QString text;
649         if (unicode) {
650             text = QString::fromUcs4(&unicode, 1);
651         }
652 
653         int key = keysymToQtKey(keyval, text);
654 
655         newEvent =
656             new QKeyEvent(isRelease ? (QEvent::KeyRelease) : (QEvent::KeyPress),
657                           key, qstate, 0, keyval, state, text, false, count);
658         if (event) {
659             newEvent->setTimestamp(event->timestamp());
660         }
661     }
662 
663     return newEvent;
664 }
665 
forwardEvent(QWindow * window,const QKeyEvent & keyEvent)666 void QFcitxPlatformInputContext::forwardEvent(QWindow *window,
667                                               const QKeyEvent &keyEvent) {
668     // use same variable name as in QXcbKeyboard::handleKeyEvent
669     QEvent::Type type = keyEvent.type();
670     int qtcode = keyEvent.key();
671     Qt::KeyboardModifiers modifiers = keyEvent.modifiers();
672     quint32 code = keyEvent.nativeScanCode();
673     quint32 sym = keyEvent.nativeVirtualKey();
674     quint32 state = keyEvent.nativeModifiers();
675     QString string = keyEvent.text();
676     bool isAutoRepeat = keyEvent.isAutoRepeat();
677     ulong time = keyEvent.timestamp();
678     // copied from QXcbKeyboard::handleKeyEvent()
679     if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu) {
680         QPoint globalPos, pos;
681         if (window->screen()) {
682             globalPos = window->screen()->handle()->cursor()->pos();
683             pos = window->mapFromGlobal(globalPos);
684         }
685         QWindowSystemInterface::handleContextMenuEvent(window, false, pos,
686                                                        globalPos, modifiers);
687     }
688     QWindowSystemInterface::handleExtendedKeyEvent(window, time, type, qtcode,
689                                                    modifiers, code, sym, state,
690                                                    string, isAutoRepeat);
691 }
692 
filterEvent(const QEvent * event)693 bool QFcitxPlatformInputContext::filterEvent(const QEvent *event) {
694     do {
695         if (event->type() != QEvent::KeyPress &&
696             event->type() != QEvent::KeyRelease) {
697             break;
698         }
699 
700         const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
701         quint32 keyval = keyEvent->nativeVirtualKey();
702         quint32 keycode = keyEvent->nativeScanCode();
703         quint32 state = keyEvent->nativeModifiers();
704         bool isRelease = keyEvent->type() == QEvent::KeyRelease;
705 
706         if (!inputMethodAccepted() && !objectAcceptsInputMethod())
707             break;
708 
709         QObject *input = qApp->focusObject();
710 
711         if (!input) {
712             break;
713         }
714 
715         FcitxInputContextProxy *proxy = validICByWindow(qApp->focusWindow());
716 
717         if (!proxy) {
718             if (filterEventFallback(keyval, keycode, state, isRelease)) {
719                 return true;
720             } else {
721                 break;
722             }
723         }
724 
725         proxy->focusIn();
726 
727         auto reply = proxy->processKeyEvent(keyval, keycode, state, isRelease,
728                                             keyEvent->timestamp());
729 
730         if (Q_UNLIKELY(m_syncMode)) {
731             reply.waitForFinished();
732 
733             auto filtered = proxy->processKeyEventResult(reply);
734             if (!filtered) {
735                 if (filterEventFallback(keyval, keycode, state, isRelease)) {
736                     return true;
737                 } else {
738                     break;
739                 }
740             } else {
741                 update(Qt::ImCursorRectangle);
742                 return true;
743             }
744         } else {
745             ProcessKeyWatcher *watcher = new ProcessKeyWatcher(
746                 *keyEvent, qApp->focusWindow(), reply, proxy);
747             connect(watcher, &QDBusPendingCallWatcher::finished, this,
748                     &QFcitxPlatformInputContext::processKeyEventFinished);
749             return true;
750         }
751     } while (0);
752     return QPlatformInputContext::filterEvent(event);
753 }
754 
processKeyEventFinished(QDBusPendingCallWatcher * w)755 void QFcitxPlatformInputContext::processKeyEventFinished(
756     QDBusPendingCallWatcher *w) {
757     ProcessKeyWatcher *watcher = static_cast<ProcessKeyWatcher *>(w);
758     auto proxy = qobject_cast<FcitxInputContextProxy *>(watcher->parent());
759     bool filtered = false;
760 
761     QWindow *window = watcher->window();
762     // if window is already destroyed, we can only throw this event away.
763     if (!window) {
764         delete watcher;
765         return;
766     }
767 
768     const QKeyEvent &keyEvent = watcher->keyEvent();
769 
770     // use same variable name as in QXcbKeyboard::handleKeyEvent
771     QEvent::Type type = keyEvent.type();
772     quint32 code = keyEvent.nativeScanCode();
773     quint32 sym = keyEvent.nativeVirtualKey();
774     quint32 state = keyEvent.nativeModifiers();
775     QString string = keyEvent.text();
776 
777     if (!proxy->processKeyEventResult(*watcher)) {
778         filtered =
779             filterEventFallback(sym, code, state, type == QEvent::KeyRelease);
780     } else {
781         filtered = true;
782     }
783 
784     if (!watcher->isError()) {
785         update(Qt::ImCursorRectangle);
786     }
787 
788     if (!filtered) {
789         forwardEvent(window, keyEvent);
790     } else {
791         auto proxy = qobject_cast<FcitxInputContextProxy *>(watcher->parent());
792         if (proxy) {
793             FcitxQtICData &data = *static_cast<FcitxQtICData *>(
794                 proxy->property("icData").value<void *>());
795 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
796             data.event = std::make_unique<QKeyEvent>(
797                 keyEvent.type(), keyEvent.key(), keyEvent.modifiers(),
798                 keyEvent.nativeScanCode(), keyEvent.nativeVirtualKey(),
799                 keyEvent.nativeModifiers(), keyEvent.text(),
800                 keyEvent.isAutoRepeat(), keyEvent.count(), keyEvent.device());
801 #else
802             data.event = std::make_unique<QKeyEvent>(keyEvent);
803 #endif
804         }
805     }
806 
807     delete watcher;
808 }
809 
filterEventFallback(uint keyval,uint keycode,uint state,bool isRelease)810 bool QFcitxPlatformInputContext::filterEventFallback(uint keyval, uint keycode,
811                                                      uint state,
812                                                      bool isRelease) {
813     Q_UNUSED(keycode);
814     if (processCompose(keyval, state, isRelease)) {
815         return true;
816     }
817     return false;
818 }
819 
validIC()820 FcitxInputContextProxy *QFcitxPlatformInputContext::validIC() {
821     if (m_icMap.empty()) {
822         return nullptr;
823     }
824     QWindow *window = qApp->focusWindow();
825     return validICByWindow(window);
826 }
827 
828 FcitxInputContextProxy *
validICByWindow(QWindow * w)829 QFcitxPlatformInputContext::validICByWindow(QWindow *w) {
830     if (!w) {
831         return nullptr;
832     }
833 
834     if (m_icMap.empty()) {
835         return nullptr;
836     }
837     auto iter = m_icMap.find(w);
838     if (iter == m_icMap.end())
839         return nullptr;
840     auto &data = iter->second;
841     if (!data.proxy || !data.proxy->isValid()) {
842         return nullptr;
843     }
844     return data.proxy;
845 }
846 
processCompose(uint keyval,uint state,bool isRelease)847 bool QFcitxPlatformInputContext::processCompose(uint keyval, uint state,
848                                                 bool isRelease) {
849     Q_UNUSED(state);
850 
851     if (!m_xkbComposeTable || isRelease)
852         return false;
853 
854     struct xkb_compose_state *xkbComposeState = m_xkbComposeState.data();
855 
856     enum xkb_compose_feed_result result =
857         xkb_compose_state_feed(xkbComposeState, keyval);
858     if (result == XKB_COMPOSE_FEED_IGNORED) {
859         return false;
860     }
861 
862     enum xkb_compose_status status =
863         xkb_compose_state_get_status(xkbComposeState);
864     if (status == XKB_COMPOSE_NOTHING) {
865         return 0;
866     } else if (status == XKB_COMPOSE_COMPOSED) {
867         char buffer[] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0'};
868         int length =
869             xkb_compose_state_get_utf8(xkbComposeState, buffer, sizeof(buffer));
870         xkb_compose_state_reset(xkbComposeState);
871         if (length != 0) {
872             commitString(QString::fromUtf8(buffer));
873         }
874 
875     } else if (status == XKB_COMPOSE_CANCELLED) {
876         xkb_compose_state_reset(xkbComposeState);
877     }
878 
879     return true;
880 }
881 
882 // kate: indent-mode cstyle; space-indent on; indent-width 0;
883