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