diff --git a/lib/src/cef_web_view.dart b/lib/src/cef_web_view.dart index 462ddd5..f141ff8 100644 --- a/lib/src/cef_web_view.dart +++ b/lib/src/cef_web_view.dart @@ -597,12 +597,20 @@ class _CefWebViewState extends State _isWindows ? wkc : (cefMacNativeKeyCode(event.physicalKey) ?? wkc); final ch = event.character; final isText = ch != null && _isPrintable(ch); - // Editing / navigation keys MUST carry the macOS NSEvent character or CEF - // OSR double-applies them (one Backspace deletes two, one arrow moves two). - // macOS-only: the table holds NSEvent codepoints (incl. private-use - // 0xF7xx function-key values) that would corrupt a Windows key event — - // Windows CEF derives the character from the VK itself, so send 0 there. - final keyChar = _isWindows ? 0 : cefMacCharForKey(event.logicalKey); + // Every key MUST carry its macOS NSEvent character. Editing/navigation keys + // because CEF OSR otherwise double-applies them (one Backspace deletes two, + // one arrow moves two); printable keys because a zero character pair makes + // CEF build a FlagsChanged event, which costs the page `e.key` entirely — + // see [cefMacCharForKey]. The typed character wins when there is one, so a + // shifted key reports `A` like a browser rather than the unshifted `a`. + // macOS-only: these are NSEvent codepoints (incl. private-use 0xF7xx + // function-key values) that would corrupt a Windows key event — Windows CEF + // derives the character from the VK itself, so send 0 there. + final keyChar = _isWindows + ? 0 + : (ch != null && isText + ? ch.codeUnitAt(0) + : cefMacCharForKey(event.logicalKey)); // The page should see keydown→keypress→keyup for every character, like a // browser. We always send RAWKEYDOWN/KEYUP; the keypress (CHAR) is // synthesized by [_commitText] when the IME's insertText delivers a typed diff --git a/packages/flutter_cef_platform_interface/lib/src/cef_input.dart b/packages/flutter_cef_platform_interface/lib/src/cef_input.dart index fe152e0..e1b7ede 100644 --- a/packages/flutter_cef_platform_interface/lib/src/cef_input.dart +++ b/packages/flutter_cef_platform_interface/lib/src/cef_input.dart @@ -169,11 +169,44 @@ final Map kCefMacKeyChars = { LogicalKeyboardKey.end: 0xF72B, LogicalKeyboardKey.pageUp: 0xF72C, LogicalKeyboardKey.pageDown: 0xF72D, + LogicalKeyboardKey.f1: 0xF704, // NSF1FunctionKey … NSF12FunctionKey + LogicalKeyboardKey.f2: 0xF705, + LogicalKeyboardKey.f3: 0xF706, + LogicalKeyboardKey.f4: 0xF707, + LogicalKeyboardKey.f5: 0xF708, + LogicalKeyboardKey.f6: 0xF709, + LogicalKeyboardKey.f7: 0xF70A, + LogicalKeyboardKey.f8: 0xF70B, + LogicalKeyboardKey.f9: 0xF70C, + LogicalKeyboardKey.f10: 0xF70D, + LogicalKeyboardKey.f11: 0xF70E, + LogicalKeyboardKey.f12: 0xF70F, }; -/// The macOS NSEvent character for an editing/navigation [key], or 0 for keys -/// whose text rides the IME/CHAR path. See [kCefMacKeyChars]. -int cefMacCharForKey(LogicalKeyboardKey key) => kCefMacKeyChars[key] ?? 0; +/// The macOS NSEvent character for [key]: the table above, then the key's own +/// printable codepoint. +/// +/// A printable key must carry a character too — 0 for *both* `character` and +/// `unmodified_character` does not mean "no character" to CEF, it means a +/// different event. `TranslateWebKeyEvent` treats that pair as +/// `NSEventTypeFlagsChanged`, and Chromium's `DomKeyFromNSEvent` answers a +/// FlagsChanged event with `DomKeyFromKeyCode()`, which maps only the modifier +/// keys — so every printable keydown reached the page as +/// `e.key === 'Unidentified'`, breaking any page-level shortcut. (`e.code` +/// survived because `DomCodeFromNSEvent` reads `keyCode` whatever the type, +/// which is why the symptom looked like a partial keyboard rather than none.) +/// +/// [LogicalKeyboardKey.keyId] is the lowercase Unicode value for a character +/// key and follows the active layout, so it is exactly the unmodified +/// character macOS would report. +int cefMacCharForKey(LogicalKeyboardKey key) { + final mapped = kCefMacKeyChars[key]; + if (mapped != null) return mapped; + final id = key.keyId; + // Above the Unicode range are Flutter's synthetic plane ids (modifiers, media + // keys); those genuinely have no character and stay 0. + return (id > 0x20 && id < 0x7F) || (id > 0x7F && id <= 0x10FFFF) ? id : 0; +} /// The Windows virtual-key code for [key]: the special-key table first, then /// a→VK_A..z→VK_Z, A–Z, and 0–9. 0 if unmapped (a printable that rides CHAR). diff --git a/test/cef_input_test.dart b/test/cef_input_test.dart index 6bb1250..f7792f8 100644 --- a/test/cef_input_test.dart +++ b/test/cef_input_test.dart @@ -71,9 +71,33 @@ void main() { expect(cefMacCharForKey(key), isNonZero); // the whole point }); }); - test('printable keys carry no override char (-> 0; they ride the IME)', () { - expect(cefMacCharForKey(LogicalKeyboardKey.keyA), 0); - expect(cefMacCharForKey(LogicalKeyboardKey.digit5), 0); + // A printable key must carry its character too, though it rides the IME for + // text. Zero for BOTH character fields is not "no character" to CEF: it is + // the FlagsChanged branch of TranslateWebKeyEvent, and Chromium answers a + // FlagsChanged event with DomKeyFromKeyCode(), which maps only modifiers — + // so the page saw `e.key === 'Unidentified'` for every printable keydown. + test('printable keys carry their own codepoint (e.key, not Unidentified)', + () { + expect(cefMacCharForKey(LogicalKeyboardKey.keyA), 0x61); // 'a' + expect(cefMacCharForKey(LogicalKeyboardKey.digit5), 0x35); // '5' + expect(cefMacCharForKey(LogicalKeyboardKey.slash), 0x2F); // '/' + }); + test('function keys carry their NSEvent private-use codepoint', () { + expect(cefMacCharForKey(LogicalKeyboardKey.f1), 0xF704); + expect(cefMacCharForKey(LogicalKeyboardKey.f12), 0xF70F); + }); + // Modifiers are the one case where FlagsChanged is the CORRECT event, and + // DomKeyFromKeyCode does map them — so these must stay 0. + test('modifier keys stay 0 — FlagsChanged is right for them', () { + for (final key in [ + LogicalKeyboardKey.shiftLeft, + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.altLeft, + LogicalKeyboardKey.metaLeft, + LogicalKeyboardKey.capsLock, + ]) { + expect(cefMacCharForKey(key), 0, reason: key.debugName); + } }); });