diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 92ae5c0d6..93e8a0be1 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -19,6 +19,7 @@ import 'package:window_manager/window_manager.dart'; import 'common/widgets/overlay.dart'; import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/remote_page.dart'; +import 'models/input_model.dart'; import 'models/model.dart'; import 'models/platform_model.dart'; @@ -486,12 +487,12 @@ class OverlayDialogManager { position: Offset(left, top), width: overlayW, height: overlayH, - onBackPressed: () => session.tap(MouseButtons.right), - onHomePressed: () => session.tap(MouseButtons.wheel), + onBackPressed: () => session.inputModel.tap(MouseButtons.right), + onHomePressed: () => session.inputModel.tap(MouseButtons.wheel), onRecentPressed: () async { - session.sendMouse('down', MouseButtons.wheel); + session.inputModel.sendMouse('down', MouseButtons.wheel); await Future.delayed(const Duration(milliseconds: 500)); - session.sendMouse('up', MouseButtons.wheel); + session.inputModel.sendMouse('up', MouseButtons.wheel); }, onHidePressed: () => hideMobileActionsOverlay(), ); diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index f44088d6e..cc91a698f 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -58,7 +58,7 @@ class _FileManagerPageState extends State void initState() { super.initState(); _ffi = FFI(); - _ffi.connect(widget.id, isFileTransfer: true); + _ffi.start(widget.id, isFileTransfer: true); WidgetsBinding.instance.addPostFrameCallback((_) { _ffi.dialogManager .showLoading(translate('Connecting...'), onCancel: closeConnection); diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index ccbf1805e..b2458d096 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -47,7 +47,7 @@ class _PortForwardPageState extends State void initState() { super.initState(); _ffi = FFI(); - _ffi.connect(widget.id, isPortForward: true); + _ffi.start(widget.id, isPortForward: true); Get.put(_ffi, tag: 'pf_${widget.id}'); if (!Platform.isLinux) { Wakelock.enable(); diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f46af0931..3d6f3ee21 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -5,7 +5,6 @@ import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_hbb/models/input_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; @@ -49,12 +48,9 @@ class _RemotePageState extends State Function(bool)? _onEnterOrLeaveImage4Menubar; late FFI _ffi; - late Keyboard _keyboard_input; - late Mouse _mouse_input; void _updateTabBarHeight() { _ffi.canvasModel.tabBarHeight = widget.tabBarHeight; - _mouse_input.tabBarHeight = widget.tabBarHeight; } void _initStates(String id) { @@ -84,12 +80,10 @@ class _RemotePageState extends State _initStates(widget.id); _ffi = FFI(); - _keyboard_input = Keyboard(_ffi, widget.id); - _mouse_input = Mouse(_ffi, widget.id, widget.tabBarHeight); _updateTabBarHeight(); Get.put(_ffi, tag: widget.id); - _ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight); + _ffi.start(widget.id, tabBarHeight: super.widget.tabBarHeight); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); _ffi.dialogManager @@ -175,7 +169,7 @@ class _RemotePageState extends State onFocusChange: (bool v) { _imageFocused = v; }, - onKey: _keyboard_input.handleRawKeyEvent, + onKey: _ffi.inputModel.handleRawKeyEvent, child: child)); } @@ -191,7 +185,7 @@ class _RemotePageState extends State // } } - _ffi.enterOrLeave(true); + _ffi.inputModel.enterOrLeave(true); } void leaveView(PointerExitEvent evt) { @@ -203,16 +197,16 @@ class _RemotePageState extends State // } } - _ffi.enterOrLeave(false); + _ffi.inputModel.enterOrLeave(false); } Widget _buildImageListener(Widget child) { return Listener( - onPointerHover: _mouse_input.onPointHoverImage, - onPointerDown: _mouse_input.onPointDownImage, - onPointerUp: _mouse_input.onPointUpImage, - onPointerMove: _mouse_input.onPointMoveImage, - onPointerSignal: _mouse_input.onPointerSignalImage, + onPointerHover: _ffi.inputModel.onPointHoverImage, + onPointerDown: _ffi.inputModel.onPointDownImage, + onPointerUp: _ffi.inputModel.onPointUpImage, + onPointerMove: _ffi.inputModel.onPointMoveImage, + onPointerSignal: _ffi.inputModel.onPointerSignalImage, child: MouseRegion(onEnter: enterView, onExit: leaveView, child: child)); } diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index 13059d188..ce270ffea 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -26,7 +26,7 @@ class _FileManagerPageState extends State { @override void initState() { super.initState(); - gFFI.connect(widget.id, isFileTransfer: true); + gFFI.start(widget.id, isFileTransfer: true); WidgetsBinding.instance.addPostFrameCallback((_) { gFFI.dialogManager .showLoading(translate('Connecting...'), onCancel: closeConnection); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 0673ce6f4..5be604ee7 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -12,6 +12,7 @@ import 'package:wakelock/wakelock.dart'; import '../../common.dart'; import '../../consts.dart'; +import '../../models/input_model.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; import '../widgets/dialog.dart'; @@ -45,10 +46,12 @@ class _RemotePageState extends State { var _showEdit = false; // use soft keyboard var _isPhysicalMouse = false; + get inputModel => gFFI.inputModel; + @override void initState() { super.initState(); - gFFI.connect(widget.id); + gFFI.start(widget.id); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); gFFI.dialogManager @@ -59,14 +62,14 @@ class _RemotePageState extends State { Wakelock.enable(); _physicalFocusNode.requestFocus(); gFFI.ffiModel.updateEventListener(widget.id); - gFFI.listenToMouse(true); + gFFI.inputModel.listenToMouse(true); gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id); } @override void dispose() { gFFI.dialogManager.hideMobileActionsOverlay(); - gFFI.listenToMouse(false); + gFFI.inputModel.listenToMouse(false); gFFI.invokeMethod("enable_soft_keyboard", true); _mobileFocusNode.dispose(); _physicalFocusNode.dispose(); @@ -81,7 +84,7 @@ class _RemotePageState extends State { } void resetTool() { - gFFI.resetModifiers(); + inputModel.resetModifiers(); } bool isKeyboardShown() { @@ -133,7 +136,7 @@ class _RemotePageState extends State { newValue[common] == oldValue[common]; ++common) {} for (i = 0; i < oldValue.length - common; ++i) { - gFFI.inputKey('VK_BACK'); + inputModel.inputKey('VK_BACK'); } if (newValue.length > common) { var s = newValue.substring(common); @@ -156,7 +159,7 @@ class _RemotePageState extends State { // ? } else if (newValue.length < oldValue.length) { final char = 'VK_BACK'; - gFFI.inputKey(char); + inputModel.inputKey(char); } else { final content = newValue.substring(oldValue.length); if (content.length > 1) { @@ -189,7 +192,7 @@ class _RemotePageState extends State { } else if (char == ' ') { char = 'VK_SPACE'; } - gFFI.inputKey(char); + inputModel.inputKey(char); } void openKeyboard() { @@ -216,7 +219,7 @@ class _RemotePageState extends State { final label = logicalKeyMap[e.logicalKey.keyId] ?? physicalKeyMap[e.physicalKey.usbHidUsage] ?? e.logicalKey.keyLabel; - gFFI.inputKey(label, down: down, press: press ?? false); + inputModel.inputKey(label, down: down, press: press ?? false); } @override @@ -296,7 +299,7 @@ class _RemotePageState extends State { }); } if (_isPhysicalMouse) { - gFFI.handleMouse(getEvent(e, 'mousemove')); + inputModel.handleMouse(getEvent(e, 'mousemove')); } }, onPointerDown: (e) { @@ -308,19 +311,19 @@ class _RemotePageState extends State { } } if (_isPhysicalMouse) { - gFFI.handleMouse(getEvent(e, 'mousedown')); + inputModel.handleMouse(getEvent(e, 'mousedown')); } }, onPointerUp: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - gFFI.handleMouse(getEvent(e, 'mouseup')); + inputModel.handleMouse(getEvent(e, 'mouseup')); } }, onPointerMove: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - gFFI.handleMouse(getEvent(e, 'mousemove')); + inputModel.handleMouse(getEvent(e, 'mousemove')); } }, onPointerSignal: (e) { @@ -331,7 +334,7 @@ class _RemotePageState extends State { } else if (e.scrollDelta.dy < 0) { dy = 1; } - gFFI.scroll(dy); + inputModel.scroll(dy); } }, child: MouseRegion( @@ -353,14 +356,14 @@ class _RemotePageState extends State { sendRawKey(e, press: true); } else { sendRawKey(e, down: true); - if (e.isAltPressed && !gFFI.alt) { - gFFI.alt = true; - } else if (e.isControlPressed && !gFFI.ctrl) { - gFFI.ctrl = true; - } else if (e.isShiftPressed && !gFFI.shift) { - gFFI.shift = true; - } else if (e.isMetaPressed && !gFFI.command) { - gFFI.command = true; + if (e.isAltPressed && !inputModel.alt) { + inputModel.alt = true; + } else if (e.isControlPressed && !inputModel.ctrl) { + inputModel.ctrl = true; + } else if (e.isShiftPressed && !inputModel.shift) { + inputModel.shift = true; + } else if (e.isMetaPressed && !inputModel.command) { + inputModel.command = true; } } } @@ -368,16 +371,16 @@ class _RemotePageState extends State { if (!_showEdit && e is RawKeyUpEvent) { if (key == LogicalKeyboardKey.altLeft || key == LogicalKeyboardKey.altRight) { - gFFI.alt = false; + inputModel.alt = false; } else if (key == LogicalKeyboardKey.controlLeft || key == LogicalKeyboardKey.controlRight) { - gFFI.ctrl = false; + inputModel.ctrl = false; } else if (key == LogicalKeyboardKey.shiftRight || key == LogicalKeyboardKey.shiftLeft) { - gFFI.shift = false; + inputModel.shift = false; } else if (key == LogicalKeyboardKey.metaLeft || key == LogicalKeyboardKey.metaRight) { - gFFI.command = false; + inputModel.command = false; } sendRawKey(e); } @@ -489,10 +492,10 @@ class _RemotePageState extends State { child: getBodyForMobile(), onTapUp: (d) { if (touchMode) { - gFFI.cursorModel.touch( - d.localPosition.dx, d.localPosition.dy, MouseButtons.left); + gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + inputModel.tap(MouseButtons.left); } else { - gFFI.tap(MouseButtons.left); + inputModel.tap(MouseButtons.left); } }, onDoubleTapDown: (d) { @@ -501,8 +504,8 @@ class _RemotePageState extends State { } }, onDoubleTap: () { - gFFI.tap(MouseButtons.left); - gFFI.tap(MouseButtons.left); + inputModel.tap(MouseButtons.left); + inputModel.tap(MouseButtons.left); }, onLongPressDown: (d) { if (touchMode) { @@ -515,16 +518,16 @@ class _RemotePageState extends State { gFFI.cursorModel .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); } - gFFI.tap(MouseButtons.right); + inputModel.tap(MouseButtons.right); }, onDoubleFinerTap: (d) { if (!touchMode) { - gFFI.tap(MouseButtons.right); + inputModel.tap(MouseButtons.right); } }, onHoldDragStart: (d) { if (!touchMode) { - gFFI.sendMouse('down', MouseButtons.left); + inputModel.sendMouse('down', MouseButtons.left); } }, onHoldDragUpdate: (d) { @@ -534,13 +537,13 @@ class _RemotePageState extends State { }, onHoldDragEnd: (_) { if (!touchMode) { - gFFI.sendMouse('up', MouseButtons.left); + inputModel.sendMouse('up', MouseButtons.left); } }, onOneFingerPanStart: (d) { if (touchMode) { gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - gFFI.sendMouse('down', MouseButtons.left); + inputModel.sendMouse('down', MouseButtons.left); } else { final cursorX = gFFI.cursorModel.x; final cursorY = gFFI.cursorModel.y; @@ -557,7 +560,7 @@ class _RemotePageState extends State { }, onOneFingerPanEnd: (d) { if (touchMode) { - gFFI.sendMouse('up', MouseButtons.left); + inputModel.sendMouse('up', MouseButtons.left); } }, // scale + pan event @@ -576,10 +579,10 @@ class _RemotePageState extends State { : (d) { _mouseScrollIntegral += d.delta.dy / 4; if (_mouseScrollIntegral > 1) { - gFFI.scroll(1); + inputModel.scroll(1); _mouseScrollIntegral = 0; } else if (_mouseScrollIntegral < -1) { - gFFI.scroll(-1); + inputModel.scroll(-1); _mouseScrollIntegral = 0; } }); @@ -627,15 +630,16 @@ class _RemotePageState extends State { int lastMouseDownButtons = 0; + // 重复 Map getEvent(PointerEvent evt, String type) { final Map out = {}; out['type'] = type; out['x'] = evt.position.dx; out['y'] = evt.position.dy; - if (gFFI.alt) out['alt'] = 'true'; - if (gFFI.shift) out['shift'] = 'true'; - if (gFFI.ctrl) out['ctrl'] = 'true'; - if (gFFI.command) out['command'] = 'true'; + if (inputModel.alt) out['alt'] = 'true'; + if (inputModel.shift) out['shift'] = 'true'; + if (inputModel.ctrl) out['ctrl'] = 'true'; + if (inputModel.command) out['command'] = 'true'; out['buttons'] = evt .buttons; // left button: 1, right button: 2, middle button: 4, 1 | 2 = 3 (left + right) if (evt.buttons != 0) { @@ -776,7 +780,7 @@ class _RemotePageState extends State { return SizedBox(); } final size = MediaQuery.of(context).size; - var wrap = (String text, void Function() onPressed, + wrap(String text, void Function() onPressed, [bool? active, IconData? icon]) { return TextButton( style: TextButton.styleFrom( @@ -795,22 +799,23 @@ class _RemotePageState extends State { : Text(translate(text), style: TextStyle(color: Colors.white, fontSize: 11)), onPressed: onPressed); - }; + } + final pi = gFFI.ffiModel.pi; final isMac = pi.platform == "Mac OS"; final modifiers = [ wrap('Ctrl ', () { - setState(() => gFFI.ctrl = !gFFI.ctrl); - }, gFFI.ctrl), + setState(() => inputModel.ctrl = !inputModel.ctrl); + }, inputModel.ctrl), wrap(' Alt ', () { - setState(() => gFFI.alt = !gFFI.alt); - }, gFFI.alt), + setState(() => inputModel.alt = !inputModel.alt); + }, inputModel.alt), wrap('Shift', () { - setState(() => gFFI.shift = !gFFI.shift); - }, gFFI.shift), + setState(() => inputModel.shift = !inputModel.shift); + }, inputModel.shift), wrap(isMac ? ' Cmd ' : ' Win ', () { - setState(() => gFFI.command = !gFFI.command); - }, gFFI.command), + setState(() => inputModel.command = !inputModel.command); + }, inputModel.command), ]; final keys = [ wrap( @@ -842,44 +847,44 @@ class _RemotePageState extends State { for (var i = 1; i <= 12; ++i) { final name = 'F' + i.toString(); fn.add(wrap(name, () { - gFFI.inputKey('VK_' + name); + inputModel.inputKey('VK_' + name); })); } final more = [ SizedBox(width: 9999), wrap('Esc', () { - gFFI.inputKey('VK_ESCAPE'); + inputModel.inputKey('VK_ESCAPE'); }), wrap('Tab', () { - gFFI.inputKey('VK_TAB'); + inputModel.inputKey('VK_TAB'); }), wrap('Home', () { - gFFI.inputKey('VK_HOME'); + inputModel.inputKey('VK_HOME'); }), wrap('End', () { - gFFI.inputKey('VK_END'); + inputModel.inputKey('VK_END'); }), wrap('Del', () { - gFFI.inputKey('VK_DELETE'); + inputModel.inputKey('VK_DELETE'); }), wrap('PgUp', () { - gFFI.inputKey('VK_PRIOR'); + inputModel.inputKey('VK_PRIOR'); }), wrap('PgDn', () { - gFFI.inputKey('VK_NEXT'); + inputModel.inputKey('VK_NEXT'); }), SizedBox(width: 9999), wrap('', () { - gFFI.inputKey('VK_LEFT'); + inputModel.inputKey('VK_LEFT'); }, false, Icons.keyboard_arrow_left), wrap('', () { - gFFI.inputKey('VK_UP'); + inputModel.inputKey('VK_UP'); }, false, Icons.keyboard_arrow_up), wrap('', () { - gFFI.inputKey('VK_DOWN'); + inputModel.inputKey('VK_DOWN'); }, false, Icons.keyboard_arrow_down), wrap('', () { - gFFI.inputKey('VK_RIGHT'); + inputModel.inputKey('VK_RIGHT'); }, false, Icons.keyboard_arrow_right), wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () { sendPrompt(isMac, 'VK_C'); @@ -1202,16 +1207,16 @@ void showSetOSPassword( } void sendPrompt(bool isMac, String key) { - final old = isMac ? gFFI.command : gFFI.ctrl; + final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl; if (isMac) { - gFFI.command = true; + gFFI.inputModel.command = true; } else { - gFFI.ctrl = true; + gFFI.inputModel.ctrl = true; } - gFFI.inputKey(key); + gFFI.inputModel.inputKey(key); if (isMac) { - gFFI.command = old; + gFFI.inputModel.command = old; } else { - gFFI.ctrl = old; + gFFI.inputModel.ctrl = old; } } diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index bdb3e9abf..3ebc97d61 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -50,10 +50,9 @@ class ChatModel with ChangeNotifier { bool get isShowChatPage => _isShowChatPage; - WeakReference _ffi; + final WeakReference parent; - /// Constructor - ChatModel(this._ffi); + ChatModel(this.parent); ChatUser get currentUser { final user = messages[currentID]?.chatUser; @@ -182,7 +181,7 @@ class ChatModel with ChangeNotifier { _currentID = id; notifyListeners(); } else { - final client = _ffi.target?.serverModel.clients + final client = parent.target?.serverModel.clients .firstWhere((client) => client.id == id); if (client == null) { return debugPrint( @@ -208,23 +207,23 @@ class ChatModel with ChangeNotifier { if (!_isShowChatPage) { toggleCMChatPage(id); } - _ffi.target?.serverModel.jumpTo(id); + parent.target?.serverModel.jumpTo(id); late final chatUser; if (id == clientModeID) { chatUser = ChatUser( - firstName: _ffi.target?.ffiModel.pi.username, + firstName: parent.target?.ffiModel.pi.username, id: await bind.mainGetLastRemoteId(), ); } else { - final client = _ffi.target?.serverModel.clients + final client = parent.target?.serverModel.clients .firstWhere((client) => client.id == id); if (client == null) { return debugPrint("Failed to receive msg,user doesn't exist"); } if (isDesktop) { window_on_top(null); - var index = _ffi.target?.serverModel.clients + var index = parent.target?.serverModel.clients .indexWhere((client) => client.id == id); if (index != null && index >= 0) { gFFI.serverModel.tabController.jumpTo(index); @@ -246,8 +245,8 @@ class ChatModel with ChangeNotifier { if (message.text.isNotEmpty) { _messages[_currentID]?.insert(message); if (_currentID == clientModeID) { - if (_ffi.target != null) { - bind.sessionSendChat(id: _ffi.target!.id, text: message.text); + if (parent.target != null) { + bind.sessionSendChat(id: parent.target!.id, text: message.text); } } else { bind.cmSendChat(connId: _currentID, msg: message.text); diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index d42eb7f2d..0274f2884 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -1,55 +1,85 @@ +import 'dart:convert'; +import 'dart:math'; + import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; +import 'package:get/get_core/src/get_main.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; +import '../common.dart'; import '../consts.dart'; import 'dart:ui' as ui; -class Keyboard { - late FFI _ffi; - late String _id; +/// Mouse button enum. +enum MouseButtons { left, right, wheel } + +extension ToString on MouseButtons { + String get value { + switch (this) { + case MouseButtons.left: + return 'left'; + case MouseButtons.right: + return 'right'; + case MouseButtons.wheel: + return 'wheel'; + } + } +} + +class InputModel { + final WeakReference parent; String keyboardMode = "legacy"; - Keyboard(FFI ffi, String id) { - _ffi = ffi; - _id = id; - } + // keyboard + var shift = false; + var ctrl = false; + var alt = false; + var command = false; + + // mouse + var _isPhysicalMouse = false; + int _lastMouseDownButtons = 0; + + get id => parent.target?.id ?? ""; + + InputModel(this.parent); KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) { - bind.sessionGetKeyboardName(id: _id).then((result) { + bind.sessionGetKeyboardName(id: id).then((result) { keyboardMode = result.toString(); }); final key = e.logicalKey; if (e is RawKeyDownEvent) { - if (!e.repeat){ - if (e.isAltPressed && !_ffi.alt) { - _ffi.alt = true; - } else if (e.isControlPressed && !_ffi.ctrl) { - _ffi.ctrl = true; - } else if (e.isShiftPressed && !_ffi.shift) { - _ffi.shift = true; - } else if (e.isMetaPressed && !_ffi.command) { - _ffi.command = true; + if (!e.repeat) { + if (e.isAltPressed && !alt) { + alt = true; + } else if (e.isControlPressed && !ctrl) { + ctrl = true; + } else if (e.isShiftPressed && !shift) { + shift = true; + } else if (e.isMetaPressed && !command) { + command = true; } } } if (e is RawKeyUpEvent) { if (key == LogicalKeyboardKey.altLeft || key == LogicalKeyboardKey.altRight) { - _ffi.alt = false; + alt = false; } else if (key == LogicalKeyboardKey.controlLeft || key == LogicalKeyboardKey.controlRight) { - _ffi.ctrl = false; + ctrl = false; } else if (key == LogicalKeyboardKey.shiftRight || key == LogicalKeyboardKey.shiftLeft) { - _ffi.shift = false; + shift = false; } else if (key == LogicalKeyboardKey.metaLeft || key == LogicalKeyboardKey.metaRight || key == LogicalKeyboardKey.superKey) { - _ffi.command = false; + command = false; } } @@ -91,12 +121,20 @@ class Keyboard { } else { down = false; } + inputRawKey(e.character ?? "", keyCode, scanCode, down); + } - _ffi.inputRawKey(e.character ?? "", keyCode, scanCode, down); + /// Send raw Key Event + void inputRawKey(String name, int keyCode, int scanCode, bool down) { + bind.sessionHandleFlutterKeyEvent( + id: id, + name: name, + keycode: keyCode, + scancode: scanCode, + downOrUp: down); } void legacyKeyboardMode(RawKeyEvent e) { - final key = e.logicalKey; if (e is RawKeyDownEvent) { if (e.repeat) { sendRawKey(e, press: true); @@ -114,22 +152,23 @@ class Keyboard { final label = physicalKeyMap[e.physicalKey.usbHidUsage] ?? logicalKeyMap[e.logicalKey.keyId] ?? e.logicalKey.keyLabel; - _ffi.inputKey(label, down: down, press: press ?? false); + inputKey(label, down: down, press: press ?? false); } -} -class Mouse { - var _isPhysicalMouse = false; - int _lastMouseDownButtons = 0; - - late FFI _ffi; - late String _id; - late double tabBarHeight; - - Mouse(FFI ffi, String id, double tabBarHeight_) { - _ffi = ffi; - _id = id; - tabBarHeight = tabBarHeight_; + /// Send key stroke event. + /// [down] indicates the key's state(down or up). + /// [press] indicates a click event(down and up). + void inputKey(String name, {bool? down, bool? press}) { + if (!parent.target!.ffiModel.keyboard()) return; + bind.sessionInputKey( + id: id, + name: name, + down: down ?? false, + press: press ?? true, + alt: alt, + ctrl: ctrl, + shift: shift, + command: command); } Map getEvent(PointerEvent evt, String type) { @@ -137,10 +176,10 @@ class Mouse { out['type'] = type; out['x'] = evt.position.dx; out['y'] = evt.position.dy; - if (_ffi.alt) out['alt'] = 'true'; - if (_ffi.shift) out['shift'] = 'true'; - if (_ffi.ctrl) out['ctrl'] = 'true'; - if (_ffi.command) out['command'] = 'true'; + if (alt) out['alt'] = 'true'; + if (shift) out['shift'] = 'true'; + if (ctrl) out['ctrl'] = 'true'; + if (command) out['command'] = 'true'; out['buttons'] = evt .buttons; // left button: 1, right button: 2, middle button: 4, 1 | 2 = 3 (left + right) if (evt.buttons != 0) { @@ -151,38 +190,92 @@ class Mouse { return out; } + /// Send a mouse tap event(down and up). + void tap(MouseButtons button) { + sendMouse('down', button); + sendMouse('up', button); + } + + /// Send scroll event with scroll distance [y]. + void scroll(int y) { + bind.sessionSendMouse( + id: id, + msg: json + .encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); + } + + /// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command]. + void resetModifiers() { + shift = ctrl = alt = command = false; + } + + /// Modify the given modifier map [evt] based on current modifier key status. + Map modify(Map evt) { + if (ctrl) evt['ctrl'] = 'true'; + if (shift) evt['shift'] = 'true'; + if (alt) evt['alt'] = 'true'; + if (command) evt['command'] = 'true'; + return evt; + } + + /// Send mouse press event. + void sendMouse(String type, MouseButtons button) { + if (!parent.target!.ffiModel.keyboard()) return; + bind.sessionSendMouse( + id: id, + msg: json.encode(modify({'type': type, 'buttons': button.value}))); + } + + void enterOrLeave(bool enter) { + // Fix status + if (!enter) { + resetModifiers(); + } + bind.sessionEnterOrLeave(id: id, enter: enter); + } + + /// Send mouse movement event with distance in [x] and [y]. + void moveMouse(double x, double y) { + if (!parent.target!.ffiModel.keyboard()) return; + var x2 = x.toInt(); + var y2 = y.toInt(); + bind.sessionSendMouse( + id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'}))); + } + void onPointHoverImage(PointerHoverEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (!_isPhysicalMouse) { _isPhysicalMouse = true; } if (_isPhysicalMouse) { - _ffi.handleMouse(getEvent(e, 'mousemove'), tabBarHeight: tabBarHeight); + handleMouse(getEvent(e, 'mousemove')); } } void onPointDownImage(PointerDownEvent e) { + debugPrint("onPointDownImage"); if (e.kind != ui.PointerDeviceKind.mouse) { if (_isPhysicalMouse) { _isPhysicalMouse = false; } } if (_isPhysicalMouse) { - _ffi.handleMouse(getEvent(e, 'mousedown'), tabBarHeight: tabBarHeight); + handleMouse(getEvent(e, 'mousedown')); } } void onPointUpImage(PointerUpEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - _ffi.handleMouse(getEvent(e, 'mouseup'), tabBarHeight: tabBarHeight); + handleMouse(getEvent(e, 'mouseup')); } } void onPointMoveImage(PointerMoveEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - _ffi.handleMouse(getEvent(e, 'mousemove'), tabBarHeight: tabBarHeight); + handleMouse(getEvent(e, 'mousemove')); } } @@ -201,7 +294,93 @@ class Mouse { dy = 1; } bind.sessionSendMouse( - id: _id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); + id: id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); + } + } + + void handleMouse(Map evt) { + var type = ''; + var isMove = false; + switch (evt['type']) { + case 'mousedown': + type = 'down'; + break; + case 'mouseup': + type = 'up'; + break; + case 'mousemove': + isMove = true; + break; + default: + return; + } + evt['type'] = type; + double x = evt['x']; + double y = max(0.0, evt['y']); + if (isDesktop) { + final RxBool fullscreen = Get.find(tag: 'fullscreen'); + final tabBarHeight = fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight; + y = y - tabBarHeight; + } + final canvasModel = parent.target!.canvasModel; + final ffiModel = parent.target!.ffiModel; + if (isMove) { + canvasModel.moveDesktopMouse(x, y); + } + final d = ffiModel.display; + if (canvasModel.scrollStyle == ScrollStyle.scrollbar) { + final imageWidth = d.width * canvasModel.scale; + final imageHeight = d.height * canvasModel.scale; + x += imageWidth * canvasModel.scrollX; + y += imageHeight * canvasModel.scrollY; + + // boxed size is a center widget + if (canvasModel.size.width > imageWidth) { + x -= ((canvasModel.size.width - imageWidth) / 2); + } + if (canvasModel.size.height > imageHeight) { + y -= ((canvasModel.size.height - imageHeight) / 2); + } + } else { + x -= canvasModel.x; + y -= canvasModel.y; + } + + x /= canvasModel.scale; + y /= canvasModel.scale; + x += d.x; + y += d.y; + if (type != '') { + x = 0; + y = 0; + } + // fix mouse out of bounds + x = min(max(0.0, x), d.width.toDouble()); + y = min(max(0.0, y), d.height.toDouble()); + evt['x'] = '${x.round()}'; + evt['y'] = '${y.round()}'; + var buttons = ''; + switch (evt['buttons']) { + case 1: + buttons = 'left'; + break; + case 2: + buttons = 'right'; + break; + case 4: + buttons = 'wheel'; + break; + } + evt['buttons'] = buttons; + bind.sessionSendMouse(id: id, msg: json.encode(evt)); + } + + /// Web only + void listenToMouse(bool yesOrNo) { + if (yesOrNo) { + platformFFI.startDesktopWebListener(); + } else { + platformFFI.stopDesktopWebListener(); } } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a1796ccd7..02ac6b2af 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -21,6 +21,7 @@ import '../common.dart'; import '../common/shared_state.dart'; import '../utils/image.dart' as img; import '../mobile/widgets/dialog.dart'; +import 'input_model.dart'; import 'platform_model.dart'; typedef HandleMsgBox = Function(Map evt, String id); @@ -723,15 +724,9 @@ class CursorModel with ChangeNotifier { return h - thresh; } - touch(double x, double y, MouseButtons button) { - moveLocal(x, y); - parent.target?.moveMouse(_x, _y); - parent.target?.tap(button); - } - move(double x, double y) { moveLocal(x, y); - parent.target?.moveMouse(_x, _y); + parent.target?.inputModel.moveMouse(_x, _y); } moveLocal(double x, double y) { @@ -746,7 +741,7 @@ class CursorModel with ChangeNotifier { reset() { _x = _displayOriginX; _y = _displayOriginY; - parent.target?.moveMouse(_x, _y); + parent.target?.inputModel.moveMouse(_x, _y); parent.target?.canvasModel.clear(true); notifyListeners(); } @@ -757,7 +752,7 @@ class CursorModel with ChangeNotifier { final scale = parent.target?.canvasModel.scale ?? 1.0; _x += dx / scale; _y += dy / scale; - parent.target?.moveMouse(_x, _y); + parent.target?.inputModel.moveMouse(_x, _y); notifyListeners(); return; } @@ -822,7 +817,7 @@ class CursorModel with ChangeNotifier { parent.target?.canvasModel.panY(-dy); } - parent.target?.moveMouse(_x, _y); + parent.target?.inputModel.moveMouse(_x, _y); notifyListeners(); } @@ -892,7 +887,7 @@ class CursorModel with ChangeNotifier { _displayOriginY = y; _x = x + 1; _y = y + 1; - parent.target?.moveMouse(x, y); + parent.target?.inputModel.moveMouse(x, y); parent.target?.canvasModel.resetOffset(); notifyListeners(); } @@ -903,7 +898,7 @@ class CursorModel with ChangeNotifier { _displayOriginY = y; _x = xCursor; _y = yCursor; - parent.target?.moveMouse(x, y); + parent.target?.inputModel.moveMouse(x, y); notifyListeners(); } @@ -1009,31 +1004,11 @@ class RecordingModel with ChangeNotifier { } } -/// Mouse button enum. -enum MouseButtons { left, right, wheel } - -extension ToString on MouseButtons { - String get value { - switch (this) { - case MouseButtons.left: - return 'left'; - case MouseButtons.right: - return 'right'; - case MouseButtons.wheel: - return 'wheel'; - } - } -} - enum ConnType { defaultConn, fileTransfer, portForward, rdp } /// Flutter state manager and data communication with the Rust core. class FFI { var id = ''; - var shift = false; - var ctrl = false; - var alt = false; - var command = false; var version = ''; var connType = ConnType.defaultConn; @@ -1051,6 +1026,7 @@ class FFI { late final UserModel userModel; // global late final QualityMonitorModel qualityMonitorModel; // session late final RecordingModel recordingModel; // recording + late final InputModel inputModel; // session FFI() { imageModel = ImageModel(WeakReference(this)); @@ -1064,89 +1040,11 @@ class FFI { userModel = UserModel(WeakReference(this)); qualityMonitorModel = QualityMonitorModel(WeakReference(this)); recordingModel = RecordingModel(WeakReference(this)); + inputModel = InputModel(WeakReference(this)); } - /// Send a mouse tap event(down and up). - tap(MouseButtons button) { - sendMouse('down', button); - sendMouse('up', button); - } - - /// Send scroll event with scroll distance [y]. - scroll(int y) { - bind.sessionSendMouse( - id: id, - msg: json - .encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); - } - - /// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command]. - resetModifiers() { - shift = ctrl = alt = command = false; - } - - /// Modify the given modifier map [evt] based on current modifier key status. - Map modify(Map evt) { - if (ctrl) evt['ctrl'] = 'true'; - if (shift) evt['shift'] = 'true'; - if (alt) evt['alt'] = 'true'; - if (command) evt['command'] = 'true'; - return evt; - } - - /// Send mouse press event. - sendMouse(String type, MouseButtons button) { - if (!ffiModel.keyboard()) return; - bind.sessionSendMouse( - id: id, - msg: json.encode(modify({'type': type, 'buttons': button.value}))); - } - - /// Send raw Key Event - inputRawKey(String name, int keyCode, int scanCode, bool down) { - bind.sessionHandleFlutterKeyEvent( - id: id, - name: name, - keycode: keyCode, - scancode: scanCode, - downOrUp: down); - } - - enterOrLeave(bool enter) { - // Fix status - if (!enter) { - resetModifiers(); - } - bind.sessionEnterOrLeave(id: id, enter: enter); - } - - /// Send key stroke event. - /// [down] indicates the key's state(down or up). - /// [press] indicates a click event(down and up). - inputKey(String name, {bool? down, bool? press}) { - if (!ffiModel.keyboard()) return; - bind.sessionInputKey( - id: id, - name: name, - down: down ?? false, - press: press ?? true, - alt: alt, - ctrl: ctrl, - shift: shift, - command: command); - } - - /// Send mouse movement event with distance in [x] and [y]. - moveMouse(double x, double y) { - if (!ffiModel.keyboard()) return; - var x2 = x.toInt(); - var y2 = y.toInt(); - bind.sessionSendMouse( - id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'}))); - } - - /// Connect with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward]. - connect(String id, + /// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward]. + void start(String id, {bool isFileTransfer = false, bool isPortForward = false, double tabBarHeight = 0.0}) { @@ -1190,7 +1088,7 @@ class FFI { } /// Login with [password], choose if the client should [remember] it. - login(String id, String password, bool remember) { + void login(String id, String password, bool remember) { bind.sessionLogin(id: id, password: password, remember: remember); } @@ -1207,89 +1105,11 @@ class FFI { cursorModel.clear(); ffiModel.clear(); canvasModel.clear(); - resetModifiers(); + inputModel.resetModifiers(); debugPrint('model $id closed'); } - handleMouse(Map evt, {double tabBarHeight = 0.0}) { - var type = ''; - var isMove = false; - switch (evt['type']) { - case 'mousedown': - type = 'down'; - break; - case 'mouseup': - type = 'up'; - break; - case 'mousemove': - isMove = true; - break; - default: - return; - } - evt['type'] = type; - double x = evt['x']; - double y = max(0.0, (evt['y'] as double) - tabBarHeight); - if (isMove) { - canvasModel.moveDesktopMouse(x, y); - } - final d = ffiModel.display; - if (canvasModel.scrollStyle == ScrollStyle.scrollbar) { - final imageWidth = d.width * canvasModel.scale; - final imageHeight = d.height * canvasModel.scale; - x += imageWidth * canvasModel.scrollX; - y += imageHeight * canvasModel.scrollY; - - // boxed size is a center widget - if (canvasModel.size.width > imageWidth) { - x -= ((canvasModel.size.width - imageWidth) / 2); - } - if (canvasModel.size.height > imageHeight) { - y -= ((canvasModel.size.height - imageHeight) / 2); - } - } else { - x -= canvasModel.x; - y -= canvasModel.y; - } - - x /= canvasModel.scale; - y /= canvasModel.scale; - x += d.x; - y += d.y; - if (type != '') { - x = 0; - y = 0; - } - // fix mouse out of bounds - x = min(max(0.0, x), d.width.toDouble()); - y = min(max(0.0, y), d.height.toDouble()); - evt['x'] = '${x.round()}'; - evt['y'] = '${y.round()}'; - var buttons = ''; - switch (evt['buttons']) { - case 1: - buttons = 'left'; - break; - case 2: - buttons = 'right'; - break; - case 4: - buttons = 'wheel'; - break; - } - evt['buttons'] = buttons; - bind.sessionSendMouse(id: id, msg: json.encode(evt)); - } - - listenToMouse(bool yesOrNo) { - if (yesOrNo) { - platformFFI.startDesktopWebListener(); - } else { - platformFFI.stopDesktopWebListener(); - } - } - - setMethodCallHandler(FMethod callback) { + void setMethodCallHandler(FMethod callback) { platformFFI.setMethodCallHandler(callback); } @@ -1324,8 +1144,8 @@ class PeerInfo { List displays = []; } -savePreference(String id, double xCursor, double yCursor, double xCanvas, - double yCanvas, double scale, int currentDisplay) async { +Future savePreference(String id, double xCursor, double yCursor, + double xCanvas, double yCanvas, double scale, int currentDisplay) async { SharedPreferences prefs = await SharedPreferences.getInstance(); final p = {}; p['xCursor'] = xCursor; @@ -1346,12 +1166,12 @@ Future?> getPreference(String id) async { return m; } -removePreference(String id) async { +void removePreference(String id) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.remove('peer$id'); } -initializeCursorAndCanvas(FFI ffi) async { +Future initializeCursorAndCanvas(FFI ffi) async { var p = await getPreference(ffi.id); int currentDisplay = 0; if (p != null) { @@ -1371,16 +1191,3 @@ initializeCursorAndCanvas(FFI ffi) async { ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor); ffi.canvasModel.update(xCanvas, yCanvas, scale); } - -/// Translate text based on the pre-defined dictionary. -/// note: params [FFI?] can be used to replace global FFI implementation -/// for example: during global initialization, gFFI not exists yet. -// String translate(String name, {FFI? ffi}) { -// if (name.startsWith('Failed to') && name.contains(': ')) { -// return name.split(': ').map((x) => translate(x)).join(': '); -// } -// var a = 'translate'; -// var b = '{"locale": "$localeName", "text": "$name"}'; -// -// return (ffi ?? gFFI).getByName(a, b); -// }