Browse Source

Key handling improvements (#12549)

* Physical key handling for Windows

* Physical key handling for macOS

* Physical key handling for X11

* Physical keys: cleanup unused keys

* Key symbols: ensure consistent behavior between platforms

* Fix dead key symbol for Windows

* Physical key handling for browser

* Physical keys: use new overloads where possible

* Key symbol for VNC

* Physical key handling in previewer

* Key symbol for forwarded X11 IME key

* Key symbol for Android

* Obsolete old RawKeyEventArgs ctor

* Fix key symbols for macOS with modifiers

* Adjust PhysicalKey members naming

* Use explicit std::hash for AvnKey/AvnPhysicalKey

Should hopefully satisfy the older compiler on the CI server

* Headless: added KeyPressQwerty

---------

Co-authored-by: Dan Walmsley <dan@walms.co.uk>
Co-authored-by: Steven Kirk <grokys@users.noreply.github.com>
pull/12996/head
Julien Lebosquain 2 years ago
committed by GitHub
parent
commit
ac00fe2bf4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      native/Avalonia.Native/src/OSX/AvnView.mm
  2. 11
      native/Avalonia.Native/src/OSX/KeyTransform.h
  3. 866
      native/Avalonia.Native/src/OSX/KeyTransform.mm
  4. 23
      native/Avalonia.Native/src/OSX/menu.mm
  5. 5
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  6. 76
      src/Avalonia.Base/Input/KeyEventArgs.cs
  7. 26
      src/Avalonia.Base/Input/KeySymbolHelper.cs
  8. 6
      src/Avalonia.Base/Input/KeyboardDevice.cs
  9. 944
      src/Avalonia.Base/Input/PhysicalKey.cs
  10. 394
      src/Avalonia.Base/Input/PhysicalKeyExtensions.cs
  11. 29
      src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
  12. 5
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  13. 4
      src/Avalonia.Controls/TopLevel.cs
  14. 22
      src/Avalonia.Native/WindowImplBase.cs
  15. 549
      src/Avalonia.Native/avn.idl
  16. 2
      src/Avalonia.Native/regen.sh
  17. 1
      src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
  18. 11
      src/Avalonia.Remote.Protocol/InputMessages.cs
  19. 25
      src/Avalonia.X11/X11Info.cs
  20. 209
      src/Avalonia.X11/X11KeyTransform.cs
  21. 256
      src/Avalonia.X11/X11Window.Ime.cs
  22. 3
      src/Avalonia.X11/X11Window.cs
  23. 32
      src/Avalonia.X11/XLib.cs
  24. 48
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  25. 435
      src/Browser/Avalonia.Browser/KeyInterop.cs
  26. 129
      src/Browser/Avalonia.Browser/Keycodes.cs
  27. 408
      src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  28. 32
      src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
  29. 27
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  30. 6
      src/Headless/Avalonia.Headless/IHeadlessWindow.cs
  31. 8
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  32. 816
      src/Windows/Avalonia.Win32/Input/KeyInterop.cs
  33. 98
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  34. 25
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  35. 49
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  36. 13
      src/iOS/Avalonia.iOS/TextInputResponder.cs
  37. 12
      tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs
  38. 4
      tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs
  39. 6
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  40. 24
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

23
native/Avalonia.Native/src/OSX/AvnView.mm

@ -447,24 +447,25 @@
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
if([self ignoreUserInput: false])
if([self ignoreUserInput: false] || _parent == nullptr)
{
return;
}
auto key = s_KeyMap[[event keyCode]];
auto scanCode = [event keyCode];
auto key = VirtualKeyFromScanCode(scanCode, [event modifierFlags]);
auto physicalKey = PhysicalKeyFromScanCode(scanCode);
auto keySymbol = KeySymbolFromScanCode(scanCode, [event modifierFlags]);
auto keySymbolUtf8 = keySymbol == nullptr ? nullptr : [keySymbol UTF8String];
uint64_t timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]];
if(_parent != nullptr)
{
auto handled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
if (key != LeftCtrl && key != RightCtrl) {
_lastKeyHandled = handled;
} else {
_lastKeyHandled = false;
}
auto handled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key, physicalKey, keySymbolUtf8);
if (key != AvnKeyLeftCtrl && key != AvnKeyRightCtrl) {
_lastKeyHandled = handled;
} else {
_lastKeyHandled = false;
}
}

11
native/Avalonia.Native/src/OSX/KeyTransform.h

@ -1,14 +1,15 @@
#ifndef keytransform_h
#define keytransform_h
#import <cstdint>
#include "common.h"
#include <map>
extern std::map<int, AvnKey> s_KeyMap;
AvnPhysicalKey PhysicalKeyFromScanCode(uint16_t scanCode);
extern std::map<AvnKey, int> s_AvnKeyMap;
AvnKey VirtualKeyFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags);
extern std::map<int, const char*> s_QwertyKeyMap;
NSString* KeySymbolFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags);
extern std::map<AvnKey, int> s_UnicodeKeyMap;
uint16_t MenuCharFromVirtualKey(AvnKey key);
#endif

866
native/Avalonia.Native/src/OSX/KeyTransform.mm

@ -1,391 +1,511 @@
#include "KeyTransform.h"
const int kVK_ANSI_A = 0x00;
const int kVK_ANSI_S = 0x01;
const int kVK_ANSI_D = 0x02;
const int kVK_ANSI_F = 0x03;
const int kVK_ANSI_H = 0x04;
const int kVK_ANSI_G = 0x05;
const int kVK_ANSI_Z = 0x06;
const int kVK_ANSI_X = 0x07;
const int kVK_ANSI_C = 0x08;
const int kVK_ANSI_V = 0x09;
const int kVK_ANSI_B = 0x0B;
const int kVK_ANSI_Q = 0x0C;
const int kVK_ANSI_W = 0x0D;
const int kVK_ANSI_E = 0x0E;
const int kVK_ANSI_R = 0x0F;
const int kVK_ANSI_Y = 0x10;
const int kVK_ANSI_T = 0x11;
const int kVK_ANSI_1 = 0x12;
const int kVK_ANSI_2 = 0x13;
const int kVK_ANSI_3 = 0x14;
const int kVK_ANSI_4 = 0x15;
const int kVK_ANSI_6 = 0x16;
const int kVK_ANSI_5 = 0x17;
const int kVK_ANSI_Equal = 0x18;
const int kVK_ANSI_9 = 0x19;
const int kVK_ANSI_7 = 0x1A;
const int kVK_ANSI_Minus = 0x1B;
const int kVK_ANSI_8 = 0x1C;
const int kVK_ANSI_0 = 0x1D;
const int kVK_ANSI_RightBracket = 0x1E;
const int kVK_ANSI_O = 0x1F;
const int kVK_ANSI_U = 0x20;
const int kVK_ANSI_LeftBracket = 0x21;
const int kVK_ANSI_I = 0x22;
const int kVK_ANSI_P = 0x23;
const int kVK_ANSI_L = 0x25;
const int kVK_ANSI_J = 0x26;
const int kVK_ANSI_Quote = 0x27;
const int kVK_ANSI_K = 0x28;
const int kVK_ANSI_Semicolon = 0x29;
const int kVK_ANSI_Backslash = 0x2A;
const int kVK_ANSI_Comma = 0x2B;
const int kVK_ANSI_Slash = 0x2C;
const int kVK_ANSI_N = 0x2D;
const int kVK_ANSI_M = 0x2E;
const int kVK_ANSI_Period = 0x2F;
const int kVK_ANSI_Grave = 0x32;
const int kVK_ANSI_KeypadDecimal = 0x41;
const int kVK_ANSI_KeypadMultiply = 0x43;
const int kVK_ANSI_KeypadPlus = 0x45;
const int kVK_ANSI_KeypadClear = 0x47;
const int kVK_ANSI_KeypadDivide = 0x4B;
const int kVK_ANSI_KeypadEnter = 0x4C;
const int kVK_ANSI_KeypadMinus = 0x4E;
const int kVK_ANSI_KeypadEquals = 0x51;
const int kVK_ANSI_Keypad0 = 0x52;
const int kVK_ANSI_Keypad1 = 0x53;
const int kVK_ANSI_Keypad2 = 0x54;
const int kVK_ANSI_Keypad3 = 0x55;
const int kVK_ANSI_Keypad4 = 0x56;
const int kVK_ANSI_Keypad5 = 0x57;
const int kVK_ANSI_Keypad6 = 0x58;
const int kVK_ANSI_Keypad7 = 0x59;
const int kVK_ANSI_Keypad8 = 0x5B;
const int kVK_ANSI_Keypad9 = 0x5C;
const int kVK_Return = 0x24;
const int kVK_Tab = 0x30;
const int kVK_Space = 0x31;
const int kVK_Delete = 0x33;
const int kVK_Escape = 0x35;
const int kVK_Command = 0x37;
const int kVK_Shift = 0x38;
const int kVK_CapsLock = 0x39;
const int kVK_Option = 0x3A;
const int kVK_Control = 0x3B;
const int kVK_RightCommand = 0x36;
const int kVK_RightShift = 0x3C;
const int kVK_RightOption = 0x3D;
const int kVK_RightControl = 0x3E;
//const int kVK_Function = 0x3F;
const int kVK_F17 = 0x40;
const int kVK_VolumeUp = 0x48;
const int kVK_VolumeDown = 0x49;
const int kVK_Mute = 0x4A;
const int kVK_F18 = 0x4F;
const int kVK_F19 = 0x50;
const int kVK_F20 = 0x5A;
const int kVK_F5 = 0x60;
const int kVK_F6 = 0x61;
const int kVK_F7 = 0x62;
const int kVK_F3 = 0x63;
const int kVK_F8 = 0x64;
const int kVK_F9 = 0x65;
const int kVK_F11 = 0x67;
const int kVK_F13 = 0x69;
const int kVK_F16 = 0x6A;
const int kVK_F14 = 0x6B;
const int kVK_F10 = 0x6D;
const int kVK_F12 = 0x6F;
const int kVK_F15 = 0x71;
const int kVK_Help = 0x72;
const int kVK_Home = 0x73;
const int kVK_PageUp = 0x74;
const int kVK_ForwardDelete = 0x75;
const int kVK_F4 = 0x76;
const int kVK_End = 0x77;
const int kVK_F2 = 0x78;
const int kVK_PageDown = 0x79;
const int kVK_F1 = 0x7A;
const int kVK_LeftArrow = 0x7B;
const int kVK_RightArrow = 0x7C;
const int kVK_DownArrow = 0x7D;
const int kVK_UpArrow = 0x7E;
//const int kVK_ISO_Section = 0x0A;
//const int kVK_JIS_Yen = 0x5D;
//const int kVK_JIS_Underscore = 0x5E;
//const int kVK_JIS_KeypadComma = 0x5F;
//const int kVK_JIS_Eisu = 0x66;
const int kVK_JIS_Kana = 0x68;
// converts from AvaloniaKeys to UnicodeSpecial keys.
std::map<AvnKey, int> s_UnicodeKeyMap =
#import <Carbon/Carbon.h>
#include <array>
#include <unordered_map>
struct KeyInfo
{
{ Up, NSUpArrowFunctionKey },
{ Down, NSDownArrowFunctionKey },
{ Left, NSLeftArrowFunctionKey },
{ Right, NSRightArrowFunctionKey },
{ F1, NSF1FunctionKey },
{ F2, NSF2FunctionKey },
{ F3, NSF3FunctionKey },
{ F4, NSF4FunctionKey },
{ F5, NSF5FunctionKey },
{ F6, NSF6FunctionKey },
{ F7, NSF7FunctionKey },
{ F8, NSF8FunctionKey },
{ F9, NSF9FunctionKey },
{ F10, NSF10FunctionKey },
{ F11, NSF11FunctionKey },
{ F12, NSF12FunctionKey },
{ F13, NSF13FunctionKey },
{ F14, NSF14FunctionKey },
{ F15, NSF15FunctionKey },
{ F16, NSF16FunctionKey },
{ F17, NSF17FunctionKey },
{ F18, NSF18FunctionKey },
{ F19, NSF19FunctionKey },
{ F20, NSF20FunctionKey },
{ F21, NSF21FunctionKey },
{ F22, NSF22FunctionKey },
{ F23, NSF23FunctionKey },
{ F24, NSF24FunctionKey },
{ Insert, NSInsertFunctionKey },
{ Delete, NSDeleteFunctionKey },
{ Home, NSHomeFunctionKey },
//{ Begin, NSBeginFunctionKey },
{ End, NSEndFunctionKey },
{ PageUp, NSPageUpFunctionKey },
{ PageDown, NSPageDownFunctionKey },
{ PrintScreen, NSPrintScreenFunctionKey },
{ Scroll, NSScrollLockFunctionKey },
//{ SysReq, NSSysReqFunctionKey },
//{ Break, NSBreakFunctionKey },
//{ Reset, NSResetFunctionKey },
//{ Stop, NSStopFunctionKey },
//{ Menu, NSMenuFunctionKey },
//{ UserFunction, NSUserFunctionKey },
//{ SystemFunction, NSSystemFunctionKey },
{ Print, NSPrintFunctionKey },
//{ ClearLine, NSClearLineFunctionKey },
//{ ClearDisplay, NSClearDisplayFunctionKey },
uint16_t scanCode;
AvnPhysicalKey physicalKey;
AvnKey qwertyKey;
uint16_t menuChar;
};
// Converts from Ansi virtual keys to Qwerty Keyboard map.
std::map<int, const char*> s_QwertyKeyMap =
// ScanCode - PhysicalKey - Key mapping (the virtual key is mapped as in a standard QWERTY keyboard)
// https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/dom_code_data.inc
// This list has the same order as the PhysicalKey enum.
const KeyInfo keyInfos[] =
{
{ 0, "a" },
{ 1, "s" },
{ 2, "d" },
{ 3, "f" },
{ 4, "h" },
{ 5, "g" },
{ 6, "z" },
{ 7, "x" },
{ 8, "c" },
{ 9, "v" },
{ 10, "§" },
{ 11, "b" },
{ 12, "q" },
{ 13, "w" },
{ 14, "e" },
{ 15, "r" },
{ 16, "y" },
{ 17, "t" },
{ 18, "1" },
{ 19, "2" },
{ 20, "3" },
{ 21, "4" },
{ 22, "6" },
{ 23, "5" },
{ 24, "=" },
{ 25, "9" },
{ 26, "7" },
{ 27, "-" },
{ 28, "8" },
{ 29, "0" },
{ 30, "]" },
{ 31, "o" },
{ 32, "u" },
{ 33, "[" },
{ 34, "i" },
{ 35, "p" },
{ 37, "l" },
{ 38, "j" },
{ 39, "'" },
{ 40, "k" },
{ 41, ";" },
{ 42, "\\" },
{ 43, "," },
{ 44, "/" },
{ 45, "n" },
{ 46, "m" },
{ 47, "." },
{ 48, "\t" },
{ 49, " " },
{ 50, "`" },
{ 51, "" },
{ 52, "" },
{ 53, "" },
{ 65, "." },
{ 66, "" },
{ 67, "*" },
{ 69, "+" },
{ 70, "" },
{ 71, "" },
{ 72, "" },
{ 75, "/" },
{ 76, "" },
{ 77, "" },
{ 78, "-" },
{ 81, "=" },
{ 82, "0" },
{ 83, "1" },
{ 84, "2" },
{ 85, "3" },
{ 86, "4" },
{ 87, "5" },
{ 88, "6" },
{ 89, "7" },
{ 91, "8" },
{ 92, "9" }
// Writing System Keys
{ 0x32, AvnPhysicalKeyBackquote, AvnKeyOem3, '`' },
{ 0x2A, AvnPhysicalKeyBackslash, AvnKeyOem5, '\\' },
{ 0x21, AvnPhysicalKeyBracketLeft,AvnKeyOem4, '[' },
{ 0x1E, AvnPhysicalKeyBracketRight, AvnKeyOem6, ']' },
{ 0x2B, AvnPhysicalKeyComma, AvnKeyOemComma, ',' },
{ 0x1D, AvnPhysicalKeyDigit0, AvnKeyD0, '0' },
{ 0x12, AvnPhysicalKeyDigit1, AvnKeyD1, '1' },
{ 0x13, AvnPhysicalKeyDigit2, AvnKeyD2, '2' },
{ 0x14, AvnPhysicalKeyDigit3, AvnKeyD3, '3' },
{ 0x15, AvnPhysicalKeyDigit4, AvnKeyD4, '4' },
{ 0x17, AvnPhysicalKeyDigit5, AvnKeyD5, '5' },
{ 0x16, AvnPhysicalKeyDigit6, AvnKeyD6, '6' },
{ 0x1A, AvnPhysicalKeyDigit7, AvnKeyD7, '7' },
{ 0x1C, AvnPhysicalKeyDigit8, AvnKeyD8, '8' },
{ 0x19, AvnPhysicalKeyDigit9, AvnKeyD9, '9' },
{ 0x18, AvnPhysicalKeyEqual, AvnKeyOemMinus, '-' },
{ 0x0A, AvnPhysicalKeyIntlBackslash, AvnKeyOem102, 0 },
{ 0x5E, AvnPhysicalKeyIntlRo, AvnKeyOem102, 0 },
{ 0x5D, AvnPhysicalKeyIntlYen, AvnKeyOem5, 0 },
{ 0x00, AvnPhysicalKeyA, AvnKeyA, 'a' },
{ 0x0B, AvnPhysicalKeyB, AvnKeyB, 'b' },
{ 0x08, AvnPhysicalKeyC, AvnKeyC, 'c' },
{ 0x02, AvnPhysicalKeyD, AvnKeyD, 'd' },
{ 0x0E, AvnPhysicalKeyE, AvnKeyE, 'e' },
{ 0x03, AvnPhysicalKeyF, AvnKeyF, 'f' },
{ 0x05, AvnPhysicalKeyG, AvnKeyG, 'g' },
{ 0x04, AvnPhysicalKeyH, AvnKeyH, 'h' },
{ 0x22, AvnPhysicalKeyI, AvnKeyI, 'i' },
{ 0x26, AvnPhysicalKeyJ, AvnKeyJ, 'j' },
{ 0x28, AvnPhysicalKeyK, AvnKeyK, 'k' },
{ 0x25, AvnPhysicalKeyL, AvnKeyL, 'l' },
{ 0x2E, AvnPhysicalKeyM, AvnKeyM, 'm' },
{ 0x2D, AvnPhysicalKeyN, AvnKeyN, 'n' },
{ 0x1F, AvnPhysicalKeyO, AvnKeyO, 'o' },
{ 0x23, AvnPhysicalKeyP, AvnKeyP, 'p' },
{ 0x0C, AvnPhysicalKeyQ, AvnKeyQ, 'q' },
{ 0x0F, AvnPhysicalKeyR, AvnKeyR, 'r' },
{ 0x01, AvnPhysicalKeyS, AvnKeyS, 's' },
{ 0x11, AvnPhysicalKeyT, AvnKeyT, 't' },
{ 0x20, AvnPhysicalKeyU, AvnKeyU, 'u' },
{ 0x09, AvnPhysicalKeyV, AvnKeyV, 'v' },
{ 0x0D, AvnPhysicalKeyW, AvnKeyW, 'w' },
{ 0x07, AvnPhysicalKeyX, AvnKeyX, 'x' },
{ 0x10, AvnPhysicalKeyY, AvnKeyY, 'y' },
{ 0x06, AvnPhysicalKeyZ, AvnKeyZ, 'z' },
{ 0x1B, AvnPhysicalKeyMinus, AvnKeyOemMinus, '-' },
{ 0x2F, AvnPhysicalKeyPeriod, AvnKeyOemPeriod, '.' },
{ 0x27, AvnPhysicalKeyQuote, AvnKeyOem7, '\'' },
{ 0x29, AvnPhysicalKeySemicolon, AvnKeyOem1, ';' },
{ 0x2C, AvnPhysicalKeySlash, AvnKeyOem2, '/' },
// Functional Keys
{ 0x3A, AvnPhysicalKeyAltLeft, AvnKeyLeftAlt, 0 },
{ 0x3D, AvnPhysicalKeyAltRight, AvnKeyRightAlt, 0 },
{ 0x33, AvnPhysicalKeyBackspace, AvnKeyBack, kBackspaceCharCode },
{ 0x39, AvnPhysicalKeyCapsLock, AvnKeyCapsLock, 0 },
{ 0x6E, AvnPhysicalKeyContextMenu, AvnKeyApps, 0 },
{ 0x3B, AvnPhysicalKeyControlLeft, AvnKeyLeftCtrl, 0 },
{ 0x3E, AvnPhysicalKeyControlRight, AvnKeyRightCtrl, 0 },
{ 0x24, AvnPhysicalKeyEnter, AvnKeyEnter, kReturnCharCode },
{ 0x37, AvnPhysicalKeyMetaLeft, AvnKeyLWin, 0 },
{ 0x36, AvnPhysicalKeyMetaRight, AvnKeyRWin, 0 },
{ 0x38, AvnPhysicalKeyShiftLeft, AvnKeyLeftShift, 0 },
{ 0x3C, AvnPhysicalKeyShiftRight, AvnKeyRightShift, 0 },
{ 0x31, AvnPhysicalKeySpace, AvnKeySpace, kSpaceCharCode },
{ 0x30, AvnPhysicalKeyTab, AvnKeyTab, kTabCharCode },
//{ , AvnPhysicalKeyConvert, 0 },
//{ , AvnPhysicalKeyKanaMode, 0 },
{ 0x68, AvnPhysicalKeyLang1, AvnKeyKanaMode, 0 },
{ 0x66, AvnPhysicalKeyLang2, AvnKeyHanjaMode, 0 },
//{ , AvnPhysicalKeyLang3, 0 },
//{ , AvnPhysicalKeyLang4, 0 },
//{ , AvnPhysicalKeyLang5, 0 },
//{ , AvnPhysicalKeyNonConvert, 0 },
// Control Pad Section
{ 0x75, AvnPhysicalKeyDelete, AvnKeyDelete, NSDeleteFunctionKey },
{ 0x77, AvnPhysicalKeyEnd, AvnKeyEnd, NSEndFunctionKey },
//{ , AvnPhysicalKeyHelp, 0 },
{ 0x73, AvnPhysicalKeyHome, AvnKeyHome, NSHomeFunctionKey },
{ 0x72, AvnPhysicalKeyInsert, AvnKeyInsert, NSInsertFunctionKey },
{ 0x79, AvnPhysicalKeyPageDown, AvnKeyPageDown, NSPageDownFunctionKey },
{ 0x74, AvnPhysicalKeyPageUp, AvnKeyPageUp, NSPageUpFunctionKey },
// Arrow Pad Section
{ 0x7D, AvnPhysicalKeyArrowDown, AvnKeyDown, NSDownArrowFunctionKey },
{ 0x7B, AvnPhysicalKeyArrowLeft, AvnKeyLeft, NSLeftArrowFunctionKey },
{ 0x7C, AvnPhysicalKeyArrowRight, AvnKeyRight, NSRightArrowFunctionKey },
{ 0x7E, AvnPhysicalKeyArrowUp, AvnKeyUp, NSUpArrowFunctionKey },
// Numpad Section
{ 0x47, AvnPhysicalKeyNumLock, AvnKeyClear, kClearCharCode },
{ 0x52, AvnPhysicalKeyNumPad0, AvnKeyNumPad0, '0' },
{ 0x53, AvnPhysicalKeyNumPad1, AvnKeyNumPad1, '1' },
{ 0x54, AvnPhysicalKeyNumPad2, AvnKeyNumPad2, '2' },
{ 0x55, AvnPhysicalKeyNumPad3, AvnKeyNumPad3, '3' },
{ 0x56, AvnPhysicalKeyNumPad4, AvnKeyNumPad4, '4' },
{ 0x57, AvnPhysicalKeyNumPad5, AvnKeyNumPad5, '5' },
{ 0x58, AvnPhysicalKeyNumPad6, AvnKeyNumPad6, '6' },
{ 0x59, AvnPhysicalKeyNumPad7, AvnKeyNumPad7, '7' },
{ 0x5B, AvnPhysicalKeyNumPad8, AvnKeyNumPad8, '8' },
{ 0x5C, AvnPhysicalKeyNumPad9, AvnKeyNumPad9, '9' },
{ 0x45, AvnPhysicalKeyNumPadAdd, AvnKeyAdd, '+' },
//{ , AvnPhysicalKeyNumPadClear, 0 },
{ 0x5F, AvnPhysicalKeyNumPadComma, AvnKeyAbntC2, 0 },
{ 0x41, AvnPhysicalKeyNumPadDecimal, AvnKeyDecimal, '.' },
{ 0x4B, AvnPhysicalKeyNumPadDivide, AvnKeyDivide, '/' },
{ 0x4C, AvnPhysicalKeyNumPadEnter, AvnKeyEnter, kReturnCharCode },
{ 0x51, AvnPhysicalKeyNumPadEqual, AvnKeyOemPlus, '=' },
{ 0x43, AvnPhysicalKeyNumPadMultiply, AvnKeyMultiply, '*' },
//{ , AvnPhysicalKeyNumPadParenLeft, 0 },
//{ , AvnPhysicalKeyNumPadParenRight, 0 },
{ 0x4E, AvnPhysicalKeyNumPadSubtract, AvnKeySubtract, '-' },
// Function Section
{ 0x35, AvnPhysicalKeyEscape, AvnKeyEscape, kEscapeCharCode },
{ 0x7A, AvnPhysicalKeyF1, AvnKeyF1, NSF1FunctionKey },
{ 0x78, AvnPhysicalKeyF2, AvnKeyF2, NSF2FunctionKey },
{ 0x63, AvnPhysicalKeyF3, AvnKeyF3, NSF3FunctionKey },
{ 0x76, AvnPhysicalKeyF4, AvnKeyF4, NSF4FunctionKey },
{ 0x60, AvnPhysicalKeyF5, AvnKeyF5, NSF5FunctionKey },
{ 0x61, AvnPhysicalKeyF6, AvnKeyF6, NSF6FunctionKey },
{ 0x62, AvnPhysicalKeyF7, AvnKeyF7, NSF7FunctionKey },
{ 0x64, AvnPhysicalKeyF8, AvnKeyF8, NSF8FunctionKey },
{ 0x65, AvnPhysicalKeyF9, AvnKeyF9, NSF9FunctionKey },
{ 0x6D, AvnPhysicalKeyF10, AvnKeyF10, NSF10FunctionKey },
{ 0x67, AvnPhysicalKeyF11, AvnKeyF11, NSF11FunctionKey },
{ 0x6F, AvnPhysicalKeyF12, AvnKeyF12, NSF12FunctionKey },
{ 0x69, AvnPhysicalKeyF13, AvnKeyF13, NSF13FunctionKey },
{ 0x6B, AvnPhysicalKeyF14, AvnKeyF14, NSF14FunctionKey },
{ 0x71, AvnPhysicalKeyF15, AvnKeyF15, NSF15FunctionKey },
{ 0x6A, AvnPhysicalKeyF16, AvnKeyF16, NSF16FunctionKey },
{ 0x40, AvnPhysicalKeyF17, AvnKeyF17, NSF17FunctionKey },
{ 0x4F, AvnPhysicalKeyF18, AvnKeyF18, NSF18FunctionKey },
{ 0x50, AvnPhysicalKeyF19, AvnKeyF19, NSF19FunctionKey },
{ 0x5A, AvnPhysicalKeyF20, AvnKeyF20, NSF20FunctionKey },
//{ , AvnPhysicalKeyF21, 0 },
//{ , AvnPhysicalKeyF22, 0 },
//{ , AvnPhysicalKeyF23, 0 },
//{ , AvnPhysicalKeyF24, 0 },
//{ , AvnPhysicalKeyPrintScreen, 0 },
//{ , AvnPhysicalKeyScrollLock, 0 },
//{ , AvnPhysicalKeyPause, 0 },
// Media Keys
//{ , AvnPhysicalKeyBrowserBack, 0 },
//{ , AvnPhysicalKeyBrowserFavorites, 0 },
//{ , AvnPhysicalKeyBrowserForward, 0 },
//{ , AvnPhysicalKeyBrowserHome, 0 },
//{ , AvnPhysicalKeyBrowserRefresh, 0 },
//{ , AvnPhysicalKeyBrowserSearch, 0 },
//{ , AvnPhysicalKeyBrowserStop, 0 },
//{ , AvnPhysicalKeyEject, 0 },
//{ , AvnPhysicalKeyLaunchApp1, 0 },
//{ , AvnPhysicalKeyLaunchApp2, 0 },
//{ , AvnPhysicalKeyLaunchMail, 0 },
//{ , AvnPhysicalKeyMediaPlayPause, 0 },
//{ , AvnPhysicalKeyMediaSelect, 0 },
//{ , AvnPhysicalKeyMediaStop, 0 },
//{ , AvnPhysicalKeyMediaTrackNext, 0 },
//{ , AvnPhysicalKeyMediaTrackPrevious, 0 },
//{ , AvnPhysicalKeyPower, 0 },
//{ , AvnPhysicalKeySleep, 0 },
{ 0x49, AvnPhysicalKeyAudioVolumeDown, AvnKeyVolumeDown, 0 },
{ 0x4A, AvnPhysicalKeyAudioVolumeMute, AvnKeyVolumeMute, 0 },
{ 0x48, AvnPhysicalKeyAudioVolumeUp, AvnKeyVolumeUp, 0 },
//{ , AvnPhysicalKeyWakeUp, 0 },
// Legacy Keys
//{ , AvnPhysicalKeyAgain, 0 },
//{ , AvnPhysicalKeyCopy, 0 },
//{ , AvnPhysicalKeyCut, 0 },
//{ , AvnPhysicalKeyFind, 0 },
//{ , AvnPhysicalKeyOpen, 0 },
//{ , AvnPhysicalKeyPaste, 0 },
//{ , AvnPhysicalKeyProps, 0 },
//{ , AvnPhysicalKeySelect, 0 },
//{ , AvnPhysicalKeyUndo, 0 }
};
// converts from ansi virtualkeys to AvnKeys.
std::map<int, AvnKey> s_KeyMap =
{
{kVK_ANSI_A, A},
{kVK_ANSI_S, S},
{kVK_ANSI_D, D},
{kVK_ANSI_F, F},
{kVK_ANSI_H, H},
{kVK_ANSI_G, G},
{kVK_ANSI_Z, Z},
{kVK_ANSI_X, X},
{kVK_ANSI_C, C},
{kVK_ANSI_V, V},
{kVK_ANSI_B, B},
{kVK_ANSI_Q, Q},
{kVK_ANSI_W, W},
{kVK_ANSI_E, E},
{kVK_ANSI_R, R},
{kVK_ANSI_Y, Y},
{kVK_ANSI_T, T},
{kVK_ANSI_1, D1},
{kVK_ANSI_2, D2},
{kVK_ANSI_3, D3},
{kVK_ANSI_4, D4},
{kVK_ANSI_6, D6},
{kVK_ANSI_5, D5},
{kVK_ANSI_Equal, OemPlus},
{kVK_ANSI_9, D9},
{kVK_ANSI_7, D7},
{kVK_ANSI_Minus, OemMinus},
{kVK_ANSI_8, D8},
{kVK_ANSI_0, D0},
{kVK_ANSI_RightBracket, OemCloseBrackets},
{kVK_ANSI_O, O},
{kVK_ANSI_U, U},
{kVK_ANSI_LeftBracket, OemOpenBrackets},
{kVK_ANSI_I, I},
{kVK_ANSI_P, P},
{kVK_ANSI_L, L},
{kVK_ANSI_J, J},
{kVK_ANSI_Quote, OemQuotes},
{kVK_ANSI_K, AvnKeyK},
{kVK_ANSI_Semicolon, OemSemicolon},
{kVK_ANSI_Backslash, OemBackslash},
{kVK_ANSI_Comma, OemComma},
{kVK_ANSI_Slash, Oem2},
{kVK_ANSI_N, N},
{kVK_ANSI_M, M},
{kVK_ANSI_Period, OemPeriod},
{kVK_ANSI_Grave, OemTilde},
{kVK_ANSI_KeypadDecimal, Decimal},
{kVK_ANSI_KeypadMultiply, Multiply},
{kVK_ANSI_KeypadPlus, OemPlus},
{kVK_ANSI_KeypadClear, AvnKeyClear},
{kVK_ANSI_KeypadDivide, Divide},
{kVK_ANSI_KeypadEnter, AvnKeyEnter},
{kVK_ANSI_KeypadMinus, OemMinus},
{kVK_ANSI_KeypadEquals, OemPlus},
{kVK_ANSI_Keypad0, NumPad0},
{kVK_ANSI_Keypad1, NumPad1},
{kVK_ANSI_Keypad2, NumPad2},
{kVK_ANSI_Keypad3, NumPad3},
{kVK_ANSI_Keypad4, NumPad4},
{kVK_ANSI_Keypad5, NumPad5},
{kVK_ANSI_Keypad6, NumPad6},
{kVK_ANSI_Keypad7, NumPad7},
{kVK_ANSI_Keypad8, NumPad8},
{kVK_ANSI_Keypad9, NumPad9},
{kVK_Return, AvnKeyReturn},
{kVK_Tab, AvnKeyTab},
{kVK_Space, Space},
{kVK_Delete, AvnKeyBack},
{kVK_Escape, Escape},
{kVK_Command, LWin},
{kVK_Shift, LeftShift},
{kVK_CapsLock, AvnKeyCapsLock},
{kVK_Option, LeftAlt},
{kVK_Control, LeftCtrl},
{kVK_RightCommand, RWin},
{kVK_RightShift, RightShift},
{kVK_RightOption, RightAlt},
{kVK_RightControl, RightCtrl},
//{kVK_Function, ?},
{kVK_F17, F17},
{kVK_VolumeUp, VolumeUp},
{kVK_VolumeDown, VolumeDown},
{kVK_Mute, VolumeMute},
{kVK_F18, F18},
{kVK_F19, F19},
{kVK_F20, F20},
{kVK_F5, F5},
{kVK_F6, F6},
{kVK_F7, F7},
{kVK_F3, F3},
{kVK_F8, F8},
{kVK_F9, F9},
{kVK_F11, F11},
{kVK_F13, F13},
{kVK_F16, F16},
{kVK_F14, F14},
{kVK_F10, F10},
{kVK_F12, F12},
{kVK_F15, F15},
{kVK_Help, Help},
{kVK_Home, Home},
{kVK_PageUp, PageUp},
{kVK_ForwardDelete, Delete},
{kVK_F4, F4},
{kVK_End, End},
{kVK_F2, F2},
{kVK_PageDown, PageDown},
{kVK_F1, F1},
{kVK_LeftArrow, Left},
{kVK_RightArrow, Right},
{kVK_DownArrow, Down},
{kVK_UpArrow, Up},
{kVK_JIS_Kana, AvnKeyKanaMode},
std::unordered_map<uint16_t, AvnKey> virtualKeyFromChar =
{
// Alphabetic keys
{ 'A', AvnKeyA },
{ 'B', AvnKeyB },
{ 'C', AvnKeyC },
{ 'D', AvnKeyD },
{ 'E', AvnKeyE },
{ 'F', AvnKeyF },
{ 'G', AvnKeyG },
{ 'H', AvnKeyH },
{ 'I', AvnKeyI },
{ 'J', AvnKeyJ },
{ 'K', AvnKeyK },
{ 'L', AvnKeyL },
{ 'M', AvnKeyM },
{ 'N', AvnKeyN },
{ 'O', AvnKeyO },
{ 'P', AvnKeyP },
{ 'Q', AvnKeyQ },
{ 'R', AvnKeyR },
{ 'S', AvnKeyS },
{ 'T', AvnKeyT },
{ 'U', AvnKeyU },
{ 'V', AvnKeyV },
{ 'W', AvnKeyW },
{ 'X', AvnKeyX },
{ 'Y', AvnKeyY },
{ 'Z', AvnKeyZ },
{ 'a', AvnKeyA },
{ 'b', AvnKeyB },
{ 'c', AvnKeyC },
{ 'd', AvnKeyD },
{ 'e', AvnKeyE },
{ 'f', AvnKeyF },
{ 'g', AvnKeyG },
{ 'h', AvnKeyH },
{ 'i', AvnKeyI },
{ 'j', AvnKeyJ },
{ 'k', AvnKeyK },
{ 'l', AvnKeyL },
{ 'm', AvnKeyM },
{ 'n', AvnKeyN },
{ 'o', AvnKeyO },
{ 'p', AvnKeyP },
{ 'q', AvnKeyQ },
{ 'r', AvnKeyR },
{ 's', AvnKeyS },
{ 't', AvnKeyT },
{ 'u', AvnKeyU },
{ 'v', AvnKeyV },
{ 'w', AvnKeyW },
{ 'x', AvnKeyX },
{ 'y', AvnKeyY },
{ 'z', AvnKeyZ },
// Punctuation: US specific mappings (same as Chromium)
{ ';', AvnKeyOem1 },
{ ':', AvnKeyOem1 },
{ '=', AvnKeyOemPlus },
{ '+', AvnKeyOemPlus },
{ ',', AvnKeyOemComma },
{ '<', AvnKeyOemComma },
{ '-', AvnKeyOemMinus },
{ '_', AvnKeyOemMinus },
{ '.', AvnKeyOemPeriod },
{ '>', AvnKeyOemPeriod },
{ '/', AvnKeyOem2 },
{ '?', AvnKeyOem2 },
{ '`', AvnKeyOem3 },
{ '~', AvnKeyOem3 },
{ '[', AvnKeyOem4 },
{ '{', AvnKeyOem4 },
{ '\\', AvnKeyOem5 },
{ '|', AvnKeyOem5 },
{ ']', AvnKeyOem6 },
{ '}', AvnKeyOem6 },
{ '\'', AvnKeyOem7 },
{ '"', AvnKeyOem7 },
// Apple function keys
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicode_values
{ NSDeleteFunctionKey, AvnKeyDelete },
{ NSUpArrowFunctionKey, AvnKeyUp },
{ NSLeftArrowFunctionKey, AvnKeyLeft },
{ NSRightArrowFunctionKey, AvnKeyRight },
{ NSPageUpFunctionKey, AvnKeyPageUp },
{ NSPageDownFunctionKey, AvnKeyPageDown },
{ NSHomeFunctionKey, AvnKeyHome },
{ NSEndFunctionKey, AvnKeyEnd },
{ NSClearLineFunctionKey, AvnKeyClear },
{ NSExecuteFunctionKey, AvnKeyExecute },
{ NSHelpFunctionKey, AvnKeyHelp },
{ NSInsertFunctionKey, AvnKeyInsert },
{ NSMenuFunctionKey, AvnKeyApps },
{ NSPauseFunctionKey, AvnKeyPause },
{ NSPrintFunctionKey, AvnKeyPrint },
{ NSPrintScreenFunctionKey, AvnKeyPrintScreen },
{ NSScrollLockFunctionKey, AvnKeyScroll },
{ NSF1FunctionKey, AvnKeyF1 },
{ NSF2FunctionKey, AvnKeyF2 },
{ NSF3FunctionKey, AvnKeyF3 },
{ NSF4FunctionKey, AvnKeyF4 },
{ NSF5FunctionKey, AvnKeyF5 },
{ NSF6FunctionKey, AvnKeyF6 },
{ NSF7FunctionKey, AvnKeyF7 },
{ NSF8FunctionKey, AvnKeyF8 },
{ NSF9FunctionKey, AvnKeyF9 },
{ NSF10FunctionKey, AvnKeyF10 },
{ NSF11FunctionKey, AvnKeyF11 },
{ NSF12FunctionKey, AvnKeyF12 },
{ NSF13FunctionKey, AvnKeyF13 },
{ NSF14FunctionKey, AvnKeyF14 },
{ NSF15FunctionKey, AvnKeyF15 },
{ NSF16FunctionKey, AvnKeyF16 },
{ NSF17FunctionKey, AvnKeyF17 },
{ NSF18FunctionKey, AvnKeyF18 },
{ NSF19FunctionKey, AvnKeyF19 },
{ NSF20FunctionKey, AvnKeyF20 },
{ NSF21FunctionKey, AvnKeyF21 },
{ NSF22FunctionKey, AvnKeyF22 },
{ NSF23FunctionKey, AvnKeyF23 },
{ NSF24FunctionKey, AvnKeyF24 }
};
static std::map<AvnKey, int> BuildAvnKeyMap ()
typedef std::array<AvnPhysicalKey, 0x7F> PhysicalKeyArray;
static PhysicalKeyArray BuildPhysicalKeyFromScanCode()
{
std::map<AvnKey, int> result;
for( auto it = s_KeyMap.begin(); it != s_KeyMap.end(); ++it )
PhysicalKeyArray result {};
for (auto& keyInfo : keyInfos)
{
int key = it->first;
AvnKey value = it->second;
result[value] = key;
result[keyInfo.scanCode] = keyInfo.physicalKey;
}
return result;
}
PhysicalKeyArray physicalKeyFromScanCode = BuildPhysicalKeyFromScanCode();
static std::unordered_map<AvnPhysicalKey, AvnKey, std::hash<int>> BuildQwertyVirtualKeyFromPhysicalKey()
{
std::unordered_map<AvnPhysicalKey, AvnKey, std::hash<int>> result;
result.reserve(sizeof(keyInfos) / sizeof(keyInfos[0]));
for (auto& keyInfo : keyInfos)
{
result[keyInfo.physicalKey] = keyInfo.qwertyKey;
}
return result;
}
std::unordered_map<AvnPhysicalKey, AvnKey, std::hash<int>> qwertyVirtualKeyFromPhysicalKey = BuildQwertyVirtualKeyFromPhysicalKey();
static std::unordered_map<AvnKey, uint16_t, std::hash<int>> BuildMenuCharFromVirtualKey()
{
std::unordered_map<AvnKey, uint16_t, std::hash<int>> result;
result.reserve(100);
for (auto& keyInfo : keyInfos)
{
if (keyInfo.menuChar != 0)
result[keyInfo.qwertyKey] = keyInfo.menuChar;
}
return result;
}
// Converts AvnKeys to Ansi VirtualKeys
std::map<AvnKey, int> s_AvnKeyMap = BuildAvnKeyMap();
std::unordered_map<AvnKey, uint16_t, std::hash<int>> menuCharFromVirtualKey = BuildMenuCharFromVirtualKey();
static bool IsNumpadOrNumericKey(AvnPhysicalKey physicalKey)
{
return (physicalKey >= AvnPhysicalKeyDigit0 && physicalKey <= AvnPhysicalKeyDigit9)
|| (physicalKey >= AvnPhysicalKeyNumLock && physicalKey <= AvnPhysicalKeyNumPadSubtract);
}
AvnPhysicalKey PhysicalKeyFromScanCode(uint16_t scanCode)
{
return scanCode < physicalKeyFromScanCode.size() ? physicalKeyFromScanCode[scanCode] : AvnPhysicalKeyNone;
}
static bool IsAllowedAsciiChar(UniChar c)
{
if (c < 0x20)
{
switch (c)
{
case kBackspaceCharCode:
case kReturnCharCode:
case kTabCharCode:
case kEscapeCharCode:
return true;
default:
return false;
}
}
if (c == kDeleteCharCode)
return false;
return true;
}
static UniCharCount CharsFromScanCode(UInt16 scanCode, NSEventModifierFlags modifierFlags, UInt16 keyAction, UniChar* buffer, UniCharCount bufferSize)
{
auto currentKeyboard = TISCopyCurrentKeyboardInputSource();
if (!currentKeyboard)
return 0;
auto layoutData = static_cast<CFDataRef>(TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData));
if (!layoutData)
return 0;
auto* keyboardLayout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(layoutData));
UInt32 deadKeyState = 0;
UniCharCount length = 0;
int glyphModifiers = 0;
if (modifierFlags & NSEventModifierFlagShift)
glyphModifiers |= shiftKey;
if (modifierFlags & NSEventModifierFlagCapsLock)
glyphModifiers |= alphaLock;
if (modifierFlags & NSEventModifierFlagOption)
glyphModifiers |= optionKey;
auto result = UCKeyTranslate(
keyboardLayout,
scanCode,
keyAction,
(glyphModifiers >> 8) & 0xFF,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
bufferSize,
&length,
buffer);
if (result != noErr)
return 0;
if (deadKeyState)
{
// translate a space with dead key state to get the dead key itself
result = UCKeyTranslate(
keyboardLayout,
kVK_Space,
keyAction,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
bufferSize,
&length,
buffer);
if (result != noErr)
return 0;
}
if (length == 1 && buffer[0] <= 0x7F && !IsAllowedAsciiChar(buffer[0]))
return 0;
return length;
}
AvnKey VirtualKeyFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags)
{
auto physicalKey = PhysicalKeyFromScanCode(scanCode);
if (!IsNumpadOrNumericKey(physicalKey))
{
const UniCharCount charCount = 4;
UniChar chars[charCount];
auto length = CharsFromScanCode(scanCode, modifierFlags, kUCKeyActionDown, chars, charCount);
if (length > 0)
{
auto it = virtualKeyFromChar.find(chars[0]);
if (it != virtualKeyFromChar.end())
return it->second;
}
}
auto it = qwertyVirtualKeyFromPhysicalKey.find(physicalKey);
return it == qwertyVirtualKeyFromPhysicalKey.end() ? AvnKeyNone : it->second;
}
NSString* KeySymbolFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags)
{
auto physicalKey = PhysicalKeyFromScanCode(scanCode);
const UniCharCount charCount = 4;
UniChar chars[charCount];
auto length = CharsFromScanCode(scanCode, modifierFlags, kUCKeyActionDisplay, chars, charCount);
if (length > 0)
return [NSString stringWithCharacters:chars length:length];
auto it = qwertyVirtualKeyFromPhysicalKey.find(physicalKey);
if (it == qwertyVirtualKeyFromPhysicalKey.end())
return nullptr;
auto menuChar = MenuCharFromVirtualKey(it->second);
return menuChar == 0 || menuChar > 0x7E ? nullptr : [NSString stringWithCharacters:&menuChar length:1];
}
uint16_t MenuCharFromVirtualKey(AvnKey key)
{
auto it = menuCharFromVirtualKey.find(key);
return it == menuCharFromVirtualKey.end() ? 0 : it->second;
}

