event_simulation.dart 39.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:io';
7
import 'dart:ui' as ui;
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/services.dart';
11 12 13 14

import 'binding.dart';
import 'test_async_utils.dart';
import 'widget_tester.dart';
15

16 17 18 19 20 21 22 23 24 25
// A tuple of `key` and `location` from Web's `KeyboardEvent` class.
//
// See [RawKeyEventDataWeb]'s `key` and `location` fields for details.
@immutable
class _WebKeyLocationPair {
  const _WebKeyLocationPair(this.key, this.location);
  final String key;
  final int location;
}

26 27 28 29 30
// TODO(gspencergoog): Replace this with more robust key simulation code once
// the new key event code is in.
// https://github.com/flutter/flutter/issues/33521
// This code can only simulate keys which appear in the key maps.

31
String? _keyLabel(LogicalKeyboardKey key) {
32
  final String keyLabel = key.keyLabel;
33
  if (keyLabel.length == 1) {
34
    return keyLabel.toLowerCase();
35
  }
36
  return null;
37 38
}

39 40
/// A class that serves as a namespace for a bunch of keyboard-key generation
/// utilities.
41
abstract final class KeyEventSimulator {
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
  // Look up a synonym key, and just return the left version of it.
  static LogicalKeyboardKey _getKeySynonym(LogicalKeyboardKey origKey) {
    if (origKey == LogicalKeyboardKey.shift) {
      return LogicalKeyboardKey.shiftLeft;
    }
    if (origKey == LogicalKeyboardKey.alt) {
      return LogicalKeyboardKey.altLeft;
    }
    if (origKey == LogicalKeyboardKey.meta) {
      return LogicalKeyboardKey.metaLeft;
    }
    if (origKey == LogicalKeyboardKey.control) {
      return LogicalKeyboardKey.controlLeft;
    }
    return origKey;
57 58
  }

59 60 61 62 63 64
  static bool _osIsSupported(String platform) {
    switch (platform) {
      case 'android':
      case 'fuchsia':
      case 'macos':
      case 'linux':
65
      case 'web':
66
      case 'ios':
67
      case 'windows':
68 69 70
        return true;
    }
    return false;
71 72
  }

73
  static int _getScanCode(PhysicalKeyboardKey key, String platform) {
74
    assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
75
    late Map<int, PhysicalKeyboardKey> map;
76 77 78 79 80 81 82
    switch (platform) {
      case 'android':
        map = kAndroidToPhysicalKey;
      case 'fuchsia':
        map = kFuchsiaToPhysicalKey;
      case 'macos':
        map = kMacOsToPhysicalKey;
83 84
      case 'ios':
        map = kIosToPhysicalKey;
85 86
      case 'linux':
        map = kLinuxToPhysicalKey;
87 88
      case 'windows':
        map = kWindowsToPhysicalKey;
89
      case 'web':
90 91
        // web doesn't have int type code
        return -1;
92
    }
93
    int? scanCode;
94
    for (final int code in map.keys) {
95
      if (key.usbHidUsage == map[code]!.usbHidUsage) {
96 97 98
        scanCode = code;
        break;
      }
99
    }
100 101
    assert(scanCode != null, 'Physical key for $key not found in $platform scanCode map');
    return scanCode!;
102 103
  }

104 105
  static int _getKeyCode(LogicalKeyboardKey key, String platform) {
    assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
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
    if (kIsWeb) {
      // web doesn't have int type code. This check is used to treeshake
      // keyboard map code.
      return -1;
    } else {
      late Map<int, LogicalKeyboardKey> map;
      switch (platform) {
        case 'android':
          map = kAndroidToLogicalKey;
        case 'fuchsia':
          map = kFuchsiaToLogicalKey;
        case 'macos':
        // macOS doesn't do key codes, just scan codes.
          return -1;
        case 'ios':
        // iOS doesn't do key codes, just scan codes.
          return -1;
        case 'web':
          // web doesn't have int type code.
          return -1;
        case 'linux':
          map = kGlfwToLogicalKey;
        case 'windows':
          map = kWindowsToLogicalKey;
      }
      int? keyCode;
      for (final int code in map.keys) {
        if (key.keyId == map[code]!.keyId) {
          keyCode = code;
          break;
        }
137
      }
138 139
      assert(keyCode != null, 'Key $key not found in $platform keyCode map');
      return keyCode!;
140
    }
141
  }
142

143 144 145 146 147 148 149 150 151 152 153 154 155
  static PhysicalKeyboardKey _inferPhysicalKey(LogicalKeyboardKey key) {
    PhysicalKeyboardKey? result;
    for (final PhysicalKeyboardKey physicalKey in PhysicalKeyboardKey.knownPhysicalKeys) {
      if (physicalKey.debugName == key.debugName) {
        result = physicalKey;
        break;
      }
    }
    assert(result != null, 'Unable to infer physical key for $key');
    return result!;
  }

