IOUserHIDEventDriver.cpp [plain text]
#include <assert.h>
#include <AssertMacros.h>
#include <stdio.h>
#include <stdlib.h>
#include <DriverKit/DriverKit.h>
#include <DriverKit/OSCollections.h>
#include <DriverKit/IOBufferMemoryDescriptor.h>
#include <HIDDriverKit/HIDDriverKit_Private.h>
#define DEFAULT_TIP_THRESHOLD 75
struct IOUserHIDEventDriver_IVars
{
OSArray *elements;
struct {
OSArray *elements;
} keyboard;
struct {
OSArray *relative;
OSArray *absolute;
} pointer;
struct {
OSArray *elements;
} scroll;
struct {
OSArray *elements;
} led;
struct {
OSArray *collections;
IOHIDElement *relativeScanTime;
} digitizer;
};
#define _elements ivars->elements
#define _keyboard ivars->keyboard
#define _pointer ivars->pointer
#define _scroll ivars->scroll
#define _led ivars->led
#define _digitizer ivars->digitizer
#define kHIDUsage_MaxUsage 0xFFFF
#undef super
#define super IOUserHIDEventService
static IOFixed getFixedValue(uint32_t value, uint32_t unit, uint32_t exponent)
{
int64_t orgValue = (int64_t)value;
IOFixed returnValue = 0;
uint32_t numExp = 1;
uint32_t denomExp = 1;
int resExponent = exponent & 0x0F;
switch (unit) {
case 0x11:
orgValue = orgValue * 10;
break;
default: {
return (IOFixed)orgValue;
}
}
if (resExponent < 8) {
for (int i = resExponent; i > 0; i--) {
numExp *= 10;
}
} else {
for (int i = 0x10 - resExponent; i > 0; i--) {
denomExp *= 10;
}
}
returnValue = (IOFixed)(((orgValue << 16) / denomExp) * numExp);
return returnValue;
}
bool IOUserHIDEventDriver::init()
{
bool result = false;
result = super::init();
require_action(result, exit, HIDLogError("Init:%x", result));
assert(IOService::ivars);
ivars = IONewZero(IOUserHIDEventDriver_IVars, 1);
require(ivars, exit);
exit:
return result;
}
void IOUserHIDEventDriver::free()
{
if (ivars) {
OSSafeReleaseNULL(_elements);
OSSafeReleaseNULL(_keyboard.elements);
OSSafeReleaseNULL(_pointer.relative);
OSSafeReleaseNULL(_pointer.absolute);
OSSafeReleaseNULL(_scroll.elements);
OSSafeReleaseNULL(_led.elements);
OSSafeReleaseNULL(_digitizer.collections);
OSSafeReleaseNULL(_digitizer.relativeScanTime);
}
IOSafeDeleteNULL(ivars, IOUserHIDEventDriver_IVars, 1);
super::free();
}
kern_return_t
IMPL(IOUserHIDEventDriver, Start)
{
kern_return_t ret = kIOReturnError;
bool result = false;
ret = Start(provider, SUPERDISPATCH);
require_noerr_action(ret, exit, HIDServiceLogError("Start failed: 0x%x", ret));
_elements = getElements();
require_action(_elements, exit, HIDServiceLogError("Failed to get elements"));
_elements->retain();
require_action(parseElements(_elements), exit, HIDServiceLogError("No supported elements found"));
result = true;
exit:
if (!result) {
HIDServiceLogError("Start failed: 0x%x", ret);
ret = kIOReturnError;
}
return ret;
}
bool IOUserHIDEventDriver::parseElements(OSArray *elements)
{
bool result = false;
OSArray *buttonElements = NULL;
for (unsigned int i = 0; i < elements->getCount(); i++) {
IOHIDElement *element = (IOHIDElement *)elements->getObject(i);
if (element->getType() == kIOHIDElementTypeCollection ||
!element->getUsage()) {
continue;
}
if (parseKeyboardElement(element) ||
parseDigitizerElement(element) ||
parsePointerElement(element) ||
parseScrollElement(element) ||
parseLEDElement(element)) {
result = true;
continue;
}
if (element->getUsagePage() == kHIDPage_Button) {
if (!buttonElements) {
buttonElements = OSArray::withCapacity(4);
require_action(buttonElements, exit, result = false);
}
buttonElements->setObject(element);
}
}
if (buttonElements) {
buttonElements->iterateObjects(^bool(OSObject *object) {
if (_pointer.relative) {
_pointer.relative->setObject(object);
} else if (_pointer.absolute) {
_pointer.absolute->setObject(object);
} else {
if (!_pointer.relative) {
_pointer.relative = OSArray::withCapacity(4);
}
if (_pointer.relative) {
_pointer.relative->setObject(object);
}
}
return false;
});
}
if (_led.elements) {
for (int index = 0; index < _led.elements->getCount(); ++index) {
IOHIDElement *element = (IOHIDElement *)_led.elements->getObject(index);
IOReturn status;
element->setValue(0);
status = element->commit(kIOHIDElementCommitDirectionOut);
HIDServiceLog("Set LED 0x%x: 0 0x%x", element->getUsage(), status);
}
}
HIDServiceLog("parseElements: keyboard: %d digitizer: %d pointer: %d %d scroll: %d led: %d",
_keyboard.elements ? _keyboard.elements->getCount() : 0,
_digitizer.collections ? _digitizer.collections->getCount() : 0,
_pointer.relative ? _pointer.relative->getCount() : 0,
_pointer.absolute ? _pointer.absolute->getCount() : 0,
_scroll.elements ? _scroll.elements->getCount() : 0,
_led.elements ? _led.elements->getCount() : 0);
setAccelerationProperties();
setSurfaceDimensions();
exit:
OSSafeReleaseNULL(buttonElements);
return result;
}
void IOUserHIDEventDriver::setAccelerationProperties()
{
OSDictionaryPtr properties = OSDictionaryCreate();
require(properties, exit);
if (conformsTo(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) {
OSDictionarySetStringValue(properties,
kIOHIDPointerAccelerationTypeKey,
kIOHIDMouseAccelerationTypeKey);
if (_scroll.elements) {
OSDictionarySetStringValue(properties,
kIOHIDScrollAccelerationTypeKey,
kIOHIDMouseScrollAccelerationKey);
}
} else if (conformsTo(kHIDPage_GenericDesktop, kHIDUsage_GD_Pointer)) {
OSDictionarySetStringValue(properties,
kIOHIDPointerAccelerationTypeKey,
kIOHIDPointerAccelerationKey);
if (_scroll.elements) {
OSDictionarySetStringValue(properties,
kIOHIDScrollAccelerationTypeKey,
kIOHIDScrollAccelerationKey);
}
}
if (OSDictionaryGetCount(properties)) {
kern_return_t ret = SetProperties(properties);
if (ret != kIOReturnSuccess) {
HIDServiceLogError("Failed to set acceleration properties: 0x%x", ret);
}
}
exit:
OSSafeReleaseNULL(properties);
}
void IOUserHIDEventDriver::setSurfaceDimensions()
{
OSDictionaryPtr dimensions = NULL;
bool (^elementInterator)(OSObject *object);
require_quiet(_pointer.absolute || _digitizer.collections, exit);
dimensions = OSDictionaryCreate();
require(dimensions, exit);
elementInterator = ^bool(OSObject *object) {
IOHIDElement *element = OSDynamicCast(IOHIDElement, object);
uint32_t usagePage, usage, pDelta;
if (!element) {
return false;
}
usagePage = element->getUsagePage();
usage = element->getUsage();
pDelta = getFixedValue(element->getPhysicalMax() - element->getPhysicalMin(), element->getUnit(), element->getUnitExponent());
if (usagePage != kHIDPage_GenericDesktop) {
return false;
}
if (usage != kHIDUsage_GD_X && usage != kHIDUsage_GD_Y) {
return false;
}
if ((element->getFlags() & kIOHIDElementFlagsRelativeMask) != 0) {
return false;
}
if (usage == kHIDUsage_GD_X) {
OSDictionarySetUInt64Value(dimensions, kIOHIDWidthKey, pDelta);
} else if (usage == kHIDUsage_GD_Y) {
OSDictionarySetUInt64Value(dimensions, kIOHIDHeightKey, pDelta);
}
return false;
};
if (_pointer.absolute) {
_pointer.absolute->iterateObjects(elementInterator);
}
require_quiet(!OSDictionaryGetCount(dimensions) && _digitizer.collections, exit);
_digitizer.collections->iterateObjects(^bool(OSObject *object) {
IOHIDDigitizerCollection *collection = OSDynamicCast(IOHIDDigitizerCollection, object);
OSArray *elements = NULL;
if (!collection) {
return false;
}
elements = collection->getElements();
if (!elements) {
return false;
}
elements->iterateObjects(elementInterator);
if (OSDictionaryGetCount(dimensions)) {
return true;
} else {
return false;
}
});
exit:
if (dimensions && OSDictionaryGetCount(dimensions)) {
OSDictionaryPtr properties = OSDictionaryCreate();
OSDictionarySetValue(properties, kIOHIDSurfaceDimensionsKey, dimensions);
kern_return_t ret = SetProperties(properties);
if (ret != kIOReturnSuccess) {
HIDServiceLogError("Failed to set surface dimensions: 0x%x", ret);
}
properties->release();
}
OSSafeReleaseNULL(dimensions);
}
bool IOUserHIDEventDriver::parseKeyboardElement(IOHIDElement *element)
{
bool result = false;
uint32_t usagePage = element->getUsagePage();
uint32_t usage = element->getUsage();
require(usage <= kHIDUsage_MaxUsage, exit);
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_Start:
case kHIDUsage_GD_Select:
case kHIDUsage_GD_SystemPowerDown:
case kHIDUsage_GD_SystemSleep:
case kHIDUsage_GD_SystemWakeUp:
case kHIDUsage_GD_SystemContextMenu:
case kHIDUsage_GD_SystemMainMenu:
case kHIDUsage_GD_SystemAppMenu:
case kHIDUsage_GD_SystemMenuHelp:
case kHIDUsage_GD_SystemMenuExit:
case kHIDUsage_GD_SystemMenuSelect:
case kHIDUsage_GD_SystemMenuRight:
case kHIDUsage_GD_SystemMenuLeft:
case kHIDUsage_GD_SystemMenuUp:
case kHIDUsage_GD_SystemMenuDown:
case kHIDUsage_GD_DPadUp:
case kHIDUsage_GD_DPadDown:
case kHIDUsage_GD_DPadRight:
case kHIDUsage_GD_DPadLeft:
case kHIDUsage_GD_DoNotDisturb:
result = true;
break;
}
break;
case kHIDPage_KeyboardOrKeypad:
if (usage < kHIDUsage_KeyboardA ||
usage > kHIDUsage_KeyboardRightGUI) {
break;
}
case kHIDPage_Consumer:
if (usage != kHIDUsage_Csmr_ACPan) {
result = true;
}
break;
case kHIDPage_Telephony:
case kHIDPage_CameraControl:
result = true;
break;
default:
break;
}
require_quiet(result, exit);
if (!_keyboard.elements) {
_keyboard.elements = OSArray::withCapacity(4);
require(_keyboard.elements, exit);
}
_keyboard.elements->setObject(element);
exit:
return result;
}
bool IOUserHIDEventDriver::parsePointerElement(IOHIDElement *element)
{
bool result = false;
uint32_t usagePage = element->getUsagePage();
uint32_t usage = element->getUsage();
bool absolute = false;
require(usage <= kHIDUsage_MaxUsage, exit);
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
if (!(element->getFlags() & kIOHIDElementFlagsRelativeMask)) {
IOHIDElementPrivate *priv = (IOHIDElementPrivate *)element;
priv->setCalibration(0, 1, element->getLogicalMin(), element->getLogicalMax(), 0, 0, 0);
absolute = true;
}
result = true;
break;
}
break;
default:
break;
}
require_quiet(result, exit);
if (absolute) {
if (!_pointer.absolute) {
_pointer.absolute = OSArray::withCapacity(4);
require(_pointer.absolute, exit);
}
_pointer.absolute->setObject(element);
} else {
if (!_pointer.relative) {
_pointer.relative = OSArray::withCapacity(4);
require(_pointer.relative, exit);
}
_pointer.relative->setObject(element);
}
exit:
return result;
}
bool IOUserHIDEventDriver::parseScrollElement(IOHIDElement *element)
{
bool result = false;
uint32_t usagePage = element->getUsagePage();
uint32_t usage = element->getUsage();
require(usage <= kHIDUsage_MaxUsage, exit);
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_Dial:
case kHIDUsage_GD_Wheel:
case kHIDUsage_GD_Z:
result = true;
break;
}
break;
case kHIDPage_Consumer:
switch (usage) {
case kHIDUsage_Csmr_ACPan:
result = true;
break;
}
break;
}
require_quiet(result, exit);
if (!_scroll.elements) {
_scroll.elements = OSArray::withCapacity(4);
require(_scroll.elements, exit);
}
_scroll.elements->setObject(element);
exit:
return result;
}
bool IOUserHIDEventDriver::parseLEDElement(IOHIDElement *element)
{
uint32_t usagePage = element->getUsagePage();
uint32_t usage = element->getUsage();
bool result = false;
require(usage <= kHIDUsage_MaxUsage, exit);
switch (usagePage) {
case kHIDPage_LEDs:
result = true;
break;
}
require(result, exit);
if (!_led.elements) {
_led.elements = OSArray::withCapacity(4);
require(_led.elements, exit);
}
_led.elements->setObject(element);
exit:
return result;
}
void IOUserHIDEventDriver::setTipThreshold()
{
OSDictionaryPtr props = NULL;
OSDictionaryPtr newProps = NULL;
kern_return_t ret;
CopyProperties(&props);
if (props) {
require(!OSDictionaryGetValue(props, kIOHIDDigitizerTipThresholdKey), exit);
}
newProps = OSDictionaryCreate();
require(newProps, exit);
OSDictionarySetUInt64Value(newProps,
kIOHIDDigitizerTipThresholdKey,
DEFAULT_TIP_THRESHOLD);
ret = SetProperties(newProps);
if (ret != kIOReturnSuccess) {
HIDServiceLogError("Failed to set tip threshold: 0x%x", ret);
}
exit:
OSSafeReleaseNULL(props);
OSSafeReleaseNULL(newProps);
}
bool IOUserHIDEventDriver::parseDigitizerElement(IOHIDElement *element)
{
bool result = false;
IOHIDElement *parent = element;
IOHIDDigitizerCollection *collection = NULL;
require(element->getType() <= kIOHIDElementTypeInput_ScanCodes, exit);
while ((parent = parent->getParentElement())) {
IOHIDElementCollectionType collectionType = parent->getCollectionType();
uint32_t usagePage = parent->getUsagePage();
uint32_t usage = parent->getUsage();
if (usagePage != kHIDPage_Digitizer) {
continue;
}
if (collectionType == kIOHIDElementCollectionTypeLogical ||
collectionType == kIOHIDElementCollectionTypePhysical) {
if (usage >= kHIDUsage_Dig_Stylus &&
usage <= kHIDUsage_Dig_GestureCharacter) {
break;
}
} else if (collectionType == kIOHIDElementCollectionTypeApplication) {
if (usage >= kHIDUsage_Dig_Digitizer &&
usage <= kHIDUsage_Dig_DeviceConfiguration) {
break;
}
}
}
if (element->getUsagePage() == kHIDPage_Digitizer && element->getUsage() == kHIDUsage_Dig_RelativeScanTime) {
OSSafeReleaseNULL(_digitizer.relativeScanTime);
element->retain();
_digitizer.relativeScanTime = element;
}
require(parent, exit);
switch (element->getUsagePage()) {
case kHIDPage_GenericDesktop:
switch (element->getUsage()) {
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
case kHIDUsage_GD_Z:
require_quiet((element->getFlags() & kIOHIDElementFlagsRelativeMask) == 0, exit);
IOHIDElementPrivate *priv = (IOHIDElementPrivate *)element;
priv->setCalibration(0, 1, element->getLogicalMin(), element->getLogicalMax(), 0, 0, 0);
break;
}
break;
case kHIDPage_Digitizer:
switch (element->getUsage()) {
case kHIDUsage_Dig_TipPressure:
setTipThreshold();
case kHIDUsage_Dig_BarrelPressure:
IOHIDElementPrivate *priv = (IOHIDElementPrivate *)element;
priv->setCalibration(0, 1, element->getLogicalMin(), element->getLogicalMax(), 0, 0, 0);
break;
}
break;
}
if (!_digitizer.collections) {
_digitizer.collections = OSArray::withCapacity(4);
require(_digitizer.collections, exit);
}
for (unsigned int i = 0; i < _digitizer.collections->getCount(); i++) {
IOHIDDigitizerCollection *tmp = OSDynamicCast(IOHIDDigitizerCollection,
_digitizer.collections->getObject(i));
if (!tmp) {
continue;
}
if (tmp->getParentCollection() == parent) {
collection = tmp;
break;
}
}
if (!collection) {
IOHIDDigitizerCollectionType type = kIOHIDDigitizerCollectionTypeStylus;
switch (parent->getUsage()) {
case kHIDUsage_Dig_Puck:
type = kIOHIDDigitizerCollectionTypePuck;
break;
case kHIDUsage_Dig_Finger:
case kHIDUsage_Dig_TouchScreen:
case kHIDUsage_Dig_TouchPad:
type = kIOHIDDigitizerCollectionTypeFinger;
break;
default:
break;
}
collection = IOHIDDigitizerCollection::withType(type, parent);
require(collection, exit);
_digitizer.collections->setObject(collection);
collection->release();
}
collection->addElement(element);
result = true;
exit:
return result;
}
void IOUserHIDEventDriver::handleReport(uint64_t timestamp,
uint8_t *report __unused,
uint32_t reportLength __unused,
IOHIDReportType type,
uint32_t reportID)
{
handleKeyboardReport(timestamp, reportID);
handleRelativePointerReport(timestamp, reportID);
handleAbsolutePointerReport(timestamp, reportID);
handleScrollReport(timestamp, reportID);
handleDigitizerReport(timestamp, reportID);
}
void IOUserHIDEventDriver::handleKeyboardReport(uint64_t timestamp,
uint32_t reportID)
{
require_quiet(_keyboard.elements, exit);
for (unsigned int i = 0; i < _keyboard.elements->getCount(); i++) {
IOHIDElementPrivate *element = NULL;
uint64_t elementTimeStamp;
uint32_t usagePage, usage, value, preValue;
element = (IOHIDElementPrivate *)_keyboard.elements->getObject(i);
if (element->getReportID() != reportID) {
continue;
}
elementTimeStamp = element->getTimeStamp();
if (timestamp != elementTimeStamp) {
continue;
}
preValue = element->getValue(kIOHIDValueOptionsFlagPrevious) != 0;
value = element->getValue() != 0;
if (value == preValue) {
continue;
}
usagePage = element->getUsagePage();
usage = element->getUsage();
dispatchKeyboardEvent(timestamp, usagePage, usage, value, 0, true);
}
exit:
return;
}
static inline void setButtonState(uint32_t *state, uint32_t bit, uint32_t value)
{
uint32_t buttonMask = (1 << bit);
if (value != 0) {
(*state) |= buttonMask;
} else {
(*state) &= ~buttonMask;
}
}
void IOUserHIDEventDriver::handleRelativePointerReport(uint64_t timestamp,
uint32_t reportID)
{
bool handled = false;
IOFixed x = 0;
IOFixed y = 0;
uint32_t buttonState = 0;
require_quiet(_pointer.relative, exit);
for (unsigned int i = 0; i < _pointer.relative->getCount(); i++) {
IOHIDElementPrivate *element;
uint64_t elementTimeStamp;
uint32_t usagePage, usage;
bool elementIsCurrent;
element = (IOHIDElementPrivate *)_pointer.relative->getObject(i);
elementTimeStamp = element->getTimeStamp();
elementIsCurrent = (element->getReportID()==reportID) && (timestamp == elementTimeStamp);
handled |= elementIsCurrent;
usagePage = element->getUsagePage();
usage = element->getUsage();
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
x = elementIsCurrent ? element->getValue() << 16 : 0;
break;
case kHIDUsage_GD_Y:
y = elementIsCurrent ? element->getValue() << 16 : 0;
break;
}
break;
case kHIDPage_Button:
setButtonState(&buttonState, (usage - 1), element->getValue());
break;
}
}
require_quiet(handled, exit);
dispatchRelativePointerEvent(timestamp, x, y, buttonState, 0, true);
exit:
return;
}
void IOUserHIDEventDriver::handleAbsolutePointerReport(uint64_t timestamp,
uint32_t reportID)
{
bool handled = false;
IOFixed x = 0;
IOFixed y = 0;
uint32_t buttonState = 0;
require_quiet(_pointer.absolute, exit);
for (unsigned int i = 0; i < _pointer.absolute->getCount(); i++) {
IOHIDElementPrivate *element;
uint64_t elementTimeStamp;
uint32_t usagePage, usage;
bool elementIsCurrent;
element = (IOHIDElementPrivate *)_pointer.absolute->getObject(i);
elementTimeStamp = element->getTimeStamp();
elementIsCurrent = (element->getReportID()==reportID) && (timestamp == elementTimeStamp);
handled |= elementIsCurrent;
usagePage = element->getUsagePage();
usage = element->getUsage();
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
x = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
break;
case kHIDUsage_GD_Y:
y = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
break;
}
break;
case kHIDPage_Button:
setButtonState(&buttonState, (usage - 1), element->getValue());
break;
}
}
require_quiet(handled, exit);
dispatchAbsolutePointerEvent(timestamp, x, y, buttonState, 0, true);
exit:
return;
}
void IOUserHIDEventDriver::handleScrollReport(uint64_t timestamp,
uint32_t reportID)
{
IOFixed scrollVert = 0;
IOFixed scrollHoriz = 0;
require_quiet(_scroll.elements, exit);
for (unsigned int i = 0; i < _scroll.elements->getCount(); i++) {
IOHIDElementPrivate *element;
uint64_t elementTimeStamp;
uint32_t usagePage, usage;
element = (IOHIDElementPrivate *)_scroll.elements->getObject(i);
if (element->getReportID() != reportID) {
continue;
}
elementTimeStamp = element->getTimeStamp();
if (timestamp != elementTimeStamp) {
continue;
}
usagePage = element->getUsagePage();
usage = element->getUsage();
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_Wheel:
case kHIDUsage_GD_Dial:
if (element->getFlags() & kIOHIDElementFlagsWrapMask) {
scrollVert = element->getValue(kIOHIDValueOptionsFlagRelativeSimple);
} else {
scrollVert = element->getValue();
}
break;
case kHIDUsage_GD_Z:
if (element->getFlags() & kIOHIDElementFlagsWrapMask) {
scrollHoriz = element->getValue(kIOHIDValueOptionsFlagRelativeSimple);
} else {
scrollHoriz = element->getValue();
}
break;
default:
break;
}
break;
case kHIDPage_Consumer:
switch (usage) {
case kHIDUsage_Csmr_ACPan:
scrollHoriz = (-element->getValue());
break;
default:
break;
}
break;
default:
break;
}
}
require_quiet(scrollVert || scrollHoriz, exit);
dispatchRelativeScrollWheelEvent(timestamp, scrollVert << 16, scrollHoriz << 16, 0, 0, true);
exit:
return;
}
void IOUserHIDEventDriver::handleDigitizerReport(uint64_t timestamp,
uint32_t reportID)
{
IOHIDEvent *collectionEvent = NULL;
bool touch = false;
bool range = false;
uint32_t fingerCount = 0;
uint32_t eventMask = 0;
uint32_t buttonMask = 0;
IOFixed touchX = 0;
IOFixed touchY = 0;
IOFixed inRangeX = 0;
IOFixed inRangeY = 0;
IOFixed centroidX = 0;
IOFixed centroidY = 0;
uint32_t touchCount = 0;
uint32_t inRangeCount = 0;
IOHIDEvent *scanTimeEvent = NULL;
OSData *scanTimeValue = NULL;
require_quiet(_digitizer.collections, exit);
for (unsigned int i = 0; i < _digitizer.collections->getCount(); i++) {
IOHIDDigitizerCollection *collection = OSDynamicCast(IOHIDDigitizerCollection,
_digitizer.collections->getObject(i));
IOHIDEvent *event = NULL;
bool eventTouch, eventInRange;
if (!collection) {
continue;
}
event = createEventForDigitizerCollection(collection, timestamp, reportID);
if (!event) {
continue;
}
if (!collectionEvent) {
collectionEvent = IOHIDEvent::withType(kIOHIDEventTypeDigitizer,
timestamp,
0,
kIOHIDEventOptionIsAbsolute);
require_action(collectionEvent, exit, event->release());
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerCollection, true);
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerType, collection->getType());
}
eventTouch = event->getIntegerValue(kIOHIDEventFieldDigitizerTouch) ? true : false;
if (eventTouch) {
touchCount++;
touchX += event->getFixedValue(kIOHIDEventFieldDigitizerX);
touchY += event->getFixedValue(kIOHIDEventFieldDigitizerY);
}
eventInRange = event->getIntegerValue(kIOHIDEventFieldDigitizerRange) ? true : false;
if (eventInRange) {
inRangeCount++;
inRangeX += event->getFixedValue(kIOHIDEventFieldDigitizerX);
inRangeY += event->getFixedValue(kIOHIDEventFieldDigitizerY);
}
touch |= eventTouch;
range |= eventInRange;
eventMask |= event->getIntegerValue(kIOHIDEventFieldDigitizerEventMask);
buttonMask |= event->getIntegerValue(kIOHIDEventFieldDigitizerButtonMask);
if (event->getIntegerValue(kIOHIDEventFieldDigitizerType) == kIOHIDDigitizerCollectionTypeFinger) {
fingerCount++;
}
collectionEvent->appendChild(event);
event->release();
}
require(collectionEvent, exit);
if (collectionEvent && _digitizer.relativeScanTime) {
scanTimeValue = _digitizer.relativeScanTime->getDataValue(0);
if (scanTimeValue && scanTimeValue->getLength()) {
scanTimeEvent = IOHIDEvent::vendorDefinedEvent( timestamp, kHIDPage_Digitizer, kHIDUsage_Dig_RelativeScanTime, 0, (uint8_t *)scanTimeValue->getBytesNoCopy(), scanTimeValue->getLength(), 0);
if (scanTimeEvent) {
collectionEvent->appendChild(scanTimeEvent);
scanTimeEvent->release();
}
}
}
if (touchCount) {
centroidX = IOFixedDivide(touchX, touchCount << 16);
centroidY = IOFixedDivide(touchY, touchCount << 16);
} else if (inRangeCount) {
centroidX = IOFixedDivide(inRangeX, inRangeCount << 16);
centroidY = IOFixedDivide(inRangeY, inRangeCount << 16);
}
collectionEvent->setFixedValue(kIOHIDEventFieldDigitizerX, centroidX);
collectionEvent->setFixedValue(kIOHIDEventFieldDigitizerY, centroidY);
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerRange, range);
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerEventMask, eventMask);
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerTouch, touch);
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerButtonMask, buttonMask);
if (fingerCount > 1) {
collectionEvent->setIntegerValue(kIOHIDEventFieldDigitizerType, kIOHIDDigitizerCollectionTypeHand);
}
dispatchEvent(collectionEvent);
exit:
OSSafeReleaseNULL(collectionEvent);
}
IOHIDEvent *IOUserHIDEventDriver::createEventForDigitizerCollection(IOHIDDigitizerCollection *collection,
uint64_t timestamp,
uint32_t reportID)
{
IOHIDEvent *event = NULL;
OSArray *elements = collection->getElements();
bool handled = false;
bool inRange = true;
bool invert = false;
bool touch = false;
bool unTouch = false;
bool isFinger = false;
IOFixed x = 0;
IOFixed y = 0;
IOFixed z = 0;
IOFixed tipPressure = 0;
IOFixed barrelPressure = 0;
IOFixed tiltX = 0;
IOFixed tiltY = 0;
IOFixed twist = 0;
uint32_t buttonState = 0;
uint32_t transducerID = reportID;
uint32_t eventMask = 0;
uint32_t eventOptions = kIOHIDEventOptionIsAbsolute;
bool hasInRangeUsage = false;
IOHIDDigitizerTransducerType transducerType = collection->getType();
require(elements, exit);
for (unsigned int i = 0; i < elements->getCount(); i++) {
IOHIDElement *element = OSDynamicCast(IOHIDElement, elements->getObject(i));
uint64_t elementTimeStamp;
uint32_t usagePage, usage, value;
bool elementIsCurrent;
if (!element) {
continue;
}
elementTimeStamp = element->getTimeStamp();
elementIsCurrent = (element->getReportID()==reportID) && (timestamp == elementTimeStamp);
usagePage = element->getUsagePage();
usage = element->getUsage();
value = element->getValue(0);
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
x = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
handled |= elementIsCurrent;
break;
case kHIDUsage_GD_Y:
y = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
handled |= elementIsCurrent;
break;
case kHIDUsage_GD_Z:
z = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
handled |= elementIsCurrent;
break;
}
break;
case kHIDPage_Button:
setButtonState(&buttonState, (usage - 1), value);
handled |= (elementIsCurrent | (buttonState != 0));
break;
case kHIDPage_Digitizer:
switch (usage) {
case kHIDUsage_Dig_TransducerIndex:
case kHIDUsage_Dig_ContactIdentifier:
transducerID = value;
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_Untouch:
unTouch = value!=0;
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_Touch:
case kHIDUsage_Dig_TipSwitch:
touch = value != 0;
handled |= (elementIsCurrent | (touch != 0));
break;
case kHIDUsage_Dig_BarrelSwitch:
setButtonState(&buttonState, 1, value);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_Eraser:
setButtonState(&buttonState, 2, value);
invert = value != 0;
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_InRange:
inRange = value != 0;
handled |= elementIsCurrent;
hasInRangeUsage = true;
break;
case kHIDUsage_Dig_BarrelPressure:
barrelPressure = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_TipPressure:
tipPressure = element->getScaledFixedValue(kIOHIDValueScaleTypeCalibrated);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_XTilt:
tiltX = element->getScaledFixedValue(kIOHIDValueScaleTypePhysical);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_YTilt:
tiltY = element->getScaledFixedValue(kIOHIDValueScaleTypePhysical);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_Twist:
twist = element->getScaledFixedValue(kIOHIDValueScaleTypePhysical);
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_Invert:
invert = value != 0;
handled |= elementIsCurrent;
break;
case kHIDUsage_Dig_TouchValid:
handled |= elementIsCurrent;
if (value == 1) {
isFinger = true;
}
break;
default:
break;
}
break;
}
}
require(handled, exit);
if (invert) {
eventOptions |= kIOHIDTransducerInvert;
}
if ( tipPressure ) {
setButtonState ( &buttonState, 0, tipPressure);
}
if (transducerType == kIOHIDDigitizerCollectionTypeFinger || transducerType == kIOHIDDigitizerCollectionTypeHand) {
transducerType = isFinger ? kIOHIDDigitizerCollectionTypeFinger : kIOHIDDigitizerCollectionTypeHand;
}
if (hasInRangeUsage == false && (unTouch || touch == 0)) {
inRange = false;
}
event = IOHIDEvent::withType(kIOHIDEventTypeDigitizer,
timestamp,
0,
eventOptions);
require(event, exit);
if (touch != collection->getTouch()) {
eventMask |= kIOHIDDigitizerEventTouch;
}
if (touch & unTouch & 1) {
eventMask |= kIOHIDDigitizerEventCancel;
}
if (inRange) {
if (collection->getX() != x ||
collection->getY() != y ||
collection->getZ() != z) {
eventMask |= kIOHIDDigitizerEventPosition;
}
}
if (inRange != collection->getInRange()) {
eventMask |= kIOHIDDigitizerEventRange;
}
if (collection->getTouch() == touch && touch == 0 && inRange == false) {
event = NULL;
} else {
event->setIntegerValue(kIOHIDEventFieldDigitizerEventMask, eventMask);
event->setIntegerValue(kIOHIDEventFieldDigitizerRange, inRange);
event->setIntegerValue(kIOHIDEventFieldDigitizerTouch, touch);
event->setIntegerValue(kIOHIDEventFieldDigitizerIndex, transducerID);
event->setIntegerValue(kIOHIDEventFieldDigitizerType, transducerType);
event->setIntegerValue(kIOHIDEventFieldDigitizerButtonMask, buttonState);
event->setFixedValue(kIOHIDEventFieldDigitizerX, x);
event->setFixedValue(kIOHIDEventFieldDigitizerY, y);
event->setFixedValue(kIOHIDEventFieldDigitizerZ, z);
event->setFixedValue(kIOHIDEventFieldDigitizerPressure, tipPressure);
event->setFixedValue(kIOHIDEventFieldDigitizerAuxiliaryPressure, barrelPressure);
event->setFixedValue(kIOHIDEventFieldDigitizerTwist, twist);
event->setFixedValue(kIOHIDEventFieldDigitizerTiltX, tiltX);
event->setFixedValue(kIOHIDEventFieldDigitizerTiltY, tiltY);
}
collection->setTouch(touch);
collection->setX(x);
collection->setY(y);
collection->setZ(z);
collection->setInRange(inRange);
exit:
return event;
}
void
IMPL(IOUserHIDEventDriver, SetLED)
{
require(_led.elements, exit);
for (unsigned int i = 0; i < _led.elements->getCount(); i++) {
IOHIDElement *element = (IOHIDElement *)_led.elements->getObject(i);
if (element->getUsage() == usage) {
IOReturn ret;
element->setValue(on ? 1 : 0);
ret = element->commit(kIOHIDElementCommitDirectionOut);
HIDServiceLog("Set LED 0x%x: %d 0x%x", usage, on, ret);
break;
}
}
exit:
return;
}