Merge pull request #161 from StarCitizenToolBox/feat-wry_webview

feat: Replace desktop_webview_window with tao&wry , from tauri
This commit is contained in:
xkeyC 2025-12-05 10:29:33 +08:00 committed by GitHub
commit 855ea1fe8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 7158 additions and 550 deletions

View File

@ -1,3 +1,4 @@
{
"dart.flutterSdkPath": ".fvm/versions/stable"
"dart.flutterSdkPath": ".fvm/versions/stable",
"cmake.ignoreCMakeListsMissing": true
}

View File

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

View File

@ -207,7 +207,7 @@ function ReportUnTranslate(k, v) {
const jsRegex = /(?:^|[^<])<script[^>]*>[\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;

View File

@ -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<WebViewEvent> webviewPollEvents({required String id}) =>
RustLib.instance.api.crateApiWebviewApiWebviewPollEvents(id: id);
/// Get a list of all active WebView IDs
List<String> 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<WebViewConfiguration> 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<WebViewNavigationState> default_() =>
RustLib.instance.api.crateApiWebviewApiWebViewNavigationStateDefault();
}

File diff suppressed because it is too large Load Diff

View File

@ -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<void> sendNotify({

View File

@ -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<RustLibApi, RustLibApiImpl, RustLibWire> {
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<void> crateApiOrtApiUnloadTranslationModel({required String modelKey});
Future<WebViewConfiguration> crateApiWebviewApiWebViewConfigurationDefault();
Future<WebViewNavigationState>
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<String> crateApiWebviewApiWebviewListAll();
void crateApiWebviewApiWebviewNavigate({
required String id,
required String url,
});
List<WebViewEvent> 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<void> crateApiRsProcessWrite({
required int rsPid,
required String data,
@ -893,6 +950,451 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["modelKey"],
);
@override
Future<WebViewConfiguration> 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<WebViewNavigationState>
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<String> 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<WebViewEvent> 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<void> 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<dynamic>).map(dco_decode_record_string_string).toList();
}
@protected
List<WebViewEvent> dco_decode_list_web_view_event(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return (raw as List<dynamic>).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<dynamic>;
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<dynamic>;
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<WebViewEvent> 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_ = <WebViewEvent>[];
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<WebViewEvent> 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);
}
}

View File

@ -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<RustLibWire> {
@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<RustLibWire> {
@protected
List<(String, String)> dco_decode_list_record_string_string(dynamic raw);
@protected
List<WebViewEvent> 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<RustLibWire> {
@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<RustLibWire> {
@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<RustLibWire> {
SseDeserializer deserializer,
);
@protected
List<WebViewEvent> 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<RustLibWire> {
@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<wire_cst_list_prim_u_8_strict> cst_encode_AnyhowException(
AnyhowException raw,
@ -324,6 +365,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return wire.cst_new_box_autoadd_u_64(cst_encode_u_64(raw));
}
@protected
ffi.Pointer<wire_cst_web_view_configuration>
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<RustLibWire> {
return ans;
}
@protected
ffi.Pointer<wire_cst_list_web_view_event> cst_encode_list_web_view_event(
List<WebViewEvent> 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<wire_cst_list_record_string_string>
cst_encode_opt_Map_String_String_None(Map<String, String>? raw) {
@ -449,6 +511,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
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<wire_cst_web_view_configuration> 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<RustLibWire> {
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<RustLibWire> {
@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<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_list_web_view_event(
List<WebViewEvent> self,
SseSerializer serializer,
);
@protected
void sse_encode_my_http_version(MyHttpVersion self, SseSerializer serializer);
@ -697,6 +846,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@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<wire_cst_list_prim_u_8_strict>)
>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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<void Function(int)>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_close(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_create(
ffi.Pointer<wire_cst_web_view_configuration> 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<wire_cst_web_view_configuration>,
)
>
>(
'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<wire_cst_web_view_configuration>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_execute_script(
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_get_state(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_back(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_go_forward(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_is_closed(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
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<ffi.NativeFunction<WireSyncRust2DartDco Function()>>(
'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 Function()>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_navigate(
ffi.Pointer<wire_cst_list_prim_u_8_strict> id,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_poll_events(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_reload(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_visibility(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
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<wire_cst_list_prim_u_8_strict>,
bool,
)
>();
WireSyncRust2DartDco
wire__crate__api__webview_api__webview_set_window_position(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
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<wire_cst_list_prim_u_8_strict>,
int,
int,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_set_window_size(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
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<wire_cst_list_prim_u_8_strict>,
int,
int,
)
>();
WireSyncRust2DartDco wire__crate__api__webview_api__webview_stop(
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
)
>();
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<ffi.Pointer<ffi.Uint64> Function(int)>();
ffi.Pointer<wire_cst_web_view_configuration>
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<wire_cst_web_view_configuration> 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<wire_cst_web_view_configuration> Function()
>();
ffi.Pointer<wire_cst_list_String> cst_new_list_String(int len) {
return _cst_new_list_String(len);
}
@ -1519,6 +2110,21 @@ class RustLibWire implements BaseWire {
ffi.Pointer<wire_cst_list_record_string_string> Function(int)
>();
ffi.Pointer<wire_cst_list_web_view_event> 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<wire_cst_list_web_view_event> 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<ffi.Pointer<wire_cst_list_web_view_event> 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<wire_cst_list_prim_u_8_strict> title;
@ffi.Uint32()
external int width;
@ffi.Uint32()
external int height;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> user_data_folder;
@ffi.Bool()
external bool enable_devtools;
@ffi.Bool()
external bool transparent;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> user_agent;
}
final class wire_cst_p_4_k_file_item extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict> url;
}
final class wire_cst_WebViewEvent_NavigationCompleted extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
}
final class wire_cst_WebViewEvent_TitleChanged extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> title;
}
final class wire_cst_WebViewEvent_WebMessage extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> message;
}
final class wire_cst_WebViewEvent_Error extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_web_view_event> 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<wire_cst_list_prim_u_8_strict> data;
}
final class wire_cst_web_view_navigation_state extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> title;
@ffi.Bool()
external bool can_go_back;
@ffi.Bool()
external bool can_go_forward;
@ffi.Bool()
external bool is_loading;
}

View File

@ -0,0 +1,341 @@
// 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<OnWebMessageCallback> _messageCallbacks = [];
final List<OnNavigationCallback> _navigationCallbacks = [];
final List<OnNavigationCompletedCallback> _navigationCompletedCallbacks = [];
final List<OnWindowClosedCallback> _closeCallbacks = [];
Timer? _pollTimer;
bool _isDisposed = false;
/// assets
String _localizationScript = "";
///
String _requestInterceptorScript = "";
/// URL
String _currentUrl = "";
String get currentUrl => _currentUrl;
RustWebViewController._(this.id);
/// WebView
static Future<RustWebViewController> 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<void> _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<Map<String, String>> 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();
}
}

View File

@ -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<void> main(List<String> 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(),
],
),
);
}

View File

@ -41,7 +41,7 @@ final class Unp4kCModelProvider
}
}
String _$unp4kCModelHash() => r'b46274b1409dc904db2d96acf692869edf034b9f';
String _$unp4kCModelHash() => r'72ee23ad9864cdfb73a588ea1a0509b083e7dee8';
abstract class _$Unp4kCModel extends $Notifier<Unp4kcState> {
Unp4kcState build();

View File

@ -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<void> 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<String, dynamic> 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<String, dynamic> 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<void> goWebView(BuildContext context, String title, String url,
{bool useLocalization = false,
bool loginMode = false,
RsiLoginCallback? rsiLoginCallback,
required HomeUIModelState homeState}) async {
Future<void> 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<void> _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);

View File

@ -42,7 +42,7 @@ final class HomeGameLoginUIModelProvider
}
String _$homeGameLoginUIModelHash() =>
r'c9e9ec2e85f2459b6bfc1518406b091ff4675a85';
r'217a57f797b37f3467be2e7711f220610e9e67d8';
abstract class _$HomeGameLoginUIModel extends $Notifier<HomeGameLoginState> {
HomeGameLoginState build();

View File

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

View File

@ -41,7 +41,7 @@ final class HomeUIModelProvider
}
}
String _$homeUIModelHash() => r'7dfe73383f7be2e520a42d176e199a8db208f008';
String _$homeUIModelHash() => r'cc795e27213d02993459dd711de4a897c8491575';
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
HomeUIModelState build();

View File

@ -41,7 +41,7 @@ final class ToolsUIModelProvider
}
}
String _$toolsUIModelHash() => r'78732ff16e87cc9f92174bda43d0fafadba51146';
String _$toolsUIModelHash() => r'ee1de3d555443f72b4fbb395a5728b2de1e8aaf4';
abstract class _$ToolsUIModel extends $Notifier<ToolsUIState> {
ToolsUIState build();

View File

@ -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<void> 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<void> _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<String> _handleMirrorsUrl(String url, AppVersionData appVersionData) async {
var finalUrl = url;
if (isEnableToolSiteMirrors) {
@ -189,7 +206,7 @@ class WebViewModel {
}
Future<void> launch(String url, AppVersionData appVersionData) async {
webview.launch(await _handleMirrorsUrl(url, appVersionData));
webview.navigate(await _handleMirrorsUrl(url, appVersionData));
}
Future<void> 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<void> 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();
}
}

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h"
#include <desktop_multi_window/desktop_multi_window_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@ -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);

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_multi_window
desktop_webview_window
flutter_acrylic
screen_retriever_linux
url_launcher_linux

View File

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

View File

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

View File

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

View File

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

1765
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,22 +15,32 @@ 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.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" }
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]
windows = { version = "0.62.2", features = [
@ -39,7 +49,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)'] }

View File

@ -7,3 +7,4 @@ pub mod win32_api;
pub mod asar_api;
pub mod ort_api;
pub mod unp4k_api;
pub mod webview_api;

209
rust/src/api/webview_api.rs Normal file
View File

@ -0,0 +1,209 @@
// WebView API using wry + tao
use std::sync::atomic::Ordering;
use flutter_rust_bridge::frb;
use serde::{Deserialize, Serialize};
use crate::webview::{WEBVIEW_INSTANCES, send_command};
// ============ 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<String>,
pub enable_devtools: bool,
pub transparent: bool,
pub user_agent: Option<String>,
}
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),
}
// ============ Public API ============
/// Create a new WebView window and return its ID
#[frb(sync)]
pub fn webview_create(config: WebViewConfiguration) -> Result<String, String> {
crate::webview::create_webview(config)
}
/// 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<WebViewNavigationState, String> {
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<WebViewEvent> {
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<String> {
let instances = WEBVIEW_INSTANCES.read();
let keys: Vec<String> = instances.keys().cloned().collect();
drop(instances);
keys
}

File diff suppressed because it is too large Load Diff

View File

@ -2,3 +2,4 @@ pub mod api;
mod frb_generated;
pub mod http_package;
pub mod ort_models;
pub mod webview;

16
rust/src/webview/mod.rs Normal file
View File

@ -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::*;

View File

@ -0,0 +1,529 @@
// 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!("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<String>,
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<WebViewCommand>,
pub event_receiver: Receiver<WebViewEvent>,
pub state: Arc<RwLock<WebViewNavigationState>>,
pub is_closed: Arc<AtomicBool>,
}
pub static WEBVIEW_INSTANCES: Lazy<RwLock<HashMap<WebViewId, WebViewInstance>>> =
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<String, String> {
let id = uuid::Uuid::new_v4().to_string();
let id_clone = id.clone();
let (cmd_tx, cmd_rx) = bounded::<WebViewCommand>(100);
let (event_tx, event_rx) = bounded::<WebViewEvent>(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<WebViewCommand>,
event_tx: Sender<WebViewEvent>,
state: Arc<RwLock<WebViewNavigationState>>,
is_closed: Arc<AtomicBool>,
) {
// Create event loop with any_thread support for non-main thread execution
#[cfg(target_os = "windows")]
let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event()
.with_any_thread(true)
.build();
#[cfg(not(target_os = "windows"))]
let mut event_loop: EventLoop<UserEvent> = 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::<serde_json::Value>(&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);
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();
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
}
})
.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<WebView>,
window: &Arc<Window>,
command: WebViewCommand,
state: &Arc<RwLock<WebViewNavigationState>>,
nav_history: &Arc<RwLock<NavigationHistory>>,
event_tx: &Sender<WebViewEvent>,
is_closed: &Arc<AtomicBool>,
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<Icon> {
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
}
}
}

