fix: Android soft keyboard IME input corruption (#13737, #9789, #11073)

When the Android soft keyboard is active, key events from the IME carry
unreliable physicalKey data (Flutter issue #157771). The RawKeyFocusScope
handler was processing these garbled scancodes, desynchronising the hidden
TextFormField's text buffer and causing every subsequent keypress to repeat
a single character (space or '1').

Changes:
- InputModel: add androidSoftKeyboardActive flag; when set, handleKeyEvent
  returns handled but skips the normal key processing pipeline. Backspace
  and Enter are sent directly using reliable logicalKey data.
- remote_page.dart: set/clear the flag via onSoftKeyboardChanged callback;
  fix multi-delete counting for Samsung keyboard acceleration.

Fixes: rustdesk/rustdesk#13737, rustdesk/rustdesk#9789, rustdesk/rustdesk#11073
This commit is contained in:
Yousif Almulla 2026-03-09 05:05:12 -07:00
commit 7b04665f33
2 changed files with 35 additions and 2 deletions

View file

@ -201,6 +201,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
);
void onSoftKeyboardChanged(bool visible) {
inputModel.androidSoftKeyboardActive = visible;
if (!visible) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
// [pi.version.isNotEmpty] -> check ready or not, avoid login without soft-keyboard
@ -300,8 +301,11 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
if (newValue.length == oldValue.length) {
// ?
} else if (newValue.length < oldValue.length) {
final char = 'VK_BACK';
inputModel.inputKey(char);
// Send exactly one VK_BACK per onChanged callback regardless of how many
// characters the IME removed (Samsung accelerates held-delete). The
// IME's own callback frequency provides a steady, controllable repeat
// rate instead of runaway exponential deletion.
inputModel.inputKey('VK_BACK');
} else {
final content = newValue.substring(oldValue.length);
if (content.length > 1) {

View file

@ -367,6 +367,12 @@ class InputModel {
bool _pointerMovedAfterEnter = false;
bool _pointerInsideImage = false;
/// True while the Android soft keyboard editor is active.
/// When set, key events are ignored so they flow through to the
/// hidden TextFormField's onChanged handler instead of being
/// processed here with potentially incorrect physicalKey data.
bool androidSoftKeyboardActive = false;
// mouse
final isPhysicalMouse = false.obs;
int _lastButtons = 0;
@ -687,6 +693,29 @@ class InputModel {
KeyEventResult handleKeyEvent(KeyEvent e) {
if (isViewOnly) return KeyEventResult.handled;
if (isViewCamera) return KeyEventResult.handled;
// When the Android soft keyboard is active, avoid processing key events
// through the normal input pipeline because physicalKey data from the
// soft keyboard is unreliable (Flutter issue #157771) and can corrupt
// subsequent input, causing every keypress to repeat a single character.
//
// Return `handled` (not `ignored`) so Android keeps sending key-repeat
// events for held keys and the TextFormField does not consume sentinel
// buffer characters.
//
// For Backspace and Enter, send them directly using the reliable logical
// key data. This is required because for some IMEs (ko/zh/ja) returning
// `handled` prevents the IME from processing the key through onChanged.
if (isAndroid && androidSoftKeyboardActive) {
if (e is KeyDownEvent || e is KeyRepeatEvent) {
if (e.logicalKey == LogicalKeyboardKey.backspace) {
inputKey('VK_BACK', press: true);
} else if (e.logicalKey == LogicalKeyboardKey.enter ||
e.logicalKey == LogicalKeyboardKey.numpadEnter) {
inputKey('VK_RETURN', press: true);
}
}
return KeyEventResult.handled;
}
if (!isInputSourceFlutter) {
if (isDesktop) {
return KeyEventResult.handled;