This commit is contained in:
kodjomoustapha 2024-02-06 17:27:34 +01:00
parent e33b1b55f0
commit e0b770d2a8
56 changed files with 3478 additions and 254 deletions

View file

@ -5,5 +5,9 @@
],
"rust-analyzer.linkedProjects": [
".\\native\\hub\\Cargo.toml"
]
],
"files.associations": {
"*.html.erb": "erb",
"cstring": "cpp"
}
}

View file

@ -27,7 +27,7 @@ class $Client implements $Instance {
'': BridgeConstructorDef(
BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [
BridgeParameter(
'source', BridgeTypeAnnotation($MSource.$type), false),
'source', BridgeTypeAnnotation($MSource.$type), true),
], namedParams: []))
},
methods: {

View file

@ -518,21 +518,6 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
false),
]),
),
'http': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(
CoreTypes.future, [BridgeTypeRef(CoreTypes.string)])),
params: [
BridgeParameter(
'method',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
false),
BridgeParameter(
'datas',
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
false),
]),
),
'gogoCdnExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future, [
@ -764,10 +749,6 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
}
return $String(out);
}),
'http' => $Function((_, __, List<$Value?> args) {
return $Future.wrap(MBridge.http(args[0]!.$reified, args[1]!.$reified)
.then((value) => $String(value)));
}),
'parseHtml' => $Function((_, __, List<$Value?> args) {
final res = MBridge.parsHtml(args[0]!.$reified);
return $MDocument.wrap(res);

View file

@ -1,5 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:bot_toast/bot_toast.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';
@ -21,20 +20,16 @@ import 'package:mangayomi/services/anime_extractors/sendvid_extractor.dart';
import 'package:mangayomi/services/anime_extractors/sibnet_extractor.dart';
import 'package:mangayomi/services/anime_extractors/streamlare_extractor.dart';
import 'package:mangayomi/services/anime_extractors/streamtape_extractor.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/services/anime_extractors/streamwish_extractor.dart';
import 'package:mangayomi/services/anime_extractors/vidbom_extractor.dart';
import 'package:mangayomi/services/anime_extractors/voe_extractor.dart';
import 'package:mangayomi/services/anime_extractors/your_upload_extractor.dart';
import 'package:mangayomi/services/http/cloudflare.dart';
import 'package:mangayomi/utils/cryptoaes/crypto_aes.dart';
import 'package:mangayomi/utils/cryptoaes/deobfuscator.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart';
import 'package:http/http.dart' as hp;
import 'package:encrypt/encrypt.dart' as encrypt;
class WordSet {
@ -300,79 +295,6 @@ class MBridge {
return regCustomMatcher(expression, source, group);
}
//http request and also webview
static Future<String> http(String method, String datas) async {
try {
hp.StreamedResponse? res;
//Get headers
final headersMap = jsonDecode(datas)["headers"] as Map?;
//Get sourceId
final sourceId = jsonDecode(datas)["sourceId"] as int?;
//Get body
final bodyMap = jsonDecode(datas)["body"] as Map?;
final useFormBuilder =
(jsonDecode(datas)["useFormBuilder"] as bool?) ?? false;
final url = jsonDecode(datas)["url"] as String;
//Convert body Map<dynamic,dynamic> to Map<String,String>
Map<String, dynamic> body = {};
if (bodyMap != null) {
body = bodyMap.map((key, value) => MapEntry(key.toString(), value));
}
//Convert headers Map<dynamic,dynamic> to Map<String,String>
Map<String, String> headers = {};
if (headersMap != null) {
headers = headersMap
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
//Get the serie source
final source = sourceId != null ? isar.sources.getSync(sourceId) : null;
if (useFormBuilder) {
var request = hp.MultipartRequest(method, Uri.parse(url));
if (bodyMap != null) {
final fields = bodyMap
.map((key, value) => MapEntry(key.toString(), value.toString()));
request.fields.addAll(fields);
}
request.headers.addAll(headers);
res = await request.send();
} else {
var request = hp.Request(method, Uri.parse(url));
if (bodyMap != null) {
request.body = json.encode(body);
}
request.headers.addAll(headers);
res = await request.send();
}
if (res.statusCode == 403 && (source?.hasCloudflare ?? false)) {
log("Http request: ${res.statusCode}, Cloudflare");
return await cloudflareBypass(
url: url, sourceId: source!.id.toString());
} else if (res.statusCode == 200) {
log("Http request: ${res.statusCode}");
return await res.stream.bytesToString();
} else {
log("Http request: ${res.statusCode}, reasonPhrase: ${res.reasonPhrase}");
return "error";
}
} catch (e) {
log("Http error: $e");
// botToast(e.toString());
return "error";
}
}
static Future<List<Video>> gogoCdnExtractor(String url) async {
return await GogoCdnExtractor().videosFromUrl(url);
}

View file

@ -40,7 +40,7 @@ void main(List<String> args) async {
// Override the default HTTP client.
HttpOverrides.global = MyHttpoverrides();
// If running on desktop platforms and web view title bar widget is active, exit.
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
if (Platform.isLinux) {
if (runWebViewTitleBarWidget(args)) {
return;
}

View file

@ -1444,10 +1444,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
? "${source.baseUrl}${widget.manga!.link!}"
: widget.manga!.link!;
Map<String, String> data = {
Map<String, dynamic> data = {
'url': url,
'sourceId': source.id.toString(),
'title': manga.name!
'title': manga.name!,
"hasCloudFlare": source.hasCloudflare ?? false,
};
context.push("/mangawebview", extra: data);
},

View file

@ -181,10 +181,11 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
Icon(Icons.search, color: Theme.of(context).hintColor)),
IconButton(
onPressed: () {
Map<String, String> data = {
Map<String, dynamic> data = {
'url': widget.source.baseUrl!,
'sourceId': widget.source.id.toString(),
'title': ''
'title': '',
"hasCloudFlare": widget.source.hasCloudflare ?? false
};
context.push("/mangawebview", extra: data);
},
@ -474,10 +475,12 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
children: [
IconButton(
onPressed: () {
Map<String, String> data = {
Map<String, dynamic> data = {
'url': widget.source.baseUrl!,
'sourceId': widget.source.id.toString(),
'title': ''
'title': '',
"hasCloudFlare":
widget.source.hasCloudflare ?? false
};
context.push("/mangawebview", extra: data);
},

View file

@ -1209,10 +1209,11 @@ class _MangaChapterPageGalleryState
String url = chapter.url!.startsWith('/')
? "${source.baseUrl}/${chapter.url!}"
: chapter.url!;
Map<String, String> data = {
Map<String, dynamic> data = {
'url': url,
'sourceId': source.id.toString(),
'title': chapter.name!
'title': chapter.name!,
"hasCloudFlare": source.hasCloudflare ?? false
};
context.push("/mangawebview", extra: data);
},

View file

@ -1,11 +1,11 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_windows_webview/flutter_windows_webview.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/http/interceptor.dart';
import 'package:url_launcher/url_launcher.dart';
@ -17,12 +17,13 @@ class MangaWebView extends ConsumerStatefulWidget {
final String url;
final String sourceId;
final String title;
const MangaWebView({
super.key,
required this.url,
required this.sourceId,
required this.title,
});
final bool hasCloudFlare;
const MangaWebView(
{super.key,
required this.url,
required this.sourceId,
required this.title,
required this.hasCloudFlare});
@override
ConsumerState<MangaWebView> createState() => _MangaWebViewState();
@ -30,6 +31,7 @@ class MangaWebView extends ConsumerStatefulWidget {
class _MangaWebViewState extends ConsumerState<MangaWebView> {
final GlobalKey webViewKey = GlobalKey();
late final MyInAppBrowser _macOSbrowser;
double progress = 0;
@override
@ -44,25 +46,57 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
super.initState();
}
Webview? webview;
_runWebViewDesktop() async {
webview = await WebviewWindow.create(
configuration: CreateConfiguration(
userDataFolderWindows: await getWebViewPath(),
),
);
webview!
..setBrightness(Brightness.dark)
..launch(widget.url)
..addOnUrlRequestCallback((url) async {
decodeHtml(webview!, url);
// final newCookie =
// await webview!.evaluateJavaScript("window.document.cookie;");
// log(newCookie.toString());
})
..onClose.whenComplete(() {
Navigator.pop(context);
});
final _windowsWebview = FlutterWindowsWebview();
Webview? _linuxWebview;
void _runWebViewDesktop() async {
if (Platform.isLinux) {
_linuxWebview = await WebviewWindow.create(
configuration: CreateConfiguration(
userDataFolderWindows: await getWebViewPath(),
),
);
_linuxWebview!
..launch(widget.url)
..onClose.whenComplete(() {
Navigator.pop(context);
});
} else if (Platform.isWindows &&
await FlutterWindowsWebview.isAvailable()) {
_windowsWebview.launchWebview(
widget.url,
WebviewOptions(messageReceiver: (s) {
if (s.substring(0, 2) == "UA") {
MInterceptor.setCookie(_url, s.replaceFirst("UA", ""));
}
}, onNavigation: (url) {
if (Uri.parse(url).host != Uri.parse(widget.url).host) return false;
_windowsWebview.runScript(
"window.chrome.webview.postMessage(\"UA\" + navigator.userAgent)");
_windowsWebview.getCookies(widget.url).then((cookies) {
final cookie =
cookies.entries.map((e) => "${e.key}=${e.value}").join(";");
MInterceptor.setCookie(_url, "", cookie: cookie);
});
return false;
}));
} else if (Platform.isMacOS) {
await _macOSbrowser.openUrlRequest(
urlRequest: URLRequest(url: WebUri(widget.url)),
settings: InAppBrowserClassSettings(
browserSettings: InAppBrowserSettings(
toolbarTopBackgroundColor: Colors.blue,
presentationStyle: ModalPresentationStyle.POPOVER),
webViewSettings: InAppWebViewSettings(
isInspectable: kDebugMode,
useShouldOverrideUrlLoading: true,
useOnLoadResource: true,
),
),
);
}
}
bool isNotDesktop = false;
@ -85,7 +119,13 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
),
leading: IconButton(
onPressed: () {
webview!.close();
if (Platform.isMacOS) {
if (_macOSbrowser.isOpened()) {
_macOSbrowser.close();
}
} else if (Platform.isLinux) {
_linuxWebview!.close();
}
Navigator.pop(context);
},
icon: const Icon(Icons.close)),
@ -161,7 +201,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
Share.share(_url);
} else if (value == 2) {
await InAppBrowser.openWithSystemBrowser(
url: Uri.parse(_url));
url: WebUri(_url));
} else if (value == 3) {
CookieManager.instance().deleteAllCookies();
}
@ -234,7 +274,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
_canGoForward = canGoForward;
});
},
initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
),
),
],
@ -252,25 +292,60 @@ Future<String> getWebViewPath() async {
);
}
Future<String?> decodeHtml(Webview webview, String url) async {
try {
final html = await webview
.evaluateJavaScript("window.document.documentElement.outerHTML;");
final ua = await webview.evaluateJavaScript("navigator.userAgent") ?? "";
final newCookie =
await webview.evaluateJavaScript("window.document.cookie;");
log(ua);
if (newCookie != null) {
await MInterceptor.setCookie(url, jsonDecode(ua),
cookie: jsonDecode(newCookie));
class MyInAppBrowser extends InAppBrowser {
MyInAppBrowser({required this.context});
late BuildContext context;
@override
Future onBrowserCreated() async {}
@override
void onUpdateVisitedHistory(WebUri? url, bool? isReload) async {
final ua = await webViewController?.evaluateJavascript(
source: "navigator.userAgent") ??
"";
await MInterceptor.setCookie(url.toString(), ua);
}
@override
Future onLoadStart(url) async {}
@override
Future onLoadStop(url) async {}
@override
void onProgressChanged(progress) {
if (progress == 100) {
pullToRefreshController?.endRefreshing();
}
}
@override
void onExit() {
Navigator.pop(context);
}
@override
Future<NavigationActionPolicy> shouldOverrideUrlLoading(
navigationAction) async {
var uri = navigationAction.request.url!;
if (!["http", "https", "file", "chrome", "data", "javascript", "about"]
.contains(uri.scheme)) {
if (await canLaunchUrl(uri)) {
// Launch the App
await launchUrl(
uri,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
final res = jsonDecode(html!) as String;
return NavigationActionPolicy.ALLOW;
}
return res == "<html><head></head><body></body></html>" || res.isEmpty
? null
: res;
} catch (_) {
return null;
@override
void onMainWindowWillClose() {
close();
}
}

View file

@ -360,20 +360,23 @@ class RouterNotifier extends ChangeNotifier {
path: "/mangawebview",
name: "mangawebview",
builder: (context, state) {
final data = state.extra as Map<String, String>;
final data = state.extra as Map<String, dynamic>;
return MangaWebView(
url: data["url"]!,
sourceId: data["sourceId"]!,
title: data['title']!);
url: data["url"]!,
sourceId: data["sourceId"]!,
title: data['title']!,
hasCloudFlare: data["hasCloudFlare"]!,
);
},
pageBuilder: (context, state) {
final data = state.extra as Map<String, String>;
final data = state.extra as Map<String, dynamic>;
return transitionPage(
key: state.pageKey,
child: MangaWebView(
url: data["url"]!,
sourceId: data["sourceId"]!,
title: data['title']!,
hasCloudFlare: data["hasCloudFlare"]!,
),
);
},

View file

@ -1,73 +0,0 @@
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/services/http/interceptor.dart';
Future<String> cloudflareBypass(
{required String url, required String sourceId}) async {
String ua = "";
bool isOk = false;
String? html;
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 500,
windowWidth: 500,
userDataFolderWindows: await getWebViewPath(),
),
);
webview
..setBrightness(Brightness.dark)
..launch(url);
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
html = await decodeHtml(webview, url);
if (html == null ||
html!.contains("Just a moment") ||
html!.contains("challenges.cloudflare.com")) {
return true;
}
return false;
});
isOk = true;
webview.close();
} else {
HeadlessInAppWebView? headlessWebView;
headlessWebView = HeadlessInAppWebView(
onLoadStop: (controller, u) async {
html = await controller.getHtml();
await Future.doWhile(() async {
if (html == null ||
html!.contains("Just a moment") ||
html!.contains("challenges.cloudflare.com")) {
html = await controller.getHtml();
return true;
}
return false;
});
html = await controller.getHtml();
ua = await controller.evaluateJavascript(
source: "navigator.userAgent") ??
"";
isOk = true;
headlessWebView!.dispose();
},
initialUrlRequest: URLRequest(url: Uri.parse(url)),
);
headlessWebView.run();
await Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (isOk == true) {
return false;
}
return true;
});
await MInterceptor.setCookie(url, ua);
}
return html!;
}

View file

@ -61,13 +61,15 @@ class MInterceptor {
static Future<void> setCookie(String url, String ua, {String? cookie}) async {
List<String> cookies = [];
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
cookies = cookie!
.split(RegExp('(?<=)(,)(?=[^;]+?=)'))
.where((cookie) => cookie.isNotEmpty)
.toList();
if (Platform.isWindows || Platform.isLinux) {
cookies = cookie
?.split(RegExp('(?<=)(,)(?=[^;]+?=)'))
.where((cookie) => cookie.isNotEmpty)
.toList() ??
[];
} else {
cookies = (await _cookieManager.getCookies(url: Uri.parse(url)))
cookies = (await _cookieManager.getCookies(
url: flutter_inappwebview.WebUri(url)))
.map((e) => "${e.name}=${e.value}")
.toList();
}
@ -80,8 +82,9 @@ class MInterceptor {
);
}
await setCookiesPref(url);
}
if (ua.isNotEmpty) {
final settings = isar.settings.getSync(227);
isar.writeTxnSync(() => isar.settings.putSync(settings!..userAgent = ua));
}
}

View file

@ -12,6 +12,7 @@
#include <media_kit_video/media_kit_video_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <webf/webf_plugin.h>
#include <window_manager/window_manager_plugin.h>
#include <window_to_front/window_to_front_plugin.h>
@ -34,6 +35,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
g_autoptr(FlPluginRegistrar) webf_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WebfPlugin");
webf_plugin_register_with_registrar(webf_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);

View file

@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_video
screen_retriever
url_launcher_linux
webf
window_manager
window_to_front
)