View File

@ -0,0 +1,269 @@
// 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: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
forward: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
reload: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M13.5 8C13.5 11.0376 11.0376 13.5 8 13.5C4.96243 13.5 2.5 11.0376 2.5 8C2.5 4.96243 4.96243 2.5 8 2.5C10.1012 2.5 11.9254 3.67022 12.8169 5.4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M10.5 5.5H13V3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'
};
// 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;
if (!document.body) {
setTimeout(createNavBar, 50);
return;
}
const nav = document.createElement('div');
nav.id = 'sct-navbar';
nav.innerHTML = `
<style>
#sct-navbar {
position: fixed;
top: 0.5rem; /* 8px */
left: 50%;
transform: translateX(-50%);
height: 2.25rem; /* 36px */
background: rgba(19, 36, 49, 0.95);
backdrop-filter: blur(0.75rem); /* 12px */
display: flex;
align-items: center;
padding: 0 0.5rem; /* 0 8px */
z-index: 2147483647;
font-family: 'Segoe UI', system-ui, sans-serif;
box-shadow: 0 0.125rem 0.75rem rgba(0,0,0,0.4); /* 0 2px 12px */
user-select: none;
border-radius: 0.5rem; /* 8px */
gap: 0.125rem; /* 2px */
border: 0.0625rem solid rgba(10, 49, 66, 0.8); /* 1px */
}
#sct-navbar button {
background: transparent;
border: none;
color: rgba(255,255,255,0.85);
width: 2rem; /* 32px */
height: 1.75rem; /* 28px */
border-radius: 0.25rem; /* 4px */
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
padding: 0;
}
#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 {
width: 16.25rem; /* 260px */
height: 1.625rem; /* 26px */
padding: 0 0.625rem; /* 0 10px */
border: 0.0625rem solid rgba(10, 49, 66, 0.6); /* 1px */
border-radius: 0.25rem; /* 4px */
background: rgba(0,0,0,0.25);
color: rgba(255,255,255,0.9);
font-size: 0.75rem; /* 12px */
outline: none;
text-overflow: ellipsis;
margin-left: 0.25rem; /* 4px */
}
#sct-navbar-url:focus { border-color: rgba(10, 49, 66, 1); background: rgba(0,0,0,0.35); }
/* ---------- Spinner & Favicon Slot ---------- */
#sct-spinner {
display: block; /* default: show spinner during loading */
width: 1.125rem; /* 18px */
height: 1.125rem; /* 18px */
border: 0.125rem solid rgba(255, 255, 255, 0.16); /* ~2px */
border-top-color: rgba(255, 255, 255, 0.92);
border-radius: 50%;
margin-left: 0.5rem;
-webkit-animation: sct-spin 0.8s linear infinite;
animation: sct-spin 0.8s linear infinite;
pointer-events: none;
align-self: center;
flex-shrink: 0;
}
#sct-favicon-slot {
display: none; /* hidden until page ready */
align-items: center;
justify-content: center;
width: 1.125rem; /* 18px - same as spinner */
height: 1.125rem; /* 18px - same as spinner */
margin-left: 0.5rem;
pointer-events: none;
flex-shrink: 0;
}
#sct-favicon {
width: 1.125rem; /* 18px */
height: 1.125rem; /* 18px */
object-fit: cover;
border-radius: 0.25rem; /* 4px */
}
@-webkit-keyframes sct-spin { to { -webkit-transform: rotate(360deg); } }
@keyframes sct-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
#sct-spinner { -webkit-animation: none; animation: none; }
}
</style>
<button id="sct-back" title="Back" disabled>${icons.back}</button>
<button id="sct-forward" title="Forward" disabled>${icons.forward}</button>
<button id="sct-reload" title="Reload">${icons.reload}</button>
<div id="sct-spinner" role="progressbar" aria-hidden="false" aria-valuetext="Loading" title="Loading"></div>
<div id="sct-favicon-slot"><img id="sct-favicon" src="" alt="Page icon" /></div>
<input type="text" id="sct-navbar-url" readonly value="${window.location.href}" />
`;
document.body.insertBefore(nav, document.body.firstChild);
// Navigation buttons - send commands to Rust
document.getElementById('sct-back').onclick = () => {
sendToRust('nav_back', {});
};
document.getElementById('sct-forward').onclick = () => {
sendToRust('nav_forward', {});
};
document.getElementById('sct-reload').onclick = () => {
sendToRust('nav_reload', {});
};
// Apply initial state from Rust
updateNavBarFromState();
// 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');
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');
spinner.setAttribute('aria-busy', 'true');
}
if (faviconSlot) {
faviconSlot.style.display = 'none';
}
} 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 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();
}
})();

View File

@ -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<WebViewCommand>,
pub event_receiver: Receiver<WebViewEvent>,
pub state: Arc<RwLock<WebViewNavigationState>>,
pub is_closed: Arc<AtomicBool>,
}
pub static WEBVIEW_INSTANCES: Lazy<RwLock<HashMap<WebViewId, WebViewInstance>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
// ============ macOS 存根实现 ============
/// macOS 上 WebView 不可用
pub fn create_webview(_config: WebViewConfiguration) -> Result<String, String> {
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))
}

View File

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

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h"
#include <desktop_multi_window/desktop_multi_window_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -16,8 +15,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopMultiWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin"));
DesktopWebviewWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
FlutterAcrylicPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
desktop_multi_window
desktop_webview_window
flutter_acrylic
screen_retriever_windows
url_launcher_windows