From d1a5ed8b3d5c1e495618db47b321d500072ff66b Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 14 May 2024 19:23:29 +0800 Subject: [PATCH] feat: support launch at startup (#532) --- .../app/controllers/app_controller.dart | 26 ++++++++++++- .../modules/setting/views/setting_view.dart | 35 +++++++++++++++-- ui/flutter/lib/i18n/langs/en_us.dart | 1 + ui/flutter/lib/i18n/langs/zh_cn.dart | 1 + ui/flutter/lib/i18n/langs/zh_tw.dart | 1 + ui/flutter/lib/main.dart | 39 +++++++++++++------ ui/flutter/lib/util/util.dart | 2 +- ui/flutter/linux/my_application.cc | 3 +- .../macos/Runner/MainFlutterWindow.swift | 6 +++ ui/flutter/pubspec.lock | 22 +++++++++-- ui/flutter/pubspec.yaml | 2 + ui/flutter/windows/runner/flutter_window.cpp | 1 - 12 files changed, 118 insertions(+), 21 deletions(-) diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index f5551ae3..abeea3a9 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'dart:ui'; import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:get/get.dart'; -import 'package:gopeed/database/entity.dart'; +import 'package:launch_at_startup/launch_at_startup.dart'; import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tray_manager/tray_manager.dart'; @@ -18,7 +19,9 @@ import '../../../../api/api.dart'; import '../../../../api/model/downloader_config.dart'; import '../../../../core/common/start_config.dart'; import '../../../../database/database.dart'; +import '../../../../database/entity.dart'; import '../../../../i18n/message.dart'; +import '../../../../main.dart'; import '../../../../util/locale_manager.dart'; import '../../../../util/log_util.dart'; import '../../../../util/package_info.dart'; @@ -73,6 +76,7 @@ final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls, class AppController extends GetxController with WindowListener, TrayListener { static StartConfig? _defaultStartConfig; + final autoStartup = false.obs; final startConfig = StartConfig().obs; final runningPort = 0.obs; final downloaderConfig = DownloaderConfig().obs; @@ -98,6 +102,9 @@ class AppController extends GetxController with WindowListener, TrayListener { _initTrackerUpdate().onError((error, stackTrace) => logger.w("initTrackerUpdate error", error, stackTrace)); + + _initLaunchAtStartup().onError((error, stackTrace) => + logger.w("initLaunchAtStartup error", error, stackTrace)); } @override @@ -114,6 +121,12 @@ class AppController extends GetxController with WindowListener, TrayListener { } } + // According to the system_manager document, make sure to call setState once on the onWindowFocus event. + @override + void onWindowFocus() { + refresh(); + } + @override void onTrayIconMouseDown() { windowManager.show(); @@ -431,6 +444,17 @@ class AppController extends GetxController with WindowListener, TrayListener { } } + Future _initLaunchAtStartup() async { + if (!Util.isWindows() && !Util.isLinux()) { + return; + } + launchAtStartup.setup( + appName: packageInfo.appName, + appPath: Platform.resolvedExecutable, + args: ['--${Args.flagHidden}']); + autoStartup.value = await launchAtStartup.isEnabled(); + } + Future saveConfig() async { Database.instance.saveStartConfig(StartConfigEntity( network: startConfig.value.network, diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index 03a20a34..ca1e625d 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -6,13 +6,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; +import 'package:launch_at_startup/launch_at_startup.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../api/model/downloader_config.dart'; -import '../../../../database/database.dart'; import '../../../../i18n/message.dart'; import '../../../../util/input_formatter.dart'; import '../../../../util/locale_manager.dart'; +import '../../../../util/log_util.dart'; import '../../../../util/message.dart'; import '../../../../util/package_info.dart'; import '../../../../util/util.dart'; @@ -130,9 +131,36 @@ class SettingView extends GetView { "https://addons.mozilla.org/zh-CN/firefox/addon/gopeed-extension", ), ], - ).paddingOnly(top: 5, bottom: 5)); + ).paddingOnly(top: 5)); } + // Currently auto startup only support Windows and Linux + final buildAutoStartup = !Util.isWindows() && !Util.isLinux() + ? () => null + : _buildConfigItem('launchAtStartup'.tr, () { + return appController.autoStartup.value ? 'on'.tr : 'off'.tr; + }, (Key key) { + return Container( + alignment: Alignment.centerLeft, + child: Switch( + value: appController.autoStartup.value, + onChanged: (bool value) async { + try { + if (value) { + await launchAtStartup.enable(); + } else { + await launchAtStartup.disable(); + } + appController.autoStartup.value = value; + } catch (e) { + showErrorMessage(e); + logger.e('launchAtStartup fail', e); + } + }, + ), + ); + }); + // http config items start final httpConfig = downloaderCfg.value.protocolConfig.http; final buildHttpUa = @@ -784,7 +812,8 @@ class SettingView extends GetView { children: _addDivider([ buildDownloadDir(), buildMaxRunning(), - buildBrowserExtension() + buildBrowserExtension(), + buildAutoStartup(), ]), )), const Text('HTTP'), diff --git a/ui/flutter/lib/i18n/langs/en_us.dart b/ui/flutter/lib/i18n/langs/en_us.dart index d33ebc44..bdedfae9 100644 --- a/ui/flutter/lib/i18n/langs/en_us.dart +++ b/ui/flutter/lib/i18n/langs/en_us.dart @@ -93,5 +93,6 @@ const enUS = { 'thanksDesc': 'Thanks to all the contributors who have helped build and develop the Gopeed community!', 'browserExtension': 'Browser Extension', + 'launchAtStartup': 'Launch at Startup', }, }; diff --git a/ui/flutter/lib/i18n/langs/zh_cn.dart b/ui/flutter/lib/i18n/langs/zh_cn.dart index 3c409143..a33aad71 100644 --- a/ui/flutter/lib/i18n/langs/zh_cn.dart +++ b/ui/flutter/lib/i18n/langs/zh_cn.dart @@ -90,5 +90,6 @@ const zhCN = { 'thanks': '鸣谢', 'thanksDesc': '感谢所有为 Gopeed 社区建设添砖加瓦的贡献者们!', 'browserExtension': '浏览器扩展', + 'launchAtStartup': '开机自动运行', } }; diff --git a/ui/flutter/lib/i18n/langs/zh_tw.dart b/ui/flutter/lib/i18n/langs/zh_tw.dart index a1c9cfb1..b9e97d69 100644 --- a/ui/flutter/lib/i18n/langs/zh_tw.dart +++ b/ui/flutter/lib/i18n/langs/zh_tw.dart @@ -90,5 +90,6 @@ const zhTW = { 'thanks': '感謝', 'thanksDesc': '感謝所有為 Gopeed 社區建設添磚加瓦的貢獻者們!', 'browserExtension': '瀏覽器擴充程式', + 'launchAtStartup': '開機自動運行', } }; diff --git a/ui/flutter/lib/main.dart b/ui/flutter/lib/main.dart index 941ae088..b8230605 100644 --- a/ui/flutter/lib/main.dart +++ b/ui/flutter/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:args/args.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; @@ -17,14 +18,28 @@ import 'util/mac_secure_util.dart'; import 'util/package_info.dart'; import 'util/util.dart'; -void main() async { - await init(); +class Args { + static const flagHidden = "hidden"; + + bool hidden = false; + + Args.parse(List args) { + final parser = ArgParser(); + parser.addFlag(flagHidden); + final results = parser.parse(args); + hidden = results.flag(flagHidden); + } +} + +void main(List arguments) async { + final args = Args.parse(arguments); + await init(args); onStart(); runApp(const AppView()); } -Future init() async { +Future init(Args args) async { WidgetsFlutterBinding.ensureInitialized(); await Util.initStorageDir(); await Database.instance.init(); @@ -37,8 +52,10 @@ Future init() async { skipTaskbar: false, ); await windowManager.waitUntilReadyToShow(windowOptions, () async { - await windowManager.show(); - await windowManager.focus(); + if (!args.hidden) { + await windowManager.show(); + await windowManager.focus(); + } await windowManager.setPreventClose(true); // windows_manager has a bug where when window to be maximized, it will be unmaximized immediately, so can't implement this feature currently. // https://github.com/leanflutter/window_manager/issues/412 @@ -50,6 +67,12 @@ Future init() async { initLogger(); + try { + await initPackageInfo(); + } catch (e) { + logger.e("init package info fail", e); + } + final controller = Get.put(AppController()); try { await controller.loadStartConfig(); @@ -81,12 +104,6 @@ Future init() async { } catch (e) { logger.e("load config fail", e); } - - try { - await initPackageInfo(); - } catch (e) { - logger.e("init package info fail", e); - } } Future onStart() async { diff --git a/ui/flutter/lib/util/util.dart b/ui/flutter/lib/util/util.dart index 36b5bacb..9eafc7fc 100644 --- a/ui/flutter/lib/util/util.dart +++ b/ui/flutter/lib/util/util.dart @@ -52,7 +52,7 @@ class Util { static Future initStorageDir() async { var storageDir = ""; if (Util.isWindows()) { - storageDir = "./"; + storageDir = File(Platform.resolvedExecutable).parent.path; } else if (!Util.isWeb()) { if (Util.isLinux()) { storageDir = File(Platform.resolvedExecutable).parent.path; diff --git a/ui/flutter/linux/my_application.cc b/ui/flutter/linux/my_application.cc index 871a88a2..a6a85ee1 100644 --- a/ui/flutter/linux/my_application.cc +++ b/ui/flutter/linux/my_application.cc @@ -48,7 +48,8 @@ static void my_application_activate(GApplication* application) { } gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); + //gtk_widget_show(GTK_WIDGET(window)); + gtk_widget_realize(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); diff --git a/ui/flutter/macos/Runner/MainFlutterWindow.swift b/ui/flutter/macos/Runner/MainFlutterWindow.swift index 2722837e..a823d046 100644 --- a/ui/flutter/macos/Runner/MainFlutterWindow.swift +++ b/ui/flutter/macos/Runner/MainFlutterWindow.swift @@ -1,5 +1,6 @@ import Cocoa import FlutterMacOS +import window_manager class MainFlutterWindow: NSWindow { override func awakeFromNib() { @@ -12,4 +13,9 @@ class MainFlutterWindow: NSWindow { super.awakeFromNib() } + + override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { + super.order(place, relativeTo: otherWin) + hiddenWindowAtLaunch() + } } diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock index b207dd56..e518826d 100644 --- a/ui/flutter/pubspec.lock +++ b/ui/flutter/pubspec.lock @@ -34,13 +34,13 @@ packages: source: hosted version: "3.4.10" args: - dependency: transitive + dependency: "direct main" description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -493,6 +493,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.7.1" + launch_at_startup: + dependency: "direct main" + description: + name: launch_at_startup + sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb" + url: "https://pub.dev" + source: hosted + version: "0.2.2" lecle_downloads_path_provider: dependency: "direct main" description: @@ -1130,6 +1138,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + url: "https://pub.dev" + source: hosted + version: "1.1.0" window_manager: dependency: "direct main" description: diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index 84e598b8..f30d65d1 100644 --- a/ui/flutter/pubspec.yaml +++ b/ui/flutter/pubspec.yaml @@ -64,6 +64,8 @@ dependencies: tray_manager: ^0.2.1 lecle_downloads_path_provider: ^0.0.2+8 hive: ^2.2.3 + launch_at_startup: ^0.2.2 + args: ^2.5.0 dev_dependencies: flutter_test: sdk: flutter diff --git a/ui/flutter/windows/runner/flutter_window.cpp b/ui/flutter/windows/runner/flutter_window.cpp index 955ee303..1a7b26b0 100644 --- a/ui/flutter/windows/runner/flutter_window.cpp +++ b/ui/flutter/windows/runner/flutter_window.cpp @@ -28,7 +28,6 @@ bool FlutterWindow::OnCreate() { SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); }); // Flutter can complete the first frame before the "show window" callback is