hammerspoon/extensions/streamdeck/HSStreamDeckManager.m

245 lines
9.7 KiB
Objective-C

//
// HSStreamDeckManager.m
// Hammerspoon
//
// Created by Chris Jones on 06/09/2017.
// Copyright © 2017 Hammerspoon. All rights reserved.
//
#import "HSStreamDeckManager.h"
#pragma mark - IOKit C callbacks
static char *inputBuffer = NULL;
static void HIDReport(void* deviceRef, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength) {
HSStreamDeckDevice *device = (__bridge HSStreamDeckDevice*)deviceRef;
NSMutableArray* buttonReport = [NSMutableArray arrayWithCapacity:device.keyCount+1];
// We need an unused button at slot zero - all our uses of these arrays are one-indexed
[buttonReport setObject:[NSNumber numberWithInt:0] atIndexedSubscript:0];
for(int p=1; p <= device.keyCount; p++) {
[buttonReport setObject:@0 atIndexedSubscript:p];
}
uint8_t *start = report + device.dataKeyOffset;
for(int button=1; button <= device.keyCount; button ++) {
NSNumber* val = [NSNumber numberWithInt:start[button-1]];
int translatedButton = [device transformKeyIndex:button];
[buttonReport setObject:val atIndexedSubscript:translatedButton];
}
[device deviceDidSendInput:buttonReport];
}
static void HIDconnect(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
//NSLog(@"connect: %p:%p", context, (void *)device);
HSStreamDeckManager *manager = (__bridge HSStreamDeckManager *)context;
HSStreamDeckDevice *deviceId = [manager deviceDidConnect:device];
if (deviceId) {
IOHIDDeviceRegisterInputReportCallback(device, (uint8_t*)inputBuffer, 1024, HIDReport, (void*)deviceId);
//NSLog(@"Added value callback to new IOKit device %p for Deck Device %p", (void *)device, (__bridge void*)deviceId);
}
}
static void HIDdisconnect(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
//NSLog(@"disconnect: %p", (void *)device);
HSStreamDeckManager *manager = (__bridge HSStreamDeckManager *)context;
[manager deviceDidDisconnect:device];
IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
}
#pragma mark - Stream Deck Manager implementation
@implementation HSStreamDeckManager
- (id)init {
self = [super init];
if (self) {
self.devices = [[NSMutableArray alloc] initWithCapacity:5];
self.discoveryCallbackRef = LUA_NOREF;
inputBuffer = malloc(1024);
// Create a HID device manager
self.ioHIDManager = CFBridgingRelease(IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone));
//NSLog(@"Created HID Manager: %p", (void *)self.ioHIDManager);
// Configure the HID manager to match against Stream Deck devices
NSString *vendorIDKey = @(kIOHIDVendorIDKey);
NSString *productIDKey = @(kIOHIDProductIDKey);
NSDictionary *matchOriginal = @{vendorIDKey: @USB_VID_ELGATO,
productIDKey: @USB_PID_STREAMDECK_ORIGINAL};
NSDictionary *matchOriginalv2 = @{vendorIDKey: @USB_VID_ELGATO,
productIDKey: @USB_PID_STREAMDECK_ORIGINAL_V2};
NSDictionary *matchMini = @{vendorIDKey: @USB_VID_ELGATO,
productIDKey: @USB_PID_STREAMDECK_MINI};
NSDictionary *matchXL = @{vendorIDKey: @USB_VID_ELGATO,
productIDKey: @USB_PID_STREAMDECK_XL};
NSDictionary *matchMk2 = @{vendorIDKey: @USB_VID_ELGATO,
productIDKey: @USB_PID_STREAMDECK_MK2};
IOHIDManagerSetDeviceMatchingMultiple((__bridge IOHIDManagerRef)self.ioHIDManager,
(__bridge CFArrayRef)@[matchOriginal,
matchOriginalv2,
matchMini,
matchXL,
matchMk2]);
// Add our callbacks for relevant events
IOHIDManagerRegisterDeviceMatchingCallback((__bridge IOHIDManagerRef)self.ioHIDManager,
HIDconnect,
(__bridge void*)self);
IOHIDManagerRegisterDeviceRemovalCallback((__bridge IOHIDManagerRef)self.ioHIDManager,
HIDdisconnect,
(__bridge void*)self);
// Start our HID manager
IOHIDManagerScheduleWithRunLoop((__bridge IOHIDManagerRef)self.ioHIDManager,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
}
return self;
}
- (void)doGC {
if (!(__bridge IOHIDManagerRef)self.ioHIDManager) {
// Something is wrong and the manager doesn't exist, so just bail
return;
}
// Remove our callbacks
IOHIDManagerRegisterDeviceMatchingCallback((__bridge IOHIDManagerRef)self.ioHIDManager, NULL, (__bridge void*)self);
IOHIDManagerRegisterDeviceRemovalCallback((__bridge IOHIDManagerRef)self.ioHIDManager, NULL, (__bridge void*)self);
// Remove our HID manager from the runloop
IOHIDManagerUnscheduleFromRunLoop((__bridge IOHIDManagerRef)self.ioHIDManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
// Deallocate the HID manager
self.ioHIDManager = nil;
if (inputBuffer) {
free(inputBuffer);
}
}
- (BOOL)startHIDManager {
IOReturn tIOReturn = IOHIDManagerOpen((__bridge IOHIDManagerRef)self.ioHIDManager, kIOHIDOptionsTypeNone);
return tIOReturn == kIOReturnSuccess;
}
- (BOOL)stopHIDManager {
if (!(__bridge IOHIDManagerRef)self.ioHIDManager) {
return YES;
}
IOReturn tIOReturn = IOHIDManagerClose((__bridge IOHIDManagerRef)self.ioHIDManager, kIOHIDOptionsTypeNone);
return tIOReturn == kIOReturnSuccess;
}
- (HSStreamDeckDevice*)deviceDidConnect:(IOHIDDeviceRef)device {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
if (![skin checkGCCanary:self.lsCanary]) {
_lua_stackguard_exit(skin.L);
return nil;
}
if (self.discoveryCallbackRef == LUA_NOREF || self.discoveryCallbackRef == LUA_REFNIL) {
[skin logWarn:@"hs.streamdeck detected a device connecting, but no discovery callback has been set. See hs.streamdeck.discoveryCallback()"];
_lua_stackguard_exit(skin.L);
return nil;
}
NSNumber *vendorID = (__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
NSNumber *productID = (__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
if (vendorID.intValue != USB_VID_ELGATO) {
NSLog(@"deviceDidConnect from unknown vendor: %d", vendorID.intValue);
return nil;
}
HSStreamDeckDevice *deck = nil;
switch (productID.intValue) {
case USB_PID_STREAMDECK_ORIGINAL:
deck = [[HSStreamDeckDeviceOriginal alloc] initWithDevice:device manager:self];
break;
case USB_PID_STREAMDECK_MINI:
deck = [[HSStreamDeckDeviceMini alloc] initWithDevice:device manager:self];
break;
case USB_PID_STREAMDECK_XL:
deck = [[HSStreamDeckDeviceXL alloc] initWithDevice:device manager:self];
break;
case USB_PID_STREAMDECK_ORIGINAL_V2:
deck = [[HSStreamDeckDeviceOriginalV2 alloc] initWithDevice:device manager:self];
break;
case USB_PID_STREAMDECK_MK2:
deck = [[HSStreamDeckDeviceMk2 alloc] initWithDevice:device manager:self];
break;
default:
NSLog(@"deviceDidConnect from unknown device: %d", productID.intValue);
break;
}
if (!deck) {
NSLog(@"deviceDidConnect: no HSStreamDeckDevice was created, ignoring");
return nil;
}
deck.lsCanary = [skin createGCCanary];
[deck initialiseCaches];
[self.devices addObject:deck];
[skin pushLuaRef:streamDeckRefTable ref:self.discoveryCallbackRef];
lua_pushboolean(skin.L, 1);
[skin pushNSObject:deck];
[skin protectedCallAndError:@"hs.streamdeck:deviceDidConnect" nargs:2 nresults:0];
//NSLog(@"Created deck device: %p", (__bridge void*)deviceId);
//NSLog(@"Now have %lu devices", self.devices.count);
_lua_stackguard_exit(skin.L);
return deck;
}
- (void)deviceDidDisconnect:(IOHIDDeviceRef)device {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
if (![skin checkGCCanary:self.lsCanary]) {
_lua_stackguard_exit(skin.L);
return;
}
for (HSStreamDeckDevice *deckDevice in self.devices) {
if (deckDevice.device == device) {
[deckDevice invalidate];
if (self.discoveryCallbackRef == LUA_NOREF || self.discoveryCallbackRef == LUA_REFNIL) {
[skin logWarn:@"hs.streamdeck detected a device disconnecting, but no callback has been set. See hs.streamdeck.discoveryCallback()"];
} else {
[skin pushLuaRef:streamDeckRefTable ref:self.discoveryCallbackRef];
lua_pushboolean(skin.L, 0);
[skin pushNSObject:deckDevice];
[skin protectedCallAndError:@"hs.streamdeck:deviceDidDisconnect" nargs:2 nresults:0];
}
LSGCCanary tmpLSUUID = deckDevice.lsCanary;
[skin destroyGCCanary:&tmpLSUUID];
deckDevice.lsCanary = tmpLSUUID;
[self.devices removeObject:deckDevice];
_lua_stackguard_exit(skin.L);
return;
}
}
NSLog(@"ERROR: A Stream Deck was disconnected that we didn't know about");
_lua_stackguard_exit(skin.L);
return;
}
@end