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