From 985c616ca617ea54e7dc725a9ac471f6cc6ced7a Mon Sep 17 00:00:00 2001 From: kingtous Date: Mon, 13 Jun 2022 21:07:26 +0800 Subject: [PATCH] refactor: make multi FFI object && initial flutter multi sessions support Signed-off-by: Kingtous --- flutter/lib/common.dart | 23 +- .../lib/desktop/pages/connection_page.dart | 6 +- .../lib/desktop/pages/desktop_home_page.dart | 6 +- flutter/lib/desktop/pages/remote_page.dart | 317 ++++++++------- .../desktop/screen/desktop_remote_screen.dart | 9 +- flutter/lib/main.dart | 24 +- flutter/lib/mobile/pages/chat_page.dart | 7 +- flutter/lib/mobile/pages/connection_page.dart | 14 +- .../lib/mobile/pages/file_manager_page.dart | 15 +- flutter/lib/mobile/pages/remote_page.dart | 368 +++++++++--------- flutter/lib/mobile/pages/scan_page.dart | 34 +- flutter/lib/mobile/pages/server_page.dart | 35 +- flutter/lib/mobile/pages/settings_page.dart | 57 +-- flutter/lib/mobile/widgets/dialog.dart | 7 +- flutter/lib/mobile/widgets/overlay.dart | 10 +- flutter/lib/models/chat_model.dart | 17 +- flutter/lib/models/file_model.dart | 52 +-- flutter/lib/models/model.dart | 359 +++++++++-------- flutter/lib/models/native_model.dart | 36 +- flutter/lib/models/server_model.dart | 88 +++-- flutter/pubspec.lock | 331 ++++++++-------- flutter/pubspec.yaml | 1 + 22 files changed, 976 insertions(+), 840 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 32f7c4bfa..71d9ed9ad 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/instance_manager.dart'; import 'models/model.dart'; @@ -274,7 +275,7 @@ class PermissionManager { static Future check(String type) { if (!permissions.contains(type)) return Future.error("Wrong permission!$type"); - return FFI.invokeMethod("check_permission", type); + return gFFI.invokeMethod("check_permission", type); } static Future request(String type) { @@ -283,7 +284,7 @@ class PermissionManager { _current = type; _completer = Completer(); - FFI.invokeMethod("request_permission", type); + gFFI.invokeMethod("request_permission", type); // timeout _timer?.cancel(); @@ -307,3 +308,21 @@ class PermissionManager { _current = ""; } } + +/// find ffi, tag is Remote ID +/// for session specific usage +FFI ffi(String? tag) { + return Get.find(tag: tag); +} + +/// Global FFI object +late FFI _globalFFI; + +FFI get gFFI => _globalFFI; + +Future initGlobalFFI() async { + _globalFFI = FFI(); + // after `put`, can also be globally found by Get.find(); + Get.put(_globalFFI, permanent: true); + await _globalFFI.ffiModel.init(); +} \ No newline at end of file diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index c88c52e32..78d73daee 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -44,7 +44,7 @@ class _ConnectionPageState extends State { @override Widget build(BuildContext context) { Provider.of(context); - if (_idController.text.isEmpty) _idController.text = FFI.getId(); + if (_idController.text.isEmpty) _idController.text = gFFI.getId(); return SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -258,7 +258,7 @@ class _ConnectionPageState extends State { width = size.width / n - 2 * space; } final cards = []; - var peers = FFI.peers(); + var peers = gFFI.peers(); peers.forEach((p) { cards.add(Container( width: width, @@ -316,7 +316,7 @@ class _ConnectionPageState extends State { elevation: 8, ); if (value == 'remove') { - setState(() => FFI.setByName('remove', '$id')); + setState(() => gFFI.setByName('remove', '$id')); () async { removePreference(id); }(); diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 97104cbf3..bbd440712 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -61,7 +61,7 @@ class _DesktopHomePageState extends State with TrayListener { buildServerInfo(BuildContext context) { return ChangeNotifierProvider.value( - value: FFI.serverModel, + value: gFFI.serverModel, child: Container( decoration: BoxDecoration(color: MyTheme.white), child: Column( @@ -88,7 +88,7 @@ class _DesktopHomePageState extends State with TrayListener { } buildIDBoard(BuildContext context) { - final model = FFI.serverModel; + final model = gFFI.serverModel; return Container( margin: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), child: Row( @@ -123,7 +123,7 @@ class _DesktopHomePageState extends State with TrayListener { } buildPasswordBoard(BuildContext context) { - final model = FFI.serverModel; + final model = gFFI.serverModel; return Container( margin: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), child: Row( diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index c40a7cb47..5930b1f5a 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -8,6 +8,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:get/route_manager.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import 'package:window_manager/window_manager.dart'; @@ -45,10 +47,15 @@ class _RemotePageState extends State with WindowListener { var _showEdit = false; // use soft keyboard var _isPhysicalMouse = false; + FFI get _ffi => ffi(widget.id); + @override void initState() { super.initState(); - FFI.connect(widget.id); + final ffi = Get.put(FFI(), tag: widget.id); + // note: a little trick + ffi.ffiModel.platformFFI = gFFI.ffiModel.platformFFI; + ffi.connect(widget.id); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); showLoading(translate('Connecting...')); @@ -59,8 +66,8 @@ class _RemotePageState extends State with WindowListener { Wakelock.enable(); } _physicalFocusNode.requestFocus(); - // FFI.ffiModel.updateEventListener(widget.id); - FFI.listenToMouse(true); + ffi.ffiModel.updateEventListener(widget.id); + ffi.listenToMouse(true); WindowManager.instance.addListener(this); } @@ -68,11 +75,11 @@ class _RemotePageState extends State with WindowListener { void dispose() { print("remote page dispose"); hideMobileActionsOverlay(); - FFI.listenToMouse(false); - FFI.invokeMethod("enable_soft_keyboard", true); + _ffi.listenToMouse(false); + _ffi.invokeMethod("enable_soft_keyboard", true); _mobileFocusNode.dispose(); _physicalFocusNode.dispose(); - FFI.close(); + _ffi.close(); _interval?.cancel(); _timer?.cancel(); SmartDialog.dismiss(); @@ -82,11 +89,12 @@ class _RemotePageState extends State with WindowListener { Wakelock.disable(); } WindowManager.instance.removeListener(this); + Get.delete(tag: widget.id); super.dispose(); } void resetTool() { - FFI.resetModifiers(); + _ffi.resetModifiers(); } bool isKeyboardShown() { @@ -105,8 +113,8 @@ class _RemotePageState extends State with WindowListener { overlays: []); // [pi.version.isNotEmpty] -> check ready or not,avoid login without soft-keyboard if (chatWindowOverlayEntry == null && - FFI.ffiModel.pi.version.isNotEmpty) { - FFI.invokeMethod("enable_soft_keyboard", false); + _ffi.ffiModel.pi.version.isNotEmpty) { + _ffi.invokeMethod("enable_soft_keyboard", false); } } }); @@ -138,12 +146,12 @@ class _RemotePageState extends State with WindowListener { newValue[common] == oldValue[common]; ++common); for (i = 0; i < oldValue.length - common; ++i) { - FFI.inputKey('VK_BACK'); + _ffi.inputKey('VK_BACK'); } if (newValue.length > common) { var s = newValue.substring(common); if (s.length > 1) { - FFI.bind.sessionInputString(id: widget.id, value: s); + _ffi.bind.sessionInputString(id: widget.id, value: s); } else { inputChar(s); } @@ -161,7 +169,7 @@ class _RemotePageState extends State with WindowListener { // ? } else if (newValue.length < oldValue.length) { final char = 'VK_BACK'; - FFI.inputKey(char); + _ffi.inputKey(char); } else { final content = newValue.substring(oldValue.length); if (content.length > 1) { @@ -177,11 +185,11 @@ class _RemotePageState extends State with WindowListener { content == '()' || content == '【】')) { // can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input - FFI.bind.sessionInputString(id: widget.id, value: content); + _ffi.bind.sessionInputString(id: widget.id, value: content); openKeyboard(); return; } - FFI.bind.sessionInputString(id: widget.id, value: content); + _ffi.bind.sessionInputString(id: widget.id, value: content); } else { inputChar(content); } @@ -194,11 +202,11 @@ class _RemotePageState extends State with WindowListener { } else if (char == ' ') { char = 'VK_SPACE'; } - FFI.inputKey(char); + _ffi.inputKey(char); } void openKeyboard() { - FFI.invokeMethod("enable_soft_keyboard", true); + _ffi.invokeMethod("enable_soft_keyboard", true); // destroy first, so that our _value trick can work _value = initText; setState(() => _showEdit = false); @@ -221,7 +229,7 @@ class _RemotePageState extends State with WindowListener { final label = _logicalKeyMap[e.logicalKey.keyId] ?? _physicalKeyMap[e.physicalKey.usbHidUsage] ?? e.logicalKey.keyLabel; - FFI.inputKey(label, down: down, press: press ?? false); + _ffi.inputKey(label, down: down, press: press ?? false); } @override @@ -229,7 +237,7 @@ class _RemotePageState extends State with WindowListener { final pi = Provider.of(context).pi; final hideKeyboard = isKeyboardShown() && _showEdit; final showActionButton = !_showBar || hideKeyboard; - final keyboard = FFI.ffiModel.permissions['keyboard'] != false; + final keyboard = _ffi.ffiModel.permissions['keyboard'] != false; return WillPopScope( onWillPop: () async { @@ -251,7 +259,7 @@ class _RemotePageState extends State with WindowListener { setState(() { if (hideKeyboard) { _showEdit = false; - FFI.invokeMethod("enable_soft_keyboard", false); + _ffi.invokeMethod("enable_soft_keyboard", false); _mobileFocusNode.unfocus(); _physicalFocusNode.requestFocus(); } else { @@ -291,7 +299,7 @@ class _RemotePageState extends State with WindowListener { }); } if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousemove')); + _ffi.handleMouse(getEvent(e, 'mousemove')); } }, onPointerDown: (e) { @@ -303,19 +311,19 @@ class _RemotePageState extends State with WindowListener { } } if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousedown')); + _ffi.handleMouse(getEvent(e, 'mousedown')); } }, onPointerUp: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mouseup')); + _ffi.handleMouse(getEvent(e, 'mouseup')); } }, onPointerMove: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousemove')); + _ffi.handleMouse(getEvent(e, 'mousemove')); } }, onPointerSignal: (e) { @@ -328,7 +336,7 @@ class _RemotePageState extends State with WindowListener { if (dy > 0) dy = -1; else if (dy < 0) dy = 1; - FFI.setByName('send_mouse', + _ffi.setByName('send_mouse', '{"id": "${widget.id}", "type": "wheel", "x": "$dx", "y": "$dy"}'); } }, @@ -346,14 +354,14 @@ class _RemotePageState extends State with WindowListener { if (e.repeat) { sendRawKey(e, press: true); } else { - 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.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; } sendRawKey(e, down: true); } @@ -362,16 +370,16 @@ class _RemotePageState extends State with WindowListener { if (!_showEdit && e is RawKeyUpEvent) { if (key == LogicalKeyboardKey.altLeft || key == LogicalKeyboardKey.altRight) { - FFI.alt = false; + _ffi.alt = false; } else if (key == LogicalKeyboardKey.controlLeft || key == LogicalKeyboardKey.controlRight) { - FFI.ctrl = false; + _ffi.ctrl = false; } else if (key == LogicalKeyboardKey.shiftRight || key == LogicalKeyboardKey.shiftLeft) { - FFI.shift = false; + _ffi.shift = false; } else if (key == LogicalKeyboardKey.metaLeft || key == LogicalKeyboardKey.metaRight) { - FFI.command = false; + _ffi.command = false; } sendRawKey(e); } @@ -410,7 +418,7 @@ class _RemotePageState extends State with WindowListener { ] + (isWebDesktop ? [] - : FFI.ffiModel.isPeerAndroid + : _ffi.ffiModel.isPeerAndroid ? [ IconButton( color: Colors.white, @@ -431,7 +439,7 @@ class _RemotePageState extends State with WindowListener { onPressed: openKeyboard), IconButton( color: Colors.white, - icon: Icon(FFI.ffiModel.touchMode + icon: Icon(_ffi.ffiModel.touchMode ? Icons.touch_app : Icons.mouse), onPressed: changeTouchMode, @@ -444,7 +452,7 @@ class _RemotePageState extends State with WindowListener { color: Colors.white, icon: Icon(Icons.message), onPressed: () { - FFI.chatModel + _ffi.chatModel .changeCurrentID(ChatModel.clientModeID); toggleChatOverlay(); }, @@ -482,89 +490,89 @@ class _RemotePageState extends State with WindowListener { /// HoldDrag -> left drag Widget getBodyForMobileWithGesture() { - final touchMode = FFI.ffiModel.touchMode; + final touchMode = _ffi.ffiModel.touchMode; return getMixinGestureDetector( child: getBodyForMobile(), onTapUp: (d) { if (touchMode) { - FFI.cursorModel.touch( + _ffi.cursorModel.touch( d.localPosition.dx, d.localPosition.dy, MouseButtons.left); } else { - FFI.tap(MouseButtons.left); + _ffi.tap(MouseButtons.left); } }, onDoubleTapDown: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + _ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); } }, onDoubleTap: () { - FFI.tap(MouseButtons.left); - FFI.tap(MouseButtons.left); + _ffi.tap(MouseButtons.left); + _ffi.tap(MouseButtons.left); }, onLongPressDown: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + _ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); } }, onLongPress: () { - FFI.tap(MouseButtons.right); + _ffi.tap(MouseButtons.right); }, onDoubleFinerTap: (d) { if (!touchMode) { - FFI.tap(MouseButtons.right); + _ffi.tap(MouseButtons.right); } }, onHoldDragStart: (d) { if (!touchMode) { - FFI.sendMouse('down', MouseButtons.left); + _ffi.sendMouse('down', MouseButtons.left); } }, onHoldDragUpdate: (d) { if (!touchMode) { - FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); + _ffi.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); } }, onHoldDragEnd: (_) { if (!touchMode) { - FFI.sendMouse('up', MouseButtons.left); + _ffi.sendMouse('up', MouseButtons.left); } }, onOneFingerPanStart: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - FFI.sendMouse('down', MouseButtons.left); + _ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + _ffi.sendMouse('down', MouseButtons.left); } }, onOneFingerPanUpdate: (d) { - FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); + _ffi.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); }, onOneFingerPanEnd: (d) { if (touchMode) { - FFI.sendMouse('up', MouseButtons.left); + _ffi.sendMouse('up', MouseButtons.left); } }, // scale + pan event onTwoFingerScaleUpdate: (d) { - FFI.canvasModel.updateScale(d.scale / _scale); + _ffi.canvasModel.updateScale(d.scale / _scale); _scale = d.scale; - FFI.canvasModel.panX(d.focalPointDelta.dx); - FFI.canvasModel.panY(d.focalPointDelta.dy); + _ffi.canvasModel.panX(d.focalPointDelta.dx); + _ffi.canvasModel.panY(d.focalPointDelta.dy); }, onTwoFingerScaleEnd: (d) { _scale = 1; - FFI.bind + _ffi.bind .sessionPeerOption(id: widget.id, name: "view-style", value: ""); }, - onThreeFingerVerticalDragUpdate: FFI.ffiModel.isPeerAndroid + onThreeFingerVerticalDragUpdate: _ffi.ffiModel.isPeerAndroid ? null : (d) { _mouseScrollIntegral += d.delta.dy / 4; if (_mouseScrollIntegral > 1) { - FFI.scroll(1); + _ffi.scroll(1); _mouseScrollIntegral = 0; } else if (_mouseScrollIntegral < -1) { - FFI.scroll(-1); + _ffi.scroll(-1); _mouseScrollIntegral = 0; } }); @@ -574,8 +582,8 @@ class _RemotePageState extends State with WindowListener { return Container( color: MyTheme.canvasColor, child: Stack(children: [ - ImagePaint(), - CursorPaint(), + ImagePaint(id: widget.id), + CursorPaint(id: widget.id), getHelpTools(), SizedBox( width: 0, @@ -599,11 +607,17 @@ class _RemotePageState extends State with WindowListener { } Widget getBodyForDesktopWithListener(bool keyboard) { - var paints = [ImagePaint()]; - final cursor = FFI.bind + var paints = [ + ImagePaint( + id: widget.id, + ) + ]; + final cursor = _ffi.bind .getSessionToggleOptionSync(id: widget.id, arg: 'show-remote-cursor'); if (keyboard || cursor) { - paints.add(CursorPaint()); + paints.add(CursorPaint( + id: widget.id, + )); } return Container( color: MyTheme.canvasColor, child: Stack(children: paints)); @@ -616,10 +630,10 @@ class _RemotePageState extends State with WindowListener { 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 (_ffi.alt) out['alt'] = 'true'; + if (_ffi.shift) out['shift'] = 'true'; + if (_ffi.ctrl) out['ctrl'] = 'true'; + if (_ffi.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) { @@ -635,8 +649,8 @@ class _RemotePageState extends State with WindowListener { final x = 120.0; final y = size.height; final more = >[]; - final pi = FFI.ffiModel.pi; - final perms = FFI.ffiModel.permissions; + final pi = _ffi.ffiModel.pi; + final perms = _ffi.ffiModel.permissions; if (pi.version.isNotEmpty) { more.add(PopupMenuItem( child: Text(translate('Refresh')), value: 'refresh')); @@ -672,11 +686,11 @@ class _RemotePageState extends State with WindowListener { more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - await FFI.bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != + await _ffi.bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem( - child: Text(translate( - (FFI.ffiModel.inputBlocked ? 'Unb' : 'B') + 'lock user input')), + child: Text(translate((_ffi.ffiModel.inputBlocked ? 'Unb' : 'B') + + 'lock user input')), value: 'block-input')); } } @@ -688,33 +702,33 @@ class _RemotePageState extends State with WindowListener { elevation: 8, ); if (value == 'cad') { - FFI.bind.sessionCtrlAltDel(id: widget.id); + _ffi.bind.sessionCtrlAltDel(id: widget.id); } else if (value == 'lock') { - FFI.bind.sessionLockScreen(id: widget.id); + _ffi.bind.sessionLockScreen(id: widget.id); } else if (value == 'block-input') { - FFI.bind.sessionToggleOption( + _ffi.bind.sessionToggleOption( id: widget.id, - value: (FFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); - FFI.ffiModel.inputBlocked = !FFI.ffiModel.inputBlocked; + value: (_ffi.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); + _ffi.ffiModel.inputBlocked = !_ffi.ffiModel.inputBlocked; } else if (value == 'refresh') { - FFI.bind.sessionRefresh(id: widget.id); + _ffi.bind.sessionRefresh(id: widget.id); } else if (value == 'paste') { () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); if (data != null && data.text != null) { - FFI.bind.sessionInputString(id: widget.id, value: data.text ?? ""); + _ffi.bind.sessionInputString(id: widget.id, value: data.text ?? ""); } }(); } else if (value == 'enter_os_password') { var password = - await FFI.bind.getSessionOption(id: id, arg: "os-password"); + await _ffi.bind.getSessionOption(id: id, arg: "os-password"); if (password != null) { - FFI.bind.sessionInputOsPassword(id: widget.id, value: password); + _ffi.bind.sessionInputOsPassword(id: widget.id, value: password); } else { showSetOSPassword(widget.id, true); } } else if (value == 'reset_canvas') { - FFI.cursorModel.reset(); + _ffi.cursorModel.reset(); } }(); } @@ -733,11 +747,11 @@ class _RemotePageState extends State with WindowListener { return SingleChildScrollView( padding: EdgeInsets.symmetric(vertical: 10), child: GestureHelp( - touchMode: FFI.ffiModel.touchMode, + touchMode: _ffi.ffiModel.touchMode, onTouchModeChange: (t) { - FFI.ffiModel.toggleTouchMode(); - final v = FFI.ffiModel.touchMode ? 'Y' : ''; - FFI.bind.sessionPeerOption( + _ffi.ffiModel.toggleTouchMode(); + final v = _ffi.ffiModel.touchMode ? 'Y' : ''; + _ffi.bind.sessionPeerOption( id: widget.id, name: "touch-mode", value: v); })); })); @@ -769,21 +783,21 @@ class _RemotePageState extends State with WindowListener { style: TextStyle(color: Colors.white, fontSize: 11)), onPressed: onPressed); }; - final pi = FFI.ffiModel.pi; + final pi = _ffi.ffiModel.pi; final isMac = pi.platform == "Mac OS"; final modifiers = [ wrap('Ctrl ', () { - setState(() => FFI.ctrl = !FFI.ctrl); - }, FFI.ctrl), + setState(() => _ffi.ctrl = !_ffi.ctrl); + }, _ffi.ctrl), wrap(' Alt ', () { - setState(() => FFI.alt = !FFI.alt); - }, FFI.alt), + setState(() => _ffi.alt = !_ffi.alt); + }, _ffi.alt), wrap('Shift', () { - setState(() => FFI.shift = !FFI.shift); - }, FFI.shift), + setState(() => _ffi.shift = !_ffi.shift); + }, _ffi.shift), wrap(isMac ? ' Cmd ' : ' Win ', () { - setState(() => FFI.command = !FFI.command); - }, FFI.command), + setState(() => _ffi.command = !_ffi.command); + }, _ffi.command), ]; final keys = [ wrap( @@ -815,53 +829,53 @@ class _RemotePageState extends State with WindowListener { for (var i = 1; i <= 12; ++i) { final name = 'F' + i.toString(); fn.add(wrap(name, () { - FFI.inputKey('VK_' + name); + _ffi.inputKey('VK_' + name); })); } final more = [ SizedBox(width: 9999), wrap('Esc', () { - FFI.inputKey('VK_ESCAPE'); + _ffi.inputKey('VK_ESCAPE'); }), wrap('Tab', () { - FFI.inputKey('VK_TAB'); + _ffi.inputKey('VK_TAB'); }), wrap('Home', () { - FFI.inputKey('VK_HOME'); + _ffi.inputKey('VK_HOME'); }), wrap('End', () { - FFI.inputKey('VK_END'); + _ffi.inputKey('VK_END'); }), wrap('Del', () { - FFI.inputKey('VK_DELETE'); + _ffi.inputKey('VK_DELETE'); }), wrap('PgUp', () { - FFI.inputKey('VK_PRIOR'); + _ffi.inputKey('VK_PRIOR'); }), wrap('PgDn', () { - FFI.inputKey('VK_NEXT'); + _ffi.inputKey('VK_NEXT'); }), SizedBox(width: 9999), wrap('', () { - FFI.inputKey('VK_LEFT'); + _ffi.inputKey('VK_LEFT'); }, false, Icons.keyboard_arrow_left), wrap('', () { - FFI.inputKey('VK_UP'); + _ffi.inputKey('VK_UP'); }, false, Icons.keyboard_arrow_up), wrap('', () { - FFI.inputKey('VK_DOWN'); + _ffi.inputKey('VK_DOWN'); }, false, Icons.keyboard_arrow_down), wrap('', () { - FFI.inputKey('VK_RIGHT'); + _ffi.inputKey('VK_RIGHT'); }, false, Icons.keyboard_arrow_right), wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () { - sendPrompt(isMac, 'VK_C'); + sendPrompt(widget.id, isMac, 'VK_C'); }), wrap(isMac ? 'Cmd+V' : 'Ctrl+V', () { - sendPrompt(isMac, 'VK_V'); + sendPrompt(widget.id, isMac, 'VK_V'); }), wrap(isMac ? 'Cmd+S' : 'Ctrl+S', () { - sendPrompt(isMac, 'VK_S'); + sendPrompt(widget.id, isMac, 'VK_S'); }), ]; final space = size.width > 320 ? 4.0 : 2.0; @@ -884,11 +898,11 @@ class _RemotePageState extends State with WindowListener { print("window event: $eventName"); switch (eventName) { case 'resize': - FFI.canvasModel.updateViewStyle(); + _ffi.canvasModel.updateViewStyle(); break; case 'maximize': Future.delayed(Duration(milliseconds: 100), () { - FFI.canvasModel.updateViewStyle(); + _ffi.canvasModel.updateViewStyle(); }); break; } @@ -896,11 +910,15 @@ class _RemotePageState extends State with WindowListener { } class ImagePaint extends StatelessWidget { + final String id; + + const ImagePaint({Key? key, required this.id}) : super(key: key); + @override Widget build(BuildContext context) { - final m = Provider.of(context); - final c = Provider.of(context); - final adjust = FFI.cursorModel.adjustForKeyboard(); + final m = ffi(this.id).imageModel; + final c = ffi(this.id).canvasModel; + final adjust = ffi(this.id).cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( painter: new ImagePainter( @@ -910,11 +928,15 @@ class ImagePaint extends StatelessWidget { } class CursorPaint extends StatelessWidget { + final String id; + + const CursorPaint({Key? key, required this.id}) : super(key: key); + @override Widget build(BuildContext context) { - final m = Provider.of(context); - final c = Provider.of(context); - final adjust = FFI.cursorModel.adjustForKeyboard(); + final m = ffi(this.id).cursorModel; + final c = ffi(this.id).canvasModel; + final adjust = ffi(this.id).cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( painter: new ImagePainter( @@ -954,12 +976,12 @@ class ImagePainter extends CustomPainter { CheckboxListTile getToggle( String id, void Function(void Function()) setState, option, name) { - final opt = FFI.bind.getSessionToggleOptionSync(id: id, arg: option); + final opt = ffi(id).bind.getSessionToggleOptionSync(id: id, arg: option); return CheckboxListTile( value: opt, onChanged: (v) { setState(() { - FFI.bind.sessionToggleOption(id: id, value: option); + ffi(id).bind.sessionToggleOption(id: id, value: option); }); }, dense: true, @@ -979,13 +1001,14 @@ RadioListTile getRadio(String name, String toValue, String curValue, } void showOptions(String id) async { - String quality = await FFI.bind.getSessionImageQuality(id: id) ?? 'balanced'; + String quality = + await ffi(id).bind.getSessionImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; String viewStyle = - await FFI.bind.getSessionOption(id: id, arg: 'view-style') ?? ''; + await ffi(id).bind.getSessionOption(id: id, arg: 'view-style') ?? ''; var displays = []; - final pi = FFI.ffiModel.pi; - final image = FFI.ffiModel.getConnectionImage(); + final pi = ffi(id).ffiModel.pi; + final image = ffi(id).ffiModel.getConnectionImage(); if (image != null) displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); if (pi.displays.length > 1) { @@ -995,7 +1018,7 @@ void showOptions(String id) async { children.add(InkWell( onTap: () { if (i == cur) return; - FFI.bind.sessionSwitchDisplay(id: id, value: i); + ffi(id).bind.sessionSwitchDisplay(id: id, value: i); SmartDialog.dismiss(); }, child: Ink( @@ -1019,7 +1042,7 @@ void showOptions(String id) async { if (displays.isNotEmpty) { displays.add(Divider(color: MyTheme.border)); } - final perms = FFI.ffiModel.permissions; + final perms = ffi(id).ffiModel.permissions; DialogManager.show((setState, close) { final more = []; @@ -1040,15 +1063,17 @@ void showOptions(String id) async { if (value == null) return; setState(() { quality = value; - FFI.bind.sessionSetImageQuality(id: id, value: value); + ffi(id).bind.sessionSetImageQuality(id: id, value: value); }); }; var setViewStyle = (String? value) { if (value == null) return; setState(() { viewStyle = value; - FFI.bind.sessionPeerOption(id: id, name: "view-style", value: value); - FFI.canvasModel.updateViewStyle(); + ffi(id) + .bind + .sessionPeerOption(id: id, name: "view-style", value: value); + ffi(id).canvasModel.updateViewStyle(); }); }; return CustomAlertDialog( @@ -1078,9 +1103,9 @@ void showOptions(String id) async { void showSetOSPassword(String id, bool login) async { final controller = TextEditingController(); var password = - await FFI.bind.getSessionOption(id: id, arg: "os-password") ?? ""; + await ffi(id).bind.getSessionOption(id: id, arg: "os-password") ?? ""; var autoLogin = - await FFI.bind.getSessionOption(id: id, arg: "auto-login") != ""; + await ffi(id).bind.getSessionOption(id: id, arg: "auto-login") != ""; controller.text = password; DialogManager.show((setState, close) { return CustomAlertDialog( @@ -1113,12 +1138,13 @@ void showSetOSPassword(String id, bool login) async { style: flatButtonStyle, onPressed: () { var text = controller.text.trim(); - FFI.bind + ffi(id) + .bind .sessionPeerOption(id: id, name: "os-password", value: text); - FFI.bind.sessionPeerOption( + ffi(id).bind.sessionPeerOption( id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); if (text != "" && login) { - FFI.bind.sessionInputOsPassword(id: id, value: text); + ffi(id).bind.sessionInputOsPassword(id: id, value: text); } close(); }, @@ -1128,18 +1154,19 @@ void showSetOSPassword(String id, bool login) async { }); } -void sendPrompt(bool isMac, String key) { - final old = isMac ? FFI.command : FFI.ctrl; +void sendPrompt(String id, bool isMac, String key) { + FFI _ffi = ffi(id); + final old = isMac ? _ffi.command : _ffi.ctrl; if (isMac) { - FFI.command = true; + _ffi.command = true; } else { - FFI.ctrl = true; + _ffi.ctrl = true; } - FFI.inputKey(key); + _ffi.inputKey(key); if (isMac) { - FFI.command = old; + _ffi.command = old; } else { - FFI.ctrl = old; + _ffi.ctrl = old; } } diff --git a/flutter/lib/desktop/screen/desktop_remote_screen.dart b/flutter/lib/desktop/screen/desktop_remote_screen.dart index d2a9ab952..c5e5ecbfa 100644 --- a/flutter/lib/desktop/screen/desktop_remote_screen.dart +++ b/flutter/lib/desktop/screen/desktop_remote_screen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/connection_tab_page.dart'; -import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; @@ -15,10 +14,10 @@ class DesktopRemoteScreen extends StatelessWidget { Widget build(BuildContext context) { return MultiProvider( providers: [ - ChangeNotifierProvider.value(value: FFI.ffiModel), - ChangeNotifierProvider.value(value: FFI.imageModel), - ChangeNotifierProvider.value(value: FFI.cursorModel), - ChangeNotifierProvider.value(value: FFI.canvasModel), + ChangeNotifierProvider.value(value: gFFI.ffiModel), + ChangeNotifierProvider.value(value: gFFI.imageModel), + ChangeNotifierProvider.value(value: gFFI.cursorModel), + ChangeNotifierProvider.value(value: gFFI.canvasModel), ], child: MaterialApp( navigatorKey: globalKey, diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 4b71d4a22..2707d9535 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/route_manager.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -13,13 +14,15 @@ import 'common.dart'; import 'mobile/pages/home_page.dart'; import 'mobile/pages/server_page.dart'; import 'mobile/pages/settings_page.dart'; -import 'models/model.dart'; int? windowId; Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); - await FFI.ffiModel.init(); + // global FFI, use this **ONLY** for global configuration + // for convenience, use global FFI on mobile platform + // focus on multi-ffi on desktop first + initGlobalFFI(); // await Firebase.initializeApp(); if (isAndroid) { toAndroidChannelInit(); @@ -54,7 +57,7 @@ void runRustDeskApp(List args) async { } else { // disable tray // initTray(); - FFI.serverModel.startService(); + gFFI.serverModel.startService(); runApp(App()); doWhenWindowReady(() { const initialSize = Size(1280, 720); @@ -72,12 +75,13 @@ class App extends StatelessWidget { // final analytics = FirebaseAnalytics.instance; return MultiProvider( providers: [ - ChangeNotifierProvider.value(value: FFI.ffiModel), - ChangeNotifierProvider.value(value: FFI.imageModel), - ChangeNotifierProvider.value(value: FFI.cursorModel), - ChangeNotifierProvider.value(value: FFI.canvasModel), + // TODO remove it, only for compile + ChangeNotifierProvider.value(value: gFFI.ffiModel), + ChangeNotifierProvider.value(value: gFFI.imageModel), + ChangeNotifierProvider.value(value: gFFI.cursorModel), + ChangeNotifierProvider.value(value: gFFI.canvasModel), ], - child: MaterialApp( + child: GetMaterialApp( navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk', @@ -88,8 +92,8 @@ class App extends StatelessWidget { home: isDesktop ? DesktopHomePage() : !isAndroid - ? WebHomePage() - : HomePage(), + ? WebHomePage() + : HomePage(), navigatorObservers: [ // FirebaseAnalyticsObserver(analytics: analytics), FlutterSmartDialog.observer diff --git a/flutter/lib/mobile/pages/chat_page.dart b/flutter/lib/mobile/pages/chat_page.dart index a4cf83ab8..c5beda6f1 100644 --- a/flutter/lib/mobile/pages/chat_page.dart +++ b/flutter/lib/mobile/pages/chat_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:provider/provider.dart'; + import '../../models/model.dart'; import 'home_page.dart'; @@ -20,7 +21,7 @@ class ChatPage extends StatelessWidget implements PageShape { PopupMenuButton( icon: Icon(Icons.group), itemBuilder: (context) { - final chatModel = FFI.chatModel; + final chatModel = gFFI.chatModel; return chatModel.messages.entries.map((entry) { final id = entry.key; final user = entry.value.chatUser; @@ -31,14 +32,14 @@ class ChatPage extends StatelessWidget implements PageShape { }).toList(); }, onSelected: (id) { - FFI.chatModel.changeCurrentID(id); + gFFI.chatModel.changeCurrentID(id); }) ]; @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( - value: FFI.chatModel, + value: gFFI.chatModel, child: Container( color: MyTheme.grayBg, child: Consumer(builder: (context, chatModel, child) { diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 113c41676..68841de89 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -1,14 +1,16 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'dart:async'; + import '../../common.dart'; import '../../models/model.dart'; import 'home_page.dart'; import 'remote_page.dart'; -import 'settings_page.dart'; import 'scan_page.dart'; +import 'settings_page.dart'; /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget implements PageShape { @@ -41,7 +43,7 @@ class _ConnectionPageState extends State { super.initState(); if (isAndroid) { Timer(Duration(seconds: 5), () { - _updateUrl = FFI.getByName('software_update_url'); + _updateUrl = gFFI.getByName('software_update_url'); if (_updateUrl.isNotEmpty) setState(() {}); }); } @@ -50,7 +52,7 @@ class _ConnectionPageState extends State { @override Widget build(BuildContext context) { Provider.of(context); - if (_idController.text.isEmpty) _idController.text = FFI.getId(); + if (_idController.text.isEmpty) _idController.text = gFFI.getId(); return SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -220,7 +222,7 @@ class _ConnectionPageState extends State { width = size.width / n - 2 * space; } final cards = []; - var peers = FFI.peers(); + var peers = gFFI.peers(); peers.forEach((p) { cards.add(Container( width: width, @@ -278,7 +280,7 @@ class _ConnectionPageState extends State { elevation: 8, ); if (value == 'remove') { - setState(() => FFI.setByName('remove', '$id')); + setState(() => gFFI.setByName('remove', '$id')); () async { removePreference(id); }(); diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index 0370bedff..1f588a461 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -1,11 +1,12 @@ import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; -import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; -import 'package:wakelock/wakelock.dart'; import 'package:toggle_switch/toggle_switch.dart'; +import 'package:wakelock/wakelock.dart'; import '../../common.dart'; import '../../models/model.dart'; @@ -20,22 +21,22 @@ class FileManagerPage extends StatefulWidget { } class _FileManagerPageState extends State { - final model = FFI.fileModel; + final model = gFFI.fileModel; final _selectedItems = SelectedItems(); final _breadCrumbScroller = ScrollController(); @override void initState() { super.initState(); - FFI.connect(widget.id, isFileTransfer: true); - FFI.ffiModel.updateEventListener(widget.id); + gFFI.connect(widget.id, isFileTransfer: true); + gFFI.ffiModel.updateEventListener(widget.id); Wakelock.enable(); } @override void dispose() { model.onClose(); - FFI.close(); + gFFI.close(); SmartDialog.dismiss(); Wakelock.disable(); super.dispose(); @@ -43,7 +44,7 @@ class _FileManagerPageState extends State { @override Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: FFI.fileModel, + value: gFFI.fileModel, child: Consumer(builder: (_context, _model, _child) { return WillPopScope( onWillPop: () async { diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 6f10b234d..fcc5fcde8 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -46,7 +46,7 @@ class _RemotePageState extends State { @override void initState() { super.initState(); - FFI.connect(widget.id); + gFFI.connect(widget.id); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); showLoading(translate('Connecting...')); @@ -55,18 +55,18 @@ class _RemotePageState extends State { }); Wakelock.enable(); _physicalFocusNode.requestFocus(); - FFI.ffiModel.updateEventListener(widget.id); - FFI.listenToMouse(true); + gFFI.ffiModel.updateEventListener(widget.id); + gFFI.listenToMouse(true); } @override void dispose() { hideMobileActionsOverlay(); - FFI.listenToMouse(false); - FFI.invokeMethod("enable_soft_keyboard", true); + gFFI.listenToMouse(false); + gFFI.invokeMethod("enable_soft_keyboard", true); _mobileFocusNode.dispose(); _physicalFocusNode.dispose(); - FFI.close(); + gFFI.close(); _interval?.cancel(); _timer?.cancel(); SmartDialog.dismiss(); @@ -77,7 +77,7 @@ class _RemotePageState extends State { } void resetTool() { - FFI.resetModifiers(); + gFFI.resetModifiers(); } bool isKeyboardShown() { @@ -96,8 +96,8 @@ class _RemotePageState extends State { overlays: []); // [pi.version.isNotEmpty] -> check ready or not,avoid login without soft-keyboard if (chatWindowOverlayEntry == null && - FFI.ffiModel.pi.version.isNotEmpty) { - FFI.invokeMethod("enable_soft_keyboard", false); + gFFI.ffiModel.pi.version.isNotEmpty) { + gFFI.invokeMethod("enable_soft_keyboard", false); } } }); @@ -129,12 +129,12 @@ class _RemotePageState extends State { newValue[common] == oldValue[common]; ++common); for (i = 0; i < oldValue.length - common; ++i) { - FFI.inputKey('VK_BACK'); + gFFI.inputKey('VK_BACK'); } if (newValue.length > common) { var s = newValue.substring(common); if (s.length > 1) { - FFI.setByName('input_string', s); + gFFI.setByName('input_string', s); } else { inputChar(s); } @@ -152,7 +152,7 @@ class _RemotePageState extends State { // ? } else if (newValue.length < oldValue.length) { final char = 'VK_BACK'; - FFI.inputKey(char); + gFFI.inputKey(char); } else { final content = newValue.substring(oldValue.length); if (content.length > 1) { @@ -168,11 +168,11 @@ class _RemotePageState extends State { content == '()' || content == '【】')) { // can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input - FFI.setByName('input_string', content); + gFFI.setByName('input_string', content); openKeyboard(); return; } - FFI.setByName('input_string', content); + gFFI.setByName('input_string', content); } else { inputChar(content); } @@ -185,11 +185,11 @@ class _RemotePageState extends State { } else if (char == ' ') { char = 'VK_SPACE'; } - FFI.inputKey(char); + gFFI.inputKey(char); } void openKeyboard() { - FFI.invokeMethod("enable_soft_keyboard", true); + gFFI.invokeMethod("enable_soft_keyboard", true); // destroy first, so that our _value trick can work _value = initText; setState(() => _showEdit = false); @@ -212,7 +212,7 @@ class _RemotePageState extends State { final label = _logicalKeyMap[e.logicalKey.keyId] ?? _physicalKeyMap[e.physicalKey.usbHidUsage] ?? e.logicalKey.keyLabel; - FFI.inputKey(label, down: down, press: press ?? false); + gFFI.inputKey(label, down: down, press: press ?? false); } @override @@ -220,7 +220,7 @@ class _RemotePageState extends State { final pi = Provider.of(context).pi; final hideKeyboard = isKeyboardShown() && _showEdit; final showActionButton = !_showBar || hideKeyboard; - final keyboard = FFI.ffiModel.permissions['keyboard'] != false; + final keyboard = gFFI.ffiModel.permissions['keyboard'] != false; return WillPopScope( onWillPop: () async { @@ -230,7 +230,7 @@ class _RemotePageState extends State { child: getRawPointerAndKeyBody( keyboard, Scaffold( - // resizeToAvoidBottomInset: true, + // resizeToAvoidBottomInset: true, floatingActionButton: !showActionButton ? null : FloatingActionButton( @@ -241,14 +241,14 @@ class _RemotePageState extends State { onPressed: () { setState(() { if (hideKeyboard) { - _showEdit = false; - FFI.invokeMethod("enable_soft_keyboard", false); - _mobileFocusNode.unfocus(); - _physicalFocusNode.requestFocus(); - } else { - _showBar = !_showBar; - } - }); + _showEdit = false; + gFFI.invokeMethod("enable_soft_keyboard", false); + _mobileFocusNode.unfocus(); + _physicalFocusNode.requestFocus(); + } else { + _showBar = !_showBar; + } + }); }), bottomNavigationBar: _showBar && pi.displays.length > 0 ? getBottomAppBar(keyboard) @@ -282,7 +282,7 @@ class _RemotePageState extends State { }); } if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousemove')); + gFFI.handleMouse(getEvent(e, 'mousemove')); } }, onPointerDown: (e) { @@ -294,19 +294,19 @@ class _RemotePageState extends State { } } if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousedown')); + gFFI.handleMouse(getEvent(e, 'mousedown')); } }, onPointerUp: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mouseup')); + gFFI.handleMouse(getEvent(e, 'mouseup')); } }, onPointerMove: (e) { if (e.kind != ui.PointerDeviceKind.mouse) return; if (_isPhysicalMouse) { - FFI.handleMouse(getEvent(e, 'mousemove')); + gFFI.handleMouse(getEvent(e, 'mousemove')); } }, onPointerSignal: (e) { @@ -319,7 +319,7 @@ class _RemotePageState extends State { if (dy > 0) dy = -1; else if (dy < 0) dy = 1; - FFI.setByName( + gFFI.setByName( 'send_mouse', '{"type": "wheel", "x": "$dx", "y": "$dy"}'); } }, @@ -337,14 +337,14 @@ class _RemotePageState extends State { if (e.repeat) { sendRawKey(e, press: true); } else { - 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.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; } sendRawKey(e, down: true); } @@ -353,16 +353,16 @@ class _RemotePageState extends State { if (!_showEdit && e is RawKeyUpEvent) { if (key == LogicalKeyboardKey.altLeft || key == LogicalKeyboardKey.altRight) { - FFI.alt = false; + gFFI.alt = false; } else if (key == LogicalKeyboardKey.controlLeft || key == LogicalKeyboardKey.controlRight) { - FFI.ctrl = false; + gFFI.ctrl = false; } else if (key == LogicalKeyboardKey.shiftRight || key == LogicalKeyboardKey.shiftLeft) { - FFI.shift = false; + gFFI.shift = false; } else if (key == LogicalKeyboardKey.metaLeft || key == LogicalKeyboardKey.metaRight) { - FFI.command = false; + gFFI.command = false; } sendRawKey(e); } @@ -401,32 +401,32 @@ class _RemotePageState extends State { ] + (isWebDesktop ? [] - : FFI.ffiModel.isPeerAndroid - ? [ - IconButton( - color: Colors.white, - icon: Icon(Icons.build), - onPressed: () { - if (mobileActionsOverlayEntry == null) { - showMobileActionsOverlay(); - } else { - hideMobileActionsOverlay(); - } - }, - ) - ] - : [ - IconButton( - color: Colors.white, - icon: Icon(Icons.keyboard), - onPressed: openKeyboard), - IconButton( - color: Colors.white, - icon: Icon(FFI.ffiModel.touchMode - ? Icons.touch_app - : Icons.mouse), - onPressed: changeTouchMode, - ), + : gFFI.ffiModel.isPeerAndroid + ? [ + IconButton( + color: Colors.white, + icon: Icon(Icons.build), + onPressed: () { + if (mobileActionsOverlayEntry == null) { + showMobileActionsOverlay(); + } else { + hideMobileActionsOverlay(); + } + }, + ) + ] + : [ + IconButton( + color: Colors.white, + icon: Icon(Icons.keyboard), + onPressed: openKeyboard), + IconButton( + color: Colors.white, + icon: Icon(gFFI.ffiModel.touchMode + ? Icons.touch_app + : Icons.mouse), + onPressed: changeTouchMode, + ), ]) + (isWeb ? [] @@ -435,10 +435,10 @@ class _RemotePageState extends State { color: Colors.white, icon: Icon(Icons.message), onPressed: () { - FFI.chatModel - .changeCurrentID(ChatModel.clientModeID); - toggleChatOverlay(); - }, + gFFI.chatModel + .changeCurrentID(ChatModel.clientModeID); + toggleChatOverlay(); + }, ) ]) + [ @@ -473,91 +473,91 @@ class _RemotePageState extends State { /// HoldDrag -> left drag Widget getBodyForMobileWithGesture() { - final touchMode = FFI.ffiModel.touchMode; + final touchMode = gFFI.ffiModel.touchMode; return getMixinGestureDetector( child: getBodyForMobile(), onTapUp: (d) { if (touchMode) { - FFI.cursorModel.touch( + gFFI.cursorModel.touch( d.localPosition.dx, d.localPosition.dy, MouseButtons.left); } else { - FFI.tap(MouseButtons.left); + gFFI.tap(MouseButtons.left); } }, onDoubleTapDown: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); } }, onDoubleTap: () { - FFI.tap(MouseButtons.left); - FFI.tap(MouseButtons.left); + gFFI.tap(MouseButtons.left); + gFFI.tap(MouseButtons.left); }, onLongPressDown: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); } }, onLongPress: () { - FFI.tap(MouseButtons.right); + gFFI.tap(MouseButtons.right); }, onDoubleFinerTap: (d) { if (!touchMode) { - FFI.tap(MouseButtons.right); + gFFI.tap(MouseButtons.right); } }, onHoldDragStart: (d) { if (!touchMode) { - FFI.sendMouse('down', MouseButtons.left); + gFFI.sendMouse('down', MouseButtons.left); } }, onHoldDragUpdate: (d) { if (!touchMode) { - FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); + gFFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); } }, onHoldDragEnd: (_) { if (!touchMode) { - FFI.sendMouse('up', MouseButtons.left); + gFFI.sendMouse('up', MouseButtons.left); } }, onOneFingerPanStart: (d) { if (touchMode) { - FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - FFI.sendMouse('down', MouseButtons.left); + gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); + gFFI.sendMouse('down', MouseButtons.left); } }, onOneFingerPanUpdate: (d) { - FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); + gFFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, touchMode); }, onOneFingerPanEnd: (d) { if (touchMode) { - FFI.sendMouse('up', MouseButtons.left); + gFFI.sendMouse('up', MouseButtons.left); } }, // scale + pan event onTwoFingerScaleUpdate: (d) { - FFI.canvasModel.updateScale(d.scale / _scale); + gFFI.canvasModel.updateScale(d.scale / _scale); _scale = d.scale; - FFI.canvasModel.panX(d.focalPointDelta.dx); - FFI.canvasModel.panY(d.focalPointDelta.dy); + gFFI.canvasModel.panX(d.focalPointDelta.dx); + gFFI.canvasModel.panY(d.focalPointDelta.dy); }, onTwoFingerScaleEnd: (d) { _scale = 1; - FFI.setByName('peer_option', '{"name": "view-style", "value": ""}'); + gFFI.setByName('peer_option', '{"name": "view-style", "value": ""}'); }, - onThreeFingerVerticalDragUpdate: FFI.ffiModel.isPeerAndroid + onThreeFingerVerticalDragUpdate: gFFI.ffiModel.isPeerAndroid ? null : (d) { - _mouseScrollIntegral += d.delta.dy / 4; - if (_mouseScrollIntegral > 1) { - FFI.scroll(1); - _mouseScrollIntegral = 0; - } else if (_mouseScrollIntegral < -1) { - FFI.scroll(-1); - _mouseScrollIntegral = 0; - } - }); + _mouseScrollIntegral += d.delta.dy / 4; + if (_mouseScrollIntegral > 1) { + gFFI.scroll(1); + _mouseScrollIntegral = 0; + } else if (_mouseScrollIntegral < -1) { + gFFI.scroll(-1); + _mouseScrollIntegral = 0; + } + }); } Widget getBodyForMobile() { @@ -591,7 +591,7 @@ class _RemotePageState extends State { Widget getBodyForDesktopWithListener(bool keyboard) { var paints = [ImagePaint()]; if (keyboard || - FFI.getByName('toggle_option', 'show-remote-cursor') == 'true') { + gFFI.getByName('toggle_option', 'show-remote-cursor') == 'true') { paints.add(CursorPaint()); } return Container( @@ -605,10 +605,10 @@ class _RemotePageState extends State { 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 (gFFI.alt) out['alt'] = 'true'; + if (gFFI.shift) out['shift'] = 'true'; + if (gFFI.ctrl) out['ctrl'] = 'true'; + if (gFFI.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) { @@ -624,8 +624,8 @@ class _RemotePageState extends State { final x = 120.0; final y = size.height; final more = >[]; - final pi = FFI.ffiModel.pi; - final perms = FFI.ffiModel.permissions; + final pi = gFFI.ffiModel.pi; + final perms = gFFI.ffiModel.permissions; if (pi.version.isNotEmpty) { more.add(PopupMenuItem( child: Text(translate('Refresh')), value: 'refresh')); @@ -633,16 +633,16 @@ class _RemotePageState extends State { more.add(PopupMenuItem( child: Row( children: ([ - Container(width: 100.0, child: Text(translate('OS Password'))), - TextButton( - style: flatButtonStyle, - onPressed: () { - Navigator.pop(context); - showSetOSPassword(false); - }, - child: Icon(Icons.edit, color: MyTheme.accent), - ) - ])), + Container(width: 100.0, child: Text(translate('OS Password'))), + TextButton( + style: flatButtonStyle, + onPressed: () { + Navigator.pop(context); + showSetOSPassword(false); + }, + child: Icon(Icons.edit, color: MyTheme.accent), + ) + ])), value: 'enter_os_password')); if (!isWebDesktop) { if (perms['keyboard'] != false && perms['clipboard'] != false) { @@ -661,10 +661,10 @@ class _RemotePageState extends State { more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - FFI.getByName('toggle_option', 'privacy-mode') != 'true') { + gFFI.getByName('toggle_option', 'privacy-mode') != 'true') { more.add(PopupMenuItem( - child: Text(translate( - (FFI.ffiModel.inputBlocked ? 'Unb' : 'B') + 'lock user input')), + child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + + 'lock user input')), value: 'block-input')); } } @@ -676,31 +676,31 @@ class _RemotePageState extends State { elevation: 8, ); if (value == 'cad') { - FFI.setByName('ctrl_alt_del'); + gFFI.setByName('ctrl_alt_del'); } else if (value == 'lock') { - FFI.setByName('lock_screen'); + gFFI.setByName('lock_screen'); } else if (value == 'block-input') { - FFI.setByName('toggle_option', - (FFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); - FFI.ffiModel.inputBlocked = !FFI.ffiModel.inputBlocked; + gFFI.setByName('toggle_option', + (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); + gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; } else if (value == 'refresh') { - FFI.setByName('refresh'); + gFFI.setByName('refresh'); } else if (value == 'paste') { () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); if (data != null && data.text != null) { - FFI.setByName('input_string', '${data.text}'); + gFFI.setByName('input_string', '${data.text}'); } }(); } else if (value == 'enter_os_password') { - var password = FFI.getByName('peer_option', "os-password"); + var password = gFFI.getByName('peer_option', "os-password"); if (password != "") { - FFI.setByName('input_os_password', password); + gFFI.setByName('input_os_password', password); } else { showSetOSPassword(true); } } else if (value == 'reset_canvas') { - FFI.cursorModel.reset(); + gFFI.cursorModel.reset(); } }(); } @@ -719,11 +719,11 @@ class _RemotePageState extends State { return SingleChildScrollView( padding: EdgeInsets.symmetric(vertical: 10), child: GestureHelp( - touchMode: FFI.ffiModel.touchMode, + touchMode: gFFI.ffiModel.touchMode, onTouchModeChange: (t) { - FFI.ffiModel.toggleTouchMode(); - final v = FFI.ffiModel.touchMode ? 'Y' : ''; - FFI.setByName('peer_option', + gFFI.ffiModel.toggleTouchMode(); + final v = gFFI.ffiModel.touchMode ? 'Y' : ''; + gFFI.setByName('peer_option', '{"name": "touch-mode", "value": "$v"}'); })); })); @@ -752,24 +752,24 @@ class _RemotePageState extends State { child: icon != null ? Icon(icon, size: 17, color: Colors.white) : Text(translate(text), - style: TextStyle(color: Colors.white, fontSize: 11)), + style: TextStyle(color: Colors.white, fontSize: 11)), onPressed: onPressed); }; - final pi = FFI.ffiModel.pi; + final pi = gFFI.ffiModel.pi; final isMac = pi.platform == "Mac OS"; final modifiers = [ wrap('Ctrl ', () { - setState(() => FFI.ctrl = !FFI.ctrl); - }, FFI.ctrl), + setState(() => gFFI.ctrl = !gFFI.ctrl); + }, gFFI.ctrl), wrap(' Alt ', () { - setState(() => FFI.alt = !FFI.alt); - }, FFI.alt), + setState(() => gFFI.alt = !gFFI.alt); + }, gFFI.alt), wrap('Shift', () { - setState(() => FFI.shift = !FFI.shift); - }, FFI.shift), + setState(() => gFFI.shift = !gFFI.shift); + }, gFFI.shift), wrap(isMac ? ' Cmd ' : ' Win ', () { - setState(() => FFI.command = !FFI.command); - }, FFI.command), + setState(() => gFFI.command = !gFFI.command); + }, gFFI.command), ]; final keys = [ wrap( @@ -801,44 +801,44 @@ class _RemotePageState extends State { for (var i = 1; i <= 12; ++i) { final name = 'F' + i.toString(); fn.add(wrap(name, () { - FFI.inputKey('VK_' + name); + gFFI.inputKey('VK_' + name); })); } final more = [ SizedBox(width: 9999), wrap('Esc', () { - FFI.inputKey('VK_ESCAPE'); + gFFI.inputKey('VK_ESCAPE'); }), wrap('Tab', () { - FFI.inputKey('VK_TAB'); + gFFI.inputKey('VK_TAB'); }), wrap('Home', () { - FFI.inputKey('VK_HOME'); + gFFI.inputKey('VK_HOME'); }), wrap('End', () { - FFI.inputKey('VK_END'); + gFFI.inputKey('VK_END'); }), wrap('Del', () { - FFI.inputKey('VK_DELETE'); + gFFI.inputKey('VK_DELETE'); }), wrap('PgUp', () { - FFI.inputKey('VK_PRIOR'); + gFFI.inputKey('VK_PRIOR'); }), wrap('PgDn', () { - FFI.inputKey('VK_NEXT'); + gFFI.inputKey('VK_NEXT'); }), SizedBox(width: 9999), wrap('', () { - FFI.inputKey('VK_LEFT'); + gFFI.inputKey('VK_LEFT'); }, false, Icons.keyboard_arrow_left), wrap('', () { - FFI.inputKey('VK_UP'); + gFFI.inputKey('VK_UP'); }, false, Icons.keyboard_arrow_up), wrap('', () { - FFI.inputKey('VK_DOWN'); + gFFI.inputKey('VK_DOWN'); }, false, Icons.keyboard_arrow_down), wrap('', () { - FFI.inputKey('VK_RIGHT'); + gFFI.inputKey('VK_RIGHT'); }, false, Icons.keyboard_arrow_right), wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () { sendPrompt(isMac, 'VK_C'); @@ -871,7 +871,7 @@ class ImagePaint extends StatelessWidget { Widget build(BuildContext context) { final m = Provider.of(context); final c = Provider.of(context); - final adjust = FFI.cursorModel.adjustForKeyboard(); + final adjust = gFFI.cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( painter: new ImagePainter( @@ -885,7 +885,7 @@ class CursorPaint extends StatelessWidget { Widget build(BuildContext context) { final m = Provider.of(context); final c = Provider.of(context); - final adjust = FFI.cursorModel.adjustForKeyboard(); + final adjust = gFFI.cursorModel.adjustForKeyboard(); var s = c.scale; return CustomPaint( painter: new ImagePainter( @@ -925,10 +925,10 @@ class ImagePainter extends CustomPainter { CheckboxListTile getToggle(void Function(void Function()) setState, option, name) { return CheckboxListTile( - value: FFI.getByName('toggle_option', option) == 'true', + value: gFFI.getByName('toggle_option', option) == 'true', onChanged: (v) { setState(() { - FFI.setByName('toggle_option', option); + gFFI.setByName('toggle_option', option); }); }, dense: true, @@ -948,12 +948,12 @@ RadioListTile getRadio(String name, String toValue, String curValue, } void showOptions() { - String quality = FFI.getByName('image_quality'); + String quality = gFFI.getByName('image_quality'); if (quality == '') quality = 'balanced'; - String viewStyle = FFI.getByName('peer_option', 'view-style'); + String viewStyle = gFFI.getByName('peer_option', 'view-style'); var displays = []; - final pi = FFI.ffiModel.pi; - final image = FFI.ffiModel.getConnectionImage(); + final pi = gFFI.ffiModel.pi; + final image = gFFI.ffiModel.getConnectionImage(); if (image != null) displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); if (pi.displays.length > 1) { @@ -963,7 +963,7 @@ void showOptions() { children.add(InkWell( onTap: () { if (i == cur) return; - FFI.setByName('switch_display', i.toString()); + gFFI.setByName('switch_display', i.toString()); SmartDialog.dismiss(); }, child: Ink( @@ -987,7 +987,7 @@ void showOptions() { if (displays.isNotEmpty) { displays.add(Divider(color: MyTheme.border)); } - final perms = FFI.ffiModel.permissions; + final perms = gFFI.ffiModel.permissions; DialogManager.show((setState, close) { final more = []; @@ -1007,16 +1007,16 @@ void showOptions() { if (value == null) return; setState(() { quality = value; - FFI.setByName('image_quality', value); + gFFI.setByName('image_quality', value); }); }; var setViewStyle = (String? value) { if (value == null) return; setState(() { viewStyle = value; - FFI.setByName( + gFFI.setByName( 'peer_option', '{"name": "view-style", "value": "$value"}'); - FFI.canvasModel.updateViewStyle(); + gFFI.canvasModel.updateViewStyle(); }); }; return CustomAlertDialog( @@ -1044,8 +1044,8 @@ void showOptions() { void showSetOSPassword(bool login) { final controller = TextEditingController(); - var password = FFI.getByName('peer_option', "os-password"); - var autoLogin = FFI.getByName('peer_option', "auto-login") != ""; + var password = gFFI.getByName('peer_option', "os-password"); + var autoLogin = gFFI.getByName('peer_option', "auto-login") != ""; controller.text = password; DialogManager.show((setState, close) { return CustomAlertDialog( @@ -1078,12 +1078,12 @@ void showSetOSPassword(bool login) { style: flatButtonStyle, onPressed: () { var text = controller.text.trim(); - FFI.setByName( + gFFI.setByName( 'peer_option', '{"name": "os-password", "value": "$text"}'); - FFI.setByName('peer_option', + gFFI.setByName('peer_option', '{"name": "auto-login", "value": "${autoLogin ? 'Y' : ''}"}'); if (text != "" && login) { - FFI.setByName('input_os_password', text); + gFFI.setByName('input_os_password', text); } close(); }, @@ -1094,17 +1094,17 @@ void showSetOSPassword(bool login) { } void sendPrompt(bool isMac, String key) { - final old = isMac ? FFI.command : FFI.ctrl; + final old = isMac ? gFFI.command : gFFI.ctrl; if (isMac) { - FFI.command = true; + gFFI.command = true; } else { - FFI.ctrl = true; + gFFI.ctrl = true; } - FFI.inputKey(key); + gFFI.inputKey(key); if (isMac) { - FFI.command = old; + gFFI.command = old; } else { - FFI.ctrl = old; + gFFI.ctrl = old; } } diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index a7d01f0b8..2f5a9d991 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -1,11 +1,13 @@ -import 'package:flutter/material.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:image/image.dart' as img; -import 'package:zxing2/qrcode.dart'; -import 'dart:io'; import 'dart:async'; import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image/image.dart' as img; +import 'package:image_picker/image_picker.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:zxing2/qrcode.dart'; + import '../../common.dart'; import '../../models/model.dart'; @@ -153,10 +155,10 @@ class _ScanPageState extends State { void showServerSettingsWithValue( String id, String relay, String key, String api) { final formKey = GlobalKey(); - final id0 = FFI.getByName('option', 'custom-rendezvous-server'); - final relay0 = FFI.getByName('option', 'relay-server'); - final api0 = FFI.getByName('option', 'api-server'); - final key0 = FFI.getByName('option', 'key'); + final id0 = gFFI.getByName('option', 'custom-rendezvous-server'); + final relay0 = gFFI.getByName('option', 'relay-server'); + final api0 = gFFI.getByName('option', 'api-server'); + final key0 = gFFI.getByName('option', 'key'); DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate('ID/Relay Server')), @@ -227,17 +229,17 @@ void showServerSettingsWithValue( formKey.currentState!.validate()) { formKey.currentState!.save(); if (id != id0) - FFI.setByName('option', + gFFI.setByName('option', '{"name": "custom-rendezvous-server", "value": "$id"}'); if (relay != relay0) - FFI.setByName( + gFFI.setByName( 'option', '{"name": "relay-server", "value": "$relay"}'); if (key != key0) - FFI.setByName('option', '{"name": "key", "value": "$key"}'); + gFFI.setByName('option', '{"name": "key", "value": "$key"}'); if (api != api0) - FFI.setByName( + gFFI.setByName( 'option', '{"name": "api-server", "value": "$api"}'); - FFI.ffiModel.updateUser(); + gFFI.ffiModel.updateUser(); close(); } }, @@ -253,6 +255,6 @@ String? validate(value) { if (value.isEmpty) { return null; } - final res = FFI.getByName('test_if_valid_server', value); + final res = gFFI.getByName('test_if_valid_server', value); return res.isEmpty ? null : res; } diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 9caa327ea..3b0332fa7 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/mobile/widgets/dialog.dart'; +import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import '../../common.dart'; +import '../../models/model.dart'; import '../../models/server_model.dart'; import 'home_page.dart'; -import '../../models/model.dart'; class ServerPage extends StatelessWidget implements PageShape { @override @@ -30,12 +30,12 @@ class ServerPage extends StatelessWidget implements PageShape { PopupMenuItem( child: Text(translate("Set your own password")), value: "changePW", - enabled: FFI.serverModel.isStart, + enabled: gFFI.serverModel.isStart, ), PopupMenuItem( child: Text(translate("Refresh random password")), value: "refreshPW", - enabled: FFI.serverModel.isStart, + enabled: gFFI.serverModel.isStart, ) ]; }, @@ -47,7 +47,7 @@ class ServerPage extends StatelessWidget implements PageShape { } else if (value == "refreshPW") { () async { showLoading(translate("Waiting")); - if (await FFI.serverModel.updatePassword("")) { + if (await gFFI.serverModel.updatePassword("")) { showSuccess(); } else { showError(); @@ -62,10 +62,10 @@ class ServerPage extends StatelessWidget implements PageShape { Widget build(BuildContext context) { checkService(); return ChangeNotifierProvider.value( - value: FFI.serverModel, + value: gFFI.serverModel, child: Consumer( builder: (context, serverModel, child) => SingleChildScrollView( - controller: FFI.serverModel.controller, + controller: gFFI.serverModel.controller, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -82,9 +82,9 @@ class ServerPage extends StatelessWidget implements PageShape { } void checkService() async { - FFI.invokeMethod("check_service"); // jvm + gFFI.invokeMethod("check_service"); // jvm // for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page - if (PermissionManager.isWaitingFile() && !FFI.serverModel.fileOk) { + if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) { PermissionManager.complete("file", await PermissionManager.check("file")); debugPrint("file permission finished"); } @@ -96,7 +96,7 @@ class ServerInfo extends StatefulWidget { } class _ServerInfoState extends State { - final model = FFI.serverModel; + final model = gFFI.serverModel; var _passwdShow = false; @override @@ -327,7 +327,7 @@ class ConnectionManager extends StatelessWidget { ? SizedBox.shrink() : IconButton( onPressed: () { - FFI.chatModel + gFFI.chatModel .changeCurrentID(entry.value.id); final bar = navigationBarKey.currentWidget; @@ -355,8 +355,9 @@ class ConnectionManager extends StatelessWidget { MaterialStateProperty.all(Colors.red)), icon: Icon(Icons.close), onPressed: () { - FFI.setByName("close_conn", entry.key.toString()); - FFI.invokeMethod( + gFFI.setByName( + "close_conn", entry.key.toString()); + gFFI.invokeMethod( "cancel_notification", entry.key); }, label: Text(translate("Close"))) @@ -461,14 +462,14 @@ Widget clientInfo(Client client) { } void toAndroidChannelInit() { - FFI.setMethodCallHandler((method, arguments) { + gFFI.setMethodCallHandler((method, arguments) { debugPrint("flutter got android msg,$method,$arguments"); try { switch (method) { case "start_capture": { SmartDialog.dismiss(); - FFI.serverModel.updateClientState(); + gFFI.serverModel.updateClientState(); break; } case "on_state_changed": @@ -476,7 +477,7 @@ void toAndroidChannelInit() { var name = arguments["name"] as String; var value = arguments["value"] as String == "true"; debugPrint("from jvm:on_state_changed,$name:$value"); - FFI.serverModel.changeStatue(name, value); + gFFI.serverModel.changeStatue(name, value); break; } case "on_android_permission_result": @@ -488,7 +489,7 @@ void toAndroidChannelInit() { } case "on_media_projection_canceled": { - FFI.serverModel.stopService(); + gFFI.serverModel.stopService(); break; } } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index a1225ae85..a3965c199 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -1,12 +1,14 @@ -import 'package:settings_ui/settings_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:provider/provider.dart'; import 'dart:convert'; + +import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; +import 'package:settings_ui/settings_ui.dart'; +import 'package:url_launcher/url_launcher.dart'; + import '../../common.dart'; -import '../widgets/dialog.dart'; import '../../models/model.dart'; +import '../widgets/dialog.dart'; import 'home_page.dart'; import 'scan_page.dart'; @@ -89,10 +91,10 @@ class _SettingsState extends State { } void showServerSettings() { - final id = FFI.getByName('option', 'custom-rendezvous-server'); - final relay = FFI.getByName('option', 'relay-server'); - final api = FFI.getByName('option', 'api-server'); - final key = FFI.getByName('option', 'key'); + final id = gFFI.getByName('option', 'custom-rendezvous-server'); + final relay = gFFI.getByName('option', 'relay-server'); + final api = gFFI.getByName('option', 'api-server'); + final key = gFFI.getByName('option', 'key'); showServerSettingsWithValue(id, relay, key, api); } @@ -145,8 +147,8 @@ fetch('http://localhost:21114/api/login', { final body = { 'username': name, 'password': pass, - 'id': FFI.getByName('server_id'), - 'uuid': FFI.getByName('uuid') + 'id': gFFI.getByName('server_id'), + 'uuid': gFFI.getByName('uuid') }; try { final response = await http.post(Uri.parse('${url}/api/login'), @@ -166,24 +168,25 @@ String parseResp(String body) { } final token = data['access_token']; if (token != null) { - FFI.setByName('option', '{"name": "access_token", "value": "$token"}'); + gFFI.setByName('option', '{"name": "access_token", "value": "$token"}'); } final info = data['user']; if (info != null) { final value = json.encode(info); - FFI.setByName('option', json.encode({"name": "user_info", "value": value})); - FFI.ffiModel.updateUser(); + gFFI.setByName( + 'option', json.encode({"name": "user_info", "value": value})); + gFFI.ffiModel.updateUser(); } return ''; } void refreshCurrentUser() async { - final token = FFI.getByName("option", "access_token"); + final token = gFFI.getByName("option", "access_token"); if (token == '') return; final url = getUrl(); final body = { - 'id': FFI.getByName('server_id'), - 'uuid': FFI.getByName('uuid') + 'id': gFFI.getByName('server_id'), + 'uuid': gFFI.getByName('uuid') }; try { final response = await http.post(Uri.parse('${url}/api/currentUser'), @@ -204,12 +207,12 @@ void refreshCurrentUser() async { } void logout() async { - final token = FFI.getByName("option", "access_token"); + final token = gFFI.getByName("option", "access_token"); if (token == '') return; final url = getUrl(); final body = { - 'id': FFI.getByName('server_id'), - 'uuid': FFI.getByName('uuid') + 'id': gFFI.getByName('server_id'), + 'uuid': gFFI.getByName('uuid') }; try { await http.post(Uri.parse('${url}/api/logout'), @@ -225,15 +228,15 @@ void logout() async { } void resetToken() { - FFI.setByName('option', '{"name": "access_token", "value": ""}'); - FFI.setByName('option', '{"name": "user_info", "value": ""}'); - FFI.ffiModel.updateUser(); + gFFI.setByName('option', '{"name": "access_token", "value": ""}'); + gFFI.setByName('option', '{"name": "user_info", "value": ""}'); + gFFI.ffiModel.updateUser(); } String getUrl() { - var url = FFI.getByName('option', 'api-server'); + var url = gFFI.getByName('option', 'api-server'); if (url == '') { - url = FFI.getByName('option', 'custom-rendezvous-server'); + url = gFFI.getByName('option', 'custom-rendezvous-server'); if (url != '') { if (url.contains(':')) { final tmp = url.split(':'); @@ -323,10 +326,10 @@ void showLogin() { } String? getUsername() { - final token = FFI.getByName("option", "access_token"); + final token = gFFI.getByName("option", "access_token"); String? username; if (token != "") { - final info = FFI.getByName("option", "user_info"); + final info = gFFI.getByName("option", "user_info"); if (info != "") { try { Map tmp = json.decode(info); diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 54f034627..c1e8a31e5 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + import '../../common.dart'; import '../../models/model.dart'; @@ -86,7 +87,7 @@ void updatePasswordDialog() { ? () async { close(); showLoading(translate("Waiting")); - if (await FFI.serverModel.updatePassword(p0.text)) { + if (await gFFI.serverModel.updatePassword(p0.text)) { showSuccess(); } else { showError(); @@ -102,7 +103,7 @@ void updatePasswordDialog() { void enterPasswordDialog(String id) { final controller = TextEditingController(); - var remember = FFI.getByName('remember', id) == 'true'; + var remember = gFFI.getByName('remember', id) == 'true'; DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate('Password Required')), @@ -137,7 +138,7 @@ void enterPasswordDialog(String id) { onPressed: () { var text = controller.text.trim(); if (text == '') return; - FFI.login(id, text, remember); + gFFI.login(id, text, remember); close(); showLoading(translate('Logging in...')); }, diff --git a/flutter/lib/mobile/widgets/overlay.dart b/flutter/lib/mobile/widgets/overlay.dart index b2176ef0a..d2a1bdb57 100644 --- a/flutter/lib/mobile/widgets/overlay.dart +++ b/flutter/lib/mobile/widgets/overlay.dart @@ -157,7 +157,7 @@ hideChatWindowOverlay() { toggleChatOverlay() { if (chatIconOverlayEntry == null || chatWindowOverlayEntry == null) { - FFI.invokeMethod("enable_soft_keyboard", true); + gFFI.invokeMethod("enable_soft_keyboard", true); showChatIconOverlay(); showChatWindowOverlay(); } else { @@ -248,12 +248,12 @@ showMobileActionsOverlay() { position: Offset(left, top), width: overlayW, height: overlayH, - onBackPressed: () => FFI.tap(MouseButtons.right), - onHomePressed: () => FFI.tap(MouseButtons.wheel), + onBackPressed: () => gFFI.tap(MouseButtons.right), + onHomePressed: () => gFFI.tap(MouseButtons.wheel), onRecentPressed: () async { - FFI.sendMouse('down', MouseButtons.wheel); + gFFI.sendMouse('down', MouseButtons.wheel); await Future.delayed(Duration(milliseconds: 500)); - FFI.sendMouse('up', MouseButtons.wheel); + gFFI.sendMouse('up', MouseButtons.wheel); }, ); }); diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index efef5f1e4..0eb6db279 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -41,6 +41,11 @@ class ChatModel with ChangeNotifier { int get currentID => _currentID; + WeakReference _ffi; + + /// Constructor + ChatModel(this._ffi); + ChatUser get currentUser { final user = messages[currentID]?.chatUser; if (user == null) { @@ -56,7 +61,7 @@ class ChatModel with ChangeNotifier { _currentID = id; notifyListeners(); } else { - final client = FFI.serverModel.clients[id]; + final client = _ffi.target?.serverModel.clients[id]; if (client == null) { return debugPrint( "Failed to changeCurrentID,remote user doesn't exist"); @@ -80,11 +85,11 @@ class ChatModel with ChangeNotifier { late final chatUser; if (id == clientModeID) { chatUser = ChatUser( - name: FFI.ffiModel.pi.username, - uid: FFI.getId(), + name: _ffi.target?.ffiModel.pi.username, + uid: _ffi.target?.getId(), ); } else { - final client = FFI.serverModel.clients[id]; + final client = _ffi.target?.serverModel.clients[id]; if (client == null) { return debugPrint("Failed to receive msg,user doesn't exist"); } @@ -112,12 +117,12 @@ class ChatModel with ChangeNotifier { if (message.text != null && message.text!.isNotEmpty) { _messages[_currentID]?.add(message); if (_currentID == clientModeID) { - FFI.setByName("chat_client_mode", message.text!); + _ffi.target?.setByName("chat_client_mode", message.text!); } else { final msg = Map() ..["id"] = _currentID ..["text"] = message.text!; - FFI.setByName("chat_server_mode", jsonEncode(msg)); + _ffi.target?.setByName("chat_server_mode", jsonEncode(msg)); } } notifyListeners(); diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 2122b146f..0f7ce0df2 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter_hbb/common.dart'; + import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:path/path.dart' as Path; @@ -69,6 +70,10 @@ class FileModel extends ChangeNotifier { final _jobResultListener = JobResultListener>(); + final WeakReference _ffi; + + FileModel(this._ffi); + toggleSelectMode() { if (jobState == JobState.inProgress) { return; @@ -162,7 +167,7 @@ class FileModel extends ChangeNotifier { // overwrite msg['need_override'] = 'true'; } - FFI.setByName("set_confirm_override_file", jsonEncode(msg)); + _ffi.target?.setByName("set_confirm_override_file", jsonEncode(msg)); } } @@ -172,20 +177,23 @@ class FileModel extends ChangeNotifier { } onReady() async { - _localOption.home = FFI.getByName("get_home_dir"); + _localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; _localOption.showHidden = - FFI.getByName("peer_option", "local_show_hidden").isNotEmpty; + _ffi.target?.getByName("peer_option", "local_show_hidden").isNotEmpty ?? + false; - _remoteOption.showHidden = - FFI.getByName("peer_option", "remote_show_hidden").isNotEmpty; - _remoteOption.isWindows = FFI.ffiModel.pi.platform == "Windows"; + _remoteOption.showHidden = _ffi.target + ?.getByName("peer_option", "remote_show_hidden") + .isNotEmpty ?? + false; + _remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows"; - debugPrint("remote platform: ${FFI.ffiModel.pi.platform}"); + debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}"); await Future.delayed(Duration(milliseconds: 100)); - final local = FFI.getByName("peer_option", "local_dir"); - final remote = FFI.getByName("peer_option", "remote_dir"); + final local = _ffi.target?.getByName("peer_option", "local_dir") ?? ""; + final remote = _ffi.target?.getByName("peer_option", "remote_dir") ?? ""; openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true); openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false); await Future.delayed(Duration(seconds: 1)); @@ -205,19 +213,19 @@ class FileModel extends ChangeNotifier { msg["name"] = "local_dir"; msg["value"] = _currentLocalDir.path; - FFI.setByName('peer_option', jsonEncode(msg)); + _ffi.target?.setByName('peer_option', jsonEncode(msg)); msg["name"] = "local_show_hidden"; msg["value"] = _localOption.showHidden ? "Y" : ""; - FFI.setByName('peer_option', jsonEncode(msg)); + _ffi.target?.setByName('peer_option', jsonEncode(msg)); msg["name"] = "remote_dir"; msg["value"] = _currentRemoteDir.path; - FFI.setByName('peer_option', jsonEncode(msg)); + _ffi.target?.setByName('peer_option', jsonEncode(msg)); msg["name"] = "remote_show_hidden"; msg["value"] = _remoteOption.showHidden ? "Y" : ""; - FFI.setByName('peer_option', jsonEncode(msg)); + _ffi.target?.setByName('peer_option', jsonEncode(msg)); _currentLocalDir.clear(); _currentRemoteDir.clear(); _localOption.clear(); @@ -279,7 +287,7 @@ class FileModel extends ChangeNotifier { "show_hidden": showHidden.toString(), "is_remote": (!(items.isLocal!)).toString() }; - FFI.setByName("send_files", jsonEncode(msg)); + _ffi.target?.setByName("send_files", jsonEncode(msg)); }); } @@ -478,7 +486,7 @@ class FileModel extends ChangeNotifier { "file_num": fileNum.toString(), "is_remote": (!(isLocal)).toString() }; - FFI.setByName("remove_file", jsonEncode(msg)); + _ffi.target?.setByName("remove_file", jsonEncode(msg)); } sendRemoveEmptyDir(String path, int fileNum, bool isLocal) { @@ -487,7 +495,7 @@ class FileModel extends ChangeNotifier { "path": path, "is_remote": (!isLocal).toString() }; - FFI.setByName("remove_all_empty_dirs", jsonEncode(msg)); + _ffi.target?.setByName("remove_all_empty_dirs", jsonEncode(msg)); } createDir(String path) { @@ -497,11 +505,11 @@ class FileModel extends ChangeNotifier { "path": path, "is_remote": (!isLocal).toString() }; - FFI.setByName("create_dir", jsonEncode(msg)); + _ffi.target?.setByName("create_dir", jsonEncode(msg)); } cancelJob(int id) { - FFI.setByName("cancel_job", id.toString()); + _ffi.target?.setByName("cancel_job", id.toString()); jobReset(); } @@ -627,11 +635,11 @@ class FileFetcher { try { final msg = {"path": path, "show_hidden": showHidden.toString()}; if (isLocal) { - final res = FFI.getByName("read_local_dir_sync", jsonEncode(msg)); + final res = gFFI.getByName("read_local_dir_sync", jsonEncode(msg)); final fd = FileDirectory.fromJson(jsonDecode(res)); return fd; } else { - FFI.setByName("read_remote_dir", jsonEncode(msg)); + gFFI.setByName("read_remote_dir", jsonEncode(msg)); return registerReadTask(isLocal, path); } } catch (e) { @@ -649,7 +657,7 @@ class FileFetcher { "show_hidden": showHidden.toString(), "is_remote": (!isLocal).toString() }; - FFI.setByName("read_dir_recursive", jsonEncode(msg)); + gFFI.setByName("read_dir_recursive", jsonEncode(msg)); return registerReadRecursiveTask(id); } catch (e) { return Future.error(e); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 3659a85df..85bdc13b7 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -25,6 +25,8 @@ bool _waitForImage = false; class FfiModel with ChangeNotifier { PeerInfo _pi = PeerInfo(); Display _display = Display(); + PlatformFFI _platformFFI = PlatformFFI(); + var _inputBlocked = false; final _permissions = Map(); bool? _secure; @@ -32,11 +34,18 @@ class FfiModel with ChangeNotifier { bool _touchMode = false; Timer? _timer; var _reconnects = 1; + WeakReference parent; Map get permissions => _permissions; Display get display => _display; + PlatformFFI get platformFFI => _platformFFI; + + set platformFFI(PlatformFFI value) { + _platformFFI = value; + } + bool? get secure => _secure; bool? get direct => _direct; @@ -53,13 +62,13 @@ class FfiModel with ChangeNotifier { _inputBlocked = v; } - FfiModel() { + FfiModel(this.parent) { Translator.call = translate; clear(); } Future init() async { - await PlatformFFI.init(); + await _platformFFI.init(); } void toggleTouchMode() { @@ -130,41 +139,41 @@ class FfiModel with ChangeNotifier { } else if (name == 'peer_info') { handlePeerInfo(evt, peerId); } else if (name == 'connection_ready') { - FFI.ffiModel.setConnectionType( - evt['secure'] == 'true', evt['direct'] == 'true'); + setConnectionType(evt['secure'] == 'true', evt['direct'] == 'true'); } else if (name == 'switch_display') { handleSwitchDisplay(evt); } else if (name == 'cursor_data') { - FFI.cursorModel.updateCursorData(evt); + parent.target?.cursorModel.updateCursorData(evt); } else if (name == 'cursor_id') { - FFI.cursorModel.updateCursorId(evt); + parent.target?.cursorModel.updateCursorId(evt); } else if (name == 'cursor_position') { - FFI.cursorModel.updateCursorPosition(evt); + parent.target?.cursorModel.updateCursorPosition(evt); } else if (name == 'clipboard') { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { - FFI.ffiModel.updatePermission(evt); + parent.target?.ffiModel.updatePermission(evt); } else if (name == 'chat_client_mode') { - FFI.chatModel.receive(ChatModel.clientModeID, evt['text'] ?? ""); + parent.target?.chatModel + .receive(ChatModel.clientModeID, evt['text'] ?? ""); } else if (name == 'chat_server_mode') { - FFI.chatModel + parent.target?.chatModel .receive(int.parse(evt['id'] as String), evt['text'] ?? ""); } else if (name == 'file_dir') { - FFI.fileModel.receiveFileDir(evt); + parent.target?.fileModel.receiveFileDir(evt); } else if (name == 'job_progress') { - FFI.fileModel.tryUpdateJobProgress(evt); + parent.target?.fileModel.tryUpdateJobProgress(evt); } else if (name == 'job_done') { - FFI.fileModel.jobDone(evt); + parent.target?.fileModel.jobDone(evt); } else if (name == 'job_error') { - FFI.fileModel.jobError(evt); + parent.target?.fileModel.jobError(evt); } else if (name == 'override_file_confirm') { - FFI.fileModel.overrideFileConfirm(evt); + parent.target?.fileModel.overrideFileConfirm(evt); } else if (name == 'try_start_without_auth') { - FFI.serverModel.loginRequest(evt); + parent.target?.serverModel.loginRequest(evt); } else if (name == 'on_client_authorized') { - FFI.serverModel.onClientAuthorized(evt); + parent.target?.serverModel.onClientAuthorized(evt); } else if (name == 'on_client_remove') { - FFI.serverModel.onClientRemove(evt); + parent.target?.serverModel.onClientRemove(evt); } }; } @@ -178,44 +187,45 @@ class FfiModel with ChangeNotifier { } else if (name == 'peer_info') { handlePeerInfo(evt, peerId); } else if (name == 'connection_ready') { - FFI.ffiModel.setConnectionType( + parent.target?.ffiModel.setConnectionType( evt['secure'] == 'true', evt['direct'] == 'true'); } else if (name == 'switch_display') { handleSwitchDisplay(evt); } else if (name == 'cursor_data') { - FFI.cursorModel.updateCursorData(evt); + parent.target?.cursorModel.updateCursorData(evt); } else if (name == 'cursor_id') { - FFI.cursorModel.updateCursorId(evt); + parent.target?.cursorModel.updateCursorId(evt); } else if (name == 'cursor_position') { - FFI.cursorModel.updateCursorPosition(evt); + parent.target?.cursorModel.updateCursorPosition(evt); } else if (name == 'clipboard') { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { - FFI.ffiModel.updatePermission(evt); + parent.target?.ffiModel.updatePermission(evt); } else if (name == 'chat_client_mode') { - FFI.chatModel.receive(ChatModel.clientModeID, evt['text'] ?? ""); + parent.target?.chatModel + .receive(ChatModel.clientModeID, evt['text'] ?? ""); } else if (name == 'chat_server_mode') { - FFI.chatModel + parent.target?.chatModel .receive(int.parse(evt['id'] as String), evt['text'] ?? ""); } else if (name == 'file_dir') { - FFI.fileModel.receiveFileDir(evt); + parent.target?.fileModel.receiveFileDir(evt); } else if (name == 'job_progress') { - FFI.fileModel.tryUpdateJobProgress(evt); + parent.target?.fileModel.tryUpdateJobProgress(evt); } else if (name == 'job_done') { - FFI.fileModel.jobDone(evt); + parent.target?.fileModel.jobDone(evt); } else if (name == 'job_error') { - FFI.fileModel.jobError(evt); + parent.target?.fileModel.jobError(evt); } else if (name == 'override_file_confirm') { - FFI.fileModel.overrideFileConfirm(evt); + parent.target?.fileModel.overrideFileConfirm(evt); } else if (name == 'try_start_without_auth') { - FFI.serverModel.loginRequest(evt); + parent.target?.serverModel.loginRequest(evt); } else if (name == 'on_client_authorized') { - FFI.serverModel.onClientAuthorized(evt); + parent.target?.serverModel.onClientAuthorized(evt); } else if (name == 'on_client_remove') { - FFI.serverModel.onClientRemove(evt); + parent.target?.serverModel.onClientRemove(evt); } }; - PlatformFFI.setEventCallback(cb); + platformFFI.setEventCallback(cb); } void handleSwitchDisplay(Map evt) { @@ -226,7 +236,7 @@ class FfiModel with ChangeNotifier { _display.width = int.parse(evt['width']); _display.height = int.parse(evt['height']); if (old != _pi.currentDisplay) - FFI.cursorModel.updateDisplayOrigin(_display.x, _display.y); + parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y); notifyListeners(); } @@ -252,7 +262,7 @@ class FfiModel with ChangeNotifier { _timer?.cancel(); if (hasRetry) { _timer = Timer(Duration(seconds: _reconnects), () { - FFI.bind.sessionReconnect(id: id); + parent.target?.bind.sessionReconnect(id: id); clearPermissions(); showLoading(translate('Connecting...')); }); @@ -274,16 +284,17 @@ class FfiModel with ChangeNotifier { if (isPeerAndroid) { _touchMode = true; - if (FFI.ffiModel.permissions['keyboard'] != false) { + if (parent.target?.ffiModel.permissions['keyboard'] != false) { Timer(Duration(milliseconds: 100), showMobileActionsOverlay); } } else { - _touchMode = - await FFI.bind.getSessionOption(id: peerId, arg: "touch-mode") != ''; + _touchMode = await parent.target?.bind + .getSessionOption(id: peerId, arg: "touch-mode") != + ''; } if (evt['is_file_transfer'] == "true") { - FFI.fileModel.onReady(); + parent.target?.fileModel.onReady(); } else { _pi.displays = []; List displays = json.decode(evt['displays']); @@ -316,18 +327,22 @@ class ImageModel with ChangeNotifier { String _id = ""; + WeakReference parent; + + ImageModel(this.parent); + void onRgba(Uint8List rgba) { if (_waitForImage) { _waitForImage = false; SmartDialog.dismiss(); } - final pid = FFI.id; + final pid = parent.target?.id; ui.decodeImageFromPixels( rgba, - FFI.ffiModel.display.width, - FFI.ffiModel.display.height, + parent.target?.ffiModel.display.width ?? 0, + parent.target?.ffiModel.display.height ?? 0, isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888, (image) { - if (FFI.id != pid) return; + if (parent.target?.id != pid) return; try { // my throw exception, because the listener maybe already dispose update(image); @@ -340,19 +355,21 @@ class ImageModel with ChangeNotifier { void update(ui.Image? image) { if (_image == null && image != null) { if (isWebDesktop) { - FFI.canvasModel.updateViewStyle(); + parent.target?.canvasModel.updateViewStyle(); } else { final size = MediaQueryData.fromWindow(ui.window).size; final xscale = size.width / image.width; final yscale = size.height / image.height; - FFI.canvasModel.scale = max(xscale, yscale); + parent.target?.canvasModel.scale = max(xscale, yscale); + } + if (parent.target != null) { + initializeCursorAndCanvas(parent.target!); } - initializeCursorAndCanvas(); Future.delayed(Duration(milliseconds: 1), () { - if (FFI.ffiModel.isPeerAndroid) { - FFI.bind + if (parent.target?.ffiModel.isPeerAndroid ?? false) { + parent.target?.bind .sessionPeerOption(id: _id, name: "view-style", value: "shrink"); - FFI.canvasModel.updateViewStyle(); + parent.target?.canvasModel.updateViewStyle(); } }); } @@ -383,7 +400,9 @@ class CanvasModel with ChangeNotifier { double _scale = 1.0; String id = ""; // TODO multi canvas model - CanvasModel(); + WeakReference parent; + + CanvasModel(this.parent); double get x => _x; @@ -392,13 +411,14 @@ class CanvasModel with ChangeNotifier { double get scale => _scale; void updateViewStyle() async { - final s = await FFI.bind.getSessionOption(id: id, arg: 'view-style'); + final s = + await parent.target?.bind.getSessionOption(id: id, arg: 'view-style'); if (s == null) { return; } final size = MediaQueryData.fromWindow(ui.window).size; - final s1 = size.width / FFI.ffiModel.display.width; - final s2 = size.height / FFI.ffiModel.display.height; + final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); + final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); if (s == 'shrink') { final s = s1 < s2 ? s1 : s2; if (s < 1) { @@ -412,8 +432,8 @@ class CanvasModel with ChangeNotifier { } else { _scale = 1; } - _x = (size.width - FFI.ffiModel.display.width * _scale) / 2; - _y = (size.height - FFI.ffiModel.display.height * _scale) / 2; + _x = (size.width - getDisplayWidth() * _scale) / 2; + _y = (size.height - getDisplayHeight() * _scale) / 2; notifyListeners(); } @@ -424,10 +444,18 @@ class CanvasModel with ChangeNotifier { notifyListeners(); } + int getDisplayWidth() { + return parent.target?.ffiModel.display.width ?? 1080; + } + + int getDisplayHeight() { + return parent.target?.ffiModel.display.height ?? 720; + } + void moveDesktopMouse(double x, double y) { final size = MediaQueryData.fromWindow(ui.window).size; - final dw = FFI.ffiModel.display.width * _scale; - final dh = FFI.ffiModel.display.height * _scale; + final dw = getDisplayWidth() * _scale; + final dh = getDisplayHeight() * _scale; var dxOffset = 0; var dyOffset = 0; if (dw > size.width) { @@ -441,7 +469,7 @@ class CanvasModel with ChangeNotifier { if (dxOffset != 0 || dyOffset != 0) { notifyListeners(); } - FFI.cursorModel.moveLocal(x, y); + parent.target?.cursorModel.moveLocal(x, y); } set scale(v) { @@ -470,17 +498,17 @@ class CanvasModel with ChangeNotifier { } void updateScale(double v) { - if (FFI.imageModel.image == null) return; - final offset = FFI.cursorModel.offset; - var r = FFI.cursorModel.getVisibleRect(); + if (parent.target?.imageModel.image == null) return; + final offset = parent.target?.cursorModel.offset ?? Offset(0, 0); + var r = parent.target?.cursorModel.getVisibleRect() ?? Rect.zero; final px0 = (offset.dx - r.left) * _scale; final py0 = (offset.dy - r.top) * _scale; _scale *= v; - final maxs = FFI.imageModel.maxScale; - final mins = FFI.imageModel.minScale; + final maxs = parent.target?.imageModel.maxScale ?? 1; + final mins = parent.target?.imageModel.minScale ?? 1; if (_scale > maxs) _scale = maxs; if (_scale < mins) _scale = mins; - r = FFI.cursorModel.getVisibleRect(); + r = parent.target?.cursorModel.getVisibleRect() ?? Rect.zero; final px1 = (offset.dx - r.left) * _scale; final py1 = (offset.dy - r.top) * _scale; _x -= px1 - px0; @@ -506,6 +534,7 @@ class CursorModel with ChangeNotifier { double _displayOriginX = 0; double _displayOriginY = 0; String id = ""; // TODO multi cursor model + WeakReference parent; ui.Image? get image => _image; @@ -519,12 +548,14 @@ class CursorModel with ChangeNotifier { double get hoty => _hoty; + CursorModel(this.parent); + // remote physical display coordinate Rect getVisibleRect() { final size = MediaQueryData.fromWindow(ui.window).size; - final xoffset = FFI.canvasModel.x; - final yoffset = FFI.canvasModel.y; - final scale = FFI.canvasModel.scale; + final xoffset = parent.target?.canvasModel.x ?? 0; + final yoffset = parent.target?.canvasModel.y ?? 0; + final scale = parent.target?.canvasModel.scale ?? 1; final x0 = _displayOriginX - xoffset / scale; final y0 = _displayOriginY - yoffset / scale; return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale); @@ -535,7 +566,7 @@ class CursorModel with ChangeNotifier { var keyboardHeight = m.viewInsets.bottom; final size = m.size; if (keyboardHeight < 100) return 0; - final s = FFI.canvasModel.scale; + final s = parent.target?.canvasModel.scale ?? 1.0; final thresh = (size.height - keyboardHeight) / 2; var h = (_y - getVisibleRect().top) * s; // local physical display height return h - thresh; @@ -543,19 +574,19 @@ class CursorModel with ChangeNotifier { void touch(double x, double y, MouseButtons button) { moveLocal(x, y); - FFI.moveMouse(_x, _y); - FFI.tap(button); + parent.target?.moveMouse(_x, _y); + parent.target?.tap(button); } void move(double x, double y) { moveLocal(x, y); - FFI.moveMouse(_x, _y); + parent.target?.moveMouse(_x, _y); } void moveLocal(double x, double y) { - final scale = FFI.canvasModel.scale; - final xoffset = FFI.canvasModel.x; - final yoffset = FFI.canvasModel.y; + final scale = parent.target?.canvasModel.scale ?? 1.0; + final xoffset = parent.target?.canvasModel.x ?? 0; + final yoffset = parent.target?.canvasModel.y ?? 0; _x = (x - xoffset) / scale + _displayOriginX; _y = (y - yoffset) / scale + _displayOriginY; notifyListeners(); @@ -564,22 +595,22 @@ class CursorModel with ChangeNotifier { void reset() { _x = _displayOriginX; _y = _displayOriginY; - FFI.moveMouse(_x, _y); - FFI.canvasModel.clear(true); + parent.target?.moveMouse(_x, _y); + parent.target?.canvasModel.clear(true); notifyListeners(); } void updatePan(double dx, double dy, bool touchMode) { - if (FFI.imageModel.image == null) return; + if (parent.target?.imageModel.image == null) return; if (touchMode) { - final scale = FFI.canvasModel.scale; + final scale = parent.target?.canvasModel.scale ?? 1.0; _x += dx / scale; _y += dy / scale; - FFI.moveMouse(_x, _y); + parent.target?.moveMouse(_x, _y); notifyListeners(); return; } - final scale = FFI.canvasModel.scale; + final scale = parent.target?.canvasModel.scale ?? 1.0; dx /= scale; dy /= scale; final r = getVisibleRect(); @@ -588,7 +619,7 @@ class CursorModel with ChangeNotifier { var tryMoveCanvasX = false; if (dx > 0) { final maxCanvasCanMove = _displayOriginX + - FFI.imageModel.image!.width - + (parent.target?.imageModel.image!.width ?? 1280) - r.right.roundToDouble(); tryMoveCanvasX = _x + dx > cx && maxCanvasCanMove > 0; if (tryMoveCanvasX) { @@ -610,7 +641,7 @@ class CursorModel with ChangeNotifier { var tryMoveCanvasY = false; if (dy > 0) { final mayCanvasCanMove = _displayOriginY + - FFI.imageModel.image!.height - + (parent.target?.imageModel.image!.height ?? 720) - r.bottom.roundToDouble(); tryMoveCanvasY = _y + dy > cy && mayCanvasCanMove > 0; if (tryMoveCanvasY) { @@ -634,13 +665,13 @@ class CursorModel with ChangeNotifier { _x += dx; _y += dy; if (tryMoveCanvasX && dx != 0) { - FFI.canvasModel.panX(-dx); + parent.target?.canvasModel.panX(-dx); } if (tryMoveCanvasY && dy != 0) { - FFI.canvasModel.panY(-dy); + parent.target?.canvasModel.panY(-dy); } - FFI.moveMouse(_x, _y); + parent.target?.moveMouse(_x, _y); notifyListeners(); } @@ -652,10 +683,10 @@ class CursorModel with ChangeNotifier { var height = int.parse(evt['height']); List colors = json.decode(evt['colors']); final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); - var pid = FFI.id; + var pid = parent.target?.id; ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888, (image) { - if (FFI.id != pid) return; + if (parent.target?.id != pid) return; _image = image; _images[id] = Tuple3(image, _hotx, _hoty); try { @@ -688,8 +719,8 @@ class CursorModel with ChangeNotifier { _displayOriginY = y; _x = x + 1; _y = y + 1; - FFI.moveMouse(x, y); - FFI.canvasModel.resetOffset(); + parent.target?.moveMouse(x, y); + parent.target?.canvasModel.resetOffset(); notifyListeners(); } @@ -699,7 +730,7 @@ class CursorModel with ChangeNotifier { _displayOriginY = y; _x = xCursor; _y = yCursor; - FFI.moveMouse(x, y); + parent.target?.moveMouse(x, y); notifyListeners(); } @@ -729,33 +760,43 @@ extension ToString on MouseButtons { /// FFI class for communicating with the Rust core. class FFI { - static var id = ""; - static var shift = false; - static var ctrl = false; - static var alt = false; - static var command = false; - static var version = ""; - static final imageModel = ImageModel(); - static final ffiModel = FfiModel(); - static final cursorModel = CursorModel(); - static final canvasModel = CanvasModel(); - static final serverModel = ServerModel(); - static final chatModel = ChatModel(); - static final fileModel = FileModel(); + var id = ""; + var shift = false; + var ctrl = false; + var alt = false; + var command = false; + var version = ""; + late final ImageModel imageModel; + late final FfiModel ffiModel; + late final CursorModel cursorModel; + late final CanvasModel canvasModel; + late final ServerModel serverModel; + late final ChatModel chatModel; + late final FileModel fileModel; + + FFI() { + this.imageModel = ImageModel(WeakReference(this)); + this.ffiModel = FfiModel(WeakReference(this)); + this.cursorModel = CursorModel(WeakReference(this)); + this.canvasModel = CanvasModel(WeakReference(this)); + this.serverModel = ServerModel(WeakReference(this)); // use global FFI + this.chatModel = ChatModel(WeakReference(this)); + this.fileModel = FileModel(WeakReference(this)); + } /// Get the remote id for current client. - static String getId() { + String getId() { return getByName('remote_id'); // TODO } /// Send a mouse tap event(down and up). - static void tap(MouseButtons button) { + void tap(MouseButtons button) { sendMouse('down', button); sendMouse('up', button); } /// Send scroll event with scroll distance [y]. - static void scroll(int y) { + void scroll(int y) { setByName('send_mouse', json.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); } @@ -763,16 +804,16 @@ class FFI { /// Reconnect to the remote peer. // static void reconnect() { // setByName('reconnect'); - // FFI.ffiModel.clearPermissions(); + // parent.target?.ffiModel.clearPermissions(); // } /// Reset key modifiers to false, including [shift], [ctrl], [alt] and [command]. - static void resetModifiers() { + void resetModifiers() { shift = ctrl = alt = command = false; } /// Modify the given modifier map [evt] based on current modifier key status. - static Map modify(Map evt) { + Map modify(Map evt) { if (ctrl) evt['ctrl'] = 'true'; if (shift) evt['shift'] = 'true'; if (alt) evt['alt'] = 'true'; @@ -781,7 +822,7 @@ class FFI { } /// Send mouse press event. - static void sendMouse(String type, MouseButtons button) { + void sendMouse(String type, MouseButtons button) { if (!ffiModel.keyboard()) return; setByName('send_mouse', json.encode(modify({'id': id, 'type': type, 'buttons': button.value}))); @@ -790,7 +831,7 @@ class FFI { /// Send key stroke event. /// [down] indicates the key's state(down or up). /// [press] indicates a click event(down and up). - static void inputKey(String name, {bool? down, bool? press}) { + void inputKey(String name, {bool? down, bool? press}) { if (!ffiModel.keyboard()) return; // final Map out = Map(); // out['name'] = name; @@ -804,7 +845,7 @@ class FFI { // } // setByName('input_key', json.encode(modify(out))); // TODO id - FFI.bind.sessionInputKey( + bind.sessionInputKey( id: id, name: name, down: down ?? false, @@ -816,7 +857,7 @@ class FFI { } /// Send mouse movement event with distance in [x] and [y]. - static void moveMouse(double x, double y) { + void moveMouse(double x, double y) { if (!ffiModel.keyboard()) return; var x2 = x.toInt(); var y2 = y.toInt(); @@ -825,7 +866,7 @@ class FFI { } /// List the saved peers. - static List peers() { + List peers() { try { var str = getByName('peers'); // TODO if (str == "") return []; @@ -842,19 +883,19 @@ class FFI { } /// Connect with the given [id]. Only transfer file if [isFileTransfer]. - static void connect(String id, {bool isFileTransfer = false}) { + void connect(String id, {bool isFileTransfer = false}) { if (isFileTransfer) { setByName('connect_file_transfer', id); } else { - FFI.chatModel.resetClientMode(); + chatModel.resetClientMode(); // setByName('connect', id); // TODO multi model instances - FFI.canvasModel.id = id; - FFI.imageModel._id = id; - FFI.cursorModel.id = id; + canvasModel.id = id; + imageModel._id = id; + cursorModel.id = id; final stream = - FFI.bind.sessionConnect(id: id, isFileTransfer: isFileTransfer); - final cb = FFI.ffiModel.startEventListener(id); + bind.sessionConnect(id: id, isFileTransfer: isFileTransfer); + final cb = ffiModel.startEventListener(id); () async { await for (final message in stream) { if (message is Event) { @@ -866,28 +907,28 @@ class FFI { print('json.decode fail(): $e'); } } else if (message is Rgba) { - FFI.imageModel.onRgba(message.field0); + imageModel.onRgba(message.field0); } } }(); // every instance will bind a stream } - FFI.id = id; + id = id; } /// Login with [password], choose if the client should [remember] it. - static void login(String id, String password, bool remember) { - FFI.bind.sessionLogin(id: id, password: password, remember: remember); + void login(String id, String password, bool remember) { + bind.sessionLogin(id: id, password: password, remember: remember); } /// Close the remote session. - static void close() { + void close() { chatModel.close(); - if (FFI.imageModel.image != null && !isWebDesktop) { + if (imageModel.image != null && !isWebDesktop) { savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x, canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay); } - FFI.bind.sessionClose(id: id); + bind.sessionClose(id: id); id = ""; imageModel.update(null); cursorModel.clear(); @@ -898,18 +939,18 @@ class FFI { /// Send **get** command to the Rust core based on [name] and [arg]. /// Return the result as a string. - static String getByName(String name, [String arg = '']) { - return PlatformFFI.getByName(name, arg); + String getByName(String name, [String arg = '']) { + return ffiModel.platformFFI.getByName(name, arg); } /// Send **set** command to the Rust core based on [name] and [value]. - static void setByName(String name, [String value = '']) { - PlatformFFI.setByName(name, value); + void setByName(String name, [String value = '']) { + ffiModel.platformFFI.setByName(name, value); } - static RustdeskImpl get bind => PlatformFFI.ffiBind; + RustdeskImpl get bind => ffiModel.platformFFI.ffiBind; - static handleMouse(Map evt) { + handleMouse(Map evt) { var type = ''; var isMove = false; switch (evt['type']) { @@ -929,16 +970,16 @@ class FFI { var x = evt['x']; var y = evt['y']; if (isMove) { - FFI.canvasModel.moveDesktopMouse(x, y); + canvasModel.moveDesktopMouse(x, y); } - final d = FFI.ffiModel.display; - x -= FFI.canvasModel.x; - y -= FFI.canvasModel.y; + final d = ffiModel.display; + x -= canvasModel.x; + y -= canvasModel.y; if (!isMove && (x < 0 || x > d.width || y < 0 || y > d.height)) { return; } - x /= FFI.canvasModel.scale; - y /= FFI.canvasModel.scale; + x /= canvasModel.scale; + y /= canvasModel.scale; x += d.x; y += d.y; if (type != '') { @@ -964,20 +1005,20 @@ class FFI { setByName('send_mouse', json.encode(evt)); } - static listenToMouse(bool yesOrNo) { + listenToMouse(bool yesOrNo) { if (yesOrNo) { - PlatformFFI.startDesktopWebListener(); + ffiModel.platformFFI.startDesktopWebListener(); } else { - PlatformFFI.stopDesktopWebListener(); + ffiModel.platformFFI.stopDesktopWebListener(); } } - static void setMethodCallHandler(FMethod callback) { - PlatformFFI.setMethodCallHandler(callback); + void setMethodCallHandler(FMethod callback) { + ffiModel.platformFFI.setMethodCallHandler(callback); } - static Future invokeMethod(String method, [dynamic arguments]) async { - return await PlatformFFI.invokeMethod(method, arguments); + Future invokeMethod(String method, [dynamic arguments]) async { + return await ffiModel.platformFFI.invokeMethod(method, arguments); } } @@ -1038,15 +1079,15 @@ void removePreference(String id) async { prefs.remove('peer' + id); } -void initializeCursorAndCanvas() async { - var p = await getPreference(FFI.id); +void initializeCursorAndCanvas(FFI ffi) async { + var p = await getPreference(ffi.id); int currentDisplay = 0; if (p != null) { currentDisplay = p['currentDisplay']; } - if (p == null || currentDisplay != FFI.ffiModel.pi.currentDisplay) { - FFI.cursorModel - .updateDisplayOrigin(FFI.ffiModel.display.x, FFI.ffiModel.display.y); + if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) { + ffi.cursorModel + .updateDisplayOrigin(ffi.ffiModel.display.x, ffi.ffiModel.display.y); return; } double xCursor = p['xCursor']; @@ -1054,17 +1095,19 @@ void initializeCursorAndCanvas() async { double xCanvas = p['xCanvas']; double yCanvas = p['yCanvas']; double scale = p['scale']; - FFI.cursorModel.updateDisplayOriginWithCursor( - FFI.ffiModel.display.x, FFI.ffiModel.display.y, xCursor, yCursor); - FFI.canvasModel.update(xCanvas, yCanvas, scale); + ffi.cursorModel.updateDisplayOriginWithCursor( + ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor); + ffi.canvasModel.update(xCanvas, yCanvas, scale); } /// Translate text based on the pre-defined dictionary. -String translate(String name) { +/// 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.getByName(a, b); + return (ffi ?? gFFI).getByName(a, b); } diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index a425ea810..1ae523b8b 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -25,15 +25,15 @@ typedef F3 = void Function(Pointer, Pointer); /// FFI wrapper around the native Rust core. /// Hides the platform differences. class PlatformFFI { - static Pointer? _lastRgbaFrame; - static String _dir = ''; - static String _homeDir = ''; - static F2? _getByName; - static F3? _setByName; - static late RustdeskImpl _ffiBind; - static void Function(Map)? _eventCallback; + Pointer? _lastRgbaFrame; + String _dir = ''; + String _homeDir = ''; + F2? _getByName; + F3? _setByName; + late RustdeskImpl _ffiBind; + void Function(Map)? _eventCallback; - static RustdeskImpl get ffiBind => _ffiBind; + RustdeskImpl get ffiBind => _ffiBind; static Future getVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); @@ -42,7 +42,7 @@ class PlatformFFI { /// Send **get** command to the Rust core based on [name] and [arg]. /// Return the result as a string. - static String getByName(String name, [String arg = '']) { + String getByName(String name, [String arg = '']) { if (_getByName == null) return ''; var a = name.toNativeUtf8(); var b = arg.toNativeUtf8(); @@ -56,7 +56,7 @@ class PlatformFFI { } /// Send **set** command to the Rust core based on [name] and [value]. - static void setByName(String name, [String value = '']) { + void setByName(String name, [String value = '']) { if (_setByName == null) return; var a = name.toNativeUtf8(); var b = value.toNativeUtf8(); @@ -66,7 +66,7 @@ class PlatformFFI { } /// Init the FFI class, loads the native Rust core library. - static Future init() async { + Future init() async { isIOS = Platform.isIOS; isAndroid = Platform.isAndroid; isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux; @@ -134,7 +134,7 @@ class PlatformFFI { } /// Start listening to the Rust core's events and frames. - static void _startListenEvent(RustdeskImpl rustdeskImpl) { + void _startListenEvent(RustdeskImpl rustdeskImpl) { () async { await for (final message in rustdeskImpl.startGlobalEventStream()) { if (_eventCallback != null) { @@ -149,24 +149,24 @@ class PlatformFFI { }(); } - static void setEventCallback(void Function(Map) fun) async { + void setEventCallback(void Function(Map) fun) async { _eventCallback = fun; } - static void setRgbaCallback(void Function(Uint8List) fun) async {} + void setRgbaCallback(void Function(Uint8List) fun) async {} - static void startDesktopWebListener() {} + void startDesktopWebListener() {} - static void stopDesktopWebListener() {} + void stopDesktopWebListener() {} - static void setMethodCallHandler(FMethod callback) { + void setMethodCallHandler(FMethod callback) { toAndroidChannel.setMethodCallHandler((call) async { callback(call.method, call.arguments); return null; }); } - static invokeMethod(String method, [dynamic arguments]) async { + invokeMethod(String method, [dynamic arguments]) async { if (!isAndroid) return Future(() => false); return await toAndroidChannel.invokeMethod(method, arguments); } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 68d3d2391..311fef334 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -10,7 +10,6 @@ import '../mobile/pages/server_page.dart'; import 'model.dart'; const loginDialogTag = "LOGIN"; -final _emptyIdShow = translate("Generating ..."); class ServerModel with ChangeNotifier { bool _isStart = false; // Android MainService status @@ -20,7 +19,8 @@ class ServerModel with ChangeNotifier { bool _fileOk = false; int _connectStatus = 0; // Rendezvous Server status - final _serverId = TextEditingController(text: _emptyIdShow); + late String _emptyIdShow; + late final TextEditingController _serverId; final _serverPasswd = TextEditingController(text: ""); Map _clients = {}; @@ -45,8 +45,12 @@ class ServerModel with ChangeNotifier { final controller = ScrollController(); - ServerModel() { + WeakReference parent; + + ServerModel(this.parent) { () async { + _emptyIdShow = translate("Generating ...", ffi: this.parent.target); + _serverId = TextEditingController(text: this._emptyIdShow); /** * 1. check android permission * 2. check config @@ -59,39 +63,42 @@ class ServerModel with ChangeNotifier { // audio if (androidVersion < 30 || !await PermissionManager.check("audio")) { _audioOk = false; - FFI.setByName( + parent.target?.setByName( 'option', jsonEncode(Map() ..["name"] = "enable-audio" ..["value"] = "N")); } else { - final audioOption = FFI.getByName('option', 'enable-audio'); - _audioOk = audioOption.isEmpty; + final audioOption = parent.target?.getByName('option', 'enable-audio'); + _audioOk = audioOption?.isEmpty ?? false; } // file if (!await PermissionManager.check("file")) { _fileOk = false; - FFI.setByName( + parent.target?.setByName( 'option', jsonEncode(Map() ..["name"] = "enable-file-transfer" ..["value"] = "N")); } else { - final fileOption = FFI.getByName('option', 'enable-file-transfer'); - _fileOk = fileOption.isEmpty; + final fileOption = + parent.target?.getByName('option', 'enable-file-transfer'); + _fileOk = fileOption?.isEmpty ?? false; } // input (mouse control) Map res = Map() ..["name"] = "enable-keyboard" ..["value"] = 'N'; - FFI.setByName('option', jsonEncode(res)); // input false by default + parent.target + ?.setByName('option', jsonEncode(res)); // input false by default notifyListeners(); }(); Timer.periodic(Duration(seconds: 1), (timer) { - var status = int.tryParse(FFI.getByName('connect_statue')) ?? 0; + var status = + int.tryParse(parent.target?.getByName('connect_statue') ?? "") ?? 0; if (status > 0) { status = 1; } @@ -99,8 +106,9 @@ class ServerModel with ChangeNotifier { _connectStatus = status; notifyListeners(); } - final res = - FFI.getByName('check_clients_length', _clients.length.toString()); + final res = parent.target + ?.getByName('check_clients_length', _clients.length.toString()) ?? + ""; if (res.isNotEmpty) { debugPrint("clients not match!"); updateClientState(res); @@ -121,7 +129,7 @@ class ServerModel with ChangeNotifier { Map res = Map() ..["name"] = "enable-audio" ..["value"] = _audioOk ? '' : 'N'; - FFI.setByName('option', jsonEncode(res)); + parent.target?.setByName('option', jsonEncode(res)); notifyListeners(); } @@ -138,15 +146,17 @@ class ServerModel with ChangeNotifier { Map res = Map() ..["name"] = "enable-file-transfer" ..["value"] = _fileOk ? '' : 'N'; - FFI.setByName('option', jsonEncode(res)); + parent.target?.setByName('option', jsonEncode(res)); notifyListeners(); } toggleInput() { if (_inputOk) { - FFI.invokeMethod("stop_input"); + parent.target?.invokeMethod("stop_input"); } else { - showInputWarnAlert(); + if (parent.target != null) { + showInputWarnAlert(parent.target!); + } } } @@ -203,9 +213,10 @@ class ServerModel with ChangeNotifier { Future startService() async { _isStart = true; notifyListeners(); - FFI.ffiModel.updateEventListener(""); - await FFI.invokeMethod("init_service"); - FFI.setByName("start_service"); + // TODO + parent.target?.ffiModel.updateEventListener(""); + await parent.target?.invokeMethod("init_service"); + parent.target?.setByName("start_service"); getIDPasswd(); updateClientState(); if (!Platform.isLinux) { @@ -217,9 +228,10 @@ class ServerModel with ChangeNotifier { /// Stop the screen sharing service. Future stopService() async { _isStart = false; - FFI.serverModel.closeAll(); - await FFI.invokeMethod("stop_service"); - FFI.setByName("stop_service"); + // TODO + parent.target?.serverModel.closeAll(); + await parent.target?.invokeMethod("stop_service"); + parent.target?.setByName("stop_service"); notifyListeners(); if (!Platform.isLinux) { // current linux is not supported @@ -228,12 +240,12 @@ class ServerModel with ChangeNotifier { } Future initInput() async { - await FFI.invokeMethod("init_input"); + await parent.target?.invokeMethod("init_input"); } Future updatePassword(String pw) async { final oldPasswd = _serverPasswd.text; - FFI.setByName("update_password", pw); + parent.target?.setByName("update_password", pw); await Future.delayed(Duration(milliseconds: 500)); await getIDPasswd(force: true); @@ -261,8 +273,8 @@ class ServerModel with ChangeNotifier { const maxCount = 10; while (count < maxCount) { await Future.delayed(Duration(seconds: 1)); - final id = FFI.getByName("server_id"); - final passwd = FFI.getByName("server_password"); + final id = parent.target?.getByName("server_id") ?? ""; + final passwd = parent.target?.getByName("server_password") ?? ""; if (id.isEmpty) { continue; } else { @@ -299,7 +311,7 @@ class ServerModel with ChangeNotifier { Map res = Map() ..["name"] = "enable-keyboard" ..["value"] = value ? '' : 'N'; - FFI.setByName('option', jsonEncode(res)); + parent.target?.setByName('option', jsonEncode(res)); } _inputOk = value; break; @@ -310,7 +322,7 @@ class ServerModel with ChangeNotifier { } updateClientState([String? json]) { - var res = json ?? FFI.getByName("clients_state"); + var res = json ?? parent.target?.getByName("clients_state") ?? ""; try { final List clientsJson = jsonDecode(res); for (var clientJson in clientsJson) { @@ -397,16 +409,16 @@ class ServerModel with ChangeNotifier { response["id"] = client.id; response["res"] = res; if (res) { - FFI.setByName("login_res", jsonEncode(response)); + parent.target?.setByName("login_res", jsonEncode(response)); if (!client.isFileTransfer) { - FFI.invokeMethod("start_capture"); + parent.target?.invokeMethod("start_capture"); } - FFI.invokeMethod("cancel_notification", client.id); + parent.target?.invokeMethod("cancel_notification", client.id); _clients[client.id]?.authorized = true; notifyListeners(); } else { - FFI.setByName("login_res", jsonEncode(response)); - FFI.invokeMethod("cancel_notification", client.id); + parent.target?.setByName("login_res", jsonEncode(response)); + parent.target?.invokeMethod("cancel_notification", client.id); _clients.remove(client.id); } } @@ -427,7 +439,7 @@ class ServerModel with ChangeNotifier { if (_clients.containsKey(id)) { _clients.remove(id); DialogManager.dismissByTag(getLoginDialogTag(id)); - FFI.invokeMethod("cancel_notification", id); + parent.target?.invokeMethod("cancel_notification", id); } notifyListeners(); } catch (e) { @@ -437,7 +449,7 @@ class ServerModel with ChangeNotifier { closeAll() { _clients.forEach((id, client) { - FFI.setByName("close_conn", id.toString()); + parent.target?.setByName("close_conn", id.toString()); }); _clients.clear(); } @@ -485,7 +497,7 @@ String getLoginDialogTag(int id) { return loginDialogTag + id.toString(); } -showInputWarnAlert() { +showInputWarnAlert(FFI ffi) { DialogManager.show((setState, close) => CustomAlertDialog( title: Text(translate("How to get Android input permission?")), content: Column( @@ -501,7 +513,7 @@ showInputWarnAlert() { ElevatedButton( child: Text(translate("Open System Setting")), onPressed: () { - FFI.serverModel.initInput(); + ffi.serverModel.initInput(); close(); }), ], diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index b4cde8caf..d82f4c367 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -5,217 +5,217 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "40.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.0" archive: dependency: transitive description: name: archive - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.3.0" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.8.2" bitsdojo_window: dependency: "direct main" description: name: bitsdojo_window - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" bitsdojo_window_linux: dependency: transitive description: name: bitsdojo_window_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" bitsdojo_window_macos: dependency: transitive description: name: bitsdojo_window_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" bitsdojo_window_platform_interface: dependency: transitive description: name: bitsdojo_window_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" bitsdojo_window_windows: dependency: transitive description: name: bitsdojo_window_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.9" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.11" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "7.2.3" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "8.3.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.16.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" cross_file: dependency: transitive description: name: cross_file - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.3+1" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.4" + version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.3" dash_chat: dependency: "direct main" description: name: dash_chat - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.16" desktop_multi_window: @@ -231,133 +231,133 @@ packages: dependency: "direct main" description: name: device_info_plus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.2.3" + version: "3.2.4" device_info_plus_linux: dependency: transitive description: name: device_info_plus_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" device_info_plus_macos: dependency: transitive description: name: device_info_plus_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.3" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0+1" device_info_plus_web: dependency: transitive description: name: device_info_plus_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" device_info_plus_windows: dependency: transitive description: name: device_info_plus_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" draggable_float_widget: dependency: "direct main" description: name: draggable_float_widget - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.2" event_bus: dependency: transitive description: name: event_bus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" external_path: dependency: "direct main" description: name: external_path - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" ffi: dependency: "direct main" description: name: ffi - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.2" firebase_analytics: dependency: "direct main" description: name: firebase_analytics - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "9.1.9" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.7" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0+14" firebase_core: dependency: transitive description: name: firebase_core - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.4.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.4" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" flutter: @@ -369,28 +369,28 @@ packages: dependency: "direct main" description: name: flutter_breadcrumb - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.9.2" + version: "0.9.3" flutter_parsed_text: dependency: transitive description: name: flutter_parsed_text - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.6" flutter_rust_bridge: @@ -406,9 +406,9 @@ packages: dependency: "direct main" description: name: flutter_smart_dialog - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.3.2+1" + version: "4.5.3+2" flutter_test: dependency: "direct dev" description: flutter @@ -423,343 +423,350 @@ packages: dependency: "direct dev" description: name: freezed - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3+1" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" + get: + dependency: "direct main" + description: + name: get + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.6.5" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.4" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.1" image: dependency: "direct main" description: name: image - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.0" image_picker: dependency: "direct main" description: name: image_picker - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.5+3" image_picker_android: dependency: transitive description: name: image_picker_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.8.4+13" + version: "0.8.5" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" image_picker_ios: dependency: transitive description: name: image_picker_ios - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.5+5" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.0" intl: dependency: transitive description: name: intl - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.4" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.5.0" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.11" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.4" menu_base: dependency: transitive description: name: menu_base - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.7.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.2" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" package_info_plus_web: dependency: transitive description: name: package_info_plus_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.1" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.10" + version: "2.0.11" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.14" path_provider_ios: dependency: transitive description: name: path_provider_ios - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.9" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.7" pedantic: dependency: transitive description: name: pedantic - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.11.1" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.4" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" qr_code_scanner: @@ -775,98 +782,98 @@ packages: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" screen_retriever: dependency: transitive description: name: screen_retriever - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" settings_ui: dependency: "direct main" description: name: settings_ui - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" shortid: dependency: transitive description: name: shortid - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" sky_engine: @@ -878,259 +885,259 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.9" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" toggle_switch: dependency: "direct main" description: name: toggle_switch - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" transparent_image: dependency: transitive description: name: transparent_image - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" tray_manager: dependency: "direct main" description: name: tray_manager - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.7" tuple: dependency: "direct main" description: name: tuple - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.1.2" + version: "6.1.3" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.5" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.11" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" uuid: dependency: transitive description: name: uuid - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.6" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" wakelock: dependency: "direct main" description: name: wakelock - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.5.6" wakelock_macos: dependency: transitive description: name: wakelock_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.0" wakelock_web: dependency: transitive description: name: wakelock_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.0" wakelock_windows: dependency: transitive description: name: wakelock_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.1" window_manager: dependency: "direct main" description: name: window_manager - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.5" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0+1" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.0" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" zxing2: dependency: "direct main" description: name: zxing2 - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.0" sdks: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 5ff7cc6a0..65bd819ff 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: bitsdojo_window: ^0.1.2 freezed_annotation: ^2.0.3 tray_manager: 0.1.7 + get: ^4.6.5 dev_dependencies: flutter_launcher_icons: ^0.9.1