View file

@ -5,7 +5,7 @@
import FlutterMacOS
import Foundation
import desktop_webview_window
import flutter_inappwebview_macos
import flutter_web_auth_2
import isar_flutter_libs
import media_kit_libs_macos_video
@ -15,14 +15,16 @@ import path_provider_foundation
import screen_brightness_macos
import screen_retriever
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
import wakelock_plus
import webf
import window_manager
import window_to_front
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
@ -32,9 +34,11 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WebFPlugin.register(with: registry.registrar(forPlugin: "WebFPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
}

View file

@ -0,0 +1,83 @@
## 0.2.3
* fix macOS webview window size not working.
## 0.2.2
* fix memory leak on macOS after close webview window.
* Show and Hide Webview window by [@Iri-Hor](https://github.com/Iri-Hor) in [#268](https://github.com/MixinNetwork/flutter-plugins/pull/268)
## 0.2.1
* add Windows attentions to readme.
* fix linux close sub window cause app exited.
* fix linux webview title bar expanded unexpected.
* More control over webview position and size under windows. [#206](https://github.com/MixinNetwork/flutter-plugins/pull/206) by [Lukas Heinze](https://github.com/Iri-Hor)
* fix zone mismatch [#250](https://github.com/MixinNetwork/flutter-plugins/pull/250) by [CD](https://github.com/459217974)
* fix linux webkit2gtk deprecated error [#246](https://github.com/MixinNetwork/flutter-plugins/pull/246) by [Zhiqiang Zhang](https://github.com/zhangzqs)
## 0.2.0
* BREAK CHANGE: bump linux webkit2gtk version to 4.1
## 0.1.6
* fix WebView render area wrong offset on Windows.
## 0.1.5
* add `close` method for WebView.
* add `onUrlRequest` event for WebView.
## 0.1.4
* support custom userDataFolder on Windows.
* fix open web view failed cause crash on Windows.
## 0.1.3
Remove windows addition import requirements.
## 0.1.2
fix TitleBar reload do not work.
## 0.1.1
fix window title not show on macOS
## 0.1.0
support custom titlebar.
NOTE: contains break change. more details see readme.
## 0.0.7
1. support `isWebviewAvailable` check.
2. fix `clearAll` crash on Linux if no webview created.
## 0.0.6
fix swift definition conflict on macOS. [flutter-plugins#17](https://github.com/MixinNetwork/flutter-plugins/issues/17)
## 0.0.5
add `setApplicationNameForUserAgent` for append application name to webview user agent.
## 0.0.4
1. implement `addScriptToExecuteOnDocumentCreated` on macOS.
2. add hot key `command + w` to close window.
## 0.0.3
fix linux build
## 0.0.2
* rename project to desktop_webview_window
## 0.0.1
* add Windows, Linux, macOS support.

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2021] [Mixin]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,77 @@
# desktop_webview_window
[![Pub](https://img.shields.io/pub/v/desktop_webview_window.svg)](https://pub.dev/packages/desktop_webview_window)
Show a webview window on your flutter desktop application.
| | | |
| -------- | ------- | ---- |
| Windows | ✅ | [Webview2](https://www.nuget.org/packages/Microsoft.Web.WebView2) 1.0.992.28 |
| Linux | ✅ | [WebKitGTK-4.1](https://webkitgtk.org/reference/webkit2gtk/stable/index.html) |
| macOS | ✅ | WKWebview |
## Getting Started
1. modify your `main` method.
```dart
import 'package:desktop_webview_window/desktop_webview_window.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Add this your main method.
// used to show a webview title bar.
if (runWebViewTitleBarWidget(args)) {
return;
}
runApp(MyApp());
}
```
2. launch WebViewWindow
```dart
final webview = await WebviewWindow.create();
webview.launch("https://example.com");
```
## linux requirement
```shell
sudo apt-get install webkit2gtk-4.1
```
## Windows
### Requirement
The backend of desktop_webview_window on Windows is WebView2, which requires **WebView2 Runtime** installed.
[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2) is ship in box with Windows11, but
it may not installed on Windows10 devices. So you need consider how to distribute the runtime to your users.
See more: https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution
For convenience, you can use `WebviewWindow.isWebviewAvailable()` check whether the WebView2 is available.
### Attention
The default user data folder of WebView2 is `your_exe_file\WebView2`, which is not a good place to store user data.
eg. if the application is installed in a read-only directory, the application will crash when WebView2 try to write data.
you can use `WebviewWindow.create()` to create a webview with a custom user data folder.
```dart
final webview = await WebviewWindow.create(
confiruation: CreateConfiguration(
userDataFolderWindows: 'your_custom_user_data_folder',
),
);
```
## License
see [LICENSE](./LICENSE)

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,16 @@
# webview_window_example
Demonstrates how to use the webview_window plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,184 @@
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
void main(List<String> args) {
debugPrint('args: $args');
if (runWebViewTitleBarWidget(args)) {
return;
}
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController(
text: 'https://example.com',
);
bool? _webviewAvailable;
@override
void initState() {
super.initState();
WebviewWindow.isWebviewAvailable().then((value) {
setState(() {
_webviewAvailable = value;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
actions: [
IconButton(
onPressed: () async {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 1280,
windowWidth: 720,
title: "ExampleTestWindow",
titleBarTopPadding: Platform.isMacOS ? 20 : 0,
userDataFolderWindows: await _getWebViewPath(),
),
);
webview
..registerJavaScriptMessageHandler("test", (name, body) {
debugPrint('on javaScipt message: $name $body');
})
..setApplicationNameForUserAgent(" WebviewExample/1.0.0")
..setPromptHandler((prompt, defaultText) {
if (prompt == "test") {
return "Hello World!";
} else if (prompt == "init") {
return "initial prompt";
}
return "";
})
..addScriptToExecuteOnDocumentCreated("""
const mixinContext = {
platform: 'Desktop',
conversation_id: 'conversationId',
immersive: false,
app_version: '1.0.0',
appearance: 'dark',
}
window.MixinContext = {
getContext: function() {
return JSON.stringify(mixinContext)
}
}
""")
..launch("http://localhost:3000/test.html");
},
icon: const Icon(Icons.bug_report),
)
],
),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(controller: _controller),
const SizedBox(height: 16),
TextButton(
onPressed: _webviewAvailable != true ? null : _onTap,
child: const Text('Open'),
),
const SizedBox(height: 20),
TextButton(
onPressed: () async {
await WebviewWindow.clearAll(
userDataFolderWindows: await _getWebViewPath(),
);
debugPrint('clear complete');
},
child: const Text('Clear all'),
)
],
),
),
),
),
);
}
void _onTap() async {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
userDataFolderWindows: await _getWebViewPath(),
titleBarTopPadding: Platform.isMacOS ? 20 : 0,
),
);
webview
..setBrightness(Brightness.dark)
..setApplicationNameForUserAgent(" WebviewExample/1.0.0")
..launch(_controller.text)
..addOnUrlRequestCallback((url) {
debugPrint('url: $url');
final uri = Uri.parse(url);
if (uri.path == '/login_success') {
debugPrint('login success. token: ${uri.queryParameters['token']}');
webview.close();
}
})
..onClose.whenComplete(() {
debugPrint("on close");
});
await Future.delayed(const Duration(seconds: 2));
for (final javaScript in _javaScriptToEval) {
try {
final ret = await webview.evaluateJavaScript(javaScript);
debugPrint('evaluateJavaScript: $ret');
} catch (e) {
debugPrint('evaluateJavaScript error: $e \n $javaScript');
}
}
}
}
const _javaScriptToEval = [
"""
function test() {
return;
}
test();
""",
'eval({"name": "test", "user_agent": navigator.userAgent})',
'1 + 1',
'undefined',
'1.0 + 1.0',
'"test"',
];
Future<String> _getWebViewPath() async {
final document = await getApplicationDocumentsDirectory();
return p.join(
document.path,
'desktop_webview_window',
);
}

View file

@ -0,0 +1,116 @@
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
set(BINARY_NAME "webview_window_example")
set(APPLICATION_ID "com.example.webview_window")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Application build
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View file

@ -0,0 +1,87 @@
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View file

@ -0,0 +1 @@
C:/DEV/flutter/mangayomi/packages/desktop_webview_window/

View file

@ -0,0 +1 @@
C:/Users/kodjo/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/

View file

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <desktop_webview_window/desktop_webview_window_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);
}