  static _WebKeyLocationPair _getWebKeyLocation(LogicalKeyboardKey key, String keyLabel) {
156
    String? result;
157
    for (final MapEntry<String, List<LogicalKeyboardKey?>> entry in kWebLocationMap.entries) {
158
      final int foundIndex = entry.value.lastIndexOf(key);
159 160 161 162 163 164 165 166 167 168
      // If foundIndex is -1, then the key is not defined in kWebLocationMap.
      // If foundIndex is 0, then the key is in the standard part of the keyboard,
      // but we have to check `keyLabel` to see if it's remapped or modified.
      if (foundIndex != -1 && foundIndex != 0) {
        return _WebKeyLocationPair(entry.key, foundIndex);
      }
    }
    if (keyLabel.isNotEmpty) {
      return _WebKeyLocationPair(keyLabel, 0);
    }
169
    for (final String code in kWebToLogicalKey.keys) {
170 171 172
      if (key.keyId == kWebToLogicalKey[code]!.keyId) {
        result = code;
        break;
173 174
      }
    }
175
    assert(result != null, 'Key $key not found in web keyCode map');
176 177 178 179 180 181 182 183 184 185 186 187
    return _WebKeyLocationPair(result!, 0);
  }

  static String _getWebCode(PhysicalKeyboardKey key) {
    String? result;
    for (final MapEntry<String, PhysicalKeyboardKey> entry in kWebToPhysicalKey.entries) {
      if (entry.value.usbHidUsage == key.usbHidUsage) {
        result = entry.key;
        break;
      }
    }
    assert(result != null, 'Key $key not found in web code map');
188
    return result!;
189
  }
190

191
  static PhysicalKeyboardKey _findPhysicalKeyByPlatform(LogicalKeyboardKey key, String platform) {
192
    assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
193
    late Map<dynamic, PhysicalKeyboardKey> map;
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    if (kIsWeb) {
      // This check is used to treeshake keymap code.
      map = kWebToPhysicalKey;
    } else {
      switch (platform) {
        case 'android':
          map = kAndroidToPhysicalKey;
        case 'fuchsia':
          map = kFuchsiaToPhysicalKey;
        case 'macos':
          map = kMacOsToPhysicalKey;
        case 'ios':
          map = kIosToPhysicalKey;
        case 'linux':
          map = kLinuxToPhysicalKey;
        case 'web':
          map = kWebToPhysicalKey;
        case 'windows':
          map = kWindowsToPhysicalKey;
      }
214
    }
215
    PhysicalKeyboardKey? result;
216 217
    for (final PhysicalKeyboardKey physicalKey in map.values) {
      if (key.debugName == physicalKey.debugName) {
218 219
        result = physicalKey;
        break;
220 221
      }
    }
222 223
    assert(result != null, 'Physical key for $key not found in $platform physical key map');
    return result!;
224 225
  }

226
  /// Get a raw key data map given a [LogicalKeyboardKey] and a platform.
227
  static Map<String, dynamic> getKeyData(
228
    LogicalKeyboardKey key, {
229
    required String platform,
230
    bool isDown = true,
231
    PhysicalKeyboardKey? physicalKey,
Tong Mu's avatar
Tong Mu committed
232
    String? character,
233
  }) {
234
    assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
235

236
    key = _getKeySynonym(key);
237

238
    // Find a suitable physical key if none was supplied.
239
    physicalKey ??= _findPhysicalKeyByPlatform(key, platform);
240

241
    assert(key.debugName != null);
242

243 244 245 246
    final Map<String, dynamic> result = <String, dynamic>{
      'type': isDown ? 'keydown' : 'keyup',
      'keymap': platform,
    };
247

248
    final String resultCharacter = character ?? _keyLabel(key) ?? '';
Tong Mu's avatar
Tong Mu committed
249
    void assignWeb() {
250 251 252 253 254
      final _WebKeyLocationPair keyLocation = _getWebKeyLocation(key, resultCharacter);
      final PhysicalKeyboardKey actualPhysicalKey = physicalKey ?? _inferPhysicalKey(key);
      result['code'] = _getWebCode(actualPhysicalKey);
      result['key'] = keyLocation.key;
      result['location'] = keyLocation.location;
255
      result['metaState'] = _getWebModifierFlags(key, isDown);
Tong Mu's avatar
Tong Mu committed
256 257 258
    }
    if (kIsWeb) {
      assignWeb();
259 260
      return result;
    }
261 262
    final int keyCode = _getKeyCode(key, platform);
    final int scanCode = _getScanCode(physicalKey, platform);
263

264 265 266
    switch (platform) {
      case 'android':
        result['keyCode'] = keyCode;
Tong Mu's avatar
Tong Mu committed
267 268 269
        if (resultCharacter.isNotEmpty) {
          result['codePoint'] = resultCharacter.codeUnitAt(0);
          result['character'] = resultCharacter;
270
        }
271 272 273
        result['scanCode'] = scanCode;
        result['metaState'] = _getAndroidModifierFlags(key, isDown);
      case 'fuchsia':
274
        result['hidUsage'] = physicalKey.usbHidUsage;
Tong Mu's avatar
Tong Mu committed
275 276
        if (resultCharacter.isNotEmpty) {
          result['codePoint'] = resultCharacter.codeUnitAt(0);
277
        }
278 279 280 281 282 283
        result['modifiers'] = _getFuchsiaModifierFlags(key, isDown);
      case 'linux':
        result['toolkit'] = 'glfw';
        result['keyCode'] = keyCode;
        result['scanCode'] = scanCode;
        result['modifiers'] = _getGlfwModifierFlags(key, isDown);
Tong Mu's avatar
Tong Mu committed
284
        result['unicodeScalarValues'] = resultCharacter.isNotEmpty ? resultCharacter.codeUnitAt(0) : 0;
285 286
      case 'macos':
        result['keyCode'] = scanCode;
Tong Mu's avatar
Tong Mu committed
287 288 289
        if (resultCharacter.isNotEmpty) {
          result['characters'] = resultCharacter;
          result['charactersIgnoringModifiers'] = resultCharacter;
290
        }
291
        result['modifiers'] = _getMacOsModifierFlags(key, isDown);
292 293
      case 'ios':
        result['keyCode'] = scanCode;
Tong Mu's avatar
Tong Mu committed
294 295
        result['characters'] = resultCharacter;
        result['charactersIgnoringModifiers'] = resultCharacter;
296
        result['modifiers'] = _getIOSModifierFlags(key, isDown);
297 298 299
      case 'windows':
        result['keyCode'] = keyCode;
        result['scanCode'] = scanCode;
Tong Mu's avatar
Tong Mu committed
300 301
        if (resultCharacter.isNotEmpty) {
          result['characterCodePoint'] = resultCharacter.codeUnitAt(0);
302
        }
303
        result['modifiers'] = _getWindowsModifierFlags(key, isDown);
Tong Mu's avatar
Tong Mu committed
304 305
      case 'web':
        assignWeb();
306 307
    }
    return result;
308 309
  }

310 311
  static int _getAndroidModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
312
    // ignore: deprecated_member_use
313 314 315 316 317 318 319
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
320
    // ignore: deprecated_member_use
321 322 323
      result |= RawKeyEventDataAndroid.modifierLeftShift | RawKeyEventDataAndroid.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
324
      // ignore: deprecated_member_use
325 326 327
      result |= RawKeyEventDataAndroid.modifierRightShift | RawKeyEventDataAndroid.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
328
      // ignore: deprecated_member_use
329 330 331
      result |= RawKeyEventDataAndroid.modifierLeftMeta | RawKeyEventDataAndroid.modifierMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
332
      // ignore: deprecated_member_use
333 334 335
      result |= RawKeyEventDataAndroid.modifierRightMeta | RawKeyEventDataAndroid.modifierMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
336
      // ignore: deprecated_member_use
337 338 339
      result |= RawKeyEventDataAndroid.modifierLeftControl | RawKeyEventDataAndroid.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
340
      // ignore: deprecated_member_use
341 342 343
      result |= RawKeyEventDataAndroid.modifierRightControl | RawKeyEventDataAndroid.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
344
      // ignore: deprecated_member_use
345 346 347
      result |= RawKeyEventDataAndroid.modifierLeftAlt | RawKeyEventDataAndroid.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
348
      // ignore: deprecated_member_use
349 350 351
      result |= RawKeyEventDataAndroid.modifierRightAlt | RawKeyEventDataAndroid.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.fn)) {
352
      // ignore: deprecated_member_use
353 354 355
      result |= RawKeyEventDataAndroid.modifierFunction;
    }
    if (pressed.contains(LogicalKeyboardKey.scrollLock)) {
356
      // ignore: deprecated_member_use
357 358 359
      result |= RawKeyEventDataAndroid.modifierScrollLock;
    }
    if (pressed.contains(LogicalKeyboardKey.numLock)) {
360
      // ignore: deprecated_member_use
361 362 363
      result |= RawKeyEventDataAndroid.modifierNumLock;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
364
      // ignore: deprecated_member_use
365 366 367
      result |= RawKeyEventDataAndroid.modifierCapsLock;
    }
    return result;
368 369
  }

370 371
  static int _getGlfwModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
372
    // ignore: deprecated_member_use
373 374 375 376 377 378 379
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft) || pressed.contains(LogicalKeyboardKey.shiftRight)) {
380
      // ignore: deprecated_member_use
381 382 383
      result |= GLFWKeyHelper.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight)) {
384
      // ignore: deprecated_member_use
385 386 387
      result |= GLFWKeyHelper.modifierMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight)) {
388
      // ignore: deprecated_member_use
389 390 391
      result |= GLFWKeyHelper.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft) || pressed.contains(LogicalKeyboardKey.altRight)) {
392
      // ignore: deprecated_member_use
393 394 395
      result |= GLFWKeyHelper.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
396
      // ignore: deprecated_member_use
397 398 399
      result |= GLFWKeyHelper.modifierCapsLock;
    }
    return result;
400 401
  }

402 403
  static int _getWindowsModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
404
    // ignore: deprecated_member_use
405 406 407 408 409 410 411
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shift)) {
412
      // ignore: deprecated_member_use
413 414 415
      result |= RawKeyEventDataWindows.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
416
      // ignore: deprecated_member_use
417 418 419
      result |= RawKeyEventDataWindows.modifierLeftShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
420
      // ignore: deprecated_member_use
421 422 423
      result |= RawKeyEventDataWindows.modifierRightShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
424
      // ignore: deprecated_member_use
425 426 427
      result |= RawKeyEventDataWindows.modifierLeftMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
428
      // ignore: deprecated_member_use
429 430 431
      result |= RawKeyEventDataWindows.modifierRightMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.control)) {
432
      // ignore: deprecated_member_use
433 434 435
      result |= RawKeyEventDataWindows.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
436
      // ignore: deprecated_member_use
437 438 439
      result |= RawKeyEventDataWindows.modifierLeftControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
440
      // ignore: deprecated_member_use
441 442 443
      result |= RawKeyEventDataWindows.modifierRightControl;
    }
    if (pressed.contains(LogicalKeyboardKey.alt)) {
444
      // ignore: deprecated_member_use
445 446 447
      result |= RawKeyEventDataWindows.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
448
      // ignore: deprecated_member_use
449 450 451
      result |= RawKeyEventDataWindows.modifierLeftAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
452
      // ignore: deprecated_member_use
453 454 455
      result |= RawKeyEventDataWindows.modifierRightAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
456
      // ignore: deprecated_member_use
457 458 459
      result |= RawKeyEventDataWindows.modifierCaps;
    }
    if (pressed.contains(LogicalKeyboardKey.numLock)) {
460
      // ignore: deprecated_member_use
461 462 463
      result |= RawKeyEventDataWindows.modifierNumLock;
    }
    if (pressed.contains(LogicalKeyboardKey.scrollLock)) {
464
      // ignore: deprecated_member_use
465 466 467 468 469
      result |= RawKeyEventDataWindows.modifierScrollLock;
    }
    return result;
  }

