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