Skip to content

Commit ede550c

Browse files
committed
Fix wake recovery and status sync for 1.13.1
1 parent cb5668f commit ede550c

9 files changed

Lines changed: 198 additions & 58 deletions

File tree

BLEUnlock.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@
561561
"$(inherited)",
562562
"@executable_path/../Frameworks",
563563
);
564-
MARKETING_VERSION = 1.13.0;
564+
MARKETING_VERSION = 1.13.1;
565565
PRODUCT_BUNDLE_IDENTIFIER = jp.sone.BLEUnlock;
566566
PRODUCT_NAME = "$(TARGET_NAME)";
567567
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -592,7 +592,7 @@
592592
"$(inherited)",
593593
"@executable_path/../Frameworks",
594594
);
595-
MARKETING_VERSION = 1.13.0;
595+
MARKETING_VERSION = 1.13.1;
596596
PRODUCT_BUNDLE_IDENTIFIER = jp.sone.BLEUnlock;
597597
PRODUCT_NAME = "$(TARGET_NAME)";
598598
PROVISIONING_PROFILE_SPECIFIER = "";

BLEUnlock/AboutBox.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ class AboutBox: NSWindowController, NSWindowDelegate {
66
@IBOutlet weak var versionLabel: NSTextField!
77

88
@IBAction func visitHomepage(_ sender: Any) {
9-
NSWorkspace.shared.open(URL(string: "https://github.com/ts1/BLEUnlock#readme")!)
9+
NSWorkspace.shared.open(URL(string: "https://github.com/Skyearn/BLEUnlock#readme")!)
1010
}
1111

1212
@IBAction func checkReleases(_ sender: Any) {
13-
NSWorkspace.shared.open(URL(string: "https://github.com/ts1/BLEUnlock/releases")!)
13+
NSWorkspace.shared.open(URL(string: "https://github.com/Skyearn/BLEUnlock/releases")!)
1414
}
1515
convenience init() {
1616
self.init(windowNibName: "AboutBox")

BLEUnlock/AppDelegate.swift

Lines changed: 117 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
120120
var userNotificationID: String?
121121
var nowPlayingWasPlaying = false
122122
var aboutBox: AboutBox? = nil
123-
var wakeTimer: Timer?
124123
var manualLock = false
125124
var unlockedAt = 0.0
126125
var inScreensaver = false
127126
var lastRSSI: Int? = nil
128127
var deviceMenuIsOpen = false
129128
var deviceMenuNeedsRefresh = false
129+
var systemWakeTimer: Timer?
130130
var wakeUnlockTimer: Timer?
131+
var postUnlockRetryTimer: Timer?
132+
var permissionRecoveryTimer: Timer?
131133
var lastWakeAt = 0.0
134+
var lastDisplayWakeRequestAt = 0.0
135+
let minimumWakeRequestInterval = 15.0
132136
let wakeUnlockRetryDelay = 0.5
133137
let wakeUnlockMaxRetries = 8
134138

@@ -210,7 +214,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
210214
} else {
211215
desc = device.description
212216
}
213-
return String(format: "%@ (%ddBm)", desc, displayedRSSI(for: device))
217+
if let rssi = displayedRSSI(for: device.uuid) {
218+
return menuItemTitle(title: desc, rssi: rssi)
219+
}
220+
return menuItemTitleNotDetected(title: desc)
214221
}
215222

216223
func menuItemTitleNotDetected(title: String) -> String {
@@ -221,11 +228,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
221228
menuItemTitleNotDetected(title: device.description)
222229
}
223230

