Flutter Windows Embedder
keyboard_key_embedder_handler.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <windows.h>
8 
9 #include <chrono>
10 #include <string>
11 
12 #include "flutter/fml/logging.h"
14 
15 namespace flutter {
16 
17 namespace {
18 // An arbitrary size for the character cache in bytes.
19 //
20 // It should hold a UTF-32 character encoded in UTF-8 as well as the trailing
21 // '\0'.
22 constexpr size_t kCharacterCacheSize = 8;
23 
24 constexpr SHORT kStateMaskToggled = 0x01;
25 constexpr SHORT kStateMaskPressed = 0x80;
26 
27 const char* empty_character = "";
28 
29 // Get some bits of the char, from the start'th bit from the right (excluded)
30 // to the end'th bit from the right (included).
31 //
32 // For example, _GetBit(0x1234, 8, 4) => 0x3.
33 char _GetBit(char32_t ch, size_t start, size_t end) {
34  return (ch >> end) & ((1 << (start - end)) - 1);
35 }
36 } // namespace
37 
38 std::string ConvertChar32ToUtf8(char32_t ch) {
39  std::string result;
40  FML_DCHECK(0 <= ch && ch <= 0x10FFFF) << "Character out of range";
41  if (ch <= 0x007F) {
42  result.push_back(ch);
43  } else if (ch <= 0x07FF) {
44  result.push_back(0b11000000 + _GetBit(ch, 11, 6));
45  result.push_back(0b10000000 + _GetBit(ch, 6, 0));
46  } else if (ch <= 0xFFFF) {
47  result.push_back(0b11100000 + _GetBit(ch, 16, 12));
48  result.push_back(0b10000000 + _GetBit(ch, 12, 6));
49  result.push_back(0b10000000 + _GetBit(ch, 6, 0));
50  } else {
51  result.push_back(0b11110000 + _GetBit(ch, 21, 18));
52  result.push_back(0b10000000 + _GetBit(ch, 18, 12));
53  result.push_back(0b10000000 + _GetBit(ch, 12, 6));
54  result.push_back(0b10000000 + _GetBit(ch, 6, 0));
55  }
56  return result;
57 }
58 
60  SendEventHandler send_event,
61  GetKeyStateHandler get_key_state,
62  MapVirtualKeyToScanCode map_virtual_key_to_scan_code)
63  : perform_send_event_(send_event),
64  get_key_state_(get_key_state),
65  response_id_(1) {
66  InitCriticalKeys(map_virtual_key_to_scan_code);
67 }
68 
70 
71 static bool isEasciiPrintable(int codeUnit) {
72  return (codeUnit <= 0x7f && codeUnit >= 0x20) ||
73  (codeUnit <= 0xff && codeUnit >= 0x80);
74 }
75 
76 // Converts upper letters to lower letters in ASCII and extended ASCII, and
77 // returns as-is otherwise.
78 //
79 // Independent of locale.
80 static uint64_t toLower(uint64_t n) {
81  constexpr uint64_t lower_a = 0x61;
82  constexpr uint64_t upper_a = 0x41;
83  constexpr uint64_t upper_z = 0x5a;
84 
85  constexpr uint64_t lower_a_grave = 0xe0;
86  constexpr uint64_t upper_a_grave = 0xc0;
87  constexpr uint64_t upper_thorn = 0xde;
88  constexpr uint64_t division = 0xf7;
89 
90  // ASCII range.
91  if (n >= upper_a && n <= upper_z) {
92  return n - upper_a + lower_a;
93  }
94 
95  // EASCII range.
96  if (n >= upper_a_grave && n <= upper_thorn && n != division) {
97  return n - upper_a_grave + lower_a_grave;
98  }
99 
100  return n;
101 }
102 
103 // Transform scancodes sent by windows to scancodes written in Chromium spec.
104 static uint16_t normalizeScancode(int windowsScanCode, bool extended) {
105  // In Chromium spec the extended bit is shown as 0xe000 bit,
106  // e.g. PageUp is represented as 0xe049.
107  return (windowsScanCode & 0xff) | (extended ? 0xe000 : 0);
108 }
109 
110 uint64_t KeyboardKeyEmbedderHandler::ApplyPlaneToId(uint64_t id,
111  uint64_t plane) {
112  return (id & valueMask) | plane;
113 }
114 
115 uint64_t KeyboardKeyEmbedderHandler::GetPhysicalKey(int scancode,
116  bool extended) {
117  int chromiumScancode = normalizeScancode(scancode, extended);
118  auto resultIt = windowsToPhysicalMap_.find(chromiumScancode);
119  if (resultIt != windowsToPhysicalMap_.end())
120  return resultIt->second;
121  return ApplyPlaneToId(scancode, windowsPlane);
122 }
123 
124 uint64_t KeyboardKeyEmbedderHandler::GetLogicalKey(int key,
125  bool extended,
126  int scancode) {
127  if (key == VK_PROCESSKEY) {
128  return VK_PROCESSKEY;
129  }
130 
131  // Normally logical keys should only be derived from key codes, but since some
132  // key codes are either 0 or ambiguous (multiple keys using the same key
133  // code), these keys are resolved by scan codes.
134  auto numpadIter =
135  scanCodeToLogicalMap_.find(normalizeScancode(scancode, extended));
136  if (numpadIter != scanCodeToLogicalMap_.cend())
137  return numpadIter->second;
138 
139  // Check if the keyCode is one we know about and have a mapping for.
140  auto logicalIt = windowsToLogicalMap_.find(key);
141  if (logicalIt != windowsToLogicalMap_.cend())
142  return logicalIt->second;
143 
144  // Upper case letters should be normalized into lower case letters.
145  if (isEasciiPrintable(key)) {
146  return ApplyPlaneToId(toLower(key), unicodePlane);
147  }
148 
149  return ApplyPlaneToId(toLower(key), windowsPlane);
150 }
151 
152 void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
153  int key,
154  int scancode,
155  int action,
156  char32_t character,
157  bool extended,
158  bool was_down,
159  std::function<void(bool)> callback) {
160  const uint64_t physical_key = GetPhysicalKey(scancode, extended);
161  const uint64_t logical_key = GetLogicalKey(key, extended, scancode);
162  FML_DCHECK(action == WM_KEYDOWN || action == WM_KEYUP ||
163  action == WM_SYSKEYDOWN || action == WM_SYSKEYUP);
164 
165  auto last_logical_record_iter = pressingRecords_.find(physical_key);
166  bool had_record = last_logical_record_iter != pressingRecords_.end();
167  uint64_t last_logical_record =
168  had_record ? last_logical_record_iter->second : 0;
169 
170  // The logical key for the current "tap sequence".
171  //
172  // Key events are formed in tap sequences: down, repeats, up. The logical key
173  // stays consistent throughout a tap sequence, which is this value.
174  uint64_t sequence_logical_key =
175  had_record ? last_logical_record : logical_key;
176 
177  if (sequence_logical_key == VK_PROCESSKEY) {
178  // VK_PROCESSKEY means that the key press is used by an IME. These key
179  // presses are considered handled and not sent to Flutter. These events must
180  // be filtered by result_logical_key because the key up event of such
181  // presses uses the "original" logical key.
182  callback(true);
183  return;
184  }
185 
186  const bool is_event_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN;
187 
188  bool event_key_can_be_repeat = true;
189  UpdateLastSeenCriticalKey(key, physical_key, sequence_logical_key);
190  // Synchronize the toggled states of critical keys (such as whether CapsLocks
191  // is enabled). Toggled states can only be changed upon a down event, so if
192  // the recorded toggled state does not match the true state, this function
193  // will synthesize (an up event if the key is recorded pressed, then) a down
194  // event.
195  //
196  // After this function, all critical keys will have their toggled state
197  // updated to the true state, while the critical keys whose toggled state have
198  // been changed will be pressed regardless of their true pressed state.
199  // Updating the pressed state will be done by
200  // SynchronizeCriticalPressedStates.
201  SynchronizeCriticalToggledStates(key, is_event_down,
202  &event_key_can_be_repeat);
203  // Synchronize the pressed states of critical keys (such as whether CapsLocks
204  // is pressed).
205  //
206  // After this function, all critical keys except for the target key will have
207  // their toggled state and pressed state matched with their true states. The
208  // target key's pressed state will be updated immediately after this.
209  SynchronizeCriticalPressedStates(key, physical_key, is_event_down,
210  event_key_can_be_repeat);
211 
212  // Reassess the last logical record in case pressingRecords_ was modified
213  // by the above synchronization methods.
214  last_logical_record_iter = pressingRecords_.find(physical_key);
215  had_record = last_logical_record_iter != pressingRecords_.end();
216  last_logical_record = had_record ? last_logical_record_iter->second : 0;
217 
218  // The resulting event's `type`.
219  FlutterKeyEventType type;
221  char character_bytes[kCharacterCacheSize];
222  // What pressingRecords_[physical_key] should be after the KeyboardHookImpl
223  // returns (0 if the entry should be removed).
224  uint64_t eventual_logical_record;
225  uint64_t result_logical_key;
226 
227  if (is_event_down) {
228  if (had_record && !was_down) {
229  // Windows delivered a new down event without a matching up event. This
230  // happens if the window lost focus before it could receive the key up.
231  // Synthesize an up event so the framework releases the key before
232  // processing the incoming down.
233  SendSynthesizeUpEvent(physical_key, last_logical_record);
234  last_logical_record_iter = pressingRecords_.find(physical_key);
235  had_record = last_logical_record_iter != pressingRecords_.end();
236  last_logical_record = had_record ? last_logical_record_iter->second : 0;
237  }
238 
239  if (had_record) {
240  // A normal repeated key.
241  type = kFlutterKeyEventTypeRepeat;
242  FML_DCHECK(had_record);
243  ConvertUtf32ToUtf8_(character_bytes, character);
244  eventual_logical_record = last_logical_record;
245  result_logical_key = last_logical_record;
246  } else {
247  // A normal down event (whether the system event is a repeat or not).
248  type = kFlutterKeyEventTypeDown;
249  FML_DCHECK(!had_record);
250  ConvertUtf32ToUtf8_(character_bytes, character);
251  eventual_logical_record = logical_key;
252  result_logical_key = logical_key;
253  }
254  } else { // isPhysicalDown is false
255  if (last_logical_record == 0) {
256  // The physical key has been released before. It might indicate a missed
257  // event due to loss of focus, or multiple keyboards pressed keys with the
258  // same physical key. Ignore the up event.
259  callback(true);
260  return;
261  } else {
262  // A normal up event.
263  type = kFlutterKeyEventTypeUp;
264  FML_DCHECK(had_record);
265  // Up events never have character.
266  character_bytes[0] = '\0';
267  eventual_logical_record = 0;
268  result_logical_key = last_logical_record;
269  }
270  }
271 
272  if (eventual_logical_record != 0) {
273  pressingRecords_[physical_key] = eventual_logical_record;
274  } else {
275  auto record_iter = pressingRecords_.find(physical_key);
276  // Assert this in debug mode. But in cases that it doesn't satisfy
277  // (such as due to a bug), make sure the `erase` is only called
278  // with a valid value to avoid crashing.
279  if (record_iter != pressingRecords_.end()) {
280  pressingRecords_.erase(record_iter);
281  } else {
282  FML_DCHECK(false);
283  }
284  }
285 
286  FlutterKeyEvent key_data{
287  .struct_size = sizeof(FlutterKeyEvent),
288  .timestamp = static_cast<double>(
289  std::chrono::duration_cast<std::chrono::microseconds>(
290  std::chrono::high_resolution_clock::now().time_since_epoch())
291  .count()),
292  .type = type,
293  .physical = physical_key,
294  .logical = result_logical_key,
295  .character = character_bytes,
296  .synthesized = false,
297  };
298 
299  response_id_ += 1;
300  uint64_t response_id = response_id_;
301  PendingResponse pending{
302  .callback =
303  [this, callback = std::move(callback)](bool handled,
304  uint64_t response_id) {
305  auto found = pending_responses_.find(response_id);
306  if (found != pending_responses_.end()) {
307  pending_responses_.erase(found);
308  }
309  callback(handled);
310  },
311  .response_id = response_id,
312  };
313  auto pending_ptr = std::make_unique<PendingResponse>(std::move(pending));
314  pending_responses_[response_id] = std::move(pending_ptr);
315  SendEvent(key_data, KeyboardKeyEmbedderHandler::HandleResponse,
316  reinterpret_cast<void*>(pending_responses_[response_id].get()));
317 
318  // Post-event synchronization. It is useful in cases where the true pressing
319  // state does not match the event type. For example, a CapsLock down event is
320  // received despite that GetKeyState says that CapsLock is not pressed. In
321  // such case, post-event synchronization will synthesize a CapsLock up event
322  // after the main event.
323  SynchronizeCriticalPressedStates(key, physical_key, is_event_down,
324  event_key_can_be_repeat);
325 }
326 
328  int key,
329  int scancode,
330  int action,
331  char32_t character,
332  bool extended,
333  bool was_down,
334  std::function<void(bool)> callback) {
335  sent_any_events = false;
336  KeyboardHookImpl(key, scancode, action, character, extended, was_down,
337  std::move(callback));
338  if (!sent_any_events) {
339  FlutterKeyEvent empty_event{
340  .struct_size = sizeof(FlutterKeyEvent),
341  .timestamp = static_cast<double>(
342  std::chrono::duration_cast<std::chrono::microseconds>(
343  std::chrono::high_resolution_clock::now().time_since_epoch())
344  .count()),
345  .type = kFlutterKeyEventTypeDown,
346  .physical = 0,
347  .logical = 0,
348  .character = empty_character,
349  .synthesized = false,
350  };
351  SendEvent(empty_event, nullptr, nullptr);
352  }
353 }
354 
355 std::map<uint64_t, uint64_t> KeyboardKeyEmbedderHandler::GetPressedState() {
356  return pressingRecords_;
357 }
358 
359 void KeyboardKeyEmbedderHandler::UpdateLastSeenCriticalKey(
360  int virtual_key,
361  uint64_t physical_key,
362  uint64_t logical_key) {
363  auto found = critical_keys_.find(virtual_key);
364  if (found != critical_keys_.end()) {
365  found->second.physical_key = physical_key;
366  found->second.logical_key = logical_key;
367  }
368 }
369 
370 void KeyboardKeyEmbedderHandler::SynchronizeCriticalToggledStates(
371  int event_virtual_key,
372  bool is_event_down,
373  bool* event_key_can_be_repeat) {
374  // NowState ----------------> PreEventState --------------> TrueState
375  // Synchronization Event
376  for (auto& kv : critical_keys_) {
377  UINT virtual_key = kv.first;
378  CriticalKey& key_info = kv.second;
379  if (key_info.physical_key == 0) {
380  // Never seen this key.
381  continue;
382  }
383  FML_DCHECK(key_info.logical_key != 0);
384 
385  // Check toggling state first, because it might alter pressing state.
386  if (key_info.check_toggled) {
387  const bool target_is_pressed =
388  pressingRecords_.find(key_info.physical_key) !=
389  pressingRecords_.end();
390  // The togglable keys observe a 4-phase cycle:
391  //
392  // Phase# 0 1 2 3
393  // Event Down Up Down Up
394  // Pressed 0 1 0 1
395  // Toggled 0 1 1 0
396  const bool true_toggled = get_key_state_(virtual_key) & kStateMaskToggled;
397  bool pre_event_toggled = true_toggled;
398  // Check if the main event's key is the key being checked. If it's the
399  // non-repeat down event, toggle the state.
400  if (virtual_key == event_virtual_key && !target_is_pressed &&
401  is_event_down) {
402  pre_event_toggled = !pre_event_toggled;
403  }
404  if (key_info.toggled_on != pre_event_toggled) {
405  // If the key is pressed, release it first.
406  if (target_is_pressed) {
407  SendEvent(SynthesizeSimpleEvent(
408  kFlutterKeyEventTypeUp, key_info.physical_key,
409  key_info.logical_key, empty_character),
410  nullptr, nullptr);
411  }
412  // Synchronizing toggle state always ends with the key being pressed.
413  pressingRecords_[key_info.physical_key] = key_info.logical_key;
414  SendEvent(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown,
415  key_info.physical_key,
416  key_info.logical_key, empty_character),
417  nullptr, nullptr);
418  *event_key_can_be_repeat = false;
419  }
420  key_info.toggled_on = true_toggled;
421  }
422  }
423 }
424 
425 void KeyboardKeyEmbedderHandler::SynchronizeCriticalPressedStates(
426  int event_virtual_key,
427  int event_physical_key,
428  bool is_event_down,
429  bool event_key_can_be_repeat) {
430  // During an incoming event, there might be a synthesized Flutter event for
431  // each key of each pressing goal, followed by an eventual main Flutter
432  // event.
433  //
434  // NowState ----------------> PreEventState --------------> TrueState
435  // Synchronization Event
436  //
437  // The goal of the synchronization algorithm is to derive a pre-event state
438  // that can satisfy the true state (`true_pressed`) after the event, and that
439  // requires as few synthesized events based on the current state
440  // (`now_pressed`) as possible.
441  for (auto& kv : critical_keys_) {
442  UINT virtual_key = kv.first;
443  CriticalKey& key_info = kv.second;
444  if (key_info.physical_key == 0) {
445  // Never seen this key.
446  continue;
447  }
448  FML_DCHECK(key_info.logical_key != 0);
449  if (key_info.check_pressed) {
450  SHORT true_pressed = get_key_state_(virtual_key) & kStateMaskPressed;
451  auto pressing_record_iter = pressingRecords_.find(key_info.physical_key);
452  bool now_pressed = pressing_record_iter != pressingRecords_.end();
453  bool pre_event_pressed = true_pressed;
454  // Check if the main event is the key being checked to get the correct
455  // target state.
456  if (is_event_down) {
457  // For down events, this key is the event key if they have the same
458  // virtual key, because virtual key represents "functionality."
459  //
460  // In that case, normally Flutter should synthesize nothing since the
461  // resulting event can adapt to the current state by dispatching either
462  // a down or a repeat event. However, in certain cases (when Flutter has
463  // just synchronized the key's toggling state) the event must not be a
464  // repeat event.
465  if (virtual_key == event_virtual_key) {
466  if (event_key_can_be_repeat) {
467  continue;
468  } else {
469  pre_event_pressed = false;
470  }
471  }
472  } else {
473  // For up events, this key is the event key if they have the same
474  // physical key, because it is necessary to ensure that the physical
475  // key is correctly released.
476  //
477  // In that case, although the previous state should be pressed, don't
478  // synthesize a down event even if it's not. The later code will handle
479  // such cases by skipping abrupt up events. Obviously don't synthesize
480  // up events either.
481  if (event_physical_key == key_info.physical_key) {
482  continue;
483  }
484  }
485  if (now_pressed != pre_event_pressed) {
486  if (now_pressed) {
487  pressingRecords_.erase(pressing_record_iter);
488  } else {
489  pressingRecords_[key_info.physical_key] = key_info.logical_key;
490  }
491  const char* empty_character = "";
492  SendEvent(
493  SynthesizeSimpleEvent(
494  now_pressed ? kFlutterKeyEventTypeUp : kFlutterKeyEventTypeDown,
495  key_info.physical_key, key_info.logical_key, empty_character),
496  nullptr, nullptr);
497  }
498  }
499  }
500 }
501 
503  // TODO(bleroux): consider exposing these constants in flutter_key_map.g.cc?
504  const uint64_t physical_shift_left =
505  windowsToPhysicalMap_.at(kScanCodeShiftLeft);
506  const uint64_t physical_shift_right =
507  windowsToPhysicalMap_.at(kScanCodeShiftRight);
508  const uint64_t logical_shift_left =
509  windowsToLogicalMap_.at(kKeyCodeShiftLeft);
510  const uint64_t physical_control_left =
511  windowsToPhysicalMap_.at(kScanCodeControlLeft);
512  const uint64_t physical_control_right =
513  windowsToPhysicalMap_.at(kScanCodeControlRight);
514  const uint64_t logical_control_left =
515  windowsToLogicalMap_.at(kKeyCodeControlLeft);
516 
517  bool shift_pressed = (modifiers_state & kShift) != 0;
518  SynthesizeIfNeeded(physical_shift_left, physical_shift_right,
519  logical_shift_left, shift_pressed);
520  bool control_pressed = (modifiers_state & kControl) != 0;
521  SynthesizeIfNeeded(physical_control_left, physical_control_right,
522  logical_control_left, control_pressed);
523 }
524 
525 void KeyboardKeyEmbedderHandler::SynthesizeIfNeeded(uint64_t physical_left,
526  uint64_t physical_right,
527  uint64_t logical_left,
528  bool is_pressed) {
529  auto pressing_record_iter_left = pressingRecords_.find(physical_left);
530  bool left_pressed = pressing_record_iter_left != pressingRecords_.end();
531  auto pressing_record_iter_right = pressingRecords_.find(physical_right);
532  bool right_pressed = pressing_record_iter_right != pressingRecords_.end();
533  bool already_pressed = left_pressed || right_pressed;
534  bool synthesize_down = is_pressed && !already_pressed;
535  bool synthesize_up = !is_pressed && already_pressed;
536 
537  if (synthesize_down) {
538  SendSynthesizeDownEvent(physical_left, logical_left);
539  }
540 
541  if (synthesize_up && left_pressed) {
542  uint64_t known_logical = pressing_record_iter_left->second;
543  SendSynthesizeUpEvent(physical_left, known_logical);
544  }
545 
546  if (synthesize_up && right_pressed) {
547  uint64_t known_logical = pressing_record_iter_right->second;
548  SendSynthesizeUpEvent(physical_right, known_logical);
549  }
550 }
551 
552 void KeyboardKeyEmbedderHandler::SendSynthesizeDownEvent(uint64_t physical,
553  uint64_t logical) {
554  SendEvent(
555  SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, physical, logical, ""),
556  nullptr, nullptr);
557  pressingRecords_[physical] = logical;
558 };
559 
560 void KeyboardKeyEmbedderHandler::SendSynthesizeUpEvent(uint64_t physical,
561  uint64_t logical) {
562  SendEvent(
563  SynthesizeSimpleEvent(kFlutterKeyEventTypeUp, physical, logical, ""),
564  nullptr, nullptr);
565  pressingRecords_.erase(physical);
566 };
567 
568 void KeyboardKeyEmbedderHandler::HandleResponse(bool handled, void* user_data) {
569  PendingResponse* pending = reinterpret_cast<PendingResponse*>(user_data);
570  auto callback = std::move(pending->callback);
571  callback(handled, pending->response_id);
572 }
573 
574 void KeyboardKeyEmbedderHandler::InitCriticalKeys(
575  MapVirtualKeyToScanCode map_virtual_key_to_scan_code) {
576  auto createCheckedKey = [this, &map_virtual_key_to_scan_code](
577  UINT virtual_key, bool extended,
578  bool check_pressed,
579  bool check_toggled) -> CriticalKey {
580  UINT scan_code = map_virtual_key_to_scan_code(virtual_key, extended);
581  return CriticalKey{
582  .physical_key = GetPhysicalKey(scan_code, extended),
583  .logical_key = GetLogicalKey(virtual_key, extended, scan_code),
584  .check_pressed = check_pressed || check_toggled,
585  .check_toggled = check_toggled,
586  .toggled_on = check_toggled
587  ? !!(get_key_state_(virtual_key) & kStateMaskToggled)
588  : false,
589  };
590  };
591 
592  critical_keys_.emplace(VK_LSHIFT,
593  createCheckedKey(VK_LSHIFT, false, true, false));
594  critical_keys_.emplace(VK_RSHIFT,
595  createCheckedKey(VK_RSHIFT, false, true, false));
596  critical_keys_.emplace(VK_LCONTROL,
597  createCheckedKey(VK_LCONTROL, false, true, false));
598  critical_keys_.emplace(VK_RCONTROL,
599  createCheckedKey(VK_RCONTROL, true, true, false));
600  critical_keys_.emplace(VK_LMENU,
601  createCheckedKey(VK_LMENU, false, true, false));
602  critical_keys_.emplace(VK_RMENU,
603  createCheckedKey(VK_RMENU, true, true, false));
604  critical_keys_.emplace(VK_LWIN, createCheckedKey(VK_LWIN, true, true, false));
605  critical_keys_.emplace(VK_RWIN, createCheckedKey(VK_RWIN, true, true, false));
606  critical_keys_.emplace(VK_CAPITAL,
607  createCheckedKey(VK_CAPITAL, false, true, true));
608  critical_keys_.emplace(VK_SCROLL,
609  createCheckedKey(VK_SCROLL, false, true, true));
610  critical_keys_.emplace(VK_NUMLOCK,
611  createCheckedKey(VK_NUMLOCK, true, true, true));
612 }
613 
614 void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) {
615  if (ch == 0) {
616  out[0] = '\0';
617  return;
618  }
619  std::string result = ConvertChar32ToUtf8(ch);
620  strcpy_s(out, kCharacterCacheSize, result.c_str());
621 }
622 
623 FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent(
624  FlutterKeyEventType type,
625  uint64_t physical,
626  uint64_t logical,
627  const char* character) {
628  return FlutterKeyEvent{
629  .struct_size = sizeof(FlutterKeyEvent),
630  .timestamp = static_cast<double>(
631  std::chrono::duration_cast<std::chrono::microseconds>(
632  std::chrono::high_resolution_clock::now().time_since_epoch())
633  .count()),
634  .type = type,
635  .physical = physical,
636  .logical = logical,
637  .character = character,
638  .synthesized = true,
639  };
640 }
641 
642 void KeyboardKeyEmbedderHandler::SendEvent(const FlutterKeyEvent& event,
643  FlutterKeyEventCallback callback,
644  void* user_data) {
645  sent_any_events = true;
646  perform_send_event_(event, callback, user_data);
647 }
648 
649 } // namespace flutter
std::function< void(const FlutterKeyEvent &, FlutterKeyEventCallback, void *)> SendEventHandler
void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down, std::function< void(bool)> callback) override
std::function< SHORT(UINT, bool)> MapVirtualKeyToScanCode
std::map< uint64_t, uint64_t > GetPressedState() override
void SyncModifiersIfNeeded(int modifiers_state) override
KeyboardKeyEmbedderHandler(SendEventHandler send_event, GetKeyStateHandler get_key_state, MapVirtualKeyToScanCode map_vk_to_scan)
FlutterDesktopBinaryReply callback
enum flutter::testing::@98::KeyboardChange::Type type
constexpr int kShift
constexpr int kKeyCodeShiftLeft
constexpr int kScanCodeShiftRight
constexpr int kScanCodeShiftLeft
constexpr int kScanCodeControlRight
std::string ConvertChar32ToUtf8(char32_t ch)
static uint16_t normalizeScancode(int windowsScanCode, bool extended)
static uint64_t toLower(uint64_t n)
static bool isEasciiPrintable(int codeUnit)
constexpr int kScanCodeControlLeft
constexpr int kKeyCodeControlLeft
constexpr int kControl
uint32_t UndeadChar(uint32_t ch)