Skip to content

Commit

Permalink
Add playing now artwork switch, fix battery icon on Big Sur + delay +…
Browse files Browse the repository at this point in the history
… add preference option to show time instead of percentage, fix Big Sur issues with Clock and Lang
  • Loading branch information
konstantintuev committed Nov 18, 2020
1 parent 6910935 commit c5ea513
Show file tree
Hide file tree
Showing 44 changed files with 991 additions and 126 deletions.
36 changes: 36 additions & 0 deletions Pock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@
96BE2701237171DC0031B31F /* CCScreensaverItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BE2700237171DC0031B31F /* CCScreensaverItem.swift */; };
96E7A152239B99F700CB4C4C /* CCDoNotDisturbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E7A151239B99F700CB4C4C /* CCDoNotDisturbItem.swift */; };
96FE269F22D0C3810071645C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96FE26A122D0C3810071645C /* Localizable.strings */; };
99510002256574DC00E34E4D /* BatteryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99510001256574DC00E34E4D /* BatteryService.swift */; };
99510005256574FD00E34E4D /* RegistryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99510004256574FD00E34E4D /* RegistryKey.swift */; };
995100092565752800E34E4D /* BatteryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995100072565752800E34E4D /* BatteryState.swift */; };
9951000A2565752800E34E4D /* BatteryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995100082565752800E34E4D /* BatteryError.swift */; };
9951000F2565912200E34E4D /* BatteryImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9951000C2565912200E34E4D /* BatteryImageCache.swift */; };
995100102565912200E34E4D /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9951000D2565912200E34E4D /* NSImage.swift */; };
995100112565912200E34E4D /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9951000E2565912200E34E4D /* StatusBarIcon.swift */; };
9955665222DC668500EFFE6D /* CCVolumeMuteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9955665122DC668500EFFE6D /* CCVolumeMuteItem.swift */; };
999276BE249FD8C300EFF44A /* SLangItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 999276BD249FD8C300EFF44A /* SLangItem.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -210,6 +217,13 @@
96F29996231797C00055DA26 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/GeneralPreferencePane.strings; sourceTree = "<group>"; };
96F2999A2317980B0055DA26 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ControlCenterWidgetPreferencePane.strings"; sourceTree = "<group>"; };
96FE26A022D0C3810071645C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
99510001256574DC00E34E4D /* BatteryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryService.swift; sourceTree = "<group>"; };
99510004256574FD00E34E4D /* RegistryKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistryKey.swift; sourceTree = "<group>"; };
995100072565752800E34E4D /* BatteryState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryState.swift; sourceTree = "<group>"; };
995100082565752800E34E4D /* BatteryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryError.swift; sourceTree = "<group>"; };
9951000C2565912200E34E4D /* BatteryImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryImageCache.swift; sourceTree = "<group>"; };
9951000D2565912200E34E4D /* NSImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
9951000E2565912200E34E4D /* StatusBarIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = "<group>"; };
9955665122DC668500EFFE6D /* CCVolumeMuteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCVolumeMuteItem.swift; sourceTree = "<group>"; };
999276BD249FD8C300EFF44A /* SLangItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SLangItem.swift; sourceTree = "<group>"; };
AAA3636F2333699C0016267D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/DockFolderController.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -320,6 +334,7 @@
342D83932278D5CF000D79DA /* Pock */ = {
isa = PBXGroup;
children = (
995100132565B99100E34E4D /* BatteryTools */,
342D83B32278D5CF000D79DA /* AppDelegate.swift */,
3407F9DD22D0F0A60072E742 /* Extensions.swift */,
342D83942278D5CF000D79DA /* Interfaces UI */,
Expand Down Expand Up @@ -639,6 +654,20 @@
name = Frameworks;
sourceTree = "<group>";
};
995100132565B99100E34E4D /* BatteryTools */ = {
isa = PBXGroup;
children = (
99510001256574DC00E34E4D /* BatteryService.swift */,
995100082565752800E34E4D /* BatteryError.swift */,
995100072565752800E34E4D /* BatteryState.swift */,
99510004256574FD00E34E4D /* RegistryKey.swift */,
9951000C2565912200E34E4D /* BatteryImageCache.swift */,
9951000D2565912200E34E4D /* NSImage.swift */,
9951000E2565912200E34E4D /* StatusBarIcon.swift */,
);
path = BatteryTools;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -826,6 +855,7 @@
342D83FA2278D5CF000D79DA /* SSpotlightItem.swift in Sources */,
342D83F92278D5CF000D79DA /* StatusItem.swift in Sources */,
999276BE249FD8C300EFF44A /* SLangItem.swift in Sources */,
995100092565752800E34E4D /* BatteryState.swift in Sources */,
3406433E227ED8BC00981372 /* DockFolderItemView.swift in Sources */,
347F336022D0D68400FA9805 /* CCLockItem.swift in Sources */,
342D83E62278D5CF000D79DA /* StatusWidgetPreferencePane.swift in Sources */,
Expand All @@ -844,26 +874,32 @@
342D83F12278D5CF000D79DA /* CCVolumeDownItem.swift in Sources */,
342D84032278D5CF000D79DA /* FileEvent.swift in Sources */,
9675D6D323701BC900C36234 /* CCBrightnessToggleItem.swift in Sources */,
995100112565912200E34E4D /* StatusBarIcon.swift in Sources */,
3407F9EB22D1C7F40072E742 /* AppExposeItem.swift in Sources */,
34E644F4227CB6CE003D0450 /* KeySender.swift in Sources */,
342D840B2278D5CF000D79DA /* DockItemView.swift in Sources */,
342D840C2278D5CF000D79DA /* DockWidget.swift in Sources */,
34E644FB227D9A69003D0450 /* DockWidgetPreferencePane.swift in Sources */,
3407F9DC22D0E75E0072E742 /* ControlCenterWidgetPreferencePane.swift in Sources */,
99510002256574DC00E34E4D /* BatteryService.swift in Sources */,
342D83EE2278D5CF000D79DA /* ControlCenterWidget.swift in Sources */,
342D83EC2278D5CF000D79DA /* OSDUIHelper.swift in Sources */,
995100102565912200E34E4D /* NSImage.swift in Sources */,
3418D9FF22882EC600E11E37 /* PKSlideableController.swift in Sources */,
342D83FE2278D5CF000D79DA /* EscWidget.swift in Sources */,
345C955423A53D4D00E1AC53 /* NowPlayingPreferencePane.swift in Sources */,
34064343227EDC2200981372 /* DockFolderRepository.swift in Sources */,
342D83E02278D5CF000D79DA /* PKTouchBarController.swift in Sources */,
9951000F2565912200E34E4D /* BatteryImageCache.swift in Sources */,
3406434A227F323A00981372 /* PKTouchBarNavController.swift in Sources */,
9951000A2565752800E34E4D /* BatteryError.swift in Sources */,
34064337227E224700981372 /* DockFolderController.swift in Sources */,
342D83F22278D5CF000D79DA /* CCBrightnessUpItem.swift in Sources */,
3407F9DE22D0F0A60072E742 /* Extensions.swift in Sources */,
342D83F02278D5CF000D79DA /* CCBrightnessDownItem.swift in Sources */,
347F336722D0DB9200FA9805 /* SystemHelper.m in Sources */,
342D83F52278D5CF000D79DA /* NowPlayingItem.swift in Sources */,
99510005256574FD00E34E4D /* RegistryKey.swift in Sources */,
342D83F62278D5CF000D79DA /* NowPlayingWidget.swift in Sources */,
342D840D2278D5CF000D79DA /* FileMonitor.swift in Sources */,
3407F9E722D1C5170072E742 /* AppExposeController.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Pock/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
/// Initialize Crashlytics
if isProd {
UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true])
Fabric.with([Crashlytics.self])
//Fabric.with([Crashlytics.self])
}

/// Check for accessibility (needed for badges to work)
Expand Down
16 changes: 16 additions & 0 deletions Pock/BatteryTools/BatteryError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// BatteryError.swift
// Apple Juice
// https://github.com/raphaelhanneken/apple-juice
//

/// Exceptions for the Battery class.
///
/// - connectionAlreadyOpen: Get's thrown in case the connection to the battery's IOService
/// is already open. Accepts an error description of type String.
/// - serviceNotFound: Get's thrown in case the supplied IOService wasn't found.
/// Accepts an error description of type String.
enum BatteryError: Error {
case connectionAlreadyOpen(String)
case serviceNotFound(String)
}
25 changes: 25 additions & 0 deletions Pock/BatteryTools/BatteryImageCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// BatteryImageCache.swift
// Apple Juice
// https://github.com/raphaelhanneken/apple-juice
//

import Cocoa

struct BatteryImageCache {

/// The cached battery icon.
let image: NSImage?
/// The BatteryState associated with the cached battery icon.
let batteryStatus: BatteryState

/// Cache a battery icon alongside it's corresponding BatteryState.
///
/// - parameter status: The BatteryState to cache the battery icon for.
/// - parameter img: The battery icon to cache.
init(forStatus status: BatteryState, withImage img: NSImage?) {
batteryStatus = status
image = img
}

}
240 changes: 240 additions & 0 deletions Pock/BatteryTools/BatteryService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
//
// Battery.swift
// Apple Juice
// https://github.com/raphaelhanneken/apple-juice
//

import Foundation
import IOKit.ps

/// Notification name for the power source changed callback.
let powerSourceChangedNotification = "com.raphaelhanneken.apple-juice.powersourcechanged"

/// Posts a notification every time the power source changes.
private let powerSourceCallback: IOPowerSourceCallbackType = { _ in
NotificationCenter.default.post(name: Notification.Name(rawValue: powerSourceChangedNotification),
object: nil)
}

/// Accesses the battery's IO service.
final class BatteryService {

/// Closed state value for the service connection object.
private static let connectionClosed: UInt32 = 0

/// An IOService object that matches battery's IO service dictionary.
private var service: io_object_t = BatteryService.connectionClosed

/// The current status of the battery, e.g. charging.
var state: BatteryState? {
guard
let plugged = isPlugged,
let charged = isCharged,
let percentage = percentage else {
return nil
}
if charged && plugged {
return .chargedAndPlugged
}
if plugged {
return .charging(percentage: percentage)
}

return .discharging(percentage: percentage)
}

/// The remaining time until the battery is empty or fully charged
/// in a human readable format, e.g. hh:mm.
var timeRemainingFormatted: String {
// Unwrap required information.
guard let charged = isCharged, let plugged = isPlugged else {
return NSLocalizedString("-1:-1", comment: "")
}
// Check if the battery is charged and plugged into an unlimited power supply.
if charged && plugged {
return NSLocalizedString("Full", comment: "")
}
// The battery is (dis)charging, display the remaining time.
if let time = timeRemaining {
return String(format: "%d:%02d", arguments: [time / 60, time % 60])
}

return NSLocalizedString("Wait", comment: "")
}

/// The remaining time in _minutes_ until the battery is empty or fully charged.
var timeRemaining: Int? {
// Get the estimated time remaining.
let time = IOPSGetTimeRemainingEstimate()

switch time {
case kIOPSTimeRemainingUnknown:
return nil
case kIOPSTimeRemainingUnlimited:
// The battery is connected to a power outlet, get the remaining time
// until the battery is fully charged.
if let prop = getRegistryProperty(forKey: .timeRemaining) as? Int, prop < 600 {
return prop
}
return nil
default:
// The estimated time in minutes
return Int(time / 60)
}
}

/// The current percentage, based on the current charge and the maximum capacity.
var percentage: Int? {
return getPowerSourceProperty(forKey: .percentage) as? Int
}

/// The current percentage, formatted according to the selected client locale, e.g.
/// en_US: 42% fr_FR: 42 %
var percentageFormatted: String {
guard let percentage = self.percentage else {
return NSLocalizedString("Calculating", comment: "")
}

let percentageFormatter = NumberFormatter()
percentageFormatter.numberStyle = .percent
percentageFormatter.generatesDecimalNumbers = false
percentageFormatter.localizesFormat = true
percentageFormatter.multiplier = 1.0
percentageFormatter.minimumFractionDigits = 0
percentageFormatter.maximumFractionDigits = 0

return percentageFormatter.string(from: percentage as NSNumber) ?? "\(percentage) %"
}

/// The current charge in mAh.
var charge: Int? {
return getRegistryProperty(forKey: .currentCharge) as? Int
}

/// The maximum capacity in mAh.
var capacity: Int? {
return getRegistryProperty(forKey: .maxCapacity) as? Int
}

/// The source from which the Mac currently draws its power.
var powerSource: String {
guard let plugged = isPlugged else {
return NSLocalizedString("Unknown", comment: "")
}
// Check whether the MacBook currently is plugged into a power adapter.
if plugged {
return NSLocalizedString("Power Adapter", comment: "")
}

return NSLocalizedString("Battery", comment: "")
}

/// Checks whether the battery is charging and connected to a power outlet.
var isCharging: Bool? {
return getRegistryProperty(forKey: .isCharging) as? Bool
}

/// Checks whether the battery is fully charged.
var isCharged: Bool? {
return getRegistryProperty(forKey: .fullyCharged) as? Bool
}

/// Checks whether the battery is plugged into an unlimited power supply.
var isPlugged: Bool? {
return getRegistryProperty(forKey: .isPlugged) as? Bool
}

/// Calculates the current power usage in Watts.
var powerUsage: Double? {
guard
let voltage = getRegistryProperty(forKey: .voltage) as? Double,
let amperage = getRegistryProperty(forKey: .amperage) as? Double else {
return nil
}
return round((voltage * amperage) / 1_000_000)
}

/// Current flowing into or out of the battery.
var amperage: Int? {
guard
let amperage = getRegistryProperty(forKey: .amperage) as? Int else {
return nil
}
return amperage
}

/// The number of charging cycles.
var cycleCount: Int? {
return getRegistryProperty(forKey: .cycleCount) as? Int
}

/// The battery's current temperature.
var temperature: Double? {
guard let temp = getRegistryProperty(forKey: .temperature) as? Double else {
return nil
}
return (temp / 100)
}

/// The batteries' health status
var health: String? {
return getPowerSourceProperty(forKey: .health) as? String
}

/// Initializes a new Battery object.
init() throws {
try openServiceConnection()
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IOPSNotificationCreateRunLoopSource(powerSourceCallback, nil).takeRetainedValue(),
CFRunLoopMode.defaultMode)
}

/// Opens a connection to the battery's IOService object.
///
/// - throws: A BatteryError if something went wrong.
private func openServiceConnection() throws {
if service != BatteryService.connectionClosed && !closeServiceConnection() {
// For some reason we have an open IO Service connection which we cannot close.
throw BatteryError.connectionAlreadyOpen("Closing the IOService connection failed.")
}
service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceNameMatching(RegistryKey.service.rawValue))

if service == BatteryService.connectionClosed {
throw BatteryError
.serviceNotFound("Opening the provided IOService (\(RegistryKey.service.rawValue)) failed.")
}
}

/// Closes the connection the the battery's IOService object.
///
/// - returns: True, when the IOService connection was successfully closed.
public func closeServiceConnection() -> Bool {
if kIOReturnSuccess == IOObjectRelease(service) {
service = BatteryService.connectionClosed
}

return (service == BatteryService.connectionClosed)
}

/// Get the registry entry's property for the supplied SmartBatteryKey.
///
/// - parameter key: A SmartBatteryKey to get the corresponding registry entry's property.
/// - returns: The registry entry for the provided SmartBatteryKey.
private func getRegistryProperty(forKey key: RegistryKey) -> AnyObject? {
return IORegistryEntryCreateCFProperty(service, key.rawValue as CFString?, nil, 0)
.takeRetainedValue()
}

private func getPowerSourceProperty(forKey key: RegistryKey) -> Any? {
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as? [CFDictionary]

guard let powerSources = psList else {
return nil
}
let powerSource = powerSources[0] as NSDictionary

return powerSource[key.rawValue]
}
}
Loading

0 comments on commit c5ea513

Please sign in to comment.