rmv linux webview

This commit is contained in:
kodjomoustapha 2024-02-23 20:35:20 +01:00
parent 45fbd42c19
commit b46c69dace
11 changed files with 73 additions and 723 deletions

View file

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

View file

@ -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: [

View file

@ -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,

View file

@ -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)),
],

View file

@ -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
)

View file

@ -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);
}

View file

@ -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_

View file

@ -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);
});
}

View file

@ -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_

View file

@ -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));
}

View file

@ -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_