470 471
  static int _getFuchsiaModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
472
    // ignore: deprecated_member_use
473 474 475 476 477 478 479
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
480
      // ignore: deprecated_member_use
481 482 483
      result |= RawKeyEventDataFuchsia.modifierLeftShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
484
      // ignore: deprecated_member_use
485 486 487
      result |= RawKeyEventDataFuchsia.modifierRightShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
488
      // ignore: deprecated_member_use
489 490 491
      result |= RawKeyEventDataFuchsia.modifierLeftMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
492
      // ignore: deprecated_member_use
493 494 495
      result |= RawKeyEventDataFuchsia.modifierRightMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
496
      // ignore: deprecated_member_use
497 498 499
      result |= RawKeyEventDataFuchsia.modifierLeftControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
500
      // ignore: deprecated_member_use
501 502 503
      result |= RawKeyEventDataFuchsia.modifierRightControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
504
      // ignore: deprecated_member_use
505 506 507
      result |= RawKeyEventDataFuchsia.modifierLeftAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
508
      // ignore: deprecated_member_use
509 510 511
      result |= RawKeyEventDataFuchsia.modifierRightAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
512
      // ignore: deprecated_member_use
513 514 515
      result |= RawKeyEventDataFuchsia.modifierCapsLock;
    }
    return result;
516 517
  }

518 519
  static int _getWebModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
520
    // ignore: deprecated_member_use
521 522 523 524 525 526 527
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
528
      // ignore: deprecated_member_use
529 530 531
      result |= RawKeyEventDataWeb.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
532
      // ignore: deprecated_member_use
533 534 535
      result |= RawKeyEventDataWeb.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
536
      // ignore: deprecated_member_use
537 538 539
      result |= RawKeyEventDataWeb.modifierMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
540
      // ignore: deprecated_member_use
541 542 543
      result |= RawKeyEventDataWeb.modifierMeta;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
544
      // ignore: deprecated_member_use
545 546 547
      result |= RawKeyEventDataWeb.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
548
      // ignore: deprecated_member_use
549 550 551
      result |= RawKeyEventDataWeb.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
552
      // ignore: deprecated_member_use
553 554 555
      result |= RawKeyEventDataWeb.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
556
      // ignore: deprecated_member_use
557 558 559
      result |= RawKeyEventDataWeb.modifierAlt;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
560
      // ignore: deprecated_member_use
561 562 563
      result |= RawKeyEventDataWeb.modifierCapsLock;
    }
    if (pressed.contains(LogicalKeyboardKey.numLock)) {
564
      // ignore: deprecated_member_use
565 566 567
      result |= RawKeyEventDataWeb.modifierNumLock;
    }
    if (pressed.contains(LogicalKeyboardKey.scrollLock)) {
568
      // ignore: deprecated_member_use
569 570 571 572 573
      result |= RawKeyEventDataWeb.modifierScrollLock;
    }
    return result;
  }

574 575
  static int _getMacOsModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
576
      // ignore: deprecated_member_use
577 578 579 580 581 582 583
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
584
      // ignore: deprecated_member_use
585 586 587
      result |= RawKeyEventDataMacOs.modifierLeftShift | RawKeyEventDataMacOs.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
588
      // ignore: deprecated_member_use
589 590 591
      result |= RawKeyEventDataMacOs.modifierRightShift | RawKeyEventDataMacOs.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
592
      // ignore: deprecated_member_use
593 594 595
      result |= RawKeyEventDataMacOs.modifierLeftCommand | RawKeyEventDataMacOs.modifierCommand;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
596
      // ignore: deprecated_member_use
597 598 599
      result |= RawKeyEventDataMacOs.modifierRightCommand | RawKeyEventDataMacOs.modifierCommand;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
