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









up vote
0
down vote

favorite












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 and you should be able to 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









share|improve this question























  • Why can't you use an event tap?
    – Willeke
    Nov 10 at 19:12










  • I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
    – simsula
    Nov 10 at 19:14















up vote
0
down vote

favorite












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 and you should be able to 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









share|improve this question























  • Why can't you use an event tap?
    – Willeke
    Nov 10 at 19:12










  • I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
    – simsula
    Nov 10 at 19:14













up vote
0
down vote

favorite









up vote
0
down vote

favorite











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 and you should be able to 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









share|improve this question















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 and you should be able to 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






objective-c macos usb hid nslog






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 11 at 11:07

























asked Nov 10 at 17:04









simsula

176




176











  • Why can't you use an event tap?
    – Willeke
    Nov 10 at 19:12










  • I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
    – simsula
    Nov 10 at 19:14

















  • Why can't you use an event tap?
    – Willeke
    Nov 10 at 19:12










  • I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
    – simsula
    Nov 10 at 19:14
















Why can't you use an event tap?
– Willeke
Nov 10 at 19:12




Why can't you use an event tap?
– Willeke
Nov 10 at 19:12












I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
– simsula
Nov 10 at 19:14





I could't find a way to block the system from receiving mouseMoved events using an eventTap. Even when you return nil from within the callback, the mouse pointer still moves...
– simsula
Nov 10 at 19:14


















active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53241316%2fmac-trying-to-read-input-from-usb-device-with-a-callback-input-values-gettin%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown






























active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes















 

draft saved


draft discarded















































 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53241316%2fmac-trying-to-read-input-from-usb-device-with-a-callback-input-values-gettin%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

How to read a connectionString WITH PROVIDER in .NET Core?

Node.js Script on GitHub Pages or Amazon S3

Museum of Modern and Contemporary Art of Trento and Rovereto