2025-02-18 17:10:15 +08:00
|
|
|
#include "pch.h"
|
|
|
|
|
#include "KeyboardManagerEditorLibraryWrapper.h"
|
2026-03-04 15:46:42 -05:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <memory>
|
2025-02-18 17:10:15 +08:00
|
|
|
|
2026-05-19 22:00:33 +08:00
|
|
|
#include <common/utils/json.h>
|
|
|
|
|
|
2025-02-18 17:10:15 +08:00
|
|
|
#include <common/utils/logger_helper.h>
|
|
|
|
|
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
|
2026-03-04 15:46:42 -05:00
|
|
|
#include <keyboardmanager/KeyboardManagerEditorLibrary/EditorHelpers.h>
|
|
|
|
|
#include <common/interop/keyboard_layout.h>
|
2025-02-18 17:10:15 +08:00
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
extern "C"
|
2025-02-18 17:10:15 +08:00
|
|
|
{
|
2026-03-04 15:46:42 -05:00
|
|
|
void* CreateMappingConfiguration()
|
|
|
|
|
{
|
|
|
|
|
return new MappingConfiguration();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DestroyMappingConfiguration(void* config)
|
|
|
|
|
{
|
|
|
|
|
delete static_cast<MappingConfiguration*>(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LoadMappingSettings(void* config)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<MappingConfiguration*>(config)->LoadSettings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SaveMappingSettings(void* config)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<MappingConfiguration*>(config)->SaveSettingsToFile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wchar_t* AllocateAndCopyString(const std::wstring& str)
|
|
|
|
|
{
|
|
|
|
|
size_t len = str.length();
|
|
|
|
|
wchar_t* buffer = new wchar_t[len + 1];
|
|
|
|
|
wcscpy_s(buffer, len + 1, str.c_str());
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 13:05:13 +08:00
|
|
|
// Populates the template metadata fields on a marshaled mapping. Always sets both pointers
|
|
|
|
|
// (the editor frees every field), using empty strings for non-template / non-RunProgram entries.
|
|
|
|
|
// Note: kept void-returning so it is valid inside the surrounding extern "C" block (a C-linkage
|
|
|
|
|
// function may not return a C++ type such as std::wstring).
|
|
|
|
|
void SetTemplateMetadata(ShortcutMapping* mapping, const Shortcut& targetShortcut)
|
2026-06-15 12:36:28 +08:00
|
|
|
{
|
2026-06-15 13:05:13 +08:00
|
|
|
mapping->templateId = AllocateAndCopyString(targetShortcut.templateId);
|
|
|
|
|
|
|
|
|
|
if (targetShortcut.templateParameters.empty())
|
2026-06-15 12:36:28 +08:00
|
|
|
{
|
2026-06-15 13:05:13 +08:00
|
|
|
mapping->templateParametersJson = AllocateAndCopyString(L"");
|
|
|
|
|
return;
|
2026-06-15 12:36:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json::JsonObject paramsObj;
|
2026-06-15 13:05:13 +08:00
|
|
|
for (auto const& [k, v] : targetShortcut.templateParameters)
|
2026-06-15 12:36:28 +08:00
|
|
|
{
|
|
|
|
|
paramsObj.SetNamedValue(k, json::JsonValue::CreateStringValue(v));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 13:05:13 +08:00
|
|
|
mapping->templateParametersJson = AllocateAndCopyString(paramsObj.Stringify().c_str());
|
2026-06-15 12:36:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetEmptyTemplateMetadata(ShortcutMapping* mapping)
|
|
|
|
|
{
|
|
|
|
|
mapping->templateId = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->templateParametersJson = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
int GetSingleKeyRemapCount(void* config)
|
|
|
|
|
{
|
|
|
|
|
auto mapping = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
return static_cast<int>(mapping->singleKeyReMap.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetSingleKeyRemap(void* config, int index, SingleKeyMapping* mapping)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
std::vector<std::pair<DWORD, KeyShortcutTextUnion>> allMappings;
|
|
|
|
|
|
|
|
|
|
for (const auto& kv : mappingConfig->singleKeyReMap)
|
|
|
|
|
{
|
|
|
|
|
allMappings.push_back(kv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index < 0 || index >= allMappings.size())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto& kv = allMappings[index];
|
|
|
|
|
mapping->originalKey = static_cast<int>(kv.first);
|
|
|
|
|
|
|
|
|
|
// Remap to single key
|
|
|
|
|
if (kv.second.index() == 0)
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKey = AllocateAndCopyString(std::to_wstring(std::get<DWORD>(kv.second)));
|
|
|
|
|
mapping->isShortcut = false;
|
|
|
|
|
}
|
|
|
|
|
// Remap to shortcut
|
|
|
|
|
else if (kv.second.index() == 1)
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKey = AllocateAndCopyString(std::get<Shortcut>(kv.second).ToHstringVK().c_str());
|
|
|
|
|
mapping->isShortcut = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKey = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->isShortcut = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetSingleKeyToTextRemapCount(void* config)
|
|
|
|
|
{
|
|
|
|
|
auto mapping = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
return static_cast<int>(mapping->singleKeyToTextReMap.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
if (index < 0 || index >= mappingConfig->singleKeyToTextReMap.size())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto it = mappingConfig->singleKeyToTextReMap.begin();
|
|
|
|
|
std::advance(it, index);
|
|
|
|
|
|
|
|
|
|
mapping->originalKey = static_cast<int>(it->first);
|
|
|
|
|
std::wstring text = std::get<std::wstring>(it->second);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(text);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetShortcutRemapCountByType(void* config, int operationType)
|
|
|
|
|
{
|
|
|
|
|
auto mapping = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
|
|
for (const auto& kv : mapping->osLevelShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
bool shouldCount = false;
|
|
|
|
|
|
|
|
|
|
if (operationType == 0)
|
|
|
|
|
{
|
|
|
|
|
if ((kv.second.targetShortcut.index() == 0) ||
|
|
|
|
|
(kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 1)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 2)
|
|
|
|
|
{
|
|
|
|
|
if (kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 3)
|
|
|
|
|
{
|
|
|
|
|
if (kv.second.targetShortcut.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldCount)
|
|
|
|
|
{
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& shortcutKv : appMap.second)
|
|
|
|
|
{
|
|
|
|
|
bool shouldCount = false;
|
|
|
|
|
|
|
|
|
|
if (operationType == 0)
|
|
|
|
|
{
|
|
|
|
|
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
|
|
|
|
(shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 1)
|
|
|
|
|
{
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 2)
|
|
|
|
|
{
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (operationType == 3)
|
|
|
|
|
{
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
shouldCount = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldCount)
|
|
|
|
|
{
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> filteredMappings;
|
|
|
|
|
|
|
|
|
|
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
bool shouldAdd = false;
|
|
|
|
|
|
|
|
|
|
switch (operationType)
|
|
|
|
|
{
|
|
|
|
|
case 0: // RemapShortcut
|
|
|
|
|
if ((kv.second.targetShortcut.index() == 0) ||
|
|
|
|
|
(kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1: // RunProgram
|
|
|
|
|
if (kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2: // OpenURI
|
|
|
|
|
if (kv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
if (kv.second.targetShortcut.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldAdd)
|
|
|
|
|
{
|
|
|
|
|
filteredMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& shortcutKv : appKv.second)
|
|
|
|
|
{
|
|
|
|
|
bool shouldAdd = false;
|
|
|
|
|
|
|
|
|
|
switch (operationType)
|
|
|
|
|
{
|
|
|
|
|
case 0: // RemapShortcut
|
|
|
|
|
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
|
|
|
|
(shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1: // RunProgram
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2: // OpenURI
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
|
|
|
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
if (shortcutKv.second.targetShortcut.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
shouldAdd = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldAdd)
|
|
|
|
|
{
|
|
|
|
|
filteredMappings.push_back(std::make_tuple(
|
|
|
|
|
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index < 0 || index >= filteredMappings.size())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto& [origShortcut, targetShortcutUnion, app] = filteredMappings[index];
|
|
|
|
|
|
|
|
|
|
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
|
|
|
|
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
|
|
|
|
mapping->targetApp = AllocateAndCopyString(app);
|
|
|
|
|
|
|
|
|
|
if (targetShortcutUnion.index() == 0)
|
|
|
|
|
{
|
|
|
|
|
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
|
|
|
|
mapping->operationType = 0;
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
else if (targetShortcutUnion.index() == 1)
|
|
|
|
|
{
|
|
|
|
|
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
|
|
|
|
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
|
|
|
|
|
|
|
|
|
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
|
|
|
|
|
|
|
|
|
switch (targetShortcut.operationType)
|
|
|
|
|
{
|
|
|
|
|
case Shortcut::OperationType::RunProgram:
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Shortcut::OperationType::OpenURI:
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (targetShortcutUnion.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->operationType = 0;
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(text);
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 12:36:28 +08:00
|
|
|
// Carry template metadata back to the editor so a template mapping re-opens as RunTemplate.
|
|
|
|
|
// Only RunProgram shortcuts ever carry it; every other entry gets empty (but allocated) fields.
|
|
|
|
|
if (targetShortcutUnion.index() == 1)
|
|
|
|
|
{
|
|
|
|
|
SetTemplateMetadata(mapping, std::get<Shortcut>(targetShortcutUnion));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetEmptyTemplateMetadata(mapping);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetShortcutRemapCount(void* config)
|
|
|
|
|
{
|
|
|
|
|
auto mapping = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
int count = static_cast<int>(mapping->osLevelShortcutReMap.size());
|
|
|
|
|
|
|
|
|
|
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
count += static_cast<int>(appMap.second.size());
|
|
|
|
|
}
|
2025-02-18 17:10:15 +08:00
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
return count;
|
|
|
|
|
}
|
2025-02-18 17:10:15 +08:00
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
2025-02-18 17:10:15 +08:00
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> allMappings;
|
|
|
|
|
|
|
|
|
|
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
allMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& shortcutKv : appKv.second)
|
|
|
|
|
{
|
|
|
|
|
allMappings.push_back(std::make_tuple(
|
|
|
|
|
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index < 0 || index >= allMappings.size())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto& [origShortcut, targetShortcutUnion, app] = allMappings[index];
|
|
|
|
|
|
|
|
|
|
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
|
|
|
|
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
|
|
|
|
|
|
|
|
|
mapping->targetApp = AllocateAndCopyString(app);
|
|
|
|
|
|
|
|
|
|
if (targetShortcutUnion.index() == 0)
|
|
|
|
|
{
|
|
|
|
|
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
|
|
|
|
mapping->operationType = 0;
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
else if (targetShortcutUnion.index() == 1)
|
|
|
|
|
{
|
|
|
|
|
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
|
|
|
|
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
|
|
|
|
|
|
|
|
|
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
|
|
|
|
|
|
|
|
|
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (targetShortcutUnion.index() == 2)
|
|
|
|
|
{
|
|
|
|
|
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
|
|
|
|
mapping->targetKeys = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->operationType = 0;
|
|
|
|
|
mapping->targetText = AllocateAndCopyString(text);
|
|
|
|
|
mapping->programPath = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->programArgs = AllocateAndCopyString(L"");
|
|
|
|
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 12:36:28 +08:00
|
|
|
// Carry template metadata back to the editor so a template mapping re-opens as RunTemplate.
|
|
|
|
|
// Only RunProgram shortcuts ever carry it; every other entry gets empty (but allocated) fields.
|
|
|
|
|
if (targetShortcutUnion.index() == 1)
|
|
|
|
|
{
|
|
|
|
|
SetTemplateMetadata(mapping, std::get<Shortcut>(targetShortcutUnion));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetEmptyTemplateMetadata(mapping);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 15:46:42 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeString(wchar_t* str)
|
|
|
|
|
{
|
|
|
|
|
delete[] str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AddSingleKeyRemap(void* config, int originalKey, int targetKey)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), static_cast<DWORD>(targetKey));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
if (text == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mappingConfig->AddSingleKeyToTextRemap(static_cast<DWORD>(originalKey), text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AddSingleKeyToShortcutRemap(void* config, int originalKey, const wchar_t* targetKeys)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
if (!targetKeys)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Shortcut targetShortcut(targetKeys);
|
|
|
|
|
|
|
|
|
|
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), targetShortcut);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AddShortcutRemap(void* config,
|
|
|
|
|
const wchar_t* originalKeys,
|
|
|
|
|
const wchar_t* targetKeys,
|
|
|
|
|
const wchar_t* targetApp,
|
2026-05-19 22:00:33 +08:00
|
|
|
int operationType,
|
2026-03-04 15:46:42 -05:00
|
|
|
const wchar_t* appPathOrUri,
|
|
|
|
|
const wchar_t* args,
|
|
|
|
|
const wchar_t* startDirectory,
|
|
|
|
|
int elevation,
|
|
|
|
|
int ifRunningAction,
|
2026-05-19 22:00:33 +08:00
|
|
|
int visibility,
|
|
|
|
|
const wchar_t* templateId,
|
|
|
|
|
const wchar_t* templateParametersJson)
|
2026-03-04 15:46:42 -05:00
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
Shortcut originalShortcut(originalKeys);
|
|
|
|
|
|
|
|
|
|
KeyShortcutTextUnion targetShortcut;
|
|
|
|
|
|
|
|
|
|
switch (operationType)
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
targetShortcut = Shortcut(targetKeys);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).runProgramFilePath = std::wstring(appPathOrUri);
|
|
|
|
|
if (args)
|
|
|
|
|
{
|
|
|
|
|
std::get<Shortcut>(targetShortcut).runProgramArgs = std::wstring(args);
|
|
|
|
|
}
|
|
|
|
|
if (startDirectory)
|
|
|
|
|
{
|
|
|
|
|
std::get<Shortcut>(targetShortcut).runProgramStartInDir = std::wstring(startDirectory);
|
|
|
|
|
}
|
|
|
|
|
std::get<Shortcut>(targetShortcut).elevationLevel = static_cast<Shortcut::ElevationLevel>(elevation);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(ifRunningAction);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).startWindowType = static_cast<Shortcut::StartWindowType>(visibility);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
2026-05-19 22:00:33 +08:00
|
|
|
|
|
|
|
|
// Optional template metadata — only set when provided.
|
|
|
|
|
if (templateId && templateId[0] != L'\0')
|
|
|
|
|
{
|
|
|
|
|
std::get<Shortcut>(targetShortcut).templateId = std::wstring(templateId);
|
|
|
|
|
}
|
|
|
|
|
if (templateParametersJson && templateParametersJson[0] != L'\0')
|
|
|
|
|
{
|
|
|
|
|
json::JsonObject paramsObj;
|
|
|
|
|
if (json::JsonObject::TryParse(templateParametersJson, paramsObj))
|
|
|
|
|
{
|
|
|
|
|
for (auto const& kv : paramsObj)
|
|
|
|
|
{
|
|
|
|
|
if (kv.Value().ValueType() == json::JsonValueType::String)
|
|
|
|
|
{
|
|
|
|
|
std::get<Shortcut>(targetShortcut).templateParameters.emplace(
|
|
|
|
|
std::wstring(kv.Key()),
|
|
|
|
|
std::wstring(kv.Value().GetString()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-04 15:46:42 -05:00
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
targetShortcut = Shortcut(targetKeys);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).uriToOpen = std::wstring(appPathOrUri);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
targetShortcut = std::wstring(targetKeys);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
targetShortcut = Shortcut(targetKeys);
|
|
|
|
|
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::wstring app(targetApp ? targetApp : L"");
|
|
|
|
|
|
|
|
|
|
if (app.empty())
|
|
|
|
|
{
|
|
|
|
|
return mappingConfig->AddOSLevelShortcut(originalShortcut, targetShortcut);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return mappingConfig->AddAppSpecificShortcut(app, originalShortcut, targetShortcut);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount)
|
|
|
|
|
{
|
|
|
|
|
if (keyName == nullptr || maxCount <= 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
LayoutMap layoutMap;
|
|
|
|
|
std::wstring name = layoutMap.GetKeyName(static_cast<DWORD>(keyCode));
|
|
|
|
|
wcsncpy_s(keyName, maxCount, name.c_str(), _TRUNCATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetKeyCodeFromName(const wchar_t* keyName)
|
|
|
|
|
{
|
|
|
|
|
Logger::info(L"Getting key code for key name: {0}", keyName ? keyName : L"null");
|
|
|
|
|
|
|
|
|
|
if (keyName == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LayoutMap layoutMap;
|
|
|
|
|
std::wstring name(keyName);
|
|
|
|
|
int keyCode = static_cast<int>(layoutMap.GetKeyFromName(name));
|
|
|
|
|
Logger::info(L"Key code for key name {0}: {1}", keyName, keyCode);
|
|
|
|
|
return keyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to get the type of a key (Win, Ctrl, Alt, Shift, or Action)
|
|
|
|
|
int GetKeyType(int key)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(Helpers::GetKeyType(static_cast<DWORD>(key)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to check if a shortcut is illegal
|
|
|
|
|
bool IsShortcutIllegal(const wchar_t* shortcutKeys)
|
|
|
|
|
{
|
|
|
|
|
Logger::info(L"Checking if shortcut is illegal: {0}", shortcutKeys ? shortcutKeys : L"null");
|
|
|
|
|
|
|
|
|
|
if (!shortcutKeys)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Shortcut shortcut(shortcutKeys);
|
|
|
|
|
|
|
|
|
|
ShortcutErrorType result = EditorHelpers::IsShortcutIllegal(shortcut);
|
|
|
|
|
|
|
|
|
|
// Return true if an error was detected (anything other than NoError)
|
|
|
|
|
return result != ShortcutErrorType::NoError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to check if two shortcuts are equal
|
|
|
|
|
bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort)
|
|
|
|
|
{
|
|
|
|
|
if (!lShort || !rShort)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Shortcut lhs(lShort);
|
|
|
|
|
Shortcut rhs(rShort);
|
|
|
|
|
|
|
|
|
|
return lhs == rhs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to delete a single key remapping
|
|
|
|
|
bool DeleteSingleKeyRemap(void* config, int originalKey)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
// Find and delete the single key remapping
|
|
|
|
|
auto it = mappingConfig->singleKeyReMap.find(static_cast<DWORD>(originalKey));
|
|
|
|
|
if (it != mappingConfig->singleKeyReMap.end())
|
|
|
|
|
{
|
|
|
|
|
mappingConfig->singleKeyReMap.erase(it);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DeleteSingleKeyToTextRemap(void* config, int originalKey)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
auto it = mappingConfig->singleKeyToTextReMap.find(originalKey);
|
|
|
|
|
if (it != mappingConfig->singleKeyToTextReMap.end())
|
|
|
|
|
{
|
|
|
|
|
mappingConfig->singleKeyToTextReMap.erase(it);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to delete a shortcut remapping
|
|
|
|
|
bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp)
|
|
|
|
|
{
|
|
|
|
|
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
|
|
|
|
|
|
|
|
|
if (originalKeys == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::wstring appName = targetApp ? targetApp : L"";
|
|
|
|
|
Shortcut shortcut(originalKeys);
|
|
|
|
|
|
|
|
|
|
// Determine the type of remapping to delete based on the app name
|
|
|
|
|
if (appName.empty())
|
|
|
|
|
{
|
|
|
|
|
// Delete OS level shortcut mapping
|
|
|
|
|
auto it = mappingConfig->osLevelShortcutReMap.find(shortcut);
|
|
|
|
|
if (it != mappingConfig->osLevelShortcutReMap.end())
|
|
|
|
|
{
|
|
|
|
|
mappingConfig->osLevelShortcutReMap.erase(it);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Delete app-specific shortcut mapping
|
|
|
|
|
auto appIt = mappingConfig->appSpecificShortcutReMap.find(appName);
|
|
|
|
|
if (appIt != mappingConfig->appSpecificShortcutReMap.end())
|
|
|
|
|
{
|
|
|
|
|
auto shortcutIt = appIt->second.find(shortcut);
|
|
|
|
|
if (shortcutIt != appIt->second.end())
|
|
|
|
|
{
|
|
|
|
|
appIt->second.erase(shortcutIt);
|
|
|
|
|
|
|
|
|
|
// If the app-specific mapping is empty, remove the app entry
|
|
|
|
|
if (appIt->second.empty())
|
|
|
|
|
{
|
|
|
|
|
mappingConfig->appSpecificShortcutReMap.erase(appIt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-02-18 17:10:15 +08:00
|
|
|
}
|
2026-03-04 15:46:42 -05:00
|
|
|
|
|
|
|
|
// Get the list of keyboard keys in Editor
|
|
|
|
|
int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount)
|
|
|
|
|
{
|
|
|
|
|
if (keyList == nullptr || maxCount <= 0)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LayoutMap layoutMap;
|
|
|
|
|
auto keyNameList = layoutMap.GetKeyNameList(isShortcut);
|
|
|
|
|
|
|
|
|
|
int count = (std::min)(static_cast<int>(keyNameList.size()), maxCount);
|
|
|
|
|
|
|
|
|
|
// Transfer the key list to the output struct format
|
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
|
|
|
{
|
|
|
|
|
keyList[i].keyCode = static_cast<int>(keyNameList[i].first);
|
|
|
|
|
wcsncpy_s(keyList[i].keyName, keyNameList[i].second.c_str(), _countof(keyList[i].keyName) - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}
|