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