View file

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -0,0 +1,24 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View file

@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "webview_window_example");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "webview_window_example");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

View file

@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

View file

@ -0,0 +1,284 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
desktop_webview_window:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.2.3"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493
url: "https://pub.dev"
source: hosted
version: "1.0.4"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c
url: "https://pub.dev"
source: hosted
version: "1.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.10.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
win32:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.10.0"

View file

@ -0,0 +1,85 @@
name: webview_window_example
description: Demonstrates how to use the webview_window plugin.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=2.14.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
desktop_webview_window:
# When depending on this package from a real application you should use:
# webview_window: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
path_provider: ^2.0.7
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View file

@ -0,0 +1,8 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
void main() {}

View file

@ -0,0 +1,195 @@
// You have generated a new plugin project without
// specifying the `--platforms` flag. A plugin project supports no platforms is generated.
// To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
// directory. You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as p;
import 'src/create_configuration.dart';
import 'src/message_channel.dart';
import 'src/webview.dart';
import 'src/webview_impl.dart';
export 'src/create_configuration.dart';
export 'src/title_bar.dart';
export 'src/webview.dart';
final List<WebviewImpl> _webviews = [];
class WebviewWindow {
static const MethodChannel _channel = MethodChannel('webview_window');
static const _otherIsolateMessageHandler = ClientMessageChannel();
static bool _inited = false;
static void _init() {
if (_inited) {
return;
}
_inited = true;
_channel.setMethodCallHandler((call) async {
try {
return await _handleMethodCall(call);
} catch (e, s) {
debugPrint("method: ${call.method} args: ${call.arguments}");
debugPrint('handleMethodCall error: $e $s');
}
});
_otherIsolateMessageHandler.setMessageHandler((call) async {
try {
return await _handleOtherIsolateMethodCall(call);
} catch (e, s) {
debugPrint('_handleOtherIsolateMethodCall error: $e $s');
}
});
}
/// Check if WebView runtime is available on the current devices.
static Future<bool> isWebviewAvailable() async {
return true;
}
static Future<Webview> create({
CreateConfiguration? configuration,
}) async {
configuration ??= CreateConfiguration.platform();
_init();
final viewId = await _channel.invokeMethod(
"create",
configuration.toMap(),
) as int;
final webview = WebviewImpl(viewId, _channel);
_webviews.add(webview);
return webview;
}
static Future<dynamic> _handleOtherIsolateMethodCall(MethodCall call) async {
final webViewId = call.arguments['webViewId'] as int;
final webView = _webviews
.cast<WebviewImpl?>()
.firstWhere((w) => w?.viewId == webViewId, orElse: () => null);
if (webView == null) {
return;
}
switch (call.method) {
case 'onBackPressed':
await webView.back();
break;
case 'onForwardPressed':
await webView.forward();
break;
case 'onRefreshPressed':
await webView.reload();
break;
case 'onStopPressed':
await webView.stop();
break;
case 'onClosePressed':
webView.close();
break;
}
}
static Future<dynamic> _handleMethodCall(MethodCall call) async {
final args = call.arguments as Map;
final viewId = args['id'] as int;
final webview = _webviews
.cast<WebviewImpl?>()
.firstWhere((e) => e?.viewId == viewId, orElse: () => null);
assert(webview != null);
if (webview == null) {
return;
}
switch (call.method) {
case "onWindowClose":
_webviews.remove(webview);
webview.onClosed();
break;
case "onJavaScriptMessage":
webview.onJavaScriptMessage(args['name'], args['body']);
break;
case "runJavaScriptTextInputPanelWithPrompt":
return webview.onRunJavaScriptTextInputPanelWithPrompt(
args['prompt'],
args['defaultText'],
);
case "onHistoryChanged":
webview.onHistoryChanged(args['canGoBack'], args['canGoForward']);
await _otherIsolateMessageHandler.invokeMethod('onHistoryChanged', {
'webViewId': viewId,
'canGoBack': args['canGoBack'] as bool,
'canGoForward': args['canGoForward'] as bool,
});
break;
case "onNavigationStarted":
webview.onNavigationStarted();
await _otherIsolateMessageHandler.invokeMethod('onNavigationStarted', {
'webViewId': viewId,
});
break;
case "onUrlRequested":
final url = args['url'] as String;
webview.notifyUrlChanged(url);
await _otherIsolateMessageHandler.invokeMethod('onUrlRequested', {
'webViewId': viewId,
'url': url,
});
break;
case "onWebMessageReceived":
final message = args['message'] as String;
webview.notifyWebMessageReceived(message);
await _otherIsolateMessageHandler.invokeMethod('onWebMessageReceived', {
'webViewId': viewId,
'message': message,
});
break;
case "onNavigationCompleted":
webview.onNavigationCompleted();
await _otherIsolateMessageHandler
.invokeMethod('onNavigationCompleted', {
'webViewId': viewId,
});
break;
default:
return;
}
}
/// Clear all cookies and storage.
static Future<void> clearAll({
String userDataFolderWindows = 'webview_window_WebView2',
}) async {
await _channel.invokeMethod('clearAll');
// FIXME(boyan01) Move the logic to windows platform if WebView2 provider a way to clean caches.
// https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/user-data-folder#create-user-data-folders
if (Platform.isWindows) {
final Directory webview2Dir;
if (p.isAbsolute(userDataFolderWindows)) {
webview2Dir = Directory(userDataFolderWindows);
} else {
webview2Dir = Directory(p.join(
p.dirname(Platform.resolvedExecutable), userDataFolderWindows));
}
if (await (webview2Dir.exists())) {
for (var i = 0; i <= 4; i++) {
try {
await webview2Dir.delete(recursive: true);
break;
} catch (e) {
debugPrint("delete cache failed. retring.... $e");
}
// wait to ensure all web window has been closed and file handle has been release.
await Future.delayed(const Duration(seconds: 1));
}
}
}
}
}