224-
func displayedRSSI(for device: Device) -> Int {
225-
if let monitoredRSSI = ble.monitoredStates[device.uuid]?.lastRSSI {
231+
func menuItemTitle(title: String, rssi: Int) -> String {
232+
String(format: "%@ (%ddBm)", title, rssi)
233+
}
234+
235+
func displayedRSSI(for uuid: UUID) -> Int? {
236+
if let monitoredRSSI = ble.monitoredStates[uuid]?.lastRSSI {
226237
return monitoredRSSI
227238
}
228-
return device.rssi
239+
if let device = ble.devices[uuid], device.isVisible {
240+
return device.rssi
241+
}
242+
return nil
229243
}
230244

231245
func configuredDeviceCheckbox(uuid: UUID, title: String) -> NSButton {
@@ -303,8 +317,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
303317
func removeDevice(device: Device) {
304318
if ble.isMonitoring(uuid: device.uuid) {
305319
if let checkbox = deviceCheckboxDict[device.uuid] {
306-
updateDeviceCheckbox(checkbox, uuid: device.uuid, title: menuItemTitleNotDetected(device: device))
320+
let title: String
321+
if displayedRSSI(for: device.uuid) != nil {
322+
title = menuItemTitle(device: device)
323+
} else {
324+
title = menuItemTitleNotDetected(device: device)
325+
}
326+
updateDeviceCheckbox(checkbox, uuid: device.uuid, title: title)
307327
}
328+
updateMonitorStatusItems()
308329
return
309330
}
310331
if let menuItem = deviceDict[device.uuid] {
@@ -343,7 +364,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
343364
}
344365
} else if let checkbox = deviceCheckboxDict[uuid] {
345366
if ble.isMonitoring(uuid: uuid) {
346-
updateDeviceCheckbox(checkbox, uuid: uuid, title: menuItemTitleNotDetected(title: monitoredDeviceTitle(uuid: uuid)))
367+
let title: String
368+
if let rssi = displayedRSSI(for: uuid) {
369+
title = menuItemTitle(title: monitoredDeviceTitle(uuid: uuid), rssi: rssi)
370+
} else {
371+
title = menuItemTitleNotDetected(title: monitoredDeviceTitle(uuid: uuid))
372+
}
373+
updateDeviceCheckbox(checkbox, uuid: uuid, title: title)
347374
} else {
348375
staleUUIDs.append(uuid)
349376
}
@@ -371,8 +398,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
371398

372399
func monitoredDeviceStatusTitle(uuid: UUID) -> String {
373400
let title = monitoredDeviceTitle(uuid: uuid)
374-
if let state = ble.monitoredStates[uuid], let rssi = state.lastRSSI {
375-
let activeSuffix = state.active ? t("monitor_status_active_suffix") : ""
401+
if let rssi = displayedRSSI(for: uuid) {
402+
let state = ble.monitoredStates[uuid]
403+
let activeSuffix = state?.active == true ? t("monitor_status_active_suffix") : ""
376404
return String(format: t("monitor_status_device_detected"), title, rssi, activeSuffix)
377405
}
378406
return String(format: t("monitor_status_device_not_detected"), title, t("not_detected"))
@@ -386,13 +414,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
386414
return t("device_not_set")
387415
}
388416

389-
if let bestUUID = orderedUUIDs.max(by: { (ble.monitoredStates[$0]?.lastRSSI ?? Int.min) < (ble.monitoredStates[$1]?.lastRSSI ?? Int.min) }),
390-
let bestState = ble.monitoredStates[bestUUID],
391-
let bestRSSI = bestState.lastRSSI
392-
{
393-
let detected = ble.monitoredUUIDs.compactMap { ble.monitoredStates[$0]?.lastRSSI }.count
394-
let activeSuffix = bestState.active ? t("monitor_status_active_suffix") : ""
395-
return String(format: t("monitor_status_strongest_detected"), detected, orderedUUIDs.count, bestRSSI, activeSuffix)
417+
let visibleDevices = orderedUUIDs.compactMap { uuid -> (UUID, Int)? in
418+
guard let rssi = displayedRSSI(for: uuid) else { return nil }
419+
return (uuid, rssi)
420+
}
421+
422+
if let strongest = visibleDevices.max(by: { $0.1 < $1.1 }) {
423+
let detected = visibleDevices.count
424+
return String(format: t("monitor_status_strongest_detected"), detected, orderedUUIDs.count, strongest.1)
396425
}
397426
return String(format: t("monitor_status_not_detected"), 0, orderedUUIDs.count)
398427
}
@@ -556,12 +585,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
556585
userNotification = nil
557586
}
558587
if displaySleep && !systemSleep && prefs.bool(forKey: "wakeOnProximity") {
559-
print("Waking display")
560-
wakeDisplay()
561-
wakeTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
562-
print("Retrying waking display")
588+
let now = Date().timeIntervalSince1970
589+
if now - lastDisplayWakeRequestAt >= minimumWakeRequestInterval {
590+
print("Waking display")
591+
lastDisplayWakeRequestAt = now
563592
wakeDisplay()
564-
})
593+
} else {
594+
print("Skipping wake display retry while display wake is still pending")
595+
}
565596
}
566597
tryUnlockScreen()
567598
}
@@ -657,14 +688,25 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
657688

658689
// On wake, the first attempt can land before the login UI is fully ready.
659690
if (recentlyWoke || self.inScreensaver) && retryCount < self.wakeUnlockMaxRetries {
660-
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false, block: { _ in
691+
self.postUnlockRetryTimer?.invalidate()
692+
self.postUnlockRetryTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false, block: { _ in
693+
self.postUnlockRetryTimer = nil
661694
guard self.isScreenLocked() else { return }
662695
self.scheduleWakeUnlock(after: self.wakeUnlockRetryDelay, retryCount: retryCount + 1)
663696
})
664697
}
665698
})
666699
}
667700

