/*
 *  IOHIDNXEventTranslatorSessionFilter.cpp
 *  IOHIDEventSystemPlugIns
 *
 *  Created by Rob Yepez on 05/21/2013.
 *  Copyright 2013 Apple Inc. All rights reserved.
 *
 */

#include <new>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDSessionFilterPlugIn.h>
#include <IOKit/hid/IOHIDEventSystemPrivate.h>
#include <IOKit/hid/IOHIDEventTypes.h>
#include <IOKit/hid/IOHIDEventData.h>
#include <IOKit/hid/IOHIDSession.h>
#include <IOKit/hid/IOHIDService.h>
#include <IOKit/hid/IOHIDPrivateKeys.h>
#include <IOKit/hid/IOHIDEventSystemKeys.h>
#include <IOKit/hid/AppleHIDUsageTables.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/pwr_mgt/IOPM.h>
#include <SkyLight/SkyLight.h>
#include <SkyLight/SLSDisplayManager.h>
#include "IOHIDNXEventTranslatorServiceFilter.h"
#include "IOHIDDebug.h"
#include "IOHIDNXEventTranslatorSessionFilter.h"
#include <IOKit/hidsystem/IOHIDParameter.h>
#include "IOHIDEventTranslation.h"
#include <IOKit/IOMessage.h>
#include <sstream>
#include <cmath>
#include <iomanip>
#include <sys/time.h>


enum {
  kIOHIDGlobalModifersReportToService    = 0x1,
  kIOHIDGlobalModifersUse                = 0x2
};

// AC658E84-3DFE-43DE-945D-6E7919FBEDE1
#define kIOHIDNXEventTranslatorSessionFilterFactory CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0xAC, 0x65, 0x8E, 0x84, 0x3D, 0xFE, 0x43, 0xDE, 0x94, 0x5D, 0x6E, 0x79, 0x19, 0xFB, 0xED, 0xE1)

extern "C" void * IOHIDNXEventTranslatorSessionFilterFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID);

static boolean_t IsCompanionService(IOHIDServiceRef s1, IOHIDServiceRef s2);
static NXEventHandle openHIDSystem(void);


enum {
  kPMDisplayOff        = 0,
  kPMDisplayDim        = 1,
  kPMDisplayOn         = 2
};

static const int64_t one_mil = 1000*1000;

#define to_ns(ticks) ((ticks * tb_info.numer) / (tb_info.denom))
#define to_ms(ticks) (to_ns(ticks)/one_mil)
#define ns_to_absolute_time(x)  x / (tb_info.numer / tb_info.denom);

static mach_timebase_info_data_t tb_info;

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilterFactory
//------------------------------------------------------------------------------
// Implementation of the factory function for this type.
void *IOHIDNXEventTranslatorSessionFilterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID)
{
    // If correct type is being requested, allocate an
    // instance of TestType and return the IUnknown interface.
    if (CFEqual(typeUUID, kIOHIDSessionFilterPlugInTypeID)) {
        void *p = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDNXEventTranslatorSessionFilter), 0);
        return new(p) IOHIDNXEventTranslatorSessionFilter(kIOHIDNXEventTranslatorSessionFilterFactory);
    }
    // If the requested type is incorrect, return NULL.
    return NULL;
}

// The IOHIDEventSystemStatistics function table.
IOHIDSessionFilterPlugInInterface IOHIDNXEventTranslatorSessionFilter::sIOHIDNXEventTranslatorSessionFilterFtbl =
{
    // Required padding for COM
    NULL,
    // These three are the required COM functions
    IOHIDNXEventTranslatorSessionFilter::QueryInterface,
    IOHIDNXEventTranslatorSessionFilter::AddRef,
    IOHIDNXEventTranslatorSessionFilter::Release,
    // IOHIDSimpleSessionFilterPlugInInterface functions
    IOHIDNXEventTranslatorSessionFilter::filter,
    NULL,
    NULL,
    // IOHIDSessionFilterPlugInInterface functions
    IOHIDNXEventTranslatorSessionFilter::open,
    IOHIDNXEventTranslatorSessionFilter::close,
    NULL,
    NULL,
    IOHIDNXEventTranslatorSessionFilter::registerService,
    IOHIDNXEventTranslatorSessionFilter::unregisterService,
    IOHIDNXEventTranslatorSessionFilter::scheduleWithDispatchQueue,
    IOHIDNXEventTranslatorSessionFilter::unscheduleFromDispatchQueue,
    IOHIDNXEventTranslatorSessionFilter::getPropertyForClient,
    IOHIDNXEventTranslatorSessionFilter::setPropertyForClient,
};

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::IOHIDNXEventTranslatorSessionFilter
//------------------------------------------------------------------------------
IOHIDNXEventTranslatorSessionFilter::IOHIDNXEventTranslatorSessionFilter(CFUUIDRef factoryID):
_sessionInterface(&sIOHIDNXEventTranslatorSessionFilterFtbl),
_factoryID( static_cast<CFUUIDRef>( CFRetain(factoryID) ) ),
_refCount(1),
_globalModifiers (0),
_hidSystem(MACH_PORT_NULL),
_translator (IOHIDPointerEventTranslatorCreate (kCFAllocatorDefault, 0)),
_powerConnect (MACH_PORT_NULL),
_powerNotifier(MACH_PORT_NULL),
_powerPort (NULL),
_powerState (0),
_powerOnThresholdEventCount (0),
_powerOnThreshold (kIOHIDPowerOnThresholdMS),
_displayState (kPMDisplayOn),
_displaySleepAbortThreshold (kIOHIDDisplaySleepAbortThresholdMS),
_displayWakeAbortThreshold (kIOHIDDisplayWakeAbortThresholdMS),
_previousEventTime (0),
_declareActivityThreshold (0),
_powerStateChangeTime (0),
_displayStateChangeTime (0),
_displayLog(NULL),
_nxEventLog(NULL),
_isTranslationEnabled(true)
{
    CFPlugInAddInstanceForFactory( factoryID );
    
    if (tb_info.denom == 0) {
        mach_timebase_info(&tb_info);
    }
    _declareActivityThreshold = ns_to_absolute_time(kIOHIDDeclareActivityThresholdMS * NSEC_PER_MSEC);
    _updateActivityQueue = dispatch_queue_create("com.apple.HID.updateActivity", NULL);
}
//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::IOHIDNXEventTranslatorSessionFilter
//------------------------------------------------------------------------------
IOHIDNXEventTranslatorSessionFilter::~IOHIDNXEventTranslatorSessionFilter()
{
    CFPlugInRemoveInstanceForFactory( _factoryID );
    CFRelease( _factoryID );
}
//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::QueryInterface
//------------------------------------------------------------------------------
HRESULT IOHIDNXEventTranslatorSessionFilter::QueryInterface( void *self, REFIID iid, LPVOID *ppv )
{
    return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->QueryInterface(iid, ppv);
}
// Implementation of the IUnknown QueryInterface function.
HRESULT IOHIDNXEventTranslatorSessionFilter::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;
}
//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::AddRef
//------------------------------------------------------------------------------
ULONG IOHIDNXEventTranslatorSessionFilter::AddRef( void *self )
{
    return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->AddRef();
}
ULONG IOHIDNXEventTranslatorSessionFilter::AddRef()
{
    _refCount += 1;
    return _refCount;
}
//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::Release
//------------------------------------------------------------------------------
ULONG IOHIDNXEventTranslatorSessionFilter::Release( void *self )
{
    return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->Release();
}
ULONG IOHIDNXEventTranslatorSessionFilter::Release()
{
    _refCount -= 1;
    if (_refCount == 0) {
        delete this;
        return 0;
    }
    return _refCount;
}
//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::open
//------------------------------------------------------------------------------
boolean_t IOHIDNXEventTranslatorSessionFilter::open(void * self, IOHIDSessionRef session, IOOptionBits options)
{
    return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->open(session, options);
}

