import 'dart:io'; import 'package:flutter_acrylic/window.dart'; import 'package:flutter_acrylic/window_effect.dart'; import 'package:path/path.dart' as p; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; import 'package:tray_manager/tray_manager.dart'; import '../config/app_config.dart'; import '../core/utils/app_logger.dart'; import '../presentation/providers/theme_provider.dart'; import 'sync_service.dart'; import '../src/rust/api/ffi.dart' as ffi; /// 桌面端服务(窗口管理 + 系统托盘) class DesktopService with TrayListener, WindowListener { static DesktopService? _instance; DesktopService._(); static DesktopService get instance { _instance ??= DesktopService._(); return _instance!; } static bool get isDesktopPlatform => Platform.isWindows || Platform.isLinux; bool _initialized = false; /// 初始化桌面端服务,必须在 runApp 之前调用 Future initialize() async { if (!isDesktopPlatform || _initialized) return; // 1. 初始化窗口管理器 await windowManager.ensureInitialized(); windowManager.addListener(this); // --- 新增:初始化 flutter_acrylic --- if (Platform.isWindows) { var themeProvider = ThemeProvider(); await Window.initialize(); // 设置 Mica 效果 await Window.setEffect( effect: WindowEffect.mica, // 根据你的 ThemeProvider 判断是深色还是浅色 Mica dark: themeProvider.isDark, // 建议这里先写死 false 测试,后续对接 ThemeProvider ); } // 2. 窗口选项设置 WindowOptions windowOptions = const WindowOptions( size: Size(1280, 720), center: true, backgroundColor: Colors.transparent, skipTaskbar: false, titleBarStyle: TitleBarStyle.hidden, ); // 3. 等待窗口准备就绪后执行居中和显示 await windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.setTitle(AppConfig.appName); await windowManager.setMinimumSize(const Size(400, 300)); await windowManager.setPreventClose(true); // 如果设置了 center: true 仍未生效(某些 Linux 环境),可以手动补刀: // await windowManager.center(); await windowManager.show(); await windowManager.focus(); }); // 托盘管理器 trayManager.addListener(this); final iconPath = await _getTrayIconPath(); AppLogger.d('DesktopService: tray icon path: $iconPath'); await trayManager.setIcon(iconPath); try { await trayManager.setToolTip(AppConfig.appName); } catch (e) { AppLogger.e('DesktopService: tray icon error: $e'); } final menu = Menu(items: [ MenuItem(key: 'show', label: '显示主窗口'), MenuItem.separator(), MenuItem(key: 'quit', label: '退出'), ]); await trayManager.setContextMenu(menu); _initialized = true; AppLogger.d('DesktopService initialized'); } Future _getTrayIconPath() async { if (Platform.isWindows) { // 获取当前可执行文件 (.exe) 所在的目录 String exePath = Platform.resolvedExecutable; String exeDir = p.dirname(exePath); // 拼接 Windows 下 Flutter Assets 的标准物理路径 // 注意:这里的路径必须与打包后的文件夹结构一致 return p.join(exeDir, 'data', 'flutter_assets', 'assets/icons/tray_icon.ico'); } else if (Platform.isLinux) { return '/opt/cloudreve4/data/flutter_assets/assets/icons/tray_icon.png'; } // 调试模式下通常直接用 assets 路径 return 'assets/icons/tray_icon.png'; } // ========== TrayListener ========== @override void onTrayIconMouseDown() { showWindow(); } @override void onTrayIconRightMouseDown() { trayManager.popUpContextMenu(); } @override void onTrayIconRightMouseUp() {} @override void onTrayMenuItemClick(MenuItem menuItem) { switch (menuItem.key) { case 'show': showWindow(); break; case 'quit': _quitApp(); break; } } // ========== WindowListener ========== @override void onWindowClose() async { AppLogger.d('DesktopService: onWindowClose -> hiding to tray'); await windowManager.hide(); } @override void onWindowFocus() {} @override void onWindowBlur() {} @override void onWindowMaximize() {} @override void onWindowUnmaximize() {} @override void onWindowMinimize() {} @override void onWindowRestore() {} @override void onWindowResize() {} @override void onWindowResized() {} @override void onWindowMove() {} @override void onWindowMoved() {} @override void onWindowEnterFullScreen() {} @override void onWindowLeaveFullScreen() {} // ========== Private ========== Future showWindow() async { await windowManager.show(); await windowManager.focus(); } Future _quitApp() async { try { AppLogger.d('DesktopService: Cleaning up before exit...'); // 同步引擎清理(WCF 模式下必须调用,同步释放占位符、注销 sync root) try { await SyncService.instance.stop(); AppLogger.d('DesktopService: Sync engine stopped'); } catch (e) { AppLogger.e('DesktopService: Error stopping sync: $e'); } // 进程退出前同步清理(确保 WCF 资源释放,不依赖 tokio runtime) try { await ffi.syncShutdown(); AppLogger.d('DesktopService: Sync shutdown complete'); } catch (e) { AppLogger.e('DesktopService: Error in sync shutdown: $e'); } // 彻底解绑 windowManager.removeListener(this); trayManager.removeListener(this); // 彻底销毁托盘(防止残留僵尸图标) await trayManager.destroy(); // 允许关闭并销毁窗口 await windowManager.setPreventClose(false); // 给系统一点点时间(50ms)处理最后的事件队列 await Future.delayed(const Duration(milliseconds: 50)); await windowManager.destroy(); } catch (e) { AppLogger.e('Exit error: $e'); } finally { exit(0); } } }