mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: initialized Bluetooth module (#277)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
24
Modules/Bluetooth/Info.plist
Normal file
24
Modules/Bluetooth/Info.plist
Normal 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>
|
||||
49
Modules/Bluetooth/config.plist
Normal file
49
Modules/Bluetooth/config.plist
Normal 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>
|
||||
116
Modules/Bluetooth/main.swift
Normal file
116
Modules/Bluetooth/main.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Modules/Bluetooth/popup.swift
Normal file
111
Modules/Bluetooth/popup.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
179
Modules/Bluetooth/readers.swift
Normal file
179
Modules/Bluetooth/readers.swift
Normal 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)")]
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Modules/Bluetooth/settings.swift
Normal file
112
Modules/Bluetooth/settings.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user