mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
Upgrade
This commit is contained in:
parent
e33b1b55f0
commit
e0b770d2a8
56 changed files with 3478 additions and 254 deletions
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
|
@ -5,5 +5,9 @@
|
|||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
".\\native\\hub\\Cargo.toml"
|
||||
]
|
||||
],
|
||||
"files.associations": {
|
||||
"*.html.erb": "erb",
|
||||
"cstring": "cpp"
|
||||
}
|
||||
}
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]!,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
media_kit_video
|
||||
screen_retriever
|
||||
url_launcher_linux
|
||||
webf
|
||||
window_manager
|
||||
window_to_front
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
83
packages/desktop_webview_window/CHANGELOG.md
Normal file
83
packages/desktop_webview_window/CHANGELOG.md
Normal 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.
|
||||
201
packages/desktop_webview_window/LICENSE
Normal file
201
packages/desktop_webview_window/LICENSE
Normal 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.
|
||||
77
packages/desktop_webview_window/README.md
Normal file
77
packages/desktop_webview_window/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# desktop_webview_window
|
||||
|
||||
[](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)
|
||||
4
packages/desktop_webview_window/analysis_options.yaml
Normal file
4
packages/desktop_webview_window/analysis_options.yaml
Normal 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
|
||||
16
packages/desktop_webview_window/example/README.md
Normal file
16
packages/desktop_webview_window/example/README.md
Normal 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.
|
||||
|
|
@ -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
|
||||
184
packages/desktop_webview_window/example/lib/main.dart
Normal file
184
packages/desktop_webview_window/example/lib/main.dart
Normal 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',
|
||||
);
|
||||
}
|
||||
116
packages/desktop_webview_window/example/linux/CMakeLists.txt
Normal file
116
packages/desktop_webview_window/example/linux/CMakeLists.txt
Normal 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()
|
||||
|
|
@ -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}
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
C:/DEV/flutter/mangayomi/packages/desktop_webview_window/
|
||||
|
|
@ -0,0 +1 @@
|
|||
C:/Users/kodjo/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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)
|
||||
6
packages/desktop_webview_window/example/linux/main.cc
Normal file
6
packages/desktop_webview_window/example/linux/main.cc
Normal 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);
|
||||
}
|
||||
104
packages/desktop_webview_window/example/linux/my_application.cc
Normal file
104
packages/desktop_webview_window/example/linux/my_application.cc
Normal 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));
|
||||
}
|
||||
|
|
@ -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_
|
||||
284
packages/desktop_webview_window/example/pubspec.lock
Normal file
284
packages/desktop_webview_window/example/pubspec.lock
Normal 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"
|
||||
85
packages/desktop_webview_window/example/pubspec.yaml
Normal file
85
packages/desktop_webview_window/example/pubspec.yaml
Normal 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
|
||||
|
|
@ -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() {}
|
||||
195
packages/desktop_webview_window/lib/desktop_webview_window.dart
Normal file
195
packages/desktop_webview_window/lib/desktop_webview_window.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
17
packages/desktop_webview_window/lib/src/message_channel.dart
Normal file
17
packages/desktop_webview_window/lib/src/message_channel.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
255
packages/desktop_webview_window/lib/src/title_bar.dart
Normal file
255
packages/desktop_webview_window/lib/src/title_bar.dart
Normal 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()
|
||||
],
|
||||
);
|
||||
}
|
||||
91
packages/desktop_webview_window/lib/src/webview.dart
Normal file
91
packages/desktop_webview_window/lib/src/webview.dart
Normal 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);
|
||||
}
|
||||
262
packages/desktop_webview_window/lib/src/webview_impl.dart
Normal file
262
packages/desktop_webview_window/lib/src/webview_impl.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
33
packages/desktop_webview_window/linux/CMakeLists.txt
Normal file
33
packages/desktop_webview_window/linux/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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_
|
||||
242
packages/desktop_webview_window/linux/webview_window.cc
Normal file
242
packages/desktop_webview_window/linux/webview_window.cc
Normal 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));
|
||||
}
|
||||
63
packages/desktop_webview_window/linux/webview_window.h
Normal file
63
packages/desktop_webview_window/linux/webview_window.h
Normal 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_
|
||||
181
packages/desktop_webview_window/pubspec.lock
Normal file
181
packages/desktop_webview_window/pubspec.lock
Normal 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"
|
||||
28
packages/desktop_webview_window/pubspec.yaml
Normal file
28
packages/desktop_webview_window/pubspec.yaml
Normal 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
|
||||
1
packages/desktop_webview_window/run_local_test_server.sh
Normal file
1
packages/desktop_webview_window/run_local_test_server.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
python3 -m http.server 3000 --directory example/test_web_pages
|
||||
|
|
@ -0,0 +1 @@
|
|||
void main() {}
|
||||
140
pubspec.lock
140
pubspec.lock
|
|
@ -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:
|
||||
|
|
|
|||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue