From beb20091ee8570ea932e7fbf678408b8086d41d9 Mon Sep 17 00:00:00 2001 From: haorendashu <385321165@qq.com> Date: Sat, 8 Feb 2025 19:05:31 +0800 Subject: [PATCH] try to add linux webview support --- lib/component/webview/web_info.dart | 4 +- lib/component/webview/webview_component.dart | 2 +- lib/component/webview/webview_controller.dart | 61 +++ .../webview/webview_controller_interface.dart | 20 + .../webview/webview_linux_component.dart | 420 ++++++++++++++++++ .../webview/webview_linux_controller.dart | 70 +++ lib/const/base.dart | 4 + lib/main.dart | 6 + lib/provider/web_provider.dart | 21 +- lib/router/index/index_web_component.dart | 44 +- .../web_tabs_select_item_component.dart | 6 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + linux/main.cc | 2 + linux/my_application.cc | 3 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 50 ++- pubspec.yaml | 5 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 20 files changed, 689 insertions(+), 40 deletions(-) create mode 100644 lib/component/webview/webview_controller.dart create mode 100644 lib/component/webview/webview_controller_interface.dart create mode 100644 lib/component/webview/webview_linux_component.dart create mode 100644 lib/component/webview/webview_linux_controller.dart diff --git a/lib/component/webview/web_info.dart b/lib/component/webview/web_info.dart index 1890540..357f3ed 100644 --- a/lib/component/webview/web_info.dart +++ b/lib/component/webview/web_info.dart @@ -1,6 +1,6 @@ -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import '../../data/browser_history.dart'; +import 'webview_controller_interface.dart'; class WebInfo { String id; @@ -9,7 +9,7 @@ class WebInfo { WebInfo(this.id, this.url); - InAppWebViewController? controller; + WebviewControllerInterface? controller; String? title; diff --git a/lib/component/webview/webview_component.dart b/lib/component/webview/webview_component.dart index 760b66e..b33d252 100644 --- a/lib/component/webview/webview_component.dart +++ b/lib/component/webview/webview_component.dart @@ -53,7 +53,7 @@ class _WebViewComponent extends State iframeAllow: "camera; microphone", iframeAllowFullscreen: true, userAgent: - "${Base.APP_NAME} ${PlatformUtil.getPlatformName()} ${Base.VERSION_NAME}", + Base.USER_AGENT, ); PullToRefreshController? pullToRefreshController; diff --git a/lib/component/webview/webview_controller.dart b/lib/component/webview/webview_controller.dart new file mode 100644 index 0000000..6d29e1e --- /dev/null +++ b/lib/component/webview/webview_controller.dart @@ -0,0 +1,61 @@ + +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:nowser/component/webview/webview_controller_interface.dart'; + +class WebviewController extends WebviewControllerInterface { + + InAppWebViewController controller; + + WebviewController(this.controller); + + @override + Future reload() async { + await controller.reload(); + } + + @override + Future goBack() async { + await controller.goBack(); + } + + @override + Future canGoBack() async { + return await controller.canGoBack(); + } + + @override + Future goForward() async { + await controller.goForward(); + } + + @override + Future getUrl() async { + var webUrl = await controller.getUrl(); + try { + if (webUrl != null) { + return webUrl.uriValue; + } + } catch (e) {} + return null; + } + + @override + Future getFavicon() async { + var favicons = await controller.getFavicons(); + if (favicons.isNotEmpty) { + return favicons.first.url.toString(); + } + return null; + } + + @override + Future loadUrl(String url) async { + await controller.loadUrl(urlRequest: URLRequest(url: WebUri(url))); + } + + @override + Future getTitle() async { + return controller.getTitle(); + } + +} \ No newline at end of file diff --git a/lib/component/webview/webview_controller_interface.dart b/lib/component/webview/webview_controller_interface.dart new file mode 100644 index 0000000..9d47196 --- /dev/null +++ b/lib/component/webview/webview_controller_interface.dart @@ -0,0 +1,20 @@ + +abstract class WebviewControllerInterface { + + Future reload(); + + Future goBack(); + + Future canGoBack(); + + Future goForward(); + + Future getUrl(); + + Future getFavicon(); + + Future loadUrl(String url); + + Future getTitle(); + +} \ No newline at end of file diff --git a/lib/component/webview/webview_linux_component.dart b/lib/component/webview/webview_linux_component.dart new file mode 100644 index 0000000..74e6e3a --- /dev/null +++ b/lib/component/webview/webview_linux_component.dart @@ -0,0 +1,420 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:nostr_sdk/event.dart'; +import 'package:nostr_sdk/utils/string_util.dart'; +import 'package:nowser/component/webview/web_info.dart'; +import 'package:nowser/const/app_type.dart'; +import 'package:nowser/const/auth_type.dart'; +import 'package:nowser/main.dart'; +import 'package:nowser/provider/permission_check_mixin.dart'; +import 'package:webview_cef/webview_cef.dart'; +import 'package:webview_cef/src/webview_inject_user_script.dart'; + +import '../../data/app.dart'; + +class WebViewLinuxComponent extends StatefulWidget { + WebInfo webInfo; + + Function(WebInfo, WebViewController) onWebViewCreated; + + Function(WebInfo, WebViewController, String?) onTitleChanged; + + Function(WebInfo, WebViewController, String?) onUrlChanged; + + Function(WebInfo, WebViewController) onLoadStop; + + WebViewLinuxComponent( + this.webInfo, + this.onWebViewCreated, + this.onTitleChanged, + this.onUrlChanged, + this.onLoadStop, + ); + + @override + State createState() { + return _WebViewLinuxComponent(); + } +} + +class _WebViewLinuxComponent extends State + with PermissionCheckMixin { + late WebViewController controller; + + double progress = 0; + + Set javascriptChannels = {}; + + InjectUserScripts injectScript = InjectUserScripts(); + + String url = ""; + + String title = ""; + + @override + void initState() { + super.initState(); + + initInjectScript(); + initJSHandle(); + + controller = WebviewManager().createWebView( + loading: const Text("Loading"), + injectUserScripts: injectScript); + controller.setWebviewListener(WebviewEventsListener( + onTitleChanged: (title) { + title = title; + widget.onTitleChanged(widget.webInfo, controller, title); + }, + onUrlChanged: (url) { + url = url; + widget.onUrlChanged(widget.webInfo, controller, url); + }, + onConsoleMessage: (int level, String message, String source, int line) { + print("$level $source $line $message"); + }, + // onLoadStart: (controller, url) { + // }, + onLoadEnd: (controller, url) { + widget.onLoadStop(widget.webInfo, controller); + }, + )); + + controller.initialize(widget.webInfo.url).then((v) { + controller.setJavaScriptChannels(javascriptChannels); + setState(() { + inited = true; + }); + + // controller.openDevTools(); + }); + } + + var inited = false; + + @override + Widget build(BuildContext context) { + return Container( + child: inited ? controller.webviewWidget : controller.loadingWidget, + ); + } + + Future nip07Reject(String resultId, String contnet) async { + var script = "window.nostr.reject(\"$resultId\", \"${contnet}\");"; + await controller.executeJavaScript(script); + } + + void initJSHandle() { + javascriptChannels.add(JavascriptChannel(name: "NowserJSgetPublicKey", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSgetPublicKey $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.GET_PUBLIC_KEY, + (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) { + print("confirm get pubkey"); + var pubkey = app.pubkey; + var script = "window.nostr.callback(\"$resultId\", \"$pubkey\");"; + controller.executeJavaScript(script); + }); + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSsignEvent", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSsignEvent $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + var content = jsonObj["msg"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + try { + var eventObj = jsonDecode(content); + var eventKind = eventObj["kind"]; + if (eventKind is int) { + checkPermission(context, AppType.WEB, code, AuthType.SIGN_EVENT, + eventKind: eventKind, authDetail: content, (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) async { + var tags = eventObj["tags"]; + Event? event = Event(app.pubkey!, eventObj["kind"], tags ?? [], + eventObj["content"], + createdAt: eventObj["created_at"]); + event = await signer.signEvent(event); + if (event == null) { + return; + } + + var eventResultStr = jsonEncode(event.toJson()); + // TODO this method to handle " may be error + eventResultStr = eventResultStr.replaceAll("\"", "\\\""); + var script = + "window.nostr.callback(\"$resultId\", JSON.parse(\"$eventResultStr\"));"; + controller.executeJavaScript(script); + }); + } + } catch (e) { + nip07Reject(resultId, "Sign fail"); + } + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSgetRelays", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSgetRelays $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.GET_RELAYS, (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) { + // TODO handle getRelays + // var app = appProvider.getApp(AppType.WEB, code); + // if (app != null) { + // var relayMaps = {}; + // var relayAddrs = relayProvider.relayAddrs; + // for (var relayAddr in relayAddrs) { + // relayMaps[relayAddr] = {"read": true, "write": true}; + // } + // var resultStr = jsonEncode(relayMaps); + // resultStr = resultStr.replaceAll("\"", "\\\""); + // var script = + // "window.nostr.callback(\"$resultId\", JSON.parse(\"$resultStr\"));"; + // webViewController!.evaluateJavascript(source: script); + // } + }); + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSnip04encrypt", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSnip04encrypt $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + var msg = jsonObj["msg"]; + if (msg != null && msg is Map) { + var pubkey = msg["pubkey"]; + var plaintext = msg["plaintext"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.NIP04_ENCRYPT, + (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) async { + var resultStr = await signer.encrypt(pubkey, plaintext); + if (StringUtil.isBlank(resultStr)) { + return; + } + var script = + "window.nostr.callback(\"$resultId\", \"$resultStr\");"; + controller.executeJavaScript(script); + }); + } + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSnip04decrypt", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSnip04decrypt $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + var msg = jsonObj["msg"]; + if (msg != null && msg is Map) { + var pubkey = msg["pubkey"]; + var ciphertext = msg["ciphertext"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.NIP04_DECRYPT, + (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) async { + var app = appProvider.getApp(AppType.WEB, code); + if (app != null) { + var resultStr = await signer.decrypt(pubkey, ciphertext); + if (StringUtil.isBlank(resultStr)) { + return; + } + var script = + "window.nostr.callback(\"$resultId\", \"$resultStr\");"; + controller.executeJavaScript(script); + } + }); + } + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSnip44encrypt", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSnip44encrypt $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + var msg = jsonObj["msg"]; + if (msg != null && msg is Map) { + var pubkey = msg["pubkey"]; + var plaintext = msg["plaintext"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.NIP44_ENCRYPT, + (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) async { + var resultStr = await signer.nip44Encrypt(pubkey, plaintext); + if (StringUtil.isBlank(resultStr)) { + return; + } + var script = + "window.nostr.callback(\"$resultId\", \"$resultStr\");"; + controller.executeJavaScript(script); + }); + } + },) + ); + javascriptChannels.add(JavascriptChannel(name: "NowserJSnip44decrypt", + onMessageReceived: (javascriptMessage) async { + var jsMsg = javascriptMessage.message; + print("NowserJSnip44decrypt $jsMsg"); + var jsonObj = jsonDecode(jsMsg); + var resultId = jsonObj["resultId"]; + var msg = jsonObj["msg"]; + if (msg != null && msg is Map) { + var pubkey = msg["pubkey"]; + var ciphertext = msg["ciphertext"]; + + String? code = await getCode(); + if (code == null) { + return; + } + + checkPermission(context, AppType.WEB, code, AuthType.NIP44_DECRYPT, + (app) { + nip07Reject(resultId, "Forbid"); + }, (app, signer) async { + var resultStr = await signer.nip44Decrypt(pubkey, ciphertext); + if (StringUtil.isBlank(resultStr)) { + return; + } + var script = + "window.nostr.callback(\"$resultId\", \"$resultStr\");"; + controller.executeJavaScript(script); + }); + } + },) + ); + } + + void initInjectScript() { + injectScript.add(UserScript(""" +window.nostr = { +_call(channel, message) { + return new Promise((resolve, reject) => { + var resultId = "callbackResult_" + Math.floor(Math.random() * 100000000); + var arg = {"resultId": resultId}; + if (message) { + arg["msg"] = message; + } + // var argStr = JSON.stringify(arg); + // window.flutter_inappwebview + // .callHandler(channel, argStr); + channel(arg); + window.nostr._requests[resultId] = {resolve, reject} + }); +}, +_requests: {}, +callback(resultId, message) { + window.nostr._requests[resultId].resolve(message); +}, +reject(resultId, message) { + window.nostr._requests[resultId].reject(message); +}, +async getPublicKey() { + return window.nostr._call(NowserJSgetPublicKey); +}, +async signEvent(event) { + return window.nostr._call(NowserJSsignEvent, JSON.stringify(event)); +}, +async getRelays() { + return window.nostr._call(NowserJSgetRelays); +}, +nip04: { + async encrypt(pubkey, plaintext) { + return window.nostr._call(NowserJSnip04encrypt, {"pubkey": pubkey, "plaintext": plaintext}); + }, + async decrypt(pubkey, ciphertext) { + return window.nostr._call(NowserJSnip04decrypt, {"pubkey": pubkey, "ciphertext": ciphertext}); + }, +}, +nip44: { + async encrypt(pubkey, plaintext) { + return window.nostr._call(NowserJSnip44encrypt, {"pubkey": pubkey, "plaintext": plaintext}); + }, + async decrypt(pubkey, ciphertext) { + return window.nostr._call(NowserJSnip44decrypt, {"pubkey": pubkey, "ciphertext": ciphertext}); + }, +}, +}; +""", ScriptInjectTime.LOAD_START)); +// injectScript.add(UserScript("console.log(window.nostr);", ScriptInjectTime.LOAD_END)); + } + + Future getCode() async { + if (StringUtil.isBlank(url)) { + url = widget.webInfo.url; + } + + if (StringUtil.isNotBlank(url)) { + var uri = Uri.parse(url); + return uri.host; + } + + return null; + } + + @override + Future getApp(int appType, String code) async { + String? name = title; + String? image; + // var favicons = await webViewController!.getFavicons(); + // if (favicons.isNotEmpty) { + // image = favicons.first.url.toString(); + // } + return App(appType: appType, code: code, name: name, image: image); + } + + @override + void dispose() { + super.dispose(); + controller.dispose(); + } + +} diff --git a/lib/component/webview/webview_linux_controller.dart b/lib/component/webview/webview_linux_controller.dart new file mode 100644 index 0000000..a19bc23 --- /dev/null +++ b/lib/component/webview/webview_linux_controller.dart @@ -0,0 +1,70 @@ + + +import 'package:webview_cef/webview_cef.dart'; + +import 'webview_controller_interface.dart'; + +class WebviewLinuxController extends WebviewControllerInterface { + + WebViewController controller; + + WebviewLinuxController(this.controller); + + @override + Future reload() async { + await controller.reload(); + } + + @override + Future goBack() async { + await controller.goBack(); + } + + @override + Future canGoBack() async { + return true; + } + + @override + Future goForward() async { + await controller.goForward(); + } + + @override + Future getUrl() async { + try { + if (url != null) { + return Uri.parse(url!); + } + } catch (e) {} + return null; + } + + @override + Future getFavicon() async { + return null; + } + + @override + Future loadUrl(String url) async { + await controller.loadUrl(url); + } + + @override + Future getTitle() async { + return title; + } + + String? title; + + String? url; + + void setTitle(String title) { + title = title; + } + + void setUrl(String url) { + url = url; + } + +} \ No newline at end of file diff --git a/lib/const/base.dart b/lib/const/base.dart index 8280afc..7ce4ea8 100644 --- a/lib/const/base.dart +++ b/lib/const/base.dart @@ -1,3 +1,5 @@ +import 'package:nostr_sdk/utils/platform_util.dart'; + class Base { static const APP_NAME = "Nowser"; @@ -8,4 +10,6 @@ class Base { static const double BASE_PADDING_HALF = 6; static double BASE_FONT_SIZE = 15; + + static String USER_AGENT = "${Base.APP_NAME} ${PlatformUtil.getPlatformName()} ${Base.VERSION_NAME}"; } diff --git a/lib/main.dart b/lib/main.dart index b003c96..a30b426 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; @@ -34,6 +35,7 @@ import 'package:provider/provider.dart'; import 'package:quick_actions/quick_actions.dart'; import 'package:receive_intent/receive_intent.dart' as receiveIntent; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:webview_cef/webview_cef.dart'; import 'package:window_manager/window_manager.dart'; import 'const/base.dart'; @@ -105,6 +107,10 @@ Future main() async { print(e); } + if (Platform.isLinux) { + WebviewManager().initialize(userAgent: Base.USER_AGENT); + } + await doInit(); mediaDataCache = MediaDataCache(); diff --git a/lib/provider/web_provider.dart b/lib/provider/web_provider.dart index 60f549e..009f7b1 100644 --- a/lib/provider/web_provider.dart +++ b/lib/provider/web_provider.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:nostr_sdk/utils/string_util.dart'; @@ -161,21 +163,26 @@ class WebProvider extends ChangeNotifier { } try { + String? urlStr; var url = await webInfo.controller!.getUrl(); if (url == null) { + if (Platform.isLinux) { + urlStr = webInfo.url; + } + } else { + urlStr = url.toString(); + } + + if (urlStr == null) { return; } var title = await webInfo.controller!.getTitle(); - var favicons = await webInfo.controller!.getFavicons(); - String? favicon; - if (favicons.isNotEmpty) { - favicon = favicons.first.url.toString(); - } + String? favicon = await webInfo.controller!.getFavicon(); var browserHistory = BrowserHistory( title: title, favicon: favicon, - url: url.toString(), + url: urlStr, createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); if (webInfo.browserHistory != null && @@ -226,7 +233,7 @@ class WebProvider extends ChangeNotifier { webInfo.url = url; webInfo.title = null; if (webInfo.controller != null) { - webInfo.controller!.loadUrl(urlRequest: URLRequest(url: WebUri(url))); + webInfo.controller!.loadUrl(url); return true; } else { updateWebInfo(webInfo); diff --git a/lib/router/index/index_web_component.dart b/lib/router/index/index_web_component.dart index 6861697..fe48f8b 100644 --- a/lib/router/index/index_web_component.dart +++ b/lib/router/index/index_web_component.dart @@ -1,8 +1,13 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:nostr_sdk/utils/string_util.dart'; import 'package:nowser/component/webview/web_info.dart'; import 'package:nowser/component/webview/webview_component.dart'; +import 'package:nowser/component/webview/webview_controller.dart'; +import 'package:nowser/component/webview/webview_linux_component.dart'; +import 'package:nowser/component/webview/webview_linux_controller.dart'; import 'package:nowser/const/base.dart'; import 'package:nowser/const/router_path.dart'; import 'package:nowser/main.dart'; @@ -53,17 +58,48 @@ class _IndexWebComponent extends State { return WebHomeComponent(webInfo); } - var webComp = WebViewComponent( + Widget? webComp; + if (!Platform.isLinux) { + webComp = WebViewComponent( webInfo, (webInfo, controller) { - webInfo.controller = controller; + webInfo.controller = WebviewController(controller); webProvider.updateWebInfo(webInfo); }, onTitleChanged, (webInfo, controller) { - webInfo.controller = controller; + webInfo.controller = WebviewController(controller); webProvider.onLoadStop(webInfo); }); + } else { + webComp = WebViewLinuxComponent( + webInfo, + (webInfo, controller) { + webInfo.controller = WebviewLinuxController(controller); + webProvider.updateWebInfo(webInfo); + }, + (webInfo, controller, title) { + if (webInfo.controller is WebviewLinuxController && StringUtil.isNotBlank(title)) { + (webInfo.controller as WebviewLinuxController).setTitle(title!); + webInfo.title = title; + webProvider.updateWebInfo(webInfo); + } + }, + (webInfo, controller, url) { + if (webInfo.controller is WebviewLinuxController && StringUtil.isNotBlank(url)) { + print("url change! $url"); + (webInfo.controller as WebviewLinuxController).setUrl(url!); + webInfo.url = url; + } + }, + (webInfo, controller) async { + webInfo.controller ??= WebviewLinuxController(controller); + var title = await webInfo.controller!.getTitle(); + webInfo.title = title; + webProvider.onLoadStop(webInfo); + }); + } + var main = webComp; @@ -78,7 +114,7 @@ class _IndexWebComponent extends State { void onTitleChanged( WebInfo webInfo, InAppWebViewController controller, String? title) { - webInfo.controller = controller; + webInfo.controller = WebviewController(controller); webInfo.title = title; webProvider.updateWebInfo(webInfo); } diff --git a/lib/router/web_tabs_select/web_tabs_select_item_component.dart b/lib/router/web_tabs_select/web_tabs_select_item_component.dart index 6fa99a9..a8b6436 100644 --- a/lib/router/web_tabs_select/web_tabs_select_item_component.dart +++ b/lib/router/web_tabs_select/web_tabs_select_item_component.dart @@ -25,10 +25,10 @@ class _WebTabsSelectItemComponent extends State { Future loadFavicon() async { if (widget.webInfo.controller != null) { - var favicons = await widget.webInfo.controller!.getFavicons(); - if (favicons.isNotEmpty) { + var favicon = await widget.webInfo.controller!.getFavicon(); + if (StringUtil.isNotBlank(favicon)) { setState(() { - faviconUrl = favicons.first.url.toString(); + faviconUrl = favicon; }); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 1bf5c50..7895868 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); + g_autoptr(FlPluginRegistrar) webview_cef_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WebviewCefPlugin"); + webview_cef_plugin_register_with_registrar(webview_cef_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e05f456..716295c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux screen_retriever_linux + webview_cef window_manager ) diff --git a/linux/main.cc b/linux/main.cc index e7c5c54..af61505 100644 --- a/linux/main.cc +++ b/linux/main.cc @@ -1,6 +1,8 @@ +#include #include "my_application.h" int main(int argc, char** argv) { + initCEFProcesses(argc, argv); g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } diff --git a/linux/my_application.cc b/linux/my_application.cc index 4a3a54b..55710b5 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -6,6 +6,7 @@ #endif #include "flutter/generated_plugin_registrant.h" +#include struct _MyApplication { GtkApplication parent_instance; @@ -54,6 +55,8 @@ static void my_application_activate(GApplication* application) { fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); + g_signal_connect(view, "key_press_event", G_CALLBACK(processKeyEventForCEF), nullptr); + g_signal_connect(view, "key_release_event", G_CALLBACK(processKeyEventForCEF), nullptr); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e4f8e50..bd3a294 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,6 +13,7 @@ import path_provider_foundation import screen_retriever_macos import shared_preferences_foundation import sqflite_darwin +import webview_cef import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -24,5 +25,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + WebviewCefPlugin.register(with: registry.registrar(forPlugin: "WebviewCefPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index cfb73f4..02b94c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" convert: dependency: transitive description: @@ -431,10 +431,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: ab7dbb16f783307c9d7762ede2593ce32c220ba2ba0fd540a3db8e9a3acba71a + sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.1.2" flutter_socks_proxy: dependency: transitive description: @@ -529,18 +529,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -863,10 +863,10 @@ packages: dependency: "direct main" description: name: searchfield - sha256: "9d091c2731868926e2aeb9ac551d1b9116a4533a424373119509d754ae0d0f45" + sha256: "8d23d53967ac5b0774611150b286dacd70c9c5de74d3db433bda2104b4803755" url: "https://pub.dev" source: hosted - version: "1.2.4" + version: "1.2.0" shared_preferences: dependency: "direct main" description: @@ -927,7 +927,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" source_span: dependency: transitive description: @@ -1012,10 +1012,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" stream_channel: dependency: transitive description: @@ -1028,10 +1028,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" string_validator: dependency: transitive description: @@ -1060,10 +1060,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" typed_data: dependency: transitive description: @@ -1100,10 +1100,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" web: dependency: transitive description: @@ -1128,6 +1128,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + webview_cef: + dependency: "direct main" + description: + name: webview_cef + sha256: "2e660bf593dc1168beb7b765dd477a703e0b29968669f017e178c2c716f6437f" + url: "https://pub.dev" + source: hosted + version: "0.2.2" win32: dependency: transitive description: @@ -1177,5 +1185,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.5.3 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index e12c448..e60b122 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,12 +50,13 @@ dependencies: flutter_secure_storage: ^9.2.2 pretty_qr_code: ^3.3.0 qr_code_scanner: ^1.0.1 - flutter_slidable: ^4.0.0 + flutter_slidable: ^3.1.1 window_manager: ^0.4.2 quick_actions: ^1.0.8 flutter_pinned_shortcut_plus: ^0.0.2 flutter_cache_manager: ^3.4.1 - searchfield: ^1.2.4 + searchfield: 1.2.0 + webview_cef: ^0.2.2 dev_dependencies: flutter_launcher_icons: ^0.13.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 2898f8d..0b24d9d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); + WebviewCefPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WebviewCefPluginCApi")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d228be8..2ac9805 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_inappwebview_windows flutter_secure_storage_windows screen_retriever_windows + webview_cef window_manager )