Add boot self-start function

This commit is contained in:
ZhangLei
2025-10-30 19:19:11 +08:00
parent f194c37558
commit a2aabf6fbd
4 changed files with 163 additions and 20 deletions

View File

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

View File

@@ -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)下载。
</div>
@@ -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"
---
<div align="center">
**⭐ 如果这个项目对你有帮助,请给它一个 Star**
Made with ❤️ for the Bitcoin community
</div>
## ⭐ 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)

View File

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

View File

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