From a15cd62fd4f92c06b655567bebe44428433cd3ec Mon Sep 17 00:00:00 2001 From: fufesou <fufesou@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:08:55 +0800 Subject: [PATCH] Refact. Flutter web, mid commit (#7482) Signed-off-by: fufesou <shuanglongchen@yeah.net> --- flutter/lib/common.dart | 31 +++--- flutter/lib/main.dart | 8 +- flutter/lib/mobile/pages/connection_page.dart | 2 +- flutter/lib/models/web_model.dart | 15 ++- flutter/lib/web/bridge.dart | 98 ++++++++++++------- flutter/web/js/package.json | 6 +- flutter/web/js/src/connection.ts | 16 ++- flutter/web/js/src/globals.js | 79 ++++++++++++++- 8 files changed, 187 insertions(+), 68 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 611cdecb3..21a1c3b5e 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'dart:math'; import 'package:back_button_interceptor/back_button_interceptor.dart'; @@ -1544,7 +1543,7 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async { late Size sz; late bool isMaximized; bool isFullscreen = stateGlobal.fullscreen.isTrue || - (Platform.isMacOS && stateGlobal.closeOnFullscreen == true); + (isMacOS && stateGlobal.closeOnFullscreen == true); setFrameIfMaximized() { if (isMaximized) { final pos = bind.getLocalFlutterOption(k: windowFramePrefix + type.name); @@ -1583,7 +1582,7 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async { setFrameIfMaximized(); break; } - if (Platform.isWindows) { + if (isWindows) { const kMinOffset = -10000; const kMaxOffset = 10000; if (position.dx < kMinOffset || @@ -1854,7 +1853,7 @@ Future<bool> restoreWindowPosition(WindowType type, /// initUniLinks should only be used on macos/windows. /// we use dbus for linux currently. Future<bool> initUniLinks() async { - if (Platform.isLinux) { + if (isLinux) { return false; } // check cold boot @@ -1876,7 +1875,7 @@ Future<bool> initUniLinks() async { /// /// Returns a [StreamSubscription] which can listen the uni links. StreamSubscription? listenUniLinks({handleByFlutter = true}) { - if (Platform.isLinux) { + if (isLinux) { return null; } @@ -2024,7 +2023,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) { command = '--connect'; id = uri.path.substring("/new/".length); } else if (uri.authority == "config") { - if (Platform.isAndroid || Platform.isIOS) { + if (isAndroid || isIOS) { final config = uri.path.substring("/".length); // add a timer to make showToast work Timer(Duration(seconds: 1), () { @@ -2033,7 +2032,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) { } return null; } else if (uri.authority == "password") { - if (Platform.isAndroid || Platform.isIOS) { + if (isAndroid || isIOS) { final password = uri.path.substring("/".length); if (password.isNotEmpty) { Timer(Duration(seconds: 1), () async { @@ -2253,7 +2252,7 @@ Future<void> reloadAllWindows() async { /// [Note] /// Portable build is only available on Windows. bool isRunningInPortableMode() { - if (!Platform.isWindows) { + if (!isWindows) { return false; } return bool.hasEnvironment(kEnvPortableExecutable); @@ -2266,7 +2265,7 @@ Future<void> onActiveWindowChanged() async { if (rustDeskWinManager.getActiveWindows().isEmpty) { // close all sub windows try { - if (Platform.isLinux) { + if (isLinux) { await Future.wait([ saveWindowPosition(WindowType.Main), rustDeskWinManager.closeAllSubWindows() @@ -2280,7 +2279,7 @@ Future<void> onActiveWindowChanged() async { debugPrint("Start closing RustDesk..."); await windowManager.setPreventClose(false); await windowManager.close(); - if (Platform.isMacOS) { + if (isMacOS) { RdPlatformChannel.instance.terminate(); } } @@ -2296,7 +2295,7 @@ Timer periodic_immediate(Duration duration, Future<void> Function() callback) { /// return a human readable windows version WindowsTarget getWindowsTarget(int buildNumber) { - if (!Platform.isWindows) { + if (!isWindows) { return WindowsTarget.naw; } if (buildNumber >= 22000) { @@ -2330,7 +2329,7 @@ int getWindowsTargetBuildNumber() { /// [Conditions] /// - Windows 7, window will overflow when we use frameless ui. bool get kUseCompatibleUiMode => - Platform.isWindows && + isWindows && const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion); class ServerConfig { @@ -2460,7 +2459,7 @@ Future<void> updateSystemWindowTheme() async { // Set system window theme for macOS. final userPreference = MyTheme.getThemeModePreference(); if (userPreference != ThemeMode.system) { - if (Platform.isMacOS) { + if (isMacOS) { await RdPlatformChannel.instance.changeSystemWindowTheme( userPreference == ThemeMode.light ? SystemWindowTheme.light @@ -2548,7 +2547,7 @@ void onCopyFingerprint(String value) { Future<bool> callMainCheckSuperUserPermission() async { bool checked = await bind.mainCheckSuperUserPermission(); - if (Platform.isMacOS) { + if (isMacOS) { await windowManager.show(); } return checked; @@ -2556,7 +2555,7 @@ Future<bool> callMainCheckSuperUserPermission() async { Future<void> start_service(bool is_start) async { bool checked = !bind.mainIsInstalled() || - !Platform.isMacOS || + !isMacOS || await callMainCheckSuperUserPermission(); if (checked) { bind.mainSetOption(key: "stop-service", value: is_start ? "" : "Y"); @@ -3114,7 +3113,7 @@ Widget loadIcon(double size) { var imcomingOnlyHomeSize = Size(280, 300); Size getIncomingOnlyHomeSize() { - final magicWidth = Platform.isWindows ? 11.0 : 2.0; + final magicWidth = isWindows ? 11.0 : 2.0; final magicHeight = 10.0; return imcomingOnlyHomeSize + Offset(magicWidth, kDesktopRemoteTabBarHeight + magicHeight); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index e1632184e..e7e5aee22 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -125,6 +125,10 @@ void runMainApp(bool startService) async { await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); gFFI.userModel.refreshCurrentUser(); runApp(App()); + if (isWeb) { + // Web does not support window manager. + return; + } // Set window option. WindowOptions windowOptions = getHiddenTitleBarWindowOptions(); windowManager.waitUntilReadyToShow(windowOptions, () async { @@ -150,11 +154,11 @@ void runMainApp(bool startService) async { void runMobileApp() async { await initEnv(kAppTypeMain); if (isAndroid) androidChannelInit(); - platformFFI.syncAndroidServiceAppDirConfigPath(); + if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath(); await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); gFFI.userModel.refreshCurrentUser(); runApp(App()); - await initUniLinks(); + if (!isWeb) await initUniLinks(); } void runMultiWindow( diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 09c1e7ea6..d2a3c0347 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -53,7 +53,7 @@ class _ConnectionPageState extends State<ConnectionPage> { @override void initState() { super.initState(); - _uniLinksSubscription = listenUniLinks(); + if (!isWeb) _uniLinksSubscription = listenUniLinks(); if (_idController.text.isEmpty) { () async { final lastRemoteId = await bind.mainGetLastRemoteId(); diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index d8c8491c9..ef8bfff21 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -18,7 +18,7 @@ typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt); class PlatformFFI { final _eventHandlers = <String, Map<String, HandleEvent>>{}; - late RustdeskImpl _ffiBind; + final RustdeskImpl _ffiBind = RustdeskImpl(); static String getByName(String name, [String arg = '']) { return context.callMethod('getByName', [name, arg]); @@ -101,6 +101,15 @@ class PlatformFFI { isWebDesktop = !context.callMethod('isMobile'); context.callMethod('init'); version = getByName('version'); + + context['onRegisteredEvent'] = (String message) { + try { + Map<String, dynamic> event = json.decode(message); + tryHandle(event); + } catch (e) { + print('json.decode fail(): $e'); + } + }; } void setEventCallback(void Function(Map<String, dynamic>) fun) { @@ -145,7 +154,5 @@ class PlatformFFI { } // just for compilation - void syncAndroidServiceAppDirConfigPath() { - throw UnimplementedError(); - } + void syncAndroidServiceAppDirConfigPath() {} } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 8c55c1d7e..5c6dc8c98 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:js' as js; +import 'dart:convert'; import 'dart:typed_data'; import 'package:uuid/uuid.dart'; @@ -176,29 +178,35 @@ class RustdeskImpl { Future<String?> sessionGetFlutterOptionByPeerId( {required String id, required String k, dynamic hint}) { - throw UnimplementedError(); + return Future.value(null); } int getNextTextureKey({dynamic hint}) { - throw UnimplementedError(); + return 0; } String getLocalFlutterOption({required String k, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['option:flutter:local', k]); } Future<void> setLocalFlutterOption( {required String k, required String v, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'option:flutter:local', + jsonEncode({'name': k, 'value': v}) + ])); } String getLocalKbLayoutType({dynamic hint}) { - throw UnimplementedError(); + throw js.context.callMethod('getByName', ['option:local', 'kb_layout']); } Future<void> setLocalKbLayoutType( {required String kbLayoutType, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'option:local', + jsonEncode({'name': 'kb_layout', 'value': kbLayoutType}) + ])); } Future<String?> sessionGetViewStyle( @@ -538,11 +546,11 @@ class RustdeskImpl { } Future<String> mainGetOption({required String key, dynamic hint}) { - throw UnimplementedError(); + return Future.value(mainGetOptionSync(key: key)); } String mainGetOptionSync({required String key, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['option', key]); } Future<String> mainGetError({dynamic hint}) { @@ -555,7 +563,10 @@ class RustdeskImpl { Future<void> mainSetOption( {required String key, required String value, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('setByName', [ + 'option', + jsonEncode({'name': key, 'value': value}) + ]); } Future<String> mainGetOptions({dynamic hint}) { @@ -623,11 +634,13 @@ class RustdeskImpl { } Future<String> mainGetConnectStatus({dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('getByName', ["get_conn_status"])); } Future<void> mainCheckConnectStatus({dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('setByName', ["check_conn_status"])); } Future<bool> mainIsUsingPublicServer({dynamic hint}) { @@ -635,7 +648,7 @@ class RustdeskImpl { } Future<void> mainDiscover({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['discover'])); } Future<String> mainGetApiServer({dynamic hint}) { @@ -651,7 +664,7 @@ class RustdeskImpl { } String mainGetLocalOption({required String key, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['option:local', key]); } String mainGetEnv({required String key, dynamic hint}) { @@ -660,7 +673,10 @@ class RustdeskImpl { Future<void> mainSetLocalOption( {required String key, required String value, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'option:local', + jsonEncode({'name': key, 'value': value}) + ])); } String mainGetInputSource({dynamic hint}) { @@ -741,15 +757,16 @@ class RustdeskImpl { } Future<void> mainLoadRecentPeers({dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('getByName', ['load_recent_peers'])); } String mainLoadRecentPeersSync({dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['load_recent_peers_sync']); } String mainLoadLanPeersSync({dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['load_lan_peers_sync']); } Future<String> mainLoadRecentPeersForAb( @@ -758,15 +775,16 @@ class RustdeskImpl { } Future<void> mainLoadFavPeers({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['load_fav_peers'])); } Future<void> mainLoadLanPeers({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['load_lan_peers'])); } Future<void> mainRemoveDiscovered({required String id, dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('getByName', ['remove_discovered'])); } Future<void> mainChangeTheme({required String dark, dynamic hint}) { @@ -840,7 +858,8 @@ class RustdeskImpl { } Future<String> mainGetLastRemoteId({dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('getByName', ['option', 'last_remote_id'])); } Future<String> mainGetSoftwareUpdateUrl({dynamic hint}) { @@ -848,7 +867,7 @@ class RustdeskImpl { } Future<String> mainGetHomeDir({dynamic hint}) { - throw UnimplementedError(); + return Future.value(''); } Future<String> mainGetLangs({dynamic hint}) { @@ -856,11 +875,11 @@ class RustdeskImpl { } Future<String> mainGetTemporaryPassword({dynamic hint}) { - throw UnimplementedError(); + return Future.value(''); } Future<String> mainGetPermanentPassword({dynamic hint}) { - throw UnimplementedError(); + return Future.value(''); } Future<String> mainGetFingerprint({dynamic hint}) { @@ -880,7 +899,7 @@ class RustdeskImpl { } Future<void> mainInit({required String appDir, dynamic hint}) { - throw UnimplementedError(); + return Future.value(); } Future<void> mainDeviceId({required String id, dynamic hint}) { @@ -1093,7 +1112,10 @@ class RustdeskImpl { String translate( {required String name, required String locale, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', [ + 'translate', + jsonEncode({'locale': locale, 'text': name}) + ]); } int sessionGetRgbaSize( @@ -1131,7 +1153,7 @@ class RustdeskImpl { } Future<bool> optionSynced({dynamic hint}) { - throw UnimplementedError(); + return Future.value(true); } bool mainIsInstalled({dynamic hint}) { @@ -1257,43 +1279,45 @@ class RustdeskImpl { } Future<void> mainTestWallpaper({required int second, dynamic hint}) { - throw UnimplementedError(); + // TODO: implement mainTestWallpaper + return Future.value(); } Future<bool> mainSupportRemoveWallpaper({dynamic hint}) { - throw UnimplementedError(); + // TODO: implement mainSupportRemoveWallpaper + return Future.value(false); } bool isIncomingOnly({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isOutgoingOnly({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isCustomClient({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isDisableSettings({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isDisableAb({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isDisableAccount({dynamic hint}) { - throw UnimplementedError(); + return false; } bool isDisableInstallation({dynamic hint}) { - throw UnimplementedError(); + return false; } Future<bool> isPresetPassword({dynamic hint}) { - throw UnimplementedError(); + return Future.value(false); } Future<void> sendUrlScheme({required String url, dynamic hint}) { diff --git a/flutter/web/js/package.json b/flutter/web/js/package.json index 15e0e75b8..436806e8d 100644 --- a/flutter/web/js/package.json +++ b/flutter/web/js/package.json @@ -3,19 +3,19 @@ "version": "1.0.0", "scripts": { "dev": "vite", - "build": "./gen_js_from_hbb.py > src/gen_js_from_hbb.ts && ./ts_proto.py && tsc && vite build", + "build": "python ./gen_js_from_hbb.py > src/gen_js_from_hbb.ts && python ./ts_proto.py && tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^4.4.4", - "vite": "^2.7.2" + "vite": "2.8" }, "dependencies": { "fast-sha256": "^1.3.0", "libsodium": "^0.7.9", "libsodium-wrappers": "^0.7.9", "pcm-player": "^0.0.11", - "ts-proto": "^1.101.0", + "ts-proto": "^1.169.1", "wasm-feature-detect": "^1.2.11", "zstddec": "^0.0.2" } diff --git a/flutter/web/js/src/connection.ts b/flutter/web/js/src/connection.ts index b0c479c90..b6707b4d6 100644 --- a/flutter/web/js/src/connection.ts +++ b/flutter/web/js/src/connection.ts @@ -443,9 +443,9 @@ export default class Connection { if (this._videoTestSpeed[0] >= 30) { console.log( "video decoder: " + - parseInt( - "" + this._videoTestSpeed[1] / this._videoTestSpeed[0] - ) + parseInt( + "" + this._videoTestSpeed[1] / this._videoTestSpeed[0] + ) ); this._videoTestSpeed = [0, 0]; } @@ -456,6 +456,7 @@ export default class Connection { } handlePeerInfo(pi: message.PeerInfo) { + localStorage.setItem('last_remote_id', this._id); this._peerInfo = pi; if (pi.displays.length == 0) { this.msgbox("error", "Remote Error", "No Display"); @@ -540,6 +541,15 @@ export default class Connection { return this._options[name]; } + // TODO: + getStatus(): String { + return JSON.stringify({status_num: 10}); + } + + // TODO: + checkConnStatus() { + } + setOption(name: string, value: any) { if (value == undefined) { delete this._options[name]; diff --git a/flutter/web/js/src/globals.js b/flutter/web/js/src/globals.js index 953add18d..992a7f1a6 100644 --- a/flutter/web/js/src/globals.js +++ b/flutter/web/js/src/globals.js @@ -257,6 +257,14 @@ window.setByName = (name, value) => { value = JSON.parse(value); localStorage.setItem(value.name, value.value); break; + case 'option:local': + value = JSON.parse(value); + localStorage.setItem('option:local:' + value.name, value.value); + break; + case 'option:flutter:local': + value = JSON.parse(value); + localStorage.setItem('option:flutter:local:' + value.name, value.value); + break; case 'peer_option': value = JSON.parse(value); curConn.setOption(value.name, value.value); @@ -264,6 +272,15 @@ window.setByName = (name, value) => { case 'input_os_password': curConn.inputOsPassword(value); break; + case 'check_conn_status': + curConn.checkConnStatus(); + break; + case 'remove_discovered': + removeDiscovered(value); + break; + case 'discover': + // TODO: discover + break; default: break; } @@ -300,6 +317,10 @@ function _getByName(name, arg) { return curConn.getOption(arg) || false; case 'option': return localStorage.getItem(arg); + case 'option:local': + return localStorage.getItem('option:local:' + arg); + case 'option:flutter:local': + return localStorage.getItem('option:flutter:local:' + arg); case 'image_quality': return curConn.getImageQuality(); case 'translate': @@ -307,10 +328,38 @@ function _getByName(name, arg) { return translate(arg.locale, arg.text); case 'peer_option': return curConn.getOption(arg); + case 'get_conn_status': + if (curConn) { + return curConn.getStatus(); + } else { + return JSON.stringify({ status_num: 0 }); + } case 'test_if_valid_server': break; case 'version': return version; + case 'load_recent_peers': + const peersRecent = localStorage.getItem('peers-recent'); + if (peersRecent) { + onRegisteredEvent(JSON.stringify({ name: 'load_recent_peers', peers: peersRecent })); + } + break; + case 'load_fav_peers': + const peersFav = localStorage.getItem('peers-fav'); + if (peersFav) { + onRegisteredEvent(JSON.stringify({ name: 'load_fav_peers', peers: peersFav })); + } + break; + case 'load_lan_peers': + const peersLan = localStorage.getItem('peers-lan'); + if (peersLan) { + onRegisteredEvent(JSON.stringify({ name: 'load_lan_peers', peers: peersLan })); + } + break; + case 'load_recent_peers_sync': + return localStorage.getItem('peers-recent') ?? '{}'; + case 'load_lan_peers_sync': + return localStorage.getItem('peers-lan') ?? '{}'; } return ''; } @@ -342,8 +391,20 @@ window.init = async () => { } export function getPeers() { + return _getJsonObj('peers'); +} + +export function getRecentPeers() { + return _getJsonObj('peers-recent'); +} + +export function getLanPeers() { + return _getJsonObj('peers-lan'); +} + +export function getJsonObj(key) { try { - return JSON.parse(localStorage.getItem('peers')) || {}; + return JSON.parse(localStorage.getItem(key)) || {}; } catch (e) { return {}; } @@ -380,4 +441,18 @@ export function copyToClipboard(text) { document.body.removeChild(textarea); } } -} \ No newline at end of file +} + +// ========================== peers begin ========================== +function removeDiscovered(id) { + try { + const v = localStorage.getItem('discovered'); + if (!v) return; + const discovered = JSON.parse(localStorage.getItem('discovered')); + delete discovered[id]; + localStorage.setItem('discovered', JSON.stringify(discovered)); + } catch (e) { + console.error(e); + } +} +// ========================== peers end ===========================