600
      // ignore: deprecated_member_use
601 602 603
      result |= RawKeyEventDataMacOs.modifierLeftControl | RawKeyEventDataMacOs.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
604
      // ignore: deprecated_member_use
605 606 607
      result |= RawKeyEventDataMacOs.modifierRightControl | RawKeyEventDataMacOs.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
608
      // ignore: deprecated_member_use
609 610 611
      result |= RawKeyEventDataMacOs.modifierLeftOption | RawKeyEventDataMacOs.modifierOption;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
612
      // ignore: deprecated_member_use
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
      result |= RawKeyEventDataMacOs.modifierRightOption | RawKeyEventDataMacOs.modifierOption;
    }
    final Set<LogicalKeyboardKey> functionKeys = <LogicalKeyboardKey>{
      LogicalKeyboardKey.f1,
      LogicalKeyboardKey.f2,
      LogicalKeyboardKey.f3,
      LogicalKeyboardKey.f4,
      LogicalKeyboardKey.f5,
      LogicalKeyboardKey.f6,
      LogicalKeyboardKey.f7,
      LogicalKeyboardKey.f8,
      LogicalKeyboardKey.f9,
      LogicalKeyboardKey.f10,
      LogicalKeyboardKey.f11,
      LogicalKeyboardKey.f12,
      LogicalKeyboardKey.f13,
      LogicalKeyboardKey.f14,
      LogicalKeyboardKey.f15,
      LogicalKeyboardKey.f16,
      LogicalKeyboardKey.f17,
      LogicalKeyboardKey.f18,
      LogicalKeyboardKey.f19,
      LogicalKeyboardKey.f20,
      LogicalKeyboardKey.f21,
    };
    if (pressed.intersection(functionKeys).isNotEmpty) {
639
      // ignore: deprecated_member_use
640 641 642
      result |= RawKeyEventDataMacOs.modifierFunction;
    }
    if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) {
643
      // ignore: deprecated_member_use
644 645 646
      result |= RawKeyEventDataMacOs.modifierNumericPad;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
647
      // ignore: deprecated_member_use
648 649 650
      result |= RawKeyEventDataMacOs.modifierCapsLock;
    }
    return result;
651
  }
652

653 654
  static int _getIOSModifierFlags(LogicalKeyboardKey newKey, bool isDown) {
    int result = 0;
655
    // ignore: deprecated_member_use
656 657 658 659 660 661 662
    final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;
    if (isDown) {
      pressed.add(newKey);
    } else {
      pressed.remove(newKey);
    }
    if (pressed.contains(LogicalKeyboardKey.shiftLeft)) {
663
      // ignore: deprecated_member_use
664 665 666
      result |= RawKeyEventDataIos.modifierLeftShift | RawKeyEventDataIos.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.shiftRight)) {
667
      // ignore: deprecated_member_use
668 669 670
      result |= RawKeyEventDataIos.modifierRightShift | RawKeyEventDataIos.modifierShift;
    }
    if (pressed.contains(LogicalKeyboardKey.metaLeft)) {
671
      // ignore: deprecated_member_use
672 673 674
      result |= RawKeyEventDataIos.modifierLeftCommand | RawKeyEventDataIos.modifierCommand;
    }
    if (pressed.contains(LogicalKeyboardKey.metaRight)) {
675
      // ignore: deprecated_member_use
676 677 678
      result |= RawKeyEventDataIos.modifierRightCommand | RawKeyEventDataIos.modifierCommand;
    }
    if (pressed.contains(LogicalKeyboardKey.controlLeft)) {
679
      // ignore: deprecated_member_use
680 681 682
      result |= RawKeyEventDataIos.modifierLeftControl | RawKeyEventDataIos.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.controlRight)) {
683
      // ignore: deprecated_member_use
684 685 686
      result |= RawKeyEventDataIos.modifierRightControl | RawKeyEventDataIos.modifierControl;
    }
    if (pressed.contains(LogicalKeyboardKey.altLeft)) {
687
      // ignore: deprecated_member_use
688 689 690
      result |= RawKeyEventDataIos.modifierLeftOption | RawKeyEventDataIos.modifierOption;
    }
    if (pressed.contains(LogicalKeyboardKey.altRight)) {
691
      // ignore: deprecated_member_use
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
      result |= RawKeyEventDataIos.modifierRightOption | RawKeyEventDataIos.modifierOption;
    }
    final Set<LogicalKeyboardKey> functionKeys = <LogicalKeyboardKey>{
      LogicalKeyboardKey.f1,
      LogicalKeyboardKey.f2,
      LogicalKeyboardKey.f3,
      LogicalKeyboardKey.f4,
      LogicalKeyboardKey.f5,
      LogicalKeyboardKey.f6,
      LogicalKeyboardKey.f7,
      LogicalKeyboardKey.f8,
      LogicalKeyboardKey.f9,
      LogicalKeyboardKey.f10,
      LogicalKeyboardKey.f11,
      LogicalKeyboardKey.f12,
      LogicalKeyboardKey.f13,
      LogicalKeyboardKey.f14,
      LogicalKeyboardKey.f15,
      LogicalKeyboardKey.f16,
      LogicalKeyboardKey.f17,
      LogicalKeyboardKey.f18,
      LogicalKeyboardKey.f19,
      LogicalKeyboardKey.f20,
      LogicalKeyboardKey.f21,
    };
    if (pressed.intersection(functionKeys).isNotEmpty) {
718
      // ignore: deprecated_member_use
719 720 721
      result |= RawKeyEventDataIos.modifierFunction;
    }
    if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) {
722
      // ignore: deprecated_member_use
723 724 725
      result |= RawKeyEventDataIos.modifierNumericPad;
    }
    if (pressed.contains(LogicalKeyboardKey.capsLock)) {
726
      // ignore: deprecated_member_use
727 728 729 730 731
      result |= RawKeyEventDataIos.modifierCapsLock;
    }
    return result;
  }

