AlDente-Charge-Limiter扩展开发:创建自定义插件与集成

AlDente-Charge-Limiter扩展开发:创建自定义插件与集成

【免费下载链接】AlDente-Charge-Limiter macOS menubar tool to set Charge Limits and prolong battery lifespan 【免费下载链接】AlDente-Charge-Limiter 项目地址: https://gitcode.com/gh_mirrors/al/AlDente-Charge-Limiter

痛点与解决方案

你是否在使用AlDente-Charge-Limiter时,发现默认功能无法满足特定场景的电池管理需求?本文将带你从零开始构建自定义插件系统,通过扩展开发实现个性化电池保护策略,彻底解决 macOS 设备电池寿命优化的灵活性问题。

读完本文你将获得:

  • 完整的插件开发框架设计与实现
  • 与AlDente核心系统的通信协议集成方案
  • 权限管理与安全验证最佳实践
  • 3个实用插件示例(电量统计、充电计划、温度监控)
  • 插件调试与分发的完整流程

开发准备与环境搭建

技术栈要求

组件版本要求作用
Xcode13.0+Swift开发环境与代码签名
Swift5.5+插件主体开发语言
macOS11.0+运行环境与API兼容性
Python3.7+辅助工具签名验证

开发环境配置

# 克隆项目仓库
git clone https://gitcode.com/gh_mirrors/al/AlDente-Charge-Limiter
cd AlDente-Charge-Limiter

# 安装依赖工具
brew install swiftlint
pip3 install pyobjc-framework-Security

项目结构解析

mermaid

插件系统架构设计

核心架构

mermaid

插件接口定义

创建PluginProtocol.swift定义核心接口:

import Foundation

@objc public protocol BatteryPluginProtocol {
    // 插件元数据
    var pluginID: String { get }
    var displayName: String { get }
    var version: String { get }
    
    // 生命周期方法
    func initialize() -> Bool
    func terminate()
    
    // 数据回调方法
    func onBatteryStatusChanged(percentage: UInt8, isCharging: Bool)
    func onChargeLimitChanged(limit: UInt8)
    
    // 配置界面
    func getPreferenceView() -> NSView?
}

// 插件工厂协议
public protocol BatteryPluginFactory {
    func createPluginInstance() -> BatteryPluginProtocol
}

// 插件通信协议
@objc public protocol PluginCommunicationProtocol {
    func setChargeLimit(_ limit: UInt8, completion: @escaping (Bool) -> Void)
    func getBatteryInfo(completion: @escaping (BatteryInfo) -> Void)
}

// 电池信息模型
public struct BatteryInfo {
    let currentCharge: UInt8
    let maxCharge: UInt8
    let isCharging: Bool
    let temperature: Double
    let cycleCount: Int
}

与核心系统集成

XPC通信通道实现

// 插件端通信实现
class PluginXPCConnection {
    private var connection: NSXPCConnection!
    
    init() {
        connection = NSXPCConnection(
            machServiceName: "com.davidwernhart.AlDente.Plugin",
            options: .privileged
        )
        connection.remoteObjectInterface = NSXPCInterface(
            with: HelperToolProtocol.self
        )
        connection.resume()
    }
    
    func setChargeLimit(_ limit: UInt8, completion: @escaping (Bool) -> Void) {
        guard let helper = connection.remoteObjectProxyWithErrorHandler({ error in
            print("XPC连接错误: \(error.localizedDescription)")
            completion(false)
        }) as? HelperToolProtocol else {
            completion(false)
            return
        }
        
        helper.setSMCByte(key: "BCLM", value: limit)
        // 验证设置结果
        helper.readSMCByte(key: "BCLM") { value in
            completion(value == limit)
        }
    }
    
    // 其他通信方法实现...
}

权限管理集成

使用SMJobBless机制实现权限验证:

# 插件签名验证脚本 (plugin_sign_check.py)
import sys
from SMJobBlessUtil import checkCodeSignature

def verify_plugin_signature(plugin_path):
    # 检查代码签名
    result = checkCodeSignature(plugin_path, "plugin")
    if not result:
        print(f"插件 {plugin_path} 签名验证失败")
        sys.exit(1)
    
    # 检查权限要求
    info_plist = readInfoPlistFromPath(f"{plugin_path}/Contents/Info.plist")
    required_rights = info_plist.get("SMAuthorizedClients", [])
    
    if not required_rights:
        print("插件缺少必要的权限声明")
        sys.exit(1)
    
    print("插件签名验证通过")
    sys.exit(0)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("用法: python plugin_sign_check.py <插件路径>")
        sys.exit(1)
    verify_plugin_signature(sys.argv[1])

