From 729f4c0733a24cad283879c18f85b1624c2590b0 Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 17:52:36 +0800 Subject: [PATCH 1/6] remove unused of model.dart --- flutter/lib/common.dart | 7 +++++++ flutter/lib/desktop/pages/remote_page.dart | 4 ---- flutter/lib/models/ab_model.dart | 10 ++++----- flutter/lib/models/model.dart | 24 ---------------------- flutter/lib/models/user_model.dart | 7 ++----- 5 files changed, 13 insertions(+), 39 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index a708a0a15..92ae5c0d6 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1095,3 +1095,10 @@ void connect(BuildContext context, String id, currentFocus.unfocus(); } } + +Future> getHttpHeaders() async { + return { + 'Authorization': + 'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}' + }; +} diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f7366b960..f46af0931 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -131,10 +131,6 @@ class _RemotePageState extends State _removeStates(widget.id); } - void resetTool() { - _ffi.resetModifiers(); - } - Widget buildBody(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).backgroundColor, diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 161a4d8a5..b4de861e9 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -6,6 +6,8 @@ import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; +import '../common.dart'; + class AbModel with ChangeNotifier { var abLoading = false; var abError = ""; @@ -27,7 +29,7 @@ class AbModel with ChangeNotifier { final api = "${await getApiServer()}/api/ab/get"; try { final resp = - await http.post(Uri.parse(api), headers: await _getHeaders()); + await http.post(Uri.parse(api), headers: await getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); if (json.containsKey('error')) { @@ -61,10 +63,6 @@ class AbModel with ChangeNotifier { notifyListeners(); } - Future>? _getHeaders() { - return _ffi?.getHttpHeaders(); - } - void addId(String id) async { if (idContainBy(id)) { return; @@ -93,7 +91,7 @@ class AbModel with ChangeNotifier { abLoading = true; notifyListeners(); final api = "${await getApiServer()}/api/ab"; - var authHeaders = await _getHeaders() ?? Map(); + var authHeaders = await getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; final body = jsonEncode({ "data": jsonEncode({"tags": tags, "peers": peers}) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c90b07daf..a1796ccd7 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -1297,29 +1296,6 @@ class FFI { Future invokeMethod(String method, [dynamic arguments]) async { return await platformFFI.invokeMethod(method, arguments); } - - Future> getAudioInputs() async { - return await bind.mainGetSoundInputs(); - } - - Future getDefaultAudioInput() async { - final input = await bind.mainGetOption(key: 'audio-input'); - if (input.isEmpty && Platform.isWindows) { - return 'System Sound'; - } - return input; - } - - setDefaultAudioInput(String input) { - bind.mainSetOption(key: 'audio-input', value: input); - } - - Future> getHttpHeaders() async { - return { - 'Authorization': - 'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}' - }; - } } class Display { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index b43b4510b..e195c205d 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; +import '../common.dart'; import 'model.dart'; import 'platform_model.dart'; @@ -35,7 +36,7 @@ class UserModel extends ChangeNotifier { "id": await bind.mainGetMyId(), "uuid": await bind.mainGetUuid(), }, - headers: await _getHeaders()); + headers: await getHttpHeaders()); await Future.wait([ bind.mainSetLocalOption(key: 'access_token', value: ''), bind.mainSetLocalOption(key: 'user_info', value: ''), @@ -46,10 +47,6 @@ class UserModel extends ChangeNotifier { notifyListeners(); } - Future>? _getHeaders() { - return parent.target?.getHttpHeaders(); - } - Future> login(String userName, String pass) async { final url = await bind.mainGetApiServer(); try { From 3dc9ecce2947ca29f25539edb1ad78434bce08f6 Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 20:35:02 +0800 Subject: [PATCH 2/6] refactor input_model --- flutter/lib/common.dart | 9 +- .../lib/desktop/pages/file_manager_page.dart | 2 +- .../lib/desktop/pages/port_forward_page.dart | 2 +- flutter/lib/desktop/pages/remote_page.dart | 24 +- .../lib/mobile/pages/file_manager_page.dart | 2 +- flutter/lib/mobile/pages/remote_page.dart | 145 +++++----- flutter/lib/models/chat_model.dart | 19 +- flutter/lib/models/input_model.dart | 271 +++++++++++++++--- flutter/lib/models/model.dart | 229 ++------------- 9 files changed, 344 insertions(+), 359 deletions(-) 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); -// } From 77fcf2d4faed35c138adbdf096b6fc2a4fc80f6a Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 22:16:27 +0800 Subject: [PATCH 3/6] refactor RawPointerMouseRegion & RawKeyFocusScope --- flutter/lib/common/widgets/remote_input.dart | 60 +++++ flutter/lib/desktop/pages/remote_page.dart | 41 ++- flutter/lib/mobile/pages/remote_page.dart | 247 ++++++------------- flutter/lib/models/input_model.dart | 19 +- 4 files changed, 157 insertions(+), 210 deletions(-) create mode 100644 flutter/lib/common/widgets/remote_input.dart diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart new file mode 100644 index 000000000..ad50d4839 --- /dev/null +++ b/flutter/lib/common/widgets/remote_input.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../models/input_model.dart'; + +class RawKeyFocusScope extends StatelessWidget { + final FocusNode? focusNode; + final ValueChanged? onFocusChange; + final InputModel inputModel; + final Widget child; + + RawKeyFocusScope( + {this.focusNode, + this.onFocusChange, + required this.inputModel, + required this.child}); + + @override + Widget build(BuildContext context) { + return FocusScope( + autofocus: true, + child: Focus( + autofocus: true, + canRequestFocus: true, + focusNode: focusNode, + onFocusChange: onFocusChange, + onKey: inputModel.handleRawKeyEvent, + child: child)); + } +} + +class RawPointerMouseRegion extends StatelessWidget { + final InputModel inputModel; + final Widget child; + final MouseCursor? cursor; + final PointerEnterEventListener? onEnter; + final PointerExitEventListener? onExit; + + RawPointerMouseRegion( + {this.onEnter, + this.onExit, + this.cursor, + required this.inputModel, + required this.child}); + + @override + Widget build(BuildContext context) { + return Listener( + onPointerHover: inputModel.onPointHoverImage, + onPointerDown: inputModel.onPointDownImage, + onPointerUp: inputModel.onPointUpImage, + onPointerMove: inputModel.onPointMoveImage, + onPointerSignal: inputModel.onPointerSignalImage, + child: MouseRegion( + cursor: cursor ?? MouseCursor.defer, + onEnter: onEnter, + onExit: onExit, + child: child)); + } +} diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 3d6f3ee21..8f3644ded 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; +import '../../common/widgets/remote_input.dart'; import '../widgets/remote_menubar.dart'; import '../../common.dart'; import '../../mobile/widgets/dialog.dart'; @@ -135,7 +136,13 @@ class _RemotePageState extends State _ffi.dialogManager.setOverlayState(Overlay.of(context)); return Container( color: Colors.black, - child: getRawPointerAndKeyBody(getBodyForDesktop(context))); + child: RawKeyFocusScope( + focusNode: _rawKeyFocusNode, + onFocusChange: (bool v) { + _imageFocused = v; + }, + inputModel: _ffi.inputModel, + child: getBodyForDesktop(context))); }) ], )); @@ -159,20 +166,6 @@ class _RemotePageState extends State ], child: buildBody(context))); } - Widget getRawPointerAndKeyBody(Widget child) { - return FocusScope( - autofocus: true, - child: Focus( - autofocus: true, - canRequestFocus: true, - focusNode: _rawKeyFocusNode, - onFocusChange: (bool v) { - _imageFocused = v; - }, - onKey: _ffi.inputModel.handleRawKeyEvent, - child: child)); - } - void enterView(PointerEnterEvent evt) { if (!_imageFocused) { _rawKeyFocusNode.requestFocus(); @@ -200,17 +193,6 @@ class _RemotePageState extends State _ffi.inputModel.enterOrLeave(false); } - Widget _buildImageListener(Widget child) { - return Listener( - 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)); - } - Widget getBodyForDesktop(BuildContext context) { var paints = [ MouseRegion(onEnter: (evt) { @@ -226,7 +208,12 @@ class _RemotePageState extends State cursorOverImage: _cursorOverImage, keyboardEnabled: _keyboardEnabled, remoteCursorMoved: _remoteCursorMoved, - listenerBuilder: _buildImageListener, + listenerBuilder: (child) => RawPointerMouseRegion( + onEnter: enterView, + onExit: leaveView, + inputModel: _ffi.inputModel, + child: child, + ), ); })) ]; diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 5be604ee7..30b52b141 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -7,11 +7,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/models/chat_model.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import '../../common.dart'; -import '../../consts.dart'; +import '../../common/widgets/remote_input.dart'; import '../../models/input_model.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; @@ -26,7 +27,7 @@ class RemotePage extends StatefulWidget { final String id; @override - _RemotePageState createState() => _RemotePageState(); + State createState() => _RemotePageState(); } class _RemotePageState extends State { @@ -44,9 +45,8 @@ class _RemotePageState extends State { final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode(); var _showEdit = false; // use soft keyboard - var _isPhysicalMouse = false; - get inputModel => gFFI.inputModel; + InputModel get inputModel => gFFI.inputModel; @override void initState() { @@ -148,8 +148,8 @@ class _RemotePageState extends State { } return; } - if (oldValue.length > 0 && - newValue.length > 0 && + if (oldValue.isNotEmpty && + newValue.isNotEmpty && oldValue[0] == '\1' && newValue[0] != '\1') { // clipboard @@ -214,14 +214,6 @@ class _RemotePageState extends State { }); } - void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) { - // for maximum compatibility - final label = logicalKeyMap[e.logicalKey.keyId] ?? - physicalKeyMap[e.physicalKey.usbHidUsage] ?? - e.logicalKey.keyLabel; - inputModel.inputKey(label, down: down, press: press ?? false); - } - @override Widget build(BuildContext context) { final pi = Provider.of(context).pi; @@ -234,159 +226,68 @@ class _RemotePageState extends State { clientClose(gFFI.dialogManager); return false; }, - child: getRawPointerAndKeyBody( - keyboard, - Scaffold( - // resizeToAvoidBottomInset: true, - floatingActionButton: !showActionButton - ? null - : FloatingActionButton( - mini: !hideKeyboard, - child: Icon( - hideKeyboard ? Icons.expand_more : Icons.expand_less), - backgroundColor: MyTheme.accent, - onPressed: () { - setState(() { - if (hideKeyboard) { - _showEdit = false; - gFFI.invokeMethod("enable_soft_keyboard", false); - _mobileFocusNode.unfocus(); - _physicalFocusNode.requestFocus(); - } else { - _showBar = !_showBar; - } - }); - }), - bottomNavigationBar: _showBar && pi.displays.length > 0 - ? getBottomAppBar(keyboard) - : null, - body: Overlay( - initialEntries: [ - OverlayEntry(builder: (context) { - return Container( - color: Colors.black, - child: isWebDesktop - ? getBodyForDesktopWithListener(keyboard) - : SafeArea(child: - OrientationBuilder(builder: (ctx, orientation) { - if (_currentOrientation != orientation) { - Timer(const Duration(milliseconds: 200), () { - gFFI.dialogManager - .resetMobileActionsOverlay(ffi: gFFI); - _currentOrientation = orientation; - gFFI.canvasModel.updateViewStyle(); - }); - } - return Container( - color: MyTheme.canvasColor, - child: _isPhysicalMouse - ? getBodyForMobile() - : getBodyForMobileWithGesture()); - }))); - }) - ], - ))), + child: getRawPointerAndKeyBody(Scaffold( + // resizeToAvoidBottomInset: true, + floatingActionButton: !showActionButton + ? null + : FloatingActionButton( + mini: !hideKeyboard, + child: Icon( + hideKeyboard ? Icons.expand_more : Icons.expand_less), + backgroundColor: MyTheme.accent, + onPressed: () { + setState(() { + if (hideKeyboard) { + _showEdit = false; + gFFI.invokeMethod("enable_soft_keyboard", false); + _mobileFocusNode.unfocus(); + _physicalFocusNode.requestFocus(); + } else { + _showBar = !_showBar; + } + }); + }), + bottomNavigationBar: _showBar && pi.displays.isNotEmpty + ? getBottomAppBar(keyboard) + : null, + body: Overlay( + initialEntries: [ + OverlayEntry(builder: (context) { + return Container( + color: Colors.black, + child: isWebDesktop + ? getBodyForDesktopWithListener(keyboard) + : SafeArea(child: + OrientationBuilder(builder: (ctx, orientation) { + if (_currentOrientation != orientation) { + Timer(const Duration(milliseconds: 200), () { + gFFI.dialogManager + .resetMobileActionsOverlay(ffi: gFFI); + _currentOrientation = orientation; + gFFI.canvasModel.updateViewStyle(); + }); + } + return Obx(() => Container( + color: MyTheme.canvasColor, + child: inputModel.isPhysicalMouse.value + ? getBodyForMobile() + : getBodyForMobileWithGesture())); + }))); + }) + ], + ))), ); } - Widget getRawPointerAndKeyBody(bool keyboard, Widget child) { - return Listener( - onPointerHover: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) return; - if (!_isPhysicalMouse) { - setState(() { - _isPhysicalMouse = true; - }); - } - if (_isPhysicalMouse) { - inputModel.handleMouse(getEvent(e, 'mousemove')); - } - }, - onPointerDown: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) { - if (_isPhysicalMouse) { - setState(() { - _isPhysicalMouse = false; - }); - } - } - if (_isPhysicalMouse) { - inputModel.handleMouse(getEvent(e, 'mousedown')); - } - }, - onPointerUp: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) return; - if (_isPhysicalMouse) { - inputModel.handleMouse(getEvent(e, 'mouseup')); - } - }, - onPointerMove: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) return; - if (_isPhysicalMouse) { - inputModel.handleMouse(getEvent(e, 'mousemove')); - } - }, - onPointerSignal: (e) { - if (e is PointerScrollEvent) { - var dy = 0; - if (e.scrollDelta.dy > 0) { - dy = -1; - } else if (e.scrollDelta.dy < 0) { - dy = 1; - } - inputModel.scroll(dy); - } - }, - child: MouseRegion( - cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer, - child: FocusScope( - autofocus: true, - child: Focus( - autofocus: true, - canRequestFocus: true, - focusNode: _physicalFocusNode, - onKey: (data, e) { - final key = e.logicalKey; - if (e is RawKeyDownEvent) { - if (e.repeat && - !e.isAltPressed && - !e.isControlPressed && - !e.isShiftPressed && - !e.isMetaPressed) { - sendRawKey(e, press: true); - } else { - sendRawKey(e, down: 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; - } - } - } - // [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter - if (!_showEdit && e is RawKeyUpEvent) { - if (key == LogicalKeyboardKey.altLeft || - key == LogicalKeyboardKey.altRight) { - inputModel.alt = false; - } else if (key == LogicalKeyboardKey.controlLeft || - key == LogicalKeyboardKey.controlRight) { - inputModel.ctrl = false; - } else if (key == LogicalKeyboardKey.shiftRight || - key == LogicalKeyboardKey.shiftLeft) { - inputModel.shift = false; - } else if (key == LogicalKeyboardKey.metaLeft || - key == LogicalKeyboardKey.metaRight) { - inputModel.command = false; - } - sendRawKey(e); - } - return KeyEventResult.handled; - }, - child: child)))); + Widget getRawPointerAndKeyBody(Widget child) { + final keyboard = gFFI.ffiModel.permissions['keyboard'] != false; + return RawPointerMouseRegion( + cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer, + inputModel: inputModel, + child: RawKeyFocusScope( + focusNode: _physicalFocusNode, + inputModel: inputModel, + child: child)); } Widget getBottomAppBar(bool keyboard) { @@ -685,7 +586,7 @@ class _RemotePageState extends State { if (perms['keyboard'] != false) { if (pi.platform == 'Linux' || pi.sasEnabled) { more.add(PopupMenuItem( - child: Text(translate('Insert') + ' Ctrl + Alt + Del'), + child: Text('${translate('Insert')} Ctrl + Alt + Del'), value: 'cad')); } more.add(PopupMenuItem( @@ -694,8 +595,8 @@ class _RemotePageState extends State { await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem( - child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + - 'lock user input')), + child: Text(translate( + '${gFFI.ffiModel.inputBlocked ? 'Unb' : 'B'}lock user input')), value: 'block-input')); } } @@ -720,7 +621,7 @@ class _RemotePageState extends State { } else if (value == 'block-input') { bind.sessionToggleOption( id: widget.id, - value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); + value: '${gFFI.ffiModel.inputBlocked ? 'un' : ''}block-input'); gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; } else if (value == 'refresh') { bind.sessionRefresh(id: widget.id); @@ -845,9 +746,9 @@ class _RemotePageState extends State { SizedBox(width: 9999), ]; for (var i = 1; i <= 12; ++i) { - final name = 'F' + i.toString(); + final name = 'F$i'; fn.add(wrap(name, () { - inputModel.inputKey('VK_' + name); + inputModel.inputKey('VK_$name'); })); } final more = [ @@ -920,7 +821,7 @@ class ImagePaint extends StatelessWidget { final adjust = gFFI.cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( - painter: new ImagePainter( + painter: ImagePainter( image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s), ); } @@ -934,7 +835,7 @@ class CursorPaint extends StatelessWidget { final adjust = gFFI.cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( - painter: new ImagePainter( + painter: ImagePainter( image: m.image, x: m.x * s - m.hotx + c.x, y: m.y * s - m.hoty + c.y - adjust, @@ -960,7 +861,7 @@ class ImagePainter extends CustomPainter { void paint(Canvas canvas, Size size) { if (image == null) return; canvas.scale(scale, scale); - canvas.drawImage(image!, new Offset(x, y), new Paint()); + canvas.drawImage(image!, Offset(x, y), Paint()); } @override diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 0274f2884..4f71f591b 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -5,7 +5,6 @@ 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'; @@ -40,7 +39,7 @@ class InputModel { var command = false; // mouse - var _isPhysicalMouse = false; + final isPhysicalMouse = false.obs; int _lastMouseDownButtons = 0; get id => parent.target?.id ?? ""; @@ -245,10 +244,10 @@ class InputModel { void onPointHoverImage(PointerHoverEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; - if (!_isPhysicalMouse) { - _isPhysicalMouse = true; + if (!isPhysicalMouse.value) { + isPhysicalMouse.value = true; } - if (_isPhysicalMouse) { + if (isPhysicalMouse.value) { handleMouse(getEvent(e, 'mousemove')); } } @@ -256,25 +255,25 @@ class InputModel { void onPointDownImage(PointerDownEvent e) { debugPrint("onPointDownImage"); if (e.kind != ui.PointerDeviceKind.mouse) { - if (_isPhysicalMouse) { - _isPhysicalMouse = false; + if (isPhysicalMouse.value) { + isPhysicalMouse.value = false; } } - if (_isPhysicalMouse) { + if (isPhysicalMouse.value) { handleMouse(getEvent(e, 'mousedown')); } } void onPointUpImage(PointerUpEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; - if (_isPhysicalMouse) { + if (isPhysicalMouse.value) { handleMouse(getEvent(e, 'mouseup')); } } void onPointMoveImage(PointerMoveEvent e) { if (e.kind != ui.PointerDeviceKind.mouse) return; - if (_isPhysicalMouse) { + if (isPhysicalMouse.value) { handleMouse(getEvent(e, 'mousemove')); } } From 6b664d0965c2d443701304f909db07a231cac68a Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 22:56:18 +0800 Subject: [PATCH 4/6] mobile add new input map mode radio --- flutter/lib/common.dart | 4 +++- flutter/lib/mobile/pages/remote_page.dart | 25 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 93e8a0be1..a5edbcee1 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -817,8 +817,10 @@ class PermissionManager { } RadioListTile getRadio( - String name, T toValue, T curValue, void Function(T?) onChange) { + String name, T toValue, T curValue, void Function(T?) onChange, + {EdgeInsetsGeometry? contentPadding}) { return RadioListTile( + contentPadding: contentPadding, controlAffinity: ListTileControlAffinity.trailing, title: Text(translate(name)), value: toValue, diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 30b52b141..021c3fd6a 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -584,6 +584,9 @@ class _RemotePageState extends State { child: Text(translate('Reset canvas')), value: 'reset_canvas')); } if (perms['keyboard'] != false) { + more.add(PopupMenuItem( + child: Text(translate('Physical Keyboard Input Mode')), + value: 'input-mode')); if (pi.platform == 'Linux' || pi.sasEnabled) { more.add(PopupMenuItem( child: Text('${translate('Insert')} Ctrl + Alt + Del'), @@ -616,6 +619,8 @@ class _RemotePageState extends State { ); if (value == 'cad') { bind.sessionCtrlAltDel(id: widget.id); + } else if (value == 'input-mode') { + changePhysicalKeyboardInputMode(); } else if (value == 'lock') { bind.sessionLockScreen(id: widget.id); } else if (value == 'block-input') { @@ -675,6 +680,26 @@ class _RemotePageState extends State { })); } + void changePhysicalKeyboardInputMode() async { + var current = await bind.sessionGetKeyboardName(id: widget.id); + gFFI.dialogManager.show((setState, close) { + void setMode(String? v) async { + await bind.sessionSetKeyboardMode(id: widget.id, keyboardMode: v ?? ''); + setState(() => current = v ?? ''); + Future.delayed(Duration(milliseconds: 300), close); + } + + return CustomAlertDialog( + title: Text(translate('Physical Keyboard Input Mode')), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + getRadio('Legacy mode', 'legacy', current, setMode, + contentPadding: EdgeInsets.zero), + getRadio('Map mode', 'map', current, setMode, + contentPadding: EdgeInsets.zero), + ])); + }, clickMaskDismiss: true); + } + Widget getHelpTools() { final keyboard = isKeyboardShown(); if (!keyboard) { From 23f0f4e33a4938e26ec155d2070ca526c8706a39 Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 23:05:11 +0800 Subject: [PATCH 5/6] move QualityMonitor widget to common --- flutter/lib/common/widgets/overlay.dart | 47 ++++++++++++++ flutter/lib/desktop/pages/remote_page.dart | 46 +------------- flutter/lib/mobile/pages/remote_page.dart | 73 ++-------------------- 3 files changed, 52 insertions(+), 114 deletions(-) diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index 89dbe2ea6..c5a3ad8b6 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; +import 'package:provider/provider.dart'; import '../../desktop/widgets/tabbar_widget.dart'; import '../../mobile/pages/chat_page.dart'; import '../../models/chat_model.dart'; +import '../../models/model.dart'; class DraggableChatWindow extends StatelessWidget { const DraggableChatWindow( @@ -311,3 +313,48 @@ class _DraggableState extends State { child: widget.builder(context, onPanUpdate)); } } + +class QualityMonitor extends StatelessWidget { + static const textStyle = TextStyle(color: MyTheme.grayBg); + final QualityMonitorModel qualityMonitorModel; + QualityMonitor(this.qualityMonitorModel); + + @override + Widget build(BuildContext context) => ChangeNotifierProvider.value( + value: qualityMonitorModel, + child: Consumer( + builder: (context, qualityMonitorModel, child) => Positioned( + top: 10, + right: 10, + child: qualityMonitorModel.show + ? Container( + padding: const EdgeInsets.all(8), + color: MyTheme.canvasColor.withAlpha(120), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Speed: ${qualityMonitorModel.data.speed ?? ''}", + style: textStyle, + ), + Text( + "FPS: ${qualityMonitorModel.data.fps ?? ''}", + style: textStyle, + ), + Text( + "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", + style: textStyle, + ), + Text( + "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", + style: textStyle, + ), + Text( + "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", + style: textStyle, + ), + ], + ), + ) + : const SizedBox.shrink()))); +} diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 8f3644ded..a7bed04ef 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; +import '../../common/widgets/overlay.dart'; import '../../common/widgets/remote_input.dart'; import '../widgets/remote_menubar.dart'; import '../../common.dart'; @@ -418,48 +419,3 @@ class ImagePainter extends CustomPainter { return oldDelegate != this; } } - -class QualityMonitor extends StatelessWidget { - static const textStyle = TextStyle(color: MyTheme.grayBg); - final QualityMonitorModel qualityMonitorModel; - QualityMonitor(this.qualityMonitorModel); - - @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: qualityMonitorModel, - child: Consumer( - builder: (context, qualityMonitorModel, child) => Positioned( - top: 10, - right: 10, - child: qualityMonitorModel.show - ? Container( - padding: const EdgeInsets.all(8), - color: MyTheme.canvasColor.withAlpha(120), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Speed: ${qualityMonitorModel.data.speed ?? ''}", - style: textStyle, - ), - Text( - "FPS: ${qualityMonitorModel.data.fps ?? ''}", - style: textStyle, - ), - Text( - "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", - style: textStyle, - ), - Text( - "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", - style: textStyle, - ), - Text( - "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", - style: textStyle, - ), - ], - ), - ) - : const SizedBox.shrink()))); -} diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 021c3fd6a..014266fb8 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:ui' as ui; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; @@ -12,6 +11,7 @@ import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import '../../common.dart'; +import '../../common/widgets/overlay.dart'; import '../../common/widgets/remote_input.dart'; import '../../models/input_model.dart'; import '../../models/model.dart'; @@ -118,7 +118,7 @@ class _RemotePageState extends State { } // handle mobile virtual keyboard - void handleInput(String newValue) { + void handleSoftKeyboardInput(String newValue) { var oldValue = _value; _value = newValue; if (isIOS) { @@ -495,7 +495,7 @@ class _RemotePageState extends State { child: Stack(children: [ ImagePaint(), CursorPaint(), - QualityMonitor(), + QualityMonitor(gFFI.qualityMonitorModel), getHelpTools(), SizedBox( width: 0, @@ -512,7 +512,7 @@ class _RemotePageState extends State { initialValue: _value, // trick way to make backspace work always keyboardType: TextInputType.multiline, - onChanged: handleInput, + onChanged: handleSoftKeyboardInput, ), ), ])); @@ -529,28 +529,6 @@ class _RemotePageState extends State { color: MyTheme.canvasColor, child: Stack(children: paints)); } - 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 (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) { - lastMouseDownButtons = evt.buttons; - } else { - out['buttons'] = lastMouseDownButtons; - } - return out; - } - void showActions(String id) async { final size = MediaQuery.of(context).size; final x = 120.0; @@ -895,49 +873,6 @@ class ImagePainter extends CustomPainter { } } -// TODO global widget -class QualityMonitor extends StatelessWidget { - static final textColor = Colors.grey.shade200; - @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: gFFI.qualityMonitorModel, - child: Consumer( - builder: (context, qualityMonitorModel, child) => Positioned( - top: 10, - right: 10, - child: qualityMonitorModel.show - ? Container( - padding: EdgeInsets.all(8), - color: MyTheme.canvasColor.withAlpha(120), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Speed: ${qualityMonitorModel.data.speed ?? ''}", - style: TextStyle(color: textColor), - ), - Text( - "FPS: ${qualityMonitorModel.data.fps ?? ''}", - style: TextStyle(color: textColor), - ), - Text( - "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", - style: TextStyle(color: textColor), - ), - Text( - "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", - style: TextStyle(color: textColor), - ), - Text( - "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", - style: TextStyle(color: textColor), - ), - ], - ), - ) - : SizedBox.shrink()))); -} - void showOptions(String id, OverlayDialogManager dialogManager) async { String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; From f45bb13d24dd2a65938178cdc354f1bdc1740fd4 Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 27 Sep 2022 23:08:01 +0800 Subject: [PATCH 6/6] adjust kWindowEdgeSize --- flutter/lib/consts.dart | 4 ++-- flutter/lib/desktop/widgets/scroll_wrapper.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 2ee6c49f4..b9086bde9 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -24,9 +24,9 @@ const Size kConnectionManagerWindowSize = Size(300, 400); /// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse const kDefaultScrollAmountMultiplier = 5.0; const kDefaultScrollDuration = Duration(milliseconds: 50); -const kDefaultMouseWhellThrottleDuration = Duration(milliseconds: 50); +const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50); const kFullScreenEdgeSize = 0.0; -const kWindowEdgeSize = 1.0; +const kWindowEdgeSize = 5.0; const kInvalidValueStr = "InvalidValueStr"; diff --git a/flutter/lib/desktop/widgets/scroll_wrapper.dart b/flutter/lib/desktop/widgets/scroll_wrapper.dart index aaa3aa403..32ed149e5 100644 --- a/flutter/lib/desktop/widgets/scroll_wrapper.dart +++ b/flutter/lib/desktop/widgets/scroll_wrapper.dart @@ -18,7 +18,7 @@ class DesktopScrollWrapper extends StatelessWidget { scrollDuration: kDefaultScrollDuration, scrollCurve: Curves.linearToEaseOut, mouseWheelTurnsThrottleTimeMs: - kDefaultMouseWhellThrottleDuration.inMilliseconds, + kDefaultMouseWheelThrottleDuration.inMilliseconds, scrollAmountMultiplier: kDefaultScrollAmountMultiplier), child: child, );