732
  static Future<bool> _simulateKeyEventByRawEvent(ValueGetter<Map<String, dynamic>> buildKeyData) async {
733 734
    return TestAsyncUtils.guard<bool>(() async {
      final Completer<bool> result = Completer<bool>();
735
      await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
736
        SystemChannels.keyEvent.name,
737
        SystemChannels.keyEvent.codec.encodeMessage(buildKeyData()),
738 739 740 741 742
        (ByteData? data) {
          if (data == null) {
            result.complete(false);
            return;
          }
743 744
          final Map<String, Object?> decoded = SystemChannels.keyEvent.codec.decodeMessage(data)! as Map<String, dynamic>;
          result.complete(decoded['handled']! as bool);
745 746 747 748
        }
      );
      return result.future;
    });
Tong Mu's avatar
Tong Mu committed
749 750
  }

751
  static final Map<String, PhysicalKeyboardKey> _debugNameToPhysicalKey = (() {
752 753 754
    final Map<String, PhysicalKeyboardKey> result = <String, PhysicalKeyboardKey>{};
    for (final PhysicalKeyboardKey key in PhysicalKeyboardKey.knownPhysicalKeys) {
      final String? debugName = key.debugName;
755
      if (debugName != null) {
756
        result[debugName] = key;
757
      }
758 759 760 761 762 763 764 765 766
    }
    return result;
  })();
  static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key) {
    final PhysicalKeyboardKey? result = _debugNameToPhysicalKey[key.debugName];
    assert(result != null, 'Physical key for $key not found in known physical keys');
    return result!;
  }

767
  // ignore: deprecated_member_use
768 769 770 771 772 773 774 775 776 777
  static const KeyDataTransitMode _defaultTransitMode = KeyDataTransitMode.rawKeyData;

  // The simulation transit mode for [simulateKeyDownEvent], [simulateKeyUpEvent],
  // and [simulateKeyRepeatEvent].
  //
  // Simulation transit mode is the mode that simulated key events are constructed
  // and delivered. For detailed introduction, see [KeyDataTransitMode] and
  // its values.
  //
  // The `_transitMode` defaults to [KeyDataTransitMode.rawKeyEvent], and can be
778
  // overridden with [debugKeyEventSimulatorTransitModeOverride]. In widget tests, it
779
  // is often set with [KeySimulationModeVariant].
780
  // ignore: deprecated_member_use
781
  static KeyDataTransitMode get _transitMode {
782
    // ignore: deprecated_member_use
783 784
    KeyDataTransitMode? result;
    assert(() {
785
      // ignore: deprecated_member_use
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
      result = debugKeyEventSimulatorTransitModeOverride;
      return true;
    }());
    return result ?? _defaultTransitMode;
  }

  static String get _defaultPlatform => kIsWeb ? 'web' : Platform.operatingSystem;

  /// Simulates sending a hardware key down event.
  ///
  /// This only simulates key presses coming from a physical keyboard, not from a
  /// soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type of
801
  /// system. Defaults to the operating system that the test is running on.
802 803 804 805 806 807 808 809 810 811 812 813 814 815
  ///
  /// Keys that are down when the test completes are cleared after each test.
  ///
  /// Returns true if the key event was handled by the framework.
  ///
  /// See also:
  ///
  ///  * [simulateKeyUpEvent] to simulate the corresponding key up event.
  static Future<bool> simulateKeyDownEvent(
    LogicalKeyboardKey key, {
    String? platform,
    PhysicalKeyboardKey? physicalKey,
    String? character,
  }) async {
816
    Future<bool> simulateByRawEvent() {
817 818
      return _simulateKeyEventByRawEvent(() {
        platform ??= _defaultPlatform;
819
        return getKeyData(key, platform: platform!, physicalKey: physicalKey, character: character);
820 821 822
      });
    }
    switch (_transitMode) {
823
      // ignore: deprecated_member_use
824
      case KeyDataTransitMode.rawKeyData:
825
        return simulateByRawEvent();
826
      // ignore: deprecated_member_use
827 828
      case KeyDataTransitMode.keyDataThenRawKeyData:
        final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
829
        // ignore: deprecated_member_use
830
        final bool resultByKeyEvent = ServicesBinding.instance.keyEventManager.handleKeyData(
831 832 833 834 835 836 837 838 839
          ui.KeyData(
            type: ui.KeyEventType.down,
            physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
            logical: logicalKey.keyId,
            timeStamp: Duration.zero,
            character: character ?? _keyLabel(key),
            synthesized: false,
          ),
        );
840
        return (await simulateByRawEvent()) || resultByKeyEvent;
841 842 843
    }
  }