插件开发实战

1. 电量统计插件

class StatsPlugin: NSObject, BatteryPluginProtocol {
    let pluginID = "com.example.AlDente.StatsPlugin"
    let displayName = "电量统计分析"
    let version = "1.0.0"
    
    private var dataStore: [BatterySample] = []
    private var timer: Timer?
    private var comm: PluginCommunicationProtocol!
    
    func initialize() -> Bool {
        comm = PluginXPCConnection()
        startSampling()
        return true
    }
    
    func terminate() {
        timer?.invalidate()
        saveSamplesToDisk()
    }
    
    private func startSampling() {
        timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak self] _ in
            self?.sampleBatteryData()
        }
    }
    
    private func sampleBatteryData() {
        comm.getBatteryInfo { info in
            let sample = BatterySample(
                timestamp: Date(),
                percentage: info.currentCharge,
                temperature: info.temperature,
                isCharging: info.isCharging
            )
            self.dataStore.append(sample)
            
            // 仅保留最近7天数据
            if self.dataStore.count > 7*24*60 {
                self.dataStore.removeFirst()
            }
        }
    }
    
    func onBatteryStatusChanged(percentage: UInt8, isCharging: Bool) {
        // 实时更新UI
        NotificationCenter.default.post(name: .statsUpdated, object: nil)
    }
    
    // 其他实现...
}

// 注册插件
extension StatsPlugin: BatteryPluginFactory {
    func createPluginInstance() -> BatteryPluginProtocol {
        return StatsPlugin()
    }
}

// 插件入口
@objc public class StatsPluginLoader: NSObject, BatteryPluginFactory {
    @objc public static func factory() -> BatteryPluginFactory {
        return StatsPluginLoader()
    }
    
    public func createPluginInstance() -> BatteryPluginProtocol {
        return StatsPlugin()
    }
}

2. 充电计划插件

class SchedulerPlugin: NSObject, BatteryPluginProtocol {
    let pluginID = "com.example.AlDente.SchedulerPlugin"
    let displayName = "智能充电计划"
    let version = "1.0.0"
    
    private var scheduleItems: [ChargeSchedule] = []
    private var processInfo: ProcessInfo!
    private var comm: PluginCommunicationProtocol!
    
    func initialize() -> Bool {
        comm = PluginXPCConnection()
        processInfo = ProcessInfo()
        loadScheduleFromDefaults()
        setupTimer()
        return true
    }
    
    private func setupTimer() {
        // 每分钟检查一次计划
        Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak self] _ in
            self?.checkSchedule()
        }
    }
    
    private func checkSchedule() {
        let currentTime = Calendar.current.component(.hour, from: Date())
        let currentDay = Calendar.current.component(.weekday, from: Date())
        
        for schedule in scheduleItems {
            if schedule.weekdays.contains(currentDay) && 
               schedule.startHour <= currentTime && 
               schedule.endHour > currentTime {
                // 执行计划
                comm.setChargeLimit(schedule.targetLimit) { success in
                    if success {
                        print("已应用计划: \(schedule.name)")
                    }
                }
                return
            }
        }
        
        // 无匹配计划时恢复默认限制
        comm.setChargeLimit(80) { _ in }
    }
    
    // 其他实现...
}

3. 温度监控插件

class TempMonitorPlugin: NSObject, BatteryPluginProtocol {
    let pluginID = "com.example.AlDente.TempMonitor"
    let displayName = "电池温度监控"
    let version = "1.0.0"
    
    private var tempThreshold: Double = 35.0 // 默认阈值35°C
    private var comm: PluginCommunicationProtocol!
    private var alertWindow: NSWindow?
    
    func initialize() -> Bool {
        comm = PluginXPCConnection()
        startTemperatureMonitoring()
        return true
    }
    
