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