From a2aabf6fbd319042f5d11a577798e159465ee4c3 Mon Sep 17 00:00:00 2001 From: ZhangLei Date: Thu, 30 Oct 2025 19:19:11 +0800 Subject: [PATCH] Add boot self-start function --- Bitcoin Monitoring.xcodeproj/project.pbxproj | 13 +-- README.md | 20 ++-- test1/AppSettings.swift | 96 +++++++++++++++++++- test1/BTCMenuBarApp.swift | 54 +++++++++++ 4 files changed, 163 insertions(+), 20 deletions(-) diff --git a/Bitcoin Monitoring.xcodeproj/project.pbxproj b/Bitcoin Monitoring.xcodeproj/project.pbxproj index 05a6b9a..2746dac 100644 --- a/Bitcoin Monitoring.xcodeproj/project.pbxproj +++ b/Bitcoin Monitoring.xcodeproj/project.pbxproj @@ -76,7 +76,7 @@ 4E9410682EB09F90003658CB /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = 1; + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 2600; TargetAttributes = { @@ -86,11 +86,12 @@ }; }; buildConfigurationList = 4E94106B2EB09F90003658CB /* Build configuration list for PBXProject "Bitcoin Monitoring" */; - developmentRegion = en; + developmentRegion = "zh-Hans"; hasScannedForEncodings = 0; knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = 4E9410672EB09F90003658CB; minimizedProjectReferenceProxies = 1; @@ -271,8 +272,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.4; - MARKETING_VERSION = 1.0.3; + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0.4; PRODUCT_BUNDLE_IDENTIFIER = com.mark.test1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -304,8 +305,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.4; - MARKETING_VERSION = 1.0.3; + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0.4; PRODUCT_BUNDLE_IDENTIFIER = com.mark.test1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/README.md b/README.md index 7a2668e..d528bd0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![macOS](https://img.shields.io/badge/macOS-12.4+-blue?style=for-the-badge&logo=apple) ![License](https://img.shields.io/badge/License-GPL%20v3-green?style=for-the-badge) -一款 macOS 原生菜单栏应用,用于实时监控主流虚拟货币价格,支持 BTC/ETH/BNB/SOL/DOGE 多种币种,之前使用Python写过虽然也蛮好用但最终还是决定用macOS原生语言开发,已经编译了`Intel`与`Apple Silicon`的通用应用,请至[releases](https://github.com/jiayouzl/Bitcoin-Monitoring/releases/latest)下载。 +一款 macOS 原生菜单栏应用,用于实时监控主流虚拟货币价格,支持 BTC/ETH/BNB/SOL/DOGE 多种币种。之前使用Python写过虽然也蛮好用但最终还是决定用macOS原生语言重写,已经编译了`Intel`与`Apple Silicon`的通用应用,请至[releases](https://github.com/jiayouzl/Bitcoin-Monitoring/releases/latest)下载。 @@ -29,13 +29,14 @@ - **状态指示**: 直观显示加载、更新、错误状态 - **价格复制功能**: 支持一键复制当前价格到剪贴板 - **配置持久化**: 用户设置自动保存,重启后保持配置 +- **开机自启动**: 可选是否开机自动启动APP ### 🎨 用户体验 -- **SF Symbols 图标**: 使用原生 macOS 图标系统 - **中文界面**: 完整的中文用户界面 - **优雅动画**: 流畅的状态切换动画 - **轻量级设计**: 最小化系统资源占用 - **后台运行**: 不占用 Dock 空间,专注菜单栏 +- **SF Symbols 图标**: 使用原生 macOS 图标系统 ### 🛡️ 可靠性 - **网络容错**: 完善的网络异常处理机制 @@ -46,13 +47,13 @@ ## 📋 安装要求 ### 系统要求 -- **操作系统**: macOS 12.4 或更高版本 +- **操作系统**: macOS 13.1 或更高版本 - **架构支持**: Intel 和 Apple Silicon (M1/M2/M3/M4/M5) ### 开发环境 - **开发工具**: Xcode 16.2 或更高版本 - **Swift 版本**: Swift 5.0 -- **部署目标**: macOS 12.4 +- **部署目标**: macOS 13.1 ### 网络要求 - 需要稳定的互联网连接 @@ -107,7 +108,7 @@ xcodebuild -project "Bitcoin Monitoring.xcodeproj" -scheme "Bitcoin Monitoring" - 支持 BTC/ETH/BNB/SOL/DOGE 五种主流币种 4. **复制价格** - - **Option + 点击** 币种名称可快速复制当前价格到剪贴板 + - **Option + 左键点击** 币种名称可快速复制当前价格到剪贴板 - 价格格式为 `$XXX.XX`,可直接粘贴使用 5. **交互菜单** @@ -126,6 +127,7 @@ xcodebuild -project "Bitcoin Monitoring.xcodeproj" -scheme "Bitcoin Monitoring" | 更新时间 | 显示上次成功更新时间 | - | | 刷新价格 | 手动获取最新价格 | `Cmd+R` | | 刷新设置 | 设置刷新的间隔 | - | +| 开机启动 | 设置开机自动启动APP | - | | GitHub | 打开仓库查询更新 | - | | 关于 | 查看应用信息和版本 | - | | 退出 | 完全退出应用 | `Cmd+Q` | @@ -310,14 +312,6 @@ curl "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT" --- -
- -**⭐ 如果这个项目对你有帮助,请给它一个 Star!** - -Made with ❤️ for the Bitcoin community - -
- ## ⭐ Star History [![Star History Chart](https://api.star-history.com/svg?repos=jiayouzl/Bitcoin-Monitoring&type=Date)](https://www.star-history.com/#jiayouzl/Bitcoin-Monitoring&Date) \ No newline at end of file diff --git a/test1/AppSettings.swift b/test1/AppSettings.swift index fd567af..89fa00e 100644 --- a/test1/AppSettings.swift +++ b/test1/AppSettings.swift @@ -7,6 +7,7 @@ import Foundation import Combine +import ServiceManagement /// 应用配置管理类 /// 负责管理用户的刷新间隔设置和其他应用配置 @@ -19,12 +20,15 @@ class AppSettings: ObservableObject { @Published var refreshInterval: RefreshInterval = .thirtySeconds /// 当前选中的币种 @Published var selectedSymbol: CryptoSymbol = .btc + /// 是否开机自启动 + @Published var launchAtLogin: Bool = false // MARK: - Private Properties private let defaults = UserDefaults.standard private let refreshIntervalKey = "BTCRefreshInterval" private let selectedSymbolKey = "SelectedCryptoSymbol" + private let launchAtLoginKey = "LaunchAtLogin" // MARK: - Initialization @@ -101,8 +105,14 @@ class AppSettings: ObservableObject { saveSelectedSymbol(.btc) } + // 加载开机自启动设置 + launchAtLogin = defaults.bool(forKey: launchAtLoginKey) + + // 检查实际的自启动状态并同步 + checkAndSyncLaunchAtLoginStatus() + #if DEBUG - print("🔧 [AppSettings] 配置加载完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(selectedSymbol.displayName)") + print("🔧 [AppSettings] 配置加载完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(selectedSymbol.displayName), 开机自启动: \(launchAtLogin)") #endif } @@ -123,6 +133,13 @@ class AppSettings: ObservableObject { #if DEBUG print("🔧 [AppSettings] 重置完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(selectedSymbol.displayName)") #endif + + // 重置开机自启动设置 + launchAtLogin = false + defaults.set(false, forKey: launchAtLoginKey) + + // 禁用开机自启动 + toggleLoginItem(enabled: false) } /// 保存用户选择的刷新间隔 @@ -141,4 +158,81 @@ class AppSettings: ObservableObject { #endif defaults.set(symbol.rawValue, forKey: selectedSymbolKey) } + + // MARK: - 开机自启动相关方法 + + /// 切换开机自启动状态 + /// - Parameter enabled: 是否启用开机自启动 + func toggleLoginItem(enabled: Bool) { + // 检查 macOS 版本是否支持 SMAppService (macOS 13+) + if #available(macOS 13.0, *) { + do { + if enabled { + try SMAppService.mainApp.register() + #if DEBUG + print("🔧 [AppSettings] ✅ 开机自启动已启用") + #endif + } else { + try SMAppService.mainApp.unregister() + #if DEBUG + print("🔧 [AppSettings] ❌ 开机自启动已禁用") + #endif + } + + // 保存到 UserDefaults + launchAtLogin = enabled + defaults.set(enabled, forKey: launchAtLoginKey) + + } catch { + #if DEBUG + print("🔧 [AppSettings] ⚠️ 设置开机自启动失败: \(error.localizedDescription)") + #endif + + // 如果操作失败,恢复到之前的状态 + let actualStatus = SMAppService.mainApp.status + launchAtLogin = (actualStatus == .enabled) + defaults.set(launchAtLogin, forKey: launchAtLoginKey) + } + } else { + // 对于低于 macOS 13 的版本,显示警告信息 + #if DEBUG + print("🔧 [AppSettings] ⚠️ 当前 macOS 版本不支持 SMAppService,无法设置开机自启动") + #endif + } + } + + /// 检查并同步开机自启动状态 + /// 确保应用内部状态与系统实际状态保持一致 + private func checkAndSyncLaunchAtLoginStatus() { + guard #available(macOS 13.0, *) else { + #if DEBUG + print("🔧 [AppSettings] ⚠️ 当前 macOS 版本不支持 SMAppService") + #endif + return + } + + let actualStatus = SMAppService.mainApp.status + let isEnabled = (actualStatus == .enabled) + + // 如果系统状态与应用内部状态不一致,则同步 + if isEnabled != launchAtLogin { + launchAtLogin = isEnabled + defaults.set(isEnabled, forKey: launchAtLoginKey) + + #if DEBUG + print("🔧 [AppSettings] 🔄 已同步开机自启动状态: \(isEnabled)") + #endif + } + } + + /// 获取当前开机自启动状态 + /// - Returns: 是否已启用开机自启动 + func isLaunchAtLoginEnabled() -> Bool { + guard #available(macOS 13.0, *) else { + return false + } + + let actualStatus = SMAppService.mainApp.status + return actualStatus == .enabled + } } diff --git a/test1/BTCMenuBarApp.swift b/test1/BTCMenuBarApp.swift index 516967a..58c112d 100644 --- a/test1/BTCMenuBarApp.swift +++ b/test1/BTCMenuBarApp.swift @@ -264,6 +264,18 @@ class BTCMenuBarApp: NSObject, ObservableObject { menu.addItem(NSMenuItem.separator()) + // 添加开机启动开关 + let launchAtLoginTitle = appSettings.launchAtLogin ? "✓ 开机启动" : "开机启动" + let launchAtLoginItem = NSMenuItem(title: launchAtLoginTitle, action: #selector(toggleLaunchAtLogin), keyEquivalent: "") + if let powerImage = NSImage(systemSymbolName: "tv", accessibilityDescription: "开机启动") { + powerImage.size = NSSize(width: 16, height: 16) + launchAtLoginItem.image = powerImage + } + launchAtLoginItem.target = self + menu.addItem(launchAtLoginItem) + + menu.addItem(NSMenuItem.separator()) + #if DEBUG // 添加重置设置按钮(仅在 Debug 模式下显示) let resetItem = NSMenuItem(title: "重置设置", action: #selector(resetSettings), keyEquivalent: "") @@ -474,6 +486,48 @@ class BTCMenuBarApp: NSObject, ObservableObject { } + // 切换开机自启动状态 + @objc private func toggleLaunchAtLogin() { + let newState = !appSettings.launchAtLogin + + // 检查 macOS 版本兼容性 + if #available(macOS 13.0, *) { + // 显示确认对话框 + let alert = NSAlert() + alert.messageText = newState ? "启用开机自启动" : "禁用开机自启动" + alert.informativeText = newState ? + "应用将在系统启动时自动运行,您也可以随时在系统偏好设置中更改此选项。" : + "应用将不再在系统启动时自动运行。" + alert.alertStyle = .informational + alert.addButton(withTitle: "确定") + alert.addButton(withTitle: "取消") + + let response = alert.runModal() + if response == .alertFirstButtonReturn { + // 用户确认,执行切换 + appSettings.toggleLoginItem(enabled: newState) + + // 显示结果反馈 + let resultAlert = NSAlert() + resultAlert.messageText = newState ? "开机自启动已启用" : "开机自启动已禁用" + resultAlert.informativeText = newState ? + "Bitcoin Monitoring 将在下次系统启动时自动运行。" : + "Bitcoin Monitoring 不会在系统启动时自动运行。" + resultAlert.alertStyle = .informational + resultAlert.addButton(withTitle: "确定") + resultAlert.runModal() + } + } else { + // 不支持的系统版本 + let alert = NSAlert() + alert.messageText = "系统版本不支持" + alert.informativeText = "开机自启动功能需要 macOS 13.0 (Ventura) 或更高版本。" + alert.alertStyle = .warning + alert.addButton(withTitle: "确定") + alert.runModal() + } + } + // 退出应用 @objc private func quitApp() { NSApplication.shared.terminate(nil)