844 845 846 847 848 849 850
  /// Simulates sending a hardware key up event through the system channel.
  ///
  /// This only simulates key presses coming from a physical keyboard, not from a
  /// soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type of
851
  /// system. Defaults to the operating system that the test is running on.
852
  ///
853 854
  /// Returns true if the key event was handled by the framework.
  ///
855 856
  /// See also:
  ///
857 858 859 860 861 862
  ///  * [simulateKeyDownEvent] to simulate the corresponding key down event.
  static Future<bool> simulateKeyUpEvent(
    LogicalKeyboardKey key, {
    String? platform,
    PhysicalKeyboardKey? physicalKey,
  }) async {
863
    Future<bool> simulateByRawEvent() {
864 865 866 867 868 869
      return _simulateKeyEventByRawEvent(() {
        platform ??= _defaultPlatform;
        return getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey);
      });
    }
    switch (_transitMode) {
870
      // ignore: deprecated_member_use
871
      case KeyDataTransitMode.rawKeyData:
872
        return simulateByRawEvent();
873
      // ignore: deprecated_member_use
874 875
      case KeyDataTransitMode.keyDataThenRawKeyData:
        final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
876
        // ignore: deprecated_member_use
877
        final bool resultByKeyEvent = ServicesBinding.instance.keyEventManager.handleKeyData(
878 879 880 881 882 883 884 885 886
          ui.KeyData(
            type: ui.KeyEventType.up,
            physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
            logical: logicalKey.keyId,
            timeStamp: Duration.zero,
            character: null,
            synthesized: false,
          ),
        );
887
        return (await simulateByRawEvent()) || resultByKeyEvent;
888 889
    }
  }
890

891 892 893 894 895 896 897
  /// Simulates sending a hardware key repeat event through the system channel.
  ///
  /// This only simulates key presses coming from a physical keyboard, not from a
  /// soft keyboard.
  ///
  /// Specify `platform` as one of the platforms allowed in
  /// [Platform.operatingSystem] to make the event appear to be from that type of
898
  /// system. Defaults to the operating system that the test is running on.
899 900 901 902 903 904 905 906 907 908 909 910
  ///
  /// Returns true if the key event was handled by the framework.
  ///
  /// See also:
  ///
  ///  * [simulateKeyDownEvent] to simulate the corresponding key down event.
  static Future<bool> simulateKeyRepeatEvent(
    LogicalKeyboardKey key, {
    String? platform,
    PhysicalKeyboardKey? physicalKey,
    String? character,
  }) async {
911
    Future<bool> simulateByRawEvent() {
912 913
      return _simulateKeyEventByRawEvent(() {
        platform ??= _defaultPlatform;
914
        return getKeyData(key, platform: platform!, physicalKey: physicalKey, character: character);
915 916 917
      });
    }
    switch (_transitMode) {
918
      // ignore: deprecated_member_use
919
      case KeyDataTransitMode.rawKeyData:
920
        return simulateByRawEvent();
921
      // ignore: deprecated_member_use
922 923
      case KeyDataTransitMode.keyDataThenRawKeyData:
        final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
924
        // ignore: deprecated_member_use
925
        final bool resultByKeyEvent = ServicesBinding.instance.keyEventManager.handleKeyData(
926 927 928 929 930 931 932 933 934
          ui.KeyData(
            type: ui.KeyEventType.repeat,
            physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
            logical: logicalKey.keyId,
            timeStamp: Duration.zero,
            character: character ?? _keyLabel(key),
            synthesized: false,
          ),
        );
935
        return (await simulateByRawEvent()) || resultByKeyEvent;
936
    }
937 938 939 940 941
  }
}

/// Simulates sending a hardware key down event through the system channel.
///
942 943
/// It is intended for use in writing tests.
///
944
/// This only simulates key presses coming from a physical keyboard, not from a
945 946
/// soft keyboard, and it can only simulate keys that appear in the key maps
/// such as [kAndroidToLogicalKey], [kMacOsToPhysicalKey], etc.
947 948 949
///
/// Specify `platform` as one of the platforms allowed in
/// [Platform.operatingSystem] to make the event appear to be from that type of
950
/// system. Defaults to the operating system that the test is running on.
951 952 953
///
/// Keys that are down when the test completes are cleared after each test.
///
954 955
/// Returns true if the key event was handled by the framework.
///
956 957
/// See also:
///
958 959 960 961 962 963 964
///  * [simulateKeyUpEvent] and [simulateKeyRepeatEvent] to simulate the
///    corresponding key up and repeat event.
Future<bool> simulateKeyDownEvent(
  LogicalKeyboardKey key, {
  String? platform,
  PhysicalKeyboardKey? physicalKey,
  String? character,
965 966 967 968 969 970 971
}) async {
  final bool handled = await KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey, character: character);
  final ServicesBinding binding = ServicesBinding.instance;
  if (!handled && binding is TestWidgetsFlutterBinding) {
    await binding.testTextInput.handleKeyDownEvent(key);
  }
  return handled;
