rmv linux webview
This commit is contained in:
parent
45fbd42c19
commit
b46c69dace
11 changed files with 73 additions and 723 deletions
|
|
@ -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.isMacOS) {
|
||||
if (Platform.isMacOS) {
|
||||
if (runWebViewTitleBarWidget(args)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import 'package:mangayomi/modules/widgets/progress_center.dart';
|
|||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../utils/constant.dart';
|
||||
|
||||
|
|
@ -1484,7 +1485,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0),
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
final manga = widget.manga!;
|
||||
|
||||
final source =
|
||||
|
|
@ -1500,7 +1501,22 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
'sourceId': source.id.toString(),
|
||||
'title': manga.name!
|
||||
};
|
||||
context.push("/mangawebview", extra: data);
|
||||
if (Platform.isLinux) {
|
||||
final urll = Uri.parse(url);
|
||||
if (!await launchUrl(
|
||||
urll,
|
||||
mode: LaunchMode.inAppBrowserView,
|
||||
)) {
|
||||
if (!await launchUrl(
|
||||
urll,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.push("/mangawebview", extra: data);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -23,6 +24,7 @@ import 'package:mangayomi/modules/manga/home/widget/mangas_card_selector.dart';
|
|||
import 'package:mangayomi/modules/widgets/gridview_widget.dart';
|
||||
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
|
||||
import 'package:mangayomi/utils/global_style.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MangaHomeScreen extends ConsumerStatefulWidget {
|
||||
final Source source;
|
||||
|
|
@ -255,7 +257,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) {
|
||||
onSelected: (value) async {
|
||||
if (value == 0) {
|
||||
final baseUrl =
|
||||
ref.watch(sourceBaseUrlProvider(source: widget.source));
|
||||
|
|
@ -264,7 +266,22 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
'sourceId': widget.source.id.toString(),
|
||||
'title': ''
|
||||
};
|
||||
context.push("/mangawebview", extra: data);
|
||||
if (Platform.isLinux) {
|
||||
final url = Uri.parse(baseUrl);
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.inAppBrowserView,
|
||||
)) {
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.push("/mangawebview", extra: data);
|
||||
}
|
||||
} else {
|
||||
context.push('/extension_detail', extra: widget.source);
|
||||
}
|
||||
|
|
@ -570,7 +587,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
final baseUrl = ref.watch(
|
||||
sourceBaseUrlProvider(source: widget.source));
|
||||
Map<String, dynamic> data = {
|
||||
|
|
@ -580,7 +597,22 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
|||
"hasCloudFlare":
|
||||
widget.source.hasCloudflare ?? false
|
||||
};
|
||||
context.push("/mangawebview", extra: data);
|
||||
if (Platform.isLinux) {
|
||||
final url = Uri.parse(baseUrl);
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.inAppBrowserView,
|
||||
)) {
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.push("/mangawebview", extra: data);
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.public,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import 'package:photo_view/photo_view.dart';
|
|||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
typedef DoubleClickAnimationListener = void Function();
|
||||
|
|
@ -1361,7 +1362,7 @@ class _MangaChapterPageGalleryState
|
|||
: Icons.bookmark_border_outlined)),
|
||||
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
final manga = chapter.manga.value!;
|
||||
final source = getSource(manga.lang!, manga.source!)!;
|
||||
String url = chapter.url!.startsWith('/')
|
||||
|
|
@ -1373,7 +1374,22 @@ class _MangaChapterPageGalleryState
|
|||
'title': chapter.name!,
|
||||
"hasCloudFlare": source.hasCloudflare ?? false
|
||||
};
|
||||
context.push("/mangawebview", extra: data);
|
||||
if (Platform.isLinux) {
|
||||
final urll = Uri.parse(url);
|
||||
if (!await launchUrl(
|
||||
urll,
|
||||
mode: LaunchMode.inAppBrowserView,
|
||||
)) {
|
||||
if (!await launchUrl(
|
||||
urll,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.push("/mangawebview", extra: data);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.public)),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
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
|
||||
)
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
#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);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
#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_
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
//
|
||||
// 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_
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
//
|
||||
// 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));
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// 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_
|
||||
Loading…
Reference in a new issue