    private func startTemperatureMonitoring() {
        Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] _ in
            self?.checkTemperature()
        }
    }
    
    private func checkTemperature() {
        comm.getBatteryInfo { info in
            if info.temperature > self.tempThreshold {
                self.handleOverheating(temp: info.temperature)
            }
        }
    }
    
    private func handleOverheating(temp: Double) {
        DispatchQueue.main.async {
            if self.alertWindow == nil {
                self.alertWindow = NSWindow(
                    contentRect: NSRect(x: 100, y: 100, width: 300, height: 150),
                    styleMask: [.titled, .closable],
                    backing: .buffered,
                    defer: false
                )
                self.alertWindow?.center()
                self.alertWindow?.title = "电池过热警告"
            }
            
            let viewController = TempAlertViewController()
            viewController.temperature = temp
            self.alertWindow?.contentViewController = viewController
            self.alertWindow?.makeKeyAndOrderFront(nil)
            
            // 自动降低充电限制
            self.comm.setChargeLimit(60) { _ in }
        }
    }
    
    // 其他实现...
}

插件通信协议实现

XPC服务配置

修改HelperToolProtocol.swift扩展通信接口:

@objc(HelperToolProtocol) protocol HelperToolProtocol {
    // 原有接口...
    func getVersion(withReply reply: @escaping (String) -> Void)
    func setSMCByte(key: String, value: UInt8)
    func readSMCByte(key: String, withReply reply: @escaping (UInt8) -> Void)
    func readSMCUInt32(key: String, withReply reply: @escaping (UInt32) -> Void)
    
    // 新增插件支持接口
    func registerPlugin(pluginID: String, withReply reply: @escaping (Bool) -> Void)
    func unregisterPlugin(pluginID: String)
    func subscribeToBatteryEvents(pluginID: String, withReply reply: @escaping (BatteryInfo) -> Void)
}

通信流程时序图

mermaid

安全与权限管理

代码签名验证流程

mermaid

权限配置示例

Info.plist配置:

<key>SMAuthorizedClients</key>
<array>
    <string>identifier "com.example.AlDente.StatsPlugin" and certificate leaf[subject.CN] = "Developer ID Application: Your Name"</string>
    <string>identifier "com.example.AlDente.SchedulerPlugin" and certificate leaf[subject.CN] = "Developer ID Application: Your Name"</string>
</array>
<key>PluginCapabilities</key>
<dict>
    <key>ChargeControl</key>
    <true/>
    <key>TemperatureMonitoring</key>
    <true/>
    <key>ScheduledTasks</key>
    <true/>
</dict>

调试与测试

调试环境配置

// 在Helper.swift中添加调试日志
func enablePluginDebugging() {
    #if DEBUG
    let fileHandle = FileHandle(forWritingAtPath: "/tmp/aldente_plugin_debug.log")
    fileHandle?.write("插件调试日志开启\n".data(using: .utf8)!)
    #endif
}

调试命令工具

# 查看插件加载状态
defaults read com.davidwernhart.AlDente pluginStatus

# 启用调试日志
defaults write com.davidwernhart.AlDente enablePluginDebug -bool YES

# 重置插件系统
defaults delete com.davidwernhart.AlDente plugins
killall AlDente

单元测试示例

import XCTest
@testable import AlDente
@testable import StatsPlugin

class StatsPluginTests: XCTestCase {
    var plugin: StatsPlugin!
    
    override func setUp() {
        super.setUp()
        plugin = StatsPlugin()
        let success = plugin.initialize()
        XCTAssertTrue(success)
    }
    
    override func tearDown() {
        plugin.terminate()
        plugin = nil
        super.tearDown()
    }
    
    func testDataSampling() {
        // 模拟10分钟数据采样
        for _ in 0..<10 {
            plugin.onBatteryStatusChanged(percentage: UInt8.random(in: 20...100), isCharging: true)
            Thread.sleep(forTimeInterval: 1)
        }
        
        // 验证数据存储
        let samples = plugin.getStoredSamples()
        XCTAssertGreaterThan(samples.count, 5)
    }
}

插件分发与部署

打包格式规范

MyPlugin.alplugin/
├── Contents/
│   ├── Info.plist        # 插件元数据
│   ├── MacOS/            # 可执行代码
│   │   └── MyPlugin
│   ├── Resources/        # 资源文件
│   │   ├── icon.icns
│   │   └── preferences.xib
│   └── _CodeSignature/   # 代码签名
└── README.md             # 插件说明

打包脚本

#!/bin/bash
# build_plugin.sh

PLUGIN_NAME="StatsPlugin"
DEVELOPER_ID="Developer ID Application: Your Name (ABC123XYZ)"

