simsula
simsula

Reputation: 157

(Mac) trying to read input from USB Device with a Callback - Input values getting mixed up and needing to NSLog for code to work

Context:

I'm trying to write a script that can fully disable/customize mouse acceleration on Mac. I haven't found a way to block mouseMoved events using Quartz Event Services (CGEventTap), so I tried using the lower level IOHID APIs (IOHIDManager / IOHIDDevice) to gain exclusive access to the the device and then create and post my own CGEvents from within an input callback. I'm having some very weird problems...

Stopping mouse input from being processed by the System (kIOHIDOptionsTypeSeizeDevice), receiving X and Y input from the mouse on a callback, and updating the cursor position based on that input worked okay, but then I tried fetching button input as well and everything fell apart.


Problem

When setting up the callback function for a device using IOHIDDeviceRegisterInputValueCallback, I specify that I want to receive IOHIDValues with a "Usage" of 48 and 49 (data fields for position deltas on the X and Y axis respectively) as well as IOHIDValues with a "UsagePage" of 9 (data field for button input).

The callback works fine in the sense that it reacts to these, and only these input events, but when I try to access the "Usage" "UsagePage" and IntegerValue of the IOHIDElement contained in the IOHIDValue that the callback provides things get weird.

The IntegerValue of an IOHIDElement that the callback provides following the press of a mouse button is always zero. The "Usage" and "UsagePage" are 1 and 48/49 respectively - right now at least. Earlier I printed the same values and it was 48/49 for everything - even the "UsagePage" for button input... (The "UsagePage" and "Usage" for Button input should be 0 and 9 respectively - you can look up the definitions of different "Usage" and "UsagePage" values here)

Also I need to NSLog something, anything, at certain spots in the callback function, in order to make mouse pointer movement on the Y-Axis work... The longer the string I print, the smoother the movement seems to be. When I print at other spots, it makes the movement on the Y-Axis more jerky...

Thank you for your help. I'm at the end of my non existent wits.

Oh and if you feel like critiquing my code, please do! I'm still learning.


Edit:

Okay so I didn't solve the problem itself, but I found out the the "proper" way to change mouse acceleration and speed - the IORegistry. Take a look at this post it might help you figure it out.



This is the callback function, which also posts CGEvents:

static void Handle_InputValueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) {

    double _mouseSpeed = 0.5;

    // getting Usage and UsagePage of the Input
    IOHIDElementRef element = IOHIDValueGetElement(value);
    uint32_t elementUsage = IOHIDElementGetUsage(element);
    uint32_t elementUsagePage = IOHIDElementGetUsagePage(element);

    // reading the current mouse position
    CGEventRef eventForReadingMousePos = CGEventCreate(NULL);
    CGPoint mouseLocation = CGEventGetLocation(eventForReadingMousePos);

    // printing things

    //NSLog(@"value: %@", value);
    //NSLog(@"element: %@", element);
    //int intValue = (int) IOHIDValueGetIntegerValue(value);
    //NSLog(@"intValue: %d", intValue);

    /*
     intValue is 0 when you click a button
     */

    NSLog(@"elementUsage: %d", elementUsage);
    NSLog(@"elementUsagePage: %d", elementUsagePage);
    /*
         ^
     when you print here, it messes up mouse movement on the Y Axis....
     */


    // parsing input based on its Usage / UsagePage

    if (elementUsagePage == 9) { // button

        /*
         This doesn't work. elementUsage and elementUsagePage are always 48 or 49 for any input ("Usage" should be 0 for button input and 48/49 for mouse-position-delta input, "UsagePage" should be 9 for button input and 1 for pos delta... but both fields are always 48/49...) (This is where I have these numbers from: http://www.freebsddiary.org/APC/usb_hid_usages.php )
         */


    } else if (elementUsage == 48) { // x input
        double deltaX = IOHIDValueGetIntegerValue(value) * _mouseSpeed;
        double newX = mouseLocation.x + deltaX;
        CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake(newX, mouseLocation.y), 0);
        CGEventPost(kCGHIDEventTap, event);

        CFRelease(event);

    } else if (elementUsage == 49) { // y input
        double deltaY = IOHIDValueGetIntegerValue(value) * _mouseSpeed;
        double newY = mouseLocation.y + deltaY;
        CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake(mouseLocation.x, newY), 0);
        CGEventPost(kCGHIDEventTap, event);

        CFRelease(event);



        NSLog(@"some");

        /* You NEED to print something here to make Y input work... I'm so confused... */

    }


    NSLog(@"thing");

    /* You need to print here, too, otherwise y movement is kind of jerky.. But if you print something more complex than some or thing at either one of these spots and don't print at the other, it seems to work fine as well. */



    CFRelease(eventForReadingMousePos);

}