23
native/Avalonia.Native/src/OSX/menu.mm

@ -147,34 +147,17 @@ HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers)
if (modifiers & Windows)
flags |= NSEventModifierFlagCommand;
auto it = s_UnicodeKeyMap.find(key);
auto menuChar = MenuCharFromVirtualKey(key);
if(it != s_UnicodeKeyMap.end())
if (menuChar != 0)
{
auto keyString= [NSString stringWithFormat:@"%C", (unsigned short)it->second];
auto keyString = [NSString stringWithCharacters:&menuChar length:1];
[_native setKeyEquivalent: keyString];
[_native setKeyEquivalentModifierMask:flags];
return S_OK;
}
else
{
auto it = s_AvnKeyMap.find(key); // check if a virtual key is mapped.
if(it != s_AvnKeyMap.end())
{
auto it1 = s_QwertyKeyMap.find(it->second); // convert virtual key to qwerty string.
if(it1 != s_QwertyKeyMap.end())
{
[_native setKeyEquivalent: [NSString stringWithUTF8String: it1->second]];
[_native setKeyEquivalentModifierMask:flags];
return S_OK;
}
}
}
}
// Nothing matched... clear.

5
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -54,7 +54,10 @@ namespace Avalonia.Android.Platform.Specific.Helpers
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e),
PhysicalKey.None,
e.DisplayLabel == '\0' ? null : new string(e.DisplayLabel, 1));
_view.Input(rawKeyEvent);

76
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -1,12 +1,74 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
namespace Avalonia.Input;
/// <summary>
/// Provides information specific to a keyboard event.
/// </summary>
public class KeyEventArgs : RoutedEventArgs
{
public class KeyEventArgs : RoutedEventArgs
{
public Key Key { get; init; }
/// <summary>
/// <para>
/// Gets the virtual-key for the associated event.<br/>
/// A given physical key can result in different virtual keys depending on the current keyboard layout.<br/>
/// This is the key that is generally referred to when creating keyboard shortcuts.
/// </para>
/// <para>
/// For example, when pressing the key located at the <c>Z</c> position on standard US English QWERTY keyboard,
/// this property returns:<br/>
/// - <see cref="Input.Key.Z"/> for an English (QWERTY) layout<br/>
/// - <see cref="Input.Key.W"/> for a French (AZERTY) layout<br/>
/// - <see cref="Input.Key.Y"/> for a German (QWERTZ) layout<br/>
/// - <see cref="Input.Key.Z"/> for a Russian (JCUKEN) layout
/// </para>
/// </summary>
/// <remarks>
/// This property should be used for letter-related shortcuts only.<br/>
/// Prefer using <see cref="PhysicalKey"/> if you need to refer to a key given its position on the keyboard (a
/// common usage is moving the player with WASD-like keys in games), or <see cref="KeySymbol"/> if you want to know
/// which character the key will output.<br/>
/// Avoid using this for shortcuts related to punctuation keys, as they differ wildly depending on keyboard layouts.
/// </remarks>
/// <seealso cref="PhysicalKey"/>
/// <seealso cref="KeySymbol"/>
public Key Key { get; init; }
/// <summary>
/// Gets the key modifiers for the associated event.
/// </summary>
public KeyModifiers KeyModifiers { get; init; }
/// <summary>
/// <para>
/// Gets the physical key for the associated event.
/// </para>
/// <para>
/// This value is independent of the current keyboard layout and usually correspond to the key printed on a standard
/// US English QWERTY keyboard.
/// </para>
/// </summary>
/// <remarks>
/// Use this property if you need to refer to a key given its position on the keyboard (a common usage is moving the
/// player with WASD-like keys).
/// </remarks>
/// <seealso cref="Key"/>
/// <seealso cref="KeySymbol"/>
public PhysicalKey PhysicalKey { get; init; }
public KeyModifiers KeyModifiers { get; init; }
}
/// <summary>
/// <para>
/// Gets the unicode symbol of the key, or null if none is applicable.
/// </para>
/// <para>
/// For example, when pressing the key located at the <c>Z</c> position on standard US English QWERTY keyboard,
/// this property returns:<br/>
/// - <c>z</c> for an English (QWERTY) layout<br/>
/// - <c>w</c> for a French (AZERTY) layout<br/>
/// - <c>y</c> for a German (QWERTZ) layout<br/>
/// - <c>я</c> for a Russian (JCUKEN) layout
/// </para>
/// </summary>
/// <see cref="Key"/>
/// <see cref="PhysicalKey"/>
public string? KeySymbol { get; init; }
}

26
src/Avalonia.Base/Input/KeySymbolHelper.cs

@ -0,0 +1,26 @@
namespace Avalonia.Input;
internal static class KeySymbolHelper
{
public static bool IsAllowedAsciiKeySymbol(char c)
{
if (c < 0x20)
{
switch (c)
{
case '\b': // backspace
case '\t': // tab
case '\r': // return
case (char)0x1B: // escape
return true;
default:
return false;
}
}
if (c == 0x07) // delete
return false;
return true;
}
}

6
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -189,12 +189,14 @@ namespace Avalonia.Input
? InputElement.KeyDownEvent
: InputElement.KeyUpEvent;
KeyEventArgs ev = new KeyEventArgs
var ev = new KeyEventArgs
{
RoutedEvent = routedEvent,
Key = keyInput.Key,
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element,
PhysicalKey = keyInput.PhysicalKey,
KeySymbol = keyInput.KeySymbol,
Source = element
};
var currentHandler = element as Visual;

944
src/Avalonia.Base/Input/PhysicalKey.cs

