Reputation: 5078
I've written a hotkey control for WPF, and want to display the friendly name to the user. For that I'm using GetKeyNameText
.
However, e.g. when using Key.MediaNextTrack
as input, GetKeyNameText
returns P
, what apparently looks wrong. Can anyone help me to get the correct name for such esoteric keys?
My code does following:
KeyInterop.VirtualKeyFromKey
to get the Win32 virtual keyMapVirtualKey
GetKeyNameText
The complete code is this (you need to reference WindowsBase):
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Input;
namespace ConsoleApplication1 {
class Program {
static void Main() {
var key = Key.MediaNextTrack;
var virtualKeyFromKey = KeyInterop.VirtualKeyFromKey(key);
var displayString = GetLocalizedKeyStringUnsafe(virtualKeyFromKey);
Console.WriteLine($"{key}: {displayString}");
}
private static string GetLocalizedKeyStringUnsafe(int key) {
// strip any modifier keys
long keyCode = key & 0xffff;
var sb = new StringBuilder(256);
long scanCode = MapVirtualKey((uint) keyCode, MAPVK_VK_TO_VSC);
// shift the scancode to the high word
scanCode = (scanCode << 16); // | (1 << 24);
if (keyCode == 45 ||
keyCode == 46 ||
keyCode == 144 ||
(33 <= keyCode && keyCode <= 40)) {
// add the extended key flag
scanCode |= 0x1000000;
}
GetKeyNameText((int) scanCode, sb, 256);
return sb.ToString();
}
private const uint MAPVK_VK_TO_VSC = 0x00;
[DllImport("user32.dll")]
private static extern int MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", EntryPoint = "GetKeyNameTextW", CharSet = CharSet.Unicode)]
private static extern int GetKeyNameText(int lParam, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder str, int size);
}
}
Upvotes: 2
Views: 3547
Reputation: 1431
Names for these media keys are not contained in keyboard layout dll and thus not available via Win32 API. Scan code list is documented here.
Here is my wrapper around GetKeyNameTextW
API in C++:
// Clears keyboard buffer
// Needed to avoid side effects on other calls to ToUnicode API
// http://archives.miloush.net/michkap/archive/2007/10/27/5717859.html
inline void ClearKeyboardBuffer(uint16_t vkCode)
{
std::array<wchar_t, 10> chars{};
const uint16_t scanCode = LOWORD(::MapVirtualKeyW(vkCode, MAPVK_VK_TO_VSC_EX));
int count = 0;
do
{
count = ::ToUnicode(vkCode, scanCode, nullptr, chars.data(), static_cast<int>(chars.size()), 0);
} while (count < 0);
}
std::string GetStringFromKeyPress(uint16_t scanCode)
{
std::array<wchar_t, 10> chars{};
const uint16_t vkCode = LOWORD(::MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX));
std::array<uint8_t, 256> keyboardState{};
// Turn on CapsLock to return capital letters
keyboardState[VK_CAPITAL] = 0b00000001;
ClearKeyboardBuffer(VK_DECIMAL);
// For some keyboard layouts ToUnicode() API call can produce multiple chars: UTF-16 surrogate pairs or ligatures.
// Such layouts are listed here: https://kbdlayout.info/features/ligatures
int count = ::ToUnicode(vkCode, scanCode, keyboardState.data(), chars.data(), static_cast<int>(chars.size()), 0);
ClearKeyboardBuffer(VK_DECIMAL);
return utf8::narrow(chars.data(), std::abs(count));
}
std::string GetScanCodeName(uint16_t scanCode)
{
static struct
{
uint16_t scanCode;
const char* keyText;
} mediaKeys[] =
{
{ 0xe010, "Previous Track"},
{ 0xe019, "Next Track"},
{ 0xe020, "Volume Mute"},
{ 0xe021, "Launch App 2"},
{ 0xe022, "Media Play/Pause"},
{ 0xe024, "Media Stop"},
{ 0xe02e, "Volume Down"},
{ 0xe030, "Volume Up"},
{ 0xe032, "Browser Home"},
{ 0xe05e, "System Power"},
{ 0xe05f, "System Sleep"},
{ 0xe063, "System Wake"},
{ 0xe065, "Browser Search"},
{ 0xe066, "Browser Favorites"},
{ 0xe067, "Browser Refresh"},
{ 0xe068, "Browser Stop"},
{ 0xe069, "Browser Forward"},
{ 0xe06a, "Browser Back"},
{ 0xe06b, "Launch App 1"},
{ 0xe06c, "Launch Mail"},
{ 0xe06d, "Launch Media Selector"}
};
auto it = std::find_if(std::begin(mediaKeys), std::end(mediaKeys),
[scanCode](auto& key) { return key.scanCode == scanCode; });
if (it != std::end(mediaKeys))
return it->keyText;
std::string keyText = GetStringFromKeyPress(scanCode);
std::wstring keyTextWide = utf8::widen(keyText);
if (!keyTextWide.empty() && !std::iswblank(keyTextWide[0]) && !std::iswcntrl(keyTextWide[0]))
{
return keyText;
}
std::array<wchar_t, 128> buffer{};
const LPARAM lParam = MAKELPARAM(0, ((scanCode & 0xff00) ? KF_EXTENDED : 0) | (scanCode & 0xff));
int count = ::GetKeyNameTextW(static_cast<LONG>(lParam), buffer.data(), static_cast<int>(buffer.size()));
return utf8::narrow(buffer.data(), count);
}
In RawInput API you can get full scan code via such code:
// ...got `RAWINPUT* input` from WM_INPUT message
if (raw->header.dwType == RIM_TYPEKEYBOARD)
{
RAWKEYBOARD& keyboard = raw->data.keyboard;
// Ignore key overrun state
if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE)
return 0;
// Ignore keys not mapped to any virtual key code
if (keyboard.VKey >= UCHAR_MAX)
return 0;
BOOL keyUp = keyboard.Flags & RI_KEY_BREAK;
// Some apps may send wrong make scan codes with
// high-order bit set (key break code).
// Strip high-order bit and add extended scan code value.
WORD scanCode = MAKEWORD(keyboard.MakeCode & 0x7f, ((keyboard.Flags & RI_KEY_E0) ? 0xe0 : ((keyboard.Flags & RI_KEY_E1) ? 0xe1 : 0x00)));
if (!keyboard.MakeCode)
{
// Scan codes may be empty for some buttons (like multimedia buttons).
// Try to map them from the virtual key code.
scanCode = LOWORD(MapVirtualKey(keyboard.VKey, MAPVK_VK_TO_VSC_EX));
}
std::string scanCodeName = GetScanCodeName(scanCode);
}
Upvotes: 1
Reputation: 1225
&ffff
.Upvotes: 0