This function registers the callback:

static void registerInputCallbackForDevice(IOHIDDeviceRef device) {

    NSLog(@"registering device: %@", device);
    NSCAssert(device != NULL, @"tried to register a device which equals NULL");

    // create matching dicts
    CFMutableDictionaryRef elementMatchDict1
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    CFMutableDictionaryRef elementMatchDict2
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    CFMutableDictionaryRef elementMatchDict3
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    // creating values for the "elementMatchDicts" - definition of values for keys "Usage" and "UsagePage" here: http://www.freebsddiary.org/APC/usb_hid_usages.php
    int fourtyEight = 48;
    int fourtyNine = 49;
    int nine = 9;
    CFNumberRef UsageX = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fourtyEight); // "Usage" value for X input
    CFNumberRef UsageY = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fourtyNine); // "Usage" value for Y input
    CFNumberRef usagePageButton = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &nine); // "UsagePage" for button input

    // filling up the dictionaries
    CFDictionarySetValue(elementMatchDict1, CFSTR("Usage"), UsageX);
    CFDictionarySetValue(elementMatchDict2, CFSTR("Usage"), UsageY);
    CFDictionarySetValue(elementMatchDict3, CFSTR("UsagePage"), usagePageButton);
    CFMutableDictionaryRef matchArrayPrimitive[3] = {elementMatchDict1, elementMatchDict2, elementMatchDict3};
    CFArrayRef matchArray = CFArrayCreate(kCFAllocatorDefault, (const void **)matchArrayPrimitive, 2, NULL);
    IOHIDDeviceSetInputValueMatchingMultiple(device, matchArray);

    IOHIDDeviceRegisterInputValueCallback(device, &Handle_InputValueCallback, NULL);

    CFRelease(elementMatchDict1);
    CFRelease(elementMatchDict2);
    CFRelease(elementMatchDict3);

}

AppDelegate.m - for copy/pasting.

You'll need to turn off App Sandbox, or it'll throw a runtime error (also remember that this will make your mouse unusable while its running - it should ignore trackpads and magic mice though)

#import "AppDelegate.h"
#import "IOKit/hid/IOHIDManager.h"

@implementation AppDelegate


// global variables
IOHIDManagerRef HIDManager;


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    setupHIDManagerAndCallbacks();
}

static void setupHIDManagerAndCallbacks() {


    // Create an HID Manager
    HIDManager = IOHIDManagerCreate(kCFAllocatorDefault,
                                    kIOHIDOptionsTypeNone);

    // Create a Matching Dictionaries
    CFMutableDictionaryRef matchDict1
    = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFMutableDictionaryRef matchDict2
    = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFMutableDictionaryRef matchDict3
    = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    // Specify properties of the devices which we want to add to the HID Manager in the Matching Dictionary
    CFArrayRef matches;
    CFDictionarySetValue(matchDict1, CFSTR("PrimaryUsage"), (const void *)0x227);       // add USB mice
    CFDictionarySetValue(matchDict1, CFSTR("Transport"), CFSTR("USB"));                 //
    CFDictionarySetValue(matchDict2, CFSTR("PrimaryUsage"), (const void *)0x227);       // add Bluetooth mice
    CFDictionarySetValue(matchDict2, CFSTR("Transport"), CFSTR("Bluetooth"));           //
    CFDictionarySetValue(matchDict3, CFSTR("PrimaryUsage"), (const void *)0x227);       // add Bluetooth low energy mice (?)
    CFDictionarySetValue(matchDict3, CFSTR("Transport"), CFSTR("BluetoothLowEnergy"));  //

    CFMutableDictionaryRef matchesList[] = {matchDict1, matchDict2, matchDict3};
    matches = CFArrayCreate(kCFAllocatorDefault, (const void **)matchesList, 3, NULL);


    //Register the Matching Dictionary to the HID Manager
    IOHIDManagerSetDeviceMatchingMultiple(HIDManager, matches);

    CFRelease(matches);
    CFRelease(matchDict1);
    CFRelease(matchDict2);
    CFRelease(matchDict3);


    // Register the HID Manager on our app’s run loop
    IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);

    // Open the HID Manager
    IOReturn IOReturn = IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeSeizeDevice);
    if(IOReturn) NSLog(@"IOHIDManagerOpen failed.");  //  Couldn't open the HID manager! TODO: proper error handling


    // Register callback for USB device detection with the HID Manager, this will invoke Handle_DeviceMatchingCallback for each matching device
    IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, &Handle_DeviceMatchingCallback, NULL);
}