972 973 974 975
}

/// Simulates sending a hardware key up event through the system channel.
///
976 977
/// It is intended for use in writing tests.
///
978
/// This only simulates key presses coming from a physical keyboard, not from a
979 980
/// soft keyboard, and it can only simulate keys that appear in the key maps
/// such as [kAndroidToLogicalKey], [kMacOsToPhysicalKey], etc.
981 982 983
///
/// Specify `platform` as one of the platforms allowed in
/// [Platform.operatingSystem] to make the event appear to be from that type of
984
/// system. Defaults to the operating system that the test is running on.
985
///
986 987
/// Returns true if the key event was handled by the framework.
///
988 989
/// See also:
///
990 991 992 993 994 995
///  * [simulateKeyDownEvent] and [simulateKeyRepeatEvent] to simulate the
///    corresponding key down and repeat event.
Future<bool> simulateKeyUpEvent(
  LogicalKeyboardKey key, {
  String? platform,
  PhysicalKeyboardKey? physicalKey,
996 997 998 999 1000 1001 1002
}) async {
  final bool handled = await KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
  final ServicesBinding binding = ServicesBinding.instance;
  if (!handled && binding is TestWidgetsFlutterBinding) {
    await binding.testTextInput.handleKeyUpEvent(key);
  }
  return handled;
1003
}
1004 1005 1006 1007 1008 1009 1010 1011

/// Simulates sending a hardware key repeat event through the system channel.
///
/// This only simulates key presses coming from a physical keyboard, not from a
/// soft keyboard.
///
/// Specify `platform` as one of the platforms allowed in
/// [Platform.operatingSystem] to make the event appear to be from that type of
1012
/// system. Defaults to the operating system that the test is running on.
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
///
/// Returns true if the key event was handled by the framework.
///
/// See also:
///
///  - [simulateKeyDownEvent] and [simulateKeyUpEvent] to simulate the
///    corresponding key down and up event.
Future<bool> simulateKeyRepeatEvent(
  LogicalKeyboardKey key, {
  String? platform,
  PhysicalKeyboardKey? physicalKey,
  String? character,
}) {
  return KeyEventSimulator.simulateKeyRepeatEvent(key, platform: platform, physicalKey: physicalKey, character: character);
}

/// A [TestVariant] that runs tests with transit modes set to different values
/// of [KeyDataTransitMode].
1031 1032 1033 1034
@Deprecated(
  'No longer supported. Transit mode is always key data only. '
  'This feature was deprecated after v3.18.0-2.0.pre.',
)
1035 1036
class KeySimulatorTransitModeVariant extends TestVariant<KeyDataTransitMode> {
  /// Creates a [KeySimulatorTransitModeVariant] that tests the given [values].
1037 1038 1039 1040
  @Deprecated(
    'No longer supported. Transit mode is always key data only. '
    'This feature was deprecated after v3.18.0-2.0.pre.',
  )
1041 1042 1043 1044
  const KeySimulatorTransitModeVariant(this.values);

  /// Creates a [KeySimulatorTransitModeVariant] for each value option of
  /// [KeyDataTransitMode].
1045 1046 1047 1048
  @Deprecated(
    'No longer supported. Transit mode is always key data only. '
    'This feature was deprecated after v3.18.0-2.0.pre.',
  )
1049 1050 1051 1052 1053
  KeySimulatorTransitModeVariant.all()
    : this(KeyDataTransitMode.values.toSet());

  /// Creates a [KeySimulatorTransitModeVariant] that only contains
  /// [KeyDataTransitMode.keyDataThenRawKeyData].
1054 1055 1056 1057
  @Deprecated(
    'No longer supported. Transit mode is always key data only. '
    'This feature was deprecated after v3.18.0-2.0.pre.',
  )
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
  KeySimulatorTransitModeVariant.keyDataThenRawKeyData()
    : this(<KeyDataTransitMode>{KeyDataTransitMode.keyDataThenRawKeyData});

  @override
  final Set<KeyDataTransitMode> values;

  @override
  String describeValue(KeyDataTransitMode value) {
    switch (value) {
      case KeyDataTransitMode.rawKeyData:
        return 'RawKeyEvent';
      case KeyDataTransitMode.keyDataThenRawKeyData:
        return 'ui.KeyData then RawKeyEvent';
    }
  }

  @override
  Future<KeyDataTransitMode?> setUp(KeyDataTransitMode value) async {
    final KeyDataTransitMode? previousSetting = debugKeyEventSimulatorTransitModeOverride;
    debugKeyEventSimulatorTransitModeOverride = value;
    return previousSetting;
  }

  @override
  Future<void> tearDown(KeyDataTransitMode value, KeyDataTransitMode? memento) async {
    // ignore: invalid_use_of_visible_for_testing_member
    RawKeyboard.instance.clearKeysPressed();
    // ignore: invalid_use_of_visible_for_testing_member
    HardwareKeyboard.instance.clearState();
    // ignore: invalid_use_of_visible_for_testing_member
1088
    ServicesBinding.instance.keyEventManager.clearState();
1089 1090 1091
    debugKeyEventSimulatorTransitModeOverride = memento;
  }
}