mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-19 19:01:05 +03:00
feat(terminal): use platform-native copy and paste shortcuts (#15931)
This commit is contained in:
66
flutter/lib/models/terminal_copy_shortcut.dart
Normal file
66
flutter/lib/models/terminal_copy_shortcut.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
|
const _controlShiftVPasteShortcut = SingleActivator(
|
||||||
|
LogicalKeyboardKey.keyV,
|
||||||
|
control: true,
|
||||||
|
shift: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> writeTerminalClipboard(String text) async {
|
||||||
|
try {
|
||||||
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint('[Terminal] Failed to write clipboard: $error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<ShortcutActivator, Intent>? platformTerminalShortcuts() {
|
||||||
|
if (defaultTargetPlatform != TargetPlatform.linux) return null;
|
||||||
|
return {
|
||||||
|
for (final entry in defaultTerminalShortcuts.entries)
|
||||||
|
if (!_isControlVShortcut(entry.key)) entry.key: entry.value,
|
||||||
|
_controlShiftVPasteShortcut:
|
||||||
|
const PasteTextIntent(SelectionChangedCause.keyboard),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isControlVShortcut(ShortcutActivator shortcut) =>
|
||||||
|
shortcut is SingleActivator &&
|
||||||
|
shortcut.trigger == LogicalKeyboardKey.keyV &&
|
||||||
|
shortcut.control &&
|
||||||
|
!shortcut.shift &&
|
||||||
|
!shortcut.alt &&
|
||||||
|
!shortcut.meta;
|
||||||
|
|
||||||
|
FocusOnKeyEventCallback terminalCopyHandler(
|
||||||
|
Terminal terminal,
|
||||||
|
TerminalController controller,
|
||||||
|
) =>
|
||||||
|
(_, event) {
|
||||||
|
if (!_isWindowsCopyShortcut(event)) return KeyEventResult.ignored;
|
||||||
|
final selection = controller.selection;
|
||||||
|
if (selection == null || selection.isCollapsed) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
if (event is KeyDownEvent) {
|
||||||
|
final text = terminal.buffer.getText(selection);
|
||||||
|
unawaited(writeTerminalClipboard(text));
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool _isWindowsCopyShortcut(KeyEvent event) {
|
||||||
|
final keyboard = HardwareKeyboard.instance;
|
||||||
|
return defaultTargetPlatform == TargetPlatform.windows &&
|
||||||
|
(event is KeyDownEvent || event is KeyRepeatEvent) &&
|
||||||
|
event.logicalKey == LogicalKeyboardKey.keyC &&
|
||||||
|
keyboard.isControlPressed &&
|
||||||
|
!keyboard.isShiftPressed &&
|
||||||
|
!keyboard.isAltPressed &&
|
||||||
|
!keyboard.isMetaPressed;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:xterm/xterm.dart';
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
|
import 'terminal_copy_shortcut.dart';
|
||||||
import 'terminal_mouse_drag_reporter.dart';
|
import 'terminal_mouse_drag_reporter.dart';
|
||||||
|
|
||||||
/// xterm 4.0.0 encodes wheel buttons as 68..71; the extra bit reads as a Shift
|
/// xterm 4.0.0 encodes wheel buttons as 68..71; the extra bit reads as a Shift
|
||||||
@@ -291,6 +292,8 @@ class _TerminalMouseInteractionState extends State<TerminalMouseInteraction> {
|
|||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
backgroundOpacity: widget.backgroundOpacity,
|
backgroundOpacity: widget.backgroundOpacity,
|
||||||
padding: widget.padding,
|
padding: widget.padding,
|
||||||
|
shortcuts: platformTerminalShortcuts(),
|
||||||
|
onKeyEvent: terminalCopyHandler(widget.terminal, widget.controller),
|
||||||
onSecondaryTapDown: widget.onSecondaryTapDown,
|
onSecondaryTapDown: widget.onSecondaryTapDown,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_hbb/models/terminal_mouse_handler.dart';
|
import 'package:flutter_hbb/models/terminal_mouse_handler.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:xterm/xterm.dart';
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
@@ -38,6 +40,34 @@ void main() {
|
|||||||
..onOutput = output.add;
|
..onOutput = output.add;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Linux Ctrl+V is not paste', (tester) async {
|
||||||
|
debugDefaultTargetPlatformOverride = TargetPlatform.linux;
|
||||||
|
try {
|
||||||
|
final messenger = tester.binding.defaultBinaryMessenger;
|
||||||
|
messenger.setMockMethodCallHandler(
|
||||||
|
SystemChannels.platform,
|
||||||
|
(_) async => {'text': 'clipboard'},
|
||||||
|
);
|
||||||
|
final controller = TerminalController();
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
await tester.pumpWidget(_terminalHarness(terminal, controller));
|
||||||
|
await tester.tap(find.byType(TerminalView));
|
||||||
|
await tester.pump(kDoubleTapTimeout);
|
||||||
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.keyV);
|
||||||
|
final controlVOutput = List.of(output);
|
||||||
|
output.clear();
|
||||||
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.keyV);
|
||||||
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||||
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||||
|
expect(controlVOutput, ['\x16']);
|
||||||
|
expect(output, ['clipboard']);
|
||||||
|
} finally {
|
||||||
|
debugDefaultTargetPlatformOverride = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
String? report(
|
String? report(
|
||||||
TerminalMouseButton button, [
|
TerminalMouseButton button, [
|
||||||
TerminalMouseButtonState state = TerminalMouseButtonState.down,
|
TerminalMouseButtonState state = TerminalMouseButtonState.down,
|
||||||
|
|||||||
Reference in New Issue
Block a user