fcitx5-qt/qt5/platforminputcontext/qfcitxplatforminputcontext.cpp

872 lines
28 KiB
C++

/*
* Copyright (C) 2011~2017 by CSSlayer
* wengxt@gmail.com
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*/
#include <QDBusConnection>
#include <QGuiApplication>
#include <QInputMethod>
#include <QKeyEvent>
#include <QPalette>
#include <QTextCharFormat>
#include <QWindow>
#include <QX11Info>
#include <qpa/qplatformcursor.h>
#include <qpa/qplatformscreen.h>
#include <qpa/qwindowsysteminterface.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include "keyserver_x11.h"
#include "fcitxqtconnection.h"
#include "fcitxqtinputcontextproxy.h"
#include "fcitxqtinputmethodproxy.h"
#include "qfcitxplatforminputcontext.h"
#include <fcitx-utils/key.h>
#include <fcitx-utils/textformatflags.h>
#include <fcitx-utils/utf8.h>
#include <memory>
#include <xcb/xcb.h>
template <typename T>
using XCBReply = std::unique_ptr<T, decltype(&std::free)>;
template <typename T>
XCBReply<T> makeXCBReply(T *ptr) {
return {ptr, &std::free};
}
void setFocusGroupForX11(const QByteArray &uuid) {
if (uuid.size() != 16) {
return;
}
if (!QX11Info::isPlatformX11()) {
return;
}
auto connection = QX11Info::connection();
xcb_atom_t result = XCB_ATOM_NONE;
{
char atomName[] = "_FCITX_SERVER";
xcb_intern_atom_cookie_t cookie =
xcb_intern_atom(connection, false, strlen(atomName), atomName);
auto reply =
makeXCBReply(xcb_intern_atom_reply(connection, cookie, nullptr));
if (reply) {
result = reply->atom;
}
if (result == XCB_ATOM_NONE) {
return;
}
}
xcb_window_t owner = XCB_WINDOW_NONE;
{
auto cookie = xcb_get_selection_owner(connection, result);
auto reply = makeXCBReply(
xcb_get_selection_owner_reply(connection, cookie, nullptr));
if (reply) {
owner = reply->owner;
}
}
if (owner == XCB_WINDOW_NONE) {
return;
}
xcb_client_message_event_t ev;
memset(&ev, 0, sizeof(ev));
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = owner;
ev.type = result;
ev.format = 8;
memcpy(ev.data.data8, uuid.constData(), 16);
xcb_send_event(connection, false, owner, XCB_EVENT_MASK_NO_EVENT,
reinterpret_cast<char *>(&ev));
}
static bool key_filtered = false;
static bool get_boolean_env(const char *name, bool defval) {
const char *value = getenv(name);
if (value == nullptr)
return defval;
if (strcmp(value, "") == 0 || strcmp(value, "0") == 0 ||
strcmp(value, "false") == 0 || strcmp(value, "False") == 0 ||
strcmp(value, "FALSE") == 0)
return false;
return true;
}
static inline const char *get_locale() {
const char *locale = getenv("LC_ALL");
if (!locale)
locale = getenv("LC_CTYPE");
if (!locale)
locale = getenv("LANG");
if (!locale)
locale = "C";
return locale;
}
struct xkb_context *_xkb_context_new_helper() {
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (context) {
xkb_context_set_log_level(context, XKB_LOG_LEVEL_CRITICAL);
}
return context;
}
QFcitxPlatformInputContext::QFcitxPlatformInputContext()
: m_connection(new FcitxQtConnection(this)), m_improxy(nullptr),
m_cursorPos(0), m_useSurroundingText(false),
m_syncMode(get_boolean_env("FCITX_QT_USE_SYNC", false)), m_destroy(false),
m_xkbContext(_xkb_context_new_helper()),
m_xkbComposeTable(m_xkbContext ? xkb_compose_table_new_from_locale(
m_xkbContext.data(), get_locale(),
XKB_COMPOSE_COMPILE_NO_FLAGS)
: 0),
m_xkbComposeState(m_xkbComposeTable
? xkb_compose_state_new(m_xkbComposeTable.data(),
XKB_COMPOSE_STATE_NO_FLAGS)
: 0) {
FcitxQtFormattedPreedit::registerMetaType();
FcitxQtInputContextArgument::registerMetaType();
connect(m_connection, &FcitxQtConnection::connected, this,
&QFcitxPlatformInputContext::connected);
connect(m_connection, &FcitxQtConnection::disconnected, this,
&QFcitxPlatformInputContext::cleanUp);
m_connection->startConnection();
}
QFcitxPlatformInputContext::~QFcitxPlatformInputContext() {
m_destroy = true;
cleanUp();
}
void QFcitxPlatformInputContext::connected() {
if (!m_connection->isConnected())
return;
// qDebug() << "create Input Context" << m_connection->name();
if (m_improxy) {
delete m_improxy;
m_improxy = nullptr;
}
m_improxy = new FcitxQtInputMethodProxy(m_connection->serviceName(),
QLatin1String("/inputmethod"),
*m_connection->connection(), this);
QWindow *w = qApp->focusWindow();
if (w)
createICData(w);
}
void QFcitxPlatformInputContext::cleanUp() {
m_icMap.clear();
if (m_improxy) {
delete m_improxy;
m_improxy = nullptr;
}
if (!m_destroy) {
commitPreedit();
}
}
bool QFcitxPlatformInputContext::isValid() const { return true; }
void QFcitxPlatformInputContext::invokeAction(QInputMethod::Action action,
int cursorPosition) {
if (action == QInputMethod::Click &&
(cursorPosition <= 0 || cursorPosition >= m_preedit.length())) {
// qDebug() << action << cursorPosition;
commitPreedit();
}
}
void QFcitxPlatformInputContext::commitPreedit() {
QObject *input = qApp->focusObject();
if (!input)
return;
if (m_commitPreedit.length() <= 0)
return;
QInputMethodEvent e;
e.setCommitString(m_commitPreedit);
QCoreApplication::sendEvent(input, &e);
m_commitPreedit.clear();
}
void QFcitxPlatformInputContext::reset() {
commitPreedit();
FcitxQtInputContextProxy *proxy = validIC();
if (proxy)
proxy->Reset();
if (m_xkbComposeState) {
xkb_compose_state_reset(m_xkbComposeState.data());
}
QPlatformInputContext::reset();
}
void QFcitxPlatformInputContext::update(Qt::InputMethodQueries queries) {
QWindow *window = qApp->focusWindow();
FcitxQtInputContextProxy *proxy = validICByWindow(window);
if (!proxy)
return;
auto &data = m_icMap[window];
QObject *input = qApp->focusObject();
if (!input)
return;
QInputMethodQueryEvent query(queries);
QGuiApplication::sendEvent(input, &query);
if (queries & Qt::ImCursorRectangle) {
cursorRectChanged();
}
if (queries & Qt::ImHints) {
Qt::InputMethodHints hints =
Qt::InputMethodHints(query.value(Qt::ImHints).toUInt());
#define CHECK_HINTS(_HINTS, _CAPABILITY) \
if (hints & _HINTS) \
addCapacity(data, fcitx::CapabilityFlag::_CAPABILITY); \
else \
removeCapacity(data, fcitx::CapabilityFlag::_CAPABILITY);
CHECK_HINTS(Qt::ImhNoAutoUppercase, NoAutoUpperCase)
CHECK_HINTS(Qt::ImhPreferNumbers, Number)
CHECK_HINTS(Qt::ImhPreferUppercase, Uppercase)
CHECK_HINTS(Qt::ImhPreferLowercase, Lowercase)
CHECK_HINTS(Qt::ImhNoPredictiveText, NoSpellCheck)
CHECK_HINTS(Qt::ImhDigitsOnly, Digit)
CHECK_HINTS(Qt::ImhFormattedNumbersOnly, Number)
CHECK_HINTS(Qt::ImhUppercaseOnly, Uppercase)
CHECK_HINTS(Qt::ImhLowercaseOnly, Lowercase)
CHECK_HINTS(Qt::ImhDialableCharactersOnly, Dialable)
CHECK_HINTS(Qt::ImhEmailCharactersOnly, Email)
}
bool setSurrounding = false;
do {
if (!m_useSurroundingText)
break;
if (!((queries & Qt::ImSurroundingText) &&
(queries & Qt::ImCursorPosition)))
break;
if (data.capability.test(fcitx::CapabilityFlag::Password))
break;
QVariant var = query.value(Qt::ImSurroundingText);
QVariant var1 = query.value(Qt::ImCursorPosition);
QVariant var2 = query.value(Qt::ImAnchorPosition);
if (!var.isValid() || !var1.isValid())
break;
QString text = var.toString();
/* we don't want to waste too much memory here */
#define SURROUNDING_THRESHOLD 4096
if (text.length() < SURROUNDING_THRESHOLD) {
if (fcitx::utf8::validate(text.toUtf8().data())) {
addCapacity(data, fcitx::CapabilityFlag::SurroundingText);
int cursor = var1.toInt();
int anchor;
if (var2.isValid())
anchor = var2.toInt();
else
anchor = cursor;
// adjust it to real character size
QVector<uint> tempUCS4 = text.leftRef(cursor).toUcs4();
cursor = tempUCS4.size();
tempUCS4 = text.leftRef(anchor).toUcs4();
anchor = tempUCS4.size();
if (data.surroundingText != text) {
data.surroundingText = text;
proxy->SetSurroundingText(text, cursor, anchor);
} else {
if (data.surroundingAnchor != anchor ||
data.surroundingCursor != cursor)
proxy->SetSurroundingTextPosition(cursor, anchor);
}
data.surroundingCursor = cursor;
data.surroundingAnchor = anchor;
setSurrounding = true;
}
}
if (!setSurrounding) {
data.surroundingAnchor = -1;
data.surroundingCursor = -1;
data.surroundingText = QString::null;
removeCapacity(data, fcitx::CapabilityFlag::SurroundingText);
}
} while (0);
}
void QFcitxPlatformInputContext::commit() { QPlatformInputContext::commit(); }
void QFcitxPlatformInputContext::setFocusObject(QObject *object) {
Q_UNUSED(object);
FcitxQtInputContextProxy *proxy = validICByWindow(m_lastWindow);
if (proxy) {
proxy->FocusOut();
}
QWindow *window = qApp->focusWindow();
m_lastWindow = window;
if (!window) {
return;
}
proxy = validICByWindow(window);
if (proxy)
proxy->FocusIn();
else {
createICData(window);
}
}
void QFcitxPlatformInputContext::windowDestroyed(QObject *object) {
/* access QWindow is not possible here, so we use our own map to do so */
m_icMap.erase(reinterpret_cast<QWindow *>(object));
// qDebug() << "Window Destroyed and we destroy IC correctly, horray!";
}
void QFcitxPlatformInputContext::cursorRectChanged() {
QWindow *inputWindow = qApp->focusWindow();
if (!inputWindow)
return;
FcitxQtInputContextProxy *proxy = validICByWindow(inputWindow);
if (!proxy)
return;
auto &data = m_icMap[inputWindow];
QRect r = qApp->inputMethod()->cursorRectangle().toRect();
if (!r.isValid())
return;
r.moveTopLeft(inputWindow->mapToGlobal(r.topLeft()));
qreal scale = inputWindow->devicePixelRatio();
if (data.rect != r) {
data.rect = r;
proxy->SetCursorRect(r.x() * scale, r.y() * scale, r.width() * scale,
r.height() * scale);
}
}
void QFcitxPlatformInputContext::createInputContext(QWindow *w) {
if (!m_connection->isConnected())
return;
// qDebug() << "create Input Context" << m_connection->connection()->name();
if (!m_improxy) {
m_improxy = new FcitxQtInputMethodProxy(
m_connection->serviceName(), QLatin1String("/inputmethod"),
*m_connection->connection(), this);
}
if (!m_improxy->isValid())
return;
QFileInfo info(QCoreApplication::applicationFilePath());
FcitxQtInputContextArgumentList args;
args << FcitxQtInputContextArgument("program", info.fileName());
auto result = m_improxy->CreateInputContext(args);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(result);
watcher->setProperty("wid", qVariantFromValue(static_cast<void *>(w)));
connect(watcher, &QDBusPendingCallWatcher::finished, this,
&QFcitxPlatformInputContext::createInputContextFinished);
}
void QFcitxPlatformInputContext::createInputContextFinished(
QDBusPendingCallWatcher *watcher) {
auto w =
reinterpret_cast<QWindow *>(watcher->property("wid").value<void *>());
auto iter = m_icMap.find(w);
if (iter == m_icMap.end()) {
return;
}
auto &data = iter->second;
QDBusPendingReply<QDBusObjectPath, QByteArray> result = *watcher;
do {
if (result.isError()) {
break;
}
if (!m_connection->isConnected())
break;
auto objectPath = result.argumentAt<0>();
if (data.proxy) {
delete data.proxy;
}
data.proxy = new FcitxQtInputContextProxy(
m_connection->serviceName(), objectPath.path(),
*m_connection->connection(), this);
data.proxy->setProperty("icData",
qVariantFromValue(static_cast<void *>(&data)));
connect(data.proxy, &FcitxQtInputContextProxy::CommitString, this,
&QFcitxPlatformInputContext::commitString);
connect(data.proxy, &FcitxQtInputContextProxy::ForwardKey, this,
&QFcitxPlatformInputContext::forwardKey);
connect(data.proxy, &FcitxQtInputContextProxy::UpdateFormattedPreedit,
this, &QFcitxPlatformInputContext::updateFormattedPreedit);
connect(data.proxy, &FcitxQtInputContextProxy::DeleteSurroundingText,
this, &QFcitxPlatformInputContext::deleteSurroundingText);
connect(data.proxy, &FcitxQtInputContextProxy::CurrentIM, this,
&QFcitxPlatformInputContext::updateCurrentIM);
if (data.proxy->isValid()) {
QWindow *window = qApp->focusWindow();
if (window && window == w)
data.proxy->FocusIn();
}
setFocusGroupForX11(result.argumentAt<1>());
fcitx::CapabilityFlags flag;
flag |= fcitx::CapabilityFlag::Preedit;
flag |= fcitx::CapabilityFlag::FormattedPreedit;
flag |= fcitx::CapabilityFlag::ClientUnfocusCommit;
flag |= fcitx::CapabilityFlag::GetIMInfoOnFocus;
m_useSurroundingText =
get_boolean_env("FCITX_QT_ENABLE_SURROUNDING_TEXT", true);
if (m_useSurroundingText)
flag |= fcitx::CapabilityFlag::SurroundingText;
addCapacity(data, flag, true);
} while (0);
delete watcher;
}
void QFcitxPlatformInputContext::updateCapacity(const FcitxQtICData &data) {
if (!data.proxy || !data.proxy->isValid())
return;
QDBusPendingReply<void> result =
data.proxy->SetCapability((uint)data.capability);
}
void QFcitxPlatformInputContext::commitString(const QString &str) {
m_cursorPos = 0;
m_preeditList.clear();
m_commitPreedit.clear();
QObject *input = qApp->focusObject();
if (!input)
return;
QInputMethodEvent event;
event.setCommitString(str);
QCoreApplication::sendEvent(input, &event);
}
void QFcitxPlatformInputContext::updateFormattedPreedit(
const FcitxQtFormattedPreeditList &preeditList, int cursorPos) {
QObject *input = qApp->focusObject();
if (!input)
return;
if (cursorPos == m_cursorPos && preeditList == m_preeditList)
return;
m_preeditList = preeditList;
m_cursorPos = cursorPos;
QString str, commitStr;
int pos = 0;
QList<QInputMethodEvent::Attribute> attrList;
Q_FOREACH (const FcitxQtFormattedPreedit &preedit, preeditList) {
str += preedit.string();
if (!(fcitx::TextFormatFlags(preedit.format()) &
fcitx::TextFormatFlag::DontCommit))
commitStr += preedit.string();
QTextCharFormat format;
if (fcitx::TextFormatFlags(preedit.format()) &
fcitx::TextFormatFlag::UnderLine) {
format.setUnderlineStyle(QTextCharFormat::DashUnderline);
}
if (fcitx::TextFormatFlags(preedit.format()) &
fcitx::TextFormatFlag::HighLight) {
QBrush brush;
QPalette palette;
palette = QGuiApplication::palette();
format.setBackground(QBrush(
QColor(palette.color(QPalette::Active, QPalette::Highlight))));
format.setForeground(QBrush(QColor(
palette.color(QPalette::Active, QPalette::HighlightedText))));
}
attrList.append(
QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, pos,
preedit.string().length(), format));
pos += preedit.string().length();
}
QByteArray array = str.toUtf8();
array.truncate(cursorPos);
cursorPos = QString::fromUtf8(array).length();
attrList.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
cursorPos, 1, 0));
m_preedit = str;
m_commitPreedit = commitStr;
QInputMethodEvent event(str, attrList);
QCoreApplication::sendEvent(input, &event);
update(Qt::ImCursorRectangle);
}
void QFcitxPlatformInputContext::deleteSurroundingText(int offset,
uint _nchar) {
QObject *input = qApp->focusObject();
if (!input)
return;
QInputMethodEvent event;
FcitxQtInputContextProxy *proxy =
qobject_cast<FcitxQtInputContextProxy *>(sender());
if (!proxy) {
return;
}
FcitxQtICData *data =
static_cast<FcitxQtICData *>(proxy->property("icData").value<void *>());
QVector<uint> ucsText = data->surroundingText.toUcs4();
int cursor = data->surroundingCursor;
// make nchar signed so we are safer
int nchar = _nchar;
// Qt's reconvert semantics is different from gtk's. It doesn't count the
// current
// selection. Discard selection from nchar.
if (data->surroundingAnchor < data->surroundingCursor) {
nchar -= data->surroundingCursor - data->surroundingAnchor;
offset += data->surroundingCursor - data->surroundingAnchor;
cursor = data->surroundingAnchor;
} else if (data->surroundingAnchor > data->surroundingCursor) {
nchar -= data->surroundingAnchor - data->surroundingCursor;
cursor = data->surroundingCursor;
}
// validates
if (nchar >= 0 && cursor + offset >= 0 &&
cursor + offset + nchar < ucsText.size()) {
// order matters
QVector<uint> replacedChars = ucsText.mid(cursor + offset, nchar);
nchar = QString::fromUcs4(replacedChars.data(), replacedChars.size())
.size();
int start, len;
if (offset >= 0) {
start = cursor;
len = offset;
} else {
start = cursor;
len = -offset;
}
QVector<uint> prefixedChars = ucsText.mid(start, len);
offset = QString::fromUcs4(prefixedChars.data(), prefixedChars.size())
.size() *
(offset >= 0 ? 1 : -1);
event.setCommitString("", offset, nchar);
QCoreApplication::sendEvent(input, &event);
}
}
void QFcitxPlatformInputContext::forwardKey(uint keyval, uint state, int type) {
QObject *input = qApp->focusObject();
if (input != nullptr) {
key_filtered = true;
QKeyEvent *keyevent = createKeyEvent(keyval, state, type);
QCoreApplication::sendEvent(input, keyevent);
delete keyevent;
key_filtered = false;
}
}
void QFcitxPlatformInputContext::updateCurrentIM(const QString &name,
const QString &uniqueName,
const QString &langCode) {
Q_UNUSED(name);
Q_UNUSED(uniqueName);
QLocale newLocale(langCode);
if (m_locale != newLocale) {
m_locale = newLocale;
emitLocaleChanged();
}
}
QLocale QFcitxPlatformInputContext::locale() const { return m_locale; }
void QFcitxPlatformInputContext::createICData(QWindow *w) {
auto iter = m_icMap.find(w);
if (iter == m_icMap.end()) {
m_icMap.emplace(std::piecewise_construct, std::forward_as_tuple(w),
std::forward_as_tuple());
connect(w, &QObject::destroyed, this,
&QFcitxPlatformInputContext::windowDestroyed);
}
createInputContext(w);
}
QKeyEvent *QFcitxPlatformInputContext::createKeyEvent(uint keyval, uint _state,
bool isRelease) {
Qt::KeyboardModifiers qstate = Qt::NoModifier;
fcitx::KeyStates state(_state);
int count = 1;
if (state & fcitx::KeyState::Alt) {
qstate |= Qt::AltModifier;
count++;
}
if (state & fcitx::KeyState::Shift) {
qstate |= Qt::ShiftModifier;
count++;
}
if (state & fcitx::KeyState::Ctrl) {
qstate |= Qt::ControlModifier;
count++;
}
int key;
symToKeyQt(keyval, key);
QKeyEvent *keyevent =
new QKeyEvent(isRelease ? (QEvent::KeyRelease) : (QEvent::KeyPress),
key, qstate, QString(), false, count);
return keyevent;
}
bool QFcitxPlatformInputContext::filterEvent(const QEvent *event) {
do {
if (event->type() != QEvent::KeyPress &&
event->type() != QEvent::KeyRelease) {
break;
}
const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
quint32 keyval = keyEvent->nativeVirtualKey();
quint32 keycode = keyEvent->nativeScanCode();
quint32 state = keyEvent->nativeModifiers();
bool isRelease = keyEvent->type() == QEvent::KeyRelease;
if (key_filtered) {
break;
}
if (!inputMethodAccepted())
break;
QObject *input = qApp->focusObject();
if (!input) {
break;
}
FcitxQtInputContextProxy *proxy = validICByWindow(qApp->focusWindow());
if (!proxy) {
if (filterEventFallback(keyval, keycode, state, isRelease)) {
return true;
} else {
break;
}
}
proxy->FocusIn();
auto reply =
proxy->ProcessKeyEvent(keyval, keycode, state, isRelease,
QDateTime::currentDateTime().toTime_t());
if (Q_UNLIKELY(m_syncMode)) {
reply.waitForFinished();
if (!m_connection->isConnected() || !reply.isFinished() ||
reply.isError() || !reply.value()) {
if (filterEventFallback(keyval, keycode, state, isRelease)) {
return true;
} else {
break;
}
} else {
update(Qt::ImCursorRectangle);
return true;
}
} else {
ProcessKeyWatcher *watcher = new ProcessKeyWatcher(
*keyEvent, qApp->focusWindow(), reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this,
&QFcitxPlatformInputContext::processKeyEventFinished);
return true;
}
} while (0);
return QPlatformInputContext::filterEvent(event);
}
void QFcitxPlatformInputContext::processKeyEventFinished(
QDBusPendingCallWatcher *w) {
ProcessKeyWatcher *watcher = static_cast<ProcessKeyWatcher *>(w);
QDBusPendingReply<bool> result(*watcher);
bool filtered = false;
QWindow *window = watcher->window();
// if window is already destroyed, we can only throw this event away.
if (!window) {
return;
}
const QKeyEvent &keyEvent = watcher->keyEvent();
// use same variable name as in QXcbKeyboard::handleKeyEvent
QEvent::Type type = keyEvent.type();
int qtcode = keyEvent.key();
Qt::KeyboardModifiers modifiers = keyEvent.modifiers();
quint32 code = keyEvent.nativeScanCode();
quint32 sym = keyEvent.nativeVirtualKey();
quint32 state = keyEvent.nativeModifiers();
QString string = keyEvent.text();
bool isAutoRepeat = keyEvent.isAutoRepeat();
ulong time = keyEvent.timestamp();
if (result.isError() || !result.value()) {
filtered =
filterEventFallback(sym, code, state, type == QEvent::KeyPress);
} else {
filtered = true;
}
if (!result.isError()) {
update(Qt::ImCursorRectangle);
}
if (!filtered) {
// copied from QXcbKeyboard::handleKeyEvent()
if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu) {
QPoint globalPos, pos;
if (window->screen()) {
globalPos = window->screen()->handle()->cursor()->pos();
pos = window->mapFromGlobal(globalPos);
}
QWindowSystemInterface::handleContextMenuEvent(
window, false, pos, globalPos, modifiers);
}
QWindowSystemInterface::handleExtendedKeyEvent(
window, time, type, qtcode, modifiers, code, sym, state, string,
isAutoRepeat);
}
delete watcher;
}
bool QFcitxPlatformInputContext::filterEventFallback(uint keyval, uint keycode,
uint state,
bool isRelease) {
Q_UNUSED(keycode);
if (processCompose(keyval, state, isRelease)) {
return true;
}
return false;
}
FcitxQtInputContextProxy *QFcitxPlatformInputContext::validIC() {
if (m_icMap.empty()) {
return nullptr;
}
QWindow *window = qApp->focusWindow();
return validICByWindow(window);
}
FcitxQtInputContextProxy *
QFcitxPlatformInputContext::validICByWindow(QWindow *w) {
if (!w) {
return nullptr;
}
if (m_icMap.empty()) {
return nullptr;
}
auto iter = m_icMap.find(w);
if (iter == m_icMap.end())
return nullptr;
auto &data = iter->second;
if (!data.proxy || !data.proxy->isValid()) {
return nullptr;
}
return data.proxy;
}
bool QFcitxPlatformInputContext::processCompose(uint keyval, uint state,
bool isRelease) {
Q_UNUSED(state);
if (!m_xkbComposeTable || isRelease)
return false;
struct xkb_compose_state *xkbComposeState = m_xkbComposeState.data();
enum xkb_compose_feed_result result =
xkb_compose_state_feed(xkbComposeState, keyval);
if (result == XKB_COMPOSE_FEED_IGNORED) {
return false;
}
enum xkb_compose_status status =
xkb_compose_state_get_status(xkbComposeState);
if (status == XKB_COMPOSE_NOTHING) {
return 0;
} else if (status == XKB_COMPOSE_COMPOSED) {
char buffer[] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0'};
int length =
xkb_compose_state_get_utf8(xkbComposeState, buffer, sizeof(buffer));
xkb_compose_state_reset(xkbComposeState);
if (length != 0) {
commitString(QString::fromUtf8(buffer));
}
} else if (status == XKB_COMPOSE_CANCELLED) {
xkb_compose_state_reset(xkbComposeState);
}
return true;
}
// kate: indent-mode cstyle; space-indent on; indent-width 0;