701+
func cancelWakeRelatedTimers() {
702+
systemWakeTimer?.invalidate()
703+
systemWakeTimer = nil
704+
wakeUnlockTimer?.invalidate()
705+
wakeUnlockTimer = nil
706+
postUnlockRetryTimer?.invalidate()
707+
postUnlockRetryTimer = nil
708+
}
709+
668710
func scheduleWakeUnlock(after delay: TimeInterval, retryCount: Int = 0) {
669711
wakeUnlockTimer?.invalidate()
670712
wakeUnlockTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { _ in
@@ -678,21 +720,21 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
678720
//unlockedAt = Date().timeIntervalSince1970
679721
displaySleep = false
680722
lastWakeAt = Date().timeIntervalSince1970
681-
wakeTimer?.invalidate()
682-
wakeTimer = nil
723+
lastDisplayWakeRequestAt = 0
683724
scheduleWakeUnlock(after: 0.75)
684725
}
685726

686727
@objc func onDisplaySleep() {
687728
print("display sleep")
688729
displaySleep = true
689-
wakeUnlockTimer?.invalidate()
690-
wakeUnlockTimer = nil
730+
cancelWakeRelatedTimers()
691731
}
692732

693733
@objc func onSystemWake() {
694734
print("system wake")
695-
Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
735+
systemWakeTimer?.invalidate()
736+
systemWakeTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
737+
self.systemWakeTimer = nil
696738
print("delayed system wake job")
697739
NSApp.setActivationPolicy(.accessory) // Hide Dock icon again
698740
self.systemSleep = false
@@ -704,17 +746,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
704746
@objc func onSystemSleep() {
705747
print("system sleep")
706748
systemSleep = true
707-
wakeUnlockTimer?.invalidate()
708-
wakeUnlockTimer = nil
749+
cancelWakeRelatedTimers()
709750
// Set activation policy to regular, so the CBCentralManager can scan for peripherals
710751
// when the Bluetooth will become on again.
711752
// This enables Dock icon but the screen is off anyway.
712753
NSApp.setActivationPolicy(.regular)
713754
}
714755

715756
@objc func onUnlock() {
716-
wakeUnlockTimer?.invalidate()
717-
wakeUnlockTimer = nil
757+
cancelWakeRelatedTimers()
718758
Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: { _ in
719759
print("onUnlock")
720760
if Date().timeIntervalSince1970 >= self.unlockedAt + 10 {
@@ -1073,16 +1113,52 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
10731113
statusItem.menu = mainMenu
10741114
}
10751115

1076-
func checkAccessibility() {
1077-
let key = kAXTrustedCheckOptionPrompt.takeRetainedValue() as String
1078-
if (!AXIsProcessTrustedWithOptions([key: true] as CFDictionary)) {
1116+
@discardableResult
1117+
func checkAccessibility(showPrompt: Bool = true) -> Bool {
1118+
let trusted: Bool
1119+
if showPrompt {
1120+
let key = kAXTrustedCheckOptionPrompt.takeRetainedValue() as String
1121+
trusted = AXIsProcessTrustedWithOptions([key: true] as CFDictionary)
1122+
} else {
1123+
trusted = AXIsProcessTrusted()
1124+
}
1125+
if !trusted && showPrompt {
10791126
// Sometimes Prompt option above doesn't work.
10801127
// Actually trying to send key may open that dialog.
10811128
let src = CGEventSource(stateID: .hidSystemState)
10821129
// "Fn" key down and up
10831130
CGEvent(keyboardEventSource: src, virtualKey: 63, keyDown: true)?.post(tap: .cghidEventTap)
10841131
CGEvent(keyboardEventSource: src, virtualKey: 63, keyDown: false)?.post(tap: .cghidEventTap)
10851132
}
1133+
return trusted
1134+
}
1135+
1136+
func requiresAccessibilityPermission() -> Bool {
1137+
ble.unlockRSSI != ble.UNLOCK_DISABLED && !prefs.bool(forKey: "wakeWithoutUnlocking")
1138+
}
1139+
1140+
func refreshPermissionRecovery() {
1141+
let accessibilityTrusted = !requiresAccessibilityPermission() || checkAccessibility(showPrompt: false)
1142+
ble.recoverAfterPermissionChangeIfNeeded()
1143+
guard !accessibilityTrusted || ble.needsPermissionRecovery else {
1144+
permissionRecoveryTimer?.invalidate()
1145+
permissionRecoveryTimer = nil
1146+
return
1147+
}
1148+
guard permissionRecoveryTimer == nil else { return }
1149+
permissionRecoveryTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { _ in
1150+
self.refreshPermissionRecovery()
1151+
})
1152+
if let timer = permissionRecoveryTimer {
1153+
RunLoop.main.add(timer, forMode: .common)
1154+
}
1155+
}
1156+
1157+
func startPermissionRecovery(promptAccessibility: Bool) {
1158+
if requiresAccessibilityPermission() {
1159+
_ = checkAccessibility(showPrompt: promptAccessibility)
1160+
}
1161+
refreshPermissionRecovery()
10861162
}
10871163

10881164
func launcherBundleIdentifier() -> String {
@@ -1218,15 +1294,21 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemVa
12181294
if prefs.bool(forKey: "launchAtLogin") {
12191295
_ = setLaunchAtLogin(true, showErrors: false)
12201296
}
1221-
checkAccessibility()
1297+
startPermissionRecovery(promptAccessibility: true)
12221298
checkUpdate()
12231299

12241300
// Hide dock icon.
12251301
// This is required because we can't have LSUIElement set to true in Info.plist,
12261302
// otherwise CBCentralManager.scanForPeripherals won't work.
12271303
NSApp.setActivationPolicy(.accessory)
12281304
}
1305+
1306+
func applicationDidBecomeActive(_ notification: Notification) {
1307+
refreshPermissionRecovery()
1308+
}
12291309

12301310
func applicationWillTerminate(_ aNotification: Notification) {
1311+
permissionRecoveryTimer?.invalidate()
1312+
permissionRecoveryTimer = nil
12311313
}
12321314
}

0 commit comments

Comments
 (0)