From b2ef353190f04d9f288b0f6a52f374ac711e8be5 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 22 Sep 2023 15:43:37 +0200 Subject: [PATCH] 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 Co-authored-by: Steven Kirk --- native/Avalonia.Native/src/OSX/AvnView.mm | 23 +- native/Avalonia.Native/src/OSX/KeyTransform.h | 11 +- .../Avalonia.Native/src/OSX/KeyTransform.mm | 866 +++++++++------- native/Avalonia.Native/src/OSX/menu.mm | 23 +- .../Helpers/AndroidKeyboardEventsHelper.cs | 5 +- src/Avalonia.Base/Input/KeyEventArgs.cs | 76 +- src/Avalonia.Base/Input/KeySymbolHelper.cs | 26 + src/Avalonia.Base/Input/KeyboardDevice.cs | 6 +- src/Avalonia.Base/Input/PhysicalKey.cs | 944 ++++++++++++++++++ .../Input/PhysicalKeyExtensions.cs | 394 ++++++++ .../Input/Raw/RawKeyEventArgs.cs | 29 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 5 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.Native/WindowImplBase.cs | 22 +- src/Avalonia.Native/avn.idl | 549 ++++++---- src/Avalonia.Native/regen.sh | 2 - .../Avalonia.Remote.Protocol.csproj | 1 + src/Avalonia.Remote.Protocol/InputMessages.cs | 11 +- src/Avalonia.X11/X11Info.cs | 25 +- src/Avalonia.X11/X11KeyTransform.cs | 209 +++- src/Avalonia.X11/X11Window.Ime.cs | 256 +++-- src/Avalonia.X11/X11Window.cs | 3 +- src/Avalonia.X11/XLib.cs | 32 +- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 48 +- src/Browser/Avalonia.Browser/KeyInterop.cs | 435 ++++++++ src/Browser/Avalonia.Browser/Keycodes.cs | 129 --- .../HeadlessVncFramebufferSource.cs | 408 ++++---- .../HeadlessWindowExtensions.cs | 32 +- .../Avalonia.Headless/HeadlessWindowImpl.cs | 27 +- .../Avalonia.Headless/IHeadlessWindow.cs | 6 +- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 8 +- .../Avalonia.Win32/Input/KeyInterop.cs | 816 ++++++++------- .../Input/WindowsKeyboardDevice.cs | 98 +- .../Interop/UnmanagedMethods.cs | 25 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 49 +- src/iOS/Avalonia.iOS/TextInputResponder.cs | 13 +- .../Input/KeyboardDeviceTests.cs | 12 +- .../HotKeyedControlsTests.cs | 4 +- .../TopLevelTests.cs | 6 +- .../Utils/HotKeyManagerTests.cs | 24 +- 40 files changed, 4078 insertions(+), 1584 deletions(-) create mode 100644 src/Avalonia.Base/Input/KeySymbolHelper.cs create mode 100644 src/Avalonia.Base/Input/PhysicalKey.cs create mode 100644 src/Avalonia.Base/Input/PhysicalKeyExtensions.cs delete mode 100755 src/Avalonia.Native/regen.sh create mode 100644 src/Browser/Avalonia.Browser/KeyInterop.cs delete mode 100644 src/Browser/Avalonia.Browser/Keycodes.cs diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 0508a0557f3..4027c3e1194 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -447,24 +447,25 @@ - (void)mouseExited:(NSEvent *)event - (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([event timestamp] * 1000); + auto timestamp = static_cast([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; } } diff --git a/native/Avalonia.Native/src/OSX/KeyTransform.h b/native/Avalonia.Native/src/OSX/KeyTransform.h index 2f434570c9e..95b483ea9a9 100644 --- a/native/Avalonia.Native/src/OSX/KeyTransform.h +++ b/native/Avalonia.Native/src/OSX/KeyTransform.h @@ -1,14 +1,15 @@ #ifndef keytransform_h #define keytransform_h + +#import #include "common.h" -#include -extern std::map s_KeyMap; +AvnPhysicalKey PhysicalKeyFromScanCode(uint16_t scanCode); -extern std::map s_AvnKeyMap; +AvnKey VirtualKeyFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags); -extern std::map s_QwertyKeyMap; +NSString* KeySymbolFromScanCode(uint16_t scanCode, NSEventModifierFlags modifierFlags); -extern std::map s_UnicodeKeyMap; +uint16_t MenuCharFromVirtualKey(AvnKey key); #endif diff --git a/native/Avalonia.Native/src/OSX/KeyTransform.mm b/native/Avalonia.Native/src/OSX/KeyTransform.mm index 4817ad0ccf4..ba3d809dd91 100644 --- a/native/Avalonia.Native/src/OSX/KeyTransform.mm +++ b/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 s_UnicodeKeyMap = +#import +#include +#include + +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 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 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 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 BuildAvnKeyMap () +typedef std::array PhysicalKeyArray; + +static PhysicalKeyArray BuildPhysicalKeyFromScanCode() { - std::map 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> BuildQwertyVirtualKeyFromPhysicalKey() +{ + std::unordered_map> result; + result.reserve(sizeof(keyInfos) / sizeof(keyInfos[0])); + + for (auto& keyInfo : keyInfos) + { + result[keyInfo.physicalKey] = keyInfo.qwertyKey; + } + + return result; +} + +std::unordered_map> qwertyVirtualKeyFromPhysicalKey = BuildQwertyVirtualKeyFromPhysicalKey(); + +static std::unordered_map> BuildMenuCharFromVirtualKey() +{ + std::unordered_map> 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 s_AvnKeyMap = BuildAvnKeyMap(); +std::unordered_map> 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(TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData)); + if (!layoutData) + return 0; + + auto* keyboardLayout = reinterpret_cast(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; +} diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index ce07aa3511d..3905987aab4 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -161,34 +161,17 @@ - (void)didSelectItem:(nullable id)sender 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. diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index bc24a16e564..26940f467bb 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -54,7 +54,10 @@ static string UnicodeTextInput(KeyEvent keyEvent) 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); diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index 4de0c2d2333..3de078fb37b 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -1,12 +1,74 @@ -using System; using Avalonia.Interactivity; -namespace Avalonia.Input +namespace Avalonia.Input; + +/// +/// Provides information specific to a keyboard event. +/// +public class KeyEventArgs : RoutedEventArgs { - public class KeyEventArgs : RoutedEventArgs - { - public Key Key { get; init; } + /// + /// + /// Gets the virtual-key for the associated event.
+ /// A given physical key can result in different virtual keys depending on the current keyboard layout.
+ /// This is the key that is generally referred to when creating keyboard shortcuts. + ///
+ /// + /// For example, when pressing the key located at the Z position on standard US English QWERTY keyboard, + /// this property returns:
+ /// - for an English (QWERTY) layout
+ /// - for a French (AZERTY) layout
+ /// - for a German (QWERTZ) layout
+ /// - for a Russian (JCUKEN) layout + ///
+ ///
+ /// + /// This property should be used for letter-related shortcuts only.
+ /// Prefer using 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 if you want to know + /// which character the key will output.
+ /// Avoid using this for shortcuts related to punctuation keys, as they differ wildly depending on keyboard layouts. + ///
+ /// + /// + public Key Key { get; init; } + + /// + /// Gets the key modifiers for the associated event. + /// + public KeyModifiers KeyModifiers { get; init; } + + /// + /// + /// Gets the physical key for the associated event. + /// + /// + /// This value is independent of the current keyboard layout and usually correspond to the key printed on a standard + /// US English QWERTY keyboard. + /// + /// + /// + /// 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). + /// + /// + /// + public PhysicalKey PhysicalKey { get; init; } - public KeyModifiers KeyModifiers { get; init; } - } + /// + /// + /// Gets the unicode symbol of the key, or null if none is applicable. + /// + /// + /// For example, when pressing the key located at the Z position on standard US English QWERTY keyboard, + /// this property returns:
+ /// - z for an English (QWERTY) layout
+ /// - w for a French (AZERTY) layout
+ /// - y for a German (QWERTZ) layout
+ /// - я for a Russian (JCUKEN) layout + ///
+ ///
+ /// + /// + public string? KeySymbol { get; init; } } diff --git a/src/Avalonia.Base/Input/KeySymbolHelper.cs b/src/Avalonia.Base/Input/KeySymbolHelper.cs new file mode 100644 index 00000000000..8a4bb227f83 --- /dev/null +++ b/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; + } +} diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs index c5e3ef5bf0a..0cee1bbc96e 100644 --- a/src/Avalonia.Base/Input/KeyboardDevice.cs +++ b/src/Avalonia.Base/Input/KeyboardDevice.cs @@ -189,12 +189,14 @@ public void ProcessRawEvent(RawInputEventArgs e) ? 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; diff --git a/src/Avalonia.Base/Input/PhysicalKey.cs b/src/Avalonia.Base/Input/PhysicalKey.cs new file mode 100644 index 00000000000..029133324c1 --- /dev/null +++ b/src/Avalonia.Base/Input/PhysicalKey.cs @@ -0,0 +1,944 @@ +#if AVALONIA_REMOTE_PROTOCOL +namespace Avalonia.Remote.Protocol.Input; +#else +namespace Avalonia.Input; +#endif + +/// +/// Represents a keyboard physical key.
+///
+/// +/// The names follow the W3C codes: https://www.w3.org/TR/uievents-code/ +/// +public enum PhysicalKey +{ + /// + /// Represents no key. + /// + None = 0, + + + // ################### + // Writing System Keys + // ################### + + /// + /// `~ on a US keyboard. + /// This is the 半角/全角/漢字 (hankaku/zenkaku/kanji) key on Japanese keyboards. + /// + Backquote = 1, + + /// + /// Used for both the US \| (on the 101-key layout) and also for the key located between the " and + /// Enter keys on row C of the 102-, 104- and 106-key layouts. + /// #~ on a UK (102) keyboard. + /// + Backslash = 2, + + /// + /// [{ on a US keyboard. + /// + BracketLeft = 3, + + /// + /// ]} on a US keyboard. + /// + BracketRight = 4, + + /// + /// ,< on a US keyboard. + /// + Comma = 5, + + /// + /// 0) on a US keyboard. + /// + Digit0 = 6, + + /// + /// 1! on a US keyboard. + /// + Digit1 = 7, + + /// + /// 2@ on a US keyboard. + /// + Digit2 = 8, + + /// + /// 3# on a US keyboard. + /// + Digit3 = 9, + + /// + /// 4$ on a US keyboard. + /// + Digit4 = 10, + + /// + /// 5% on a US keyboard. + /// + Digit5 = 11, + + /// + /// 6^ on a US keyboard. + /// + Digit6 = 12, + + /// + /// 7& on a US keyboard. + /// + Digit7 = 13, + + /// + /// 8* on a US keyboard. + /// + Digit8 = 14, + + /// + /// 9( on a US keyboard. + /// + Digit9 = 15, + + /// + /// =+ on a US keyboard. + /// + Equal = 16, + + /// + /// Located between the left Shift and Z keys. + /// \| on a UK keyboard. + /// + IntlBackslash = 17, + + /// + /// Located between the / and right Shift keys. + /// \ろ (ro) on a Japanese keyboard. + /// + IntlRo = 18, + + /// + /// Located between the = and Backspace keys. + /// ¥ (yen) on a Japanese keyboard. + /// \/ on a Russian keyboard. + /// + IntlYen = 19, + + /// + /// a on a US keyboard. + /// q on an AZERTY (e.g., French) keyboard. + /// + A = 20, + + /// + /// b on a US keyboard. + /// + B = 21, + + /// + /// c on a US keyboard. + /// + C = 22, + + /// + /// d on a US keyboard. + /// + D = 23, + + /// + /// e on a US keyboard. + /// + E = 24, + + /// + /// f on a US keyboard. + /// + F = 25, + + /// + /// g on a US keyboard. + /// + G = 26, + + /// + /// h on a US keyboard. + /// + H = 27, + + /// + /// i on a US keyboard. + /// + I = 28, + + /// + /// j on a US keyboard. + /// + J = 29, + + /// + /// k on a US keyboard. + /// + K = 30, + + /// + /// l on a US keyboard. + /// + L = 31, + + /// + /// m on a US keyboard. + /// + M = 32, + + /// + /// n on a US keyboard. + /// + N = 33, + + /// + /// o on a US keyboard. + /// + O = 34, + + /// + /// p on a US keyboard. + /// + P = 35, + + /// + /// q on a US keyboard. + /// a on an AZERTY (e.g., French) keyboard. + /// + Q = 36, + + /// + /// r on a US keyboard. + /// + R = 37, + + /// + /// s on a US keyboard. + /// + S = 38, + + /// + /// t on a US keyboard. + /// + T = 39, + + /// + /// u on a US keyboard. + /// + U = 40, + + /// + /// v on a US keyboard. + /// + V = 41, + + /// + /// w on a US keyboard. + /// z on an AZERTY (e.g., French) keyboard. + /// + W = 42, + + /// + /// x on a US keyboard. + /// + X = 43, + + /// + /// y on a US keyboard. + /// z on a QWERTZ (e.g., German) keyboard. + /// + Y = 44, + + /// + /// z on a US keyboard. + /// w on an AZERTY (e.g., French) keyboard. + /// y on a QWERTZ (e.g., German) keyboard. + /// + Z = 45, + + /// + /// -_ on a US keyboard. + /// + Minus = 46, + + /// + /// .> on a US keyboard. + /// + Period = 47, + + /// + /// '" on a US keyboard. + /// + Quote = 48, + + /// + /// ;: on a US keyboard. + /// + Semicolon = 49, + + /// + /// /? on a US keyboard. + /// + Slash = 50, + + + // ############### + // Functional keys + // ############### + + /// + /// Alt, Option or . + /// + AltLeft = 51, + + /// + /// Alt, Option or . + /// This is labelled AltGr key on many keyboard layouts. + /// + AltRight = 52, + + /// + /// Backspace or . + /// Labelled Delete on Apple keyboards. + /// + Backspace = 53, + + /// + /// CapsLock or . + /// + CapsLock = 54, + + /// + /// The application context menu key, which is typically found between the right Meta key + /// and the right Control key. + /// + ContextMenu = 55, + + /// + /// Control or . + /// + ControlLeft = 56, + + /// + /// Control or . + /// + ControlRight = 57, + + /// + /// Enter or . + /// Labelled Return on Apple keyboards. + /// + Enter = 58, + + /// + /// The (Windows), , Command or other OS symbol key. + /// + MetaLeft = 59, + + /// + /// The (Windows), , Command or other OS symbol key. + /// + MetaRight = 60, + + /// + /// Shift or . + /// + ShiftLeft = 61, + + /// + /// Shift or . + /// + ShiftRight = 62, + + /// + /// (space). + /// + Space = 63, + + /// + /// Tab or . + /// + Tab = 64, + + /// + /// Japanese: 変換 (henkan). + /// + Convert = 65, + + /// + /// Japanese: カタカナ/ひらがな/ローマ字 (katakana/hiragana/romaji). + /// + KanaMode = 66, + + /// + /// Korean: HangulMode 한/영 (han/yeong). + /// Japanese (Mac keyboard): かな (kana). + /// + Lang1 = 67, + + /// + /// Korean: Hanja 한자 (hanja). + /// Japanese (Mac keyboard): 英数 (eisu). + /// + Lang2 = 68, + + /// + /// Japanese (word-processing keyboard): Katakana. + /// + Lang3 = 69, + + /// + /// Japanese (word-processing keyboard): Hiragana. + /// + Lang4 = 70, + + /// + /// Japanese (word-processing keyboard): Zenkaku/Hankaku. + /// + Lang5 = 71, + + /// + /// Japanese: 無変換 (muhenkan). + /// + NonConvert = 72, + + + // ################### + // Control Pad Section + // ################### + + /// + /// . The forward delete key. + /// Note that on Apple keyboards, the key labelled Delete on the main part of the keyboard is + /// . + /// + Delete = 73, + + /// + /// End or . + /// + End = 74, + + /// + /// Help. + /// Not present on standard PC keyboards. + /// + Help = 75, + + /// + /// Home or . + /// + Home = 76, + + /// + /// Insert or Ins. + /// Not present on Apple keyboards. + /// + Insert = 77, + + /// + /// Page Down, PgDn or . + /// + PageDown = 78, + + /// + /// Page Up, PgUp or . + /// + PageUp = 79, + + + // ################# + // Arrow Pad Section + // ################# + + /// + /// . + /// + ArrowDown = 80, + + /// + /// . + /// + ArrowLeft = 81, + + /// + /// . + /// + ArrowRight = 82, + + /// + /// . + /// + ArrowUp = 83, + + + // ###################### + // Numeric Keypad Section + // ###################### + + /// + /// Numeric keypad Num Lock. + /// On the Mac, this is used for the numpad Clear key. + /// + NumLock = 84, + + /// + /// Numeric keypad 0 Ins on a keyboard. + /// 0 on a phone or remote control. + /// + NumPad0 = 85, + + /// + /// Numeric keypad 1 End on a keyboard. + /// 1 or 1 QZ on a phone or remote control. + /// + NumPad1 = 86, + + /// + /// Numeric keypad 2 ↓ on a keyboard. + /// 2 ABC on a phone or remote control. + /// + NumPad2 = 87, + + /// + /// Numeric keypad 3 PgDn on a keyboard. + /// 3 DEF on a phone or remote control. + /// + NumPad3 = 88, + + /// + /// Numeric keypad 4 ← on a keyboard. + /// 4 GHI on a phone or remote control. + /// + NumPad4 = 89, + + /// + /// Numeric keypad 5 on a keyboard. + /// 5 JKL on a phone or remote control. + /// + NumPad5 = 90, + + /// + /// Numeric keypad 6 → on a keyboard. + /// 6 MNO on a phone or remote control. + /// + NumPad6 = 91, + + /// + /// Numeric keypad 7 Home on a keyboard. + /// 7 PQRS or 7 PRS on a phone or remote control. + /// + NumPad7 = 92, + + /// + /// Numeric keypad 8 ↑ on a keyboard. + /// 8 TUV on a phone or remote control. + /// + NumPad8 = 93, + + /// + /// Numeric keypad 9 PgUp on a keyboard. + /// 9 WXYZ or 9 WXY on a phone or remote control. + /// + NumPad9 = 94, + + /// + /// Numeric keypad +. + /// + NumPadAdd = 95, + + /// + /// Numeric keypad C or AC (All Clear). + /// Also for use with numpads that have a Clear key that is separate from the NumLock key. + /// On the Mac, the numpad Clear key is . + /// + NumPadClear = 96, + + /// + /// Numeric keypad , (thousands separator). + /// For locales where the thousands separator is a "." (e.g., Brazil), this key may generate a .. + /// + NumPadComma = 97, + + /// + /// Numeric keypad . Del. + /// For locales where the decimal separator is "," (e.g., Brazil), this key may generate a ,. + /// + NumPadDecimal = 98, + + /// + /// Numeric keypad /. + /// + NumPadDivide = 99, + + /// + /// Numeric keypad Enter. + /// + NumPadEnter = 100, + + /// + /// Numeric keypad =. + /// + NumPadEqual = 101, + + /// + /// Numeric keypad * on a keyboard. + /// For use with numpads that provide mathematical operations (+, -, * and /). + /// + NumPadMultiply = 102, + + /// + /// Numeric keypad (. + /// Found on the Microsoft Natural Keyboard. + /// + NumPadParenLeft = 103, + + /// + /// Numeric keypad ). + /// Found on the Microsoft Natural Keyboard. + /// + NumPadParenRight = 104, + + /// + /// Numeric keypad -. + /// + NumPadSubtract = 105, + + + // ################ + // Function Section + // ################ + + /// + /// Esc or . + /// + Escape = 106, + + /// + /// F1. + /// + F1 = 107, + + /// + /// F2. + /// + F2 = 108, + + /// + /// F3. + /// + F3 = 109, + + /// + /// F4. + /// + F4 = 110, + + /// + /// F5. + /// + F5 = 111, + + /// + /// F6. + /// + F6 = 112, + + /// + /// F7. + /// + F7 = 113, + + /// + /// F8. + /// + F8 = 114, + + /// + /// F9. + /// + F9 = 115, + + /// + /// F10. + /// + F10 = 116, + + /// + /// F11. + /// + F11 = 117, + + /// + /// F12. + /// + F12 = 118, + + /// + /// F13. + /// + F13 = 119, + + /// + /// F14. + /// + F14 = 120, + + /// + /// F15. + /// + F15 = 121, + + /// + /// F16. + /// + F16 = 122, + + /// + /// F17. + /// + F17 = 123, + + /// + /// F18. + /// + F18 = 124, + + /// + /// F19. + /// + F19 = 125, + + /// + /// F20. + /// + F20 = 126, + + /// + /// F21. + /// + F21 = 127, + + /// + /// F22. + /// + F22 = 128, + + /// + /// F23. + /// + F23 = 129, + + /// + /// F24. + /// + F24 = 130, + + /// + /// PrtScr SysRq or Print Screen. + /// + PrintScreen = 131, + + /// + /// Scroll Lock. + /// + ScrollLock = 132, + + /// + /// Pause Break. + /// + Pause = 133, + + + // ########## + // Media Keys + // ########## + + /// + /// Browser Back. + /// Some laptops place this key to the left of the key. + /// + BrowserBack = 134, + + /// + /// Browser Favorites. + /// + BrowserFavorites = 135, + + /// + /// Browser Forward. + /// Some laptops place this key to the right of the key. + /// + BrowserForward = 136, + + /// + /// Browser Home. + /// + BrowserHome = 137, + + /// + /// Browser Refresh. + /// + BrowserRefresh = 138, + + /// + /// Browser Search. + /// + BrowserSearch = 139, + + /// + /// Browser Stop. + /// + BrowserStop = 140, + + /// + /// Eject or . + /// This key is placed in the function section on some Apple keyboards. + /// + Eject = 141, + + /// + /// App 1. + /// Sometimes labelled My Computer on the keyboard. + /// + LaunchApp1 = 142, + + /// + /// App 2. + /// Sometimes labelled Calculator on the keyboard. + /// + LaunchApp2 = 143, + + /// + /// Mail. + /// + LaunchMail = 144, + + /// + /// Media Play/Pause or ⏵⏸. + /// + MediaPlayPause = 145, + + /// + /// Media Select. + /// + MediaSelect = 146, + + /// + /// Media Stop or . + /// + MediaStop = 147, + + /// + /// Media Next or . + /// + MediaTrackNext = 148, + + /// + /// Media Previous or . + /// + MediaTrackPrevious = 149, + + /// + /// Power. + /// + Power = 150, + + /// + /// Sleep. + /// + Sleep = 151, + + /// + /// Volume Down. + /// + AudioVolumeDown = 152, + + /// + /// Mute. + /// + AudioVolumeMute = 153, + + /// + /// Volume Up. + /// + AudioVolumeUp = 154, + + /// + /// Wake Up. + /// + WakeUp = 155, + + + // ########### + // Legacy Keys + // ########### + + /// + /// Again. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Again = 156, + + /// + /// Copy. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Copy = 157, + + /// + /// Cut. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Cut = 158, + + /// + /// Find. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Find = 159, + + /// + /// Open. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Open = 160, + + /// + /// Paste. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Paste = 161, + + /// + /// Props. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Props = 162, + + /// + /// Select. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Select = 163, + + /// + /// Undo. + /// Legacy. + /// Found on Sun’s USB keyboard. + /// + Undo = 164 + +} diff --git a/src/Avalonia.Base/Input/PhysicalKeyExtensions.cs b/src/Avalonia.Base/Input/PhysicalKeyExtensions.cs new file mode 100644 index 00000000000..c834759cadd --- /dev/null +++ b/src/Avalonia.Base/Input/PhysicalKeyExtensions.cs @@ -0,0 +1,394 @@ +namespace Avalonia.Input; + +/// +/// Contains extension methods related to . +/// +public static class PhysicalKeyExtensions +{ + /// + /// Maps a physical key to a corresponding key, if possible, on a QWERTY keyboard. + /// + /// the physical key to map. + /// The key corresponding to , or . + 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 + }; + + /// + /// Maps a physical key to a corresponding key symbol, if possible, on a QWERTY keyboard. + /// + /// the physical key to map. + /// Indicates whether the Shift key is considered pressed. + /// The key corresponding to , or . + 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 + }; +} diff --git a/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs index b80d09e420c..339481ef637 100644 --- a/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Metadata; namespace Avalonia.Input.Raw @@ -11,23 +12,47 @@ public enum RawKeyEventType [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; } } } diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index ac7e8ec1793..f2fef55d288 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -12,6 +12,7 @@ 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 @@ protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, o InputRoot!, key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, (Key)key.Key, - GetAvaloniaRawInputModifiers(key.Modifiers))); + GetAvaloniaRawInputModifiers(key.Modifiers), + (PhysicalKey)key.PhysicalKey, + key.KeySymbol)); }, DispatcherPriority.Input); break; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5d143218fa9..72e02feb157 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -258,7 +258,9 @@ public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResol 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)); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 3b4e457ba51..a2874a38d0c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -247,9 +247,9 @@ void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, ulong timeSta _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 @@ public bool RawTextInputEvent(ulong timeStamp, string text) 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); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 5945c99036c..279d2d82cdc 100644 --- a/src/Avalonia.Native/avn.idl +++ b/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, @@ -591,7 +765,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(); diff --git a/src/Avalonia.Native/regen.sh b/src/Avalonia.Native/regen.sh deleted file mode 100755 index 38f03b7409d..00000000000 --- a/src/Avalonia.Native/regen.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -dotnet msbuild /t:Clean,GenerateSharpGenBindings diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 4a35bb2ca1e..584ab052920 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Avalonia.Remote.Protocol/InputMessages.cs b/src/Avalonia.Remote.Protocol/InputMessages.cs index 6b8cdebbcc9..770bb0eac52 100644 --- a/src/Avalonia.Remote.Protocol/InputMessages.cs +++ b/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 @@ public enum MouseButton public abstract class InputEventMessageBase { - public InputModifiers[] Modifiers { get; set; } + public InputModifiers[]? Modifiers { get; set; } } public abstract class PointerEventMessageBase : InputEventMessageBase @@ -73,12 +76,14 @@ public class KeyEventMessage : InputEventMessageBase { 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; } } diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 890ac0d5ac2..e6ef066572f 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/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 @@ internal unsafe class X11Info 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 @@ public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) { //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 + } } } } diff --git a/src/Avalonia.X11/X11KeyTransform.cs b/src/Avalonia.X11/X11KeyTransform.cs index 6732a0f4480..3d360f53d7e 100644 --- a/src/Avalonia.X11/X11KeyTransform.cs +++ b/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 KeyDic = new Dictionary + // 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 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 s_keyFromX11Key = new(180) { {X11Key.Cancel, Key.Cancel}, {X11Key.BackSpace, Key.Back}, @@ -198,26 +388,15 @@ internal static class X11KeyTransform {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 @@ internal static class X11KeyTransform //{ 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; } } diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index 1264a9dc88e..7a9bfd254a1 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/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 @@ private void InitializeIme() (_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 @@ private async void ProcessNextImeEvent() // 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; } } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index e524eb473d6..197f8836534 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -841,7 +841,8 @@ public void Invalidate(Rect rect) } - public IInputRoot? InputRoot => _inputRoot; + public IInputRoot InputRoot + => _inputRoot ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called"); public void SetInputRoot(IInputRoot inputRoot) { diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index d129e4b0256..3500e3c5a98 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -465,19 +465,31 @@ public enum XLookupStatus : uint } [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 @@ public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, X11PlatformThre 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); } } diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index d05eff00003..0809f3a42cb 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -13,7 +13,6 @@ 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 @@ public bool RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers) 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) diff --git a/src/Browser/Avalonia.Browser/KeyInterop.cs b/src/Browser/Avalonia.Browser/KeyInterop.cs new file mode 100644 index 00000000000..fc81cf8c2cd --- /dev/null +++ b/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 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 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 + }; + } + } +} diff --git a/src/Browser/Avalonia.Browser/Keycodes.cs b/src/Browser/Avalonia.Browser/Keycodes.cs deleted file mode 100644 index 5b6e3a329ac..00000000000 --- a/src/Browser/Avalonia.Browser/Keycodes.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Collections.Generic; - -using Avalonia.Input; - -namespace Avalonia.Browser -{ - internal static class Keycodes - { - public static Dictionary 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 } - }; - } -} diff --git a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs index 91ef9427238..8cc33ea359d 100644 --- a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs +++ b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs @@ -65,21 +65,21 @@ MouseButton TranslateButton(VncButton vncButton) => 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 @@ private bool CheckKeyIsInputModifier(KeyChangedEventArgs args) 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) }; diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs index 3d07cac64e4..9934eded165 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs @@ -43,14 +43,42 @@ public static class HeadlessWindowExtensions /// /// Simulates keyboard press on the headless window/toplevel. /// + [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); + + /// + /// Simulates keyboard press on the headless window/toplevel. + /// + public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, + string? keySymbol) => + RunJobsOnImpl(topLevel, w => w.KeyPress(key, modifiers, physicalKey, keySymbol)); + + /// + /// Simulates keyboard press on the headless window/toplevel, as if typed on a QWERTY keyboard. + /// + public static void KeyPressQwerty(this TopLevel topLevel, PhysicalKey physicalKey, RawInputModifiers modifiers) => + RunJobsOnImpl(topLevel, w => w.KeyPress(physicalKey.ToQwertyKey(), modifiers, physicalKey, physicalKey.ToQwertyKeySymbol())); /// /// Simulates keyboard release on the headless window/toplevel. /// + [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); + + /// + /// Simulates keyboard release on the headless window/toplevel. + /// + public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers, PhysicalKey physicalKey, + string? keySymbol) => + RunJobsOnImpl(topLevel, w => w.KeyRelease(key, modifiers, physicalKey, keySymbol)); + + /// + /// Simulates keyboard release on the headless window/toplevel, as if typed on a QWERTY keyboard. + /// + public static void KeyReleaseQwerty(this TopLevel topLevel, PhysicalKey physicalKey, RawInputModifiers modifiers) => + RunJobsOnImpl(topLevel, w => w.KeyRelease(physicalKey.ToQwertyKey(), modifiers, physicalKey, physicalKey.ToQwertyKeySymbol())); /// /// Simulates a text input event on the headless window/toplevel diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 964934d6cfe..0ccff00c231 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -9,11 +9,8 @@ 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 @@ public ILockedFramebuffer Lock() 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) diff --git a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs index 6d34dbbd4bc..3eeea2bbbaf 100644 --- a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs +++ b/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); diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 495e808a04f..49716f2dba6 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -327,7 +327,7 @@ public void HandleCompositionStart() if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End) { - KeyPress(Key.Delete); + KeyPress(Key.Delete, PhysicalKey.Delete); } } @@ -398,15 +398,15 @@ private static int ToInt32(IntPtr ptr) 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)); } } diff --git a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs index 1ab4c0d2dc1..834feb861ee 100644 --- a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs +++ b/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 { + /// + /// Contains methods used to translate a Windows virtual/physical key to an Avalonia . + /// public static class KeyInterop { - private static readonly Dictionary s_virtualKeyFromKey = new Dictionary + // source: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + private static readonly Dictionary 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 s_keyFromVirtualKey = new Dictionary + private static readonly Dictionary 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 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 }, }; /// @@ -371,56 +384,67 @@ private static bool IsExtended(int keyData) 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; } + /// + /// Gets an Avalonia key from a Windows virtual-key and key data. + /// + /// The Windows virtual-key. + /// The key data (in the same format as lParam for WM_KEYDOWN). + /// An Avalonia key, or if none matched. public static Key KeyFromVirtualKey(int virtualKey, int keyData) { virtualKey = GetVirtualKey(virtualKey, keyData); @@ -430,11 +454,79 @@ public static Key KeyFromVirtualKey(int virtualKey, int keyData) return result; } + /// + /// Gets a Windows virtual-key from an Avalonia key. + /// + /// The Avalonia key. + /// A Windows virtual-key code, or 0 if none matched. public static int VirtualKeyFromKey(Key key) { s_virtualKeyFromKey.TryGetValue(key, out var result); return result; } + + /// + /// Gets a physical Avalonia key from a Windows virtual-key and key data. + /// + /// The Windows virtual-key. + /// The key data (in the same format as lParam for WM_KEYDOWN). + /// An Avalonia physical key, or if none matched. + 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; + } + + /// + /// Gets a key symbol from a Windows virtual-key and key data. + /// + /// The Windows virtual-key. + /// The key data (in the same format as lParam for WM_KEYDOWN). + /// A key symbol, or null if none matched. + 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) + }; + } + } } } diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 4f9b6c54d38..30b3ba7160d 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/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; } } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 226ed2a4061..445bec979e5 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -366,6 +366,8 @@ public enum VirtualKeyStates : int 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 @@ public static extern IntPtr CreateWindowEx( [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 @@ public static extern bool CreateTimerQueueTimer( [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); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 3ad4f194ad5..d521f1ba328 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -151,18 +151,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, 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 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, 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 @@ private static RawInputModifiers GetInputModifiers(PointerFlags flags) 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); + } } } diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs index 6cd59300517..05a3c208c38 100644 --- a/src/iOS/Avalonia.iOS/TextInputResponder.cs +++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs @@ -155,14 +155,15 @@ private void SurroundingTextChanged(object? sender, EventArgs e) } } - 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 @@ void IUIKeyInput.InsertText(string text) if (text == "\n") { - KeyPress(Key.Enter); + KeyPress(Key.Enter, PhysicalKey.Enter, "\r"); switch (ReturnKeyType) { @@ -198,7 +199,7 @@ void IUIKeyInput.InsertText(string text) TextInput(text); } - void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back); + void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back, PhysicalKey.Backspace, "\b"); bool IUIKeyInput.HasText => true; diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs index 325a900316d..c486a66da0e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs @@ -22,7 +22,9 @@ public void Keypresses_Should_Be_Sent_To_Root_If_No_Focused_Element() root.Object, RawKeyEventType.KeyDown, Key.A, - RawInputModifiers.None)); + RawInputModifiers.None, + PhysicalKey.A, + "a")); root.Verify(x => x.RaiseEvent(It.IsAny())); } @@ -49,7 +51,9 @@ public void Keypresses_Should_Be_Sent_To_Focused_Element() root, RawKeyEventType.KeyDown, Key.A, - RawInputModifiers.None)); + RawInputModifiers.None, + PhysicalKey.A, + "a")); Assert.Equal(1, raised); } @@ -121,7 +125,9 @@ public void Can_Change_KeyBindings_In_Keybinding_Event_Handler() root, RawKeyEventType.KeyDown, Key.O, - RawInputModifiers.Control)); + RawInputModifiers.Control, + PhysicalKey.O, + "o")); Assert.Equal(1, raised); } diff --git a/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs index 55a3f0d5d4a..c15fc7b9168 100644 --- a/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs +++ b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs @@ -114,7 +114,9 @@ public void HotKeyedTextBox_Focus_Performed_On_Hotkey() root, RawKeyEventType.KeyDown, Key.F, - RawInputModifiers.Control)); + RawInputModifiers.Control, + PhysicalKey.F, + "f")); Assert.True(hotKeyedTextBox.IsFocused); } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index a611992ab85..8da5265ecc5 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -210,7 +210,11 @@ public void Impl_Input_Should_Pass_Input_To_InputManager() 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)); diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 75a1baad6ed..6d47f2504bc 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -88,7 +88,9 @@ public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factor 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 @@ public void HotKeyManager_Should_Do_Not_Executed_When_IsEnabled_False(string fac 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 @@ void Clickable_Click(object sender, Interactivity.RoutedEventArgs e) root, RawKeyEventType.KeyDown, Key.A, - RawInputModifiers.Control)); + RawInputModifiers.Control, + PhysicalKey.A, + "a")); element.IsEnabled = false; @@ -180,7 +186,9 @@ void Clickable_Click(object sender, Interactivity.RoutedEventArgs e) 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 @@ void Clickable_Click(object sender, Interactivity.RoutedEventArgs e) root, RawKeyEventType.KeyDown, Key.A, - RawInputModifiers.Control)); + RawInputModifiers.Control, + PhysicalKey.A, + "a")); element.IsEnabled = false; @@ -238,7 +248,9 @@ void Clickable_Click(object sender, Interactivity.RoutedEventArgs e) 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.");