/* * Support for Mac OS X via the HID Manager APIs that are new to OS X 10.5 * ("Leopard"). The technotes suggest that after 10.5, the code in * macosx_hidutilities.c may stop working. We dynamically look up the 10.5 * symbols, and if they are there, we use them. If they aren't, we fail so * the legacy code can do its magic. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. */ #include "manymouse.h" #if ( (defined(__MACH__)) && (defined(__APPLE__)) ) # include // we need the 10.5 SDK headers here... # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 # define MANYMOUSE_DO_MAC_10_POINT_5_API 1 # endif #endif #if MANYMOUSE_DO_MAC_10_POINT_5_API #include #define ALLOCATOR kCFAllocatorDefault #define RUNLOOPMODE (CFSTR("ManyMouse")) #define HIDOPS kIOHIDOptionsTypeNone typedef struct { IOHIDDeviceRef device; char *name; int logical; /* maps to what ManyMouse reports for an index. */ } MouseStruct; static unsigned int logical_mice = 0; static unsigned int physical_mice = 0; static IOHIDManagerRef hidman = NULL; static MouseStruct *mice = NULL; static char *get_device_name(IOHIDDeviceRef device) { char *buf = NULL; void *ptr = NULL; CFIndex len = 0; CFStringRef cfstr = (CFStringRef) IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); if (!cfstr) { /* Maybe we can't get "AwesomeMouse2000", but we can get "Logitech"? */ cfstr = (CFStringRef) IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey)); } /* if */ if (!cfstr) return strdup("Unidentified mouse device"); /* oh well. */ CFRetain(cfstr); len = (CFStringGetLength(cfstr)+1) * 12; /* 12 is overkill, but oh well. */ buf = (char *) malloc(len); if (!buf) { CFRelease(cfstr); return NULL; } /* if */ if (!CFStringGetCString(cfstr, buf, len, kCFStringEncodingUTF8)) { free(buf); CFRelease(cfstr); return NULL; } /* if */ CFRelease(cfstr); ptr = realloc(buf, strlen(buf) + 1); /* shrink down our allocation. */ if (ptr != NULL) buf = (char *) ptr; return buf; } /* get_device_name */ static inline int is_trackpad(const MouseStruct *mouse) { /* * This stupid thing shows up as two logical devices. One does * most of the mouse events, the other does the mouse wheel. */ return (strcmp(mouse->name, "Apple Internal Keyboard / Trackpad") == 0); } /* is_trackpad */ /* * Just trying to avoid malloc() here...we statically allocate a buffer * for events and treat it as a ring buffer. */ /* !!! FIXME: tweak this? */ #define MAX_EVENTS 1024 static ManyMouseEvent input_events[MAX_EVENTS]; static volatile int input_events_read = 0; static volatile int input_events_write = 0; static void queue_event(const ManyMouseEvent *event) { /* copy the event info. We'll process it in ManyMouse_PollEvent(). */ memcpy(&input_events[input_events_write], event, sizeof (ManyMouseEvent)); input_events_write = ((input_events_write + 1) % MAX_EVENTS); /* Ring buffer full? Lose oldest event. */ if (input_events_write == input_events_read) { /* !!! FIXME: we need to not lose mouse buttons here. */ input_events_read = ((input_events_read + 1) % MAX_EVENTS); } /* if */ } /* queue_event */ static int dequeue_event(ManyMouseEvent *event) { if (input_events_read != input_events_write) /* no events if equal. */ { memcpy(event, &input_events[input_events_read], sizeof (*event)); input_events_read = ((input_events_read + 1) % MAX_EVENTS); return 1; } /* if */ return 0; /* no event. */ } /* dequeue_event */ /* returns non-zero if (a <= b). */ typedef unsigned long long ui64; static inline int oldEvent(const AbsoluteTime *a, const AbsoluteTime *b) { #if 0 // !!! FIXME: doesn't work, timestamps aren't reliable. const ui64 a64 = (((unsigned long long) a->hi) << 32) | a->lo; const ui64 b64 = (((unsigned long long) b->hi) << 32) | b->lo; #endif return 0; } /* oldEvent */ /* Callback fires whenever a device is unplugged/lost/whatever. */ static void unplugged_callback(void *ctx, IOReturn res, void *sender) { const unsigned int idx = (unsigned int) ((size_t) ctx); if ((idx < physical_mice) && (mice[idx].device) && (mice[idx].logical >= 0)) { unsigned int i; const int logical = mice[idx].logical; ManyMouseEvent ev; memset(&ev, '\0', sizeof (ev)); ev.type = MANYMOUSE_EVENT_DISCONNECT; ev.device = logical; queue_event(&ev); /* disable any physical devices that back the same logical mouse. */ for (i = 0; i < physical_mice; i++) { if (mice[i].logical == logical) { mice[i].device = NULL; mice[i].logical = -1; } /* if */ } /* for */ } /* if */ } /* unplugged_callback */ /* Callback fires for new mouse input events. */ static void input_callback(void *ctx, IOReturn res, void *sender, IOHIDValueRef val) { const unsigned int idx = (unsigned int) ((size_t) ctx); const MouseStruct *mouse = NULL; if ((res == kIOReturnSuccess) && (idx < physical_mice)) mouse = &mice[idx]; if ((mouse != NULL) && (mouse->device != NULL) && (mouse->logical >= 0)) { ManyMouseEvent ev; IOHIDElementRef elem = IOHIDValueGetElement(val); const CFIndex value = IOHIDValueGetIntegerValue(val); const uint32_t page = IOHIDElementGetUsagePage(elem); const uint32_t usage = IOHIDElementGetUsage(elem); memset(&ev, '\0', sizeof (ev)); ev.value = (int) value; ev.device = mouse->logical; if (page == kHIDPage_GenericDesktop) { /* * some devices (two-finger-scroll trackpads?) seem to give * a flood of events with values of zero for every legitimate * event. Throw these zero events out. */ if (value != 0) { switch (usage) { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: /*if (!oldEvent(&event.timestamp, &mouse->lastScrollTime))*/ { ev.type = MANYMOUSE_EVENT_RELMOTION; ev.item = (usage == kHIDUsage_GD_X) ? 0 : 1; queue_event(&ev); } /* if */ break; case kHIDUsage_GD_Wheel: /*memcpy(&mouse->lastScrollTime, &event.timestamp, sizeof (AbsoluteTime)); */ ev.type = MANYMOUSE_EVENT_SCROLL; ev.item = 0; /* !!! FIXME: horiz scroll? */ queue_event(&ev); break; /*default: !!! FIXME: absolute motion? */ } /* switch */ } /* if */ } /* if */ else if (page == kHIDPage_Button) { ev.type = MANYMOUSE_EVENT_BUTTON; ev.item = ((int) usage) - 1; queue_event(&ev); } /* else if */ } /* if */ } /* input_callback */ /* We ignore hotplugs...this callback is only for initial device discovery. */ static void enum_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef device) { if (res == kIOReturnSuccess) { const size_t len = sizeof (MouseStruct) * (physical_mice + 1); void *ptr = realloc(mice, len); if (ptr != NULL) /* if realloc fails, we just drop the device. */ { mice = (MouseStruct *) ptr; mice[physical_mice].device = device; mice[physical_mice].logical = -1; /* filled in later. */ mice[physical_mice].name = get_device_name(device); if (mice[physical_mice].name == NULL) return; /* This is bad! Don't add this mouse, I guess. */ physical_mice++; } /* if */ } /* if */ } /* enum_callback */ static int config_hidmanager(CFMutableDictionaryRef dict) { CFRunLoopRef runloop = CFRunLoopGetCurrent(); int trackpad = -1; unsigned int i; IOHIDManagerRegisterDeviceMatchingCallback(hidman, enum_callback, NULL); IOHIDManagerScheduleWithRunLoop(hidman,CFRunLoopGetCurrent(),RUNLOOPMODE); IOHIDManagerSetDeviceMatching(hidman, dict); IOHIDManagerOpen(hidman, HIDOPS); while (CFRunLoopRunInMode(RUNLOOPMODE,0,TRUE)==kCFRunLoopRunHandledSource) /* no-op. Callback fires once per existing device. */ ; /* globals (physical_mice) and (mice) are now configured. */ /* don't care about any hotplugged devices after the initial list. */ IOHIDManagerRegisterDeviceMatchingCallback(hidman, NULL, NULL); IOHIDManagerUnscheduleFromRunLoop(hidman, runloop, RUNLOOPMODE); /* now put all those discovered devices into the runloop instead... */ for (i = 0; i < physical_mice; i++) { MouseStruct *mouse = &mice[i]; IOHIDDeviceRef dev = mouse->device; if (IOHIDDeviceOpen(dev, HIDOPS) != kIOReturnSuccess) { mouse->device = NULL; /* oh well. */ mouse->logical = -1; } /* if */ else { void *ctx = (void *) ((size_t) i); if (!is_trackpad(mouse)) mouse->logical = logical_mice++; else { if (trackpad < 0) trackpad = logical_mice++; mouse->logical = trackpad; } /* else */ IOHIDDeviceRegisterRemovalCallback(dev, unplugged_callback, ctx); IOHIDDeviceRegisterInputValueCallback(dev, input_callback, ctx); IOHIDDeviceScheduleWithRunLoop(dev, runloop, RUNLOOPMODE); } /* else */ } /* for */ return 1; /* good to go. */ } /* config_hidmanager */ static int create_hidmanager(const UInt32 page, const UInt32 usage) { int retval = -1; CFNumberRef num = NULL; CFMutableDictionaryRef dict = CFDictionaryCreateMutable(ALLOCATOR, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (dict != NULL) { num = CFNumberCreate(ALLOCATOR, kCFNumberIntType, &page); if (num != NULL) { CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), num); CFRelease(num); num = CFNumberCreate(ALLOCATOR, kCFNumberIntType, &usage); if (num != NULL) { CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), num); CFRelease(num); hidman = IOHIDManagerCreate(ALLOCATOR, HIDOPS); if (hidman != NULL) retval = config_hidmanager(dict); } /* if */ } /* if */ CFRelease(dict); } /* if */ return retval; } /* create_hidmanager */ /* ManyMouseDriver interface... */ static void macosx_hidmanager_quit(void) { unsigned int i; for (i = 0; i < physical_mice; i++) free(mice[i].name); if (hidman != NULL) { /* closing (hidman) should close all open devices, too. */ IOHIDManagerClose(hidman, HIDOPS); CFRelease(hidman); hidman = NULL; } /* if */ logical_mice = 0; physical_mice = 0; free(mice); mice = NULL; memset(input_events, '\0', sizeof (input_events)); input_events_read = input_events_write = 0; } /* macosx_hidmanager_quit */ static int macosx_hidmanager_init(void) { if (IOHIDManagerCreate == NULL) return -1; /* weak symbol is NULL...we don't have OS X >= 10.5.0 */ macosx_hidmanager_quit(); /* just in case... */ /* Prepare global (hidman), (mice), (physical_mice), etc. */ if (!create_hidmanager(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) return -1; return (int) logical_mice; } /* macosx_hidmanager_init */ /* returns the first physical device that backs a logical device. */ static MouseStruct *map_logical_device(const unsigned int index) { if (index < logical_mice) { unsigned int i; for (i = 0; i < physical_mice; i++) { if (mice[i].logical == ((int) index)) return &mice[i]; } /* for */ } /* if */ return NULL; /* not found (maybe unplugged?) */ } /* map_logical_device */ static const char *macosx_hidmanager_name(unsigned int index) { const MouseStruct *mouse = map_logical_device(index); return mouse ? mouse->name : NULL; } /* macosx_hidmanager_name */ static int macosx_hidmanager_poll(ManyMouseEvent *event) { /* ...favor existing events in the queue... */ if (dequeue_event(event)) return 1; /* pump runloop for new hardware events... */ while (CFRunLoopRunInMode(RUNLOOPMODE,0,TRUE)==kCFRunLoopRunHandledSource) /* no-op. We're filling our queue... !!! FIXME: stop if queue fills. */ ; return dequeue_event(event); /* see if anything had shown up... */ } /* macosx_hidmanager_poll */ static const ManyMouseDriver ManyMouseDriver_interface = { "Mac OS X 10.5+ HID Manager", macosx_hidmanager_init, macosx_hidmanager_quit, macosx_hidmanager_name, macosx_hidmanager_poll }; const ManyMouseDriver *ManyMouseDriver_hidmanager = &ManyMouseDriver_interface; #else const ManyMouseDriver *ManyMouseDriver_hidmanager = 0; #endif /* ifdef Mac OS X blocker */ /* end of macosx_hidmanager.c ... */