From 3a5096f9a5bfad14c3684c4938a2f3c2ebe1f17a Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Fri, 27 Oct 2017 09:30:17 +0200 Subject: [PATCH] Save and restore keyboard/input source mappings Mapping ofi(keyboardId -> Input source ID) is saved in UserDefaults as a Dictionary [String:String] Upon startup, autokbis builds a mapping of (InputSourceID -> TSInputSource) then reads the saved mapping and reassociates each keyboardeh with its input source. If the input source associated with a keyboard is no longer available, the keyboard will be left unmapped and will therefore use the last selected input source until it is given a specific mapping. Upon each inputsource change, and upon program exit the mapping is saved to UserDefaults. fixes #5 --- autokbisw/IOKeyEventMonitor.swift | 54 ++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/autokbisw/IOKeyEventMonitor.swift b/autokbisw/IOKeyEventMonitor.swift index c9cad2e..67176c1 100644 --- a/autokbisw/IOKeyEventMonitor.swift +++ b/autokbisw/IOKeyEventMonitor.swift @@ -20,11 +20,12 @@ import IOKit.hid final internal class IOKeyEventMonitor { private let hidManager: IOHIDManager - + fileprivate let MAPPINGS_DEFAULTS_KEY = "keyboardISMapping" fileprivate let notificationCenter: CFNotificationCenter fileprivate let userOptions: UserOptions fileprivate var lastActiveKeyboard: String = "" fileprivate var kb2is: [String: TISInputSource] = [String: TISInputSource]() + fileprivate var defaults: UserDefaults = UserDefaults.standard; init? ( usagePage: Int, usage: Int, _userOptions: UserOptions) { userOptions = _userOptions; @@ -32,10 +33,11 @@ final internal class IOKeyEventMonitor { notificationCenter = CFNotificationCenterGetDistributedCenter(); let deviceMatch: CFMutableDictionary = [kIOHIDDeviceUsageKey: usage, kIOHIDDeviceUsagePageKey: usagePage] as NSMutableDictionary IOHIDManagerSetDeviceMatching( hidManager, deviceMatch); - + self.loadMappings() } deinit { + self.saveMappings(); let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()); IOHIDManagerRegisterInputValueCallback( hidManager, Optional.none , context); CFNotificationCenterRemoveObserver(notificationCenter, context, CFNotificationName(kTISNotifySelectedKeyboardInputSourceChanged), nil); @@ -97,8 +99,6 @@ final internal class IOKeyEventMonitor { extension IOKeyEventMonitor { - - func restoreInputSource(keyboard: String) -> Void { if let targetIs = kb2is[keyboard] { if(userOptions.verbosity >= UserOptions.DEBUG){ @@ -113,6 +113,7 @@ extension IOKeyEventMonitor { func storeInputSource(keyboard: String) -> Void { let currentSource: TISInputSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue(); kb2is[keyboard] = currentSource; + self.saveMappings(); } func onInputSourceChanged() -> Void { @@ -125,4 +126,49 @@ extension IOKeyEventMonitor { self.restoreInputSource(keyboard: keyboard); self.lastActiveKeyboard = keyboard; } + + func loadMappings()-> Void { + let selectableIsProperties = [ + kTISPropertyInputSourceIsEnableCapable:true, + kTISPropertyInputSourceCategory:kTISCategoryKeyboardInputSource + ] as CFDictionary; + let inputSources = TISCreateInputSourceList(selectableIsProperties,false).takeUnretainedValue() as! Array + + let inputSourcesById = inputSources.reduce([String: TISInputSource]()) { + (dict, inputSource) -> [String: TISInputSource] in + var dict = dict; + if let id=unmanagedStringToString(TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID)) { + dict[id] = inputSource; + } + return dict; + } + + if let mappings=self.defaults.dictionary(forKey: MAPPINGS_DEFAULTS_KEY){ + for (keyboardId,inputSourceId) in mappings { + kb2is[keyboardId]=inputSourcesById[String(describing:inputSourceId)]; + } + } + } + + func saveMappings()-> Void { + let mappings = kb2is.mapValues(is2Id) + self.defaults.set(mappings, forKey: MAPPINGS_DEFAULTS_KEY) + } + + private func is2Id(_ inputSource:TISInputSource) -> String? { + return unmanagedStringToString(TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID))! + } + + func unmanagedStringToString(_ p : UnsafeMutableRawPointer?) -> String?{ + if let cfValue = p { + let value = Unmanaged.fromOpaque(cfValue).takeUnretainedValue() as CFString + if CFGetTypeID(value) == CFStringGetTypeID(){ + return value as String + } else { + return nil + } + }else{ + return nil + } + } }