diff --git a/lib/app.dart b/lib/app.dart index d084e56..7529ddc 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:go_router/go_router.dart'; @@ -24,6 +25,7 @@ import 'api/api.dart'; import 'common/conf/url_conf.dart'; import 'common/io/rs_http.dart'; import 'common/rust/frb_generated.dart'; +import 'common/rust/api/applinks_api.dart' as applinks; import 'common/rust/api/win32_api.dart' as win32; import 'data/app_version_data.dart'; import 'generated/no_l10n_strings.dart'; @@ -125,6 +127,11 @@ class AppGlobalModel extends _$AppGlobalModel { await RSHttp.init(); dPrint("---- rust bridge init -----"); + // Register URL scheme + if ((!ConstConf.isMSE || kDebugMode) && Platform.isWindows) { + await _registerUrlScheme(); + } + // init Hive try { Hive.init("$applicationSupportDir/db"); @@ -166,16 +173,19 @@ class AppGlobalModel extends _$AppGlobalModel { windowManager.waitUntilReadyToShow().then((_) async { await windowManager.setTitle("SCToolBox"); await windowManager.setSkipTaskbar(false); - await windowManager.show(); if (Platform.isWindows) { - await Window.initialize(); - await Window.hideWindowControls(); if (windowsDeviceInfo?.productName.contains("Windows 11") ?? false) { - await Window.setEffect(effect: WindowEffect.acrylic); + // Apply acrylic effect before showing window + await Window.setEffect(effect: WindowEffect.acrylic, color: Colors.transparent, dark: true); state = state.copyWith(windowsVersion: 11); - dPrint("---- Windows 11 Acrylic Effect init -----"); + dPrint("---- Windows 11 Acrylic Effect applied -----"); + } else { + state = state.copyWith(windowsVersion: 10); + await Window.setEffect(effect: WindowEffect.disabled); } } + // Show window after acrylic effect is applied + await windowManager.show(); }); dPrint("---- Window init -----"); @@ -318,6 +328,27 @@ class AppGlobalModel extends _$AppGlobalModel { } } + /// Register sctoolbox:// URL scheme for non-MSE builds + Future _registerUrlScheme() async { + try { + const scheme = "sctoolbox"; + const appName = "SCToolBox"; + final result = await applinks.registerApplinks(scheme: scheme, appName: appName); + if (result.success) { + if (result.wasModified) { + dPrint("URL scheme '$scheme' registered successfully: ${result.message}"); + } else { + dPrint("URL scheme '$scheme' already registered: ${result.message}"); + } + } else { + dPrint("URL scheme '$scheme' registration check: ${result.message}"); + // Even if check fails, the registration might have succeeded + } + } catch (e) { + dPrint("Failed to register URL scheme: $e"); + } + } + Future _initAppDir() async { if (Platform.isWindows) { final userProfileDir = Platform.environment["USERPROFILE"]; diff --git a/lib/app.g.dart b/lib/app.g.dart index 6e5e5bb..07ae13f 100644 --- a/lib/app.g.dart +++ b/lib/app.g.dart @@ -82,7 +82,7 @@ final class AppGlobalModelProvider } } -String _$appGlobalModelHash() => r'0e46d72594d94e2beb4d2ccb8616eb37facba288'; +String _$appGlobalModelHash() => r'853586e6ce83942638f0971e08d7b9106a2e7186'; abstract class _$AppGlobalModel extends $Notifier { AppGlobalState build(); diff --git a/lib/common/rust/api/applinks_api.dart b/lib/common/rust/api/applinks_api.dart new file mode 100644 index 0000000..6ed7987 --- /dev/null +++ b/lib/common/rust/api/applinks_api.dart @@ -0,0 +1,68 @@ +// 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'; + +// 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` + +/// Check if the URL scheme is already registered with the correct executable path +Future checkApplinksRegistration({ + required String scheme, +}) => RustLib.instance.api.crateApiApplinksApiCheckApplinksRegistration( + scheme: scheme, +); + +/// Register URL scheme in Windows registry +/// This will create or update the registry keys for the custom URL scheme +/// +/// # Arguments +/// * `scheme` - The URL scheme to register (e.g., "sctoolbox") +/// * `app_name` - Optional application display name (e.g., "SCToolBox"). If provided, +/// the registry will show "URL:{app_name} Protocol" as the scheme description. +Future registerApplinks({ + required String scheme, + String? appName, +}) => RustLib.instance.api.crateApiApplinksApiRegisterApplinks( + scheme: scheme, + appName: appName, +); + +/// Unregister URL scheme from Windows registry +Future unregisterApplinks({ + required String scheme, +}) => + RustLib.instance.api.crateApiApplinksApiUnregisterApplinks(scheme: scheme); + +/// Applinks URL scheme registration result +class ApplinksRegistrationResult { + /// Whether registration was successful + final bool success; + + /// Detailed message about the operation + final String message; + + /// Whether the registry was modified (false if already configured correctly) + final bool wasModified; + + const ApplinksRegistrationResult({ + required this.success, + required this.message, + required this.wasModified, + }); + + @override + int get hashCode => + success.hashCode ^ message.hashCode ^ wasModified.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ApplinksRegistrationResult && + runtimeType == other.runtimeType && + success == other.success && + message == other.message && + wasModified == other.wasModified; +} diff --git a/lib/common/rust/api/win32_api.dart b/lib/common/rust/api/win32_api.dart index d0c7f96..cda3105 100644 --- a/lib/common/rust/api/win32_api.dart +++ b/lib/common/rust/api/win32_api.dart @@ -6,6 +6,7 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +// These functions are ignored because they are not marked as `pub`: `get_process_path` // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `fmt`, `fmt` Future sendNotify({ @@ -20,21 +21,27 @@ Future sendNotify({ appId: appId, ); +/// Get system memory size in GB Future getSystemMemorySizeGb() => RustLib.instance.api.crateApiWin32ApiGetSystemMemorySizeGb(); +/// Get number of logical processors Future getNumberOfLogicalProcessors() => RustLib.instance.api.crateApiWin32ApiGetNumberOfLogicalProcessors(); +/// Get all system information at once Future getSystemInfo() => RustLib.instance.api.crateApiWin32ApiGetSystemInfo(); +/// Get GPU info from registry (more accurate VRAM) Future getGpuInfoFromRegistry() => RustLib.instance.api.crateApiWin32ApiGetGpuInfoFromRegistry(); +/// Resolve shortcut (.lnk) file to get target path Future resolveShortcut({required String lnkPath}) => RustLib.instance.api.crateApiWin32ApiResolveShortcut(lnkPath: lnkPath); +/// Open file explorer and select file/folder Future openDirWithExplorer({ required String path, required bool isFile, @@ -58,16 +65,19 @@ Future> getProcessListByName({required String processName}) => processName: processName, ); +/// Kill processes by name Future killProcessByName({required String processName}) => RustLib .instance .api .crateApiWin32ApiKillProcessByName(processName: processName); +/// Get disk physical sector size for performance Future getDiskPhysicalSectorSize({required String driveLetter}) => RustLib .instance .api .crateApiWin32ApiGetDiskPhysicalSectorSize(driveLetter: driveLetter); +/// Create a desktop shortcut Future createDesktopShortcut({ required String targetPath, required String shortcutName, @@ -76,12 +86,14 @@ Future createDesktopShortcut({ shortcutName: shortcutName, ); +/// Run a program with admin privileges (UAC) Future runAsAdmin({required String program, required String args}) => RustLib.instance.api.crateApiWin32ApiRunAsAdmin( program: program, args: args, ); +/// Start a program (without waiting) Future startProcess({ required String program, required List args, @@ -90,12 +102,15 @@ Future startProcess({ args: args, ); +/// Check if NVME patch is applied Future checkNvmePatchStatus() => RustLib.instance.api.crateApiWin32ApiCheckNvmePatchStatus(); +/// Add NVME patch to registry Future addNvmePatch() => RustLib.instance.api.crateApiWin32ApiAddNvmePatch(); +/// Remove NVME patch from registry Future removeNvmePatch() => RustLib.instance.api.crateApiWin32ApiRemoveNvmePatch(); diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index 77745bb..390886a 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/applinks_api.dart'; import 'api/asar_api.dart'; import 'api/downloader_api.dart'; import 'api/http_api.dart'; @@ -72,7 +73,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => -1903117367; + int get rustContentHash => -351025706; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -85,6 +86,9 @@ class RustLib extends BaseEntrypoint { abstract class RustLibApi extends BaseApi { Future crateApiWin32ApiAddNvmePatch(); + Future + crateApiApplinksApiCheckApplinksRegistration({required String scheme}); + Future crateApiWin32ApiCheckNvmePatchStatus(); Future crateApiOrtApiClearAllModels(); @@ -268,6 +272,11 @@ abstract class RustLibApi extends BaseApi { Future crateApiUnp4KApiP4KOpen({required String p4KPath}); + Future crateApiApplinksApiRegisterApplinks({ + required String scheme, + String? appName, + }); + Future crateApiWin32ApiRemoveNvmePatch(); Future crateApiWin32ApiResolveShortcut({required String lnkPath}); @@ -320,6 +329,10 @@ abstract class RustLibApi extends BaseApi { Future crateApiOrtApiUnloadTranslationModel({required String modelKey}); + Future crateApiApplinksApiUnregisterApplinks({ + required String scheme, + }); + Future crateApiWebviewApiWebViewConfigurationDefault(); Future @@ -411,6 +424,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWin32ApiAddNvmePatchConstMeta => const TaskConstMeta(debugName: "add_nvme_patch", argNames: []); + @override + Future + crateApiApplinksApiCheckApplinksRegistration({required String scheme}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(scheme); + return wire + .wire__crate__api__applinks_api__check_applinks_registration( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_applinks_registration_result, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiApplinksApiCheckApplinksRegistrationConstMeta, + argValues: [scheme], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiApplinksApiCheckApplinksRegistrationConstMeta => + const TaskConstMeta( + debugName: "check_applinks_registration", + argNames: ["scheme"], + ); + @override Future crateApiWin32ApiCheckNvmePatchStatus() { return handler.executeNormal( @@ -1987,6 +2030,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiUnp4KApiP4KOpenConstMeta => const TaskConstMeta(debugName: "p4k_open", argNames: ["p4KPath"]); + @override + Future crateApiApplinksApiRegisterApplinks({ + required String scheme, + String? appName, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(scheme); + var arg1 = cst_encode_opt_String(appName); + return wire.wire__crate__api__applinks_api__register_applinks( + port_, + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_applinks_registration_result, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiApplinksApiRegisterApplinksConstMeta, + argValues: [scheme, appName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiApplinksApiRegisterApplinksConstMeta => + const TaskConstMeta( + debugName: "register_applinks", + argNames: ["scheme", "appName"], + ); + @override Future crateApiWin32ApiRemoveNvmePatch() { return handler.executeNormal( @@ -2369,6 +2445,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["modelKey"], ); + @override + Future crateApiApplinksApiUnregisterApplinks({ + required String scheme, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(scheme); + return wire.wire__crate__api__applinks_api__unregister_applinks( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_applinks_registration_result, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiApplinksApiUnregisterApplinksConstMeta, + argValues: [scheme], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiApplinksApiUnregisterApplinksConstMeta => + const TaskConstMeta( + debugName: "unregister_applinks", + argNames: ["scheme"], + ); + @override Future crateApiWebviewApiWebViewConfigurationDefault() { return handler.executeNormal( @@ -2869,6 +2975,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as String; } + @protected + ApplinksRegistrationResult dco_decode_applinks_registration_result( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return ApplinksRegistrationResult( + success: dco_decode_bool(arr[0]), + message: dco_decode_String(arr[1]), + wasModified: dco_decode_bool(arr[2]), + ); + } + @protected bool dco_decode_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -3347,6 +3468,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + ApplinksRegistrationResult sse_decode_applinks_registration_result( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_success = sse_decode_bool(deserializer); + var var_message = sse_decode_String(deserializer); + var var_wasModified = sse_decode_bool(deserializer); + return ApplinksRegistrationResult( + success: var_success, + message: var_message, + wasModified: var_wasModified, + ); + } + @protected bool sse_decode_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4047,6 +4183,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_applinks_registration_result( + ApplinksRegistrationResult self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_bool(self.success, serializer); + sse_encode_String(self.message, serializer); + sse_encode_bool(self.wasModified, serializer); + } + @protected void sse_encode_bool(bool self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index 43d0af0..900c127 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/applinks_api.dart'; import 'api/asar_api.dart'; import 'api/downloader_api.dart'; import 'api/http_api.dart'; @@ -39,6 +40,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + ApplinksRegistrationResult dco_decode_applinks_registration_result( + dynamic raw, + ); + @protected bool dco_decode_bool(dynamic raw); @@ -216,6 +222,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + ApplinksRegistrationResult sse_decode_applinks_registration_result( + SseDeserializer deserializer, + ); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -672,6 +683,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw.toSigned(64).toInt(); } + @protected + void cst_api_fill_to_wire_applinks_registration_result( + ApplinksRegistrationResult apiObj, + wire_cst_applinks_registration_result wireObj, + ) { + wireObj.success = cst_encode_bool(apiObj.success); + wireObj.message = cst_encode_String(apiObj.message); + wireObj.was_modified = cst_encode_bool(apiObj.wasModified); + } + @protected void cst_api_fill_to_wire_box_autoadd_rsi_launcher_asar_data( RsiLauncherAsarData apiObj, @@ -946,6 +967,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_applinks_registration_result( + ApplinksRegistrationResult self, + SseSerializer serializer, + ); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -1227,6 +1254,33 @@ class RustLibWire implements BaseWire { _wire__crate__api__win32_api__add_nvme_patchPtr .asFunction(); + void wire__crate__api__applinks_api__check_applinks_registration( + int port_, + ffi.Pointer scheme, + ) { + return _wire__crate__api__applinks_api__check_applinks_registration( + port_, + scheme, + ); + } + + late final _wire__crate__api__applinks_api__check_applinks_registrationPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__check_applinks_registration', + ); + late final _wire__crate__api__applinks_api__check_applinks_registration = + _wire__crate__api__applinks_api__check_applinks_registrationPtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + void wire__crate__api__win32_api__check_nvme_patch_status(int port_) { return _wire__crate__api__win32_api__check_nvme_patch_status(port_); } @@ -2453,6 +2507,40 @@ class RustLibWire implements BaseWire { void Function(int, ffi.Pointer) >(); + void wire__crate__api__applinks_api__register_applinks( + int port_, + ffi.Pointer scheme, + ffi.Pointer app_name, + ) { + return _wire__crate__api__applinks_api__register_applinks( + port_, + scheme, + app_name, + ); + } + + late final _wire__crate__api__applinks_api__register_applinksPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__register_applinks', + ); + late final _wire__crate__api__applinks_api__register_applinks = + _wire__crate__api__applinks_api__register_applinksPtr + .asFunction< + void Function( + int, + ffi.Pointer, + ffi.Pointer, + ) + >(); + void wire__crate__api__win32_api__remove_nvme_patch(int port_) { return _wire__crate__api__win32_api__remove_nvme_patch(port_); } @@ -2467,9 +2555,9 @@ class RustLibWire implements BaseWire { void wire__crate__api__win32_api__resolve_shortcut( int port_, - ffi.Pointer _lnk_path, + ffi.Pointer lnk_path, ) { - return _wire__crate__api__win32_api__resolve_shortcut(port_, _lnk_path); + return _wire__crate__api__win32_api__resolve_shortcut(port_, lnk_path); } late final _wire__crate__api__win32_api__resolve_shortcutPtr = @@ -2799,6 +2887,30 @@ class RustLibWire implements BaseWire { void Function(int, ffi.Pointer) >(); + void wire__crate__api__applinks_api__unregister_applinks( + int port_, + ffi.Pointer scheme, + ) { + return _wire__crate__api__applinks_api__unregister_applinks(port_, scheme); + } + + late final _wire__crate__api__applinks_api__unregister_applinksPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__applinks_api__unregister_applinks', + ); + late final _wire__crate__api__applinks_api__unregister_applinks = + _wire__crate__api__applinks_api__unregister_applinksPtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + void wire__crate__api__webview_api__web_view_configuration_default( int port_, ) { @@ -3721,6 +3833,16 @@ final class wire_cst_list_web_view_event extends ffi.Struct { external int len; } +final class wire_cst_applinks_registration_result extends ffi.Struct { + @ffi.Bool() + external bool success; + + external ffi.Pointer message; + + @ffi.Bool() + external bool was_modified; +} + final class wire_cst_download_global_stat extends ffi.Struct { @ffi.Uint64() external int download_speed; diff --git a/lib/main.dart b/lib/main.dart index 6a03acc..cacbbcb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter_acrylic/flutter_acrylic.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:starcitizen_doctor/generated/l10n.dart'; @@ -36,10 +37,16 @@ Future main(List args) async { } Future _initWindow() async { + // Initialize flutter_acrylic before runApp (same as official example) + await Window.initialize(); + await Window.hideWindowControls(); await windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: false); await windowManager.setSize(const Size(1280, 810)); await windowManager.setMinimumSize(const Size(1280, 810)); await windowManager.center(animate: true); + if (Platform.isWindows) { + await Window.setEffect(effect: WindowEffect.transparent, color: Colors.transparent, dark: true); + } } class App extends HookConsumerWidget with WindowListener { diff --git a/lib/provider/party_room.g.dart b/lib/provider/party_room.g.dart index d121c18..d8b186f 100644 --- a/lib/provider/party_room.g.dart +++ b/lib/provider/party_room.g.dart @@ -44,7 +44,7 @@ final class PartyRoomProvider } } -String _$partyRoomHash() => r'3247b6ea5482a8938f118c2d0d6f9ecf2e55fba7'; +String _$partyRoomHash() => r'446e4cc88be96c890f8e676c6faf0e4d3b33a529'; /// PartyRoom Provider diff --git a/lib/provider/unp4kc.g.dart b/lib/provider/unp4kc.g.dart index 0661822..d61d4d4 100644 --- a/lib/provider/unp4kc.g.dart +++ b/lib/provider/unp4kc.g.dart @@ -41,7 +41,7 @@ final class Unp4kCModelProvider } } -String _$unp4kCModelHash() => r'68c24d50113e9e734ae8d277f65999bbef05dc05'; +String _$unp4kCModelHash() => r'6713e8e95e2bec1adcb36b63f6c6f9240a5959be'; abstract class _$Unp4kCModel extends $Notifier { Unp4kcState build(); diff --git a/lib/ui/auth/auth_ui_model.g.dart b/lib/ui/auth/auth_ui_model.g.dart index 1ad190f..b9ad821 100644 --- a/lib/ui/auth/auth_ui_model.g.dart +++ b/lib/ui/auth/auth_ui_model.g.dart @@ -59,7 +59,7 @@ final class AuthUIModelProvider } } -String _$authUIModelHash() => r'cef2bc3fecb2c52e507fa24bc352b4553d918a38'; +String _$authUIModelHash() => r'485bf56e488ba01cd1371131e6d92077c76176df'; final class AuthUIModelFamily extends $Family with diff --git a/lib/ui/home/localization/localization_ui_model.g.dart b/lib/ui/home/localization/localization_ui_model.g.dart index 5719052..2d98d83 100644 --- a/lib/ui/home/localization/localization_ui_model.g.dart +++ b/lib/ui/home/localization/localization_ui_model.g.dart @@ -42,7 +42,7 @@ final class LocalizationUIModelProvider } String _$localizationUIModelHash() => - r'7b398d3b2ddd306ff8f328be39f28200fe8bf49e'; + r'34bfb83eb271b16d24a10219bbf86fc4b873f9aa'; abstract class _$LocalizationUIModel extends $Notifier { LocalizationUIState build(); diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart index d7c23ae..26d151a 100644 --- a/lib/ui/settings/settings_ui_model.g.dart +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -41,7 +41,7 @@ final class SettingsUIModelProvider } } -String _$settingsUIModelHash() => r'c448be241a29891e90d8f17bef9e7f61d66c05be'; +String _$settingsUIModelHash() => r'302b8ce09601218c28c0cf2bd133f1749ec471dc'; abstract class _$SettingsUIModel extends $Notifier { SettingsUIState build(); diff --git a/lib/ui/tools/tools_ui_model.g.dart b/lib/ui/tools/tools_ui_model.g.dart index 596e238..7e40955 100644 --- a/lib/ui/tools/tools_ui_model.g.dart +++ b/lib/ui/tools/tools_ui_model.g.dart @@ -41,7 +41,7 @@ final class ToolsUIModelProvider } } -String _$toolsUIModelHash() => r'2b0c851c677a03a88c02f6f9d146624d505e974f'; +String _$toolsUIModelHash() => r'85ccd6ff3fe9622f9d2bfbf3c8385fd8c9363e7e'; abstract class _$ToolsUIModel extends $Notifier { ToolsUIState build(); diff --git a/rust/src/api/applinks_api.rs b/rust/src/api/applinks_api.rs new file mode 100644 index 0000000..b0c7b31 --- /dev/null +++ b/rust/src/api/applinks_api.rs @@ -0,0 +1,400 @@ +use std::env; + +/// Applinks URL scheme registration result +#[derive(Debug, Clone)] +pub struct ApplinksRegistrationResult { + /// Whether registration was successful + pub success: bool, + /// Detailed message about the operation + pub message: String, + /// Whether the registry was modified (false if already configured correctly) + pub was_modified: bool, +} + +/// Check if the URL scheme is already registered with the correct executable path +#[cfg(target_os = "windows")] +pub fn check_applinks_registration(scheme: String) -> anyhow::Result { + use windows::core::{HSTRING, PCWSTR}; + use windows::Win32::System::Registry::{ + RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER, KEY_READ, + REG_VALUE_TYPE, + }; + + let app_path = env::current_exe() + .map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))? + .to_string_lossy() + .to_string(); + + let expected_command = format!("\"{}\" \"%1\"", app_path); + let protocol_key_path = format!("Software\\Classes\\{}", scheme); + let command_key_path = format!("{}\\shell\\open\\command", protocol_key_path); + + unsafe { + // Check if URL Protocol value exists + let mut protocol_key = std::mem::zeroed(); + let protocol_key_hstring = HSTRING::from(&protocol_key_path); + + if RegOpenKeyExW( + HKEY_CURRENT_USER, + PCWSTR(protocol_key_hstring.as_ptr()), + Some(0), + KEY_READ, + &mut protocol_key, + ) + .is_err() + { + return Ok(ApplinksRegistrationResult { + success: false, + message: format!("URL scheme '{}' is not registered", scheme), + was_modified: false, + }); + } + + // Check URL Protocol value + let url_protocol_name = HSTRING::from("URL Protocol"); + let mut data_type: REG_VALUE_TYPE = REG_VALUE_TYPE::default(); + let mut data_size: u32 = 0; + + if RegQueryValueExW( + protocol_key, + PCWSTR(url_protocol_name.as_ptr()), + None, + Some(&mut data_type), + None, + Some(&mut data_size), + ) + .is_err() + { + let _ = RegCloseKey(protocol_key); + return Ok(ApplinksRegistrationResult { + success: false, + message: format!("URL Protocol value not found for scheme '{}'", scheme), + was_modified: false, + }); + } + + let _ = RegCloseKey(protocol_key); + + // Check command key + let mut command_key = std::mem::zeroed(); + let command_key_hstring = HSTRING::from(&command_key_path); + + if RegOpenKeyExW( + HKEY_CURRENT_USER, + PCWSTR(command_key_hstring.as_ptr()), + Some(0), + KEY_READ, + &mut command_key, + ) + .is_err() + { + return Ok(ApplinksRegistrationResult { + success: false, + message: format!("Command key not found for scheme '{}'", scheme), + was_modified: false, + }); + } + + // Read command value (default value with empty name) + let empty_name = HSTRING::from(""); + let mut data_size: u32 = 0; + + if RegQueryValueExW( + command_key, + PCWSTR(empty_name.as_ptr()), + None, + Some(&mut data_type), + None, + Some(&mut data_size), + ) + .is_err() + || data_size == 0 + { + let _ = RegCloseKey(command_key); + return Ok(ApplinksRegistrationResult { + success: false, + message: format!("Command value not found for scheme '{}'", scheme), + was_modified: false, + }); + } + + // Read the actual command value + let mut buffer: Vec = vec![0; data_size as usize]; + if RegQueryValueExW( + command_key, + PCWSTR(empty_name.as_ptr()), + None, + Some(&mut data_type), + Some(buffer.as_mut_ptr()), + Some(&mut data_size), + ) + .is_err() + { + let _ = RegCloseKey(command_key); + return Ok(ApplinksRegistrationResult { + success: false, + message: format!("Failed to read command value for scheme '{}'", scheme), + was_modified: false, + }); + } + + let _ = RegCloseKey(command_key); + + // Convert buffer to string (UTF-16 to UTF-8) + let command_value = String::from_utf16_lossy( + &buffer + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .take_while(|&c| c != 0) + .collect::>(), + ); + + // Compare with expected command (case-insensitive for path) + if command_value.to_lowercase() == expected_command.to_lowercase() { + Ok(ApplinksRegistrationResult { + success: true, + message: format!("URL scheme '{}' is already registered correctly", scheme), + was_modified: false, + }) + } else { + Ok(ApplinksRegistrationResult { + success: false, + message: format!( + "URL scheme '{}' is registered but with different path. Current: '{}', Expected: '{}'", + scheme, command_value, expected_command + ), + was_modified: false, + }) + } + } +} + +#[cfg(not(target_os = "windows"))] +pub fn check_applinks_registration(scheme: String) -> anyhow::Result { + Ok(ApplinksRegistrationResult { + success: false, + message: format!( + "URL scheme registration check is not supported on this platform for scheme '{}'", + scheme + ), + was_modified: false, + }) +} + +/// Register URL scheme in Windows registry +/// This will create or update the registry keys for the custom URL scheme +/// +/// # Arguments +/// * `scheme` - The URL scheme to register (e.g., "sctoolbox") +/// * `app_name` - Optional application display name (e.g., "SCToolBox"). If provided, +/// the registry will show "URL:{app_name} Protocol" as the scheme description. +#[cfg(target_os = "windows")] +pub fn register_applinks(scheme: String, app_name: Option) -> anyhow::Result { + use windows::core::{HSTRING, PCWSTR}; + use windows::Win32::System::Registry::{ + RegCloseKey, RegCreateKeyExW, RegSetValueExW, HKEY_CURRENT_USER, KEY_WRITE, + REG_CREATE_KEY_DISPOSITION, REG_OPTION_NON_VOLATILE, REG_SZ, + }; + + // First check if already registered correctly + let check_result = check_applinks_registration(scheme.clone())?; + if check_result.success { + return Ok(check_result); + } + + let app_path = env::current_exe() + .map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))? + .to_string_lossy() + .to_string(); + + let command_value = format!("\"{}\" \"%1\"", app_path); + let protocol_key_path = format!("Software\\Classes\\{}", scheme); + let command_key_path = format!("{}\\shell\\open\\command", protocol_key_path); + + unsafe { + // Create protocol key + let mut protocol_key = std::mem::zeroed(); + let protocol_key_hstring = HSTRING::from(&protocol_key_path); + let mut disposition: REG_CREATE_KEY_DISPOSITION = REG_CREATE_KEY_DISPOSITION::default(); + + if RegCreateKeyExW( + HKEY_CURRENT_USER, + PCWSTR(protocol_key_hstring.as_ptr()), + Some(0), + PCWSTR::null(), + REG_OPTION_NON_VOLATILE, + KEY_WRITE, + None, + &mut protocol_key, + Some(&mut disposition), + ) + .is_err() + { + return Err(anyhow::anyhow!( + "Failed to create registry key '{}'", + protocol_key_path + )); + } + + // Set default value (display name) if app_name is provided + if let Some(ref name) = app_name { + let display_name = format!("URL:{} Protocol", name); + let empty_name = HSTRING::from(""); + let display_name_bytes: Vec = display_name + .encode_utf16() + .chain(std::iter::once(0)) + .flat_map(|c| c.to_le_bytes()) + .collect(); + + if RegSetValueExW( + protocol_key, + PCWSTR(empty_name.as_ptr()), + Some(0), + REG_SZ, + Some(&display_name_bytes), + ) + .is_err() + { + let _ = RegCloseKey(protocol_key); + return Err(anyhow::anyhow!("Failed to set display name value")); + } + } + + // Set URL Protocol value (empty string) + let url_protocol_name = HSTRING::from("URL Protocol"); + let empty_value: [u8; 2] = [0, 0]; // Empty UTF-16 string + + if RegSetValueExW( + protocol_key, + PCWSTR(url_protocol_name.as_ptr()), + Some(0), + REG_SZ, + Some(&empty_value), + ) + .is_err() + { + let _ = RegCloseKey(protocol_key); + return Err(anyhow::anyhow!("Failed to set URL Protocol value")); + } + + let _ = RegCloseKey(protocol_key); + + // Create command key + let mut command_key = std::mem::zeroed(); + let command_key_hstring = HSTRING::from(&command_key_path); + + if RegCreateKeyExW( + HKEY_CURRENT_USER, + PCWSTR(command_key_hstring.as_ptr()), + Some(0), + PCWSTR::null(), + REG_OPTION_NON_VOLATILE, + KEY_WRITE, + None, + &mut command_key, + Some(&mut disposition), + ) + .is_err() + { + return Err(anyhow::anyhow!( + "Failed to create command key '{}'", + command_key_path + )); + } + + // Set command value + let empty_name = HSTRING::from(""); + let command_bytes: Vec = command_value + .encode_utf16() + .chain(std::iter::once(0)) + .flat_map(|c| c.to_le_bytes()) + .collect(); + + if RegSetValueExW( + command_key, + PCWSTR(empty_name.as_ptr()), + Some(0), + REG_SZ, + Some(&command_bytes), + ) + .is_err() + { + let _ = RegCloseKey(command_key); + return Err(anyhow::anyhow!("Failed to set command value")); + } + + let _ = RegCloseKey(command_key); + + Ok(ApplinksRegistrationResult { + success: true, + message: format!( + "Successfully registered URL scheme '{}' with command '{}'", + scheme, command_value + ), + was_modified: true, + }) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn register_applinks(scheme: String, _app_name: Option) -> anyhow::Result { + Ok(ApplinksRegistrationResult { + success: false, + message: format!( + "URL scheme registration is not supported on this platform for scheme '{}'", + scheme + ), + was_modified: false, + }) +} + +/// Unregister URL scheme from Windows registry +#[cfg(target_os = "windows")] +pub fn unregister_applinks(scheme: String) -> anyhow::Result { + use windows::core::{HSTRING, PCWSTR}; + use windows::Win32::System::Registry::{RegDeleteTreeW, HKEY_CURRENT_USER}; + + let protocol_key_path = format!("Software\\Classes\\{}", scheme); + + unsafe { + let protocol_key_hstring = HSTRING::from(&protocol_key_path); + + let result = RegDeleteTreeW(HKEY_CURRENT_USER, PCWSTR(protocol_key_hstring.as_ptr())); + + if result.is_err() { + // Check if the key doesn't exist (not an error in this case) + let error_code = result.0 as u32; + if error_code == 2 { + // ERROR_FILE_NOT_FOUND + return Ok(ApplinksRegistrationResult { + success: true, + message: format!("URL scheme '{}' was not registered", scheme), + was_modified: false, + }); + } + return Err(anyhow::anyhow!( + "Failed to delete registry key '{}': error code {}", + protocol_key_path, + error_code + )); + } + + Ok(ApplinksRegistrationResult { + success: true, + message: format!("Successfully unregistered URL scheme '{}'", scheme), + was_modified: true, + }) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn unregister_applinks(scheme: String) -> anyhow::Result { + Ok(ApplinksRegistrationResult { + success: false, + message: format!( + "URL scheme unregistration is not supported on this platform for scheme '{}'", + scheme + ), + was_modified: false, + }) +} diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 61ebd9a..de63ff5 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1,6 +1,7 @@ // // Do not put code in `mod.rs`, but put in e.g. `simple.rs`. // +pub mod applinks_api; pub mod http_api; pub mod rs_process; pub mod win32_api; diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index aaac6ec..cb427e0 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1903117367; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -351025706; // Section: executor @@ -66,6 +66,30 @@ fn wire__crate__api__win32_api__add_nvme_patch_impl( }, ) } +fn wire__crate__api__applinks_api__check_applinks_registration_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + scheme: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "check_applinks_registration", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_scheme = scheme.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::applinks_api::check_applinks_registration(api_scheme)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__win32_api__check_nvme_patch_status_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ) { @@ -1462,6 +1486,32 @@ fn wire__crate__api__unp4k_api__p4k_open_impl( }, ) } +fn wire__crate__api__applinks_api__register_applinks_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + scheme: impl CstDecode, + app_name: impl CstDecode>, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "register_applinks", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_scheme = scheme.cst_decode(); + let api_app_name = app_name.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::applinks_api::register_applinks(api_scheme, api_app_name)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__win32_api__remove_nvme_patch_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ) { @@ -1485,7 +1535,7 @@ fn wire__crate__api__win32_api__remove_nvme_patch_impl( } fn wire__crate__api__win32_api__resolve_shortcut_impl( port_: flutter_rust_bridge::for_generated::MessagePort, - _lnk_path: impl CstDecode, + lnk_path: impl CstDecode, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( flutter_rust_bridge::for_generated::TaskInfo { @@ -1494,11 +1544,11 @@ fn wire__crate__api__win32_api__resolve_shortcut_impl( mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, }, move || { - let api__lnk_path = _lnk_path.cst_decode(); + let api_lnk_path = lnk_path.cst_decode(); move |context| { transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( (move || { - let output_ok = crate::api::win32_api::resolve_shortcut(api__lnk_path)?; + let output_ok = crate::api::win32_api::resolve_shortcut(api_lnk_path)?; Ok(output_ok) })(), ) @@ -1788,6 +1838,29 @@ fn wire__crate__api__ort_api__unload_translation_model_impl( }, ) } +fn wire__crate__api__applinks_api__unregister_applinks_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + scheme: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "unregister_applinks", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_scheme = scheme.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::applinks_api::unregister_applinks(api_scheme)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__webview_api__web_view_configuration_default_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ) { @@ -2296,6 +2369,20 @@ impl SseDecode for String { } } +impl SseDecode for crate::api::applinks_api::ApplinksRegistrationResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_success = ::sse_decode(deserializer); + let mut var_message = ::sse_decode(deserializer); + let mut var_wasModified = ::sse_decode(deserializer); + return crate::api::applinks_api::ApplinksRegistrationResult { + success: var_success, + message: var_message, + was_modified: var_wasModified, + }; + } +} + impl SseDecode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2940,6 +3027,28 @@ fn pde_ffi_dispatcher_sync_impl( // Section: rust2dart +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::applinks_api::ApplinksRegistrationResult { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.success.into_into_dart().into_dart(), + self.message.into_into_dart().into_dart(), + self.was_modified.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::applinks_api::ApplinksRegistrationResult +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::applinks_api::ApplinksRegistrationResult +{ + fn into_into_dart(self) -> crate::api::applinks_api::ApplinksRegistrationResult { + self + } +} // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::unp4k_api::DcbRecordItem { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -3416,6 +3525,15 @@ impl SseEncode for String { } } +impl SseEncode for crate::api::applinks_api::ApplinksRegistrationResult { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.success, serializer); + ::sse_encode(self.message, serializer); + ::sse_encode(self.was_modified, serializer); + } +} + impl SseEncode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -3978,6 +4096,18 @@ mod io { String::from_utf8(vec).unwrap() } } + impl CstDecode + for wire_cst_applinks_registration_result + { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::applinks_api::ApplinksRegistrationResult { + crate::api::applinks_api::ApplinksRegistrationResult { + success: self.success.cst_decode(), + message: self.message.cst_decode(), + was_modified: self.was_modified.cst_decode(), + } + } + } impl CstDecode for *mut bool { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> bool { @@ -4321,6 +4451,20 @@ mod io { } } } + impl NewWithNullPtr for wire_cst_applinks_registration_result { + fn new_with_null_ptr() -> Self { + Self { + success: Default::default(), + message: core::ptr::null_mut(), + was_modified: Default::default(), + } + } + } + impl Default for wire_cst_applinks_registration_result { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_dcb_record_item { fn new_with_null_ptr() -> Self { Self { @@ -4557,6 +4701,14 @@ mod io { wire__crate__api__win32_api__add_nvme_patch_impl(port_) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__applinks_api__check_applinks_registration( + port_: i64, + scheme: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__applinks_api__check_applinks_registration_impl(port_, scheme) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__check_nvme_patch_status( port_: i64, @@ -5042,6 +5194,15 @@ mod io { wire__crate__api__unp4k_api__p4k_open_impl(port_, p4k_path) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__applinks_api__register_applinks( + port_: i64, + scheme: *mut wire_cst_list_prim_u_8_strict, + app_name: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__applinks_api__register_applinks_impl(port_, scheme, app_name) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__remove_nvme_patch( port_: i64, @@ -5052,9 +5213,9 @@ mod io { #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__resolve_shortcut( port_: i64, - _lnk_path: *mut wire_cst_list_prim_u_8_strict, + lnk_path: *mut wire_cst_list_prim_u_8_strict, ) { - wire__crate__api__win32_api__resolve_shortcut_impl(port_, _lnk_path) + wire__crate__api__win32_api__resolve_shortcut_impl(port_, lnk_path) } #[unsafe(no_mangle)] @@ -5154,6 +5315,14 @@ mod io { wire__crate__api__ort_api__unload_translation_model_impl(port_, model_key) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__applinks_api__unregister_applinks( + port_: i64, + scheme: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__applinks_api__unregister_applinks_impl(port_, scheme) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__webview_api__web_view_configuration_default( port_: i64, @@ -5467,6 +5636,13 @@ mod io { flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_applinks_registration_result { + success: bool, + message: *mut wire_cst_list_prim_u_8_strict, + was_modified: bool, + } #[repr(C)] #[derive(Clone, Copy)] pub struct wire_cst_dcb_record_item { diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 62a4643..6a0fa1a 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -90,7 +90,7 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.xkeyc.tools.starcitizen.doctor" "\0" - VALUE "FileDescription", "starcitizen_doctor" "\0" + VALUE "FileDescription", "SCToolBox" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "starcitizen_doctor" "\0" VALUE "LegalCopyright", "Copyright (C) 2023 com.xkeyc.tools.starcitizen.doctor. All rights reserved." "\0" diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 9c69cdb..7750c30 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -34,9 +34,10 @@ bool FlutterWindow::OnCreate() { }); SetChildContent(flutter_controller_->view()->GetNativeWindow()); - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); + // Window display is now controlled by Dart layer via windowManager.show() + // flutter_controller_->engine()->SetNextFrameCallback([&]() { + // this->Show(); + // }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index 041a385..f9b97f7 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -150,7 +150,9 @@ bool Win32Window::Create(const std::wstring& title, } bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); + // Use SW_SHOWNOACTIVATE to avoid stealing focus from other windows + // This is consistent with bitsdojo_window behavior used in flutter_acrylic example + return ShowWindow(window_handle_, SW_SHOWNOACTIVATE); } // static