/ src / modules / keyboardmanager / KeyboardManagerEditorLibrary / LoadingAndSavingRemappingHelper.cpp
LoadingAndSavingRemappingHelper.cpp
  1  #include "pch.h"
  2  #include "LoadingAndSavingRemappingHelper.h"
  3  
  4  #include <set>
  5  #include <variant>
  6  #include <common/interop/shared_constants.h>
  7  #include <keyboardmanager/common/MappingConfiguration.h>
  8  
  9  #include "KeyboardManagerState.h"
 10  #include "keyboardmanager/KeyboardManagerEditorLibrary/trace.h"
 11  #include "EditorHelpers.h"
 12  #include "ShortcutErrorType.h"
 13  
 14  namespace LoadingAndSavingRemappingHelper
 15  {
 16      // Function to check if the set of remappings in the buffer are valid
 17      ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings)
 18      {
 19          ShortcutErrorType isSuccess = ShortcutErrorType::NoError;
 20          std::map<std::wstring, std::set<KeyShortcutTextUnion>> ogKeys;
 21          for (int i = 0; i < remappings.size(); i++)
 22          {
 23              KeyShortcutTextUnion ogKey = remappings[i].mapping[0];
 24              KeyShortcutTextUnion newKey = remappings[i].mapping[1];
 25              std::wstring appName = remappings[i].appName;
 26  
 27              const bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(ogKey)));
 28              const bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) || (newKey.index() == 2 && !std::get<std::wstring>(newKey).empty());
 29  
 30              // Add new set for a new target app name
 31              if (ogKeys.find(appName) == ogKeys.end())
 32              {
 33                  ogKeys[appName] = std::set<KeyShortcutTextUnion>();
 34              }
 35  
 36              if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end())
 37              {
 38                  ogKeys[appName].insert(ogKey);
 39              }
 40              else if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) != ogKeys[appName].end())
 41              {
 42                  isSuccess = ShortcutErrorType::RemapUnsuccessful;
 43              }
 44              else
 45              {
 46                  isSuccess = ShortcutErrorType::RemapUnsuccessful;
 47              }
 48          }
 49  
 50          return isSuccess;
 51      }
 52  
 53      // Function to return the set of keys that have been orphaned from the remap buffer
 54      std::vector<DWORD> GetOrphanedKeys(const RemapBuffer& remappings)
 55      {
 56          std::set<DWORD> ogKeys;
 57          std::set<DWORD> newKeys;
 58  
 59          for (int i = 0; i < remappings.size(); i++)
 60          {
 61              DWORD ogKey = std::get<DWORD>(remappings[i].mapping[0]);
 62              KeyShortcutTextUnion newKey = remappings[i].mapping[1];
 63  
 64              const bool hasValidKeyRemapping = newKey.index() == 0 && std::get<DWORD>(newKey) != 0;
 65              const bool hasValidShortcutRemapping = newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey));
 66              const bool hasValidTextRemapping = newKey.index() == 2 && !std::get<std::wstring>(newKey).empty();
 67              if (ogKey != NULL && (hasValidKeyRemapping || hasValidShortcutRemapping || hasValidTextRemapping))
 68              {
 69                  ogKeys.insert(ogKey);
 70  
 71                  // newKey should be added only if the target is a key
 72                  if (remappings[i].mapping[1].index() == 0)
 73                  {
 74                      newKeys.insert(std::get<DWORD>(newKey));
 75                  }
 76              }
 77          }
 78  
 79          for (auto& k : newKeys)
 80          {
 81              ogKeys.erase(k);
 82          }
 83  
 84          return std::vector(ogKeys.begin(), ogKeys.end());
 85      }
 86  
 87      // Function to combine remappings if the L and R version of the modifier is mapped to the same key
 88      void CombineRemappings(SingleKeyRemapTable& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey)
 89      {
 90          if (table.find(leftKey) != table.end() && table.find(rightKey) != table.end())
 91          {
 92              // If they are mapped to the same key, delete those entries and set the common version
 93              if (table[leftKey] == table[rightKey])
 94              {
 95                  if (std::holds_alternative<DWORD>(table[leftKey]) && std::get<DWORD>(table[leftKey]) == combinedKey)
 96                  {
 97                      // Avoid mapping a key to itself when the combined key is equal to the resulting mapping.
 98                      return;
 99                  }
100                  table[combinedKey] = table[leftKey];
101                  table.erase(leftKey);
102                  table.erase(rightKey);
103              }
104          }
105      }
106  
107      // Function to pre process the remap table before loading it into the UI
108      void PreProcessRemapTable(SingleKeyRemapTable& table)
109      {
110          // Pre process the table to combine L and R versions of Ctrl/Alt/Shift/Win that are mapped to the same key
111          CombineRemappings(table, VK_LCONTROL, VK_RCONTROL, VK_CONTROL);
112          CombineRemappings(table, VK_LMENU, VK_RMENU, VK_MENU);
113          CombineRemappings(table, VK_LSHIFT, VK_RSHIFT, VK_SHIFT);
114          CombineRemappings(table, VK_LWIN, VK_RWIN, CommonSharedConstants::VK_WIN_BOTH);
115      }
116  
117      // Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
118      void ApplySingleKeyRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired)
119      {
120          // Clear existing Key Remaps
121          mappingConfiguration.ClearSingleKeyRemaps();
122          mappingConfiguration.ClearSingleKeyToTextRemaps();
123          DWORD successfulKeyToKeyRemapCount = 0;
124          DWORD successfulKeyToShortcutRemapCount = 0;
125          DWORD successfulKeyToTextRemapCount = 0;
126          for (int i = 0; i < remappings.size(); i++)
127          {
128              const DWORD originalKey = std::get<DWORD>(remappings[i].mapping[0]);
129              KeyShortcutTextUnion newKey = remappings[i].mapping[1];
130  
131              if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(newKey).empty()))
132              {
133                  // If Ctrl/Alt/Shift are added, add their L and R versions instead to the same key
134                  bool result = false;
135                  std::vector<DWORD> originalKeysWithModifiers;
136                  if (originalKey == VK_CONTROL)
137                  {
138                      originalKeysWithModifiers.push_back(VK_LCONTROL);
139                      originalKeysWithModifiers.push_back(VK_RCONTROL);
140                  }
141                  else if (originalKey == VK_MENU)
142                  {
143                      originalKeysWithModifiers.push_back(VK_LMENU);
144                      originalKeysWithModifiers.push_back(VK_RMENU);
145                  }
146                  else if (originalKey == VK_SHIFT)
147                  {
148                      originalKeysWithModifiers.push_back(VK_LSHIFT);
149                      originalKeysWithModifiers.push_back(VK_RSHIFT);
150                  }
151                  else if (originalKey == CommonSharedConstants::VK_WIN_BOTH)
152                  {
153                      originalKeysWithModifiers.push_back(VK_LWIN);
154                      originalKeysWithModifiers.push_back(VK_RWIN);
155                  }
156                  else
157                  {
158                      originalKeysWithModifiers.push_back(originalKey);
159                  }
160  
161                  for (const DWORD key : originalKeysWithModifiers)
162                  {
163                      const bool mappedToText = newKey.index() == 2;
164                      result = mappedToText ? mappingConfiguration.AddSingleKeyToTextRemap(key, std::get<std::wstring>(newKey)) : mappingConfiguration.AddSingleKeyRemap(key, newKey) && result;
165                  }
166  
167                  if (result)
168                  {
169                      if (newKey.index() == 0)
170                      {
171                          ++successfulKeyToKeyRemapCount;
172                      }
173                      else if (newKey.index() == 1)
174                      {
175                          ++successfulKeyToShortcutRemapCount;
176                      }
177                      else if (newKey.index() == 2)
178                      {
179                          ++successfulKeyToTextRemapCount;
180                      }
181                  }
182              }
183          }
184  
185          // If telemetry is to be logged, log the key remap counts
186          if (isTelemetryRequired)
187          {
188              Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount, successfulKeyToTextRemapCount);
189          }
190      }
191  
192      // Function to apply the shortcut remappings from the buffer to the KeyboardManagerState variable
193      void ApplyShortcutRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired)
194      {
195          // Clear existing shortcuts
196          mappingConfiguration.ClearOSLevelShortcuts();
197          mappingConfiguration.ClearAppSpecificShortcuts();
198          DWORD successfulOSLevelShortcutToShortcutRemapCount = 0;
199          DWORD successfulOSLevelShortcutToKeyRemapCount = 0;
200          DWORD successfulAppSpecificShortcutToShortcutRemapCount = 0;
201          DWORD successfulAppSpecificShortcutToKeyRemapCount = 0;
202  
203          // Save the shortcuts that are valid and report if any of them were invalid
204          for (int i = 0; i < remappings.size(); i++)
205          {
206              Shortcut originalShortcut = std::get<Shortcut>(remappings[i].mapping[0]);
207              KeyShortcutTextUnion newShortcut = remappings[i].mapping[1];
208  
209              if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newShortcut))) || (newShortcut.index() == 2 && !std::get<std::wstring>(newShortcut).empty())))
210              {
211                  if (remappings[i].appName == L"")
212                  {
213                      bool result = mappingConfiguration.AddOSLevelShortcut(originalShortcut, newShortcut);
214                      if (result)
215                      {
216                          if (newShortcut.index() == 0)
217                          {
218                              successfulOSLevelShortcutToKeyRemapCount += 1;
219                          }
220                          else
221                          {
222                              successfulOSLevelShortcutToShortcutRemapCount += 1;
223                          }
224                      }
225                  }
226                  else
227                  {
228                      bool result = mappingConfiguration.AddAppSpecificShortcut(remappings[i].appName, originalShortcut, newShortcut);
229                      if (result)
230                      {
231                          if (newShortcut.index() == 0)
232                          {
233                              successfulAppSpecificShortcutToKeyRemapCount += 1;
234                          }
235                          else
236                          {
237                              successfulAppSpecificShortcutToShortcutRemapCount += 1;
238                          }
239                      }
240                  }
241              }
242          }
243  
244          // If telemetry is to be logged, log the shortcut remap counts
245          if (isTelemetryRequired)
246          {
247              Trace::OSLevelShortcutRemapCount(successfulOSLevelShortcutToShortcutRemapCount, successfulOSLevelShortcutToKeyRemapCount);
248              Trace::AppSpecificShortcutRemapCount(successfulAppSpecificShortcutToShortcutRemapCount, successfulAppSpecificShortcutToKeyRemapCount);
249          }
250      }
251  }