From 10639164744547213d5253a9ea403e932a068e40 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Wed, 1 Feb 2023 21:34:16 +0800 Subject: [PATCH] `ssh`: support copy/paste, fix ios backspace --- ios/Runner.xcodeproj/project.pbxproj | 12 +-- lib/app.dart | 24 +++-- lib/core/utils/misc.dart | 14 +++ lib/data/model/ssh/virtual_key.dart | 10 +-- lib/data/res/build_data.dart | 4 +- lib/data/res/virtual_key.dart | 38 ++++---- lib/view/page/pkg.dart | 5 +- lib/view/page/ssh.dart | 127 ++++++++++++++++----------- 8 files changed, 141 insertions(+), 93 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 96397d60..dafb7253 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -356,7 +356,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 205; + CURRENT_PROJECT_VERSION = 208; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -364,7 +364,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.205; + MARKETING_VERSION = 1.0.208; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -486,7 +486,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 205; + CURRENT_PROJECT_VERSION = 208; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -494,7 +494,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.205; + MARKETING_VERSION = 1.0.208; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -510,7 +510,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 205; + CURRENT_PROJECT_VERSION = 208; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -518,7 +518,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.205; + MARKETING_VERSION = 1.0.208; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/app.dart b/lib/app.dart index 805ca585..af795770 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -63,19 +63,17 @@ class MyApp extends StatelessWidget { radioTheme: radioTheme, ), darkTheme: ThemeData.dark().copyWith( - useMaterial3: false, - floatingActionButtonTheme: fabTheme, - iconTheme: iconTheme, - primaryIconTheme: iconTheme, - switchTheme: switchTheme, - inputDecorationTheme: inputDecorationTheme, - radioTheme: radioTheme, - colorScheme: ColorScheme.fromSwatch( - primarySwatch: primaryColor.materialColor, - brightness: Brightness.dark, - accentColor: primaryColor - ) - ), + useMaterial3: false, + floatingActionButtonTheme: fabTheme, + iconTheme: iconTheme, + primaryIconTheme: iconTheme, + switchTheme: switchTheme, + inputDecorationTheme: inputDecorationTheme, + radioTheme: radioTheme, + colorScheme: ColorScheme.fromSwatch( + primarySwatch: primaryColor.materialColor, + brightness: Brightness.dark, + accentColor: primaryColor)), home: const MyHomePage(), ); }, diff --git a/lib/core/utils/misc.dart b/lib/core/utils/misc.dart index 9ecda919..95a0a032 100644 --- a/lib/core/utils/misc.dart +++ b/lib/core/utils/misc.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:share_plus/share_plus.dart'; @@ -21,3 +22,16 @@ Future shareFiles(BuildContext context, List filePaths) async { await Share.shareXFiles(xfiles, text: 'ServerBox -> $text'); return filePaths.isNotEmpty; } + +bool get longPressEnabled { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + return true; + case TargetPlatform.macOS: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return false; + } +} diff --git a/lib/data/model/ssh/virtual_key.dart b/lib/data/model/ssh/virtual_key.dart index 052b7611..3dd552f9 100644 --- a/lib/data/model/ssh/virtual_key.dart +++ b/lib/data/model/ssh/virtual_key.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:xterm/core.dart'; class VirtualKey { - final TerminalKey key; final String text; final bool toggleable; + final TerminalKey? key; final IconData? icon; - final VirtualKeyType? extFunc; + final VirtualKeyFunc? func; - VirtualKey(this.key, this.text, - {this.toggleable = false, this.icon, this.extFunc}); + VirtualKey(this.text, + {this.key, this.toggleable = false, this.icon, this.func}); } -enum VirtualKeyType { toggleIME, backspace } +enum VirtualKeyFunc { toggleIME, backspace, copy, paste } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 5e1395f7..04cdaccd 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 206; + static const int build = 208; static const String engine = "Flutter 3.7.0 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision b06b8b2710 (8 days ago) • 2023-01-23 16:55:55 -0800\nEngine • revision b24591ed32\nTools • Dart 2.19.0 • DevTools 2.20.1\n"; - static const String buildAt = "2023-02-01 17:24:19.787396"; + static const String buildAt = "2023-02-01 18:33:57.761096"; static const int modifications = 4; } diff --git a/lib/data/res/virtual_key.dart b/lib/data/res/virtual_key.dart index 04b3af72..8b15f93f 100644 --- a/lib/data/res/virtual_key.dart +++ b/lib/data/res/virtual_key.dart @@ -3,19 +3,27 @@ import 'package:xterm/core.dart'; import '../model/ssh/virtual_key.dart'; -var virtualKeys = [ - VirtualKey(TerminalKey.escape, 'Esc'), - VirtualKey(TerminalKey.alt, 'Alt', toggleable: true), - VirtualKey(TerminalKey.home, 'Home'), - VirtualKey(TerminalKey.arrowUp, 'Up', icon: Icons.arrow_upward), - VirtualKey(TerminalKey.end, 'End'), - VirtualKey(TerminalKey.backspace, 'Backspace', - extFunc: VirtualKeyType.backspace, icon: Icons.backspace), - VirtualKey(TerminalKey.tab, 'Tab'), - VirtualKey(TerminalKey.control, 'Ctrl', toggleable: true), - VirtualKey(TerminalKey.arrowLeft, 'Left', icon: Icons.arrow_back), - VirtualKey(TerminalKey.arrowDown, 'Down', icon: Icons.arrow_downward), - VirtualKey(TerminalKey.arrowRight, 'Right', icon: Icons.arrow_forward), - VirtualKey(TerminalKey.none, 'IME', - extFunc: VirtualKeyType.toggleIME, icon: Icons.keyboard_hide), +final virtualKeys = [ + VirtualKey('Esc', key: TerminalKey.escape), + VirtualKey('Alt', key: TerminalKey.alt, toggleable: true), + VirtualKey('Home', key: TerminalKey.home), + VirtualKey('Up', key: TerminalKey.arrowUp, icon: Icons.arrow_upward), + VirtualKey('End', key: TerminalKey.end), + VirtualKey( + 'Copy', + func: VirtualKeyFunc.copy, + icon: Icons.copy, + ), + VirtualKey('Backspace', func: VirtualKeyFunc.backspace, icon: Icons.backspace,), + VirtualKey('Tab', key: TerminalKey.tab), + VirtualKey('Ctrl', key: TerminalKey.control, toggleable: true), + VirtualKey('Left', key: TerminalKey.arrowLeft, icon: Icons.arrow_back), + VirtualKey('Down', key: TerminalKey.arrowDown, icon: Icons.arrow_downward), + VirtualKey('Right', key: TerminalKey.arrowRight, icon: Icons.arrow_forward), + VirtualKey( + 'Paste', + func: VirtualKeyFunc.paste, + icon: Icons.paste, + ), + VirtualKey('IME', func: VirtualKeyFunc.toggleIME, icon: Icons.keyboard_hide,), ]; diff --git a/lib/view/page/pkg.dart b/lib/view/page/pkg.dart index acd47180..173747fa 100644 --- a/lib/view/page/pkg.dart +++ b/lib/view/page/pkg.dart @@ -143,9 +143,8 @@ class _PkgManagePageState extends State ), ConstrainedBox( constraints: BoxConstraints( - maxHeight: _media.size.height * 0.3, - minWidth: _media.size.width - ), + maxHeight: _media.size.height * 0.3, + minWidth: _media.size.width), child: Padding( padding: const EdgeInsets.all(17), child: RoundRectCard( diff --git a/lib/view/page/ssh.dart b/lib/view/page/ssh.dart index b14a2318..63e1d5e0 100644 --- a/lib/view/page/ssh.dart +++ b/lib/view/page/ssh.dart @@ -1,11 +1,11 @@ import 'dart:convert'; +import 'dart:io'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:toolbox/data/res/color.dart'; import 'package:xterm/xterm.dart'; import '../../core/utils/ui.dart'; @@ -13,6 +13,7 @@ import '../../core/utils/server.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/model/ssh/virtual_key.dart'; import '../../data/provider/virtual_keyboard.dart'; +import '../../data/res/color.dart'; import '../../data/res/terminal_theme.dart'; import '../../data/res/virtual_key.dart'; import '../../locator.dart'; @@ -26,11 +27,12 @@ class SSHPage extends StatefulWidget { } class _SSHPageState extends State { - late final terminal = Terminal(inputHandler: keyboard); - SSHClient? client; - final keyboard = locator(); + late final _terminal = Terminal(inputHandler: _keyboard); + SSHClient? _client; + final _keyboard = locator(); late MediaQueryData _media; final _virtualKeyboardHeight = 57.0; + final TerminalController _terminalController = TerminalController(); var isDark = false; @@ -49,39 +51,39 @@ class _SSHPageState extends State { @override void dispose() { - client?.close(); + _client?.close(); super.dispose(); } Future initTerminal() async { - terminal.write('Connecting...\r\n'); + _terminal.write('Connecting...\r\n'); - client = await genClient(widget.spi); - terminal.write('Connected\r\n'); + _client = await genClient(widget.spi); + _terminal.write('Connected\r\n'); - final session = await client!.shell( + final session = await _client!.shell( pty: SSHPtyConfig( - width: terminal.viewWidth, - height: terminal.viewHeight, + width: _terminal.viewWidth, + height: _terminal.viewHeight, ), ); - terminal.buffer.clear(); - terminal.buffer.setCursor(0, 0); + _terminal.buffer.clear(); + _terminal.buffer.setCursor(0, 0); - terminal.onOutput = (data) { + _terminal.onOutput = (data) { session.write(utf8.encode(data) as Uint8List); }; session.stdout .cast>() .transform(const Utf8Decoder()) - .listen(terminal.write); + .listen(_terminal.write); session.stderr .cast>() .transform(const Utf8Decoder()) - .listen(terminal.write); + .listen(_terminal.write); await session.done; if (mounted) { @@ -100,11 +102,14 @@ class _SSHPageState extends State { _media.padding.bottom - _media.padding.top, child: TerminalView( - terminal, - keyboardType: TextInputType.visiblePassword, - theme: termTheme, - keyboardAppearance: isDark ? Brightness.dark : Brightness.light, - ), + _terminal, + controller: _terminalController, + keyboardType: TextInputType.visiblePassword, + theme: termTheme, + deleteDetection: Platform.isIOS, + autofocus: true, + keyboardAppearance: isDark ? Brightness.dark : Brightness.light, + ), ), bottomNavigationBar: AnimatedPadding( padding: _media.viewInsets, @@ -141,10 +146,10 @@ class _SSHPageState extends State { var selected = false; switch (item.key) { case TerminalKey.control: - selected = keyboard.ctrl; + selected = _keyboard.ctrl; break; case TerminalKey.alt: - selected = keyboard.alt; + selected = _keyboard.alt; break; default: break; @@ -165,32 +170,7 @@ class _SSHPageState extends State { ); return InkWell( - onTap: () { - if (item.extFunc != null) { - switch (item.extFunc!) { - case VirtualKeyType.toggleIME: - FocusScope.of(context).requestFocus(FocusNode()); - break; - case VirtualKeyType.backspace: - terminal.keyInput(TerminalKey.backspace); - break; - } - return; - } - switch (item.key) { - case TerminalKey.control: - keyboard.ctrl = !keyboard.ctrl; - setState(() {}); - break; - case TerminalKey.alt: - keyboard.alt = !keyboard.alt; - setState(() {}); - break; - default: - terminal.keyInput(item.key); - break; - } - }, + onTap: () => _doVirtualKey(item), child: SizedBox( width: _media.size.width / (virtualKeys.length / 2), height: _virtualKeyboardHeight / 2, @@ -200,4 +180,53 @@ class _SSHPageState extends State { ), ); } + + void _doVirtualKey(VirtualKey item) { + if (item.func != null) { + _doVirtualKeyFunc(item.func!); + return; + } + if (item.key != null) { + _doVirtualKeyInput(item.key!); + } + } + + void _doVirtualKeyInput(TerminalKey key) { + switch (key) { + case TerminalKey.control: + _keyboard.ctrl = !_keyboard.ctrl; + setState(() {}); + break; + case TerminalKey.alt: + _keyboard.alt = !_keyboard.alt; + setState(() {}); + break; + default: + _terminal.keyInput(key); + break; + } + } + + void _doVirtualKeyFunc(VirtualKeyFunc type) { + switch (type) { + case VirtualKeyFunc.toggleIME: + FocusScope.of(context).requestFocus(FocusNode()); + break; + case VirtualKeyFunc.backspace: + _terminal.keyInput(TerminalKey.backspace); + break; + case VirtualKeyFunc.paste: + Clipboard.getData(Clipboard.kTextPlain).then((value) { + if (value != null) { + _terminal.textInput(value.text!); + } + }); + break; + case VirtualKeyFunc.copy: + final range = _terminalController.selection; + final text = _terminal.buffer.getText(range); + Clipboard.setData(ClipboardData(text: text)); + break; + } + } }