Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
4e04e3ff
Unverified
Commit
4e04e3ff
authored
Jan 20, 2022
by
Tong Mu
Committed by
GitHub
Jan 20, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Keyboard] Dispatch solitary synthesized `KeyEvent`s (#96874)
parent
2cdef81e
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
148 additions
and
35 deletions
+148
-35
hardware_keyboard.dart
packages/flutter/lib/src/services/hardware_keyboard.dart
+58
-32
raw_keyboard.dart
packages/flutter/lib/src/services/raw_keyboard.dart
+5
-1
focus_manager.dart
packages/flutter/lib/src/widgets/focus_manager.dart
+2
-2
hardware_keyboard_test.dart
packages/flutter/test/services/hardware_keyboard_test.dart
+83
-0
No files found.
packages/flutter/lib/src/services/hardware_keyboard.dart
View file @
4e04e3ff
...
...
@@ -633,13 +633,15 @@ enum KeyDataTransitMode {
/// platform, every native message might result in multiple [KeyEvent]s. For
/// example, this might happen in order to synthesize missed modifier key
/// presses or releases.
///
/// A [KeyMessage] bundles all information related to a native key message
/// together for the convenience of propagation on the [FocusNode] tree.
///
/// When dispatched to handlers or listeners, or propagated through the
/// [FocusNode] tree, all handlers or listeners belonging to a node are
/// executed regardless of their [KeyEventResult], and all results are combined
/// into the result of the node using [combineKeyEventResults].
/// into the result of the node using [combineKeyEventResults]. Empty [events]
/// or [rawEvent] should be considered as a result of [KeyEventResult.ignored].
///
/// In very rare cases, a native key message might not result in a [KeyMessage].
/// For example, key messages for Fn key are ignored on macOS for the
...
...
@@ -671,13 +673,16 @@ class KeyMessage {
/// form as [RawKeyEvent]. Their stream is not as regular as [KeyEvent]'s,
/// but keeps as much native information and structure as possible.
///
/// The [rawEvent] will be deprecated in the future.
/// The [rawEvent] field might be empty, for example, when the event
/// converting system dispatches solitary synthesized events.
///
/// The [rawEvent] field will be deprecated in the future.
///
/// See also:
///
/// * [RawKeyboard.addListener], [RawKeyboardListener], [Focus.onKey],
/// where [RawKeyEvent]s are commonly used.
final
RawKeyEvent
rawEvent
;
final
RawKeyEvent
?
rawEvent
;
@override
String
toString
()
{
...
...
@@ -787,19 +792,60 @@ class KeyEventManager {
assert
(
false
,
'Should never encounter KeyData when transitMode is rawKeyData.'
);
return
false
;
case
KeyDataTransitMode
.
keyDataThenRawKeyData
:
assert
((
data
.
physical
==
0
&&
data
.
logical
==
0
)
||
(
data
.
physical
!=
0
&&
data
.
logical
!=
0
));
// Postpone key event dispatching until the handleRawKeyMessage.
//
// Having 0 as the physical or logical ID indicates an empty key data,
// transmitted to ensure that the transit mode is correctly inferred.
if
(
data
.
physical
!=
0
&&
data
.
logical
!=
0
)
{
_keyEventsSinceLastMessage
.
add
(
_eventFromData
(
data
));
// Having 0 as the physical and logical ID indicates an empty key data
// (the only occassion either field can be 0,) transmitted to ensure
// that the transit mode is correctly inferred. These events should be
// ignored.
if
(
data
.
physical
==
0
&&
data
.
logical
==
0
)
{
return
false
;
}
assert
(
data
.
physical
!=
0
&&
data
.
logical
!=
0
);
final
KeyEvent
event
=
_eventFromData
(
data
);
if
(
data
.
synthesized
&&
_keyEventsSinceLastMessage
.
isEmpty
)
{
// Dispatch the event instantly if both conditions are met:
//
// - The event is synthesized, therefore the result does not matter.
// - The current queue is empty, therefore the order does not matter.
//
// This allows solitary synthesized `KeyEvent`s to be dispatched,
// since they won't be followed by `RawKeyEvent`s.
_hardwareKeyboard
.
handleKeyEvent
(
event
);
_dispatchKeyMessage
(<
KeyEvent
>[
event
],
null
);
}
else
{
// Otherwise, postpone key event dispatching until the next raw
// event. Normal key presses always send 0 or more `KeyEvent`s first,
// then 1 `RawKeyEvent`.
_keyEventsSinceLastMessage
.
add
(
event
);
}
return
false
;
}
}
bool
_dispatchKeyMessage
(
List
<
KeyEvent
>
keyEvents
,
RawKeyEvent
?
rawEvent
)
{
if
(
keyMessageHandler
!=
null
)
{
final
KeyMessage
message
=
KeyMessage
(
keyEvents
,
rawEvent
);
try
{
return
keyMessageHandler
!(
message
);
}
catch
(
exception
,
stack
)
{
InformationCollector
?
collector
;
assert
(()
{
collector
=
()
=>
<
DiagnosticsNode
>[
DiagnosticsProperty
<
KeyMessage
>(
'KeyMessage'
,
message
),
];
return
true
;
}());
FlutterError
.
reportError
(
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'services library'
,
context:
ErrorDescription
(
'while processing the key message handler'
),
informationCollector:
collector
,
));
}
}
return
false
;
}
/// Handles a raw key message.
///
/// This method is the handler to [SystemChannels.keyEvent], processing
...
...
@@ -826,27 +872,7 @@ class KeyEventManager {
'while HardwareKeyboard reported
${_hardwareKeyboard.physicalKeysPressed}
'
);
}
if
(
keyMessageHandler
!=
null
)
{
final
KeyMessage
message
=
KeyMessage
(
_keyEventsSinceLastMessage
,
rawEvent
);
try
{
handled
=
keyMessageHandler
!(
message
)
||
handled
;
}
catch
(
exception
,
stack
)
{
InformationCollector
?
collector
;
assert
(()
{
collector
=
()
=>
<
DiagnosticsNode
>[
DiagnosticsProperty
<
KeyMessage
>(
'KeyMessage'
,
message
),
];
return
true
;
}());
FlutterError
.
reportError
(
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'services library'
,
context:
ErrorDescription
(
'while processing the key message handler'
),
informationCollector:
collector
,
));
}
}
handled
=
_dispatchKeyMessage
(
_keyEventsSinceLastMessage
,
rawEvent
)
||
handled
;
_keyEventsSinceLastMessage
.
clear
();
return
<
String
,
dynamic
>{
'handled'
:
handled
};
...
...
packages/flutter/lib/src/services/raw_keyboard.dart
View file @
4e04e3ff
...
...
@@ -650,7 +650,11 @@ class RawKeyboard {
_cachedKeyEventHandler
=
handler
;
_cachedKeyMessageHandler
=
handler
==
null
?
null
:
(
KeyMessage
message
)
=>
handler
(
message
.
rawEvent
);
(
KeyMessage
message
)
{
if
(
message
.
rawEvent
!=
null
)
return
handler
(
message
.
rawEvent
!);
return
false
;
};
ServicesBinding
.
instance
!.
keyEventManager
.
keyMessageHandler
=
_cachedKeyMessageHandler
;
}
...
...
packages/flutter/lib/src/widgets/focus_manager.dart
View file @
4e04e3ff
...
...
@@ -1681,8 +1681,8 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
results
.
add
(
node
.
onKeyEvent
!(
node
,
event
));
}
}
if
(
node
.
onKey
!=
null
)
{
results
.
add
(
node
.
onKey
!(
node
,
message
.
rawEvent
));
if
(
node
.
onKey
!=
null
&&
message
.
rawEvent
!=
null
)
{
results
.
add
(
node
.
onKey
!(
node
,
message
.
rawEvent
!
));
}
final
KeyEventResult
result
=
combineKeyEventResults
(
results
);
switch
(
result
)
{
...
...
packages/flutter/test/services/hardware_keyboard_test.dart
View file @
4e04e3ff
...
...
@@ -195,6 +195,89 @@ void main() {
logs
.
clear
();
},
variant:
KeySimulatorTransitModeVariant
.
all
());
testWidgets
(
'Instantly dispatch synthesized key events when the queue is empty'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
final
List
<
int
>
logs
=
<
int
>[];
await
tester
.
pumpWidget
(
KeyboardListener
(
autofocus:
true
,
focusNode:
focusNode
,
child:
Container
(),
onKeyEvent:
(
KeyEvent
event
)
{
logs
.
add
(
1
);
},
),
);
ServicesBinding
.
instance
!.
keyboard
.
addHandler
((
KeyEvent
event
)
{
logs
.
add
(
2
);
return
false
;
});
// Dispatch a solitary synthesized event.
expect
(
ServicesBinding
.
instance
!.
keyEventManager
.
handleKeyData
(
ui
.
KeyData
(
timeStamp:
Duration
.
zero
,
type:
ui
.
KeyEventType
.
down
,
logical:
LogicalKeyboardKey
.
keyA
.
keyId
,
physical:
PhysicalKeyboardKey
.
keyA
.
usbHidUsage
,
character:
null
,
synthesized:
true
,
)),
false
);
expect
(
logs
,
<
int
>[
2
,
1
]);
logs
.
clear
();
},
variant:
KeySimulatorTransitModeVariant
.
keyDataThenRawKeyData
());
testWidgets
(
'Postpone synthesized key events when the queue is not empty'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
final
List
<
String
>
logs
=
<
String
>[];
await
tester
.
pumpWidget
(
RawKeyboardListener
(
focusNode:
FocusNode
(),
onKey:
(
RawKeyEvent
event
)
{
logs
.
add
(
'
${event.runtimeType}
'
);
},
child:
KeyboardListener
(
autofocus:
true
,
focusNode:
focusNode
,
child:
Container
(),
onKeyEvent:
(
KeyEvent
event
)
{
logs
.
add
(
'
${event.runtimeType}
'
);
},
),
),
);
// On macOS, a CapsLock tap yields a down event and a synthesized up event.
expect
(
ServicesBinding
.
instance
!.
keyEventManager
.
handleKeyData
(
ui
.
KeyData
(
timeStamp:
Duration
.
zero
,
type:
ui
.
KeyEventType
.
down
,
logical:
LogicalKeyboardKey
.
capsLock
.
keyId
,
physical:
PhysicalKeyboardKey
.
capsLock
.
usbHidUsage
,
character:
null
,
synthesized:
false
,
)),
false
);
expect
(
ServicesBinding
.
instance
!.
keyEventManager
.
handleKeyData
(
ui
.
KeyData
(
timeStamp:
Duration
.
zero
,
type:
ui
.
KeyEventType
.
up
,
logical:
LogicalKeyboardKey
.
capsLock
.
keyId
,
physical:
PhysicalKeyboardKey
.
capsLock
.
usbHidUsage
,
character:
null
,
synthesized:
true
,
)),
false
);
expect
(
await
ServicesBinding
.
instance
!.
keyEventManager
.
handleRawKeyMessage
(<
String
,
dynamic
>{
'type'
:
'keydown'
,
'keymap'
:
'macos'
,
'keyCode'
:
0x00000039
,
'characters'
:
''
,
'charactersIgnoringModifiers'
:
''
,
'modifiers'
:
0x10000
,
}),
equals
(<
String
,
dynamic
>{
'handled'
:
false
}));
expect
(
logs
,
<
String
>[
'RawKeyDownEvent'
,
'KeyDownEvent'
,
'KeyUpEvent'
]);
logs
.
clear
();
},
variant:
KeySimulatorTransitModeVariant
.
keyDataThenRawKeyData
());
// The first key data received from the engine might be an empty key data.
// In that case, the key data should not be converted to any [KeyEvent]s,
// but is only used so that *a* key data comes before the raw key message
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment