🎯 Motivasi

Saya cari fitur untuk membatasi pengisian baterai di MacBook ke 80% biar awet...

dan ketemulah AIDente Pro — fitur bagus tapi yaa... IDR 200K+ 😅

Karena gak ada opsi bawaan macOS, akhirnya bikin:

RyBatteryNotifier – macOS menu bar app untuk pantau status baterai, kasih notifikasi saat sudah penuh.


🛠️ Tools yang Digunakan

  • Swift + SwiftUI
  • IOKit.ps – untuk baca status baterai
  • UserNotifications – buat kirim notifikasi
  • NSStatusBar – tampil di menu bar
  • ✨ Optional: LaunchAgent untuk auto-start

Fitur Utama

✅ Tampilkan status baterai real-time

✅ Menu bar dengan ikon baterai
✅ Bisa atur limit pengisian (misal 80%)

✅ Notifikasi otomatis saat limit tercapai
✅ Mode Auto Discharge (simulasi nonaktif charging) masih on progress
✅ Tombol menu untuk "Show App", "Discharge", dan "Quit" di menu bar.


Membuat Proyek Xcode

  1. Buka Xcode → New ProjectApp
  2. Pilih SwiftUI dan platform macOS
  3. Nama project: RyBatteryNotifier

🧠 Cek Status Baterai via IOKit

Buat file BatteryService.swift:

import IOKit.ps
import UserNotifications
import Foundation

class BatteryService: ObservableObject {
    @Published var batteryPercentage: Int = 0
    @Published var isCharging: Bool = false
    @Published var isInDischargeMode: Bool = false
    @Published var autoDischargeEnabled: Bool = UserDefaults.standard.bool(forKey: "autoDischargeEnabled")

    init() {
        Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
            self.updateBatteryInfo()
        }
        updateBatteryInfo()
    }

    func updateBatteryInfo() {
        guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue(),
              let sources = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue() as? [CFTypeRef] else {
            return
        }

        for ps in sources {
            if let info = IOPSGetPowerSourceDescription(snapshot, ps)?.takeUnretainedValue() as? [String: Any],
               let isCharging = info[kIOPSIsChargingKey as String] as? Bool,
               let current = info[kIOPSCurrentCapacityKey as String] as? Int,
               let max = info[kIOPSMaxCapacityKey as String] as? Int {

                let percent = Int((Double(current) / Double(max)) * 100)
                DispatchQueue.main.async {
                    self.batteryPercentage = percent
                    self.isCharging = isCharging
                }

                let limit = UserDefaults.standard.integer(forKey: "maxLimit")
                if autoDischargeEnabled && isCharging && percent >= limit {
                    DispatchQueue.main.async {
                        self.isInDischargeMode = true
                    }
                    self.sendDischargePrompt()
                    return
                }

                if isCharging && percent >= limit {
                    self.sendNotification()
                }
            }
        }
    }

    func sendNotification() {
        let content = UNMutableNotificationContent()
        content.title = "⚡ Baterai Penuh"
        content.body = "Baterai sudah mencapai limit. Cabut charger ya~"
        content.sound = .default
        UNUserNotificationCenter.current().add(UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: nil
        ))
    }

    func sendDischargePrompt() {
        let content = UNMutableNotificationContent()
        content.title = "🔌 Mode Discharge Aktif"
        content.body = "Auto Discharge aktif. Cabut charger manual ya~"
        content.sound = .default
        UNUserNotificationCenter.current().add(UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: nil
        ))
    }
}

Menu Bar App (Agent Style)

Tambahkan StatusBarController.swift untuk icon di taskbar:

import AppKit

class StatusBarController {
    private var statusItem: NSStatusItem!

    init(showApp: @escaping () -> Void, discharge: @escaping () -> Void) {
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        statusItem.button?.image = NSImage(systemSymbolName: "battery.100", accessibilityDescription: "RyBatteryNotifier")

        let menu = NSMenu()
        let dischargeItem = NSMenuItem(title: "Discharge Mode", action: #selector(doDischarge), keyEquivalent: "d")
        dischargeItem.target = self

        let showAppItem = NSMenuItem(title: "Show App", action: #selector(doShowApp), keyEquivalent: "s")
        showAppItem.target = self

        let quitItem = NSMenuItem(title: "Quit RyBatteryNotifier", action: #selector(doQuit), keyEquivalent: "q")
        quitItem.target = self

        menu.addItem(dischargeItem)
        menu.addItem(showAppItem)
        menu.addItem(.separator())
        menu.addItem(quitItem)

        statusItem.menu = menu

        self.onShowApp = showApp
        self.onDischarge = discharge
    }

    private var onShowApp: (() -> Void)?
    private var onDischarge: (() -> Void)?

    @objc func doShowApp() { onShowApp?() }
    @objc func doDischarge() { onDischarge?() }
    @objc func doQuit() { NSApp.terminate(nil) }
}

Info.plist Tweaks

Tambahkan ke Info.plist agar app jadi menu bar agent (tanpa dock icon):

LSUIElement

LSApplicationCategoryType
public.app-category.utilities

Install Manual (Karena Gratis Account)

Xcode personal account tidak bisa Archive-distribute, jadi:

cd ~/Library/Developer/Xcode/DerivedData
open .

Masuk ke folder project → Build/Products/Debug/

Temukan RyBatteryNotifier.app dan copy ke /Applications/:

cp -R RyBatteryNotifier.app /Applications/

Jalankan:

open /Applications/RyBatteryNotifier.app

🔁 Auto Start Saat Login (Optional)

  1. Buat file ~/Library/LaunchAgents/com.ryfazrin.battery.plist
version="1.0">

  Label
  com.ryfazrin.battery
  ProgramArguments
  
    /Applications/RyBatteryNotifier.app/Contents/MacOS/RyBatteryNotifier
  
  RunAtLoad
  1. Jalankan:
launchctl load ~/Library/LaunchAgents/com.ryfazrin.battery.plist

Penutup

Kini saya punya macOS app buatan sendiri di menu bar dan bisa bantu rawat baterai MacBook.

💸 Gratis, open-source, dan fun to build.

✨ Gak perlu AIDente Pro, cukup semangat dan Xcode 😄


Source code?

Repo GitHub ada di komentar~ 🧪