IOHIDEventProcessorFilter.cpp [plain text]
#include "IOHIDEventProcessorFilter.hpp"
#include <IOKit/hid/IOHIDEventTypes.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDPrivateKeys.h>
#include <IOKit/hid/IOHIDLibPrivate.h>
#include <IOKit/hid/IOHIDEventData.h>
#include "IOHIDDebug.h"
#include "CF.h"
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <new>
#define MAX_BUTTON_EVENTS 11
#define MAX_TAP_EVENTS 1
#define KEY_CREATE(usagePage, usage) (((UInt64)(usagePage)<<32) | (usage))
#define KEY_GETUSAGEPAGE(key) (UInt32)((key) >> 32)
#define KEY_GETUSAGE(key) (UInt32)(key & 0xFFFFFFFF)
#define STATE_IS_DOWN(state) ((state == kKPStateFirstDown) || \
(state == kKPStateSecondDown) || \
(state == kKPStateThirdDown) || \
(state == kKPStateLongPress))
#define kIOHIDEventProcessorFilterFactory CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, \
0x7D, 0xCF, 0x18, 0xB5, 0x07, 0xBE, 0x4F, 0xF5, 0x87, 0xCF, 0x44, 0xB3, 0xC1, 0x7C, 0x92, 0x16)
#define AbsoluteToUS(abs) ((abs) * sEventProcessorTimebaseInfo.numer / sEventProcessorTimebaseInfo.denom / NSEC_PER_USEC)
#define DeltaInUS(last, prev) AbsoluteToUS(last - prev)
#define PastDeadline(current, reference, deadline) (DeltaInUS(current, reference) > deadline)
extern "C" void * IOHIDEventProcessorFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID);
static mach_timebase_info_data_t sEventProcessorTimebaseInfo;
static bool isDownEvent(IOHIDEventRef event);
void *IOHIDEventProcessorFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID)
{
if (CFEqual(typeUUID, kIOHIDServiceFilterPlugInTypeID)) {
void *p = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDEventProcessor), 0);
return new(p) IOHIDEventProcessor(kIOHIDEventProcessorFilterFactory);
}
return NULL;
}
IOHIDServiceFilterPlugInInterface IOHIDEventProcessor::sIOHIDEventProcessorFtbl =
{
NULL,
IOHIDEventProcessor::QueryInterface,
IOHIDEventProcessor::AddRef,
IOHIDEventProcessor::Release,
IOHIDEventProcessor::match,
IOHIDEventProcessor::filter,
IOHIDEventProcessor::filterCopyEvent,
IOHIDEventProcessor::open,
NULL,
IOHIDEventProcessor::scheduleWithDispatchQueue,
IOHIDEventProcessor::unscheduleFromDispatchQueue,
IOHIDEventProcessor::copyPropertyForClient,
IOHIDEventProcessor::setPropertyForClient,
NULL,
IOHIDEventProcessor::setEventCallback,
};
IOHIDEventProcessor::IOHIDEventProcessor(CFUUIDRef factoryID)
:
_serviceInterface(&sIOHIDEventProcessorFtbl),
_factoryID( static_cast<CFUUIDRef>( CFRetain(factoryID) ) ),
_refCount(1),
_matchScore(0),
_queue(0),
_service(0),
_eventCallback(0),
_eventTarget(0),
_eventContext(0),
_multiPressTrackingEnabled(0),
_multiPressUsagePairs(NULL),
_multiPressDoublePressTimeout(0),
_multiPressTriplePressTimeout(0),
_multiTapTrackingEnabled(0),
_multiTapDoubleTapTimeout(0),
_multiTapTripleTapTimeout(0),
_longPressTimeout(0),
_eventHead(0),
_freeButtonHead(0),
_freeTapHead(0)
{
_timer = new Timer;
CFPlugInAddInstanceForFactory( factoryID );
if (sEventProcessorTimebaseInfo.denom == 0) {
(void) mach_timebase_info(&sEventProcessorTimebaseInfo);
}
}
IOHIDEventProcessor::~IOHIDEventProcessor()
{
if (_multiPressUsagePairs) {
CFRelease(_multiPressUsagePairs);
}
Event * e = _freeButtonHead;
Event * p = 0;
while (e) {
p = e->getNextEvent();
delete e;
e = p;
}
e = _freeTapHead;
while (e) {
p = e->getNextEvent();
delete e;
e = p;
}
e = _eventHead;
while (e) {
p = e->getNextEvent();
delete e;
e = p;
}
delete _timer;
CFPlugInRemoveInstanceForFactory( _factoryID );
CFRelease( _factoryID );
}
HRESULT IOHIDEventProcessor::QueryInterface( void *self, REFIID iid, LPVOID *ppv )
{
return static_cast<IOHIDEventProcessor *>(self)->QueryInterface(iid, ppv);
}
HRESULT IOHIDEventProcessor::QueryInterface( REFIID iid, LPVOID *ppv )
{
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes( NULL, iid );
if (CFEqual(interfaceID, kIOHIDSimpleServiceFilterPlugInInterfaceID) || CFEqual(interfaceID, kIOHIDServiceFilterPlugInInterfaceID)) {
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
if (CFEqual(interfaceID, IUnknownUUID)) {
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
*ppv = NULL;
CFRelease( interfaceID );
return E_NOINTERFACE;
}
ULONG IOHIDEventProcessor::AddRef( void *self )
{
return static_cast<IOHIDEventProcessor *>(self)->AddRef();
}
ULONG IOHIDEventProcessor::AddRef()
{
_refCount += 1;
return _refCount;
}
ULONG IOHIDEventProcessor::Release( void *self )
{
return static_cast<IOHIDEventProcessor *>(self)->Release();
}
ULONG IOHIDEventProcessor::Release()
{
_refCount -= 1;
if (_refCount == 0) {
delete this;
return 0;
}
return _refCount;
}
static CFStringRef PropertyList [] = {
CFSTR(kIOHIDKeyboardPressCountTrackingEnabledKey),
CFSTR(kIOHIDKeyboardPressCountUsagePairsKey),
CFSTR(kIOHIDKeyboardPressCountDoublePressTimeoutKey),
CFSTR(kIOHIDKeyboardPressCountTriplePressTimeoutKey),
CFSTR(kIOHIDKeyboardLongPressTimeoutKey),
CFSTR(kIOHIDBiometricTapTrackingEnabledKey),
CFSTR(kIOHIDBiometricDoubleTapTimeoutKey),
CFSTR(kIOHIDBiometricTripleTapTimeoutKey)
};
void IOHIDEventProcessor::setPropertyForClient(void * self,CFStringRef key,CFTypeRef property,CFTypeRef client)
{
static_cast<IOHIDEventProcessor *>(self)->setPropertyForClient(key, property, client);
}
void IOHIDEventProcessor::setPropertyForClient(CFStringRef key,CFTypeRef property, CFTypeRef client __unused)
{
UInt64 number = 0;
Event * e = NULL;
Event * p = NULL;
if (!key || !property)
return;
if (CFStringCompare(key, CFSTR(kIOHIDKeyboardPressCountTrackingEnabledKey), 0) == kCFCompareEqualTo) {
_multiPressTrackingEnabled = (property == kCFBooleanTrue);
HIDLogDebug("Press Count %s", _multiPressTrackingEnabled ? "enabled" : "false");
if ( _multiPressTrackingEnabled && !_freeButtonHead ) {
for (int i = 0; i < MAX_BUTTON_EVENTS; i++) {
e = new ButtonEvent;
if (e)
e->setNextEvent(p);
p = e;
}
_freeButtonHead = e;
}
}
if (CFStringCompare(key, CFSTR(kIOHIDKeyboardPressCountUsagePairsKey), 0) == kCFCompareEqualTo) {
if (_multiPressUsagePairs) {
CFRelease(_multiPressUsagePairs);
}
_multiPressUsagePairs = (CFArrayRef)property;
CFRetain(_multiPressUsagePairs);
HIDLogDebug("Press Count Usage Pairs %@", _multiPressUsagePairs);
}
if (CFStringCompare(key, CFSTR(kIOHIDKeyboardPressCountDoublePressTimeoutKey),0) == kCFCompareEqualTo) {
CFNumberGetValue((CFNumberRef)property, kCFNumberLongLongType, &number);
_multiPressDoublePressTimeout = number;
HIDLogDebug("doublePressTimeout now %llu", _multiPressDoublePressTimeout);
}
if (CFStringCompare(key, CFSTR(kIOHIDKeyboardPressCountTriplePressTimeoutKey),0) == kCFCompareEqualTo) {
CFNumberGetValue((CFNumberRef)property, kCFNumberLongLongType, &number);
_multiPressTriplePressTimeout = number;
HIDLogDebug("triplePressTimeout now %llu", _multiPressTriplePressTimeout);
}
if (CFStringCompare(key, CFSTR(kIOHIDKeyboardLongPressTimeoutKey),0) == kCFCompareEqualTo) {
CFNumberGetValue((CFNumberRef)property, kCFNumberLongLongType, &number);
_longPressTimeout = number;
HIDLogDebug("LongPress now %llu", _longPressTimeout);
}
if (CFStringCompare(key, CFSTR(kIOHIDBiometricTapTrackingEnabledKey), 0) == kCFCompareEqualTo) {
_multiTapTrackingEnabled = (property == kCFBooleanTrue);
HIDLogDebug("Tap Count %s", _multiTapTrackingEnabled? "enabled" : "false");
if ( _multiTapTrackingEnabled && !_freeTapHead ) {
for (int i = 0; i < MAX_TAP_EVENTS; i++) {
e = new TapEvent;
if (e)
e->setNextEvent(p);
p = e;
}
_freeTapHead = e;
}
}
if (CFStringCompare(key, CFSTR(kIOHIDBiometricDoubleTapTimeoutKey),0) == kCFCompareEqualTo) {
CFNumberGetValue((CFNumberRef)property, kCFNumberLongLongType, &number);
_multiTapDoubleTapTimeout = number;
HIDLogDebug("double tap timeout now %llu", _multiTapDoubleTapTimeout);
}
if (CFStringCompare(key, CFSTR(kIOHIDBiometricTripleTapTimeoutKey),0) == kCFCompareEqualTo) {
CFNumberGetValue((CFNumberRef)property, kCFNumberLongLongType, &number);
_multiTapTripleTapTimeout = number;
HIDLogDebug("triple tap timeout now %llu", _multiTapTripleTapTimeout);
}
}
CFTypeRef IOHIDEventProcessor::copyPropertyForClient(void * self,CFStringRef key, CFTypeRef client)
{
return static_cast<IOHIDEventProcessor *>(self)->copyPropertyForClient(key, client);
}
CFTypeRef IOHIDEventProcessor::copyPropertyForClient(CFStringRef key, CFTypeRef client __unused)
{
CFTypeRef result = NULL;
if (CFEqual(key, CFSTR(kIOHIDServiceFilterDebugKey))) {
CFMutableDictionaryRefWrap serializer;
if (serializer) {
serialize(serializer);
result = CFRetain(serializer.Reference());
}
}
return result;
}
SInt32 IOHIDEventProcessor::match(void * self, IOHIDServiceRef service, IOOptionBits options)
{
return static_cast<IOHIDEventProcessor *>(self)->match(service, options);
}
SInt32 IOHIDEventProcessor::match(IOHIDServiceRef service, IOOptionBits options __unused)
{
#if TARGET_OS_EMBEDDED
CFNumberRef queueSize = (CFNumberRef)IOHIDServiceCopyProperty(service, CFSTR(kIOHIDEventServiceQueueSize));
if (queueSize) {
uint32_t value = 0;
CFNumberGetValue (queueSize, kCFNumberSInt32Type, &value);
if (value != 0) {
_matchScore = 200;
_service = service;
}
CFRelease(queueSize);
} else {
_matchScore = 200;
_service = service;
}
#else
_matchScore = 200;
_service = service;
#endif
HIDLogDebug("(%p) for ServiceID %@ with score %d", this, IOHIDServiceGetRegistryID(service), (int)_matchScore);
return _matchScore;
}
IOHIDEventRef IOHIDEventProcessor::filter(void * self, IOHIDEventRef event)
{
return static_cast<IOHIDEventProcessor *>(self)->filter(event);
}
IOHIDEventRef IOHIDEventProcessor::filter(IOHIDEventRef event)
{
Event * curr = NULL;
Event ** freeHead = NULL;
IOHIDEventType eventType = 0;
UInt32 usagePage = 0;
UInt32 usage = 0;
UInt64 doubleTO = 0;
UInt64 tripleTO = 0;
if (!_queue) {
return event;
}
eventType = IOHIDEventGetType(event);
if (eventType == kIOHIDEventTypeKeyboard) {
if (!_multiPressTrackingEnabled) {
goto exit;
}
doubleTO = _multiPressDoublePressTimeout;
tripleTO = _multiPressTriplePressTimeout;
freeHead = &_freeButtonHead;
}
else if (eventType == kIOHIDEventTypeBiometric) {
if (!_multiTapTrackingEnabled) {
goto exit;
}
doubleTO = _multiTapDoubleTapTimeout;
tripleTO = _multiTapTripleTapTimeout;
freeHead = &_freeTapHead;
}
else {
goto exit;
}
usagePage = (UInt32)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsagePage);
usage = (UInt32)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsage);
HIDLogDebug("filter: type = %d p = %d u = %d", (int)eventType, (int)usagePage, (int)usage);
if (eventType == kIOHIDEventTypeKeyboard && _multiPressUsagePairs) {
bool match = false;
CFIndex count = CFArrayGetCount(_multiPressUsagePairs);
for (CFIndex i = 0; i < count; i++) {
CFNumberRef pairNum = (CFNumberRef)CFArrayGetValueAtIndex(_multiPressUsagePairs, i);
UInt64 pair;
CFNumberGetValue(pairNum, kCFNumberLongLongType, &pair);
if (((usagePage<<16)|usage) == pair) {
match = true;
}
}
if (match == false) {
HIDLogDebug("usage pair should not be processed, letting through");
goto exit;
}
}
if ((IOHIDEventGetPhase(event) & kIOHIDEventPhaseEnded) != 0) {
HIDLogDebug("terminal event detected, letting through");
goto exit;
}
if (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardLongPress) != 0) {
HIDLogDebug("letting long press event through");
goto exit;
}
curr = _eventHead;
while (curr != NULL) {
if (curr->conformsTo(eventType, usagePage, usage))
break;
curr = curr->getNextEvent();
}
if (curr == NULL) {
curr = *freeHead;
if (!curr) {
HIDLogError("No more free events");
return event;
}
*freeHead = curr->getNextEvent();
curr->init(this,
_timer,
eventType,
usagePage,
usage,
doubleTO,
tripleTO,
eventType == kIOHIDEventTypeKeyboard ? _longPressTimeout : 0);
if (!curr) {
HIDLogError("Could not create new event");
return event;
}
curr->setNextEvent(_eventHead);
_eventHead = curr;
}
curr->stateHandler(isDownEvent(event) ? kKPTransitionDown : kKPTransitionUp, event);
exit:
return event;
}
void IOHIDEventProcessor::scheduleWithDispatchQueue(void * self, dispatch_queue_t queue)
{
static_cast<IOHIDEventProcessor *>(self)->scheduleWithDispatchQueue(queue);
}
void IOHIDEventProcessor::scheduleWithDispatchQueue(dispatch_queue_t queue)
{
_queue = queue;
_timer->init(_queue);
}
void IOHIDEventProcessor::unscheduleFromDispatchQueue(void * self, dispatch_queue_t queue)
{
static_cast<IOHIDEventProcessor *>(self)->unscheduleFromDispatchQueue(queue);
}
void IOHIDEventProcessor::unscheduleFromDispatchQueue(dispatch_queue_t queue)
{
if ( _queue != queue )
return;
_timer->cancel(queue);
_queue = NULL;
}
void IOHIDEventProcessor::setEventCallback(void * self, IOHIDServiceEventCallback callback, void * target, void * refcon)
{
static_cast<IOHIDEventProcessor *>(self)->setEventCallback(callback, target, refcon);
}
void IOHIDEventProcessor::setEventCallback(IOHIDServiceEventCallback callback, void * target, void * refcon)
{
_eventCallback = callback;
_eventTarget = target;
_eventContext = refcon;
}
IOHIDEventRef IOHIDEventProcessor::filterCopyEvent(void * self, IOHIDEventRef event)
{
return static_cast<IOHIDEventProcessor *>(self)->filterCopyEvent(event);
}
IOHIDEventRef IOHIDEventProcessor::filterCopyEvent(IOHIDEventRef event)
{
return event;
}
void IOHIDEventProcessor::open(void * self, IOHIDServiceRef service, IOOptionBits options) {
static_cast<IOHIDEventProcessor *>(self)->open(service, options);
}
void IOHIDEventProcessor::open(IOHIDServiceRef service, IOOptionBits options __unused) {
for (size_t i = 0; i < sizeof(PropertyList)/sizeof(PropertyList[0]); i++) {
CFTypeRef value = IOHIDServiceCopyProperty(service, PropertyList[i]);
if (value) {
setPropertyForClient(PropertyList[i], value, NULL);
CFRelease(value);
}
}
}
void IOHIDEventProcessor::dispatchEvent(IOHIDEventRef event, bool async)
{
if (!_queue)
return;
if ( async ) {
CFRetain(event);
dispatch_async(_queue, ^{
HIDLogDebug("asynchronously dispatching event = %p", event);
_eventCallback(_eventTarget, _eventContext, _service, event, 0);
CFRelease(event);
});
}
else {
HIDLogDebug("synchronously dispatching event = %p", event);
_eventCallback(_eventTarget, _eventContext, _service, event, 0);
}
}
void IOHIDEventProcessor::returnToFreePool(Event * event)
{
Event * p = 0;
Event * c = _eventHead;
Event ** f = NULL;
HIDLogDebug("returning event %p to free pool", event);
if (event->getEventType() == kIOHIDEventTypeKeyboard) {
f = &_freeButtonHead;
}
else if (event->getEventType() == kIOHIDEventTypeBiometric){
f = &_freeTapHead;
}
while (c) {
if (*c == *event) {
if (p) {
p->setNextEvent(event->getNextEvent());
}
else {
_eventHead = _eventHead->getNextEvent();
}
break;
}
p = c;
c = c->getNextEvent();
}
if (f) {
event->setNextEvent(*f);
*f = event;
}
}
void IOHIDEventProcessor::serialize (CFMutableDictionaryRef dict) const {
CFMutableDictionaryRefWrap serializer (dict);
serializer.SetValueForKey(CFSTR("Class"), CFSTR("IOHIDEventProcessor"));
serializer.SetValueForKey(CFSTR(kIOHIDKeyboardPressCountTrackingEnabledKey), _multiPressTrackingEnabled);
serializer.SetValueForKey(CFSTR(kIOHIDKeyboardPressCountUsagePairsKey), _multiPressUsagePairs);
serializer.SetValueForKey(CFSTR(kIOHIDKeyboardPressCountDoublePressTimeoutKey), _multiPressDoublePressTimeout);
serializer.SetValueForKey(CFSTR(kIOHIDKeyboardPressCountTriplePressTimeoutKey), _multiPressTriplePressTimeout);
serializer.SetValueForKey(CFSTR(kIOHIDKeyboardLongPressTimeoutKey), _longPressTimeout);
serializer.SetValueForKey(CFSTR(kIOHIDBiometricTapTrackingEnabledKey), _multiPressTriplePressTimeout);
serializer.SetValueForKey(CFSTR(kIOHIDBiometricDoubleTapTimeoutKey), _multiTapDoubleTapTimeout);
serializer.SetValueForKey(CFSTR(kIOHIDBiometricTripleTapTimeoutKey), _multiTapTripleTapTimeout);
serializer.SetValueForKey(CFSTR("MatchScore"), (uint64_t)_matchScore);
}
#pragma mark -
#pragma mark Event
#pragma mark -
Event::Event()
:
_nextTimeout(0),
_nextEvent(0),
_nextTimerEvent(0),
_owner(0),
_isComplete(true),
_eventType(0),
_usagePage(0),
_usage(0),
_timer(0),
_state(0),
_timeoutState(0),
_multiEventCount(0),
_terminalEventDispatched(0),
_secondEventTimeout(0),
_thirdEventTimeout(0),
_lastActionTimestamp(0),
_longPressTimeout(0)
{
}
Event::~Event()
{
}
void Event::init(IOHIDEventProcessor * owner,
Timer * timer,
IOHIDEventType eventType,
UInt32 usagePage,
UInt32 usage,
UInt64 secondEventTimeout,
UInt64 thirdEventTimeout,
UInt64 longPressTimeout)
{
_owner = owner;
_eventType = eventType;
_usagePage = usagePage;
_usage = usage;
_secondEventTimeout = secondEventTimeout;
_thirdEventTimeout = thirdEventTimeout;
_longPressTimeout = longPressTimeout;
_timer = timer;
_terminalEventDispatched = false;
_lastActionTimestamp = 0;
_nextEvent = 0;
_nextTimerEvent = 0;
_state = 0;
_timeoutState = 0;
_nextTimeout = 0;
_isComplete = false;
_multiEventCount = 0;
if (_longPressTimeout) {
if (_longPressTimeout == _secondEventTimeout)
_secondEventTimeout++;
if (_longPressTimeout == _thirdEventTimeout)
_thirdEventTimeout++;
}
if ((_longPressTimeout < _secondEventTimeout) ||
(_longPressTimeout < _thirdEventTimeout)) {
HIDLogDebug("long %llu second %llu third %llu\n",
_longPressTimeout, _secondEventTimeout, _thirdEventTimeout);
}
}
bool Event::conformsTo(IOHIDEventType eventType, UInt32 usagePage, UInt32 usage)
{
bool ret = false;
do {
if (eventType != _eventType)
break;
if (usage != _usage)
break;
if (usagePage != _usagePage)
break;
ret = true;
} while (0);
return ret;
}
static bool isDownEvent(IOHIDEventRef event)
{
bool down = false;
IOHIDEventType eventType = IOHIDEventGetType(event);
if (eventType == kIOHIDEventTypeKeyboard) {
down = (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown) == 1);
}
else if (eventType == kIOHIDEventTypeBiometric) {
down = (IOHIDEventGetFloatValue(event, kIOHIDEventFieldBiometricLevel) == 1.0);
}
return down;
}
void Event::dispatchEvent(IOHIDEventRef event, bool async)
{
_owner->dispatchEvent(event, async);
}
void Event::completed()
{
_isComplete = true;
_owner->returnToFreePool(this);
}
static void FDEnter(Event * e, IOHIDEventRef event);
static void FUEnter(Event * e, IOHIDEventRef event);
static void SDEnter(Event * e, IOHIDEventRef event);
static void SUEnter(Event * e, IOHIDEventRef event);
static void TDEnter(Event * e, IOHIDEventRef event);
static void TUEnter(Event * e, IOHIDEventRef event);
static void TOEnter(Event * e, IOHIDEventRef event);
static void TEEnter(Event * e, IOHIDEventRef event);
static void LPEnter(Event * e, IOHIDEventRef event);
static void NoneEnter(Event * e, IOHIDEventRef event);
typedef void (*transition_handler_t)(Event * e, IOHIDEventRef event);
transition_handler_t _stateMap[kKPStateCount][kKPTransitionCount] = {
{ &FDEnter, NULL, NULL, },
{ NULL, &FUEnter, &TOEnter },
{ &SDEnter, NULL, &TOEnter },
{ NULL, &SUEnter, &TOEnter },
{ &TDEnter, NULL, &TOEnter },
{ NULL, &TUEnter, &TOEnter },
{ NULL, NULL, &TOEnter },
{ &FDEnter, &NoneEnter, &TOEnter },
{ NULL, &NoneEnter, &TOEnter }
};
static const char * _stateNames[] = {"NON", "FDN", "FUP", "SDN", "SUP", "TDN", "TUP", "TEE", "LPE"};
static const char * _transitions[] = {"DOWN", "UPUP", "TOUT" };
bool Event::stateHandler(KPTransition transition, IOHIDEventRef event)
{
bool ret = false;
transition_handler_t handler = NULL;
_timer->checkEventTimeouts();
handler = _stateMap[_state][transition];
HIDLogDebug("state = %s transition = %s", _stateNames[_state], _transitions[transition]);
if (!handler) {
HIDLogDebug("Invalid state transition: [%d][%d]", (unsigned int)_state, transition);
goto exit;
}
ret = true;
handler(this, event);
HIDLogDebug("new state = %s", _stateNames[_state]);
exit:
return ret;
}
static void NoneEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event*>(e)->NoneEnter(event);
}
static void FDEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->FDEnter(event);
}
static void FUEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->FUEnter(event);
}
static void SDEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->SDEnter(event);
}
static void SUEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->SUEnter(event);
}
static void TDEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->TDEnter(event);
}
static void TUEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->TUEnter(event);
}
static void TOEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->TOEnter(event);
}
__unused static void TEEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->TEEnter(event);
}
__unused static void LPEnter(Event * e, IOHIDEventRef event)
{
static_cast <Event *>(e)->LPEnter(event);
}
#pragma mark -
#pragma mark ButtonEvent functions
#pragma mark -
void ButtonEvent::setMultiEventCount(IOHIDEventRef event, CFIndex count)
{
HIDLogDebug("%p %p setting multi count = %d", this, event, (int)count);
IOHIDEventSetIntegerValue(event, kIOHIDEventFieldKeyboardPressCount, count);
_multiEventCount = count ? (int)count : _multiEventCount;
}
IOHIDEventRef ButtonEvent::createSyntheticEvent(bool isTerminalEvent)
{
IOHIDEventRef event = NULL;
UInt64 timestamp = mach_absolute_time();
event = IOHIDEventCreateKeyboardEvent(kCFAllocatorDefault,
timestamp,
_usagePage,
_usage,
STATE_IS_DOWN(_state),
0);
if (event && isTerminalEvent) {
setMultiEventCount(event, _multiEventCount);
IOHIDEventSetPhase(event, IOHIDEventGetPhase(event) | kIOHIDEventPhaseEnded);
_terminalEventDispatched = true;
}
HIDLogDebug("created terminal(%d) event %p type %d", isTerminalEvent, event, (int)_eventType);
return event;
}
void ButtonEvent::NoneEnter(IOHIDEventRef event __unused)
{
if (event) {
_timer->registerEventTimeout(this, 0);
}
if (event) {
setMultiEventCount(event, _multiEventCount);
}
completed();
_state = kKPStateNone;
}
void ButtonEvent::FDEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
IOHIDEventSetPhase(event, IOHIDEventGetPhase(event) | kIOHIDEventPhaseBegan);
setMultiEventCount(event, 1);
_state = kKPStateFirstDown;
if ( _secondEventTimeout == 0 ) {
TEEnter(event);
}
if ( _longPressTimeout > _secondEventTimeout || _longPressTimeout == 0 ) {
_timeoutState = kKPStateTerminalEvent;
_timer->registerEventTimeout(this, _secondEventTimeout);
}
else {
_timeoutState = kKPStateLongPress;
_timer->registerEventTimeout(this, _longPressTimeout);
}
}
void ButtonEvent::FUEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 1);
_state = kKPStateFirstUp;
}
void ButtonEvent::SDEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
setMultiEventCount(event, 2);
_state = kKPStateSecondDown;
if ( _thirdEventTimeout == 0 ) {
TEEnter(event);
}
else if ( _longPressTimeout > _thirdEventTimeout || _longPressTimeout == 0 ) {
_timeoutState = kKPStateTerminalEvent;
_timer->registerEventTimeout(this, _thirdEventTimeout);
}
else {
_timeoutState = kKPStateLongPress;
_timer->registerEventTimeout(this, _longPressTimeout);
}
}
void ButtonEvent::SUEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 2);
_state = kKPStateSecondUp;
}
void ButtonEvent::TDEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
setMultiEventCount(event, 3);
_state = kKPStateThirdDown;
TEEnter(event);
}
void ButtonEvent::TUEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 3);
NoneEnter(event);
}
void ButtonEvent::TOEnter(IOHIDEventRef event)
{
if (_timeoutState == kKPStateTerminalEvent) {
TEEnter(event);
}
else if (_timeoutState == kKPStateLongPress) {
LPEnter(event);
}
}
void ButtonEvent::TEEnter(IOHIDEventRef event)
{
uint64_t nextTimeout = 0;
bool isDown = false;
isDown = ((_state == kKPStateFirstDown) ||
(_state == kKPStateSecondDown) ||
(_state == kKPStateThirdDown) ||
(_state == kKPStateLongPress));
if (isDown && _longPressTimeout) {
if (_state == kKPStateFirstDown && _longPressTimeout > _secondEventTimeout) {
nextTimeout = _longPressTimeout - _secondEventTimeout;
}
else if (_state == kKPStateSecondDown && _longPressTimeout > _thirdEventTimeout) {
nextTimeout = _longPressTimeout - _thirdEventTimeout;
}
else if (_state == kKPStateThirdDown) {
nextTimeout = _longPressTimeout;
}
_timeoutState = kKPStateLongPress;
}
IOHIDEventRef terminalEvent = createSyntheticEvent(true);
dispatchEvent(terminalEvent, (event ? true : false));
_lastActionTimestamp = IOHIDEventGetTimeStamp(terminalEvent);
CFRelease(terminalEvent);
_timer->registerEventTimeout(this, nextTimeout);
if (isDown) {
_state = kKPStateTerminalEvent;
}
else {
NoneEnter(NULL);
}
}
void ButtonEvent::LPEnter(IOHIDEventRef event)
{
bool isDown = false;
bool isUp = false;
isDown = ((_state == kKPStateFirstDown) ||
(_state == kKPStateSecondDown) ||
(_state == kKPStateThirdDown));
isUp = ((_state == kKPStateFirstUp) ||
(_state == kKPStateSecondUp) ||
(_state == kKPStateThirdUp));
if (isUp) {
uint64_t nextTimeout = 0;
if (_state == kKPStateFirstUp && _secondEventTimeout > _longPressTimeout) {
nextTimeout = _secondEventTimeout - _longPressTimeout;
}
else if (_state == kKPStateSecondUp && _thirdEventTimeout > _longPressTimeout) {
nextTimeout = _thirdEventTimeout - _longPressTimeout;
}
_timeoutState = kKPStateTerminalEvent;
_lastActionTimestamp = mach_absolute_time();
_timer->registerEventTimeout(this, nextTimeout);
}
else {
IOHIDEventRef lpEvent = createSyntheticEvent(false);
IOHIDEventSetIntegerValue(lpEvent, kIOHIDEventFieldKeyboardLongPress, kIOHIDKeyboardLongPress);
IOHIDEventSetIntegerValue(lpEvent, kIOHIDEventFieldKeyboardDown, true);
setMultiEventCount(lpEvent, _multiEventCount);
dispatchEvent(lpEvent, (event ? true : false));
_state = kKPStateLongPress;
if (isDown) {
_lastActionTimestamp = IOHIDEventGetTimeStamp(lpEvent);
TEEnter(event);
}
CFRelease(lpEvent);
}
}
#pragma mark -
#pragma mark TapEvent functions
#pragma mark -
void TapEvent::setMultiEventCount(IOHIDEventRef event, CFIndex count)
{
HIDLogDebug("%p %p setting multi count = %d", this, event, (int)count);
IOHIDEventSetIntegerValue(event, kIOHIDEventFieldBiometricTapCount, count);
_multiEventCount = count ? (int)count : _multiEventCount;
}
IOHIDEventRef TapEvent::createSyntheticEvent(bool isTerminalEvent)
{
IOHIDEventRef event = NULL;
UInt64 timestamp = mach_absolute_time();
event = IOHIDEventCreateBiometricEvent(kCFAllocatorDefault,
timestamp,
kIOHIDBiometricEventTypeHumanTouch,
(STATE_IS_DOWN(_state) ?
1.0 : 0),
0);
if (event) {
IOHIDEventSetIntegerValue(event,
kIOHIDEventFieldBiometricUsagePage,
_usagePage);
IOHIDEventSetIntegerValue(event,
kIOHIDEventFieldBiometricUsage,
_usage);
}
if (event && isTerminalEvent) {
setMultiEventCount(event, _multiEventCount);
IOHIDEventSetPhase(event, IOHIDEventGetPhase(event) | kIOHIDEventPhaseEnded);
_terminalEventDispatched = true;
}
HIDLogDebug("created terminal(%d) event %p type %d", isTerminalEvent, event, (int)_eventType);
return event;
}
void TapEvent::NoneEnter(IOHIDEventRef event __unused)
{
if (event) {
_timer->registerEventTimeout(this, 0);
}
if (event) {
setMultiEventCount(event, _multiEventCount);
}
completed();
_state = kKPStateNone;
}
void TapEvent::FDEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 0);
_state = kKPStateFirstDown;
}
void TapEvent::FUEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
IOHIDEventSetPhase(event, IOHIDEventGetPhase(event) | kIOHIDEventPhaseBegan);
setMultiEventCount(event, 1);
_state = kKPStateFirstUp;
if ( _secondEventTimeout == 0) {
TEEnter(event);
}
else {
_timer->registerEventTimeout(this, _secondEventTimeout);
}
}
void TapEvent::SDEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 1);
_state = kKPStateSecondDown;
}
void TapEvent::SUEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
setMultiEventCount(event, 2);
_state = kKPStateSecondUp;
if ( _thirdEventTimeout == 0 ) {
TEEnter(event);
}
else {
_timer->registerEventTimeout(this, _thirdEventTimeout);
}
}
void TapEvent::TDEnter(IOHIDEventRef event)
{
setMultiEventCount(event, 2);
_state = kKPStateThirdDown;
}
void TapEvent::TUEnter(IOHIDEventRef event)
{
_lastActionTimestamp = IOHIDEventGetTimeStamp(event);
setMultiEventCount(event, 3);
_state = kKPStateThirdUp;
TEEnter(event);
}
void TapEvent::TOEnter(IOHIDEventRef event __unused)
{
TEEnter(event);
}
void TapEvent::TEEnter(IOHIDEventRef event)
{
bool isDown = false;
isDown = ((_state == kKPStateFirstDown) ||
(_state == kKPStateSecondDown) ||
(_state == kKPStateThirdDown));
IOHIDEventRef terminalEvent = createSyntheticEvent(true);
dispatchEvent(terminalEvent, (event ? true : false));
CFRelease(terminalEvent);
_timer->registerEventTimeout(this, 0);
if (isDown) {
_state = kKPStateFirstDown;
}
else {
NoneEnter(NULL);
}
}
void TapEvent::LPEnter(IOHIDEventRef event __unused)
{
_state = kKPStateLongPress;
}
#pragma mark -
#pragma mark Timer functions
#pragma mark -
Timer::Timer()
:
_timer(0),
_queue(0),
_headEvent(0)
{
}
void Timer::init(dispatch_queue_t q)
{
dispatch_source_t tempTimer;
setQueue(q);
if (_timer == NULL) {
tempTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, _queue);
dispatch_source_set_event_handler(tempTimer, ^{
timeoutHandler();
});
dispatch_source_set_cancel_handler(tempTimer, ^(void) {
dispatch_release(tempTimer);
});
dispatch_source_set_timer(tempTimer, DISPATCH_TIME_FOREVER, 0, 0);
_timer = tempTimer;
dispatch_resume(_timer);
}
}
void Timer::cancel(dispatch_queue_t q __unused)
{
if (_timer != NULL) {
dispatch_source_cancel(_timer);
}
setQueue(NULL);
}
void Timer::removeEvent(Event * event)
{
Event * e = _headEvent;
Event * p = NULL;
while (e) {
if (*e == *event) {
if (p) {
p->setNextTimerEvent(event->getNextTimerEvent());
} else {
_headEvent = _headEvent->getNextTimerEvent();
}
break;
}
p = e;
e = e->getNextTimerEvent();
}
event->setNextTimerEvent(NULL);
}
void Timer::insertEvent(Event * event)
{
removeEvent(event);
event->setNextTimerEvent(_headEvent);
_headEvent = event;
}
void Timer::timeoutHandler()
{
HIDLogDebug("%p timeout occurred", this);
checkEventTimeouts();
}
void Timer::checkEventTimeouts()
{
Event * event = _headEvent;
UInt64 currentTime = mach_absolute_time();
Event * nextEvent = NULL;
while (event) {
nextEvent = event->getNextTimerEvent();
if ((event->getNextTimeout() == 0) ||
(event->isComplete())) {
removeEvent(event);
event = nextEvent;
continue;
}
if (PastDeadline(currentTime, event->epoch(), event->getNextTimeout())) {
HIDLogDebug("%p past deadline %lld us", event, DeltaInUS(currentTime, event->epoch()) - event->getNextTimeout());
removeEvent(event);
event->stateHandler(kKPTransitionTimeout, NULL);
}
event = nextEvent;
}
updateTimeout();
}
void Timer::updateTimeout()
{
UInt64 currentTime = mach_absolute_time();
SInt64 nextTimeout = INT64_MAX;
Event* event = _headEvent;
while (event) {
SInt64 eventTimeout = event->getNextTimeout() - DeltaInUS(currentTime, event->epoch());
if (nextTimeout > eventTimeout) {
nextTimeout = eventTimeout;
}
event = event->getNextTimerEvent();
}
if (nextTimeout < 0) {
nextTimeout = 0;
}
if (nextTimeout == INT64_MAX) {
dispatch_source_set_timer(_timer, DISPATCH_TIME_FOREVER, 0, 0);
}
else {
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW, nextTimeout * NSEC_PER_USEC),
DISPATCH_TIME_FOREVER,
0);
}
return;
}
void Timer::registerEventTimeout(Event * event, UInt64 deadline)
{
HIDLogDebug("registering %p for timeout in %llu uS", event, deadline);
event->setNextTimeout(deadline);
if (deadline == 0)
removeEvent(event);
else
insertEvent(event);
updateTimeout();
}