InputMapping.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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 <QDebug>
  10. #include "Paths.h"
  11. #include "utils/Utils.h"
  12. ///////////////////////////////////////////////////////////////////////////////////////////////////
  13. InputMapping::InputMapping(QObject *parent) : QObject(parent), m_sourceMatcher(false)
  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. qInfo() << "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. QVariantList 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 { QVariant(keycode) };
  50. QVariantList strActions;
  51. // first we need to match the source
  52. for (auto src : m_sourceMatcher.match(source))
  53. strActions << m_inputMatcher.value(src.toString())->match(keycode);
  54. return strActions;
  55. }
  56. ///////////////////////////////////////////////////////////////////////////////////////////////////
  57. bool InputMapping::loadMappingFile(const QString& path, QPair<QString, QVariantMap> &mappingPair)
  58. {
  59. QJsonParseError err;
  60. auto doc = Utils::OpenJsonDocument(path, &err);
  61. if (doc.isNull())
  62. {
  63. qWarning() << "Failed to parse input mapping file:" << path << "," << err.errorString();
  64. return false;
  65. }
  66. if (doc.isObject())
  67. {
  68. auto obj = doc.object();
  69. if (!obj.contains("name"))
  70. {
  71. qWarning() << "Missing elements 'name' from mapping file:" << path;
  72. return false;
  73. }
  74. if (!obj.contains("idmatcher"))
  75. {
  76. qWarning() << "Missing element 'idmatcher' from mapping file:" << path;
  77. return false;
  78. }
  79. if (!obj.contains("mapping"))
  80. {
  81. qWarning() << "Missing element 'mapping' from mapping file:" << path;
  82. return false;
  83. }
  84. mappingPair = qMakePair(obj["name"].toString(), obj.toVariantMap());
  85. return true;
  86. }
  87. qWarning() << "Wrong format for file:" << path;
  88. return false;
  89. }
  90. ///////////////////////////////////////////////////////////////////////////////////////////////////
  91. bool InputMapping::loadMappingDirectory(const QString& path, bool copy)
  92. {
  93. qInfo() << "Loading inputmaps from:" << path;
  94. QDirIterator it(path);
  95. while (it.hasNext())
  96. {
  97. QFileInfo finfo = QFileInfo(it.next());
  98. if (finfo.isFile() && finfo.isReadable() && finfo.fileName().endsWith(".json"))
  99. {
  100. // make a copy of the original file to the example directory
  101. if (copy)
  102. {
  103. QDir userdir(Paths::dataDir());
  104. userdir.mkpath("inputmaps/examples/");
  105. QString examplePath(userdir.filePath("inputmaps/examples/" + finfo.fileName()));
  106. // make sure we really overwrite the file. copy will not do this.
  107. if (QFile(examplePath).exists())
  108. QFile::remove(examplePath);
  109. QFile::copy(finfo.absoluteFilePath(), examplePath);
  110. QFile(examplePath).setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::WriteOwner |
  111. QFileDevice::WriteGroup | QFileDevice::ReadOther);
  112. }
  113. QPair<QString, QVariantMap> mapping;
  114. if (loadMappingFile(finfo.absoluteFilePath(), mapping))
  115. {
  116. // add the source regexp to the matcher
  117. if (m_sourceMatcher.addMatcher(mapping.second.value("idmatcher").toString(), mapping.first))
  118. {
  119. // get the input map and add it to a new CachedMatcher
  120. QVariantMap inputMap = mapping.second.value("mapping").toMap();
  121. auto inputMatcher = new CachedRegexMatcher(true, this);
  122. for(const QString& pattern : inputMap.keys())
  123. inputMatcher->addMatcher("^" + pattern + "$", inputMap.value(pattern));
  124. m_inputMatcher.insert(mapping.first, inputMatcher);
  125. }
  126. }
  127. }
  128. }
  129. return true;
  130. }