1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include "lipisharedrecognizer_p.h"
31 #include <QLoggingCategory>
32 #include "lipiworker_p.h"
33
34 #include "LTKMacros.h"
35 #include "LTKInc.h"
36 #include "LTKTypes.h"
37 #include "LTKOSUtil.h"
38 #include "LTKOSUtilFactory.h"
39 #include "LTKErrorsList.h"
40 #include "LTKErrors.h"
41 #include "LTKLogger.h"
42 #include "LTKConfigFileReader.h"
43 #include "LTKException.h"
44 #include "LTKLipiEngineInterface.h"
45
46 #include <QDir>
47 #include <QtCore/QLibraryInfo>
48
49 QT_BEGIN_NAMESPACE
50 namespace QtVirtualKeyboard {
51
52 Q_DECLARE_LOGGING_CATEGORY(lcLipi)
53
54 int LipiSharedRecognizer::s_lipiEngineRefCount = 0;
55 QString LipiSharedRecognizer::s_lipiRoot;
56 QString LipiSharedRecognizer::s_lipiLib;
57 void *LipiSharedRecognizer::s_lipiEngineHandle = nullptr;
58 LipiSharedRecognizer::FN_PTR_CREATELTKLIPIENGINE LipiSharedRecognizer::s_createLTKLipiEngine = nullptr;
59 LipiSharedRecognizer::FN_PTR_DELETELTKLIPIENGINE LipiSharedRecognizer::s_deleteLTKLipiEngine = nullptr;
60 LTKLipiEngineInterface *LipiSharedRecognizer::s_lipiEngine = nullptr;
61 LTKShapeRecognizer *LipiSharedRecognizer::s_shapeRecognizer = nullptr;
62 LipiWorker *LipiSharedRecognizer::s_lipiWorker = nullptr;
63 QMap<int, QChar> LipiSharedRecognizer::s_unicodeMap;
64 QString LipiSharedRecognizer::s_activeModel;
65 stringStringMap LipiSharedRecognizer::s_lipiEngineConfigEntries;
66 int LipiSharedRecognizer::s_recognitionCount = 0;
67
68 /*!
69 \class QtVirtualKeyboard::LipiSharedRecognizer
70 \internal
71 */
72
LipiSharedRecognizer()73 LipiSharedRecognizer::LipiSharedRecognizer()
74 {
75 loadLipiInterface();
76 }
77
~LipiSharedRecognizer()78 LipiSharedRecognizer::~LipiSharedRecognizer()
79 {
80 unloadLipiInterface();
81 }
82
model() const83 QString LipiSharedRecognizer::model() const
84 {
85 return s_activeModel;
86 }
87
setModel(const QString & modelName)88 bool LipiSharedRecognizer::setModel(const QString &modelName)
89 {
90 qCDebug(lcLipi) << "LipiSharedRecognizer::setModel():" << modelName;
91
92 if (!s_lipiEngine) {
93 qCWarning(lcLipi) << "Engine not initialized";
94 return false;
95 }
96
97 if (modelName.isEmpty())
98 return false;
99
100 if (modelName == s_activeModel)
101 return true;
102
103 unloadModelData();
104
105 return loadModelData(modelName) == SUCCESS;
106 }
107
subsetOfClasses(const QString & charset,vector<int> & outSubsetOfClasses) const108 void LipiSharedRecognizer::subsetOfClasses(const QString &charset, vector<int> &outSubsetOfClasses) const
109 {
110 outSubsetOfClasses.clear();
111 outSubsetOfClasses.reserve(charset.length());
112 QString notFound;
113 for (int i = 0; i < charset.length(); i++) {
114 int classId = s_unicodeMap.key(charset.at(i), -1);
115 if (classId != -1)
116 outSubsetOfClasses.push_back(classId);
117 else if (lcLipi().isDebugEnabled())
118 notFound.append(charset.at(i));
119 }
120 if (!notFound.isEmpty())
121 qCDebug(lcLipi) << "LipiSharedRecognizer::subsetOfClasses(): unrecognized characters" << notFound;
122 }
123
newRecognition(const LTKCaptureDevice & deviceInfo,const LTKScreenContext & screenContext,const vector<int> & inSubsetOfClasses,float confThreshold,int numChoices)124 QSharedPointer<LipiRecognitionTask> LipiSharedRecognizer::newRecognition(const LTKCaptureDevice& deviceInfo,
125 const LTKScreenContext& screenContext,
126 const vector<int>& inSubsetOfClasses,
127 float confThreshold,
128 int numChoices)
129 {
130 if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker)
131 return QSharedPointer<LipiRecognitionTask>();
132
133 QSharedPointer<LipiRecognitionTask> task(new LipiRecognitionTask(deviceInfo,
134 screenContext,
135 inSubsetOfClasses,
136 confThreshold,
137 numChoices,
138 s_recognitionCount));
139
140 ++s_recognitionCount;
141
142 return task;
143 }
144
startRecognition(QSharedPointer<LipiRecognitionTask> & recognitionTask)145 QSharedPointer<LipiRecognitionResultsTask> LipiSharedRecognizer::startRecognition(QSharedPointer<LipiRecognitionTask> &recognitionTask)
146 {
147 if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker)
148 return QSharedPointer<LipiRecognitionResultsTask>();
149
150 QSharedPointer<LipiRecognitionResultsTask> resultsTask(new LipiRecognitionResultsTask(recognitionTask->resultVector,
151 s_unicodeMap,
152 recognitionTask->resultId()));
153
154 s_lipiWorker->addTask(recognitionTask);
155 s_lipiWorker->addTask(resultsTask);
156
157 return resultsTask;
158 }
159
cancelRecognition()160 bool LipiSharedRecognizer::cancelRecognition()
161 {
162 if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker)
163 return false;
164
165 return s_lipiWorker->removeAllTasks() > 0;
166 }
167
cancelRecognitionTask(QSharedPointer<LipiRecognitionTask> & recognitionTask)168 bool LipiSharedRecognizer::cancelRecognitionTask(QSharedPointer<LipiRecognitionTask> &recognitionTask)
169 {
170 if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker || !recognitionTask)
171 return false;
172
173 return recognitionTask->cancelRecognition() || s_lipiWorker->removeTask(recognitionTask) > 0;
174 }
175
loadLipiInterface()176 int LipiSharedRecognizer::loadLipiInterface()
177 {
178 qCDebug(lcLipi) << "LipiSharedRecognizer::loadLipiInterface():" << s_lipiEngineRefCount;
179
180 if (++s_lipiEngineRefCount == 1) {
181 if (s_lipiRoot.isEmpty()) {
182 /* LIPI_ROOT defines the root directory for lipi-toolkit project.
183 LIPI_LIB is an extension implemented for QtVirtualKeyboard and
184 allows using different location for lipi-toolkit plugins.
185
186 LIPI_LIB defaults to LIPI_ROOT + "/lib".
187 */
188 bool lipiRootVarIsEmpty = qEnvironmentVariableIsEmpty("LIPI_ROOT");
189 s_lipiRoot = lipiRootVarIsEmpty ?
190 QDir(QLibraryInfo::location(QLibraryInfo::DataPath) + QLatin1String("/qtvirtualkeyboard/lipi_toolkit")).absolutePath() :
191 qEnvironmentVariable("LIPI_ROOT");
192
193 bool lipiLibVarIsEmpty = qEnvironmentVariableIsEmpty("LIPI_LIB");
194 if (!lipiLibVarIsEmpty)
195 s_lipiLib = qEnvironmentVariable("LIPI_LIB");
196 else if (!lipiRootVarIsEmpty)
197 s_lipiLib = s_lipiRoot + QLatin1String("/lib");
198 else
199 s_lipiLib = QDir(QLibraryInfo::location(QLibraryInfo::PluginsPath) + QLatin1String("/lipi_toolkit")).absolutePath();
200 }
201
202 QScopedPointer<LTKOSUtil> osUtil(LTKOSUtilFactory::getInstance());
203 const string lipiRootPath(QDir::toNativeSeparators(s_lipiRoot).toStdString());
204 const string lipiLibPath(QDir::toNativeSeparators(s_lipiLib).toStdString());
205
206 int result = osUtil->loadSharedLib(lipiLibPath, LIPIENGINE_MODULE_STR, &s_lipiEngineHandle);
207 if (result != SUCCESS) {
208 qCWarning(lcLipi) << QStringLiteral("Error %1: Could not open shared library for module '%2'").arg(result).arg(QLatin1String(LIPIENGINE_MODULE_STR));
209 return result;
210 }
211
212 result = loadLipiEngineConfig();
213 if (result != SUCCESS)
214 return result;
215
216 result = osUtil->getFunctionAddress(s_lipiEngineHandle, "createLTKLipiEngine", (void **)&s_createLTKLipiEngine);
217 if (result != SUCCESS) {
218 qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str()));
219 return result;
220 }
221
222 result = osUtil->getFunctionAddress(s_lipiEngineHandle, "deleteLTKLipiEngine", (void **)&s_deleteLTKLipiEngine);
223 if (result != SUCCESS) {
224 qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str()));
225 return result;
226 }
227
228 s_lipiEngine = s_createLTKLipiEngine();
229 s_lipiEngine->setLipiRootPath(lipiRootPath);
230 s_lipiEngine->setLipiLibPath(lipiLibPath);
231 #if 0
232 s_lipiEngine->setLipiLogFileName(QDir::toNativeSeparators(QString("%1/lipi.log").arg(s_lipiRoot)).toStdString());
233 s_lipiEngine->setLipiLogLevel("DEBUG");
234 #endif
235
236 result = s_lipiEngine->initializeLipiEngine();
237 if (result != SUCCESS) {
238 qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str()));
239 return result;
240 }
241 }
242
243 return SUCCESS;
244 }
245
unloadLipiInterface()246 void LipiSharedRecognizer::unloadLipiInterface()
247 {
248 qCDebug(lcLipi) << "LipiSharedRecognizer::unloadLipiInterface():" << s_lipiEngineRefCount;
249
250 Q_ASSERT(s_lipiEngineRefCount > 0);
251 if (--s_lipiEngineRefCount == 0) {
252 unloadModelData();
253 if (s_lipiEngine) {
254 s_deleteLTKLipiEngine();
255 s_lipiEngine = nullptr;
256 }
257 s_createLTKLipiEngine = nullptr;
258 s_deleteLTKLipiEngine = nullptr;
259 QScopedPointer<LTKOSUtil> osUtil(LTKOSUtilFactory::getInstance());
260 osUtil->unloadSharedLib(s_lipiEngineHandle);
261 s_lipiEngineHandle = nullptr;
262 }
263 }
264
loadLipiEngineConfig()265 int LipiSharedRecognizer::loadLipiEngineConfig()
266 {
267 s_lipiEngineConfigEntries.clear();
268
269 const QString &lipiEngineConfigFile(QDir::toNativeSeparators(QStringLiteral("%1/projects/lipiengine.cfg").arg(s_lipiRoot)));
270 if (!QFileInfo::exists(lipiEngineConfigFile)) {
271 qCWarning(lcLipi) << "File not found" << lipiEngineConfigFile;
272 return FAILURE;
273 }
274
275 try {
276 LTKConfigFileReader configReader(lipiEngineConfigFile.toStdString());
277 s_lipiEngineConfigEntries = configReader.getCfgFileMap();
278 } catch (LTKException e) {
279 return e.getErrorCode();
280 }
281
282 return SUCCESS;
283 }
284
resolveLogicalNameToProjectProfile(const QString & logicalName,QString & outProjectName,QString & outProfileName)285 int LipiSharedRecognizer::resolveLogicalNameToProjectProfile(const QString &logicalName, QString &outProjectName, QString &outProfileName)
286 {
287 outProjectName.clear();
288 outProfileName.clear();
289
290 stringStringMap::const_iterator configEntry = s_lipiEngineConfigEntries.find(logicalName.toStdString());
291 if (configEntry == s_lipiEngineConfigEntries.end())
292 return FAILURE;
293
294 QStringList parts = QString::fromStdString(configEntry->second).split(QLatin1Char('('), Qt::SkipEmptyParts);
295 if (parts.length() != 2)
296 return FAILURE;
297
298 parts[1].replace(QLatin1Char(')'), QString());
299
300 outProjectName = parts[0].trimmed();
301 outProfileName = parts[1].trimmed();
302
303 return SUCCESS;
304 }
305
loadModelData(const QString & logicalName)306 int LipiSharedRecognizer::loadModelData(const QString &logicalName)
307 {
308 qCDebug(lcLipi) << "LipiSharedRecognizer::loadModelData():" << logicalName;
309
310 Q_ASSERT(s_shapeRecognizer == nullptr);
311 Q_ASSERT(s_lipiWorker == nullptr);
312
313 QTime perf;
314 perf.start();
315
316 s_activeModel = logicalName;
317
318 QString project;
319 QString profile;
320 int result = resolveLogicalNameToProjectProfile(logicalName, project, profile);
321 if (result == SUCCESS) {
322 string strProject = project.toStdString();
323 string strProfile = profile.toStdString();
324 int result = s_lipiEngine->createShapeRecognizer(strProject, strProfile, &s_shapeRecognizer);
325 if (result == SUCCESS) {
326 result = loadMapping(QDir::toNativeSeparators(QStringLiteral("%1/projects/%2/config/unicodeMapfile_%2.ini").arg(s_lipiRoot).arg(project)));
327 if (result == SUCCESS) {
328 s_lipiWorker = new LipiWorker(s_shapeRecognizer);
329 QSharedPointer<LipiLoadModelDataTask> loadModelDataTask(new LipiLoadModelDataTask());
330 s_lipiWorker->addTask(loadModelDataTask);
331 s_lipiWorker->start();
332 }
333 }
334 }
335
336 if (result == SUCCESS)
337 qCDebug(lcLipi) << "LipiSharedRecognizer::loadModelData(): time:" << perf.elapsed() << "ms";
338
339 if (result != SUCCESS) {
340 qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str()));
341 unloadModelData();
342 }
343
344 return result;
345 }
346
unloadModelData()347 void LipiSharedRecognizer::unloadModelData()
348 {
349 if (!s_shapeRecognizer)
350 return;
351
352 qCDebug(lcLipi) << "LipiSharedRecognizer::unloadModelData():" << s_activeModel;
353
354 QTime perf;
355 perf.start();
356
357 if (s_lipiWorker) {
358 delete s_lipiWorker;
359 s_lipiWorker = nullptr;
360 }
361
362 s_lipiEngine->deleteShapeRecognizer(s_shapeRecognizer);
363 s_shapeRecognizer = nullptr;
364 s_unicodeMap.clear();
365 s_activeModel.clear();
366
367 qCDebug(lcLipi) << "LipiSharedRecognizer::unloadModelData(): time:" << perf.elapsed() << "ms";
368 }
369
loadMapping(const QString & mapFile)370 int LipiSharedRecognizer::loadMapping(const QString &mapFile)
371 {
372 if (!QFileInfo(mapFile).exists()) {
373 qCWarning(lcLipi) << "File not found" << mapFile;
374 return FAILURE;
375 }
376
377 try {
378 LTKConfigFileReader configfilereader(mapFile.toStdString());
379 const stringStringMap &cfgFileMap = configfilereader.getCfgFileMap();
380
381 for (stringStringMap::const_iterator i = cfgFileMap.begin(); i != cfgFileMap.end(); i++) {
382 if (i->first.empty())
383 continue;
384 if (!QChar::fromLatin1(i->first.at(0)).isDigit())
385 continue;
386
387 bool ok;
388 int id = QString::fromLatin1(i->first.c_str()).toInt(&ok, 10);
389 if (!ok)
390 continue;
391
392 QChar ch = QChar(QString::fromLatin1(i->second.c_str()).toInt(&ok, 16));
393 if (!ok)
394 continue;
395
396 s_unicodeMap[id] = ch;
397 }
398 } catch (LTKException) {
399 return FAILURE;
400 }
401
402 qCDebug(lcLipi) << s_unicodeMap;
403
404 return SUCCESS;
405 }
406
407 } // namespace QtVirtualKeyboard
408 QT_END_NAMESPACE
409