View file

@ -0,0 +1,54 @@
import 'dart:io';
class CreateConfiguration {
final int windowWidth;
final int windowHeight;
/// Position of the top left point of the webview window
final int windowPosX;
final int windowPosY;
/// the title of window
final String title;
final int titleBarHeight;
final int titleBarTopPadding;
final String userDataFolderWindows;
final bool useWindowPositionAndSize;
final bool openMaximized;
const CreateConfiguration({
this.windowWidth = 1280,
this.windowHeight = 720,
this.windowPosX = 0,
this.windowPosY = 0,
this.title = "",
this.titleBarHeight = 40,
this.titleBarTopPadding = 0,
this.userDataFolderWindows = 'webview_window_WebView2',
this.useWindowPositionAndSize = false,
this.openMaximized = false,
});
factory CreateConfiguration.platform() {
return CreateConfiguration(
titleBarTopPadding: Platform.isMacOS ? 24 : 0,
);
}
Map toMap() => {
"windowWidth": windowWidth,
"windowHeight": windowHeight,
"windowPosX": windowPosX,
"windowPosY": windowPosY,
"title": title,
"titleBarHeight": titleBarHeight,
"titleBarTopPadding": titleBarTopPadding,
"userDataFolderWindows": userDataFolderWindows,
"useWindowPositionAndSize": useWindowPositionAndSize,
"openMaximized": openMaximized,
};
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/services.dart';
typedef MessageHandler = Future<dynamic> Function(MethodCall call);
class ClientMessageChannel {
const ClientMessageChannel();
static const _channel = MethodChannel('webview_message/client_channel');
Future<dynamic> invokeMethod(String method, [dynamic arguments]) {
return _channel.invokeMethod(method, arguments);
}
void setMessageHandler(MessageHandler? handler) {
_channel.setMethodCallHandler(handler);
}
}

View file

@ -0,0 +1,255 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'message_channel.dart';
const _channel = ClientMessageChannel();
/// runs the title bar
/// title bar is a widget that displays the title of the webview window
/// return true if the args is matchs the title bar
///
/// [builder] custom TitleBar widget builder.
/// can use [TitleBarWebViewController] to controller the WebView
/// use [TitleBarWebViewState] to triger the title bar status.
///
bool runWebViewTitleBarWidget(
List<String> args, {
WidgetBuilder? builder,
Color? backgroundColor,
void Function(Object error, StackTrace stack)? onError,
}) {
if (args.isEmpty || args[0] != 'web_view_title_bar') {
return false;
}
final webViewId = int.tryParse(args[1]);
if (webViewId == null) {
return false;
}
final titleBarTopPadding = int.tryParse(args.length > 2 ? args[2] : '0') ?? 0;
runZonedGuarded(
() {
WidgetsFlutterBinding.ensureInitialized();
runApp(_TitleBarApp(
webViewId: webViewId,
titleBarTopPadding: titleBarTopPadding,
backgroundColor: backgroundColor,
builder: builder ?? _defaultTitleBar,
));
},
onError ??
(e, s) {
debugPrint('WebViewTitleBar: unhandled expections: $e, $s');
},
);
return true;
}
mixin TitleBarWebViewController {
static TitleBarWebViewController of(BuildContext context) {
final state = context.findAncestorStateOfType<_TitleBarAppState>();
assert(state != null,
'only can find TitleBarWebViewController in widget which run from runWebViewTitleBarWidget');
return state!;
}
int get _webViewId;
/// navigate back
void back() {
_channel.invokeMethod('onBackPressed', {
'webViewId': _webViewId,
});
}
/// navigate forward
void forward() {
_channel.invokeMethod('onForwardPressed', {
'webViewId': _webViewId,
});
}
/// reload the webview
void reload() {
_channel.invokeMethod('onRefreshPressed', {
'webViewId': _webViewId,
});
}
/// stop loading the webview
void stop() {
_channel.invokeMethod('onStopPressed', {
'webViewId': _webViewId,
});
}
/// close the webview
void close() {
_channel.invokeMethod('onClosePressed', {
'webViewId': _webViewId,
});
}
}
class TitleBarWebViewState extends InheritedWidget {
const TitleBarWebViewState({
Key? key,
required Widget child,
required this.isLoading,
required this.canGoBack,
required this.canGoForward,
required this.url,
}) : super(key: key, child: child);
final bool isLoading;
final bool canGoBack;
final bool canGoForward;
final String? url;
static TitleBarWebViewState of(BuildContext context) {
final TitleBarWebViewState? result =
context.dependOnInheritedWidgetOfExactType<TitleBarWebViewState>();
assert(result != null, 'No WebViewState found in context');
return result!;
}
@override
bool updateShouldNotify(TitleBarWebViewState oldWidget) {
return isLoading != oldWidget.isLoading ||
canGoBack != oldWidget.canGoBack ||
canGoForward != oldWidget.canGoForward;
}
}
class _TitleBarApp extends StatefulWidget {
const _TitleBarApp({
Key? key,
required this.webViewId,
required this.titleBarTopPadding,
required this.builder,
this.backgroundColor,
}) : super(key: key);
final int webViewId;
final int titleBarTopPadding;
final WidgetBuilder builder;
final Color? backgroundColor;
@override
State<_TitleBarApp> createState() => _TitleBarAppState();
}
class _TitleBarAppState extends State<_TitleBarApp>
with TitleBarWebViewController {
bool _canGoBack = false;
bool _canGoForward = false;
bool _isLoading = false;
String? _url;
@override
int get _webViewId => widget.webViewId;
@override
void initState() {
super.initState();
_channel.setMessageHandler((call) async {
final args = call.arguments as Map;
final webViewId = args['webViewId'] as int;
if (webViewId != widget.webViewId) {
return;
}
switch (call.method) {
case "onHistoryChanged":
setState(() {
_canGoBack = args['canGoBack'] as bool;
_canGoForward = args['canGoForward'] as bool;
});
break;
case "onNavigationStarted":
setState(() {
_isLoading = true;
});
break;
case "onNavigationCompleted":
setState(() {
_isLoading = false;
});
break;
case "onUrlRequested":
setState(() {
_url = args['url'] as String;
});
break;
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Material(
color:
widget.backgroundColor ?? Theme.of(context).scaffoldBackgroundColor,
child: Padding(
padding: EdgeInsets.only(top: widget.titleBarTopPadding.toDouble()),
child: TitleBarWebViewState(
isLoading: _isLoading,
canGoBack: _canGoBack,
canGoForward: _canGoForward,
url: _url,
child: Builder(builder: widget.builder),
),
),
),
);
}
}
Widget _defaultTitleBar(BuildContext context) {
final state = TitleBarWebViewState.of(context);
final controller = TitleBarWebViewController.of(context);
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
padding: EdgeInsets.zero,
splashRadius: 16,
iconSize: 16,
onPressed: !state.canGoBack ? null : controller.back,
icon: const Icon(Icons.arrow_back),
),
IconButton(
padding: EdgeInsets.zero,
splashRadius: 16,
iconSize: 16,
onPressed: !state.canGoForward ? null : controller.forward,
icon: const Icon(Icons.arrow_forward),
),
if (state.isLoading)
IconButton(
padding: EdgeInsets.zero,
splashRadius: 16,
iconSize: 16,
onPressed: controller.stop,
icon: const Icon(Icons.close),
)
else
IconButton(
padding: EdgeInsets.zero,
splashRadius: 16,
iconSize: 16,
onPressed: controller.reload,
icon: const Icon(Icons.refresh),
),
const Spacer()
],
);
}

