(Mac) trying to read input from USB Device with a Callback - Input values getting mixed up and needing to...
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
objective-c macos usb hid nslog
add a comment |
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
objective-c macos usb hid nslog
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
add a comment |
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
objective-c macos usb hid nslog
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
objective-c macos usb hid nslog
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
add a comment |
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
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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