@ -0,0 +1,944 @@
#if AVALONIA_REMOTE_PROTOCOL
namespace Avalonia.Remote.Protocol.Input;
#else
namespace Avalonia.Input;
#endif
/// <summary>
/// Represents a keyboard physical key.<br/>
/// </summary>
/// <remarks>
/// The names follow the W3C codes: https://www.w3.org/TR/uievents-code/
/// </remarks>
public enum PhysicalKey
{
/// <summary>
/// Represents no key.
/// </summary>
None = 0,
// ###################
// Writing System Keys
// ###################
/// <summary>
/// <c>`~</c> on a US keyboard.
/// This is the <c>半角/全角/漢字</c> (hankaku/zenkaku/kanji) key on Japanese keyboards.
/// </summary>
Backquote = 1,
/// <summary>
/// Used for both the US <c>\|</c> (on the 101-key layout) and also for the key located between the <c>"</c> and
/// <c>Enter</c> keys on row C of the 102-, 104- and 106-key layouts.
/// <c>#~</c> on a UK (102) keyboard.
/// </summary>
Backslash = 2,
/// <summary>
/// <c>[{</c> on a US keyboard.
/// </summary>
BracketLeft = 3,
/// <summary>
/// <c>]}</c> on a US keyboard.
/// </summary>
BracketRight = 4,
/// <summary>
/// <c>,&lt;</c> on a US keyboard.
/// </summary>
Comma = 5,
/// <summary>
/// <c>0)</c> on a US keyboard.
/// </summary>
Digit0 = 6,
/// <summary>
/// <c>1!</c> on a US keyboard.
/// </summary>
Digit1 = 7,
/// <summary>
/// <c>2@</c> on a US keyboard.
/// </summary>
Digit2 = 8,
/// <summary>
/// <c>3#</c> on a US keyboard.
/// </summary>
Digit3 = 9,
/// <summary>
/// <c>4$</c> on a US keyboard.
/// </summary>
Digit4 = 10,
/// <summary>
/// <c>5%</c> on a US keyboard.
/// </summary>
Digit5 = 11,
/// <summary>
/// <c>6^</c> on a US keyboard.
/// </summary>
Digit6 = 12,
/// <summary>
/// <c>7&amp;</c> on a US keyboard.
/// </summary>
Digit7 = 13,
/// <summary>
/// <c>8*</c> on a US keyboard.
/// </summary>
Digit8 = 14,
/// <summary>
/// <c>9(</c> on a US keyboard.
/// </summary>
Digit9 = 15,
/// <summary>
/// <c>=+</c> on a US keyboard.
/// </summary>
Equal = 16,
/// <summary>
/// Located between the left <c>Shift</c> and <c>Z</c> keys.
/// <c>\|</c> on a UK keyboard.
/// </summary>
IntlBackslash = 17,
/// <summary>
/// Located between the <c>/</c> and right <c>Shift</c> keys.
/// <c>\ろ</c> (ro) on a Japanese keyboard.
/// </summary>
IntlRo = 18,
/// <summary>
/// Located between the <c>=</c> and <c>Backspace</c> keys.
/// <c>¥</c> (yen) on a Japanese keyboard.
/// <c>\/</c> on a Russian keyboard.
/// </summary>
IntlYen = 19,
/// <summary>
/// <c>a</c> on a US keyboard.
/// <c>q</c> on an AZERTY (e.g., French) keyboard.
/// </summary>
A = 20,
/// <summary>
/// <c>b</c> on a US keyboard.
/// </summary>
B = 21,
/// <summary>
/// <c>c</c> on a US keyboard.
/// </summary>
C = 22,
/// <summary>
/// <c>d</c> on a US keyboard.
/// </summary>
D = 23,
/// <summary>
/// <c>e</c> on a US keyboard.
/// </summary>
E = 24,
/// <summary>
/// <c>f</c> on a US keyboard.
/// </summary>
F = 25,
/// <summary>
/// <c>g</c> on a US keyboard.
/// </summary>
G = 26,
/// <summary>
/// <c>h</c> on a US keyboard.
/// </summary>
H = 27,
/// <summary>
/// <c>i</c> on a US keyboard.
/// </summary>
I = 28,
/// <summary>
/// <c>j</c> on a US keyboard.
/// </summary>
J = 29,
/// <summary>
/// <c>k</c> on a US keyboard.
/// </summary>
K = 30,
/// <summary>
/// <c>l</c> on a US keyboard.
/// </summary>
L = 31,
/// <summary>
/// <c>m</c> on a US keyboard.
/// </summary>
M = 32,
/// <summary>
/// <c>n</c> on a US keyboard.
/// </summary>
N = 33,
/// <summary>
/// <c>o</c> on a US keyboard.
/// </summary>
O = 34,
/// <summary>
/// <c>p</c> on a US keyboard.
/// </summary>
P = 35,
/// <summary>
/// <c>q</c> on a US keyboard.
/// <c>a</c> on an AZERTY (e.g., French) keyboard.
/// </summary>
Q = 36,
/// <summary>
/// <c>r</c> on a US keyboard.
/// </summary>
R = 37,
/// <summary>
/// <c>s</c> on a US keyboard.
/// </summary>
S = 38,
/// <summary>
/// <c>t</c> on a US keyboard.
/// </summary>
T = 39,
/// <summary>
/// <c>u</c> on a US keyboard.
/// </summary>
U = 40,
/// <summary>
/// <c>v</c> on a US keyboard.
/// </summary>
V = 41,
/// <summary>
/// <c>w</c> on a US keyboard.
/// <c>z</c> on an AZERTY (e.g., French) keyboard.
/// </summary>
W = 42,
/// <summary>
/// <c>x</c> on a US keyboard.
/// </summary>
X = 43,
/// <summary>
/// <c>y</c> on a US keyboard.
/// <c>z</c> on a QWERTZ (e.g., German) keyboard.
/// </summary>
Y = 44,
/// <summary>
/// <c>z</c> on a US keyboard.
/// <c>w</c> on an AZERTY (e.g., French) keyboard.
/// <c>y</c> on a QWERTZ (e.g., German) keyboard.
/// </summary>
Z = 45,
/// <summary>
/// <c>-_</c> on a US keyboard.
/// </summary>
Minus = 46,
/// <summary>
/// <c>.&gt;</c> on a US keyboard.
/// </summary>
Period = 47,
/// <summary>
/// <c>'"</c> on a US keyboard.
/// </summary>
Quote = 48,
/// <summary>
/// <c>;:</c> on a US keyboard.
/// </summary>
Semicolon = 49,
/// <summary>
/// <c>/?</c> on a US keyboard.
/// </summary>
Slash = 50,
// ###############
// Functional keys
// ###############
/// <summary>
/// <c>Alt</c>, <c>Option</c> or <c>⌥</c>.
/// </summary>
AltLeft = 51,
/// <summary>
/// <c>Alt</c>, <c>Option</c> or <c>⌥</c>.
/// This is labelled <c>AltGr</c> key on many keyboard layouts.
/// </summary>
AltRight = 52,
/// <summary>
/// <c>Backspace</c> or <c>⌫</c>.
/// Labelled <c>Delete</c> on Apple keyboards.
/// </summary>
Backspace = 53,
/// <summary>
/// <c>CapsLock</c> or <c>⇪</c>.
/// </summary>
CapsLock = 54,
/// <summary>
/// The application context menu key, which is typically found between the right <c>Meta</c> key
/// and the right <c>Control</c> key.
/// </summary>
ContextMenu = 55,
/// <summary>
/// <c>Control</c> or <c>⌃</c>.
/// </summary>
ControlLeft = 56,
/// <summary>
/// <c>Control</c> or <c>⌃</c>.
/// </summary>
ControlRight = 57,
/// <summary>
/// <c>Enter</c> or <c>↵</c>.
/// Labelled <c>Return</c> on Apple keyboards.
/// </summary>
Enter = 58,
/// <summary>
/// The <c>⊞</c> (Windows), <c>⌘</c>, <c>Command</c> or other OS symbol key.
/// </summary>
MetaLeft = 59,
/// <summary>
/// The <c>⊞</c> (Windows), <c>⌘</c>, <c>Command</c> or other OS symbol key.
/// </summary>
MetaRight = 60,
/// <summary>
/// <c>Shift</c> or <c>⇧</c>.
/// </summary>
ShiftLeft = 61,
/// <summary>
/// <c>Shift</c> or <c>⇧</c>.
/// </summary>
ShiftRight = 62,
/// <summary>
/// <c> </c> (space).
/// </summary>
Space = 63,
/// <summary>
/// <c>Tab</c> or <c>⇥</c>.
/// </summary>
Tab = 64,
/// <summary>
/// Japanese: <c>変換</c> (henkan).
/// </summary>
Convert = 65,
/// <summary>
/// Japanese: <c>カタカナ/ひらがな/ローマ字</c> (katakana/hiragana/romaji).
/// </summary>
KanaMode = 66,
/// <summary>
/// Korean: HangulMode <c>한/영</c> (han/yeong).
/// Japanese (Mac keyboard): <c>かな</c> (kana).
/// </summary>
Lang1 = 67,
/// <summary>
/// Korean: Hanja <c>한자</c> (hanja).
/// Japanese (Mac keyboard): <c>英数</c> (eisu).
/// </summary>
Lang2 = 68,
/// <summary>
/// Japanese (word-processing keyboard): Katakana.
/// </summary>
Lang3 = 69,
/// <summary>
/// Japanese (word-processing keyboard): Hiragana.
/// </summary>
Lang4 = 70,
/// <summary>
/// Japanese (word-processing keyboard): Zenkaku/Hankaku.
/// </summary>
Lang5 = 71,
/// <summary>
/// Japanese: <c>無変換</c> (muhenkan).
/// </summary>
NonConvert = 72,
// ###################
// Control Pad Section
// ###################
/// <summary>
/// <c>⌦</c>. The forward delete key.
/// Note that on Apple keyboards, the key labelled <c>Delete</c> on the main part of the keyboard is
/// <see cref="Backspace"/>.
/// </summary>
Delete = 73,
/// <summary>
/// <c>End</c> or <c>↘</c>.
/// </summary>
End = 74,
/// <summary>
/// <c>Help</c>.
/// Not present on standard PC keyboards.
/// </summary>
Help = 75,
/// <summary>
/// <c>Home</c> or <c>↖</c>.
/// </summary>
Home = 76,
/// <summary>
/// <c>Insert</c> or <c>Ins</c>.
/// Not present on Apple keyboards.
/// </summary>
Insert = 77,
/// <summary>
/// <c>Page Down</c>, <c>PgDn</c> or <c>⇟</c>.
/// </summary>
PageDown = 78,
/// <summary>
/// <c>Page Up</c>, <c>PgUp</c> or <c>⇞</c>.
/// </summary>
PageUp = 79,
// #################
// Arrow Pad Section
// #################
/// <summary>
/// <c>↓</c>.
/// </summary>
ArrowDown = 80,
/// <summary>
/// <c>←</c>.
/// </summary>
ArrowLeft = 81,
/// <summary>
/// <c>→</c>.
/// </summary>
ArrowRight = 82,
/// <summary>
/// <c>↑</c>.
/// </summary>
ArrowUp = 83,
// ######################
// Numeric Keypad Section
// ######################
/// <summary>
/// Numeric keypad <c>Num Lock</c>.
/// On the Mac, this is used for the numpad <c>Clear</c> key.
/// </summary>
NumLock = 84,
/// <summary>
/// Numeric keypad <c>0 Ins</c> on a keyboard.
/// <c>0</c> on a phone or remote control.
/// </summary>
NumPad0 = 85,
/// <summary>
/// Numeric keypad <c>1 End</c> on a keyboard.
/// <c>1</c> or <c>1 QZ</c> on a phone or remote control.
/// </summary>
NumPad1 = 86,
/// <summary>
/// Numeric keypad <c>2 ↓</c> on a keyboard.
/// <c>2 ABC</c> on a phone or remote control.
/// </summary>
NumPad2 = 87,
/// <summary>
/// Numeric keypad <c>3 PgDn</c> on a keyboard.
/// <c>3 DEF</c> on a phone or remote control.
/// </summary>
NumPad3 = 88,
/// <summary>
/// Numeric keypad <c>4 ←</c> on a keyboard.
/// <c>4 GHI</c> on a phone or remote control.
/// </summary>
NumPad4 = 89,
/// <summary>
/// Numeric keypad <c>5</c> on a keyboard.
/// <c>5 JKL</c> on a phone or remote control.
/// </summary>
NumPad5 = 90,
/// <summary>
/// Numeric keypad <c>6 →</c> on a keyboard.
/// <c>6 MNO</c> on a phone or remote control.
/// </summary>
NumPad6 = 91,
/// <summary>
/// Numeric keypad <c>7 Home</c> on a keyboard.
/// <c>7 PQRS</c> or <c>7 PRS</c> on a phone or remote control.
/// </summary>
NumPad7 = 92,
/// <summary>
/// Numeric keypad <c>8 ↑</c> on a keyboard.
/// <c>8 TUV</c> on a phone or remote control.
/// </summary>
NumPad8 = 93,
/// <summary>
/// Numeric keypad <c>9 PgUp</c> on a keyboard.
/// <c>9 WXYZ</c> or <c>9 WXY</c> on a phone or remote control.
/// </summary>
NumPad9 = 94,
/// <summary>
/// Numeric keypad <c>+</c>.
/// </summary>
NumPadAdd = 95,
/// <summary>
/// Numeric keypad <c>C</c> or <c>AC</c> (All Clear).
/// Also for use with numpads that have a <c>Clear</c> key that is separate from the <c>NumLock</c> key.
/// On the Mac, the numpad <c>Clear</c> key is <see cref="NumLock"/>.
/// </summary>
NumPadClear = 96,
/// <summary>
/// Numeric keypad <c>,</c> (thousands separator).
/// For locales where the thousands separator is a "." (e.g., Brazil), this key may generate a <c>.</c>.
/// </summary>
NumPadComma = 97,
/// <summary>
/// Numeric keypad <c>. Del</c>.
/// For locales where the decimal separator is "," (e.g., Brazil), this key may generate a <c>,</c>.
/// </summary>
NumPadDecimal = 98,
/// <summary>
/// Numeric keypad <c>/</c>.
/// </summary>
NumPadDivide = 99,
/// <summary>
/// Numeric keypad <c>Enter</c>.
/// </summary>
NumPadEnter = 100,
/// <summary>
/// Numeric keypad <c>=</c>.
/// </summary>
NumPadEqual = 101,
/// <summary>
/// Numeric keypad <c>*</c> on a keyboard.
/// For use with numpads that provide mathematical operations (<c>+</c>, <c>-</c>, <c>*</c> and <c>/</c>).
/// </summary>
NumPadMultiply = 102,
/// <summary>
/// Numeric keypad <c>(</c>.
/// Found on the Microsoft Natural Keyboard.
/// </summary>
NumPadParenLeft = 103,
/// <summary>
/// Numeric keypad <c>)</c>.
/// Found on the Microsoft Natural Keyboard.
/// </summary>
NumPadParenRight = 104,
/// <summary>
/// Numeric keypad <c>-</c>.
/// </summary>
NumPadSubtract = 105,
// ################
// Function Section
// ################
/// <summary>
/// <c>Esc</c> or <c>⎋</c>.
/// </summary>
Escape = 106,
/// <summary>
/// <c>F1</c>.
/// </summary>
F1 = 107,
/// <summary>
/// <c>F2</c>.
/// </summary>
F2 = 108,
/// <summary>
/// <c>F3</c>.
/// </summary>
F3 = 109,
/// <summary>
/// <c>F4</c>.
/// </summary>
F4 = 110,
/// <summary>
/// <c>F5</c>.
/// </summary>
F5 = 111,
/// <summary>
/// <c>F6</c>.
/// </summary>
F6 = 112,
/// <summary>
/// <c>F7</c>.
/// </summary>
F7 = 113,
/// <summary>
/// <c>F8</c>.
/// </summary>
F8 = 114,
/// <summary>
/// <c>F9</c>.
/// </summary>
F9 = 115,
/// <summary>
/// <c>F10</c>.
/// </summary>
F10 = 116,
/// <summary>
/// <c>F11</c>.
/// </summary>
F11 = 117,
/// <summary>
/// <c>F12</c>.
/// </summary>
F12 = 118,
/// <summary>
/// <c>F13</c>.
/// </summary>
F13 = 119,
/// <summary>
/// <c>F14</c>.
/// </summary>
F14 = 120,
/// <summary>
/// <c>F15</c>.
/// </summary>
F15 = 121,
/// <summary>
/// <c>F16</c>.
/// </summary>
F16 = 122,
/// <summary>
/// <c>F17</c>.
/// </summary>
F17 = 123,
/// <summary>
/// <c>F18</c>.
/// </summary>
F18 = 124,
/// <summary>
/// <c>F19</c>.
/// </summary>
F19 = 125,
/// <summary>
/// <c>F20</c>.
/// </summary>
F20 = 126,
/// <summary>
/// <c>F21</c>.
/// </summary>
F21 = 127,
/// <summary>
/// <c>F22</c>.
/// </summary>
F22 = 128,
/// <summary>
/// <c>F23</c>.
/// </summary>
F23 = 129,
/// <summary>
/// <c>F24</c>.
/// </summary>
F24 = 130,
/// <summary>
/// <c>PrtScr SysRq</c> or <c>Print Screen</c>.
/// </summary>
PrintScreen = 131,
/// <summary>
/// <c>Scroll Lock</c>.
/// </summary>
ScrollLock = 132,
/// <summary>
/// <c>Pause Break</c>.
/// </summary>
Pause = 133,
// ##########
// Media Keys
// ##########
/// <summary>
/// Browser <c>Back</c>.
/// Some laptops place this key to the left of the <c>↑</c> key.
/// </summary>
BrowserBack = 134,
/// <summary>
/// Browser <c>Favorites</c>.
/// </summary>
BrowserFavorites = 135,
/// <summary>
/// Browser <c>Forward</c>.
/// Some laptops place this key to the right of the <c>↑</c> key.
/// </summary>
BrowserForward = 136,
/// <summary>
/// Browser <c>Home</c>.
/// </summary>
BrowserHome = 137,
/// <summary>
/// Browser <c>Refresh</c>.
/// </summary>
BrowserRefresh = 138,
/// <summary>
/// Browser <c>Search</c>.
/// </summary>
BrowserSearch = 139,
/// <summary>
/// Browser <c>Stop</c>.
/// </summary>
BrowserStop = 140,
/// <summary>
/// <c>Eject</c> or <c>⏏</c>.
/// This key is placed in the function section on some Apple keyboards.
/// </summary>
Eject = 141,
/// <summary>
/// <c>App 1</c>.
/// Sometimes labelled <c>My Computer</c> on the keyboard.
/// </summary>
LaunchApp1 = 142,
/// <summary>
/// <c>App 2</c>.
/// Sometimes labelled <c>Calculator</c> on the keyboard.
/// </summary>
LaunchApp2 = 143,
/// <summary>
/// <c>Mail</c>.
/// </summary>
LaunchMail = 144,
/// <summary>
/// Media <c>Play/Pause</c> or <c>⏵⏸</c>.
/// </summary>
MediaPlayPause = 145,
/// <summary>
/// Media <c>Select</c>.
/// </summary>
MediaSelect = 146,
/// <summary>
/// Media <c>Stop</c> or <c>⏹</c>.
/// </summary>
MediaStop = 147,
/// <summary>
/// Media <c>Next</c> or <c>⏭</c>.
/// </summary>
MediaTrackNext = 148,
/// <summary>
/// Media <c>Previous</c> or <c>⏮</c>.
/// </summary>
MediaTrackPrevious = 149,
/// <summary>
/// <c>Power</c>.
/// </summary>
Power = 150,
/// <summary>
/// <c>Sleep</c>.
/// </summary>
Sleep = 151,
/// <summary>
/// <c>Volume Down</c>.
/// </summary>
AudioVolumeDown = 152,
/// <summary>
/// <c>Mute</c>.
/// </summary>
AudioVolumeMute = 153,
/// <summary>
/// <c>Volume Up</c>.
/// </summary>
AudioVolumeUp = 154,
/// <summary>
/// <c>Wake Up</c>.
/// </summary>
WakeUp = 155,
// ###########
// Legacy Keys
// ###########
/// <summary>
/// <c>Again</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Again = 156,
/// <summary>
/// <c>Copy</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Copy = 157,
/// <summary>
/// <c>Cut</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Cut = 158,
/// <summary>
/// <c>Find</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Find = 159,
/// <summary>
/// <c>Open</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Open = 160,
/// <summary>
/// <c>Paste</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Paste = 161,
/// <summary>
/// <c>Props</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Props = 162,
/// <summary>
/// <c>Select</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Select = 163,
/// <summary>
/// <c>Undo</c>.
/// Legacy.
/// Found on Sun’s USB keyboard.
/// </summary>
Undo = 164
}

394
src/Avalonia.Base/Input/PhysicalKeyExtensions.cs

@ -0,0 +1,394 @@
namespace Avalonia.Input;
/// <summary>
/// Contains extension methods related to <see cref="PhysicalKey"/>.
/// </summary>
public static class PhysicalKeyExtensions
{
/// <summary>
/// Maps a physical key to a corresponding key, if possible, on a QWERTY keyboard.
/// </summary>
/// <param name="physicalKey">the physical key to map.</param>
/// <returns>The key corresponding to <paramref name="physicalKey"/>, or <see cref="Key.None"/>.</returns>
public static Key ToQwertyKey(this PhysicalKey physicalKey)
=> physicalKey switch
{
PhysicalKey.None => Key.None,
// Writing System Keys
PhysicalKey.Backquote => Key.Oem3,
PhysicalKey.Backslash => Key.Oem5,
PhysicalKey.BracketLeft => Key.Oem4,
PhysicalKey.BracketRight => Key.Oem6,
PhysicalKey.Comma => Key.OemComma,
PhysicalKey.Digit0 => Key.D0,
PhysicalKey.Digit1 => Key.D1,
PhysicalKey.Digit2 => Key.D2,
PhysicalKey.Digit3 => Key.D3,
PhysicalKey.Digit4 => Key.D4,
PhysicalKey.Digit5 => Key.D5,
PhysicalKey.Digit6 => Key.D6,
PhysicalKey.Digit7 => Key.D7,
PhysicalKey.Digit8 => Key.D8,
PhysicalKey.Digit9 => Key.D9,
PhysicalKey.Equal => Key.OemMinus,
PhysicalKey.IntlBackslash => Key.Oem102,
PhysicalKey.IntlRo => Key.Oem102,
PhysicalKey.IntlYen => Key.Oem5,
PhysicalKey.A => Key.A,
PhysicalKey.B => Key.B,
PhysicalKey.C => Key.C,
PhysicalKey.D => Key.D,
PhysicalKey.E => Key.E,
PhysicalKey.F => Key.F,
PhysicalKey.G => Key.G,
PhysicalKey.H => Key.H,
PhysicalKey.I => Key.I,
PhysicalKey.J => Key.J,
PhysicalKey.K => Key.K,
PhysicalKey.L => Key.L,
PhysicalKey.M => Key.M,
PhysicalKey.N => Key.N,
PhysicalKey.O => Key.O,
PhysicalKey.P => Key.P,
PhysicalKey.Q => Key.Q,
PhysicalKey.R => Key.R,
PhysicalKey.S => Key.S,
PhysicalKey.T => Key.T,
PhysicalKey.U => Key.U,
PhysicalKey.V => Key.V,
PhysicalKey.W => Key.W,
PhysicalKey.X => Key.X,
PhysicalKey.Y => Key.Y,
PhysicalKey.Z => Key.Z,
PhysicalKey.Minus => Key.OemMinus,
PhysicalKey.Period => Key.OemPeriod,
PhysicalKey.Quote => Key.Oem7,
PhysicalKey.Semicolon => Key.Oem1,
PhysicalKey.Slash => Key.Oem2,
// Functional Keys
PhysicalKey.AltLeft => Key.LeftAlt,
PhysicalKey.AltRight => Key.RightAlt,
PhysicalKey.Backspace => Key.Back,
PhysicalKey.CapsLock => Key.CapsLock,
PhysicalKey.ContextMenu => Key.Apps,
PhysicalKey.ControlLeft => Key.LeftCtrl,
PhysicalKey.ControlRight => Key.RightCtrl,
PhysicalKey.Enter => Key.Enter,
PhysicalKey.MetaLeft => Key.LWin,
PhysicalKey.MetaRight => Key.RWin,
PhysicalKey.ShiftLeft => Key.LeftShift,
PhysicalKey.ShiftRight => Key.RightShift,
PhysicalKey.Space => Key.Space,
PhysicalKey.Tab => Key.Tab,
PhysicalKey.Convert => Key.ImeConvert,
PhysicalKey.KanaMode => Key.KanaMode,
PhysicalKey.Lang1 => Key.HangulMode,
PhysicalKey.Lang2 => Key.HanjaMode,
PhysicalKey.Lang3 => Key.DbeKatakana,
PhysicalKey.Lang4 => Key.DbeHiragana,
PhysicalKey.Lang5 => Key.OemAuto,
PhysicalKey.NonConvert => Key.ImeNonConvert,
// Control Pad Section
PhysicalKey.Delete => Key.Delete,
PhysicalKey.End => Key.End,
PhysicalKey.Help => Key.Help,
PhysicalKey.Home => Key.Home,
PhysicalKey.Insert => Key.Insert,
PhysicalKey.PageDown => Key.PageDown,
PhysicalKey.PageUp => Key.PageUp,
// Arrow Pad Section
PhysicalKey.ArrowDown => Key.Down,
PhysicalKey.ArrowLeft => Key.Left,
PhysicalKey.ArrowRight => Key.Right,
PhysicalKey.ArrowUp => Key.Up,
// Numpad Section
PhysicalKey.NumLock => Key.NumLock,
PhysicalKey.NumPad0 => Key.NumPad0,
PhysicalKey.NumPad1 => Key.NumPad1,
PhysicalKey.NumPad2 => Key.NumPad2,
PhysicalKey.NumPad3 => Key.NumPad3,
PhysicalKey.NumPad4 => Key.NumPad4,
PhysicalKey.NumPad5 => Key.NumPad5,
PhysicalKey.NumPad6 => Key.NumPad6,
PhysicalKey.NumPad7 => Key.NumPad7,
PhysicalKey.NumPad8 => Key.NumPad8,
PhysicalKey.NumPad9 => Key.NumPad9,
PhysicalKey.NumPadAdd => Key.Add,
PhysicalKey.NumPadClear => Key.Clear,
PhysicalKey.NumPadComma => Key.AbntC2,
PhysicalKey.NumPadDecimal => Key.Decimal,
PhysicalKey.NumPadDivide => Key.Divide,
PhysicalKey.NumPadEnter => Key.Enter,
PhysicalKey.NumPadEqual => Key.OemPlus,
PhysicalKey.NumPadMultiply => Key.Multiply,
PhysicalKey.NumPadParenLeft => Key.Oem4,
PhysicalKey.NumPadParenRight => Key.Oem6,
PhysicalKey.NumPadSubtract => Key.Subtract,
// Function Section
PhysicalKey.Escape => Key.Escape,
PhysicalKey.F1 => Key.F1,
PhysicalKey.F2 => Key.F2,
PhysicalKey.F3 => Key.F3,
PhysicalKey.F4 => Key.F4,
PhysicalKey.F5 => Key.F5,
PhysicalKey.F6 => Key.F6,
PhysicalKey.F7 => Key.F7,
PhysicalKey.F8 => Key.F8,
PhysicalKey.F9 => Key.F9,
PhysicalKey.F10 => Key.F10,
PhysicalKey.F11 => Key.F11,
PhysicalKey.F12 => Key.F12,
PhysicalKey.F13 => Key.F13,
PhysicalKey.F14 => Key.F14,
PhysicalKey.F15 => Key.F15,
PhysicalKey.F16 => Key.F16,
PhysicalKey.F17 => Key.F17,
PhysicalKey.F18 => Key.F18,
PhysicalKey.F19 => Key.F19,
PhysicalKey.F20 => Key.F20,
PhysicalKey.F21 => Key.F21,
PhysicalKey.F22 => Key.F22,
PhysicalKey.F23 => Key.F23,
PhysicalKey.F24 => Key.F24,
PhysicalKey.PrintScreen => Key.PrintScreen,
PhysicalKey.ScrollLock => Key.Scroll,
PhysicalKey.Pause => Key.Pause,
// Media Keys
PhysicalKey.BrowserBack => Key.BrowserBack,
PhysicalKey.BrowserFavorites => Key.BrowserFavorites,
PhysicalKey.BrowserForward => Key.BrowserForward,
PhysicalKey.BrowserHome => Key.BrowserHome,
PhysicalKey.BrowserRefresh => Key.BrowserRefresh,
PhysicalKey.BrowserSearch => Key.BrowserSearch,
PhysicalKey.BrowserStop => Key.BrowserStop,
PhysicalKey.Eject => Key.None,
PhysicalKey.LaunchApp1 => Key.LaunchApplication1,
PhysicalKey.LaunchApp2 => Key.LaunchApplication2,
PhysicalKey.LaunchMail => Key.LaunchMail,
PhysicalKey.MediaPlayPause => Key.MediaPlayPause,
PhysicalKey.MediaSelect => Key.SelectMedia,
PhysicalKey.MediaStop => Key.MediaStop,
PhysicalKey.MediaTrackNext => Key.MediaNextTrack,
PhysicalKey.MediaTrackPrevious => Key.MediaPreviousTrack,
PhysicalKey.Power => Key.None,
PhysicalKey.Sleep => Key.Sleep,
PhysicalKey.AudioVolumeDown => Key.VolumeDown,
PhysicalKey.AudioVolumeMute => Key.VolumeMute,
PhysicalKey.AudioVolumeUp => Key.VolumeUp,
PhysicalKey.WakeUp => Key.None,
// Legacy Keys
PhysicalKey.Again => Key.None,
PhysicalKey.Copy => Key.OemCopy,
PhysicalKey.Cut => Key.None,
PhysicalKey.Find => Key.None,
PhysicalKey.Open => Key.None,
PhysicalKey.Paste => Key.None,
PhysicalKey.Props => Key.None,
PhysicalKey.Select => Key.Select,
PhysicalKey.Undo => Key.None,
_ => Key.None
};
/// <summary>
/// Maps a physical key to a corresponding key symbol, if possible, on a QWERTY keyboard.
/// </summary>
/// <param name="physicalKey">the physical key to map.</param>
/// <param name="useShiftModifier">Indicates whether the Shift key is considered pressed.</param>
/// <returns>The key corresponding to <paramref name="physicalKey"/>, or <see cref="Key.None"/>.</returns>
public static string? ToQwertyKeySymbol(this PhysicalKey physicalKey, bool useShiftModifier = false)
=> physicalKey switch
{
PhysicalKey.None => null,
// Writing System Keys
PhysicalKey.Backquote => useShiftModifier ? "~" : "`",
PhysicalKey.Backslash => useShiftModifier ? "|" : "\\",
PhysicalKey.BracketLeft => useShiftModifier ? "{" : "[",
PhysicalKey.BracketRight => useShiftModifier ? "}" : "]",
PhysicalKey.Comma => useShiftModifier ? "<" : ",",
PhysicalKey.Digit0 => useShiftModifier ? ")" : "0",
PhysicalKey.Digit1 => useShiftModifier ? "!" : "1",
PhysicalKey.Digit2 => useShiftModifier ? "@" : "2",
PhysicalKey.Digit3 => useShiftModifier ? "#" : "3",
PhysicalKey.Digit4 => useShiftModifier ? "$" : "4",
PhysicalKey.Digit5 => useShiftModifier ? "%" : "5",
PhysicalKey.Digit6 => useShiftModifier ? "^" : "6",
PhysicalKey.Digit7 => useShiftModifier ? "&" : "7",
PhysicalKey.Digit8 => useShiftModifier ? "*" : "8",
PhysicalKey.Digit9 => useShiftModifier ? "(" : "9",
PhysicalKey.Equal => useShiftModifier ? "+" : "=",
PhysicalKey.IntlBackslash => useShiftModifier ? "|" : "\\",
PhysicalKey.IntlRo => null,
PhysicalKey.IntlYen => null,
PhysicalKey.A => useShiftModifier ? "A" : "a",
PhysicalKey.B => useShiftModifier ? "B" : "b",
PhysicalKey.C => useShiftModifier ? "C" : "c",
PhysicalKey.D => useShiftModifier ? "D" : "d",
PhysicalKey.E => useShiftModifier ? "E" : "e",
PhysicalKey.F => useShiftModifier ? "F" : "f",
PhysicalKey.G => useShiftModifier ? "G" : "g",
PhysicalKey.H => useShiftModifier ? "H" : "h",
PhysicalKey.I => useShiftModifier ? "I" : "i",
PhysicalKey.J => useShiftModifier ? "J" : "j",
PhysicalKey.K => useShiftModifier ? "K" : "k",
PhysicalKey.L => useShiftModifier ? "L" : "l",
PhysicalKey.M => useShiftModifier ? "M" : "m",
PhysicalKey.N => useShiftModifier ? "N" : "n",
PhysicalKey.O => useShiftModifier ? "O" : "o",
PhysicalKey.P => useShiftModifier ? "P" : "p",
PhysicalKey.Q => useShiftModifier ? "Q" : "q",
PhysicalKey.R => useShiftModifier ? "R" : "r",
PhysicalKey.S => useShiftModifier ? "S" : "s",
PhysicalKey.T => useShiftModifier ? "T" : "t",
PhysicalKey.U => useShiftModifier ? "U" : "u",
PhysicalKey.V => useShiftModifier ? "V" : "v",
PhysicalKey.W => useShiftModifier ? "W" : "w",
PhysicalKey.X => useShiftModifier ? "X" : "x",
PhysicalKey.Y => useShiftModifier ? "Y" : "y",
PhysicalKey.Z => useShiftModifier ? "Z" : "z",
PhysicalKey.Minus => useShiftModifier ? "_" : "-",
PhysicalKey.Period => useShiftModifier ? ">" : ".",
PhysicalKey.Quote => useShiftModifier ? "\"" : "'",
PhysicalKey.Semicolon => useShiftModifier ? ":" : ";",
PhysicalKey.Slash => useShiftModifier ? "?" : "/",
// Functional Keys
PhysicalKey.AltLeft => null,
PhysicalKey.AltRight => null,
PhysicalKey.Backspace => "\b",
PhysicalKey.CapsLock => null,
PhysicalKey.ContextMenu => null,
PhysicalKey.ControlLeft => null,
PhysicalKey.ControlRight => null,
PhysicalKey.Enter => "\r",
PhysicalKey.MetaLeft => null,
PhysicalKey.MetaRight => null,
PhysicalKey.ShiftLeft => null,
PhysicalKey.ShiftRight => null,
PhysicalKey.Space => " ",
PhysicalKey.Tab => "\t",
PhysicalKey.Convert => null,
PhysicalKey.KanaMode => null,
PhysicalKey.Lang1 => null,
PhysicalKey.Lang2 => null,
PhysicalKey.Lang3 => null,
PhysicalKey.Lang4 => null,
PhysicalKey.Lang5 => null,
PhysicalKey.NonConvert => null,
// Control Pad Section
PhysicalKey.Delete => null,
PhysicalKey.End => null,
PhysicalKey.Help => null,
PhysicalKey.Home => null,
PhysicalKey.Insert => null,
PhysicalKey.PageDown => null,
PhysicalKey.PageUp => null,
// Arrow Pad Section
PhysicalKey.ArrowDown => null,
PhysicalKey.ArrowLeft => null,
PhysicalKey.ArrowRight => null,
PhysicalKey.ArrowUp => null,
// Numpad Section
PhysicalKey.NumLock => null,
PhysicalKey.NumPad0 => "0",
PhysicalKey.NumPad1 => "1",
PhysicalKey.NumPad2 => "2",
PhysicalKey.NumPad3 => "3",
PhysicalKey.NumPad4 => "4",
PhysicalKey.NumPad5 => "5",
PhysicalKey.NumPad6 => "6",
PhysicalKey.NumPad7 => "7",
PhysicalKey.NumPad8 => "8",
PhysicalKey.NumPad9 => "9",
PhysicalKey.NumPadAdd => "+",
PhysicalKey.NumPadClear => null,
PhysicalKey.NumPadComma => ",",
PhysicalKey.NumPadDecimal => ".",
PhysicalKey.NumPadDivide => "/",
PhysicalKey.NumPadEnter => "\r",
PhysicalKey.NumPadEqual => "=",
PhysicalKey.NumPadMultiply => "*",
PhysicalKey.NumPadParenLeft => "(",
PhysicalKey.NumPadParenRight => ")",
PhysicalKey.NumPadSubtract => "-",
// Function Section
PhysicalKey.Escape => "\u001B",
PhysicalKey.F1 => null,
PhysicalKey.F2 => null,
PhysicalKey.F3 => null,
PhysicalKey.F4 => null,
PhysicalKey.F5 => null,
PhysicalKey.F6 => null,
PhysicalKey.F7 => null,
PhysicalKey.F8 => null,
PhysicalKey.F9 => null,
PhysicalKey.F10 => null,
PhysicalKey.F11 => null,
PhysicalKey.F12 => null,
PhysicalKey.F13 => null,
PhysicalKey.F14 => null,
PhysicalKey.F15 => null,
PhysicalKey.F16 => null,
PhysicalKey.F17 => null,
PhysicalKey.F18 => null,
PhysicalKey.F19 => null,
PhysicalKey.F20 => null,
PhysicalKey.F21 => null,
PhysicalKey.F22 => null,
PhysicalKey.F23 => null,
PhysicalKey.F24 => null,
PhysicalKey.PrintScreen => null,
PhysicalKey.ScrollLock => null,
PhysicalKey.Pause => null,
// Media Keys
PhysicalKey.BrowserBack => null,
PhysicalKey.BrowserFavorites => null,
PhysicalKey.BrowserForward => null,
PhysicalKey.BrowserHome => null,
PhysicalKey.BrowserRefresh => null,
PhysicalKey.BrowserSearch => null,
PhysicalKey.BrowserStop => null,
PhysicalKey.Eject => null,
PhysicalKey.LaunchApp1 => null,
PhysicalKey.LaunchApp2 => null,
PhysicalKey.LaunchMail => null,
PhysicalKey.MediaPlayPause => null,
PhysicalKey.MediaSelect => null,
PhysicalKey.MediaStop => null,
PhysicalKey.MediaTrackNext => null,
PhysicalKey.MediaTrackPrevious => null,
PhysicalKey.Power => null,
PhysicalKey.Sleep => null,
PhysicalKey.AudioVolumeDown => null,
PhysicalKey.AudioVolumeMute => null,
PhysicalKey.AudioVolumeUp => null,
PhysicalKey.WakeUp => null,
// Legacy Keys
PhysicalKey.Again => null,
PhysicalKey.Copy => null,
PhysicalKey.Cut => null,
PhysicalKey.Find => null,
PhysicalKey.Open => null,
PhysicalKey.Paste => null,
PhysicalKey.Props => null,
PhysicalKey.Select => null,
PhysicalKey.Undo => null,
_ => null
};
}

