DEV Community

Cover image for RyBatteryNotifier: Aplikasi macOS untuk Batasi Charger Otomatis (Gratisan)
Ryan Pazrin
Ryan Pazrin

Posted on

RyBatteryNotifier: Aplikasi macOS untuk Batasi Charger Otomatis (Gratisan)

🎯 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 Project β†’ App
  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
        ))
    }
}
Enter fullscreen mode Exit fullscreen mode

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) }
}
Enter fullscreen mode Exit fullscreen mode

Info.plist Tweaks

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

<key>LSUIElement</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
Enter fullscreen mode Exit fullscreen mode

Install Manual (Karena Gratis Account)

Xcode personal account tidak bisa Archive-distribute, jadi:

cd ~/Library/Developer/Xcode/DerivedData
open .
Enter fullscreen mode Exit fullscreen mode

Masuk ke folder project β†’ Build/Products/Debug/

Temukan RyBatteryNotifier.app dan copy ke /Applications/:

cp -R RyBatteryNotifier.app /Applications/
Enter fullscreen mode Exit fullscreen mode

Jalankan:

open /Applications/RyBatteryNotifier.app
Enter fullscreen mode Exit fullscreen mode

πŸ” Auto Start Saat Login (Optional)

  1. Buat file ~/Library/LaunchAgents/com.ryfazrin.battery.plist
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.ryfazrin.battery</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Applications/RyBatteryNotifier.app/Contents/MacOS/RyBatteryNotifier</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>
Enter fullscreen mode Exit fullscreen mode
  1. Jalankan:
launchctl load ~/Library/LaunchAgents/com.ryfazrin.battery.plist
Enter fullscreen mode Exit fullscreen mode

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~ πŸ§ͺ

Top comments (1)

Collapse
 
ryfazrin profile image
Ryan Pazrin