diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 9367d483e..8d4492f97 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 9.0
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 7e14f0968..953dad47b 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -166,7 +166,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140cfd..3db53b6e1 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
{
final _idController = TextEditingController();
var _updateUrl = '';
+ var _menuPos;
@override
void initState() {
super.initState();
nowCtx = context;
- if (Platform.isAndroid) {
+ if (isAndroid) {
Timer(Duration(seconds: 5), () {
_updateUrl = FFI.getByName('software_update_url');
if (_updateUrl.isNotEmpty) setState(() {});
@@ -45,37 +43,45 @@ class _HomePageState extends State {
appBar: AppBar(
centerTitle: true,
actions: [
- IconButton(
- icon: Icon(Icons.more_vert),
- onPressed: () {
- () async {
- var value = await showMenu(
- context: context,
- position: RelativeRect.fromLTRB(3000, 70, 3000, 70),
- items: [
- PopupMenuItem(
- child: Text(translate('ID Server')),
- value: 'id_server'),
- Platform.isAndroid
- ? PopupMenuItem(
- child: Text(translate('Share My Screen')),
- value: 'server')
- : null,
- PopupMenuItem(
- child: Text(translate('About') + ' RustDesk'),
- value: 'about'),
- ],
- elevation: 8,
- );
- if (value == 'id_server') {
- showServer(context);
- } else if (value == 'server') {
- Navigator.pushNamed(context, "server_page");
- } else if (value == 'about') {
- showAbout(context);
- }
- }();
- })
+ Ink(
+ child: InkWell(
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Icon(Icons.more_vert)),
+ onTapDown: (e) {
+ var x = e.globalPosition.dx;
+ var y = e.globalPosition.dy;
+ this._menuPos = RelativeRect.fromLTRB(x, y, x, y);
+ },
+ onTap: () {
+ () async {
+ var value = await showMenu(
+ context: context,
+ position: this._menuPos,
+ items: [
+ PopupMenuItem(
+ child: Text(translate('ID Server')),
+ value: 'server'),
+ isAndroid
+ ? PopupMenuItem(
+ child: Text(translate('Share My Screen')),
+ value: 'server')
+ : null,
+ PopupMenuItem(
+ child: Text(translate('About') + ' RustDesk'),
+ value: 'about'),
+ ],
+ elevation: 8,
+ );
+ if (value == 'server') {
+ showServer(context);
+ } else if (value == 'server') {
+ Navigator.pushNamed(context, "server_page");
+ } else if (value == 'about') {
+ showAbout(context);
+ }
+ }();
+ }))
],
title: Text(widget.title),
),
@@ -104,6 +110,7 @@ class _HomePageState extends State {
color: Colors.white,
fontWeight: FontWeight.bold)))),
getSearchBarUI(),
+ Container(height: 12),
getPeers(),
]),
));
@@ -136,13 +143,13 @@ class _HomePageState extends State {
if (!FFI.ffiModel.initialized) {
return Container();
}
- return Padding(
+ var w = Padding(
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0.0),
child: Container(
height: 84,
child: Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8),
- child: Container(
+ child: Ink(
decoration: BoxDecoration(
color: MyTheme.white,
borderRadius: const BorderRadius.only(
@@ -166,7 +173,7 @@ class _HomePageState extends State {
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
fontSize: 30,
- color: Color(0xFF00B6F0),
+ color: MyTheme.idColor,
),
decoration: InputDecoration(
labelText: translate('Remote ID'),
@@ -175,13 +182,13 @@ class _HomePageState extends State {
helperStyle: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
- color: Color(0xFFB9BABC),
+ color: MyTheme.darkGray,
),
labelStyle: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
letterSpacing: 0.2,
- color: Color(0xFFB9BABC),
+ color: MyTheme.darkGray,
),
),
autofocus: _idController.text.isEmpty,
@@ -194,7 +201,7 @@ class _HomePageState extends State {
height: 60,
child: IconButton(
icon: Icon(Icons.arrow_forward,
- color: Color(0xFFB9BABC), size: 45),
+ color: MyTheme.darkGray, size: 45),
onPressed: onConnect,
autofocus: _idController.text.isNotEmpty,
),
@@ -205,6 +212,8 @@ class _HomePageState extends State {
),
),
);
+ return Center(
+ child: Container(constraints: BoxConstraints(maxWidth: 600), child: w));
}
@override
@@ -225,46 +234,75 @@ class _HomePageState extends State {
if (!FFI.ffiModel.initialized) {
return Container();
}
+ final size = MediaQuery.of(context).size;
+ final space = 8.0;
+ var width = size.width - 2 * space;
+ final minWidth = 320.0;
+ if (size.width > minWidth + 2 * space) {
+ final n = (size.width / (minWidth + 2 * space)).floor();
+ width = size.width / n - 2 * space;
+ }
final cards = [];
var peers = FFI.peers();
peers.forEach((p) {
- cards.add(Padding(
- padding: EdgeInsets.symmetric(horizontal: 12),
+ cards.add(Container(
+ width: width,
child: Card(
child: GestureDetector(
- onTap: () => connect('${p.id}'),
+ onTap: () => {
+ if (!isDesktop) {connect('${p.id}')}
+ },
+ onDoubleTap: () => {
+ if (isDesktop) {connect('${p.id}')}
+ },
onLongPressStart: (details) {
var x = details.globalPosition.dx;
var y = details.globalPosition.dy;
- () async {
- var value = await showMenu(
- context: context,
- position: RelativeRect.fromLTRB(x, y, x, y),
- items: [
- PopupMenuItem(
- child: Text(translate('Remove')),
- value: 'remove'),
- ],
- elevation: 8,
- );
- if (value == 'remove') {
- setState(() => FFI.setByName('remove', '${p.id}'));
- () async {
- removePreference(p.id);
- }();
- }
- }();
+ this._menuPos = RelativeRect.fromLTRB(x, y, x, y);
+ this.showPeerMenu(context, p.id);
},
child: ListTile(
+ contentPadding: const EdgeInsets.only(left: 12),
subtitle: Text('${p.username}@${p.hostname}'),
title: Text('${p.id}'),
leading: Container(
padding: const EdgeInsets.all(6),
child: getPlatformImage('${p.platform}'),
color: str2color('${p.id}${p.platform}', 0x7f)),
+ trailing: InkWell(
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Icon(Icons.more_vert)),
+ onTapDown: (e) {
+ var x = e.globalPosition.dx;
+ var y = e.globalPosition.dy;
+ this._menuPos = RelativeRect.fromLTRB(x, y, x, y);
+ },
+ onDoubleTap: () {},
+ onTap: () {
+ showPeerMenu(context, p.id);
+ }),
)))));
});
- return Wrap(children: cards);
+ return Wrap(children: cards, spacing: space, runSpacing: space);
+ }
+
+ void showPeerMenu(BuildContext context, String id) async {
+ var value = await showMenu(
+ context: context,
+ position: this._menuPos,
+ items: [
+ PopupMenuItem(
+ child: Text(translate('Remove')), value: 'remove'),
+ ],
+ elevation: 8,
+ );
+ if (value == 'remove') {
+ setState(() => FFI.setByName('remove', '$id'));
+ () async {
+ removePreference(id);
+ }();
+ }
}
}
@@ -332,13 +370,13 @@ void showServer(BuildContext context) {
formKey.currentState.save();
if (id != id0)
FFI.setByName('option',
- '{"name": "custom-rendezvous-server", "value": "${id}"}');
+ '{"name": "custom-rendezvous-server", "value": "$id"}');
if (relay != relay0)
FFI.setByName('option',
- '{"name": "relay-server", "value": "${relay}"}');
+ '{"name": "relay-server", "value": "$relay"}');
if (key != key0)
FFI.setByName(
- 'option', '{"name": "key", "value": "${key}"}');
+ 'option', '{"name": "key", "value": "$key"}');
Navigator.pop(context);
}
},
@@ -349,13 +387,13 @@ void showServer(BuildContext context) {
}
Future showAbout(BuildContext context) async {
- PackageInfo packageInfo = await PackageInfo.fromPlatform();
+ var version = await FFI.getVersion();
showAlertDialog(
context,
(setState) => Tuple3(
null,
Wrap(direction: Axis.vertical, spacing: 12, children: [
- Text('Version: ${packageInfo.version}'),
+ Text('Version: $version'),
InkWell(
onTap: () async {
const url = 'https://rustdesk.com/';
diff --git a/lib/main.dart b/lib/main.dart
index ba8ffcb27..00b297b13 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,16 +1,11 @@
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_hbb/server_page.dart';
import 'package:provider/provider.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:firebase_core/firebase_core.dart';
-import 'common.dart';
import 'model.dart';
import 'home_page.dart';
-
-const toAndroidChannel = MethodChannel("mChannel");
-BuildContext nowCtx;
+import 'server_page.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -20,7 +15,6 @@ Future main() async {
}
class App extends StatelessWidget {
-
@override
Widget build(BuildContext context) {
final analytics = FirebaseAnalytics();
@@ -40,7 +34,7 @@ class App extends StatelessWidget {
),
home: HomePage(title: 'RustDesk'),
routes: {
- "server_page":(context) => ServerPage(),
+ "server_page": (context) => ServerPage(),
},
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
diff --git a/lib/model.dart b/lib/model.dart
index 78041ea1d..52d4506e6 100644
--- a/lib/model.dart
+++ b/lib/model.dart
@@ -1,13 +1,6 @@
-import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';
-import 'package:flutter/gestures.dart';
-import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
-import 'package:device_info/device_info.dart';
-import 'package:external_path/external_path.dart';
-import 'dart:io';
import 'dart:math';
-import 'dart:ffi';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui' as ui;
@@ -15,17 +8,7 @@ import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
import 'dart:async';
import 'common.dart';
-
-class RgbaFrame extends Struct {
- @Uint32()
- int len;
- Pointer data;
-}
-
-typedef F2 = Pointer Function(Pointer, Pointer);
-typedef F3 = void Function(Pointer, Pointer);
-typedef F4 = void Function(Pointer);
-typedef F5 = Pointer Function();
+import 'native_model.dart' if (dart.library.html) 'web_model.dart';
class FfiModel with ChangeNotifier {
PeerInfo _pi;
@@ -33,6 +16,7 @@ class FfiModel with ChangeNotifier {
var _decoding = false;
bool _waitForImage;
bool _initialized = false;
+ var _inputBlocked = false;
final _permissions = Map();
bool _secure;
bool _direct;
@@ -48,12 +32,17 @@ class FfiModel with ChangeNotifier {
get direct => _direct;
get pi => _pi;
+ get inputBlocked => _inputBlocked;
+
+ set inputBlocked(v) {
+ _inputBlocked = v;
+ }
FfiModel() {
Translator.call = translate;
clear();
() async {
- await FFI.init();
+ await PlatformFFI.init();
_initialized = true;
print("FFI initialized");
notifyListeners();
@@ -66,6 +55,7 @@ class FfiModel with ChangeNotifier {
_permissions[k] = v == 'true';
});
print('$_permissions');
+ notifyListeners();
}
bool keyboard() => _permissions['keyboard'] != false;
@@ -76,6 +66,7 @@ class FfiModel with ChangeNotifier {
_waitForImage = false;
_secure = null;
_direct = null;
+ _inputBlocked = false;
clearPermissions();
}
@@ -101,6 +92,7 @@ class FfiModel with ChangeNotifier {
}
void clearPermissions() {
+ _inputBlocked = false;
_permissions.clear();
}
@@ -140,7 +132,7 @@ class FfiModel with ChangeNotifier {
}
if (pos != null) FFI.cursorModel.updateCursorPosition(pos);
if (!_decoding) {
- var rgba = FFI.getRgba();
+ var rgba = PlatformFFI.getRgba();
if (rgba != null) {
if (_waitForImage) {
_waitForImage = false;
@@ -151,7 +143,7 @@ class FfiModel with ChangeNotifier {
ui.decodeImageFromPixels(
rgba, _display.width, _display.height, ui.PixelFormat.bgra8888,
(image) {
- FFI.clearRgbaFrame();
+ PlatformFFI.clearRgbaFrame();
_decoding = false;
if (FFI.id != pid) return;
try {
@@ -198,7 +190,6 @@ class FfiModel with ChangeNotifier {
}
if (_pi.currentDisplay < _pi.displays.length) {
_display = _pi.displays[_pi.currentDisplay];
- initializeCursorAndCanvas();
}
if (displays.length > 0) {
showLoading(translate('Connected, waiting for image...'), context);
@@ -215,10 +206,15 @@ class ImageModel with ChangeNotifier {
void update(ui.Image image) {
if (_image == null && image != null) {
- 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);
+ if (isDesktop) {
+ FFI.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);
+ }
+ initializeCursorAndCanvas();
}
_image = image;
if (image != null) notifyListeners();
@@ -256,6 +252,29 @@ class CanvasModel with ChangeNotifier {
double get scale => _scale;
+ void updateViewStyle() {
+ final s = FFI.getByName('peer_option', 'view-style');
+ final size = MediaQueryData.fromWindow(ui.window).size;
+ final s1 = size.width / FFI.ffiModel.display.width;
+ final s2 = size.height / FFI.ffiModel.display.height;
+ if (s == 'shrink') {
+ final s = s1 < s2 ? s1 : s2;
+ if (s < 1) {
+ _scale = s;
+ }
+ } else if (s == 'stretch') {
+ final s = s1 > s2 ? s1 : s2;
+ if (s > 1) {
+ _scale = s;
+ }
+ } else {
+ _scale = 1;
+ }
+ _x = (size.width - FFI.ffiModel.display.width * _scale) / 2;
+ _y = (size.height - FFI.ffiModel.display.height * _scale) / 2;
+ notifyListeners();
+ }
+
void update(double x, double y, double scale) {
_x = x;
_y = y;
@@ -263,6 +282,26 @@ class CanvasModel with ChangeNotifier {
notifyListeners();
}
+ 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;
+ var dxOffset = 0;
+ var dyOffset = 0;
+ if (dw > size.width) {
+ dxOffset = (x - dw * (x / size.width) - _x).toInt();
+ }
+ if (dh > size.height) {
+ dyOffset = (y - dh * (y / size.height) - _y).toInt();
+ }
+ _x += dxOffset;
+ _y += dyOffset;
+ if (dxOffset != 0 || dyOffset != 0) {
+ notifyListeners();
+ }
+ FFI.cursorModel.move(x, y);
+ }
+
set scale(v) {
_scale = v;
notifyListeners();
@@ -274,8 +313,12 @@ class CanvasModel with ChangeNotifier {
}
void resetOffset() {
- _x = 0;
- _y = 0;
+ if (isDesktop) {
+ updateViewStyle();
+ } else {
+ _x = 0;
+ _y = 0;
+ }
notifyListeners();
}
@@ -356,13 +399,17 @@ class CursorModel with ChangeNotifier {
}
void touch(double x, double y, bool right) {
+ move(x, y);
+ FFI.moveMouse(_x, _y);
+ FFI.tap(right);
+ }
+
+ void move(double x, double y) {
final scale = FFI.canvasModel.scale;
final xoffset = FFI.canvasModel.x;
final yoffset = FFI.canvasModel.y;
_x = (x - xoffset) / scale + _displayOriginX;
_y = (y - yoffset) / scale + _displayOriginY;
- FFI.moveMouse(_x, _y);
- FFI.tap(right);
notifyListeners();
}
@@ -558,7 +605,7 @@ class ServerModel with ChangeNotifier {
bool get inputOk => _inputOk;
// bool get needServerOpen => _needServerOpen;
-
+
bool get isPeerStart => _isPeerStart;
bool get isFileTransfer => _isFileTransfer;
@@ -625,13 +672,6 @@ class ServerModel with ChangeNotifier {
class FFI {
static String id = "";
- static String _dir = "";
- static String _homeDir = "";
- static F2 _getByName;
- static F3 _setByName;
- static F4 _freeRgba;
- static F5 _getRgba;
- static Pointer _lastRgbaFrame;
static var shift = false;
static var ctrl = false;
static var alt = false;
@@ -640,6 +680,7 @@ class FFI {
static final ffiModel = FfiModel();
static final cursorModel = CursorModel();
static final canvasModel = CanvasModel();
+ static final serverModel = ServerModel();
static String getId() {
return getByName('remote_id');
@@ -682,7 +723,8 @@ class FFI {
static void inputKey(String name) {
if (!ffiModel.keyboard()) return;
- setByName('input_key', json.encode(modify({'name': name})));
+ setByName(
+ 'input_key', json.encode(modify({'name': name, 'press': 'true'})));
}
static void moveMouse(double x, double y) {
@@ -711,19 +753,6 @@ class FFI {
FFI.id = id;
}
- static void clearRgbaFrame() {
- if (_lastRgbaFrame != null && _lastRgbaFrame != nullptr)
- _freeRgba(_lastRgbaFrame);
- }
-
- static Uint8List getRgba() {
- if (_getRgba == null) return null;
- _lastRgbaFrame = _getRgba();
- if (_lastRgbaFrame == null || _lastRgbaFrame == nullptr) return null;
- final ref = _lastRgbaFrame.ref;
- return Uint8List.sublistView(ref.data.asTypedList(ref.len));
- }
-
static Map popEvent() {
var s = getByName('event');
if (s == '') return null;
@@ -746,8 +775,10 @@ class FFI {
}
static void close() {
- savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x,
- canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
+ if (FFI.imageModel.image != null && !isDesktop) {
+ savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x,
+ canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
+ }
id = "";
setByName('close', '');
imageModel.update(null);
@@ -757,63 +788,86 @@ class FFI {
resetModifiers();
}
- static void setByName(String name, [String value = '']) {
- if (_setByName == null) return;
- var a = name.toNativeUtf8();
- var b = value.toNativeUtf8();
- _setByName(a, b);
- calloc.free(a);
- calloc.free(b);
- }
-
static String getByName(String name, [String arg = '']) {
- if (_getByName == null) return '';
- var a = name.toNativeUtf8();
- var b = arg.toNativeUtf8();
- var p = _getByName(a, b);
- assert(p != nullptr && p != null);
- var res = p.toDartString();
- calloc.free(p);
- calloc.free(a);
- calloc.free(b);
- return res;
+ return PlatformFFI.getByName(name, arg);
}
- static Future init() async {
- final dylib = Platform.isAndroid
- ? DynamicLibrary.open('librustdesk.so')
- : DynamicLibrary.process();
- print('initializing FFI');
- try {
- _getByName = dylib.lookupFunction('get_by_name');
- _setByName =
- dylib.lookupFunction, Pointer), F3>(
- 'set_by_name');
- _freeRgba = dylib
- .lookupFunction), F4>('free_rgba');
- _getRgba = dylib.lookupFunction('get_rgba');
- _dir = (await getApplicationDocumentsDirectory()).path;
- _homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
- String id = 'NA';
- String name = 'Flutter';
- DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
- if (Platform.isAndroid) {
- AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
- name = '${androidInfo.brand}-${androidInfo.model}';
- id = androidInfo.id.hashCode.toString();
- } else {
- IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
- name = iosInfo.utsname.machine;
- id = iosInfo.identifierForVendor.hashCode.toString();
- }
- debugPrint("info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir");
- setByName('info1', id);
- setByName('info2', name);
- setByName('home_dir',_homeDir);
- setByName('init', _dir);
- } catch (e) {
- print(e);
+ static void setByName(String name, [String value = '']) {
+ PlatformFFI.setByName(name, value);
+ }
+
+ static Future getVersion() async {
+ return await PlatformFFI.getVersion();
+ }
+
+ static handleMouse(Map evt) {
+ var type = '';
+ var isMove = false;
+ switch (evt['type']) {
+ case 'mousedown':
+ type = 'down';
+ break;
+ case 'mouseup':
+ type = 'up';
+ break;
+ case 'mousemove':
+ isMove = true;
+ break;
+ default:
+ return;
}
+ evt['type'] = type;
+ var x = evt['x'];
+ var y = evt['y'];
+ if (isMove) {
+ FFI.canvasModel.moveDesktopMouse(x, y);
+ }
+ final d = FFI.ffiModel.display;
+ x -= FFI.canvasModel.x;
+ y -= FFI.canvasModel.y;
+ if (!isMove && (x < 0 || x > d.width || y < 0 || y > d.height)) {
+ return;
+ }
+ x /= FFI.canvasModel.scale;
+ y /= FFI.canvasModel.scale;
+ x += d.x;
+ y += d.y;
+ if (type != '') {
+ x = 0;
+ y = 0;
+ }
+ evt['x'] = '$x';
+ evt['y'] = '$y';
+ var buttons = '';
+ switch (evt['buttons']) {
+ case 1:
+ buttons = 'left';
+ break;
+ case 2:
+ buttons = 'right';
+ break;
+ case 4:
+ buttons = 'wheel';
+ break;
+ }
+ evt['buttons'] = buttons;
+ setByName('send_mouse', json.encode(evt));
+ }
+
+ static listenToMouse(bool yesOrNo) {
+ if (yesOrNo) {
+ PlatformFFI.startDesktopWebListener(handleMouse);
+ } else {
+ PlatformFFI.stopDesktopWebListener();
+ }
+ }
+
+ static void setMethodCallHandler(FMethod callback) {
+ PlatformFFI.setMethodCallHandler(callback);
+ }
+
+ static Future invokeMethod(String method) async {
+ return await PlatformFFI.invokeMethod(method);
}
}
@@ -861,6 +915,7 @@ void savePreference(String id, double xCursor, double yCursor, double xCanvas,
}
Future