# 编译插件
xcodebuild -project Plugin.xcodeproj -scheme $PLUGIN_NAME -configuration Release clean build

# 创建目录结构
mkdir -p $PLUGIN_NAME.alplugin/Contents/{MacOS,Resources,_CodeSignature}

# 复制文件
cp -R build/Release/$PLUGIN_NAME $PLUGIN_NAME.alplugin/Contents/MacOS/
cp Info.plist $PLUGIN_NAME.alplugin/Contents/
cp Resources/* $PLUGIN_NAME.alplugin/Contents/Resources/

# 代码签名
codesign --sign "$DEVELOPER_ID" --force --deep --timestamp $PLUGIN_NAME.alplugin

# 验证签名
spctl -a -vv $PLUGIN_NAME.alplugin

高级功能与优化

性能优化策略

  1. 数据缓存机制
class BatteryDataCache {
    private var cache: [String: BatteryInfo] = [:]
    private var cacheTime: [String: Date] = [:]
    private let cacheDuration: TimeInterval = 5 // 5秒缓存
    
    func getCachedData(for key: String) -> BatteryInfo? {
        guard let data = cache[key], 
              let time = cacheTime[key],
              Date().timeIntervalSince(time) < cacheDuration else {
            return nil
        }
        return data
    }
    
    func cacheData(_ data: BatteryInfo, for key: String) {
        cache[key] = data
        cacheTime[key] = Date()
    }
}
  1. 后台任务调度
func scheduleBackgroundTask() {
    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 1
    
    let operation = BlockOperation {
        self.processBatteryData()
    }
    
    // 设置依赖关系
    operation.completionBlock = {
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            self.scheduleBackgroundTask()
        }
    }
    
    queue.addOperation(operation)
}

插件冲突解决

class PluginConflictResolver {
    func detectConflicts(plugins: [BatteryPluginProtocol]) -> [[String]] {
        var capabilityMap: [String: [String]] = [:]
        var conflicts: [[String]] = []
        
        // 按功能分组插件
        for plugin in plugins {
            for capability in plugin.requiredCapabilities {
                if capabilityMap[capability] == nil {
                    capabilityMap[capability] = []
                }
                capabilityMap[capability]?.append(plugin.pluginID)
            }
        }
        
        // 检测冲突
        for (capability, pluginIDs) in capabilityMap {
            if pluginIDs.count > 1 {
                conflicts.append(pluginIDs)
                print("功能冲突: \(capability) - 插件: \(pluginIDs.joined(separator: ","))")
            }
        }
        
        return conflicts
    }
    
    func resolveConflicts(conflicts: [[String]], userPreferences: [String]) -> [String] {
        var enabledPlugins: [String] = []
        
        for conflictGroup in conflicts {
            // 优先保留用户偏好的插件
            let preferred = conflictGroup.filter { userPreferences.contains($0) }
            if !preferred.isEmpty {
                enabledPlugins.append(preferred.first!)
            } else {
                // 无偏好时保留版本最新的
                enabledPlugins.append(conflictGroup.sorted { $0.version > $1.version }.first!)
            }
        }
        
        return enabledPlugins
    }
}

总结与展望

通过本文介绍的插件开发框架,你可以构建几乎无限的电池管理扩展功能。目前的实现支持基础的电池数据获取与控制,未来可扩展方向包括:

  1. AI预测模型:基于历史数据预测电池健康度变化
  2. 云同步:跨设备同步充电策略与插件配置
  3. 更丰富的系统集成:与日历、提醒事项等系统应用联动

插件开发的完整示例代码已上传至项目仓库的Examples/Plugins目录,包含本文介绍的所有功能实现。遵循本文的开发规范,你可以构建安全、高效的AlDente扩展,为macOS电池管理带来更多可能性。

下一步行动

  1. 尝试修改示例插件的阈值参数,观察对电池管理的影响
  2. 实现一个全新的插件功能(如电池循环计数统计)
  3. 参与社区插件生态建设,分享你的创意实现

记住,良好的电池管理习惯结合智能工具,才能最大限度延长macOS设备的电池使用寿命。开始你的插件开发之旅吧!

【免费下载链接】AlDente-Charge-Limiter macOS menubar tool to set Charge Limits and prolong battery lifespan 【免费下载链接】AlDente-Charge-Limiter 项目地址: https://gitcode.com/gh_mirrors/al/AlDente-Charge-Limiter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值