IOHIDDFREventFilter.mm [plain text]
/*
* IOHIDDFREventFilter.cpp
* IOHIDEventSystemPlugIns
*
* Created by dekom on 08/16/2016.
* Copyright 2016 Apple Inc. All rights reserved.
*
*/
#include "IOHIDDFREventFilter.hpp"
#include "IOHIDDebug.h"
#include "IOHIDProperties.h"
#include <new>
#include <IOKit/hid/IOHIDService.h>
#include <IOKit/hid/IOHIDSession.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDPrivateKeys.h>
#include <mach/mach_time.h>
// 4F2A35AF-A17D-4020-B62A-0E64B825F069
#define kIOHIDDFREventFilterFactor CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0x4F, 0x2A, 0x35, 0xAF, 0xA1, 0x7D, 0x40, 0x20, 0xB6, 0x2A, 0x0E, 0x64, 0xB8, 0x25, 0xF0, 0x69)
extern "C" void * IOHIDDFREventFilterFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID);
//------------------------------------------------------------------------------
// IOHIDDFREventFilterFactory
//------------------------------------------------------------------------------
void *IOHIDDFREventFilterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID)
{
if (CFEqual(typeUUID, kIOHIDSessionFilterPlugInTypeID)) {
void *p = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDDFREventFilter), 0);
return new(p) IOHIDDFREventFilter(kIOHIDDFREventFilterFactor);
}
return NULL;
}
// The IOHIDDFREventFilter function table.
IOHIDSessionFilterPlugInInterface IOHIDDFREventFilter::sIOHIDDFREventFilterFtbl =
{
// Required padding for COM
NULL,
// These three are the required COM functions
IOHIDDFREventFilter::QueryInterface,
IOHIDDFREventFilter::AddRef,
IOHIDDFREventFilter::Release,
// IOHIDSimpleSessionFilterPlugInInterface functions
IOHIDDFREventFilter::filter,
NULL,
NULL,
// IOHIDSessionFilterPlugInInterface functions
IOHIDDFREventFilter::open,
IOHIDDFREventFilter::close,
NULL,
NULL,
IOHIDDFREventFilter::registerService,
IOHIDDFREventFilter::unregisterService,
IOHIDDFREventFilter::scheduleWithDispatchQueue,
IOHIDDFREventFilter::unscheduleFromDispatchQueue,
IOHIDDFREventFilter::getPropertyForClient,
IOHIDDFREventFilter::setPropertyForClient,
};
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::IOHIDDFREventFilter
//------------------------------------------------------------------------------
IOHIDDFREventFilter::IOHIDDFREventFilter(CFUUIDRef factoryID):
_sessionInterface(&sIOHIDDFREventFilterFtbl),
_factoryID(static_cast<CFUUIDRef>(CFRetain(factoryID))),
_refCount(1),
_dispatchQueue(0),
_keyboard(NULL),
_dfr(NULL),
_session(NULL),
_lastDFREvent(NULL),
_keyboardFilterEnabled(true),
_touchIDFilterEnabled(true),
_touchInProgress(false),
_bioInProgress(false),
_cancel(false)
{
CFPlugInAddInstanceForFactory(factoryID);
};
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::~IOHIDDFREventFilter
//------------------------------------------------------------------------------
IOHIDDFREventFilter::~IOHIDDFREventFilter()
{
CFPlugInRemoveInstanceForFactory(_factoryID);
CFRelease(_factoryID);
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::QueryInterface
//------------------------------------------------------------------------------
HRESULT IOHIDDFREventFilter::QueryInterface(void *self, REFIID iid, LPVOID *ppv)
{
return static_cast<IOHIDDFREventFilter *>(self)->QueryInterface(iid, ppv);
}
HRESULT IOHIDDFREventFilter::QueryInterface(REFIID iid, LPVOID *ppv)
{
// Create a CoreFoundation UUIDRef for the requested interface.
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
// Test the requested ID against the valid interfaces.
if (CFEqual(interfaceID, kIOHIDSimpleSessionFilterPlugInInterfaceID) || CFEqual(interfaceID, kIOHIDSessionFilterPlugInInterfaceID)) {
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
if (CFEqual(interfaceID, IUnknownUUID)) {
// If the IUnknown interface was requested, same as above.
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
// Requested interface unknown, bail with error.
*ppv = NULL;
CFRelease(interfaceID);
return E_NOINTERFACE;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::AddRef
//------------------------------------------------------------------------------
ULONG IOHIDDFREventFilter::AddRef(void *self)
{
return static_cast<IOHIDDFREventFilter *>(self)->AddRef();
}
ULONG IOHIDDFREventFilter::AddRef()
{
_refCount += 1;
return _refCount;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::Release
//------------------------------------------------------------------------------
ULONG IOHIDDFREventFilter::Release(void *self)
{
return static_cast<IOHIDDFREventFilter *>(self)->Release();
}
ULONG IOHIDDFREventFilter::Release()
{
_refCount -= 1;
if (_refCount == 0) {
delete this;
return 0;
}
return _refCount;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::open
//------------------------------------------------------------------------------
boolean_t IOHIDDFREventFilter::open(void *self, IOHIDSessionRef session, IOOptionBits options)
{
return static_cast<IOHIDDFREventFilter *>(self)->open(session, options);
}
boolean_t IOHIDDFREventFilter::open(IOHIDSessionRef session, IOOptionBits options __unused)
{
_session = session;
return true;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::close
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::close(void *self, IOHIDSessionRef session, IOOptionBits options)
{
static_cast<IOHIDDFREventFilter *>(self)->close(session, options);
}
void IOHIDDFREventFilter::close(IOHIDSessionRef session __unused, IOOptionBits options __unused)
{
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::registerService
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::registerService(void *self, IOHIDServiceRef service)
{
static_cast<IOHIDDFREventFilter *>(self)->registerService(service);
}
void IOHIDDFREventFilter::registerService(IOHIDServiceRef service)
{
if (IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) {
CFBooleanRef builtin = (CFBooleanRef)IOHIDServiceGetProperty(service, CFSTR(kIOHIDBuiltInKey));
if (builtin == kCFBooleanTrue) {
_keyboard = service;
}
} else if (IOHIDServiceConformsTo(service, kHIDPage_AppleVendor, kHIDUsage_AppleVendor_DFR)) {
_dfr = service;
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::unregisterService
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::unregisterService(void *self, IOHIDServiceRef service)
{
static_cast<IOHIDDFREventFilter *>(self)->unregisterService(service);
}
void IOHIDDFREventFilter::unregisterService(IOHIDServiceRef service)
{
if (service == _keyboard) {
_keyboard = NULL;
} else if (service == _dfr) {
_dfr = NULL;
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::scheduleWithDispatchQueue
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::scheduleWithDispatchQueue(void *self, dispatch_queue_t queue)
{
static_cast<IOHIDDFREventFilter *>(self)->scheduleWithDispatchQueue(queue);
}
void IOHIDDFREventFilter::scheduleWithDispatchQueue(dispatch_queue_t queue)
{
_dispatchQueue = queue;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::unscheduleFromDispatchQueue
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::unscheduleFromDispatchQueue(void *self, dispatch_queue_t queue)
{
static_cast<IOHIDDFREventFilter *>(self)->unscheduleFromDispatchQueue(queue);
}
void IOHIDDFREventFilter::unscheduleFromDispatchQueue(dispatch_queue_t queue __unused)
{
if (_lastDFREvent) {
CFRelease(_lastDFREvent);
_lastDFREvent = NULL;
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::filter
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDDFREventFilter::filter(void *self, IOHIDServiceRef sender, IOHIDEventRef event)
{
return static_cast<IOHIDDFREventFilter *>(self)->filter(sender, event);
}
IOHIDEventRef IOHIDDFREventFilter::filter(IOHIDServiceRef sender, IOHIDEventRef event)
{
if (!event) {
goto exit;
}
if (sender != _dfr && sender != _keyboard && IOHIDEventGetType(event) != kIOHIDEventTypeBiometric) {
goto exit;
}
if (IOHIDEventGetType(event) == kIOHIDEventTypeKeyboard) {
handleKeyboardEvent(event);
goto exit;
}
if (IOHIDEventGetType(event) == kIOHIDEventTypeBiometric) {
handleBiometricEvent(event);
goto exit;
}
if (IOHIDEventGetType(event) == kIOHIDEventTypeDigitizer) {
if ((IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerEventMask) & kIOHIDDigitizerEventCancel) != 0) {
goto exit;
}
if (!handleDFREvent(event)) {
// keep track of active touches during cancellation phase, so we know to continue to cancel them
_touchInProgress = (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerRange) == 1);
HIDLogDebug("Event cancelled due to %s. touch: %d flags = %x", _bioInProgress ? "touch ID" : "active keys", _touchInProgress, (unsigned int)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerEventMask));
startTouchCancellation();
CFRelease(event);
event = NULL;
} else if (_touchInProgress && _cancel) {
// Touch has ended on DFR, allow events
_touchInProgress = (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerRange) == 1);
HIDLogDebug("Event cancelled due to touch in progress.");
CFRelease(event);
event = NULL;
} else {
// end cancellation phase
_cancel = false;
}
}
exit:
return event;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::getPropertyForClient
//------------------------------------------------------------------------------
CFTypeRef IOHIDDFREventFilter::getPropertyForClient(void *self, CFStringRef key, CFTypeRef client)
{
return static_cast<IOHIDDFREventFilter *>(self)->getPropertyForClient(key,client);
}
CFTypeRef IOHIDDFREventFilter::getPropertyForClient(CFStringRef key, CFTypeRef client __unused)
{
CFTypeRef result = NULL;
if (CFEqual(key, CFSTR(kIOHIDDFRKeyboardEventFilterEnabledKey))) {
result = _keyboardFilterEnabled ? kCFBooleanTrue : kCFBooleanFalse;
} else if (CFEqual(key, CFSTR(kIOHIDDFRTouchIDEventFilterEnabledKey))) {
result = _touchIDFilterEnabled ? kCFBooleanTrue : kCFBooleanFalse;
}
return result;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::setPropertyForClient
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::setPropertyForClient(void *self, CFStringRef key, CFTypeRef property, CFTypeRef client)
{
static_cast<IOHIDDFREventFilter *>(self)->setPropertyForClient(key, property, client);
}
void IOHIDDFREventFilter::setPropertyForClient(CFStringRef key, CFTypeRef property, CFTypeRef client __unused)
{
if (CFEqual(key, CFSTR(kIOHIDDFRKeyboardEventFilterEnabledKey))) {
_keyboardFilterEnabled = CFBooleanGetValue((CFBooleanRef)property);
} else if (CFEqual(key, CFSTR(kIOHIDDFRTouchIDEventFilterEnabledKey))) {
_touchIDFilterEnabled = CFBooleanGetValue((CFBooleanRef)property);
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleKeyboardEvent
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::handleKeyboardEvent(IOHIDEventRef event)
{
uint32_t usage;
uint32_t usagePage;
uint32_t keyDown;
uint32_t flags;
Key key;
usage = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsage);
usagePage = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsagePage);
keyDown = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown);
flags = (uint32_t)IOHIDEventGetEventFlags(event);
if ((IOHIDEventGetIntegerValue (event, kIOHIDEventFieldKeyboardSlowKeyPhase) == kIOHIDKeyboardSlowKeyPhaseStart ||
IOHIDEventGetIntegerValue (event, kIOHIDEventFieldKeyboardSlowKeyPhase) == kIOHIDKeyboardSlowKeyPhaseAbort)) {
return;
}
key = Key(usagePage, usage);
if (keyDown) {
_activeKeys.insert(std::make_pair(key, KeyAttribute(flags)));
if (!key.isModifier()) {
startTouchCancellation();
}
} else {
auto iter = _activeKeys.find(key);
if (iter!=_activeKeys.end()) {
_activeKeys.erase(iter);
}
if (_activeKeys.empty() && !_bioInProgress && !_touchInProgress) {
_cancel = false;
}
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleBiometicEvent
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::handleBiometricEvent(IOHIDEventRef event)
{
if (!_touchIDFilterEnabled) {
return;
}
if (IOHIDEventGetType(event) == kIOHIDEventTypeBiometric) {
if (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldBiometricLevel) == 1) {
_bioInProgress = true;
startTouchCancellation();
} else {
_bioInProgress = false;
}
}
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::modifierPressed
//------------------------------------------------------------------------------
bool IOHIDDFREventFilter::modifierPressed() {
auto iter = _activeKeys.begin();
for (; iter != _activeKeys.end(); ++iter) {
if (iter->first.isModifier()) {
return true;
}
}
return false;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleDFREvent
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDDFREventFilter::handleDFREvent(IOHIDEventRef event)
{
IOHIDEventRef result = event;
// Cancel events when a non-modifier key is pressed or biometric event is in
// progress
if ((!_activeKeys.empty() && !modifierPressed() && _keyboardFilterEnabled) ||
(_bioInProgress)) {
result = NULL;
goto exit;
}
if (_lastDFREvent) {
CFRelease(_lastDFREvent);
_lastDFREvent = NULL;
}
// save last event to dispatch on next cancellation
_lastDFREvent = event;
CFRetain(event);
exit:
return result;
}
//------------------------------------------------------------------------------
// IOHIDDFREventFilter::startTouchCancellation
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::startTouchCancellation()
{
if (_cancel) {
return;
}
if (_lastDFREvent) {
CFArrayRef children = NULL;
if (IOHIDEventGetIntegerValue(_lastDFREvent, kIOHIDEventFieldDigitizerRange) == 0) {
goto exit;
} else {
_touchInProgress = true;
}
// Set cancel flag for event and all child digitizer events
IOHIDEventSetIntegerValue(_lastDFREvent, kIOHIDEventFieldDigitizerEventMask, kIOHIDDigitizerEventCancel);
children = IOHIDEventGetChildren(_lastDFREvent);
for (CFIndex index = 0, count = children ? CFArrayGetCount(children) : 0; index < count; index++) {
IOHIDEventRef child = (IOHIDEventRef)CFArrayGetValueAtIndex(children, index);
if (IOHIDEventGetType(child) == kIOHIDEventTypeDigitizer) {
IOHIDEventSetIntegerValue(child, kIOHIDEventFieldDigitizerEventMask, kIOHIDDigitizerEventCancel);
}
}
IOHIDEventSetTimeStamp(_lastDFREvent, mach_absolute_time());
_IOHIDSessionDispatchEvent(_session, _lastDFREvent);
CFRelease(_lastDFREvent);
_lastDFREvent = NULL;
}
exit:
_cancel = true;
}