// 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. part of dart.ui; /// The type of a key event. // Must match the KeyEventType enum in ui/window/key_data.h. enum KeyEventType { /// The key is pressed. down, /// The key is released. up, /// The key is held, causing a repeated key input. repeat; // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. // ignore: public_member_api_docs String get label { return switch (this) { down => 'Key Down', up => 'Key Up', repeat => 'Key Repeat', }; } } /// The source device for the key event. /// /// Not all platforms supply an accurate type. // Must match the KeyEventDeviceType enum in ui/window/key_data.h. enum KeyEventDeviceType { /// The device is a keyboard. keyboard, /// The device is a directional pad on something like a television remote /// control or similar. directionalPad, /// The device is a gamepad button gamepad, /// The device is a joystick button joystick, /// The device is a device connected to an HDMI bus. hdmi; // TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917. // ignore: public_member_api_docs String get label { return switch (this) { keyboard => 'Keyboard', directionalPad => 'Directional Pad', gamepad => 'Gamepad', joystick => 'Joystick', hdmi => 'HDMI', }; } } /// Information about a key event. class KeyData { /// Creates an object that represents a key event. const KeyData({ required this.timeStamp, required this.type, required this.physical, required this.logical, required this.character, required this.synthesized, this.deviceType = KeyEventDeviceType.keyboard, }); /// Time of event dispatch, relative to an arbitrary timeline. /// /// For synthesized events, the [timeStamp] might not be the actual time that /// the key press or release happens. final Duration timeStamp; /// The type of the event. final KeyEventType type; /// Describes what type of device (keyboard, directional pad, etc.) this event /// originated from. final KeyEventDeviceType deviceType; /// The key code for the physical key that has changed. final int physical; /// The key code for the logical key that has changed. final int logical; /// Character input from the event. /// /// Ignored for up events. final String? character; /// If [synthesized] is true, this event does not correspond to a native /// event. /// /// Although most of Flutter's keyboard events are transformed from native /// events, some events are not based on native events, and are synthesized /// only to conform Flutter's key event model (as documented in the /// `HardwareKeyboard` class in the framework). /// /// For example, some key downs or ups might be lost when the window loses /// focus. Some platforms provide ways to query whether a key is being held. /// If the embedder detects an inconsistency between its internal record and /// the state returned by the system, the embedder will synthesize a /// corresponding event to synchronize the state without breaking the event /// model. /// /// As another example, macOS treats CapsLock in a special way by sending down /// and up events at the down of alternate presses to indicate the direction /// in which the lock is toggled instead of that the physical key is going. A /// macOS embedder should normalize the behavior by converting a native down /// event into a down event followed immediately by a synthesized up event, /// and the native up event also into a down event followed immediately by a /// synthesized up event. /// /// Synthesized events do not have a trustworthy [timeStamp], and should not /// be processed as if the key actually went down or up at the time of the /// callback. /// /// [KeyRepeatEvent] is never synthesized. final bool synthesized; // Returns the bits that are not included in [valueMask], shifted to the // right. // // For example, if the input is 0x12abcdabcd, then the result is 0x12. // // This is mostly equivalent to a right shift, resolving the problem that // JavaScript only support 32-bit bitwise operations and needs to use division // instead. static int _nonValueBits(int n) { const valueMask = 0x000FFFFFFFF; // `n >> valueMaskWidth` is equivalent to `n / divisorForValueMask`. const int divisorForValueMask = valueMask + 1; const valueMaskWidth = 32; // Equivalent to assert(divisorForValueMask == (1 << valueMaskWidth)). const firstDivisorWidth = 28; assert( divisorForValueMask == (1 << firstDivisorWidth) * (1 << (valueMaskWidth - firstDivisorWidth)), ); // JS only supports up to 2^53 - 1, therefore non-value bits can only // contain (maxSafeIntegerWidth - valueMaskWidth) bits. const maxSafeIntegerWidth = 52; const int nonValueMask = (1 << (maxSafeIntegerWidth - valueMaskWidth)) - 1; if (identical(0, 0.0)) { // Detects if we are on the web. return (n / divisorForValueMask).floor() & nonValueMask; } else { return (n >> valueMaskWidth) & nonValueMask; } } String _logicalToString() { final result = '0x${logical.toRadixString(16)}'; final int planeNum = _nonValueBits(logical) & 0x0FF; final String planeDescription = (() { switch (planeNum) { case 0x000: return ' (Unicode)'; case 0x001: return ' (Unprintable)'; case 0x002: return ' (Flutter)'; case 0x011: return ' (Android)'; case 0x012: return ' (Fuchsia)'; case 0x013: return ' (iOS)'; case 0x014: return ' (macOS)'; case 0x015: return ' (GTK)'; case 0x016: return ' (Windows)'; case 0x017: return ' (Web)'; case 0x018: return ' (GLFW)'; } return ''; })(); return '$result$planeDescription'; } String? _escapeCharacter() { if (character == null) { return ''; } switch (character!) { case '\n': return r'"\n"'; case '\t': return r'"\t"'; case '\r': return r'"\r"'; case '\b': return r'"\b"'; case '\f': return r'"\f"'; default: return '"$character"'; } } String? _quotedCharCode() { if (character == null) { return ''; } final Iterable hexChars = character!.codeUnits.map( (int code) => code.toRadixString(16).padLeft(2, '0'), ); return ' (0x${hexChars.join(' ')})'; } @override String toString() { return 'KeyData(${type.label}, ' 'physical: 0x${physical.toRadixString(16)}, ' 'logical: ${_logicalToString()}, ' 'character: ${_escapeCharacter()}${_quotedCharCode()}' '${synthesized ? ', synthesized' : ''}'; } /// Returns a complete textual description of the information in this object. String toStringFull() { return '$runtimeType(type: ${type.label}, ' 'deviceType: ${deviceType.label}, ' 'timeStamp: $timeStamp, ' 'physical: 0x${physical.toRadixString(16)}, ' 'logical: 0x${logical.toRadixString(16)}, ' 'character: ${_escapeCharacter()}, ' 'synthesized: $synthesized' ')'; } }