Revision control
++ // 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 "t9writeworker_p.h"
++ #include <QLoggingCategory>
++
++ #include <QFile>
++ #include <QElapsedTimer>
++
++ QT_BEGIN_NAMESPACE
++ namespace QtVirtualKeyboard {
++
++ Q_DECLARE_LOGGING_CATEGORY(lcT9Write)
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteTask
++ \internal
++ */
++
++ T9WriteTask::T9WriteTask(QObject *parent) :
++ QObject(parent),
++ decumaSession(nullptr),
++ runSema()
++ {
++ }
++
++ void T9WriteTask::wait()
++ {
++ runSema.acquire();
++ runSema.release();
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteDictionaryTask
++ \internal
++ */
++
++ T9WriteDictionaryTask::T9WriteDictionaryTask(QSharedPointer<T9WriteAbstractDictionary> dictionary,
++ bool convertDictionary) :
++ dictionary(dictionary),
++ convertDictionary(convertDictionary)
++ {
++ }
++
++ void T9WriteDictionaryTask::run()
++ {
++ qCDebug(lcT9Write) << "T9WriteDictionaryTask::run()";
++
++ QElapsedTimer perf;
++ perf.start();
++
++ bool result = false;
++ if (dictionary) {
++ result = dictionary->load();
++ if (result && convertDictionary)
++ result = dictionary->convert();
++ }
++
++ qCDebug(lcT9Write) << "T9WriteDictionaryTask::run(): time:" << perf.elapsed() << "ms";
++
++ if (result) {
++ dictionary->state++;
++ emit completed(dictionary);
++ }
++ }
++
++ T9WriteAddArcTask::T9WriteAddArcTask(QVirtualKeyboardTrace *trace) :
++ trace(trace)
++ {
++ }
++
++ void T9WriteAddArcTask::run()
++ {
++ QElapsedTimer perf;
++ perf.start();
++ DECUMA_UINT32 arcID = (DECUMA_UINT32)trace->traceId();
++ DECUMA_STATUS status = DECUMA_API(StartNewArc)(decumaSession, arcID);
++ Q_ASSERT(status == decumaNoError);
++ if (status != decumaNoError) {
++ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to start new arc, status:" << status;
++ return;
++ }
++
++ const QVariantList points = trace->points();
++ Q_ASSERT(!points.isEmpty());
++
++ for (const QVariant &p : points) {
++ const QPoint pt(p.toPointF().toPoint());
++ status = DECUMA_API(AddPoint)(decumaSession, (DECUMA_COORD)pt.x(),(DECUMA_COORD)pt.y(), arcID);
++ if (status != decumaNoError) {
++ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to add point, status:" << status;
++ DECUMA_API(CancelArc)(decumaSession, arcID);
++ return;
++ }
++ }
++
++ status = DECUMA_API(CommitArc)(decumaSession, arcID);
++ if (status != decumaNoError)
++ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to commit arc, status:" << status;
++ else
++ qCDebug(lcT9Write) << "T9WriteAddArcTask::run(): time:" << perf.elapsed() << "ms";
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteRecognitionResult
++ \internal
++ */
++
++ T9WriteRecognitionResult::T9WriteRecognitionResult(int id, int maxResults, int maxCharsPerWord) :
++ status(decumaNoError),
++ numResults(0),
++ instantGesture(0),
++ id(id),
++ maxResults(maxResults),
++ maxCharsPerWord(maxCharsPerWord)
++ {
++ Q_ASSERT(maxResults > 0);
++ Q_ASSERT(maxCharsPerWord > 0);
++ results.resize(maxResults);
++ int bufferLength = (maxCharsPerWord + 1);
++ _chars.resize(maxResults * bufferLength);
++ _symbolChars.resize(maxResults * bufferLength);
++ _symbolStrokes.resize(maxResults * bufferLength);
++ for (int i = 0; i < maxResults; i++) {
++ DECUMA_HWR_RESULT &hwrResult = results[i];
++ hwrResult.pChars = &_chars[i * bufferLength];
++ hwrResult.pSymbolChars = &_symbolChars[i * bufferLength];
++ hwrResult.pSymbolStrokes = &_symbolStrokes[i * bufferLength];
++ }
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteRecognitionTask
++ \internal
++ */
++
++ T9WriteRecognitionTask::T9WriteRecognitionTask(QSharedPointer<T9WriteRecognitionResult> result) :
++ T9WriteTask(),
++ result(result),
++ stateCancelled(false)
++ {
++ }
++
++ void T9WriteRecognitionTask::run()
++ {
++ if (!decumaSession)
++ return;
++
++ perf.start();
++
++ while (true) {
++ DECUMA_BG_REC_STATE bgRecState = bgRecIdle;
++ DECUMA_STATUS status = DECUMA_API(GetBackgroundRecognitionState(decumaSession, &bgRecState));
++ if (status) {
++ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): GetBackgroundRecognitionState failed, status:" << status;
++ break;
++ }
++
++ if (bgRecState != bgRecStarted) {
++ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): state:" << bgRecState << "time:" << perf.elapsed() << "ms";
++ break;
++ }
++
++ if (checkCancelled())
++ return;
++
++ QThread::msleep(25);
++
++ if (checkCancelled())
++ return;
++ }
++
++ result->status = DECUMA_API(Recognize)(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, nullptr, nullptr);
++ if (result->status != decumaNoError)
++ qCWarning(lcT9Write) << "T9WriteRecognitionTask::run(): Recognition failed, status:" << result->status;
++
++ if (checkCancelled())
++ return;
++
++ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): time:" << perf.elapsed() << "ms";
++ }
++
++ bool T9WriteRecognitionTask::cancelRecognition()
++ {
++ QMutexLocker stateGuard(&stateLock);
++ Q_UNUSED(stateGuard)
++ stateCancelled = true;
++ return true;
++ }
++
++ bool T9WriteRecognitionTask::checkCancelled()
++ {
++ QMutexLocker stateGuard(&stateLock);
++ Q_UNUSED(stateGuard)
++ if (stateCancelled) {
++ result.reset();
++ qCDebug(lcT9Write) << "T9WriteRecognitionTask cancelled, time:" << perf.elapsed() << "ms";
++ return true;
++ }
++
++ return false;
++ }
++
++ int T9WriteRecognitionTask::resultId() const
++ {
++ return result != nullptr ? result->id : -1;
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteRecognitionResultsTask
++ \internal
++ */
++
++ T9WriteRecognitionResultsTask::T9WriteRecognitionResultsTask(QSharedPointer<T9WriteRecognitionResult> result) :
++ T9WriteTask(),
++ result(result)
++ {
++ }
++
++ void T9WriteRecognitionResultsTask::run()
++ {
++ if (!result)
++ return;
++
++ if (result->status != decumaNoError) {
++ emit recognitionError(result->status);
++ return;
++ }
++
++ QVariantList resultList;
++ for (int i = 0; i < result->numResults; i++)
++ {
++ QVariantMap resultMap;
++ QString resultString;
++ QString gesture;
++ const DECUMA_HWR_RESULT &hwrResult = result->results.at(i);
++ resultString.reserve(hwrResult.nChars);
++ QVariantList symbolStrokes;
++ int charPos = 0;
++ for (int symbolIndex = 0; symbolIndex < hwrResult.nSymbols; symbolIndex++) {
++ int symbolLength = hwrResult.pSymbolChars[symbolIndex];
++ QString symbol(QString::fromUtf16(reinterpret_cast<const char16_t *>(&hwrResult.pChars[charPos]), symbolLength));
++ // Do not append gesture symbol to result string
++ if (hwrResult.bGesture) {
++ gesture = symbol.right(1);
++ symbol.chop(1);
++ }
++ resultString.append(symbol);
++ charPos += symbolLength;
++ if (hwrResult.pSymbolStrokes)
++ symbolStrokes.append(QVariant((int)hwrResult.pSymbolStrokes[symbolIndex]));
++ }
++
++ resultMap[QLatin1String("resultId")] = result->id;
++ resultMap[QLatin1String("chars")] = resultString;
++ resultMap[QLatin1String("symbolStrokes")] = symbolStrokes;
++ if (!gesture.isEmpty())
++ resultMap[QLatin1String("gesture")] = gesture;
++
++ resultList.append(resultMap);
++ }
++
++ if (resultList.isEmpty()) {
++ qCDebug(lcT9Write) << "T9WriteRecognitionResultsTask::run(): no results available";
++ return;
++ }
++
++ qCDebug(lcT9Write) << "T9WriteRecognitionResultsTask::run():" << resultList.size() << "results available";
++ emit resultsAvailable(resultList);
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteLearnWordTask
++ \internal
++ */
++
++ T9WriteDlmWordTask::T9WriteDlmWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word, const QString &stringStart) :
++ T9WriteTask(),
++ dlmDictionary(dlmDictionary),
++ word(word),
++ stringStart(stringStart)
++ {
++
++ }
++
++ void T9WriteDlmWordTask::run()
++ {
++ DECUMA_RECOGNITION_SETTINGS recSettings;
++ memset(&recSettings, 0, sizeof(recSettings));
++ recSettings.boostLevel = boostDictWords;
++ recSettings.stringCompleteness = canBeContinued;
++ if (!stringStart.isEmpty())
++ recSettings.pStringStart = const_cast<DECUMA_UNICODE *>(stringStart.utf16());
++
++ DECUMA_UINT16 nDictionaries = 0;
++ DECUMA_STATUS status = DECUMA_API(GetNAttachedDictionaries)(decumaSession, &nDictionaries);
++ if (status)
++ return;
++
++ bool wordFound = false;
++ if (nDictionaries != 0) {
++ QVector<DECUMA_MATCH_RESULT> matchResults;
++ matchResults.resize(nDictionaries);
++ status = DECUMA_API(MatchWord)(decumaSession, word.utf16(),
++ static_cast<DECUMA_UINT16>(word.length()),
++ &recSettings, matchResults.data());
++ if (!status) {
++ for (const auto &matchResult : std::as_const(matchResults)) {
++ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): MatchWord string type" << matchResult.stringType;
++ if (matchResult.stringType != notFromDictionary) {
++ wordFound = true;
++ break;
++ }
++ }
++ } else {
++ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): MatchWord failed" << status;
++ return;
++ }
++ }
++
++ if (!wordFound) {
++ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): DynamicDictionaryAddWord";
++ status = DECUMA_API(DynamicDictionaryAddWord)(
++ const_cast<DECUMA_DYNAMIC_DICTIONARY *>(
++ reinterpret_cast<const DECUMA_DYNAMIC_DICTIONARY *>(dlmDictionary->data())),
++ word.utf16());
++
++ if (!status) {
++ persist();
++ }
++ }
++ }
++
++ void T9WriteDlmWordTask::persist()
++ {
++ T9WriteDynamicDictionary *dictionary = static_cast<T9WriteDynamicDictionary *>(dlmDictionary.data());
++
++ QElapsedTimer perf;
++ perf.start();
++
++ dictionary->save();
++
++ qCDebug(lcT9Write) << "T9WriteDlmWordTask::persist(): time:" << perf.elapsed() << "ms";
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteDlmRemoveWordTask
++ \internal
++ */
++
++ T9WriteDlmRemoveWordTask::T9WriteDlmRemoveWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word) :
++ T9WriteDlmWordTask(dlmDictionary, word, QString())
++ {
++
++ }
++
++ void T9WriteDlmRemoveWordTask::run()
++ {
++ T9WriteDynamicDictionary *dictionary = static_cast<T9WriteDynamicDictionary *>(dlmDictionary.data());
++ if (dictionary->removeWord(word)) {
++ persist();
++ }
++ }
++
++ /*!
++ \class QtVirtualKeyboard::T9WriteWorker
++ \internal
++ */
++
++ T9WriteWorker::T9WriteWorker(DECUMA_SESSION *decumaSession, const bool cjk, QObject *parent) :
++ QThread(parent),
++ taskSema(),
++ taskLock(),
++ decumaSession(decumaSession),
++ cjk(cjk)
++ {
++ abort = false;
++ }
++
++ T9WriteWorker::~T9WriteWorker()
++ {
++ abort = true;
++ taskSema.release();
++ wait();
++ }
++
++ void T9WriteWorker::addTask(QSharedPointer<T9WriteTask> task)
++ {
++ if (task) {
++ QMutexLocker guard(&taskLock);
++ task->moveToThread(this);
++ taskList.append(task);
++ taskSema.release();
++ }
++ }
++
++ int T9WriteWorker::removeTask(QSharedPointer<T9WriteTask> task)
++ {
++ int count = 0;
++ if (task) {
++ QMutexLocker guard(&taskLock);
++ const bool isRunning = taskList.indexOf(task) == 0;
++ if (isRunning) {
++ task->wait();
++ } else if (taskList.removeOne(task)) {
++ ++count;
++ task->runSema.release();
++ }
++ }
++ return count;
++ }
++
++ int T9WriteWorker::removeAllTasks()
++ {
++ idleSema.acquire();
++ QMutexLocker guard(&taskLock);
++ int count = taskList.size();
++ for (QSharedPointer<T9WriteTask> task : taskList) {
++ task->runSema.release();
++ }
++ taskList.clear();
++ idleSema.release();
++ return count;
++ }
++
++ void T9WriteWorker::waitForAllTasks()
++ {
++ while (isRunning()) {
++ idleSema.acquire();
++ QMutexLocker guard(&taskLock);
++ if (taskList.isEmpty()) {
++ idleSema.release();
++ break;
++ }
++ idleSema.release();
++ }
++ }
++
++ int T9WriteWorker::numberOfPendingTasks()
++ {
++ QMutexLocker guard(&taskLock);
++ return taskList.size();
++ }
++
++ void T9WriteWorker::run()
++ {
++ while (!abort) {
++ idleSema.release();
++ taskSema.acquire();
++ if (abort)
++ break;
++ idleSema.acquire();
++ QSharedPointer<T9WriteTask> currentTask;
++ {
++ QMutexLocker guard(&taskLock);
++ if (!taskList.isEmpty()) {
++ currentTask = taskList.front();
++ }
++ }
++ if (currentTask) {
++ currentTask->decumaSession = decumaSession;
++ currentTask->cjk = cjk;
++ currentTask->run();
++ currentTask->runSema.release();
++ QMutexLocker guard(&taskLock);
++ taskList.removeOne(currentTask);
++ }
++ }
++ }
++
++ } // namespace QtVirtualKeyboard
++ QT_END_NAMESPACE