View file

@ -0,0 +1,91 @@
import 'package:flutter/foundation.dart';
/// Handle custom message from JavaScript in your app.
typedef JavaScriptMessageHandler = void Function(String name, dynamic body);
typedef PromptHandler = String Function(String prompt, String defaultText);
typedef OnHistoryChangedCallback = void Function(
bool canGoBack, bool canGoForward);
/// Callback when WebView start to load a URL.
/// [url] is the URL string.
typedef OnUrlRequestCallback = void Function(String url);
/// Callback when WebView receives a web message
/// [message] constains the webmessage
typedef OnWebMessageReceivedCallback = void Function(String message);
abstract class Webview {
Future<void> get onClose;
/// true if the webview is currently loading a page.
ValueListenable<bool> get isNavigating;
/// Install a message handler that you can call from your Javascript code.
///
/// available: macOS (10.10+)
void registerJavaScriptMessageHandler(
String name, JavaScriptMessageHandler handler);
/// available: macOS
void unregisterJavaScriptMessageHandler(String name);
/// available: macOS
void setPromptHandler(PromptHandler? handler);
/// Navigates to the given URL.
void launch(String url);
/// change webview theme.
///
/// available only: macOS (Brightness.dark only 10.14+)
void setBrightness(Brightness? brightness);
void addScriptToExecuteOnDocumentCreated(String javaScript);
/// Append a string to the webview's user-agent.
Future<void> setApplicationNameForUserAgent(String applicationName);
/// Navigate to the previous page in the history.
Future<void> back();
/// Navigate to the next page in the history.
Future<void> forward();
/// Show or hide webview window
Future<void> setWebviewWindowVisibility(bool visible);
/// Reload the current page.
Future<void> reload();
/// Stop all navigations and pending resource fetches.
Future<void> stop();
/// Opens the Browser DevTools in a separate window
Future<void> openDevToolsWindow();
/// Register a callback that will be invoked when the webview history changes.
void setOnHistoryChangedCallback(OnHistoryChangedCallback? callback);
void addOnUrlRequestCallback(OnUrlRequestCallback callback);
void removeOnUrlRequestCallback(OnUrlRequestCallback callback);
void addOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback);
void removeOnWebMessageReceivedCallback(
OnWebMessageReceivedCallback callback);
/// Close the web view window.
void close();
/// evaluate JavaScript in the web view.
Future<String?> evaluateJavaScript(String javaScript);
/// post a web message as String to the top level document in this WebView
Future<void> postWebMessageAsString(String webMessage);
/// post a web message as JSON to the top level document in this WebView
Future<void> postWebMessageAsJson(String webMessage);
}

View file

@ -0,0 +1,262 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'webview.dart';
class WebviewImpl extends Webview {
final int viewId;
final MethodChannel channel;
final Map<String, JavaScriptMessageHandler> _javaScriptMessageHandlers = {};
bool _closed = false;
PromptHandler? _promptHandler;
final _closeCompleter = Completer<void>();
OnHistoryChangedCallback? _onHistoryChanged;
final ValueNotifier<bool> _isNaivgating = ValueNotifier<bool>(false);
final Set<OnUrlRequestCallback> _onUrlRequestCallbacks = {};
final Set<OnWebMessageReceivedCallback> _onWebMessageReceivedCallbacks = {};
WebviewImpl(this.viewId, this.channel);
@override
Future<void> get onClose => _closeCompleter.future;
void onClosed() {
_closed = true;
_closeCompleter.complete();
}
void onJavaScriptMessage(String name, dynamic body) {
assert(!_closed);
final handler = _javaScriptMessageHandlers[name];
assert(handler != null, "handler $name is not registed.");
handler?.call(name, body);
}
String onRunJavaScriptTextInputPanelWithPrompt(
String prompt, String defaultText) {
assert(!_closed);
return _promptHandler?.call(prompt, defaultText) ?? defaultText;
}
void onHistoryChanged(bool canGoBack, bool canGoForward) {
assert(!_closed);
_onHistoryChanged?.call(canGoBack, canGoForward);
}
void onNavigationStarted() {
_isNaivgating.value = true;
}
void notifyUrlChanged(String url) {
for (final callback in _onUrlRequestCallbacks) {
callback(url);
}
}
void notifyWebMessageReceived(String message) {
for (final callback in _onWebMessageReceivedCallbacks) {
callback(message);
}
}
void onNavigationCompleted() {
_isNaivgating.value = false;
}
@override
ValueListenable<bool> get isNavigating => _isNaivgating;
@override
void registerJavaScriptMessageHandler(
String name, JavaScriptMessageHandler handler) {
if (Platform.isLinux) {
return;
}
assert(!_closed);
if (_closed) {
return;
}
assert(name.isNotEmpty);
assert(!_javaScriptMessageHandlers.containsKey(name));
_javaScriptMessageHandlers[name] = handler;
channel.invokeMethod("registerJavaScripInterface", {
"viewId": viewId,
"name": name,
});
}
@override
void unregisterJavaScriptMessageHandler(String name) {
if (Platform.isLinux) {
return;
}
if (_closed) {
return;
}
channel.invokeMethod("unregisterJavaScripInterface", {
"viewId": viewId,
"name": name,
});
}
@override
void setPromptHandler(PromptHandler? handler) {
if (Platform.isLinux) {
return;
}
_promptHandler = handler;
}
@override
void launch(String url) async {
await channel.invokeMethod("launch", {
"url": url,
"viewId": viewId,
});
}
@override
void setBrightness(Brightness? brightness) {
/// -1 : system default
/// 0 : dark
/// 1 : light
if (Platform.isLinux) {
return;
}
channel.invokeMethod("setBrightness", {
"viewId": viewId,
"brightness": brightness?.index ?? -1,
});
}
@override
void addScriptToExecuteOnDocumentCreated(String javaScript) {
if (Platform.isLinux) {
return;
}
assert(javaScript.trim().isNotEmpty);
channel.invokeMethod("addScriptToExecuteOnDocumentCreated", {
"viewId": viewId,
"javaScript": javaScript,
});
}
@override
Future<void> setApplicationNameForUserAgent(String applicationName) async {
if (Platform.isLinux) {
return;
}
await channel.invokeMethod("setApplicationNameForUserAgent", {
"viewId": viewId,
"applicationName": applicationName,
});
}
@override
Future<void> forward() {
return channel.invokeMethod("forward", {"viewId": viewId});
}
@override
Future<void> setWebviewWindowVisibility(bool visible) {
return channel.invokeMethod("setWebviewWindowVisibility", {
"viewId": viewId,
"visible": visible,
});
}
@override
Future<void> back() {
return channel.invokeMethod("back", {"viewId": viewId});
}
@override
Future<void> reload() {
return channel.invokeMethod("reload", {"viewId": viewId});
}
@override
Future<void> stop() {
return channel.invokeMethod("stop", {"viewId": viewId});
}
@override
Future<void> openDevToolsWindow() {
return channel.invokeMethod('openDevToolsWindow', {"viewId": viewId});
}
@override
void setOnHistoryChangedCallback(OnHistoryChangedCallback? callback) {
_onHistoryChanged = callback;
}
@override
void addOnUrlRequestCallback(OnUrlRequestCallback callback) {
_onUrlRequestCallbacks.add(callback);
}
@override
void removeOnUrlRequestCallback(OnUrlRequestCallback callback) {
_onUrlRequestCallbacks.remove(callback);
}
@override
void addOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) {
_onWebMessageReceivedCallbacks.add(callback);
}
@override
void removeOnWebMessageReceivedCallback(
OnWebMessageReceivedCallback callback) {
_onWebMessageReceivedCallbacks.remove(callback);
}
@override
void close() {
if (_closed) {
return;
}
channel.invokeMethod("close", {"viewId": viewId});
}
@override
Future<String?> evaluateJavaScript(String javaScript) async {
final dynamic result = await channel.invokeMethod("evaluateJavaScript", {
"viewId": viewId,
"javaScriptString": javaScript,
});
if (result is String || result == null) {
return result;
}
return json.encode(result);
}
@override
Future<void> postWebMessageAsString(String webMessage) async {
return channel.invokeMethod("postWebMessageAsString", {
"viewId": viewId,
"webMessage": webMessage,
});
}
@override
Future<void> postWebMessageAsJson(String webMessage) async {
return channel.invokeMethod("postWebMessageAsJson", {
"viewId": viewId,
"webMessage": webMessage,
});
}
}

