@@ -48,11 +48,14 @@ public final class IOKeyEventMonitor {
4848 notificationCenter = CFNotificationCenterGetDistributedCenter ( )
4949 let deviceMatch : CFMutableDictionary = [ kIOHIDDeviceUsageKey: usage, kIOHIDDeviceUsagePageKey: usagePage] as NSMutableDictionary
5050 IOHIDManagerSetDeviceMatching ( hidManager, deviceMatch)
51+
5152 loadMappings ( )
5253 }
5354
5455 deinit {
5556 self . saveMappings ( )
57+ stopObservingSettingsChanges ( )
58+
5659 let context = UnsafeMutableRawPointer ( Unmanaged . passUnretained ( self ) . toOpaque ( ) )
5760 IOHIDManagerRegisterInputValueCallback ( hidManager, Optional . none, context)
5861 CFNotificationCenterRemoveObserver ( notificationCenter, context, CFNotificationName ( kTISNotifySelectedKeyboardInputSourceChanged) , nil )
@@ -66,6 +69,8 @@ public final class IOKeyEventMonitor {
6669
6770 IOHIDManagerScheduleWithRunLoop ( hidManager, CFRunLoopGetMain ( ) , CFRunLoopMode . defaultMode!. rawValue)
6871 IOHIDManagerOpen ( hidManager, IOOptionBits ( kIOHIDOptionsTypeNone) )
72+
73+ startObservingSettingsChanges ( )
6974 }
7075
7176 private func observeIputSourceChangedNotification( context: UnsafeMutableRawPointer ) {
@@ -169,7 +174,12 @@ public extension IOKeyEventMonitor {
169174 }
170175
171176 func onKeyboardEvent( keyboard: String , conformsToKeyboard: Bool ? = nil ) {
172- guard lastActiveKeyboard != keyboard else { return }
177+ guard lastActiveKeyboard != keyboard else {
178+ if verbosity >= TRACE {
179+ print ( " change: ignoring event from keyboard \( keyboard) because active device hasn't changed " )
180+ }
181+ return
182+ }
173183
174184 if verbosity >= DEBUG {
175185 print ( " change: keyboard changed from \( lastActiveKeyboard ?? " nil " ) to \( keyboard) " )
@@ -201,52 +211,6 @@ public extension IOKeyEventMonitor {
201211 }
202212}
203213
204- // MARK: - Persistence
205-
206- extension IOKeyEventMonitor {
207- func loadMappings( ) {
208- let selectableIsProperties = [
209- kTISPropertyInputSourceIsEnableCapable: true ,
210- kTISPropertyInputSourceCategory: kTISCategoryKeyboardInputSource ?? " " as CFString ,
211- ] as CFDictionary
212- let inputSources = TISCreateInputSourceList ( selectableIsProperties, false ) . takeUnretainedValue ( ) as! [ TISInputSource ]
213-
214- let inputSourcesById = inputSources. reduce ( [ String: TISInputSource] ( ) ) {
215- dict, inputSource -> [ String : TISInputSource ] in
216- var dict = dict
217- if let id = unmanagedStringToString ( TISGetInputSourceProperty ( inputSource, kTISPropertyInputSourceID) ) {
218- dict [ id] = inputSource
219- }
220- return dict
221- }
222-
223- if let mappings = defaults. dictionary ( forKey: MAPPINGS_DEFAULTS_KEY) {
224- for (keyboardId, inputSourceId) in mappings {
225- kb2is [ keyboardId] = inputSourcesById [ String ( describing: inputSourceId) ]
226- }
227- }
228-
229- if let enabledMappings = defaults. dictionary ( forKey: MAPPING_ENABLED_KEY) as? [ String : Bool ] {
230- deviceEnabled = enabledMappings
231- }
232- }
233-
234- func saveMappings( ) {
235- let mappings = kb2is. mapValues ( is2Id)
236- defaults. set ( mappings, forKey: MAPPINGS_DEFAULTS_KEY)
237- defaults. set ( deviceEnabled, forKey: MAPPING_ENABLED_KEY)
238- }
239-
240- public func clearAllSettings( ) {
241- kb2is. removeAll ( )
242- deviceEnabled. removeAll ( )
243- lastActiveKeyboard = nil
244- defaults. removeObject ( forKey: MAPPINGS_DEFAULTS_KEY)
245- defaults. removeObject ( forKey: MAPPING_ENABLED_KEY)
246- defaults. synchronize ( )
247- }
248- }
249-
250214// MARK: - Device Management
251215
252216public extension IOKeyEventMonitor {
@@ -305,6 +269,126 @@ public extension IOKeyEventMonitor {
305269 }
306270}
307271
272+ // MARK: - Persistence
273+
274+ extension IOKeyEventMonitor {
275+ func loadMappings( ) {
276+ let selectableIsProperties = [
277+ kTISPropertyInputSourceIsEnableCapable: true ,
278+ kTISPropertyInputSourceCategory: kTISCategoryKeyboardInputSource ?? " " as CFString ,
279+ ] as CFDictionary
280+ let inputSources = TISCreateInputSourceList ( selectableIsProperties, false ) . takeUnretainedValue ( ) as! [ TISInputSource ]
281+
282+ let inputSourcesById = inputSources. reduce ( [ String: TISInputSource] ( ) ) {
283+ dict, inputSource -> [ String : TISInputSource ] in
284+ var dict = dict
285+ if let id = unmanagedStringToString ( TISGetInputSourceProperty ( inputSource, kTISPropertyInputSourceID) ) {
286+ dict [ id] = inputSource
287+ }
288+ return dict
289+ }
290+
291+ if let mappings = defaults. dictionary ( forKey: MAPPINGS_DEFAULTS_KEY) {
292+ for (keyboardId, inputSourceId) in mappings {
293+ kb2is [ keyboardId] = inputSourcesById [ String ( describing: inputSourceId) ]
294+ }
295+ }
296+
297+ if let enabledMappings = defaults. dictionary ( forKey: MAPPING_ENABLED_KEY) as? [ String : Bool ] {
298+ deviceEnabled = enabledMappings
299+ }
300+ }
301+
302+ func saveMappings( ) {
303+ withPausedSettingsObserver {
304+ let mappings = kb2is. mapValues ( is2Id)
305+ defaults. set ( mappings, forKey: MAPPINGS_DEFAULTS_KEY)
306+ defaults. set ( deviceEnabled, forKey: MAPPING_ENABLED_KEY)
307+ defaults. synchronize ( )
308+
309+ postSettingsChangedNotification ( )
310+ }
311+
312+ if verbosity >= TRACE {
313+ print ( " Saved keyboard mappings to UserDefaults " )
314+ }
315+ }
316+
317+ public func clearAllSettings( ) {
318+ kb2is. removeAll ( )
319+ deviceEnabled. removeAll ( )
320+ lastActiveKeyboard = nil
321+ defaults. removeObject ( forKey: MAPPINGS_DEFAULTS_KEY)
322+ defaults. removeObject ( forKey: MAPPING_ENABLED_KEY)
323+ defaults. synchronize ( )
324+ }
325+ }
326+
327+ // MARK: - Settings Change Notifications
328+
329+ private extension IOKeyEventMonitor {
330+ private func startObservingSettingsChanges( ) {
331+ if verbosity >= TRACE {
332+ print ( " Starting UserDefaults observation " )
333+ }
334+
335+ let context = UnsafeMutableRawPointer ( Unmanaged . passUnretained ( self ) . toOpaque ( ) )
336+ let callback : CFNotificationCallback = { _, observer, _, _, _ in
337+ let selfPtr = Unmanaged < IOKeyEventMonitor > . fromOpaque ( observer!) . takeUnretainedValue ( )
338+ if selfPtr. verbosity >= TRACE {
339+ print ( " Received settings change notification " )
340+ }
341+ selfPtr. onSettingsChanged ( )
342+ }
343+
344+ CFNotificationCenterAddObserver (
345+ notificationCenter,
346+ context,
347+ callback,
348+ " com.autokbisw.settingsChanged " as CFString ,
349+ nil ,
350+ . deliverImmediately
351+ )
352+ }
353+
354+ private func stopObservingSettingsChanges( ) {
355+ let context = UnsafeMutableRawPointer ( Unmanaged . passUnretained ( self ) . toOpaque ( ) )
356+ CFNotificationCenterRemoveObserver (
357+ notificationCenter,
358+ context,
359+ CFNotificationName ( " com.autokbisw.settingsChanged " as CFString ) ,
360+ nil
361+ )
362+ }
363+
364+ private func postSettingsChangedNotification( ) {
365+ if verbosity >= TRACE {
366+ print ( " Posting settings changed notification " )
367+ }
368+
369+ CFNotificationCenterPostNotification (
370+ notificationCenter,
371+ CFNotificationName ( " com.autokbisw.settingsChanged " as CFString ) ,
372+ nil ,
373+ nil ,
374+ true
375+ )
376+ }
377+
378+ private func withPausedSettingsObserver< T> ( _ operation: ( ) -> T ) -> T {
379+ stopObservingSettingsChanges ( )
380+ defer { startObservingSettingsChanges ( ) }
381+ return operation ( )
382+ }
383+
384+ private func onSettingsChanged( ) {
385+ loadMappings ( )
386+ if verbosity >= TRACE {
387+ print ( " Reloaded mappings due to UserDefaults change " )
388+ }
389+ }
390+ }
391+
308392// MARK: - Utilities
309393
310394extension IOKeyEventMonitor {
0 commit comments