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