View file

@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME "desktop_webview_window")
project(${PROJECT_NAME} LANGUAGES CXX)
# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "desktop_webview_window_plugin")
find_package(PkgConfig REQUIRED)
pkg_check_modules(WebKit REQUIRED IMPORTED_TARGET webkit2gtk-4.1)
add_library(${PLUGIN_NAME} SHARED
"desktop_webview_window_plugin.cc"
webview_window.cc
webview_window.h
message_channel_plugin.h
message_channel_plugin.cc
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::WebKit)
# List of absolute paths to libraries that should be bundled with the plugin
set(desktop_webview_window_bundled_libraries
""
PARENT_SCOPE
)

View file

@ -0,0 +1,246 @@
#include "include/desktop_webview_window/desktop_webview_window_plugin.h"
#include <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
#include <memory>
#include <cstring>
#include <map>
#include <webkit2/webkit2.h>
#include "webview_window.h"
#include "message_channel_plugin.h"
namespace {
int64_t next_window_id_ = 0;
}
#define WEBVIEW_WINDOW_PLUGIN(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), webview_window_plugin_get_type(), \
WebviewWindowPlugin))
struct _WebviewWindowPlugin {
GObject parent_instance;
FlMethodChannel *method_channel;
std::map<int64_t, std::unique_ptr<WebviewWindow>> *windows;
};
G_DEFINE_TYPE(WebviewWindowPlugin, webview_window_plugin, g_object_get_type())
// Called when a method call is received from Flutter.
static void webview_window_plugin_handle_method_call(
WebviewWindowPlugin *self,
FlMethodCall *method_call) {
const gchar *method = fl_method_call_get_name(method_call);
if (strcmp(method, "create") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "create args is not map", nullptr, nullptr);
return;
}
auto width = fl_value_get_int(fl_value_lookup_string(args, "windowWidth"));
auto height = fl_value_get_int(fl_value_lookup_string(args, "windowHeight"));
auto title = fl_value_get_string(fl_value_lookup_string(args, "title"));
auto title_bar_height = fl_value_get_int(fl_value_lookup_string(args, "titleBarHeight"));
auto window_id = next_window_id_;
g_object_ref(self);
auto webview = std::make_unique<WebviewWindow>(
self->method_channel, window_id,
[self, window_id]() {
self->windows->erase(window_id);
g_object_unref(self);
}, title, width, height, title_bar_height);
self->windows->insert({window_id, std::move(webview)});
next_window_id_++;
fl_method_call_respond_success(method_call, fl_value_new_int(window_id), nullptr);
} else if (strcmp(method, "launch") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "create args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
auto url = fl_value_get_string(fl_value_lookup_string(args, "url"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->Navigate(url);
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "addScriptToExecuteOnDocumentCreated") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
auto java_script = fl_value_get_string(fl_value_lookup_string(args, "javaScript"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->RunJavaScriptWhenContentReady(java_script);
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "clearAll") == 0) {
for (const auto &item: *self->windows) {
item.second->Close();
}
// If application didn't create a webview, but we called webkit_website_data_manager_clear, there will be a segment fault.
// To avoid crash, we create a fake webview first and then clear all data.
auto *web_view = webkit_web_view_new();
auto *context = webkit_web_view_get_context(WEBKIT_WEB_VIEW(web_view));
auto *website_data_manager = webkit_web_context_get_website_data_manager(context);
webkit_website_data_manager_clear(website_data_manager, WEBKIT_WEBSITE_DATA_ALL, 0,
nullptr, nullptr, nullptr);
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "setApplicationNameForUserAgent") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call,
"0",
"setApplicationNameForUserAgent args is not map",
nullptr,
nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
auto application_name = fl_value_get_string(fl_value_lookup_string(args, "applicationName"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->SetApplicationNameForUserAgent(application_name);
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "back") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "back args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->GoBack();
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "forward") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "forward args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->GoForward();
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "reload") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "reload args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->Reload();
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "stop") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "stop args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->StopLoading();
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "close") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "close args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
self->windows->at(window_id)->Close();
fl_method_call_respond_success(method_call, nullptr, nullptr);
} else if (strcmp(method, "evaluateJavaScript") == 0) {
auto *args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "0", "evaluateJavaScript args is not map", nullptr, nullptr);
return;
}
auto window_id = fl_value_get_int(fl_value_lookup_string(args, "viewId"));
if (!self->windows->count(window_id)) {
fl_method_call_respond_error(method_call, "0", "can not found webview for viewId", nullptr, nullptr);
return;
}
auto *js = fl_value_get_string(fl_value_lookup_string(args, "javaScriptString"));
self->windows->at(window_id)->EvaluateJavaScript(js, method_call);
} else {
fl_method_call_respond_not_implemented(method_call, nullptr);
}
}
static void webview_window_plugin_dispose(GObject *object) {
delete WEBVIEW_WINDOW_PLUGIN(object)->windows;
g_object_unref(WEBVIEW_WINDOW_PLUGIN(object)->method_channel);
G_OBJECT_CLASS(webview_window_plugin_parent_class)->dispose(object);
}
static void webview_window_plugin_class_init(WebviewWindowPluginClass *klass) {
G_OBJECT_CLASS(klass)->dispose = webview_window_plugin_dispose;
}
static void webview_window_plugin_init(WebviewWindowPlugin *self) {
self->windows = new std::map<int64_t, std::unique_ptr<WebviewWindow>>();
}
static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call,
gpointer user_data) {
WebviewWindowPlugin *plugin = WEBVIEW_WINDOW_PLUGIN(user_data);
webview_window_plugin_handle_method_call(plugin, method_call);
}
void desktop_webview_window_plugin_register_with_registrar(FlPluginRegistrar *registrar) {
client_message_channel_plugin_register_with_registrar(registrar);
WebviewWindowPlugin *plugin = WEBVIEW_WINDOW_PLUGIN(
g_object_new(webview_window_plugin_get_type(), nullptr));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlMethodChannel) channel =
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"webview_window",
FL_METHOD_CODEC(codec));
g_object_ref(channel);
plugin->method_channel = channel;
fl_method_channel_set_method_call_handler(channel, method_call_cb,
g_object_ref(plugin),
g_object_unref);
g_object_unref(plugin);
}

View file

@ -0,0 +1,26 @@
#ifndef FLUTTER_PLUGIN_WEBVIEW_WINDOW_PLUGIN_H_
#define FLUTTER_PLUGIN_WEBVIEW_WINDOW_PLUGIN_H_
#include <flutter_linux/flutter_linux.h>
G_BEGIN_DECLS
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define FLUTTER_PLUGIN_EXPORT
#endif
typedef struct _WebviewWindowPlugin WebviewWindowPlugin;
typedef struct {
GObjectClass parent_class;
} WebviewWindowPluginClass;
FLUTTER_PLUGIN_EXPORT GType webview_window_plugin_get_type();
FLUTTER_PLUGIN_EXPORT void desktop_webview_window_plugin_register_with_registrar(
FlPluginRegistrar* registrar);
G_END_DECLS
#endif // FLUTTER_PLUGIN_WEBVIEW_WINDOW_PLUGIN_H_

View file

