Skip to content

Commit

Permalink
Save and restore keyboard/input source mappings
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jeantil committed Oct 27, 2017
1 parent 2e2bda7 commit 3a5096f
Showing 1 changed file with 50 additions and 4 deletions.
54 changes: 50 additions & 4 deletions autokbisw/IOKeyEventMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,24 @@ 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;
hidManager = IOHIDManagerCreate( kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone));
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);
Expand Down Expand Up @@ -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){
Expand All @@ -113,6 +113,7 @@ extension IOKeyEventMonitor {
func storeInputSource(keyboard: String) -> Void {
let currentSource: TISInputSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue();
kb2is[keyboard] = currentSource;
self.saveMappings();
}

func onInputSourceChanged() -> Void {
Expand All @@ -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<TISInputSource>

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

0 comments on commit 3a5096f

Please sign in to comment.