Răsfoiți Sursa

feat: add hardwareKeyboardOnly

Georg Wechslberger 3 ani în urmă
părinte
comite
3a9d04287f
2 a modificat fișierele cu 114 adăugiri și 28 ștergeri
  1. 53 28
      lib/src/terminal_view.dart
  2. 61 0
      lib/src/ui/keyboard_listener.dart

+ 53 - 28
lib/src/terminal_view.dart

@@ -11,6 +11,7 @@ import 'package:xterm/src/ui/cursor_type.dart';
 import 'package:xterm/src/ui/custom_text_edit.dart';
 import 'package:xterm/src/ui/gesture/gesture_handler.dart';
 import 'package:xterm/src/ui/input_map.dart';
+import 'package:xterm/src/ui/keyboard_listener.dart';
 import 'package:xterm/src/ui/keyboard_visibility.dart';
 import 'package:xterm/src/ui/render.dart';
 import 'package:xterm/src/ui/shortcut/actions.dart';
@@ -43,6 +44,7 @@ class TerminalView extends StatefulWidget {
     this.deleteDetection = false,
     this.shortcuts,
     this.readOnly = false,
+    this.hardwareKeyboardOnly = false,
   }) : super(key: key);
 
   /// The underlying terminal that this widget renders.
@@ -119,6 +121,10 @@ class TerminalView extends StatefulWidget {
   /// True if no input should send to the terminal.
   final bool readOnly;
 
+  /// True if only hardware keyboard events should be used as input. This will
+  /// also prevent any on-screen keyboard to be shown.
+  final bool hardwareKeyboardOnly;
+
   @override
   State<TerminalView> createState() => TerminalViewState();
 }
@@ -204,33 +210,39 @@ class TerminalViewState extends State<TerminalView> {
       },
     );
 
-    child = CustomTextEdit(
-      key: _customTextEditKey,
-      focusNode: _focusNode,
-      inputType: widget.keyboardType,
-      keyboardAppearance: widget.keyboardAppearance,
-      deleteDetection: widget.deleteDetection,
-      onInsert: (text) {
-        _scrollToBottom();
-        widget.terminal.textInput(text);
-      },
-      onDelete: () {
-        _scrollToBottom();
-        widget.terminal.keyInput(TerminalKey.backspace);
-      },
-      onComposing: (text) {
-        setState(() => _composingText = text);
-      },
-      onAction: (action) {
-        _scrollToBottom();
-        if (action == TextInputAction.done) {
-          widget.terminal.keyInput(TerminalKey.enter);
-        }
-      },
-      onKey: _onKeyEvent,
-      readOnly: widget.readOnly,
-      child: child,
-    );
+    if (!widget.hardwareKeyboardOnly) {
+      child = CustomTextEdit(
+        key: _customTextEditKey,
+        focusNode: _focusNode,
+        inputType: widget.keyboardType,
+        keyboardAppearance: widget.keyboardAppearance,
+        deleteDetection: widget.deleteDetection,
+        onInsert: _onInsert,
+        onDelete: () {
+          _scrollToBottom();
+          widget.terminal.keyInput(TerminalKey.backspace);
+        },
+        onComposing: _onComposing,
+        onAction: (action) {
+          _scrollToBottom();
+          if (action == TextInputAction.done) {
+            widget.terminal.keyInput(TerminalKey.enter);
+          }
+        },
+        onKey: _onKeyEvent,
+        readOnly: widget.readOnly,
+        child: child,
+      );
+    } else if (!widget.readOnly) {
+      // Only listen for key input from a hardware keyboard.
+      child = CustomKeyboardListener(
+        child: child,
+        focusNode: _focusNode,
+        onInsert: _onInsert,
+        onComposing: _onComposing,
+        onKey: _onKeyEvent,
+      );
+    }
 
     child = TerminalActions(
       terminal: widget.terminal,
@@ -285,7 +297,11 @@ class TerminalViewState extends State<TerminalView> {
     if (_controller.selection != null) {
       _controller.clearSelection();
     } else {
-      _customTextEditKey.currentState?.requestKeyboard();
+      if (!widget.hardwareKeyboardOnly) {
+        _customTextEditKey.currentState?.requestKeyboard();
+      } else {
+        _focusNode.requestFocus();
+      }
     }
   }
 
@@ -303,6 +319,15 @@ class TerminalViewState extends State<TerminalView> {
     return _customTextEditKey.currentState?.hasInputConnection == true;
   }
 
+  void _onInsert(String text) {
+    _scrollToBottom();
+    widget.terminal.textInput(text);
+  }
+
+  void _onComposing(String? text) {
+    setState(() => _composingText = text);
+  }
+
   KeyEventResult _onKeyEvent(FocusNode focusNode, RawKeyEvent event) {
     // ignore: invalid_use_of_protected_member
     final shortcutResult = _shortcutManager.handleKeypress(

+ 61 - 0
lib/src/ui/keyboard_listener.dart

@@ -0,0 +1,61 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+
+class CustomKeyboardListener extends StatelessWidget {
+  final Widget child;
+
+  final FocusNode focusNode;
+
+  final void Function(String) onInsert;
+
+  final void Function(String?) onComposing;
+
+  final KeyEventResult Function(FocusNode, RawKeyEvent) onKey;
+
+  const CustomKeyboardListener({
+    Key? key,
+    required this.child,
+    required this.focusNode,
+    required this.onInsert,
+    required this.onComposing,
+    required this.onKey,
+  }) : super(key: key);
+
+  KeyEventResult _onKey(FocusNode focusNode, RawKeyEvent keyEvent) {
+    // First try to handle the key event directly.
+    final handled = onKey(focusNode, keyEvent);
+    if (handled == KeyEventResult.ignored) {
+      // If it was not handled, but the key corresponds to a character,
+      // insert the character.
+      if (keyEvent.character != null && keyEvent.character != "") {
+        onInsert(keyEvent.character!);
+        return KeyEventResult.handled;
+      } else if (keyEvent.data is RawKeyEventDataIos &&
+          keyEvent is RawKeyDownEvent) {
+        // On iOS keyEvent.character is always null. But data.characters
+        // contains the the character(s) corresponding to the input.
+        final data = keyEvent.data as RawKeyEventDataIos;
+        if (data.characters != "") {
+          onComposing(null);
+          onInsert(data.characters);
+        } else if (data.charactersIgnoringModifiers != "") {
+          // If characters is an empty string but charactersIgnoringModifiers is
+          // not an empty string, this indicates that the current characters is
+          // being composed. The current composing state is
+          // charactersIgnoringModifiers.
+          onComposing(data.charactersIgnoringModifiers);
+        }
+      }
+    }
+    return handled;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Focus(
+      focusNode: focusNode,
+      onKey: _onKey,
+      child: child,
+    );
+  }
+}