Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "xt9inputmethodprivate_p.h"
#include "xt9inputmethod_p.h"
#include "xt9languagemap.h"
#include "xt9dbfile.h"
#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
#include <QtVirtualKeyboard/qvirtualkeyboarddictionary.h>
#include <QtVirtualKeyboard/private/settings_p.h>
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {
Xt9InputMethodPrivate::Xt9InputMethodPrivate(Xt9InputMethod *q_ptr, Xt9Ime *xt9Ime, const QString &aDlmFileName) :
QVirtualKeyboardAbstractInputMethodPrivate(),
q_ptr(q_ptr),
_xt9Ime(xt9Ime),
inputMode(QVirtualKeyboardInputEngine::InputMode::Latin),
selectionListUpdateCount(0),
defaultListIndex(ET9_NO_ACTIVE_INDEX),
autoSpaceAllowed(false),
initDone(false),
dlmFileName(aDlmFileName)
{
#ifdef HAVE_XT9_RESOURCE
Q_INIT_RESOURCE(qmake_cerencecommondata_db);
#endif
}
void Xt9InputMethodPrivate::sysInit()
{
xt9Ime()->sysInit();
bindToSettings();
xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
xt9Ime()->removeAllIndexes();
}
bool Xt9InputMethodPrivate::init(QLocale aLocale, QVirtualKeyboardInputEngine::InputMode aInputMode)
{
initDone = true;
xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
bindToDictionaryManager();
bindToSettings();
bindToKeyboard();
this->locale = aLocale;
this->inputMode = aInputMode;
updateLdb();
updateShiftState();
updateLayout();
updateDlm();
updateDynamicDictionaries();
return true;
}
void Xt9InputMethodPrivate::uninit()
{
Q_Q(Xt9InputMethod);
initDone = false;
QObject::disconnect(availableDictionariesChangedConnection);
QObject::disconnect(activeDictionariesChangedConnection);
QObject::disconnect(userDataPathChangedConnection);
QObject::disconnect(userDataResetConnection);
QObject::disconnect(layoutChangedConnection);
QObject::disconnect(shiftActiveChangedConnection);
QObject::disconnect(capsLockActiveChangedConnection);
QObject::disconnect(inputMethodHintsChangedConnection);
dlmDeactivate();
removeAllDynamicDictionaries();
xt9Ime()->uninit();
}
void Xt9InputMethodPrivate::bindToDictionaryManager()
{
if (!availableDictionariesChangedConnection)
availableDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
&QVirtualKeyboardDictionaryManager::availableDictionariesChanged,
this, &Xt9InputMethodPrivate::onAvailableDynamicDictionariesChanged);
if (!activeDictionariesChangedConnection)
activeDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
&QVirtualKeyboardDictionaryManager::activeDictionariesChanged,
this, &Xt9InputMethodPrivate::onActiveDynamicDictionariesChanged);
}
void Xt9InputMethodPrivate::bindToSettings()
{
if (!userDataPathChangedConnection)
userDataPathChangedConnection = QObjectPrivate::connect(
Settings::instance(), &Settings::userDataPathChanged,
this, &Xt9InputMethodPrivate::onUserDataPathChanged);
if (!userDataResetConnection)
userDataResetConnection = QObjectPrivate::connect(
Settings::instance(), &Settings::userDataReset,
this, &Xt9InputMethodPrivate::onUserDataReset);
}
void Xt9InputMethodPrivate::bindToKeyboard()
{
Q_Q(Xt9InputMethod);
QVirtualKeyboardInputContext *ic = q->inputContext();
if (!ic)
return;
if (!layoutChangedConnection)
layoutChangedConnection = QObjectPrivate::connect(
ic->keyboardObserver(), &QVirtualKeyboardObserver::layoutChanged,
this, &Xt9InputMethodPrivate::updateLayout);
if (!shiftActiveChangedConnection)
shiftActiveChangedConnection = QObjectPrivate::connect(
ic, &QVirtualKeyboardInputContext::shiftActiveChanged,
this, &Xt9InputMethodPrivate::updateShiftState);
if (!capsLockActiveChangedConnection)
capsLockActiveChangedConnection = QObjectPrivate::connect(
ic, &QVirtualKeyboardInputContext::capsLockActiveChanged,
this, &Xt9InputMethodPrivate::updateShiftState);
if (!inputMethodHintsChangedConnection)
inputMethodHintsChangedConnection = QObjectPrivate::connect(
ic, &QVirtualKeyboardInputContext::inputMethodHintsChanged,
this, &Xt9InputMethodPrivate::updateDlm);
}
void Xt9InputMethodPrivate::dlmActivate()
{
const QString userDataPath = Settings::instance()->userDataPath();
const QString dlmFile = QStringLiteral("%1/%2").arg(userDataPath).arg(dlmFileName);
if (dlm && dlm->fileName() != dlmFile)
dlmDeactivate();
if (!dlm) {
if (!userDataPath.isEmpty()) {
qCDebug(lcXT9) << "dlmActivate" << dlmFile;
dlm.reset(new Xt9DbFile(dlmFile));
void *data = dlm->rwData(xt9Ime()->dlmPreferredSize());
qint64 size = dlm->size();
if (data != nullptr && size > 0) {
if (!xt9Ime()->dlmInit(data, size)) {
qCWarning(lcXT9) << "Failed to init DLM file - " << dlmFile;
dlm.reset();
}
} else {
qCWarning(lcXT9) << "Failed to open DLM file - " << dlmFile;
dlm.reset();
}
}
}
}
void Xt9InputMethodPrivate::dlmDeactivate()
{
if (dlm) {
qCDebug(lcXT9) << "dlmDeactivate";
xt9Ime()->dlmInit(nullptr, 0);
dlm.reset();
}
}
void Xt9InputMethodPrivate::updateLdb()
{
ET9U32 dwFirstLdbNum = Xt9LanguageMap::languageId(locale);
ET9U32 eInputMode = inputModeToET9InputMode(inputMode);
xt9Ime()->ldbInit(dwFirstLdbNum, ET9PLIDNone, eInputMode);
}
void Xt9InputMethodPrivate::updateDlm()
{
Q_Q(Xt9InputMethod);
QVirtualKeyboardInputContext *ic = q->inputContext();
if (ic == nullptr)
return;
const Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
if (!inputMethodHints.testFlag(Qt::ImhHiddenText) && !inputMethodHints.testFlag(Qt::ImhSensitiveData))
dlmActivate();
else
dlmDeactivate();
}
void Xt9InputMethodPrivate::removeAllDynamicDictionaries()
{
Q_Q(Xt9InputMethod);
xt9Ime()->removeAllIndexes();
QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
for (const QString &dictionaryName : availableDictionaries) {
QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
dictionary->disconnect(q); // lambdas
}
attachedDynamicDictionaries.clear();
dynamicDictionaries.clear();
dynamicDictionaryNextId = 0;
}
void Xt9InputMethodPrivate::updateDynamicDictionaries()
{
onAvailableDynamicDictionariesChanged();
onActiveDynamicDictionariesChanged();
}
void Xt9InputMethodPrivate::updateShiftState()
{
Q_Q(Xt9InputMethod);
QVirtualKeyboardInputContext *ic = q->inputContext();
if (ic) {
if (ic->isCapsLockActive())
xt9Ime()->setCapsLock();
else if (ic->isShiftActive())
xt9Ime()->setShift();
else
xt9Ime()->setUnShift();
}
}
bool Xt9InputMethodPrivate::updateLayout()
{
Q_Q(Xt9InputMethod);
QVirtualKeyboardInputContext *ic = q->inputContext();
if (ic == nullptr)
return false;
QVariantMap vkbLayout = ic->keyboardObserver()->layout().toMap();
ET9U32 dwFirstLdbNum = Xt9LanguageMap::languageId(locale);
vkbLayout[Xt9KeyboardGenerator::PRIMARY_ID] = static_cast<int>(dwFirstLdbNum & ET9PLIDMASK);
vkbLayout[Xt9KeyboardGenerator::SECONDARY_ID] = static_cast<int>(ET9SKIDNone);
return xt9Ime()->kdbInit(vkbLayout);
}
void Xt9InputMethodPrivate::updatePreeditText()
{
Q_Q(Xt9InputMethod);
QString exactWord = xt9Ime()->exactWord();
q->inputContext()->setPreeditText(exactWord);
}
void Xt9InputMethodPrivate::buildSelectionList()
{
ET9STATUS eStatus = ET9STATUS_NONE;
buildSelectionList(eStatus);
}
void Xt9InputMethodPrivate::buildSelectionList(ET9STATUS &eStatus)
{
ET9U16 gestureValue;
Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
eStatus = ET9STATUS_NONE;
if (xt9Ime()->exactWord().isEmpty()) {
// Check if next word prediction is not allowed
Q_Q(Xt9InputMethod);
const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
if (inputMethodHints.testFlag(Qt::ImhHiddenText) || inputMethodHints.testFlag(Qt::ImhNoPredictiveText)) {
selectionList.clear();
defaultListIndex = -1;
return;
}
// Ensure the buffer context is up-to-date for next word prediction
xt9Ime()->cursorMoved();
}
selectionList = xt9Ime()->buildSelectionList(&defaultListIndex, &gestureValue, eStatus);
}
void Xt9InputMethodPrivate::selectionListUpdate()
{
Q_Q(Xt9InputMethod);
emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, defaultListIndex);
}
void Xt9InputMethodPrivate::updatePunctuationBreaking()
{
Q_Q(Xt9InputMethod);
Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
bool enabled = !inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) &&
!inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly);
if (enabled)
XT9_API(ET9SetPunctuationBreaking, &xt9Ime()->sWordSymbInfo);
else
XT9_API(ET9ClearPunctuationBreaking, &xt9Ime()->sWordSymbInfo);
}
bool Xt9InputMethodPrivate::processBackspace()
{
if (XT9_API(ET9ClearOneSymb, &xt9Ime()->sWordSymbInfo))
return false;
buildSelectionList();
updatePreeditText();
return true;
}
bool Xt9InputMethodPrivate::processKeyBySymbol(const QChar &symbol)
{
QString exactWord = xt9Ime()->exactWord();
bool addToWord = isValidInputChar(symbol) && (!exactWord.isEmpty() || !isJoiner(symbol));
if (!addToWord) {
Q_Q(Xt9InputMethod);
q->update();
return false;
}
if (maybeInsertSpaceBeforeNextInputSymbol(symbol)) {
Q_Q(Xt9InputMethod);
q->update();
q->inputContext()->commit(QLatin1String(" "));
}
if (exactWord.isEmpty()) {
updatePunctuationBreaking();
updateShiftState();
xt9Ime()->cursorMoved();
}
ET9STATUS eStatus;
ET9SYMB functionKey = 0;
const ET9U8 currIndexInList = defaultListIndex < 0 ?
ET9_NO_ACTIVE_INDEX : static_cast<ET9U8>(defaultListIndex);
const ET9BOOL bInitialSymCheck = 1;
ET9U32 dwTimeMS = static_cast<ET9U32>(QDateTime::currentMSecsSinceEpoch());
eStatus = XT9_API(ET9KDB_ProcessKeyBySymbol,
&xt9Ime()->sKdbInfo,
symbol.unicode(),
dwTimeMS,
currIndexInList,
&functionKey,
bInitialSymCheck);
const bool noKey = eStatus == ET9STATUS_NO_KEY;
if (noKey) {
const ET9INPUTSHIFTSTATE eShiftState = ET9SHIFT_STATE(&xt9Ime()->sWordSymbInfo);
eStatus = XT9_API(ET9AddExplicitSymb, &xt9Ime()->sWordSymbInfo, symbol.unicode(), dwTimeMS, eShiftState, 0);
} else if (eStatus == ET9STATUS_FULL) {
/*
Reject input when buffer is full. If we would return false,
the input would be added as an explicit symbol to text editor,
which is not what is wanted.
*/
return true;
} else if (eStatus) {
return false;
}
Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
buildSelectionList(eStatus);
if (eStatus == ET9STATUS_INVALID_INPUT) {
/*
The symbol rejected as an invalid input:
1. Remove the symbol and rebuild selection list
2. Select the default candidate from selection list and finalize input (update)
3. Start new input with the symbol
*/
XT9_API(ET9ClearOneSymb, &xt9Ime()->sWordSymbInfo);
buildSelectionList(eStatus);
Q_Q(Xt9InputMethod);
q->update();
dwTimeMS = static_cast<ET9U32>(QDateTime::currentMSecsSinceEpoch());
if (noKey) {
const ET9INPUTSHIFTSTATE eShiftState = ET9SHIFT_STATE(&xt9Ime()->sWordSymbInfo);
XT9_API(ET9AddExplicitSymb,
&xt9Ime()->sWordSymbInfo,
symbol.unicode(),
dwTimeMS,
eShiftState, 0);
} else {
XT9_API(ET9KDB_ProcessKeyBySymbol,
&xt9Ime()->sKdbInfo,
symbol.unicode(),
dwTimeMS,
ET9_NO_ACTIVE_INDEX,
&functionKey,
bInitialSymCheck);
}
buildSelectionList();
}
updatePreeditText();
return true;
}
bool Xt9InputMethodPrivate::maybeInsertSpaceBeforeNextInputSymbol(QChar symbol) const
{
Q_UNUSED(symbol)
QString exactWord = xt9Ime()->exactWord();
if (exactWord.isEmpty()) {
Q_Q(const Xt9InputMethod);
if (QVirtualKeyboardInputContext *ic = q->inputContext()) {
const QString surroundingText = ic->surroundingText();
const int cursorPosition = ic->cursorPosition();
if (!surroundingText.isEmpty() && cursorPosition == surroundingText.length()) {
QChar lastChar = surroundingText.at(cursorPosition - 1);
if (!lastChar.isSpace() && lastChar != QChar(Qt::Key_Minus) && isAutoSpaceAllowed())
return symbol.isLetterOrNumber();
}
}
}
return false;
}
void Xt9InputMethodPrivate::setAutoSpaceAllowed(bool value)
{
if (autoSpaceAllowed == value)
return;
autoSpaceAllowed = value;
qCDebug(lcXT9) << "setAutoSpaceAllowed():" << value;
}
bool Xt9InputMethodPrivate::isAutoSpaceAllowed() const
{
Q_Q(const Xt9InputMethod);
if (!autoSpaceAllowed)
return false;
if (q->inputEngine()->inputMode() == QVirtualKeyboardInputEngine::InputMode::Numeric)
return false;
Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
return !inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) &&
!inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly);
}
bool Xt9InputMethodPrivate::isValidInputChar(const QChar &c) const
{
if (c.isLetterOrNumber())
return true;
if (isJoiner(c))
return true;
if (c.isMark())
return true;
return false;
}
bool Xt9InputMethodPrivate::isJoiner(const QChar &c) const
{
if (c.isPunct() || c.isSymbol()) {
Q_Q(const Xt9InputMethod);
if (QVirtualKeyboardInputContext *ic = q->inputContext()) {
Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) || inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly))
return QStringView(u":/?#[]@!$&'()*+,;=-_.%").contains(c);
}
}
return false;
}
void Xt9InputMethodPrivate::setShiftState(bool shift, bool caps)
{
if (caps)
xt9Ime()->setCapsLock();
else if (shift)
xt9Ime()->setShift();
else
xt9Ime()->setUnShift();
}
void Xt9InputMethodPrivate::reset()
{
xt9Ime()->clearInput();
if (!selectionList.isEmpty() || defaultListIndex != -1) {
Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
selectionList.clear();
defaultListIndex = -1;
}
setAutoSpaceAllowed(false);
}
void Xt9InputMethodPrivate::onAvailableDynamicDictionariesChanged()
{
Q_Q(Xt9InputMethod);
QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
for (const QString &dictionaryName : availableDictionaries) {
if (!dynamicDictionaries.contains(dictionaryName)) {
QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
const quint16 id = static_cast<quint16>(dynamicDictionaryNextId.fetchAndAddRelaxed(1));
dynamicDictionaries[dictionaryName] = id;
xt9Ime()->updateIndex(id, dictionary->contents());
q->connect(dictionary, &QVirtualKeyboardDictionary::contentsChanged, q, [=]() {
xt9Ime()->updateIndex(id, dictionary->contents());
if (attachedDynamicDictionaries.contains(dictionaryName))
xt9Ime()->mountIndex(id);
});
}
}
}
void Xt9InputMethodPrivate::onActiveDynamicDictionariesChanged()
{
QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
// Attach
const QStringList activeDictionaries = dictionaryManager->activeDictionaries();
for (const QString &dictionaryName : activeDictionaries) {
if (!attachedDynamicDictionaries.contains(dictionaryName)) {
const quint16 id = dynamicDictionaries.value(dictionaryName);
xt9Ime()->mountIndex(id);
attachedDynamicDictionaries[dictionaryName] = id;
}
}
// Detach
for (const QString &dictionaryName : attachedDynamicDictionaries.keys()) {
if (!activeDictionaries.contains(dictionaryName)) {
const quint16 id = attachedDynamicDictionaries[dictionaryName];
xt9Ime()->unmountIndex(id);
attachedDynamicDictionaries.remove(dictionaryName);
}
}
}
void Xt9InputMethodPrivate::onUserDataPathChanged()
{
updateDlm();
xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
}
void Xt9InputMethodPrivate::onUserDataReset()
{
dlmDeactivate();
removeAllDynamicDictionaries();
}
ET9STATUS Xt9InputMethodPrivate::request(ET9_Request *const pRequest)
{
Q_Q(Xt9InputMethod);
switch (pRequest->eType) {
case ET9_REQ_AutoCap:
break;
case ET9_REQ_AutoAccept:
selectionListSelectItem(defaultListIndex);
break;
case ET9_REQ_BufferContext:
{
QVirtualKeyboardInputContext *ic = q->inputContext();
if (!ic)
break;
const ET9U32 dwContextLen = static_cast<ET9U32>(ic->cursorPosition());
const ET9U32 dwStartIndex =
dwContextLen <= pRequest->data.sBufferContextInfo.dwMaxBufLen ?
0 : dwContextLen - pRequest->data.sBufferContextInfo.dwMaxBufLen;
pRequest->data.sBufferContextInfo.dwBufLen = dwContextLen - dwStartIndex;
const QString surroundingText = ic->surroundingText();
memcpy(pRequest->data.sBufferContextInfo.psBuf, surroundingText.utf16() + dwStartIndex,
pRequest->data.sBufferContextInfo.dwBufLen * sizeof(ET9SYMB));
break;
}
default:
break;
}
return ET9STATUS_NONE;
}
} // namespace QtVirtualKeyboard
QT_END_NAMESPACE