29
src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Input.Raw
@ -11,23 +12,47 @@ namespace Avalonia.Input.Raw
[PrivateApi]
public class RawKeyEventArgs : RawInputEventArgs
{
[Obsolete("Use the overload that takes a physical key and key symbol instead.")]
public RawKeyEventArgs(
IKeyboardDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key, RawInputModifiers modifiers)
: base(device, timestamp, root)
Key key,
RawInputModifiers modifiers)
: this(device, timestamp, root, type, key, modifiers, PhysicalKey.None, null)
{
Key = key;
Type = type;
Modifiers = modifiers;
}
public RawKeyEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
string? keySymbol)
: base(device, timestamp, root)
{
Key = key;
Modifiers = modifiers;
Type = type;
PhysicalKey = physicalKey;
KeySymbol = keySymbol;
}
public Key Key { get; set; }
public RawInputModifiers Modifiers { get; set; }
public RawKeyEventType Type { get; set; }
public PhysicalKey PhysicalKey { get; set; }
public string? KeySymbol { get; set; }
}
}

5
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -12,6 +12,7 @@ using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using Key = Avalonia.Input.Key;
using PhysicalKey = Avalonia.Input.PhysicalKey;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
@ -226,7 +227,9 @@ namespace Avalonia.Controls.Remote.Server
InputRoot!,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaRawInputModifiers(key.Modifiers)));
GetAvaloniaRawInputModifiers(key.Modifiers),
(PhysicalKey)key.PhysicalKey,
key.KeySymbol));
}, DispatcherPriority.Input);
break;

4
src/Avalonia.Controls/TopLevel.cs

@ -258,7 +258,9 @@ namespace Avalonia.Controls
var keyEvent = new KeyEventArgs()
{
KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers,
Key = rawKeyEventArgs.Key
Key = rawKeyEventArgs.Key,
PhysicalKey = rawKeyEventArgs.PhysicalKey,
KeySymbol = rawKeyEventArgs.KeySymbol
};
backRequested = keymap.Any( key => key.Matches(keyEvent));

22
src/Avalonia.Native/WindowImplBase.cs

