diff --git a/Kit/extensions.swift b/Kit/extensions.swift index e9fdab40..e28bdb1d 100644 --- a/Kit/extensions.swift +++ b/Kit/extensions.swift @@ -15,6 +15,8 @@ import Carbon extension String: @retroactive LocalizedError { public var errorDescription: String? { return self } + public var nilIfEmpty: String? { self.isEmpty ? nil : self } + public var digits: String { return components(separatedBy: CharacterSet.decimalDigits.inverted).joined() } diff --git a/Modules/Bluetooth/popup.swift b/Modules/Bluetooth/popup.swift index b043a575..8771abce 100644 --- a/Modules/Bluetooth/popup.swift +++ b/Modules/Bluetooth/popup.swift @@ -113,6 +113,11 @@ internal class BLEView: NSStackView { batteryLevel.forEach { (pair: KeyValue_t) in if let view = self.levels.first(where: { $0.identifier?.rawValue == pair.key }) { view.stringValue = "\(pair.value)%" + if let additional = pair.additional as? String { + view.toolTip = "\(pair.key) - \(additional)" + } else { + view.toolTip = pair.key + } } else { self.addLevel(pair) } @@ -124,7 +129,11 @@ internal class BLEView: NSStackView { valueView.identifier = NSUserInterfaceItemIdentifier(rawValue: pair.key) valueView.font = NSFont.systemFont(ofSize: 12, weight: .regular) valueView.stringValue = "\(pair.value)%" - valueView.toolTip = pair.key + if let additional = pair.additional as? String { + valueView.toolTip = "\(pair.key) - \(additional)" + } else { + valueView.toolTip = pair.key + } self.addArrangedSubview(valueView) self.levels.append(valueView) } diff --git a/Modules/Bluetooth/readers.swift b/Modules/Bluetooth/readers.swift index e324d769..663b1d51 100644 --- a/Modules/Bluetooth/readers.swift +++ b/Modules/Bluetooth/readers.swift @@ -49,6 +49,7 @@ internal class DevicesReader: Reader<[BLEDevice]>, CBCentralManagerDelegate, CBP let hid = self.HIDDevices() let SPB = self.profilerDevices() var list = self.cacheDevices() + let pmsetLevels = self.pmsetAccessoryLevels() hid.forEach { v in if !list.contains(where: {$0.address == v.address}) { @@ -142,6 +143,43 @@ internal class DevicesReader: Reader<[BLEDevice]>, CBCentralManagerDelegate, CBP self.devices = self.devices.filter({ !SPB.1.contains($0.address) }) } + pmsetLevels.forEach { p in + let pmsetName = (p.name ?? "") + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + + if !pmsetName.isEmpty, + let idx = self.devices.firstIndex(where: { + $0.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == pmsetName + }) { + if !p.batteryLevel.isEmpty { + self.devices[idx].batteryLevel = p.batteryLevel + } + return + } + + if !p.address.isEmpty, + let idx = self.devices.firstIndex(where: { + !$0.address.isEmpty && + $0.address.caseInsensitiveCompare(p.address) == .orderedSame + }) { + if !p.batteryLevel.isEmpty { + self.devices[idx].batteryLevel = p.batteryLevel + } + return + } + + self.devices.append(BLEDevice( + address: p.address, + name: p.name ?? "", + uuid: p.uuid, + RSSI: 100, + batteryLevel: p.batteryLevel, + isConnected: true, + isPaired: false + )) + } + self.callback(self.devices.filter({ $0.RSSI != nil })) } @@ -330,4 +368,125 @@ internal class DevicesReader: Reader<[BLEDevice]>, CBCentralManagerDelegate, CBP self.bleLevels[peripheral.identifier] = KeyValue_t(key: "battery", value: "\(batteryLevel)") } } + + // MARK: - PMSET data + private func pmsetAccessoryLevels() -> [bleDevice] { + guard let res = process(path: "/usr/bin/pmset", arguments: ["-g", "accps"]) else { return [] } + + struct Entry { + let originalName: String + let normalizedName: String + let percent: Int + let id: String + let isCase: Bool + let state: String? // "charging" | "discharging" + } + + var grouped: [String: [Entry]] = [:] + var displayNameForGroup: [String: String] = [:] + + for raw in res.components(separatedBy: .newlines) { + let line = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard line.hasPrefix("-"), let tabIdx = line.firstIndex(of: "\t") else { continue } + + var namePart = String(line[line.index(after: line.startIndex)..= 1 { + kv.append(KeyValue_t(key: "first", value: "\(buds[0].percent)", additional: buds[0].state)) + } + if buds.count >= 2 { + kv.append(KeyValue_t(key: "second", value: "\(buds[1].percent)", additional: buds[1].state)) + } + + if kv.isEmpty, let first = entries.first { + kv = [KeyValue_t(key: "battery", value: "\(first.percent)", additional: first.state)] + } + } + + let mergedAddress = entries.map { $0.id }.sorted().joined(separator: "x") + + out.append(bleDevice( + name: displayName, + address: mergedAddress, + uuid: nil, + batteryLevel: kv + )) + } + + return out + } }