@ -0,0 +1,92 @@
//
// Created by boyan on 11/23/21.
//
#include "message_channel_plugin.h"
#include <set>
#include "glib.h"
namespace {
class ClientMessageChannelPlugin {
public:
explicit ClientMessageChannelPlugin(FlMethodChannel *channel);
void DispatchMethodCall(FlMethodCall *call) {
auto *name = fl_method_call_get_name(call);
auto *args = fl_method_call_get_args(call);
fl_method_channel_invoke_method(channel_, name, args, nullptr, nullptr, nullptr);
}
~ClientMessageChannelPlugin();
private:
FlMethodChannel *channel_;
};
class ServerMessageChannelPlugin {
public:
void AddClient(ClientMessageChannelPlugin *client) {
clients_.insert(client);
}
void RemoveClient(ClientMessageChannelPlugin *client) {
clients_.erase(client);
}
void DispatchMethodCall(FlMethodCall *call, ClientMessageChannelPlugin *client_from) {
for (auto client: clients_) {
if (client != client_from) {
client->DispatchMethodCall(call);
}
}
}
private:
std::set<ClientMessageChannelPlugin *> clients_;
};
ServerMessageChannelPlugin *g_server_message_channel_plugin = nullptr;
ClientMessageChannelPlugin::ClientMessageChannelPlugin(FlMethodChannel *channel) : channel_(channel) {
g_object_ref(channel_);
g_server_message_channel_plugin->AddClient(this);
}
ClientMessageChannelPlugin::~ClientMessageChannelPlugin() {
g_object_unref(channel_);
g_server_message_channel_plugin->RemoveClient(this);
}
void client_plugin_proxy_dispatch_method_call(FlMethodChannel *channel, FlMethodCall *call, gpointer user_data) {
auto *client = static_cast<ClientMessageChannelPlugin *>(user_data);
g_assert(g_server_message_channel_plugin);
g_server_message_channel_plugin->DispatchMethodCall(call, client);
fl_method_call_respond_success(call, nullptr, nullptr);
}
}
void client_message_channel_plugin_register_with_registrar(FlPluginRegistrar *registrar) {
if (!g_server_message_channel_plugin) {
g_server_message_channel_plugin = new ServerMessageChannelPlugin();
}
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
FlMethodChannel *channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"webview_message/client_channel",
FL_METHOD_CODEC(codec));
auto *client_message_channel_plugin = new ClientMessageChannelPlugin(channel);
fl_method_channel_set_method_call_handler(channel,
client_plugin_proxy_dispatch_method_call,
client_message_channel_plugin,
[](gpointer pointer) {
delete static_cast<ClientMessageChannelPlugin *>(pointer);
});
}

View file

@ -0,0 +1,12 @@
//
// Created by boyan on 11/23/21.
//
#ifndef DESKTOP_WEBVIEW_WINDOW_LINUX_MESSAGE_CHANNEL_PLUGIN_H_
#define DESKTOP_WEBVIEW_WINDOW_LINUX_MESSAGE_CHANNEL_PLUGIN_H_
#include <flutter_linux/flutter_linux.h>
void client_message_channel_plugin_register_with_registrar(FlPluginRegistrar *registrar);
#endif //DESKTOP_WEBVIEW_WINDOW_LINUX_MESSAGE_CHANNEL_PLUGIN_H_

View file

@ -0,0 +1,242 @@
//
// Created by boyan on 10/21/21.
//
#include "webview_window.h"
#include <utility>
#include "message_channel_plugin.h"
namespace {
gboolean on_load_failed_with_tls_errors(
WebKitWebView *web_view,
char *failing_uri,
GTlsCertificate *certificate,
GTlsCertificateFlags errors,
gpointer user_data) {
auto *webview = static_cast<WebviewWindow *>(user_data);
g_critical("on_load_failed_with_tls_errors: %s %p error= %d", failing_uri, webview, errors);
// TODO allow certificate for some certificate ?
// maybe we can use the pem from https://source.chromium.org/chromium/chromium/src/+/master:net/data/ssl/ev_roots/
// webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(web_view), certificate, uri->host);
// webkit_web_view_load_uri(web_view, failing_uri);
return false;
}
GtkWidget *on_create(WebKitWebView *web_view,
WebKitNavigationAction *navigation_action,
gpointer user_data) {
return GTK_WIDGET(web_view);
}
void on_load_changed(WebKitWebView *web_view,
WebKitLoadEvent load_event,
gpointer user_data) {
auto *window = static_cast<WebviewWindow *>(user_data);
window->OnLoadChanged(load_event);
}
gboolean decide_policy_cb(WebKitWebView *web_view,
WebKitPolicyDecision *decision,
WebKitPolicyDecisionType type,
gpointer user_data) {
auto *window = static_cast<WebviewWindow *>(user_data);
return window->DecidePolicy(decision, type);
}
}
WebviewWindow::WebviewWindow(
FlMethodChannel *method_channel,
int64_t window_id,
std::function<void()> on_close_callback,
const std::string &title,
int width,
int height,
int title_bar_height
) : method_channel_(method_channel),
window_id_(window_id),
on_close_callback_(std::move(on_close_callback)),
default_user_agent_() {
g_object_ref(method_channel_);
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(G_OBJECT(window_), "destroy",
G_CALLBACK(+[](GtkWidget *, gpointer arg) {
auto *window = static_cast<WebviewWindow *>(arg);
if (window->on_close_callback_) {
window->on_close_callback_();
}
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window->window_id_));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(window->method_channel_), "onWindowClose", args,
nullptr, nullptr, nullptr);
}), this);
gtk_window_set_title(GTK_WINDOW(window_), title.c_str());
gtk_window_set_default_size(GTK_WINDOW(window_), width, height);
gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
box_ = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
gtk_container_add(GTK_CONTAINER(window_), GTK_WIDGET(box_));
// initial flutter_view
g_autoptr(FlDartProject) project = fl_dart_project_new();
const char *args[] = {"web_view_title_bar", g_strdup_printf("%ld", window_id), nullptr};
fl_dart_project_set_dart_entrypoint_arguments(project, const_cast<char **>(args));
auto *title_bar = fl_view_new(project);
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
fl_plugin_registry_get_registrar_for_plugin(FL_PLUGIN_REGISTRY(title_bar), "DesktopWebviewWindowPlugin");
client_message_channel_plugin_register_with_registrar(desktop_webview_window_registrar);
gtk_widget_set_size_request(GTK_WIDGET(title_bar), -1, title_bar_height);
gtk_widget_set_vexpand(GTK_WIDGET(title_bar), FALSE);
gtk_box_pack_start(box_, GTK_WIDGET(title_bar), FALSE, FALSE, 0);
// initial web_view
webview_ = webkit_web_view_new();
g_signal_connect(G_OBJECT(webview_), "load-failed-with-tls-errors",
G_CALLBACK(on_load_failed_with_tls_errors), this);
g_signal_connect(G_OBJECT(webview_), "create",
G_CALLBACK(on_create), this);
g_signal_connect(G_OBJECT(webview_), "load-changed",
G_CALLBACK(on_load_changed), this);
g_signal_connect(G_OBJECT(webview_), "decide-policy",
G_CALLBACK(decide_policy_cb), this);
auto settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_));
webkit_settings_set_javascript_can_open_windows_automatically(settings, true);
default_user_agent_ = webkit_settings_get_user_agent(settings);
gtk_box_pack_end(box_, webview_, true, true, 0);
gtk_widget_show_all(GTK_WIDGET(window_));
gtk_widget_grab_focus(GTK_WIDGET(webview_));
// FROM: https://github.com/leanflutter/window_manager/pull/343
// Disconnect all delete-event handlers first in flutter 3.10.1, which causes delete_event not working.
// Issues from flutter/engine: https://github.com/flutter/engine/pull/40033
guint handler_id = g_signal_handler_find(window_, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, title_bar);
if (handler_id > 0) {
g_signal_handler_disconnect(window_, handler_id);
}
}
WebviewWindow::~WebviewWindow() {
g_object_unref(method_channel_);
printf("~WebviewWindow\n");
}
void WebviewWindow::Navigate(const char *url) {
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview_), url);
}
void WebviewWindow::RunJavaScriptWhenContentReady(const char *java_script) {
auto *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview_));
webkit_user_content_manager_add_script(
manager,
webkit_user_script_new(java_script,
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
nullptr,
nullptr));
}
void WebviewWindow::SetApplicationNameForUserAgent(const std::string &app_name) {
auto *setting = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_));
webkit_settings_set_user_agent(setting, (default_user_agent_ + app_name).c_str());
}
void WebviewWindow::Close() {
gtk_window_close(GTK_WINDOW(window_));
}
void WebviewWindow::OnLoadChanged(WebKitLoadEvent load_event) {
// notify history changed event.
{
auto can_go_back = webkit_web_view_can_go_back(WEBKIT_WEB_VIEW(webview_));
auto can_go_forward = webkit_web_view_can_go_forward(WEBKIT_WEB_VIEW(webview_));
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
fl_value_set(args, fl_value_new_string("canGoBack"), fl_value_new_bool(can_go_back));
fl_value_set(args, fl_value_new_string("canGoForward"), fl_value_new_bool(can_go_forward));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(method_channel_), "onHistoryChanged", args,
nullptr, nullptr, nullptr);
}
// notify load start/finished event.
switch (load_event) {
case WEBKIT_LOAD_STARTED: {
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(method_channel_), "onNavigationStarted", args,
nullptr, nullptr, nullptr);
break;
}
case WEBKIT_LOAD_FINISHED: {
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(method_channel_), "onNavigationCompleted", args,
nullptr, nullptr, nullptr);
break;
}
default :break;
}
}
void WebviewWindow::GoForward() {
webkit_web_view_go_forward(WEBKIT_WEB_VIEW(webview_));
}
void WebviewWindow::GoBack() {
webkit_web_view_go_back(WEBKIT_WEB_VIEW(webview_));
}
void WebviewWindow::Reload() {
webkit_web_view_reload(WEBKIT_WEB_VIEW(webview_));
}
void WebviewWindow::StopLoading() {
webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(webview_));
}
gboolean WebviewWindow::DecidePolicy(WebKitPolicyDecision *decision, WebKitPolicyDecisionType type) {
if (type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
auto *navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
auto *navigation_action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
auto *request = webkit_navigation_action_get_request(navigation_action);
auto *uri = webkit_uri_request_get_uri(request);
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
fl_value_set(args, fl_value_new_string("url"), fl_value_new_string(uri));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(method_channel_), "onUrlRequested", args,
nullptr, nullptr, nullptr);
}
return false;
}
void WebviewWindow::EvaluateJavaScript(const char *java_script, FlMethodCall *call) {
webkit_web_view_evaluate_javascript(
WEBKIT_WEB_VIEW(webview_), java_script, -1, nullptr, nullptr, nullptr,
[](GObject *object, GAsyncResult *result, gpointer user_data) {
auto *call = static_cast<FlMethodCall *>(user_data);
GError *error = nullptr;
auto *js_result = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error);
if (!js_result) {
fl_method_call_respond_error(call, "failed to evaluate javascript.", error->message, nullptr, nullptr);
g_error_free(error);
} else {
auto *js_value = jsc_value_to_json(js_result, 0);
fl_method_call_respond_success(call, js_value ? fl_value_new_string(js_value) : nullptr, nullptr);
}
g_object_unref(call);
},
g_object_ref(call));
}

View file

@ -0,0 +1,63 @@
//
// Created by boyan on 10/21/21.
//
#ifndef WEBVIEW_WINDOW_LINUX_WEBVIEW_WINDOW_H_
#define WEBVIEW_WINDOW_LINUX_WEBVIEW_WINDOW_H_
#include <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
#include <functional>
#include <string>
class WebviewWindow {
public:
WebviewWindow(
FlMethodChannel *method_channel,
int64_t window_id,
std::function<void()> on_close_callback,
const std::string &title, int width, int height,
int title_bar_height
);
virtual ~WebviewWindow();
void Navigate(const char *url);
void RunJavaScriptWhenContentReady(const char *java_script);
void Close();
void SetApplicationNameForUserAgent(const std::string &app_name);
void OnLoadChanged(WebKitLoadEvent load_event);
void GoBack();
void GoForward();
void Reload();
void StopLoading();
gboolean DecidePolicy(WebKitPolicyDecision *decision,
WebKitPolicyDecisionType type);
void EvaluateJavaScript(const char *java_script, FlMethodCall* call);
private:
FlMethodChannel *method_channel_;
int64_t window_id_;
std::function<void()> on_close_callback_;
std::string default_user_agent_;
GtkWidget *window_ = nullptr;
GtkWidget *webview_ = nullptr;
GtkBox *box_ = nullptr;
};
#endif //WEBVIEW_WINDOW_LINUX_WEBVIEW_WINDOW_H_

View file

@ -0,0 +1,181 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493
url: "https://pub.dev"
source: hosted
version: "1.0.4"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c
url: "https://pub.dev"
source: hosted
version: "1.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.10.0"
path:
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
sdks:
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=2.5.0"

View file

@ -0,0 +1,28 @@
name: desktop_webview_window
description: Show a webview window on your flutter desktop application.
version: 0.2.3
homepage: https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_webview_window
environment:
sdk: ">=2.14.0 <4.0.0"
flutter: ">=2.5.0"
dependencies:
flutter:
sdk: flutter
path: ^1.8.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
plugin:
platforms:
linux:
pluginClass: DesktopWebviewWindowPlugin

View file

@ -0,0 +1 @@
python3 -m http.server 3000 --directory example/test_web_pages

View file

@ -0,0 +1 @@
void main() {}

View file

@ -325,10 +325,9 @@ packages:
desktop_webview_window:
dependency: "direct main"
description:
name: desktop_webview_window
sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0"
url: "https://pub.dev"
source: hosted
path: "packages/desktop_webview_window"
relative: true
source: path
version: "0.2.3"
directed_graph:
dependency: transitive
@ -483,10 +482,58 @@ packages:
dependency: "direct main"
description:
name: flutter_inappwebview
sha256: d198297060d116b94048301ee6749cd2e7d03c1f2689783f52d210a6b7aba350
sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959"
url: "https://pub.dev"
source: hosted
version: "5.8.0"
version: "6.0.0"
flutter_inappwebview_android:
dependency: transitive
description:
name: flutter_inappwebview_android
sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421
url: "https://pub.dev"
source: hosted
version: "1.0.13"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
name: flutter_inappwebview_internal_annotations
sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter_inappwebview_ios:
dependency: transitive
description:
name: flutter_inappwebview_ios
sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f
url: "https://pub.dev"
source: hosted
version: "1.0.13"
flutter_inappwebview_macos:
dependency: transitive
description:
name: flutter_inappwebview_macos
sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636
url: "https://pub.dev"
source: hosted
version: "1.0.11"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
name: flutter_inappwebview_platform_interface
sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187"
url: "https://pub.dev"
source: hosted
version: "1.0.10"
flutter_inappwebview_web:
dependency: transitive
description:
name: flutter_inappwebview_web
sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07
url: "https://pub.dev"
source: hosted
version: "1.0.8"
flutter_launcher_icons:
dependency: "direct dev"
description:
@ -550,6 +597,15 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_windows_webview:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: "096d24d88454819b9f8a94af14a130cb372fa5ae"
url: "https://github.com/wgh136/flutter_windows_webview"
source: git
version: "0.0.1"
font_awesome_flutter:
dependency: "direct main"
description:
@ -614,6 +670,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.2"
hive:
dependency: transitive
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
html:
dependency: "direct main"
description:
@ -1271,6 +1335,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shelf:
dependency: transitive
description:
@ -1572,6 +1692,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webf:
dependency: "direct main"
description:
name: webf
sha256: "55e69ad0d56c79f33c9c1ac011051d20f2b2d7722542a729119586b40648381e"
url: "https://pub.dev"
source: hosted
version: "0.16.0-beta.3"
win32:
dependency: transitive
description:

View file

@ -33,13 +33,14 @@ dependencies:
url: https://github.com/kodjodevf/background_downloader.git
ref: 2cc16eeb475788ec0443b64badc325c00d7dab90
permission_handler: ^11.2.0
flutter_inappwebview: ^5.8.0
flutter_inappwebview: ^6.0.0
draggable_menu: ^4.4.1
isar: 3.1.0+1
isar_flutter_libs: 3.1.0+1
share_plus: ^7.2.2
xpath_selector_html_parser: ^3.0.1
desktop_webview_window: ^0.2.3
desktop_webview_window:
path: ./packages/desktop_webview_window
archive: ^3.4.10
file_picker: ^6.1.1
path_provider: ^2.1.2
@ -68,6 +69,11 @@ dependencies:
http_interceptor: ^2.0.0-beta.7
cookie_jar: ^4.0.0
http: ^1.2.0
flutter_windows_webview:
git:
url: https://github.com/wgh136/flutter_windows_webview
ref: master
webf: ^0.16.0-beta.3
dependency_overrides:

View file

@ -6,7 +6,7 @@
#include "generated_plugin_registrant.h"
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <flutter_windows_webview/flutter_windows_webview_plugin_c_api.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
@ -15,12 +15,13 @@
#include <screen_retriever/screen_retriever_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <webf/webf_plugin.h>
#include <window_manager/window_manager_plugin.h>
#include <window_to_front/window_to_front_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopWebviewWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
FlutterWindowsWebviewPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWindowsWebviewPluginCApi"));
IsarFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
@ -37,6 +38,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WebfPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WebfPlugin"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
WindowToFrontPluginRegisterWithRegistrar(

View file

@ -3,7 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window
flutter_windows_webview
isar_flutter_libs
media_kit_libs_windows_video
media_kit_video
@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
share_plus
url_launcher_windows
webf
window_manager
window_to_front
)