boolean_t IOHIDNXEventTranslatorSessionFilter::open(IOHIDSessionRef session __unused, IOOptionBits options __unused)
{
    _powerConnect = IORegisterForSystemPower (this, &_powerPort, powerNotificationCallback, &_powerNotifier);
    
    return _keyboards && _companions  && _modifiers;
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::close
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::close(void * self, IOHIDSessionRef session, IOOptionBits options)
{
    static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->close(session, options);
}

void IOHIDNXEventTranslatorSessionFilter::close(IOHIDSessionRef session __unused, IOOptionBits options __unused)
{
    if (_powerConnect) {
        IOServiceClose(_powerConnect);
    }
    
    if (_hidSystem != MACH_PORT_NULL) {
        IOServiceClose(_hidSystem);
        _hidSystem = MACH_PORT_NULL;
    }
    
    if (_displayLog) {
        CFRelease(_displayLog);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::registerService
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::registerService(void * self, IOHIDServiceRef service)
{
    static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->registerService(service);
}
void IOHIDNXEventTranslatorSessionFilter::registerService(IOHIDServiceRef service)
{
    //
    // Keep track of all keyboard like services
    //
    if (_keyboards &&
       ( IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard) ||
        IOHIDServiceConformsTo(service, kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl))) {
        _keyboards.SetValue(service);
    }
    
    if (IOHIDServiceConformsTo(service, kHIDPage_AppleVendor, kHIDUsage_AppleVendor_DFR)) {
        _dfr = service;
    }
    
    if (_modifiers) {
        CFNumberRefWrap modifiersMask (IOHIDServiceCopyProperty (service, CFSTR(kHIDEventTranslationModifierFlags)), true);
        if (modifiersMask) {
            _modifiers.SetValueForKey ((CFTypeRef)service, modifiersMask.Reference());
            if (_companions) {
                IOHIDServiceRef companion = getCompanionService (service);
                if (companion) {
                    _companions.SetValueForKey ((CFTypeRef)service, (CFTypeRef)companion);
                    _companions.SetValueForKey ((CFTypeRef)companion, (CFTypeRef)service);
                }
            }
            updateModifiers();
        }
    }
  
    CFTypeRef propertyValue = IOHIDServiceCopyProperty(service, CFSTR(kIOHIDServiceGlobalModifiersUsageKey));
    if (propertyValue) {
        unsigned int value = 0;
        if (CFGetTypeID(propertyValue) == CFDateGetTypeID()) {
            CFDataRef propertyValueData =  (CFDataRef)propertyValue;
            size_t propertyValueLength = std::min((size_t)CFDataGetLength(propertyValueData), sizeof(uint32_t));
            CFDataGetBytes(propertyValueData, CFRangeMake(0, propertyValueLength), (UInt8 *)&value);
        } else if (CFGetTypeID(propertyValue) == CFNumberGetTypeID()) {
            CFNumberRef propertyValueNum =  (CFNumberRef)propertyValue;
            CFNumberGetValue(propertyValueNum, kCFNumberSInt32Type, &value);
        }
        HIDLog ("kIOHIDServiceGlobalModifiersUsageKey:0x%x", value);
        if (value & kIOHIDGlobalModifersReportToService) {
            _reportModifiers.SetValue(service);
        }
        if (value & kIOHIDGlobalModifersUse) {
            _updateModifiers.SetValue(service);
        }
        CFRelease(propertyValue);
    }
 
    if (_translator &&
       (IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse) ||
        IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Pointer) ||
        IOHIDServiceConformsTo(service, kHIDPage_AppleVendor,    kHIDUsage_GD_Pointer) ||
        IOHIDServiceConformsTo(service, kHIDPage_AppleVendor,    kHIDUsage_AppleVendor_NXEvent))
        ){
        IOHIDPointerEventTranslatorRegisterService (_translator, service);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::unregisterService
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::unregisterService(void * self, IOHIDServiceRef service)
{
  static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->unregisterService(service);
}
void IOHIDNXEventTranslatorSessionFilter::unregisterService(IOHIDServiceRef service)
{
    if (_keyboards) {
        _keyboards.RemoveValue(service);
    }
    
    if (service == _dfr) {
        _dfr = NULL;
    }
  
    if (_updateModifiers) {
        _updateModifiers.RemoveValue(service);
    }

    if (_reportModifiers) {
        _reportModifiers.RemoveValue(service);
    }
  
    if (_companions) {
        CFRefWrap<CFTypeRef> companion (_companions[service]);
        if (companion) {
            _companions.Remove(service);
            _companions.Remove(companion);
        }
    }
  
    if (_modifiers && _modifiers.ContainKey(service)) {
        _modifiers.Remove(service);
        updateModifiers();
    }

    if (_assertionNames && _assertionNames.ContainKey(service)) {
        _assertionNames.Remove(service);
    }
  
    if (_translator) {
       IOHIDPointerEventTranslatorUnRegisterService (_translator, service);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::scheduleWithDispatchQueue
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::scheduleWithDispatchQueue(void * self, dispatch_queue_t queue)
{
    static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->scheduleWithDispatchQueue(queue);
}
void IOHIDNXEventTranslatorSessionFilter::scheduleWithDispatchQueue(dispatch_queue_t queue)
{
    _dispatch_queue = queue;
    __block IOHIDNXEventTranslatorSessionFilter *blockSelf = this;
    CGError err;
    
    if (_powerPort) {
        IONotificationPortSetDispatchQueue (_powerPort, _dispatch_queue);
    }

    SLSDisplayManagerNotificationBlockType displayNotification = ^(const SLSDisplayPowerStateNotificationType state,
                                                                   void * _Nullable refcon)
    {
        blockSelf->displayNotificationCallback(state);
    };

    err = SLSDisplayManagerRegisterPowerStateNotificationOptions(_dispatch_queue,
                                                                 NULL,
                                                                 kPowerStateNotificationOptionDidWake |
                                                                 kPowerStateNotificationOptionWillSleep |
                                                                 kPowerStateNotificationOptionAsync,
                                                                 displayNotification);
    if (err != kCGErrorSuccess) {
        HIDLogError ("Unable to register display sleep/wake notifications. "
                     "HID events may not wake the system as expected. Error code: %d", err);
    }
    // Dim is not supported on all platforms, but the state isn't essential.
    err = SLSDisplayManagerRegisterPowerStateNotification(_dispatch_queue,
                                                          NULL,
                                                          kPowerStateNotificationTypeWillDim | kPowerStateNotificationTypeAsync,
                                                          displayNotification);
    if (err != kCGErrorSuccess) {
        HIDLogError ("Unable to register display dim notification: %d", err);
    }

}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::unscheduleFromDispatchQueue
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::unscheduleFromDispatchQueue(void * self, dispatch_queue_t queue)
{
    static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->unscheduleFromDispatchQueue(queue);
}
void IOHIDNXEventTranslatorSessionFilter::unscheduleFromDispatchQueue(dispatch_queue_t queue __unused)
{
    
    if (_powerNotifier) {
        IODeregisterForSystemPower (&_powerNotifier);
        _powerNotifier = MACH_PORT_NULL;
    }
    
    if ( _powerPort ) {
        IONotificationPortDestroy(_powerPort);
        _powerPort = NULL;
    }

    if ( _powerConnect ) {
        IOServiceClose(_powerConnect);
        _powerConnect = MACH_PORT_NULL;
    }

    CGError err;

    err = SLSDisplayManagerUnregisterPowerStateNotificationOptions(kPowerStateNotificationTypeDidWake | kPowerStateNotificationTypeDidSleep | kPowerStateNotificationTypeAsync);
    if (err) {
        HIDLogError ("Unable to unregister display sleep/wake notification: %d", err);
    }
    err = SLSDisplayManagerUnregisterPowerStateNotification(kPowerStateNotificationTypeDidDim | kPowerStateNotificationTypeAsync);
    if (err) {
        HIDLogError ("Unable to unregister display dim notification: %d", err);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::filter
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDNXEventTranslatorSessionFilter::filter(void * self, IOHIDServiceRef sender, IOHIDEventRef event)
{
    return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->filter(sender, event);
}
IOHIDEventRef IOHIDNXEventTranslatorSessionFilter::filter(IOHIDServiceRef sender, IOHIDEventRef event)
{
    if (!event) {
        return event;
    }
    
    if (!displayStateFilter (sender, event)) {
        CFRelease(event);
        return NULL;
    }
    
    if (!powerStateFilter (event)) {
        CFRelease(event);
        return NULL;
    }
    
    if (!sender) {
        return event;
    }
    

    bool doUpdateModifiers = false;

    // handles modifier updates, for syntetic CapsLock onoff with setting kIOHIDServiceCapsLockStateKey
    if (IOHIDEventConformsTo(event, kIOHIDEventTypeVendorDefined)) {
        IOHIDEventRef ev = IOHIDEventGetEvent(event, kIOHIDEventTypeVendorDefined);
        if (IOHIDEventGetIntegerValue(ev, kIOHIDEventFieldVendorDefinedUsage) == kHIDUsage_AppleVendor_NXEvent_Translated) {
            NXEventExt  *nxEvent = NULL;
            CFIndex     nxEventLength = 0;
            IOHIDEventGetVendorDefinedData(ev, (uint8_t**)&nxEvent, &nxEventLength);
            if (nxEvent && nxEventLength >= sizeof (NXEventExt) && nxEvent->payload.type == NX_FLAGSCHANGED) {
                doUpdateModifiers = true;
            }
        }
    }

    if (IOHIDEventConformsTo(event, kIOHIDEventTypeKeyboard) && _modifiers.ContainKey(sender)) {
        CFArrayRefWrap childrens (IOHIDEventGetChildren(event));
        if (childrens.Reference()) {
            for (CFIndex index = 0; index < childrens.Count(); index++) {
                IOHIDEventRef childEvent = (IOHIDEventRef)childrens[index];
                if (childEvent && IOHIDEventGetType(childEvent) == kIOHIDEventTypeVendorDefined &&
                    IOHIDEventGetIntegerValue(childEvent, kIOHIDEventFieldVendorDefinedUsagePage) == kHIDPage_AppleVendor &&
                    IOHIDEventGetIntegerValue(childEvent, kIOHIDEventFieldVendorDefinedUsage) == kHIDUsage_AppleVendor_NXEvent_Translated) {
                    
                    if (_companions) {
                        IOHIDServiceRef companion = (IOHIDServiceRef)_companions[sender];
                        if (companion) {
                            CFNumberRefWrap companionServiceModifiers ((CFNumberRef)_modifiers[companion]);
                            if (companionServiceModifiers) {
                                IOHIDKeyboardEventTranslatorUpdateWithCompanionModifiers(childEvent,  (uint32_t) companionServiceModifiers);
                            }
                        }
                    }
                    
                    if (_updateModifiers.ContainValue(sender)) {
                        IOHIDKeyboardEventTranslatorUpdateWithCompanionModifiers (childEvent, _globalModifiers);
                    }
                }
            }
        }
        doUpdateModifiers = true;
    }
    
    if (doUpdateModifiers) {
        CFNumberRefWrap currentServiceModifiers ((CFNumberRef)IOHIDServiceCopyProperty (sender, CFSTR(kHIDEventTranslationModifierFlags)), true);
        CFNumberRefWrap cachedServiceModifiers ((CFNumberRef)_modifiers[sender]);
        if (currentServiceModifiers && cachedServiceModifiers && currentServiceModifiers != cachedServiceModifiers) {
            _modifiers.SetValueForKey(sender, currentServiceModifiers.Reference());
            updateModifiers();
        }
    }
    
    if (_translator && _isTranslationEnabled &&
        (IOHIDEventGetType(event) == kIOHIDEventTypePointer ||
         IOHIDEventGetType(event) == kIOHIDEventTypeScroll  ||
         (IOHIDEventGetType(event) == kIOHIDEventTypeVendorDefined &&
          IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedUsage) == kHIDUsage_AppleVendor_NXEvent
          ))) {
             
        CFArrayRef collection = IOHIDPointerEventTranslatorCreateEventCollection (_translator, event, sender, _globalModifiers, 0);
        if (collection) {
            for (CFIndex index = 0; index < CFArrayGetCount(collection); index++) {
                IOHIDEventRef translatedEvent = (IOHIDEventRef)CFArrayGetValueAtIndex(collection, index);
                if (translatedEvent) {
                    IOHIDEventAppendEvent(event, translatedEvent, 0);
                }
            }
            CFRelease(collection);
        }
    }
    
    if (resetStickyKeys(event)) {
        CFRetain(sender);
        dispatch_async(_dispatch_queue, ^() {
            [[NSNotificationCenter defaultCenter] postNotificationName:@(kIOHIDResetStickyKeyNotification)
                                                                object:(__bridge id)sender];
            CFRelease(sender);
        });
    }
    
    return event;
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::resetStickyKeys
//------------------------------------------------------------------------------
boolean_t IOHIDNXEventTranslatorSessionFilter::resetStickyKeys(IOHIDEventRef event) {
    // Ignore digitizer events; the associated pointer event
    // will have the correct button state.
    if (IOHIDEventConformsTo(event, kIOHIDEventTypeDigitizer)) {
        return false;
    }
    
    if (IOHIDEventConformsTo(event, kIOHIDEventTypeButton)) {
        if (_translator) {
            // Reset when all buttons are up
            if (!IOHIDPointerEventTranslatorGetGlobalButtonState(_translator)) {
                return true;
            }
        }
    }
    
    if (IOHIDEventConformsTo(event, kIOHIDEventTypeVendorDefined)) {
        IOHIDEventRef ev = IOHIDEventGetEvent(event, kIOHIDEventTypeVendorDefined);
        if (IOHIDEventGetIntegerValue(ev, kIOHIDEventFieldVendorDefinedUsage) == kHIDUsage_AppleVendor_NXEvent) {
            NXEventExt  *nxEvent = NULL;
            CFIndex     eventLength = 0;
            IOHIDEventGetVendorDefinedData(ev, (uint8_t**)&nxEvent, &eventLength);
            if (nxEvent) {
                if (nxEvent->payload.type == NX_LMOUSEDOWN ||
                    nxEvent->payload.type == NX_RMOUSEDOWN ||
                    nxEvent->payload.type == NX_OMOUSEDOWN) {
                    return true;
                }
            }
        }
    }
    
    if (IOHIDEventConformsTo(event, kIOHIDEventTypeKeyboard)) {
        IOHIDEventRef ev = IOHIDEventGetEvent(event, kIOHIDEventTypeKeyboard);
        if (IOHIDEventGetIntegerValue(ev, kIOHIDEventFieldKeyboardDown)) {
            return true;
        }
    }
    
    return false;
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::getPropertyForClient
//------------------------------------------------------------------------------
CFTypeRef IOHIDNXEventTranslatorSessionFilter::getPropertyForClient (void * self, CFStringRef key, CFTypeRef client) {
   return static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->getPropertyForClient(key,client);
}

CFTypeRef IOHIDNXEventTranslatorSessionFilter::getPropertyForClient (CFStringRef key, CFTypeRef client __unused) {
    CFTypeRef result = NULL;

    if (CFEqual(key, CFSTR(kIOHIDSessionFilterDebugKey))) {
        CFMutableDictionaryRefWrap serializer;
        serialize(serializer);

        if (serializer) {
          result = CFRetain(serializer.Reference());
        }
    } else if (CFEqual(key, CFSTR(kIOHIDNXEventTranslation))) {
        result = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &_isTranslationEnabled);
    }
  
    return result;
  
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::setPropertyForClient
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::setPropertyForClient (void * self, CFStringRef key, CFTypeRef property, CFTypeRef client) {
   static_cast<IOHIDNXEventTranslatorSessionFilter *>(self)->setPropertyForClient(key, property, client);
}
void IOHIDNXEventTranslatorSessionFilter::setPropertyForClient (CFStringRef key, CFTypeRef property, CFTypeRef client __unused) {

  if (CFEqual(key, CFSTR(kIOHIDActivityStateKey))) {
      dispatch_sync(_updateActivityQueue, ^{
          updateActivity(CFBooleanGetValue((CFBooleanRef)key));
      });
  } else if (CFEqual(key, CFSTR(kIOHIDDeclareActivityThreshold))) {
      if (property && CFGetTypeID(property) == CFNumberGetTypeID()) {
          uint32_t val;
          CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &val);
          _declareActivityThreshold = ns_to_absolute_time(val * NSEC_PER_MSEC);
      }
  } else if (CFEqual(key,CFSTR(kIOHIDNXEventTranslation))) {
      
      if (CFNumberGetTypeID() == CFGetTypeID(property)) {
          uint8_t tmp = 0;
          CFNumberGetValue((CFNumberRef)property, kCFNumberCharType, &tmp);
          _isTranslationEnabled = tmp ? true : false;
      }
  }
  if (_translator) {
      IOHIDPointerEventTranslatorSetProperty (_translator, key, property);
  }
    
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::updateModifiers
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::updateModifiers() {
     uint32_t globalModifiers = 0;
    _modifiers.Apply ([&globalModifiers](const void *key __unused, const void *value) {
        if (value ) {
           globalModifiers |= (uint32_t) CFNumberRefWrap((CFTypeRef)value);
        }
    });
    if (_globalModifiers != globalModifiers) {
        HIDLogDebug("Update modifier flags: 0x%x", globalModifiers);
        _globalModifiers = globalModifiers;
        
        NSSet *modifiers = [NSSet setWithSet:(__bridge NSSet *)_reportModifiers.Reference()];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [modifiers enumerateObjectsUsingBlock:^(id obj,
                                                    BOOL *stop __unused) {
                IOHIDServiceRef service = (__bridge IOHIDServiceRef)obj;
                
                IOHIDServiceSetProperty(service,
                                        CFSTR(kIOHIDKeyboardGlobalModifiersKey),
                                        CFNumberRefWrap(globalModifiers));
            }];
        });
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::getCompanionService
//------------------------------------------------------------------------------
IOHIDServiceRef IOHIDNXEventTranslatorSessionFilter::getCompanionService(IOHIDServiceRef service) {
  IOHIDServiceRef companion = NULL;
  CFIndex count = _modifiers.Count();
  if (count == 0 || service == NULL) {
    return companion;
  }
  IOHIDServiceRef *list = (IOHIDServiceRef*)malloc(count * sizeof (IOHIDServiceRef));
  if (list != NULL) {
    CFDictionaryGetKeysAndValues(_modifiers.Reference(), (const void **)list, NULL);
    for (CFIndex index = 0; index < count; ++index) {
      
      if (IsCompanionService(service, list[index])) {
        companion = list[index];
        break;
      }
    }
    free(list);
  }
                                               return companion;
}

void IOHIDNXEventTranslatorSessionFilter::displayNotificationCallback (SLSDisplayPowerStateNotificationType state) {

    HIDLogDebug ("displayNotificationCallback : displayState: 0x%x", state);

    uint32_t currentDisplayState;

    switch (state) {

    case  kPowerStateNotificationTypeWillWake:
    case  kPowerStateNotificationTypeDidWake:
        currentDisplayState = kPMDisplayOn;
        break;

    case  kPowerStateNotificationTypeWillDim:
    case  kPowerStateNotificationTypeDidDim:
        currentDisplayState = kPMDisplayDim;
        break;

    case kPowerStateNotificationTypeWillSleep:
    case kPowerStateNotificationTypeDidSleep:
        currentDisplayState = kPMDisplayOff;
        break;

    default:
        HIDLogError ("Got unrecognized display state: %#x", state);
        return;
    }
  
    if (_displayState != currentDisplayState) {
      _displayStateChangeTime = mach_continuous_time();
      HIDLogInfo ("displayNotificationCallback : displayState change 0x%x -> 0x%x", _displayState, currentDisplayState);
      _displayState = currentDisplayState;
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::_AssertionID
//------------------------------------------------------------------------------

IOPMAssertionID IOHIDNXEventTranslatorSessionFilter::_AssertionID = 0;

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::displayStateFilter
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDNXEventTranslatorSessionFilter::displayStateFilter (IOHIDServiceRef sender, IOHIDEventRef  event)
{
    IOHIDEventPolicyValue   policy              = IOHIDEventGetPolicy(event, kIOHIDEventPowerPolicy);
    IOHIDEventSenderID      senderID            = IOHIDEventGetSenderID(event);
    IOHIDEventType          eventType           = IOHIDEventGetType(event);
    uint64_t                eventTime           = IOHIDEventGetTimeStamp(event);
    IOHIDEventRef           result              = event;
    uint64_t                deltaMS             = to_ms(mach_continuous_time()) - to_ms(_displayStateChangeTime);
    uint64_t                eventDelta          = eventTime - _previousEventTime;
    bool                    declareActivity     = true;
    bool                    nxEvent             = false;

    // Cancel keyboard/button events when display is asleep or waking up
    // Will still declare using activity.
    if ((_displayState < kPMDisplayDim || (_displayState > kPMDisplayDim && deltaMS < _displayWakeAbortThreshold)) &&
        shouldCancelEvent(event)) {
       result = NULL;
    }

    if (eventType == kIOHIDEventTypeVendorDefined) {
        IOHIDEventRef vendorEvent = IOHIDEventGetEvent(event, kIOHIDEventTypeVendorDefined);
        if (vendorEvent) {
            uint32_t usage = (uint32_t)IOHIDEventGetIntegerValue(vendorEvent, kIOHIDEventFieldVendorDefinedUsage);
            uint32_t page = (uint32_t)IOHIDEventGetIntegerValue(vendorEvent, kIOHIDEventFieldVendorDefinedUsagePage);
            if (page == kHIDPage_AppleVendor && usage == kHIDUsage_AppleVendor_NXEvent) {
                nxEvent = true;
            }
        }
    }
    
    if (policy == kIOHIDEventNoPolicy) {
        
        // Only wake/maintain policies should declare activity
        declareActivity = false;
        
    } else if (policy == kIOHIDEventPowerPolicyMaintainSystem &&
               nxEvent) {
        // Limit the number of event that declare activity.
        if (eventDelta < _declareActivityThreshold) {
            declareActivity = false;
        }
        // Don't post NXEvents to keep display active when display is off.
        if (_displayState < kPMDisplayDim) {
            declareActivity = false;
        }

    } else if (policy == kIOHIDEventPowerPolicyMaintainSystem &&
        _displayState < kPMDisplayDim &&
        deltaMS > _displaySleepAbortThreshold &&
        sender != _dfr) {
        
        // Prevent digitizer/pointer events from tickling display after 5s, unless sender is DFR.
        declareActivity = false;
        
    } else if (policy == kIOHIDEventPowerPolicyMaintainSystem &&
        eventDelta < _declareActivityThreshold) {
        // Only declare user activity every 250ms for digitizer/pointer events
        declareActivity = false;
    }

    if (declareActivity) {
        _previousEventTime = eventTime;
    }
    
    HIDLogActivityDebug ("displayStateFilter policy:%llu declareActivity:%d result:%p event:%d sender:0x%llx eventDelta:0x%llx deltaMS:0x%llx displayState:%u prevTimeStamp:0x%llx eventTime:0x%llx", policy, declareActivity, result, IOHIDEventGetType(event), senderID, eventDelta, deltaMS, _displayState, _previousEventTime, eventTime);
    
    if (declareActivity) {
        static const size_t kStrSize = 128;
        CFStringRef         tmpStr = NULL;
        CFStringRef         assertionNameStr = NULL;

        // Log display wakes
        if (_displayState < kPMDisplayDim) {
            if (nxEvent) {
                updateNXEventLog(policy, event, eventTime);
            } else {
                updateDisplayLog(senderID, policy, eventType, eventTime);
            }
        }

        if (sender) {
            tmpStr = (CFStringRef)_assertionNames[sender];
        }

        if (tmpStr) {
            CFRetain(tmpStr);
        }
        else {
            // Cache assertion name
            char        assertionName[kStrSize] = { 0 };
            CFStringRef nameCFStr = CFSTR("NULL");
            char        nameStr[kStrSize] = { 0 };
            CFStringRef productCFStr = CFSTR("NULL");
            char        productStr[kStrSize] = { 0 };

            if (sender) {
                CFTypeRef prop = IOHIDServiceGetProperty(sender, CFSTR(kIOClassKey));
                if (prop && CFGetTypeID(prop) == CFStringGetTypeID()) {
                    nameCFStr = (CFStringRef)prop;
                }

                prop = IOHIDServiceGetProperty(sender, CFSTR(kIOHIDProductKey));
                if (prop && CFGetTypeID(prop) == CFStringGetTypeID()) {
                    productCFStr = (CFStringRef)prop;
                }
            }
            CFStringGetCString(nameCFStr, nameStr, kStrSize, kCFStringEncodingUTF8);
            CFStringGetCString(productCFStr, productStr, kStrSize, kCFStringEncodingUTF8);

            // Assertion name can be up to 128 characters. Limit string sizes to fit this constraint.
            nameStr[19]    = '\0';
            productStr[19] = '\0';

            // Assertion name format:
            // com.apple.iohideventsystem.queue.tickle:serviceID:XXX name:XXX product:XXX eventType:XXX
            // Only eventType is variable
            int ret = snprintf(assertionName, kStrSize, "%s.queue.tickle serviceID:%llx name:%s product:%s eventType:",
                               kIOHIDEventSystemServerName,
                               (long long unsigned int)senderID,
                               nameStr,
                               productStr);
            if (ret < 0 || ret >= kStrSize) {
                HIDLogError ("Error generating assertion name (snprintf %d)", ret);
            }

            tmpStr = CFStringCreateWithCString(NULL, assertionName, kCFStringEncodingUTF8);
            if (sender && tmpStr) {
                _assertionNames.SetValueForKey(sender, tmpStr);
            } else {
                HIDLogError ("Error generating assertion string");
            }
        }

        if (tmpStr) {
            assertionNameStr = (CFStringRef)CFStringCreateMutableCopy(NULL, kStrSize, tmpStr);
            CFStringAppendFormat((CFMutableStringRef)assertionNameStr, NULL, CFSTR("%u"), (unsigned int)eventType);
            CFRelease(tmpStr);
        } else {
            HIDLogError ("No assertion name string for service:%llx", senderID);
            assertionNameStr = CFSTR(kIOHIDEventSystemServerName ".queue.tickle");
        }

        if (_updateActivityQueue) {
            CFRetain(assertionNameStr);
            dispatch_async(_updateActivityQueue, ^() {
                IOReturn status = IOPMAssertionDeclareUserActivity(assertionNameStr,
                                                                   kIOPMUserActiveLocal,
                                                                   &_AssertionID);
                if (status) {
                    HIDLogError ("IOPMAssertionDeclareUserActivity status:0x%x", status);
                }

                updateActivity(true);

                CFRelease(assertionNameStr);
            });
        }
        if (assertionNameStr) {
            CFRelease(assertionNameStr);
        }
    }
    return result;
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::testCancelEvent
//------------------------------------------------------------------------------
boolean_t IOHIDNXEventTranslatorSessionFilter::shouldCancelEvent (IOHIDEventRef  event) {
    IOHIDEventRef testEvent;
    testEvent = IOHIDEventGetEvent (event, kIOHIDEventTypeKeyboard);
    if (testEvent && IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown)) {
      return true;
    }
    testEvent = IOHIDEventGetEvent (event, kIOHIDEventTypePointer);
    if (testEvent && IOHIDEventGetIntegerValue (event, kIOHIDEventFieldPointerButtonMask)) {
      return true;
    }
    testEvent = IOHIDEventGetEvent (event, kIOHIDEventTypeButton);
    if (testEvent && IOHIDEventGetIntegerValue (event, kIOHIDEventFieldButtonState)) {
      return true;
    }
    testEvent = IOHIDEventGetEvent(event, kIOHIDEventTypeVendorDefined);
    if (testEvent) {
        uint32_t usagePage = (uint32_t)IOHIDEventGetIntegerValue(testEvent, kIOHIDEventFieldVendorDefinedUsagePage);
        uint32_t usage = (uint32_t)IOHIDEventGetIntegerValue(testEvent, kIOHIDEventFieldVendorDefinedUsage);
        if (usagePage == kHIDPage_AppleVendor && usage == kHIDUsage_AppleVendor_NXEvent) {
            return true;
        }
    }
    return false;
}


//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::updateDisplayLog
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::updateDisplayLog (IOHIDEventSenderID serviceID, IOHIDEventPolicyValue policy, IOHIDEventType eventType, uint64_t timestamp) {
    LogEntry entry;
    
    gettimeofday(&entry.time, NULL);
    entry.serviceID     = serviceID;
    entry.policy        = policy;
    entry.eventType     = eventType;
    entry.timestamp     = timestamp;
    
    if (_displayLog == NULL) {
        _displayLog = _IOHIDSimpleQueueCreate(kCFAllocatorDefault, sizeof(LogEntry), LOG_MAX_ENTRIES);
    }
    if (_displayLog) {
        _IOHIDSimpleQueueEnqueue(_displayLog, &entry, true);
    }
}

void IOHIDNXEventTranslatorSessionFilter::updateNXEventLog (IOHIDEventPolicyValue policy, IOHIDEventRef event, uint64_t timestamp) {
    LogNXEventEntry entry;
    NXEventExt * nxEvent = NULL;
    CFIndex eventSize = 0;
    IOHIDEventRef vendorEvent;

    vendorEvent = IOHIDEventGetEvent(event, kIOHIDEventTypeVendorDefined);
    IOHIDEventGetVendorDefinedData(vendorEvent, (uint8_t**)&nxEvent, &eventSize);

    gettimeofday(&entry.time, NULL);
    entry.policy = policy;
    entry.senderPID = nxEvent ? nxEvent->payload.ext_pid : 0;
    entry.nxEventType = nxEvent ? nxEvent->payload.type : 0;
    entry.timestamp = timestamp;

    if (_nxEventLog == NULL) {
        _nxEventLog = _IOHIDSimpleQueueCreate(kCFAllocatorDefault, sizeof(LogNXEventEntry), LOG_MAX_ENTRIES);
    }
    if (_nxEventLog) {
        _IOHIDSimpleQueueEnqueue(_nxEventLog, &entry, true);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::updateActivity
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::updateActivity (bool active) {
    if (_hidSystem == MACH_PORT_NULL) {
        _hidSystem = openHIDSystem();
    }
    
    kern_return_t status = IOHIDSetStateForSelector(_hidSystem, kIOHIDActivityUserIdle, active ? 0 : 1);
    if (status) {
         HIDLogError ("IOHIDSetStateForSelector(kIOHIDActivityUserIdle,%d):0x%x", status, active ? 0 : 1);
    }
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::powerNotificationCallback
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::powerNotificationCallback (void * refcon, io_service_t service, uint32_t messageType, void * messageArgument) {
     static_cast<IOHIDNXEventTranslatorSessionFilter *>(refcon)->powerNotificationCallback(service, messageType, messageArgument);
}

void IOHIDNXEventTranslatorSessionFilter::powerNotificationCallback (io_service_t service __unused, uint32_t messageType, void * messageArgument) {
    switch (messageType) {
    case    kIOMessageCanSystemSleep:
    case    kIOMessageSystemWillSleep:
        IOAllowPowerChange (_powerConnect, (long)messageArgument);
    default:
        _powerStateChangeTime = mach_continuous_time();
        _powerState = messageType;
        break;
    }
    HIDLogDebug ("powerNotificationCallback message:0x%x\n", messageType);
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::powerStateFilter
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDNXEventTranslatorSessionFilter::powerStateFilter (IOHIDEventRef  event) {
    uint64_t   deltaMS = to_ms(mach_continuous_time()) - to_ms(_powerStateChangeTime);
    
    if (_powerState == kIOMessageSystemWillPowerOn && deltaMS < _powerOnThreshold  && shouldCancelEvent (event)) {
        ++_powerOnThresholdEventCount;
        HIDLogDebug ("powerStateFilter : Cancel event (type:%d  power state:0x%x changed %lldms ago (threshold:%dms)\n",
                     IOHIDEventGetType(event),
                     _powerState,
                      deltaMS,
                     _powerOnThreshold
                     );
        return NULL;
    }
    _powerOnThresholdEventCount = 0;
    return event;
}

//------------------------------------------------------------------------------
// IOHIDNXEventTranslatorSessionFilter::serialize
//------------------------------------------------------------------------------
void IOHIDNXEventTranslatorSessionFilter::serialize (CFMutableDictionaryRef dict) const {
    CFMutableDictionaryRefWrap serializer (dict);
    serializer.SetValueForKey(CFSTR("Class"), CFSTR("IOHIDNXEventTranslatorSessionFilter"));
    serializer.SetValueForKey(CFSTR("CanceledEventCount"), CFNumberRefWrap(_powerOnThresholdEventCount));
    serializer.SetValueForKey(CFSTR("DisplayPowerStateChangeTime"), CFNumberRefWrap(to_ms(mach_continuous_time()) - to_ms(_displayStateChangeTime)));
    serializer.SetValueForKey(CFSTR("DisplayPowerState"), CFNumberRefWrap(_displayState));
    serializer.SetValueForKey(CFSTR("SystemPowerStateChangeTime"), CFNumberRefWrap(to_ms(mach_continuous_time()) - to_ms(_powerStateChangeTime)));
    serializer.SetValueForKey(CFSTR("SystemPowerState"), CFNumberRefWrap(_powerState));
    serializer.SetValueForKey(CFSTR("DeclareActivityThreshold"), CFNumberRefWrap(_declareActivityThreshold));
    serializer.SetValueForKey(CFSTR(kIOHIDNXEventTranslation), _isTranslationEnabled);

    CFMutableArrayRefWrap companions;
    _companions.Apply ([&companions](const void *key, const void *value) {
        CFMutableDictionaryRefWrap  pair;
        pair.SetValueForKey(CFSTR("ServiceId0"), IOHIDServiceGetRegistryID((IOHIDServiceRef)key));
        pair.SetValueForKey(CFSTR("ServiceId1"), IOHIDServiceGetRegistryID((IOHIDServiceRef)value));
        companions.Append(pair);
    });
    serializer.SetValueForKey(CFSTR("CompanionMap"), companions);

    CFMutableArrayRefWrap reportModifiers;
    _reportModifiers.Apply([&reportModifiers](const void * value) {
        reportModifiers.Append(IOHIDServiceGetRegistryID((IOHIDServiceRef)value));
    });
    serializer.SetValueForKey(CFSTR("ReportGlobalModifiers"), reportModifiers);
  
    CFMutableArrayRefWrap updateModifiers;
    _updateModifiers.Apply([&updateModifiers](const void * value) {
        updateModifiers.Append(IOHIDServiceGetRegistryID((IOHIDServiceRef)value));
    });
    serializer.SetValueForKey(CFSTR("TranslateWithGlobalModifiers"), updateModifiers);
    
    if (_displayLog) {
        CFMutableArrayRef displayLog = CFArrayCreateMutable(kCFAllocatorDefault, LOG_MAX_ENTRIES, &kCFTypeArrayCallBacks);
        if (displayLog) {
            _IOHIDSimpleQueueApplyBlock (_displayLog, ^(void * entry, void * ctx) {
                CFMutableArrayRef log = (CFMutableArrayRef)ctx;
                LogEntry * entryData = (LogEntry *)entry;
                
                CFMutableDictionaryRef entryDict = CFDictionaryCreateMutable(CFGetAllocator(log), 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                if (!entryDict) {
                    return;
                }
                
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("ServiceID"), entryData->serviceID);
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("Policy"), entryData->policy);
                _IOHIDDictionaryAddSInt32(entryDict, CFSTR("EventType"), entryData->eventType);
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("timestamp"), entryData->timestamp);
                
                CFStringRef time = _IOHIDCreateTimeString(kCFAllocatorDefault, &entryData->time);
                if (time) {
                    CFDictionaryAddValue(entryDict, CFSTR("Time"), time);
                    CFRelease(time);
                }
                
                CFArrayAppendValue(log, entryDict);
                CFRelease(entryDict);
                
            }, (void *)displayLog);
            
            serializer.SetValueForKey(CFSTR("DisplayWakeLog"), displayLog);
            CFRelease(displayLog);
        }
    }
    if (_nxEventLog) {
        CFMutableArrayRef nxEventLog = CFArrayCreateMutable(kCFAllocatorDefault, LOG_MAX_ENTRIES, &kCFTypeArrayCallBacks);
        if (nxEventLog) {
            _IOHIDSimpleQueueApplyBlock (_nxEventLog, ^(void * entry, void * ctx) {
                CFMutableArrayRef log = (CFMutableArrayRef)ctx;
                LogNXEventEntry * entryData = (LogNXEventEntry *)entry;

                CFMutableDictionaryRef entryDict = CFDictionaryCreateMutable(CFGetAllocator(log), 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                if (!entryDict) {
                    return;
                }

                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("SenderPID"), entryData->senderPID);
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("NXEventType"), (int32_t)entryData->nxEventType);
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("Policy"), entryData->policy);
                _IOHIDDictionaryAddSInt64(entryDict, CFSTR("timestamp"), entryData->timestamp);

                CFStringRef time = _IOHIDCreateTimeString(kCFAllocatorDefault, &entryData->time);
                if (time) {
                    CFDictionaryAddValue(entryDict, CFSTR("Time"), time);
                    CFRelease(time);
                }

                CFArrayAppendValue(log, entryDict);
                CFRelease(entryDict);

            }, (void *)nxEventLog);

            serializer.SetValueForKey(CFSTR("NXEventWakeLog"), nxEventLog);
            CFRelease(nxEventLog);
        }
    }
}

//------------------------------------------------------------------------------
// Helper funciton
//------------------------------------------------------------------------------

static const CFStringRef propertyTable [] = {
  CFSTR(kIOHIDVendorIDKey),
  CFSTR(kIOHIDProductIDKey),
  CFSTR(kIOHIDLocationIDKey),
  CFSTR(kIOHIDTransportKey)
};

static boolean_t IsAltSender(IOHIDServiceRef s1, IOHIDServiceRef s2) {

  CFTypeRef altSenderID = IOHIDServiceGetProperty (s1, CFSTR(kIOHIDAltSenderIdKey));
  if (altSenderID) {
     CFTypeRef registryID = IOHIDServiceGetRegistryID(s2);
     if (altSenderID && registryID && CFEqual(altSenderID, registryID)) {
        return true;
     }
  }
  return false;
}

static boolean_t IsCompanionService(IOHIDServiceRef s1, IOHIDServiceRef s2) {
  
  CFTypeRef s1Property;
  CFTypeRef s2Property;
  
  if (CFEqual(IOHIDServiceGetRegistryID(s1), IOHIDServiceGetRegistryID(s2))) {
    return false;
  }
  
  if (IsAltSender(s1,s2) || IsAltSender(s2,s1)) {
    return true;
  }
  
  for (size_t index = 0; index < sizeof(propertyTable)/sizeof(propertyTable[0]); ++index) {
    s1Property = IOHIDServiceGetProperty(s1,  propertyTable[index]);
    s2Property = IOHIDServiceGetProperty(s2,  propertyTable[index]);
    if (s1Property == NULL || s2Property == NULL || !CFEqual(s1Property, s2Property)) {
      return false;
    }
  }
  return true;
}

static NXEventHandle openHIDSystem(void)
{
    kern_return_t    kr;
    io_service_t     service = MACH_PORT_NULL;
    NXEventHandle    handle = MACH_PORT_NULL;
    mach_port_t      masterPort;
    
    kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
    if(kr == KERN_SUCCESS) {
        service = IORegistryEntryFromPath(masterPort, kIOServicePlane ":/IOResources/IOHIDSystem");
        if (service) {
            IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &handle);
            IOObjectRelease(service);
        }
    }
    
    return handle;
}