@ -247,9 +247,9 @@ namespace Avalonia.Native
_parent.RawMouseEvent(type, timeStamp, modifiers, point, delta);
}
int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key)
int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnKey key, AvnPhysicalKey physicalKey, string keySymbol)
{
return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool();
return _parent.RawKeyEvent(type, timeStamp, modifiers, key, physicalKey, keySymbol).AsComBool();
}
int IAvnWindowBaseEvents.RawTextInputEvent(ulong timeStamp, string text)
@ -322,14 +322,28 @@ namespace Avalonia.Native
return args.Handled;
}
public bool RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key)
public bool RawKeyEvent(
AvnRawKeyEventType type,
ulong timeStamp,
AvnInputModifiers modifiers,
AvnKey key,
AvnPhysicalKey physicalKey,
string keySymbol)
{
if (_inputRoot is null)
return false;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var args = new RawKeyEventArgs(_keyboard, timeStamp, _inputRoot, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers);
var args = new RawKeyEventArgs(
_keyboard,
timeStamp,
_inputRoot,
(RawKeyEventType)type,
(Key)key,
(RawInputModifiers)modifiers,
(PhysicalKey)physicalKey,
keySymbol);
Input?.Invoke(args);

549
src/Avalonia.Native/avn.idl

@ -26,195 +26,369 @@ enum AvnKey
AvnKeyJunjaMode = 10,
AvnKeyFinalMode = 11,
AvnKeyKanjiMode = 12,
HanjaMode = 12,
Escape = 13,
ImeConvert = 14,
ImeNonConvert = 15,
ImeAccept = 16,
ImeModeChange = 17,
Space = 18,
PageUp = 19,
Prior = 19,
PageDown = 20,
Next = 20,
End = 21,
Home = 22,
Left = 23,
Up = 24,
Right = 25,
Down = 26,
Select = 27,
Print = 28,
Execute = 29,
Snapshot = 30,
PrintScreen = 30,
Insert = 31,
Delete = 32,
Help = 33,
D0 = 34,
D1 = 35,
D2 = 36,
D3 = 37,
D4 = 38,
D5 = 39,
D6 = 40,
D7 = 41,
D8 = 42,
D9 = 43,
A = 44,
B = 45,
C = 46,
D = 47,
E = 48,
F = 49,
G = 50,
H = 51,
I = 52,
J = 53,
AvnKeyHanjaMode = 12,
AvnKeyEscape = 13,
AvnKeyImeConvert = 14,
AvnKeyImeNonConvert = 15,
AvnKeyImeAccept = 16,
AvnKeyImeModeChange = 17,
AvnKeySpace = 18,
AvnKeyPageUp = 19,
AvnKeyPrior = 19,
AvnKeyPageDown = 20,
AvnKeyNext = 20,
AvnKeyEnd = 21,
AvnKeyHome = 22,
AvnKeyLeft = 23,
AvnKeyUp = 24,
AvnKeyRight = 25,
AvnKeyDown = 26,
AvnKeySelect = 27,
AvnKeyPrint = 28,
AvnKeyExecute = 29,
AvnKeySnapshot = 30,
AvnKeyPrintScreen = 30,
AvnKeyInsert = 31,
AvnKeyDelete = 32,
AvnKeyHelp = 33,
AvnKeyD0 = 34,
AvnKeyD1 = 35,
AvnKeyD2 = 36,
AvnKeyD3 = 37,
AvnKeyD4 = 38,
AvnKeyD5 = 39,
AvnKeyD6 = 40,
AvnKeyD7 = 41,
AvnKeyD8 = 42,
AvnKeyD9 = 43,
AvnKeyA = 44,
AvnKeyB = 45,
AvnKeyC = 46,
AvnKeyD = 47,
AvnKeyE = 48,
AvnKeyF = 49,
AvnKeyG = 50,
AvnKeyH = 51,
AvnKeyI = 52,
AvnKeyJ = 53,
AvnKeyK = 54,
L = 55,
M = 56,
N = 57,
O = 58,
P = 59,
Q = 60,
R = 61,
S = 62,
T = 63,
U = 64,
V = 65,
W = 66,
X = 67,
Y = 68,
Z = 69,
LWin = 70,
RWin = 71,
Apps = 72,
Sleep = 73,
NumPad0 = 74,
NumPad1 = 75,
NumPad2 = 76,
NumPad3 = 77,
NumPad4 = 78,
NumPad5 = 79,
NumPad6 = 80,
NumPad7 = 81,
NumPad8 = 82,
NumPad9 = 83,
Multiply = 84,
Add = 85,
Separator = 86,
Subtract = 87,
Decimal = 88,
Divide = 89,
F1 = 90,
F2 = 91,
F3 = 92,
F4 = 93,
F5 = 94,
F6 = 95,
F7 = 96,
F8 = 97,
F9 = 98,
F10 = 99,
F11 = 100,
F12 = 101,
F13 = 102,
F14 = 103,
F15 = 104,
F16 = 105,
F17 = 106,
F18 = 107,
F19 = 108,
F20 = 109,
F21 = 110,
F22 = 111,
F23 = 112,
F24 = 113,
NumLock = 114,
Scroll = 115,
LeftShift = 116,
RightShift = 117,
LeftCtrl = 118,
RightCtrl = 119,
LeftAlt = 120,
RightAlt = 121,
BrowserBack = 122,
BrowserForward = 123,
BrowserRefresh = 124,
BrowserStop = 125,
BrowserSearch = 126,
BrowserFavorites = 127,
BrowserHome = 128,
VolumeMute = 129,
VolumeDown = 130,
VolumeUp = 131,
MediaNextTrack = 132,
MediaPreviousTrack = 133,
MediaStop = 134,
MediaPlayPause = 135,
LaunchMail = 136,
SelectMedia = 137,
LaunchApplication1 = 138,
LaunchApplication2 = 139,
OemSemicolon = 140,
Oem1 = 140,
OemPlus = 141,
OemComma = 142,
OemMinus = 143,
OemPeriod = 144,
OemQuestion = 145,
Oem2 = 145,
OemTilde = 146,
Oem3 = 146,
AbntC1 = 147,
AbntC2 = 148,
OemOpenBrackets = 149,
Oem4 = 149,
OemPipe = 150,
Oem5 = 150,
OemCloseBrackets = 151,
Oem6 = 151,
OemQuotes = 152,
Oem7 = 152,
Oem8 = 153,
OemBackslash = 154,
Oem102 = 154,
ImeProcessed = 155,
System = 156,
OemAttn = 157,
DbeAlphanumeric = 157,
OemFinish = 158,
DbeKatakana = 158,
DbeHiragana = 159,
OemCopy = 159,
DbeSbcsChar = 160,
OemAuto = 160,
DbeDbcsChar = 161,
OemEnlw = 161,
OemBackTab = 162,
DbeRoman = 162,
DbeNoRoman = 163,
Attn = 163,
CrSel = 164,
DbeEnterWordRegisterMode = 164,
ExSel = 165,
DbeEnterImeConfigureMode = 165,
EraseEof = 166,
DbeFlushString = 166,
Play = 167,
DbeCodeInput = 167,
DbeNoCodeInput = 168,
Zoom = 168,
NoName = 169,
DbeDetermineString = 169,
DbeEnterDialogConversionMode = 170,
Pa1 = 170,
OemClear = 171,
DeadCharProcessed = 172,
AvnKeyL = 55,
AvnKeyM = 56,
AvnKeyN = 57,
AvnKeyO = 58,
AvnKeyP = 59,
AvnKeyQ = 60,
AvnKeyR = 61,
AvnKeyS = 62,
AvnKeyT = 63,
AvnKeyU = 64,
AvnKeyV = 65,
AvnKeyW = 66,
AvnKeyX = 67,
AvnKeyY = 68,
AvnKeyZ = 69,
AvnKeyLWin = 70,
AvnKeyRWin = 71,
AvnKeyApps = 72,
AvnKeySleep = 73,
AvnKeyNumPad0 = 74,
AvnKeyNumPad1 = 75,
AvnKeyNumPad2 = 76,
AvnKeyNumPad3 = 77,
AvnKeyNumPad4 = 78,
AvnKeyNumPad5 = 79,
AvnKeyNumPad6 = 80,
AvnKeyNumPad7 = 81,
AvnKeyNumPad8 = 82,
AvnKeyNumPad9 = 83,
AvnKeyMultiply = 84,
AvnKeyAdd = 85,
AvnKeySeparator = 86,
AvnKeySubtract = 87,
AvnKeyDecimal = 88,
AvnKeyDivide = 89,
AvnKeyF1 = 90,
AvnKeyF2 = 91,
AvnKeyF3 = 92,
AvnKeyF4 = 93,
AvnKeyF5 = 94,
AvnKeyF6 = 95,
AvnKeyF7 = 96,
AvnKeyF8 = 97,
AvnKeyF9 = 98,
AvnKeyF10 = 99,
AvnKeyF11 = 100,
AvnKeyF12 = 101,
AvnKeyF13 = 102,
AvnKeyF14 = 103,
AvnKeyF15 = 104,
AvnKeyF16 = 105,
AvnKeyF17 = 106,
AvnKeyF18 = 107,
AvnKeyF19 = 108,
AvnKeyF20 = 109,
AvnKeyF21 = 110,
AvnKeyF22 = 111,
AvnKeyF23 = 112,
AvnKeyF24 = 113,
AvnKeyNumLock = 114,
AvnKeyScroll = 115,
AvnKeyLeftShift = 116,
AvnKeyRightShift = 117,
AvnKeyLeftCtrl = 118,
AvnKeyRightCtrl = 119,
AvnKeyLeftAlt = 120,
AvnKeyRightAlt = 121,
AvnKeyBrowserBack = 122,
AvnKeyBrowserForward = 123,
AvnKeyBrowserRefresh = 124,
AvnKeyBrowserStop = 125,
AvnKeyBrowserSearch = 126,
AvnKeyBrowserFavorites = 127,
AvnKeyBrowserHome = 128,
AvnKeyVolumeMute = 129,
AvnKeyVolumeDown = 130,
AvnKeyVolumeUp = 131,
AvnKeyMediaNextTrack = 132,
AvnKeyMediaPreviousTrack = 133,
AvnKeyMediaStop = 134,
AvnKeyMediaPlayPause = 135,
AvnKeyLaunchMail = 136,
AvnKeySelectMedia = 137,
AvnKeyLaunchApplication1 = 138,
AvnKeyLaunchApplication2 = 139,
AvnKeyOemSemicolon = 140,
AvnKeyOem1 = 140,
AvnKeyOemPlus = 141,
AvnKeyOemComma = 142,
AvnKeyOemMinus = 143,
AvnKeyOemPeriod = 144,
AvnKeyOemQuestion = 145,
AvnKeyOem2 = 145,
AvnKeyOemTilde = 146,
AvnKeyOem3 = 146,
AvnKeyAbntC1 = 147,
AvnKeyAbntC2 = 148,
AvnKeyOemOpenBrackets = 149,
AvnKeyOem4 = 149,
AvnKeyOemPipe = 150,
AvnKeyOem5 = 150,
AvnKeyOemCloseBrackets = 151,
AvnKeyOem6 = 151,
AvnKeyOemQuotes = 152,
AvnKeyOem7 = 152,
AvnKeyOem8 = 153,
AvnKeyOemBackslash = 154,
AvnKeyOem102 = 154,
AvnKeyImeProcessed = 155,
AvnKeySystem = 156,
AvnKeyOemAttn = 157,
AvnKeyDbeAlphanumeric = 157,
AvnKeyOemFinish = 158,
AvnKeyDbeKatakana = 158,
AvnKeyDbeHiragana = 159,
AvnKeyOemCopy = 159,
AvnKeyDbeSbcsChar = 160,
AvnKeyOemAuto = 160,
AvnKeyDbeDbcsChar = 161,
AvnKeyOemEnlw = 161,
AvnKeyOemBackTab = 162,
AvnKeyDbeRoman = 162,
AvnKeyDbeNoRoman = 163,
AvnKeyAttn = 163,
AvnKeyCrSel = 164,
AvnKeyDbeEnterWordRegisterMode = 164,
AvnKeyExSel = 165,
AvnKeyDbeEnterImeConfigureMode = 165,
AvnKeyEraseEof = 166,
AvnKeyDbeFlushString = 166,
AvnKeyPlay = 167,
AvnKeyDbeCodeInput = 167,
AvnKeyDbeNoCodeInput = 168,
AvnKeyZoom = 168,
AvnKeyNoName = 169,
AvnKeyDbeDetermineString = 169,
AvnKeyDbeEnterDialogConversionMode = 170,
AvnKeyPa1 = 170,
AvnKeyOemClear = 171,
AvnKeyDeadCharProcessed = 172,
AvnKeyFnLeftArrow = 10001,
AvnKeyFnRightArrow = 10002,
AvnKeyFnUpArrow = 10003,
AvnKeyFnDownArrow = 10004,
};
enum SystemDecorations {
enum AvnPhysicalKey
{
AvnPhysicalKeyNone = 0,
AvnPhysicalKeyBackquote = 1,
AvnPhysicalKeyBackslash = 2,
AvnPhysicalKeyBracketLeft = 3,
AvnPhysicalKeyBracketRight = 4,
AvnPhysicalKeyComma = 5,
AvnPhysicalKeyDigit0 = 6,
AvnPhysicalKeyDigit1 = 7,
AvnPhysicalKeyDigit2 = 8,
AvnPhysicalKeyDigit3 = 9,
AvnPhysicalKeyDigit4 = 10,
AvnPhysicalKeyDigit5 = 11,
AvnPhysicalKeyDigit6 = 12,
AvnPhysicalKeyDigit7 = 13,
AvnPhysicalKeyDigit8 = 14,
AvnPhysicalKeyDigit9 = 15,
AvnPhysicalKeyEqual = 16,
AvnPhysicalKeyIntlBackslash = 17,
AvnPhysicalKeyIntlRo = 18,
AvnPhysicalKeyIntlYen = 19,
AvnPhysicalKeyA = 20,
AvnPhysicalKeyB = 21,
AvnPhysicalKeyC = 22,
AvnPhysicalKeyD = 23,
AvnPhysicalKeyE = 24,
AvnPhysicalKeyF = 25,
AvnPhysicalKeyG = 26,
AvnPhysicalKeyH = 27,
AvnPhysicalKeyI = 28,
AvnPhysicalKeyJ = 29,
AvnPhysicalKeyK = 30,
AvnPhysicalKeyL = 31,
AvnPhysicalKeyM = 32,
AvnPhysicalKeyN = 33,
AvnPhysicalKeyO = 34,
AvnPhysicalKeyP = 35,
AvnPhysicalKeyQ = 36,
AvnPhysicalKeyR = 37,
AvnPhysicalKeyS = 38,
AvnPhysicalKeyT = 39,
AvnPhysicalKeyU = 40,
AvnPhysicalKeyV = 41,
AvnPhysicalKeyW = 42,
AvnPhysicalKeyX = 43,
AvnPhysicalKeyY = 44,
AvnPhysicalKeyZ = 45,
AvnPhysicalKeyMinus = 46,
AvnPhysicalKeyPeriod = 47,
AvnPhysicalKeyQuote = 48,
AvnPhysicalKeySemicolon = 49,
AvnPhysicalKeySlash = 50,
AvnPhysicalKeyAltLeft = 51,
AvnPhysicalKeyAltRight = 52,
AvnPhysicalKeyBackspace = 53,
AvnPhysicalKeyCapsLock = 54,
AvnPhysicalKeyContextMenu = 55,
AvnPhysicalKeyControlLeft = 56,
AvnPhysicalKeyControlRight = 57,
AvnPhysicalKeyEnter = 58,
AvnPhysicalKeyMetaLeft = 59,
AvnPhysicalKeyMetaRight = 60,
AvnPhysicalKeyShiftLeft = 61,
AvnPhysicalKeyShiftRight = 62,
AvnPhysicalKeySpace = 63,
AvnPhysicalKeyTab = 64,
AvnPhysicalKeyConvert = 65,
AvnPhysicalKeyKanaMode = 66,
AvnPhysicalKeyLang1 = 67,
AvnPhysicalKeyLang2 = 68,
AvnPhysicalKeyLang3 = 69,
AvnPhysicalKeyLang4 = 70,
AvnPhysicalKeyLang5 = 71,
AvnPhysicalKeyNonConvert = 72,
AvnPhysicalKeyDelete = 73,
AvnPhysicalKeyEnd = 74,
AvnPhysicalKeyHelp = 75,
AvnPhysicalKeyHome = 76,
AvnPhysicalKeyInsert = 77,
AvnPhysicalKeyPageDown = 78,
AvnPhysicalKeyPageUp = 79,
AvnPhysicalKeyArrowDown = 80,
AvnPhysicalKeyArrowLeft = 81,
AvnPhysicalKeyArrowRight = 82,
AvnPhysicalKeyArrowUp = 83,
AvnPhysicalKeyNumLock = 84,
AvnPhysicalKeyNumPad0 = 85,
AvnPhysicalKeyNumPad1 = 86,
AvnPhysicalKeyNumPad2 = 87,
AvnPhysicalKeyNumPad3 = 88,
AvnPhysicalKeyNumPad4 = 89,
AvnPhysicalKeyNumPad5 = 90,
AvnPhysicalKeyNumPad6 = 91,
AvnPhysicalKeyNumPad7 = 92,
AvnPhysicalKeyNumPad8 = 93,
AvnPhysicalKeyNumPad9 = 94,
AvnPhysicalKeyNumPadAdd = 95,
AvnPhysicalKeyNumPadClear = 96,
AvnPhysicalKeyNumPadComma = 97,
AvnPhysicalKeyNumPadDecimal = 98,
AvnPhysicalKeyNumPadDivide = 99,
AvnPhysicalKeyNumPadEnter = 100,
AvnPhysicalKeyNumPadEqual = 101,
AvnPhysicalKeyNumPadMultiply = 102,
AvnPhysicalKeyNumPadParenLeft = 103,
AvnPhysicalKeyNumPadParenRight = 104,
AvnPhysicalKeyNumPadSubtract = 105,
AvnPhysicalKeyEscape = 106,
AvnPhysicalKeyF1 = 107,
AvnPhysicalKeyF2 = 108,
AvnPhysicalKeyF3 = 109,
AvnPhysicalKeyF4 = 110,
AvnPhysicalKeyF5 = 111,
AvnPhysicalKeyF6 = 112,
AvnPhysicalKeyF7 = 113,
AvnPhysicalKeyF8 = 114,
AvnPhysicalKeyF9 = 115,
AvnPhysicalKeyF10 = 116,
AvnPhysicalKeyF11 = 117,
AvnPhysicalKeyF12 = 118,
AvnPhysicalKeyF13 = 119,
AvnPhysicalKeyF14 = 120,
AvnPhysicalKeyF15 = 121,
AvnPhysicalKeyF16 = 122,
AvnPhysicalKeyF17 = 123,
AvnPhysicalKeyF18 = 124,
AvnPhysicalKeyF19 = 125,
AvnPhysicalKeyF20 = 126,
AvnPhysicalKeyF21 = 127,
AvnPhysicalKeyF22 = 128,
AvnPhysicalKeyF23 = 129,
AvnPhysicalKeyF24 = 130,
AvnPhysicalKeyPrintScreen = 131,
AvnPhysicalKeyScrollLock = 132,
AvnPhysicalKeyPause = 133,
AvnPhysicalKeyBrowserBack = 134,
AvnPhysicalKeyBrowserFavorites = 135,
AvnPhysicalKeyBrowserForward = 136,
AvnPhysicalKeyBrowserHome = 137,
AvnPhysicalKeyBrowserRefresh = 138,
AvnPhysicalKeyBrowserSearch = 139,
AvnPhysicalKeyBrowserStop = 140,
AvnPhysicalKeyEject = 141,
AvnPhysicalKeyLaunchApp1 = 142,
AvnPhysicalKeyLaunchApp2 = 143,
AvnPhysicalKeyLaunchMail = 144,
AvnPhysicalKeyMediaPlayPause = 145,
AvnPhysicalKeyMediaSelect = 146,
AvnPhysicalKeyMediaStop = 147,
AvnPhysicalKeyMediaTrackNext = 148,
AvnPhysicalKeyMediaTrackPrevious = 149,
AvnPhysicalKeyPower = 150,
AvnPhysicalKeySleep = 151,
AvnPhysicalKeyAudioVolumeDown = 152,
AvnPhysicalKeyAudioVolumeMute = 153,
AvnPhysicalKeyAudioVolumeUp = 154,
AvnPhysicalKeyWakeUp = 155,
AvnPhysicalKeyAgain = 156,
AvnPhysicalKeyCopy = 157,
AvnPhysicalKeyCut = 158,
AvnPhysicalKeyFind = 159,
AvnPhysicalKeyOpen = 160,
AvnPhysicalKeyPaste = 161,
AvnPhysicalKeyProps = 162,
AvnPhysicalKeySelect = 163,
AvnPhysicalKeyUndo = 164
}
enum SystemDecorations
{
SystemDecorationsNone = 0,
SystemDecorationsBorderOnly = 1,
SystemDecorationsFull = 2,
@ -592,7 +766,8 @@ interface IAvnWindowBaseEvents : IUnknown
AvnInputModifiers modifiers,
AvnPoint point,
AvnVector delta);
bool RawKeyEvent(AvnRawKeyEventType type, u_int64_t timeStamp, AvnInputModifiers modifiers, uint key);
bool RawKeyEvent(AvnRawKeyEventType type, u_int64_t timeStamp, AvnInputModifiers modifiers,
AvnKey key, AvnPhysicalKey physicalKey, [const] char* keySymbol);
bool RawTextInputEvent(u_int64_t timeStamp, [const] char* text);
void ScalingChanged(double scaling);
void RunRenderPriorityJobs();

2
src/Avalonia.Native/regen.sh

@ -1,2 +0,0 @@
#!/bin/sh
dotnet msbuild /t:Clean,GenerateSharpGenBindings

1
src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Input\Key.cs" />
<Compile Include="..\Avalonia.Base\Input\PhysicalKey.cs" />
<Compile Include="..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Visible="False" />
</ItemGroup>
<Import Project="..\..\build\TrimmingEnable.props" />

11
src/Avalonia.Remote.Protocol/InputMessages.cs

@ -1,4 +1,7 @@
using System;
#nullable enable
using System;
/*
We are keeping copies of core events here, so they can be used
without referencing Avalonia itself, e. g. from projects that
@ -34,7 +37,7 @@ namespace Avalonia.Remote.Protocol.Input
public abstract class InputEventMessageBase
{
public InputModifiers[] Modifiers { get; set; }
public InputModifiers[]? Modifiers { get; set; }
}
public abstract class PointerEventMessageBase : InputEventMessageBase
@ -73,12 +76,14 @@ namespace Avalonia.Remote.Protocol.Input
{
public bool IsDown { get; set; }
public Key Key { get; set; }
public PhysicalKey PhysicalKey { get; set; }
public string? KeySymbol { get; set; }
}
[AvaloniaRemoteMessageGuid("C174102E-7405-4594-916F-B10B8248A17D")]
public class TextInputEventMessage : InputEventMessageBase
{
public string Text { get; set; }
public string Text { get; set; } = string.Empty;
}
}

25
src/Avalonia.X11/X11Info.cs

@ -1,6 +1,4 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
@ -30,10 +28,13 @@ namespace Avalonia.X11
public Version XInputVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
public XVisualInfo? TransparentVisualInfo { get; set; }
public bool HasXim { get; set; }
public bool HasXSync { get; set; }
public IntPtr DefaultFontSet { get; set; }
public XVisualInfo? TransparentVisualInfo { get; }
public bool HasXim { get; }
public bool HasXSync { get; }
public IntPtr DefaultFontSet { get; }
public bool HasXkb { get; }
[DllImport("libc")]
private static extern void setlocale(int type, string s);
@ -124,6 +125,18 @@ namespace Avalonia.X11
{
//Ignore, XSync is not supported
}
try
{
var xkbMajor = 1;
var xkbMinor = 0;
HasXkb = XkbLibraryVersion(ref xkbMajor, ref xkbMinor)
&& XkbQueryExtension(display, out _, out _, out _, ref xkbMajor, ref xkbMinor);
}
catch
{
// Ignore, XKB is not supported
}
}
}
}

209
src/Avalonia.X11/X11KeyTransform.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
@ -6,7 +5,198 @@ namespace Avalonia.X11
{
internal static class X11KeyTransform
{
private static readonly Dictionary<X11Key, Key> KeyDic = new Dictionary<X11Key, Key>
// X scan code to physical key map.
// https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/dom_code_data.inc
// This list has the same order as the PhysicalKey enum.
private static readonly Dictionary<byte, PhysicalKey> s_physicalKeyFromScanCode = new(162)
{
// Writing System Keys
{ 0x31, PhysicalKey.Backquote },
{ 0x33, PhysicalKey.Backslash },
{ 0x22, PhysicalKey.BracketLeft },
{ 0x23, PhysicalKey.BracketRight },
{ 0x3B, PhysicalKey.Comma },
{ 0x13, PhysicalKey.Digit0 },
{ 0x0A, PhysicalKey.Digit1 },
{ 0x0B, PhysicalKey.Digit2 },
{ 0x0C, PhysicalKey.Digit3 },
{ 0x0D, PhysicalKey.Digit4 },
{ 0x0E, PhysicalKey.Digit5 },
{ 0x0F, PhysicalKey.Digit6 },
{ 0x10, PhysicalKey.Digit7 },
{ 0x11, PhysicalKey.Digit8 },
{ 0x12, PhysicalKey.Digit9 },
{ 0x15, PhysicalKey.Equal },
{ 0x5E, PhysicalKey.IntlBackslash },
{ 0x61, PhysicalKey.IntlRo },
{ 0x84, PhysicalKey.IntlYen },
{ 0x26, PhysicalKey.A },
{ 0x38, PhysicalKey.B },
{ 0x36, PhysicalKey.C },
{ 0x28, PhysicalKey.D },
{ 0x1A, PhysicalKey.E },
{ 0x29, PhysicalKey.F },
{ 0x2A, PhysicalKey.G },
{ 0x2B, PhysicalKey.H },
{ 0x1F, PhysicalKey.I },
{ 0x2C, PhysicalKey.J },
{ 0x2D, PhysicalKey.K },
{ 0x2E, PhysicalKey.L },
{ 0x3A, PhysicalKey.M },
{ 0x39, PhysicalKey.N },
{ 0x20, PhysicalKey.O },
{ 0x21, PhysicalKey.P },
{ 0x18, PhysicalKey.Q },
{ 0x1B, PhysicalKey.R },
{ 0x27, PhysicalKey.S },
{ 0x1C, PhysicalKey.T },
{ 0x1E, PhysicalKey.U },
{ 0x37, PhysicalKey.V },
{ 0x19, PhysicalKey.W },
{ 0x35, PhysicalKey.X },
{ 0x1D, PhysicalKey.Y },
{ 0x34, PhysicalKey.Z },
{ 0x14, PhysicalKey.Minus },
{ 0x3C, PhysicalKey.Period },
{ 0x30, PhysicalKey.Quote },
{ 0x2F, PhysicalKey.Semicolon },
{ 0x3D, PhysicalKey.Slash },
// Functional Keys
{ 0x40, PhysicalKey.AltLeft },
{ 0x6C, PhysicalKey.AltRight },
{ 0x16, PhysicalKey.Backspace },
{ 0x42, PhysicalKey.CapsLock },
{ 0x87, PhysicalKey.ContextMenu },
{ 0x25, PhysicalKey.ControlLeft },
{ 0x69, PhysicalKey.ControlRight },
{ 0x24, PhysicalKey.Enter },
{ 0x85, PhysicalKey.MetaLeft },
{ 0x86, PhysicalKey.MetaRight },
{ 0x32, PhysicalKey.ShiftLeft },
{ 0x3E, PhysicalKey.ShiftRight },
{ 0x41, PhysicalKey.Space },
{ 0x17, PhysicalKey.Tab },
{ 0x64, PhysicalKey.Convert },
{ 0x65, PhysicalKey.KanaMode },
{ 0x82, PhysicalKey.Lang1 },
{ 0x83, PhysicalKey.Lang2 },
{ 0x62, PhysicalKey.Lang3 },
{ 0x63, PhysicalKey.Lang4 },
{ 0x5D, PhysicalKey.Lang5 },
{ 0x66, PhysicalKey.NonConvert },
// Control Pad Section
{ 0x77, PhysicalKey.Delete },
{ 0x73, PhysicalKey.End },
{ 0x92, PhysicalKey.Help },
{ 0x6E, PhysicalKey.Home },
{ 0x76, PhysicalKey.Insert },
{ 0x75, PhysicalKey.PageDown },
{ 0x70, PhysicalKey.PageUp },
// Arrow Pad Section
{ 0x74, PhysicalKey.ArrowDown },
{ 0x71, PhysicalKey.ArrowLeft },
{ 0x72, PhysicalKey.ArrowRight },
{ 0x6F, PhysicalKey.ArrowUp },
// Numpad Section
{ 0x4D, PhysicalKey.NumLock },
{ 0x5A, PhysicalKey.NumPad0 },
{ 0x57, PhysicalKey.NumPad1 },
{ 0x58, PhysicalKey.NumPad2 },
{ 0x59, PhysicalKey.NumPad3 },
{ 0x53, PhysicalKey.NumPad4 },
{ 0x54, PhysicalKey.NumPad5 },
{ 0x55, PhysicalKey.NumPad6 },
{ 0x4F, PhysicalKey.NumPad7 },
{ 0x50, PhysicalKey.NumPad8 },
{ 0x51, PhysicalKey.NumPad9 },
{ 0x56, PhysicalKey.NumPadAdd },
//{ , PhysicalKey.NumPadClear },
{ 0x81, PhysicalKey.NumPadComma },
{ 0x5B, PhysicalKey.NumPadDecimal },
{ 0x6A, PhysicalKey.NumPadDivide },
{ 0x68, PhysicalKey.NumPadEnter },
{ 0x7D, PhysicalKey.NumPadEqual },
{ 0x3F, PhysicalKey.NumPadMultiply },
{ 0xBB, PhysicalKey.NumPadParenLeft },
{ 0xBC, PhysicalKey.NumPadParenRight },
{ 0x52, PhysicalKey.NumPadSubtract },
// Function Section
{ 0x09, PhysicalKey.Escape },
{ 0x43, PhysicalKey.F1 },
{ 0x44, PhysicalKey.F2 },
{ 0x45, PhysicalKey.F3 },
{ 0x46, PhysicalKey.F4 },
{ 0x47, PhysicalKey.F5 },
{ 0x48, PhysicalKey.F6 },
{ 0x49, PhysicalKey.F7 },
{ 0x4A, PhysicalKey.F8 },
{ 0x4B, PhysicalKey.F9 },
{ 0x4C, PhysicalKey.F10 },
{ 0x5F, PhysicalKey.F11 },
{ 0x60, PhysicalKey.F12 },
{ 0xBF, PhysicalKey.F13 },
{ 0xC0, PhysicalKey.F14 },
{ 0xC1, PhysicalKey.F15 },
{ 0xC2, PhysicalKey.F16 },
{ 0xC3, PhysicalKey.F17 },
{ 0xC4, PhysicalKey.F18 },
{ 0xC5, PhysicalKey.F19 },
{ 0xC6, PhysicalKey.F20 },
{ 0xC7, PhysicalKey.F21 },
{ 0xC8, PhysicalKey.F22 },
{ 0xC9, PhysicalKey.F23 },
{ 0xCA, PhysicalKey.F24 },
{ 0x6B, PhysicalKey.PrintScreen },
{ 0x4E, PhysicalKey.ScrollLock },
{ 0x7F, PhysicalKey.Pause },
// Media Keys
{ 0xA6, PhysicalKey.BrowserBack },
{ 0xA4, PhysicalKey.BrowserFavorites },
{ 0xA7, PhysicalKey.BrowserForward },
{ 0xB4, PhysicalKey.BrowserHome },
{ 0xB5, PhysicalKey.BrowserRefresh },
{ 0xE1, PhysicalKey.BrowserSearch },
{ 0x88, PhysicalKey.BrowserStop },
{ 0xA9, PhysicalKey.Eject },
{ 0x98, PhysicalKey.LaunchApp1 },
{ 0x94, PhysicalKey.LaunchApp2 },
{ 0xA3, PhysicalKey.LaunchMail },
{ 0xAC, PhysicalKey.MediaPlayPause },
{ 0xB3, PhysicalKey.MediaSelect },
{ 0xAE, PhysicalKey.MediaStop },
{ 0xAB, PhysicalKey.MediaTrackNext },
{ 0xAD, PhysicalKey.MediaTrackPrevious },
{ 0x7C, PhysicalKey.Power },
{ 0x96, PhysicalKey.Sleep },
{ 0x7A, PhysicalKey.AudioVolumeDown },
{ 0x79, PhysicalKey.AudioVolumeMute },
{ 0x7B, PhysicalKey.AudioVolumeUp },
{ 0x97, PhysicalKey.WakeUp },
// Legacy Keys
{ 0x89, PhysicalKey.Again },
{ 0x8D, PhysicalKey.Copy },
{ 0x91, PhysicalKey.Cut },
{ 0x90, PhysicalKey.Find },
{ 0x8E, PhysicalKey.Open },
{ 0x8F, PhysicalKey.Paste },
//{ , PhysicalKey.Props },
{ 0x8C, PhysicalKey.Select },
{ 0x8B, PhysicalKey.Undo }
};
public static PhysicalKey PhysicalKeyFromScanCode(int scanCode)
=> scanCode is > 0 and <= 255 && s_physicalKeyFromScanCode.TryGetValue((byte)scanCode, out var result) ?
result :
PhysicalKey.None;
private static readonly Dictionary<X11Key, Key> s_keyFromX11Key = new(180)
{
{X11Key.Cancel, Key.Cancel},
{X11Key.BackSpace, Key.Back},
@ -198,26 +388,15 @@ namespace Avalonia.X11
{X11Key.grave, Key.OemTilde},
{X11Key.asciitilde, Key.OemTilde},
{X11Key.XK_1, Key.D1},
{X11Key.exclam, Key.D1},
{X11Key.XK_2, Key.D2},
{X11Key.at, Key.D2},
{X11Key.XK_3, Key.D3},
{X11Key.numbersign, Key.D3},
{X11Key.XK_4, Key.D4},
{X11Key.dollar, Key.D4},
{X11Key.XK_5, Key.D5},
{X11Key.percent, Key.D5},
{X11Key.XK_6, Key.D6},
{X11Key.asciicircum, Key.D6},
{X11Key.XK_7, Key.D7},
{X11Key.ampersand, Key.D7},
{X11Key.XK_8, Key.D8},
{X11Key.asterisk, Key.D8},
{X11Key.XK_9, Key.D9},
{X11Key.parenleft, Key.D9},
{X11Key.XK_0, Key.D0},
{X11Key.parenright, Key.D0},
//{ X11Key.?, Key.AbntC1 }
//{ X11Key.?, Key.AbntC2 }
//{ X11Key.?, Key.Oem8 }
@ -242,8 +421,8 @@ namespace Avalonia.X11
//{ X11Key.?, Key.DeadCharProcessed }
};
public static Key ConvertKey(X11Key key)
=> KeyDic.TryGetValue(key, out var result) ? result : Key.None;
public static Key KeyFromX11Key(X11Key key)
=> s_keyFromX11Key.TryGetValue(key, out var result) ? result : Key.None;
}
}

256
src/Avalonia.X11/X11Window.Ime.cs

@ -1,3 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@ -13,12 +15,11 @@ namespace Avalonia.X11
{
internal partial class X11Window
{
private ITextInputMethodImpl _ime;
private IX11InputMethodControl _imeControl;
private ITextInputMethodImpl? _ime;
private IX11InputMethodControl? _imeControl;
private bool _processingIme;
private Queue<(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)> _imeQueue =
new Queue<(RawKeyEventArgs args, XEvent xev, int keyVal, int keyCode)>();
private readonly Queue<(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)> _imeQueue = new();
private unsafe void CreateIC()
{
@ -79,94 +80,211 @@ namespace Avalonia.X11
(_ime, _imeControl) = ime.Value;
_imeControl.Commit += s =>
ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, s));
_imeControl.ForwardKey += ev =>
{
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, ev.Type, X11KeyTransform.ConvertKey((X11Key)ev.KeyVal),
(RawInputModifiers)ev.Modifiers));
};
InputRoot, s));
_imeControl.ForwardKey += OnImeControlForwardKey;
}
}
private void OnImeControlForwardKey(X11InputMethodForwardedKey forwardedKey)
{
var x11Key = (X11Key)forwardedKey.KeyVal;
var keySymbol = _x11.HasXkb ? GetKeySymbolXkb(x11Key) : GetKeySymbolXCore(x11Key);
ScheduleInput(new RawKeyEventArgs(
_keyboard,
(ulong)_x11.LastActivityTimestamp.ToInt64(),
InputRoot,
forwardedKey.Type,
X11KeyTransform.KeyFromX11Key(x11Key),
(RawInputModifiers)forwardedKey.Modifiers,
PhysicalKey.None,
keySymbol));
}
private void UpdateImePosition() => _imeControl?.UpdateWindowInfo(_position ?? default, RenderScaling);
private void HandleKeyEvent(ref XEvent ev)
{
var index = ev.KeyEvent.state.HasAllFlags(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasAllFlags(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
var convertedKey = X11KeyTransform.ConvertKey(key);
var physicalKey = X11KeyTransform.PhysicalKeyFromScanCode(ev.KeyEvent.keycode);
var (x11Key, key, symbol) = LookupKey(ref ev.KeyEvent, physicalKey);
var modifiers = TranslateModifiers(ev.KeyEvent.state);
var timestamp = (ulong)ev.KeyEvent.time.ToInt64();
RawKeyEventArgs args =
ev.type == XEventName.KeyPress
? new RawKeyEventArgsWithText(_keyboard, timestamp, _inputRoot, RawKeyEventType.KeyDown,
convertedKey, modifiers, TranslateEventToString(ref ev))
: new RawKeyEventArgs(_keyboard, timestamp, _inputRoot, RawKeyEventType.KeyUp, convertedKey,
modifiers);
ScheduleKeyInput(args, ref ev, (int)key, ev.KeyEvent.keycode);
var args = ev.type == XEventName.KeyPress ?
new RawKeyEventArgsWithText(
_keyboard,
timestamp,
InputRoot,
RawKeyEventType.KeyDown,
key,
modifiers,
physicalKey,
symbol,
TranslateEventToString(ref ev, symbol)) :
new RawKeyEventArgs(
_keyboard,
timestamp,
InputRoot,
RawKeyEventType.KeyUp,
key,
modifiers,
physicalKey,
symbol);
ScheduleKeyInput(args, ref ev, (int)x11Key, ev.KeyEvent.keycode);
}
private void TriggerClassicTextInputEvent(ref XEvent ev)
private (X11Key x11Key, Key key, string? symbol) LookupKey(ref XKeyEvent keyEvent, PhysicalKey physicalKey)
{
var text = TranslateEventToString(ref ev);
if (text != null)
ScheduleInput(
new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, text),
ref ev);
var (x11Key, key, symbol) = _x11.HasXkb ? LookUpKeyXkb(ref keyEvent) : LookupKeyXCore(ref keyEvent);
// Always use digits keys if possible, matching Windows/macOS.
if (physicalKey is >= PhysicalKey.Digit0 and <= PhysicalKey.Digit9)
key = physicalKey.ToQwertyKey();
// No key sym matched a key (e.g. non-latin keyboard without US fallback): fallback to a basic QWERTY map.
if (x11Key != 0 && key == Key.None)
key = physicalKey.ToQwertyKey();
return (x11Key, key, symbol);
}
private const int ImeBufferSize = 64 * 1024;
[ThreadStatic] private static IntPtr ImeBuffer;
private (X11Key x11Key, Key key, string? symbol) LookUpKeyXkb(ref XKeyEvent keyEvent)
{
// First lookup using the current keyboard layout group (contained in state).
var state = (int)keyEvent.state;
if (!XkbLookupKeySym(_x11.Display, keyEvent.keycode, state, out _, out var originalKeySym))
return (0, Key.None, null);
private unsafe string TranslateEventToString(ref XEvent ev)
var x11Key = (X11Key)originalKeySym;
var symbol = GetKeySymbolXkb(x11Key);
var key = X11KeyTransform.KeyFromX11Key(x11Key);
if (key != Key.None)
return (x11Key, key, symbol);
var originalGroup = XkbGetGroupForCoreState(state);
// We got a KeySym that doesn't match a key: try the other groups.
// This is needed to get a latin key for non-latin keyboard layouts.
for (var group = 0; group < 4; ++group)
{
if (group == originalGroup)
continue;
var newState = XkbSetGroupForCoreState(state, group);
if (XkbLookupKeySym(_x11.Display, keyEvent.keycode, newState, out _, out var groupKeySym))
{
key = X11KeyTransform.KeyFromX11Key((X11Key)groupKeySym);
if (key != Key.None)
return (x11Key, key, symbol);
}
}
return (x11Key, Key.None, null);
}
private unsafe string? GetKeySymbolXkb(X11Key x11Key)
{
if (ImeBuffer == IntPtr.Zero)
ImeBuffer = Marshal.AllocHGlobal(ImeBufferSize);
IntPtr istatus;
int len;
if(_xic != IntPtr.Zero)
len = Xutf8LookupString(_xic, ref ev, ImeBuffer.ToPointer(),
ImeBufferSize, out _, out istatus);
else
len = XLookupString(ref ev, ImeBuffer.ToPointer(), ImeBufferSize,
out _, out istatus);
var keySym = (nint)x11Key;
const int bufferSize = 4;
var buffer = stackalloc byte[bufferSize];
var length = XkbTranslateKeySym(_x11.Display, ref keySym, 0, buffer, bufferSize, out var extraSize);
if (length == 0)
return null;
if (length == 1 && !KeySymbolHelper.IsAllowedAsciiKeySymbol((char)buffer[0]))
return null;
if (len == 0)
if (extraSize <= 0)
return Encoding.UTF8.GetString(buffer, length);
// A symbol should normally fit in 4 bytes, so this path isn't expected to be taken.
var heapBuffer = new byte[length + extraSize];
fixed (byte* heapBufferPtr = heapBuffer)
length = XkbTranslateKeySym(_x11.Display, ref keySym, 0, heapBufferPtr, heapBuffer.Length, out _);
return Encoding.UTF8.GetString(heapBuffer, 0, length);
}
private static unsafe (X11Key x11Key, Key key, string? symbol) LookupKeyXCore(ref XKeyEvent keyEvent)
{
const int bufferSize = 4;
var buffer = stackalloc byte[bufferSize];
// We don't have Xkb enabled, which should be rare: use XLookupString which will map to the first keyboard
// while handling modifiers for us (XKeycodeToKeysym doesn't).
var length = XLookupString(ref keyEvent, buffer, bufferSize, out var keySym, IntPtr.Zero);
var x11Key = (X11Key)keySym;
var key = X11KeyTransform.KeyFromX11Key(x11Key);
var symbol = length switch
{
0 => null,
1 when !KeySymbolHelper.IsAllowedAsciiKeySymbol((char)buffer[0]) => null,
_ => Encoding.UTF8.GetString(buffer, length)
};
return (x11Key, key, symbol);
}
private static unsafe string? GetKeySymbolXCore(X11Key x11Key)
{
var bytes = XKeysymToString((nint)x11Key);
if (bytes is null)
return null;
var status = (XLookupStatus)istatus;
var length = 0;
for (var p = bytes; *p != 0; ++p)
++length;
string text;
if (status == XLookupStatus.XBufferOverflow)
if (length == 0)
return null;
return Encoding.UTF8.GetString(bytes, length);
}
private const int ImeBufferSize = 64 * 1024;
[ThreadStatic] private static IntPtr ImeBuffer;
private unsafe string? TranslateEventToString(ref XEvent ev, string? symbol)
{
string? text;
if (!_x11.HasXkb && _xic == IntPtr.Zero)
text = symbol; // We already got the symbol from XLookupString, no need to call it again.
else
text = Encoding.UTF8.GetString((byte*)ImeBuffer.ToPointer(), len);
{
if (ImeBuffer == IntPtr.Zero)
ImeBuffer = Marshal.AllocHGlobal(ImeBufferSize);
var imeBufferPtr = (byte*)ImeBuffer.ToPointer();
XLookupStatus status = 0;
var len = _xic == IntPtr.Zero ?
XLookupString(ref ev.KeyEvent, imeBufferPtr, ImeBufferSize, out _, IntPtr.Zero) :
Xutf8LookupString(_xic, ref ev.KeyEvent, imeBufferPtr, ImeBufferSize, out _, out status);
if (len == 0 || status == XLookupStatus.XBufferOverflow)
return null;
text = Encoding.UTF8.GetString(imeBufferPtr, len);
}
if (text == null)
if (text is null)
return null;
if (text.Length == 1)
{
if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
if (text[0] < ' ' || text[0] == 0x7f) // Control codes or DEL
return null;
}
return text;
}
private void ScheduleKeyInput(RawKeyEventArgs args, ref XEvent xev, int keyval, int keycode)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
@ -212,14 +330,22 @@ namespace Avalonia.X11
// This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event
private class RawKeyEventArgsWithText : RawKeyEventArgs
{
public RawKeyEventArgsWithText(IKeyboardDevice device, ulong timestamp, IInputRoot root,
RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) :
base(device, timestamp, root, type, key, modifiers)
public RawKeyEventArgsWithText(
IKeyboardDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
string? keySymbol,
string? text)
: base(device, timestamp, root, type, key, modifiers, physicalKey, keySymbol)
{
Text = text;
}
public string Text { get; }
public string? Text { get; }
}
}
}

3
src/Avalonia.X11/X11Window.cs

@ -838,7 +838,8 @@ namespace Avalonia.X11
}
public IInputRoot? InputRoot => _inputRoot;
public IInputRoot InputRoot
=> _inputRoot ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called");
public void SetInputRoot(IInputRoot inputRoot)
{

32
src/Avalonia.X11/XLib.cs

@ -465,19 +465,31 @@ namespace Avalonia.X11
}
[DllImport (libX11)]
public static extern unsafe int XLookupString(ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
public static extern int XLookupString(ref XKeyEvent xevent, byte* buffer, int num_bytes, out nint keysym, IntPtr composeStatus);
[DllImport (libX11)]
public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
public static extern int Xutf8LookupString(IntPtr xic, ref XKeyEvent xevent, byte* buffer, int num_bytes, out nint keysym, out XLookupStatus status);
[DllImport (libX11)]
public static extern unsafe int Xutf8LookupString(IntPtr xic, XEvent* xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
public static extern byte* XKeysymToString(nint keysym);
[DllImport (libX11)]
public static extern bool XkbLibraryVersion(ref int libMajor, ref int libMinor);
[DllImport (libX11)]
public static extern bool XkbQueryExtension(IntPtr display, out int *opcode, out int eventBase, out int *errorBase, ref int major, ref int minor);
[DllImport (libX11)]
public static extern bool XkbIgnoreExtension(bool ignore);
[DllImport (libX11)]
public static extern unsafe IntPtr XKeycodeToKeysym(IntPtr display, int keycode, int index);
public static extern bool XkbLookupKeySym(IntPtr display, int keycode, int modifiers, out XModifierMask consumedModifiers, out nint keysym);
[DllImport (libX11)]
public static extern int XkbTranslateKeySym(IntPtr display, ref nint keySym, int modifiers, byte* buffer, int bufferSize, out int extraSize);
[DllImport (libX11)]
public static extern unsafe IntPtr XSetLocaleModifiers(string modifiers);
public static extern IntPtr XSetLocaleModifiers(string modifiers);
[DllImport (libX11)]
public static extern IntPtr XOpenIM (IntPtr display, IntPtr rdb, IntPtr res_name, IntPtr res_class);
@ -731,5 +743,11 @@ namespace Avalonia.X11
plat.Windows[win] = handler;
return win;
}
public static int XkbGetGroupForCoreState(int state)
=> (state >> 13) & 0x3;
public static int XkbSetGroupForCoreState(int state, int newGroup)
=> (state & ~(0x3 << 13)) | ((newGroup & 0x3) << 13);
}
}

48
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -13,7 +13,6 @@ using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
[assembly: SupportedOSPlatform("browser")]
@ -131,32 +130,29 @@ namespace Avalonia.Browser
return false;
}
public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers)
public bool RawKeyboardEvent(RawKeyEventType type, string domCode, string domKey, RawInputModifiers modifiers)
{
if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
{
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
Input?.Invoke(args);
return args.Handled;
}
}
else if (Keycodes.KeyCodes.TryGetValue(key, out avkey))
{
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
Input?.Invoke(args);
return args.Handled;
}
}
return false;
if (_inputRoot is null)
return false;
var physicalKey = KeyInterop.PhysicalKeyFromDomCode(domCode);
var key = KeyInterop.KeyFromDomKey(domKey, physicalKey);
var keySymbol = KeyInterop.KeySymbolFromDomKey(domKey);
var args = new RawKeyEventArgs(
KeyboardDevice,
Timestamp,
_inputRoot,
type,
key,
modifiers,
physicalKey,
keySymbol
);
Input?.Invoke(args);
return args.Handled;
}
public bool RawTextEvent(string text)

435
src/Browser/Avalonia.Browser/KeyInterop.cs

@ -0,0 +1,435 @@
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.Browser
{
internal static class KeyInterop
{
// https://www.w3.org/TR/uievents-code/
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
// This list has the same order as the PhysicalKey enum.
private static readonly Dictionary<string, PhysicalKey> s_physicalKeyFromDomCode = new()
{
{ "Unidentified", PhysicalKey.None },
// Writing System Keys
{ "Backquote", PhysicalKey.Backquote },
{ "Backslash", PhysicalKey.Backslash },
{ "BracketLeft", PhysicalKey.BracketLeft },
{ "BracketRight", PhysicalKey.BracketRight },
{ "Comma", PhysicalKey.Comma },
{ "Digit0", PhysicalKey.Digit0 },
{ "Digit1", PhysicalKey.Digit1 },
{ "Digit2", PhysicalKey.Digit2 },
{ "Digit3", PhysicalKey.Digit3 },
{ "Digit4", PhysicalKey.Digit4 },
{ "Digit5", PhysicalKey.Digit5 },
{ "Digit6", PhysicalKey.Digit6 },
{ "Digit7", PhysicalKey.Digit7 },
{ "Digit8", PhysicalKey.Digit8 },
{ "Digit9", PhysicalKey.Digit9 },
{ "Equal", PhysicalKey.Equal },
{ "IntlBackslash", PhysicalKey.IntlBackslash },
{ "IntlRo", PhysicalKey.IntlRo },
{ "IntlYen", PhysicalKey.IntlYen },
{ "KeyA", PhysicalKey.A },
{ "KeyB", PhysicalKey.B },
{ "KeyC", PhysicalKey.C },
{ "KeyD", PhysicalKey.D },
{ "KeyE", PhysicalKey.E },
{ "KeyF", PhysicalKey.F },
{ "KeyG", PhysicalKey.G },
{ "KeyH", PhysicalKey.H },
{ "KeyI", PhysicalKey.I },
{ "KeyJ", PhysicalKey.J },
{ "KeyK", PhysicalKey.K },
{ "KeyL", PhysicalKey.L },
{ "KeyM", PhysicalKey.M },
{ "KeyN", PhysicalKey.N },
{ "KeyO", PhysicalKey.O },
{ "KeyP", PhysicalKey.P },
{ "KeyQ", PhysicalKey.Q },
{ "KeyR", PhysicalKey.R },
{ "KeyS", PhysicalKey.S },
{ "KeyT", PhysicalKey.T },
{ "KeyU", PhysicalKey.U },
{ "KeyV", PhysicalKey.V },
{ "KeyW", PhysicalKey.W },
{ "KeyX", PhysicalKey.X },
{ "KeyY", PhysicalKey.Y },
{ "KeyZ", PhysicalKey.Z },
{ "Minus", PhysicalKey.Minus },
{ "Period", PhysicalKey.Period },
{ "Quote", PhysicalKey.Quote },
{ "Semicolon", PhysicalKey.Semicolon },
{ "Slash", PhysicalKey.Slash },
// Functional Keys
{ "AltLeft", PhysicalKey.AltLeft },
{ "AltRight", PhysicalKey.AltRight },
{ "Backspace", PhysicalKey.Backspace },
{ "CapsLock", PhysicalKey.CapsLock },
{ "ContextMenu", PhysicalKey.ContextMenu },
{ "ControlLeft", PhysicalKey.ControlLeft },
{ "ControlRight", PhysicalKey.ControlRight },
{ "Enter", PhysicalKey.Enter },
{ "MetaLeft", PhysicalKey.MetaLeft },
{ "OSLeft", PhysicalKey.MetaLeft },
{ "MetaRight", PhysicalKey.MetaRight },
{ "OSRight", PhysicalKey.MetaRight },
{ "ShiftLeft", PhysicalKey.ShiftLeft },
{ "ShiftRight", PhysicalKey.ShiftRight },
{ "Space", PhysicalKey.Space },
{ "Tab", PhysicalKey.Tab },
{ "Convert", PhysicalKey.Convert },
{ "KanaMode", PhysicalKey.KanaMode },
{ "Lang1", PhysicalKey.Lang1 },
{ "Lang2", PhysicalKey.Lang2 },
{ "Lang3", PhysicalKey.Lang3 },
{ "Lang4", PhysicalKey.Lang4 },
{ "Lang5", PhysicalKey.Lang5 },
{ "NonConvert", PhysicalKey.NonConvert },
// Control Pad Section
{ "Delete", PhysicalKey.Delete },
{ "End", PhysicalKey.End },
{ "Help", PhysicalKey.Help },
{ "Home", PhysicalKey.Home },
{ "Insert", PhysicalKey.Insert },
{ "PageDown", PhysicalKey.PageDown },
{ "PageUp", PhysicalKey.PageUp },
// Arrow Pad Section
{ "ArrowDown", PhysicalKey.ArrowDown },
{ "ArrowLeft", PhysicalKey.ArrowLeft },
{ "ArrowRight", PhysicalKey.ArrowRight },
{ "ArrowUp", PhysicalKey.ArrowUp },
// Numpad Section
{ "NumLock", PhysicalKey.NumLock },
{ "Numpad0", PhysicalKey.NumPad0 },
{ "Numpad1", PhysicalKey.NumPad1 },
{ "Numpad2", PhysicalKey.NumPad2 },
{ "Numpad3", PhysicalKey.NumPad3 },
{ "Numpad4", PhysicalKey.NumPad4 },
{ "Numpad5", PhysicalKey.NumPad5 },
{ "Numpad6", PhysicalKey.NumPad6 },
{ "Numpad7", PhysicalKey.NumPad7 },
{ "Numpad8", PhysicalKey.NumPad8 },
{ "Numpad9", PhysicalKey.NumPad9 },
{ "NumpadAdd", PhysicalKey.NumPadAdd },
{ "NumpadClear", PhysicalKey.NumPadClear },
{ "NumpadComma", PhysicalKey.NumPadComma },
{ "NumpadDecimal", PhysicalKey.NumPadDecimal },
{ "NumpadDivide", PhysicalKey.NumPadDivide },
{ "NumpadEnter", PhysicalKey.NumPadEnter },
{ "NumpadEqual", PhysicalKey.NumPadEqual },
{ "NumpadMultiply", PhysicalKey.NumPadMultiply },
{ "NumpadParenLeft", PhysicalKey.NumPadParenLeft },
{ "NumpadParenRight", PhysicalKey.NumPadParenRight },
{ "NumpadSubtract", PhysicalKey.NumPadSubtract },
// Function Section
{ "Escape", PhysicalKey.Escape },
{ "F1", PhysicalKey.F1 },
{ "F2", PhysicalKey.F2 },
{ "F3", PhysicalKey.F3 },
{ "F4", PhysicalKey.F4 },
{ "F5", PhysicalKey.F5 },
{ "F6", PhysicalKey.F6 },
{ "F7", PhysicalKey.F7 },
{ "F8", PhysicalKey.F8 },
{ "F9", PhysicalKey.F9 },
{ "F10", PhysicalKey.F10 },
{ "F11", PhysicalKey.F11 },
{ "F12", PhysicalKey.F12 },
{ "F13", PhysicalKey.F13 },
{ "F14", PhysicalKey.F14 },
{ "F15", PhysicalKey.F15 },
{ "F16", PhysicalKey.F16 },
{ "F17", PhysicalKey.F17 },
{ "F18", PhysicalKey.F18 },
{ "F19", PhysicalKey.F19 },
{ "F20", PhysicalKey.F20 },
{ "F21", PhysicalKey.F21 },
{ "F22", PhysicalKey.F22 },
{ "F23", PhysicalKey.F23 },
{ "F24", PhysicalKey.F24 },
{ "PrintScreen", PhysicalKey.PrintScreen },
{ "ScrollLock", PhysicalKey.ScrollLock },
{ "Pause", PhysicalKey.Pause },
// Media Keys
{ "BrowserBack", PhysicalKey.BrowserBack },
{ "BrowserFavorites", PhysicalKey.BrowserFavorites },
{ "BrowserForward", PhysicalKey.BrowserForward },
{ "BrowserHome", PhysicalKey.BrowserHome },
{ "BrowserRefresh", PhysicalKey.BrowserRefresh },
{ "BrowserSearch", PhysicalKey.BrowserSearch },
{ "BrowserStop", PhysicalKey.BrowserStop },
{ "Abort", PhysicalKey.BrowserStop },
{ "Eject", PhysicalKey.Eject },
{ "LaunchApp1", PhysicalKey.LaunchApp1 },
{ "LaunchApp2", PhysicalKey.LaunchApp2 },
{ "LaunchMail", PhysicalKey.LaunchMail },
{ "MediaPlayPause", PhysicalKey.MediaPlayPause },
{ "MediaSelect", PhysicalKey.MediaSelect },
{ "MediaStop", PhysicalKey.MediaStop },
{ "MediaTrackNext", PhysicalKey.MediaTrackNext },
{ "MediaTrackPrevious", PhysicalKey.MediaTrackPrevious },
{ "Power", PhysicalKey.Power },
{ "Sleep", PhysicalKey.Sleep },
{ "AudioVolumeDown", PhysicalKey.AudioVolumeDown },
{ "VolumeDown", PhysicalKey.AudioVolumeDown },
{ "AudioVolumeMute", PhysicalKey.AudioVolumeMute },
{ "VolumeMute", PhysicalKey.AudioVolumeMute },
{ "AudioVolumeUp", PhysicalKey.AudioVolumeUp },
{ "VolumeUp", PhysicalKey.AudioVolumeUp },
{ "WakeUp", PhysicalKey.WakeUp },
// Legacy Keys
{ "Copy", PhysicalKey.Copy },
{ "Cut", PhysicalKey.Cut },
{ "Find", PhysicalKey.Find },
{ "Open", PhysicalKey.Open },
{ "Paste", PhysicalKey.Paste },
{ "Props", PhysicalKey.Props },
{ "Select", PhysicalKey.Select },
{ "Undo", PhysicalKey.Undo }
};
public static PhysicalKey PhysicalKeyFromDomCode(string? domCode)
=> !string.IsNullOrEmpty(domCode) && s_physicalKeyFromDomCode.TryGetValue(domCode, out var physicalKey) ?
physicalKey :
PhysicalKey.None;
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
private static readonly Dictionary<string, Key> s_keyFromDomKey = new()
{
// Alphabetic keys
{ "A", Key.A },
{ "B", Key.B },
{ "C", Key.C },
{ "D", Key.D },
{ "E", Key.E },
{ "F", Key.F },
{ "G", Key.G },
{ "H", Key.H },
{ "I", Key.I },
{ "J", Key.J },
{ "K", Key.K },
{ "L", Key.L },
{ "M", Key.M },
{ "N", Key.N },
{ "O", Key.O },
{ "P", Key.P },
{ "Q", Key.Q },
{ "R", Key.R },
{ "S", Key.S },
{ "T", Key.T },
{ "U", Key.U },
{ "V", Key.V },
{ "W", Key.W },
{ "X", Key.X },
{ "Y", Key.Y },
{ "Z", Key.Z },
{ "a", Key.A },
{ "b", Key.B },
{ "c", Key.C },
{ "d", Key.D },
{ "e", Key.E },
{ "f", Key.F },
{ "g", Key.G },
{ "h", Key.H },
{ "i", Key.I },
{ "j", Key.J },
{ "k", Key.K },
{ "l", Key.L },
{ "m", Key.M },
{ "n", Key.N },
{ "o", Key.O },
{ "p", Key.P },
{ "q", Key.Q },
{ "r", Key.R },
{ "s", Key.S },
{ "t", Key.T },
{ "u", Key.U },
{ "v", Key.V },
{ "w", Key.W },
{ "x", Key.X },
{ "y", Key.Y },
{ "z", Key.Z },
// Modifier keys (left/right keys are handled separately)
{ "AltGr", Key.RightAlt },
{ "CapsLock", Key.CapsLock },
{ "NumLock", Key.NumLock },
{ "ScrollLock", Key.Scroll },
// Whitespace keys
{ "Enter", Key.Enter },
{ "Tab", Key.Tab },
{ " ", Key.Space },
// Navigation keys
{ "ArrowDown", Key.Down },
{ "ArrowLeft", Key.Left },
{ "ArrowRight", Key.Right },
{ "ArrowUp", Key.Up },
{ "End", Key.End },
{ "Home", Key.Home },
{ "PageDown", Key.PageDown },
{ "PageUp", Key.PageUp },
// Editing keys
{ "Backspace", Key.Back },
{ "Clear", Key.Clear },
{ "CrSel", Key.CrSel },
{ "Delete", Key.Delete },
{ "EraseEof", Key.EraseEof },
{ "ExSel", Key.ExSel },
{ "Insert", Key.Insert },
// UI keys
{ "Accept", Key.ImeAccept },
{ "Attn", Key.OemAttn },
{ "Cancel", Key.Cancel },
{ "ContextMenu", Key.Apps },
{ "Escape", Key.Escape },
{ "Execute", Key.Execute },
{ "Finish", Key.OemFinish },
{ "Help", Key.Help },
{ "Pause", Key.Pause },
{ "Play", Key.Play },
{ "Select", Key.Select },
{ "ZoomIn", Key.Zoom },
// Device keys
{ "PrintScreen", Key.PrintScreen },
// IME keys
{ "Convert", Key.ImeConvert },
{ "FinalMode", Key.FinalMode },
{ "ModeChange", Key.ImeModeChange },
{ "NonConvert", Key.ImeNonConvert },
{ "Process", Key.ImeProcessed },
{ "HangulMode", Key.HangulMode },
{ "HanjaMode", Key.HanjaMode },
{ "JunjaMode", Key.JunjaMode },
{ "Hankaku", Key.OemAuto },
{ "Hiragana", Key.DbeHiragana },
{ "KanaMode", Key.KanaMode },
{ "KanjiMode", Key.KanjiMode },
{ "Katakana", Key.DbeKatakana },
{ "Romaji", Key.OemBackTab },
{ "Zenkaku", Key.OemEnlw },
// Function keys
{ "F1", Key.F1 },
{ "F2", Key.F2 },
{ "F3", Key.F3 },
{ "F4", Key.F4 },
{ "F5", Key.F5 },
{ "F6", Key.F6 },
{ "F7", Key.F7 },
{ "F8", Key.F8 },
{ "F9", Key.F9 },
{ "F10", Key.F10 },
{ "F11", Key.F11 },
{ "F12", Key.F12 },
{ "F13", Key.F13 },
{ "F14", Key.F14 },
{ "F15", Key.F15 },
{ "F16", Key.F16 },
{ "F17", Key.F17 },
{ "F18", Key.F18 },
{ "F19", Key.F19 },
{ "F20", Key.F20 },
// Multimedia keys
{ "MediaPlayPause", Key.MediaPlayPause },
{ "MediaStop", Key.MediaStop },
{ "MediaTrackNext", Key.MediaNextTrack },
{ "MediaTrackPrevious", Key.MediaPreviousTrack },
// Audio control keys
{ "AudioVolumeDown", Key.VolumeDown },
{ "AudioVolumeMute", Key.VolumeMute },
{ "AudioVolumeUp", Key.VolumeUp },
// Application selector keys
{ "LaunchCalculator", Key.LaunchApplication2 },
{ "LaunchMail", Key.LaunchMail },
{ "LaunchMyComputer", Key.LaunchApplication1 },
{ "LaunchApplication1", Key.LaunchApplication1 },
{ "LaunchApplication2", Key.LaunchApplication2 },
// Browser control keys
{ "BrowserBack", Key.BrowserBack },
{ "BrowserFavorites", Key.BrowserFavorites },
{ "BrowserForward", Key.BrowserForward },
{ "BrowserHome", Key.BrowserHome },
{ "BrowserRefresh", Key.BrowserRefresh },
{ "BrowserSearch", Key.BrowserSearch },
{ "BrowserStop", Key.BrowserStop },
// Numeric keypad keys
{ "Decimal", Key.Decimal },
{ "Multiply", Key.Multiply },
{ "Add", Key.Add },
{ "Divide", Key.Divide },
{ "Subtract", Key.Subtract },
{ "Separator", Key.Separator },
};
public static Key KeyFromDomKey(string? domKey, PhysicalKey physicalKey)
{
if (string.IsNullOrEmpty(domKey))
return Key.None;
if (s_keyFromDomKey.TryGetValue(domKey, out var key))
return key;
key = domKey switch
{
"Alt" => physicalKey == PhysicalKey.AltRight ? Key.RightAlt : Key.LeftAlt,
"Control" => physicalKey == PhysicalKey.ControlRight ? Key.RightCtrl : Key.LeftCtrl,
"Shift" => physicalKey == PhysicalKey.ShiftRight ? Key.RightShift : Key.LeftShift,
"Meta" => physicalKey == PhysicalKey.MetaRight ? Key.RWin : Key.LWin,
"0" => physicalKey == PhysicalKey.NumPad0 ? Key.NumPad0 : Key.D0,
"1" => physicalKey == PhysicalKey.NumPad1 ? Key.NumPad1 : Key.D1,
"2" => physicalKey == PhysicalKey.NumPad2 ? Key.NumPad2 : Key.D2,
"3" => physicalKey == PhysicalKey.NumPad3 ? Key.NumPad3 : Key.D3,
"4" => physicalKey == PhysicalKey.NumPad4 ? Key.NumPad4 : Key.D4,
"5" => physicalKey == PhysicalKey.NumPad5 ? Key.NumPad5 : Key.D5,
"6" => physicalKey == PhysicalKey.NumPad6 ? Key.NumPad6 : Key.D6,
"7" => physicalKey == PhysicalKey.NumPad7 ? Key.NumPad7 : Key.D7,
"8" => physicalKey == PhysicalKey.NumPad8 ? Key.NumPad8 : Key.D8,
"9" => physicalKey == PhysicalKey.NumPad9 ? Key.NumPad9 : Key.D9,
"+" => physicalKey == PhysicalKey.NumPadAdd ? Key.Add : Key.OemPlus,
"-" => physicalKey == PhysicalKey.NumPadSubtract ? Key.Subtract : Key.OemMinus,
"*" => physicalKey == PhysicalKey.NumPadMultiply ? Key.Multiply : Key.None,
"/" => physicalKey == PhysicalKey.NumPadDivide ? Key.Divide : Key.None,
_ => Key.None
};
if (key != Key.None)
return key;
return physicalKey.ToQwertyKey();
}
public static string? KeySymbolFromDomKey(string? domKey)
{
if (string.IsNullOrEmpty(domKey))
return null;
return domKey.Length switch
{
1 => domKey,
2 when char.IsSurrogatePair(domKey[0], domKey[1]) => domKey,
_ => null
};
}
}
}

129
src/Browser/Avalonia.Browser/Keycodes.cs

@ -1,129 +0,0 @@
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.Browser
{
internal static class Keycodes
{
public static Dictionary<string, Key> KeyCodes = new()
{
{ "Escape", Key.Escape },
{ "Digit1", Key.D1 },
{ "Digit2", Key.D2 },
{ "Digit3", Key.D3 },
{ "Digit4", Key.D4 },
{ "Digit5", Key.D5 },
{ "Digit6", Key.D6 },
{ "Digit7", Key.D7 },
{ "Digit8", Key.D8 },
{ "Digit9", Key.D9 },
{ "Digit0", Key.D0 },
{ "Minus", Key.OemMinus },
//{ "Equal" , Key. },
{ "Backspace", Key.Back },
{ "Tab", Key.Tab },
{ "KeyQ", Key.Q },
{ "KeyW", Key.W },
{ "KeyE", Key.E },
{ "KeyR", Key.R },
{ "KeyT", Key.T },
{ "KeyY", Key.Y },
{ "KeyU", Key.U },
{ "KeyI", Key.I },
{ "KeyO", Key.O },
{ "KeyP", Key.P },
{ "BracketLeft", Key.OemOpenBrackets },
{ "BracketRight", Key.OemCloseBrackets },
{ "Enter", Key.Enter },
{ "ControlLeft", Key.LeftCtrl },
{ "KeyA", Key.A },
{ "KeyS", Key.S },
{ "KeyD", Key.D },
{ "KeyF", Key.F },
{ "KeyG", Key.G },
{ "KeyH", Key.H },
{ "KeyJ", Key.J },
{ "KeyK", Key.K },
{ "KeyL", Key.L },
{ "Semicolon", Key.OemSemicolon },
{ "Quote", Key.OemQuotes },
//{ "Backquote" , Key. },
{ "ShiftLeft", Key.LeftShift },
{ "Backslash", Key.OemBackslash },
{ "KeyZ", Key.Z },
{ "KeyX", Key.X },
{ "KeyC", Key.C },
{ "KeyV", Key.V },
{ "KeyB", Key.B },
{ "KeyN", Key.N },
{ "KeyM", Key.M },
{ "Comma", Key.OemComma },
{ "Period", Key.OemPeriod },
//{ "Slash" , Key. },
{ "ShiftRight", Key.RightShift },
{ "NumpadMultiply", Key.Multiply },
{ "AltLeft", Key.LeftAlt },
{ "Space", Key.Space },
{ "CapsLock", Key.CapsLock },
{ "F1", Key.F1 },
{ "F2", Key.F2 },
{ "F3", Key.F3 },
{ "F4", Key.F4 },
{ "F5", Key.F5 },
{ "F6", Key.F6 },
{ "F7", Key.F7 },
{ "F8", Key.F8 },
{ "F9", Key.F9 },
{ "F10", Key.F10 },
{ "NumLock", Key.NumLock },
{ "ScrollLock", Key.Scroll },
{ "Numpad7", Key.NumPad7 },
{ "Numpad8", Key.NumPad8 },
{ "Numpad9", Key.NumPad9 },
{ "NumpadSubtract", Key.Subtract },
{ "Numpad4", Key.NumPad4 },
{ "Numpad5", Key.NumPad5 },
{ "Numpad6", Key.NumPad6 },
{ "NumpadAdd", Key.Add },
{ "Numpad1", Key.NumPad1 },
{ "Numpad2", Key.NumPad2 },
{ "Numpad3", Key.NumPad3 },
{ "Numpad0", Key.NumPad0 },
{ "NumpadDecimal", Key.Decimal },
{ "Unidentified", Key.NoName },
//{ "IntlBackslash" , Key.bac },
{ "F11", Key.F11 },
{ "F12", Key.F12 },
//{ "IntlRo" , Key.Ro },
//{ "Unidentified" , Key. },
{ "Convert", Key.ImeConvert },
{ "KanaMode", Key.KanaMode },
{ "NonConvert", Key.ImeNonConvert },
//{ "Unidentified" , Key. },
{ "NumpadEnter", Key.Enter },
{ "ControlRight", Key.RightCtrl },
{ "NumpadDivide", Key.Divide },
{ "PrintScreen", Key.PrintScreen },
{ "AltRight", Key.RightAlt },
//{ "Unidentified" , Key. },
{ "Home", Key.Home },
{ "ArrowUp", Key.Up },
{ "PageUp", Key.PageUp },
{ "ArrowLeft", Key.Left },
{ "ArrowRight", Key.Right },
{ "End", Key.End },
{ "ArrowDown", Key.Down },
{ "PageDown", Key.PageDown },
{ "Insert", Key.Insert },
{ "Delete", Key.Delete },
//{ "Unidentified" , Key. },
{ "AudioVolumeMute", Key.VolumeMute },
{ "AudioVolumeDown", Key.VolumeDown },
{ "AudioVolumeUp", Key.VolumeUp },
//{ "NumpadEqual" , Key. },
{ "Pause", Key.Pause },
{ "NumpadComma", Key.OemComma }
};
}
}

408
src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -65,21 +65,21 @@ namespace Avalonia.Headless.Vnc
if (isModifierKey)
return;
Key? key = TranslateKey(args.Keysym);
if (key == null)
var (key, keySymbol) = TranslateKey(args.Keysym);
if (key == Key.None)
return;
//we only care about text input on key up if not using Ctrl or Alt
string? inputText = args.Pressed || _keyState.HasFlag(RawInputModifiers.Control) || _keyState.HasFlag(RawInputModifiers.Alt)
? null
: KeyToText(args.Keysym);
? null
: keySymbol;
Dispatcher.UIThread.Post(() =>
{
if (args.Pressed)
Window?.KeyPress(key.Value, _keyState);
Window?.KeyPress(key, _keyState, PhysicalKey.None, keySymbol);
else
Window?.KeyRelease(key.Value, _keyState);
Window?.KeyRelease(key, _keyState, PhysicalKey.None, keySymbol);
if (inputText != null)
Window?.KeyTextInput(inputText);
@ -108,217 +108,195 @@ namespace Avalonia.Headless.Vnc
return true;
}
private static string? KeyToText(KeySym key)
{
int keyCode = (int)key;
if (key >= KeySym.Space && key <= KeySym.AsciiTilde)
return new string((char)key, 1);
//handle as normal text chars 0-9
if (key >= KeySym.NumPad0 && key <= KeySym.NumPad9)
return new string((char)(key - 65408), 1);
switch (key)
{
case KeySym.NumPadAdd: return "+";
case KeySym.NumPadSubtract: return "-";
case KeySym.NumPadMultiply: return "*";
case KeySym.NumPadDivide: return "/";
case KeySym.NumPadSeparator: return NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
}
return null;
}
private static Key? TranslateKey(KeySym key) =>
private static (Key key, string? symbol) TranslateKey(KeySym key) =>
key switch
{
KeySym.Backspace => Key.Back,
KeySym.Tab => Key.Tab,
KeySym.LineFeed => Key.LineFeed,
KeySym.Clear => Key.Clear,
KeySym.Return => Key.Return,
KeySym.Pause => Key.Pause,
KeySym.Escape => Key.Escape,
KeySym.Delete => Key.Delete,
KeySym.Home => Key.Home,
KeySym.Left => Key.Left,
KeySym.Up => Key.Up,
KeySym.Right => Key.Right,
KeySym.Down => Key.Down,
KeySym.PageUp => Key.PageUp,
KeySym.PageDown => Key.PageDown,
KeySym.End => Key.End,
KeySym.Begin => Key.Home,
KeySym.Select => Key.Select,
KeySym.Print => Key.Print,
KeySym.Execute => Key.Execute,
KeySym.Insert => Key.Insert,
KeySym.Cancel => Key.Cancel,
KeySym.Help => Key.Help,
KeySym.Break => Key.Pause,
KeySym.Num_Lock => Key.NumLock,
KeySym.NumPadSpace => Key.Space,
KeySym.NumPadTab => Key.Tab,
KeySym.NumPadEnter => Key.Enter,
KeySym.NumPadF1 => Key.F1,
KeySym.NumPadF2 => Key.F2,
KeySym.NumPadF3 => Key.F3,
KeySym.NumPadF4 => Key.F4,
KeySym.NumPadHome => Key.Home,
KeySym.NumPadLeft => Key.Left,
KeySym.NumPadUp => Key.Up,
KeySym.NumPadRight => Key.Right,
KeySym.NumPadDown => Key.Down,
KeySym.NumPadPageUp => Key.PageUp,
KeySym.NumPadPageDown => Key.PageDown,
KeySym.NumPadEnd => Key.End,
KeySym.NumPadBegin => Key.Home,
KeySym.NumPadInsert => Key.Insert,
KeySym.NumPadDelete => Key.Delete,
KeySym.NumPadEqual => Key.Return,
KeySym.NumPadMultiply => Key.Multiply,
KeySym.NumPadAdd => Key.Add,
KeySym.NumPadSeparator => Key.Separator,
KeySym.NumPadSubtract => Key.Subtract,
KeySym.NumPadDecimal => Key.Decimal,
KeySym.NumPadDivide => Key.Divide,
KeySym.NumPad0 => Key.NumPad0,
KeySym.NumPad1 => Key.NumPad1,
KeySym.NumPad2 => Key.NumPad2,
KeySym.NumPad3 => Key.NumPad3,
KeySym.NumPad4 => Key.NumPad4,
KeySym.NumPad5 => Key.NumPad5,
KeySym.NumPad6 => Key.NumPad6,
KeySym.NumPad7 => Key.NumPad7,
KeySym.NumPad8 => Key.NumPad8,
KeySym.NumPad9 => Key.NumPad9,
KeySym.F1 => Key.F1,
KeySym.F2 => Key.F2,
KeySym.F3 => Key.F3,
KeySym.F4 => Key.F4,
KeySym.F5 => Key.F5,
KeySym.F6 => Key.F6,
KeySym.F7 => Key.F7,
KeySym.F8 => Key.F8,
KeySym.F9 => Key.F9,
KeySym.F10 => Key.F10,
KeySym.F11 => Key.F11,
KeySym.F12 => Key.F12,
KeySym.F13 => Key.F13,
KeySym.F14 => Key.F14,
KeySym.F15 => Key.F15,
KeySym.F16 => Key.F16,
KeySym.F17 => Key.F17,
KeySym.F18 => Key.F18,
KeySym.F19 => Key.F19,
KeySym.F20 => Key.F20,
KeySym.F21 => Key.F21,
KeySym.F22 => Key.F22,
KeySym.F23 => Key.F23,
KeySym.F24 => Key.F24,
KeySym.ShiftLeft => Key.LeftShift,
KeySym.ShiftRight => Key.RightShift,
KeySym.ControlLeft => Key.LeftCtrl,
KeySym.ControlRight => Key.RightCtrl,
KeySym.CapsLock => Key.CapsLock,
KeySym.AltLeft => Key.LeftAlt,
KeySym.AltRight => Key.RightAlt,
KeySym.Space => Key.Space,
KeySym.Exclamation => Key.D1,
KeySym.Quote => Key.D2,
KeySym.NumberSign => Key.D3,
KeySym.Dollar => Key.D4,
KeySym.Percent => Key.D5,
KeySym.Ampersand => Key.D7,
KeySym.Apostrophe => Key.Oem3,
KeySym.ParenthesisLeft => Key.D9,
KeySym.ParenthesisRight => Key.D0,
KeySym.Asterisk => Key.D8,
KeySym.Plus => Key.OemPlus,
KeySym.Comma => Key.OemComma,
KeySym.Minus => Key.OemMinus,
KeySym.Period => Key.OemPeriod,
KeySym.Slash => Key.OemQuestion,
KeySym.D0 => Key.D0,
KeySym.D1 => Key.D1,
KeySym.D2 => Key.D2,
KeySym.D3 => Key.D3,
KeySym.D4 => Key.D4,
KeySym.D5 => Key.D5,
KeySym.D6 => Key.D6,
KeySym.D7 => Key.D7,
KeySym.D8 => Key.D8,
KeySym.D9 => Key.D9,
KeySym.Colon => Key.OemSemicolon,
KeySym.Semicolon => Key.OemSemicolon,
KeySym.Less => Key.OemComma,
KeySym.Equal => Key.OemPlus,
KeySym.Greater => Key.OemPeriod,
KeySym.Question => Key.OemQuestion,
KeySym.At => Key.Oem3,
KeySym.A => Key.A,
KeySym.B => Key.B,
KeySym.C => Key.C,
KeySym.D => Key.D,
KeySym.E => Key.E,
KeySym.F => Key.F,
KeySym.G => Key.G,
KeySym.H => Key.H,
KeySym.I => Key.I,
KeySym.J => Key.J,
KeySym.K => Key.K,
KeySym.L => Key.L,
KeySym.M => Key.M,
KeySym.N => Key.N,
KeySym.O => Key.O,
KeySym.P => Key.P,
KeySym.Q => Key.Q,
KeySym.R => Key.R,
KeySym.S => Key.S,
KeySym.T => Key.T,
KeySym.U => Key.U,
KeySym.V => Key.V,
KeySym.W => Key.W,
KeySym.X => Key.X,
KeySym.Y => Key.Y,
KeySym.Z => Key.Z,
KeySym.BracketLeft => Key.OemOpenBrackets,
KeySym.Backslash => Key.OemPipe,
KeySym.Bracketright => Key.OemCloseBrackets,
KeySym.Underscore => Key.OemMinus,
KeySym.Grave => Key.Oem8,
KeySym.a => Key.A,
KeySym.b => Key.B,
KeySym.c => Key.C,
KeySym.d => Key.D,
KeySym.e => Key.E,
KeySym.f => Key.F,
KeySym.g => Key.G,
KeySym.h => Key.H,
KeySym.i => Key.I,
KeySym.j => Key.J,
KeySym.k => Key.K,
KeySym.l => Key.L,
KeySym.m => Key.M,
KeySym.n => Key.M,
KeySym.o => Key.O,
KeySym.p => Key.P,
KeySym.q => Key.Q,
KeySym.r => Key.R,
KeySym.s => Key.S,
KeySym.t => Key.T,
KeySym.u => Key.U,
KeySym.v => Key.V,
KeySym.w => Key.W,
KeySym.x => Key.X,
KeySym.y => Key.Y,
KeySym.z => Key.Z,
KeySym.BraceLeft => Key.OemOpenBrackets,
KeySym.Bar => Key.OemPipe,
KeySym.BraceRight => Key.OemCloseBrackets,
KeySym.AsciiTilde => Key.OemTilde,
_ => null
KeySym.Backspace => (Key.Back, "\b"),
KeySym.Tab => (Key.Tab, "\t"),
KeySym.LineFeed => (Key.LineFeed, null),
KeySym.Clear => (Key.Clear, null),
KeySym.Return => (Key.Return, "\r"),
KeySym.Pause => (Key.Pause, null),
KeySym.Escape => (Key.Escape, "\u001B"),
KeySym.Delete => (Key.Delete, null),
KeySym.Home => (Key.Home, null),
KeySym.Left => (Key.Left, null),
KeySym.Up => (Key.Up, null),
KeySym.Right => (Key.Right, null),
KeySym.Down => (Key.Down, null),
KeySym.PageUp => (Key.PageUp, null),
KeySym.PageDown => (Key.PageDown, null),
KeySym.End => (Key.End, null),
KeySym.Begin => (Key.Home, null),
KeySym.Select => (Key.Select, null),
KeySym.Print => (Key.Print, null),
KeySym.Execute => (Key.Execute, null),
KeySym.Insert => (Key.Insert, null),
KeySym.Cancel => (Key.Cancel, null),
KeySym.Help => (Key.Help, null),
KeySym.Break => (Key.Pause, null),
KeySym.Num_Lock => (Key.NumLock, null),
KeySym.NumPadSpace => (Key.Space, null),
KeySym.NumPadTab => (Key.Tab, null),
KeySym.NumPadEnter => (Key.Enter, null),
KeySym.NumPadF1 => (Key.F1, null),
KeySym.NumPadF2 => (Key.F2, null),
KeySym.NumPadF3 => (Key.F3, null),
KeySym.NumPadF4 => (Key.F4, null),
KeySym.NumPadHome => (Key.Home, null),
KeySym.NumPadLeft => (Key.Left, null),
KeySym.NumPadUp => (Key.Up, null),
KeySym.NumPadRight => (Key.Right, null),
KeySym.NumPadDown => (Key.Down, null),
KeySym.NumPadPageUp => (Key.PageUp, null),
KeySym.NumPadPageDown => (Key.PageDown, null),
KeySym.NumPadEnd => (Key.End, null),
KeySym.NumPadBegin => (Key.Home, null),
KeySym.NumPadInsert => (Key.Insert, null),
KeySym.NumPadDelete => (Key.Delete, null),
KeySym.NumPadEqual => (Key.Enter, "="),
KeySym.NumPadMultiply => (Key.Multiply, "*"),
KeySym.NumPadAdd => (Key.Add, "+"),
KeySym.NumPadSeparator => (Key.Separator, NumberFormatInfo.CurrentInfo.NumberGroupSeparator),
KeySym.NumPadSubtract => (Key.Subtract, "-"),
KeySym.NumPadDecimal => (Key.Decimal, NumberFormatInfo.CurrentInfo.NumberDecimalSeparator),
KeySym.NumPadDivide => (Key.Divide, "/"),
KeySym.NumPad0 => (Key.NumPad0, "0"),
KeySym.NumPad1 => (Key.NumPad1, "1"),
KeySym.NumPad2 => (Key.NumPad2, "2"),
KeySym.NumPad3 => (Key.NumPad3, "3"),
KeySym.NumPad4 => (Key.NumPad4, "4"),
KeySym.NumPad5 => (Key.NumPad5, "5"),
KeySym.NumPad6 => (Key.NumPad6, "6"),
KeySym.NumPad7 => (Key.NumPad7, "7"),
KeySym.NumPad8 => (Key.NumPad8, "8"),
KeySym.NumPad9 => (Key.NumPad9, "9"),
KeySym.F1 => (Key.F1, null),
KeySym.F2 => (Key.F2, null),
KeySym.F3 => (Key.F3, null),
KeySym.F4 => (Key.F4, null),
KeySym.F5 => (Key.F5, null),
KeySym.F6 => (Key.F6, null),
KeySym.F7 => (Key.F7, null),
KeySym.F8 => (Key.F8, null),
KeySym.F9 => (Key.F9, null),
KeySym.F10 => (Key.F10, null),
KeySym.F11 => (Key.F11, null),
KeySym.F12 => (Key.F12, null),
KeySym.F13 => (Key.F13, null),
KeySym.F14 => (Key.F14, null),
KeySym.F15 => (Key.F15, null),
KeySym.F16 => (Key.F16, null),
KeySym.F17 => (Key.F17, null),
KeySym.F18 => (Key.F18, null),
KeySym.F19 => (Key.F19, null),
KeySym.F20 => (Key.F20, null),
KeySym.F21 => (Key.F21, null),
KeySym.F22 => (Key.F22, null),
KeySym.F23 => (Key.F23, null),
KeySym.F24 => (Key.F24, null),
KeySym.ShiftLeft => (Key.LeftShift, null),
KeySym.ShiftRight => (Key.RightShift, null),
KeySym.ControlLeft => (Key.LeftCtrl, null),
KeySym.ControlRight => (Key.RightCtrl, null),
KeySym.CapsLock => (Key.CapsLock, null),
KeySym.AltLeft => (Key.LeftAlt, null),
KeySym.AltRight => (Key.RightAlt, null),
KeySym.Space => (Key.Space, " "),
KeySym.Exclamation => (Key.D1, "!"),
KeySym.Quote => (Key.D2, "\""),
KeySym.NumberSign => (Key.D3, "#"),
KeySym.Dollar => (Key.D4, "$"),
KeySym.Percent => (Key.D5, "%"),
KeySym.Ampersand => (Key.D7, "&"),
KeySym.Apostrophe => (Key.Oem3, "'"),
KeySym.ParenthesisLeft => (Key.D9, "("),
KeySym.ParenthesisRight => (Key.D0, ")"),
KeySym.Asterisk => (Key.D8, "*"),
KeySym.Plus => (Key.OemPlus, "+"),
KeySym.Comma => (Key.OemComma, ","),
KeySym.Minus => (Key.OemMinus, "-"),
KeySym.Period => (Key.OemPeriod, "."),
KeySym.Slash => (Key.OemQuestion, "/"),
KeySym.D0 => (Key.D0, "0"),
KeySym.D1 => (Key.D1, "1"),
KeySym.D2 => (Key.D2, "2"),
KeySym.D3 => (Key.D3, "3"),
KeySym.D4 => (Key.D4, "4"),
KeySym.D5 => (Key.D5, "5"),
KeySym.D6 => (Key.D6, "6"),
KeySym.D7 => (Key.D7, "7"),
KeySym.D8 => (Key.D8, "8"),
KeySym.D9 => (Key.D9, "9"),
KeySym.Colon => (Key.OemSemicolon, ":"),
KeySym.Semicolon => (Key.OemSemicolon, ";"),
KeySym.Less => (Key.OemComma, "<"),
KeySym.Equal => (Key.OemPlus, "="),
KeySym.Greater => (Key.OemPeriod, ">"),
KeySym.Question => (Key.OemQuestion, "?"),
KeySym.At => (Key.Oem3, "@"),
KeySym.A => (Key.A, "A"),
KeySym.B => (Key.B, "B"),
KeySym.C => (Key.C, "C"),
KeySym.D => (Key.D, "D"),
KeySym.E => (Key.E, "E"),
KeySym.F => (Key.F, "F"),
KeySym.G => (Key.G, "G"),
KeySym.H => (Key.H, "H"),
KeySym.I => (Key.I, "I"),
KeySym.J => (Key.J, "J"),
KeySym.K => (Key.K, "K"),
KeySym.L => (Key.L, "L"),
KeySym.M => (Key.M, "M"),
KeySym.N => (Key.N, "N"),
KeySym.O => (Key.O, "O"),
KeySym.P => (Key.P, "P"),
KeySym.Q => (Key.Q, "Q"),
KeySym.R => (Key.R, "R"),
KeySym.S => (Key.S, "S"),
KeySym.T => (Key.T, "T"),
KeySym.U => (Key.U, "U"),
KeySym.V => (Key.V, "V"),
KeySym.W => (Key.W, "W"),
KeySym.X => (Key.X, "X"),
KeySym.Y => (Key.Y, "Y"),
KeySym.Z => (Key.Z, "Z"),
KeySym.BracketLeft => (Key.OemOpenBrackets, "["),
KeySym.Backslash => (Key.OemPipe, "\\"),
KeySym.Bracketright => (Key.OemCloseBrackets, "]"),
KeySym.Underscore => (Key.OemMinus, "_"),
KeySym.Grave => (Key.Oem8, "`"),
KeySym.a => (Key.A, "a"),
KeySym.b => (Key.B, "b"),
KeySym.c => (Key.C, "c"),
KeySym.d => (Key.D, "d"),
KeySym.e => (Key.E, "e"),
KeySym.f => (Key.F, "f"),
KeySym.g => (Key.G, "g"),
KeySym.h => (Key.H, "h"),
KeySym.i => (Key.I, "i"),
KeySym.j => (Key.J, "j"),
KeySym.k => (Key.K, "k"),
KeySym.l => (Key.L, "l"),
KeySym.m => (Key.M, "m"),
KeySym.n => (Key.M, "n"),
KeySym.o => (Key.O, "o"),
KeySym.p => (Key.P, "p"),
KeySym.q => (Key.Q, "q"),
KeySym.r => (Key.R, "r"),
KeySym.s => (Key.S, "s"),
KeySym.t => (Key.T, "t"),
KeySym.u => (Key.U, "u"),
KeySym.v => (Key.V, "v"),
KeySym.w => (Key.W, "w"),
KeySym.x => (Key.X, "x"),
KeySym.y => (Key.Y, "y"),
KeySym.z => (Key.Z, "z"),
KeySym.BraceLeft => (Key.OemOpenBrackets, "{"),
KeySym.Bar => (Key.OemPipe, "|"),
KeySym.BraceRight => (Key.OemCloseBrackets, "}"),
KeySym.AsciiTilde => (Key.OemTilde, "~"),
_ => (Key.None, null)
};

32
src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs

@ -43,14 +43,42 @@ public static class HeadlessWindowExtensions
/// <summary>
/// Simulates keyboard press on the headless window/toplevel.
/// </summary>
[Obsolete("Use the overload that takes a physical key and key symbol instead, or KeyPressQwerty alternatively.")]
public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
RunJobsOnImpl(topLevel, w => w.KeyPress(key, modifiers));
KeyPress(topLevel, key, modifiers, PhysicalKey.None, null);
/// <summary>
/// Simulates keyboard press on the headless window/toplevel.
/// </summary>
public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers, PhysicalKey physicalKey,
string? keySymbol) =>
RunJobsOnImpl(topLevel, w => w.KeyPress(key, modifiers, physicalKey, keySymbol));
/// <summary>
/// Simulates keyboard press on the headless window/toplevel, as if typed on a QWERTY keyboard.
/// </summary>
public static void KeyPressQwerty(this TopLevel topLevel, PhysicalKey physicalKey, RawInputModifiers modifiers) =>
RunJobsOnImpl(topLevel, w => w.KeyPress(physicalKey.ToQwertyKey(), modifiers, physicalKey, physicalKey.ToQwertyKeySymbol()));
/// <summary>
/// Simulates keyboard release on the headless window/toplevel.
/// </summary>
[Obsolete("Use the overload that takes a physical key and key symbol instead, or KeyReleaseQwerty alternatively.")]
public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
RunJobsOnImpl(topLevel, w => w.KeyRelease(key, modifiers));
KeyRelease(topLevel, key, modifiers, PhysicalKey.None, null);
/// <summary>
/// Simulates keyboard release on the headless window/toplevel.
/// </summary>
public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers, PhysicalKey physicalKey,
string? keySymbol) =>
RunJobsOnImpl(topLevel, w => w.KeyRelease(key, modifiers, physicalKey, keySymbol));
/// <summary>
/// Simulates keyboard release on the headless window/toplevel, as if typed on a QWERTY keyboard.
/// </summary>
public static void KeyReleaseQwerty(this TopLevel topLevel, PhysicalKey physicalKey, RawInputModifiers modifiers) =>
RunJobsOnImpl(topLevel, w => w.KeyRelease(physicalKey.ToQwertyKey(), modifiers, physicalKey, physicalKey.ToQwertyKeySymbol()));
/// <summary>
/// Simulates a text input event on the headless window/toplevel

27
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -9,11 +9,8 @@ using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Headless
{
@ -271,14 +268,30 @@ namespace Avalonia.Headless
return null;
}
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, string? keySymbol)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyDown, key, modifiers));
Input?.Invoke(new RawKeyEventArgs(
_keyboard,
Timestamp,
InputRoot!,
RawKeyEventType.KeyDown,
key,
modifiers,
physicalKey,
keySymbol));
}
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers)
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, string? keySymbol)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyUp, key, modifiers));
Input?.Invoke(new RawKeyEventArgs(
_keyboard,
Timestamp,
InputRoot!,
RawKeyEventType.KeyUp,
key,
modifiers,
physicalKey,
keySymbol));
}
void IHeadlessWindow.TextInput(string text)

6
src/Headless/Avalonia.Headless/IHeadlessWindow.cs

@ -1,16 +1,14 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Headless
{
internal interface IHeadlessWindow
{
WriteableBitmap? GetLastRenderedFrame();
void KeyPress(Key key, RawInputModifiers modifiers);
void KeyRelease(Key key, RawInputModifiers modifiers);
void KeyPress(Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, string? keySymbol);
void KeyRelease(Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, string? keySymbol);
void TextInput(string text);
void MouseDown(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None);

8
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -327,7 +327,7 @@ namespace Avalonia.Win32.Input
if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End)
{
KeyPress(Key.Delete);
KeyPress(Key.Delete, PhysicalKey.Delete);
}
}
@ -398,15 +398,15 @@ namespace Avalonia.Win32.Input
return (int)(ptr.ToInt64() & 0xffffffff);
}
private void KeyPress(Key key)
private void KeyPress(Key key, PhysicalKey physicalKey)
{
if (_parent?.Input != null)
{
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyDown, key, RawInputModifiers.None));
RawKeyEventType.KeyDown, key, RawInputModifiers.None, physicalKey, null));
_parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner,
RawKeyEventType.KeyUp, key, RawInputModifiers.None));
RawKeyEventType.KeyUp, key, RawInputModifiers.None, physicalKey, null));
}
}

816
src/Windows/Avalonia.Win32/Input/KeyInterop.cs

@ -1,363 +1,376 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.Interop.UnmanagedMethods.VirtualKeyStates;
namespace Avalonia.Win32.Input
{
/// <summary>
/// Contains methods used to translate a Windows virtual/physical key to an Avalonia <see cref="Key"/>.
/// </summary>
public static class KeyInterop
{
private static readonly Dictionary<Key, int> s_virtualKeyFromKey = new Dictionary<Key, int>
// source: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
private static readonly Dictionary<Key, int> s_virtualKeyFromKey = new(169)
{
{ Key.None, 0 },
{ Key.Cancel, 3 },
{ Key.Back, 8 },
{ Key.Tab, 9 },
{ Key.LineFeed, 0 },
{ Key.Clear, 12 },
{ Key.Return, 13 },
{ Key.Pause, 19 },
{ Key.Capital, 20 },
{ Key.KanaMode, 21 },
{ Key.JunjaMode, 23 },
{ Key.FinalMode, 24 },
{ Key.HanjaMode, 25 },
{ Key.Escape, 27 },
{ Key.ImeConvert, 28 },
{ Key.ImeNonConvert, 29 },
{ Key.ImeAccept, 30 },
{ Key.ImeModeChange, 31 },
{ Key.Space, 32 },
{ Key.PageUp, 33 },
{ Key.Next, 34 },
{ Key.End, 35 },
{ Key.Home, 36 },
{ Key.Left, 37 },
{ Key.Up, 38 },
{ Key.Right, 39 },
{ Key.Down, 40 },
{ Key.Select, 41 },
{ Key.Print, 42 },
{ Key.Execute, 43 },
{ Key.Snapshot, 44 },
{ Key.Insert, 45 },
{ Key.Delete, 46 },
{ Key.Help, 47 },
{ Key.D0, 48 },
{ Key.D1, 49 },
{ Key.D2, 50 },
{ Key.D3, 51 },
{ Key.D4, 52 },
{ Key.D5, 53 },
{ Key.D6, 54 },
{ Key.D7, 55 },
{ Key.D8, 56 },
{ Key.D9, 57 },
{ Key.A, 65 },
{ Key.B, 66 },
{ Key.C, 67 },
{ Key.D, 68 },
{ Key.E, 69 },
{ Key.F, 70 },
{ Key.G, 71 },
{ Key.H, 72 },
{ Key.I, 73 },
{ Key.J, 74 },
{ Key.K, 75 },
{ Key.L, 76 },
{ Key.M, 77 },
{ Key.N, 78 },
{ Key.O, 79 },
{ Key.P, 80 },
{ Key.Q, 81 },
{ Key.R, 82 },
{ Key.S, 83 },
{ Key.T, 84 },
{ Key.U, 85 },
{ Key.V, 86 },
{ Key.W, 87 },
{ Key.X, 88 },
{ Key.Y, 89 },
{ Key.Z, 90 },
{ Key.LWin, 91 },
{ Key.RWin, 92 },
{ Key.Apps, 93 },
{ Key.Sleep, 95 },
{ Key.NumPad0, 96 },
{ Key.NumPad1, 97 },
{ Key.NumPad2, 98 },
{ Key.NumPad3, 99 },
{ Key.NumPad4, 100 },
{ Key.NumPad5, 101 },
{ Key.NumPad6, 102 },
{ Key.NumPad7, 103 },
{ Key.NumPad8, 104 },
{ Key.NumPad9, 105 },
{ Key.Multiply, 106 },
{ Key.Add, 107 },
{ Key.Separator, 108 },
{ Key.Subtract, 109 },
{ Key.Decimal, 110 },
{ Key.Divide, 111 },
{ Key.F1, 112 },
{ Key.F2, 113 },
{ Key.F3, 114 },
{ Key.F4, 115 },
{ Key.F5, 116 },
{ Key.F6, 117 },
{ Key.F7, 118 },
{ Key.F8, 119 },
{ Key.F9, 120 },
{ Key.F10, 121 },
{ Key.F11, 122 },
{ Key.F12, 123 },
{ Key.F13, 124 },
{ Key.F14, 125 },
{ Key.F15, 126 },
{ Key.F16, 127 },
{ Key.F17, 128 },
{ Key.F18, 129 },
{ Key.F19, 130 },
{ Key.F20, 131 },
{ Key.F21, 132 },
{ Key.F22, 133 },
{ Key.F23, 134 },
{ Key.F24, 135 },
{ Key.NumLock, 144 },
{ Key.Scroll, 145 },
{ Key.LeftShift, 160 },
{ Key.RightShift, 161 },
{ Key.LeftCtrl, 162 },
{ Key.RightCtrl, 163 },
{ Key.LeftAlt, 164 },
{ Key.RightAlt, 165 },
{ Key.BrowserBack, 166 },
{ Key.BrowserForward, 167 },
{ Key.BrowserRefresh, 168 },
{ Key.BrowserStop, 169 },
{ Key.BrowserSearch, 170 },
{ Key.BrowserFavorites, 171 },
{ Key.BrowserHome, 172 },
{ Key.VolumeMute, 173 },
{ Key.VolumeDown, 174 },
{ Key.VolumeUp, 175 },
{ Key.MediaNextTrack, 176 },
{ Key.MediaPreviousTrack, 177 },
{ Key.MediaStop, 178 },
{ Key.MediaPlayPause, 179 },
{ Key.LaunchMail, 180 },
{ Key.SelectMedia, 181 },
{ Key.LaunchApplication1, 182 },
{ Key.LaunchApplication2, 183 },
{ Key.Oem1, 186 },
{ Key.OemPlus, 187 },
{ Key.OemComma, 188 },
{ Key.OemMinus, 189 },
{ Key.OemPeriod, 190 },
{ Key.OemQuestion, 191 },
{ Key.Oem3, 192 },
{ Key.AbntC1, 193 },
{ Key.AbntC2, 194 },
{ Key.OemOpenBrackets, 219 },
{ Key.Oem5, 220 },
{ Key.Oem6, 221 },
{ Key.OemQuotes, 222 },
{ Key.Oem8, 223 },
{ Key.OemBackslash, 226 },
{ Key.ImeProcessed, 229 },
{ Key.System, 0 },
{ Key.OemAttn, 240 },
{ Key.OemFinish, 241 },
{ Key.OemCopy, 242 },
{ Key.DbeSbcsChar, 243 },
{ Key.OemEnlw, 244 },
{ Key.OemBackTab, 245 },
{ Key.DbeNoRoman, 246 },
{ Key.DbeEnterWordRegisterMode, 247 },
{ Key.DbeEnterImeConfigureMode, 248 },
{ Key.EraseEof, 249 },
{ Key.Play, 250 },
{ Key.DbeNoCodeInput, 251 },
{ Key.NoName, 252 },
{ Key.Pa1, 253 },
{ Key.OemClear, 254 },
{ Key.DeadCharProcessed, 0 },
{ Key.Cancel, (int)VK_CANCEL },
{ Key.Back, (int)VK_BACK },
{ Key.Tab, (int)VK_TAB },
{ Key.Clear, (int)VK_CLEAR },
{ Key.Return, (int)VK_RETURN },
{ Key.Pause, (int)VK_PAUSE },
{ Key.Capital, (int)VK_CAPITAL },
{ Key.KanaMode, (int)VK_KANA },
{ Key.JunjaMode, (int)VK_JUNJA },
{ Key.FinalMode, (int)VK_FINAL },
{ Key.HanjaMode, (int)VK_HANJA },
{ Key.Escape, (int)VK_ESCAPE },
{ Key.ImeConvert, (int)VK_CONVERT },
{ Key.ImeNonConvert, (int)VK_NONCONVERT },
{ Key.ImeAccept, (int)VK_ACCEPT },
{ Key.ImeModeChange, (int)VK_MODECHANGE },
{ Key.Space, (int)VK_SPACE },
{ Key.PageUp, (int)VK_PRIOR },
{ Key.PageDown, (int)VK_NEXT },
{ Key.End, (int)VK_END },
{ Key.Home, (int)VK_HOME },
{ Key.Left, (int)VK_LEFT },
{ Key.Up, (int)VK_UP },
{ Key.Right, (int)VK_RIGHT },
{ Key.Down, (int)VK_DOWN },
{ Key.Select, (int)VK_SELECT },
{ Key.Print, (int)VK_PRINT },
{ Key.Execute, (int)VK_EXECUTE },
{ Key.Snapshot, (int)VK_SNAPSHOT },
{ Key.Insert, (int)VK_INSERT },
{ Key.Delete, (int)VK_DELETE },
{ Key.Help, (int)VK_HELP },
{ Key.D0, '0' },
{ Key.D1, '1' },
{ Key.D2, '2' },
{ Key.D3, '3' },
{ Key.D4, '4' },
{ Key.D5, '5' },
{ Key.D6, '6' },
{ Key.D7, '7' },
{ Key.D8, '8' },
{ Key.D9, '9' },
{ Key.A, 'A' },
{ Key.B, 'B' },
{ Key.C, 'C' },
{ Key.D, 'D' },
{ Key.E, 'E' },
{ Key.F, 'F' },
{ Key.G, 'G' },
{ Key.H, 'H' },
{ Key.I, 'I' },
{ Key.J, 'J' },
{ Key.K, 'K' },
{ Key.L, 'L' },
{ Key.M, 'M' },
{ Key.N, 'N' },
{ Key.O, 'O' },
{ Key.P, 'P' },
{ Key.Q, 'Q' },
{ Key.R, 'R' },
{ Key.S, 'S' },
{ Key.T, 'T' },
{ Key.U, 'U' },
{ Key.V, 'V' },
{ Key.W, 'W' },
{ Key.X, 'X' },
{ Key.Y, 'Y' },
{ Key.Z, 'Z' },
{ Key.LWin, (int)VK_LWIN },
{ Key.RWin, (int)VK_RWIN },
{ Key.Apps, (int)VK_APPS },
{ Key.Sleep, (int)VK_SLEEP },
{ Key.NumPad0, (int)VK_NUMPAD0 },
{ Key.NumPad1, (int)VK_NUMPAD1 },
{ Key.NumPad2, (int)VK_NUMPAD2 },
{ Key.NumPad3, (int)VK_NUMPAD3 },
{ Key.NumPad4, (int)VK_NUMPAD4 },
{ Key.NumPad5, (int)VK_NUMPAD5 },
{ Key.NumPad6, (int)VK_NUMPAD6 },
{ Key.NumPad7, (int)VK_NUMPAD7 },
{ Key.NumPad8, (int)VK_NUMPAD8 },
{ Key.NumPad9, (int)VK_NUMPAD9 },
{ Key.Multiply, (int)VK_MULTIPLY },
{ Key.Add, (int)VK_ADD },
{ Key.Separator, (int)VK_SEPARATOR },
{ Key.Subtract, (int)VK_SUBTRACT },
{ Key.Decimal, (int)VK_DECIMAL },
{ Key.Divide, (int)VK_DIVIDE },
{ Key.F1, (int)VK_F1 },
{ Key.F2, (int)VK_F2 },
{ Key.F3, (int)VK_F3 },
{ Key.F4, (int)VK_F4 },
{ Key.F5, (int)VK_F5 },
{ Key.F6, (int)VK_F6 },
{ Key.F7, (int)VK_F7 },
{ Key.F8, (int)VK_F8 },
{ Key.F9, (int)VK_F9 },
{ Key.F10, (int)VK_F10 },
{ Key.F11, (int)VK_F11 },
{ Key.F12, (int)VK_F12 },
{ Key.F13, (int)VK_F13 },
{ Key.F14, (int)VK_F14 },
{ Key.F15, (int)VK_F15 },
{ Key.F16, (int)VK_F16 },
{ Key.F17, (int)VK_F17 },
{ Key.F18, (int)VK_F18 },
{ Key.F19, (int)VK_F19 },
{ Key.F20, (int)VK_F20 },
{ Key.F21, (int)VK_F21 },
{ Key.F22, (int)VK_F22 },
{ Key.F23, (int)VK_F23 },
{ Key.F24, (int)VK_F24 },
{ Key.NumLock, (int)VK_NUMLOCK },
{ Key.Scroll, (int)VK_SCROLL },
{ Key.LeftShift, (int)VK_LSHIFT },
{ Key.RightShift, (int)VK_RSHIFT },
{ Key.LeftCtrl, (int)VK_LCONTROL },
{ Key.RightCtrl, (int)VK_RCONTROL },
{ Key.LeftAlt, (int)VK_LMENU },
{ Key.RightAlt, (int)VK_RMENU },
{ Key.BrowserBack, (int)VK_BROWSER_BACK },
{ Key.BrowserForward, (int)VK_BROWSER_FORWARD },
{ Key.BrowserRefresh, (int)VK_BROWSER_REFRESH },
{ Key.BrowserStop, (int)VK_BROWSER_STOP },
{ Key.BrowserSearch, (int)VK_BROWSER_SEARCH },
{ Key.BrowserFavorites, (int)VK_BROWSER_FAVORITES },
{ Key.BrowserHome, (int)VK_BROWSER_HOME },
{ Key.VolumeMute, (int)VK_VOLUME_MUTE },
{ Key.VolumeDown, (int)VK_VOLUME_DOWN },
{ Key.VolumeUp, (int)VK_VOLUME_UP },
{ Key.MediaNextTrack, (int)VK_MEDIA_NEXT_TRACK },
{ Key.MediaPreviousTrack, (int)VK_MEDIA_PREV_TRACK },
{ Key.MediaStop, (int)VK_MEDIA_STOP },
{ Key.MediaPlayPause, (int)VK_MEDIA_PLAY_PAUSE },
{ Key.LaunchMail, (int)VK_LAUNCH_MAIL },
{ Key.SelectMedia, (int)VK_LAUNCH_MEDIA_SELECT },
{ Key.LaunchApplication1, (int)VK_LAUNCH_APP1 },
{ Key.LaunchApplication2, (int)VK_LAUNCH_APP2 },
{ Key.Oem1, (int)VK_OEM_1 },
{ Key.OemPlus, (int)VK_OEM_PLUS },
{ Key.OemComma, (int)VK_OEM_COMMA },
{ Key.OemMinus, (int)VK_OEM_MINUS },
{ Key.OemPeriod, (int)VK_OEM_PERIOD },
{ Key.OemQuestion, (int)VK_OEM_2 },
{ Key.Oem3, (int)VK_OEM_3 },
{ Key.AbntC1, (int)VK_ABNT_C1 },
{ Key.AbntC2, (int)VK_ABNT_C2 },
{ Key.OemOpenBrackets, (int)VK_OEM_4 },
{ Key.Oem5, (int)VK_OEM_5 },
{ Key.Oem6, (int)VK_OEM_6 },
{ Key.OemQuotes, (int)VK_OEM_7 },
{ Key.Oem8, (int)VK_OEM_8 },
{ Key.OemBackslash, (int)VK_OEM_102 },
{ Key.ImeProcessed, (int)VK_PROCESSKEY },
{ Key.OemAttn, (int)VK_OEM_ATTN },
{ Key.OemFinish, (int)VK_OEM_FINISH },
{ Key.OemCopy, (int)VK_OEM_COPY },
{ Key.DbeSbcsChar, (int)VK_OEM_AUTO },
{ Key.OemEnlw, (int)VK_OEM_ENLW },
{ Key.OemBackTab, (int)VK_OEM_BACKTAB },
{ Key.DbeNoRoman, (int)VK_ATTN },
{ Key.DbeEnterWordRegisterMode, (int)VK_CRSEL },
{ Key.DbeEnterImeConfigureMode, (int)VK_EXSEL },
{ Key.EraseEof, (int)VK_EREOF },
{ Key.Play, (int)VK_PLAY },
{ Key.DbeNoCodeInput, (int)VK_ZOOM },
{ Key.NoName, (int)VK_NONAME },
{ Key.Pa1, (int)VK_PA1 },
{ Key.OemClear, (int)VK_OEM_CLEAR }
};
private static readonly Dictionary<int, Key> s_keyFromVirtualKey = new Dictionary<int, Key>
private static readonly Dictionary<int, Key> s_keyFromVirtualKey =
s_virtualKeyFromKey.ToDictionary(pair => pair.Value, pair => pair.Key);
// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes
// https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/dom_code_data.inc
// This list has the same order as the PhysicalKey enum.
private static readonly Dictionary<ushort, PhysicalKey> s_physicalKeyFromExtendedScanCode = new(155)
{
{ 0, Key.None },
{ 3, Key.Cancel },
{ 8, Key.Back },
{ 9, Key.Tab },
{ 12, Key.Clear },
{ 13, Key.Return },
{ 16, Key.LeftShift},
{ 17, Key.LeftCtrl},
{ 18, Key.LeftAlt },
{ 19, Key.Pause },
{ 20, Key.Capital },
{ 21, Key.KanaMode },
{ 23, Key.JunjaMode },
{ 24, Key.FinalMode },
{ 25, Key.HanjaMode },
{ 27, Key.Escape },
{ 28, Key.ImeConvert },
{ 29, Key.ImeNonConvert },
{ 30, Key.ImeAccept },
{ 31, Key.ImeModeChange },
{ 32, Key.Space },
{ 33, Key.PageUp },
{ 34, Key.PageDown },
{ 35, Key.End },
{ 36, Key.Home },
{ 37, Key.Left },
{ 38, Key.Up },
{ 39, Key.Right },
{ 40, Key.Down },
{ 41, Key.Select },
{ 42, Key.Print },
{ 43, Key.Execute },
{ 44, Key.Snapshot },
{ 45, Key.Insert },
{ 46, Key.Delete },
{ 47, Key.Help },
{ 48, Key.D0 },
{ 49, Key.D1 },
{ 50, Key.D2 },
{ 51, Key.D3 },
{ 52, Key.D4 },
{ 53, Key.D5 },
{ 54, Key.D6 },
{ 55, Key.D7 },
{ 56, Key.D8 },
{ 57, Key.D9 },
{ 65, Key.A },
{ 66, Key.B },
{ 67, Key.C },
{ 68, Key.D },
{ 69, Key.E },
{ 70, Key.F },
{ 71, Key.G },
{ 72, Key.H },
{ 73, Key.I },
{ 74, Key.J },
{ 75, Key.K },
{ 76, Key.L },
{ 77, Key.M },
{ 78, Key.N },
{ 79, Key.O },
{ 80, Key.P },
{ 81, Key.Q },
{ 82, Key.R },
{ 83, Key.S },
{ 84, Key.T },
{ 85, Key.U },
{ 86, Key.V },
{ 87, Key.W },
{ 88, Key.X },
{ 89, Key.Y },
{ 90, Key.Z },
{ 91, Key.LWin },
{ 92, Key.RWin },
{ 93, Key.Apps },
{ 95, Key.Sleep },
{ 96, Key.NumPad0 },
{ 97, Key.NumPad1 },
{ 98, Key.NumPad2 },
{ 99, Key.NumPad3 },
{ 100, Key.NumPad4 },
{ 101, Key.NumPad5 },
{ 102, Key.NumPad6 },
{ 103, Key.NumPad7 },
{ 104, Key.NumPad8 },
{ 105, Key.NumPad9 },
{ 106, Key.Multiply },
{ 107, Key.Add },
{ 108, Key.Separator },
{ 109, Key.Subtract },
{ 110, Key.Decimal },
{ 111, Key.Divide },
{ 112, Key.F1 },
{ 113, Key.F2 },
{ 114, Key.F3 },
{ 115, Key.F4 },
{ 116, Key.F5 },
{ 117, Key.F6 },
{ 118, Key.F7 },
{ 119, Key.F8 },
{ 120, Key.F9 },
{ 121, Key.F10 },
{ 122, Key.F11 },
{ 123, Key.F12 },
{ 124, Key.F13 },
{ 125, Key.F14 },
{ 126, Key.F15 },
{ 127, Key.F16 },
{ 128, Key.F17 },
{ 129, Key.F18 },
{ 130, Key.F19 },
{ 131, Key.F20 },
{ 132, Key.F21 },
{ 133, Key.F22 },
{ 134, Key.F23 },
{ 135, Key.F24 },
{ 144, Key.NumLock },
{ 145, Key.Scroll },
{ 160, Key.LeftShift },
{ 161, Key.RightShift },
{ 162, Key.LeftCtrl },
{ 163, Key.RightCtrl },
{ 164, Key.LeftAlt },
{ 165, Key.RightAlt },
{ 166, Key.BrowserBack },
{ 167, Key.BrowserForward },
{ 168, Key.BrowserRefresh },
{ 169, Key.BrowserStop },
{ 170, Key.BrowserSearch },
{ 171, Key.BrowserFavorites },
{ 172, Key.BrowserHome },
{ 173, Key.VolumeMute },
{ 174, Key.VolumeDown },
{ 175, Key.VolumeUp },
{ 176, Key.MediaNextTrack },
{ 177, Key.MediaPreviousTrack },
{ 178, Key.MediaStop },
{ 179, Key.MediaPlayPause },
{ 180, Key.LaunchMail },
{ 181, Key.SelectMedia },
{ 182, Key.LaunchApplication1 },
{ 183, Key.LaunchApplication2 },
{ 186, Key.Oem1 },
{ 187, Key.OemPlus },
{ 188, Key.OemComma },
{ 189, Key.OemMinus },
{ 190, Key.OemPeriod },
{ 191, Key.OemQuestion },
{ 192, Key.Oem3 },
{ 193, Key.AbntC1 },
{ 194, Key.AbntC2 },
{ 219, Key.OemOpenBrackets },
{ 220, Key.Oem5 },
{ 221, Key.Oem6 },
{ 222, Key.OemQuotes },
{ 223, Key.Oem8 },
{ 226, Key.OemBackslash },
{ 229, Key.ImeProcessed },
{ 240, Key.OemAttn },
{ 241, Key.OemFinish },
{ 242, Key.OemCopy },
{ 243, Key.DbeSbcsChar },
{ 244, Key.OemEnlw },
{ 245, Key.OemBackTab },
{ 246, Key.DbeNoRoman },
{ 247, Key.DbeEnterWordRegisterMode },
{ 248, Key.DbeEnterImeConfigureMode },
{ 249, Key.EraseEof },
{ 250, Key.Play },
{ 251, Key.DbeNoCodeInput },
{ 252, Key.NoName },
{ 253, Key.Pa1 },
{ 254, Key.OemClear },
// Writing System Keys
{ 0x0029, PhysicalKey.Backquote },
{ 0x002B, PhysicalKey.Backslash },
{ 0x001A, PhysicalKey.BracketLeft },
{ 0x001B, PhysicalKey.BracketRight },
{ 0x0033, PhysicalKey.Comma },
{ 0x000B, PhysicalKey.Digit0 },
{ 0x0002, PhysicalKey.Digit1 },
{ 0x0003, PhysicalKey.Digit2 },
{ 0x0004, PhysicalKey.Digit3 },
{ 0x0005, PhysicalKey.Digit4 },
{ 0x0006, PhysicalKey.Digit5 },
{ 0x0007, PhysicalKey.Digit6 },
{ 0x0008, PhysicalKey.Digit7 },
{ 0x0009, PhysicalKey.Digit8 },
{ 0x000A, PhysicalKey.Digit9 },
{ 0x000D, PhysicalKey.Equal },
{ 0x0056, PhysicalKey.IntlBackslash },
{ 0x0073, PhysicalKey.IntlRo },
{ 0x007D, PhysicalKey.IntlYen },
{ 0x001E, PhysicalKey.A },
{ 0x0030, PhysicalKey.B },
{ 0x002E, PhysicalKey.C },
{ 0x0020, PhysicalKey.D },
{ 0x0012, PhysicalKey.E },
{ 0x0021, PhysicalKey.F },
{ 0x0022, PhysicalKey.G },
{ 0x0023, PhysicalKey.H },
{ 0x0017, PhysicalKey.I },
{ 0x0024, PhysicalKey.J },
{ 0x0025, PhysicalKey.K },
{ 0x0026, PhysicalKey.L },
{ 0x0032, PhysicalKey.M },
{ 0x0031, PhysicalKey.N },
{ 0x0018, PhysicalKey.O },
{ 0x0019, PhysicalKey.P },
{ 0x0010, PhysicalKey.Q },
{ 0x0013, PhysicalKey.R },
{ 0x001F, PhysicalKey.S },
{ 0x0014, PhysicalKey.T },
{ 0x0016, PhysicalKey.U },
{ 0x002F, PhysicalKey.V },
{ 0x0011, PhysicalKey.W },
{ 0x002D, PhysicalKey.X },
{ 0x0015, PhysicalKey.Y },
{ 0x002C, PhysicalKey.Z },
{ 0x000C, PhysicalKey.Minus },
{ 0x0034, PhysicalKey.Period },
{ 0x0028, PhysicalKey.Quote },
{ 0x0027, PhysicalKey.Semicolon },
{ 0x0035, PhysicalKey.Slash },
// Functional Keys
{ 0x0038, PhysicalKey.AltLeft },
{ 0xE038, PhysicalKey.AltRight },
{ 0x000E, PhysicalKey.Backspace },
{ 0x003A, PhysicalKey.CapsLock },
{ 0xE05D, PhysicalKey.ContextMenu },
{ 0x001D, PhysicalKey.ControlLeft },
{ 0xE01D, PhysicalKey.ControlRight },
{ 0x001C, PhysicalKey.Enter },
{ 0xE05B, PhysicalKey.MetaLeft },
{ 0xE05C, PhysicalKey.MetaRight },
{ 0x002A, PhysicalKey.ShiftLeft },
{ 0x0036, PhysicalKey.ShiftRight },
{ 0x0039, PhysicalKey.Space },
{ 0x000F, PhysicalKey.Tab },
{ 0x0079, PhysicalKey.Convert },
{ 0x0070, PhysicalKey.KanaMode },
{ 0x0072, PhysicalKey.Lang1 },
{ 0x0071, PhysicalKey.Lang2 },
{ 0x0078, PhysicalKey.Lang3 },
{ 0x0077, PhysicalKey.Lang4 },
//{ , PhysicalKey.Lang5 }, Not mapped on Windows since it's the same as F24 (see Chromium remarks)
{ 0x007B, PhysicalKey.NonConvert },
// Control Pad Section
{ 0xE053, PhysicalKey.Delete },
{ 0xE04F, PhysicalKey.End },
{ 0xE03B, PhysicalKey.Help },
{ 0xE047, PhysicalKey.Home },
{ 0xE052, PhysicalKey.Insert },
{ 0xE051, PhysicalKey.PageDown },
{ 0xE049, PhysicalKey.PageUp },
// Arrow Pad Section
{ 0xE050, PhysicalKey.ArrowDown },
{ 0xE04B, PhysicalKey.ArrowLeft },
{ 0xE04D, PhysicalKey.ArrowRight },
{ 0xE048, PhysicalKey.ArrowUp },
// Numpad Section
{ 0xE045, PhysicalKey.NumLock },
{ 0x0052, PhysicalKey.NumPad0 },
{ 0x004F, PhysicalKey.NumPad1 },
{ 0x0050, PhysicalKey.NumPad2 },
{ 0x0051, PhysicalKey.NumPad3 },
{ 0x004B, PhysicalKey.NumPad4 },
{ 0x004C, PhysicalKey.NumPad5 },
{ 0x004D, PhysicalKey.NumPad6 },
{ 0x0047, PhysicalKey.NumPad7 },
{ 0x0048, PhysicalKey.NumPad8 },
{ 0x0049, PhysicalKey.NumPad9 },
{ 0x004E, PhysicalKey.NumPadAdd },
//{ , PhysicalKey.NumPadClear },
{ 0x007E, PhysicalKey.NumPadComma },
{ 0x0053, PhysicalKey.NumPadDecimal },
{ 0xE035, PhysicalKey.NumPadDivide },
{ 0xE01C, PhysicalKey.NumPadEnter },
{ 0x0059, PhysicalKey.NumPadEqual },
{ 0x0037, PhysicalKey.NumPadMultiply },
//{ , PhysicalKey.NumPadParenLeft },
//{ , PhysicalKey.NumPadParenRight },
{ 0x004A, PhysicalKey.NumPadSubtract },
// Function Section
{ 0x0001, PhysicalKey.Escape },
{ 0x003B, PhysicalKey.F1 },
{ 0x003C, PhysicalKey.F2 },
{ 0x003D, PhysicalKey.F3 },
{ 0x003E, PhysicalKey.F4 },
{ 0x003F, PhysicalKey.F5 },
{ 0x0040, PhysicalKey.F6 },
{ 0x0041, PhysicalKey.F7 },
{ 0x0042, PhysicalKey.F8 },
{ 0x0043, PhysicalKey.F9 },
{ 0x0044, PhysicalKey.F10 },
{ 0x0057, PhysicalKey.F11 },
{ 0x0058, PhysicalKey.F12 },
{ 0x0064, PhysicalKey.F13 },
{ 0x0065, PhysicalKey.F14 },
{ 0x0066, PhysicalKey.F15 },
{ 0x0067, PhysicalKey.F16 },
{ 0x0068, PhysicalKey.F17 },
{ 0x0069, PhysicalKey.F18 },
{ 0x006A, PhysicalKey.F19 },
{ 0x006B, PhysicalKey.F20 },
{ 0x006C, PhysicalKey.F21 },
{ 0x006D, PhysicalKey.F22 },
{ 0x006E, PhysicalKey.F23 },
{ 0x0076, PhysicalKey.F24 },
{ 0xE037, PhysicalKey.PrintScreen },
{ 0x0046, PhysicalKey.ScrollLock },
{ 0x0045, PhysicalKey.Pause },
// Media Keys
{ 0xE06A, PhysicalKey.BrowserBack },
{ 0xE066, PhysicalKey.BrowserFavorites },
{ 0xE069, PhysicalKey.BrowserForward },
{ 0xE032, PhysicalKey.BrowserHome },
{ 0xE067, PhysicalKey.BrowserRefresh },
{ 0xE065, PhysicalKey.BrowserSearch },
{ 0xE068, PhysicalKey.BrowserStop },
{ 0xE02C, PhysicalKey.Eject },
{ 0xE06B, PhysicalKey.LaunchApp1 },
{ 0xE021, PhysicalKey.LaunchApp2 },
{ 0xE06C, PhysicalKey.LaunchMail },
{ 0xE022, PhysicalKey.MediaPlayPause },
{ 0xE06D, PhysicalKey.MediaSelect },
{ 0xE024, PhysicalKey.MediaStop },
{ 0xE019, PhysicalKey.MediaTrackNext },
{ 0xE010, PhysicalKey.MediaTrackPrevious },
{ 0xE05E, PhysicalKey.Power },
{ 0xE05F, PhysicalKey.Sleep },
{ 0xE02E, PhysicalKey.AudioVolumeDown },
{ 0xE020, PhysicalKey.AudioVolumeMute },
{ 0xE030, PhysicalKey.AudioVolumeUp },
{ 0xE063, PhysicalKey.WakeUp },
// Legacy Keys
{ 0xE018, PhysicalKey.Copy },
{ 0xE017, PhysicalKey.Cut },
//{ , PhysicalKey.Find },
//{ , PhysicalKey.Open },
{ 0xE00A, PhysicalKey.Paste },
//{ , PhysicalKey.Props },
//{ , PhysicalKey.Select },
{ 0xE008, PhysicalKey.Undo },
};
/// <summary>
@ -371,56 +384,67 @@ namespace Avalonia.Win32.Input
return (keyData & extendedMask) != 0;
}
private static byte GetScanCode(int keyData)
{
// Bits from 16 to 23 represent scan code.
const int scanCodeMask = 0xFF0000;
return (byte)((keyData & scanCodeMask) >> 16);
}
private static int GetVirtualKey(int virtualKey, int keyData)
{
// Adapted from https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/HwndKeyboardInputProvider.cs.
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_SHIFT)
if (virtualKey == (int)VK_SHIFT)
{
// Bits from 16 to 23 represent scan code.
const int scanCodeMask = 0xFF0000;
var scanCode = (keyData & scanCodeMask) >> 16;
var scanCode = GetScanCode(keyData);
virtualKey = (int)UnmanagedMethods.MapVirtualKey((uint)scanCode, (uint)UnmanagedMethods.MapVirtualKeyMapTypes.MAPVK_VSC_TO_VK_EX);
virtualKey = (int)MapVirtualKey(scanCode, (uint)MapVirtualKeyMapTypes.MAPVK_VSC_TO_VK_EX);
if (virtualKey == 0)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LSHIFT;
virtualKey = (int)VK_LSHIFT;
}
}
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_MENU)
else if (virtualKey == (int)VK_MENU)
{
bool isRight = IsExtended(keyData);
if (isRight)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_RMENU;
virtualKey = (int)VK_RMENU;
}
else
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LMENU;
virtualKey = (int)VK_LMENU;
}
}
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_CONTROL)
else if (virtualKey == (int)VK_CONTROL)
{
bool isRight = IsExtended(keyData);
if (isRight)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_RCONTROL;
virtualKey = (int)VK_RCONTROL;
}
else
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LCONTROL;
virtualKey = (int)VK_LCONTROL;
}
}
return virtualKey;
}
/// <summary>
/// Gets an Avalonia key from a Windows virtual-key and key data.
/// </summary>
/// <param name="virtualKey">The Windows virtual-key.</param>
/// <param name="keyData">The key data (in the same format as lParam for WM_KEYDOWN).</param>
/// <returns>An Avalonia key, or <see cref="Key.None"/> if none matched.</returns>
public static Key KeyFromVirtualKey(int virtualKey, int keyData)
{
virtualKey = GetVirtualKey(virtualKey, keyData);
@ -430,11 +454,79 @@ namespace Avalonia.Win32.Input
return result;
}
/// <summary>
/// Gets a Windows virtual-key from an Avalonia key.
/// </summary>
/// <param name="key">The Avalonia key.</param>
/// <returns>A Windows virtual-key code, or 0 if none matched.</returns>
public static int VirtualKeyFromKey(Key key)
{
s_virtualKeyFromKey.TryGetValue(key, out var result);
return result;
}
/// <summary>
/// Gets a physical Avalonia key from a Windows virtual-key and key data.
/// </summary>
/// <param name="virtualKey">The Windows virtual-key.</param>
/// <param name="keyData">The key data (in the same format as lParam for WM_KEYDOWN).</param>
/// <returns>An Avalonia physical key, or <see cref="PhysicalKey.None"/> if none matched.</returns>
public static PhysicalKey PhysicalKeyFromVirtualKey(int virtualKey, int keyData)
{
uint scanCode = GetScanCode(keyData);
if (scanCode == 0U)
{
// in some cases, the scan code contained in the keyData might be zero:
// try to get one from the virtual key instead
scanCode = MapVirtualKey((uint)virtualKey, (uint)MapVirtualKeyMapTypes.MAPVK_VK_TO_VSC);
if (scanCode == 0U)
return PhysicalKey.None;
}
if (IsExtended(keyData))
scanCode |= 0xE000;
return scanCode is > 0 and <= 0xE0FF
&& s_physicalKeyFromExtendedScanCode.TryGetValue((ushort)scanCode, out var result) ?
result :
PhysicalKey.None;
}
/// <summary>
/// Gets a key symbol from a Windows virtual-key and key data.
/// </summary>
/// <param name="virtualKey">The Windows virtual-key.</param>
/// <param name="keyData">The key data (in the same format as lParam for WM_KEYDOWN).</param>
/// <returns>A key symbol, or null if none matched.</returns>
public static unsafe string? GetKeySymbol(int virtualKey, int keyData)
{
const int bufferSize = 4;
const uint doNotChangeKeyboardState = 1U << 2;
fixed (byte* keyStates = stackalloc byte[256])
fixed (char* buffer = stackalloc char[bufferSize])
{
GetKeyboardState(keyStates);
var length = ToUnicodeEx(
(uint)virtualKey,
GetScanCode(keyData),
keyStates,
buffer,
bufferSize,
doNotChangeKeyboardState,
GetKeyboardLayout(0));
return length switch
{
< 0 => new string(buffer, 0, -length), // dead key
0 => null,
1 when !KeySymbolHelper.IsAllowedAsciiKeySymbol(buffer[0]) => null,
2 when buffer[0] == buffer[1] => new string(buffer, 0, 1), // dead key second press repeats symbol
_ => new string(buffer, 0, length)
};
}
}
}
}

98
src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs

@ -1,92 +1,50 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Utilities;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.Interop.UnmanagedMethods.VirtualKeyStates;
namespace Avalonia.Win32.Input
{
internal class WindowsKeyboardDevice : KeyboardDevice
internal sealed class WindowsKeyboardDevice : KeyboardDevice
{
private readonly byte[] _keyStates = new byte[256];
public new static WindowsKeyboardDevice Instance { get; } = new();
public new static WindowsKeyboardDevice Instance { get; } = new WindowsKeyboardDevice();
public RawInputModifiers Modifiers
public unsafe RawInputModifiers Modifiers
{
get
{
UpdateKeyStates();
RawInputModifiers result = 0;
if (IsDown(Key.LeftAlt) || IsDown(Key.RightAlt))
fixed (byte* keyStates = stackalloc byte[256])
{
result |= RawInputModifiers.Alt;
}
GetKeyboardState(keyStates);
if (IsDown(Key.LeftCtrl) || IsDown(Key.RightCtrl))
{
result |= RawInputModifiers.Control;
}
var result = RawInputModifiers.None;
if (IsDown(Key.LeftShift) || IsDown(Key.RightShift))
{
result |= RawInputModifiers.Shift;
}
if (((keyStates[(int)VK_LMENU] | keyStates[(int)VK_RMENU]) & 0x80) != 0)
{
result |= RawInputModifiers.Alt;
}
if (IsDown(Key.LWin) || IsDown(Key.RWin))
{
result |= RawInputModifiers.Meta;
}
if (((keyStates[(int)VK_LCONTROL] | keyStates[(int)VK_RCONTROL]) & 0x80) != 0)
{
result |= RawInputModifiers.Control;
}
return result;
}
}
if (((keyStates[(int)VK_LSHIFT] | keyStates[(int)VK_RSHIFT]) & 0x80) != 0)
{
result |= RawInputModifiers.Shift;
}
public void WindowActivated(Window window)
{
SetFocusedElement(window, NavigationMethod.Unspecified, KeyModifiers.None);
}
if (((keyStates[(int)VK_LWIN] | keyStates[(int)VK_RWIN]) & 0x80) != 0)
{
result |= RawInputModifiers.Meta;
}
public string StringFromVirtualKey(uint virtualKey)
{
var result = StringBuilderCache.Acquire(256);
int length = UnmanagedMethods.ToUnicode(
virtualKey,
0,
_keyStates,
result,
256,
0);
return StringBuilderCache.GetStringAndRelease(result);
}
private void UpdateKeyStates()
{
UnmanagedMethods.GetKeyboardState(_keyStates);
}
private bool IsDown(Key key)
{
return (GetKeyStates(key) & KeyStates.Down) != 0;
return result;
}
}
}
private KeyStates GetKeyStates(Key key)
private WindowsKeyboardDevice()
{
int vk = KeyInterop.VirtualKeyFromKey(key);
byte state = _keyStates[vk];
KeyStates result = 0;
if ((state & 0x80) != 0)
{
result |= KeyStates.Down;
}
if ((state & 0x01) != 0)
{
result |= KeyStates.Toggled;
}
return result;
}
}
}

25
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -366,6 +366,8 @@ namespace Avalonia.Win32.Interop
VK_OEM_PERIOD = 0xBE,
VK_OEM_2 = 0xBF,
VK_OEM_3 = 0xC0,
VK_ABNT_C1 = 0xC1,
VK_ABNT_C2 = 0xC2,
VK_OEM_4 = 0xDB,
VK_OEM_5 = 0xDC,
VK_OEM_6 = 0xDD,
@ -1203,8 +1205,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern uint GetDoubleClickTime();
[DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetKeyboardState(byte* lpKeyState);
[DllImport("user32.dll", EntryPoint = "MapVirtualKeyW")]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);
@ -1399,15 +1402,15 @@ namespace Avalonia.Win32.Interop
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, IntPtr CompletionEvent);
[DllImport("user32.dll")]
public static extern int ToUnicode(
uint virtualKeyCode,
uint scanCode,
byte[] keyboardState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
StringBuilder receivingBuffer,
int bufferSize,
uint flags);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ToUnicodeEx(
uint wVirtKey,
uint wScanCode,
byte* lpKeyState,
char* pwszBuff,
int cchBuff,
uint wFlags,
IntPtr dwhkl);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool TrackMouseEvent(ref TRACKMOUSEEVENT lpEventTrack);

49
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -151,18 +151,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_KEYDOWN:
case WindowsMessage.WM_SYSKEYDOWN:
{
var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam));
if (key != Key.None)
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
Owner,
RawKeyEventType.KeyDown,
key,
WindowsKeyboardDevice.Instance.Modifiers);
}
e = TryCreateRawKeyEventArgs(RawKeyEventType.KeyDown, timestamp, wParam, lParam);
break;
}
@ -181,18 +170,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_KEYUP:
case WindowsMessage.WM_SYSKEYUP:
{
var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam));
if (key != Key.None)
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
Owner,
RawKeyEventType.KeyUp,
key,
WindowsKeyboardDevice.Instance.Modifiers);
}
e = TryCreateRawKeyEventArgs(RawKeyEventType.KeyUp, timestamp, wParam, lParam);
break;
}
case WindowsMessage.WM_CHAR:
@ -1173,5 +1151,28 @@ namespace Avalonia.Win32
return modifiers;
}
private RawKeyEventArgs? TryCreateRawKeyEventArgs(RawKeyEventType eventType, ulong timestamp, IntPtr wParam, IntPtr lParam)
{
var virtualKey = ToInt32(wParam);
var keyData = ToInt32(lParam);
var key = KeyInterop.KeyFromVirtualKey(virtualKey, keyData);
var physicalKey = KeyInterop.PhysicalKeyFromVirtualKey(virtualKey, keyData);
if (key == Key.None && physicalKey == PhysicalKey.None)
return null;
var keySymbol = KeyInterop.GetKeySymbol(virtualKey, keyData);
return new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
Owner,
eventType,
key,
WindowsKeyboardDevice.Instance.Modifiers,
physicalKey,
keySymbol);
}
}
}

13
src/iOS/Avalonia.iOS/TextInputResponder.cs

@ -155,14 +155,15 @@ partial class AvaloniaView
}
}
private void KeyPress(Key ev)
private void KeyPress(Key key, PhysicalKey physicalKey, string? keySymbol)
{
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering key press {key}", ev);
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering key press {key}", key);
_view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
RawKeyEventType.KeyDown, ev, RawInputModifiers.None));
RawKeyEventType.KeyDown, key, RawInputModifiers.None, physicalKey, keySymbol));
_view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
RawKeyEventType.KeyUp, ev, RawInputModifiers.None));
RawKeyEventType.KeyUp, key, RawInputModifiers.None, physicalKey, keySymbol));
}
private void TextInput(string text)
@ -177,7 +178,7 @@ partial class AvaloniaView
if (text == "\n")
{
KeyPress(Key.Enter);
KeyPress(Key.Enter, PhysicalKey.Enter, "\r");
switch (ReturnKeyType)
{
@ -194,7 +195,7 @@ partial class AvaloniaView
TextInput(text);
}
void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back);
void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back, PhysicalKey.Backspace, "\b");
bool IUIKeyInput.HasText => true;

12
tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs

@ -24,7 +24,9 @@ namespace Avalonia.Base.UnitTests.Input
root.Object,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.None));
RawInputModifiers.None,
PhysicalKey.A,
"a"));
root.Verify(x => x.RaiseEvent(It.IsAny<KeyEventArgs>()));
}
@ -51,7 +53,9 @@ namespace Avalonia.Base.UnitTests.Input
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.None));
RawInputModifiers.None,
PhysicalKey.A,
"a"));
Assert.Equal(1, raised);
}
@ -123,7 +127,9 @@ namespace Avalonia.Base.UnitTests.Input
root,
RawKeyEventType.KeyDown,
Key.O,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.O,
"o"));
Assert.Equal(1, raised);
}

4
tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs

@ -114,7 +114,9 @@ namespace Avalonia.Controls.UnitTests
root,
RawKeyEventType.KeyDown,
Key.F,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.F,
"f"));
Assert.True(hotKeyedTextBox.IsFocused);
}

6
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -210,7 +210,11 @@ namespace Avalonia.Controls.UnitTests
0,
target,
RawKeyEventType.KeyDown,
Key.A, RawInputModifiers.None);
Key.A,
RawInputModifiers.None,
PhysicalKey.A,
"a");
impl.Object.Input(input);
inputManagerMock.Verify(x => x.ProcessInput(input));

24
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -88,7 +88,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
Assert.True(expectedParameter == commandResult, $"{factoryName} HotKey did not carry the CommandParameter.");
}
@ -129,7 +131,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
Assert.True(isExecuted == false, $"{factoryName} Execution raised when IsEnabled is false.");
}
@ -171,7 +175,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
element.IsEnabled = false;
@ -180,7 +186,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
Assert.True(clickExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
@ -229,7 +237,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
element.IsEnabled = false;
@ -238,7 +248,9 @@ namespace Avalonia.Controls.UnitTests.Utils
root,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.Control));
RawInputModifiers.Control,
PhysicalKey.A,
"a"));
Assert.True(commandExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
Assert.True(clickExecutedCount == 0, $"{factoryName} Execution raised event Click.");

Loading…
Cancel
Save