static void Handle_DeviceMatchingCallback (void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {

    if (devicePassesFiltering(device) ) {
        registerInputCallbackForDevice(device);
    }
}
static BOOL devicePassesFiltering(IOHIDDeviceRef HIDDevice) {
    NSString *deviceName = [NSString stringWithUTF8String:
       CFStringGetCStringPtr(IOHIDDeviceGetProperty(HIDDevice, CFSTR("Product")), kCFStringEncodingMacRoman)];
    NSString *deviceNameLower = [deviceName lowercaseString];

    if ([deviceNameLower rangeOfString:@"magic"].location == NSNotFound) {
        return TRUE;
    } else {
        return FALSE;
    }
}


static void registerInputCallbackForDevice(IOHIDDeviceRef device) {

    NSLog(@"registering device: %@", device);
    NSCAssert(device != NULL, @"tried to register a device which equals NULL");

    // create matching dicts
    CFMutableDictionaryRef elementMatchDict1
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    CFMutableDictionaryRef elementMatchDict2
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    CFMutableDictionaryRef elementMatchDict3
    = CFDictionaryCreateMutable(kCFAllocatorDefault,2,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);

    // values for keys "Usage" and "UsagePage" here http://www.freebsddiary.org/APC/usb_hid_usages.php
    int fourtyEight = 48;
    int fourtyNine = 49;
    int nine = 9;
    CFNumberRef UsageX = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fourtyEight); // "Usage" value for X input
    CFNumberRef UsageY = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fourtyNine); // "Usage" value for Y input
    CFNumberRef usagePageButton = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &nine); // "UsagePage" for button input

    CFDictionarySetValue (elementMatchDict1, CFSTR("Usage"), UsageX);
    CFDictionarySetValue (elementMatchDict2, CFSTR("Usage"), UsageY);
    CFDictionarySetValue (elementMatchDict3, CFSTR("UsagePage"), usagePageButton);
    CFMutableDictionaryRef matchArrayPrimitive[3] = {elementMatchDict1, elementMatchDict2, elementMatchDict3};
    CFArrayRef matchArray = CFArrayCreate(kCFAllocatorDefault, (const void **)matchArrayPrimitive, 2, NULL);
    IOHIDDeviceSetInputValueMatchingMultiple(device, matchArray);

    IOHIDDeviceRegisterInputValueCallback(device, &Handle_InputValueCallback, NULL);

    CFRelease(elementMatchDict1);
    CFRelease(elementMatchDict2);
    CFRelease(elementMatchDict3);

}


static void Handle_InputValueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) {

    double _mouseSpeed = 0.5;

    IOHIDElementRef element = IOHIDValueGetElement(value);
    uint32_t elementUsage = IOHIDElementGetUsage(element);
    uint32_t elementUsagePage = IOHIDElementGetUsagePage(element);
    CGEventRef eventForReadingMousePos = CGEventCreate(NULL);
    CGPoint mouseLocation = CGEventGetLocation(eventForReadingMousePos);

    //NSLog(@"value: %@", value);
    //NSLog(@"element: %@", element);
    //int intValue = (int) IOHIDValueGetIntegerValue(value);
    //NSLog(@"intValue: %d", intValue);

    /*
     intValue is 0 when you click a button
     */

    NSLog(@"elementUsage: %d", elementUsage);
    NSLog(@"elementUsagePage: %d", elementUsagePage);

    /*
     when you print here, it messes up mouse movement on the Y Axis....
     */



    if (elementUsagePage == 9) { // button

        /*
         This doesn't work. elementUsage and elementUsagePage are always 48 or 49 for any input ("Usage" should be 0 for button input and 48/49 for mouse-position-delta input, "UsagePage" should be 9 for button input and 1 for pos delta... but both fields are always 48/49...) (This is where I have these numbers from: http://www.freebsddiary.org/APC/usb_hid_usages.php )
         */


    } else if (elementUsage == 48) { // x axis
        double deltaX = IOHIDValueGetIntegerValue(value) * _mouseSpeed;
        double newX = mouseLocation.x + deltaX;
        CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake(newX, mouseLocation.y), 0);
        CGEventPost(kCGHIDEventTap, event);

        CFRelease(event);

    } else if (elementUsage == 49) { // y axis
        double deltaY = IOHIDValueGetIntegerValue(value) * _mouseSpeed;
        double newY = mouseLocation.y + deltaY;
        CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, CGPointMake(mouseLocation.x, newY), 0);
        CGEventPost(kCGHIDEventTap, event);

        CFRelease(event);



        NSLog(@"some");

        /* You NEED to print something here to make Y input work... I'm so confused... */

    }


    NSLog(@"thing");

    /* You need to print here, too, otherwise y movement is kind of jerky.. But if you print something more complex than some or thing at either one of these spots and don't print at the other, it seems to work fine as well. */



    CFRelease(eventForReadingMousePos);

}

@end

Upvotes: 2

Views: 805

Answers (0)

Related Questions