feat: initialized Bluetooth module (#277)

This commit is contained in:
Serhiy Mytrovtsiy
2021-07-08 23:03:02 +02:00
parent 178b713353
commit 22386a4ae1
10 changed files with 848 additions and 46 deletions

View File

@@ -18,7 +18,7 @@ public class BatterykWidget: WidgetWrapper {
private var colorState: Bool = false
private var hideAdditionalWhenFull: Bool = true
private var percentage: Double = 1
private var percentage: Double? = nil
private var time: Int = 0
private var charging: Bool = false
private var ACStatus: Bool = false
@@ -68,10 +68,11 @@ public class BatterykWidget: WidgetWrapper {
if !self.hideAdditionalWhenFull || (self.hideAdditionalWhenFull && self.percentage != 1) {
switch self.additional {
case "percentage":
let rowWidth = self.drawOneRow(
value: "\(Int((self.percentage.rounded(toPlaces: 2)) * 100))%",
x: x
).rounded(.up)
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawOneRow(value: value, x: x).rounded(.up)
width += rowWidth + Constants.Widget.spacing
x += rowWidth + Constants.Widget.spacing
case "time":
@@ -82,17 +83,25 @@ public class BatterykWidget: WidgetWrapper {
width += rowWidth + Constants.Widget.spacing
x += rowWidth + Constants.Widget.spacing
case "percentageAndTime":
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawTwoRows(
first: "\(Int((self.percentage.rounded(toPlaces: 2)) * 100))%",
first: value,
second: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
x: x
).rounded(.up)
width += rowWidth + Constants.Widget.spacing
x += rowWidth + Constants.Widget.spacing
case "timeAndPercentage":
var value = "n/a"
if let percentage = self.percentage {
value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
}
let rowWidth = self.drawTwoRows(
first: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
second: "\(Int((self.percentage.rounded(toPlaces: 2)) * 100))%",
second: value,
x: x
).rounded(.up)
width += rowWidth + Constants.Widget.spacing
@@ -133,17 +142,33 @@ public class BatterykWidget: WidgetWrapper {
ctx.restoreGState()
width += 2 // add battery point width
let maxWidth = batterySize.width - offset*2 - borderWidth*2 - 1
let innerWidth: CGFloat = max(1, maxWidth * CGFloat(self.percentage))
let innerOffset: CGFloat = -offset + borderWidth + 1
let inner = NSBezierPath(roundedRect: NSRect(
x: batteryFrame.bounds.origin.x + innerOffset,
y: batteryFrame.bounds.origin.y + innerOffset,
width: innerWidth,
height: batterySize.height - offset*2 - borderWidth*2 - 1
), xRadius: 1, yRadius: 1)
self.percentage.batteryColor(color: self.colorState).set()
inner.fill()
if let percentage = self.percentage {
let maxWidth = batterySize.width - offset*2 - borderWidth*2 - 1
let innerWidth: CGFloat = max(1, maxWidth * CGFloat(percentage))
let innerOffset: CGFloat = -offset + borderWidth + 1
let inner = NSBezierPath(roundedRect: NSRect(
x: batteryFrame.bounds.origin.x + innerOffset,
y: batteryFrame.bounds.origin.y + innerOffset,
width: innerWidth,
height: batterySize.height - offset*2 - borderWidth*2 - 1
), xRadius: 1, yRadius: 1)
percentage.batteryColor(color: self.colorState).set()
inner.fill()
} else {
let attributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 11, weight: .regular),
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let batteryCenter: CGPoint = CGPoint(
x: batteryFrame.bounds.origin.x + (batteryFrame.bounds.width/2),
y: batteryFrame.bounds.origin.y + (batteryFrame.bounds.height/2)
)
let rect = CGRect(x: batteryCenter.x-2, y: batteryCenter.y-4, width: 8, height: 12)
NSAttributedString.init(string: "?", attributes: attributes).draw(with: rect)
}
if self.ACStatus {
let batteryCenter: CGPoint = CGPoint(
@@ -267,7 +292,7 @@ public class BatterykWidget: WidgetWrapper {
return rowWidth
}
public func setValue(percentage: Double, ACStatus: Bool, isCharging: Bool, time: Int) {
public func setValue(percentage: Double? = nil, ACStatus: Bool? = nil, isCharging: Bool? = nil, time: Int? = nil) {
var updated: Bool = false
let timeFormat: String = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
@@ -275,15 +300,15 @@ public class BatterykWidget: WidgetWrapper {
self.percentage = percentage
updated = true
}
if self.ACStatus != ACStatus {
self.ACStatus = ACStatus
if let status = ACStatus, self.ACStatus != status {
self.ACStatus = status
updated = true
}
if self.charging != isCharging {
self.charging = isCharging
if let charging = isCharging, self.charging != charging {
self.charging = charging
updated = true
}
if self.time != time {
if let time = time, self.time != time {
self.time = time
updated = true
}
@@ -302,33 +327,32 @@ public class BatterykWidget: WidgetWrapper {
// MARK: - Settings
public override func settings(width: CGFloat) -> NSView {
let rowHeight: CGFloat = 30
let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 3) + Constants.Settings.margin
let view = SettingsContainerView(width: width)
let view: NSView = NSView(frame: NSRect(
x: Constants.Settings.margin,
y: Constants.Settings.margin,
width: width - (Constants.Settings.margin*2),
height: height
))
var additionalOptions = BatteryAdditionals
if self.title == "Bluetooth" {
additionalOptions = additionalOptions.filter({ $0.key == "none" || $0.key == "percentage" })
}
view.addSubview(selectRow(
frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 2, width: view.frame.width, height: rowHeight),
view.addArrangedSubview(selectRow(
frame: NSRect(x: 0, y: 0, width: view.frame.width, height: Constants.Settings.row),
title: localizedString("Additional information"),
action: #selector(toggleAdditional),
items: BatteryAdditionals,
items: additionalOptions,
selected: self.additional
))
view.addSubview(toggleTitleRow(
frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight),
title: localizedString("Hide additional information when full"),
action: #selector(toggleHideAdditionalWhenFull),
state: self.hideAdditionalWhenFull
))
if self.title != "Bluetooth" {
view.addArrangedSubview(toggleTitleRow(
frame: NSRect(x: 0, y: 0, width: view.frame.width, height: Constants.Settings.row),
title: localizedString("Hide additional information when full"),
action: #selector(toggleHideAdditionalWhenFull),
state: self.hideAdditionalWhenFull
))
}
view.addSubview(toggleTitleRow(
frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight),
view.addArrangedSubview(toggleTitleRow(
frame: NSRect(x: 0, y: 0, width: view.frame.width, height: Constants.Settings.row),
title: localizedString("Colorize"),
action: #selector(toggleColor),
state: self.colorState

View File

@@ -80,7 +80,7 @@ open class Module: Module_p {
private let log: NextLog
private var readers: [Reader_p] = []
public init(popup: Popup_p?, settings: Settings_v?) {
public init(popup: Popup_p? = nil, settings: Settings_v? = nil) {
self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!)
self.log = NextLog.shared.copy(category: self.config.name)

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Name</key>
<string>Bluetooth</string>
<key>State</key>
<true/>
<key>Widgets</key>
<dict>
<key>label</key>
<dict>
<key>Default</key>
<false/>
<key>Title</key>
<string>BLE</string>
<key>Order</key>
<integer>0</integer>
</dict>
<key>mini</key>
<dict>
<key>Title</key>
<string>BLE</string>
<key>Default</key>
<false/>
<key>Preview</key>
<dict>
<key>Title</key>
<string>BLE</string>
<key>Value</key>
<string>0.98</string>
</dict>
<key>Unsupported colors</key>
<array>
<string>pressure</string>
</array>
<key>Order</key>
<integer>1</integer>
</dict>
<key>battery</key>
<dict>
<key>Default</key>
<true/>
<key>Order</key>
<integer>2</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,116 @@
//
// main.swift
// Bluetooth
//
// Created by Serhiy Mytrovtsiy on 08/06/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Foundation
import Kit
import CoreBluetooth
public enum BLEType: String {
case iPhone
case airPods
case unknown
}
public struct BLEDevice {
let uuid: UUID
let name: String
let type: BLEType
var RSSI: Int?
var batteryLevel: [KeyValue_t]
var isConnected: Bool
var isPaired: Bool
var isInitialized: Bool
var peripheral: CBPeripheral?
}
public class Bluetooth: Module {
private var devicesReader: DevicesReader? = nil
private let popupView: Popup = Popup()
private let settingsView: Settings
private var selectedBattery: String = ""
public init() {
self.settingsView = Settings("Bluetooth")
super.init(
popup: self.popupView,
settings: self.settingsView
)
guard self.available else { return }
self.devicesReader = DevicesReader()
self.selectedBattery = Store.shared.string(key: "\(self.config.name)_battery", defaultValue: self.selectedBattery)
self.settingsView.selectedBatteryHandler = { [unowned self] value in
self.selectedBattery = value
}
self.devicesReader?.callbackHandler = { [unowned self] value in
self.batteryCallback(value)
}
self.devicesReader?.readyCallback = { [unowned self] in
self.readyHandler()
}
if let reader = self.devicesReader {
self.addReader(reader)
}
}
private func batteryCallback(_ raw: [BLEDevice]?) {
guard let value = raw else {
return
}
let active = value.filter{ $0.isPaired && ($0.isConnected || !$0.batteryLevel.isEmpty) }
DispatchQueue.main.async(execute: {
self.popupView.batteryCallback(active)
})
self.settingsView.setList(active)
var battery = active.first?.batteryLevel.first
if self.selectedBattery != "" {
let pair = self.selectedBattery.split(separator: "@")
guard let device = value.first(where: { $0.name == pair.first! }) else {
error("cannot find selected battery: \(self.selectedBattery)")
return
}
if pair.count == 1 {
battery = device.batteryLevel.first
} else if pair.count == 2 {
battery = device.batteryLevel.first{ $0.key == pair.last! }
}
}
self.widgets.filter{ $0.isActive }.forEach { (w: Widget) in
switch w.item {
case let widget as Mini:
guard let percentage = Double(battery?.value ?? "0") else {
return
}
widget.setValue(percentage/100)
case let widget as BatterykWidget:
var percentage: Double? = nil
if let value = battery?.value {
percentage = (Double(value) ?? 0) / 100
}
widget.setValue(percentage: percentage)
default: break
}
}
}
}

View File

@@ -0,0 +1,111 @@
//
// popup.swift
// Bluetooth
//
// Created by Serhiy Mytrovtsiy on 22/06/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Kit
internal class Popup: NSStackView, Popup_p {
public var sizeCallback: ((NSSize) -> Void)? = nil
private var list: [UUID: BLEView] = [:]
public init() {
super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0))
self.orientation = .vertical
self.spacing = Constants.Popup.margins
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal func batteryCallback(_ list: [BLEDevice]) {
let views = self.subviews.filter{ $0 is BLEView }.map{ $0 as! BLEView }
list.reversed().forEach { (ble: BLEDevice) in
if let view = views.first(where: { $0.uuid == ble.uuid }) {
view.update(ble.batteryLevel)
} else {
self.addArrangedSubview(BLEView(
width: self.frame.width,
uuid: ble.uuid,
name: ble.name,
batteryLevel: ble.batteryLevel
))
}
}
let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing
if h > 0 && self.frame.size.height != h {
self.setFrameSize(NSSize(width: self.frame.width, height: h))
self.sizeCallback?(self.frame.size)
}
}
}
internal class BLEView: NSStackView {
public var uuid: UUID
open override var intrinsicContentSize: CGSize {
return CGSize(width: self.bounds.width, height: self.bounds.height)
}
public init(width: CGFloat, uuid: UUID, name: String, batteryLevel: [KeyValue_t]) {
self.uuid = uuid
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 30))
self.orientation = .horizontal
self.alignment = .centerY
self.spacing = 0
self.wantsLayer = true
self.layer?.cornerRadius = 2
let nameView: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: 0, height: 16))
nameView.font = NSFont.systemFont(ofSize: 13, weight: .light)
nameView.stringValue = name
self.addArrangedSubview(nameView)
self.addArrangedSubview(NSView())
batteryLevel.forEach { (pair: KeyValue_t) in
self.addLevel(pair)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateLayer() {
self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor
}
public func update(_ batteryLevel: [KeyValue_t]) {
batteryLevel.forEach { (pair: KeyValue_t) in
if let view = self.subviews.first(where: { $0.identifier?.rawValue == pair.key }) as? NSTextField {
view.stringValue = "\(pair.value)%"
} else {
self.addLevel(pair)
}
}
}
private func addLevel(_ pair: KeyValue_t) {
let valueView: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: 0, height: 13))
valueView.identifier = NSUserInterfaceItemIdentifier(rawValue: pair.key)
valueView.font = NSFont.systemFont(ofSize: 12, weight: .regular)
valueView.stringValue = "\(pair.value)%"
valueView.toolTip = pair.key
self.addArrangedSubview(valueView)
}
}

View File

@@ -0,0 +1,179 @@
//
// readers.swift
// Bluetooth
//
// Created by Serhiy Mytrovtsiy on 08/06/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Foundation
import Kit
import CoreBluetooth
import IOBluetooth
internal class DevicesReader: Reader<[BLEDevice]> {
private let ble: BluetoothDelegate = BluetoothDelegate()
init() {
super.init()
}
public override func read() {
self.ble.read()
self.callback(self.ble.devices)
}
}
class BluetoothDelegate: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private var manager: CBCentralManager!
private let cache = UserDefaults(suiteName: "/Library/Preferences/com.apple.Bluetooth")
private var peripherals: [CBPeripheral] = []
public var devices: [BLEDevice] = []
private var characteristicsDict: [UUID: CBCharacteristic] = [:]
private let batteryServiceUUID = CBUUID(string: "0x180F")
private let batteryCharacteristicsUUID = CBUUID(string: "0x2A19")
override init() {
super.init()
self.manager = CBCentralManager.init(delegate: self, queue: nil)
}
public func read() {
IOBluetoothDevice.pairedDevices().forEach { (d) in
guard let device = d as? IOBluetoothDevice,
let cache = self.findInCache(address: device.addressString) else {
return
}
let rssi = device.rawRSSI() == 127 ? nil : Int(device.rawRSSI())
if let idx = self.devices.firstIndex(where: { $0.uuid == cache.uuid }) {
self.devices[idx].isConnected = device.isConnected()
self.devices[idx].isPaired = device.isPaired()
self.devices[idx].RSSI = rssi
} else {
self.devices.append(BLEDevice(
uuid: cache.uuid,
name: device.nameOrAddress,
type: .unknown,
RSSI: rssi,
batteryLevel: cache.batteryLevel,
isConnected: device.isConnected(),
isPaired: device.isPaired(),
isInitialized: false
))
}
}
}
private func findInCache(address: String) -> (uuid: UUID, batteryLevel: [KeyValue_t])? {
guard let plist = self.cache,
let deviceCache = plist.object(forKey: "DeviceCache") as? [String: [String: Any]],
let coreCache = plist.object(forKey: "CoreBluetoothCache") as? [String: [String: Any]] else {
return nil
}
guard let uuid = coreCache.compactMap({ (key, dict) -> UUID? in
guard let field = dict.first(where: { $0.key == "DeviceAddress" }),
let value = field.value as? String,
value == address else {
return nil
}
return UUID(uuidString: key)
}).first else {
return nil
}
var batteryLevel: [KeyValue_t] = []
if let d = deviceCache.first(where: { $0.key == address }) {
d.value.forEach { (key, value) in
guard let value = value as? Int, key == "BatteryPercentCase" || key == "BatteryPercentLeft" || key == "BatteryPercentRight" else {
return
}
batteryLevel.append(KeyValue_t(key: key, value: "\(value)"))
}
}
return (uuid, batteryLevel)
}
// MARK: - CBCentralManagerDelegate
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOff {
self.manager.stopScan()
} else if central.state == .poweredOn {
self.manager.scanForPeripherals(withServices: nil, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
guard let idx = self.devices.firstIndex(where: { $0.uuid == peripheral.identifier }) else {
return
}
if self.devices[idx].RSSI == nil {
self.devices[idx].RSSI = Int(truncating: RSSI)
}
if self.devices[idx].peripheral == nil {
self.devices[idx].peripheral = peripheral
}
if peripheral.state == .disconnected {
central.connect(peripheral, options: nil)
} else if peripheral.state == .connected && !self.devices[idx].isInitialized {
peripheral.delegate = self
peripheral.discoverServices([batteryServiceUUID])
self.devices[idx].isInitialized = true
}
}
// MARK: - CBPeripheralDelegate
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard error == nil else {
print("didDiscoverServices: ", error!)
return
}
guard let service = peripheral.services?.first(where: { $0.uuid == self.batteryServiceUUID }) else {
print("battery service not found, skipping")
return
}
peripheral.discoverCharacteristics([self.batteryCharacteristicsUUID], for: service)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard error == nil else {
print("didDiscoverCharacteristicsFor: ", error!)
return
}
guard let batteryCharacteristics = service.characteristics?.first(where: { $0.uuid == self.batteryCharacteristicsUUID }) else {
print("characteristics not found")
return
}
self.characteristicsDict[peripheral.identifier] = batteryCharacteristics
peripheral.readValue(for: batteryCharacteristics)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
print("didUpdateValueFor: ", error!)
return
}
if let batteryLevel = characteristic.value?[0], let idx = self.devices.firstIndex(where: { $0.uuid == peripheral.identifier }) {
self.devices[idx].batteryLevel = [KeyValue_t(key: "battery", value: "\(batteryLevel)")]
}
}
}

View File

@@ -0,0 +1,112 @@
//
// settings.swift
// Bluetooth
//
// Created by Serhiy Mytrovtsiy on 07/07/2021.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Kit
internal class Settings: NSStackView, Settings_v {
public var callback: (() -> Void) = {}
public var selectedBatteryHandler: (String) -> Void = {_ in }
private let title: String
private var selectedBattery: String
private var button: NSPopUpButton?
public init(_ title: String) {
self.title = title
self.selectedBattery = Store.shared.string(key: "\(self.title)_battery", defaultValue: "")
super.init(frame: NSRect(
x: 0,
y: 0,
width: Constants.Settings.width - (Constants.Settings.margin*2),
height: 0
))
self.orientation = .vertical
self.distribution = .gravityAreas
self.edgeInsets = NSEdgeInsets(
top: Constants.Settings.margin,
left: Constants.Settings.margin,
bottom: Constants.Settings.margin,
right: Constants.Settings.margin
)
self.spacing = Constants.Settings.margin
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal func load(widgets: [widget_t]) {
self.subviews.forEach{ $0.removeFromSuperview() }
self.addArrangedSubview(self.deviceSelector())
let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing + self.edgeInsets.top + self.edgeInsets.bottom
if self.frame.size.height != h {
self.setFrameSize(NSSize(width: self.bounds.width, height: h))
}
}
private func deviceSelector() -> NSView {
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width - Constants.Settings.margin*2, height: Constants.Settings.row))
let rowTitle: NSTextField = LabelField(
frame: NSRect(x: 0, y: (view.frame.height - 16)/2, width: view.frame.width - 52, height: 17),
localizedString("Battery to show")
)
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
rowTitle.textColor = .textColor
self.button = NSPopUpButton(frame: NSRect(x: view.frame.width - 140, y: -1, width: 140, height: 30))
self.button!.target = self
self.button?.action = #selector(self.handleSelection)
view.addSubview(rowTitle)
view.addSubview(self.button!)
return view
}
internal func setList(_ list: [BLEDevice]) {
var batteries: [String] = []
list.forEach { (d: BLEDevice) in
if d.batteryLevel.count == 1 {
batteries.append(d.name)
} else {
d.batteryLevel.forEach { (pair: KeyValue_t) in
batteries.append("\(d.name)@\(pair.key)")
}
}
}
DispatchQueue.main.async(execute: {
if self.button?.itemTitles.count != batteries.count {
self.button?.removeAllItems()
}
if batteries != self.button?.itemTitles {
self.button?.addItems(withTitles: batteries.map{ $0.replacingOccurrences(of: "@", with: " - ")})
if self.selectedBattery != "" {
self.button?.selectItem(withTitle: self.selectedBattery.replacingOccurrences(of: "@", with: " - "))
}
}
})
}
@objc private func handleSelection(_ sender: NSPopUpButton) {
guard let item = sender.selectedItem else { return }
self.selectedBattery = item.title.replacingOccurrences(of: " - ", with: "@")
Store.shared.set(key: "\(self.title)_battery", value: self.selectedBattery)
self.selectedBatteryHandler(self.selectedBattery)
}
}

View File

@@ -8,6 +8,12 @@
/* Begin PBXBuildFile section */
9A045EB72594F8D100ED58F2 /* Dashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A045EB62594F8D100ED58F2 /* Dashboard.swift */; };
9A11AAD6266FD77F000C1C05 /* Bluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */; };
9A11AAD7266FD77F000C1C05 /* Bluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9A11AAF4266FD7A7000C1C05 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A11AAF3266FD7A7000C1C05 /* main.swift */; };
9A11AB26266FD828000C1C05 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A11AB25266FD828000C1C05 /* config.plist */; };
9A11AB36266FD9F4000C1C05 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A11AB35266FD9F4000C1C05 /* readers.swift */; };
9A11AB67266FDB69000C1C05 /* Kit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; };
9A27D5352538A456001BB651 /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 9A27D5342538A456001BB651 /* Reachability */; };
9A2846FE2666A9CC00EC1F6D /* Kit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; };
9A2846FF2666A9CC00EC1F6D /* Kit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -75,6 +81,7 @@
9A81C7692449A43600825D92 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7672449A43600825D92 /* main.swift */; };
9A81C76A2449A43600825D92 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7682449A43600825D92 /* readers.swift */; };
9A8AE0A326921A2A00B13054 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8AE0A226921A2A00B13054 /* Server.swift */; };
9A8B923D2696445C00FD6D83 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8B923C2696445C00FD6D83 /* settings.swift */; };
9A8DE58E253DEFA9006A748F /* Fans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A8DE587253DEFA9006A748F /* Fans.framework */; };
9A8DE58F253DEFA9006A748F /* Fans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A8DE587253DEFA9006A748F /* Fans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9A8DE5E4253DF4E2006A748F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8DE5E3253DF4E2006A748F /* main.swift */; };
@@ -85,6 +92,7 @@
9A90E19624EAD35F00471E9A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90E19524EAD35F00471E9A /* main.swift */; };
9A90E19824EAD3B000471E9A /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A90E19724EAD3B000471E9A /* config.plist */; };
9A90E1A324EAD66600471E9A /* reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90E1A224EAD66600471E9A /* reader.swift */; };
9A94B81F26822DE0001F4F2B /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A94B81E26822DE0001F4F2B /* popup.swift */; };
9A953A1424B9D22D0038EF4B /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A953A1324B9D22D0038EF4B /* settings.swift */; };
9A97CED12537331B00742D8F /* CPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A97CECA2537331B00742D8F /* CPU.framework */; };
9A97CED22537331B00742D8F /* CPU.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A97CECA2537331B00742D8F /* CPU.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -129,6 +137,20 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
9A11AAD4266FD77F000C1C05 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9A1410ED229E721100D29793 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9A11AACE266FD77F000C1C05;
remoteInfo = Bluetooth;
};
9A11AB69266FDB69000C1C05 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9A1410ED229E721100D29793 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9A2846F62666A9CC00EC1F6D;
remoteInfo = Kit;
};
9A2846FC2666A9CC00EC1F6D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9A1410ED229E721100D29793 /* Project object */;
@@ -270,6 +292,7 @@
9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */,
9A81C75E2449A41400825D92 /* RAM.framework in Embed Frameworks */,
9AE29ADD249A50350071B02D /* Sensors.framework in Embed Frameworks */,
9A11AAD7266FD77F000C1C05 /* Bluetooth.framework in Embed Frameworks */,
9A2846FF2666A9CC00EC1F6D /* Kit.framework in Embed Frameworks */,
9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */,
9A3E17D4247A94AF00449CD1 /* Net.framework in Embed Frameworks */,
@@ -316,6 +339,11 @@
98BF5451254DF04C004E9DF5 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
9A00010025CFF9D6001D02B9 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
9A045EB62594F8D100ED58F2 /* Dashboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dashboard.swift; sourceTree = "<group>"; };
9A11AACF266FD77F000C1C05 /* Bluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9A11AAD2266FD77F000C1C05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9A11AAF3266FD7A7000C1C05 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
9A11AB25266FD828000C1C05 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = "<group>"; };
9A11AB35266FD9F4000C1C05 /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = "<group>"; };
9A1410F5229E721100D29793 /* Stats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stats.app; sourceTree = BUILT_PRODUCTS_DIR; };
9A141101229E721200D29793 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9A27D4A925389EFD001BB651 /* Stats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Stats.entitlements; sourceTree = "<group>"; };
@@ -378,6 +406,7 @@
9A81C7672449A43600825D92 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
9A81C7682449A43600825D92 /* readers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = "<group>"; };
9A8AE0A226921A2A00B13054 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
9A8B923C2696445C00FD6D83 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
9A8DE587253DEFA9006A748F /* Fans.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Fans.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9A8DE58A253DEFA9006A748F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9A8DE5E3253DF4E2006A748F /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
@@ -388,6 +417,7 @@
9A90E19524EAD35F00471E9A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
9A90E19724EAD3B000471E9A /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = "<group>"; };
9A90E1A224EAD66600471E9A /* reader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = reader.swift; sourceTree = "<group>"; };
9A94B81E26822DE0001F4F2B /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
9A953A1324B9D22D0038EF4B /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
9A97CE2A25371B2300742D8F /* IntelPowerGadget.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntelPowerGadget.framework; path = ../../../Library/Frameworks/IntelPowerGadget.framework; sourceTree = "<group>"; };
9A97CECA2537331B00742D8F /* CPU.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CPU.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -441,6 +471,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
9A11AACC266FD77F000C1C05 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9A11AB67266FDB69000C1C05 /* Kit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9A1410F2229E721100D29793 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -448,6 +486,7 @@
9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */,
9AE29ADC249A50350071B02D /* Sensors.framework in Frameworks */,
9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */,
9A11AAD6266FD77F000C1C05 /* Bluetooth.framework in Frameworks */,
9A2846FE2666A9CC00EC1F6D /* Kit.framework in Frameworks */,
9A81C75D2449A41400825D92 /* RAM.framework in Frameworks */,
9A8DE58E253DEFA9006A748F /* Fans.framework in Frameworks */,
@@ -548,6 +587,19 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9A11AAD0266FD77F000C1C05 /* Bluetooth */ = {
isa = PBXGroup;
children = (
9A11AAF3266FD7A7000C1C05 /* main.swift */,
9A11AB35266FD9F4000C1C05 /* readers.swift */,
9A94B81E26822DE0001F4F2B /* popup.swift */,
9A8B923C2696445C00FD6D83 /* settings.swift */,
9A11AAD2266FD77F000C1C05 /* Info.plist */,
9A11AB25266FD828000C1C05 /* config.plist */,
);
path = Bluetooth;
sourceTree = "<group>";
};
9A1410EC229E721100D29793 = {
isa = PBXGroup;
children = (
@@ -576,6 +628,7 @@
9A8DE587253DEFA9006A748F /* Fans.framework */,
9ADE6FD8265D032100D2FBA8 /* smc */,
9A2846F72666A9CC00EC1F6D /* Kit.framework */,
9A11AACF266FD77F000C1C05 /* Bluetooth.framework */,
);
name = Products;
sourceTree = "<group>";
@@ -787,6 +840,7 @@
9A8DE588253DEFA9006A748F /* Fans */,
9A3E17CD247A94AF00449CD1 /* Net */,
9ABFF8F7248BEBCB00C9041A /* Battery */,
9A11AAD0266FD77F000C1C05 /* Bluetooth */,
);
path = Modules;
sourceTree = "<group>";
@@ -845,6 +899,13 @@
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
9A11AACA266FD77F000C1C05 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9A2846F22666A9CC00EC1F6D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
@@ -911,6 +972,25 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
9A11AACE266FD77F000C1C05 /* Bluetooth */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9A11AADA266FD77F000C1C05 /* Build configuration list for PBXNativeTarget "Bluetooth" */;
buildPhases = (
9A11AACA266FD77F000C1C05 /* Headers */,
9A11AACB266FD77F000C1C05 /* Sources */,
9A11AACC266FD77F000C1C05 /* Frameworks */,
9A11AACD266FD77F000C1C05 /* Resources */,
);
buildRules = (
);
dependencies = (
9A11AB6A266FDB69000C1C05 /* PBXTargetDependency */,
);
name = Bluetooth;
productName = Bluetooth;
productReference = 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */;
productType = "com.apple.product-type.framework";
};
9A1410F4229E721100D29793 /* Stats */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9A141105229E721200D29793 /* Build configuration list for PBXNativeTarget "Stats" */;
@@ -936,6 +1016,7 @@
9A97CED02537331B00742D8F /* PBXTargetDependency */,
9A8DE58D253DEFA9006A748F /* PBXTargetDependency */,
9A2846FD2666A9CC00EC1F6D /* PBXTargetDependency */,
9A11AAD5266FD77F000C1C05 /* PBXTargetDependency */,
);
name = Stats;
packageProductDependencies = (
@@ -1166,6 +1247,10 @@
LastUpgradeCheck = 1250;
ORGANIZATIONNAME = "Serhiy Mytrovtsiy";
TargetAttributes = {
9A11AACE266FD77F000C1C05 = {
CreatedOnToolsVersion = 12.4;
LastSwiftMigration = 1240;
};
9A1410F4229E721100D29793 = {
CreatedOnToolsVersion = 10.2.1;
LastSwiftMigration = 1030;
@@ -1262,6 +1347,7 @@
targets = (
9A1410F4229E721100D29793 /* Stats */,
9A343526243E26A0006B19F9 /* LaunchAtLogin */,
9ADE6FD7265D032100D2FBA8 /* SMC */,
9A2846F62666A9CC00EC1F6D /* Kit */,
9A97CEC92537331B00742D8F /* CPU */,
9A90E18824EAD2BB00471E9A /* GPU */,
@@ -1271,12 +1357,20 @@
9ABFF8F5248BEBCB00C9041A /* Battery */,
9AE29AD4249A50350071B02D /* Sensors */,
9A8DE586253DEFA9006A748F /* Fans */,
9ADE6FD7265D032100D2FBA8 /* SMC */,
9A11AACE266FD77F000C1C05 /* Bluetooth */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
9A11AACD266FD77F000C1C05 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9A11AB26266FD828000C1C05 /* config.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9A1410F3229E721100D29793 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1390,6 +1484,17 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
9A11AACB266FD77F000C1C05 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9A11AB36266FD9F4000C1C05 /* readers.swift in Sources */,
9A94B81F26822DE0001F4F2B /* popup.swift in Sources */,
9A8B923D2696445C00FD6D83 /* settings.swift in Sources */,
9A11AAF4266FD7A7000C1C05 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9A1410F1229E721100D29793 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1551,6 +1656,16 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
9A11AAD5266FD77F000C1C05 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9A11AACE266FD77F000C1C05 /* Bluetooth */;
targetProxy = 9A11AAD4266FD77F000C1C05 /* PBXContainerItemProxy */;
};
9A11AB6A266FDB69000C1C05 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9A2846F62666A9CC00EC1F6D /* Kit */;
targetProxy = 9A11AB69266FDB69000C1C05 /* PBXContainerItemProxy */;
};
9A2846FD2666A9CC00EC1F6D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9A2846F62666A9CC00EC1F6D /* Kit */;
@@ -1671,6 +1786,67 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
9A11AAD8266FD77F000C1C05 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = RP2S87B72W;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Modules/Bluetooth/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Bluetooth;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
9A11AAD9266FD77F000C1C05 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = RP2S87B72W;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Modules/Bluetooth/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Bluetooth;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
9A141103229E721200D29793 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -2528,6 +2704,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
9A11AADA266FD77F000C1C05 /* Build configuration list for PBXNativeTarget "Bluetooth" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9A11AAD8266FD77F000C1C05 /* Debug */,
9A11AAD9266FD77F000C1C05 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9A1410F0229E721100D29793 /* Build configuration list for PBXProject "Stats" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -18,6 +18,7 @@ import Battery
import Sensors
import GPU
import Fans
import Bluetooth
let updater = macAppUpdater(user: "exelban", repo: "stats")
var modules: [Module] = [
@@ -28,7 +29,8 @@ var modules: [Module] = [
Sensors(),
Fans(),
Network(),
Battery()
Battery(),
Bluetooth()
]
class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {