/* * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * 07 Jan 2002 ryepez * This class is based off IOHIPointing and handles * USB HID report based pointing devices */ #include #include #include #include #include #include "IOHIDPointing.h" #include "IOHIDKeys.h" #include "IOHIDElement.h" #define kMaxButtons 32 // Is this defined anywhere in the event headers? #define kMaxValues 32 // This should be plenty big to find the X, Y and wheel values - is there some absolute max? #define kDefaultFixedResolution (400 << 16) #define kDefaultScrollFixedResolution (10 << 16) #define super IOHIPointing OSDefineMetaClassAndStructors(IOHIDPointing, IOHIPointing); IOHIDPointing * IOHIDPointing::Pointing(OSArray * elements, IOHIDDevice * owner) { IOHIDPointing *nub = new IOHIDPointing; IOService *ownersProvider = owner->getProvider(); OSNumber *interfaceSubClass = 0; OSNumber *interfaceProtocol = 0; bool bootPointing = false; interfaceSubClass = OSDynamicCast(OSNumber, ((ownersProvider) ? ownersProvider->getProperty("bInterfaceSubClass") : 0)); interfaceProtocol = OSDynamicCast(OSNumber, ((ownersProvider) ? ownersProvider->getProperty("bInterfaceProtocol") : 0)); if (interfaceSubClass && interfaceProtocol) { bootPointing = ((interfaceSubClass->unsigned32BitValue() == 1) && (interfaceProtocol->unsigned32BitValue() == 2)); } if ((nub == 0) || !nub->init() || (!nub->findDesiredElements(elements) && !bootPointing)) { if (nub) nub->release(); return 0; } nub->_bootProtocol = bootPointing; return nub; } bool IOHIDPointing::init(OSDictionary * properties) { if (!super::init(properties)) return false; _numButtons = 1; _resolution = kDefaultFixedResolution; _scrollResolution = kDefaultScrollFixedResolution; _preparsedReportDescriptorData = NULL; _buttonType = 0; _buttonCollection = -1; _xAbsoluteCollection = -1; _yAbsoluteCollection = -1; _xRelativeCollection = -1; _yRelativeCollection = -1; _tipPressureCollection = -1; _digitizerButtonCollection = -1; _scrollWheelCollection = -1; _horzScrollCollection = -1; _absoluteCoordinates = false; _hasInRangeReport = false; _tipPressureMin = 255; _tipPressureMax = 255; _reportCount = 0; _cachedButtonState = 0; _bootProtocol = false; return true; } bool IOHIDPointing::findDesiredElements(OSArray *elements) { IOHIDElement *element; UInt32 usage, usagePage; UInt32 count; bool isPointing = false; if (!elements) return false; count = elements->getCount(); for (int i=0; igetObject(i); usagePage = element->getUsagePage(); usage = element->getUsage(); switch (usagePage) { case kHIDPage_GenericDesktop: if ((element->getElementType() == kIOHIDElementTypeCollection) && (element->getElementCollectionType() == kIOHIDElementCollectionTypeApplication) && (usage == kHIDUsage_GD_Mouse)) { isPointing = true; } break; default: break; // add future parsing below } } return (isPointing); } bool IOHIDPointing::start(IOService *provider) { IOMemoryDescriptor *descriptor; IOReturn ret; _provider = OSDynamicCast(IOHIDDevice, provider); if (!provider) return false; // push up properties from our provider propagateProperties(); // grab and parse the report descriptor ret = _provider->newReportDescriptor(&descriptor); if ((ret != kIOReturnSuccess) || !descriptor) { if (descriptor) descriptor->release(); return false; } ret = parseReportDescriptor(descriptor); descriptor->release(); if (ret != kIOReturnSuccess) return false; return super::start(provider); } void IOHIDPointing::free() { // maybe put in stop? if (_preparsedReportDescriptorData) { HIDCloseReportDescriptor(_preparsedReportDescriptorData); } //------ super::free(); } //--------------------------------------------------------------------------- // Parse a report descriptor and setup any relavent pointing data IOReturn IOHIDPointing::parseReportDescriptor( IOMemoryDescriptor * report, IOOptionBits options ) { OSStatus result; void * reportData; IOByteCount reportLength; IOByteCount segmentSize; IOReturn ret = kIOReturnSuccess; HIDButtonCapabilities buttonCaps[kMaxButtons]; UInt32 numButtonCaps = kMaxButtons; HIDValueCapabilities valueCaps[kMaxValues]; UInt32 numValueCaps = kMaxValues; int resIndex; reportData = report->getVirtualSegment(0, &segmentSize); reportLength = report->getLength(); if ( segmentSize != reportLength ) { reportData = IOMalloc( reportLength ); if ( reportData == 0 ) return kIOReturnNoMemory; report->readBytes( 0, reportData, reportLength ); } // Parse the report descriptor. result = HIDOpenReportDescriptor( reportData, /* report descriptor */ reportLength, /* report size in bytes */ &_preparsedReportDescriptorData, /* pre-parse data */ 0 ); /* flags */ if ( segmentSize != reportLength ) { IOFree( reportData, reportLength ); } if ( result != kHIDSuccess ) { return kIOReturnError; } do { _reportCount = (_preparsedReportDescriptorData) ? ((HIDPreparsedDataPtr)_preparsedReportDescriptorData)->reportCount : 0; numButtonCaps = kMaxButtons; result = HIDGetSpecificButtonCapabilities(kHIDInputReport, kHIDPage_Digitizer, 0, 0, buttonCaps, &numButtonCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numButtonCaps > 0)) { _digitizerButtonCollection = buttonCaps[0].collection; } numButtonCaps = kMaxButtons; result = HIDGetSpecificButtonCapabilities(kHIDInputReport, kHIDPage_Digitizer, 0, kHIDUsage_Dig_InRange, buttonCaps, &numButtonCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numButtonCaps > 0)) { _hasInRangeReport = true; } result = HIDGetSpecificValueCapabilities(kHIDInputReport, kHIDPage_GenericDesktop, 0, kHIDUsage_GD_X, valueCaps, &numValueCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numValueCaps > 0)) { // trudge through all the axis caps, if we find an // absolute axis, take it. Otherwise, take the first // relative axis seen. resIndex = -1; for (int i=0; i 0; i--) { physicalDiff *= 10; } } else { for (int i = 0x10 - resExponent; i > 0; i--) { logicalDiff *= 10; } } _resolution = (logicalDiff / physicalDiff) << 16; #if (DEBUGGING_LEVEL > 2) IOLog (" _resolution = %lx\n", _resolution); #endif // Before i added in the AppleUSBMouse::init function, resolution was called to calculate // the acceleration curves before we ever got to start, which in turn called parseHIDDescriptor // where the real resolution was calculated. In that event, if the resolution changed from the // default value, we would have to tell IOHIPointing to recalculate the curves based on the // new resolution. Per Adam Wang, we could call IOHIPointing::resetPointer() to do the // recalculation. Unfortunately IOHIPointing::resetPointer() is private and cannot be used // here. Rather than go back to IOHIPointing for an API change, since the new init function // seems to have us calling AppleUSBMouse::start first, we no longer need to deal with this. // (I am leaving this code snippet here as a reminder in case something changes and i don't // want to loose this arcane bit of knowledge.) // // if (_resolution != kDefaultFixedResolution) // { // resetPointer(); // } } } else { IOLog ("%s: error getting X axis information from HID report descriptor. err=0x%lx\n", getName(), result); ret = kIOReturnError; break; } if (_provider) { OSNumber *resolution = OSNumber::withNumber(_resolution, 32); if (resolution) _provider->setProperty(kIOHIDPointerResolutionKey, resolution); } numValueCaps = kMaxValues; result = HIDGetSpecificValueCapabilities(kHIDInputReport, kHIDPage_GenericDesktop, 0, kHIDUsage_GD_Y, valueCaps, &numValueCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numValueCaps > 0)) { // trudge through all the axis caps, if we find an // absolute axis, take it. Otherwise, take the first // relative axis seen. for (int i=0; i 0)) { _buttonCollection = buttonCaps[0].collection; // Do we actually need to look at and store all of the button page collections? if (_buttonCollection == _xRelativeCollection) _buttonType = kIOHIDPointingButtonRelative; else if (_buttonCollection == _xAbsoluteCollection) _buttonType = kIOHIDPointingButtonAbsolute; _numButtons = 0; for (int i=0; i 0)) { _tipPressureCollection = valueCaps[0].collection; _tipPressureMin = valueCaps[0].logicalMin; _tipPressureMax = valueCaps[0].logicalMax; if (_absoluteCoordinates) { setProperty("SupportsInk", 1, 32); } } numValueCaps = kMaxValues; result = HIDGetSpecificValueCapabilities(kHIDInputReport, kHIDPage_GenericDesktop, 0, kHIDUsage_GD_Wheel, valueCaps, &numValueCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numValueCaps > 0)) { _scrollWheelCollection = valueCaps[0].collection; // Check to see if this has a different resolution. (Only checking verticle scroll wheel.) // (Can use equation given in section 6.2.2.7 of the Device Class Definition for HID, v 1.1.) // _resolution = (logMax -logMin)/((physMax -physMin) * 10 ** exp) // If there is no physical min and max in HID descriptor, // cababilites calls set equal to logical min and max. // Keep default resolution if we don't have distinct physical min and max. if (valueCaps[0].physicalMin != valueCaps[0].logicalMin && valueCaps[0].physicalMax != valueCaps[0].logicalMax) { SInt32 logicalDiff = (valueCaps[0].logicalMax - valueCaps[0].logicalMin); SInt32 physicalDiff = (valueCaps[0].physicalMax - valueCaps[0].physicalMin); // Since IOFixedDivide truncated fractional part and can't use floating point // within the kernel, have to convert equation when using negative exponents: // _resolution = ((logMax -logMin) * 10 **(-exp))/(physMax -physMin) // Even though unitExponent is stored as SInt32, The real values are only // a signed nibble that doesn't expand to the full 32 bits. SInt32 resExponent = valueCaps[0].unitExponent & 0x0F; if (resExponent < 8) { for (int i = resExponent; i > 0; i--) { physicalDiff *= 10; } } else { for (int i = 0x10 - resExponent; i > 0; i--) { logicalDiff *= 10; } } _scrollResolution = (logicalDiff / physicalDiff) << 16; #if (DEBUGGING_LEVEL > 2) IOLog (" _scrollResolution = %lx\n", _scrollResolution); #endif // Before i added in the AppleUSBMouse::init function, resolution was called to calculate // the acceleration curves before we ever got to start, which in turn called parseHIDDescriptor // where the real resolution was calculated. In that event, if the resolution changed from the // default value, we would have to tell IOHIPointing to recalculate the curves based on the // new resolution. Per Adam Wang, we could call IOHIPointing::resetScroll() to do the // recalculation. Unfortunately IOHIPointing::resetScroll() is private and cannot be used // here. Rather than go back to IOHIPointing for an API change, since the new init function // seems to have us calling AppleUSBMouse::start first, we no longer need to deal with this. // (I am leaving this code snippet here as a reminder in case something changes and i don't // want to loose this arcane bit of knowledge.) // // if (_resolution != kDefaultScrollFixedResolution) // { // resetScroll(); // } } // Unfortunately, due to binary compatibility concerns, there is no virtual function // available to get the scrollResolution. To pass this value to our superclass, we // instead must set a property. OSNumber *scrollResolution = OSNumber::withNumber(_scrollResolution, 32); if (scrollResolution) setProperty(kIOHIDScrollResolutionKey, scrollResolution); } numValueCaps = kMaxValues; result = HIDGetSpecificValueCapabilities(kHIDInputReport, kHIDPage_GenericDesktop, 0, kHIDUsage_GD_Z, valueCaps, &numValueCaps, _preparsedReportDescriptorData); if ((result == noErr) && (numValueCaps > 0)) { _horzScrollCollection = valueCaps[0].collection; } } while (false); return ret; } //--------------------------------------------------------------------------- // Parse a pointing related report data and post events IOReturn IOHIDPointing::handleReport( IOMemoryDescriptor * report, IOOptionBits options) { OSStatus status; HIDUsage usageList[kMaxButtons]; UInt32 usageListSize = kMaxButtons; UInt32 reportID = 0; UInt32 bootOffset = 0; UInt32 buttonState = 0; SInt32 usageValue; SInt32 pressure = MAXPRESSURE; int adx = 0, ady = 0, rdx = 0, rdy = 0, scrollWheelDelta = 0, horzScrollDelta = 0; AbsoluteTime now; bool inRange = !_hasInRangeReport; UInt8 * mouseData; IOByteCount ret_bufsize; IOByteCount segmentSize; // Get a pointer to the data in the descriptor. mouseData = (UInt8 *)report->getVirtualSegment(0, &segmentSize); ret_bufsize = report->getLength(); if ( ret_bufsize == 0 ) return kIOReturnBadArgument; // Are there multiple segments in the descriptor? If so, // allocate a buffer and copy the data from the descriptor. if ( segmentSize != ret_bufsize ) { mouseData = (UInt8 *)IOMalloc( ret_bufsize ); if ( mouseData == 0 ) return kIOReturnNoMemory; report->readBytes( 0, mouseData, ret_bufsize ); } if (_buttonCollection != -1) { status = HIDGetButtonsOnPage (kHIDInputReport, kHIDPage_Button, _buttonCollection, usageList, &usageListSize, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { UInt32 usageNum; for (usageNum = 0; usageNum < usageListSize; usageNum++) { if (usageList[usageNum] <= kMaxButtons) { buttonState |= (1 << (usageList[usageNum] - 1)); } } } } else if ( _bootProtocol ) { bootOffset = 0; reportID = 0; if (_reportCount > 0) { bootOffset ++; reportID = mouseData[0]; } if ((reportID == 0) && (ret_bufsize > bootOffset)) buttonState = mouseData[bootOffset]; } if (_tipPressureCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_Digitizer, _tipPressureCollection, kHIDUsage_Dig_TipPressure, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { pressure = usageValue; } } if (_digitizerButtonCollection != -1) { usageListSize = kMaxButtons; status = HIDGetButtonsOnPage (kHIDInputReport, kHIDPage_Digitizer, _digitizerButtonCollection, usageList, &usageListSize, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { UInt32 usageNum; for (usageNum = 0; usageNum < usageListSize; usageNum++) { switch (usageList[usageNum]) { case kHIDUsage_Dig_BarrelSwitch: buttonState |= 2; // Set the right (secondary) button for the barrel switch break; case kHIDUsage_Dig_TipSwitch: buttonState |= 1; // Set the left (primary) button for the tip switch break; case kHIDUsage_Dig_InRange: inRange = 1; break; default: break; } } } } if (_scrollWheelCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _scrollWheelCollection, kHIDUsage_GD_Wheel, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { scrollWheelDelta = usageValue; } } if (_horzScrollCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _horzScrollCollection, kHIDUsage_GD_Z, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { horzScrollDelta = usageValue; } } if (_xAbsoluteCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _xAbsoluteCollection, kHIDUsage_GD_X, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { adx = usageValue; } } if (_yAbsoluteCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _yAbsoluteCollection, kHIDUsage_GD_Y, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { ady = usageValue; } } if (_xRelativeCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _xRelativeCollection, kHIDUsage_GD_X, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { rdx = usageValue; } } if (_yRelativeCollection != -1) { status = HIDGetUsageValue (kHIDInputReport, kHIDPage_GenericDesktop, _yRelativeCollection, kHIDUsage_GD_Y, &usageValue, _preparsedReportDescriptorData, mouseData, ret_bufsize); if (status == noErr) { rdy = usageValue; } } if (_bootProtocol && (_xRelativeCollection == -1) && (_xAbsoluteCollection == -1)) { bootOffset = 1; reportID = 0; if (_reportCount > 0) { bootOffset ++; reportID = mouseData[0]; } if ((reportID == 0) && (ret_bufsize > bootOffset)) rdx = mouseData[bootOffset]; } if (_bootProtocol && (_yRelativeCollection == -1) && (_yAbsoluteCollection == -1)) { bootOffset = 2; reportID=0; if (_reportCount > 0) { bootOffset ++; reportID = mouseData[0]; } if ((reportID == 0) && (ret_bufsize > bootOffset)) rdy = mouseData[bootOffset]; } clock_get_uptime(&now); if (_absoluteCoordinates && !rdx && !rdy && !((buttonState != _cachedButtonState) && (_buttonType == kIOHIDPointingButtonRelative))) { Point newLoc; newLoc.x = adx; newLoc.y = ady; dispatchAbsolutePointerEvent(&newLoc, &_bounds, buttonState, inRange, pressure, _tipPressureMin, _tipPressureMax, 90, now); } else if (rdx || rdy || (buttonState != _cachedButtonState)) { dispatchRelativePointerEvent(rdx, rdy, buttonState, now); } _cachedButtonState = buttonState; if (scrollWheelDelta != 0 || horzScrollDelta != 0) { dispatchScrollWheelEvent(scrollWheelDelta, horzScrollDelta, 0, now); } return kIOReturnSuccess; } // subclasses override IOItemCount IOHIDPointing::buttonCount() { return _numButtons; } IOFixed IOHIDPointing::resolution() { return _resolution; } void IOHIDPointing::propagateProperties() { OSData *data = NULL; if (_provider) { data = OSDynamicCast( OSData, _provider->getProperty( kIOHIDPointerAccelerationTableKey )); if (data) setProperty(kIOHIDPointerAccelerationTableKey, data); data = OSDynamicCast( OSData, _provider->getProperty( kIOHIDScrollAccelerationTableKey )); if (data) setProperty(kIOHIDScrollAccelerationTableKey, data); } }