From 6f0c760ab4c2e8a00c4dacf7151052e5b0eaf3f8 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Fri, 5 Dec 2025 01:29:48 +0800 Subject: [PATCH 1/4] feat: Replace desktop_webview_window with tao&wry , from tauri --- .vscode/settings.json | 3 +- .../web/input_method/style/google_icons.css | 2 +- assets/web_script.js | 220 +- lib/common/rust/api/webview_api.dart | 142 ++ lib/common/rust/api/webview_api.freezed.dart | 1092 ++++++++++ lib/common/rust/api/win32_api.dart | 1 + lib/common/rust/frb_generated.dart | 736 ++++++- lib/common/rust/frb_generated.io.dart | 687 +++++++ lib/common/rust/rust_webview_controller.dart | 350 ++++ lib/main.dart | 40 - lib/provider/unp4kc.g.dart | 2 +- .../home_game_login_dialog_ui_model.dart | 187 +- .../home_game_login_dialog_ui_model.g.dart | 2 +- lib/ui/home/home_ui_model.dart | 8 +- lib/ui/home/home_ui_model.g.dart | 2 +- lib/ui/tools/tools_ui_model.g.dart | 2 +- lib/ui/webview/webview.dart | 247 +-- linux/flutter/generated_plugin_registrant.cc | 4 - linux/flutter/generated_plugins.cmake | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 4 +- pubspec.lock | 24 +- pubspec.yaml | 1 - rust/Cargo.lock | 1765 ++++++++++++++++- rust/Cargo.toml | 33 +- rust/src/api/mod.rs | 1 + rust/src/api/webview_api.rs | 533 +++++ rust/src/assets/webview_init_script.js | 266 +++ rust/src/frb_generated.rs | 928 ++++++++- rust_builder/cargokit/build_tool/pubspec.lock | 146 +- .../flutter/generated_plugin_registrant.cc | 3 - windows/flutter/generated_plugins.cmake | 1 - 31 files changed, 6894 insertions(+), 539 deletions(-) create mode 100644 lib/common/rust/api/webview_api.dart create mode 100644 lib/common/rust/api/webview_api.freezed.dart create mode 100644 lib/common/rust/rust_webview_controller.dart create mode 100644 rust/src/api/webview_api.rs create mode 100644 rust/src/assets/webview_init_script.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 1395495..a7bafa2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "dart.flutterSdkPath": ".fvm/versions/stable" + "dart.flutterSdkPath": ".fvm/versions/stable", + "cmake.ignoreCMakeListsMissing": true } \ No newline at end of file diff --git a/assets/web/input_method/style/google_icons.css b/assets/web/input_method/style/google_icons.css index 415c528..b80b9b1 100644 --- a/assets/web/input_method/style/google_icons.css +++ b/assets/web/input_method/style/google_icons.css @@ -10,7 +10,7 @@ font-family: 'Material Icons'; font-weight: normal; font-style: normal; - font-size: 24px; + font-size: 1.5rem; /* 24px */ line-height: 1; letter-spacing: normal; text-transform: none; diff --git a/assets/web_script.js b/assets/web_script.js index 28e5bb6..93bf053 100644 --- a/assets/web_script.js +++ b/assets/web_script.js @@ -207,7 +207,7 @@ function ReportUnTranslate(k, v) { const jsRegex = /(?:^|[^<])]*>[\s\S]*?<\/script>(?:[^>]|$)/i; if (k.trim() !== "" && !cnPattern.test(k) && !htmlPattern.test(k) && !cssRegex.test(k) && !jsRegex.test(k) && enPattern.test(k) && !k.startsWith("http://") && !k.startsWith("https://")) { - window.chrome.webview.postMessage({ action: 'webview_localization_capture', key: k, value: v }); + window.ipc.postMessage(JSON.stringify({ action: 'webview_localization_capture', key: k, value: v })); } } } @@ -217,93 +217,149 @@ InitWebLocalization(); /// ----- Login Script ---- async function getRSILauncherToken(channelId) { - if (!window.location.href.includes("robertsspaceindustries.com")) return; - - // check if logged in and fix redirect - if (window.location.href.endsWith('/connect?jumpto=/account/dashboard')) { - if (document.body.textContent.trim() === "/account/dashboard") { - window.location.href = "https://robertsspaceindustries.com/account/dashboard"; - return; - } - } - - let loginBodyElement = $(".c-form authenticationForm sign_in"); - loginBodyElement.show(); - // wait login - window.chrome.webview.postMessage({ action: 'webview_rsi_login_show_window' }); - - // get claims - let claimsR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/claims", { - method: 'POST', headers: { - 'x-rsi-token': $.cookie('Rsi-Token'), - }, - }); - if (claimsR.status !== 200) return; - - loginBodyElement.hide(); - SCTShowToast("登录游戏中..."); - - let claimsData = (await claimsR.json())["data"]; - - let tokenFormData = new FormData(); - tokenFormData.append('claims', claimsData); - tokenFormData.append('gameId', 'SC'); - let tokenR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/token", { - method: 'POST', headers: { - 'x-rsi-token': $.cookie('Rsi-Token'), - }, - body: tokenFormData - }); - - if (tokenR.status !== 200) return; - let TokenData = (await tokenR.json())["data"]["token"]; - console.log(TokenData); - - // get release Data - let releaseFormData = new FormData(); - releaseFormData.append("channelId", channelId); - releaseFormData.append("claims", claimsData); - releaseFormData.append("gameId", "SC"); - releaseFormData.append("platformId", "prod"); - let releaseR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/release", { - method: 'POST', headers: { - 'x-rsi-token': $.cookie('Rsi-Token'), - }, - body: releaseFormData - }); - if (releaseR.status !== 200) return; - let releaseDataJson = (await releaseR.json())['data']; - console.log(releaseDataJson); - // get game library - let libraryR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/library", { - method: 'POST', headers: { - 'x-rsi-token': $.cookie('Rsi-Token'), - }, - body: releaseFormData - }); - - let libraryData = (await libraryR.json())["data"] - - // get user avatar - let avatarUrl = $(".orion-c-avatar__image").attr("src"); - - //post message - window.chrome.webview.postMessage({ - action: 'webview_rsi_login_success', data: { - 'webToken': $.cookie('Rsi-Token'), - 'claims': claimsData, - 'authToken': TokenData, - 'releaseInfo': releaseDataJson, - "avatar": avatarUrl, - 'libraryData': libraryData, + console.log('[SCToolbox] getRSILauncherToken called with channel:', channelId); + + try { + if (!window.location.href.includes("robertsspaceindustries.com")) { + console.log('[SCToolbox] Not on RSI site, skipping'); + return; } - }); + + // check if logged in and fix redirect + if (window.location.href.endsWith('/connect?jumpto=/account/dashboard')) { + if (document.body.textContent.trim() === "/account/dashboard") { + window.location.href = "https://robertsspaceindustries.com/account/dashboard"; + return; + } + } + + // Wait for jQuery to be ready + let waitCount = 0; + while (typeof $ === 'undefined' && waitCount < 50) { + console.log('[SCToolbox] Waiting for jQuery... attempt', waitCount); + await new Promise(r => setTimeout(r, 100)); + waitCount++; + } + + if (typeof $ === 'undefined') { + console.error('[SCToolbox] jQuery not available after waiting'); + return; + } + + // Get RSI token from cookie (don't rely on $.cookie plugin) + function getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); + return null; + } + + const rsiToken = getCookie('Rsi-Token'); + console.log('[SCToolbox] RSI Token available:', !!rsiToken); + + if (!rsiToken) { + console.log('[SCToolbox] No RSI token, showing login window'); + let loginBodyElement = $(".c-form authenticationForm sign_in"); + loginBodyElement.show(); + window.ipc.postMessage(JSON.stringify({ action: 'webview_rsi_login_show_window' })); + return; + } + + // get claims + console.log('[SCToolbox] Fetching claims...'); + let claimsR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/claims", { + method: 'POST', headers: { + 'x-rsi-token': rsiToken, + }, + }); + + console.log('[SCToolbox] Claims response status:', claimsR.status); + if (claimsR.status !== 200) { + console.error('[SCToolbox] Claims request failed'); + return; + } + + SCTShowToast("登录游戏中..."); + + let claimsData = (await claimsR.json())["data"]; + console.log('[SCToolbox] Claims data received'); + + let tokenFormData = new FormData(); + tokenFormData.append('claims', claimsData); + tokenFormData.append('gameId', 'SC'); + let tokenR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/token", { + method: 'POST', headers: { + 'x-rsi-token': rsiToken, + }, + body: tokenFormData + }); + + console.log('[SCToolbox] Token response status:', tokenR.status); + if (tokenR.status !== 200) { + console.error('[SCToolbox] Token request failed'); + return; + } + let TokenData = (await tokenR.json())["data"]["token"]; + console.log('[SCToolbox] Token received'); + + // get release Data + let releaseFormData = new FormData(); + releaseFormData.append("channelId", channelId); + releaseFormData.append("claims", claimsData); + releaseFormData.append("gameId", "SC"); + releaseFormData.append("platformId", "prod"); + let releaseR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/release", { + method: 'POST', headers: { + 'x-rsi-token': rsiToken, + }, + body: releaseFormData + }); + + console.log('[SCToolbox] Release response status:', releaseR.status); + if (releaseR.status !== 200) { + console.error('[SCToolbox] Release request failed'); + return; + } + let releaseDataJson = (await releaseR.json())['data']; + console.log('[SCToolbox] Release data received'); + + // get game library + let libraryR = await fetch("https://robertsspaceindustries.com/api/launcher/v3/games/library", { + method: 'POST', headers: { + 'x-rsi-token': rsiToken, + }, + body: releaseFormData + }); + + let libraryData = (await libraryR.json())["data"]; + console.log('[SCToolbox] Library data received'); + + // get user avatar + let avatarUrl = $(".orion-c-avatar__image").attr("src") || ''; + console.log('[SCToolbox] Avatar URL:', avatarUrl); + + //post message + console.log('[SCToolbox] Sending login success message...'); + window.ipc.postMessage(JSON.stringify({ + action: 'webview_rsi_login_success', data: { + 'webToken': rsiToken, + 'claims': claimsData, + 'authToken': TokenData, + 'releaseInfo': releaseDataJson, + "avatar": avatarUrl, + 'libraryData': libraryData, + } + })); + console.log('[SCToolbox] Login success message sent'); + } catch (error) { + console.error('[SCToolbox] Error in getRSILauncherToken:', error); + } } function SCTShowToast(message) { let m = document.createElement('div'); m.innerHTML = message; - m.style.cssText = "font-family:siyuan;max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: rgb(255, 255, 255);line-height: 40px;text-align: center;border-radius: 4px;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 16px;"; + m.style.cssText = "font-family:siyuan;max-width:60%;min-width: 9.375rem;padding:0 0.875rem;height: 2.5rem;color: rgb(255, 255, 255);line-height: 2.5rem;text-align: center;border-radius: 0.25rem;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 1rem;"; document.body.appendChild(m); setTimeout(function () { let d = 0.5; diff --git a/lib/common/rust/api/webview_api.dart b/lib/common/rust/api/webview_api.dart new file mode 100644 index 0000000..ca03cf4 --- /dev/null +++ b/lib/common/rust/api/webview_api.dart @@ -0,0 +1,142 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; +part 'webview_api.freezed.dart'; + +// These functions are ignored because they are not marked as `pub`: `handle_command`, `load_app_icon`, `run_webview_loop`, `send_command` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `UserEvent`, `WebViewCommand`, `WebViewInstance` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt` + +/// Create a new WebView window and return its ID +String webviewCreate({required WebViewConfiguration config}) => + RustLib.instance.api.crateApiWebviewApiWebviewCreate(config: config); + +/// Navigate to a URL +void webviewNavigate({required String id, required String url}) => + RustLib.instance.api.crateApiWebviewApiWebviewNavigate(id: id, url: url); + +/// Go back in history +void webviewGoBack({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewGoBack(id: id); + +/// Go forward in history +void webviewGoForward({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewGoForward(id: id); + +/// Reload the current page +void webviewReload({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewReload(id: id); + +/// Stop loading +void webviewStop({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewStop(id: id); + +/// Execute JavaScript in the WebView +void webviewExecuteScript({required String id, required String script}) => + RustLib.instance.api.crateApiWebviewApiWebviewExecuteScript( + id: id, + script: script, + ); + +/// Set window visibility +void webviewSetVisibility({required String id, required bool visible}) => + RustLib.instance.api.crateApiWebviewApiWebviewSetVisibility( + id: id, + visible: visible, + ); + +/// Close the WebView window +void webviewClose({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewClose(id: id); + +/// Set window size +void webviewSetWindowSize({ + required String id, + required int width, + required int height, +}) => RustLib.instance.api.crateApiWebviewApiWebviewSetWindowSize( + id: id, + width: width, + height: height, +); + +/// Set window position +void webviewSetWindowPosition({ + required String id, + required int x, + required int y, +}) => RustLib.instance.api.crateApiWebviewApiWebviewSetWindowPosition( + id: id, + x: x, + y: y, +); + +/// Get the current navigation state +WebViewNavigationState webviewGetState({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewGetState(id: id); + +/// Check if the WebView is closed +bool webviewIsClosed({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewIsClosed(id: id); + +/// Poll for events from the WebView (non-blocking) +List webviewPollEvents({required String id}) => + RustLib.instance.api.crateApiWebviewApiWebviewPollEvents(id: id); + +/// Get a list of all active WebView IDs +List webviewListAll() => + RustLib.instance.api.crateApiWebviewApiWebviewListAll(); + +/// WebView window configuration +@freezed +sealed class WebViewConfiguration with _$WebViewConfiguration { + const WebViewConfiguration._(); + const factory WebViewConfiguration({ + required String title, + required int width, + required int height, + String? userDataFolder, + required bool enableDevtools, + required bool transparent, + String? userAgent, + }) = _WebViewConfiguration; + static Future default_() => + RustLib.instance.api.crateApiWebviewApiWebViewConfigurationDefault(); +} + +@freezed +sealed class WebViewEvent with _$WebViewEvent { + const WebViewEvent._(); + + const factory WebViewEvent.navigationStarted({required String url}) = + WebViewEvent_NavigationStarted; + const factory WebViewEvent.navigationCompleted({required String url}) = + WebViewEvent_NavigationCompleted; + const factory WebViewEvent.titleChanged({required String title}) = + WebViewEvent_TitleChanged; + const factory WebViewEvent.webMessage({required String message}) = + WebViewEvent_WebMessage; + const factory WebViewEvent.windowClosed() = WebViewEvent_WindowClosed; + const factory WebViewEvent.error({required String message}) = + WebViewEvent_Error; +} + +/// Navigation state of the WebView +@freezed +sealed class WebViewNavigationState with _$WebViewNavigationState { + const WebViewNavigationState._(); + const factory WebViewNavigationState({ + required String url, + required String title, + required bool canGoBack, + required bool canGoForward, + required bool isLoading, + }) = _WebViewNavigationState; + static Future default_() => + RustLib.instance.api.crateApiWebviewApiWebViewNavigationStateDefault(); +} diff --git a/lib/common/rust/api/webview_api.freezed.dart b/lib/common/rust/api/webview_api.freezed.dart new file mode 100644 index 0000000..c53568d --- /dev/null +++ b/lib/common/rust/api/webview_api.freezed.dart @@ -0,0 +1,1092 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'webview_api.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$WebViewConfiguration { + + String get title; int get width; int get height; String? get userDataFolder; bool get enableDevtools; bool get transparent; String? get userAgent; +/// Create a copy of WebViewConfiguration +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewConfigurationCopyWith get copyWith => _$WebViewConfigurationCopyWithImpl(this as WebViewConfiguration, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewConfiguration&&(identical(other.title, title) || other.title == title)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.userDataFolder, userDataFolder) || other.userDataFolder == userDataFolder)&&(identical(other.enableDevtools, enableDevtools) || other.enableDevtools == enableDevtools)&&(identical(other.transparent, transparent) || other.transparent == transparent)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,width,height,userDataFolder,enableDevtools,transparent,userAgent); + +@override +String toString() { + return 'WebViewConfiguration(title: $title, width: $width, height: $height, userDataFolder: $userDataFolder, enableDevtools: $enableDevtools, transparent: $transparent, userAgent: $userAgent)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewConfigurationCopyWith<$Res> { + factory $WebViewConfigurationCopyWith(WebViewConfiguration value, $Res Function(WebViewConfiguration) _then) = _$WebViewConfigurationCopyWithImpl; +@useResult +$Res call({ + String title, int width, int height, String? userDataFolder, bool enableDevtools, bool transparent, String? userAgent +}); + + + + +} +/// @nodoc +class _$WebViewConfigurationCopyWithImpl<$Res> + implements $WebViewConfigurationCopyWith<$Res> { + _$WebViewConfigurationCopyWithImpl(this._self, this._then); + + final WebViewConfiguration _self; + final $Res Function(WebViewConfiguration) _then; + +/// Create a copy of WebViewConfiguration +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? width = null,Object? height = null,Object? userDataFolder = freezed,Object? enableDevtools = null,Object? transparent = null,Object? userAgent = freezed,}) { + return _then(_self.copyWith( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,width: null == width ? _self.width : width // ignore: cast_nullable_to_non_nullable +as int,height: null == height ? _self.height : height // ignore: cast_nullable_to_non_nullable +as int,userDataFolder: freezed == userDataFolder ? _self.userDataFolder : userDataFolder // ignore: cast_nullable_to_non_nullable +as String?,enableDevtools: null == enableDevtools ? _self.enableDevtools : enableDevtools // ignore: cast_nullable_to_non_nullable +as bool,transparent: null == transparent ? _self.transparent : transparent // ignore: cast_nullable_to_non_nullable +as bool,userAgent: freezed == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WebViewConfiguration]. +extension WebViewConfigurationPatterns on WebViewConfiguration { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WebViewConfiguration value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WebViewConfiguration() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WebViewConfiguration value) $default,){ +final _that = this; +switch (_that) { +case _WebViewConfiguration(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WebViewConfiguration value)? $default,){ +final _that = this; +switch (_that) { +case _WebViewConfiguration() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String title, int width, int height, String? userDataFolder, bool enableDevtools, bool transparent, String? userAgent)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WebViewConfiguration() when $default != null: +return $default(_that.title,_that.width,_that.height,_that.userDataFolder,_that.enableDevtools,_that.transparent,_that.userAgent);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String title, int width, int height, String? userDataFolder, bool enableDevtools, bool transparent, String? userAgent) $default,) {final _that = this; +switch (_that) { +case _WebViewConfiguration(): +return $default(_that.title,_that.width,_that.height,_that.userDataFolder,_that.enableDevtools,_that.transparent,_that.userAgent);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String title, int width, int height, String? userDataFolder, bool enableDevtools, bool transparent, String? userAgent)? $default,) {final _that = this; +switch (_that) { +case _WebViewConfiguration() when $default != null: +return $default(_that.title,_that.width,_that.height,_that.userDataFolder,_that.enableDevtools,_that.transparent,_that.userAgent);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _WebViewConfiguration extends WebViewConfiguration { + const _WebViewConfiguration({required this.title, required this.width, required this.height, this.userDataFolder, required this.enableDevtools, required this.transparent, this.userAgent}): super._(); + + +@override final String title; +@override final int width; +@override final int height; +@override final String? userDataFolder; +@override final bool enableDevtools; +@override final bool transparent; +@override final String? userAgent; + +/// Create a copy of WebViewConfiguration +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WebViewConfigurationCopyWith<_WebViewConfiguration> get copyWith => __$WebViewConfigurationCopyWithImpl<_WebViewConfiguration>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebViewConfiguration&&(identical(other.title, title) || other.title == title)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.userDataFolder, userDataFolder) || other.userDataFolder == userDataFolder)&&(identical(other.enableDevtools, enableDevtools) || other.enableDevtools == enableDevtools)&&(identical(other.transparent, transparent) || other.transparent == transparent)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,width,height,userDataFolder,enableDevtools,transparent,userAgent); + +@override +String toString() { + return 'WebViewConfiguration(title: $title, width: $width, height: $height, userDataFolder: $userDataFolder, enableDevtools: $enableDevtools, transparent: $transparent, userAgent: $userAgent)'; +} + + +} + +/// @nodoc +abstract mixin class _$WebViewConfigurationCopyWith<$Res> implements $WebViewConfigurationCopyWith<$Res> { + factory _$WebViewConfigurationCopyWith(_WebViewConfiguration value, $Res Function(_WebViewConfiguration) _then) = __$WebViewConfigurationCopyWithImpl; +@override @useResult +$Res call({ + String title, int width, int height, String? userDataFolder, bool enableDevtools, bool transparent, String? userAgent +}); + + + + +} +/// @nodoc +class __$WebViewConfigurationCopyWithImpl<$Res> + implements _$WebViewConfigurationCopyWith<$Res> { + __$WebViewConfigurationCopyWithImpl(this._self, this._then); + + final _WebViewConfiguration _self; + final $Res Function(_WebViewConfiguration) _then; + +/// Create a copy of WebViewConfiguration +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? width = null,Object? height = null,Object? userDataFolder = freezed,Object? enableDevtools = null,Object? transparent = null,Object? userAgent = freezed,}) { + return _then(_WebViewConfiguration( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,width: null == width ? _self.width : width // ignore: cast_nullable_to_non_nullable +as int,height: null == height ? _self.height : height // ignore: cast_nullable_to_non_nullable +as int,userDataFolder: freezed == userDataFolder ? _self.userDataFolder : userDataFolder // ignore: cast_nullable_to_non_nullable +as String?,enableDevtools: null == enableDevtools ? _self.enableDevtools : enableDevtools // ignore: cast_nullable_to_non_nullable +as bool,transparent: null == transparent ? _self.transparent : transparent // ignore: cast_nullable_to_non_nullable +as bool,userAgent: freezed == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +/// @nodoc +mixin _$WebViewEvent { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'WebViewEvent()'; +} + + +} + +/// @nodoc +class $WebViewEventCopyWith<$Res> { +$WebViewEventCopyWith(WebViewEvent _, $Res Function(WebViewEvent) __); +} + + +/// Adds pattern-matching-related methods to [WebViewEvent]. +extension WebViewEventPatterns on WebViewEvent { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( WebViewEvent_NavigationStarted value)? navigationStarted,TResult Function( WebViewEvent_NavigationCompleted value)? navigationCompleted,TResult Function( WebViewEvent_TitleChanged value)? titleChanged,TResult Function( WebViewEvent_WebMessage value)? webMessage,TResult Function( WebViewEvent_WindowClosed value)? windowClosed,TResult Function( WebViewEvent_Error value)? error,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted() when navigationStarted != null: +return navigationStarted(_that);case WebViewEvent_NavigationCompleted() when navigationCompleted != null: +return navigationCompleted(_that);case WebViewEvent_TitleChanged() when titleChanged != null: +return titleChanged(_that);case WebViewEvent_WebMessage() when webMessage != null: +return webMessage(_that);case WebViewEvent_WindowClosed() when windowClosed != null: +return windowClosed(_that);case WebViewEvent_Error() when error != null: +return error(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( WebViewEvent_NavigationStarted value) navigationStarted,required TResult Function( WebViewEvent_NavigationCompleted value) navigationCompleted,required TResult Function( WebViewEvent_TitleChanged value) titleChanged,required TResult Function( WebViewEvent_WebMessage value) webMessage,required TResult Function( WebViewEvent_WindowClosed value) windowClosed,required TResult Function( WebViewEvent_Error value) error,}){ +final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted(): +return navigationStarted(_that);case WebViewEvent_NavigationCompleted(): +return navigationCompleted(_that);case WebViewEvent_TitleChanged(): +return titleChanged(_that);case WebViewEvent_WebMessage(): +return webMessage(_that);case WebViewEvent_WindowClosed(): +return windowClosed(_that);case WebViewEvent_Error(): +return error(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( WebViewEvent_NavigationStarted value)? navigationStarted,TResult? Function( WebViewEvent_NavigationCompleted value)? navigationCompleted,TResult? Function( WebViewEvent_TitleChanged value)? titleChanged,TResult? Function( WebViewEvent_WebMessage value)? webMessage,TResult? Function( WebViewEvent_WindowClosed value)? windowClosed,TResult? Function( WebViewEvent_Error value)? error,}){ +final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted() when navigationStarted != null: +return navigationStarted(_that);case WebViewEvent_NavigationCompleted() when navigationCompleted != null: +return navigationCompleted(_that);case WebViewEvent_TitleChanged() when titleChanged != null: +return titleChanged(_that);case WebViewEvent_WebMessage() when webMessage != null: +return webMessage(_that);case WebViewEvent_WindowClosed() when windowClosed != null: +return windowClosed(_that);case WebViewEvent_Error() when error != null: +return error(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( String url)? navigationStarted,TResult Function( String url)? navigationCompleted,TResult Function( String title)? titleChanged,TResult Function( String message)? webMessage,TResult Function()? windowClosed,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted() when navigationStarted != null: +return navigationStarted(_that.url);case WebViewEvent_NavigationCompleted() when navigationCompleted != null: +return navigationCompleted(_that.url);case WebViewEvent_TitleChanged() when titleChanged != null: +return titleChanged(_that.title);case WebViewEvent_WebMessage() when webMessage != null: +return webMessage(_that.message);case WebViewEvent_WindowClosed() when windowClosed != null: +return windowClosed();case WebViewEvent_Error() when error != null: +return error(_that.message);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( String url) navigationStarted,required TResult Function( String url) navigationCompleted,required TResult Function( String title) titleChanged,required TResult Function( String message) webMessage,required TResult Function() windowClosed,required TResult Function( String message) error,}) {final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted(): +return navigationStarted(_that.url);case WebViewEvent_NavigationCompleted(): +return navigationCompleted(_that.url);case WebViewEvent_TitleChanged(): +return titleChanged(_that.title);case WebViewEvent_WebMessage(): +return webMessage(_that.message);case WebViewEvent_WindowClosed(): +return windowClosed();case WebViewEvent_Error(): +return error(_that.message);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( String url)? navigationStarted,TResult? Function( String url)? navigationCompleted,TResult? Function( String title)? titleChanged,TResult? Function( String message)? webMessage,TResult? Function()? windowClosed,TResult? Function( String message)? error,}) {final _that = this; +switch (_that) { +case WebViewEvent_NavigationStarted() when navigationStarted != null: +return navigationStarted(_that.url);case WebViewEvent_NavigationCompleted() when navigationCompleted != null: +return navigationCompleted(_that.url);case WebViewEvent_TitleChanged() when titleChanged != null: +return titleChanged(_that.title);case WebViewEvent_WebMessage() when webMessage != null: +return webMessage(_that.message);case WebViewEvent_WindowClosed() when windowClosed != null: +return windowClosed();case WebViewEvent_Error() when error != null: +return error(_that.message);case _: + return null; + +} +} + +} + +/// @nodoc + + +class WebViewEvent_NavigationStarted extends WebViewEvent { + const WebViewEvent_NavigationStarted({required this.url}): super._(); + + + final String url; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewEvent_NavigationStartedCopyWith get copyWith => _$WebViewEvent_NavigationStartedCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_NavigationStarted&&(identical(other.url, url) || other.url == url)); +} + + +@override +int get hashCode => Object.hash(runtimeType,url); + +@override +String toString() { + return 'WebViewEvent.navigationStarted(url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewEvent_NavigationStartedCopyWith<$Res> implements $WebViewEventCopyWith<$Res> { + factory $WebViewEvent_NavigationStartedCopyWith(WebViewEvent_NavigationStarted value, $Res Function(WebViewEvent_NavigationStarted) _then) = _$WebViewEvent_NavigationStartedCopyWithImpl; +@useResult +$Res call({ + String url +}); + + + + +} +/// @nodoc +class _$WebViewEvent_NavigationStartedCopyWithImpl<$Res> + implements $WebViewEvent_NavigationStartedCopyWith<$Res> { + _$WebViewEvent_NavigationStartedCopyWithImpl(this._self, this._then); + + final WebViewEvent_NavigationStarted _self; + final $Res Function(WebViewEvent_NavigationStarted) _then; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? url = null,}) { + return _then(WebViewEvent_NavigationStarted( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class WebViewEvent_NavigationCompleted extends WebViewEvent { + const WebViewEvent_NavigationCompleted({required this.url}): super._(); + + + final String url; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewEvent_NavigationCompletedCopyWith get copyWith => _$WebViewEvent_NavigationCompletedCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_NavigationCompleted&&(identical(other.url, url) || other.url == url)); +} + + +@override +int get hashCode => Object.hash(runtimeType,url); + +@override +String toString() { + return 'WebViewEvent.navigationCompleted(url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewEvent_NavigationCompletedCopyWith<$Res> implements $WebViewEventCopyWith<$Res> { + factory $WebViewEvent_NavigationCompletedCopyWith(WebViewEvent_NavigationCompleted value, $Res Function(WebViewEvent_NavigationCompleted) _then) = _$WebViewEvent_NavigationCompletedCopyWithImpl; +@useResult +$Res call({ + String url +}); + + + + +} +/// @nodoc +class _$WebViewEvent_NavigationCompletedCopyWithImpl<$Res> + implements $WebViewEvent_NavigationCompletedCopyWith<$Res> { + _$WebViewEvent_NavigationCompletedCopyWithImpl(this._self, this._then); + + final WebViewEvent_NavigationCompleted _self; + final $Res Function(WebViewEvent_NavigationCompleted) _then; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? url = null,}) { + return _then(WebViewEvent_NavigationCompleted( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class WebViewEvent_TitleChanged extends WebViewEvent { + const WebViewEvent_TitleChanged({required this.title}): super._(); + + + final String title; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewEvent_TitleChangedCopyWith get copyWith => _$WebViewEvent_TitleChangedCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_TitleChanged&&(identical(other.title, title) || other.title == title)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title); + +@override +String toString() { + return 'WebViewEvent.titleChanged(title: $title)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewEvent_TitleChangedCopyWith<$Res> implements $WebViewEventCopyWith<$Res> { + factory $WebViewEvent_TitleChangedCopyWith(WebViewEvent_TitleChanged value, $Res Function(WebViewEvent_TitleChanged) _then) = _$WebViewEvent_TitleChangedCopyWithImpl; +@useResult +$Res call({ + String title +}); + + + + +} +/// @nodoc +class _$WebViewEvent_TitleChangedCopyWithImpl<$Res> + implements $WebViewEvent_TitleChangedCopyWith<$Res> { + _$WebViewEvent_TitleChangedCopyWithImpl(this._self, this._then); + + final WebViewEvent_TitleChanged _self; + final $Res Function(WebViewEvent_TitleChanged) _then; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? title = null,}) { + return _then(WebViewEvent_TitleChanged( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class WebViewEvent_WebMessage extends WebViewEvent { + const WebViewEvent_WebMessage({required this.message}): super._(); + + + final String message; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewEvent_WebMessageCopyWith get copyWith => _$WebViewEvent_WebMessageCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_WebMessage&&(identical(other.message, message) || other.message == message)); +} + + +@override +int get hashCode => Object.hash(runtimeType,message); + +@override +String toString() { + return 'WebViewEvent.webMessage(message: $message)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewEvent_WebMessageCopyWith<$Res> implements $WebViewEventCopyWith<$Res> { + factory $WebViewEvent_WebMessageCopyWith(WebViewEvent_WebMessage value, $Res Function(WebViewEvent_WebMessage) _then) = _$WebViewEvent_WebMessageCopyWithImpl; +@useResult +$Res call({ + String message +}); + + + + +} +/// @nodoc +class _$WebViewEvent_WebMessageCopyWithImpl<$Res> + implements $WebViewEvent_WebMessageCopyWith<$Res> { + _$WebViewEvent_WebMessageCopyWithImpl(this._self, this._then); + + final WebViewEvent_WebMessage _self; + final $Res Function(WebViewEvent_WebMessage) _then; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? message = null,}) { + return _then(WebViewEvent_WebMessage( +message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class WebViewEvent_WindowClosed extends WebViewEvent { + const WebViewEvent_WindowClosed(): super._(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_WindowClosed); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'WebViewEvent.windowClosed()'; +} + + +} + + + + +/// @nodoc + + +class WebViewEvent_Error extends WebViewEvent { + const WebViewEvent_Error({required this.message}): super._(); + + + final String message; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewEvent_ErrorCopyWith get copyWith => _$WebViewEvent_ErrorCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewEvent_Error&&(identical(other.message, message) || other.message == message)); +} + + +@override +int get hashCode => Object.hash(runtimeType,message); + +@override +String toString() { + return 'WebViewEvent.error(message: $message)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewEvent_ErrorCopyWith<$Res> implements $WebViewEventCopyWith<$Res> { + factory $WebViewEvent_ErrorCopyWith(WebViewEvent_Error value, $Res Function(WebViewEvent_Error) _then) = _$WebViewEvent_ErrorCopyWithImpl; +@useResult +$Res call({ + String message +}); + + + + +} +/// @nodoc +class _$WebViewEvent_ErrorCopyWithImpl<$Res> + implements $WebViewEvent_ErrorCopyWith<$Res> { + _$WebViewEvent_ErrorCopyWithImpl(this._self, this._then); + + final WebViewEvent_Error _self; + final $Res Function(WebViewEvent_Error) _then; + +/// Create a copy of WebViewEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? message = null,}) { + return _then(WebViewEvent_Error( +message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc +mixin _$WebViewNavigationState { + + String get url; String get title; bool get canGoBack; bool get canGoForward; bool get isLoading; +/// Create a copy of WebViewNavigationState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WebViewNavigationStateCopyWith get copyWith => _$WebViewNavigationStateCopyWithImpl(this as WebViewNavigationState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WebViewNavigationState&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.canGoBack, canGoBack) || other.canGoBack == canGoBack)&&(identical(other.canGoForward, canGoForward) || other.canGoForward == canGoForward)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,url,title,canGoBack,canGoForward,isLoading); + +@override +String toString() { + return 'WebViewNavigationState(url: $url, title: $title, canGoBack: $canGoBack, canGoForward: $canGoForward, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class $WebViewNavigationStateCopyWith<$Res> { + factory $WebViewNavigationStateCopyWith(WebViewNavigationState value, $Res Function(WebViewNavigationState) _then) = _$WebViewNavigationStateCopyWithImpl; +@useResult +$Res call({ + String url, String title, bool canGoBack, bool canGoForward, bool isLoading +}); + + + + +} +/// @nodoc +class _$WebViewNavigationStateCopyWithImpl<$Res> + implements $WebViewNavigationStateCopyWith<$Res> { + _$WebViewNavigationStateCopyWithImpl(this._self, this._then); + + final WebViewNavigationState _self; + final $Res Function(WebViewNavigationState) _then; + +/// Create a copy of WebViewNavigationState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? url = null,Object? title = null,Object? canGoBack = null,Object? canGoForward = null,Object? isLoading = null,}) { + return _then(_self.copyWith( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,canGoBack: null == canGoBack ? _self.canGoBack : canGoBack // ignore: cast_nullable_to_non_nullable +as bool,canGoForward: null == canGoForward ? _self.canGoForward : canGoForward // ignore: cast_nullable_to_non_nullable +as bool,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WebViewNavigationState]. +extension WebViewNavigationStatePatterns on WebViewNavigationState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WebViewNavigationState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WebViewNavigationState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WebViewNavigationState value) $default,){ +final _that = this; +switch (_that) { +case _WebViewNavigationState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WebViewNavigationState value)? $default,){ +final _that = this; +switch (_that) { +case _WebViewNavigationState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String url, String title, bool canGoBack, bool canGoForward, bool isLoading)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WebViewNavigationState() when $default != null: +return $default(_that.url,_that.title,_that.canGoBack,_that.canGoForward,_that.isLoading);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String url, String title, bool canGoBack, bool canGoForward, bool isLoading) $default,) {final _that = this; +switch (_that) { +case _WebViewNavigationState(): +return $default(_that.url,_that.title,_that.canGoBack,_that.canGoForward,_that.isLoading);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String url, String title, bool canGoBack, bool canGoForward, bool isLoading)? $default,) {final _that = this; +switch (_that) { +case _WebViewNavigationState() when $default != null: +return $default(_that.url,_that.title,_that.canGoBack,_that.canGoForward,_that.isLoading);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _WebViewNavigationState extends WebViewNavigationState { + const _WebViewNavigationState({required this.url, required this.title, required this.canGoBack, required this.canGoForward, required this.isLoading}): super._(); + + +@override final String url; +@override final String title; +@override final bool canGoBack; +@override final bool canGoForward; +@override final bool isLoading; + +/// Create a copy of WebViewNavigationState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WebViewNavigationStateCopyWith<_WebViewNavigationState> get copyWith => __$WebViewNavigationStateCopyWithImpl<_WebViewNavigationState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebViewNavigationState&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.canGoBack, canGoBack) || other.canGoBack == canGoBack)&&(identical(other.canGoForward, canGoForward) || other.canGoForward == canGoForward)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,url,title,canGoBack,canGoForward,isLoading); + +@override +String toString() { + return 'WebViewNavigationState(url: $url, title: $title, canGoBack: $canGoBack, canGoForward: $canGoForward, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class _$WebViewNavigationStateCopyWith<$Res> implements $WebViewNavigationStateCopyWith<$Res> { + factory _$WebViewNavigationStateCopyWith(_WebViewNavigationState value, $Res Function(_WebViewNavigationState) _then) = __$WebViewNavigationStateCopyWithImpl; +@override @useResult +$Res call({ + String url, String title, bool canGoBack, bool canGoForward, bool isLoading +}); + + + + +} +/// @nodoc +class __$WebViewNavigationStateCopyWithImpl<$Res> + implements _$WebViewNavigationStateCopyWith<$Res> { + __$WebViewNavigationStateCopyWithImpl(this._self, this._then); + + final _WebViewNavigationState _self; + final $Res Function(_WebViewNavigationState) _then; + +/// Create a copy of WebViewNavigationState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? url = null,Object? title = null,Object? canGoBack = null,Object? canGoForward = null,Object? isLoading = null,}) { + return _then(_WebViewNavigationState( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,canGoBack: null == canGoBack ? _self.canGoBack : canGoBack // ignore: cast_nullable_to_non_nullable +as bool,canGoForward: null == canGoForward ? _self.canGoForward : canGoForward // ignore: cast_nullable_to_non_nullable +as bool,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/lib/common/rust/api/win32_api.dart b/lib/common/rust/api/win32_api.dart index 7b4de6b..78d9de7 100644 --- a/lib/common/rust/api/win32_api.dart +++ b/lib/common/rust/api/win32_api.dart @@ -6,6 +6,7 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +// These functions are ignored because they are not marked as `pub`: `get_process_path` // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt` Future sendNotify({ diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index 64a9bb9..ba7174b 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -8,6 +8,7 @@ import 'api/http_api.dart'; import 'api/ort_api.dart'; import 'api/rs_process.dart'; import 'api/unp4k_api.dart'; +import 'api/webview_api.dart'; import 'api/win32_api.dart'; import 'dart:async'; import 'dart:convert'; @@ -70,7 +71,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => 1801517256; + int get rustContentHash => -1082688871; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -175,6 +176,62 @@ abstract class RustLibApi extends BaseApi { Future crateApiOrtApiUnloadTranslationModel({required String modelKey}); + Future crateApiWebviewApiWebViewConfigurationDefault(); + + Future + crateApiWebviewApiWebViewNavigationStateDefault(); + + void crateApiWebviewApiWebviewClose({required String id}); + + String crateApiWebviewApiWebviewCreate({ + required WebViewConfiguration config, + }); + + void crateApiWebviewApiWebviewExecuteScript({ + required String id, + required String script, + }); + + WebViewNavigationState crateApiWebviewApiWebviewGetState({ + required String id, + }); + + void crateApiWebviewApiWebviewGoBack({required String id}); + + void crateApiWebviewApiWebviewGoForward({required String id}); + + bool crateApiWebviewApiWebviewIsClosed({required String id}); + + List crateApiWebviewApiWebviewListAll(); + + void crateApiWebviewApiWebviewNavigate({ + required String id, + required String url, + }); + + List crateApiWebviewApiWebviewPollEvents({required String id}); + + void crateApiWebviewApiWebviewReload({required String id}); + + void crateApiWebviewApiWebviewSetVisibility({ + required String id, + required bool visible, + }); + + void crateApiWebviewApiWebviewSetWindowPosition({ + required String id, + required int x, + required int y, + }); + + void crateApiWebviewApiWebviewSetWindowSize({ + required String id, + required int width, + required int height, + }); + + void crateApiWebviewApiWebviewStop({required String id}); + Future crateApiRsProcessWrite({ required int rsPid, required String data, @@ -893,6 +950,451 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["modelKey"], ); + @override + Future crateApiWebviewApiWebViewConfigurationDefault() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire + .wire__crate__api__webview_api__web_view_configuration_default( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_web_view_configuration, + decodeErrorData: null, + ), + constMeta: kCrateApiWebviewApiWebViewConfigurationDefaultConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebViewConfigurationDefaultConstMeta => + const TaskConstMeta( + debugName: "web_view_configuration_default", + argNames: [], + ); + + @override + Future + crateApiWebviewApiWebViewNavigationStateDefault() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire + .wire__crate__api__webview_api__web_view_navigation_state_default( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_web_view_navigation_state, + decodeErrorData: null, + ), + constMeta: kCrateApiWebviewApiWebViewNavigationStateDefaultConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebViewNavigationStateDefaultConstMeta => + const TaskConstMeta( + debugName: "web_view_navigation_state_default", + argNames: [], + ); + + @override + void crateApiWebviewApiWebviewClose({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_close(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewCloseConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewCloseConstMeta => + const TaskConstMeta(debugName: "webview_close", argNames: ["id"]); + + @override + String crateApiWebviewApiWebviewCreate({ + required WebViewConfiguration config, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_box_autoadd_web_view_configuration(config); + return wire.wire__crate__api__webview_api__webview_create(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_String, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewCreateConstMeta, + argValues: [config], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewCreateConstMeta => + const TaskConstMeta(debugName: "webview_create", argNames: ["config"]); + + @override + void crateApiWebviewApiWebviewExecuteScript({ + required String id, + required String script, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + var arg1 = cst_encode_String(script); + return wire.wire__crate__api__webview_api__webview_execute_script( + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewExecuteScriptConstMeta, + argValues: [id, script], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewExecuteScriptConstMeta => + const TaskConstMeta( + debugName: "webview_execute_script", + argNames: ["id", "script"], + ); + + @override + WebViewNavigationState crateApiWebviewApiWebviewGetState({ + required String id, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_get_state(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_web_view_navigation_state, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewGetStateConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewGetStateConstMeta => + const TaskConstMeta(debugName: "webview_get_state", argNames: ["id"]); + + @override + void crateApiWebviewApiWebviewGoBack({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_go_back(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewGoBackConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewGoBackConstMeta => + const TaskConstMeta(debugName: "webview_go_back", argNames: ["id"]); + + @override + void crateApiWebviewApiWebviewGoForward({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_go_forward(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewGoForwardConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewGoForwardConstMeta => + const TaskConstMeta(debugName: "webview_go_forward", argNames: ["id"]); + + @override + bool crateApiWebviewApiWebviewIsClosed({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_is_closed(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_bool, + decodeErrorData: null, + ), + constMeta: kCrateApiWebviewApiWebviewIsClosedConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewIsClosedConstMeta => + const TaskConstMeta(debugName: "webview_is_closed", argNames: ["id"]); + + @override + List crateApiWebviewApiWebviewListAll() { + return handler.executeSync( + SyncTask( + callFfi: () { + return wire.wire__crate__api__webview_api__webview_list_all(); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_list_String, + decodeErrorData: null, + ), + constMeta: kCrateApiWebviewApiWebviewListAllConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewListAllConstMeta => + const TaskConstMeta(debugName: "webview_list_all", argNames: []); + + @override + void crateApiWebviewApiWebviewNavigate({ + required String id, + required String url, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + var arg1 = cst_encode_String(url); + return wire.wire__crate__api__webview_api__webview_navigate( + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewNavigateConstMeta, + argValues: [id, url], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewNavigateConstMeta => + const TaskConstMeta( + debugName: "webview_navigate", + argNames: ["id", "url"], + ); + + @override + List crateApiWebviewApiWebviewPollEvents({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_poll_events(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_list_web_view_event, + decodeErrorData: null, + ), + constMeta: kCrateApiWebviewApiWebviewPollEventsConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewPollEventsConstMeta => + const TaskConstMeta(debugName: "webview_poll_events", argNames: ["id"]); + + @override + void crateApiWebviewApiWebviewReload({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_reload(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewReloadConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewReloadConstMeta => + const TaskConstMeta(debugName: "webview_reload", argNames: ["id"]); + + @override + void crateApiWebviewApiWebviewSetVisibility({ + required String id, + required bool visible, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + var arg1 = cst_encode_bool(visible); + return wire.wire__crate__api__webview_api__webview_set_visibility( + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewSetVisibilityConstMeta, + argValues: [id, visible], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewSetVisibilityConstMeta => + const TaskConstMeta( + debugName: "webview_set_visibility", + argNames: ["id", "visible"], + ); + + @override + void crateApiWebviewApiWebviewSetWindowPosition({ + required String id, + required int x, + required int y, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + var arg1 = cst_encode_i_32(x); + var arg2 = cst_encode_i_32(y); + return wire + .wire__crate__api__webview_api__webview_set_window_position( + arg0, + arg1, + arg2, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewSetWindowPositionConstMeta, + argValues: [id, x, y], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewSetWindowPositionConstMeta => + const TaskConstMeta( + debugName: "webview_set_window_position", + argNames: ["id", "x", "y"], + ); + + @override + void crateApiWebviewApiWebviewSetWindowSize({ + required String id, + required int width, + required int height, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + var arg1 = cst_encode_u_32(width); + var arg2 = cst_encode_u_32(height); + return wire.wire__crate__api__webview_api__webview_set_window_size( + arg0, + arg1, + arg2, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewSetWindowSizeConstMeta, + argValues: [id, width, height], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewSetWindowSizeConstMeta => + const TaskConstMeta( + debugName: "webview_set_window_size", + argNames: ["id", "width", "height"], + ); + + @override + void crateApiWebviewApiWebviewStop({required String id}) { + return handler.executeSync( + SyncTask( + callFfi: () { + var arg0 = cst_encode_String(id); + return wire.wire__crate__api__webview_api__webview_stop(arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_String, + ), + constMeta: kCrateApiWebviewApiWebviewStopConstMeta, + argValues: [id], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWebviewApiWebviewStopConstMeta => + const TaskConstMeta(debugName: "webview_stop", argNames: ["id"]); + @override Future crateApiRsProcessWrite({ required int rsPid, @@ -974,6 +1476,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_u_64(raw); } + @protected + WebViewConfiguration dco_decode_box_autoadd_web_view_configuration( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_web_view_configuration(raw); + } + @protected int dco_decode_i_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1022,6 +1532,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_record_string_string).toList(); } + @protected + List dco_decode_list_web_view_event(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_web_view_event).toList(); + } + @protected MyHttpVersion dco_decode_my_http_version(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1187,6 +1703,59 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dcoDecodeU64(raw); } + @protected + WebViewConfiguration dco_decode_web_view_configuration(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 7) + throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + return WebViewConfiguration( + title: dco_decode_String(arr[0]), + width: dco_decode_u_32(arr[1]), + height: dco_decode_u_32(arr[2]), + userDataFolder: dco_decode_opt_String(arr[3]), + enableDevtools: dco_decode_bool(arr[4]), + transparent: dco_decode_bool(arr[5]), + userAgent: dco_decode_opt_String(arr[6]), + ); + } + + @protected + WebViewEvent dco_decode_web_view_event(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return WebViewEvent_NavigationStarted(url: dco_decode_String(raw[1])); + case 1: + return WebViewEvent_NavigationCompleted(url: dco_decode_String(raw[1])); + case 2: + return WebViewEvent_TitleChanged(title: dco_decode_String(raw[1])); + case 3: + return WebViewEvent_WebMessage(message: dco_decode_String(raw[1])); + case 4: + return WebViewEvent_WindowClosed(); + case 5: + return WebViewEvent_Error(message: dco_decode_String(raw[1])); + default: + throw Exception("unreachable"); + } + } + + @protected + WebViewNavigationState dco_decode_web_view_navigation_state(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 5) + throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return WebViewNavigationState( + url: dco_decode_String(arr[0]), + title: dco_decode_String(arr[1]), + canGoBack: dco_decode_bool(arr[2]), + canGoForward: dco_decode_bool(arr[3]), + isLoading: dco_decode_bool(arr[4]), + ); + } + @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1245,6 +1814,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_u_64(deserializer)); } + @protected + WebViewConfiguration sse_decode_box_autoadd_web_view_configuration( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_web_view_configuration(deserializer)); + } + @protected int sse_decode_i_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1323,6 +1900,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_web_view_event( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_web_view_event(deserializer)); + } + return ans_; + } + @protected MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1525,6 +2116,76 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getBigUint64(); } + @protected + WebViewConfiguration sse_decode_web_view_configuration( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_title = sse_decode_String(deserializer); + var var_width = sse_decode_u_32(deserializer); + var var_height = sse_decode_u_32(deserializer); + var var_userDataFolder = sse_decode_opt_String(deserializer); + var var_enableDevtools = sse_decode_bool(deserializer); + var var_transparent = sse_decode_bool(deserializer); + var var_userAgent = sse_decode_opt_String(deserializer); + return WebViewConfiguration( + title: var_title, + width: var_width, + height: var_height, + userDataFolder: var_userDataFolder, + enableDevtools: var_enableDevtools, + transparent: var_transparent, + userAgent: var_userAgent, + ); + } + + @protected + WebViewEvent sse_decode_web_view_event(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_url = sse_decode_String(deserializer); + return WebViewEvent_NavigationStarted(url: var_url); + case 1: + var var_url = sse_decode_String(deserializer); + return WebViewEvent_NavigationCompleted(url: var_url); + case 2: + var var_title = sse_decode_String(deserializer); + return WebViewEvent_TitleChanged(title: var_title); + case 3: + var var_message = sse_decode_String(deserializer); + return WebViewEvent_WebMessage(message: var_message); + case 4: + return WebViewEvent_WindowClosed(); + case 5: + var var_message = sse_decode_String(deserializer); + return WebViewEvent_Error(message: var_message); + default: + throw UnimplementedError(''); + } + } + + @protected + WebViewNavigationState sse_decode_web_view_navigation_state( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_url = sse_decode_String(deserializer); + var var_title = sse_decode_String(deserializer); + var var_canGoBack = sse_decode_bool(deserializer); + var var_canGoForward = sse_decode_bool(deserializer); + var var_isLoading = sse_decode_bool(deserializer); + return WebViewNavigationState( + url: var_url, + title: var_title, + canGoBack: var_canGoBack, + canGoForward: var_canGoForward, + isLoading: var_isLoading, + ); + } + @protected bool cst_encode_bool(bool raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1650,6 +2311,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_64(self, serializer); } + @protected + void sse_encode_box_autoadd_web_view_configuration( + WebViewConfiguration self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_web_view_configuration(self, serializer); + } + @protected void sse_encode_i_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1729,6 +2399,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_web_view_event( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_web_view_event(item, serializer); + } + } + @protected void sse_encode_my_http_version( MyHttpVersion self, @@ -1908,4 +2590,56 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putBigUint64(self); } + + @protected + void sse_encode_web_view_configuration( + WebViewConfiguration self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.title, serializer); + sse_encode_u_32(self.width, serializer); + sse_encode_u_32(self.height, serializer); + sse_encode_opt_String(self.userDataFolder, serializer); + sse_encode_bool(self.enableDevtools, serializer); + sse_encode_bool(self.transparent, serializer); + sse_encode_opt_String(self.userAgent, serializer); + } + + @protected + void sse_encode_web_view_event(WebViewEvent self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case WebViewEvent_NavigationStarted(url: final url): + sse_encode_i_32(0, serializer); + sse_encode_String(url, serializer); + case WebViewEvent_NavigationCompleted(url: final url): + sse_encode_i_32(1, serializer); + sse_encode_String(url, serializer); + case WebViewEvent_TitleChanged(title: final title): + sse_encode_i_32(2, serializer); + sse_encode_String(title, serializer); + case WebViewEvent_WebMessage(message: final message): + sse_encode_i_32(3, serializer); + sse_encode_String(message, serializer); + case WebViewEvent_WindowClosed(): + sse_encode_i_32(4, serializer); + case WebViewEvent_Error(message: final message): + sse_encode_i_32(5, serializer); + sse_encode_String(message, serializer); + } + } + + @protected + void sse_encode_web_view_navigation_state( + WebViewNavigationState self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.url, serializer); + sse_encode_String(self.title, serializer); + sse_encode_bool(self.canGoBack, serializer); + sse_encode_bool(self.canGoForward, serializer); + sse_encode_bool(self.isLoading, serializer); + } } diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index cee2de8..9bdbb07 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -8,6 +8,7 @@ import 'api/http_api.dart'; import 'api/ort_api.dart'; import 'api/rs_process.dart'; import 'api/unp4k_api.dart'; +import 'api/webview_api.dart'; import 'api/win32_api.dart'; import 'dart:async'; import 'dart:convert'; @@ -51,6 +52,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt dco_decode_box_autoadd_u_64(dynamic raw); + @protected + WebViewConfiguration dco_decode_box_autoadd_web_view_configuration( + dynamic raw, + ); + @protected int dco_decode_i_32(dynamic raw); @@ -75,6 +81,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List<(String, String)> dco_decode_list_record_string_string(dynamic raw); + @protected + List dco_decode_list_web_view_event(dynamic raw); + @protected MyHttpVersion dco_decode_my_http_version(dynamic raw); @@ -135,6 +144,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt dco_decode_usize(dynamic raw); + @protected + WebViewConfiguration dco_decode_web_view_configuration(dynamic raw); + + @protected + WebViewEvent dco_decode_web_view_event(dynamic raw); + + @protected + WebViewNavigationState dco_decode_web_view_navigation_state(dynamic raw); + @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); @@ -166,6 +184,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer); + @protected + WebViewConfiguration sse_decode_box_autoadd_web_view_configuration( + SseDeserializer deserializer, + ); + @protected int sse_decode_i_32(SseDeserializer deserializer); @@ -194,6 +217,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_web_view_event( + SseDeserializer deserializer, + ); + @protected MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer); @@ -264,6 +292,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); + @protected + WebViewConfiguration sse_decode_web_view_configuration( + SseDeserializer deserializer, + ); + + @protected + WebViewEvent sse_decode_web_view_event(SseDeserializer deserializer); + + @protected + WebViewNavigationState sse_decode_web_view_navigation_state( + SseDeserializer deserializer, + ); + @protected ffi.Pointer cst_encode_AnyhowException( AnyhowException raw, @@ -324,6 +365,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return wire.cst_new_box_autoadd_u_64(cst_encode_u_64(raw)); } + @protected + ffi.Pointer + cst_encode_box_autoadd_web_view_configuration(WebViewConfiguration raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_web_view_configuration(); + cst_api_fill_to_wire_web_view_configuration(raw, ptr.ref); + return ptr; + } + @protected int cst_encode_i_64(PlatformInt64 raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -395,6 +445,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ans; } + @protected + ffi.Pointer cst_encode_list_web_view_event( + List raw, + ) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ans = wire.cst_new_list_web_view_event(raw.length); + for (var i = 0; i < raw.length; ++i) { + cst_api_fill_to_wire_web_view_event(raw[i], ans.ref.ptr[i]); + } + return ans; + } + @protected ffi.Pointer cst_encode_opt_Map_String_String_None(Map? raw) { @@ -449,6 +511,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_rsi_launcher_asar_data(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_box_autoadd_web_view_configuration( + WebViewConfiguration apiObj, + ffi.Pointer wireObj, + ) { + cst_api_fill_to_wire_web_view_configuration(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_p_4_k_file_item( P4kFileItem apiObj, @@ -518,6 +588,73 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.data = cst_encode_opt_list_prim_u_8_strict(apiObj.data); } + @protected + void cst_api_fill_to_wire_web_view_configuration( + WebViewConfiguration apiObj, + wire_cst_web_view_configuration wireObj, + ) { + wireObj.title = cst_encode_String(apiObj.title); + wireObj.width = cst_encode_u_32(apiObj.width); + wireObj.height = cst_encode_u_32(apiObj.height); + wireObj.user_data_folder = cst_encode_opt_String(apiObj.userDataFolder); + wireObj.enable_devtools = cst_encode_bool(apiObj.enableDevtools); + wireObj.transparent = cst_encode_bool(apiObj.transparent); + wireObj.user_agent = cst_encode_opt_String(apiObj.userAgent); + } + + @protected + void cst_api_fill_to_wire_web_view_event( + WebViewEvent apiObj, + wire_cst_web_view_event wireObj, + ) { + if (apiObj is WebViewEvent_NavigationStarted) { + var pre_url = cst_encode_String(apiObj.url); + wireObj.tag = 0; + wireObj.kind.NavigationStarted.url = pre_url; + return; + } + if (apiObj is WebViewEvent_NavigationCompleted) { + var pre_url = cst_encode_String(apiObj.url); + wireObj.tag = 1; + wireObj.kind.NavigationCompleted.url = pre_url; + return; + } + if (apiObj is WebViewEvent_TitleChanged) { + var pre_title = cst_encode_String(apiObj.title); + wireObj.tag = 2; + wireObj.kind.TitleChanged.title = pre_title; + return; + } + if (apiObj is WebViewEvent_WebMessage) { + var pre_message = cst_encode_String(apiObj.message); + wireObj.tag = 3; + wireObj.kind.WebMessage.message = pre_message; + return; + } + if (apiObj is WebViewEvent_WindowClosed) { + wireObj.tag = 4; + return; + } + if (apiObj is WebViewEvent_Error) { + var pre_message = cst_encode_String(apiObj.message); + wireObj.tag = 5; + wireObj.kind.Error.message = pre_message; + return; + } + } + + @protected + void cst_api_fill_to_wire_web_view_navigation_state( + WebViewNavigationState apiObj, + wire_cst_web_view_navigation_state wireObj, + ) { + wireObj.url = cst_encode_String(apiObj.url); + wireObj.title = cst_encode_String(apiObj.title); + wireObj.can_go_back = cst_encode_bool(apiObj.canGoBack); + wireObj.can_go_forward = cst_encode_bool(apiObj.canGoForward); + wireObj.is_loading = cst_encode_bool(apiObj.isLoading); + } + @protected bool cst_encode_bool(bool raw); @@ -581,6 +718,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_web_view_configuration( + WebViewConfiguration self, + SseSerializer serializer, + ); + @protected void sse_encode_i_32(int self, SseSerializer serializer); @@ -617,6 +760,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_web_view_event( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_my_http_version(MyHttpVersion self, SseSerializer serializer); @@ -697,6 +846,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_web_view_configuration( + WebViewConfiguration self, + SseSerializer serializer, + ); + + @protected + void sse_encode_web_view_event(WebViewEvent self, SseSerializer serializer); + + @protected + void sse_encode_web_view_navigation_state( + WebViewNavigationState self, + SseSerializer serializer, + ); } // Section: wire_class @@ -1368,6 +1532,416 @@ class RustLibWire implements BaseWire { void Function(int, ffi.Pointer) >(); + void wire__crate__api__webview_api__web_view_configuration_default( + int port_, + ) { + return _wire__crate__api__webview_api__web_view_configuration_default( + port_, + ); + } + + late final _wire__crate__api__webview_api__web_view_configuration_defaultPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_configuration_default', + ); + late final _wire__crate__api__webview_api__web_view_configuration_default = + _wire__crate__api__webview_api__web_view_configuration_defaultPtr + .asFunction(); + + void wire__crate__api__webview_api__web_view_navigation_state_default( + int port_, + ) { + return _wire__crate__api__webview_api__web_view_navigation_state_default( + port_, + ); + } + + late final _wire__crate__api__webview_api__web_view_navigation_state_defaultPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_navigation_state_default', + ); + late final _wire__crate__api__webview_api__web_view_navigation_state_default = + _wire__crate__api__webview_api__web_view_navigation_state_defaultPtr + .asFunction(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_close( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_close(id); + } + + late final _wire__crate__api__webview_api__webview_closePtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_close', + ); + late final _wire__crate__api__webview_api__webview_close = + _wire__crate__api__webview_api__webview_closePtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_create( + ffi.Pointer config, + ) { + return _wire__crate__api__webview_api__webview_create(config); + } + + late final _wire__crate__api__webview_api__webview_createPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_create', + ); + late final _wire__crate__api__webview_api__webview_create = + _wire__crate__api__webview_api__webview_createPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_execute_script( + ffi.Pointer id, + ffi.Pointer script, + ) { + return _wire__crate__api__webview_api__webview_execute_script(id, script); + } + + late final _wire__crate__api__webview_api__webview_execute_scriptPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_execute_script', + ); + late final _wire__crate__api__webview_api__webview_execute_script = + _wire__crate__api__webview_api__webview_execute_scriptPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_get_state( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_get_state(id); + } + + late final _wire__crate__api__webview_api__webview_get_statePtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_get_state', + ); + late final _wire__crate__api__webview_api__webview_get_state = + _wire__crate__api__webview_api__webview_get_statePtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_back( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_go_back(id); + } + + late final _wire__crate__api__webview_api__webview_go_backPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_back', + ); + late final _wire__crate__api__webview_api__webview_go_back = + _wire__crate__api__webview_api__webview_go_backPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_forward( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_go_forward(id); + } + + late final _wire__crate__api__webview_api__webview_go_forwardPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_forward', + ); + late final _wire__crate__api__webview_api__webview_go_forward = + _wire__crate__api__webview_api__webview_go_forwardPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_is_closed( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_is_closed(id); + } + + late final _wire__crate__api__webview_api__webview_is_closedPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_is_closed', + ); + late final _wire__crate__api__webview_api__webview_is_closed = + _wire__crate__api__webview_api__webview_is_closedPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_list_all() { + return _wire__crate__api__webview_api__webview_list_all(); + } + + late final _wire__crate__api__webview_api__webview_list_allPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_list_all', + ); + late final _wire__crate__api__webview_api__webview_list_all = + _wire__crate__api__webview_api__webview_list_allPtr + .asFunction(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_navigate( + ffi.Pointer id, + ffi.Pointer url, + ) { + return _wire__crate__api__webview_api__webview_navigate(id, url); + } + + late final _wire__crate__api__webview_api__webview_navigatePtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_navigate', + ); + late final _wire__crate__api__webview_api__webview_navigate = + _wire__crate__api__webview_api__webview_navigatePtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_poll_events( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_poll_events(id); + } + + late final _wire__crate__api__webview_api__webview_poll_eventsPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_poll_events', + ); + late final _wire__crate__api__webview_api__webview_poll_events = + _wire__crate__api__webview_api__webview_poll_eventsPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_reload( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_reload(id); + } + + late final _wire__crate__api__webview_api__webview_reloadPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_reload', + ); + late final _wire__crate__api__webview_api__webview_reload = + _wire__crate__api__webview_api__webview_reloadPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_visibility( + ffi.Pointer id, + bool visible, + ) { + return _wire__crate__api__webview_api__webview_set_visibility(id, visible); + } + + late final _wire__crate__api__webview_api__webview_set_visibilityPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Bool, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_visibility', + ); + late final _wire__crate__api__webview_api__webview_set_visibility = + _wire__crate__api__webview_api__webview_set_visibilityPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + bool, + ) + >(); + + WireSyncRust2DartDco + wire__crate__api__webview_api__webview_set_window_position( + ffi.Pointer id, + int x, + int y, + ) { + return _wire__crate__api__webview_api__webview_set_window_position( + id, + x, + y, + ); + } + + late final _wire__crate__api__webview_api__webview_set_window_positionPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Int32, + ffi.Int32, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_position', + ); + late final _wire__crate__api__webview_api__webview_set_window_position = + _wire__crate__api__webview_api__webview_set_window_positionPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + int, + int, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_window_size( + ffi.Pointer id, + int width, + int height, + ) { + return _wire__crate__api__webview_api__webview_set_window_size( + id, + width, + height, + ); + } + + late final _wire__crate__api__webview_api__webview_set_window_sizePtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ffi.Uint32, + ffi.Uint32, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_size', + ); + late final _wire__crate__api__webview_api__webview_set_window_size = + _wire__crate__api__webview_api__webview_set_window_sizePtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + int, + int, + ) + >(); + + WireSyncRust2DartDco wire__crate__api__webview_api__webview_stop( + ffi.Pointer id, + ) { + return _wire__crate__api__webview_api__webview_stop(id); + } + + late final _wire__crate__api__webview_api__webview_stopPtr = + _lookup< + ffi.NativeFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_stop', + ); + late final _wire__crate__api__webview_api__webview_stop = + _wire__crate__api__webview_api__webview_stopPtr + .asFunction< + WireSyncRust2DartDco Function( + ffi.Pointer, + ) + >(); + void wire__crate__api__rs_process__write( int port_, int rs_pid, @@ -1431,6 +2005,23 @@ class RustLibWire implements BaseWire { late final _cst_new_box_autoadd_u_64 = _cst_new_box_autoadd_u_64Ptr .asFunction Function(int)>(); + ffi.Pointer + cst_new_box_autoadd_web_view_configuration() { + return _cst_new_box_autoadd_web_view_configuration(); + } + + late final _cst_new_box_autoadd_web_view_configurationPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function() + > + >('frbgen_starcitizen_doctor_cst_new_box_autoadd_web_view_configuration'); + late final _cst_new_box_autoadd_web_view_configuration = + _cst_new_box_autoadd_web_view_configurationPtr + .asFunction< + ffi.Pointer Function() + >(); + ffi.Pointer cst_new_list_String(int len) { return _cst_new_list_String(len); } @@ -1519,6 +2110,21 @@ class RustLibWire implements BaseWire { ffi.Pointer Function(int) >(); + ffi.Pointer cst_new_list_web_view_event( + int len, + ) { + return _cst_new_list_web_view_event(len); + } + + late final _cst_new_list_web_view_eventPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Int32) + > + >('frbgen_starcitizen_doctor_cst_new_list_web_view_event'); + late final _cst_new_list_web_view_event = _cst_new_list_web_view_eventPtr + .asFunction Function(int)>(); + int dummy_method_to_enforce_bundling() { return _dummy_method_to_enforce_bundling(); } @@ -1582,6 +2188,26 @@ final class wire_cst_list_prim_u_8_loose extends ffi.Struct { external int len; } +final class wire_cst_web_view_configuration extends ffi.Struct { + external ffi.Pointer title; + + @ffi.Uint32() + external int width; + + @ffi.Uint32() + external int height; + + external ffi.Pointer user_data_folder; + + @ffi.Bool() + external bool enable_devtools; + + @ffi.Bool() + external bool transparent; + + external ffi.Pointer user_agent; +} + final class wire_cst_p_4_k_file_item extends ffi.Struct { external ffi.Pointer name; @@ -1621,6 +2247,52 @@ final class wire_cst_list_process_info extends ffi.Struct { external int len; } +final class wire_cst_WebViewEvent_NavigationStarted extends ffi.Struct { + external ffi.Pointer url; +} + +final class wire_cst_WebViewEvent_NavigationCompleted extends ffi.Struct { + external ffi.Pointer url; +} + +final class wire_cst_WebViewEvent_TitleChanged extends ffi.Struct { + external ffi.Pointer title; +} + +final class wire_cst_WebViewEvent_WebMessage extends ffi.Struct { + external ffi.Pointer message; +} + +final class wire_cst_WebViewEvent_Error extends ffi.Struct { + external ffi.Pointer message; +} + +final class WebViewEventKind extends ffi.Union { + external wire_cst_WebViewEvent_NavigationStarted NavigationStarted; + + external wire_cst_WebViewEvent_NavigationCompleted NavigationCompleted; + + external wire_cst_WebViewEvent_TitleChanged TitleChanged; + + external wire_cst_WebViewEvent_WebMessage WebMessage; + + external wire_cst_WebViewEvent_Error Error; +} + +final class wire_cst_web_view_event extends ffi.Struct { + @ffi.Int32() + external int tag; + + external WebViewEventKind kind; +} + +final class wire_cst_list_web_view_event extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_cst_rs_process_stream_data extends ffi.Struct { @ffi.Int32() external int data_type; @@ -1648,3 +2320,18 @@ final class wire_cst_rust_http_response extends ffi.Struct { external ffi.Pointer data; } + +final class wire_cst_web_view_navigation_state extends ffi.Struct { + external ffi.Pointer url; + + external ffi.Pointer title; + + @ffi.Bool() + external bool can_go_back; + + @ffi.Bool() + external bool can_go_forward; + + @ffi.Bool() + external bool is_loading; +} diff --git a/lib/common/rust/rust_webview_controller.dart b/lib/common/rust/rust_webview_controller.dart new file mode 100644 index 0000000..43fd6fb --- /dev/null +++ b/lib/common/rust/rust_webview_controller.dart @@ -0,0 +1,350 @@ +// Rust WebView 管理器 +// 使用 wry + tao 实现的 WebView 窗口管理 + +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:starcitizen_doctor/common/rust/api/webview_api.dart' + as rust_webview; +import 'package:starcitizen_doctor/common/utils/log.dart'; + +typedef OnWebMessageCallback = void Function(String message); +typedef OnNavigationCallback = void Function(String url); +typedef OnNavigationCompletedCallback = void Function(String url); +typedef OnWindowClosedCallback = void Function(); + +/// Rust WebView 控制器 +/// 封装 Rust wry + tao WebView 的业务逻辑 +class RustWebViewController { + final String id; + final List _messageCallbacks = []; + final List _navigationCallbacks = []; + final List _navigationCompletedCallbacks = []; + final List _closeCallbacks = []; + + Timer? _pollTimer; + bool _isDisposed = false; + + /// 本地化脚本(从 assets 加载) + String _localizationScript = ""; + + /// 请求拦截器脚本 + String _requestInterceptorScript = ""; + + /// 当前 URL + String _currentUrl = ""; + String get currentUrl => _currentUrl; + + RustWebViewController._(this.id); + + /// 创建新的 WebView 窗口 + static Future create({ + String title = "WebView", + int width = 1280, + int height = 720, + String? userDataFolder, + bool enableDevtools = false, + bool transparent = false, + String? userAgent, + }) async { + try { + final config = rust_webview.WebViewConfiguration( + title: title, + width: width, + height: height, + userDataFolder: userDataFolder, + enableDevtools: enableDevtools, + transparent: transparent, + userAgent: userAgent, + ); + + final id = rust_webview.webviewCreate(config: config); + final controller = RustWebViewController._(id); + + // 加载脚本资源 + await controller._loadScripts(); + + // 启动事件轮询 + controller._startEventPolling(); + + return controller; + } catch (e) { + throw Exception("Failed to create WebView: $e"); + } + } + + /// 加载本地化和拦截器脚本 + Future _loadScripts() async { + try { + _localizationScript = await rootBundle.loadString('assets/web_script.js'); + _requestInterceptorScript = await rootBundle.loadString( + 'assets/request_interceptor.js', + ); + } catch (e) { + dPrint("Failed to load scripts: $e"); + } + } + + /// 启动事件轮询 + void _startEventPolling() { + _pollTimer = Timer.periodic(const Duration(milliseconds: 50), (_) { + if (_isDisposed) return; + _pollEvents(); + }); + } + + /// 轮询事件 + void _pollEvents() { + try { + final events = rust_webview.webviewPollEvents(id: id); + for (final event in events) { + _handleEvent(event); + } + } catch (e) { + // WebView 可能已关闭 + if (!_isDisposed) { + dPrint("Error polling events: $e"); + } + } + } + + /// 处理事件 + void _handleEvent(rust_webview.WebViewEvent event) { + switch (event) { + case rust_webview.WebViewEvent_NavigationStarted(:final url): + dPrint("Navigation started: $url"); + _currentUrl = url; + // 导航开始时通知 + for (final callback in _navigationCallbacks) { + callback(url); + } + break; + + case rust_webview.WebViewEvent_NavigationCompleted(:final url): + dPrint("Navigation completed: $url"); + _currentUrl = url; + // 注入请求拦截器 + if (_requestInterceptorScript.isNotEmpty) { + executeScript(_requestInterceptorScript); + } + // 导航完成回调(用于注入脚本) + for (final callback in _navigationCompletedCallbacks) { + callback(url); + } + for (final callback in _navigationCallbacks) { + callback(url); + } + break; + + case rust_webview.WebViewEvent_TitleChanged(:final title): + dPrint("Title changed: $title"); + break; + + case rust_webview.WebViewEvent_WebMessage(:final message): + _handleWebMessage(message); + break; + + case rust_webview.WebViewEvent_WindowClosed(): + dPrint("Window closed"); + for (final callback in _closeCallbacks) { + callback(); + } + dispose(); + break; + + case rust_webview.WebViewEvent_Error(:final message): + dPrint("WebView error: $message"); + break; + } + } + + /// 处理来自 WebView 的消息 + void _handleWebMessage(String message) { + dPrint("Web message: $message"); + try { + final data = json.decode(message); + final action = data["action"]; + + switch (action) { + case "navigation_state": + // 从 JS 获取导航状态更新 + final url = data["url"] ?? ""; + final isLoading = data["isLoading"] ?? false; + _currentUrl = url; + if (!isLoading) { + for (final callback in _navigationCallbacks) { + callback(url); + } + } + break; + case "close_window": + // 处理来自导航栏的关闭请求 + close(); + break; + default: + // 转发其他消息给回调 + for (final callback in _messageCallbacks) { + callback(message); + } + } + } catch (e) { + // 非 JSON 消息,直接转发 + for (final callback in _messageCallbacks) { + callback(message); + } + } + } + + /// 导航到 URL + void navigate(String url) { + if (_isDisposed) return; + _currentUrl = url; + rust_webview.webviewNavigate(id: id, url: url); + } + + /// 后退 + void goBack() { + if (_isDisposed) return; + rust_webview.webviewGoBack(id: id); + } + + /// 前进 + void goForward() { + if (_isDisposed) return; + rust_webview.webviewGoForward(id: id); + } + + /// 刷新 + void reload() { + if (_isDisposed) return; + rust_webview.webviewReload(id: id); + } + + /// 停止加载 + void stop() { + if (_isDisposed) return; + rust_webview.webviewStop(id: id); + } + + /// 执行 JavaScript + void executeScript(String script) { + if (_isDisposed) return; + rust_webview.webviewExecuteScript(id: id, script: script); + } + + /// 设置窗口可见性 + void setVisible(bool visible) { + if (_isDisposed) return; + rust_webview.webviewSetVisibility(id: id, visible: visible); + } + + /// 关闭窗口 + void close() { + if (_isDisposed) return; + rust_webview.webviewClose(id: id); + dispose(); + } + + /// 设置窗口大小 + void setWindowSize(int width, int height) { + if (_isDisposed) return; + rust_webview.webviewSetWindowSize(id: id, width: width, height: height); + } + + /// 设置窗口位置 + void setWindowPosition(int x, int y) { + if (_isDisposed) return; + rust_webview.webviewSetWindowPosition(id: id, x: x, y: y); + } + + /// 获取当前导航状态 + rust_webview.WebViewNavigationState getState() { + return rust_webview.webviewGetState(id: id); + } + + /// 检查窗口是否已关闭 + bool get isClosed { + if (_isDisposed) return true; + return rust_webview.webviewIsClosed(id: id); + } + + /// 添加消息回调 + void addOnWebMessageCallback(OnWebMessageCallback callback) { + _messageCallbacks.add(callback); + } + + /// 移除消息回调 + void removeOnWebMessageCallback(OnWebMessageCallback callback) { + _messageCallbacks.remove(callback); + } + + /// 添加导航回调 + void addOnNavigationCallback(OnNavigationCallback callback) { + _navigationCallbacks.add(callback); + } + + /// 移除导航回调 + void removeOnNavigationCallback(OnNavigationCallback callback) { + _navigationCallbacks.remove(callback); + } + + /// 添加导航完成回调(用于在页面加载完成后注入脚本) + void addOnNavigationCompletedCallback( + OnNavigationCompletedCallback callback, + ) { + _navigationCompletedCallbacks.add(callback); + } + + /// 移除导航完成回调 + void removeOnNavigationCompletedCallback( + OnNavigationCompletedCallback callback, + ) { + _navigationCompletedCallbacks.remove(callback); + } + + /// 添加关闭回调 + void addOnCloseCallback(OnWindowClosedCallback callback) { + _closeCallbacks.add(callback); + } + + /// 移除关闭回调 + void removeOnCloseCallback(OnWindowClosedCallback callback) { + _closeCallbacks.remove(callback); + } + + /// 注入本地化脚本 + void injectLocalizationScript() { + if (_localizationScript.isNotEmpty) { + executeScript(_localizationScript); + } + } + + /// 初始化网页本地化 + void initWebLocalization() { + executeScript("InitWebLocalization()"); + } + + /// 更新翻译词典 + void updateReplaceWords(List> words, bool enableCapture) { + final jsonWords = json.encode(words); + executeScript( + "WebLocalizationUpdateReplaceWords($jsonWords, $enableCapture)", + ); + } + + /// 执行 RSI 登录脚本 + void executeRsiLogin(String channel) { + executeScript('getRSILauncherToken("$channel");'); + } + + /// 释放资源 + void dispose() { + if (_isDisposed) return; + _isDisposed = true; + _pollTimer?.cancel(); + _messageCallbacks.clear(); + _navigationCallbacks.clear(); + _navigationCompletedCallbacks.clear(); + _closeCallbacks.clear(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 332a3a9..6a03acc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -12,15 +11,6 @@ import 'app.dart'; import 'common/utils/multi_window_manager.dart'; Future main(List args) async { - // webview window - if (runWebViewTitleBarWidget( - args, - backgroundColor: const Color.fromRGBO(19, 36, 49, 1), - builder: _defaultWebviewTitleBar, - )) { - return; - } - WidgetsFlutterBinding.ensureInitialized(); await windowManager.ensureInitialized(); @@ -133,33 +123,3 @@ class App extends HookConsumerWidget with WindowListener { super.onWindowClose(); } } - -Widget _defaultWebviewTitleBar(BuildContext context) { - final state = TitleBarWebViewState.of(context); - final controller = TitleBarWebViewController.of(context); - return FluentTheme( - data: FluentThemeData.dark(), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (Platform.isMacOS) const SizedBox(width: 96), - IconButton(onPressed: !state.canGoBack ? null : controller.back, icon: const Icon(FluentIcons.chevron_left)), - const SizedBox(width: 12), - IconButton( - onPressed: !state.canGoForward ? null : controller.forward, - icon: const Icon(FluentIcons.chevron_right), - ), - const SizedBox(width: 12), - if (state.isLoading) - IconButton(onPressed: controller.stop, icon: const Icon(FluentIcons.chrome_close)) - else - IconButton(onPressed: controller.reload, icon: const Icon(FluentIcons.refresh)), - const SizedBox(width: 12), - (state.isLoading) ? const SizedBox(width: 24, height: 24, child: ProgressRing()) : const SizedBox(width: 24), - const SizedBox(width: 12), - SelectableText(state.url ?? ""), - const Spacer(), - ], - ), - ); -} diff --git a/lib/provider/unp4kc.g.dart b/lib/provider/unp4kc.g.dart index 97991bb..5f6c40e 100644 --- a/lib/provider/unp4kc.g.dart +++ b/lib/provider/unp4kc.g.dart @@ -41,7 +41,7 @@ final class Unp4kCModelProvider } } -String _$unp4kCModelHash() => r'b46274b1409dc904db2d96acf692869edf034b9f'; +String _$unp4kCModelHash() => r'72ee23ad9864cdfb73a588ea1a0509b083e7dee8'; abstract class _$Unp4kCModel extends $Notifier { Unp4kcState build(); diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart index 6555ac3..a40fabe 100644 --- a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart @@ -5,7 +5,6 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive_ce/hive.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; import 'package:starcitizen_doctor/common/utils/base_utils.dart'; @@ -14,7 +13,6 @@ import 'package:starcitizen_doctor/common/utils/provider.dart'; import 'package:starcitizen_doctor/data/rsi_game_library_data.dart'; import 'package:starcitizen_doctor/ui/home/home_ui_model.dart'; import 'package:starcitizen_doctor/ui/webview/webview.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:uuid/uuid.dart'; part 'home_game_login_dialog_ui_model.freezed.dart'; @@ -47,82 +45,87 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { Future launchWebLogin(BuildContext context) async { final homeState = ref.read(homeUIModelProvider); if (!context.mounted) return; - goWebView(context, S.current.home_action_login_rsi_account, - "https://robertsspaceindustries.com/en/connect?jumpto=/account/dashboard", - loginMode: true, rsiLoginCallback: (message, ok) async { - // dPrint( - // "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}"); - if (message == null || !ok) { - Navigator.pop(context); - return; - } + goWebView( + context, + S.current.home_action_login_rsi_account, + "https://robertsspaceindustries.com/en/connect?jumpto=/account/dashboard", + loginMode: true, + rsiLoginCallback: (message, ok) async { + // dPrint( + // "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}"); + if (message == null || !ok) { + Navigator.pop(context); + return; + } - // final emailBox = await Hive.openBox("quick_login_email"); - final data = message["data"]; - final authToken = data["authToken"]; - final webToken = data["webToken"]; - final releaseInfo = data["releaseInfo"]; - final libraryData = RsiGameLibraryData.fromJson(data["libraryData"]); - var avatarUrl = data["avatar"] - ?.toString() - .replaceAll("url(\"", "") - .replaceAll("\")", ""); - if (avatarUrl?.startsWith("/") ?? false) { - avatarUrl = "https://robertsspaceindustries.com$avatarUrl"; - } - final Map payload = Jwt.parseJwt(authToken!); - final nickname = payload["nickname"] ?? ""; + // final emailBox = await Hive.openBox("quick_login_email"); + final data = message["data"]; + final authToken = data["authToken"]; + final webToken = data["webToken"]; + final releaseInfo = data["releaseInfo"]; + final libraryData = RsiGameLibraryData.fromJson(data["libraryData"]); + var avatarUrl = data["avatar"]?.toString().replaceAll("url(\"", "").replaceAll("\")", ""); + if (avatarUrl?.startsWith("/") ?? false) { + avatarUrl = "https://robertsspaceindustries.com$avatarUrl"; + } + final Map payload = Jwt.parseJwt(authToken!); + final nickname = payload["nickname"] ?? ""; - state = state.copyWith( - nickname: nickname, - avatarUrl: avatarUrl, - authToken: authToken, - webToken: webToken, - releaseInfo: releaseInfo, - libraryData: libraryData, - ); + state = state.copyWith( + nickname: nickname, + avatarUrl: avatarUrl, + authToken: authToken, + webToken: webToken, + releaseInfo: releaseInfo, + libraryData: libraryData, + ); - final buildInfoFile = - File("${homeState.scInstalledPath}\\build_manifest.id"); - if (await buildInfoFile.exists()) { - final buildInfo = - json.decode(await buildInfoFile.readAsString())["Data"]; + final buildInfoFile = File("${homeState.scInstalledPath}\\build_manifest.id"); + if (await buildInfoFile.exists()) { + final buildInfo = json.decode(await buildInfoFile.readAsString())["Data"]; - if (releaseInfo?["versionLabel"] != null && - buildInfo["RequestedP4ChangeNum"] != null) { - if (!(releaseInfo!["versionLabel"]! - .toString() - .endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) { - if (!context.mounted) return; - final ok = await showConfirmDialogs( + if (releaseInfo?["versionLabel"] != null && buildInfo["RequestedP4ChangeNum"] != null) { + if (!(releaseInfo!["versionLabel"]!.toString().endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) { + if (!context.mounted) return; + final ok = await showConfirmDialogs( context, S.current.home_login_info_game_version_outdated, - Text(S.current.home_login_info_rsi_server_report( + Text( + S.current.home_login_info_rsi_server_report( releaseInfo?["versionLabel"], - buildInfo["RequestedP4ChangeNum"])), - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * .4), - cancel: S.current.home_login_info_action_ignore); - if (ok == true) { - if (!context.mounted) return; - Navigator.pop(context); - return; + buildInfo["RequestedP4ChangeNum"], + ), + ), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .4), + cancel: S.current.home_login_info_action_ignore, + ); + if (ok == true) { + if (!context.mounted) return; + Navigator.pop(context); + return; + } } } } - } - if (!context.mounted) return; - _readyForLaunch(homeState, context); - }, useLocalization: true, homeState: homeState); + if (!context.mounted) return; + _readyForLaunch(homeState, context); + }, + useLocalization: true, + homeState: homeState, + ); } // ignore: avoid_build_context_in_providers - Future goWebView(BuildContext context, String title, String url, - {bool useLocalization = false, - bool loginMode = false, - RsiLoginCallback? rsiLoginCallback, - required HomeUIModelState homeState}) async { + Future goWebView( + BuildContext context, + String title, + String url, { + bool useLocalization = false, + bool loginMode = false, + RsiLoginCallback? rsiLoginCallback, + required HomeUIModelState homeState, + }) async { if (useLocalization) { const tipVersion = 2; final box = await Hive.openBox("app_conf"); @@ -130,14 +133,11 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { if (skip != tipVersion) { if (!context.mounted) return; final ok = await showConfirmDialogs( - context, - S.current.home_login_action_title_box_one_click_launch, - Text( - S.current.home_login_info_one_click_launch_description, - style: const TextStyle(fontSize: 16), - ), - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * .6)); + context, + S.current.home_login_action_title_box_one_click_launch, + Text(S.current.home_login_info_one_click_launch_description, style: const TextStyle(fontSize: 16)), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .6), + ); if (!ok) { if (loginMode) { rsiLoginCallback?.call(null, false); @@ -147,26 +147,17 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { await box.put("skip_web_login_version", tipVersion); } } - if (!await WebviewWindow.isWebviewAvailable()) { - if (!context.mounted) return; - await showToast( - context, S.current.home_login_action_title_need_webview2_runtime); - if (!context.mounted) return; - await launchUrlString( - "https://developer.microsoft.com/en-us/microsoft-edge/webview2/"); - if (!context.mounted) return; - Navigator.pop(context); - return; - } + // Rust WebView using wry + tao - no WebView2 runtime check needed as wry handles it internally if (!context.mounted) return; - final webViewModel = WebViewModel(context, - loginMode: loginMode, - loginCallback: rsiLoginCallback, - loginChannel: getChannelID(homeState.scInstalledPath!)); + final webViewModel = WebViewModel( + context, + loginMode: loginMode, + loginCallback: rsiLoginCallback, + loginChannel: getChannelID(homeState.scInstalledPath!), + ); if (useLocalization) { try { - await webViewModel - .initLocalization(homeState.webLocalizationVersionsData!); + await webViewModel.initLocalization(homeState.webLocalizationVersionsData!); } catch (_) {} } await Future.delayed(const Duration(milliseconds: 500)); @@ -179,9 +170,10 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { } Future _readyForLaunch( - HomeUIModelState homeState, - // ignore: avoid_build_context_in_providers - BuildContext context) async { + HomeUIModelState homeState, + // ignore: avoid_build_context_in_providers + BuildContext context, + ) async { final userBox = await Hive.openBox("rsi_account_data"); state = state.copyWith(loginStatus: 2); final launchData = { @@ -211,11 +203,12 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { final homeUIModel = ref.read(homeUIModelProvider.notifier); if (!context.mounted) return; homeUIModel.doLaunchGame( - context, - '${homeState.scInstalledPath}\\$executable', - ["-no_login_dialog", ...launchOptions.toString().split(" ")], - homeState.scInstalledPath!, - processorAffinity); + context, + '${homeState.scInstalledPath}\\$executable', + ["-no_login_dialog", ...launchOptions.toString().split(" ")], + homeState.scInstalledPath!, + processorAffinity, + ); await Future.delayed(const Duration(seconds: 1)); if (!context.mounted) return; Navigator.pop(context); diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart index e68ab86..2b72d98 100644 --- a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart @@ -42,7 +42,7 @@ final class HomeGameLoginUIModelProvider } String _$homeGameLoginUIModelHash() => - r'c9e9ec2e85f2459b6bfc1518406b091ff4675a85'; + r'217a57f797b37f3467be2e7711f220610e9e67d8'; abstract class _$HomeGameLoginUIModel extends $Notifier { HomeGameLoginState build(); diff --git a/lib/ui/home/home_ui_model.dart b/lib/ui/home/home_ui_model.dart index 38e1105..2b5938d 100644 --- a/lib/ui/home/home_ui_model.dart +++ b/lib/ui/home/home_ui_model.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive_ce/hive.dart'; @@ -164,12 +163,7 @@ class HomeUIModel extends _$HomeUIModel { await box.put("skip_web_localization_tip_version", tipVersion); } } - if (!await WebviewWindow.isWebviewAvailable()) { - if (!context.mounted) return; - showToast(context, S.current.home_login_action_title_need_webview2_runtime); - launchUrlString("https://developer.microsoft.com/en-us/microsoft-edge/webview2/"); - return; - } + // Rust WebView using wry + tao - no WebView2 runtime check needed as wry handles it internally if (!context.mounted) return; final webViewModel = WebViewModel(context, loginMode: loginMode, loginCallback: rsiLoginCallback); if (useLocalization) { diff --git a/lib/ui/home/home_ui_model.g.dart b/lib/ui/home/home_ui_model.g.dart index 41f41b5..2b283cf 100644 --- a/lib/ui/home/home_ui_model.g.dart +++ b/lib/ui/home/home_ui_model.g.dart @@ -41,7 +41,7 @@ final class HomeUIModelProvider } } -String _$homeUIModelHash() => r'7dfe73383f7be2e520a42d176e199a8db208f008'; +String _$homeUIModelHash() => r'cc795e27213d02993459dd711de4a897c8491575'; abstract class _$HomeUIModel extends $Notifier { HomeUIModelState build(); diff --git a/lib/ui/tools/tools_ui_model.g.dart b/lib/ui/tools/tools_ui_model.g.dart index 23d0d07..462698f 100644 --- a/lib/ui/tools/tools_ui_model.g.dart +++ b/lib/ui/tools/tools_ui_model.g.dart @@ -41,7 +41,7 @@ final class ToolsUIModelProvider } } -String _$toolsUIModelHash() => r'78732ff16e87cc9f92174bda43d0fafadba51146'; +String _$toolsUIModelHash() => r'ee1de3d555443f72b4fbb395a5728b2de1e8aaf4'; abstract class _$ToolsUIModel extends $Notifier { ToolsUIState build(); diff --git a/lib/ui/webview/webview.dart b/lib/ui/webview/webview.dart index edbe2f0..44c3fd4 100644 --- a/lib/ui/webview/webview.dart +++ b/lib/ui/webview/webview.dart @@ -4,22 +4,24 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:hive_ce/hive.dart'; import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/io/rs_http.dart'; +import 'package:starcitizen_doctor/common/rust/rust_webview_controller.dart'; import 'package:starcitizen_doctor/common/utils/base_utils.dart'; import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/data/app_version_data.dart'; import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart'; typedef RsiLoginCallback = void Function(Map? data, bool success); +typedef OnWebMessageReceivedCallback = void Function(String message); class WebViewModel { - late Webview webview; + late RustWebViewController webview; final BuildContext context; bool _isClosed = false; @@ -51,6 +53,8 @@ class WebViewModel { final RsiLoginCallback? loginCallback; + late AppVersionData _appVersionData; + Future initWebView({ String title = "", required String applicationSupportDir, @@ -59,114 +63,35 @@ class WebViewModel { try { final userBox = await Hive.openBox("app_conf"); isEnableToolSiteMirrors = userBox.get("isEnableToolSiteMirrors", defaultValue: false); - webview = await WebviewWindow.create( - configuration: CreateConfiguration( - windowWidth: loginMode ? 960 : 1920, - windowHeight: loginMode ? 720 : 1080, - userDataFolderWindows: "$applicationSupportDir/webview_data", - title: Platform.isMacOS ? "" : title, - ), + _appVersionData = appVersionData; + + webview = await RustWebViewController.create( + title: Platform.isMacOS ? "" : title, + width: loginMode ? 960 : 1920, + height: loginMode ? 720 : 1080, + userDataFolder: "$applicationSupportDir/webview_data", + enableDevtools: kDebugMode, ); - // webview.openDevToolsWindow(); - webview.isNavigating.addListener(() async { - if (!webview.isNavigating.value && localizationResource.isNotEmpty) { - dPrint("webview Navigating url === $url"); - if (url.contains("robertsspaceindustries.com")) { - // SC 官网 - final replaceWords = _getLocalizationResource("zh-CN"); - const org = "https://robertsspaceindustries.com/orgs"; - const citizens = "https://robertsspaceindustries.com/citizens"; - const organization = "https://robertsspaceindustries.com/account/organization"; - const concierge = "https://robertsspaceindustries.com/account/concierge"; - const referral = "https://robertsspaceindustries.com/account/referral-program"; - const address = "https://robertsspaceindustries.com/account/addresses"; - const hangar = "https://robertsspaceindustries.com/account/pledges"; + // 添加导航完成回调(用于注入脚本) + webview.addOnNavigationCompletedCallback(_onNavigationCompleted); - const spectrum = "https://robertsspaceindustries.com/spectrum"; - // 跳过光谱论坛 https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx/issues/1 - if (url.startsWith(spectrum)) { - return; - } + // 添加关闭回调 + webview.addOnCloseCallback(dispose); - dPrint("load script"); - await Future.delayed(const Duration(milliseconds: 100)); - await webview.evaluateJavaScript(localizationScript); - - if (url.startsWith(org) || url.startsWith(citizens) || url.startsWith(organization)) { - replaceWords.add({"word": 'members', "replacement": S.current.webview_localization_name_member}); - replaceWords.addAll(_getLocalizationResource("orgs")); - } - - if (address.startsWith(address)) { - replaceWords.addAll(_getLocalizationResource("address")); - } - - if (url.startsWith(referral)) { - replaceWords.addAll([ - {"word": 'Total recruits: ', "replacement": S.current.webview_localization_total_invitations}, - {"word": 'Prospects ', "replacement": S.current.webview_localization_unfinished_invitations}, - {"word": 'Recruits', "replacement": S.current.webview_localization_finished_invitations}, - ]); - } - - if (url.startsWith(concierge)) { - replaceWords.clear(); - replaceWords.addAll(_getLocalizationResource("concierge")); - } - if (url.startsWith(hangar)) { - replaceWords.addAll(_getLocalizationResource("hangar")); - } - - _curReplaceWords = {}; - for (var element in replaceWords) { - _curReplaceWords?[element["word"] ?? ""] = element["replacement"] ?? ""; - } - await webview.evaluateJavaScript("InitWebLocalization()"); - await Future.delayed(const Duration(milliseconds: 100)); - dPrint("update replaceWords"); - await webview.evaluateJavaScript( - "WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)", - ); - - /// loginMode - if (loginMode) { - dPrint("--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");"); - await Future.delayed(const Duration(milliseconds: 200)); - webview.evaluateJavaScript("getRSILauncherToken(\"$loginChannel\");"); - } - } else if (url.startsWith(await _handleMirrorsUrl("https://www.erkul.games", appVersionData))) { - dPrint("load script"); - await Future.delayed(const Duration(milliseconds: 100)); - await webview.evaluateJavaScript(localizationScript); - dPrint("update replaceWords"); - final replaceWords = _getLocalizationResource("DPS"); - await webview.evaluateJavaScript( - "WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)", - ); - } else if (url.startsWith(await _handleMirrorsUrl("https://uexcorp.space", appVersionData))) { - dPrint("load script"); - await Future.delayed(const Duration(milliseconds: 100)); - await webview.evaluateJavaScript(localizationScript); - dPrint("update replaceWords"); - final replaceWords = _getLocalizationResource("UEX"); - await webview.evaluateJavaScript( - "WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)", - ); - } - } - }); - webview.addOnUrlRequestCallback(_onUrlRequest); - webview.onClose.whenComplete(dispose); if (loginMode) { - webview.addOnWebMessageReceivedCallback((messageString) { - final message = json.decode(messageString); - if (message["action"] == "webview_rsi_login_show_window") { - webview.setWebviewWindowVisibility(true); - } else if (message["action"] == "webview_rsi_login_success") { - _loginModeSuccess = true; - loginCallback?.call(message, true); - webview.close(); + webview.addOnWebMessageCallback((messageString) { + try { + final message = json.decode(messageString); + if (message["action"] == "webview_rsi_login_show_window") { + webview.setVisible(true); + } else if (message["action"] == "webview_rsi_login_success") { + _loginModeSuccess = true; + loginCallback?.call(message, true); + webview.close(); + } + } catch (e) { + dPrint("Error parsing login message: $e"); } }); } @@ -175,6 +100,98 @@ class WebViewModel { } } + Future _onNavigationCompleted(String newUrl) async { + dPrint("Navigation completed: $newUrl"); + url = newUrl; + + // 在页面加载时注入拦截器 + if (requestInterceptorScript.isNotEmpty) { + dPrint("Injecting request interceptor for: $url"); + webview.executeScript(requestInterceptorScript); + } + + if (localizationResource.isEmpty) return; + + dPrint("webview Navigating url === $url"); + if (url.contains("robertsspaceindustries.com")) { + // SC 官网 + final replaceWords = _getLocalizationResource("zh-CN"); + const org = "https://robertsspaceindustries.com/orgs"; + const citizens = "https://robertsspaceindustries.com/citizens"; + const organization = "https://robertsspaceindustries.com/account/organization"; + const concierge = "https://robertsspaceindustries.com/account/concierge"; + const referral = "https://robertsspaceindustries.com/account/referral-program"; + const address = "https://robertsspaceindustries.com/account/addresses"; + + const hangar = "https://robertsspaceindustries.com/account/pledges"; + + const spectrum = "https://robertsspaceindustries.com/spectrum"; + // 跳过光谱论坛 https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx/issues/1 + if (url.startsWith(spectrum)) { + return; + } + + dPrint("load script"); + await Future.delayed(const Duration(milliseconds: 100)); + webview.injectLocalizationScript(); + + if (url.startsWith(org) || url.startsWith(citizens) || url.startsWith(organization)) { + replaceWords.add({"word": 'members', "replacement": S.current.webview_localization_name_member}); + replaceWords.addAll(_getLocalizationResource("orgs")); + } + + if (address.startsWith(address)) { + replaceWords.addAll(_getLocalizationResource("address")); + } + + if (url.startsWith(referral)) { + replaceWords.addAll([ + {"word": 'Total recruits: ', "replacement": S.current.webview_localization_total_invitations}, + {"word": 'Prospects ', "replacement": S.current.webview_localization_unfinished_invitations}, + {"word": 'Recruits', "replacement": S.current.webview_localization_finished_invitations}, + ]); + } + + if (url.startsWith(concierge)) { + replaceWords.clear(); + replaceWords.addAll(_getLocalizationResource("concierge")); + } + if (url.startsWith(hangar)) { + replaceWords.addAll(_getLocalizationResource("hangar")); + } + + _curReplaceWords = {}; + for (var element in replaceWords) { + _curReplaceWords?[element["word"] ?? ""] = element["replacement"] ?? ""; + } + webview.initWebLocalization(); + await Future.delayed(const Duration(milliseconds: 100)); + dPrint("update replaceWords"); + webview.updateReplaceWords(replaceWords, enableCapture); + + /// loginMode + if (loginMode) { + dPrint("--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");"); + await Future.delayed(const Duration(milliseconds: 200)); + webview.executeRsiLogin(loginChannel); + } + } else if (url.startsWith(await _handleMirrorsUrl("https://www.erkul.games", _appVersionData))) { + dPrint("load script"); + await Future.delayed(const Duration(milliseconds: 100)); + webview.injectLocalizationScript(); + dPrint("update replaceWords"); + final replaceWords = _getLocalizationResource("DPS"); + webview.updateReplaceWords(replaceWords, enableCapture); + } else if (url.startsWith(await _handleMirrorsUrl("https://uexcorp.space", _appVersionData))) { + dPrint("load script"); + await Future.delayed(const Duration(milliseconds: 100)); + webview.injectLocalizationScript(); + dPrint("update replaceWords"); + final replaceWords = _getLocalizationResource("UEX"); + webview.updateReplaceWords(replaceWords, enableCapture); + } + } + Future _handleMirrorsUrl(String url, AppVersionData appVersionData) async { var finalUrl = url; if (isEnableToolSiteMirrors) { @@ -189,7 +206,7 @@ class WebViewModel { } Future launch(String url, AppVersionData appVersionData) async { - webview.launch(await _handleMirrorsUrl(url, appVersionData)); + webview.navigate(await _handleMirrorsUrl(url, appVersionData)); } Future initLocalization(AppWebLocalizationVersionsData v) async { @@ -259,29 +276,19 @@ class WebViewModel { } void addOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) { - webview.addOnWebMessageReceivedCallback(callback); + webview.addOnWebMessageCallback(callback); } void removeOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) { - webview.removeOnWebMessageReceivedCallback(callback); + webview.removeOnWebMessageCallback(callback); } FutureOr dispose() { - webview.removeOnUrlRequestCallback(_onUrlRequest); + webview.removeOnNavigationCompletedCallback(_onNavigationCompleted); if (loginMode && !_loginModeSuccess) { loginCallback?.call(null, false); } _isClosed = true; - } - - void _onUrlRequest(String url) { - dPrint("OnUrlRequestCallback === $url"); - this.url = url; - - // 在页面开始加载时立即注入拦截器 - if (requestInterceptorScript.isNotEmpty) { - dPrint("Injecting request interceptor for: $url"); - webview.evaluateJavaScript(requestInterceptorScript); - } + webview.dispose(); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c6f7148..2826f60 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -17,9 +16,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_multi_window_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopMultiWindowPlugin"); desktop_multi_window_plugin_register_with_registrar(desktop_multi_window_registrar); - 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); g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin"); flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 04e5b88..7cb4963 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_multi_window - desktop_webview_window flutter_acrylic screen_retriever_linux url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 711ce59..1fa8adc 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,20 +6,20 @@ import FlutterMacOS import Foundation import desktop_multi_window -import desktop_webview_window import device_info_plus import file_picker import macos_window_utils +import path_provider_foundation import screen_retriever_macos import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin")) - DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index a9edfb5..d148b65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -322,14 +322,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" - desktop_webview_window: - dependency: "direct main" - description: - name: desktop_webview_window - sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" - url: "https://pub.dev" - source: hosted - version: "0.2.3" device_info_plus: dependency: "direct main" description: @@ -934,14 +926,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "1f81ed9e41909d44162d7ec8663b2c647c202317cc0b56d3d56f6a13146a0b64" - url: "https://pub.dev" - source: hosted - version: "9.1.0" package_config: dependency: transitive description: @@ -986,10 +970,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "6192e477f34018ef1ea790c56fffc7302e3bc3efede9e798b934c252c8c105ba" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -1646,10 +1630,10 @@ packages: dependency: transitive description: name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" sdks: dart: ">=3.9.0 <4.0.0" flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3b5bc1c..021b3b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,7 +40,6 @@ dependencies: device_info_plus: ^12.3.0 file_picker: ^10.3.7 file_sizes: ^1.0.6 - desktop_webview_window: ^0.2.3 flutter_svg: ^2.2.3 archive: ^4.0.7 jwt_decode: ^0.3.1 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 730e9df..cdade69 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -279,7 +279,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -314,7 +314,30 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] @@ -368,6 +391,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -448,6 +477,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.0" @@ -463,6 +498,31 @@ dependencies = [ "libbz2-rs-sys", ] +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.10.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "castaway" version = "0.2.4" @@ -493,6 +553,22 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec 1.15.1", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -555,10 +631,10 @@ version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -600,6 +676,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -690,6 +776,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.18.1" @@ -729,12 +821,46 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -818,6 +944,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec 1.15.1", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.111", +] + [[package]] name = "darling" version = "0.20.11" @@ -849,7 +1002,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.111", ] [[package]] @@ -863,7 +1016,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.111", ] [[package]] @@ -874,7 +1027,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -885,7 +1038,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -939,7 +1092,7 @@ checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -970,7 +1123,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -991,7 +1144,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1001,7 +1154,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.111", ] [[package]] @@ -1015,13 +1181,40 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + [[package]] name = "dispatch2" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags", + "bitflags 2.10.0", "objc2", ] @@ -1033,7 +1226,30 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "dlopen2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d65cde5fb0c42a3d5882d99807698b459f5928de035fa7f547c784fb7b34219" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1045,6 +1261,36 @@ dependencies = [ "litrs", ] +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1084,10 +1330,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1108,7 +1354,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1180,6 +1426,25 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "filetime" version = "0.2.26" @@ -1248,7 +1513,7 @@ dependencies = [ "md-5", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1263,7 +1528,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1272,6 +1558,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1281,6 +1573,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.31" @@ -1350,7 +1652,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1383,6 +1685,114 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1393,6 +1803,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -1402,7 +1823,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1426,6 +1847,85 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec 1.15.1", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec 1.15.1", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glob" version = "0.3.3" @@ -1445,6 +1945,69 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "h2" version = "0.4.12" @@ -1482,6 +2045,12 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1516,7 +2085,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand", + "rand 0.9.2", "ring", "thiserror 2.0.17", "tinyvec", @@ -1538,7 +2107,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand", + "rand 0.9.2", "resolv-conf", "smallvec 1.15.1", "thiserror 2.0.17", @@ -1555,6 +2124,18 @@ dependencies = [ "digest", ] +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + [[package]] name = "http" version = "1.4.0" @@ -1807,6 +2388,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", +] + [[package]] name = "indenter" version = "0.3.4" @@ -1926,6 +2520,51 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -1946,6 +2585,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.12.1", + "selectors", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1970,7 +2621,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "redox_syscall", ] @@ -2033,6 +2684,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "mac-notification-sys" version = "0.6.9" @@ -2061,6 +2718,37 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "matrixmultiply" version = "0.3.10" @@ -2125,7 +2813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -2166,7 +2854,17 @@ checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "moxcms" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" +dependencies = [ + "num-traits", + "pxfm", ] [[package]] @@ -2216,19 +2914,61 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", "memoffset", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -2296,6 +3036,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "objc2" version = "0.6.3" @@ -2303,6 +3065,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] @@ -2311,7 +3086,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.10.0", "dispatch2", "objc2", ] @@ -2322,19 +3097,54 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + [[package]] name = "objc2-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags", + "bitflags 2.10.0", "block2", "libc", "objc2", "objc2-core-foundation", ] +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + [[package]] name = "object" version = "0.37.3" @@ -2366,7 +3176,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "once_cell", "onig_sys", @@ -2388,9 +3198,9 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -2405,7 +3215,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -2426,6 +3236,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2478,6 +3294,31 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.2.1" @@ -2538,6 +3379,126 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2567,6 +3528,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -2635,15 +3609,71 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.103" @@ -2669,6 +3699,15 @@ dependencies = [ "psl-types", ] +[[package]] +name = "pxfm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b" +dependencies = [ + "num-traits", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -2716,7 +3755,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -2757,14 +3796,59 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -2774,7 +3858,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -2786,6 +3888,30 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rawpointer" version = "0.2.1" @@ -2829,7 +3955,18 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", ] [[package]] @@ -2849,7 +3986,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -2957,23 +4094,30 @@ version = "0.1.0" dependencies = [ "anyhow", "asar", + "crossbeam-channel", "flutter_rust_bridge", "futures", "hickory-resolver", + "image", "ndarray 0.17.1", "notify-rust", "once_cell", "ort", + "parking_lot", "reqwest", "scopeguard", + "serde", "serde_json", + "tao", "tokenizers", "tokio", "unp4k_rs", "url", + "uuid", "walkdir", "win32job", "windows 0.62.2", + "wry", ] [[package]] @@ -3003,7 +4147,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -3111,8 +4255,8 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.10.0", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -3128,6 +4272,24 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec 1.15.1", +] + [[package]] name = "semver" version = "1.0.27" @@ -3161,7 +4323,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3185,7 +4347,16 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", ] [[package]] @@ -3228,7 +4399,17 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", ] [[package]] @@ -3283,6 +4464,18 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" @@ -3332,6 +4525,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "spm_precompiled" version = "0.1.4" @@ -3356,6 +4575,31 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3368,6 +4612,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.111" @@ -3396,7 +4651,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3405,8 +4660,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.10.0", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3420,12 +4675,77 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tao" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +dependencies = [ + "bitflags 2.10.0", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "serde", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "tar" version = "0.4.44" @@ -3437,6 +4757,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tauri-winrt-notification" version = "0.7.2" @@ -3462,6 +4788,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3488,7 +4825,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3499,7 +4836,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3595,7 +4932,7 @@ dependencies = [ "monostate", "onig", "paste", - "rand", + "rand 0.9.2", "rayon", "rayon-cond", "regex", @@ -3633,7 +4970,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3669,6 +5006,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -3678,6 +5036,30 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.12.1", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.12.1", + "serde", + "serde_spanned", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.23.7" @@ -3685,9 +5067,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap 2.12.1", - "toml_datetime", + "toml_datetime 0.7.3", "toml_parser", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -3696,7 +5078,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow", + "winnow 0.7.14", ] [[package]] @@ -3720,7 +5102,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ - "bitflags", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -3763,7 +5145,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3977,6 +5359,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -4002,6 +5390,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4062,7 +5456,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -4123,6 +5517,50 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + [[package]] name = "webpki-root-certs" version = "1.0.4" @@ -4141,6 +5579,42 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "windows 0.61.3", + "windows-core 0.61.2", +] + [[package]] name = "widestring" version = "1.2.1" @@ -4287,7 +5761,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4298,7 +5772,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4380,6 +5854,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4416,6 +5899,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4491,6 +5989,12 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4509,6 +6013,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4527,6 +6037,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4557,6 +6073,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4575,6 +6097,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4593,6 +6121,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4611,6 +6145,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4629,6 +6169,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.14" @@ -4660,6 +6209,72 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wry" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + [[package]] name = "xattr" version = "1.6.1" @@ -4689,7 +6304,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", "synstructure", ] @@ -4721,7 +6336,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 0.7.14", "zbus_macros", "zbus_names", "zvariant", @@ -4733,10 +6348,10 @@ version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", "zbus_names", "zvariant", "zvariant_utils", @@ -4750,7 +6365,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow", + "winnow 0.7.14", "zvariant", ] @@ -4771,7 +6386,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4791,7 +6406,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", "synstructure", ] @@ -4812,7 +6427,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4845,7 +6460,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4930,7 +6545,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 0.7.14", "zvariant_derive", "zvariant_utils", ] @@ -4941,10 +6556,10 @@ version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", "zvariant_utils", ] @@ -4957,6 +6572,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", - "winnow", + "syn 2.0.111", + "winnow 0.7.14", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 06ca71c..e589df6 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,22 +15,29 @@ crate-type = ["cdylib", "staticlib"] [dependencies] flutter_rust_bridge = "=2.11.1" -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "process"] } -futures = { version = "0.3", default-features = false, features = ["executor"] } -url = "2.5" -once_cell = "1.21" -reqwest = { version = "0.12", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] } -hickory-resolver = { version = "0.25" } -anyhow = "1.0" -scopeguard = "1.2" -notify-rust = "4" +tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros", "process", "sync"] } +futures = { version = "0.3.31", default-features = false, features = ["executor"] } +url = "2.5.7" +once_cell = "1.21.3" +reqwest = { version = "0.12.24", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] } +hickory-resolver = { version = "0.25.2" } +anyhow = "1.0.100" +scopeguard = "1.2.0" +notify-rust = "4.11.7" asar = "0.3.0" walkdir = "2.5.0" ort = { version = "2.0.0-rc.10", features = ["xnnpack", "download-binaries", "ndarray"] } -tokenizers = { version = "0.22", default-features = false, features = ["onig"] } -ndarray = "0.17" -serde_json = "1.0" +tokenizers = { version = "0.22.2", default-features = false, features = ["onig"] } +ndarray = "0.17.1" +serde_json = "1.0.145" +serde = { version = "1.0.228", features = ["derive"] } unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.2" } +wry = "0.53.5" +tao = { version = "0.34.5", features = ["serde"] } +uuid = { version = "1.19.0", features = ["v4"] } +parking_lot = "0.12.5" +crossbeam-channel = "0.5.15" +image = { version = "0.25.9", default-features = false, features = ["ico"] } [target.'cfg(windows)'.dependencies] windows = { version = "0.62.2", features = [ @@ -39,7 +46,7 @@ windows = { version = "0.62.2", features = [ "Win32_System_Threading", "Win32_Foundation" ] } -win32job = "2" +win32job = "2.0.3" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index d9522d7..3f37eb7 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -7,3 +7,4 @@ pub mod win32_api; pub mod asar_api; pub mod ort_api; pub mod unp4k_api; +pub mod webview_api; diff --git a/rust/src/api/webview_api.rs b/rust/src/api/webview_api.rs new file mode 100644 index 0000000..1070277 --- /dev/null +++ b/rust/src/api/webview_api.rs @@ -0,0 +1,533 @@ +// WebView API using wry + tao +// This module provides a cross-platform WebView implementation for Flutter + +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use crossbeam_channel::{bounded, Receiver, Sender}; +use flutter_rust_bridge::frb; +use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; +use tao::dpi::{LogicalPosition, LogicalSize}; +use tao::event::{Event, WindowEvent}; +use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder}; +use tao::platform::run_return::EventLoopExtRunReturn; +use tao::window::{Icon, Window, WindowBuilder}; +use wry::{PageLoadEvent, WebView, WebViewBuilder}; + +// Platform-specific imports for running event loop on any thread +#[cfg(target_os = "windows")] +use tao::platform::windows::EventLoopBuilderExtWindows; + +use once_cell::sync::Lazy; + +// Embed the app icon at compile time +static APP_ICON_DATA: &[u8] = include_bytes!("../../../windows/runner/resources/app_icon.ico"); + +// Embed the init script at compile time +static INIT_SCRIPT: &str = include_str!("../assets/webview_init_script.js"); + +// ============ Data Structures ============ + +/// WebView window configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +#[frb(dart_metadata = ("freezed"))] +pub struct WebViewConfiguration { + pub title: String, + pub width: u32, + pub height: u32, + pub user_data_folder: Option, + pub enable_devtools: bool, + pub transparent: bool, + pub user_agent: Option, +} + +impl Default for WebViewConfiguration { + fn default() -> Self { + Self { + title: "WebView".to_string(), + width: 1280, + height: 720, + user_data_folder: None, + enable_devtools: false, + transparent: false, + user_agent: None, + } + } +} + +/// Navigation state of the WebView +#[derive(Debug, Clone, Serialize, Deserialize)] +#[frb(dart_metadata = ("freezed"))] +pub struct WebViewNavigationState { + pub url: String, + pub title: String, + pub can_go_back: bool, + pub can_go_forward: bool, + pub is_loading: bool, +} + +impl Default for WebViewNavigationState { + fn default() -> Self { + Self { + url: String::new(), + title: String::new(), + can_go_back: false, + can_go_forward: false, + is_loading: false, + } + } +} + +/// Events from WebView to Dart +#[derive(Debug, Clone, Serialize, Deserialize)] +#[frb(dart_metadata = ("freezed"))] +pub enum WebViewEvent { + NavigationStarted { url: String }, + NavigationCompleted { url: String }, + TitleChanged { title: String }, + WebMessage { message: String }, + WindowClosed, + Error { message: String }, +} + +/// Commands from Dart to WebView +#[derive(Debug, Clone)] +pub enum WebViewCommand { + Navigate(String), + GoBack, + GoForward, + Reload, + Stop, + ExecuteScript(String), + SetVisibility(bool), + Close, + SetWindowSize(u32, u32), + SetWindowPosition(i32, i32), +} + +// ============ Global State ============ + +type WebViewId = String; + +struct WebViewInstance { + command_sender: Sender, + event_receiver: Receiver, + state: Arc>, + is_closed: Arc, +} + +static WEBVIEW_INSTANCES: Lazy>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +// Custom event type for the event loop +#[derive(Debug, Clone)] +#[allow(dead_code)] +enum UserEvent { + Command(WebViewCommand), + Quit, +} + +// ============ Public API ============ + +/// Create a new WebView window and return its ID +#[frb(sync)] +pub fn webview_create(config: WebViewConfiguration) -> Result { + let id = uuid::Uuid::new_v4().to_string(); + let id_clone = id.clone(); + + let (cmd_tx, cmd_rx) = bounded::(100); + let (event_tx, event_rx) = bounded::(100); + let state = Arc::new(RwLock::new(WebViewNavigationState::default())); + let state_clone = Arc::clone(&state); + let is_closed = Arc::new(AtomicBool::new(false)); + let is_closed_clone = Arc::clone(&is_closed); + + std::thread::spawn(move || { + run_webview_loop(id_clone, config, cmd_rx, event_tx, state_clone, is_closed_clone); + }); + + // Wait a moment for the window to be created + std::thread::sleep(std::time::Duration::from_millis(100)); + + let instance = WebViewInstance { + command_sender: cmd_tx, + event_receiver: event_rx, + state, + is_closed, + }; + + WEBVIEW_INSTANCES.write().insert(id.clone(), instance); + Ok(id) +} + +/// Navigate to a URL +#[frb(sync)] +pub fn webview_navigate(id: String, url: String) -> Result<(), String> { + send_command(&id, WebViewCommand::Navigate(url)) +} + +/// Go back in history +#[frb(sync)] +pub fn webview_go_back(id: String) -> Result<(), String> { + send_command(&id, WebViewCommand::GoBack) +} + +/// Go forward in history +#[frb(sync)] +pub fn webview_go_forward(id: String) -> Result<(), String> { + send_command(&id, WebViewCommand::GoForward) +} + +/// Reload the current page +#[frb(sync)] +pub fn webview_reload(id: String) -> Result<(), String> { + send_command(&id, WebViewCommand::Reload) +} + +/// Stop loading +#[frb(sync)] +pub fn webview_stop(id: String) -> Result<(), String> { + send_command(&id, WebViewCommand::Stop) +} + +/// Execute JavaScript in the WebView +#[frb(sync)] +pub fn webview_execute_script(id: String, script: String) -> Result<(), String> { + send_command(&id, WebViewCommand::ExecuteScript(script)) +} + +/// Set window visibility +#[frb(sync)] +pub fn webview_set_visibility(id: String, visible: bool) -> Result<(), String> { + send_command(&id, WebViewCommand::SetVisibility(visible)) +} + +/// Close the WebView window +#[frb(sync)] +pub fn webview_close(id: String) -> Result<(), String> { + send_command(&id, WebViewCommand::Close)?; + // Remove from instances after a short delay + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(200)); + WEBVIEW_INSTANCES.write().remove(&id); + }); + Ok(()) +} + +/// Set window size +#[frb(sync)] +pub fn webview_set_window_size(id: String, width: u32, height: u32) -> Result<(), String> { + send_command(&id, WebViewCommand::SetWindowSize(width, height)) +} + +/// Set window position +#[frb(sync)] +pub fn webview_set_window_position(id: String, x: i32, y: i32) -> Result<(), String> { + send_command(&id, WebViewCommand::SetWindowPosition(x, y)) +} + +/// Get the current navigation state +#[frb(sync)] +pub fn webview_get_state(id: String) -> Result { + let instances = WEBVIEW_INSTANCES.read(); + let instance = instances + .get(&id) + .ok_or_else(|| format!("WebView instance not found: {}", id))?; + let state = instance.state.read().clone(); + Ok(state) +} + +/// Check if the WebView is closed +#[frb(sync)] +pub fn webview_is_closed(id: String) -> bool { + let instances = WEBVIEW_INSTANCES.read(); + let result = instances + .get(&id) + .map(|i| i.is_closed.load(Ordering::SeqCst)) + .unwrap_or(true); + result +} + +/// Poll for events from the WebView (non-blocking) +#[frb(sync)] +pub fn webview_poll_events(id: String) -> Vec { + let instances = WEBVIEW_INSTANCES.read(); + if let Some(instance) = instances.get(&id) { + let mut events = Vec::new(); + while let Ok(event) = instance.event_receiver.try_recv() { + events.push(event); + } + drop(instances); + events + } else { + drop(instances); + vec![] + } +} + +/// Get a list of all active WebView IDs +#[frb(sync)] +pub fn webview_list_all() -> Vec { + let instances = WEBVIEW_INSTANCES.read(); + let keys: Vec = instances.keys().cloned().collect(); + drop(instances); + keys +} + +// ============ Internal Implementation ============ + +fn send_command(id: &str, command: WebViewCommand) -> Result<(), String> { + let instances = WEBVIEW_INSTANCES.read(); + let instance = instances + .get(id) + .ok_or_else(|| format!("WebView instance not found: {}", id))?; + + if instance.is_closed.load(Ordering::SeqCst) { + return Err("WebView is closed".to_string()); + } + + instance + .command_sender + .send(command) + .map_err(|e| e.to_string()) +} + +fn run_webview_loop( + _id: String, + config: WebViewConfiguration, + cmd_rx: Receiver, + event_tx: Sender, + state: Arc>, + is_closed: Arc, +) { + // Create event loop with any_thread support for non-main thread execution + #[cfg(target_os = "windows")] + let mut event_loop: EventLoop = EventLoopBuilder::with_user_event() + .with_any_thread(true) + .build(); + + #[cfg(not(target_os = "windows"))] + let mut event_loop: EventLoop = EventLoopBuilder::with_user_event().build(); + + let proxy = event_loop.create_proxy(); + + // Load window icon from embedded ICO file + let window_icon = load_app_icon(); + + // Build the window with decorations (title bar provided by OS) + // Set dark background color matching app theme (#1a1a1a) + let mut window_builder = WindowBuilder::new() + .with_title(&config.title) + .with_inner_size(LogicalSize::new(config.width, config.height)) + .with_visible(true); + + // Set window icon if loaded successfully + if let Some(icon) = window_icon { + window_builder = window_builder.with_window_icon(Some(icon)); + } + + let window = window_builder.build(&event_loop) + .expect("Failed to create window"); + + let window = Arc::new(window); + + let event_tx_clone = event_tx.clone(); + + // Create web context with custom data directory if provided + let mut web_context = config + .user_data_folder + .as_ref() + .map(|path| wry::WebContext::new(Some(std::path::PathBuf::from(path)))) + .unwrap_or_default(); + + let mut builder = WebViewBuilder::new_with_web_context(&mut web_context) + .with_url("about:blank") + .with_devtools(config.enable_devtools) + .with_transparent(config.transparent) + .with_background_color((26, 26, 26, 255)) // Dark background #1a1a1a + .with_initialization_script(INIT_SCRIPT); + + // Set user agent if provided + if let Some(ref user_agent) = config.user_agent { + builder = builder.with_user_agent(user_agent); + } + + let webview = builder + .with_ipc_handler(move |message| { + let msg = message.body().to_string(); + // Forward all messages to Dart + let _ = event_tx_clone.send(WebViewEvent::WebMessage { message: msg }); + }) + .with_navigation_handler({ + let event_tx = event_tx.clone(); + let state = Arc::clone(&state); + move |uri| { + { + let mut state_guard = state.write(); + state_guard.url = uri.clone(); + state_guard.is_loading = true; + } + let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri }); + true // Allow navigation + } + }) + .with_on_page_load_handler({ + let event_tx = event_tx.clone(); + let state = Arc::clone(&state); + move |event, url| { + match event { + PageLoadEvent::Started => { + let mut state_guard = state.write(); + state_guard.url = url.clone(); + state_guard.is_loading = true; + } + PageLoadEvent::Finished => { + { + let mut state_guard = state.write(); + state_guard.url = url.clone(); + state_guard.is_loading = false; + } + let _ = event_tx.send(WebViewEvent::NavigationCompleted { url }); + } + } + } + }) + .build(&window) + .expect("Failed to create webview"); + + let webview = Arc::new(webview); + let webview_cmd = Arc::clone(&webview); + + // Spawn command handler thread + let proxy_cmd = proxy.clone(); + std::thread::spawn(move || { + while let Ok(cmd) = cmd_rx.recv() { + if proxy_cmd.send_event(UserEvent::Command(cmd)).is_err() { + break; + } + } + }); + + // Run the event loop with run_return so we can properly cleanup + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + is_closed.store(true, Ordering::SeqCst); + let _ = event_tx.send(WebViewEvent::WindowClosed); + *control_flow = ControlFlow::Exit; + } + Event::UserEvent(user_event) => match user_event { + UserEvent::Command(cmd) => { + handle_command(&webview_cmd, &window, cmd, &state, &event_tx, &is_closed, control_flow); + } + UserEvent::Quit => { + is_closed.store(true, Ordering::SeqCst); + *control_flow = ControlFlow::Exit; + } + }, + _ => {} + } + }); + + // Explicitly drop in correct order: webview first, then web_context, then window + drop(webview_cmd); + drop(web_context); + drop(window); +} + +fn handle_command( + webview: &Arc, + window: &Arc, + command: WebViewCommand, + state: &Arc>, + event_tx: &Sender, + is_closed: &Arc, + control_flow: &mut ControlFlow, +) { + match command { + WebViewCommand::Navigate(url) => { + { + let mut s = state.write(); + s.url = url.clone(); + s.is_loading = true; + } + let _ = webview.load_url(&url); + let _ = event_tx.send(WebViewEvent::NavigationStarted { url }); + } + WebViewCommand::GoBack => { + let _ = webview.evaluate_script("history.back()"); + } + WebViewCommand::GoForward => { + let _ = webview.evaluate_script("history.forward()"); + } + WebViewCommand::Reload => { + let _ = webview.evaluate_script("location.reload()"); + } + WebViewCommand::Stop => { + let _ = webview.evaluate_script("window.stop()"); + } + WebViewCommand::ExecuteScript(script) => { + let _ = webview.evaluate_script(&script); + } + WebViewCommand::SetVisibility(visible) => { + window.set_visible(visible); + } + WebViewCommand::Close => { + // Properly close the window and exit the event loop + is_closed.store(true, Ordering::SeqCst); + let _ = event_tx.send(WebViewEvent::WindowClosed); + // Exit the event loop - this will cause cleanup + *control_flow = ControlFlow::Exit; + } + WebViewCommand::SetWindowSize(width, height) => { + let _ = window.set_inner_size(LogicalSize::new(width, height)); + } + WebViewCommand::SetWindowPosition(x, y) => { + window.set_outer_position(LogicalPosition::new(x, y)); + } + } +} + +/// Load the application icon from embedded ICO data +fn load_app_icon() -> Option { + use std::io::Cursor; + use image::ImageReader; + + // Parse the ICO file from embedded bytes + let cursor = Cursor::new(APP_ICON_DATA); + let reader = match ImageReader::new(cursor).with_guessed_format() { + Ok(r) => r, + Err(e) => { + eprintln!("[SCToolbox] Failed to create image reader: {}", e); + return None; + } + }; + + let img = match reader.decode() { + Ok(img) => img, + Err(e) => { + eprintln!("[SCToolbox] Failed to decode icon: {}", e); + return None; + } + }; + + // Convert to RGBA8 + let rgba = img.to_rgba8(); + let (width, height) = rgba.dimensions(); + let raw_data = rgba.into_raw(); + + match Icon::from_rgba(raw_data, width, height) { + Ok(icon) => Some(icon), + Err(e) => { + eprintln!("[SCToolbox] Failed to create icon: {:?}", e); + None + } + } +} diff --git a/rust/src/assets/webview_init_script.js b/rust/src/assets/webview_init_script.js new file mode 100644 index 0000000..2ccb910 --- /dev/null +++ b/rust/src/assets/webview_init_script.js @@ -0,0 +1,266 @@ +// SCToolbox WebView initialization script +(function() { + 'use strict'; + + if (window._sctInitialized) return; + window._sctInitialized = true; + + // ========== 导航栏 UI ========== + const icons = { + back: '', + forward: '', + reload: '' + }; + + function createNavBar() { + if (window.location.href === 'about:blank') return; + if (document.getElementById('sct-navbar')) return; + if (!document.body) { + setTimeout(createNavBar, 50); + return; + } + + const nav = document.createElement('div'); + nav.id = 'sct-navbar'; + nav.innerHTML = ` + + + + +
+
Page icon
+ + `; + document.body.insertBefore(nav, document.body.firstChild); + + document.getElementById('sct-back').onclick = () => { + // Check if going back would result in about:blank + // If so, skip this entry and go back further + const beforeBackUrl = window.location.href; + history.back(); + + // After a short delay, if we landed on about:blank, go back again + setTimeout(() => { + if (window.location.href === 'about:blank' && beforeBackUrl !== 'about:blank') { + history.back(); + } + }, 100); + }; + document.getElementById('sct-forward').onclick = () => history.forward(); + document.getElementById('sct-reload').onclick = () => location.reload(); + + // Update back button state and URL display on navigation + function updateNavBarState() { + const backBtn = document.getElementById('sct-back'); + const urlEl = document.getElementById('sct-navbar-url'); + const currentUrl = window.location.href; + + if (backBtn) { + // Disable back button if at start of history or at about:blank + backBtn.disabled = window.history.length <= 1 || currentUrl === 'about:blank'; + } + + if (urlEl) { + urlEl.value = currentUrl; + } + } + + // Listen to popstate and hashchange to update nav bar + window.addEventListener('popstate', updateNavBarState); + window.addEventListener('hashchange', updateNavBarState); + + // Initial state + updateNavBarState(); + + // Spinner and favicon show/hide helpers + const spinner = document.getElementById('sct-spinner'); + const faviconSlot = document.getElementById('sct-favicon-slot'); + const faviconImg = document.getElementById('sct-favicon'); + + function showSpinner() { + if (spinner) { + spinner.style.display = 'block'; + spinner.setAttribute('aria-hidden', 'false'); + spinner.setAttribute('aria-busy', 'true'); + } + if (faviconSlot) { + faviconSlot.style.display = 'none'; + } + } + + function hideSpin() { + if (spinner) { + spinner.style.display = 'none'; + spinner.setAttribute('aria-hidden', 'true'); + spinner.setAttribute('aria-busy', 'false'); + } + } + + // Extract favicon from page and show it when ready + function showFaviconWhenReady() { + hideSpin(); + + // Try to find favicon from page + let faviconUrl = null; + + // 1. Look for link[rel="icon"] or link[rel="shortcut icon"] + const linkIcon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'); + if (linkIcon && linkIcon.href) { + faviconUrl = linkIcon.href; + } + + // 2. Look for og:image in meta tags (fallback) + if (!faviconUrl) { + const ogImage = document.querySelector('meta[property="og:image"]'); + if (ogImage && ogImage.content) { + faviconUrl = ogImage.content; + } + } + + // 3. Check page's existing favicon from document.head or body elements + if (!faviconUrl) { + const existingFavicon = document.querySelector('img[src*="favicon"], img[src*="icon"]'); + if (existingFavicon && existingFavicon.src) { + faviconUrl = existingFavicon.src; + } + } + + // Display favicon if found, otherwise hide slot + if (faviconUrl) { + if (faviconImg) { + faviconImg.src = faviconUrl; + faviconImg.onerror = () => { + if (faviconSlot) faviconSlot.style.display = 'none'; + }; + } + if (faviconSlot) { + faviconSlot.style.display = 'flex'; + } + } else if (faviconSlot) { + faviconSlot.style.display = 'none'; + } + } + + // Monitor document readyState to show favicon when page is ready + document.addEventListener('readystatechange', function () { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + setTimeout(showFaviconWhenReady, 150); + } + }); + + // Also trigger favicon display on load event + window.addEventListener('load', function () { + setTimeout(showFaviconWhenReady, 150); + }); + } + + // 在 DOM 准备好时创建导航栏 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', createNavBar); + } else { + createNavBar(); + } + + // URL 变化时:进入加载状态,显示 spinner + window.addEventListener('popstate', () => { + // Show spinner when navigating via popstate (URL change) + const spinner = document.getElementById('sct-spinner'); + const faviconSlot = document.getElementById('sct-favicon-slot'); + if (spinner) { + spinner.style.display = 'block'; + spinner.setAttribute('aria-hidden', 'false'); + spinner.setAttribute('aria-busy', 'true'); + } + if (faviconSlot) { + faviconSlot.style.display = 'none'; + } + }); +})(); diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 0dc8575..e91ad96 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1801517256; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1082688871; // Section: executor @@ -666,6 +666,337 @@ fn wire__crate__api__ort_api__unload_translation_model_impl( }, ) } +fn wire__crate__api__webview_api__web_view_configuration_default_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "web_view_configuration_default", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| { + transform_result_dco::<_, _, ()>((move || { + let output_ok = Result::<_, ()>::Ok( + crate::api::webview_api::WebViewConfiguration::default(), + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__webview_api__web_view_navigation_state_default_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "web_view_navigation_state_default", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| { + transform_result_dco::<_, _, ()>((move || { + let output_ok = Result::<_, ()>::Ok( + crate::api::webview_api::WebViewNavigationState::default(), + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__webview_api__webview_close_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_close", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_close(api_id)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_create_impl( + config: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_create", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_config = config.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_create(api_config)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_execute_script_impl( + id: impl CstDecode, + script: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_execute_script", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + let api_script = script.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = + crate::api::webview_api::webview_execute_script(api_id, api_script)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_get_state_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_get_state", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_get_state(api_id)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_go_back_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_go_back", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_go_back(api_id)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_go_forward_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_go_forward", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_go_forward(api_id)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_is_closed_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_is_closed", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::webview_api::webview_is_closed(api_id))?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_list_all_impl( +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_list_all", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + transform_result_dco::<_, _, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::webview_api::webview_list_all())?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_navigate_impl( + id: impl CstDecode, + url: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_navigate", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + let api_url = url.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_navigate(api_id, api_url)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_poll_events_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_poll_events", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::webview_api::webview_poll_events(api_id))?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_reload_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_reload", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_reload(api_id)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_set_visibility_impl( + id: impl CstDecode, + visible: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_set_visibility", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + let api_visible = visible.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = + crate::api::webview_api::webview_set_visibility(api_id, api_visible)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_set_window_position_impl( + id: impl CstDecode, + x: impl CstDecode, + y: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_set_window_position", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + let api_x = x.cst_decode(); + let api_y = y.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = + crate::api::webview_api::webview_set_window_position(api_id, api_x, api_y)?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_set_window_size_impl( + id: impl CstDecode, + width: impl CstDecode, + height: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_set_window_size", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + let api_width = width.cst_decode(); + let api_height = height.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_set_window_size( + api_id, api_width, api_height, + )?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__webview_api__webview_stop_impl( + id: impl CstDecode, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "webview_stop", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let api_id = id.cst_decode(); + transform_result_dco::<_, _, String>((move || { + let output_ok = crate::api::webview_api::webview_stop(api_id)?; + Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__rs_process__write_impl( port_: flutter_rust_bridge::for_generated::MessagePort, rs_pid: impl CstDecode, @@ -909,6 +1240,20 @@ impl SseDecode for Vec<(String, String)> { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + impl SseDecode for crate::http_package::MyHttpVersion { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1146,6 +1491,85 @@ impl SseDecode for usize { } } +impl SseDecode for crate::api::webview_api::WebViewConfiguration { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_title = ::sse_decode(deserializer); + let mut var_width = ::sse_decode(deserializer); + let mut var_height = ::sse_decode(deserializer); + let mut var_userDataFolder = >::sse_decode(deserializer); + let mut var_enableDevtools = ::sse_decode(deserializer); + let mut var_transparent = ::sse_decode(deserializer); + let mut var_userAgent = >::sse_decode(deserializer); + return crate::api::webview_api::WebViewConfiguration { + title: var_title, + width: var_width, + height: var_height, + user_data_folder: var_userDataFolder, + enable_devtools: var_enableDevtools, + transparent: var_transparent, + user_agent: var_userAgent, + }; + } +} + +impl SseDecode for crate::api::webview_api::WebViewEvent { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_url = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewEvent::NavigationStarted { url: var_url }; + } + 1 => { + let mut var_url = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewEvent::NavigationCompleted { url: var_url }; + } + 2 => { + let mut var_title = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewEvent::TitleChanged { title: var_title }; + } + 3 => { + let mut var_message = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewEvent::WebMessage { + message: var_message, + }; + } + 4 => { + return crate::api::webview_api::WebViewEvent::WindowClosed; + } + 5 => { + let mut var_message = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewEvent::Error { + message: var_message, + }; + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for crate::api::webview_api::WebViewNavigationState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_url = ::sse_decode(deserializer); + let mut var_title = ::sse_decode(deserializer); + let mut var_canGoBack = ::sse_decode(deserializer); + let mut var_canGoForward = ::sse_decode(deserializer); + let mut var_isLoading = ::sse_decode(deserializer); + return crate::api::webview_api::WebViewNavigationState { + url: var_url, + title: var_title, + can_go_back: var_canGoBack, + can_go_forward: var_canGoForward, + is_loading: var_isLoading, + }; + } +} + fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, @@ -1364,6 +1788,93 @@ impl flutter_rust_bridge::IntoIntoDart self } } +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::webview_api::WebViewConfiguration { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.title.into_into_dart().into_dart(), + self.width.into_into_dart().into_dart(), + self.height.into_into_dart().into_dart(), + self.user_data_folder.into_into_dart().into_dart(), + self.enable_devtools.into_into_dart().into_dart(), + self.transparent.into_into_dart().into_dart(), + self.user_agent.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::webview_api::WebViewConfiguration +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::webview_api::WebViewConfiguration +{ + fn into_into_dart(self) -> crate::api::webview_api::WebViewConfiguration { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::webview_api::WebViewEvent { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::api::webview_api::WebViewEvent::NavigationStarted { url } => { + [0.into_dart(), url.into_into_dart().into_dart()].into_dart() + } + crate::api::webview_api::WebViewEvent::NavigationCompleted { url } => { + [1.into_dart(), url.into_into_dart().into_dart()].into_dart() + } + crate::api::webview_api::WebViewEvent::TitleChanged { title } => { + [2.into_dart(), title.into_into_dart().into_dart()].into_dart() + } + crate::api::webview_api::WebViewEvent::WebMessage { message } => { + [3.into_dart(), message.into_into_dart().into_dart()].into_dart() + } + crate::api::webview_api::WebViewEvent::WindowClosed => [4.into_dart()].into_dart(), + crate::api::webview_api::WebViewEvent::Error { message } => { + [5.into_dart(), message.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::webview_api::WebViewEvent +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::webview_api::WebViewEvent +{ + fn into_into_dart(self) -> crate::api::webview_api::WebViewEvent { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::webview_api::WebViewNavigationState { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.url.into_into_dart().into_dart(), + self.title.into_into_dart().into_dart(), + self.can_go_back.into_into_dart().into_dart(), + self.can_go_forward.into_into_dart().into_dart(), + self.is_loading.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::webview_api::WebViewNavigationState +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::webview_api::WebViewNavigationState +{ + fn into_into_dart(self) -> crate::api::webview_api::WebViewNavigationState { + self + } +} impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1469,6 +1980,16 @@ impl SseEncode for Vec<(String, String)> { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for crate::http_package::MyHttpVersion { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1681,6 +2202,64 @@ impl SseEncode for usize { } } +impl SseEncode for crate::api::webview_api::WebViewConfiguration { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.title, serializer); + ::sse_encode(self.width, serializer); + ::sse_encode(self.height, serializer); + >::sse_encode(self.user_data_folder, serializer); + ::sse_encode(self.enable_devtools, serializer); + ::sse_encode(self.transparent, serializer); + >::sse_encode(self.user_agent, serializer); + } +} + +impl SseEncode for crate::api::webview_api::WebViewEvent { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::api::webview_api::WebViewEvent::NavigationStarted { url } => { + ::sse_encode(0, serializer); + ::sse_encode(url, serializer); + } + crate::api::webview_api::WebViewEvent::NavigationCompleted { url } => { + ::sse_encode(1, serializer); + ::sse_encode(url, serializer); + } + crate::api::webview_api::WebViewEvent::TitleChanged { title } => { + ::sse_encode(2, serializer); + ::sse_encode(title, serializer); + } + crate::api::webview_api::WebViewEvent::WebMessage { message } => { + ::sse_encode(3, serializer); + ::sse_encode(message, serializer); + } + crate::api::webview_api::WebViewEvent::WindowClosed => { + ::sse_encode(4, serializer); + } + crate::api::webview_api::WebViewEvent::Error { message } => { + ::sse_encode(5, serializer); + ::sse_encode(message, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for crate::api::webview_api::WebViewNavigationState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.url, serializer); + ::sse_encode(self.title, serializer); + ::sse_encode(self.can_go_back, serializer); + ::sse_encode(self.can_go_forward, serializer); + ::sse_encode(self.is_loading, serializer); + } +} + #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. @@ -1763,6 +2342,15 @@ mod io { unsafe { *flutter_rust_bridge::for_generated::box_from_leak_ptr(self) } } } + impl CstDecode + for *mut wire_cst_web_view_configuration + { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::webview_api::WebViewConfiguration { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } + } impl CstDecode> for *mut wire_cst_list_String { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> Vec { @@ -1821,6 +2409,16 @@ mod io { vec.into_iter().map(CstDecode::cst_decode).collect() } } + impl CstDecode> for *mut wire_cst_list_web_view_event { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> Vec { + let vec = unsafe { + let wrap = flutter_rust_bridge::for_generated::box_from_leak_ptr(self); + flutter_rust_bridge::for_generated::vec_from_leak_ptr(wrap.ptr, wrap.len) + }; + vec.into_iter().map(CstDecode::cst_decode).collect() + } + } impl CstDecode for wire_cst_p_4_k_file_item { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::api::unp4k_api::P4kFileItem { @@ -1883,6 +2481,73 @@ mod io { } } } + impl CstDecode for wire_cst_web_view_configuration { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::webview_api::WebViewConfiguration { + crate::api::webview_api::WebViewConfiguration { + title: self.title.cst_decode(), + width: self.width.cst_decode(), + height: self.height.cst_decode(), + user_data_folder: self.user_data_folder.cst_decode(), + enable_devtools: self.enable_devtools.cst_decode(), + transparent: self.transparent.cst_decode(), + user_agent: self.user_agent.cst_decode(), + } + } + } + impl CstDecode for wire_cst_web_view_event { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::webview_api::WebViewEvent { + match self.tag { + 0 => { + let ans = unsafe { self.kind.NavigationStarted }; + crate::api::webview_api::WebViewEvent::NavigationStarted { + url: ans.url.cst_decode(), + } + } + 1 => { + let ans = unsafe { self.kind.NavigationCompleted }; + crate::api::webview_api::WebViewEvent::NavigationCompleted { + url: ans.url.cst_decode(), + } + } + 2 => { + let ans = unsafe { self.kind.TitleChanged }; + crate::api::webview_api::WebViewEvent::TitleChanged { + title: ans.title.cst_decode(), + } + } + 3 => { + let ans = unsafe { self.kind.WebMessage }; + crate::api::webview_api::WebViewEvent::WebMessage { + message: ans.message.cst_decode(), + } + } + 4 => crate::api::webview_api::WebViewEvent::WindowClosed, + 5 => { + let ans = unsafe { self.kind.Error }; + crate::api::webview_api::WebViewEvent::Error { + message: ans.message.cst_decode(), + } + } + _ => unreachable!(), + } + } + } + impl CstDecode + for wire_cst_web_view_navigation_state + { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::webview_api::WebViewNavigationState { + crate::api::webview_api::WebViewNavigationState { + url: self.url.cst_decode(), + title: self.title.cst_decode(), + can_go_back: self.can_go_back.cst_decode(), + can_go_forward: self.can_go_forward.cst_decode(), + is_loading: self.is_loading.cst_decode(), + } + } + } impl NewWithNullPtr for wire_cst_p_4_k_file_item { fn new_with_null_ptr() -> Self { Self { @@ -1972,6 +2637,53 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_web_view_configuration { + fn new_with_null_ptr() -> Self { + Self { + title: core::ptr::null_mut(), + width: Default::default(), + height: Default::default(), + user_data_folder: core::ptr::null_mut(), + enable_devtools: Default::default(), + transparent: Default::default(), + user_agent: core::ptr::null_mut(), + } + } + } + impl Default for wire_cst_web_view_configuration { + fn default() -> Self { + Self::new_with_null_ptr() + } + } + impl NewWithNullPtr for wire_cst_web_view_event { + fn new_with_null_ptr() -> Self { + Self { + tag: -1, + kind: WebViewEventKind { nil__: () }, + } + } + } + impl Default for wire_cst_web_view_event { + fn default() -> Self { + Self::new_with_null_ptr() + } + } + impl NewWithNullPtr for wire_cst_web_view_navigation_state { + fn new_with_null_ptr() -> Self { + Self { + url: core::ptr::null_mut(), + title: core::ptr::null_mut(), + can_go_back: Default::default(), + can_go_forward: Default::default(), + is_loading: Default::default(), + } + } + } + impl Default for wire_cst_web_view_navigation_state { + fn default() -> Self { + Self::new_with_null_ptr() + } + } #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__clear_all_models( @@ -2190,6 +2902,131 @@ mod io { wire__crate__api__ort_api__unload_translation_model_impl(port_, model_key) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_configuration_default( + port_: i64, + ) { + wire__crate__api__webview_api__web_view_configuration_default_impl(port_) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_navigation_state_default( + port_: i64, + ) { + wire__crate__api__webview_api__web_view_navigation_state_default_impl(port_) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_close( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_close_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_create( + config: *mut wire_cst_web_view_configuration, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_create_impl(config) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_execute_script( + id: *mut wire_cst_list_prim_u_8_strict, + script: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_execute_script_impl(id, script) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_get_state( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_get_state_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_back( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_go_back_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_go_forward( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_go_forward_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_is_closed( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_is_closed_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_list_all( + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_list_all_impl() + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_navigate( + id: *mut wire_cst_list_prim_u_8_strict, + url: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_navigate_impl(id, url) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_poll_events( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_poll_events_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_reload( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_reload_impl(id) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_visibility( + id: *mut wire_cst_list_prim_u_8_strict, + visible: bool, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_set_visibility_impl(id, visible) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_position( + id: *mut wire_cst_list_prim_u_8_strict, + x: i32, + y: i32, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_set_window_position_impl(id, x, y) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_set_window_size( + id: *mut wire_cst_list_prim_u_8_strict, + width: u32, + height: u32, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_set_window_size_impl(id, width, height) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__webview_stop( + id: *mut wire_cst_list_prim_u_8_strict, + ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartDco { + wire__crate__api__webview_api__webview_stop_impl(id) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__rs_process__write( port_: i64, @@ -2217,6 +3054,14 @@ mod io { flutter_rust_bridge::for_generated::new_leak_box_ptr(value) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_web_view_configuration( + ) -> *mut wire_cst_web_view_configuration { + flutter_rust_bridge::for_generated::new_leak_box_ptr( + wire_cst_web_view_configuration::new_with_null_ptr(), + ) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_String( len: i32, @@ -2295,6 +3140,20 @@ mod io { flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_web_view_event( + len: i32, + ) -> *mut wire_cst_list_web_view_event { + let wrap = wire_cst_list_web_view_event { + ptr: flutter_rust_bridge::for_generated::new_leak_vec_ptr( + ::new_with_null_ptr(), + len, + ), + len, + }; + flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) + } + #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_list_String { @@ -2333,6 +3192,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_list_web_view_event { + ptr: *mut wire_cst_web_view_event, + len: i32, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_p_4_k_file_item { name: *mut wire_cst_list_prim_u_8_strict, is_directory: bool, @@ -2378,6 +3243,67 @@ mod io { remote_addr: *mut wire_cst_list_prim_u_8_strict, data: *mut wire_cst_list_prim_u_8_strict, } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_web_view_configuration { + title: *mut wire_cst_list_prim_u_8_strict, + width: u32, + height: u32, + user_data_folder: *mut wire_cst_list_prim_u_8_strict, + enable_devtools: bool, + transparent: bool, + user_agent: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_web_view_event { + tag: i32, + kind: WebViewEventKind, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub union WebViewEventKind { + NavigationStarted: wire_cst_WebViewEvent_NavigationStarted, + NavigationCompleted: wire_cst_WebViewEvent_NavigationCompleted, + TitleChanged: wire_cst_WebViewEvent_TitleChanged, + WebMessage: wire_cst_WebViewEvent_WebMessage, + Error: wire_cst_WebViewEvent_Error, + nil__: (), + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_WebViewEvent_NavigationStarted { + url: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_WebViewEvent_NavigationCompleted { + url: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_WebViewEvent_TitleChanged { + title: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_WebViewEvent_WebMessage { + message: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_WebViewEvent_Error { + message: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_web_view_navigation_state { + url: *mut wire_cst_list_prim_u_8_strict, + title: *mut wire_cst_list_prim_u_8_strict, + can_go_back: bool, + can_go_forward: bool, + is_loading: bool, + } } #[cfg(not(target_family = "wasm"))] pub use io::*; diff --git a/rust_builder/cargokit/build_tool/pubspec.lock b/rust_builder/cargokit/build_tool/pubspec.lock index 109e1ac..73c5f06 100644 --- a/rust_builder/cargokit/build_tool/pubspec.lock +++ b/rust_builder/cargokit/build_tool/pubspec.lock @@ -37,18 +37,26 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" collection: dependency: "direct main" description: @@ -69,10 +77,10 @@ packages: dependency: transitive description: name: coverage - sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d" url: "https://pub.dev" source: hosted - version: "1.7.2" + version: "1.13.1" crypto: dependency: "direct main" description: @@ -101,18 +109,18 @@ packages: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" github: dependency: "direct main" description: @@ -125,10 +133,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" hex: dependency: "direct main" description: @@ -149,10 +157,10 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: @@ -165,26 +173,26 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" lints: dependency: "direct dev" description: @@ -205,26 +213,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.17.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" node_preamble: dependency: transitive description: @@ -237,10 +245,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" path: dependency: "direct main" description: @@ -261,18 +269,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" pub_semver: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shelf: dependency: transitive description: @@ -293,34 +301,34 @@ packages: dependency: transitive description: name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.0" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" source_maps: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: "direct main" description: @@ -333,58 +341,58 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.12" toml: dependency: "direct main" description: @@ -397,10 +405,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" version: dependency: "direct main" description: @@ -413,34 +421,42 @@ packages: dependency: transitive description: name: vm_service - sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "14.1.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.4" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: @@ -458,4 +474,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index a155824..b09f122 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -16,8 +15,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopMultiWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin")); - DesktopWebviewWindowPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); FlutterAcrylicPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterAcrylicPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 82bd548..547b0a1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_multi_window - desktop_webview_window flutter_acrylic screen_retriever_windows url_launcher_windows From b11603d68c0ef33fab4a40849a5abcf54d5d55ae Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Fri, 5 Dec 2025 09:59:05 +0800 Subject: [PATCH 2/4] feat: web nav update --- lib/common/rust/rust_webview_controller.dart | 19 +- rust/src/api/webview_api.rs | 214 ++++++++++++++++- rust/src/assets/webview_init_script.js | 239 ++++++++++--------- 3 files changed, 332 insertions(+), 140 deletions(-) diff --git a/lib/common/rust/rust_webview_controller.dart b/lib/common/rust/rust_webview_controller.dart index 43fd6fb..b8ec5af 100644 --- a/lib/common/rust/rust_webview_controller.dart +++ b/lib/common/rust/rust_webview_controller.dart @@ -4,8 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/services.dart'; -import 'package:starcitizen_doctor/common/rust/api/webview_api.dart' - as rust_webview; +import 'package:starcitizen_doctor/common/rust/api/webview_api.dart' as rust_webview; import 'package:starcitizen_doctor/common/utils/log.dart'; typedef OnWebMessageCallback = void Function(String message); @@ -77,9 +76,7 @@ class RustWebViewController { Future _loadScripts() async { try { _localizationScript = await rootBundle.loadString('assets/web_script.js'); - _requestInterceptorScript = await rootBundle.loadString( - 'assets/request_interceptor.js', - ); + _requestInterceptorScript = await rootBundle.loadString('assets/request_interceptor.js'); } catch (e) { dPrint("Failed to load scripts: $e"); } @@ -289,16 +286,12 @@ class RustWebViewController { } /// 添加导航完成回调(用于在页面加载完成后注入脚本) - void addOnNavigationCompletedCallback( - OnNavigationCompletedCallback callback, - ) { + void addOnNavigationCompletedCallback(OnNavigationCompletedCallback callback) { _navigationCompletedCallbacks.add(callback); } /// 移除导航完成回调 - void removeOnNavigationCompletedCallback( - OnNavigationCompletedCallback callback, - ) { + void removeOnNavigationCompletedCallback(OnNavigationCompletedCallback callback) { _navigationCompletedCallbacks.remove(callback); } @@ -327,9 +320,7 @@ class RustWebViewController { /// 更新翻译词典 void updateReplaceWords(List> words, bool enableCapture) { final jsonWords = json.encode(words); - executeScript( - "WebLocalizationUpdateReplaceWords($jsonWords, $enableCapture)", - ); + executeScript("WebLocalizationUpdateReplaceWords($jsonWords, $enableCapture)"); } /// 执行 RSI 登录脚本 diff --git a/rust/src/api/webview_api.rs b/rust/src/api/webview_api.rs index 1070277..5a9dd58 100644 --- a/rust/src/api/webview_api.rs +++ b/rust/src/api/webview_api.rs @@ -111,6 +111,82 @@ pub enum WebViewCommand { type WebViewId = String; +/// Navigation history manager to track back/forward capability +#[derive(Debug, Clone, Default)] +struct NavigationHistory { + /// List of URLs in history (excluding about:blank) + urls: Vec, + /// Current position in history (0-based index) + current_index: i32, +} + +impl NavigationHistory { + fn new() -> Self { + Self { + urls: Vec::new(), + current_index: -1, + } + } + + /// Push a new URL to history (when navigating to a new page) + fn push(&mut self, url: &str) { + // Skip about:blank + if url == "about:blank" { + return; + } + + // If we're not at the end of history, truncate forward history + if self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) { + self.urls.truncate((self.current_index + 1) as usize); + } + + // Don't add duplicate consecutive URLs + if self.urls.last().map(|s| s.as_str()) != Some(url) { + self.urls.push(url.to_string()); + } + self.current_index = (self.urls.len() as i32) - 1; + } + + /// Check if we can go back (not at first real page) + fn can_go_back(&self) -> bool { + self.current_index > 0 + } + + /// Check if we can go forward + fn can_go_forward(&self) -> bool { + self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) + } + + /// Go back in history, returns true if successful + fn go_back(&mut self) -> bool { + if self.can_go_back() { + self.current_index -= 1; + true + } else { + false + } + } + + /// Go forward in history, returns true if successful + fn go_forward(&mut self) -> bool { + if self.can_go_forward() { + self.current_index += 1; + true + } else { + false + } + } + + /// Get current URL + fn current_url(&self) -> Option<&str> { + if self.current_index >= 0 && (self.current_index as usize) < self.urls.len() { + Some(&self.urls[self.current_index as usize]) + } else { + None + } + } +} + struct WebViewInstance { command_sender: Sender, event_receiver: Receiver, @@ -333,7 +409,12 @@ fn run_webview_loop( let window = Arc::new(window); + // Navigation history for tracking back/forward state + let nav_history = Arc::new(RwLock::new(NavigationHistory::new())); + let event_tx_clone = event_tx.clone(); + let nav_history_ipc = Arc::clone(&nav_history); + let state_ipc = Arc::clone(&state); // Create web context with custom data directory if provided let mut web_context = config @@ -354,20 +435,76 @@ fn run_webview_loop( builder = builder.with_user_agent(user_agent); } + // Store proxy for IPC commands + let proxy_ipc = proxy.clone(); + let webview = builder .with_ipc_handler(move |message| { let msg = message.body().to_string(); - // Forward all messages to Dart + + // Try to parse as navigation command from JS + if let Ok(parsed) = serde_json::from_str::(&msg) { + if let Some(msg_type) = parsed.get("type").and_then(|v| v.as_str()) { + match msg_type { + "nav_back" => { + // Check if we can go back (avoid about:blank) + let can_back = nav_history_ipc.read().can_go_back(); + if can_back { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoBack)); + } + return; + } + "nav_forward" => { + let can_forward = nav_history_ipc.read().can_go_forward(); + if can_forward { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoForward)); + } + return; + } + "nav_reload" => { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::Reload)); + return; + } + "get_nav_state" => { + // Send current state to JS + let state_guard = state_ipc.read(); + let history = nav_history_ipc.read(); + let state_json = serde_json::json!({ + "can_go_back": history.can_go_back(), + "can_go_forward": history.can_go_forward(), + "is_loading": state_guard.is_loading, + "url": state_guard.url + }); + let script = format!("window._sctUpdateNavState({})", state_json); + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); + return; + } + _ => {} + } + } + } + + // Forward other messages to Dart let _ = event_tx_clone.send(WebViewEvent::WebMessage { message: msg }); }) .with_navigation_handler({ let event_tx = event_tx.clone(); let state = Arc::clone(&state); + let nav_history = Arc::clone(&nav_history); move |uri| { + // Skip about:blank for navigation events + if uri == "about:blank" { + return true; + } + { let mut state_guard = state.write(); state_guard.url = uri.clone(); state_guard.is_loading = true; + // Update can_go_back/can_go_forward from history + let history = nav_history.read(); + state_guard.can_go_back = history.can_go_back(); + state_guard.can_go_forward = history.can_go_forward(); } let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri }); true // Allow navigation @@ -376,20 +513,66 @@ fn run_webview_loop( .with_on_page_load_handler({ let event_tx = event_tx.clone(); let state = Arc::clone(&state); + let nav_history = Arc::clone(&nav_history); + let proxy = proxy.clone(); move |event, url| { + // Skip about:blank + if url == "about:blank" { + return; + } + match event { PageLoadEvent::Started => { - let mut state_guard = state.write(); - state_guard.url = url.clone(); - state_guard.is_loading = true; + let can_back; + let can_forward; + { + let mut state_guard = state.write(); + state_guard.url = url.clone(); + state_guard.is_loading = true; + let history = nav_history.read(); + can_back = history.can_go_back(); + can_forward = history.can_go_forward(); + state_guard.can_go_back = can_back; + state_guard.can_go_forward = can_forward; + } + // Send loading state to JS + let state_json = serde_json::json!({ + "can_go_back": can_back, + "can_go_forward": can_forward, + "is_loading": true, + "url": url + }); + let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); + let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); } PageLoadEvent::Finished => { + let can_back; + let can_forward; + { + // Add to history when page finishes loading + let mut history = nav_history.write(); + history.push(&url); + can_back = history.can_go_back(); + can_forward = history.can_go_forward(); + } { let mut state_guard = state.write(); state_guard.url = url.clone(); state_guard.is_loading = false; + state_guard.can_go_back = can_back; + state_guard.can_go_forward = can_forward; } - let _ = event_tx.send(WebViewEvent::NavigationCompleted { url }); + let _ = event_tx.send(WebViewEvent::NavigationCompleted { url: url.clone() }); + + // Send completed state to JS + let state_json = serde_json::json!({ + "can_go_back": can_back, + "can_go_forward": can_forward, + "is_loading": false, + "url": url + }); + let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); + let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); } } } @@ -425,7 +608,7 @@ fn run_webview_loop( } Event::UserEvent(user_event) => match user_event { UserEvent::Command(cmd) => { - handle_command(&webview_cmd, &window, cmd, &state, &event_tx, &is_closed, control_flow); + handle_command(&webview_cmd, &window, cmd, &state, &nav_history, &event_tx, &is_closed, control_flow); } UserEvent::Quit => { is_closed.store(true, Ordering::SeqCst); @@ -447,6 +630,7 @@ fn handle_command( window: &Arc, command: WebViewCommand, state: &Arc>, + nav_history: &Arc>, event_tx: &Sender, is_closed: &Arc, control_flow: &mut ControlFlow, @@ -462,10 +646,24 @@ fn handle_command( let _ = event_tx.send(WebViewEvent::NavigationStarted { url }); } WebViewCommand::GoBack => { - let _ = webview.evaluate_script("history.back()"); + // Update history index before navigation + let can_go = { + let mut history = nav_history.write(); + history.go_back() + }; + if can_go { + let _ = webview.evaluate_script("history.back()"); + } } WebViewCommand::GoForward => { - let _ = webview.evaluate_script("history.forward()"); + // Update history index before navigation + let can_go = { + let mut history = nav_history.write(); + history.go_forward() + }; + if can_go { + let _ = webview.evaluate_script("history.forward()"); + } } WebViewCommand::Reload => { let _ = webview.evaluate_script("location.reload()"); diff --git a/rust/src/assets/webview_init_script.js b/rust/src/assets/webview_init_script.js index 2ccb910..cf67089 100644 --- a/rust/src/assets/webview_init_script.js +++ b/rust/src/assets/webview_init_script.js @@ -1,10 +1,19 @@ // SCToolbox WebView initialization script +// Uses IPC (window.ipc.postMessage) to communicate with Rust backend (function() { 'use strict'; if (window._sctInitialized) return; window._sctInitialized = true; + // ========== IPC Communication ========== + // Send message to Rust backend + function sendToRust(type, payload) { + if (window.ipc && typeof window.ipc.postMessage === 'function') { + window.ipc.postMessage(JSON.stringify({ type, payload })); + } + } + // ========== 导航栏 UI ========== const icons = { back: '', @@ -12,6 +21,14 @@ reload: '' }; + // Global state from Rust + window._sctNavState = { + canGoBack: false, + canGoForward: false, + isLoading: true, + url: window.location.href + }; + function createNavBar() { if (window.location.href === 'about:blank') return; if (document.getElementById('sct-navbar')) return; @@ -57,8 +74,8 @@ transition: all 0.15s ease; padding: 0; } - #sct-navbar button:hover { background: rgba(10, 49, 66, 0.9); color: #fff; } - #sct-navbar button:active { background: rgba(10, 49, 66, 1); transform: scale(0.95); } + #sct-navbar button:hover:not(:disabled) { background: rgba(10, 49, 66, 0.9); color: #fff; } + #sct-navbar button:active:not(:disabled) { background: rgba(10, 49, 66, 1); transform: scale(0.95); } #sct-navbar button:disabled { opacity: 0.35; cursor: not-allowed; } #sct-navbar button svg { display: block; } #sct-navbar-url { @@ -112,8 +129,8 @@ #sct-spinner { -webkit-animation: none; animation: none; } } - - + +
Page icon
@@ -121,51 +138,45 @@ `; document.body.insertBefore(nav, document.body.firstChild); + // Navigation buttons - send commands to Rust document.getElementById('sct-back').onclick = () => { - // Check if going back would result in about:blank - // If so, skip this entry and go back further - const beforeBackUrl = window.location.href; - history.back(); - - // After a short delay, if we landed on about:blank, go back again - setTimeout(() => { - if (window.location.href === 'about:blank' && beforeBackUrl !== 'about:blank') { - history.back(); - } - }, 100); + sendToRust('nav_back', {}); + }; + document.getElementById('sct-forward').onclick = () => { + sendToRust('nav_forward', {}); + }; + document.getElementById('sct-reload').onclick = () => { + sendToRust('nav_reload', {}); }; - document.getElementById('sct-forward').onclick = () => history.forward(); - document.getElementById('sct-reload').onclick = () => location.reload(); - // Update back button state and URL display on navigation - function updateNavBarState() { - const backBtn = document.getElementById('sct-back'); - const urlEl = document.getElementById('sct-navbar-url'); - const currentUrl = window.location.href; - - if (backBtn) { - // Disable back button if at start of history or at about:blank - backBtn.disabled = window.history.length <= 1 || currentUrl === 'about:blank'; - } - - if (urlEl) { - urlEl.value = currentUrl; - } - } + // Apply initial state from Rust + updateNavBarFromState(); - // Listen to popstate and hashchange to update nav bar - window.addEventListener('popstate', updateNavBarState); - window.addEventListener('hashchange', updateNavBarState); - - // Initial state - updateNavBarState(); - - // Spinner and favicon show/hide helpers + // Request initial state from Rust + sendToRust('get_nav_state', {}); + } + + // Update navbar UI based on state from Rust + function updateNavBarFromState() { + const state = window._sctNavState; + const backBtn = document.getElementById('sct-back'); + const forwardBtn = document.getElementById('sct-forward'); + const urlEl = document.getElementById('sct-navbar-url'); const spinner = document.getElementById('sct-spinner'); const faviconSlot = document.getElementById('sct-favicon-slot'); - const faviconImg = document.getElementById('sct-favicon'); - - function showSpinner() { + + if (backBtn) { + backBtn.disabled = !state.canGoBack; + } + if (forwardBtn) { + forwardBtn.disabled = !state.canGoForward; + } + if (urlEl && state.url) { + urlEl.value = state.url; + } + + // Show spinner when loading, show favicon when complete + if (state.isLoading) { if (spinner) { spinner.style.display = 'block'; spinner.setAttribute('aria-hidden', 'false'); @@ -174,93 +185,85 @@ if (faviconSlot) { faviconSlot.style.display = 'none'; } - } - - function hideSpin() { + } else { if (spinner) { spinner.style.display = 'none'; spinner.setAttribute('aria-hidden', 'true'); spinner.setAttribute('aria-busy', 'false'); } + // Show favicon when page is loaded + showFaviconIfAvailable(); } - - // Extract favicon from page and show it when ready - function showFaviconWhenReady() { - hideSpin(); - - // Try to find favicon from page - let faviconUrl = null; - - // 1. Look for link[rel="icon"] or link[rel="shortcut icon"] - const linkIcon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'); - if (linkIcon && linkIcon.href) { - faviconUrl = linkIcon.href; - } - - // 2. Look for og:image in meta tags (fallback) - if (!faviconUrl) { - const ogImage = document.querySelector('meta[property="og:image"]'); - if (ogImage && ogImage.content) { - faviconUrl = ogImage.content; - } - } - - // 3. Check page's existing favicon from document.head or body elements - if (!faviconUrl) { - const existingFavicon = document.querySelector('img[src*="favicon"], img[src*="icon"]'); - if (existingFavicon && existingFavicon.src) { - faviconUrl = existingFavicon.src; - } - } - - // Display favicon if found, otherwise hide slot - if (faviconUrl) { - if (faviconImg) { - faviconImg.src = faviconUrl; - faviconImg.onerror = () => { - if (faviconSlot) faviconSlot.style.display = 'none'; - }; - } - if (faviconSlot) { - faviconSlot.style.display = 'flex'; - } - } else if (faviconSlot) { - faviconSlot.style.display = 'none'; - } - } - - // Monitor document readyState to show favicon when page is ready - document.addEventListener('readystatechange', function () { - if (document.readyState === 'interactive' || document.readyState === 'complete') { - setTimeout(showFaviconWhenReady, 150); - } - }); - - // Also trigger favicon display on load event - window.addEventListener('load', function () { - setTimeout(showFaviconWhenReady, 150); - }); } + // Extract and show favicon from page + function showFaviconIfAvailable() { + const faviconSlot = document.getElementById('sct-favicon-slot'); + const faviconImg = document.getElementById('sct-favicon'); + + if (!faviconSlot || !faviconImg) return; + + // Try to find favicon from page + let faviconUrl = null; + + // 1. Look for link[rel="icon"] or link[rel="shortcut icon"] + const linkIcon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'); + if (linkIcon && linkIcon.href) { + faviconUrl = linkIcon.href; + } + + // 2. Look for og:image in meta tags (fallback) + if (!faviconUrl) { + const ogImage = document.querySelector('meta[property="og:image"]'); + if (ogImage && ogImage.content) { + faviconUrl = ogImage.content; + } + } + + // 3. Try default favicon.ico + if (!faviconUrl) { + try { + const origin = window.location.origin; + if (origin && origin !== 'null') { + faviconUrl = origin + '/favicon.ico'; + } + } catch (e) { + // Ignore + } + } + + // Display favicon if found + if (faviconUrl) { + faviconImg.src = faviconUrl; + faviconImg.onerror = () => { + faviconSlot.style.display = 'none'; + }; + faviconImg.onload = () => { + faviconSlot.style.display = 'flex'; + }; + } else { + faviconSlot.style.display = 'none'; + } + } + + // ========== Rust -> JS Message Handler ========== + // Rust will call this function to update navigation state + window._sctUpdateNavState = function(state) { + if (state) { + window._sctNavState = { + canGoBack: !!state.can_go_back, + canGoForward: !!state.can_go_forward, + isLoading: !!state.is_loading, + url: state.url || window.location.href + }; + updateNavBarFromState(); + } + }; + // 在 DOM 准备好时创建导航栏 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createNavBar); } else { createNavBar(); } - - // URL 变化时:进入加载状态,显示 spinner - window.addEventListener('popstate', () => { - // Show spinner when navigating via popstate (URL change) - const spinner = document.getElementById('sct-spinner'); - const faviconSlot = document.getElementById('sct-favicon-slot'); - if (spinner) { - spinner.style.display = 'block'; - spinner.setAttribute('aria-hidden', 'false'); - spinner.setAttribute('aria-busy', 'true'); - } - if (faviconSlot) { - faviconSlot.style.display = 'none'; - } - }); })(); From 79e12567591e5f9bc7eceeb7cc7a459f1e30e374 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Fri, 5 Dec 2025 10:12:06 +0800 Subject: [PATCH 3/4] feat: Optimize structure --- macos/Podfile.lock | 17 +- rust/Cargo.toml | 9 +- rust/src/api/webview_api.rs | 528 +------------------------------ rust/src/lib.rs | 1 + rust/src/webview/mod.rs | 16 + rust/src/webview/webview_impl.rs | 512 ++++++++++++++++++++++++++++++ rust/src/webview/webview_stub.rs | 39 +++ 7 files changed, 583 insertions(+), 539 deletions(-) create mode 100644 rust/src/webview/mod.rs create mode 100644 rust/src/webview/webview_impl.rs create mode 100644 rust/src/webview/webview_stub.rs diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 3612db0..63e0462 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,8 +1,6 @@ PODS: - desktop_multi_window (0.0.1): - FlutterMacOS - - desktop_webview_window (0.0.1): - - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS - file_picker (0.0.1): @@ -10,7 +8,8 @@ PODS: - FlutterMacOS (1.0.0) - macos_window_utils (1.0.0): - FlutterMacOS - - objective_c (0.0.1): + - path_provider_foundation (0.0.1): + - Flutter - FlutterMacOS - rust_builder (0.0.1): - FlutterMacOS @@ -23,12 +22,11 @@ PODS: DEPENDENCIES: - desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`) - - desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - - objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - rust_builder (from `Flutter/ephemeral/.symlinks/plugins/rust_builder/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -37,8 +35,6 @@ DEPENDENCIES: EXTERNAL SOURCES: desktop_multi_window: :path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos - desktop_webview_window: - :path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos file_picker: @@ -47,8 +43,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral macos_window_utils: :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos - objective_c: - :path: Flutter/ephemeral/.symlinks/plugins/objective_c/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin rust_builder: :path: Flutter/ephemeral/.symlinks/plugins/rust_builder/macos screen_retriever_macos: @@ -60,12 +56,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: desktop_multi_window: 93667594ccc4b88d91a97972fd3b1b89667fa80a - desktop_webview_window: 7e37af677d6d19294cb433d9b1d878ef78dffa4d device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 macos_window_utils: 23f54331a0fd51eea9e0ed347253bf48fd379d1d - objective_c: ec13431e45ba099cb734eb2829a5c1cd37986cba + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 rust_builder: f99e810dace35ac9783428b039d73d1caa5bfbc1 screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e589df6..fa42165 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -22,7 +22,7 @@ once_cell = "1.21.3" reqwest = { version = "0.12.24", features = ["rustls-tls-webpki-roots", "cookies", "gzip", "json", "stream"] } hickory-resolver = { version = "0.25.2" } anyhow = "1.0.100" -scopeguard = "1.2.0" +scopeguard = "1.0" notify-rust = "4.11.7" asar = "0.3.0" walkdir = "2.5.0" @@ -32,11 +32,14 @@ ndarray = "0.17.1" serde_json = "1.0.145" serde = { version = "1.0.228", features = ["derive"] } unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.2" } -wry = "0.53.5" -tao = { version = "0.34.5", features = ["serde"] } uuid = { version = "1.19.0", features = ["v4"] } parking_lot = "0.12.5" crossbeam-channel = "0.5.15" + +# WebView +[target.'cfg(not(target_os = "macos"))'.dependencies] +wry = "0.53.5" +tao = { version = "0.34.5", features = ["serde"] } image = { version = "0.25.9", default-features = false, features = ["ico"] } [target.'cfg(windows)'.dependencies] diff --git a/rust/src/api/webview_api.rs b/rust/src/api/webview_api.rs index 5a9dd58..3a1c538 100644 --- a/rust/src/api/webview_api.rs +++ b/rust/src/api/webview_api.rs @@ -1,32 +1,11 @@ // WebView API using wry + tao -// This module provides a cross-platform WebView implementation for Flutter -use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::sync::atomic::Ordering; -use crossbeam_channel::{bounded, Receiver, Sender}; use flutter_rust_bridge::frb; -use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use tao::dpi::{LogicalPosition, LogicalSize}; -use tao::event::{Event, WindowEvent}; -use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder}; -use tao::platform::run_return::EventLoopExtRunReturn; -use tao::window::{Icon, Window, WindowBuilder}; -use wry::{PageLoadEvent, WebView, WebViewBuilder}; -// Platform-specific imports for running event loop on any thread -#[cfg(target_os = "windows")] -use tao::platform::windows::EventLoopBuilderExtWindows; - -use once_cell::sync::Lazy; - -// Embed the app icon at compile time -static APP_ICON_DATA: &[u8] = include_bytes!("../../../windows/runner/resources/app_icon.ico"); - -// Embed the init script at compile time -static INIT_SCRIPT: &str = include_str!("../assets/webview_init_script.js"); +use crate::webview::{WEBVIEW_INSTANCES, send_command}; // ============ Data Structures ============ @@ -107,135 +86,12 @@ pub enum WebViewCommand { SetWindowPosition(i32, i32), } -// ============ Global State ============ - -type WebViewId = String; - -/// Navigation history manager to track back/forward capability -#[derive(Debug, Clone, Default)] -struct NavigationHistory { - /// List of URLs in history (excluding about:blank) - urls: Vec, - /// Current position in history (0-based index) - current_index: i32, -} - -impl NavigationHistory { - fn new() -> Self { - Self { - urls: Vec::new(), - current_index: -1, - } - } - - /// Push a new URL to history (when navigating to a new page) - fn push(&mut self, url: &str) { - // Skip about:blank - if url == "about:blank" { - return; - } - - // If we're not at the end of history, truncate forward history - if self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) { - self.urls.truncate((self.current_index + 1) as usize); - } - - // Don't add duplicate consecutive URLs - if self.urls.last().map(|s| s.as_str()) != Some(url) { - self.urls.push(url.to_string()); - } - self.current_index = (self.urls.len() as i32) - 1; - } - - /// Check if we can go back (not at first real page) - fn can_go_back(&self) -> bool { - self.current_index > 0 - } - - /// Check if we can go forward - fn can_go_forward(&self) -> bool { - self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) - } - - /// Go back in history, returns true if successful - fn go_back(&mut self) -> bool { - if self.can_go_back() { - self.current_index -= 1; - true - } else { - false - } - } - - /// Go forward in history, returns true if successful - fn go_forward(&mut self) -> bool { - if self.can_go_forward() { - self.current_index += 1; - true - } else { - false - } - } - - /// Get current URL - fn current_url(&self) -> Option<&str> { - if self.current_index >= 0 && (self.current_index as usize) < self.urls.len() { - Some(&self.urls[self.current_index as usize]) - } else { - None - } - } -} - -struct WebViewInstance { - command_sender: Sender, - event_receiver: Receiver, - state: Arc>, - is_closed: Arc, -} - -static WEBVIEW_INSTANCES: Lazy>> = - Lazy::new(|| RwLock::new(HashMap::new())); - -// Custom event type for the event loop -#[derive(Debug, Clone)] -#[allow(dead_code)] -enum UserEvent { - Command(WebViewCommand), - Quit, -} - // ============ Public API ============ /// Create a new WebView window and return its ID #[frb(sync)] pub fn webview_create(config: WebViewConfiguration) -> Result { - let id = uuid::Uuid::new_v4().to_string(); - let id_clone = id.clone(); - - let (cmd_tx, cmd_rx) = bounded::(100); - let (event_tx, event_rx) = bounded::(100); - let state = Arc::new(RwLock::new(WebViewNavigationState::default())); - let state_clone = Arc::clone(&state); - let is_closed = Arc::new(AtomicBool::new(false)); - let is_closed_clone = Arc::clone(&is_closed); - - std::thread::spawn(move || { - run_webview_loop(id_clone, config, cmd_rx, event_tx, state_clone, is_closed_clone); - }); - - // Wait a moment for the window to be created - std::thread::sleep(std::time::Duration::from_millis(100)); - - let instance = WebViewInstance { - command_sender: cmd_tx, - event_receiver: event_rx, - state, - is_closed, - }; - - WEBVIEW_INSTANCES.write().insert(id.clone(), instance); - Ok(id) + crate::webview::create_webview(config) } /// Navigate to a URL @@ -351,381 +207,3 @@ pub fn webview_list_all() -> Vec { drop(instances); keys } - -// ============ Internal Implementation ============ - -fn send_command(id: &str, command: WebViewCommand) -> Result<(), String> { - let instances = WEBVIEW_INSTANCES.read(); - let instance = instances - .get(id) - .ok_or_else(|| format!("WebView instance not found: {}", id))?; - - if instance.is_closed.load(Ordering::SeqCst) { - return Err("WebView is closed".to_string()); - } - - instance - .command_sender - .send(command) - .map_err(|e| e.to_string()) -} - -fn run_webview_loop( - _id: String, - config: WebViewConfiguration, - cmd_rx: Receiver, - event_tx: Sender, - state: Arc>, - is_closed: Arc, -) { - // Create event loop with any_thread support for non-main thread execution - #[cfg(target_os = "windows")] - let mut event_loop: EventLoop = EventLoopBuilder::with_user_event() - .with_any_thread(true) - .build(); - - #[cfg(not(target_os = "windows"))] - let mut event_loop: EventLoop = EventLoopBuilder::with_user_event().build(); - - let proxy = event_loop.create_proxy(); - - // Load window icon from embedded ICO file - let window_icon = load_app_icon(); - - // Build the window with decorations (title bar provided by OS) - // Set dark background color matching app theme (#1a1a1a) - let mut window_builder = WindowBuilder::new() - .with_title(&config.title) - .with_inner_size(LogicalSize::new(config.width, config.height)) - .with_visible(true); - - // Set window icon if loaded successfully - if let Some(icon) = window_icon { - window_builder = window_builder.with_window_icon(Some(icon)); - } - - let window = window_builder.build(&event_loop) - .expect("Failed to create window"); - - let window = Arc::new(window); - - // Navigation history for tracking back/forward state - let nav_history = Arc::new(RwLock::new(NavigationHistory::new())); - - let event_tx_clone = event_tx.clone(); - let nav_history_ipc = Arc::clone(&nav_history); - let state_ipc = Arc::clone(&state); - - // Create web context with custom data directory if provided - let mut web_context = config - .user_data_folder - .as_ref() - .map(|path| wry::WebContext::new(Some(std::path::PathBuf::from(path)))) - .unwrap_or_default(); - - let mut builder = WebViewBuilder::new_with_web_context(&mut web_context) - .with_url("about:blank") - .with_devtools(config.enable_devtools) - .with_transparent(config.transparent) - .with_background_color((26, 26, 26, 255)) // Dark background #1a1a1a - .with_initialization_script(INIT_SCRIPT); - - // Set user agent if provided - if let Some(ref user_agent) = config.user_agent { - builder = builder.with_user_agent(user_agent); - } - - // Store proxy for IPC commands - let proxy_ipc = proxy.clone(); - - let webview = builder - .with_ipc_handler(move |message| { - let msg = message.body().to_string(); - - // Try to parse as navigation command from JS - if let Ok(parsed) = serde_json::from_str::(&msg) { - if let Some(msg_type) = parsed.get("type").and_then(|v| v.as_str()) { - match msg_type { - "nav_back" => { - // Check if we can go back (avoid about:blank) - let can_back = nav_history_ipc.read().can_go_back(); - if can_back { - let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoBack)); - } - return; - } - "nav_forward" => { - let can_forward = nav_history_ipc.read().can_go_forward(); - if can_forward { - let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoForward)); - } - return; - } - "nav_reload" => { - let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::Reload)); - return; - } - "get_nav_state" => { - // Send current state to JS - let state_guard = state_ipc.read(); - let history = nav_history_ipc.read(); - let state_json = serde_json::json!({ - "can_go_back": history.can_go_back(), - "can_go_forward": history.can_go_forward(), - "is_loading": state_guard.is_loading, - "url": state_guard.url - }); - let script = format!("window._sctUpdateNavState({})", state_json); - let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); - return; - } - _ => {} - } - } - } - - // Forward other messages to Dart - let _ = event_tx_clone.send(WebViewEvent::WebMessage { message: msg }); - }) - .with_navigation_handler({ - let event_tx = event_tx.clone(); - let state = Arc::clone(&state); - let nav_history = Arc::clone(&nav_history); - move |uri| { - // Skip about:blank for navigation events - if uri == "about:blank" { - return true; - } - - { - let mut state_guard = state.write(); - state_guard.url = uri.clone(); - state_guard.is_loading = true; - // Update can_go_back/can_go_forward from history - let history = nav_history.read(); - state_guard.can_go_back = history.can_go_back(); - state_guard.can_go_forward = history.can_go_forward(); - } - let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri }); - true // Allow navigation - } - }) - .with_on_page_load_handler({ - let event_tx = event_tx.clone(); - let state = Arc::clone(&state); - let nav_history = Arc::clone(&nav_history); - let proxy = proxy.clone(); - move |event, url| { - // Skip about:blank - if url == "about:blank" { - return; - } - - match event { - PageLoadEvent::Started => { - let can_back; - let can_forward; - { - let mut state_guard = state.write(); - state_guard.url = url.clone(); - state_guard.is_loading = true; - let history = nav_history.read(); - can_back = history.can_go_back(); - can_forward = history.can_go_forward(); - state_guard.can_go_back = can_back; - state_guard.can_go_forward = can_forward; - } - // Send loading state to JS - let state_json = serde_json::json!({ - "can_go_back": can_back, - "can_go_forward": can_forward, - "is_loading": true, - "url": url - }); - let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); - let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); - } - PageLoadEvent::Finished => { - let can_back; - let can_forward; - { - // Add to history when page finishes loading - let mut history = nav_history.write(); - history.push(&url); - can_back = history.can_go_back(); - can_forward = history.can_go_forward(); - } - { - let mut state_guard = state.write(); - state_guard.url = url.clone(); - state_guard.is_loading = false; - state_guard.can_go_back = can_back; - state_guard.can_go_forward = can_forward; - } - let _ = event_tx.send(WebViewEvent::NavigationCompleted { url: url.clone() }); - - // Send completed state to JS - let state_json = serde_json::json!({ - "can_go_back": can_back, - "can_go_forward": can_forward, - "is_loading": false, - "url": url - }); - let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); - let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); - } - } - } - }) - .build(&window) - .expect("Failed to create webview"); - - let webview = Arc::new(webview); - let webview_cmd = Arc::clone(&webview); - - // Spawn command handler thread - let proxy_cmd = proxy.clone(); - std::thread::spawn(move || { - while let Ok(cmd) = cmd_rx.recv() { - if proxy_cmd.send_event(UserEvent::Command(cmd)).is_err() { - break; - } - } - }); - - // Run the event loop with run_return so we can properly cleanup - event_loop.run_return(|event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - is_closed.store(true, Ordering::SeqCst); - let _ = event_tx.send(WebViewEvent::WindowClosed); - *control_flow = ControlFlow::Exit; - } - Event::UserEvent(user_event) => match user_event { - UserEvent::Command(cmd) => { - handle_command(&webview_cmd, &window, cmd, &state, &nav_history, &event_tx, &is_closed, control_flow); - } - UserEvent::Quit => { - is_closed.store(true, Ordering::SeqCst); - *control_flow = ControlFlow::Exit; - } - }, - _ => {} - } - }); - - // Explicitly drop in correct order: webview first, then web_context, then window - drop(webview_cmd); - drop(web_context); - drop(window); -} - -fn handle_command( - webview: &Arc, - window: &Arc, - command: WebViewCommand, - state: &Arc>, - nav_history: &Arc>, - event_tx: &Sender, - is_closed: &Arc, - control_flow: &mut ControlFlow, -) { - match command { - WebViewCommand::Navigate(url) => { - { - let mut s = state.write(); - s.url = url.clone(); - s.is_loading = true; - } - let _ = webview.load_url(&url); - let _ = event_tx.send(WebViewEvent::NavigationStarted { url }); - } - WebViewCommand::GoBack => { - // Update history index before navigation - let can_go = { - let mut history = nav_history.write(); - history.go_back() - }; - if can_go { - let _ = webview.evaluate_script("history.back()"); - } - } - WebViewCommand::GoForward => { - // Update history index before navigation - let can_go = { - let mut history = nav_history.write(); - history.go_forward() - }; - if can_go { - let _ = webview.evaluate_script("history.forward()"); - } - } - WebViewCommand::Reload => { - let _ = webview.evaluate_script("location.reload()"); - } - WebViewCommand::Stop => { - let _ = webview.evaluate_script("window.stop()"); - } - WebViewCommand::ExecuteScript(script) => { - let _ = webview.evaluate_script(&script); - } - WebViewCommand::SetVisibility(visible) => { - window.set_visible(visible); - } - WebViewCommand::Close => { - // Properly close the window and exit the event loop - is_closed.store(true, Ordering::SeqCst); - let _ = event_tx.send(WebViewEvent::WindowClosed); - // Exit the event loop - this will cause cleanup - *control_flow = ControlFlow::Exit; - } - WebViewCommand::SetWindowSize(width, height) => { - let _ = window.set_inner_size(LogicalSize::new(width, height)); - } - WebViewCommand::SetWindowPosition(x, y) => { - window.set_outer_position(LogicalPosition::new(x, y)); - } - } -} - -/// Load the application icon from embedded ICO data -fn load_app_icon() -> Option { - use std::io::Cursor; - use image::ImageReader; - - // Parse the ICO file from embedded bytes - let cursor = Cursor::new(APP_ICON_DATA); - let reader = match ImageReader::new(cursor).with_guessed_format() { - Ok(r) => r, - Err(e) => { - eprintln!("[SCToolbox] Failed to create image reader: {}", e); - return None; - } - }; - - let img = match reader.decode() { - Ok(img) => img, - Err(e) => { - eprintln!("[SCToolbox] Failed to decode icon: {}", e); - return None; - } - }; - - // Convert to RGBA8 - let rgba = img.to_rgba8(); - let (width, height) = rgba.dimensions(); - let raw_data = rgba.into_raw(); - - match Icon::from_rgba(raw_data, width, height) { - Ok(icon) => Some(icon), - Err(e) => { - eprintln!("[SCToolbox] Failed to create icon: {:?}", e); - None - } - } -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 118a7c7..3e7ad6c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,3 +2,4 @@ pub mod api; mod frb_generated; pub mod http_package; pub mod ort_models; +pub mod webview; diff --git a/rust/src/webview/mod.rs b/rust/src/webview/mod.rs new file mode 100644 index 0000000..51845de --- /dev/null +++ b/rust/src/webview/mod.rs @@ -0,0 +1,16 @@ +// WebView 独立模块 +// 此模块包含 wry + tao WebView 的完整实现 +// macOS 平台不支持 WebView,因为 tao 的 EventLoop 必须在主线程运行,与 Flutter 冲突 + +#[cfg(not(target_os = "macos"))] +mod webview_impl; + +#[cfg(not(target_os = "macos"))] +pub use webview_impl::*; + +// macOS 平台提供空实现 +#[cfg(target_os = "macos")] +mod webview_stub; + +#[cfg(target_os = "macos")] +pub use webview_stub::*; diff --git a/rust/src/webview/webview_impl.rs b/rust/src/webview/webview_impl.rs new file mode 100644 index 0000000..764e3c0 --- /dev/null +++ b/rust/src/webview/webview_impl.rs @@ -0,0 +1,512 @@ +// WebView 实现 (Windows/Linux) +// 使用 wry + tao 实现跨平台 WebView + +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use crossbeam_channel::{bounded, Receiver, Sender}; +use parking_lot::RwLock; +use once_cell::sync::Lazy; +use tao::dpi::{LogicalPosition, LogicalSize}; +use tao::event::{Event, WindowEvent}; +use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder}; +use tao::platform::run_return::EventLoopExtRunReturn; +use tao::window::{Icon, Window, WindowBuilder}; +use wry::{PageLoadEvent, WebView, WebViewBuilder}; + +#[cfg(target_os = "windows")] +use tao::platform::windows::EventLoopBuilderExtWindows; + +use crate::api::webview_api::{ + WebViewConfiguration, WebViewNavigationState, WebViewEvent, WebViewCommand, +}; + +// Embed the app icon at compile time +static APP_ICON_DATA: &[u8] = include_bytes!("../../windows/runner/resources/app_icon.ico"); + +// Embed the init script at compile time +static INIT_SCRIPT: &str = include_str!("../assets/webview_init_script.js"); + +// ============ Types ============ + +pub type WebViewId = String; + +/// Navigation history manager to track back/forward capability +#[derive(Debug, Clone, Default)] +struct NavigationHistory { + urls: Vec, + current_index: i32, +} + +impl NavigationHistory { + fn new() -> Self { + Self { + urls: Vec::new(), + current_index: -1, + } + } + + fn push(&mut self, url: &str) { + if url == "about:blank" { + return; + } + + if self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) { + self.urls.truncate((self.current_index + 1) as usize); + } + + if self.urls.last().map(|s| s.as_str()) != Some(url) { + self.urls.push(url.to_string()); + } + self.current_index = (self.urls.len() as i32) - 1; + } + + fn can_go_back(&self) -> bool { + self.current_index > 0 + } + + fn can_go_forward(&self) -> bool { + self.current_index >= 0 && (self.current_index as usize) < self.urls.len().saturating_sub(1) + } + + fn go_back(&mut self) -> bool { + if self.can_go_back() { + self.current_index -= 1; + true + } else { + false + } + } + + fn go_forward(&mut self) -> bool { + if self.can_go_forward() { + self.current_index += 1; + true + } else { + false + } + } + + #[allow(dead_code)] + fn current_url(&self) -> Option<&str> { + if self.current_index >= 0 && (self.current_index as usize) < self.urls.len() { + Some(&self.urls[self.current_index as usize]) + } else { + None + } + } +} + +pub struct WebViewInstance { + pub command_sender: Sender, + pub event_receiver: Receiver, + pub state: Arc>, + pub is_closed: Arc, +} + +pub static WEBVIEW_INSTANCES: Lazy>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +// Custom event type for the event loop +#[derive(Debug, Clone)] +#[allow(dead_code)] +enum UserEvent { + Command(WebViewCommand), + Quit, +} + +// ============ Public Implementation ============ + +/// Create a new WebView window +pub fn create_webview(config: WebViewConfiguration) -> Result { + let id = uuid::Uuid::new_v4().to_string(); + let id_clone = id.clone(); + + let (cmd_tx, cmd_rx) = bounded::(100); + let (event_tx, event_rx) = bounded::(100); + let state = Arc::new(RwLock::new(WebViewNavigationState::default())); + let state_clone = Arc::clone(&state); + let is_closed = Arc::new(AtomicBool::new(false)); + let is_closed_clone = Arc::clone(&is_closed); + + std::thread::spawn(move || { + run_webview_loop(id_clone, config, cmd_rx, event_tx, state_clone, is_closed_clone); + }); + + // Wait a moment for the window to be created + std::thread::sleep(std::time::Duration::from_millis(100)); + + let instance = WebViewInstance { + command_sender: cmd_tx, + event_receiver: event_rx, + state, + is_closed, + }; + + WEBVIEW_INSTANCES.write().insert(id.clone(), instance); + Ok(id) +} + +pub fn send_command(id: &str, command: WebViewCommand) -> Result<(), String> { + let instances = WEBVIEW_INSTANCES.read(); + let instance = instances + .get(id) + .ok_or_else(|| format!("WebView instance not found: {}", id))?; + + if instance.is_closed.load(Ordering::SeqCst) { + return Err("WebView is closed".to_string()); + } + + instance + .command_sender + .send(command) + .map_err(|e| e.to_string()) +} + +// ============ Internal Implementation ============ + +fn run_webview_loop( + _id: String, + config: WebViewConfiguration, + cmd_rx: Receiver, + event_tx: Sender, + state: Arc>, + is_closed: Arc, +) { + // Create event loop with any_thread support for non-main thread execution + #[cfg(target_os = "windows")] + let mut event_loop: EventLoop = EventLoopBuilder::with_user_event() + .with_any_thread(true) + .build(); + + #[cfg(not(target_os = "windows"))] + let mut event_loop: EventLoop = EventLoopBuilder::with_user_event().build(); + + let proxy = event_loop.create_proxy(); + + // Load window icon from embedded ICO file + let window_icon = load_app_icon(); + + // Build the window with decorations (title bar provided by OS) + let mut window_builder = WindowBuilder::new() + .with_title(&config.title) + .with_inner_size(LogicalSize::new(config.width, config.height)) + .with_visible(true); + + // Set window icon if loaded successfully + if let Some(icon) = window_icon { + window_builder = window_builder.with_window_icon(Some(icon)); + } + + let window = window_builder.build(&event_loop) + .expect("Failed to create window"); + + let window = Arc::new(window); + + // Navigation history for tracking back/forward state + let nav_history = Arc::new(RwLock::new(NavigationHistory::new())); + + let event_tx_clone = event_tx.clone(); + let nav_history_ipc = Arc::clone(&nav_history); + let state_ipc = Arc::clone(&state); + + // Create web context with custom data directory if provided + let mut web_context = config + .user_data_folder + .as_ref() + .map(|path| wry::WebContext::new(Some(std::path::PathBuf::from(path)))) + .unwrap_or_default(); + + let mut builder = WebViewBuilder::new_with_web_context(&mut web_context) + .with_url("about:blank") + .with_devtools(config.enable_devtools) + .with_transparent(config.transparent) + .with_background_color((26, 26, 26, 255)) // Dark background #1a1a1a + .with_initialization_script(INIT_SCRIPT); + + // Set user agent if provided + if let Some(ref user_agent) = config.user_agent { + builder = builder.with_user_agent(user_agent); + } + + // Store proxy for IPC commands + let proxy_ipc = proxy.clone(); + + let webview = builder + .with_ipc_handler(move |message| { + let msg = message.body().to_string(); + + // Try to parse as navigation command from JS + if let Ok(parsed) = serde_json::from_str::(&msg) { + if let Some(msg_type) = parsed.get("type").and_then(|v| v.as_str()) { + match msg_type { + "nav_back" => { + let can_back = nav_history_ipc.read().can_go_back(); + if can_back { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoBack)); + } + return; + } + "nav_forward" => { + let can_forward = nav_history_ipc.read().can_go_forward(); + if can_forward { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::GoForward)); + } + return; + } + "nav_reload" => { + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::Reload)); + return; + } + "get_nav_state" => { + let state_guard = state_ipc.read(); + let history = nav_history_ipc.read(); + let state_json = serde_json::json!({ + "can_go_back": history.can_go_back(), + "can_go_forward": history.can_go_forward(), + "is_loading": state_guard.is_loading, + "url": state_guard.url + }); + let script = format!("window._sctUpdateNavState({})", state_json); + let _ = proxy_ipc.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); + return; + } + _ => {} + } + } + } + + // Forward other messages to Dart + let _ = event_tx_clone.send(WebViewEvent::WebMessage { message: msg }); + }) + .with_navigation_handler({ + let event_tx = event_tx.clone(); + let state = Arc::clone(&state); + let nav_history = Arc::clone(&nav_history); + move |uri| { + if uri == "about:blank" { + return true; + } + + { + let mut state_guard = state.write(); + state_guard.url = uri.clone(); + state_guard.is_loading = true; + let history = nav_history.read(); + state_guard.can_go_back = history.can_go_back(); + state_guard.can_go_forward = history.can_go_forward(); + } + let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri }); + true + } + }) + .with_on_page_load_handler({ + let event_tx = event_tx.clone(); + let state = Arc::clone(&state); + let nav_history = Arc::clone(&nav_history); + let proxy = proxy.clone(); + move |event, url| { + if url == "about:blank" { + return; + } + + match event { + PageLoadEvent::Started => { + let can_back; + let can_forward; + { + let mut state_guard = state.write(); + state_guard.url = url.clone(); + state_guard.is_loading = true; + let history = nav_history.read(); + can_back = history.can_go_back(); + can_forward = history.can_go_forward(); + state_guard.can_go_back = can_back; + state_guard.can_go_forward = can_forward; + } + let state_json = serde_json::json!({ + "can_go_back": can_back, + "can_go_forward": can_forward, + "is_loading": true, + "url": url + }); + let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); + let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); + } + PageLoadEvent::Finished => { + let can_back; + let can_forward; + { + let mut history = nav_history.write(); + history.push(&url); + can_back = history.can_go_back(); + can_forward = history.can_go_forward(); + } + { + let mut state_guard = state.write(); + state_guard.url = url.clone(); + state_guard.is_loading = false; + state_guard.can_go_back = can_back; + state_guard.can_go_forward = can_forward; + } + let _ = event_tx.send(WebViewEvent::NavigationCompleted { url: url.clone() }); + + let state_json = serde_json::json!({ + "can_go_back": can_back, + "can_go_forward": can_forward, + "is_loading": false, + "url": url + }); + let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); + let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); + } + } + } + }) + .build(&window) + .expect("Failed to create webview"); + + let webview = Arc::new(webview); + let webview_cmd = Arc::clone(&webview); + + // Spawn command handler thread + let proxy_cmd = proxy.clone(); + std::thread::spawn(move || { + while let Ok(cmd) = cmd_rx.recv() { + if proxy_cmd.send_event(UserEvent::Command(cmd)).is_err() { + break; + } + } + }); + + // Run the event loop with run_return so we can properly cleanup + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + is_closed.store(true, Ordering::SeqCst); + let _ = event_tx.send(WebViewEvent::WindowClosed); + *control_flow = ControlFlow::Exit; + } + Event::UserEvent(user_event) => match user_event { + UserEvent::Command(cmd) => { + handle_command(&webview_cmd, &window, cmd, &state, &nav_history, &event_tx, &is_closed, control_flow); + } + UserEvent::Quit => { + is_closed.store(true, Ordering::SeqCst); + *control_flow = ControlFlow::Exit; + } + }, + _ => {} + } + }); + + // Explicitly drop in correct order + drop(webview_cmd); + drop(web_context); + drop(window); +} + +fn handle_command( + webview: &Arc, + window: &Arc, + command: WebViewCommand, + state: &Arc>, + nav_history: &Arc>, + event_tx: &Sender, + is_closed: &Arc, + control_flow: &mut ControlFlow, +) { + match command { + WebViewCommand::Navigate(url) => { + { + let mut s = state.write(); + s.url = url.clone(); + s.is_loading = true; + } + let _ = webview.load_url(&url); + let _ = event_tx.send(WebViewEvent::NavigationStarted { url }); + } + WebViewCommand::GoBack => { + let can_go = { + let mut history = nav_history.write(); + history.go_back() + }; + if can_go { + let _ = webview.evaluate_script("history.back()"); + } + } + WebViewCommand::GoForward => { + let can_go = { + let mut history = nav_history.write(); + history.go_forward() + }; + if can_go { + let _ = webview.evaluate_script("history.forward()"); + } + } + WebViewCommand::Reload => { + let _ = webview.evaluate_script("location.reload()"); + } + WebViewCommand::Stop => { + let _ = webview.evaluate_script("window.stop()"); + } + WebViewCommand::ExecuteScript(script) => { + let _ = webview.evaluate_script(&script); + } + WebViewCommand::SetVisibility(visible) => { + window.set_visible(visible); + } + WebViewCommand::Close => { + is_closed.store(true, Ordering::SeqCst); + let _ = event_tx.send(WebViewEvent::WindowClosed); + *control_flow = ControlFlow::Exit; + } + WebViewCommand::SetWindowSize(width, height) => { + let _ = window.set_inner_size(LogicalSize::new(width, height)); + } + WebViewCommand::SetWindowPosition(x, y) => { + window.set_outer_position(LogicalPosition::new(x, y)); + } + } +} + +/// Load the application icon from embedded ICO data +fn load_app_icon() -> Option { + use std::io::Cursor; + use image::ImageReader; + + let cursor = Cursor::new(APP_ICON_DATA); + let reader = match ImageReader::new(cursor).with_guessed_format() { + Ok(r) => r, + Err(e) => { + eprintln!("[SCToolbox] Failed to create image reader: {}", e); + return None; + } + }; + + let img = match reader.decode() { + Ok(img) => img, + Err(e) => { + eprintln!("[SCToolbox] Failed to decode icon: {}", e); + return None; + } + }; + + let rgba = img.to_rgba8(); + let (width, height) = rgba.dimensions(); + let raw_data = rgba.into_raw(); + + match Icon::from_rgba(raw_data, width, height) { + Ok(icon) => Some(icon), + Err(e) => { + eprintln!("[SCToolbox] Failed to create icon: {:?}", e); + None + } + } +} diff --git a/rust/src/webview/webview_stub.rs b/rust/src/webview/webview_stub.rs new file mode 100644 index 0000000..15e9089 --- /dev/null +++ b/rust/src/webview/webview_stub.rs @@ -0,0 +1,39 @@ +// macOS WebView 存根实现 +// macOS 平台不支持 WebView,因为 tao 的 EventLoop 必须在主线程运行,与 Flutter 冲突 + +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use crossbeam_channel::{Receiver, Sender}; +use parking_lot::RwLock; +use once_cell::sync::Lazy; + +use crate::api::webview_api::{ + WebViewConfiguration, WebViewNavigationState, WebViewEvent, WebViewCommand, +}; + +// ============ Types ============ + +pub type WebViewId = String; + +pub struct WebViewInstance { + pub command_sender: Sender, + pub event_receiver: Receiver, + pub state: Arc>, + pub is_closed: Arc, +} + +pub static WEBVIEW_INSTANCES: Lazy>> = + Lazy::new(|| RwLock::new(HashMap::new())); + +// ============ macOS 存根实现 ============ + +/// macOS 上 WebView 不可用 +pub fn create_webview(_config: WebViewConfiguration) -> Result { + Err("WebView is not supported on macOS".to_string()) +} + +pub fn send_command(id: &str, _command: WebViewCommand) -> Result<(), String> { + Err(format!("WebView instance not found: {} (WebView not supported on macOS)", id)) +} From dcb288597f979b8c471fea9a3947ba3a76968222 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Fri, 5 Dec 2025 10:28:12 +0800 Subject: [PATCH 4/4] feat: web nav update --- rust/src/webview/webview_impl.rs | 25 ++++++++++++++++--- .../webview_init_script.js | 0 2 files changed, 21 insertions(+), 4 deletions(-) rename rust/src/{assets => webview}/webview_init_script.js (100%) diff --git a/rust/src/webview/webview_impl.rs b/rust/src/webview/webview_impl.rs index 764e3c0..d5c0c38 100644 --- a/rust/src/webview/webview_impl.rs +++ b/rust/src/webview/webview_impl.rs @@ -23,10 +23,10 @@ use crate::api::webview_api::{ }; // Embed the app icon at compile time -static APP_ICON_DATA: &[u8] = include_bytes!("../../windows/runner/resources/app_icon.ico"); +static APP_ICON_DATA: &[u8] = include_bytes!("../../../windows/runner/resources/app_icon.ico"); // Embed the init script at compile time -static INIT_SCRIPT: &str = include_str!("../assets/webview_init_script.js"); +static INIT_SCRIPT: &str = include_str!("webview_init_script.js"); // ============ Types ============ @@ -284,19 +284,36 @@ fn run_webview_loop( let event_tx = event_tx.clone(); let state = Arc::clone(&state); let nav_history = Arc::clone(&nav_history); + let proxy = proxy.clone(); move |uri| { if uri == "about:blank" { return true; } + let can_back; + let can_forward; { let mut state_guard = state.write(); state_guard.url = uri.clone(); state_guard.is_loading = true; let history = nav_history.read(); - state_guard.can_go_back = history.can_go_back(); - state_guard.can_go_forward = history.can_go_forward(); + can_back = history.can_go_back(); + can_forward = history.can_go_forward(); + state_guard.can_go_back = can_back; + state_guard.can_go_forward = can_forward; } + + // Send loading state to JS immediately when navigation starts + // This makes the spinner appear on the current page before navigating away + let state_json = serde_json::json!({ + "can_go_back": can_back, + "can_go_forward": can_forward, + "is_loading": true, + "url": uri + }); + let script = format!("window._sctUpdateNavState && window._sctUpdateNavState({})", state_json); + let _ = proxy.send_event(UserEvent::Command(WebViewCommand::ExecuteScript(script))); + let _ = event_tx.send(WebViewEvent::NavigationStarted { url: uri }); true } diff --git a/rust/src/assets/webview_init_script.js b/rust/src/webview/webview_init_script.js similarity index 100% rename from rust/src/assets/webview_init_script.js rename to rust/src/webview/webview_init_script.js