Skip to content

Commit

Permalink
Synchronize modifier keys in RawKeyboard.keysPressed with modifier fl…
Browse files Browse the repository at this point in the history
…ags on events. (flutter#43948)

Currently, we listen to keyboard events to find out which keys should be represented in RawKeyboard.instance.keysPressed, but that's not sufficient to represent the physical state of the keys, since modifier keys could have been pressed when the overall app did not have keyboard focus (especially on desktop platforms).

This PR synchronizes the list of modifier keys in keysPressed with the modifier key flags that are present in the raw key event so that they can be relied upon to represent the current state of the keyboard. When synchronizing these states, we don't send any new key events, since they didn't happen when the app had keyboard focus, but if you ask "is this key down", we'll give the right answer
  • Loading branch information
gspencergoog authored and sahandevs committed Nov 15, 2019
1 parent 9e68465 commit d12454f
Show file tree
Hide file tree
Showing 5 changed files with 612 additions and 323 deletions.
88 changes: 86 additions & 2 deletions packages/flutter/lib/src/services/raw_keyboard.dart
Expand Up @@ -3,6 +3,8 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:ui';

import 'package:flutter/foundation.dart';

Expand Down Expand Up @@ -295,8 +297,8 @@ abstract class RawKeyEvent extends Diagnosticable {
);
break;
default:
// We don't yet implement raw key events on iOS or other platforms, but
// we don't hit this exception because the engine never sends us these
// Raw key events are not yet implemented on iOS or other platforms,
// but this exception isn't hit, because the engine never sends these
// messages.
throw FlutterError('Unknown keymap for key events: $keymap');
}
Expand Down Expand Up @@ -506,6 +508,9 @@ class RawKeyboard {
if (event is RawKeyUpEvent) {
_keysPressed.remove(event.logicalKey);
}
// Make sure that the modifiers reflect reality, in case a modifier key was
// pressed/released while the app didn't have focus.
_synchronizeModifiers(event);
if (_listeners.isEmpty) {
return;
}
Expand All @@ -516,6 +521,68 @@ class RawKeyboard {
}
}

static final Map<_ModifierSidePair, Set<LogicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<LogicalKeyboardKey>>{
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft, LogicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.capsLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.capsLock},
const _ModifierSidePair(ModifierKey.numLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.numLock},
const _ModifierSidePair(ModifierKey.scrollLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.scrollLock},
const _ModifierSidePair(ModifierKey.functionModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.fn},
// The symbolModifier doesn't have a key representation on any of the
// platforms, so don't map it here.
};

// The list of all modifier keys that are represented in modifier key bit
// masks on all platforms, so that they can be cleared out of pressedKeys when
// synchronizing.
static final Set<LogicalKeyboardKey> _allModifiers = <LogicalKeyboardKey>{
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.capsLock,
LogicalKeyboardKey.numLock,
LogicalKeyboardKey.scrollLock,
LogicalKeyboardKey.fn,
};

void _synchronizeModifiers(RawKeyEvent event) {
final Map<ModifierKey, KeyboardSide> modifiersPressed = event.data.modifiersPressed;
final Set<LogicalKeyboardKey> modifierKeys = <LogicalKeyboardKey>{};
for (ModifierKey key in modifiersPressed.keys) {
final Set<LogicalKeyboardKey> mappedKeys = _modifierKeyMap[_ModifierSidePair(key, modifiersPressed[key])];
assert(mappedKeys != null,
'Platform key support for ${Platform.operatingSystem} is '
'producing unsupported modifier combinations.');
modifierKeys.addAll(mappedKeys);
}
// Don't send any key events for these changes, since there *should* be
// separate events for each modifier key down/up that occurs while the app
// has focus. This is just to synchronize the modifier keys when they are
// pressed/released while the app doesn't have focus, to make sure that
// _keysPressed reflects reality at all times.
_keysPressed.removeAll(_allModifiers);
_keysPressed.addAll(modifierKeys);
}

final Set<LogicalKeyboardKey> _keysPressed = <LogicalKeyboardKey>{};

/// Returns the set of keys currently pressed.
Expand All @@ -531,3 +598,20 @@ class RawKeyboard {
_keysPressed.clear();
}
}

class _ModifierSidePair extends Object {
const _ModifierSidePair(this.modifier, this.side);

final ModifierKey modifier;
final KeyboardSide side;

@override
bool operator ==(dynamic other) {
return runtimeType == other.runtimeType
&& modifier == other.modifier
&& side == other.side;
}

@override
int get hashCode => hashValues(modifier, side);
}
14 changes: 8 additions & 6 deletions packages/flutter/lib/src/services/raw_keyboard_macos.dart
Expand Up @@ -113,8 +113,8 @@ class RawKeyEventDataMacOs extends RawKeyEventData {
);
}

// This is a non-printable key that we don't know about, so we mint a new
// code with the autogenerated bit set.
// This is a non-printable key that is unrecognized, so a new code is minted
// with the autogenerated bit set.
const int macOsKeyIdPlane = 0x00500000000;

return LogicalKeyboardKey(
Expand Down Expand Up @@ -154,13 +154,15 @@ class RawKeyEventDataMacOs extends RawKeyEventData {
return _isLeftRightModifierPressed(side, independentModifier & modifierCommand, modifierLeftCommand, modifierRightCommand);
case ModifierKey.capsLockModifier:
return independentModifier & modifierCapsLock != 0;
case ModifierKey.numLockModifier:
return independentModifier & modifierNumericPad != 0;
// On macOS, the function modifier bit is set for any function key, like F1,
// F2, etc., but the meaning of ModifierKey.modifierFunction in Flutter is
// that of the Fn modifier key, so there's no good way to emulate that on
// macOS.
case ModifierKey.functionModifier:
return independentModifier & modifierFunction != 0;
case ModifierKey.numLockModifier:
case ModifierKey.symbolModifier:
case ModifierKey.scrollLockModifier:
// These are not used in macOS keyboards.
// These modifier masks are not used in macOS keyboards.
return false;
}
return false;
Expand Down

0 comments on commit d12454f

Please sign in to comment.