@@ -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