InputMapping.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #include "InputMapping.h"
  2. #include <QDir>
  3. #include <QDirIterator>
  4. #include <QByteArray>
  5. #include <QFile>
  6. #include <QJsonArray>
  7. #include <QJsonObject>
  8. #include <QJsonDocument>
  9. #include "QsLog.h"
  10. #include "Paths.h"
  11. #include "utils/Utils.h"
  12. ///////////////////////////////////////////////////////////////////////////////////////////////////
  13. InputMapping::InputMapping(QObject *parent) : QObject(parent)
  14. {
  15. m_watcher = new QFileSystemWatcher(this);
  16. connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InputMapping::dirChange);
  17. connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &InputMapping::dirChange);
  18. }
  19. ///////////////////////////////////////////////////////////////////////////////////////////////////
  20. void InputMapping::dirChange()
  21. {
  22. QLOG_INFO() << "Change to user input path, reloading mappings.";
  23. loadMappings();
  24. }
  25. ///////////////////////////////////////////////////////////////////////////////////////////////////
  26. bool InputMapping::loadMappings()
  27. {
  28. m_inputMatcher.clear();
  29. m_sourceMatcher.clear();
  30. // don't watch the path while we potentially copy files to the directory
  31. if (m_watcher->directories().size() > 0)
  32. m_watcher->removePath(Paths::dataDir("inputmaps"));
  33. // first we load the bundled mappings
  34. loadMappingDirectory(":/inputmaps", true);
  35. // now we load the user ones, if there are any
  36. // they will now overload the built-in ones.
  37. //
  38. loadMappingDirectory(Paths::dataDir("inputmaps"), false);
  39. // we want to watch this dir for new files and changed files
  40. m_watcher->addPath(Paths::dataDir("inputmaps"));
  41. emit mappingChanged();
  42. return true;
  43. }
  44. /////////////////////////////////////////////////////////////////////////////////////////
  45. QVariant InputMapping::mapToAction(const QString& source, const QString& keycode)
  46. {
  47. // if the source is direct we will just use the keycode as the action
  48. if (source == "direct")
  49. return keycode;
  50. // first we need to match the source
  51. QVariant sourceName = m_sourceMatcher.match(source);
  52. if (sourceName.isValid())
  53. {
  54. QVariant action = m_inputMatcher.value(sourceName.toString())->match(keycode);
  55. if (action.isValid())
  56. return action;
  57. }
  58. return QString();
  59. }
  60. ///////////////////////////////////////////////////////////////////////////////////////////////////
  61. bool InputMapping::loadMappingFile(const QString& path, QPair<QString, QVariantMap> &mappingPair)
  62. {
  63. QJsonParseError err;
  64. auto doc = Utils::OpenJsonDocument(path, &err);
  65. if (doc.isNull())
  66. {
  67. QLOG_WARN() << "Failed to parse input mapping file:" << path << "," << err.errorString();
  68. return false;
  69. }
  70. if (doc.isObject())
  71. {
  72. auto obj = doc.object();
  73. if (!obj.contains("name"))
  74. {
  75. QLOG_WARN() << "Missing elements 'name' from mapping file:" << path;
  76. return false;
  77. }
  78. if (!obj.contains("idmatcher"))
  79. {
  80. QLOG_WARN() << "Missing element 'idmatcher' from mapping file:" << path;
  81. return false;
  82. }
  83. if (!obj.contains("mapping"))
  84. {
  85. QLOG_WARN() << "Missing element 'mapping' from mapping file:" << path;
  86. return false;
  87. }
  88. mappingPair = qMakePair(obj["name"].toString(), obj.toVariantMap());
  89. return true;
  90. }
  91. QLOG_WARN() << "Wrong format for file:" << path;
  92. return false;
  93. }
  94. ///////////////////////////////////////////////////////////////////////////////////////////////////
  95. bool InputMapping::loadMappingDirectory(const QString& path, bool copy)
  96. {
  97. QLOG_INFO() << "Loading inputmaps from:" << path;
  98. QDirIterator it(path);
  99. while (it.hasNext())
  100. {
  101. QFileInfo finfo = QFileInfo(it.next());
  102. if (finfo.isFile() && finfo.isReadable() && finfo.fileName().endsWith(".json"))
  103. {
  104. // make a copy of the original file to the example directory
  105. if (copy)
  106. {
  107. QDir userdir(Paths::dataDir());
  108. userdir.mkpath("inputmaps/examples/");
  109. QString examplePath(userdir.filePath("inputmaps/examples/" + finfo.fileName()));
  110. // make sure we really overwrite the file. copy will not do this.
  111. if (QFile(examplePath).exists())
  112. QFile::remove(examplePath);
  113. QFile::copy(finfo.absoluteFilePath(), examplePath);
  114. QFile(examplePath).setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::WriteOwner |
  115. QFileDevice::WriteGroup | QFileDevice::ReadOther);
  116. }
  117. QPair<QString, QVariantMap> mapping;
  118. if (loadMappingFile(finfo.absoluteFilePath(), mapping))
  119. {
  120. // add the source regexp to the matcher
  121. if (m_sourceMatcher.addMatcher(mapping.second.value("idmatcher").toString(), mapping.first))
  122. {
  123. // get the input map and add it to a new CachedMatcher
  124. QVariantMap inputMap = mapping.second.value("mapping").toMap();
  125. auto inputMatcher = new CachedRegexMatcher(this);
  126. foreach(const QString& pattern, inputMap.keys())
  127. inputMatcher->addMatcher("^" + pattern + "$", inputMap.value(pattern));
  128. m_inputMatcher.insert(mapping.first, inputMatcher);
  129. }
  130. }
  131. }
  132. }
  133. return true;
  134. }