802 lines
25 KiB
C++
802 lines
25 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2011~2013 by CSSlayer *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program 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 General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
***************************************************************************/
|
|
|
|
#include <QKeyEvent>
|
|
#include <QDBusConnection>
|
|
#include <QGuiApplication>
|
|
#include <QInputMethod>
|
|
#include <QTextCharFormat>
|
|
#include <QPalette>
|
|
#include <QWindow>
|
|
#include <qpa/qplatformscreen.h>
|
|
#include <qpa/qplatformcursor.h>
|
|
#include <qpa/qwindowsysteminterface.h>
|
|
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
|
|
#include "keyserver_x11.h"
|
|
|
|
#include "qfcitxplatforminputcontext.h"
|
|
#include "fcitxqtinputcontextproxy.h"
|
|
#include "fcitxqtinputmethodproxy.h"
|
|
#include "fcitxqtconnection.h"
|
|
#include "keyuni.h"
|
|
#include "utils.h"
|
|
|
|
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();
|
|
|
|
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, _CAPACITY) \
|
|
if (hints & _HINTS) \
|
|
addCapacity(data, _CAPACITY); \
|
|
else \
|
|
removeCapacity(data, _CAPACITY);
|
|
|
|
CHECK_HINTS(Qt::ImhNoAutoUppercase, CAPACITY_NOAUTOUPPERCASE)
|
|
CHECK_HINTS(Qt::ImhPreferNumbers, CAPACITY_NUMBER)
|
|
CHECK_HINTS(Qt::ImhPreferUppercase, CAPACITY_UPPERCASE)
|
|
CHECK_HINTS(Qt::ImhPreferLowercase, CAPACITY_LOWERCASE)
|
|
CHECK_HINTS(Qt::ImhNoPredictiveText, CAPACITY_NO_SPELLCHECK)
|
|
CHECK_HINTS(Qt::ImhDigitsOnly, CAPACITY_DIGIT)
|
|
CHECK_HINTS(Qt::ImhFormattedNumbersOnly, CAPACITY_NUMBER)
|
|
CHECK_HINTS(Qt::ImhUppercaseOnly, CAPACITY_UPPERCASE)
|
|
CHECK_HINTS(Qt::ImhLowercaseOnly, CAPACITY_LOWERCASE)
|
|
CHECK_HINTS(Qt::ImhDialableCharactersOnly, CAPACITY_DIALABLE)
|
|
CHECK_HINTS(Qt::ImhEmailCharactersOnly, CAPACITY_EMAIL)
|
|
}
|
|
|
|
bool setSurrounding = false;
|
|
do {
|
|
if (!m_useSurroundingText)
|
|
break;
|
|
if (!((queries & Qt::ImSurroundingText) && (queries & Qt::ImCursorPosition)))
|
|
break;
|
|
if (data.capacity.testFlag(CAPACITY_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 (_utf8_check_string(text.toUtf8().data())) {
|
|
addCapacity(data, CAPACITY_SURROUNDING_TEXT);
|
|
|
|
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, CAPACITY_SURROUNDING_TEXT);
|
|
}
|
|
} 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());
|
|
QDBusPendingReply< int, bool, uint, uint, uint, uint > result = m_improxy->CreateICv3(info.fileName(), QCoreApplication::applicationPid());
|
|
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< int, bool, uint, uint, uint, uint > result = *watcher;
|
|
|
|
do {
|
|
if (result.isError()) {
|
|
break;
|
|
}
|
|
|
|
if (!m_connection->isConnected())
|
|
break;
|
|
|
|
int id = qdbus_cast<int>(result.argumentAt(0));
|
|
QString path = QString("/inputcontext_%1").arg(id);
|
|
if (data.proxy) {
|
|
delete data.proxy;
|
|
}
|
|
data.proxy = new FcitxQtInputContextProxy(m_connection->serviceName(), 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();
|
|
}
|
|
|
|
QFlags<FcitxCapacityFlags> flag;
|
|
flag |= CAPACITY_PREEDIT;
|
|
flag |= CAPACITY_FORMATTED_PREEDIT;
|
|
flag |= CAPACITY_CLIENT_UNFOCUS_COMMIT;
|
|
flag |= CAPACITY_GET_IM_INFO_ON_FOCUS;
|
|
m_useSurroundingText = get_boolean_env("FCITX_QT_ENABLE_SURROUNDING_TEXT", true);
|
|
if (m_useSurroundingText)
|
|
flag |= CAPACITY_SURROUNDING_TEXT;
|
|
|
|
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->SetCapacity((uint) data.capacity);
|
|
}
|
|
|
|
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 (!(preedit.format() & MSG_DONOT_COMMIT_WHEN_UNFOCUS))
|
|
commitStr += preedit.string();
|
|
QTextCharFormat format;
|
|
if ((preedit.format() & MSG_NOUNDERLINE) == 0) {
|
|
format.setUnderlineStyle(QTextCharFormat::DashUnderline);
|
|
}
|
|
if (preedit.format() & MSG_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, int type)
|
|
{
|
|
Qt::KeyboardModifiers qstate = Qt::NoModifier;
|
|
|
|
int count = 1;
|
|
if (state & FcitxKeyState_Alt) {
|
|
qstate |= Qt::AltModifier;
|
|
count ++;
|
|
}
|
|
|
|
if (state & FcitxKeyState_Shift) {
|
|
qstate |= Qt::ShiftModifier;
|
|
count ++;
|
|
}
|
|
|
|
if (state & FcitxKeyState_Ctrl) {
|
|
qstate |= Qt::ControlModifier;
|
|
count ++;
|
|
}
|
|
|
|
int key;
|
|
symToKeyQt(keyval, key);
|
|
|
|
QKeyEvent* keyevent = new QKeyEvent(
|
|
(type == FCITX_PRESS_KEY) ? (QEvent::KeyPress) : (QEvent::KeyRelease),
|
|
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 press = keyEvent->type() == QEvent::KeyPress;
|
|
|
|
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, press)) {
|
|
return true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
proxy->FocusIn();
|
|
|
|
QDBusPendingReply< int > reply = proxy->ProcessKeyEvent(keyval,
|
|
keycode,
|
|
state,
|
|
(press) ? FCITX_PRESS_KEY : FCITX_RELEASE_KEY,
|
|
QDateTime::currentDateTime().toTime_t());
|
|
|
|
|
|
if (Q_UNLIKELY(m_syncMode)) {
|
|
reply.waitForFinished();
|
|
|
|
if (!m_connection->isConnected() || !reply.isFinished() || reply.isError() || reply.value() <= 0) {
|
|
if (filterEventFallback(keyval, keycode, state, press)) {
|
|
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< int > 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() <= 0) {
|
|
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 press)
|
|
{
|
|
Q_UNUSED(keycode);
|
|
if (processCompose(keyval, state, (press) ? FCITX_PRESS_KEY : FCITX_RELEASE_KEY)) {
|
|
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, FcitxKeyEventType event)
|
|
{
|
|
Q_UNUSED(state);
|
|
|
|
if (!m_xkbComposeTable || event == FCITX_RELEASE_KEY)
|
|
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;
|