Files
PowerToys/src/common/interop/keyboard_layout.cpp
Josh Soref bf16e10baf Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request

- #39572 updated check-spelling but ignored:
   > 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an 
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.

This means that check-spelling hasn't been properly doing its job 😦.

I'm sorry, I should have pushed a thing to this repo earlier,...

Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.

About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset

](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).

The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.

You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23

---------

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 17:16:52 -05:00

375 lines
13 KiB
C++

#include "pch.h"
#include <array>
#include <algorithm>
#include "keyboard_layout_impl.h"
#include "shared_constants.h"
constexpr DWORD numpadOriginBit = 1ull << 31;
LayoutMap::LayoutMap() :
impl(new LayoutMap::LayoutMapImpl())
{
}
LayoutMap::~LayoutMap()
{
delete impl;
}
void LayoutMap::UpdateLayout()
{
impl->UpdateLayout();
}
std::wstring LayoutMap::GetKeyName(DWORD key)
{
return impl->GetKeyName(key);
}
DWORD LayoutMap::GetKeyFromName(const std::wstring& name)
{
auto list = impl->GetKeyNameList(false);
for (const auto& [value, key] : list)
{
if (key == name)
return value;
}
return {};
}
std::vector<DWORD> LayoutMap::GetKeyCodeList(const bool isShortcut)
{
return impl->GetKeyCodeList(isShortcut);
}
std::vector<std::pair<DWORD, std::wstring>> LayoutMap::GetKeyNameList(const bool isShortcut)
{
return impl->GetKeyNameList(isShortcut);
}
// Function to return the unicode string name of the key
std::wstring LayoutMap::LayoutMapImpl::GetKeyName(DWORD key)
{
std::wstring result = L"Undefined";
std::lock_guard<std::mutex> lock(keyboardLayoutMap_mutex);
UpdateLayout();
auto it = keyboardLayoutMap.find(key);
if (it != keyboardLayoutMap.end())
{
result = it->second;
}
return result;
}
bool mapKeycodeToUnicode(const int vCode, HKL layout, const BYTE* keyState, std::array<wchar_t, 3>& outBuffer)
{
// Get the scan code from the virtual key code
const UINT scanCode = MapVirtualKeyExW(vCode, MAPVK_VK_TO_VSC, layout);
// Get the unicode representation from the virtual key code and scan code pair
const UINT wFlags = 1 << 2; // If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)
const int result = ToUnicodeEx(vCode, scanCode, keyState, outBuffer.data(), static_cast<int>(outBuffer.size()), wFlags, layout);
return result != 0;
}
// Update Keyboard layout according to input locale identifier
void LayoutMap::LayoutMapImpl::UpdateLayout()
{
// Get keyboard layout for current thread
const HKL layout = GetKeyboardLayout(0);
if (layout == previousLayout)
{
return;
}
previousLayout = layout;
if (!isKeyCodeListGenerated)
{
unicodeKeys.clear();
unknownKeys.clear();
}
std::array<BYTE, 256> btKeys = { 0 };
// Only set the Caps Lock key to on for the key names in uppercase
btKeys[VK_CAPITAL] = 1;
// Iterate over all the virtual key codes. virtual key 0 is not used
for (int i = 1; i < 256; i++)
{
std::array<wchar_t, 3> szBuffer = { 0 };
if (mapKeycodeToUnicode(i, layout, btKeys.data(), szBuffer))
{
keyboardLayoutMap[i] = szBuffer.data();
if (!isKeyCodeListGenerated)
{
unicodeKeys[i] = szBuffer.data();
}
continue;
}
// Store the virtual key code as string
std::wstring vk = L"VK ";
vk += std::to_wstring(i);
keyboardLayoutMap[i] = vk;
if (!isKeyCodeListGenerated)
{
unknownKeys[i] = vk;
}
}
// Override special key names like Shift, Ctrl, etc. because they don't have unicode mappings and key names like Enter, Space as they appear as "\r", " "
// To do: localization
keyboardLayoutMap[VK_CANCEL] = L"Break";
keyboardLayoutMap[VK_BACK] = L"Backspace";
keyboardLayoutMap[VK_TAB] = L"Tab";
keyboardLayoutMap[VK_CLEAR] = L"Clear";
keyboardLayoutMap[VK_SHIFT] = L"Shift";
keyboardLayoutMap[VK_CONTROL] = L"Ctrl";
keyboardLayoutMap[VK_MENU] = L"Alt";
keyboardLayoutMap[VK_PAUSE] = L"Pause";
keyboardLayoutMap[VK_CAPITAL] = L"Caps Lock";
keyboardLayoutMap[VK_ESCAPE] = L"Esc";
keyboardLayoutMap[VK_SPACE] = L"Space";
keyboardLayoutMap[VK_LEFT] = L"Left";
keyboardLayoutMap[VK_RIGHT] = L"Right";
keyboardLayoutMap[VK_UP] = L"Up";
keyboardLayoutMap[VK_DOWN] = L"Down";
keyboardLayoutMap[VK_INSERT] = L"Insert";
keyboardLayoutMap[VK_DELETE] = L"Delete";
keyboardLayoutMap[VK_PRIOR] = L"PgUp";
keyboardLayoutMap[VK_NEXT] = L"PgDn";
keyboardLayoutMap[VK_HOME] = L"Home";
keyboardLayoutMap[VK_END] = L"End";
keyboardLayoutMap[VK_RETURN] = L"Enter";
keyboardLayoutMap[VK_LEFT | numpadOriginBit] = L"Left (Numpad)";
keyboardLayoutMap[VK_RIGHT | numpadOriginBit] = L"Right (Numpad)";
keyboardLayoutMap[VK_UP | numpadOriginBit] = L"Up (Numpad)";
keyboardLayoutMap[VK_DOWN | numpadOriginBit] = L"Down (Numpad)";
keyboardLayoutMap[VK_INSERT | numpadOriginBit] = L"Insert (Numpad)";
keyboardLayoutMap[VK_DELETE | numpadOriginBit] = L"Delete (Numpad)";
keyboardLayoutMap[VK_PRIOR | numpadOriginBit] = L"PgUp (Numpad)";
keyboardLayoutMap[VK_NEXT | numpadOriginBit] = L"PgDn (Numpad)";
keyboardLayoutMap[VK_HOME | numpadOriginBit] = L"Home (Numpad)";
keyboardLayoutMap[VK_END | numpadOriginBit] = L"End (Numpad)";
keyboardLayoutMap[VK_RETURN | numpadOriginBit] = L"Enter (Numpad)";
keyboardLayoutMap[VK_DIVIDE | numpadOriginBit] = L"/ (Numpad)";
keyboardLayoutMap[VK_SUBTRACT] = L"- (Subtract)";
keyboardLayoutMap[VK_SELECT] = L"Select";
keyboardLayoutMap[VK_PRINT] = L"Print";
keyboardLayoutMap[VK_EXECUTE] = L"Execute";
keyboardLayoutMap[VK_SNAPSHOT] = L"Print Screen";
keyboardLayoutMap[VK_HELP] = L"Help";
keyboardLayoutMap[VK_LWIN] = L"Win (Left)";
keyboardLayoutMap[VK_RWIN] = L"Win (Right)";
keyboardLayoutMap[VK_APPS] = L"Apps/Menu";
keyboardLayoutMap[VK_SLEEP] = L"Sleep";
keyboardLayoutMap[VK_NUMPAD0] = L"NumPad 0";
keyboardLayoutMap[VK_NUMPAD1] = L"NumPad 1";
keyboardLayoutMap[VK_NUMPAD2] = L"NumPad 2";
keyboardLayoutMap[VK_NUMPAD3] = L"NumPad 3";
keyboardLayoutMap[VK_NUMPAD4] = L"NumPad 4";
keyboardLayoutMap[VK_NUMPAD5] = L"NumPad 5";
keyboardLayoutMap[VK_NUMPAD6] = L"NumPad 6";
keyboardLayoutMap[VK_NUMPAD7] = L"NumPad 7";
keyboardLayoutMap[VK_NUMPAD8] = L"NumPad 8";
keyboardLayoutMap[VK_NUMPAD9] = L"NumPad 9";
keyboardLayoutMap[VK_SEPARATOR] = L"Separator";
keyboardLayoutMap[VK_F1] = L"F1";
keyboardLayoutMap[VK_F2] = L"F2";
keyboardLayoutMap[VK_F3] = L"F3";
keyboardLayoutMap[VK_F4] = L"F4";
keyboardLayoutMap[VK_F5] = L"F5";
keyboardLayoutMap[VK_F6] = L"F6";
keyboardLayoutMap[VK_F7] = L"F7";
keyboardLayoutMap[VK_F8] = L"F8";
keyboardLayoutMap[VK_F9] = L"F9";
keyboardLayoutMap[VK_F10] = L"F10";
keyboardLayoutMap[VK_F11] = L"F11";
keyboardLayoutMap[VK_F12] = L"F12";
keyboardLayoutMap[VK_F13] = L"F13";
keyboardLayoutMap[VK_F14] = L"F14";
keyboardLayoutMap[VK_F15] = L"F15";
keyboardLayoutMap[VK_F16] = L"F16";
keyboardLayoutMap[VK_F17] = L"F17";
keyboardLayoutMap[VK_F18] = L"F18";
keyboardLayoutMap[VK_F19] = L"F19";
keyboardLayoutMap[VK_F20] = L"F20";
keyboardLayoutMap[VK_F21] = L"F21";
keyboardLayoutMap[VK_F22] = L"F22";
keyboardLayoutMap[VK_F23] = L"F23";
keyboardLayoutMap[VK_F24] = L"F24";
keyboardLayoutMap[VK_NUMLOCK] = L"Num Lock";
keyboardLayoutMap[VK_SCROLL] = L"Scroll Lock";
keyboardLayoutMap[VK_LSHIFT] = L"Shift (Left)";
keyboardLayoutMap[VK_RSHIFT] = L"Shift (Right)";
keyboardLayoutMap[VK_LCONTROL] = L"Ctrl (Left)";
keyboardLayoutMap[VK_RCONTROL] = L"Ctrl (Right)";
keyboardLayoutMap[VK_LMENU] = L"Alt (Left)";
keyboardLayoutMap[VK_RMENU] = L"Alt (Right)";
keyboardLayoutMap[VK_BROWSER_BACK] = L"Browser Back";
keyboardLayoutMap[VK_BROWSER_FORWARD] = L"Browser Forward";
keyboardLayoutMap[VK_BROWSER_REFRESH] = L"Browser Refresh";
keyboardLayoutMap[VK_BROWSER_STOP] = L"Browser Stop";
keyboardLayoutMap[VK_BROWSER_SEARCH] = L"Browser Search";
keyboardLayoutMap[VK_BROWSER_FAVORITES] = L"Browser Favorites";
keyboardLayoutMap[VK_BROWSER_HOME] = L"Browser Home";
keyboardLayoutMap[VK_VOLUME_MUTE] = L"Volume Mute";
keyboardLayoutMap[VK_VOLUME_DOWN] = L"Volume Down";
keyboardLayoutMap[VK_VOLUME_UP] = L"Volume Up";
keyboardLayoutMap[VK_MEDIA_NEXT_TRACK] = L"Next Track";
keyboardLayoutMap[VK_MEDIA_PREV_TRACK] = L"Previous Track";
keyboardLayoutMap[VK_MEDIA_STOP] = L"Stop Media";
keyboardLayoutMap[VK_MEDIA_PLAY_PAUSE] = L"Play/Pause Media";
keyboardLayoutMap[VK_LAUNCH_MAIL] = L"Start Mail";
keyboardLayoutMap[VK_LAUNCH_MEDIA_SELECT] = L"Select Media";
keyboardLayoutMap[VK_LAUNCH_APP1] = L"Start App 1";
keyboardLayoutMap[VK_LAUNCH_APP2] = L"Start App 2";
keyboardLayoutMap[VK_PACKET] = L"Packet";
keyboardLayoutMap[VK_ATTN] = L"Attn";
keyboardLayoutMap[VK_CRSEL] = L"CrSel";
keyboardLayoutMap[VK_EXSEL] = L"ExSel";
keyboardLayoutMap[VK_EREOF] = L"Erase EOF";
keyboardLayoutMap[VK_PLAY] = L"Play";
keyboardLayoutMap[VK_ZOOM] = L"Zoom";
keyboardLayoutMap[VK_PA1] = L"PA1";
keyboardLayoutMap[VK_OEM_CLEAR] = L"Clear";
keyboardLayoutMap[0xFF] = L"Undefined";
keyboardLayoutMap[CommonSharedConstants::VK_WIN_BOTH] = L"Win";
keyboardLayoutMap[VK_KANA] = L"IME Kana";
keyboardLayoutMap[VK_HANGEUL] = L"IME Hangeul";
keyboardLayoutMap[VK_HANGUL] = L"IME Hangul";
keyboardLayoutMap[VK_IME_ON] = L"IME On";
keyboardLayoutMap[VK_JUNJA] = L"IME Junja";
keyboardLayoutMap[VK_FINAL] = L"IME Final";
keyboardLayoutMap[VK_HANJA] = L"IME Hanja";
keyboardLayoutMap[VK_KANJI] = L"IME Kanji";
keyboardLayoutMap[VK_IME_OFF] = L"IME Off";
keyboardLayoutMap[VK_CONVERT] = L"IME Convert";
keyboardLayoutMap[VK_NONCONVERT] = L"IME Non-Convert";
keyboardLayoutMap[VK_ACCEPT] = L"IME Kana";
keyboardLayoutMap[VK_MODECHANGE] = L"IME Mode Change";
keyboardLayoutMap[VK_DECIMAL] = L". (Numpad)";
keyboardLayoutMap[CommonSharedConstants::VK_DISABLED] = L"Disable";
}
// Function to return the list of key codes in the order for the drop down. It creates it if it doesn't exist
std::vector<DWORD> LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcut)
{
std::lock_guard<std::mutex> lock(keyboardLayoutMap_mutex);
UpdateLayout();
std::vector<DWORD> keyCodes;
if (!isKeyCodeListGenerated)
{
// Add character keys
for (auto& it : unicodeKeys)
{
// If it was not renamed with a special name
if (it.second == keyboardLayoutMap[it.first])
{
keyCodes.push_back(it.first);
}
}
// Add modifier keys in alphabetical order
keyCodes.push_back(VK_MENU);
keyCodes.push_back(VK_LMENU);
keyCodes.push_back(VK_RMENU);
keyCodes.push_back(VK_CONTROL);
keyCodes.push_back(VK_LCONTROL);
keyCodes.push_back(VK_RCONTROL);
keyCodes.push_back(VK_SHIFT);
keyCodes.push_back(VK_LSHIFT);
keyCodes.push_back(VK_RSHIFT);
keyCodes.push_back(CommonSharedConstants::VK_WIN_BOTH);
keyCodes.push_back(VK_LWIN);
keyCodes.push_back(VK_RWIN);
// Add all other special keys
std::vector<DWORD> specialKeys;
for (int i = 1; i < 256; i++)
{
// If it is not already been added (i.e. it was either a modifier or had a unicode representation)
if (std::find(keyCodes.begin(), keyCodes.end(), i) == keyCodes.end())
{
// If it is any other key but it is not named as VK #
auto it = unknownKeys.find(i);
if (it == unknownKeys.end())
{
specialKeys.push_back(i);
}
else if (unknownKeys[i] != keyboardLayoutMap[i])
{
specialKeys.push_back(i);
}
}
}
// Add numpad keys
for (auto it = keyboardLayoutMap.rbegin(); it->first & numpadOriginBit; ++it)
{
keyCodes.push_back(it->first);
}
// Sort the special keys in alphabetical order
std::sort(specialKeys.begin(), specialKeys.end(), [&](const DWORD& lhs, const DWORD& rhs) {
return keyboardLayoutMap[lhs] < keyboardLayoutMap[rhs];
});
for (int i = 0; i < specialKeys.size(); i++)
{
keyCodes.push_back(specialKeys[i]);
}
// Add unknown keys
for (auto& it : unknownKeys)
{
// If it was not renamed with a special name
if (it.second == keyboardLayoutMap[it.first])
{
keyCodes.push_back(it.first);
}
}
keyCodeList = keyCodes;
isKeyCodeListGenerated = true;
}
else
{
keyCodes = keyCodeList;
}
// If it is a key list for the shortcut control then we add a "None" key at the start
if (isShortcut)
{
keyCodes.insert(keyCodes.begin(), 0);
}
return keyCodes;
}
std::vector<std::pair<DWORD, std::wstring>> LayoutMap::LayoutMapImpl::GetKeyNameList(const bool isShortcut)
{
std::vector<std::pair<DWORD, std::wstring>> keyNames;
std::vector<DWORD> keyCodes = GetKeyCodeList(isShortcut);
std::lock_guard<std::mutex> lock(keyboardLayoutMap_mutex);
// If it is a key list for the shortcut control then we add a "None" key at the start
if (isShortcut)
{
keyNames.push_back({ 0, L"None" });
for (int i = 1; i < keyCodes.size(); i++)
{
keyNames.push_back({ keyCodes[i], keyboardLayoutMap[keyCodes[i]] });
}
}
else
{
for (int i = 0; i < keyCodes.size(); i++)
{
keyNames.push_back({ keyCodes[i], keyboardLayoutMap[keyCodes[i]] });
}
}
return keyNames;
}