Search code examples
swiftobjective-cmacoshidiokit

Simple key remapping using IOKit with Swift


In macOS, the IOKit HID APIs can be used for key remapping. In the example below, A key is remapped to B key, and vice versa, using Objective-C.

Is it possible to do the same remapping programmatically with the latest Swift 5.5? If it can be done, how does the example below look rewritten in Swift?

Or is there a new, more modern API that can be used with Swift to accomplish the same task?


#import <Foundation/Foundation.h>
#import <IOKit/hidsystem/IOHIDEventSystemClient.h>
#import <IOKit/hidsystem/IOHIDServiceClient.h>
#import <IOKit/hid/IOHIDUsageTables.h>
 
int main(int argc, char *argv[])
{
    IOHIDEventSystemClientRef system;
    CFArrayRef services;
 
    uint64_t aKey = 0x700000004;
    uint64_t bKey = 0x700000005;
 
    NSArray *map = @[
                     @{@kIOHIDKeyboardModifierMappingSrcKey:@(aKey),
                        @kIOHIDKeyboardModifierMappingDstKey:@(bKey)},
                     @{@kIOHIDKeyboardModifierMappingSrcKey:@(bKey),
                        @kIOHIDKeyboardModifierMappingDstKey:@(aKey)},
                     ];
 
    system = IOHIDEventSystemClientCreateSimpleClient(kCFAllocatorDefault);
    services = IOHIDEventSystemClientCopyServices(system);
    for(CFIndex i = 0; i < CFArrayGetCount(services); i++) {
        IOHIDServiceClientRef service = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(services, i);
        if(IOHIDServiceClientConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) {
            IOHIDServiceClientSetProperty(service, CFSTR(kIOHIDUserKeyUsageMapKey), (CFArrayRef)map);
        }
    }
 
    CFRelease(services);
    CFRelease(system);
 
    return 0;
}

Solution

  • You can rewrite your cursed code in Swift 5.5 like this:

    import Cocoa
    
    class ViewController: NSViewController {
    
      override func viewDidLoad() {
        super.viewDidLoad()
        
         remapKeys()
      }
    
      func remapKeys() {
        let aKey: UInt64 = 0x700000004
        let bKey: UInt64 = 0x700000005
        
        let map: [[String: UInt64]] = [
          [kIOHIDKeyboardModifierMappingSrcKey:aKey,
           kIOHIDKeyboardModifierMappingDstKey:bKey],
          [kIOHIDKeyboardModifierMappingSrcKey:bKey,
           kIOHIDKeyboardModifierMappingDstKey:aKey],
        ]
        
        let system = IOHIDEventSystemClientCreateSimpleClient(kCFAllocatorDefault)
        let services = IOHIDEventSystemClientCopyServices(system)
        
        for service in services as! [IOHIDServiceClient] {
          if((IOHIDServiceClientConformsTo(service, UInt32((kHIDPage_GenericDesktop)), UInt32(kHIDUsage_GD_Keyboard))) != 0) {
            IOHIDServiceClientSetProperty(service, kIOHIDUserKeyUsageMapKey as CFString, map as CFArray)
          }
        }
      }
    }
    

    providing you create a bridging header containing these imports:

    #import <IOKit/hidsystem/IOHIDEventSystemClient.h>
    #import <IOKit/hidsystem/IOHIDServiceClient.h>
    #import <IOKit/hid/IOHIDUsageTables.h>