diff --git a/Sources/blts_tracker/BLTSTrackerApp.swift b/Sources/blts_tracker/BLTSTrackerApp.swift index a9b1b33..c9199aa 100644 --- a/Sources/blts_tracker/BLTSTrackerApp.swift +++ b/Sources/blts_tracker/BLTSTrackerApp.swift @@ -25,7 +25,7 @@ struct BLTSTrackerApp: App { Label(" \(p)%", systemImage: "headphones") .labelStyle(.titleAndIcon) } else if isConn { - Label(" 100%", systemImage: "headphones") + Label(" ...", systemImage: "headphones") .labelStyle(.titleAndIcon) } else { Label("", systemImage: "headphones") @@ -36,9 +36,15 @@ struct BLTSTrackerApp: App { @ViewBuilder private var menuContent: some View { if let dev = tracker.activeHeadphone, dev.isConnected { - let percentStr = dev.primaryBatteryPercent != nil ? "\(dev.primaryBatteryPercent!)%" : "100%" + let percentStr = dev.primaryBatteryPercent != nil ? "\(dev.primaryBatteryPercent!)%" : "Определение..." Text("🎧 \(dev.name): \(percentStr)") .font(.headline) + + if let l = dev.leftBattery, let r = dev.rightBattery { + Text("Левый: \(l)% | Правый: \(r)%") + .font(.caption) + .foregroundColor(.secondary) + } } else { Text("🎧 Наушники не подключены") .foregroundColor(.secondary) @@ -46,7 +52,7 @@ struct BLTSTrackerApp: App { Divider() - Button("Обновить статус") { + Button("Обновить заряд") { tracker.refreshNow() } .keyboardShortcut("r") diff --git a/Sources/blts_tracker/Services/BluetoothTracker.swift b/Sources/blts_tracker/Services/BluetoothTracker.swift index 67659da..c5da867 100644 --- a/Sources/blts_tracker/Services/BluetoothTracker.swift +++ b/Sources/blts_tracker/Services/BluetoothTracker.swift @@ -11,8 +11,6 @@ public final class BluetoothTracker: ObservableObject { @Published public var devices: [BluetoothDeviceModel] = [] private var timer: Timer? - private var lastKnownBattery: [String: Int] = [:] - private typealias IntBatteryGetter = @convention(c) (AnyObject, Selector) -> Int private typealias BoolGetter = @convention(c) (AnyObject, Selector) -> Bool @@ -28,6 +26,7 @@ public final class BluetoothTracker: ObservableObject { public func startTracking() { stopTracking() updateDeviceList() + // Fast 1.5s real-time poll timer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { [weak self] _ in self?.updateDeviceList() } @@ -61,8 +60,7 @@ public final class BluetoothTracker: ObservableObject { DispatchQueue.main.async { [weak self] in self?.updateDeviceList() } - // Retries during the first 3 seconds to catch the telemetry packet as soon as handshake finishes - for delay in [0.4, 0.8, 1.2, 1.8, 2.5] { + for delay in [0.3, 0.8, 1.5, 2.5] { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in self?.updateDeviceList() } @@ -79,7 +77,11 @@ public final class BluetoothTracker: ObservableObject { let name = device.nameOrAddress ?? "Unknown Device" let address = device.addressString ?? UUID().uuidString - let isAudioOutput = activeAudioName != nil && (name.caseInsensitiveCompare(activeAudioName!) == .orderedSame || activeAudioName!.localizedCaseInsensitiveContains(name) || name.localizedCaseInsensitiveContains(activeAudioName!)) + let isAudioOutput = activeAudioName != nil && ( + name.caseInsensitiveCompare(activeAudioName!) == .orderedSame || + activeAudioName!.localizedCaseInsensitiveContains(name) || + name.localizedCaseInsensitiveContains(activeAudioName!) + ) let isConnected = device.isConnected() || isAudioOutput let majorClass = UInt32(device.deviceClassMajor) @@ -116,7 +118,7 @@ public final class BluetoothTracker: ObservableObject { // 2. IOKit Registry enrich enrichFromIORegistry(devices: &updatedList) - // 3. Sort + // 3. Sort: Connected audio first, then connected other, then paired audio updatedList.sort { d1, d2 in if d1.isConnected != d2.isConnected { return d1.isConnected && !d2.isConnected @@ -206,14 +208,13 @@ public final class BluetoothTracker: ObservableObject { let imp = device.method(for: sel) let fn = unsafeBitCast(imp, to: IntBatteryGetter.self) let val = fn(device, sel) + // Valid battery percentages are 1 to 100 if val > 0 && val <= 100 { return val } return nil } - let addr = model.address - if isMulti { model.leftBattery = getBatteryValue(selectorName: "batteryPercentLeft") model.rightBattery = getBatteryValue(selectorName: "batteryPercentRight") @@ -227,11 +228,7 @@ public final class BluetoothTracker: ObservableObject { ?? getBatteryValue(selectorName: "batteryPercentCombined") if let single = single { - lastKnownBattery[addr] = single model.singleBattery = single - } else if let cached = lastKnownBattery[addr] { - // Keep smooth previous valid level during connection handshake - model.singleBattery = cached } } }